Implementation Plan: Investigate Historical Recurrence Check#754
Conversation
Adds a new Step 3.5 to the investigate skill that mines prior /investigate invocations from ~/.claude/projects/ JSONL logs and scans bounded git history for prior fix commits touching the affected components. When matches are found, a conditional analysis subagent compares prior fix diffs against the current root cause; when none are found, the skill records a single-line no-history result with zero overhead. The Step 4 report template gains a Historical Context section between Similar Patterns and External Research, and recurring patterns are flagged for /autoskillit:rectify remediation. 15 new structural contract tests in tests/skills/test_investigate_contracts.py lock in the invariants. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n investigate SKILL.md
- {hash} → {HASH} to satisfy isupper() placeholder contract (all-caps excluded)
- /investigate → /autoskillit:investigate to satisfy cross-references namespacing test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trecek
left a comment
There was a problem hiding this comment.
AutoSkillit PR Review — Verdict: changes_requested (posted as COMMENT — GitHub disallows REQUEST_CHANGES on own PR)
|
|
||
| ```bash | ||
| PROJECT_PATH=$(pwd) | ||
| LOG_DIR="$HOME/.claude/projects/-${PROJECT_PATH//\//-}" |
There was a problem hiding this comment.
[warning] defense: LOG_DIR path construction via ${PROJECT_PATH//\//-} is brittle. If PROJECT_PATH contains directory components that already have hyphens (e.g. /home/user/my-project), those hyphens become double-hyphens after the substitution, which L176 then collapses — silently mangling the path. The Claude log directory naming convention should be treated as an opaque implementation detail, not reconstructed via string manipulation.
There was a problem hiding this comment.
Investigated — this is intentional. The substitution ${PROJECT_PATH//\//- } replaces each / with -; existing hyphens in directory names (e.g. my-project) are not touched and do not become double-hyphens. The only double-hyphen is at the deliberate - prefix boundary (e.g. - + -home = --home), which L176 collapses. The reviewer's stated mechanism is factually incorrect.
| ```bash | ||
| PROJECT_PATH=$(pwd) | ||
| LOG_DIR="$HOME/.claude/projects/-${PROJECT_PATH//\//-}" | ||
| LOG_DIR="${LOG_DIR//--/-}" |
There was a problem hiding this comment.
[warning] bugs: The ${LOG_DIR//--/-} bash substitution is global, but a triple-hyphen --- becomes -- (not -), requiring a second pass. Any path with hyphens in directory names will also produce a wrong LOG_DIR silently. Consider sed -E 's/-+/-/g' to collapse any run of hyphens to a single hyphen.
There was a problem hiding this comment.
Valid observation — flagged for design decision. --- → -- in one pass is technically correct. However, triple-hyphens can only arise from empty path segments (//), which are not valid POSIX paths, making real-world risk near-zero. The sed -E 's/-+/-/g' suggestion is marginally more robust — whether the change is warranted is a design trade-off for human review.
| from autoskillit.core.paths import pkg_root | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") |
There was a problem hiding this comment.
[warning] tests: scope="module" fixtures with pytest-xdist (-n 4) are not shared across worker processes — each worker computes its own module scope. skill_text and its dependents will be evaluated once per worker, not once globally. For read-only file fixtures this is functionally correct but the caching intent is not realized. Consider scope="session" or document the xdist behavior.
There was a problem hiding this comment.
Investigated — this is intentional. tests/CLAUDE.md explicitly documents: 'Session-scoped fixtures run once per worker process, not once globally.' scope="module" and scope="session" behave identically under pytest-xdist with worksteal — switching would not realize any shared caching across workers. The read-only fixture is correctly structured.
…worktree paths .claude-plugin/hooks.json was committed with absolute paths pointing to a non-existent worktree (impl-investigate-historical-recurrence-20260411-232529). These paths break hooks for all users and CI. Hooks are generated artifacts managed via hook_registry.py/generate_hooks_json — not committed static files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ning find ... | xargs grep is unsafe for filenames with spaces or newlines. Switching to -print0 | xargs -0 prevents incorrect filename splitting. Also avoids platform-dependent errors when find returns no results. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nate false positives The previous pattern '"skill".*"investigate"' matched any JSON skill key containing 'investigate' anywhere (e.g. investigate_later). Anchoring to '/autoskillit:investigate' matches only actual skill invocation records. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….5 Part B The placeholder had no guidance on how to expand it when multiple files are involved. Added a blockquote note: space-separated paths as separate arguments, not a single quoted string. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nostic asserts The compound assert gave no indication of which step heading was missing on failure. Three separate asserts each name the missing heading explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… report section The combined assert identified that both sections must appear but not which one was absent. Two separate asserts each name the missing section explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve messages The bare assert did not indicate which heading (Step 3 or Step 3.5) was missing on failure. Two separate asserts each identify the missing heading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ut_still_kills _watch_session_log waits up to 1s for stdout_session_id_ready before Phase 1 starts, and Phase 1 defaults to _phase1_timeout=30.0s. The theoretical maximum time before trigger fires is 1 + 30 + 0.5s drain = 31.5s, which exceeds the previous outer timeout=30. Under CI load (xdist -n 4), poll overruns pushed the total past 30s, causing timeout_scope.cancelled_caught=True and TIMED_OUT to be returned instead of COMPLETED. timeout=60 gives 28.5s headroom above the worst-case path, matching the pattern already used in TestChannelBFullPipelineAdjudication and TestPostExitDrainWindow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Add a new Step 3.5: Historical Recurrence Check to the investigate skill (
src/autoskillit/skills_extended/investigate/SKILL.md). After the skill synthesizes its findings (Step 3) and before it writes the report (Step 4), it will check whether the identified root cause has been investigated or fixed before by: (1) mining~/.claude/projects/JSONL conversation logs for prior/investigateinvocations with overlapping root cause or components, (2) searchinggit logfor commits that claim to fix the same class of issue, and (3) spawning a conditional analysis subagent to compare prior fix diffs against the current root cause when history is found. The report template gains a## Historical Contextsection that either presents the recurrence analysis or states "No prior investigations or fixes found for this root cause."Architecture Impact
Process Flow Diagram
%%{init: {'flowchart': {'nodeSpacing': 42, 'rankSpacing': 52, 'curve': 'basis'}}}%% flowchart TB %% CLASS DEFINITIONS %% classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff; %% TERMINALS %% START([START: /autoskillit:investigate]) COMPLETE([COMPLETE: %%ORDER_UP%%]) CI_PASS([CI: contract tests PASS]) CI_FAIL([CI: contract tests FAIL]) subgraph Preflight ["Pre-Flight Checks"] direction TB IssueGuard{"GitHub Issue Ref?<br/>━━━━━━━━━━<br/>Scan ARGUMENTS for<br/>URL / owner/repo#N / #N"} FetchIssue["fetch_github_issue<br/>━━━━━━━━━━<br/>include_comments: true<br/>on failure: use raw ARGUMENTS"] CodeIndexInit["Step 0.5: Code-Index Init<br/>━━━━━━━━━━<br/>set_project_path(PROJECT_ROOT)"] IndexFallback["Fallback: Glob + Grep<br/>━━━━━━━━━━<br/>Native search when<br/>set_project_path fails"] end subgraph InvestigationAnalysis ["Investigation Analysis"] direction TB ParseTarget["Step 1: Parse Target<br/>━━━━━━━━━━<br/>Classify: error | module | question<br/>extract type, message, trace"] Subagents["Step 2: Parallel Subagents<br/>━━━━━━━━━━<br/>model: sonnet via Task tool<br/>Core impl · Deps · Tests<br/>Error ctx · Patterns · Arch · Web"] Synthesize["● Step 3: Synthesize Findings<br/>━━━━━━━━━━<br/>Summary · Root Cause<br/>Affected Components · Data Flow<br/>Test Gaps · Similar Patterns<br/>Historical Context [blank → 3.5]"] end subgraph HistoricalCheck ["● Step 3.5: Historical Recurrence Check (NEW)"] direction TB PartA["Part A: Mine JSONL Logs<br/>━━━━━━━━━━<br/>~/.claude/projects/-{cwd} scan<br/>find *.jsonl, skip */subagents/*<br/>grep root cause + components"] PartB["Part B: Git History Scan<br/>━━━━━━━━━━<br/>git log --oneline -100<br/>--grep=fix|revert|remove|replace<br/>git show {HASH} for diffs"] HistoryFound{"History Found?<br/>━━━━━━━━━━<br/>Part A OR Part B<br/>found matches"} ZeroOverhead["Zero-Overhead Path<br/>━━━━━━━━━━<br/>Record: no prior history<br/>skip subagent entirely"] PartC["Part C: Analysis Subagent<br/>━━━━━━━━━━<br/>model: sonnet via Task<br/>Read prior fix diffs<br/>Compare vs current root cause<br/>Identify what prior fix missed"] RecurringFlag{"Recurring Pattern?<br/>━━━━━━━━━━<br/>Prior fix incomplete,<br/>wrong layer, or<br/>symptom-only?"} RectifyEmit["Emit Rectify Flag<br/>━━━━━━━━━━<br/>Flag: run /autoskillit:rectify<br/>for architectural immunity"] end subgraph ReportPhase ["Report Generation"] direction TB WriteReport["Step 4: Write Report<br/>━━━━━━━━━━<br/>{{AUTOSKILLIT_TEMP}}/investigate/<br/>investigation_{topic}_{ts}.md<br/>§7 Historical Context populated"] end subgraph HookRegistration ["★ Hook Registration — .claude-plugin/hooks.json"] direction LR HookRegistry["hook_registry.py<br/>━━━━━━━━━━<br/>HOOK_REGISTRY: HookDef list<br/>single source of truth"] GenHooks["generate_hooks_json()<br/>━━━━━━━━━━<br/>autoskillit install<br/>emit absolute script paths"] HooksJson["★ .claude-plugin/hooks.json<br/>━━━━━━━━━━<br/>Generated manifest<br/>PreToolUse · PostToolUse<br/>SessionStart registrations"] ClaudeCode["Claude Code Hook Dispatch<br/>━━━━━━━━━━<br/>match tool name vs regex<br/>invoke python3 script.py<br/>read JSON decision from stdout"] end subgraph ContractTestFlow ["★ Contract Test Validation — CI Flow"] direction TB TestFile["★ test_investigate_contracts.py<br/>━━━━━━━━━━<br/>14 structural assertions<br/>reads SKILL.md as text fixture<br/>step_35_section + report_section fixtures"] SkillMd["● investigate/SKILL.md<br/>━━━━━━━━━━<br/>Step 3.5 added<br/>§Historical Context in Step 4 template<br/>Rectify flag language present"] ContractsPass{"14 assertions pass?<br/>━━━━━━━━━━<br/>Step 3.5 present · ordered<br/>JSONL mining · git log<br/>conditional subagent · fast path<br/>report template · rectify flag"} end %% MAIN INVESTIGATE FLOW %% START --> IssueGuard IssueGuard -->|"issue reference found"| FetchIssue IssueGuard -->|"no issue reference"| CodeIndexInit FetchIssue -->|"success: use returned content"| CodeIndexInit FetchIssue -->|"failure: proceed with raw ARGUMENTS"| CodeIndexInit CodeIndexInit -->|"success"| ParseTarget CodeIndexInit -->|"error"| IndexFallback IndexFallback --> ParseTarget ParseTarget --> Subagents Subagents -->|"all subagents complete"| Synthesize Synthesize --> PartA Synthesize --> PartB PartA -->|"results"| HistoryFound PartB -->|"results"| HistoryFound HistoryFound -->|"no matches"| ZeroOverhead HistoryFound -->|"matches found"| PartC ZeroOverhead -->|"record no-history sentinel"| WriteReport PartC --> RecurringFlag RecurringFlag -->|"no: first-occurrence fix"| WriteReport RecurringFlag -->|"yes: recurring pattern"| RectifyEmit RectifyEmit -->|"flag appended to §Historical Context"| WriteReport WriteReport --> COMPLETE %% HOOK REGISTRATION FLOW %% HookRegistry -->|"reads HOOK_REGISTRY"| GenHooks GenHooks -->|"writes on install"| HooksJson HooksJson -->|"loaded by Claude Code on start"| ClaudeCode %% CONTRACT TEST FLOW %% TestFile -->|"reads as text fixture"| SkillMd SkillMd -->|"14 regex/string assertions"| ContractsPass ContractsPass -->|"all pass"| CI_PASS ContractsPass -->|"any fail"| CI_FAIL %% CLASS ASSIGNMENTS %% class START,COMPLETE,CI_PASS,CI_FAIL terminal; class IssueGuard,HistoryFound,RecurringFlag,ContractsPass stateNode; class FetchIssue,CodeIndexInit,IndexFallback,ParseTarget,Subagents handler; class Synthesize,WriteReport phase; class PartA,PartB,PartC,ZeroOverhead handler; class RectifyEmit detector; class HookRegistry,GenHooks handler; class HooksJson,TestFile newComponent; class SkillMd,ClaudeCode phase;Scenarios Diagram
%%{init: {'flowchart': {'nodeSpacing': 40, 'rankSpacing': 55, 'curve': 'basis'}}}%% flowchart TB %% CLASS DEFINITIONS %% classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; %% ─────────────────────────────────────────────────────── %% %% SCENARIO 1: Investigate → Historical Recurrence Detected %% %% ─────────────────────────────────────────────────────── %% subgraph S1 ["SCENARIO 1 — Investigate → Recurrence Detected → Rectify Recommended"] direction LR S1_ENTRY["run_skill MCP<br/>━━━━━━━━━━<br/>/autoskillit:investigate<br/><error or issue ref>"] S1_HOOKS["★ hooks.json<br/>PreToolUse Guards<br/>━━━━━━━━━━<br/>quota_check<br/>skill_cmd_check<br/>skill_command_guard"] S1_INIT["● SKILL.md Steps 0.5–2<br/>━━━━━━━━━━<br/>code-index init<br/>parallel subagents<br/>(7 concern areas)"] S1_SYNTH["● SKILL.md Step 3<br/>Synthesize Findings<br/>━━━━━━━━━━<br/>root cause + affected<br/>components identified"] S1_HIST["● SKILL.md Step 3.5<br/>Historical Check<br/>━━━━━━━━━━<br/>Part A: mine ~/.claude/projects/<br/>Part B: git log --oneline -100<br/>→ prior fix found"] S1_SUB["Conditional Subagent<br/>━━━━━━━━━━<br/>compare prior diff<br/>vs. current root cause<br/>model: sonnet"] S1_RECTIFY["Rectify Flagged<br/>━━━━━━━━━━<br/>recurring pattern<br/>/autoskillit:rectify<br/>recommended"] S1_REPORT["★ investigation_*.md<br/>━━━━━━━━━━<br/>AUTOSKILLIT_TEMP/<br/>investigate/"] S1_TOKEN["investigation_path +<br/>%%ORDER_UP%%<br/>━━━━━━━━━━<br/>pipeline proceeds"] end S1_ENTRY -->|"reads args"| S1_HOOKS S1_HOOKS -->|"all guards pass"| S1_INIT S1_INIT -->|"findings ready"| S1_SYNTH S1_SYNTH -->|"root cause known"| S1_HIST S1_HIST -->|"match found"| S1_SUB S1_SUB -->|"pattern confirmed"| S1_RECTIFY S1_RECTIFY -->|"writes report"| S1_REPORT S1_REPORT -->|"emits token"| S1_TOKEN %% ──────────────────────────────────────────── %% %% SCENARIO 2: First-Occurrence Fast Path %% %% ──────────────────────────────────────────── %% subgraph S2 ["SCENARIO 2 — First-Occurrence Bug — Step 3.5 Zero-Overhead Fast Path"] direction LR S2_ENTRY["run_skill MCP<br/>━━━━━━━━━━<br/>/autoskillit:investigate"] S2_GUARDS["★ hooks.json Guards<br/>━━━━━━━━━━<br/>quota + cmd + guard<br/>all pass"] S2_STEPS["● SKILL.md Steps 0.5–3<br/>━━━━━━━━━━<br/>init + explore + synth"] S2_HIST["● SKILL.md Step 3.5<br/>Historical Check<br/>━━━━━━━━━━<br/>Part A: no JSONL match<br/>Part B: no git match"] S2_SKIP["No Subagent Spawned<br/>━━━━━━━━━━<br/>records: no prior<br/>investigations found<br/>zero overhead"] S2_REPORT["★ investigation_*.md<br/>━━━━━━━━━━<br/>written normally<br/>no rectify flag"] end S2_ENTRY --> S2_GUARDS --> S2_STEPS --> S2_HIST -->|"no match"| S2_SKIP --> S2_REPORT %% ──────────────────────────────────────────── %% %% SCENARIO 3: Hooks Gate Chain %% %% ──────────────────────────────────────────── %% subgraph S3 ["SCENARIO 3 — ★ hooks.json Gate Chain — run_skill Execution"] direction LR S3_CALLER["Pipeline / Claude<br/>━━━━━━━━━━<br/>calls run_skill MCP tool"] S3_PRE1["★ hooks.json<br/>PreToolUse ①<br/>━━━━━━━━━━<br/>skill_cmd_check.py<br/>validates command"] S3_PRE2["★ hooks.json<br/>PreToolUse ②<br/>━━━━━━━━━━<br/>quota_check.py<br/>enforces threshold"] S3_PRE3["★ hooks.json<br/>PreToolUse ③<br/>━━━━━━━━━━<br/>skill_command_guard.py<br/>blocks disallowed"] S3_EXEC["Skill Executes<br/>━━━━━━━━━━<br/>headless session<br/>investigate / any skill"] S3_POST1["★ hooks.json<br/>PostToolUse ①<br/>━━━━━━━━━━<br/>pretty_output.py<br/>MCP JSON → Markdown"] S3_POST2["★ hooks.json<br/>PostToolUse ②③<br/>━━━━━━━━━━<br/>token_summary_appender<br/>quota_post_check"] end S3_CALLER --> S3_PRE1 --> S3_PRE2 --> S3_PRE3 --> S3_EXEC --> S3_POST1 --> S3_POST2 %% ──────────────────────────────────────────── %% %% SCENARIO 4: CI Contract Validation %% %% ──────────────────────────────────────────── %% subgraph S4 ["SCENARIO 4 — CI Contract Validation — ★ test_investigate_contracts.py"] direction LR S4_CI["CI: task test-all<br/>━━━━━━━━━━<br/>pytest -n 4 (xdist)"] S4_TEST["★ test_investigate_contracts.py<br/>━━━━━━━━━━<br/>skill_text fixture<br/>step_35_section fixture<br/>report_section fixture"] S4_READ["● SKILL.md<br/>━━━━━━━━━━<br/>loaded as text<br/>sliced by fixture"] S4_CHECKS["14 Contract Assertions<br/>━━━━━━━━━━<br/>Step 3.5 ordering<br/>JSONL mining + subagent exclusion<br/>git log -100 scope + git show<br/>conditional subagent + fast path<br/>rectify link<br/>Historical Context in template"] S4_PASS["PASS / FAIL<br/>━━━━━━━━━━<br/>Step 3.5 prose contract<br/>structurally enforced"] end S4_CI --> S4_TEST --> S4_READ --> S4_CHECKS --> S4_PASS %% CLASS ASSIGNMENTS %% class S1_ENTRY,S2_ENTRY,S3_CALLER,S4_CI cli; class S1_HOOKS,S2_GUARDS,S3_PRE1,S3_PRE2,S3_PRE3,S3_POST1,S3_POST2,S4_TEST newComponent; class S1_INIT,S2_STEPS,S4_READ phase; class S1_SYNTH,S1_SUB,S3_EXEC,S4_CHECKS handler; class S1_HIST,S2_HIST handler; class S1_RECTIFY,S2_SKIP detector; class S1_REPORT,S2_REPORT,S4_PASS output; class S1_TOKEN stateNode;Closes #724
Implementation Plan
Plan file:
/home/talon/projects/autoskillit-runs/impl-20260411-225426-312280/.autoskillit/temp/make-plan/investigate_historical_recurrence_check_plan_2026-04-11_225426.md🤖 Generated with Claude Code via AutoSkillit