@jeffober/thatch 0.2.6 → 0.2.7
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/artifacts/skills/thatch-clear-writing.md +11 -0
- package/artifacts/skills/thatch-code-review.md +10 -8
- package/artifacts/skills/thatch-review-clarity.md +101 -0
- package/artifacts/skills/thatch-review-followup.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +1 -1
- package/bin/thatch +90 -29
- package/docs/dev/features/cli.md +22 -7
- package/docs/dev/features/commands.md +2 -2
- package/docs/dev/features/cross-session-chat.md +15 -7
- package/docs/dev/skills.md +2 -1
- package/docs/user/README.md +3 -2
- package/docs/user/cli.md +28 -16
- package/docs/user/code-review.md +4 -3
- package/docs/user/cross-session-chat.md +5 -4
- package/docs/user/skills.md +1 -0
- package/package.json +1 -1
- package/src/chat.ts +17 -7
- package/src/commands.ts +1 -1
- package/src/prompts.ts +1 -0
- package/src/tool-defs.ts +14 -2
|
@@ -168,6 +168,17 @@ The wart shape, for code that looks wrong but is correct:
|
|
|
168
168
|
Simple and good beats complex and great. Cut whole claims to shorten;
|
|
169
169
|
never compress the survivors.
|
|
170
170
|
|
|
171
|
+
## Documentation files
|
|
172
|
+
|
|
173
|
+
Standalone documentation - package docs (a doc.go or package comment), READMEs, and anything under a docs/ directory - serves readers that symbol-anchored text never meets: someone onboarding and an expert looking up one fact, in the same pass. An inline comment or a docstring has the code as its context. A standalone doc has only its own order, so the standard is higher: clear sentences are the floor; the document must also serve the skimmer and the learner.
|
|
174
|
+
|
|
175
|
+
- **The inverted pyramid, at the document and the section level.** Lead with the answer at the top of the doc, and again at the top of each section: what the thing is and what the reader gets, first; the mechanism after; caveats and edge cases last. The reader decides when to stop reading, so stopping early must be safe.
|
|
176
|
+
- **Basics before advanced.** Each section stands on the ones before it. A reader who knows the basics can stop or skip ahead; a reader who does not gets each idea in the order they can absorb it.
|
|
177
|
+
- **Scan anchors.** Bold the load-bearing phrases so a reader who skims only the bold fragments still gets the story; the first sentence of each section carries the section's point. Bold sparingly outside the opening sections; a doc bolded throughout cannot be skimmed.
|
|
178
|
+
- **Presentation, not specificity.** These rules demand better order, never more content. Judge a doc against its own level of detail; do not add enumerations or file pointers the doc's level never promised.
|
|
179
|
+
|
|
180
|
+
No section template follows from this. Headings and structure belong to the repo's own conventions; apply the intent, not a shape.
|
|
181
|
+
|
|
171
182
|
## Clarity pass
|
|
172
183
|
|
|
173
184
|
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.
|
|
@@ -58,7 +58,7 @@ Dispatch a sub-agent using the Task tool to research the code workflows affected
|
|
|
58
58
|
|
|
59
59
|
The sub-agent produces a code guide with one section per affected workflow or feature. Wait for it to complete and collect the guide.
|
|
60
60
|
|
|
61
|
-
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
|
|
61
|
+
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 ten specialist lenses.
|
|
62
62
|
|
|
63
63
|
If the context brief includes a sibling register (from Step 2's related-PR discovery), fold it into the guide per the `Cross-PR context` section of the thatch-code-archaeology skill — whether you dispatch a sub-agent or do this research inline. The traps the guide exists to catch are comments referencing sibling work, contracts consumed from a sibling, and ordering assumptions across the stack or train.
|
|
64
64
|
|
|
@@ -90,7 +90,7 @@ For changes estimated at 3 points or fewer, skip partitioning — dispatch one s
|
|
|
90
90
|
|
|
91
91
|
## Step 6: Dispatch specialist sub-agents
|
|
92
92
|
|
|
93
|
-
For each review unit, dispatch sub-agents using the Task tool. Each sub-agent runs one specialist lens on one review unit. The
|
|
93
|
+
For each review unit, dispatch sub-agents using the Task tool. Each sub-agent runs one specialist lens on one review unit. The ten specialists are:
|
|
94
94
|
|
|
95
95
|
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.
|
|
96
96
|
|
|
@@ -102,13 +102,15 @@ For each review unit, dispatch sub-agents using the Task tool. Each sub-agent ru
|
|
|
102
102
|
|
|
103
103
|
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.
|
|
104
104
|
|
|
105
|
-
6. **
|
|
105
|
+
6. **Clarity** — prose clarity against the project's clear-writing rules, regardless of who wrote the text: made-up phrases, buzzwords, untranslated jargon, clause chains, buried leads, doc/code responsibility splits, plus document-level presentation in standalone docs (the inverted pyramid, scan anchors, basics before advanced). Dispatch a sub-agent with instructions to: read every changed file in full plus every changed doc, scan all comments/docstrings/error strings/UI strings/doc paragraphs for reader-cost prose, check standalone docs that are new or substantially rewritten for structure, report each finding with the plain-English fix (the replacement, the split, or the claim to cut).
|
|
106
106
|
|
|
107
|
-
7. **
|
|
107
|
+
7. **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.
|
|
108
108
|
|
|
109
|
-
8. **
|
|
109
|
+
8. **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.
|
|
110
110
|
|
|
111
|
-
9. **
|
|
111
|
+
9. **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.
|
|
112
|
+
|
|
113
|
+
10. **Technique** — teaching-grade lens on better ways to do it: helpers and internal packages the author may not have seen, companion techniques, house patterns, test-craft, API-shape principles, next-reader discoverability. Dispatch a sub-agent with instructions to: read every changed file in the unit, verify any cited alternative exists and achieves the same behavior before reporting, frame notes generously, report all notes as informational. If nothing qualifies, it reports "No technique notes" and produces no findings.
|
|
112
114
|
|
|
113
115
|
For the integration review unit (5+ points), dispatch a sub-agent focused on:
|
|
114
116
|
- Cross-component contracts — do the interfaces between components match?
|
|
@@ -137,7 +139,7 @@ Pass the synthesizer:
|
|
|
137
139
|
|
|
138
140
|
The final user-facing report MUST begin with a concise explanation of the workflow(s) this PR changes before listing findings. Use one `### Workflow changes` section, with one subheading per affected workflow when there are multiple. Use the before/now walkthrough shape from the thatch-pr-description skill: numbered step = existing behavior; `NOW` sub-bullet = what the PR changes; `N/A` for new stages. This gives the user a map of the change before the findings. Keep it short: 3-6 steps per workflow.
|
|
139
141
|
|
|
140
|
-
Confirmed LOW findings are mandatory in the final report, including mechanical findings (pedantic, no-slop, breadcrumbs, docs, naming, style, comments, structural conventions). Do not omit them for being non-functional.
|
|
142
|
+
Confirmed LOW findings are mandatory in the final report, including mechanical findings (pedantic, no-slop, clarity, breadcrumbs, docs, naming, style, comments, structural conventions). Do not omit them for being non-functional.
|
|
141
143
|
|
|
142
144
|
Confirmed highlights (from the highlights specialist) appear in a `### Highlights` section after the workflow changes and before the confirmed findings. Most PRs will have none — that is expected. Do not pad with borderline calls. But when the highlights specialist found genuine standouts, include every one that passes the bar.
|
|
143
145
|
|
|
@@ -164,7 +166,7 @@ When dispatching each sub-agent, include in the prompt:
|
|
|
164
166
|
- The git range to review
|
|
165
167
|
- **The resolved head SHA of the range** — all file reads, file:line citations, and anchors MUST be computed against this ref (`git show <head-sha>:<path>`), never the working tree, which may be on a different branch than the change under review
|
|
166
168
|
- The specific files in this unit's scope
|
|
167
|
-
- The specialist focus (from the
|
|
169
|
+
- The specialist focus (from the ten specialists above)
|
|
168
170
|
- The diff stat for this unit's files
|
|
169
171
|
- **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.
|
|
170
172
|
- **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,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: thatch-review-clarity
|
|
3
|
+
description: Prose clarity in the change's comments, docstrings, error strings, and docs - made-up phrases, buzzwords, untranslated jargon, clause chains, buried leads, doc/code responsibility splits - plus document-level presentation in standalone docs, the inverted pyramid, scan anchors, basics before advanced - checked against the project's clear-writing rules regardless of who wrote the text. Use for post-implementation review of a branch, PR, or commit range.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a prose clarity review agent. Your sole job is to check the human-facing text in the change - code comments, docstrings, error messages, UI strings, and documentation - against the project's clear-writing rules.
|
|
7
|
+
|
|
8
|
+
A clarity defect is reader cost, regardless of who wrote the text. Dense jargon a human wrote fails exactly like the same jargon from an AI. Do not guess at authorship; that is the no-slop lens's job.
|
|
9
|
+
|
|
10
|
+
${REVIEW_COMMON}
|
|
11
|
+
|
|
12
|
+
## The rules
|
|
13
|
+
|
|
14
|
+
The full rule set is the thatch-clear-writing skill. The checks below are its review form. When both are loaded and they disagree, the skill wins.
|
|
15
|
+
|
|
16
|
+
The reader is an engineer who knows the stack and general engineering vocabulary and has never touched this area. Every sentence must parse on first read. The concepts stay technical; the language stays simple.
|
|
17
|
+
|
|
18
|
+
Brevity means fewer claims, not denser claims. When prose runs long, the fix is deleting a whole claim. Never report a fix that tightens a surviving sentence; tightening is what produces clause chains.
|
|
19
|
+
|
|
20
|
+
## Categories
|
|
21
|
+
|
|
22
|
+
### Made-up phrases
|
|
23
|
+
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, the label is the finding. The fix names what the thing does: what runs, what changes, what the reader gets.
|
|
24
|
+
|
|
25
|
+
### Buzzwords
|
|
26
|
+
"Leverage", "utilize", "facilitate", and any phrase that cannot be restated in plain English. If a phrase sounds like it is trying to sound smart, that is the finding. The fix uses the plain word or states the behavior.
|
|
27
|
+
|
|
28
|
+
### Untranslated jargon
|
|
29
|
+
A project-private label doing an explanation's job. Roadmap names, subsystem nicknames, ticket shorthand, and branch-local labels are not explanations. Say what the code does first (what reads, writes, blocks, retries, changes); the label may follow if it helps the reader connect prose to code. Ticket refs in place of behavior are this category: "the backfill worker", not "the TICKET-123 backfill worker". Code-domain nouns in prose are this category too: "a request waiting for a worker" not "a waiter".
|
|
30
|
+
|
|
31
|
+
### Clause chains
|
|
32
|
+
Multiple ideas crammed into one sentence, or noun stacks doing verb work: "the row read", "the waiter". The fix splits the sentence into full parallel sentences, each with its causal clause, or deletes a whole claim. Verbless chains (the telegraphic form) belong to the no-slop lens.
|
|
33
|
+
|
|
34
|
+
### Buried leads
|
|
35
|
+
A doc or docstring that saves the point for the end. Lead with the answer. Then the evidence. Then the caveats. The reader decides when to stop reading, so stopping early must be safe. Reading-order narration ("This guide will first cover X, then Y") opens with process instead of the point and is this category too.
|
|
36
|
+
|
|
37
|
+
### Doc structure failures
|
|
38
|
+
Standalone docs only (package docs, READMEs, docs/): the document's order fails the reader even when every sentence is clear.
|
|
39
|
+
- A wall of paragraphs with no scan anchors: no bold on the load-bearing phrases, section leads that do not carry the section's point, so a skimmer cannot find what they came for.
|
|
40
|
+
- Basics and advanced detail interleaved, so the reader who knows the basics cannot skip ahead and the reader who does not gets ideas out of order.
|
|
41
|
+
|
|
42
|
+
Structure findings apply to docs that are new or substantially rewritten in the change; a lightly touched doc gets only its touched sections checked. No section template is imposed: the repo's own headings and conventions are never the finding. Check the order and the scan path within whatever shape the repo uses.
|
|
43
|
+
|
|
44
|
+
### Doc/code responsibility splits
|
|
45
|
+
Implementation detail in a README or package doc, where the doc comment next to the code owns the mechanism; or a guide-level essay in a code comment, where the README owns the overview. A README is not "the code but in english". State the invariant the design buys in the comment; keep the README a high-level guide.
|
|
46
|
+
|
|
47
|
+
### Redundant glosses
|
|
48
|
+
Parentheticals and asides that restate the sentence, the adjacent code, or what the surrounding convention already guarantees, instead of adding a distinct fact. Keep "(or has been killed on)". Cut "(the worker runs when the flag is unset)" after "It defaults to enabled". Do not document de facto conventions: if the framework or the migrations make something the default rule, the comment does not say so.
|
|
49
|
+
|
|
50
|
+
## Reader model
|
|
51
|
+
|
|
52
|
+
Symbol-anchored text and standalone docs fail differently, and the same text can be correct in one and a defect in the other.
|
|
53
|
+
|
|
54
|
+
- **Inline comments and symbol docstrings** (a function, class, type, or constant doc comment) anchor to the immediate code. They may not re-explain visible code. One hop out (the caller, the callee, a guard, another writer) is the comment's context budget.
|
|
55
|
+
- **Standalone docs** (package docs like a doc.go, READMEs, anything under a docs/ directory) serve readers that symbol-anchored text never meets: someone onboarding and an expert looking up one fact, in the same pass. Background and context that a comment must not carry are correct here; restating the same claim in multiple sections is still a defect. The sentence rules are the floor; the document must also serve the skimmer and the learner: the inverted pyramid at the document and section level, scan anchors (a reader who skims only the bold fragments still gets the story), and Basics before advanced (each section stands on the ones before it, so the reader who knows the basics can skip ahead and the reader who does not gets each idea in order).
|
|
56
|
+
|
|
57
|
+
## What is NOT a finding
|
|
58
|
+
|
|
59
|
+
- Genuine lists. An enumeration the reader needs is not a clause chain.
|
|
60
|
+
- Load-bearing precision: pinned terms of art, error-class contracts (Postgres classes 40 and 55), SQL shapes in comments, cross-references that carry the why.
|
|
61
|
+
- A term of art defined once and used afterwards. Defined terms are not untranslated jargon.
|
|
62
|
+
- A metaphor verb attached to a stated mechanism ("the sweep heals the dropped ids", after the sweep's behavior is stated).
|
|
63
|
+
- First-person informality: "This should not be possible. If we screwed up, ..." is the right voice, not a defect.
|
|
64
|
+
- Wart comments: code that looks wrong but is correct, explained by naming the expected implementation, the crux, and why. This is the right shape, not sloppy prose.
|
|
65
|
+
- A parenthetical that adds a distinct fact. These are placement rules, not presence bans: report the restatement or the dominance pattern, never the element itself.
|
|
66
|
+
- Comments explaining why, tradeoffs, or non-obvious behavior.
|
|
67
|
+
- UI strings whose tone is required by the feature or by an external protocol.
|
|
68
|
+
- Presentation, not specificity. A doc-structure finding demands better order, never more content. Judge a doc against its own level of detail; do not report a doc for lacking enumerations or file pointers its own specificity never promised.
|
|
69
|
+
- Unchanged legacy text outside the touched scope, unless the current change makes it newly wrong or newly suspicious.
|
|
70
|
+
|
|
71
|
+
## Boundary with the other lenses
|
|
72
|
+
|
|
73
|
+
- The **no-slop** lens owns AI-authorship tells: typography (em dashes, smart quotes), hedging, filler, verbosity, change narration, fourth wall breaks, stale artifacts, mechanism metaphors, and verbless telegraphic chains. Do not report those here. When a sentence is both unclear and an AI tell, leave it to no-slop.
|
|
74
|
+
- The **breadcrumbs** lens owns narrative coherence: do the comments together tell the code's story? A missing comment is theirs. An unclear present comment is yours.
|
|
75
|
+
- The **pedantic** lens owns factual accuracy and the repo's structural conventions (file placement, heading conventions): wrong names, wrong file refs, stale docs. Clear but wrong is theirs. Right but unreadable is yours. A violation of the repo's own doc conventions is theirs, never a structure finding here.
|
|
76
|
+
|
|
77
|
+
## Method
|
|
78
|
+
|
|
79
|
+
1. Use the diff stat from your scope gathering to identify changed files.
|
|
80
|
+
2. For EVERY changed file, read the full current version. For changed markdown docs, read the full doc.
|
|
81
|
+
3. Scan every comment, docstring, error message string, UI string, and doc paragraph.
|
|
82
|
+
4. For standalone docs that are new or substantially rewritten in the change, also check the document: the inverted pyramid at the document and section level, scan anchors, Basics before advanced.
|
|
83
|
+
5. For each violation, report the exact quoted text and the fix: the plain-English replacement, the split, or the claim to cut.
|
|
84
|
+
|
|
85
|
+
Do NOT report on factual accuracy, narrative gaps, or AI tells. Only clarity.
|
|
86
|
+
Do NOT report issues in files you did not actually read.
|
|
87
|
+
|
|
88
|
+
Most findings are LOW. MEDIUM when the unclear prose can mislead a reader about behavior: a made-up phrase that names the wrong owner, a gloss that hides an UPDATE, a buried lead that hides a breaking change, or a doc whose structure hides the one fact the reader came for.
|
|
89
|
+
|
|
90
|
+
## Category taxonomy
|
|
91
|
+
|
|
92
|
+
- **MADE_UP_PHRASE**: A coined multi-word term that exists nowhere; the behavior must replace the label
|
|
93
|
+
- **BUZZWORD**: A phrase that cannot be restated in plain English
|
|
94
|
+
- **UNTRANSLATED_JARGON**: A project-private label, ticket ref, or code-domain noun doing an explanation's job
|
|
95
|
+
- **CLAUSE_CHAIN**: Multiple ideas in one sentence, or noun stacks doing verb work; the fix splits or deletes whole claims
|
|
96
|
+
- **BURIED_LEAD**: A doc or docstring that saves the point for the end, or opens with reading-order narration
|
|
97
|
+
- **DOC_STRUCTURE**: A standalone doc whose order fails the skimmer or the learner (no scan anchors, interleaved basics and advanced detail, section leads off-point); the repo's own conventions are never the finding
|
|
98
|
+
- **DOC_CODE_SPLIT**: Implementation detail in a README or package doc, or a guide-level essay in a code comment
|
|
99
|
+
- **REDUNDANT_GLOSS**: A parenthetical or aside that restates the sentence, the adjacent code, or a framework default
|
|
100
|
+
|
|
101
|
+
For clarity findings, the source of truth is the project's clear-writing rules and the surrounding code intent; use "N/A — mechanical finding" for the producer chain.
|
|
@@ -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 ten 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. Cross-references prior review comments and sibling PR registers when provided. 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, economy, no-slop, breadcrumbs, mark-and-sweep, highlights, technique). 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, clarity, breadcrumbs, mark-and-sweep, highlights, technique). 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.
|
package/bin/thatch
CHANGED
|
@@ -18,13 +18,25 @@ import { createDebugLog } from "../src/debug";
|
|
|
18
18
|
|
|
19
19
|
const configHome = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
20
20
|
const dbPath = process.env.THATCH_DB_PATH ?? join(configHome, "thatch", "thatch.db");
|
|
21
|
-
const db: ThatchDB = new ThatchDB(dbPath, { chatOpenTabs: chatOpenTabsProvider(() => db.chatTabClosures()) });
|
|
22
21
|
|
|
23
|
-
const
|
|
22
|
+
const argv = process.argv.slice(2);
|
|
23
|
+
|
|
24
|
+
// Help is checked before the DB is opened or any subcommand runs, and
|
|
25
|
+
// against the WHOLE argument list - not just the leading flags - so
|
|
26
|
+
// `thatch chat list -h` and `thatch -h chat list` print usage instead of
|
|
27
|
+
// executing. Asking for help is not an error: exit 0, where a bad
|
|
28
|
+
// command still exits 1.
|
|
29
|
+
if (argv.includes("-h") || argv.includes("--help")) usage(0);
|
|
30
|
+
|
|
31
|
+
const [command, ...args] = argv;
|
|
32
|
+
|
|
33
|
+
const db: ThatchDB = new ThatchDB(dbPath, { chatOpenTabs: chatOpenTabsProvider(() => db.chatTabClosures()) });
|
|
24
34
|
|
|
25
|
-
function usage(): never {
|
|
35
|
+
function usage(exitCode = 1): never {
|
|
26
36
|
console.error(`USAGE
|
|
27
37
|
thatch --version, -v Print version and exit
|
|
38
|
+
thatch -h, --help Print this usage and exit (the flag
|
|
39
|
+
works anywhere in the command line)
|
|
28
40
|
thatch stores List all stores
|
|
29
41
|
thatch list [store] List memory labels in a store
|
|
30
42
|
thatch show <label> [store] Display a memory by label
|
|
@@ -52,13 +64,16 @@ function usage(): never {
|
|
|
52
64
|
Full conversation as OpenAI chat-completions JSONL
|
|
53
65
|
thatch session search <query> [--session <id>] [--regex] [--limit N]
|
|
54
66
|
JSONL of matching parts across sessions (decoded content)
|
|
55
|
-
thatch chat list [--stale N|all]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
thatch chat list [--recent] [--stale N|all]
|
|
68
|
+
Registered chat sessions. Default shows
|
|
69
|
+
only open sessions (plus MCP rows, which
|
|
70
|
+
read mail at their next prompt): name,
|
|
71
|
+
session id, age, status, project, topic.
|
|
72
|
+
Tab-less fresh sessions collapse into a
|
|
73
|
+
hidden-count note; --recent shows them.
|
|
74
|
+
--stale adds the stale section: N bounds
|
|
75
|
+
it to sessions seen in the last N days
|
|
76
|
+
(default 1); all shows every stale one.
|
|
62
77
|
thatch chat tail [--once] [--limit N|all] [--match RE]
|
|
63
78
|
[--from NAME] [--to NAME] [--since DT] [--until DT]
|
|
64
79
|
Follow cross-session chat as a JSONL
|
|
@@ -75,7 +90,7 @@ function usage(): never {
|
|
|
75
90
|
|
|
76
91
|
Stores default to the repo detected from git remote. Use "global" for the
|
|
77
92
|
global store. Use "all" with search to search project + global together.`);
|
|
78
|
-
process.exit(
|
|
93
|
+
process.exit(exitCode);
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
async function defaultStore(): Promise<string> {
|
|
@@ -425,29 +440,42 @@ async function sessionCommand(rest: string[]): Promise<void> {
|
|
|
425
440
|
const CHAT_ROSTER_HEADER = ["NAME", "SESSION ID", "AGE", "STATUS", "PROJECT", "TOPIC"];
|
|
426
441
|
|
|
427
442
|
/**
|
|
428
|
-
* Renders the `chat list` roster as
|
|
429
|
-
* an aligned-column table under a header row. Section headers
|
|
430
|
-
* the
|
|
431
|
-
* green/red status, and a muted italic topic.
|
|
432
|
-
* the liveness verdict: open (tab open now),
|
|
433
|
-
* tab), active/idle (MCP rows), stale (host
|
|
443
|
+
* Renders the `chat list` roster as three sections - Active, Recent, and
|
|
444
|
+
* Stale - each an aligned-column table under a header row. Section headers
|
|
445
|
+
* are bold, the Recent and Stale explainers are muted italic; rows use
|
|
446
|
+
* green names, dim age, green/yellow/red status, and a muted italic topic.
|
|
447
|
+
* The STATUS column carries the liveness verdict: open (tab open now),
|
|
448
|
+
* recent (host alive, no open tab), active/idle (MCP rows), stale (host
|
|
449
|
+
* presumed gone).
|
|
450
|
+
* showRecent = false (the CLI default without `--recent`) collapses the
|
|
451
|
+
* Recent section into a muted hidden-count note pointing at the flag.
|
|
452
|
+
* Unlike the Stale hide - which the caller does by pre-filtering rows
|
|
453
|
+
* before this function, dropping them silently - the recent hide needs
|
|
454
|
+
* the rows in hand to count them, so it happens here as a note. An
|
|
455
|
+
* all-recent roster therefore renders the note alone, with no section
|
|
456
|
+
* header: there is no Recent section on the page to head, while the
|
|
457
|
+
* stale hidden note rides under a header because its section IS
|
|
458
|
+
* displayed;
|
|
434
459
|
* staleCapMs bounds how old a stale row is displayed (null = no bound);
|
|
435
460
|
* rows past the cap collapse into a muted hidden-count note so the reader
|
|
436
461
|
* knows rows exist that the window left out.
|
|
462
|
+
* now is the render's single clock sample: the split, every per-row
|
|
463
|
+
* verdict, and the caller's pre-filter must agree, or a row aging past
|
|
464
|
+
* the staleness boundary mid-render could land in two sections at once.
|
|
437
465
|
* Color is applied only when stdout is an interactive terminal (and
|
|
438
466
|
* NO_COLOR is unset), so piped output - grep, redirect, QA runners - stays
|
|
439
467
|
* clean plain text.
|
|
440
468
|
*/
|
|
441
|
-
function formatChatRoster(sessions: ChatSessionRow[], staleCapMs: number | null = null, openTabs?: ReadonlySet<string
|
|
469
|
+
function formatChatRoster(sessions: ChatSessionRow[], staleCapMs: number | null = null, openTabs?: ReadonlySet<string>, showRecent = true, now = Date.now()): string {
|
|
442
470
|
const colored = process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
443
471
|
const paint = (code: string, s: string) => (colored ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
444
|
-
const { active, stale, staleHidden } = splitChatRoster(sortChatRoster(sessions),
|
|
472
|
+
const { active, recent, stale, staleHidden } = splitChatRoster(sortChatRoster(sessions), now, staleCapMs, openTabs);
|
|
445
473
|
const renderTable = (rows: ChatSessionRow[]) => {
|
|
446
474
|
const cells = (r: ChatSessionRow) => [
|
|
447
475
|
r.name,
|
|
448
476
|
r.session_id,
|
|
449
477
|
humanAge(r.last_seen),
|
|
450
|
-
chatLiveness(r,
|
|
478
|
+
chatLiveness(r, now, openTabs),
|
|
451
479
|
r.project ?? "unknown",
|
|
452
480
|
r.topic ?? "",
|
|
453
481
|
];
|
|
@@ -476,6 +504,20 @@ function formatChatRoster(sessions: ChatSessionRow[], staleCapMs: number | null
|
|
|
476
504
|
if (active.length > 0) {
|
|
477
505
|
parts.push([paint("1", "# Active Sessions"), renderTable(active)].join("\n"));
|
|
478
506
|
}
|
|
507
|
+
if (recent.length > 0) {
|
|
508
|
+
if (showRecent) {
|
|
509
|
+
const explainer = "These sessions have a fresh heartbeat but no open tab, so a wake may not reach them until their tab reopens; headless subordinates may still read mail on their own turns.";
|
|
510
|
+
parts.push([paint("1", "# Recent Sessions"), paint("3;90", explainer), renderTable(recent)].join("\n"));
|
|
511
|
+
} else {
|
|
512
|
+
// Bare note, no section header: there is no Recent section on the
|
|
513
|
+
// page, so a header would announce a section the view does not
|
|
514
|
+
// render. The stale hidden note rides under a header because with
|
|
515
|
+
// --stale its section IS rendered.
|
|
516
|
+
parts.push(
|
|
517
|
+
paint("3;90", `${recent.length} recent session${recent.length === 1 ? "" : "s"} hidden (alive, no open tab); --recent to show`),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
479
521
|
if (stale.length > 0 || staleHidden > 0) {
|
|
480
522
|
const lines = [paint("1", "# Stale Sessions")];
|
|
481
523
|
if (stale.length > 0) {
|
|
@@ -505,13 +547,24 @@ async function chatCommand(rest: string[]): Promise<void> {
|
|
|
505
547
|
// sessions from burying the fresh signal. --stale all restores the
|
|
506
548
|
// unbounded view. A bad value dies loudly rather than silently
|
|
507
549
|
// shrinking the roster.
|
|
508
|
-
// Default:
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
//
|
|
550
|
+
// Default: open sessions only (plus MCP rows, which read mail at
|
|
551
|
+
// their next prompt - "open" is meaningless for them). --recent
|
|
552
|
+
// opts into the Recent section (alive, no open tab). The two hidden
|
|
553
|
+
// defaults work differently: --recent's leaves a muted count note
|
|
554
|
+
// in the renderer (it needs the rows in hand to count them);
|
|
555
|
+
// --stale's drops the rows silently in the pre-filter below, and
|
|
556
|
+
// only the empty-roster message points at the flag. Unlike --stale
|
|
557
|
+
// it takes no window value: recent rows are bounded by the heartbeat
|
|
558
|
+
// window itself, so there is nothing to cap. The value is parsed by
|
|
559
|
+
// hand (not flagValue) because bare --stale is meaningful.
|
|
560
|
+
const showRecent = restArgs.includes("--recent");
|
|
512
561
|
const showStale = restArgs.includes("--stale");
|
|
513
562
|
const staleIdx = restArgs.indexOf("--stale");
|
|
514
|
-
const
|
|
563
|
+
const rawNext = showStale ? restArgs[staleIdx + 1] : undefined;
|
|
564
|
+
// A token starting with "--" is a sibling flag (e.g. "--stale
|
|
565
|
+
// --recent"), not --stale's value: --stale stays bare instead of
|
|
566
|
+
// eating the flag.
|
|
567
|
+
const staleNext = rawNext !== undefined && rawNext.startsWith("--") ? undefined : rawNext;
|
|
515
568
|
let staleCapMs: number | null = 24 * 60 * 60 * 1000;
|
|
516
569
|
if (staleNext !== undefined) {
|
|
517
570
|
if (staleNext === "all") staleCapMs = null;
|
|
@@ -523,17 +576,25 @@ async function chatCommand(rest: string[]): Promise<void> {
|
|
|
523
576
|
// The open-tab set is read once per invocation at render time: rows
|
|
524
577
|
// with a tab render "open", fresh rows without one "recent".
|
|
525
578
|
const openTabs = chatOpenTabsProvider(() => db.chatTabClosures())();
|
|
526
|
-
|
|
527
|
-
|
|
579
|
+
// One clock sample per render: the pre-filter, the split, and every
|
|
580
|
+
// per-row verdict must agree, or a row aging past the staleness
|
|
581
|
+
// boundary mid-render could land in two sections at once.
|
|
582
|
+
const now = Date.now();
|
|
583
|
+
const registered = db.listChatSessions();
|
|
584
|
+
// Stale rows are hidden here (not in the renderer) so the
|
|
585
|
+
// emptiness check can tell an empty directory from a stale-only
|
|
586
|
+
// one; recent rows are left in because formatChatRoster needs them
|
|
587
|
+
// to render the hidden-count note.
|
|
588
|
+
const sessions = registered.filter((r) => showStale || chatLiveness(r, now, openTabs) !== "stale");
|
|
528
589
|
if (sessions.length === 0) {
|
|
529
590
|
console.log(
|
|
530
|
-
|
|
591
|
+
registered.length === 0
|
|
531
592
|
? "No sessions registered. Sessions join the directory automatically on their first idle (opencode), or via chat_register."
|
|
532
593
|
: "No active sessions. (Stale sessions are hidden; run with --stale to include them.)",
|
|
533
594
|
);
|
|
534
595
|
break;
|
|
535
596
|
}
|
|
536
|
-
console.log(formatChatRoster(sessions, showStale ? staleCapMs : null, openTabs));
|
|
597
|
+
console.log(formatChatRoster(sessions, showStale ? staleCapMs : null, openTabs, showRecent, now));
|
|
537
598
|
break;
|
|
538
599
|
}
|
|
539
600
|
|
package/docs/dev/features/cli.md
CHANGED
|
@@ -27,7 +27,13 @@ The thatch CLI is a Bun script at `bin/thatch`. It provides memory inspection, M
|
|
|
27
27
|
|
|
28
28
|
## Global behavior
|
|
29
29
|
|
|
30
|
-
-
|
|
30
|
+
- `--version` and `-v` print the package version and exit 0.
|
|
31
|
+
- `-h` and `--help` print usage and exit 0. The check runs before the DB
|
|
32
|
+
is opened and before any subcommand dispatch, and scans the WHOLE
|
|
33
|
+
argument list - not just the leading flags - so `thatch chat list -h`
|
|
34
|
+
and `thatch -h chat list` show usage instead of executing. Explicit
|
|
35
|
+
help exits 0 where a usage error exits 1.
|
|
36
|
+
- No other short flags. No subcommand aliases.
|
|
31
37
|
- Unknown command or missing required arg calls `usage()` and exits 1.
|
|
32
38
|
- Args are positional, parsed by hand from `process.argv.slice(2)`. No arg-parsing library.
|
|
33
39
|
- DB opened once at startup, closed at end (except `mcp` which closes early).
|
|
@@ -47,12 +53,21 @@ Inherits stdio, exits with the child's exit code. Errors and exits 1 if none fou
|
|
|
47
53
|
Read-only window on the cross-session chat directory ([cross-session-chat.md](cross-session-chat.md)):
|
|
48
54
|
|
|
49
55
|
- `chat list` renders the roster as aligned columns under a header row
|
|
50
|
-
(NAME/AGE/STATUS/PROJECT/TOPIC), colorized only when stdout
|
|
51
|
-
(`formatChatRoster()` in `bin/thatch`; pipes and QA runners
|
|
52
|
-
text). Rows are split into Active and Stale
|
|
53
|
-
`splitChatRoster()
|
|
56
|
+
(NAME/SESSION ID/AGE/STATUS/PROJECT/TOPIC), colorized only when stdout
|
|
57
|
+
is a TTY (`formatChatRoster()` in `bin/thatch`; pipes and QA runners
|
|
58
|
+
get plain text). Rows are split into Active, Recent, and Stale
|
|
59
|
+
sections by `splitChatRoster()` (it takes the open-tab set, so fresh
|
|
60
|
+
opencode rows split into open versus recent); the status column is
|
|
61
|
+
`chatLiveness()`'s verdict
|
|
54
62
|
(`open`/`recent`/`stale` for opencode rows - the open-tab reader feeds
|
|
55
|
-
the open/recent split - `active`/`idle` for MCP rows).
|
|
63
|
+
the open/recent split - `active`/`idle` for MCP rows). The Recent and
|
|
64
|
+
Stale sections are flag-guarded, and the two guards hide differently:
|
|
65
|
+
without `--recent` the Recent section is replaced by a muted
|
|
66
|
+
hidden-count note (the renderer keeps the rows to count them), while
|
|
67
|
+
without `--stale` stale rows are dropped entirely by a pre-filter in
|
|
68
|
+
the command case - only the empty-roster message points at the flag.
|
|
69
|
+
`--stale [N|all]` reveals the stale rows under a one-day display cap
|
|
70
|
+
whose overflow collapses into a hidden-count note.
|
|
56
71
|
- `chat tail` follows the message stream as JSONL: one `ChatTailEvent`
|
|
57
72
|
per line via `formatChatTailJsonl()` (a bare `JSON.stringify`; no ANSI,
|
|
58
73
|
no markdown rendering, no local-time conversion - the tail is a log for
|
|
@@ -114,7 +129,7 @@ Read-only window on the cross-session chat directory ([cross-session-chat.md](cr
|
|
|
114
129
|
## Key invariants
|
|
115
130
|
|
|
116
131
|
- No arg-parsing library. Args are positional, parsed by hand from `process.argv.slice(2)`.
|
|
117
|
-
-
|
|
132
|
+
- `--version`/`-v` prints the version; `-h`/`--help` prints usage from anywhere in the argument list (checked upstream of dispatch, before the DB opens) and exits 0. No other short flags. No subcommand aliases.
|
|
118
133
|
- DB opened once at startup, closed at end (except `mcp` which closes early).
|
|
119
134
|
- Store name auto-detected from git remote. "global" is the shared store. "all" (search only) means project + global.
|
|
120
135
|
- Unknown command or missing required arg calls `usage()` and exits 1.
|
|
@@ -55,8 +55,8 @@ same source as the install list in src/skills.ts and both host
|
|
|
55
55
|
instruction skill lists. `peer-review` weights org
|
|
56
56
|
velocity and walks the project's persistent memory (the one tool it
|
|
57
57
|
names, spelled through the `ToolNamer` so MCP renderings stay correct);
|
|
58
|
-
`self-review` weights the reputation lenses (no-slop,
|
|
59
|
-
breadcrumbs) and iterates rounds until a round produces no meaningful
|
|
58
|
+
`self-review` weights the reputation lenses (no-slop, clarity,
|
|
59
|
+
technique, breadcrumbs) and iterates rounds until a round produces no meaningful
|
|
60
60
|
findings. The couching also names the MCP `Focus:` line as a target
|
|
61
61
|
source, because MCP prompts replace the file commands' `$ARGUMENTS`
|
|
62
62
|
section with a `Focus:` line.
|
|
@@ -317,18 +317,26 @@ is restart persistence by design), so dead-window residue reads OPEN until
|
|
|
317
317
|
its heartbeat ages out; tabs disabled degrades everything to RECENT.
|
|
318
318
|
|
|
319
319
|
`chat_list`
|
|
320
|
-
groups the result into Active and Stale sections - the
|
|
321
|
-
|
|
320
|
+
groups the result into Active, Recent, and Stale sections - the Recent
|
|
321
|
+
and Stale sections' explainers say what those states mean, and each row
|
|
322
|
+
renders its verdict -
|
|
322
323
|
and `chat_send` states the recipient's
|
|
323
324
|
liveness at send time, so a sender mailing a ghost learns it immediately
|
|
324
325
|
instead of waiting on a wake that will never fire, and a sender mailing a
|
|
325
326
|
tab-less session is told the wake is not guaranteed. Both roster surfaces
|
|
326
|
-
order rows with `sortChatRoster` (also src/chat.ts)
|
|
327
|
+
order rows with `sortChatRoster` (also src/chat.ts) and split fresh
|
|
328
|
+
opencode rows along the same open/recent boundary (`splitChatRoster`
|
|
329
|
+
takes the open-tab set): project first,
|
|
327
330
|
alphabetical, with no-project rows last; most recently seen first within
|
|
328
|
-
a project. The CLI roster
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
a project. The CLI roster guards the Recent and Stale sections behind
|
|
332
|
+
flags, and the two guards hide differently: without `--recent` the
|
|
333
|
+
tab-less rows collapse into a muted hidden-count note (revealed by the
|
|
334
|
+
flag), while without `--stale` stale rows are dropped entirely; with
|
|
335
|
+
`--stale [N|all]` they show under a one-day display cap that collapses
|
|
336
|
+
older rows into a
|
|
337
|
+
hidden-count note. The tool
|
|
338
|
+
surface shows all three sections unbounded so an agent always sees the
|
|
339
|
+
full directory.
|
|
332
340
|
`session.deleted` is the graceful-exit fast path that unregisters immediately.
|
|
333
341
|
|
|
334
342
|
### Delivery, re-nudge, and the rate cap
|
package/docs/dev/skills.md
CHANGED
|
@@ -43,6 +43,7 @@ description: Extract durable project facts ... Use when ...
|
|
|
43
43
|
| `thatch-review-state-flow` | Data flow and contracts: module boundaries, implicit FSMs, error propagation. |
|
|
44
44
|
| `thatch-review-economy` | Design simplicity and maintainability: is the complexity earned? Evaluates both the overall change design (forest) and individual touch points (trees) for unnecessary complexity, redundancy, and simpler available alternatives. |
|
|
45
45
|
| `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth-wall breaks, em dashes, filler. |
|
|
46
|
+
| `thatch-review-clarity` | Prose clarity in comments, docstrings, error strings, and docs: made-up phrases, buzzwords, untranslated jargon, clause chains, buried leads, doc/code responsibility splits, checked against the project's clear-writing rules regardless of who wrote the text. |
|
|
46
47
|
| `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of behavior? |
|
|
47
48
|
| `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
|
|
48
49
|
| `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar against generic praise. |
|
|
@@ -79,7 +80,7 @@ description: Extract durable project facts ... Use when ...
|
|
|
79
80
|
|
|
80
81
|
## REVIEW_COMMON
|
|
81
82
|
|
|
82
|
-
The
|
|
83
|
+
The problem-finding review specialists share a framework interpolated via `${REVIEW_COMMON}` (the technique and highlights specialists do not use it: technique reports informational notes and highlights finds positive things, each with its own structure):
|
|
83
84
|
|
|
84
85
|
- **Static analysis only** — no running tests/linters/compilers.
|
|
85
86
|
- **Scope gathering** — resolve the git range, `git diff --stat`, read changed
|
package/docs/user/README.md
CHANGED
|
@@ -202,7 +202,7 @@ requirements.
|
|
|
202
202
|
| Tool | What it does |
|
|
203
203
|
|------|-------------|
|
|
204
204
|
| `thatch_chat_register` | Join the cross-session chat directory, so other sessions on this machine can message you. Display names are assigned by thatch, never chosen (a pool name plus a never-reused counter), so a name always refers to the same session. Call with no arguments to join, rejoin after unregistering, or check your name. On hosts without session context (Claude Code, Cursor), pass the name your thatch hook printed as `as`. |
|
|
205
|
-
| `thatch_chat_list` | List registered sessions grouped into Active and Stale sections (stale = two missed heartbeats, about a minute; its host process has probably stopped), with how long ago each last checked in, each session's project and checkout kind (project root or linked worktree), its topic, and your unread count. |
|
|
205
|
+
| `thatch_chat_list` | List registered sessions grouped into Active, Recent, and Stale sections (recent = alive but no open tab, so a wake may not be guaranteed; stale = two missed heartbeats, about a minute; its host process has probably stopped), with how long ago each last checked in, each session's project and checkout kind (project root or linked worktree), its topic, and your unread count. |
|
|
206
206
|
| `thatch_chat_send` | Send a message to another registered session, by name or session id. The recipient is nudged when its session is idle. |
|
|
207
207
|
| `thatch_chat_read` | Drain your inbox: all unread messages oldest-first, marked read. Senders identified by display name. |
|
|
208
208
|
| `thatch_chat_unregister` | Leave the chat directory. |
|
|
@@ -331,6 +331,7 @@ response, walkthrough, and workflow skills that support the review pipeline:
|
|
|
331
331
|
| `thatch-review-state-flow` | Data flow and contracts: module boundaries, implicit state machines, error propagation, separation of concerns. |
|
|
332
332
|
| `thatch-review-economy` | Design simplicity and maintainability: is the complexity earned? Evaluates both the overall change design (forest) and individual touch points (trees) for unnecessary complexity, redundancy, and simpler available alternatives. |
|
|
333
333
|
| `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth wall breaks, em dashes, hedging, filler. |
|
|
334
|
+
| `thatch-review-clarity` | Prose clarity in comments, docstrings, error strings, and docs: made-up phrases, buzzwords, untranslated jargon, clause chains, buried leads, doc/code responsibility splits, checked against the project's clear-writing rules regardless of who wrote the text. |
|
|
334
335
|
| `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
|
|
335
336
|
| `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
|
|
336
337
|
| `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar against generic praise. |
|
|
@@ -406,7 +407,7 @@ specialist in sequence, then synthesize:
|
|
|
406
407
|
```text
|
|
407
408
|
1. Load thatch-review-pedantic, review branch feature-x, report findings.
|
|
408
409
|
2. Load thatch-review-acceptance, review the same branch.
|
|
409
|
-
3. ... repeat for state-flow, no-slop, breadcrumbs, mark-and-sweep ...
|
|
410
|
+
3. ... repeat for state-flow, no-slop, clarity, breadcrumbs, mark-and-sweep ...
|
|
410
411
|
4. Load thatch-review-synthesizer, verify and aggregate all findings.
|
|
411
412
|
```
|
|
412
413
|
|
package/docs/user/cli.md
CHANGED
|
@@ -4,6 +4,10 @@ Thatch ships with a command-line tool for inspecting memories and
|
|
|
4
4
|
running maintenance tasks outside the agent. It requires `bun` on your
|
|
5
5
|
PATH.
|
|
6
6
|
|
|
7
|
+
`thatch --help` (or `-h`) prints the full command list and exits 0.
|
|
8
|
+
The flag works anywhere in the command line: `thatch chat list -h`
|
|
9
|
+
shows help instead of running the command.
|
|
10
|
+
|
|
7
11
|
## Memory inspection
|
|
8
12
|
|
|
9
13
|
```bash
|
|
@@ -45,7 +49,8 @@ Read-only access to the chat directory your opencode sessions share
|
|
|
45
49
|
(see [cross-session-chat.md](cross-session-chat.md)):
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
|
-
thatch chat list # who is
|
|
52
|
+
thatch chat list # who is open now: name, age, project, topic
|
|
53
|
+
thatch chat list --recent # also show alive sessions with no open tab
|
|
49
54
|
thatch chat list --stale all # also show stale sessions older than a day
|
|
50
55
|
thatch chat tail # follow sent and read events, live (JSONL)
|
|
51
56
|
thatch chat tail --once # print the last 20 messages and exit
|
|
@@ -55,21 +60,28 @@ thatch chat tail --from al --to bob # substring match on names
|
|
|
55
60
|
thatch chat tail --since 2026-09-12 --until "2026-09-13 09:00" # a window
|
|
56
61
|
```
|
|
57
62
|
|
|
58
|
-
`chat list`
|
|
59
|
-
Stale
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
`chat list` renders the chat roster in three sections - Active, Recent,
|
|
64
|
+
and Stale - each as aligned columns under a header row: how long since its last heartbeat, its status (`open` - a tab is open now - or
|
|
65
|
+
`recent` - host alive, no open tab - for opencode sessions; `active` or
|
|
66
|
+
`idle` for Claude Code and Cursor sessions, which only beat when a
|
|
67
|
+
prompt runs), its project, and its topic (the session's title) - the
|
|
68
|
+
fastest way to answer "which session should I talk to about X?" Rows
|
|
69
|
+
are grouped by project (alphabetical; sessions with no project last),
|
|
70
|
+
most recently seen first within each project.
|
|
71
|
+
|
|
72
|
+
The default view shows the sessions you can wake or reach now: open
|
|
73
|
+
sessions plus Claude Code and Cursor rows, which read mail at their
|
|
74
|
+
next prompt. A recent session - its harness is alive but its tab is
|
|
75
|
+
closed, so a wake may not reach it until the tab reopens - collapses
|
|
76
|
+
into a muted hidden-count note pointing at `--recent`, which reveals
|
|
77
|
+
the Recent section. A stale session has missed two heartbeats, about a
|
|
78
|
+
minute: the opencode process hosting it has probably stopped. Its
|
|
79
|
+
section is hidden too, behind `--stale`, but without a count note - the
|
|
80
|
+
default view simply drops stale rows. The stale section
|
|
81
|
+
shows rows up to one day old by default; older ones collapse into a
|
|
82
|
+
hidden-count note so long-dead sessions cannot bury the fresh signal.
|
|
83
|
+
Pass `--stale N` (days) to widen the window or `--stale all` to see
|
|
84
|
+
every stale row. Color is added only when the
|
|
73
85
|
output is a terminal; a pipe prints plain aligned text.
|
|
74
86
|
|
|
75
87
|
`chat tail` prints the conversation as a JSONL event log: one JSON
|
package/docs/user/code-review.md
CHANGED
|
@@ -25,7 +25,7 @@ the review:
|
|
|
25
25
|
3. **Research affected workflows**: load `thatch-code-archaeology`,
|
|
26
26
|
trace the code paths the change touches.
|
|
27
27
|
4. **Estimate complexity**: partition the diff into review units.
|
|
28
|
-
5. **Dispatch specialists**: fan out the
|
|
28
|
+
5. **Dispatch specialists**: fan out the specialists as
|
|
29
29
|
parallel sub-agents, each with the context brief.
|
|
30
30
|
6. **Synthesize**: the synthesizer reads all specialist output,
|
|
31
31
|
verifies each finding (cited text exists, claim is accurate,
|
|
@@ -38,7 +38,7 @@ the review:
|
|
|
38
38
|
sibling is not a defect, and inconsistencies between connected
|
|
39
39
|
PRs surface as cross-PR notes.
|
|
40
40
|
|
|
41
|
-
## The
|
|
41
|
+
## The specialists
|
|
42
42
|
|
|
43
43
|
| Skill | What it checks |
|
|
44
44
|
|-------|-------------|
|
|
@@ -47,6 +47,7 @@ the review:
|
|
|
47
47
|
| `thatch-review-state-flow` | Module boundaries, implicit state machines, error propagation, separation of concerns. |
|
|
48
48
|
| `thatch-review-economy` | Design simplicity and maintainability: is the complexity earned? Evaluates both the overall change design (forest) and individual touch points (trees) for unnecessary complexity, redundancy, and simpler available alternatives. |
|
|
49
49
|
| `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth wall breaks, em dashes, hedging, filler. |
|
|
50
|
+
| `thatch-review-clarity` | Prose clarity in comments, docstrings, error strings, and docs: made-up phrases, buzzwords, untranslated jargon, clause chains, buried leads, doc/code responsibility splits, checked against the project's clear-writing rules regardless of who wrote the text. |
|
|
50
51
|
| `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
|
|
51
52
|
| `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
|
|
52
53
|
| `thatch-review-highlights` | Positive finding detection: clever solutions, cleanup done along the way, documentation that helps. Medium-high bar. |
|
|
@@ -61,7 +62,7 @@ Two slash commands run the same pipeline with the findings weighted for
|
|
|
61
62
|
whose work is under review.
|
|
62
63
|
|
|
63
64
|
- `/thatch/self-review` reviews your own completed work before you
|
|
64
|
-
submit it. The slop, technique, and breadcrumbs lenses weigh
|
|
65
|
+
submit it. The slop, clarity, technique, and breadcrumbs lenses weigh
|
|
65
66
|
heaviest, simplicity is overweighted on shared projects, and
|
|
66
67
|
technique or highlights findings that would simplify the design are
|
|
67
68
|
surfaced for an adopt-or-dismiss decision rather than left as
|
|
@@ -25,11 +25,12 @@ explicitly with `thatch_chat_register` (no arguments) - useful after
|
|
|
25
25
|
`chat_unregister`, or just to check your assigned name. Once registered:
|
|
26
26
|
|
|
27
27
|
- `thatch_chat_list` shows every registered session on the machine, grouped
|
|
28
|
-
into Active and Stale sections: active sessions
|
|
28
|
+
into Active, Recent, and Stale sections: active sessions either have an
|
|
29
|
+
opencode tab open right now or are Claude Code and Cursor rows, which
|
|
30
|
+
read mail at their next prompt; recent ones are alive but have no open
|
|
31
|
+
tab, so a wake may not reach them until the tab reopens; stale
|
|
29
32
|
ones have missed two heartbeats, about a minute (the opencode process
|
|
30
|
-
hosting them has probably stopped). Each
|
|
31
|
-
liveness verdict: `open` means the session has an opencode tab open
|
|
32
|
-
right now, `recent` means the harness is alive but no tab is open. Each
|
|
33
|
+
hosting them has probably stopped). Each
|
|
33
34
|
row carries how long ago the session last
|
|
34
35
|
checked in, its project, whether it runs in the project root or a linked
|
|
35
36
|
git worktree, its topic, and your unread count
|
package/docs/user/skills.md
CHANGED
|
@@ -43,6 +43,7 @@ Skills are split into shared (all hosts) and opencode-only:
|
|
|
43
43
|
| `thatch-review-state-flow` | Data flow and contracts: module boundaries, implicit state machines, error propagation, separation of concerns. |
|
|
44
44
|
| `thatch-review-economy` | Design simplicity and maintainability: is the complexity earned? Evaluates both the overall change design (forest) and individual touch points (trees) for unnecessary complexity, redundancy, and simpler available alternatives. |
|
|
45
45
|
| `thatch-review-no-slop` | AI writing anti-patterns: change narration, fourth wall breaks, em dashes, hedging, filler. |
|
|
46
|
+
| `thatch-review-clarity` | Prose clarity in comments, docstrings, error strings, and docs: made-up phrases, buzzwords, untranslated jargon, clause chains, buried leads, doc/code responsibility splits, checked against the project's clear-writing rules regardless of who wrote the text. |
|
|
46
47
|
| `thatch-review-breadcrumbs` | Comment narrative: do comments form a coherent outline of the code's behavior? |
|
|
47
48
|
| `thatch-review-mark-and-sweep` | Mechanical change completeness: whole-repo sweep for stragglers after renames, flag removals, API substitutions. |
|
|
48
49
|
| `thatch-review-highlights` | Positive finding detection: notably clever solutions, cleanup done along the way, documentation that helps. Medium-high bar. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/chat.ts
CHANGED
|
@@ -389,31 +389,41 @@ export function sortChatRoster(rows: ChatSessionRow[]): ChatSessionRow[] {
|
|
|
389
389
|
});
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
/** Splits the roster for the
|
|
393
|
-
* reachable -
|
|
394
|
-
* their next prompt)
|
|
395
|
-
*
|
|
392
|
+
/** Splits the roster for the three-section display: Active (wakeable or
|
|
393
|
+
* reachable - opencode rows with an open tab, plus every MCP row, which
|
|
394
|
+
* read mail at their next prompt), Recent (fresh opencode rows with no
|
|
395
|
+
* open tab: the harness is alive and beating, but a wake may not reach
|
|
396
|
+
* the session until its tab reopens - headless subordinates may still
|
|
397
|
+
* read mail on their own turns), and Stale (opencode rows whose harness
|
|
398
|
+
* has stopped reporting; mail to them waits until they resume).
|
|
399
|
+
* openTabs refines fresh opencode rows into open versus recent; when
|
|
400
|
+
* omitted every fresh opencode row degrades to recent, never a false
|
|
401
|
+
* open (chatLiveness's rule).
|
|
396
402
|
* staleCapMs bounds how old a stale row the roster displays, so
|
|
397
403
|
* long-dead sessions cannot bury the fresh signal; hidden rows come
|
|
398
404
|
* back only as the staleHidden count, so the CLI can say what it left
|
|
399
405
|
* out. null (the default) shows every stale row - the tool surface has
|
|
400
406
|
* no cap, only the CLI applies one. */
|
|
401
|
-
export function splitChatRoster(rows: ChatSessionRow[], now = Date.now(), staleCapMs: number | null = null): { active: ChatSessionRow[]; stale: ChatSessionRow[]; staleHidden: number } {
|
|
407
|
+
export function splitChatRoster(rows: ChatSessionRow[], now = Date.now(), staleCapMs: number | null = null, openTabs?: ReadonlySet<string>): { active: ChatSessionRow[]; recent: ChatSessionRow[]; stale: ChatSessionRow[]; staleHidden: number } {
|
|
402
408
|
const active: ChatSessionRow[] = [];
|
|
409
|
+
const recent: ChatSessionRow[] = [];
|
|
403
410
|
const stale: ChatSessionRow[] = [];
|
|
404
411
|
let staleHidden = 0;
|
|
405
412
|
for (const row of rows) {
|
|
406
|
-
|
|
413
|
+
const verdict = chatLiveness(row, now, openTabs);
|
|
414
|
+
if (verdict === "stale") {
|
|
407
415
|
if (staleCapMs !== null && now - Date.parse(row.last_seen) > staleCapMs) {
|
|
408
416
|
staleHidden++;
|
|
409
417
|
} else {
|
|
410
418
|
stale.push(row);
|
|
411
419
|
}
|
|
420
|
+
} else if (verdict === "recent") {
|
|
421
|
+
recent.push(row);
|
|
412
422
|
} else {
|
|
413
423
|
active.push(row);
|
|
414
424
|
}
|
|
415
425
|
}
|
|
416
|
-
return { active, stale, staleHidden };
|
|
426
|
+
return { active, recent, stale, staleHidden };
|
|
417
427
|
}
|
|
418
428
|
|
|
419
429
|
/**
|
package/src/commands.ts
CHANGED
|
@@ -192,7 +192,7 @@ ${reviewPipelineCouching}
|
|
|
192
192
|
|
|
193
193
|
## Self review mode rules
|
|
194
194
|
|
|
195
|
-
- The reputation lenses weigh heaviest. NoSlop, Technique, and Breadcrumbs findings outrank same-severity findings from the other lenses. AI-writing tells,
|
|
195
|
+
- The reputation lenses weigh heaviest. NoSlop, Clarity, Technique, and Breadcrumbs findings outrank same-severity findings from the other lenses. AI-writing tells, unclear prose, and a missing narrative are exactly what this review exists to catch before anyone else sees them.
|
|
196
196
|
- Overweight simplicity on shared projects. Prefer findings that make the design easier to understand, easier to test, and more reliable: fewer assumptions, less surface area for bugs and side effects.
|
|
197
197
|
- Technique and highlights findings are decisions, not footnotes. Each one that would produce a simpler, clearer, or more reliable design is surfaced for an adopt-or-dismiss decision before submission. Work through them with the user one at a time; do not leave them as informational asides.
|
|
198
198
|
- Iterate rounds. After each round, work the findings with the user: fix each, or explicitly decline it with a reason. Then run another full round on the updated state. A round is meaningful when it produces a MEDIUM or higher finding, or a technique or slop finding worth adopting. A round producing only LOW mechanical findings gets them fixed but does not trigger another round. Stop at the first round with no meaningful findings, and never run more than three rounds.`,
|
package/src/prompts.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const REVIEW_SPECIALISTS: readonly string[] = [
|
|
|
19
19
|
"thatch-review-state-flow",
|
|
20
20
|
"thatch-review-economy",
|
|
21
21
|
"thatch-review-no-slop",
|
|
22
|
+
"thatch-review-clarity",
|
|
22
23
|
"thatch-review-breadcrumbs",
|
|
23
24
|
"thatch-review-mark-and-sweep",
|
|
24
25
|
"thatch-review-highlights",
|
package/src/tool-defs.ts
CHANGED
|
@@ -2139,7 +2139,10 @@ const chatRegisterDef: ToolDef = {
|
|
|
2139
2139
|
|
|
2140
2140
|
/** Lists registered sessions with liveness. Stale means the session's host
|
|
2141
2141
|
* process has not heartbeat-ed it recently - it is gone or hung. Fresh
|
|
2142
|
-
* rows split into open (a tab is open now) and recent (no known tab).
|
|
2142
|
+
* rows split into open (a tab is open now) and recent (no known tab).
|
|
2143
|
+
* The three sections render unguarded by design, unlike the CLI's
|
|
2144
|
+
* --recent/--stale guards: an agent choosing a recipient always needs
|
|
2145
|
+
* the full directory, including the tab-less rows it may still reach. */
|
|
2143
2146
|
const chatListDef: ToolDef = {
|
|
2144
2147
|
name: "chat_list",
|
|
2145
2148
|
description:
|
|
@@ -2159,7 +2162,7 @@ const chatListDef: ToolDef = {
|
|
|
2159
2162
|
}
|
|
2160
2163
|
const unread = ctx.db.unreadChatCount(identity.sessionID);
|
|
2161
2164
|
const openTabs = ctx.openTabs?.() ?? new Set<string>();
|
|
2162
|
-
const { active, stale } = splitChatRoster(sortChatRoster(sessions));
|
|
2165
|
+
const { active, recent, stale } = splitChatRoster(sortChatRoster(sessions), Date.now(), null, openTabs);
|
|
2163
2166
|
const line = (s: (typeof sessions)[number]) => {
|
|
2164
2167
|
const self = s.session_id === identity.sessionID;
|
|
2165
2168
|
const liveness = chatLiveness(s, Date.now(), openTabs);
|
|
@@ -2177,6 +2180,15 @@ const chatListDef: ToolDef = {
|
|
|
2177
2180
|
if (active.length > 0) {
|
|
2178
2181
|
sections.push(`# Active Sessions\n\n${active.map(line).join("\n")}`);
|
|
2179
2182
|
}
|
|
2183
|
+
if (recent.length > 0) {
|
|
2184
|
+
sections.push(
|
|
2185
|
+
`# Recent Sessions\n\n` +
|
|
2186
|
+
`_These sessions are alive but have no open tab: a wake may not reach them ` +
|
|
2187
|
+
`until their tab reopens, though headless subordinates may still read mail ` +
|
|
2188
|
+
`on their own turns._\n\n` +
|
|
2189
|
+
`${recent.map(line).join("\n")}`,
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2180
2192
|
if (stale.length > 0) {
|
|
2181
2193
|
sections.push(
|
|
2182
2194
|
`# Stale Sessions\n\n` +
|