@jeffober/thatch 0.2.4 → 0.2.5

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 (54) hide show
  1. package/README.md +8 -1
  2. package/artifacts/skills/thatch-clear-writing.md +103 -5
  3. package/artifacts/skills/thatch-coordination.md +69 -0
  4. package/artifacts/skills/thatch-pr-description.md +21 -13
  5. package/artifacts/skills/thatch-review-no-slop.md +15 -0
  6. package/bin/thatch +27 -3
  7. package/docs/assets/deepwiki-badge.svg +1 -0
  8. package/docs/dev/README.md +7 -2
  9. package/docs/dev/features/cli.md +2 -0
  10. package/docs/dev/features/cross-session-chat.md +118 -11
  11. package/docs/dev/features/notifications.md +1 -2
  12. package/docs/dev/features/opencode-plugin.md +18 -3
  13. package/docs/dev/features/prediction-engine.md +44 -5
  14. package/docs/dev/features/session-tabs.md +155 -0
  15. package/docs/dev/features/setup.md +5 -0
  16. package/docs/dev/features/watchers.md +62 -6
  17. package/docs/dev/gotchas.md +68 -0
  18. package/docs/dev/mcp-parity.md +2 -2
  19. package/docs/dev/setup-and-hooks.md +7 -2
  20. package/docs/dev/skills.md +1 -1
  21. package/docs/plans/generalized-session-heartbeat.md +212 -0
  22. package/docs/plans/prediction-consolidation.md +88 -68
  23. package/docs/user/README.md +12 -3
  24. package/docs/user/cli.md +3 -0
  25. package/docs/user/commands.md +11 -4
  26. package/docs/user/cross-session-chat.md +31 -8
  27. package/docs/user/prediction-engine.md +11 -0
  28. package/docs/user/setup.md +16 -0
  29. package/docs/user/skills.md +1 -1
  30. package/docs/user/watchers.md +22 -7
  31. package/package.json +10 -2
  32. package/src/alerts.ts +1 -1
  33. package/src/chat.ts +320 -27
  34. package/src/commands.ts +14 -0
  35. package/src/db.ts +51 -1
  36. package/src/git.ts +3 -1
  37. package/src/hygiene.ts +5 -0
  38. package/src/mcp.ts +95 -7
  39. package/src/opencode/tui-plugin.ts +70 -0
  40. package/src/opencode/v2.ts +197 -4
  41. package/src/prediction.ts +90 -0
  42. package/src/prompts.ts +110 -23
  43. package/src/runtime.ts +187 -9
  44. package/src/session-db.ts +4 -4
  45. package/src/session-tab-shared.ts +180 -0
  46. package/src/setup.ts +9 -3
  47. package/src/skills.ts +1 -0
  48. package/src/tool-defs.ts +412 -55
  49. package/src/tools.ts +2 -0
  50. package/src/watchers.ts +263 -26
  51. package/docs/plans/intuition-drives.md +0 -147
  52. package/docs/plans/llm-alerts.md +0 -287
  53. package/docs/plans/thatch-actions.md +0 -179
  54. package/docs/plans/worktree-deletion-recovery.md +0 -246
package/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Thatch
2
2
 
3
- [![CI](https://github.com/sysread/thatch/actions/workflows/ci.yml/badge.svg)](https://github.com/sysread/thatch/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/sysread/thatch/actions/workflows/ci.yml/badge.svg)](https://github.com/sysread/thatch/actions/workflows/ci.yml) [![Ask DeepWiki](https://raw.githubusercontent.com/sysread/thatch/main/docs/assets/deepwiki-badge.svg)](https://deepwiki.com/sysread/thatch)
4
+
5
+ <!-- Experiment (Oct 2026): deepwiki.com/badge.svg 429-challenges non-browser
6
+ fetchers, so the hotlinked badge rendered broken. The wiki indexed us
7
+ anyway ("featured in the repository"), so this vendored copy tests
8
+ whether DeepWiki keeps reindexing without any request to their domain.
9
+ If freshness holds, the hotlink never mattered; if the wiki goes stale,
10
+ restore https://deepwiki.com/badge.svg when their challenge lifts. -->
4
11
 
5
12
  Persistent memory and operational methodology for AI coding agents. Works with
6
13
  **OpenCode** (as a plugin), **Claude Code** (as a local MCP server), and
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: thatch-clear-writing
3
- description: Prose rules for every piece of text a human reads that no dedicated skill already covers - PR and ticket comments posted on the user's behalf, documentation, feature plans, reports, write-ups, status updates, and chat replies to the user. Load it before drafting any of those. When a dedicated writing skill is loaded (thatch-pr-description, thatch-ticket-description, thatch-review-response), that skill's rules win.
3
+ description: Prose rules for every piece of text a human reads that no dedicated skill already covers - PR and ticket comments posted on the user's behalf, documentation, feature plans, reports, write-ups, status updates, chat replies, and code and doc comments. Load it before drafting any of those. When a dedicated writing skill is loaded (thatch-pr-description, thatch-ticket-description, thatch-review-response), that skill's rules win.
4
4
  ---
5
5
 
6
6
  # Clear writing
@@ -19,6 +19,7 @@ Every text a human reads:
19
19
 
20
20
  - **Comments you post for the user** - PR comments, ticket comments (Linear, Jira), replies to reviewers, replies to bot findings.
21
21
  - **Written artifacts** - documentation, feature plans, reports, write-ups, design notes, status updates.
22
+ - **Code and doc comments** - comments in source files, doc comments, package READMEs.
22
23
  - **Replies to the user in chat** - explanations, summaries, answers.
23
24
 
24
25
  Same prose rules everywhere. Only the structure differs:
@@ -56,9 +57,12 @@ Hard rules. Not suggestions.
56
57
  8. **Show causal links.** If a sentence uses "so", "because", or "prevents", make the middle step visible. Cause and effect that do not obviously touch need the missing step spelled out.
57
58
  9. **Clarity wins over compression.** Brevity means fewer claims, not denser claims. Shorten by deleting whole claims. Never rewrite surviving sentences into tighter ones. Keep the connective tissue that makes a sentence parse on first read.
58
59
  10. **No invented shorthand.** Do not coin abbreviations, portmanteaus, or labels mid-text. Use the real name, or spell out the behavior. A term the reader cannot find anywhere else is a term they cannot look up.
59
- 11. **No buzzwordy abstractions.** "leverage" -> "use". "utilize" -> "use". If you cannot say what a phrase means in plain English, it does not belong.
60
- 12. **Plain ASCII.** No smart quotes, em dashes, or ellipsis glyphs.
61
- 13. **No process narration.** Never describe the order you assembled things in or narrate your own actions inside the text. State the thing itself.
60
+ 11. **No made-up technical phrases.** Do not coin multi-word terms that sound technical but mean nothing: "identity contract", "canonical hash", "semantic alignment layer". Every term must exist somewhere - in the code, the docs, or standard engineering usage. If you cannot point to where it is defined, delete the label and say what the thing does: what runs, what changes, what the reader gets.
61
+ 12. **No buzzwordy abstractions.** "leverage" -> "use". "utilize" -> "use". If you cannot say what a phrase means in plain English, it does not belong.
62
+ 13. **Plain ASCII.** No smart quotes, em dashes, or ellipsis glyphs.
63
+ 14. **No process narration.** Never describe the order you assembled things in or narrate your own actions inside the text. State the thing itself.
64
+ 15. **No sycophancy.** Never open by validating the question: phrases like "Great question", "Excellent question", "You're absolutely right", "You're asking the right questions", "I'm excited to help" signal "AI bot managing your emotions" and must never appear. Never hedge a verifiable claim with "I think", "I believe", "In my opinion", "Arguably", or "Just" as a softener. Stating uncertainty on purpose (you are genuinely unsure of a preference or a prediction) is not a hedge. Corrections are not rudeness. They are the point.
65
+ 16. **When you don't know, say so.** Say "I don't know" and stop. If you know part of it, state what you know, what you don't, and your confidence. No plausible filler.
62
66
 
63
67
  ## Length
64
68
 
@@ -70,8 +74,102 @@ Match length to complexity: how much new mental model the reader must build.
70
74
 
71
75
  For chat replies: terse is fine, illegible is not. If a two-line answer only fits by inventing shorthand, spend five lines.
72
76
 
77
+ ## Session replies
78
+
79
+ A substantial turn ends with a brief state summary: what was done, what was found, the current state, and what remains. The response comes first; the summary is a short block after it. Keep it to a few lines. Never a large task list the user must scroll past to find the response. When work spans turns, keep pending work visible as a brief outline, not a list that grows until it hides the answer.
80
+
81
+ A state summary at the end of a turn is not process narration. Rule 14 bans narrating how you assembled the text; this section requires reporting where the work stands.
82
+
83
+ ## Code comments
84
+
85
+ Comments are human-facing text. The same prose rules apply, plus these. The
86
+ reader is the same future coder from "The reader" above: they know the stack
87
+ and general engineering vocabulary, and they have never touched this area.
88
+
89
+ - **No metaphor standing in for a mechanism.** If a word labels behavior the
90
+ comment never states, replace the word with the behavior: "re-enqueued
91
+ rows get a fresh timestamp so they don't starve the rest", not "a
92
+ quarantine". A metaphor verb attached to the stated mechanism is fine
93
+ ("the sweep heals the dropped ids"). A metaphor noun doing the
94
+ mechanism's job is not.
95
+ - **Euphemisms die; call the thing what it is.** "later UPDATEs of the same
96
+ row leave it unchanged", not "enqueued_at is not bumped by re-touches"
97
+ ("bumped by re-touches" is a terrible phrase for "UPDATEs"). A pinned
98
+ term of art survives.
99
+ - **Full sentences, always.** "A failed cycle waits out the backoff delay,
100
+ an idle cycle waits the short idle cadence, and a productive cycle waits
101
+ zero because the queue still has work", not "failures wait out the
102
+ backoff delay, idle cycles the short idle cadence, productive cycles
103
+ nothing". The missing causal clause is usually the point.
104
+ - **If/then with the named cause, not a label-colon chain.** "If the
105
+ recompute returns no row for a claimed id, that id was deleted or
106
+ soft-deleted after it was enqueued; the worker then deletes any cache row
107
+ it still has", not "A claimed id with no recompute row was deleted or
108
+ soft-deleted mid-drain: its cache row is deleted". No passives
109
+ in the consequence half.
110
+ - **Subject first.** "DrainLoop is a background goroutine that repeatedly
111
+ claims", not "A background goroutine (DrainLoop) repeatedly claims".
112
+ Names never hide inside parentheticals.
113
+ - **Behavior before implementation structure.** "takes up to limit rows
114
+ off the queue, oldest first, and returns them; deleting the row is the
115
+ claim", not "a CTE selects the stalest ids ... and the outer DELETE
116
+ removes exactly those rows". State the invariant the design buys ("a
117
+ trigger enqueue for the same row never waits on this transaction"),
118
+ not the failure mechanics behind it.
119
+ - **Parentheticals only when they add a distinct fact.** Keep "(or has
120
+ been killed on)". Cut "(the worker runs when the flag is unset)" after
121
+ "It defaults to enabled".
122
+ - **Enumerate the parts once, then the collective noun.** "stops the
123
+ worker" after the drain loop and the sweep are established. Never
124
+ re-enumerate, and never enumerate what the thing does not control.
125
+ - **State what is impossible, plainly, in first person.** "This should not
126
+ be possible: the apply statements are a plain INSERT ... SELECT and
127
+ DELETE whose only inputs are ids that exist in the queue. If we screwed
128
+ up (a projection drift, a constraint we did not anticipate), the same
129
+ batch would fail again on retry, so re-enqueueing would only churn. Log
130
+ it with the ids and drop it; the daily sweep heals the dropped ids."
131
+ Invariant, failure conditions, decision, recovery. No hedging.
132
+ - **Rescope every label when behavior splits.** If re-enqueue now only
133
+ happens for lock-blocked batches, "failed batch" is wrong everywhere it
134
+ appears: comments, log messages, test names.
135
+ - **Doc comments own the mechanism; package READMEs stay high-level
136
+ guides.** A README is not "the code but in english". Implementation
137
+ detail belongs to the doc comment next to the code.
138
+ - **Do not document de facto conventions.** If the framework or the
139
+ migrations make something the default rule, the comment does not say so.
140
+ Point at the artifact that owns the contract or say nothing.
141
+ - **No ticket refs or plan-doc coordinates in place of behavior.** "the
142
+ backfill worker", not "the TICKET-123 backfill worker". The reader never
143
+ opens the ticket tracker to understand the code.
144
+ - **A term of art is defined once; plain behavior everywhere else.** "(the
145
+ tombstone correction)" in the place it is defined; "a second pass
146
+ removing cache rows whose source row no longer exists" everywhere else.
147
+ - **Keep load-bearing precision.** Translate jargon ("mid-drain" becomes
148
+ "after it was enqueued"), but keep the contract when the code depends on
149
+ it: Postgres error classes 40 and 55, genuine lists, cross-references
150
+ that carry the why.
151
+
152
+ The comment shape for a named thing (a metric, a function, a constant):
153
+
154
+ 1. What it does or reports, in behavior terms.
155
+ 2. What a reading means: "Sustained growth means the drain loop cannot
156
+ keep up with (or has been killed on) the enqueue rate".
157
+ 3. The operational caveat, when needed: "Sampled by the drain loop, so it
158
+ freezes while the kill switch is off".
159
+
160
+ The wart shape, for code that looks wrong but is correct:
161
+
162
+ 1. Name the sensible implementation the reader expects: "Why not
163
+ min(base * 3^(n-1), max)?"
164
+ 2. State the crux: the multiply wraps the int64 before min clamps.
165
+ 3. Why, in the fewest words: "30s * 3^19 overflows the Duration, an int64,
166
+ and min would clamp the overflowed value, not the intended product."
167
+
168
+ Simple and good beats complex and great. Cut whole claims to shorten;
169
+ never compress the survivors.
170
+
73
171
  ## Clarity pass
74
172
 
75
173
  Before you post, save, or send: reread the draft as a reader with zero context. Fix what they would stumble on. Do not announce the pass; just return clean prose.
76
174
 
77
- Check: Does every sentence parse on first read? Does every term carry its plain-English meaning before or with the label? Would a reader who has never seen this codebase, ticket, or conversation understand it? If the draft runs long, cut a claim; never tighten the grammar.
175
+ Check: Does every sentence parse on first read? Does every term carry its plain-English meaning before or with the label? Is every technical phrase real - could you find it in the code, the docs, or common usage? If not, say what the thing does instead of naming it. Would a reader who has never seen this codebase, ticket, or conversation understand it? If the draft runs long, cut a claim; never tighten the grammar.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: thatch-coordination
3
+ description: Coordinate subordinate LLM sessions as a supervisor. Dispatch work with thatch_session_tab (opencode v2 only), track each subordinate in your task list, supervise over cross-session chat, verify before declaring done. Use when orchestrating multi-session work - parallel tasks, delegated implementation, or any flow where you manage work happening in OTHER sessions.
4
+ ---
5
+
6
+ You are the coordinating LLM. Work happens in subordinate sessions you spawn
7
+ and supervise. Your hands are the dispatch tool and the chat; the hands-on
8
+ work belongs to the subordinates.
9
+
10
+ ## Dispatch discipline
11
+
12
+ One task list entry per subordinate, created BEFORE the dispatch. The entry
13
+ records the top-level task, the subordinate's chat name (from the
14
+ `thatch_session_tab` response), its session id, and its status. Your task
15
+ list is the stateful map of the fleet — if it is not in the list, you are
16
+ not supervising it.
17
+
18
+ Dispatch with `thatch_session_tab`:
19
+
20
+ - `title` — short task label (~50 characters).
21
+ - `prompt` — what the subordinate cannot infer: the goal, the relevant paths,
22
+ the acceptance criteria, and any coordination ground rules. The tool
23
+ prepends your coordinator identity and authority framing; you supply the
24
+ substance.
25
+ - `worktree` for work in this repository (the subordinate gets an isolated
26
+ checkout; its tab opens beside yours), `directory` for anything else.
27
+ - Dispatch one subordinate per independent task. Two tasks that touch the
28
+ same files are one task, or a sequenced pair — not two tabs.
29
+
30
+ ## Supervision loop
31
+
32
+ Reconcile your task list against reality every turn:
33
+
34
+ - `chat_read` drains mail from subordinates; answer questions, send
35
+ corrections, and record status changes in the task list.
36
+ - A subordinate that goes quiet is not necessarily dead — but the watcher
37
+ machinery sends death notices when a subordinate's session disappears
38
+ (`watcherDeathNotice` names the dead session's chat name). On a death
39
+ notice: mark the task failed, salvage what the subordinate reported, and
40
+ re-dispatch if the work still matters.
41
+ - Do not block on one subordinate while others run. Answer mail in priority
42
+ order; keep your own turns short.
43
+
44
+ ## Verification before completion
45
+
46
+ A subordinate reporting "done" is a claim, not a fact. Before you mark a
47
+ task complete or report to the user:
48
+
49
+ 1. Review the subordinate's actual output — the diff, the test results, the
50
+ files it claims to have changed.
51
+ 2. Re-run the acceptance criteria yourself where cheap (run the tests, read
52
+ the file).
53
+ 3. Only then mark the task done and say so.
54
+
55
+ ## Authority and escalation
56
+
57
+ The user outranks everything. Your instructions carry accepted priority with
58
+ subordinates, but a direct user instruction to a subordinate supersedes you,
59
+ and a direct user instruction to you supersedes your plan. When a
60
+ subordinate surfaces a conflict, resolve it toward the user's expressed
61
+ wishes and record the decision in the task list.
62
+
63
+ ## Cleanup
64
+
65
+ A finished subordinate's tab closes with `thatch_session_tab_close` (pass
66
+ the subordinate's chat name) once its work is verified — tab-level only:
67
+ the session survives and the user can reopen it. If a subordinate strands
68
+ an empty or stuck session, tell the user which session id to delete — you
69
+ cannot delete sessions yourself.
@@ -23,8 +23,8 @@ The description is not a design doc, changelog, or implementation journal. It is
23
23
 
24
24
  Each section has one job:
25
25
  - SYNOPSIS: orient to the class of change and affected area.
26
- - PURPOSE: make the reader agree there is a problem worth solving.
27
- - DESCRIPTION: teach the system's current behavior and how this change alters it.
26
+ - PURPOSE: state the premise (the defect, or the pivot the change serves) and teach the feature-level mechanism that makes the diff legible.
27
+ - DESCRIPTION: teach the system's current behavior and how this change alters it; end with a NEXT STEPS subsection when a follow-up ticket continues the work.
28
28
  - WALK-THROUGH (when needed): walk the reviewer through the new workflow step by step.
29
29
  - NOTES: flag scope limits, intentional regressions, and things AI reviewers will misread.
30
30
  - robots.txt (when needed): dense, machine-addressed context that heads off recurring false positives from automated reviewers.
@@ -35,7 +35,7 @@ AI-authored PRs tend to be large. The description is the reader's lever against
35
35
 
36
36
  ## Structure
37
37
 
38
- Sections, in order: SYNOPSIS, PURPOSE, DESCRIPTION, WALK-THROUGH (conditional), NOTES (conditional), robots.txt (conditional, always last).
38
+ Sections, in order: SYNOPSIS, PURPOSE, DESCRIPTION (optionally ending with a `### NEXT STEPS` subsection), WALK-THROUGH (conditional), NOTES (conditional), robots.txt (conditional, always last).
39
39
 
40
40
  **SYNOPSIS, PURPOSE, and DESCRIPTION are mandatory** - even for a one-line, single-concern change. A tiny PR gets short sections, not fewer sections. WALK-THROUGH and NOTES appear only when warranted; omit them when empty. robots.txt appears only after an automated reviewer has produced a false positive worth heading off (see its section); it is always the final section.
41
41
 
@@ -45,6 +45,8 @@ Sections, in order: SYNOPSIS, PURPOSE, DESCRIPTION, WALK-THROUGH (conditional),
45
45
 
46
46
  One to two lines. Orient only. The reader should come away knowing the class of change (bugfix, refactor, feature, revert, infra, cleanup) and the affected area.
47
47
 
48
+ Lead with the change: what it does or enables. A very brief statement of the problem it fixes may follow, after the change - do X to fix Y, never Y alone. Even a safety fix describes itself by behavior, not by certification: "the schema lets the worker clean up after itself", not "makes the schema safe to deploy".
49
+
48
50
  SYNOPSIS may use subsystem-specific terms as scan anchors (that is its job). It does not define them. The first teaching section (PURPOSE or DESCRIPTION) picks up that burden. An outsider who does not know the terms should still be able to orient from the plain-English nouns around them. Do not force all project context into SYNOPSIS; it is a TLDR, not the teaching section.
49
51
 
50
52
  Include a ticket link if one exists. Format: `Ticket: [PLAT-139](https://<host>/browse/PLAT-139)` on its own line directly under the SYNOPSIS text.
@@ -59,7 +61,7 @@ Do not fabricate ticket IDs. If no signal exists and the user says `none`, omit
59
61
 
60
62
  ### PURPOSE
61
63
 
62
- Frame the problem. Lead with the conclusion, then provide the evidence, not the other way around. State what is wrong, then explain why. For example:
64
+ Frame the premise: the defect, or the pivot the change serves. Lead with the conclusion, then provide the evidence, not the other way around. State what is wrong, then explain why. For example:
63
65
 
64
66
  - "The export endpoint loads every row into memory before serializing, so a large tenant runs the pod out of memory" not "the endpoint allocates a slice per row, and with enough rows... which is why the pod runs out of memory."
65
67
  - "This handler runs one query per result row, so a page of 50 items fires 51 queries" not "the handler loops over results issuing a query each time... resulting in an N+1 query pattern."
@@ -67,11 +69,11 @@ Frame the problem. Lead with the conclusion, then provide the evidence, not the
67
69
 
68
70
  The reader should know what the problem is before they read why it happens.
69
71
 
70
- No solution yet. The reader should finish this section agreeing there is something to address, even with zero code knowledge.
72
+ State the premise in one sentence: what is wrong (the defect), or what the change is for (the pivot it serves). Then teach, at the feature level, how the affected thing works - the shortest version that makes DESCRIPTION's details parse. For a defect, the reader finishes the section agreeing there is something to address, even with zero code knowledge. For a foundation or preventive change, the reader finishes understanding what this is and how the feature works; that understanding is the motivation.
71
73
 
72
- Separate the problem from its context. The problem statement is one paragraph. Hazard rating and prior-work context go in a separate paragraph. The reader who already agrees there is a problem can skip the rating; the reader who does not should not have to wade through it to find the problem.
74
+ PURPOSE stays at feature altitude. Table-level mechanics, column lists, and function names belong to DESCRIPTION.
73
75
 
74
- If the change is preventive (no current defect), say why it is worth fixing despite the low risk. "No incident has hit this shape" is a demotivation, not a motivation. Answer "why now?": a prior PR identified it as a follow-up, the API still permits a failure shape, or the next planned change would make it worse. The reviewer's question is "should I spend time on this?" - give them the answer.
76
+ No failure narratives and no urgency arguments. Do not narrate a sequence of failures to justify the change, and do not argue that it must land before something else exists. State the harm as fact where it belongs: harm evidence lives in DESCRIPTION layer 1 as existing behavior, and hazard facts live in NOTES.
75
77
 
76
78
  When a ticket description is available, use its problem statement as the seed for PURPOSE. But verify it against the actual code. Ticket descriptions can be stale or wrong. If the ticket says "the frobnicator crashes on null input" but the diff shows you fixing a race condition in the frobnicator, the PURPOSE describes the race condition, not the null input. Flag the discrepancy to the user.
77
79
 
@@ -79,13 +81,15 @@ When a ticket description is available, use its problem statement as the seed fo
79
81
 
80
82
  How much structure DESCRIPTION needs depends on the PR's complexity.
81
83
 
84
+ The spine of DESCRIPTION is the walkthrough: what the change does and how it works, readable straight through. Justify only where the overall behavior is unintuitive, and keep the why to one brief, high-level sentence: "The constraints had to be applied in a follow-up migration, because the ORM cannot express database-level referential actions." Never interleave defenses with the mechanics - a paragraph where every change sentence is followed by two or three sentences of workaround detail makes the reader skip around to reconstruct the what-and-how, like business logic buried under `if err != nil` blocks. Detailed workaround narratives are NOTES bullets, stated as facts; prose does not repeat NOTES. Factual scope stays in prose.
85
+
82
86
  **Small, self-evident change** (add a flag, guard a branch, rename a symbol, bump a constant): collapse to a sentence or two stating what the PR adds/changes. Do not manufacture a "how the code behaves today" paragraph for a change whose intent already makes the diff obvious. That is padding, not scaffolding.
83
87
 
84
88
  **Medium change with non-obvious before/after mechanics**: three layers, in order. Each layer is as short as possible while remaining honest.
85
89
 
86
90
  1. **How the existing code behaves.** High-level narrative, not a function walk. Name the key components, their contract, and the specific behavior that matters for this change. Introduce decision points explicitly - they become anchors for layer 2.
87
91
  2. **What this PR changes.** For each decision point named in layer 1, say what it does now. Keep parallelism: same names, same order. The reader's mental model from layer 1 is the load-bearing structure here.
88
- 3. **Why that fixes PURPOSE.** One or two sentences linking the mechanics in layer 2 to the harm named in PURPOSE. If the fix has limits (doesn't cover case Z, follow-up needed), call them out here in one line.
92
+ 3. **Why that fixes PURPOSE** (when PURPOSE names a defect). One or two sentences stating the net effect as fact, linking the mechanics in layer 2 to the harm named in PURPOSE. When PURPOSE is a pivot statement - the change is the feature itself - omit this layer. If the fix has limits (doesn't cover case Z, follow-up needed), call them out here in one line.
89
93
 
90
94
  The structure is simple: PURPOSE names a harm, layer 1 names the mechanism, layer 2 changes the mechanism, layer 3 shows the harm is gone. Names carry through. The reader builds one model, not four.
91
95
 
@@ -93,6 +97,8 @@ The structure is simple: PURPOSE names a harm, layer 1 names the mechanism, laye
93
97
 
94
98
  Organize by **workflow or data-flow**, not by touched symbol. A bullet list of API changes reads as disconnected touchpoints. A walkthrough following one object through the system reads as a story. If the change touches a request lifecycle, follow the request. If it touches a data pipeline, follow the data.
95
99
 
100
+ **Forward scope: `### NEXT STEPS`.** When the PR hands off to a planned follow-up ticket, do not leave it inline in DESCRIPTION prose. End DESCRIPTION with a `### NEXT STEPS` subsection: what the follow-up does, framed by the economic property that justifies the split ("amortizing the cost of updates to the cache"), not just the raw number. Link the ticket when one exists. NOTES `Remaining work:` stays for unfinished work inside this PR.
101
+
96
102
  ### WALK-THROUGH
97
103
 
98
104
  When the change has a sequence of steps - a lifecycle, a workflow, a data flow through the system - split the mechanics into a numbered WALK-THROUGH. Keep DESCRIPTION to root-cause + design concept only.
@@ -143,7 +149,7 @@ Always a `## NOTES` markdown header. Never a bare `Notes:` line ending a section
143
149
  Use NOTES for:
144
150
  - Intentional behavior changes that look like regressions.
145
151
  - Scope disclaimers (what this PR does NOT do) - max two sentences, no hedging.
146
- - Deferred follow-up items (`Remaining work:` bullet list).
152
+ - Deferred follow-up items (`Remaining work:` bullet list) - planned forward work with its own ticket goes in a DESCRIPTION `### NEXT STEPS` subsection instead.
147
153
  - Ticket scope mismatches (when the PR partially addresses the ticket).
148
154
  - Anything AI reviewers (Cursor BugBot, CodeRabbit) will misread.
149
155
 
@@ -398,8 +404,8 @@ That reading alone conveys the shape and scope of both changes. The prose is for
398
404
  1. **Gather**: `git log $MERGE_BASE..HEAD`, `git diff $MERGE_BASE..HEAD --stat`, commit messages, current branch name. Apply the ticket resolution order (SYNOPSIS section) - infer first, ask only if nothing matches. Skim the diff; identify decision points (components or branches where behavior differs before vs after). Identify the workflow or lifecycle path if the change has one.
399
405
  2. **Read ticket context**: if a ticket is resolved, fetch its description. Use it to seed PURPOSE and identify reviewer-relevant plan changes: revisions, pivots caused by code reality, discoveries after the ticket was written, and deferred or unnecessary ticket items.
400
406
  3. **Read project context for stacked work**: if the PR refers to related PRs, follow-ups, deferred design, upstream dependencies, or unexplained project terms, read the linked source material before drafting. Build a private term map. If a term controls the PR and you cannot resolve it, ask the user.
401
- 4. **Draft PURPOSE first**, in one breath, without looking at code. If you cannot state the harm in two sentences, you do not understand the PR yet. Go back and read. When a ticket is available, seed PURPOSE from its problem statement, but verify against the code.
402
- 5. **Write DESCRIPTION**: choose the right depth (collapsed for small, three-layer for medium, root-cause + concept for large with WALK-THROUGH). Name everything you will refer to later. Organize by workflow/data-flow, not by touched symbol. Add a one- or two-sentence context bridge only when stacked-project context is needed to understand this PR's delta.
407
+ 4. **Draft PURPOSE first**, in one breath, without looking at code. If you cannot state the premise in one sentence and the feature story in a few more, you do not understand the PR yet. Go back and read. When a ticket is available, seed PURPOSE from its problem statement, but verify against the code.
408
+ 5. **Write DESCRIPTION**: choose the right depth (collapsed for small, three-layer for medium, root-cause + concept for large with WALK-THROUGH). Name everything you will refer to later. Organize by workflow/data-flow, not by touched symbol. Add a one- or two-sentence context bridge only when stacked-project context is needed to understand this PR's delta. End with a `### NEXT STEPS` subsection when a follow-up ticket continues the work.
403
409
  6. **Write WALK-THROUGH** (if needed): numbered steps in execution order. Bold the step leads. State what changed, not what you did.
404
410
  7. **Write SYNOPSIS last** - it is a compression of PURPOSE + the core mechanic.
405
411
  8. **Bold and italicize the save points** in the allowed locations. Bold phrases that name the change (components, mechanisms, behaviors). Italicize phrases that orient to significance (conclusions, payoffs), at most one per paragraph. Apply the scan check: read only the bold and italic fragments and verify the shape comes through.
@@ -407,22 +413,25 @@ That reading alone conveys the shape and scope of both changes. The prose is for
407
413
  10. **robots.txt only when earned**: do not add it during initial authoring. It is added later, in response to an automated reviewer's false positive (see its section). When you do add it, use the collapsed `<details>` format with `<summary>robots.txt</summary>`, keep the editing HTML comment at the top of the block, and keep the human-warning line first after the comment.
408
414
  11. **Verify links**: check ticket/PR links, repo file links, and external docs where tooling allows. Do not claim an unverified link was verified.
409
415
  12. **Verify prose style and core principle**: check each rule in Prose style. Cut design-narration, authoring-sequence, and buzzwordy abstractions. Verify plain ASCII. Verify first-use definitions for subsystem jargon. Verify project-private labels are translated before use. Verify plain English nouns (no code-domain nouns in prose). Verify conclusion-first in PURPOSE. Verify NOTES bullets are either short pointers or self-contained. Verify one-paragraph-one-physical-line for GitHub rendering. The robots.txt section is exempt from these prose rules below its human-warning line.
410
- 13. **Clarity pass**: read the draft as a cold reviewer skimming the PR. Rewrite before returning it; do not tell the user you performed this pass. Check: does every project-specific term get plain-English meaning before or with the label? Are ambiguous terms qualified or replaced with concrete behavior? Did you replace code/comment shorthand with reader-facing behavior when the shorthand is less clear? Does every process word name object/action/effect? Does every contrast name old and new behavior? Does every causal sentence show the middle step? Did you preserve connective tissue instead of compressing meaning into noun stacks? If the draft got longer, cut a lower-value claim instead of compressing a high-value one.
416
+ 13. **Clarity pass**: read the draft as a cold reviewer skimming the PR. Rewrite before returning it; do not tell the user you performed this pass. Check: does every project-specific term get plain-English meaning before or with the label? Are ambiguous terms qualified or replaced with concrete behavior? Did you replace code/comment shorthand with reader-facing behavior when the shorthand is less clear? Does every process word name object/action/effect? Does every contrast name old and new behavior? Does every causal sentence show the middle step? Did you preserve connective tissue instead of compressing meaning into noun stacks? Does the SYNOPSIS lead with the change (do X to fix Y, never Y alone)? Is the DESCRIPTION spine readable straight through, with no interleaved defenses? Is there any failure narrative or urgency argument left in PURPOSE? If the draft got longer, cut a lower-value claim instead of compressing a high-value one.
411
417
  14. **Verify length**: estimate the PR's complexity (see the point-based budgets in Length and style). If the body exceeds the budget, step up the abstraction ladder first (replace mechanism sequences with the principle that subsumes them). If still over, cut the lowest-value claim entirely. Do not compress terminology or cut connective tissue to hit a word count.
412
418
  15. **Submit** via `gh pr create --body "$(cat <<'EOF' ... EOF)"` to preserve formatting.
413
419
 
414
420
  ## Anti-patterns
415
421
 
416
422
  - **Burying the harm.** If PURPOSE is a paragraph of setup before the actual problem, rewrite. Problem first.
423
+ - **Failure-saga PURPOSE.** Narrating a sequence of failures or arguing urgency ("this PR prevents X before Y exists") to justify a change. State the defect or pivot in one factual sentence, teach the mechanism, and put hazard facts in NOTES.
417
424
  - **Layer 2 introduces new names.** If layer 2 mentions components layer 1 never named, the scaffold broke. Go back, add them to layer 1, or drop them from layer 2.
418
425
  - **Emphasizing filler words.** Bolding "this", "the", "we", or adjectives produces scan noise. Bold only nouns and verbs that carry meaning.
419
426
  - **Bolding filler or context.** Bolding history, hazard detail, justification, or connective tissue creates scan noise. Bold the change itself (components, mechanisms, behaviors); italicize the significance (conclusions, payoffs). Not the scaffolding around them.
420
427
  - **Over-italicizing.** More than one italicized phrase per paragraph dilutes the signal. Pick the one phrase that carries the paragraph's point.
421
428
  - **Design-doc DESCRIPTION.** If layer 1 is a function walkthrough or architecture essay, you are writing for yourself, not the reviewer. Compress to the decision points.
429
+ - **Interleaved defenses.** A DESCRIPTION where every change sentence is followed by two or three sentences of workaround detail. State what IS and walk the reader through; the unintuitive part earns one brief, high-level why; detailed workarounds are NOTES facts.
422
430
  - **Matching the diff line-by-line.** The description is orthogonal to the diff, not a prose projection of it.
423
431
  - **Over-claiming scope.** A fix for a sloppy implementation is "fix a sloppy implementation," not "harden the foo subsystem." Match energy to reality.
424
432
  - **Narrating your own process.** Any sentence about the order you did things, which commit does what, or what a later commit will add. Reads as AI slop. Change-as-it-stands only.
425
433
  - **Orphaned synopsis.** A bold lead paragraph with no `## SYNOPSIS` header above a body that does use headers. Add the header; it is mandatory.
434
+ - **Problem-only synopsis.** A TLDR that describes the problem being solved without stating what the change does. Even a safety fix leads with its behavior; the problem gets a brief clause after it.
426
435
  - **Bare `Notes:` line.** Use `## NOTES` header or omit the section entirely.
427
436
  - **Organizing by touched symbol.** A bullet list of "changed X in file A, changed Y in file B" reads as disconnected touchpoints. Follow the data flow.
428
437
  - **Buzzwordy abstractions.** "identity contract", "canonical hash", "check in isolation", "shared seam" - if it sounds like it is trying to sound smart, replace it with plain concrete wording. If "seam" means a conceptual border, say "boundary". If it means common code, say "shared helper", "shared function", or "shared SQL expression".
@@ -438,7 +447,6 @@ That reading alone conveys the shape and scope of both changes. The prose is for
438
447
  - **Implementation verbs in prose.** "Publish the key" reads as jargon to someone who does not know the method is called `publish`. Say "store the key in the in-memory cache".
439
448
  - **Code-domain nouns in prose.** "Row read", "waiter", "zeroized", "backlog" (as jargon) are technically English but belong in source files, not sentences. Replace with plain English: "reading the row from the database", "a request waiting for a worker", "overwritten with zeros", "the queued scan jobs".
440
449
  - **Conclusion buried at the end.** PURPOSE that builds to its point in the last sentence forces the reader to absorb the explanation before knowing what is being explained. Lead with the problem, then explain why.
441
- - **Hazard rating without motivation.** "No incident has hit this shape" answers "how bad?" but not "why review this?" Answer "why now?" so the reviewer can decide whether to invest time.
442
450
  - **NOTES bullets that are both wordy and terse.** A bullet that compresses a paragraph of rationale into one dense sentence reads as a private note. Either flag the thing and point to the full explanation, or write a self-contained sentence or two. Not the middle thing.
443
451
  - **robots.txt as a suppression lever.** Writing "ignore the finding about X" or "skip checking X" in robots.txt turns it into a rubber stamp and a prompt-injection surface. Explain what X does and the intent behind it so the reviewer clears its own false positive; never instruct it to stop looking.
444
452
  - **Preemptive robots.txt.** Adding the section before any automated reviewer has actually produced a false positive. It is a response to a demonstrated misread, not scaffolding.
@@ -41,12 +41,25 @@ Comments that reference the AI, the user, or the conversation:
41
41
  - Comments mentioning specific ticket numbers for resolved issues
42
42
  - Commented-out code with "// removed" or "// old" annotations
43
43
 
44
+ ### Metaphors standing in for the mechanism
45
+ A word that labels behavior the comment never states:
46
+ - "a quarantine" where the behavior is "re-enqueued rows get a fresh timestamp so they don't starve the rest"
47
+ - Euphemisms for plain terms: "not bumped by re-touches" instead of "later UPDATEs of the same row leave it unchanged"
48
+ The behavior replaces the metaphor. A metaphor verb attached to the stated mechanism is fine ("the sweep heals the dropped ids"); a metaphor noun doing the mechanism's job is not.
49
+
50
+ ### Telegraphic compression
51
+ Verbless clause chains or label-colon chains where full sentences belong:
52
+ - "failures wait out the backoff delay, idle cycles the short idle cadence, productive cycles nothing"
53
+ - "A claimed id with no recompute row was deleted or soft-deleted mid-drain: its cache row is deleted"
54
+ Full parallel sentences with the causal clause are the fix; the missing "because" is usually the point.
55
+
44
56
  ## What is NOT slop
45
57
 
46
58
  - Comments explaining *why* the code behaves a certain way
47
59
  - Comments explaining tradeoffs or design decisions
48
60
  - Comments explaining non-obvious behavior
49
61
  - Docstrings describing function contracts
62
+ - Load-bearing precision: pinned terms of art, genuine lists, error-class contracts, cross-references that carry the why
50
63
  - Legitimate TODOs for future work
51
64
  - User-visible strings whose tone/content is required by the feature or by an external protocol
52
65
  - Unchanged legacy text outside the touched scope unless the current change makes it newly wrong or newly suspicious
@@ -66,6 +79,8 @@ Do NOT report issues in files you did not actually read.
66
79
  - **CHANGE_NARRATION**: Comments describing the change being made, not the code's behavior
67
80
  - **FOURTH_WALL**: Comments referencing the AI, the user, or the conversation
68
81
  - **AI_STYLE_TELL**: Typography, verbosity, hedging, filler, superlatives, meta-commentary
82
+ - **MECHANISM_METAPHOR**: A metaphor or euphemism labeling behavior the comment never states
83
+ - **TELEGRAPHIC_COMPRESSION**: Verbless clause chains or label-colon chains where full sentences with the named cause belong
69
84
  - **STALE_ARTIFACT**: TODOs referencing completed work, commented-out code, resolved ticket references
70
85
 
71
86
  For slop findings, the source of truth is usually the project's writing norms and the surrounding code intent; use "N/A — mechanical finding" for the producer chain.
package/bin/thatch CHANGED
@@ -242,9 +242,11 @@ function emit(output: string, versionWarning: string | null, isJson: boolean, ch
242
242
  else console.log(output);
243
243
  }
244
244
 
245
- /** Resolve the project directory from host-specific env vars or cwd. */
245
+ /** Resolve the project directory from host-specific env vars or cwd.
246
+ * THATCH_PROJECT_DIR (pinned by project-local MCP configs) leads the
247
+ * chain, mirroring the server's resolution in src/mcp.ts. */
246
248
  function projectDir(): string {
247
- return process.env.CURSOR_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
249
+ return process.env.THATCH_PROJECT_DIR ?? process.env.CURSOR_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
248
250
  }
249
251
 
250
252
  // ---------------------------------------------------------------------------
@@ -1064,7 +1066,20 @@ switch (command) {
1064
1066
  // This is the primary delivery channel for version warnings because
1065
1067
  // flush-tools fires on every prompt, including conversational turns
1066
1068
  // with no tool calls.
1067
- const versionWarning = detectSkew(dbPath) ?? readNpmCacheForUpdate(dbPath);
1069
+ const skew = detectSkew(dbPath);
1070
+ const versionWarning = skew ?? readNpmCacheForUpdate(dbPath);
1071
+ // Server-stale gate (issue #6's residual): under SKEW the running
1072
+ // MCP server predates the upgrade, so its compiled-at-start tool
1073
+ // list may lack the tools the nudges reference. The extraction tier
1074
+ // is the only one that names a tool for the model to call
1075
+ // (get_extraction_payload) - it degrades to a single restart
1076
+ // instruction, and the missed-count is not incremented (the
1077
+ // escalation counter counts ignored nudges; none was delivered).
1078
+ // The queue is file-backed and peek-not-drain, so nothing is lost:
1079
+ // it extracts after the restart. Tiers 2/3 reference only
1080
+ // long-standing tools and the sideband is the old server's own warm
1081
+ // model - both keep firing under skew.
1082
+ const serverStale = skew !== null;
1068
1083
  // Chat check: with a hook session ID this always prints the caller's
1069
1084
  // assigned identity (and its unread count), so the model knows who it
1070
1085
  // is before any chat tool call; with no mail the line says so.
@@ -1090,6 +1105,15 @@ switch (command) {
1090
1105
  // not the full payload - the sub-agent fetches it via
1091
1106
  // get_extraction_payload.
1092
1107
  const interactions = sessionID ? peekQueue(sessionID) : [];
1108
+ if (interactions.length > 0 && serverStale) {
1109
+ emit(
1110
+ `[thatch] The MCP server is stale: the package was upgraded but this session still runs the old server, so extraction is paused until the session restarts. The ${interactions.length} queued interaction${interactions.length === 1 ? "" : "s"} are safe (file-backed) and will extract then. Restart the session to resume.`,
1111
+ versionWarning,
1112
+ isJson,
1113
+ chatLine,
1114
+ );
1115
+ break;
1116
+ }
1093
1117
  if (interactions.length > 0) {
1094
1118
  const missed = sessionID ? getMissedCount(sessionID) : 0;
1095
1119
  if (sessionID) incrementMissedCount(sessionID);
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="109" height="20" role="img" aria-label="Ask DeepWiki"><title>Ask DeepWiki</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="109" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="24" height="20" fill="black"/><rect x="24" width="85" height="20" fill="#007ec6"/><rect width="109" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><image x="5" y="3" width="14" height="14" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAPoAAAD6AG1e1JrAAACIUlEQVRYw+2XP2gUQRTGv7d3JhYWQawkIBaCVWzETos0gmAnVoKNTSoLK0E7EQQrC20sBVFBtLPQRgTBtBaKjYgIQUSxiJq7fT+LvCGPJZdNLt4dQj5Y3u7sznzfzLw/s9IO/icANiniCujEfWdsQgBLxAbsbraPZcmBfcAt4DNwKQsZNfEUsAB8YRV12LfA6ZHuedg7AO7ed/c+UBcbQhbiu+6wXIM6EnZWkszMJe2Ke0n6I2la0v51hFeSMLN6OwIK6jJwEBdUYb2MAxRSz6sY4geiahFgLe1Hwq6YWe3us8AN4FQQU4QM64SPY69Xyr43fADgNjAPXHb3peSsD4FDQ0VLEjAHvIhBPS6AHvA1bBO9JPAXcHFbIRtJ5yzwIQZ9CZwAusDJENIkLqsG8DpNyIZJwSUk9wLHgakcesC1JCCjiHm1kYDNOEjptCxpSVK/8f73OFLxPLAYM3oCzEX7MXf/lJbc/8kWpA4HgQdpYI9IWAbeh83whi84cHXL+58EPBoQhnmm94EzwE3gZ2p/Dhzdbhg+TaRr01x7ftb4/jBwFzjXrCvDpmLW9crVLNeR9CaapoGemb2TdCERW1tNaBNQ8jktwmozqwtpqRNtdWAjARYk39IS12ZWRX7vmJmA71nQZgi36gN7gCvAj5xc3P0jcH7kx7Ik5IC73wsh14GZsZySow5002l4Jr0b6+m4eSyvpAn8lEzsx2QHo8RfUrlN+uPq4ksAAAAASUVORK5CYII="/><text aria-hidden="true" x="655" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="750">Ask DeepWiki</text><text x="655" y="140" transform="scale(.1)" fill="#fff" textLength="750">Ask DeepWiki</text></g></svg>
@@ -45,6 +45,10 @@ OpenCode plugin path
45
45
  │ lazy-imports the adapter matching the host
46
46
  ├── opencode/v1.ts → v1 adapter: @opencode-ai/plugin hooks object
47
47
  ├── opencode/v2.ts → v2 adapter: @opencode/plugin promise-context domains
48
+ ├── opencode/tui-plugin.ts → v2 TUI CLI plugin (./tui entrypoint):
49
+ │ consumes the session-tab rpc events, drives ui.tabs
50
+ ├── session-tab-shared.ts → SDK-free cross-entry contract: session-tab rpc
51
+ │ definition, subordinate prompt builder, validators
48
52
  ├── capabilities.ts → HostCapabilities seam: the host operations the
49
53
  │ runtime needs; capabilitiesFromClient bridges the
50
54
  │ v1 SDK client
@@ -85,7 +89,8 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
85
89
  | `tools.ts` | Thin opencode wrappers. `buildCoreContext` constructs the shared per-call context; `createTools` wraps each tool-def in opencode's `tool()` with a `thatch_` prefix (v1 only). |
86
90
  | `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). |
87
91
  | `index.ts` | Dual-shape OpenCode plugin entry: the merged default export (`{ id, setup, server }`) loads on opencode v1 (reads `default.server`) and v2 (reads `default.setup`, strips excess keys). Lazy-imports the host adapter so each host's SDK resolves only under its own runtime. |
88
- | `opencode/v1.ts` + `opencode/v2.ts` | Host adapters. v1 builds HostCapabilities from the PluginInput client and returns the hooks object; v2 registers through the promise-context domains (ToolEditor, session.hook, event.subscribe) and degrades the surfaces v2 lacks. |
92
+ | `opencode/v1.ts` + `opencode/v2.ts` | Host adapters. v1 builds HostCapabilities from the PluginInput client and returns the hooks object; v2 registers through the promise-context domains (ToolEditor, session.hook, event.subscribe) and degrades the surfaces v2 lacks. v2 also wires the session-tab host flow (create -> move -> chat-register -> rpc emit -> prompt) through the `CoreContext.sessionTabHost` seam. |
93
+ | `opencode/tui-plugin.ts` | The v2 TUI CLI plugin (the package's `./tui` entrypoint): listens for `rpc.thatch-tabs.tab-opened` and `rpc.thatch-tabs.tab-closed` events and opens/closes the subordinate session's tab via `ui.tabs.open`/`ui.tabs.close`. See [features/session-tabs.md](features/session-tabs.md). |
89
94
  | `capabilities.ts` | The `HostCapabilities` seam: the host operations the shared runtime needs (session create/prompt/get/list/messages/delete, toast, compaction trigger, app exit). `capabilitiesFromClient` bridges the v1 SDK client. |
90
95
  | `runtime.ts` | Shared plugin runtime: wires DB, model, extraction; the nudge tiers, system prompt injection, session event handling, child-session bookkeeping, wrap-up resolution. Host-agnostic: consumed by both adapters. |
91
96
  | `os-args.ts` | Pure argv / OS-command-line helpers for startup session resolution (`-s`/`-c`). SDK-free. |
@@ -98,7 +103,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
98
103
  | `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`. |
99
104
  | `config.ts` | User config file (`~/.config/thatch/config.json`, beside the DB): Zod section schemas (strict — unknown keys fail validation), load with missing/invalid fallback to defaults, field-level merge, atomic save (temp + rename). Backs the `config_get`/`config_set` tools. |
100
105
  | `notify.ts` | Out-of-band notification dispatch: darwin (`osascript` banner + `/usr/bin/say` voice), linux (`notify-send` + `spd-say`/`espeak`), other platforms report unsupported. Injectable spawner (`CoreContext.spawner`) so tests never fire real commands. Backs the `notify_user` tool and the alert state machine. |
101
- | `alerts.ts` | LLM alert state machine (opencode-only): pause/done/error decisions from session events, per-session dedup (no timer-based debounce - one alert per busy->idle transition, per request id for pauses). Fed by `runtime.ts`'s event hook; delivers through `notify.ts`. Plain memory by design (a reload loses at most one transition). See [../plans/llm-alerts.md](../plans/llm-alerts.md). |
106
+ | `alerts.ts` | LLM alert state machine (opencode-only): pause/done/error decisions from session events, per-session dedup (no timer-based debounce - one alert per busy->idle transition, per request id for pauses). Fed by `runtime.ts`'s event hook; delivers through `notify.ts`. Plain memory by design (a reload loses at most one transition). See [features/notifications.md](features/notifications.md). |
102
107
  | `session-db.ts` | Read-only access to the opencode session database (opencode.db): session timeline (`session list`), full part/message retrieval (`session get`), OpenAI chat-completions transcript builder (`session transcript`), and decoded-content search (`session search`). Backs the `thatch session` CLI subcommands and the opencode-only `session_search`/`session_get` tools. Path resolution honors `OPENCODE_DB`, then the XDG default; the db opens read-only so a thatch bug cannot corrupt host history. |
103
108
  | `watchers.ts` | In-memory watcher registry and background poller for event-driven notifications (opencode-only). Three sources: GitHub PRs and branches via `gh api` (injected `GhRunner`), and local shell commands via `bash -c` (injected `CommandRunner`) run as condition variables - only the exit code is read, never the command's output. Diffs against last-seen state, queues events (pointer data plus machine status like check conclusions and exit codes, never external content) per session, and delivers them through a plugin-supplied callback when the session is idle. The `pollSeconds` getter surfaces the poll cadence to tool output and notifications. Process-lifetime state by design - no SQLite. |
104
109
  | `chat.ts` | Cross-session chat between opencode sessions on one machine (opencode-only). `ChatStore` holds the shared directory and inbox in SQLite so any process can read/write them; `ChatPoller` heartbeats hosted sessions and delivers wake prompts through an injected callback when the recipient is idle - only the recipient's host process delivers, so there is no cross-process double-delivery. Heartbeat staleness flags crashed sessions; a per-recipient nudge rate cap is the anti-loop hard brake. See [features/cross-session-chat.md](features/cross-session-chat.md). |
@@ -88,6 +88,8 @@ Read-only window on the cross-session chat directory ([cross-session-chat.md](cr
88
88
  | `THATCH_CHILD_RECONCILE_MS` | Reload window before a restored extraction child is judged finished (test knob) | `10000` |
89
89
  | `CLAUDE_PROJECT_DIR` | Claude Code project dir | `process.cwd()` |
90
90
  | `CURSOR_PROJECT_DIR` | Cursor project dir | falls through |
91
+ | `THATCH_WATCH_DEATH_MINUTES` | Pending-age threshold for watcher death detection | `120` |
92
+ | `THATCH_PROJECT_DIR` | Pinned project dir (written by project-local `thatch setup` MCP configs) | falls through |
91
93
  | `CLAUDE_CONFIG_DIR` | Claude config dir | `~/.claude` |
92
94
  | `CURSOR_CONFIG_DIR` | Cursor config dir | `~/.cursor` |
93
95
  | `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | Enable async extraction | unset |