@jeffober/thatch 0.1.27 → 0.1.29

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.
Files changed (62) hide show
  1. package/README.md +24 -10
  2. package/artifacts/skills/thatch-code-review.md +9 -7
  3. package/artifacts/skills/thatch-fact-extractor.md +2 -2
  4. package/artifacts/skills/thatch-review-economy.md +97 -0
  5. package/artifacts/skills/thatch-review-followup.md +1 -1
  6. package/artifacts/skills/thatch-review-synthesizer.md +15 -5
  7. package/bin/qa-run +38 -0
  8. package/bin/thatch +13 -7
  9. package/docs/dev/README.md +76 -22
  10. package/docs/dev/features/README.md +33 -0
  11. package/docs/dev/features/behavior-engine.md +271 -0
  12. package/docs/dev/features/cicd.md +89 -0
  13. package/docs/dev/features/cli.md +81 -0
  14. package/docs/dev/features/compaction-recovery.md +48 -0
  15. package/docs/dev/features/database.md +244 -0
  16. package/docs/dev/features/deduplication.md +102 -0
  17. package/docs/dev/features/extraction.md +253 -0
  18. package/docs/dev/features/hygiene.md +81 -0
  19. package/docs/dev/features/memory-store.md +154 -0
  20. package/docs/dev/features/multi-host.md +228 -0
  21. package/docs/dev/features/nudge-pipeline.md +282 -0
  22. package/docs/dev/features/prediction-engine.md +253 -0
  23. package/docs/dev/features/qa-system.md +205 -0
  24. package/docs/dev/features/session-lifecycle.md +110 -0
  25. package/docs/dev/features/setup.md +199 -0
  26. package/docs/dev/features/sideband.md +104 -0
  27. package/docs/dev/gotchas.md +3 -3
  28. package/docs/dev/mcp-parity.md +7 -5
  29. package/docs/dev/setup-and-hooks.md +4 -4
  30. package/docs/dev/skills.md +10 -8
  31. package/docs/plans/README.md +2 -2
  32. package/docs/user/README.md +58 -20
  33. package/docs/user/behavior-engine.md +85 -0
  34. package/docs/user/cli.md +98 -0
  35. package/docs/user/code-review.md +73 -0
  36. package/docs/user/deduplication.md +70 -0
  37. package/docs/user/default-behaviors.md +75 -0
  38. package/docs/user/extraction.md +74 -0
  39. package/docs/user/hygiene.md +76 -0
  40. package/docs/user/memory.md +81 -0
  41. package/docs/user/prediction-engine.md +87 -0
  42. package/docs/user/setup.md +110 -0
  43. package/docs/user/skills.md +97 -0
  44. package/package.json +2 -2
  45. package/src/behavior.ts +120 -0
  46. package/src/db.ts +160 -417
  47. package/src/embeddings.ts +0 -41
  48. package/src/extraction.ts +2 -2
  49. package/src/git.ts +3 -1
  50. package/src/index.ts +66 -14
  51. package/src/mcp.ts +59 -2
  52. package/src/prediction.ts +127 -0
  53. package/src/prompts.ts +155 -200
  54. package/src/scoring-engine.ts +374 -0
  55. package/src/seed-behaviors.ts +339 -0
  56. package/src/setup.ts +2 -6
  57. package/src/sideband.ts +36 -8
  58. package/src/skills.ts +1 -0
  59. package/src/tool-defs.ts +235 -9
  60. package/src/tools.ts +11 -1
  61. package/src/vector-math.ts +30 -0
  62. package/docs/qa/README.md +0 -175
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/sysread/thatch/actions/workflows/ci.yml/badge.svg)](https://github.com/sysread/thatch/actions/workflows/ci.yml)
4
4
 
5
- Persistent memory and useful dev skills for AI coding agents. Works with
5
+ Persistent memory and operational methodology for AI coding agents. Works with
6
6
  **OpenCode** (as a plugin), **Claude Code** (as a local MCP server), and
7
7
  **Cursor** (as a local MCP server).
8
8
 
@@ -31,7 +31,8 @@ Without this env var, extraction still works - the child session runs
31
31
  synchronously (fire-and-forget) instead of asynchronously.
32
32
 
33
33
  Then **prime your project memory** by running `thatch prime` in your project directory.
34
- This will launch an `opencode` session to build an initial map of the code base and its architecture to seed the memory.
34
+ This launches an `opencode` session to build an initial map of the code base
35
+ and seed the memory.
35
36
 
36
37
  ### Claude Code and Cursor
37
38
 
@@ -65,14 +66,27 @@ Include thatch's instructions in your agent's system prompt manually; see
65
66
 
66
67
  ## What's inside
67
68
 
68
- - **Memory tools**: save, search, list, show, and forget memories with
69
- semantic search (local embeddings, SQLite, brute-force cosine).
70
- - **Prediction engine**: a statistical model of user decision-making
69
+ Thatch gives your agent three layers of persistent context:
70
+
71
+ - **Memory** -- save, search, and recall knowledge across sessions with
72
+ local embeddings (bge-small-en-v1.5) and SQLite. Every project gets its
73
+ own store; a shared global store holds cross-project knowledge. The agent
74
+ writes and reads memories through tools -- thatch never saves anything on
75
+ its own.
76
+ - **Prediction engine** -- a statistical model of the user's decision-making
71
77
  preferences. When a prompt matches learned contexts, predictions fire
72
- alongside the recall nudge.
73
- - **Skills**: 23 skills for memory workflows, structured multi-specialist
74
- code review, review response, change walkthroughs, and writing tasks (PR
75
- descriptions, tickets, PR splitting).
78
+ alongside the recall nudge. Confidence is graded (Bayesian posterior) and
79
+ reinforced or weakened by user feedback. The agent follows strong
80
+ predictions silently and surfaces ambiguous ones to the user.
81
+ - **Behavior engine** -- a self-discipline model where the agent codifies
82
+ its own operational rules ("when X, I do Y"). Rules auto-fire when similar
83
+ situations arise. The agent ham/spams each surfaced rule to train the
84
+ classifier. Confidence adjusts the same way as predictions. An anti-laziness
85
+ guard in the prompt prevents the agent from codifying shortcuts.
86
+
87
+ Plus **24 skills** for memory workflows, structured multi-specialist code
88
+ review, review response, change and feature walkthroughs, and writing tasks
89
+ (PR descriptions, tickets, PR splitting).
76
90
 
77
91
  See the [user guide](docs/user/README.md) for the full tool list, CLI
78
92
  commands, configuration, environment variables, and detailed setup for each
@@ -98,7 +112,7 @@ embeddings, no network.
98
112
  - [User guide](docs/user/README.md) -- setup, tools, configuration, CLI
99
113
  - [Development](docs/dev/README.md) -- architecture, module responsibilities
100
114
  - [MCP parity](docs/dev/mcp-parity.md) -- OpenCode plugin vs MCP feature comparison
101
- - [QA & tests](docs/qa/README.md) -- test conventions, use cases
115
+ - [QA tests](tests/qa/) -- executable use cases (auto/ and live/ subdirs)
102
116
  - [Design docs](docs/plans/) -- design-decision snapshots and in-progress plans
103
117
 
104
118
  ## License
@@ -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 seven specialist lenses.
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 eight 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,7 +83,7 @@ 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 seven specialists are:
86
+ For each review unit, dispatch sub-agents using the Task tool. Each sub-agent runs one specialist lens on one review unit. The eight specialists are:
87
87
 
88
88
  1. **Pedantic** — mechanical correctness: spelling, naming, doc accuracy, specs, guidelines, stale artifacts, structural conventions. Dispatch a sub-agent with instructions to: read every changed file in the unit, check comments/docs/naming/specs/style, check new file and directory placements against the repo's existing layout, report findings.
89
89
 
@@ -91,13 +91,15 @@ For each review unit, dispatch sub-agents using the Task tool. Each sub-agent ru
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
 
94
- 4. **NoSlop** — AI writing anti-pattern detection: change narration, fourth wall breaks, em dashes, hedging, filler. Dispatch a sub-agent with instructions to: read every changed file, scan all comments/docs/strings for slop.
94
+ 4. **Economy** — design simplicity and maintainability: is the complexity earned? Evaluates both the overall change design (the forest) and individual touch points (the trees) for unnecessary complexity, redundancy, and simpler available alternatives. Dispatch a sub-agent with instructions to: read the full diff and understand the goal, evaluate the overall design for unnecessary machinery, check each touch point for concrete simpler alternatives, verify any flagged complexity is not constraint-driven before reporting.
95
95
 
96
- 5. **Breadcrumbs** — comment narrative evaluation: do comments form a coherent outline? Dispatch a sub-agent with instructions to: read every changed file in full, evaluate the comment narrative, flag gaps.
96
+ 5. **NoSlop** — AI writing anti-pattern detection: change narration, fourth wall breaks, em dashes, hedging, filler. Dispatch a sub-agent with instructions to: read every changed file, scan all comments/docs/strings for slop.
97
97
 
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.
98
+ 6. **Breadcrumbs** — comment narrative evaluation: do comments form a coherent outline? Dispatch a sub-agent with instructions to: read every changed file in full, evaluate the comment narrative, flag gaps.
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.
100
+ 7. **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.
101
+
102
+ 8. **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
103
 
102
104
  For the integration review unit (5+ points), dispatch a sub-agent focused on:
103
105
  - Cross-component contracts — do the interfaces between components match?
@@ -144,7 +146,7 @@ Alternatively, perform the synthesis yourself:
144
146
  When dispatching each sub-agent, include in the prompt:
145
147
  - The git range to review
146
148
  - The specific files in this unit's scope
147
- - The specialist focus (from the seven specialists above)
149
+ - The specialist focus (from the eight specialists above)
148
150
  - The diff stat for this unit's files
149
151
  - **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.
150
152
  - **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.
@@ -5,7 +5,7 @@ description: Extract durable project facts, user preferences, and environment kn
5
5
 
6
6
  You are the Thatch Fact Extractor. Your job: review recent tool interactions and extract durable project knowledge to persist across sessions.
7
7
 
8
- You will be given a JSON payload with:
8
+ The extraction nudge or dispatch prompt will give you a session ID. Call `get_extraction_payload` (or `thatch_get_extraction_payload` / `mcp__thatch__get_extraction_payload` depending on host) with that session ID to retrieve a JSON payload containing:
9
9
  - "interactions": recent tool calls and their results (tool name, args, truncated output)
10
10
  - "projectStore": the current project's store name (e.g., "sysread/thatch")
11
11
  - "globalStore": "global"
@@ -17,7 +17,7 @@ You will be given a JSON payload with:
17
17
  3. Do not save session-specific state, ephemeral debugging details, or info already in CLAUDE.md.
18
18
  4. Apply the durability test before saving: will this still be true and useful in a future session that knows nothing about this branch, commit, or session? If not, skip it. Do not store point-in-time values (migration indices, line numbers, file counts), commit hashes or branch history narratives (git is the source of truth), or anything re-derivable from the codebase faster than recalling it.
19
19
  5. Write for a future session with zero current context. No "we", "our session", "just now".
20
- 6. Call thatch_extraction_done to mark the run complete. Do this even when nothing was worth saving: the parent session holds the buffered interactions until a completion signal arrives, and a memory write is not one on a no-save run.
20
+ 6. Call `extraction_done` (with `session_id` set to the parent session's ID from the nudge) to mark the run complete. Do this even when nothing was worth saving: the parent session holds the buffered interactions until a completion signal arrives, and a memory write is not one on a no-save run.
21
21
  7. Your final message must be exactly: "Extraction complete." — do not list or summarize what you saved. The memories are in the store; the parent session does not need a report.
22
22
 
23
23
  ## What to extract
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: thatch-review-economy
3
+ description: Design simplicity and maintainability review — is the complexity earned? Evaluates both the overall change design (the forest) and individual touch points (the trees) for unnecessary complexity, redundancy, and simpler available alternatives. Use for post-implementation review of a branch, PR, or commit range.
4
+ ---
5
+
6
+ You are a design economy review agent. You evaluate whether the complexity in changed code is earned. Every abstraction layer, indirection, generic interface, and moving part should justify itself against the actual use case. You look at both the forest (the overall design of the change) and the trees (individual touch points where code was modified).
7
+ ${REVIEW_COMMON}
8
+ ## Your focus
9
+
10
+ You care about:
11
+ - **Earned complexity**: Each piece of machinery should pay for itself in clarity, correctness, or capability. Complexity that does not serve the goal is a maintenance burden. When evaluating whether complexity is earned, weigh three concrete costs: how hard is it to debug a failure through this code, how much mental work does it take to understand what the change does and why, and how much does it slow down the next person who touches this area.
12
+ - **Simpler alternatives**: At specific touch points, is there a concrete simpler approach that achieves the same behavior? Not "I would have written it differently" — a named alternative with less code, fewer moving parts, or less indirection. The simpler alternative must be at least as debuggable and at least as easy to reason about.
13
+ - **Separation of concerns**: Does the change mix responsibilities that should be separate, or split things that should be together? An unnecessary module boundary is indirection that makes debugging harder (the failure crosses a seam that did not need to exist). A blurred boundary is coupling that makes the code harder to reason about (one module knows too much about another's internals). State-flow checks whether boundaries are correct for data flow; you check whether the boundary structure itself is more complex than the problem requires.
14
+ - **Redundancy and the duplication-dependency tradeoff**: Does the change reimplement functionality already available in the codebase, framework, or standard library? That is a clear finding. The harder question is the inverse: does the change introduce a shared abstraction to avoid duplication, and is that abstraction worse than the duplication it replaced? This is not a binary call. It depends on what kind of reuse is happening:
15
+ - **Private reuse within a package** (two functions in the same file or package sharing a helper) is generally good. No new dependencies, same ownership, the helper is easy to find and change. Do not flag this.
16
+ - **Shared abstraction that warps behavior** (a function gains a parameter that changes what it does depending on who calls it) is generally bad. The "shared" thing is now two functions wearing a trench coat. Flag it: two separate functions would be clearer than one function with a behavioral switch.
17
+ - **Reuse that creates new cross-package or cross-module dependencies** is a judgment call with real costs in both directions. The abstraction removes duplication but adds a dependency that couples the consumers. This is not a hard finding. Surface it as a tradeoff for the user to decide: present what the abstraction buys (less duplicated code) and what it costs (the coupling, the difficulty of changing one consumer without considering the other, the debugging path crossing a module boundary).
18
+ - **Speculative generality**: Code built for hypothetical future needs that are not in the current requirements, context brief, or ticket scope.
19
+
20
+ You do NOT care about:
21
+ - Style preferences or formatting
22
+ - Correctness of data flow (state-flow handles that)
23
+ - User experience or behavioral delta (acceptance handles that)
24
+ - Spelling, naming, or doc accuracy (pedantic handles that)
25
+ - Whether the code works (other reviewers handle logic)
26
+ - Test quality or coverage
27
+
28
+ ## The bar
29
+
30
+ Economy findings are design observations, not user-triggered bugs. The reachability gate and producer chain from REVIEW_COMMON do not apply in their standard form. Use "N/A — design observation" for the trigger and producer chain fields.
31
+
32
+ Before flagging, you must:
33
+ 1. **Identify the simpler alternative concretely.** "This could be simpler" is not a finding. Name the approach: "a single map lookup replaces this three-level nested conditional" or "the stdlib `strconv.Atoi` replaces this hand-rolled parser."
34
+ 2. **Verify the simpler alternative achieves the SAME behavior.** Not similar behavior — the same behavior, including error cases and edge cases. If the simpler approach changes behavior, it is not a valid finding.
35
+ 3. **Verify the simpler alternative is at least as debuggable and at least as easy to reason about.** A refactor that removes lines but makes a failure harder to trace is not an improvement. Name where a bug would manifest in both approaches; if the simpler one makes the failure path less clear, it is not a valid finding.
36
+ 4. **Check why the complexity exists.** Read callers, consult the workflow guide and context brief, check git history. If a constraint (backward compatibility, performance requirement, error handling contract, security boundary, separation-of-concerns decision, coupling limitation) drives the complexity, it is earned. Do not flag it.
37
+
38
+ ## What NOT to flag
39
+
40
+ - Code that follows the project's existing conventions (even if those conventions are verbose)
41
+ - Complexity driven by an identifiable constraint (cite the constraint if you find one)
42
+ - Code that is simply correct and clean (that is the baseline)
43
+ - Pre-existing complexity outside the change's scope (only flag complexity the change introduces or makes worse)
44
+ - Differences in personal style ("I would have used a struct instead of a map")
45
+ - Complexity in test code that makes tests clearer (test readability beats test brevity; duplication in tests is preferable to shared test helpers that hide setup and add framework surface)
46
+ - Duplication that is preferable to a shared abstraction, when the choice is clear: the shared abstraction would create coupling between unrelated callers, make independent changes harder, or add indirection that makes debugging less clear. When the choice is NOT clear (cross-module dependency creation with real tradeoffs in both directions), do not suppress it either — surface it as a TRADEOFF finding instead
47
+
48
+ ## Method
49
+
50
+ ### 1. Read the change at the forest level
51
+ Read the full diff and the commit messages. Understand the goal of the change from the context brief and PR description. Then ask:
52
+ - What is the minimum set of concepts, types, and moving parts needed to achieve this goal?
53
+ - Does the change introduce more than that?
54
+ - Are there abstraction layers, interfaces, or indirections that do not serve the actual use case?
55
+ - Does the change mix responsibilities that should be separate, or split things that should be together? Each unnecessary boundary is a seam a future debugger must cross. Each blurred boundary is coupling a future reader must untangle.
56
+ - If someone needs to debug a failure in this change, how many layers will they have to trace through? Could the same behavior be structured so a failure surfaces closer to its cause?
57
+ - How much mental work does it take to understand what the change does and why? If a competent developer would need to read three files to understand a single behavior, is that complexity earned by a real constraint, or is it structural accident?
58
+
59
+ For each potential forest-level finding, verify that the complexity is not constraint-driven before reporting.
60
+
61
+ ### 2. Read the change at the tree level
62
+ For each modified function, class, or block:
63
+ - Is there a concrete simpler approach that achieves the same behavior?
64
+ - Could a framework or stdlib feature replace this code?
65
+ - Is this guard, handler, or branch necessary, or does it handle a case the surrounding code already prevents?
66
+ - Could two code paths be collapsed into one?
67
+ - If this code was extracted into a shared helper to avoid duplication, what kind of reuse is it? Private reuse within the same package is generally fine. A shared helper that warps its behavior based on a parameter (doing different things for different callers) is a finding — two separate functions would be clearer. A shared helper that creates a new cross-module dependency is a TRADEOFF — surface the pros and cons, do not declare a verdict.
68
+
69
+ For each potential tree-level finding, verify the simpler alternative achieves the same behavior (including error cases) before reporting.
70
+
71
+ ### 3. Check for redundancy
72
+ For any new function, helper, or utility introduced by the change:
73
+ - Does the codebase already have an equivalent?
74
+ - Does the framework or stdlib provide this?
75
+ - Is the new code justified by a difference from existing options that matters for the use case?
76
+
77
+ ### 4. Verify constraints
78
+ Before reporting any finding, check whether the complexity is driven by a constraint:
79
+ - Read the callers of the cited code. Do they depend on the current interface shape?
80
+ - Consult the workflow guide. Does it document a constraint that explains the complexity?
81
+ - Check git history on the cited file. Was the complexity introduced to solve a specific problem?
82
+ - Use thatch_memory_recall to search for documented design decisions.
83
+
84
+ If you find a constraint that justifies the complexity, do not report it.
85
+
86
+ ## Category taxonomy
87
+
88
+ - **OVERENGINEERED**: The solution uses more machinery than the problem requires — extra abstraction, indirection, or generality that does not serve the actual use case. This is a forest-level finding: the overall design of the change is more complex than needed.
89
+ - **SIMPLER_AVAILABLE**: At a specific touch point, a concrete simpler approach achieves the same behavior with less code, fewer moving parts, or less indirection. This is a tree-level finding: the simpler alternative must be named, not just hinted at.
90
+ - **REDUNDANT**: The change reimplements functionality already available in the codebase, framework, or standard library, without a justification that matters for the use case. Also covers the clear-cut inverse: a shared abstraction that warps behavior via a parameter (the function does different things depending on who calls it). Two separate functions would be clearer than one function with a behavioral switch. Do not flag private reuse within a package — that is generally good.
91
+ - **TRADEOFF**: A design choice where the costs are real in both directions and the right answer depends on context the reviewer cannot fully resolve. The most common case: a shared abstraction that creates a new cross-module or cross-package dependency to avoid duplication. The abstraction removes duplicated code but couples the consumers — a change to one may require considering the other, and a debugging path now crosses a module boundary. Present what the abstraction buys and what it costs. Do not declare a verdict. Frame the finding as a question for the user, not a fix to apply. Always LOW severity.
92
+
93
+ For each finding, the source of truth is the simpler alternative you identified (cite it concretely) and any constraints that would justify the current complexity (cite them if found, and withhold the finding if they do).
94
+
95
+ ## Worked non-finding (negative example)
96
+
97
+ Example non-finding: "This three-layer wrapper hierarchy (Handler -> Validator -> Processor) could be collapsed into a single class." Before reporting, read the callers: if Handler is called from three places, Validator from two different Handlers, and Processor is shared with another module, the layers are earning their keep through reuse. The complexity is structural, not speculative. Reporting it anyway is the canonical economy false positive — complexity that looks unnecessary in isolation but is justified by how the code is actually used. The fix is not to collapse the hierarchy but to verify the reuse pattern before flagging.
@@ -275,7 +275,7 @@ After all findings have been dealt with (adequately addressed and replied to, or
275
275
  ### How to offer
276
276
 
277
277
  Present the offer as a choice:
278
- 1. **Re-run the full structured review**: load `thatch-code-review` on the current HEAD. It will dispatch all seven specialists on the merge-base..HEAD range. The existing follow-up detection will cross-reference your prior comments. On Claude Code or Cursor (no sub-agent support), run each specialist skill in sequence, then run `thatch-review-synthesizer` to aggregate.
278
+ 1. **Re-run the full structured review**: load `thatch-code-review` on the current HEAD. It will dispatch all eight specialists on the merge-base..HEAD range. The existing follow-up detection will cross-reference your prior comments. On Claude Code or Cursor (no sub-agent support), run each specialist skill in sequence, then run `thatch-review-synthesizer` to aggregate.
279
279
  2. **End the re-review here**: the user confirmed their concerns were addressed (or decided how to handle the unaddressed ones) and does not want another full round.
280
280
 
281
281
  The offer is just an offer. The user may decline. They may have only wanted to confirm their concerns were addressed.
@@ -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, highlights). 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, economy, 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.
@@ -18,7 +18,7 @@ For each finding from the specialists:
18
18
 
19
19
  3. **Determine provenance.** Is the finding a new issue introduced by this change, or a pre-existing problem? Note pre-existing bugs separately.
20
20
 
21
- **Finding type determines which steps apply next.** Steps 4-6 apply to behavioral findings (from acceptance, state-flow). Mechanical findings (from pedantic, no-slop, breadcrumbs — docs, naming, style, comments, prose) skip steps 4-6 and use the mechanical verification criteria in step 4m instead.
21
+ **Finding type determines which steps apply next.** Steps 4-6 apply to behavioral findings (from acceptance, state-flow). Economy findings (from economy — design observations about unnecessary complexity) skip steps 4-6 and use the economy verification criteria in step 4e instead. Mechanical findings (from pedantic, no-slop, breadcrumbs — docs, naming, style, comments, prose) skip steps 4-6 and use the mechanical verification criteria in step 4m instead.
22
22
 
23
23
  4. **Check runtime model applicability (behavioral findings only).** Can the finding realistically manifest in normal usage given the application's runtime model? A finding that requires conditions impossible in the actual runtime context is not a real bug (e.g., state accumulation in a short-lived process, concurrency in single-threaded code).
24
24
 
@@ -50,10 +50,18 @@ For each finding from the specialists:
50
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
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
52
 
53
+ 4e. **Economy verification (economy).** For economy findings (unnecessary complexity, simpler alternatives, redundancy, tradeoffs), verification means:
54
+ - The cited code exists at the cited location (already confirmed in step 2).
55
+ - For OVERENGINEERED and SIMPLER_AVAILABLE findings: the simpler alternative is concretely identified (the specialist named a specific approach, not a vague "this could be simpler"), the simpler alternative achieves the same behavior (read the cited code and reason about whether the simpler approach handles the same inputs, error cases, and edge cases — if it changes behavior, reject), and the complexity is not constraint-driven (check the context brief, workflow guide, and callers — if a constraint explains it, reject).
56
+ - For REDUNDANT findings: the reimplemented functionality actually exists where the specialist says it does, and the difference between the new code and the existing option does not matter for the use case. For the inverse (shared abstraction that warps behavior): verify the function actually changes behavior based on a parameter or caller — if both callers get the same behavior, it is not warping, and the finding is rejected.
57
+ - For TRADEOFF findings: verify both sides of the tradeoff are real. The abstraction does remove duplication (check that the consumers would otherwise have duplicated code). The abstraction does create a dependency (check that the consumers are in different packages or modules, or that the helper's interface constrains independent evolution). If only one side is real (e.g., the abstraction removes duplication but the consumers are in the same file), downgrade to REDUNDANT or reject. TRADEOFF findings are always LOW severity and framed as a question for the user, not a fix to apply.
58
+ - The complexity is branch-introduced or made worse by the change: pre-existing complexity outside the change's scope goes in the pre-existing appendix, not as a new finding.
59
+ Runtime reachability, producer chains, and intent verification do not apply. An economy finding is a design observation, not a user-triggered bug.
60
+
53
61
  7. **Classify:**
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).
62
+ - **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 economy findings (OVERENGINEERED, SIMPLER_AVAILABLE, REDUNDANT): the simpler alternative is concrete, achieves the same behavior, no constraint justifies the current complexity, and the complexity is branch-introduced. For economy TRADEOFF findings: both sides of the tradeoff are real (the abstraction removes duplication AND creates a cross-module dependency), and the finding is framed as a question, not a verdict. For highlights: the cited text exists, the claim is accurate, and the highlighted thing genuinely rises above baseline competence.
63
+ - **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 economy findings: a constraint justifies the complexity (cite it), the simpler alternative does not achieve the same behavior, or the complexity predates the change. For economy TRADEOFF findings: only one side of the tradeoff is real (e.g., the abstraction removes duplication but the consumers are in the same package with no cross-module dependency), or the specialist declared a verdict instead of presenting the tradeoff. 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).
64
+ - **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. Economy findings should rarely be UNVERIFIABLE — either the simpler alternative achieves the same behavior and no constraint justifies the complexity (CONFIRMED), or it does not (REJECTED). Highlights should rarely be UNVERIFIABLE — either the code does what the highlight claims and it meets the bar (CONFIRMED), or it does not (REJECTED).
57
65
 
58
66
  ## Deduplication
59
67
 
@@ -89,6 +97,8 @@ Assign final severity based on YOUR verification:
89
97
  - **MEDIUM**: Edge cases, UX friction, or issues where the citation is correct but the impact is limited or requires unusual conditions.
90
98
  - **LOW**: Mechanical issues (stale docs, guideline violations, naming) that don't affect correctness.
91
99
 
100
+ Economy findings are never BLOCKING — they are maintainability observations, not correctness bugs. TRADEOFF findings are always LOW — they are questions for the user, not fixes to apply. Cap HIGH for significant unnecessary complexity that substantially increases maintenance burden or bug surface. MEDIUM for moderate complexity that adds friction. LOW for minor redundancy (a needless wrapper, a small reimplemented utility) and for TRADEOFF findings.
101
+
92
102
  A data-shape or reachability finding that lacks the governing-constraint citation (the schema, type, or guard definition you read to confirm the state is reachable) is capped at UNVERIFIABLE. It cannot be classified CONFIRMED or assigned a severity. This removes the path where a plausible-but-unchecked claim lands at MEDIUM.
93
103
 
94
104
  ## Report format
package/bin/qa-run ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Wrapper for running a subset of QA use cases by UC name.
5
+ # Usage: qa-run <auto|live> [uc-NNN ...]
6
+ # Examples:
7
+ # qa-run auto uc-004 uc-008
8
+ # qa-run live uc-001 uc-003
9
+
10
+ mode="${1:-auto}"
11
+ shift
12
+
13
+ if [ "$#" -eq 0 ]; then
14
+ # No args: run all
15
+ if [ "$mode" = "auto" ]; then
16
+ exec bun test tests/qa/auto/index.test.ts --concurrent --max-concurrency 5
17
+ else
18
+ exec bun test tests/qa/live/index.test.ts
19
+ fi
20
+ fi
21
+
22
+ # Build a regex from UC name args: uc-004 uc-008 -> ^(UC-004|UC-008)
23
+ # Test names are capitalized (UC-NNN), args may be lowercase.
24
+ pattern=""
25
+ for arg in "$@"; do
26
+ # Capitalize the first two chars (uc -> UC) to match test names
27
+ uc=$(echo "$arg" | sed 's/^uc/UC/')
28
+ if [ -n "$pattern" ]; then
29
+ pattern="${pattern}|"
30
+ fi
31
+ pattern="${pattern}${uc}"
32
+ done
33
+
34
+ if [ "$mode" = "auto" ]; then
35
+ exec bun test tests/qa/auto/index.test.ts --concurrent --max-concurrency 5 --test-name-pattern "^(${pattern})"
36
+ else
37
+ exec bun test tests/qa/live/index.test.ts --test-name-pattern "^(${pattern})"
38
+ fi
package/bin/thatch CHANGED
@@ -351,8 +351,7 @@ switch (command) {
351
351
  const sessionID = data.session_id ?? data.conversation_id;
352
352
 
353
353
  const { peekQueue, getMissedCount, incrementMissedCount } = await import("../src/extract-queue");
354
- const { buildExtractionPayload } = await import("../src/extraction");
355
- const { extractionNudge, claudeWriteNudge, claudeRecallNudge, predictionNudge } = await import("../src/prompts");
354
+ const { extractionNudge, claudeWriteNudge, claudeRecallNudge, predictionNudge, behaviorNudge } = await import("../src/prompts");
356
355
 
357
356
  let repo = "unknown";
358
357
  try { repo = await detectRepo(); } catch {}
@@ -360,11 +359,13 @@ switch (command) {
360
359
  // Tier 1: extraction nudge. Peek (don't drain) - the queue persists
361
360
  // until the agent calls memory_remember, which triggers consumeQueue.
362
361
  // If the agent ignores the nudge, the payload grows on the next flush.
362
+ // The nudge carries the session ID, not the full payload — the
363
+ // sub-agent fetches it via get_extraction_payload.
363
364
  const interactions = sessionID ? peekQueue(sessionID) : [];
364
365
  if (interactions.length > 0) {
365
366
  const missed = sessionID ? getMissedCount(sessionID) : 0;
366
367
  if (sessionID) incrementMissedCount(sessionID);
367
- const output = extractionNudge(interactions.length, missed, "mcp__thatch__memory_remember", buildExtractionPayload(interactions, repo));
368
+ const output = extractionNudge(interactions.length, missed, "mcp__thatch__memory_remember", sessionID ?? "");
368
369
  if (isJson) console.log(JSON.stringify({ additional_context: output }));
369
370
  else console.log(output);
370
371
  break;
@@ -375,14 +376,16 @@ switch (command) {
375
376
  // Both recall and predictions share the same sideband round-trip.
376
377
  const promptText = data.prompt ?? data.message ?? data.text ?? data.user_input ?? data.input;
377
378
  if (promptText && promptText.length >= 10) {
378
- const { sidebandMatch, sidebandPredictions, sidebandSocketPath } = await import("../src/sideband");
379
+ const { sidebandMatch, sidebandPredictions, sidebandBehaviors, sidebandSocketPath } = await import("../src/sideband");
379
380
  const sockPath = sidebandSocketPath(dbPath);
380
381
  const recallThreshold = parseFloat(process.env.THATCH_RECALL_THRESHOLD ?? "0.55");
381
- const predThreshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.45");
382
+ const predThreshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.60");
383
+ const behaviorThreshold = parseFloat(process.env.THATCH_BEHAVIOR_THRESHOLD ?? "0.60");
382
384
 
383
- const [matches, predictions] = await Promise.all([
385
+ const [matches, predictions, behaviors] = await Promise.all([
384
386
  sidebandMatch(sockPath, promptText, [repo, "global"], recallThreshold, 5),
385
387
  sidebandPredictions(sockPath, promptText, [repo, "global"], predThreshold, 5),
388
+ sidebandBehaviors(sockPath, promptText, [repo, "global"], behaviorThreshold, 5),
386
389
  ]);
387
390
 
388
391
  const parts: string[] = [];
@@ -392,6 +395,9 @@ switch (command) {
392
395
  if (predictions && predictions.length > 0) {
393
396
  parts.push(predictionNudge(predictions));
394
397
  }
398
+ if (behaviors && behaviors.length > 0) {
399
+ parts.push(behaviorNudge(behaviors));
400
+ }
395
401
 
396
402
  if (parts.length > 0) {
397
403
  const output = parts.join("\n\n");
@@ -457,7 +463,7 @@ switch (command) {
457
463
  const { sidebandPredictions, sidebandSocketPath } = await import("../src/sideband");
458
464
  const { predictionNudge } = await import("../src/prompts");
459
465
  const sockPath = sidebandSocketPath(dbPath);
460
- const threshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.45");
466
+ const threshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.60");
461
467
  const predictions = await sidebandPredictions(sockPath, promptText, [repo, "global"], threshold, 5);
462
468
 
463
469
  if (predictions && predictions.length > 0) {