@jeffober/thatch 0.1.28 → 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.
- package/README.md +24 -10
- package/artifacts/skills/thatch-code-review.md +9 -7
- package/artifacts/skills/thatch-review-economy.md +97 -0
- package/artifacts/skills/thatch-review-followup.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +15 -5
- package/bin/qa-run +38 -0
- package/bin/thatch +10 -5
- package/docs/dev/README.md +71 -18
- package/docs/dev/features/README.md +33 -0
- package/docs/dev/features/behavior-engine.md +271 -0
- package/docs/dev/features/cicd.md +89 -0
- package/docs/dev/features/cli.md +81 -0
- package/docs/dev/features/compaction-recovery.md +48 -0
- package/docs/dev/features/database.md +244 -0
- package/docs/dev/features/deduplication.md +102 -0
- package/docs/dev/features/extraction.md +253 -0
- package/docs/dev/features/hygiene.md +81 -0
- package/docs/dev/features/memory-store.md +154 -0
- package/docs/dev/features/multi-host.md +228 -0
- package/docs/dev/features/nudge-pipeline.md +282 -0
- package/docs/dev/features/prediction-engine.md +253 -0
- package/docs/dev/features/qa-system.md +205 -0
- package/docs/dev/features/session-lifecycle.md +110 -0
- package/docs/dev/features/setup.md +199 -0
- package/docs/dev/features/sideband.md +104 -0
- package/docs/dev/gotchas.md +3 -3
- package/docs/dev/mcp-parity.md +6 -4
- package/docs/dev/setup-and-hooks.md +4 -4
- package/docs/dev/skills.md +10 -8
- package/docs/plans/README.md +2 -2
- package/docs/user/README.md +58 -20
- package/docs/user/behavior-engine.md +85 -0
- package/docs/user/cli.md +98 -0
- package/docs/user/code-review.md +73 -0
- package/docs/user/deduplication.md +70 -0
- package/docs/user/default-behaviors.md +75 -0
- package/docs/user/extraction.md +74 -0
- package/docs/user/hygiene.md +76 -0
- package/docs/user/memory.md +81 -0
- package/docs/user/prediction-engine.md +87 -0
- package/docs/user/setup.md +110 -0
- package/docs/user/skills.md +97 -0
- package/package.json +2 -2
- package/src/behavior.ts +120 -0
- package/src/db.ts +160 -417
- package/src/embeddings.ts +0 -41
- package/src/extraction.ts +2 -2
- package/src/git.ts +3 -1
- package/src/index.ts +55 -9
- package/src/mcp.ts +30 -1
- package/src/prediction.ts +127 -0
- package/src/prompts.ts +135 -188
- package/src/scoring-engine.ts +374 -0
- package/src/seed-behaviors.ts +339 -0
- package/src/setup.ts +2 -6
- package/src/sideband.ts +36 -8
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +172 -25
- package/src/vector-math.ts +30 -0
- package/docs/qa/README.md +0 -175
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sysread/thatch/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
Persistent memory and
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
- **
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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
|
|
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. **
|
|
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. **
|
|
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. **
|
|
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. **
|
|
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
|
|
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.
|
|
@@ -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
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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,7 +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 { extractionNudge, claudeWriteNudge, claudeRecallNudge, predictionNudge } = await import("../src/prompts");
|
|
354
|
+
const { extractionNudge, claudeWriteNudge, claudeRecallNudge, predictionNudge, behaviorNudge } = await import("../src/prompts");
|
|
355
355
|
|
|
356
356
|
let repo = "unknown";
|
|
357
357
|
try { repo = await detectRepo(); } catch {}
|
|
@@ -376,14 +376,16 @@ switch (command) {
|
|
|
376
376
|
// Both recall and predictions share the same sideband round-trip.
|
|
377
377
|
const promptText = data.prompt ?? data.message ?? data.text ?? data.user_input ?? data.input;
|
|
378
378
|
if (promptText && promptText.length >= 10) {
|
|
379
|
-
const { sidebandMatch, sidebandPredictions, sidebandSocketPath } = await import("../src/sideband");
|
|
379
|
+
const { sidebandMatch, sidebandPredictions, sidebandBehaviors, sidebandSocketPath } = await import("../src/sideband");
|
|
380
380
|
const sockPath = sidebandSocketPath(dbPath);
|
|
381
381
|
const recallThreshold = parseFloat(process.env.THATCH_RECALL_THRESHOLD ?? "0.55");
|
|
382
|
-
const predThreshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.
|
|
382
|
+
const predThreshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.60");
|
|
383
|
+
const behaviorThreshold = parseFloat(process.env.THATCH_BEHAVIOR_THRESHOLD ?? "0.60");
|
|
383
384
|
|
|
384
|
-
const [matches, predictions] = await Promise.all([
|
|
385
|
+
const [matches, predictions, behaviors] = await Promise.all([
|
|
385
386
|
sidebandMatch(sockPath, promptText, [repo, "global"], recallThreshold, 5),
|
|
386
387
|
sidebandPredictions(sockPath, promptText, [repo, "global"], predThreshold, 5),
|
|
388
|
+
sidebandBehaviors(sockPath, promptText, [repo, "global"], behaviorThreshold, 5),
|
|
387
389
|
]);
|
|
388
390
|
|
|
389
391
|
const parts: string[] = [];
|
|
@@ -393,6 +395,9 @@ switch (command) {
|
|
|
393
395
|
if (predictions && predictions.length > 0) {
|
|
394
396
|
parts.push(predictionNudge(predictions));
|
|
395
397
|
}
|
|
398
|
+
if (behaviors && behaviors.length > 0) {
|
|
399
|
+
parts.push(behaviorNudge(behaviors));
|
|
400
|
+
}
|
|
396
401
|
|
|
397
402
|
if (parts.length > 0) {
|
|
398
403
|
const output = parts.join("\n\n");
|
|
@@ -458,7 +463,7 @@ switch (command) {
|
|
|
458
463
|
const { sidebandPredictions, sidebandSocketPath } = await import("../src/sideband");
|
|
459
464
|
const { predictionNudge } = await import("../src/prompts");
|
|
460
465
|
const sockPath = sidebandSocketPath(dbPath);
|
|
461
|
-
const threshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.
|
|
466
|
+
const threshold = parseFloat(process.env.THATCH_PREDICTION_THRESHOLD ?? "0.60");
|
|
462
467
|
const predictions = await sidebandPredictions(sockPath, promptText, [repo, "global"], threshold, 5);
|
|
463
468
|
|
|
464
469
|
if (predictions && predictions.length > 0) {
|
package/docs/dev/README.md
CHANGED
|
@@ -24,9 +24,14 @@ Shared core
|
|
|
24
24
|
├── embeddings.ts → embedding model via transformers.js
|
|
25
25
|
├── git.ts → detect repo identity (store name)
|
|
26
26
|
├── hygiene.ts → hygiene report (pending dedups, stale, orphaned branches)
|
|
27
|
-
├── prompts.ts → system prompt, compaction, reminders, recall
|
|
27
|
+
├── prompts.ts → system prompt, compaction, reminders, recall/prediction/behavior nudges, CLAUDE.md instructions
|
|
28
28
|
├── sideband.ts → Unix socket server + client: warm-model semantic match for hook processes
|
|
29
|
-
|
|
29
|
+
├── skills.ts → SKILL.md content + installer (shared + opencode-only arrays)
|
|
30
|
+
├── scoring-engine.ts → generic four-table scoring base (shared by prediction + behavior engines)
|
|
31
|
+
├── prediction.ts → prediction engine wrapper (thin layer over ScoringEngine)
|
|
32
|
+
├── behavior.ts → behavior engine wrapper (thin layer over ScoringEngine)
|
|
33
|
+
├── seed-behaviors.ts → default behavior seeding on first run
|
|
34
|
+
└── vector-math.ts → blobToVector + cosineSimilarity helpers
|
|
30
35
|
|
|
31
36
|
OpenCode plugin path
|
|
32
37
|
├── index.ts → plugin entry: wires DB/model/extraction, registers tools + hooks
|
|
@@ -44,7 +49,7 @@ MCP server path
|
|
|
44
49
|
CLAUDE.md / AGENTS.md, settings/hooks JSON, installs skills
|
|
45
50
|
|
|
46
51
|
bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygiene|
|
|
47
|
-
prime|buffer-batch|buffer-tool|flush-tools|setup
|
|
52
|
+
prime|buffer-batch|buffer-tool|flush-tools|flush-predictions|setup
|
|
48
53
|
```
|
|
49
54
|
|
|
50
55
|
## Module responsibilities
|
|
@@ -55,16 +60,21 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
55
60
|
| `tools.ts` | Thin opencode wrappers. Imports tool-defs, wraps each in opencode's `tool()` with a `thatch_` prefix. |
|
|
56
61
|
| `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
62
|
| `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
|
-
| `setup.ts` | `thatch setup --claude` installer. Writes .mcp.json, appends to CLAUDE.md (idempotent), installs hooks in settings.json, installs skills. |
|
|
63
|
+
| `setup.ts` | `thatch setup --claude` / `--cursor` installer. Writes MCP config (`.mcp.json` / `.cursor/mcp.json`), appends to CLAUDE.md / AGENTS.md (idempotent), installs hooks in settings.json / hooks.json, installs skills. |
|
|
59
64
|
| `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
65
|
| `git.ts` | Parse `owner/repo` from git remote. Worktree-safe fallback chain. |
|
|
61
|
-
| `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. Prediction tables: matchers, predictions, edges, provenance. `scorePredictionNudge` is the shared auto-fire entry point for both host paths. |
|
|
66
|
+
| `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. Prediction tables: matchers, predictions, edges, provenance. `scorePredictionNudge` is the shared auto-fire entry point for both host paths. Behavior tables: same four-table shape (matchers, behaviors, edges, provenance) with `scoreBehaviorNudge` as the shared entry point. |
|
|
62
67
|
| `embeddings.ts` | Lazy-load the embedding model. Expose `queryEmbed`/`passageEmbed` and the model `name` (stored as an informational tag). `MockEmbeddingModel` for tests. |
|
|
63
68
|
| `extraction.ts` | Per-session in-memory ring buffer (cap 20) that buffers non-thatch tool interactions and serializes them into the JSON payload the `get_extraction_payload` tool returns. The in-memory pipeline is opencode-only, but the payload builders (`buildExtractionPayload`, `deriveTitle`) are shared by both paths — `extract-queue.ts` imports `deriveTitle`, `mcp.ts` imports `buildExtractionPayload` for the extraction payload provider. `summarizeArgs` is used internally by `buildExtractionPayload`. |
|
|
64
69
|
| `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
|
-
| `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
|
|
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` (
|
|
70
|
+
| `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 three methods: `match` (recall nudge), `predictions` (prediction auto-fire), and `behaviors` (behavior auto-fire). Socket path is a hash of the DB path — both processes compute it independently. |
|
|
71
|
+
| `prompts.ts` | Text constants: opencode system prompt, compaction context, session-start reminder, prompt-aware recall nudge (`recallNudge` / `claudeRecallNudge`), prediction nudge (`predictionNudge`), behavior nudge (`behaviorNudge`), prediction verb selection (`predictionVerb`), Claude Code CLAUDE.md instructions, Cursor AGENTS.md instructions, Claude Code hook text. |
|
|
72
|
+
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (23 skills: fact-extractor, dedup-classifier, project-primer, 8 review specialists, review synthesizer, review context, code archaeology, review followup, review response, change walkthrough, code walkthrough, session reflection, coding-workflow, thatch-pr-description, thatch-ticket-description, thatch-split-overlarge-pr — work on all three hosts) and `OPENCODE_ONLY_SKILLS` (1 skill: code-review coordinator — requires sub-agent support, not installed for Claude Code or Cursor). `installSkills(dir, skills)` defaults to `SHARED_SKILLS`; the opencode plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`. |
|
|
73
|
+
| `scoring-engine.ts` | Generic four-table scoring engine with Bayesian confidence. Shared base for prediction and behavior engines — each wraps it with table-specific names. |
|
|
74
|
+
| `prediction.ts` | Thin wrapper around `ScoringEngine` with prediction-specific table names. |
|
|
75
|
+
| `behavior.ts` | Thin wrapper around `ScoringEngine` with behavior-specific table names. |
|
|
76
|
+
| `seed-behaviors.ts` | Default behavior seeding on first run. Populates starter self-discipline rules. |
|
|
77
|
+
| `vector-math.ts` | `blobToVector` and `cosineSimilarity` helpers used across the codebase. |
|
|
68
78
|
|
|
69
79
|
## Plugin hooks
|
|
70
80
|
|
|
@@ -76,7 +86,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
76
86
|
| `experimental.session.compacting` | Marks the session as compacting and appends re-familiarization context so a compacted session still knows thatch exists. |
|
|
77
87
|
| `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
88
|
| `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 with the session ID and fetch tool name (not the full payload) — the sub-agent calls `thatch_get_extraction_payload` to retrieve the interactions as a tool response, keeping the full payload out of the main session's context window. 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
|
|
89
|
+
| `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 with the session ID and fetch tool name (not the full payload) — the sub-agent calls `thatch_get_extraction_payload` to retrieve the interactions as a tool response, keeping the full payload out of the main session's context window. 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`, injects `[thatch] User decision model`) and the behavior auto-fire (`db.scoreBehaviorNudge`, injects `[thatch] Situational behaviors`). All three nudges (recall, prediction, behavior) fire independently in separate try/catch blocks with separate synthetic parts. Skipped entirely while the session is compacting (tool calls are blocked during summary generation). |
|
|
80
90
|
| `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
91
|
| `dispose` | Closes the DB. |
|
|
82
92
|
|
|
@@ -100,12 +110,14 @@ Two of these hooks were dead for weeks because failures were invisible.
|
|
|
100
110
|
5. **Store creation is implicit.** First `remember` to a new store creates it.
|
|
101
111
|
6. **Default recall scope is repo + global.** The tool layer hardcodes this.
|
|
102
112
|
7. **Skills are plugin-owned files.** Installed to
|
|
103
|
-
`$XDG_CONFIG_HOME/opencode/skills` (opencode)
|
|
104
|
-
(Claude Code) — never into the
|
|
105
|
-
on plugin init or re-running
|
|
106
|
-
`
|
|
107
|
-
sub-agent support,
|
|
108
|
-
|
|
113
|
+
`$XDG_CONFIG_HOME/opencode/skills` (opencode), `~/.claude/skills/`
|
|
114
|
+
(Claude Code), or `~/.cursor/skills/` (Cursor) — never into the
|
|
115
|
+
worktree; drifted content is overwritten on plugin init or re-running
|
|
116
|
+
`thatch setup`. Skills are split into `SHARED_SKILLS` (work on all
|
|
117
|
+
three hosts) and `OPENCODE_ONLY_SKILLS` (require sub-agent support,
|
|
118
|
+
not installed for Claude Code or Cursor). The opencode plugin
|
|
119
|
+
installs both arrays; `thatch setup --claude` and `--cursor` install
|
|
120
|
+
only shared.
|
|
109
121
|
8. **Tool definitions are the single source of truth.** `tool-defs.ts` defines
|
|
110
122
|
each tool once (name, zod schema, execute function). The opencode plugin
|
|
111
123
|
wraps them in `tool()` with a `thatch_` prefix; the MCP server wraps them
|
|
@@ -138,6 +150,18 @@ Two of these hooks were dead for weeks because failures were invisible.
|
|
|
138
150
|
relevance-gated (being tested moves it, not being ignored). See the
|
|
139
151
|
prediction DB tables, auto-fire in `chat.message`, and the sideband
|
|
140
152
|
`predictions` method for the MCP path.
|
|
153
|
+
12. **Behavior engine mirrors the prediction engine but is self-graded.** Same
|
|
154
|
+
four-table data model, same Bayesian confidence, same auto-fire pipeline.
|
|
155
|
+
The difference: predictions model what the USER wants (graded by user
|
|
156
|
+
feedback); behaviors model what the LLM should do (graded by the LLM's own
|
|
157
|
+
ham/spam relevance judgment via `behavior_feedback`). The firewall
|
|
158
|
+
principle from predictions (the model that uses predictions cannot grade
|
|
159
|
+
them) does not apply: the LLM grading its own behavioral rules is the
|
|
160
|
+
point, not a violation. The ham/spam is a relevance judgment ("does this
|
|
161
|
+
rule apply here?"), not a value judgment ("is this a good rule?"). An
|
|
162
|
+
anti-laziness guard in the prompt prevents the agent from codifying
|
|
163
|
+
shortcuts. See the behavior DB tables, auto-fire in `chat.message`, and
|
|
164
|
+
the sideband `behaviors` method for the MCP path.
|
|
141
165
|
|
|
142
166
|
## Data flow
|
|
143
167
|
|
|
@@ -207,7 +231,9 @@ toast notifications (opencode-only, TUI-rendered)
|
|
|
207
231
|
→ recall matches: `[thatch] recalled N memories` (info variant, 3s) —
|
|
208
232
|
fires when chat.message matches stored memories
|
|
209
233
|
→ prediction matches: `[thatch] N predictions surfaced` (info variant,
|
|
210
|
-
|
|
234
|
+
3s) — fires when chat.message matches decision-model patterns
|
|
235
|
+
→ behavior matches: `[thatch] N behaviors surfaced` (info variant,
|
|
236
|
+
3s) — fires when chat.message matches codified behavior matchers
|
|
211
237
|
|
|
212
238
|
prompt-aware recall nudge (both paths)
|
|
213
239
|
→ opencode: chat.message hook embeds prompt text with warm in-process model,
|
|
@@ -237,7 +263,7 @@ prediction cycle (agent-driven, statistical model)
|
|
|
237
263
|
confidence is relevance-gated (being tested moves it, not being ignored).
|
|
238
264
|
→ auto-fire (opencode): chat.message reuses the prompt embedding already
|
|
239
265
|
computed for the recall nudge. db.scorePredictionNudge([repo, global],
|
|
240
|
-
|
|
266
|
+
embedding, 0.60) finds matchers above threshold, follows edges to
|
|
241
267
|
predictions, scores by cosine * weight * confidence, dedups by
|
|
242
268
|
prediction_id, and returns top 5. Injects a separate synthetic part
|
|
243
269
|
with a [thatch] User decision model block. 0-evidence predictions use
|
|
@@ -252,6 +278,29 @@ prediction cycle (agent-driven, statistical model)
|
|
|
252
278
|
thatch_prediction_list inspects the model with provenance;
|
|
253
279
|
thatch_prediction_delete removes bad predictions (cascade clears edges
|
|
254
280
|
and provenance).
|
|
281
|
+
|
|
282
|
+
behavior cycle (agent-driven, self-graded)
|
|
283
|
+
→ formation: agent calls thatch_behavior_codify(situation, behavior, rationale)
|
|
284
|
+
when it recognizes a situation it should react to in a specific, repeatable
|
|
285
|
+
way. The tool embeds the situation text, finds or creates a behavior matcher
|
|
286
|
+
(0.85 dedup), finds or creates a behavior (0.85 store-wide dedup), links
|
|
287
|
+
them via an edge. Confidence starts at p0 (0.5) with 0 evidence.
|
|
288
|
+
→ auto-fire (opencode): chat.message reuses the prompt embedding. db.
|
|
289
|
+
scoreBehaviorNudge([repo, global], embedding, 0.60) finds behavior matchers
|
|
290
|
+
above threshold, follows edges to behaviors, scores by cosine * weight *
|
|
291
|
+
confidence, dedups by behavior_id, returns top 5. Injects a separate
|
|
292
|
+
synthetic part with a [thatch] Situational behaviors block. 0-evidence
|
|
293
|
+
behaviors use "consider"; behaviors with evidence use "do".
|
|
294
|
+
→ auto-fire (Claude Code/Cursor): flush-tools fires the behavior query via
|
|
295
|
+
the sideband socket's `behaviors` method in parallel with recall and
|
|
296
|
+
predictions. Same scoreBehaviorNudge entry point prevents scoring drift.
|
|
297
|
+
→ consumption: agent evaluates each surfaced behavior against the current
|
|
298
|
+
situation. If relevant (ham), follows it and calls behavior_feedback with
|
|
299
|
+
relevant: true (confirm). If not relevant (spam), calls behavior_feedback
|
|
300
|
+
with relevant: false (disconfirm). The feedback adjusts the Bayesian
|
|
301
|
+
confidence the same way prediction signals do. thatch_behavior_list
|
|
302
|
+
inspects with provenance; thatch_behavior_delete removes bad rules
|
|
303
|
+
(cascade clears edges and provenance).
|
|
255
304
|
```
|
|
256
305
|
|
|
257
306
|
## Database
|
|
@@ -266,7 +315,11 @@ prediction cycle (agent-driven, statistical model)
|
|
|
266
315
|
`prediction_matchers(id PK, store, description, embedding BLOB, model, created_at, updated_at)`,
|
|
267
316
|
`predictions(id PK, store, statement, rationale, embedding BLOB, model, confidence REAL, confirm_count REAL, disconfirm_count REAL, created_at, updated_at)`,
|
|
268
317
|
`prediction_edges(matcher_id, prediction_id, weight REAL, PK(matcher_id, prediction_id), FK CASCADE)`,
|
|
269
|
-
`prediction_provenance(id PK, prediction_id, signal, detail, created_at, FK CASCADE)
|
|
318
|
+
`prediction_provenance(id PK, prediction_id, signal, detail, created_at, FK CASCADE)`,
|
|
319
|
+
`behavior_matchers(id PK, store, description, embedding BLOB, model, created_at, updated_at)`,
|
|
320
|
+
`behaviors(id PK, store, statement, rationale, embedding BLOB, model, confidence REAL, confirm_count REAL, disconfirm_count REAL, created_at, updated_at)`,
|
|
321
|
+
`behavior_edges(matcher_id, behavior_id, weight REAL, PK(matcher_id, behavior_id), FK CASCADE)`,
|
|
322
|
+
`behavior_provenance(id PK, behavior_id, signal, detail, created_at, FK CASCADE)`.
|
|
270
323
|
- `recall_count`, `last_recalled_at`, and `archived` are added to pre-existing
|
|
271
324
|
databases by an idempotent column migration at init (`PRAGMA table_info` +
|
|
272
325
|
`ALTER TABLE`). The `archived` column is `INTEGER NOT NULL DEFAULT 0` (0 =
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Feature Architecture
|
|
2
|
+
|
|
3
|
+
Per-feature architecture docs for thatch. Each doc covers what the feature
|
|
4
|
+
does, how it works, how it interacts with other features, and the source
|
|
5
|
+
files that implement it.
|
|
6
|
+
|
|
7
|
+
For the monolithic architecture overview, see
|
|
8
|
+
[../README.md](../README.md). For hook configuration and concrete artifact
|
|
9
|
+
paths, see [../setup-and-hooks.md](../setup-and-hooks.md). For feature
|
|
10
|
+
parity across hosts, see [../mcp-parity.md](../mcp-parity.md). For the skill
|
|
11
|
+
system, see [../skills.md](../skills.md). For non-obvious invariants, see
|
|
12
|
+
[../gotchas.md](../gotchas.md).
|
|
13
|
+
|
|
14
|
+
## Docs
|
|
15
|
+
|
|
16
|
+
| Doc | Feature |
|
|
17
|
+
|-----|---------|
|
|
18
|
+
| [memory-store.md](memory-store.md) | Persistent memory store (CRUD, search, embeddings, stores, branches) |
|
|
19
|
+
| [multi-host.md](multi-host.md) | Three-host architecture (opencode, Claude Code, Cursor) |
|
|
20
|
+
| [extraction.md](extraction.md) | Extraction pipeline (buffering, child sessions, nudge escalation, file-backed queue) |
|
|
21
|
+
| [nudge-pipeline.md](nudge-pipeline.md) | Per-message nudge pipeline (extraction, recall, prediction, behavior tiers) |
|
|
22
|
+
| [prediction-engine.md](prediction-engine.md) | Prediction engine (user decision model, Bayesian confidence, auto-fire) |
|
|
23
|
+
| [behavior-engine.md](behavior-engine.md) | Behavior engine (LLM self-discipline rules, ham/spam, auto-fire) |
|
|
24
|
+
| [hygiene.md](hygiene.md) | Hygiene system (duplicate, stale, orphaned branch signals) |
|
|
25
|
+
| [deduplication.md](deduplication.md) | Deduplication system (find pairs, verdict lifecycle) |
|
|
26
|
+
| [sideband.md](sideband.md) | Sideband IPC (Unix socket for MCP host hook processes) |
|
|
27
|
+
| [compaction-recovery.md](compaction-recovery.md) | Compaction recovery (opencode compaction hooks) |
|
|
28
|
+
| [session-lifecycle.md](session-lifecycle.md) | Session lifecycle management (opencode events, child tracking) |
|
|
29
|
+
| [setup.md](setup.md) | Setup system (markers, checkSetup, auto-refresh, binary resolution) |
|
|
30
|
+
| [cli.md](cli.md) | CLI (subcommands, environment variables) |
|
|
31
|
+
| [database.md](database.md) | Database (schema, migrations, WAL, 11 tables) |
|
|
32
|
+
| [qa-system.md](qa-system.md) | QA system (93 use cases, automatable vs live, mise tasks) |
|
|
33
|
+
| [cicd.md](cicd.md) | CI/CD (CI pipeline, OIDC publishing, release helper) |
|