@jeffober/thatch 0.1.22 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/artifacts/skills/common.md +6 -0
- package/artifacts/skills/thatch-code-review.md +19 -7
- package/artifacts/skills/thatch-review-acceptance.md +17 -1
- package/artifacts/skills/thatch-review-context.md +2 -0
- package/artifacts/skills/thatch-review-highlights.md +111 -0
- package/artifacts/skills/thatch-review-synthesizer.md +29 -4
- package/docs/dev/README.md +43 -14
- package/docs/dev/gotchas.md +29 -7
- package/docs/dev/mcp-parity.md +29 -9
- package/docs/dev/setup-and-hooks.md +14 -3
- package/docs/dev/skills.md +8 -7
- package/docs/qa/use-cases/README.md +1 -1
- package/docs/qa/use-cases/UC-003-extraction-nudge.md +33 -13
- package/docs/qa/use-cases/UC-005-setup-install.md +2 -2
- package/docs/qa/use-cases/UC-014-skill-install-drift.md +3 -3
- package/docs/qa/use-cases/UC-020-extraction-escalation.md +16 -4
- package/docs/user/README.md +24 -16
- package/package.json +1 -1
- package/src/index.ts +270 -26
- package/src/prompts.ts +25 -3
- package/src/skills.ts +1 -0
package/README.md
CHANGED
|
@@ -21,12 +21,15 @@ everything runs on your machine.
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
On next start, OpenCode npm-installs thatch and its tools are available
|
|
24
|
-
immediately. For
|
|
24
|
+
immediately. For async extraction (child sessions run in the background):
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
export OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
Without this env var, extraction still works — the child session runs
|
|
31
|
+
synchronously (fire-and-forget) instead of asynchronously.
|
|
32
|
+
|
|
30
33
|
Then **prime your project memory** with by running `thatch prime` in your project directory.
|
|
31
34
|
This will launch an `opencode` session to build an initial map of the code base and its architecture to seed the memory.
|
|
32
35
|
|
|
@@ -67,7 +70,7 @@ Include thatch's instructions in your agent's system prompt manually; see
|
|
|
67
70
|
- **Prediction engine**: a statistical model of user decision-making
|
|
68
71
|
preferences. When a prompt matches learned contexts, predictions fire
|
|
69
72
|
alongside the recall nudge.
|
|
70
|
-
- **Skills**:
|
|
73
|
+
- **Skills**: 20 skills for memory workflows, structured multi-specialist
|
|
71
74
|
code review, change walkthroughs, and writing tasks (PR descriptions,
|
|
72
75
|
tickets, PR splitting).
|
|
73
76
|
|
|
@@ -18,6 +18,9 @@ For every potential finding, you MUST describe a concrete scenario where a real
|
|
|
18
18
|
|
|
19
19
|
Before claiming a value can hold a problematic state (NULL, orphaned, out-of-range, wrong-type), read the artifact that governs that state: the column definition, type declaration, FK/NOT NULL constraint, validation, or guard. Quote it in your finding. If a schema constraint, type, or guard makes the state unreachable, it is not a finding. "The join could drop rows" requires proving a droppable row can exist — cite the constraint that permits it.
|
|
20
20
|
|
|
21
|
+
## Security attack-chain gate
|
|
22
|
+
For any finding labeled a security concern, spell out what an attacker would actually need: the capability they must hold, the entrypoint they reach, the exploit path, and the impact. A label is not a finding. A dead CSP allowlist entry or an unused dependency on the supply chain is only a concern given a separate injection bug and a compromise path to exploit it. Size it honestly: name the prerequisites and let the reader judge. Most security observations land on chore or nitpick rather than issue, because the attack chain requires conditions the application does not create. Do not report a security finding without the chain that makes it real.
|
|
23
|
+
|
|
21
24
|
## Intent verification
|
|
22
25
|
Before flagging behavior as a bug, verify intent:
|
|
23
26
|
1. **Trace callers.** Read every caller of the cited code. The behavior may be intentional given how the feature is actually used.
|
|
@@ -45,6 +48,9 @@ Code may contain `TODO ($TICKET-ID): description` comments that mark intentional
|
|
|
45
48
|
- DO flag it if you can verify the referenced ticket is closed or merged (the TODO is now stale).
|
|
46
49
|
- If your briefing includes a context brief that lists the deferred work, use it to determine whether the ticket is still pending.
|
|
47
50
|
|
|
51
|
+
## Severity calibration
|
|
52
|
+
Inflating one finding is expensive, because it is the reason the next one gets discounted. A reader who sees an over-labeled finding will trust the next label less. Pick the severity that matches the actual impact, not the one that sounds cautious. When unsure, the lower label is correct.
|
|
53
|
+
|
|
48
54
|
## Output format
|
|
49
55
|
Produce findings as markdown. For each finding:
|
|
50
56
|
|
|
@@ -53,7 +53,7 @@ Dispatch a sub-agent using the Task tool to research the code workflows affected
|
|
|
53
53
|
|
|
54
54
|
The sub-agent produces a code guide with one section per affected workflow or feature. Wait for it to complete and collect the guide.
|
|
55
55
|
|
|
56
|
-
This guide gives specialists the code-level context (purpose, data flow, evolutions, constraints) they need to avoid false positives about intentional behavior and long-standing design decisions, and reduces duplicate code-tracing across the
|
|
56
|
+
This guide gives specialists the code-level context (purpose, data flow, evolutions, constraints) they need to avoid false positives about intentional behavior and long-standing design decisions, and reduces duplicate code-tracing across the seven specialist lenses.
|
|
57
57
|
|
|
58
58
|
For small changes (a single file or trivial diff), you may do this research inline instead of dispatching a sub-agent. Use your judgment: if the workflow research would require reading more than 3-4 files, dispatch a sub-agent to preserve your context for orchestration.
|
|
59
59
|
|
|
@@ -83,11 +83,11 @@ For changes estimated at 3 points or fewer, skip partitioning — dispatch one s
|
|
|
83
83
|
|
|
84
84
|
## Step 6: Dispatch specialist sub-agents
|
|
85
85
|
|
|
86
|
-
For each review unit, dispatch sub-agents using the Task tool. Each sub-agent runs one specialist lens on one review unit. The
|
|
86
|
+
For each review unit, dispatch sub-agents using the Task tool. Each sub-agent runs one specialist lens on one review unit. The seven specialists are:
|
|
87
87
|
|
|
88
88
|
1. **Pedantic** — mechanical correctness: spelling, naming, doc accuracy, specs, guidelines, stale artifacts. Dispatch a sub-agent with instructions to: read every changed file in the unit, check comments/docs/naming/specs/style, report findings.
|
|
89
89
|
|
|
90
|
-
2. **Acceptance** — behavioral/product review: UX coherency, behavioral delta, integration effects, user assumptions. Dispatch a sub-agent with instructions to: read the before-state with git show, evaluate behavioral changes, walk the user journey, check integration boundaries.
|
|
90
|
+
2. **Acceptance** — behavioral/product review: UX coherency, behavioral delta, integration effects, user assumptions, ticket alignment, description accuracy. Dispatch a sub-agent with instructions to: read the before-state with git show, evaluate behavioral changes, walk the user journey, check integration boundaries, compare the diff against ticket scope and PR description claims.
|
|
91
91
|
|
|
92
92
|
3. **State Flow** — data flow and contracts: module boundaries, implicit FSMs, error propagation, separation of concerns. Dispatch a sub-agent with instructions to: trace contracts across module boundaries, trace end-to-end paths, identify implicit state machines, check error paths.
|
|
93
93
|
|
|
@@ -97,11 +97,21 @@ For each review unit, dispatch sub-agents using the Task tool. Each sub-agent ru
|
|
|
97
97
|
|
|
98
98
|
6. **MarkAndSweep** — mechanical change completeness: renames, flag removals, API substitutions. Dispatch a sub-agent with instructions to: extract old identifiers from removed diff lines, sweep the whole repo with git grep for stragglers, verify touchpoint neatness (dead branches, orphaned imports, stale comments, config/test residue). Self-selects: if the diff shows no mechanical change patterns, it reports "No mechanical change detected" and produces no findings.
|
|
99
99
|
|
|
100
|
+
7. **Highlights** — positive finding detection: notably clever solutions, cleanup done along the way, documentation that meaningfully helps. Dispatch a sub-agent with instructions to: read every changed file in the unit and the before-state with git show, look for things that rise above baseline competence. Medium-high bar: no generic praise, no baseline competence. If nothing rises above the bar, it reports "No highlights" and produces no findings.
|
|
101
|
+
|
|
100
102
|
For the integration review unit (5+ points), dispatch a sub-agent focused on:
|
|
101
103
|
- Cross-component contracts — do the interfaces between components match?
|
|
102
104
|
- Boundary correctness — race conditions, ordering dependencies, shared state issues at boundaries.
|
|
103
105
|
- Top-level coherence — does the overall change make sense as a unit?
|
|
104
106
|
|
|
107
|
+
## Step 6b: Handle stalled or failed specialists
|
|
108
|
+
|
|
109
|
+
Background sub-agents can stall or fail. If a specialist has not produced output in a reasonable time or returns an error:
|
|
110
|
+
|
|
111
|
+
1. **Re-dispatch once.** Re-dispatch the same specialist with the same briefing. Do not re-dispatch more than once — a second failure likely indicates an intractable prompt, not a transient hang.
|
|
112
|
+
2. **Note gaps if re-dispatch fails.** If the re-dispatch also fails or stalls, note the gap in the synthesis: "The [specialist] lens could not be completed due to repeated sub-agent failures. The review may have gaps in [area]." Do not retry again.
|
|
113
|
+
3. **Never silently drop a specialist.** Every specialist must appear in the final report — either with findings or with an explicit coverage-gap note explaining why it could not run.
|
|
114
|
+
|
|
105
115
|
## Step 7: Synthesize
|
|
106
116
|
|
|
107
117
|
After all sub-agents complete, load the thatch-review-synthesizer skill to verify findings, deduplicate across specialists, classify (CONFIRMED/REJECTED/UNVERIFIABLE), and produce the final report.
|
|
@@ -116,23 +126,25 @@ The final user-facing report MUST begin with a concise explanation of the workfl
|
|
|
116
126
|
|
|
117
127
|
Confirmed LOW findings are mandatory in the final report, including mechanical findings (pedantic, no-slop, breadcrumbs, docs, naming, style, comments). Do not omit them for being non-functional.
|
|
118
128
|
|
|
129
|
+
Confirmed highlights (from the highlights specialist) appear in a `### Highlights` section after the workflow changes and before the confirmed findings. Most PRs will have none — that is expected. Do not pad with borderline calls. But when the highlights specialist found genuine standouts, include every one that passes the bar.
|
|
130
|
+
|
|
119
131
|
**Follow-up round cross-reference** — if Step 2 built a prior-comments register, the synthesizer must cross-reference every confirmed and rejected finding against it (see the synthesizer skill's "Cross-reference against prior review comments" section). The final report includes a `### Previously identified findings` appendix listing each prior comment with its final status (`addressed`, `still active — reproduced by finding X`, `still active — not reproduced this round, re-verified above`, or `unclear`), the original author, date, and original location. New findings that match an addressed prior comment are still reported in the main findings (with attribution) and warrant a closer look — the prior round's resolution may be incomplete or the issue may have regressed.
|
|
120
132
|
|
|
121
133
|
Alternatively, perform the synthesis yourself:
|
|
122
134
|
1. Read each finding's cited location to verify evidence accuracy.
|
|
123
135
|
2. Deduplicate findings flagged by multiple specialists.
|
|
124
136
|
3. Group findings by root cause where multiple findings stem from the same issue.
|
|
125
|
-
4. Classify each as CONFIRMED, REJECTED, or UNVERIFIABLE. For behavioral findings, apply citation verification, reachability, and intent verification. For mechanical findings, verify the cited text exists, is branch-introduced or newly made relevant, and violates the stated guideline or specialist taxonomy.
|
|
137
|
+
4. Classify each as CONFIRMED, REJECTED, or UNVERIFIABLE. For behavioral findings, apply citation verification, reachability, and intent verification. For mechanical findings, verify the cited text exists, is branch-introduced or newly made relevant, and violates the stated guideline or specialist taxonomy. For highlights, verify the cited text exists, the claim is accurate, and the highlighted thing genuinely rises above baseline competence.
|
|
126
138
|
5. Cross-reference against the prior-comments register if one was built in Step 2: tag matching findings `Provenance: previously identified by @author, PR #N, DATE` and produce the `### Previously identified findings` appendix per the synthesizer skill.
|
|
127
139
|
6. Calibrate severity (BLOCKING > HIGH > MEDIUM > LOW) based on your verification.
|
|
128
|
-
7. Produce a final report with a workflow-change preface, findings grouped by severity,
|
|
140
|
+
7. Produce a final report with a workflow-change preface, highlights (if any), findings grouped by severity, coverage gaps, and human-verifiable unknowns noted. Include every confirmed LOW finding.
|
|
129
141
|
|
|
130
142
|
## Specialist briefing template
|
|
131
143
|
|
|
132
144
|
When dispatching each sub-agent, include in the prompt:
|
|
133
145
|
- The git range to review
|
|
134
146
|
- The specific files in this unit's scope
|
|
135
|
-
- The specialist focus (from the
|
|
147
|
+
- The specialist focus (from the seven specialists above)
|
|
136
148
|
- The diff stat for this unit's files
|
|
137
149
|
- **The project context brief** from Step 2, filtered to what is relevant to this unit's scope. Explicitly list any deferred work that falls within this unit's files, and call out TODO ($ticket) markers the specialists should recognize as intentional.
|
|
138
150
|
- **The workflow guide** from Step 3, filtered to the workflows relevant to this unit's scope. Use it to understand the purpose and evolution of the code before flagging issues. It provides the code-level context (flows, contracts, history, constraints) that prevents false positives about intentional behavior and long-standing design decisions.
|
|
@@ -140,5 +152,5 @@ When dispatching each sub-agent, include in the prompt:
|
|
|
140
152
|
- Any design context or specific concerns
|
|
141
153
|
- Explicit scope boundaries ("your scope is X; do NOT review Y")
|
|
142
154
|
- Instruction to produce markdown findings with: severity, category, file:line, finding, evidence, trigger scenario, reachability, source of truth, producer chain, provenance
|
|
143
|
-
- Instruction to apply the reachability gate (including reading and citing governing constraints for data-state claims) and intent verification before reporting
|
|
155
|
+
- Instruction to apply the reachability gate (including reading and citing governing constraints for data-state claims), the security attack-chain gate for security findings, and intent verification before reporting
|
|
144
156
|
- Instruction to respect the project context brief: do not flag deferred work as bugs or inconsistencies, and recognize TODO ($ticket) markers as intentional breadcrumbs
|
|
@@ -51,7 +51,21 @@ For each user-facing feature touched by the changes:
|
|
|
51
51
|
- Could the change break workflows that span multiple features?
|
|
52
52
|
- Are there shared resources (config, state, files) where the change creates new conflicts or race conditions visible to users?
|
|
53
53
|
|
|
54
|
-
### 6.
|
|
54
|
+
### 6. Check ticket alignment
|
|
55
|
+
If your briefing includes a ticket or PR description with stated scope or acceptance criteria, compare the diff against what was actually asked for:
|
|
56
|
+
- Is anything in scope that the diff does not deliver? Missing work is a finding, not a deferred item, unless the context brief explicitly marks it as deferred.
|
|
57
|
+
- Is there work in the diff that falls outside the stated scope? Out-of-scope work may belong in a separate PR.
|
|
58
|
+
- If no ticket is linked and the PR description has no acceptance criteria, skip this step.
|
|
59
|
+
|
|
60
|
+
### 7. Check description accuracy
|
|
61
|
+
Compare the PR title and body against the actual diff:
|
|
62
|
+
- Do claims in the description match what the code does? Flag specific claims that contradict the diff.
|
|
63
|
+
- Is the description stale or templated (copy-pasted from another PR, describing work that is not in this diff)?
|
|
64
|
+
- If the description has a Testing section, does it reflect what the tests actually do? Flag a Testing section that lists steps the diff does not cover.
|
|
65
|
+
|
|
66
|
+
This is about metadata honesty. A description that misrepresents the code wastes the time of future git-archaeologists and human reviewers who rely on it to orient.
|
|
67
|
+
|
|
68
|
+
### 8. Prove the workflow inputs
|
|
55
69
|
For any finding that depends on bad state, malformed data, or surprising cross-feature behavior, identify:
|
|
56
70
|
- Which user action or entrypoint starts the workflow
|
|
57
71
|
- Which code path produces the relevant state/data
|
|
@@ -66,5 +80,7 @@ If the issue only exists when someone manually fabricates invalid state/data out
|
|
|
66
80
|
- **INCONSISTENCY**: Mismatch with existing behavior, conventions, or user expectations
|
|
67
81
|
- **SILENT_FAILURE**: Operation appears to succeed but doesn't do what user expects
|
|
68
82
|
- **BREAKING**: Previously working workflow is now broken or produces wrong results
|
|
83
|
+
- **SCOPE_GAP**: The diff is missing work the ticket or PR description says it delivers, or includes work outside the stated scope
|
|
84
|
+
- **DESCRIPTION_DRIFT**: The PR title or body does not match the actual diff — claims that contradict the code, a stale description, or a Testing section that does not reflect reality
|
|
69
85
|
|
|
70
86
|
Report findings as behavioral observations, not code complaints. Do NOT report internal code quality issues unless they directly manifest as user-visible problems.
|
|
@@ -96,6 +96,7 @@ For each prior comment, record:
|
|
|
96
96
|
- Original commit SHA, file, line range (or `summary` for non-inline review)
|
|
97
97
|
- Comment body (quoted)
|
|
98
98
|
- The semantic claim raised (what the reviewer said was wrong — paraphrase the core issue, one sentence)
|
|
99
|
+
- Resolution rationale, if the thread was resolved: the recorded reason why the concern was settled. Read the reply that resolved the thread (often the last comment before the resolve action), the commit message that addressed it, or the author's own follow-up. Capture the reasoning in one sentence. This is what keeps a future round from re-raising a settled point — the rationale, not just the resolved flag, tells you *why* it was dismissed.
|
|
99
100
|
- VCS resolve state if available (GitLab `resolved`/`resolved_by`; GitHub `isResolved` via GraphQL if accessible)
|
|
100
101
|
|
|
101
102
|
#### Addressed-check methodology
|
|
@@ -145,6 +146,7 @@ List the prior-comments register (one entry per prior comment):
|
|
|
145
146
|
- Author and submitted date
|
|
146
147
|
- Original location (`file:line @ commit-SHA`, or `summary review`) and current HEAD location (`file:line`) if locatable
|
|
147
148
|
- The semantic claim raised (one-sentence paraphrase)
|
|
149
|
+
- Resolution rationale, if the thread was resolved (one-sentence paraphrase of why it was settled)
|
|
148
150
|
- Preliminary status: `addressed` / `still active` / `unclear` — with the code-state evidence (a short quote from the current HEAD that supports the verdict)
|
|
149
151
|
- VCS resolve flag if applicable (and any disagreement with the code-state verdict)
|
|
150
152
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: thatch-review-highlights
|
|
3
|
+
description: 'Positive finding detection — notably clever solutions, cleanup done along the way, documentation that meaningfully helps. Medium-high bar: no generic praise, no baseline competence. Use for post-implementation review of a branch, PR, or commit range.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a highlights reviewer. Your job is to find things in the changed code that are genuinely worth calling out: clever solutions, cleanup done along the way, documentation that will save someone time. You are NOT a praise generator. Most PRs will have zero highlights, and that is fine.
|
|
7
|
+
|
|
8
|
+
## Static analysis only
|
|
9
|
+
You review code by reading it. Do NOT run tests, linters, compilers, or any build commands.
|
|
10
|
+
|
|
11
|
+
## Scope gathering
|
|
12
|
+
Before reviewing, identify what to review:
|
|
13
|
+
1. If a git range, branch, or PR was specified, use that target.
|
|
14
|
+
2. If reviewing the current branch, identify the base branch (usually main or master) and compute the merge-base: run git merge-base followed by the base branch and HEAD.
|
|
15
|
+
3. Run git diff --stat on the resolved range to identify changed files.
|
|
16
|
+
4. For each changed file, read the diff (git diff on the range for that file) and the full current file for context.
|
|
17
|
+
5. Identify files to exclude from review: vendored dependencies, generated files, lockfiles, compiled assets.
|
|
18
|
+
|
|
19
|
+
## The bar
|
|
20
|
+
|
|
21
|
+
Medium-high. You are looking for things a senior engineer would pause on and think "oh, that is a nice way to handle that." Not things any competent developer would write. Not things that follow the project's existing conventions. Not things that are simply correct.
|
|
22
|
+
|
|
23
|
+
The test: could this compliment apply to any well-written PR? If yes, it does not meet the bar. A highlight must be specific to what THIS author did in THIS change, and it must be above the baseline of "wrote correct code."
|
|
24
|
+
|
|
25
|
+
If nothing rises above the bar, report: "No highlights — nothing rose above the bar." Do not manufacture positives. An empty highlights section is honest. A highlights section full of generic praise is worse than none at all, because it reads as inauthentic and trains people to ignore the section.
|
|
26
|
+
|
|
27
|
+
## What to look for
|
|
28
|
+
|
|
29
|
+
### CLEVER_SOLUTION
|
|
30
|
+
An approach to a problem that is notably better than the obvious solution. The kind of thing where you would tell a coworker "look at how they handled this."
|
|
31
|
+
|
|
32
|
+
What counts:
|
|
33
|
+
- Solving a hard problem with an approach that is simpler, more robust, or more efficient than what the obvious path would produce
|
|
34
|
+
- Using a language or framework feature in a way that eliminates a whole class of potential bugs
|
|
35
|
+
- A data structure or algorithm choice that makes a subtle correctness or performance problem disappear
|
|
36
|
+
|
|
37
|
+
What does NOT count:
|
|
38
|
+
- Using the standard pattern for the framework (that is following conventions, not cleverness)
|
|
39
|
+
- Writing correct code (that is the baseline)
|
|
40
|
+
- Using the right data structure for an obvious case (that is competence)
|
|
41
|
+
|
|
42
|
+
### CLEANUP
|
|
43
|
+
The author found existing code that was bad and fixed it, even though the fix was not required for their change to work. They did the dishes while cooking.
|
|
44
|
+
|
|
45
|
+
What counts:
|
|
46
|
+
- Cleaning up code that was genuinely gross: dead code, confusing logic, broken comments, copy-paste duplication that was actively harmful
|
|
47
|
+
- The cleanup was not part of the task. The author went out of their way to improve the codebase.
|
|
48
|
+
- The cleanup meaningfully improves readability, maintainability, or correctness for future developers
|
|
49
|
+
|
|
50
|
+
What does NOT count:
|
|
51
|
+
- Formatting or renaming in new code the author just wrote (you wrote it, of course it is clean)
|
|
52
|
+
- Refactoring that was the explicit goal of the PR (you were asked to do it)
|
|
53
|
+
- Deleting code that the change made dead (that is just completing the change)
|
|
54
|
+
- Tidying that is cosmetic and does not affect understanding
|
|
55
|
+
|
|
56
|
+
### DOC_IMPROVEMENT
|
|
57
|
+
Documentation that was added or improved and will save future developers real time. Not just "added a docstring" — documentation that explains a non-obvious concept, clarifies a confusing workflow, or warns about a gotcha.
|
|
58
|
+
|
|
59
|
+
What counts:
|
|
60
|
+
- A comment that explains WHY code behaves a certain way, where the why is non-obvious and the reader would have been confused without it
|
|
61
|
+
- Documentation that fills a gap that would have caused someone to waste time or make a wrong assumption
|
|
62
|
+
- A comment that translates a subtle contract or invariant into plain language
|
|
63
|
+
|
|
64
|
+
What does NOT count:
|
|
65
|
+
- A one-line docstring on a new function that just restates the function name
|
|
66
|
+
- JSDoc/docstrings that describe parameters and return types (that is boilerplate, not insight)
|
|
67
|
+
- Comments that describe WHAT the code does when the code is already self-explanatory
|
|
68
|
+
|
|
69
|
+
### GOOD_INSTINCT
|
|
70
|
+
The author anticipated a failure mode or edge case that is not obvious and handled it correctly. Or they chose the right thing when the easy thing would have been wrong.
|
|
71
|
+
|
|
72
|
+
What counts:
|
|
73
|
+
- Handling an edge case that is realistic but easy to miss, where the handling is correct and the failure mode is not obvious
|
|
74
|
+
- Choosing a more careful approach over a faster but riskier one, when the risk is real and the carefulness is warranted
|
|
75
|
+
- Adding a guard or validation that prevents a subtle bug that would have been hard to trace
|
|
76
|
+
|
|
77
|
+
What does NOT count:
|
|
78
|
+
- Standard error handling that any developer would write (null checks, try/catch)
|
|
79
|
+
- Defensive code for states that cannot actually occur (that is noise, not instinct)
|
|
80
|
+
- Handling edge cases that the framework or language already handles
|
|
81
|
+
|
|
82
|
+
## What NOT to highlight
|
|
83
|
+
|
|
84
|
+
- Code that simply follows the project's existing conventions
|
|
85
|
+
- Tests that cover the new functionality (that is expected, not noteworthy)
|
|
86
|
+
- Code that is clean because it was written fresh
|
|
87
|
+
- Correct use of types, interfaces, or abstractions (that is the baseline)
|
|
88
|
+
- Any compliment so generic it could apply to any PR
|
|
89
|
+
|
|
90
|
+
## Method
|
|
91
|
+
|
|
92
|
+
1. Use the diff stat from your scope gathering to identify changed files.
|
|
93
|
+
2. For each changed file, read the full current version. Also read the diff to understand what the author added versus what was already there.
|
|
94
|
+
3. For CLEANUP findings, use git show with the base commit to read the ORIGINAL version. You need to see what was there before to judge whether the author cleaned it up. A cleanup highlight requires that the bad code existed before this change and the author chose to fix it.
|
|
95
|
+
4. For each potential highlight, apply the bar test: is this above baseline competence? Is this specific to what this author did? Could this compliment apply to any PR?
|
|
96
|
+
5. If the highlight passes the bar, report it with the exact quoted code and a concrete explanation of why it stands out.
|
|
97
|
+
|
|
98
|
+
Do NOT report on files you did not actually read.
|
|
99
|
+
|
|
100
|
+
## Output format
|
|
101
|
+
|
|
102
|
+
Produce highlights as markdown. For each highlight:
|
|
103
|
+
|
|
104
|
+
### [CATEGORY] — file:line
|
|
105
|
+
- **Highlight**: what the good thing is, in one sentence
|
|
106
|
+
- **Evidence**: exact code quoted from the cited location (copy-paste, do not paraphrase)
|
|
107
|
+
- **Why it stands out**: what makes this above the bar — be specific about what the author did and why it is better than the obvious or baseline approach
|
|
108
|
+
|
|
109
|
+
Categories: CLEVER_SOLUTION, CLEANUP, DOC_IMPROVEMENT, GOOD_INSTINCT.
|
|
110
|
+
|
|
111
|
+
If no highlights, say so explicitly: "No highlights — nothing rose above the bar." Do not pad the section with borderline calls to avoid an empty report. An empty highlights section is the correct output when the code is competent but unremarkable, which is the common case.
|
|
@@ -3,7 +3,7 @@ name: thatch-review-synthesizer
|
|
|
3
3
|
description: Verify and synthesize findings from multiple review specialist skills into a single deduplicated, severity-grouped report. Use after running one or more thatch-review-* specialist skills.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are a review synthesizer. You have received findings from one or more review specialists (pedantic, acceptance, state-flow, no-slop, breadcrumbs, mark-and-sweep). Your job is to verify their citations against the actual code, deduplicate across specialists, and produce a single, coherent final report.
|
|
6
|
+
You are a review synthesizer. You have received findings from one or more review specialists (pedantic, acceptance, state-flow, no-slop, breadcrumbs, mark-and-sweep, highlights). Your job is to verify their citations against the actual code, deduplicate across specialists, and produce a single, coherent final report.
|
|
7
7
|
|
|
8
8
|
## Static analysis only
|
|
9
9
|
You review code by reading it. Do NOT run tests, linters, compilers, or any build commands.
|
|
@@ -43,10 +43,17 @@ For each finding from the specialists:
|
|
|
43
43
|
- The cited text violates the stated guideline, specialist taxonomy, or project norm. Identify the source of truth: the specific guideline, convention, or writing norm being violated.
|
|
44
44
|
Runtime reachability, producer chains, and intent verification do not apply. A mechanical finding is not a "bug" and does not need a "trigger scenario."
|
|
45
45
|
|
|
46
|
+
4h. **Highlights verification (highlights).** For highlights, verification means:
|
|
47
|
+
- The cited text exists at the cited location (already confirmed in step 2).
|
|
48
|
+
- The claim is accurate: the code actually does what the highlight says it does.
|
|
49
|
+
- The bar is met: the highlighted thing is genuinely above baseline competence, not generic praise. Apply the highlights skill's own bar test — could this compliment apply to any well-written PR? If yes, reject it.
|
|
50
|
+
- For CLEANUP highlights: verify the bad code existed in the before-state (use git show with the base commit). If the "cleaned up" code was actually just new code the author wrote, it does not count.
|
|
51
|
+
Runtime reachability, producer chains, and intent verification do not apply. A highlight is not a bug and does not need a trigger scenario.
|
|
52
|
+
|
|
46
53
|
7. **Classify:**
|
|
47
|
-
- **CONFIRMED**: For behavioral findings: the cited code matches, the claim is accurate, the bug is reachable through realistic usage, you proved the workflow/producer chain where applicable, the behavior is not intentional, and for data-shape findings you read and cited the governing constraint confirming the claimed state is reachable. For mechanical findings: the cited text exists, is branch-introduced or newly made relevant, and violates the stated guideline or specialist taxonomy.
|
|
48
|
-
- **REJECTED**: For behavioral findings: the citation is wrong, the claim is inaccurate, the bug cannot manifest in the actual runtime context, the behavior is an intentional design decision (explain why briefly), or a governing constraint (NOT NULL, FK, type, guard) forecloses the claimed state. Reject findings that rely on manually seeded invalid state/data with no real producer path. For mechanical findings: the cited text does not exist, does not violate the stated guideline, is unchanged legacy outside the touched scope, or the finding duplicates another confirmed finding.
|
|
49
|
-
- **UNVERIFIABLE**: The citation is correct but you cannot confirm the claim without deeper tracing. This is the default for plausible behavioral claims that lack a proven trigger path, producer chain, or authoritative source of truth. A data-shape finding that lacks the governing-constraint citation is UNVERIFIABLE, not CONFIRMED. Mechanical findings should rarely be UNVERIFIABLE — if the text exists and violates the guideline, it is CONFIRMED.
|
|
54
|
+
- **CONFIRMED**: For behavioral findings: the cited code matches, the claim is accurate, the bug is reachable through realistic usage, you proved the workflow/producer chain where applicable, the behavior is not intentional, and for data-shape findings you read and cited the governing constraint confirming the claimed state is reachable. For mechanical findings: the cited text exists, is branch-introduced or newly made relevant, and violates the stated guideline or specialist taxonomy. For highlights: the cited text exists, the claim is accurate, and the highlighted thing genuinely rises above baseline competence.
|
|
55
|
+
- **REJECTED**: For behavioral findings: the citation is wrong, the claim is inaccurate, the bug cannot manifest in the actual runtime context, the behavior is an intentional design decision (explain why briefly), or a governing constraint (NOT NULL, FK, type, guard) forecloses the claimed state. Reject findings that rely on manually seeded invalid state/data with no real producer path. For mechanical findings: the cited text does not exist, does not violate the stated guideline, is unchanged legacy outside the touched scope, or the finding duplicates another confirmed finding. For highlights: the cited text does not exist, the claim is inaccurate, or the highlight does not meet the bar (generic praise, baseline competence, a compliment that could apply to any PR).
|
|
56
|
+
- **UNVERIFIABLE**: The citation is correct but you cannot confirm the claim without deeper tracing. This is the default for plausible behavioral claims that lack a proven trigger path, producer chain, or authoritative source of truth. A data-shape finding that lacks the governing-constraint citation is UNVERIFIABLE, not CONFIRMED. Mechanical findings should rarely be UNVERIFIABLE — if the text exists and violates the guideline, it is CONFIRMED. Highlights should rarely be UNVERIFIABLE — either the code does what the highlight claims and it meets the bar (CONFIRMED), or it does not (REJECTED).
|
|
50
57
|
|
|
51
58
|
## Deduplication
|
|
52
59
|
|
|
@@ -90,6 +97,8 @@ You MUST produce the full report structure below. Do not simplify or omit sectio
|
|
|
90
97
|
|
|
91
98
|
Confirmed LOW findings are mandatory. Do not summarize them away or omit them for being non-functional. Pedantic, no-slop, breadcrumbs, docs, naming, style, and comment findings are first-class review findings when confirmed. Group them under LOW, but include every one.
|
|
92
99
|
|
|
100
|
+
Confirmed highlights are optional in the sense that most PRs will have none. Do not pad the section with borderline calls. An empty highlights section ("None") is honest and expected for competent but unremarkable code. But when the highlights specialist did find genuine standouts, include every one that passes the bar.
|
|
101
|
+
|
|
93
102
|
The report is user-facing. Start by teaching the changed workflows before listing findings. Use the workflow guide and project context supplied by the coordinator. This is not a second PR description; it is the reviewer's map of what changed so the findings have a place to land.
|
|
94
103
|
|
|
95
104
|
For each affected workflow, use the same before/now shape as PR descriptions:
|
|
@@ -118,6 +127,18 @@ For each affected workflow:
|
|
|
118
127
|
2. **N/A**
|
|
119
128
|
- **NOW** — _one sentence describing a new stage this PR adds._
|
|
120
129
|
|
|
130
|
+
### Highlights
|
|
131
|
+
|
|
132
|
+
Things the author did that are genuinely worth calling out: notably clever solutions, cleanup done along the way, documentation that will save time, good instincts on subtle edge cases. Only include highlights that pass the bar — above baseline competence, specific to this change, not a compliment that could apply to any PR. If none, write "None."
|
|
133
|
+
|
|
134
|
+
For each highlight:
|
|
135
|
+
1. **Category** (CLEVER_SOLUTION, CLEANUP, DOC_IMPROVEMENT, GOOD_INSTINCT)
|
|
136
|
+
2. **Source**: highlights specialist
|
|
137
|
+
3. **Location**: file:line
|
|
138
|
+
4. **Highlight**: what the good thing is
|
|
139
|
+
5. **Evidence**: the code you read to confirm it (quote the exact lines you verified)
|
|
140
|
+
6. **Why it stands out**: what makes this above the bar — be specific
|
|
141
|
+
|
|
121
142
|
### Confirmed findings
|
|
122
143
|
|
|
123
144
|
For each finding, grouped by severity (BLOCKING > HIGH > MEDIUM > LOW). Each finding MUST include all of these fields:
|
|
@@ -155,3 +176,7 @@ If no prior-comments register was provided (local-branch review or first round o
|
|
|
155
176
|
### Coverage gaps
|
|
156
177
|
|
|
157
178
|
Note which files or areas were NOT covered by any specialist.
|
|
179
|
+
|
|
180
|
+
### Human-verifiable unknowns
|
|
181
|
+
|
|
182
|
+
Flag conclusions that rest on something static analysis cannot reach: a rollout state, a config value in a different environment, a deploy order, a feature flag setting, a migration that has not run. These are things a human reviewer can check and the review cannot. Each entry names the unknown and where to check it. If none, write "None."
|
package/docs/dev/README.md
CHANGED
|
@@ -54,7 +54,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
54
54
|
| `tool-defs.ts` | **Single source of truth** for all tools. Each tool has a name, description, zod schema (args), and execute function. Framework-agnostic — neither opencode nor MCP specific. |
|
|
55
55
|
| `tools.ts` | Thin opencode wrappers. Imports tool-defs, wraps each in opencode's `tool()` with a `thatch_` prefix. |
|
|
56
56
|
| `mcp.ts` | Stdio JSON-RPC 2.0 server. Compiles zod schemas to JSON Schema via `z.toJSONSchema()` for `tools/list`. Validates args via `z.object().parse()` in `tools/call`. All logging to stderr (stdout is the transport). |
|
|
57
|
-
| `index.ts` | OpenCode plugin entry. Wires DB, model, extraction; registers tools and hooks; installs skills. |
|
|
57
|
+
| `index.ts` | OpenCode plugin entry. Wires DB, model, extraction; registers tools and hooks; installs skills. Internal state beyond the extraction pipeline: `extracting` set (parent IDs with an active direct-extraction child), `childMetrics` map (new/updated/deleted counts per child session), and `triggerExtraction(parentID)` (creates a child session via the SDK client and prompts it with the extraction payload). |
|
|
58
58
|
| `setup.ts` | `thatch setup --claude` installer. Writes .mcp.json, appends to CLAUDE.md (idempotent), installs hooks in settings.json, installs skills. |
|
|
59
59
|
| `hygiene.ts` | Hygiene report: pending dedup pairs, stale count, orphaned branch memories. Shared by the plugin's session-start hook and the CLI's `thatch reminder` command. |
|
|
60
60
|
| `git.ts` | Parse `owner/repo` from git remote. Worktree-safe fallback chain. |
|
|
@@ -64,7 +64,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
64
64
|
| `extract-queue.ts` | File-backed per-session JSONL queue (caps 20, oldest dropped). Shared by the Claude Code and Cursor hook paths, which fire one-shot per event with no cross-call state. This is the MCP-side equivalent of `extraction.ts`. |
|
|
65
65
|
| `sideband.ts` | Unix domain socket server + client. The MCP server (long-lived, warm model) runs `SidebandServer` so one-shot hook processes can ask it to embed a prompt and search for matches without loading the model themselves. Handles two methods: `match` (recall nudge) and `predictions` (prediction auto-fire). Socket path is a hash of the DB path — both processes compute it independently. |
|
|
66
66
|
| `prompts.ts` | Text constants: opencode system prompt, compaction context, session-start reminder, prompt-aware recall nudge (`recallNudge` / `claudeRecallNudge`), Claude Code CLAUDE.md instructions, Cursor AGENTS.md instructions, Claude Code hook text. |
|
|
67
|
-
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (
|
|
67
|
+
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (19 skills: fact-extractor, dedup-classifier, project-primer, 7 review specialists, review synthesizer, review context, workflow research, change walkthrough, code walkthrough, session reflection, pr-description, ticket-description, split-overlarge-pr — work on both opencode and Claude Code) and `OPENCODE_ONLY_SKILLS` (1 skill: code-review coordinator — requires sub-agent support, not installed for Claude Code). `installSkills(dir, skills)` defaults to `SHARED_SKILLS`; the opencode plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`. |
|
|
68
68
|
|
|
69
69
|
## Plugin hooks
|
|
70
70
|
|
|
@@ -75,9 +75,9 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
75
75
|
| `experimental.chat.system.transform` | Appends the thatch system prompt (store names, usage rules). |
|
|
76
76
|
| `experimental.session.compacting` | Marks the session as compacting and appends re-familiarization context so a compacted session still knows thatch exists. |
|
|
77
77
|
| `experimental.compaction.autocontinue` | Clears the compacting flag so `chat.message` nudges resume. Without this, nudges that instruct tool calls would fire during summary generation where tools are blocked. The `chat.message` hook also clears the flag if it fires for a session still in the compacting set but the incoming message has no compaction-type part — this handles compaction failure, where the session would otherwise be stuck with nudges off forever. |
|
|
78
|
-
| `tool.execute.after` | Buffers every non-`thatch_*`, non-`skill`, non-`task` tool call into the session's extraction buffer. (Skill/task are excluded — buffering them creates a feedback loop where the nudge triggers a skill load, which gets buffered, which triggers another nudge.) Memory writes (`thatch_memory_remember`) and `thatch_extraction_done` drain the buffer and reset the missed-nudge counter. This is a plugin hook, NOT a bus event — do not move it into the `event` handler; the event bus has no such event and it will silently never fire. |
|
|
79
|
-
| `chat.message` | Two priority tiers: (a) if extraction buffer has interactions, **peeks** the buffer (does NOT drain it) and injects a synthetic text part carrying the extraction nudge + JSON payload — the buffer persists until the agent writes a memory or calls `thatch_extraction_done`, so ignored nudges repeat and escalate (polite → insistent → ALL-CAPS) via the `missedNudges` counter; (b) otherwise, embeds the user's prompt text with the in-process warm model, searches `db.search()` across repo + global, and pushes a recall nudge if matches exceed the threshold (default 0.55). The same embedding also feeds the prediction auto-fire: `db.scorePredictionNudge` scores the prompt against prediction matchers and injects a separate `[thatch] User decision model` nudge if any matchers clear the 0.45 threshold. Recall and prediction nudges fire independently (separate try/catch blocks, separate synthetic parts). Skipped entirely while the session is compacting (tool calls are blocked during summary generation). |
|
|
80
|
-
| `event`
|
|
78
|
+
| `tool.execute.after` | Buffers every non-`thatch_*`, non-`skill`, non-`task` tool call into the session's extraction buffer. (Skill/task are excluded — buffering them creates a feedback loop where the nudge triggers a skill load, which gets buffered, which triggers another nudge.) Memory writes (`thatch_memory_remember`) and `thatch_extraction_done` drain the buffer and reset the missed-nudge counter. For child sessions (`childToParent.has(sessionID)`), also tracks metrics: `remember` with `overwrite:false` → new++, `overwrite:true` → updated++, `forget` → deleted++. This is a plugin hook, NOT a bus event — do not move it into the `event` handler; the event bus has no such event and it will silently never fire. |
|
|
79
|
+
| `chat.message` | Two priority tiers: (a) if extraction buffer has interactions and the session is NOT in the `extracting` set (direct extraction in progress), **peeks** the buffer (does NOT drain it) and injects a synthetic text part carrying the extraction nudge + JSON payload — the buffer persists until the agent writes a memory or calls `thatch_extraction_done`, so ignored nudges repeat and escalate (polite → insistent → ALL-CAPS) via the `missedNudges` counter; (b) otherwise, embeds the user's prompt text with the in-process warm model, searches `db.search()` across repo + global, and pushes a recall nudge if matches exceed the threshold (default 0.55). The same embedding also feeds the prediction auto-fire: `db.scorePredictionNudge` scores the prompt against prediction matchers and injects a separate `[thatch] User decision model` nudge if any matchers clear the 0.45 threshold. Recall and prediction nudges fire independently (separate try/catch blocks, separate synthetic parts). Skipped entirely while the session is compacting (tool calls are blocked during summary generation). |
|
|
80
|
+
| `event` | Subscribes to all session bus events. `session.created`: records `childToParent` + `parentSnapshots` (shallow copy of the parent's buffer for snapshot-aware drain), then sends the session-start reminder via `client.session.prompt` carrying the hygiene heartbeat (pending dedup pairs, stale count, orphaned branch memories) when any signal is non-zero. `session.status` (idle): if the session is a child, drains its snapshot, fires a toast with `childMetrics`, and deletes the child session; if the session is a parent with pending tool interactions and not already extracting, calls `triggerExtraction` to create a direct-extraction child. `session.error`: requeues the parent's buffer (child died without draining). `session.deleted`: cleans up `childToParent`, `parentSnapshots`, `childMetrics`, and `extracting`. `session.compacted`: clears the compacting flag so `chat.message` nudges resume. |
|
|
81
81
|
| `dispose` | Closes the DB. |
|
|
82
82
|
|
|
83
83
|
Hook failures are logged with a `[thatch]` prefix — never swallowed silently.
|
|
@@ -168,16 +168,45 @@ dedup cycle (agent-driven)
|
|
|
168
168
|
→ thatch_dedup_mark_checked records verdicts for surviving pairs
|
|
169
169
|
(overwriting or forgetting an entry clears its verdicts → can re-flag)
|
|
170
170
|
|
|
171
|
-
extraction cycle
|
|
171
|
+
extraction cycle
|
|
172
172
|
→ tool.execute.after buffers non-thatch, non-skill, non-task tool calls
|
|
173
|
-
per session (max 20)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
per session (max 20); for child sessions, also tracks new/updated/deleted
|
|
174
|
+
metrics
|
|
175
|
+
→ direct extraction (primary path, opencode-only):
|
|
176
|
+
parent goes idle (session.status idle) with pending tool interactions
|
|
177
|
+
→ triggerExtraction adds parentID to the `extracting` set (suppresses
|
|
178
|
+
nudge in chat.message), creates a child session via
|
|
179
|
+
client.session.create, and prompts it with the extraction payload
|
|
180
|
+
→ child runs the fact-extractor skill, writes memories via
|
|
181
|
+
thatch_memory_remember → consumeSnapshot drains the parent's buffer
|
|
182
|
+
(snapshot-aware: removes only entries captured at dispatch time by
|
|
183
|
+
reference identity, preserving interleaved-turn entries)
|
|
184
|
+
→ child goes idle → event handler drains remaining snapshot entries,
|
|
185
|
+
fires a toast with childMetrics, deletes the child session
|
|
186
|
+
→ nudge path (fallback): if triggerExtraction throws (create or prompt
|
|
187
|
+
fails), the `extracting` set clears and the next chat.message sees
|
|
188
|
+
pending entries with no extracting flag → peeks the buffer and injects
|
|
189
|
+
a nudge part with JSON payload; missed nudges escalate (polite →
|
|
190
|
+
insistent → ALL-CAPS) via the missedNudges counter
|
|
191
|
+
→ drain: thatch_memory_remember (or thatch_extraction_done) clears the
|
|
192
|
+
buffer and resets the missed-nudge counter
|
|
193
|
+
→ agent loads thatch-fact-extractor skill, saves facts via
|
|
194
|
+
thatch_memory_remember
|
|
195
|
+
→ MCP path (Claude Code/Cursor): unchanged — no SDK client, no child
|
|
196
|
+
sessions; extract-queue.ts + flush-tools drives the nudge via hooks
|
|
197
|
+
|
|
198
|
+
toast notifications (opencode-only, TUI-rendered)
|
|
199
|
+
→ client.tui.showToast — best-effort, silently ignored if TUI not
|
|
200
|
+
connected (headless mode); model-invisible (goes to the user only,
|
|
201
|
+
not the conversation history — the inverse of synthetic nudge parts,
|
|
202
|
+
which are model-visible but TUI-hidden)
|
|
203
|
+
→ extraction metrics: `[thatch] new: N, updated: M, deleted: K` (success
|
|
204
|
+
variant, 4s) — fires when an extraction child goes idle; no-save runs
|
|
205
|
+
show `[thatch] extraction complete — nothing to save` (info variant)
|
|
206
|
+
→ recall matches: `[thatch] recalled N memories` (info variant, 3s) —
|
|
207
|
+
fires when chat.message matches stored memories
|
|
208
|
+
→ prediction matches: `[thatch] N predictions surfaced` (info variant,
|
|
209
|
+
3s) — fires when chat.message matches decision-model patterns
|
|
181
210
|
|
|
182
211
|
prompt-aware recall nudge (both paths)
|
|
183
212
|
→ opencode: chat.message hook embeds prompt text with warm in-process model,
|
package/docs/dev/gotchas.md
CHANGED
|
@@ -65,15 +65,37 @@ here first. These are the things that have already cost time.
|
|
|
65
65
|
agent writes a memory or calls `thatch_extraction_done`. Ignored nudges
|
|
66
66
|
accumulate; the `missedNudges` counter escalates the tone (polite at 0-1
|
|
67
67
|
misses, insistent at 2, ALL-CAPS at 3+). The counter resets when the
|
|
68
|
-
buffer drains.
|
|
68
|
+
buffer drains. The primary opencode path doesn't use the nudge at all:
|
|
69
|
+
`triggerExtraction` creates a child session directly, and the `extracting`
|
|
70
|
+
set suppresses the nudge in `chat.message` while a direct-extraction child
|
|
71
|
+
is active. The peek-not-drain semantics still hold for the fallback nudge
|
|
72
|
+
path (MCP hosts and any `triggerExtraction` failure).
|
|
69
73
|
- **A child sub-agent's `thatch_memory_remember` drains the parent's buffer**
|
|
70
74
|
via the `childToParent` Map (`index.ts:65` declaration, `index.ts:131`
|
|
71
|
-
lookup).
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
lookup). Two paths reach this machinery: (a) the **plugin-initiated child
|
|
76
|
+
session** — `triggerExtraction` calls `client.session.create` with a
|
|
77
|
+
`parentID`, the primary opencode path; (b) the **agent-initiated background
|
|
78
|
+
task** — the model dispatches the fact-extractor via the `task` tool after
|
|
79
|
+
receiving the nudge, the fallback path still used by MCP hosts. Both use
|
|
80
|
+
the same `childToParent`/`parentSnapshots`/`consumeSnapshot` plumbing.
|
|
81
|
+
Without this, either path would write memories in the child but never
|
|
82
|
+
clear the parent's queue — the nudge would replay every turn.
|
|
83
|
+
`thatch_extraction_done` is the belt-and-suspenders explicit
|
|
84
|
+
acknowledgment: it drains the buffer without requiring a memory write
|
|
85
|
+
(covers cases where the sub-agent errors out or the host doesn't expose
|
|
86
|
+
parent-child session relationships).
|
|
87
|
+
- **The no-save drain runs in the child-idle handler regardless of writes.**
|
|
88
|
+
When the extraction child goes idle after a no-save run (nothing worth
|
|
89
|
+
extracting), the parent's snapshot entries must still be drained from the
|
|
90
|
+
buffer. Without this, entries linger in pending and the nudge fires as a
|
|
91
|
+
synthetic (TUI-hidden) part on the next `chat.message`. The child-idle
|
|
92
|
+
handler drains the snapshot only if it still exists (the child wrote no
|
|
93
|
+
memories). If the child did write memories, `tool.execute.after` already
|
|
94
|
+
consumed the snapshot — the idle handler skips the drain so interleaved-turn
|
|
95
|
+
entries survive.
|
|
96
|
+
- **`client.tui.showToast` is best-effort.** The toast call is wrapped in a
|
|
97
|
+
catch-and-ignore — if the TUI is not connected (headless mode), it silently
|
|
98
|
+
does nothing. The toast is TUI-rendered (in-app), not an OS notification.
|
|
77
99
|
|
|
78
100
|
## Hooks (Claude Code / Cursor)
|
|
79
101
|
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -22,7 +22,8 @@ This document maps the feature parity and documents the remaining gaps.
|
|
|
22
22
|
| **System prompt** (store names, usage rules) | `experimental.chat.system.transform` — dynamic, repo baked in at runtime | CLAUDE.md static text appended by `thatch setup`; repo auto-detected by the MCP server at startup | AGENTS.md static text appended by `thatch setup`; repo auto-detected at startup | **Approximate** — static text persists through compaction, so the agent retains the usage instructions, but the dynamic per-turn refresh is lost |
|
|
23
23
|
| **Session-start reminder** (recall nudge + hygiene heartbeat) | `session.created` event → `client.session.prompt` injects a synthetic message | `SessionStart` hook → `thatch reminder`; stdout becomes context | `sessionStart` hook → `thatch reminder --json`; output is `additional_context` JSON | **Full** |
|
|
24
24
|
| **Compaction context** (re-familiarize after compaction) | `experimental.session.compacting` hook appends context to the compaction output | `PostCompact` hook — side-effects only, **cannot inject context** | No equivalent hook | **Gapped** — see below |
|
|
25
|
-
| **Extraction
|
|
25
|
+
| **Extraction** (buffer tool calls, extract durable facts) | `tool.execute.after` buffers non-thatch, non-skill, non-task tool calls in-process; **direct extraction** is the primary path — when the parent session goes idle with pending buffer entries, the plugin creates a child session via the SDK (`client.session.create` with `parentID`) and prompts it with the extraction payload (nudge suppressed via the `extracting` set); the `chat.message` nudge path is a **fallback** used when direct extraction throws | `PostToolBatch` → `thatch buffer-batch` (file-backed JSONL queue); `UserPromptSubmit` → `thatch flush-tools` peeks the queue and prints the nudge (does not drain — the queue persists until a memory write or `extraction_done`) | `postToolUse` → `thatch buffer-tool` (single-tool, file-backed queue); `beforeSubmitPrompt` → `thatch flush-tools --json` | **Partial** — opencode uses direct extraction (parent idle → child session) as the primary path, with the nudge as fallback; MCP hosts are nudge-only. Both paths produce the same JSON shape via `buildExtractionPayload()`. Nudge timing differs: the nudge arrives at the **start** of the next turn in Claude Code/Cursor; opencode's direct extraction fires at **end of turn** (session idle), and the nudge fallback fires at the start of the next turn like MCP |
|
|
26
|
+
| **Extraction feedback** (toast notifications) | `client.tui.showToast` fires when the extraction child goes idle — shows `[thatch] new: N, updated: M, deleted: K` (success) or `extraction complete — nothing to save` (info); best-effort, silently ignored in headless mode | No TUI connection — MCP hosts have no equivalent | No TUI connection | **Gapped** — MCP hosts cannot show toast notifications |
|
|
26
27
|
| **Prompt-aware recall nudge** | `chat.message` hook embeds the prompt with the in-process warm model, searches `db.search()`, pushes a nudge part if matches ≥ threshold | `UserPromptSubmit` hook → `thatch flush-tools` connects to the MCP server's sideband socket; the warm server embeds + searches; hook prints the nudge or falls back to the write nudge | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — sideband socket gives cold hook processes access to the warm MCP server's model |
|
|
27
28
|
| **Prediction auto-fire** (user decision model) | `chat.message` hook scores the prompt embedding against prediction matchers; injects a `[thatch] User decision model` nudge alongside the recall nudge (separate synthetic part, same embedding call) | `UserPromptSubmit` hook → `thatch flush-tools` fires prediction query via the sideband socket's `predictions` method in parallel with the recall nudge | `beforeSubmitPrompt` hook → `thatch flush-tools --json` (same sideband path) | **Full** — same `scorePredictionNudge` entry point in both paths |
|
|
28
29
|
| **Skills** | Installed to `$XDG_CONFIG_HOME/opencode/skills` at plugin init (shared **+** opencode-only) | Installed to `$CLAUDE_CONFIG_DIR/skills/` by `thatch setup` (shared only) | Installed to `~/.cursor/skills/` by `thatch setup` (shared only) | **Full** — same SKILL.md format; the code-review coordinator is opencode-only (needs sub-agents) |
|
|
@@ -61,18 +62,37 @@ through compaction, so the agent retains the usage instructions. The
|
|
|
61
62
|
compaction in Claude Code, so the recall reminder runs again. There is no
|
|
62
63
|
explicit "re-familiarization" nudge for hosts without a compaction hook.
|
|
63
64
|
|
|
64
|
-
### Extraction
|
|
65
|
+
### Extraction timing
|
|
65
66
|
|
|
66
|
-
OpenCode's extraction pipeline
|
|
67
|
+
OpenCode's extraction pipeline has two paths — direct extraction (primary)
|
|
68
|
+
and the nudge (fallback):
|
|
69
|
+
|
|
70
|
+
**Direct extraction (primary path):**
|
|
67
71
|
|
|
68
72
|
1. `tool.execute.after` buffers every non-thatch, non-skill, non-task tool
|
|
69
73
|
call into a per-session in-memory array (max 20 interactions).
|
|
70
|
-
2.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`
|
|
74
|
-
|
|
75
|
-
3. The
|
|
74
|
+
2. When the parent session goes idle (`session.status` idle event) with pending
|
|
75
|
+
buffer entries, the plugin calls `triggerExtraction`: creates a child session
|
|
76
|
+
via `client.session.create` (with `parentID`), then prompts it via
|
|
77
|
+
`client.session.promptAsync` (or `prompt`) with the extraction payload. The
|
|
78
|
+
`extracting` set suppresses the nudge in `chat.message` while the child runs.
|
|
79
|
+
3. The child session runs the `thatch-fact-extractor` skill, writes memories via
|
|
80
|
+
`thatch_memory_remember`. A memory write in the child drains the parent's
|
|
81
|
+
buffer snapshot (via the `childToParent` Map and `consumeSnapshot`) and
|
|
82
|
+
resets the missed-nudge counter.
|
|
83
|
+
4. When the child goes idle, the handler drains any remaining snapshot entries
|
|
84
|
+
(covers no-save runs), deletes the child session, and fires a toast via
|
|
85
|
+
`client.tui.showToast`.
|
|
86
|
+
|
|
87
|
+
**Nudge (fallback path):**
|
|
88
|
+
|
|
89
|
+
1. If `triggerExtraction` throws (session creation or prompt fails), the catch
|
|
90
|
+
block clears the `extracting` flag. On the next `chat.message`, the buffer is
|
|
91
|
+
**peeked** (not drained) and a synthetic text part carrying the JSON payload
|
|
92
|
+
is injected into the conversation. The buffer persists until the agent writes
|
|
93
|
+
a memory or calls `thatch_extraction_done`, so ignored nudges repeat and
|
|
94
|
+
escalate (polite → insistent → ALL-CAPS) via the `missedNudges` counter.
|
|
95
|
+
2. The agent sees the nudge, loads the `thatch-fact-extractor` skill, and saves
|
|
76
96
|
durable facts via `thatch_memory_remember`. A memory write in the session
|
|
77
97
|
(or a child sub-agent via the `childToParent` Map) or `thatch_extraction_done`
|
|
78
98
|
drains the buffer and resets the missed-nudge counter.
|
|
@@ -19,14 +19,25 @@ The plugin entry (`src/index.ts`) registers these hooks in code:
|
|
|
19
19
|
| `experimental.chat.system.transform` | `{}` | `{ system: string[] }` | Pushes the system prompt (store names, usage rules). |
|
|
20
20
|
| `experimental.session.compacting` | `{ sessionID }` | `{ context: string[] }` | Marks the session as compacting, pushes re-familiarization context. |
|
|
21
21
|
| `experimental.compaction.autocontinue` | `{ sessionID }` | `{ enabled: boolean }` | Clears the compacting flag so `chat.message` nudges resume post-compaction. |
|
|
22
|
-
| `tool.execute.after` | `{ tool, sessionID, callID, args }` | `{ title, output, metadata }` | Buffers non-`thatch_`, non-`skill`, non-`task` tool calls into the in-memory extraction ring buffer (max 20). |
|
|
23
|
-
| `chat.message` | `{ sessionID, messageID }` | `{ message, parts }` | Two tiers: extraction nudge if pending, else prompt-aware recall nudge.
|
|
24
|
-
| `event`
|
|
22
|
+
| `tool.execute.after` | `{ tool, sessionID, callID, args }` | `{ title, output, metadata }` | Buffers non-`thatch_`, non-`skill`, non-`task` tool calls into the in-memory extraction ring buffer (max 20). In child sessions, also tracks new/updated/deleted counts via `childMetrics` for the extraction toast. |
|
|
23
|
+
| `chat.message` | `{ sessionID, messageID }` | `{ message, parts }` | Two tiers: extraction nudge if pending and not already extracting (fallback — direct extraction via the `event` hook is the primary path), else prompt-aware recall nudge. Suppressed while compacting or while `extracting` is set. |
|
|
24
|
+
| `event` | `{ event: { type, properties } }` | — | Dispatches on `event.type`: `session.created` (top-level) → `client.session.prompt` with reminder + hygiene heartbeat; `session.created` (child with `parentID`) → records `childToParent` + snapshots parent's pending buffer. `session.status` idle (parent, pending interactions) → `triggerExtraction` (direct child-session extraction); idle (child) → complete/drain, `showToast` with metrics, delete child. `session.error` (child) → requeue parent's accepted entries. `session.deleted` → requeue if child, drop accepted if parent, clean maps. `session.compacted` → clear compacting flag. |
|
|
25
25
|
| `dispose` | — | — | Closes the DB. |
|
|
26
26
|
|
|
27
27
|
Skills install to `$XDG_CONFIG_HOME/opencode/skills` at plugin init — shared
|
|
28
28
|
**and** opencode-only (the coordinator needs sub-agents).
|
|
29
29
|
|
|
30
|
+
Direct extraction is the primary path for opencode: when a parent session goes
|
|
31
|
+
idle with pending tool interactions, the `event` hook creates a child session
|
|
32
|
+
and prompts it directly via `triggerExtraction`. The `chat.message` extraction
|
|
33
|
+
nudge is a fallback — it fires only if direct extraction was never triggered or
|
|
34
|
+
threw an error (the `extracting` set is cleared on failure).
|
|
35
|
+
|
|
36
|
+
`client.tui.showToast` is called from the `event` hook (on child idle, with
|
|
37
|
+
extraction metrics: new/updated/deleted counts) and from `chat.message` (on
|
|
38
|
+
recall and prediction matches). Best-effort — silently ignored if the TUI is
|
|
39
|
+
not connected (headless mode).
|
|
40
|
+
|
|
30
41
|
## Claude Code (MCP server + hooks)
|
|
31
42
|
|
|
32
43
|
`thatch setup --claude` (project-local) or `thatch setup --claude --global`.
|