@jeffober/thatch 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/artifacts/skills/thatch-plan-refinement.md +3 -2
- package/artifacts/skills/thatch-refine-infra.md +66 -0
- package/artifacts/skills/thatch-refine-oss-contribution.md +68 -0
- package/artifacts/skills/thatch-refine-personal.md +61 -0
- package/artifacts/skills/thatch-refine-shared-lib.md +69 -0
- package/artifacts/skills/thatch-refine-spike.md +53 -0
- package/artifacts/skills/thatch-refine-team-app.md +69 -0
- package/artifacts/skills/thatch-refine.md +84 -0
- package/bin/thatch +9 -5
- package/docs/dev/README.md +21 -25
- package/docs/dev/features/cli.md +1 -0
- package/docs/dev/features/extraction.md +120 -81
- package/docs/dev/features/nudge-pipeline.md +44 -39
- package/docs/dev/features/session-lifecycle.md +34 -20
- package/docs/dev/features/watchers.md +9 -0
- package/docs/dev/gotchas.md +33 -26
- package/docs/dev/mcp-parity.md +17 -18
- package/docs/dev/setup-and-hooks.md +1 -1
- package/docs/dev/skills.md +8 -1
- package/docs/user/README.md +7 -0
- package/docs/user/commands.md +8 -0
- package/docs/user/skills.md +12 -0
- package/docs/user/watchers.md +6 -0
- package/package.json +1 -1
- package/src/chat.ts +1 -1
- package/src/commands.ts +23 -3
- package/src/extract-queue.ts +16 -1
- package/src/extraction.ts +4 -3
- package/src/opencode/v2.ts +4 -3
- package/src/prompts.ts +26 -33
- package/src/runtime.ts +119 -106
- package/src/skills.ts +7 -0
- package/src/tool-defs.ts +39 -31
- package/src/watchers.ts +31 -0
package/docs/dev/gotchas.md
CHANGED
|
@@ -67,32 +67,26 @@ here first. These are the things that have already cost time.
|
|
|
67
67
|
- **Hook failures are logged with a `[thatch]` prefix and never swallowed.** Two
|
|
68
68
|
hooks were dead for weeks before failures were made visible. If you add a
|
|
69
69
|
hook, log on failure.
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Without this, either path would write memories in the child but never
|
|
91
|
-
clear the parent's queue — the nudge would replay every turn.
|
|
92
|
-
`thatch_extraction_done` is the belt-and-suspenders explicit
|
|
93
|
-
acknowledgment: it drains the buffer without requiring a memory write
|
|
94
|
-
(covers cases where the sub-agent errors out or the host doesn't expose
|
|
95
|
-
parent-child session relationships).
|
|
70
|
+
- **There is no model-facing extraction nudge on opencode.** The old
|
|
71
|
+
`chat.message` extraction nudge (and its `missedNudges` escalation) drove
|
|
72
|
+
a model-driven handshake that raced its own state machine: parent acks
|
|
73
|
+
landed before the child's payload fetch, no-claim completions wiped
|
|
74
|
+
in-flight accepted sets, and mis-targeted session ids made every
|
|
75
|
+
transition a silent no-op - so the same buffered count re-fired forever
|
|
76
|
+
(the September 2026 dispatch-loop report). Extraction is now plugin-driven
|
|
77
|
+
end-to-end: `triggerExtraction` at session idle creates the child and
|
|
78
|
+
interpolates the session ID itself; the child's payload fetch records the
|
|
79
|
+
claim; completions are claim-scoped; the 15-minute stale reaper bounds
|
|
80
|
+
crashed extractors. The nudge survives on MCP hosts only, where no plugin
|
|
81
|
+
can create sessions and the file-backed queue is durable until the
|
|
82
|
+
extractor completes.
|
|
83
|
+
- **A child's `thatch_memory_remember` completes only its claimed delivery**
|
|
84
|
+
via the `childToParent` Map and the claim recorded by its payload fetch.
|
|
85
|
+
A child that never fetched has no claim, and its memory write (or idle
|
|
86
|
+
signal, or ack) consumes nothing - the entries stay held/pending for the
|
|
87
|
+
real extractor. `thatch_extraction_done` is the explicit no-save
|
|
88
|
+
completion: claim-scoped like everything else, so a mis-ordered or
|
|
89
|
+
mis-targeted ack can never drop another extractor's in-flight delivery.
|
|
96
90
|
- **The no-save drain runs in the child-idle handler regardless of writes.**
|
|
97
91
|
When the extraction child goes idle after a no-save run (nothing worth
|
|
98
92
|
extracting), the parent's snapshot entries must still be drained from the
|
|
@@ -286,3 +280,16 @@ instead of throwing "Cannot use a closed database". Two rules keep this intact:
|
|
|
286
280
|
wrap-up arming write, whose command executes are not drained by the v2
|
|
287
281
|
cleanup). Do not "fix" the guard away by moving it into db.ts: the
|
|
288
282
|
accessor cannot tell a legitimate in-flight caller from a stale writer.
|
|
283
|
+
|
|
284
|
+
## Command frontmatter descriptions must be quoted YAML scalars
|
|
285
|
+
|
|
286
|
+
- **A plain-scalar frontmatter value containing a colon followed by a space
|
|
287
|
+
is invalid YAML, and not every host path rescues it.** `/thatch/hygiene`'s
|
|
288
|
+
description ("Tend the memory store: stale entries, ...") shipped unquoted;
|
|
289
|
+
the first parse throws
|
|
290
|
+
and only opencode's fallback sanitizer recovers it — and a long-lived
|
|
291
|
+
server was observed dropping the description entirely while a fresh server
|
|
292
|
+
parsed it fine, which made the bug look like a TUI rendering issue. The
|
|
293
|
+
renderer quotes every description (`yamlQuote` in src/commands.ts) and a
|
|
294
|
+
unit test pins the quoted form. Keep it that way for any new frontmatter
|
|
295
|
+
field whose value is free-form prose.
|
package/docs/dev/mcp-parity.md
CHANGED
|
@@ -23,7 +23,7 @@ This document maps the feature parity and documents the remaining gaps.
|
|
|
23
23
|
| **Session-start reminder** (recall nudge + hygiene heartbeat) | `session.created` event → `client.session.prompt` injects a synthetic message | `SessionStart` hook → `thatch reminder`; stdout becomes context | `sessionStart` hook → `thatch reminder --json`; output is `additional_context` JSON | **Full** |
|
|
24
24
|
| **Compaction context** (re-familiarize after compaction) | `experimental.session.compacting` hook appends context to the compaction output | `PostCompact` hook — side-effects only, **cannot inject context** | No equivalent hook | **Gapped** — see below |
|
|
25
25
|
| **Wrap-up commands** (`/thatch/compact`, `/thatch/exit`) | Command markdown synced to `$XDG_CONFIG_HOME/opencode/command/thatch/` at plugin init; `command.execute.before` arms the session and the next idle resolves the greenlight token into a TUI action (`session.compact` via `executeCommand`, `app.exit` via `publish`) | No equivalent — no plugin host to arm the check or trigger the action | No equivalent | **Gapped** — needs the plugin's SDK client and TUI control routes |
|
|
26
|
-
| **On-demand actions** (`/thatch/defrag`, `extract`, `hygiene`, `reflect`) | Command markdown synced at plugin init; bodies rendered from the shared prompt cores with `thatch_` tool spellings; `extract` is opencode-only (needs session identity via `get_session_info` and the plugin's payload provider) | Command markdown synced by `thatch setup --claude` into `<claudeDir>/commands/thatch/`; same cores with `mcp__thatch__` spellings; `extract` excluded | MCP Prompts (`prompts/list` + `prompts/get`) from the same cores; Cursor surfaces them as slash commands; `extract` excluded | **Full** for the shared actions (same cores, per-host tool spellings); `extract` opencode-only |
|
|
26
|
+
| **On-demand actions** (`/thatch/defrag`, `extract`, `hygiene`, `reflect`, `refine`) | Command markdown synced at plugin init; bodies rendered from the shared prompt cores with `thatch_` tool spellings; `extract` is opencode-only (needs session identity via `get_session_info` and the plugin's payload provider) | Command markdown synced by `thatch setup --claude` into `<claudeDir>/commands/thatch/`; same cores with `mcp__thatch__` spellings; `extract` excluded | MCP Prompts (`prompts/list` + `prompts/get`) from the same cores; Cursor surfaces them as slash commands; `extract` excluded | **Full** for the shared actions (same cores, per-host tool spellings); `extract` opencode-only |
|
|
27
27
|
| **Extraction** (buffer tool calls, extract durable facts) | `tool.execute.after` buffers non-meta tool calls in-process (`isMetaToolName` in `src/extraction.ts`: thatch_*, skill, task, agent, subagent — execute calls unwrapping only thatch tools are also excluded); **direct extraction** is the primary path — when the parent session goes idle with pending buffer entries, the plugin creates a child session via the SDK (`client.session.create` with `parentID`) and prompts it to call `get_extraction_payload` (nudge suppressed via the `extracting` set); the `chat.message` nudge path is a **fallback** used when direct extraction throws | `PostToolBatch` → `thatch buffer-batch` (file-backed JSONL queue); `UserPromptSubmit` → `thatch flush-tools` peeks the queue and prints the nudge with session ID + fetch tool name (does not drain — the queue persists until a memory write or `extraction_done`) | `postToolUse` → `thatch buffer-tool` (single-tool, file-backed queue); `beforeSubmitPrompt` → `thatch flush-tools --json` | **Partial** — opencode uses direct extraction (parent idle → child session) as the primary path, with the nudge as fallback; MCP hosts are nudge-only. Both paths use `get_extraction_payload` to fetch the queued interactions as a tool response, keeping the full payload out of the main session's context window. Nudge timing differs: the nudge arrives at the **start** of the next turn in Claude Code/Cursor; opencode's direct extraction fires at **end of turn** (session idle), and the nudge fallback fires at the start of the next turn like MCP |
|
|
28
28
|
| **Extraction feedback** (toast notifications) | `client.tui.showToast` fires when the extraction child goes idle — shows `[thatch] new: N, updated: M, deleted: K` (success) or `extraction complete — nothing to save` (info); best-effort, silently ignored in headless mode | No TUI connection — MCP hosts have no equivalent | No TUI connection | **Gapped** — MCP hosts cannot show toast notifications |
|
|
29
29
|
| **Automatic alerts** (notify on LLM pause / turn end) | Plugin event hook drives the `alerts.ts` state machine: `question.asked`/`permission.asked` → pause alert; `session.status` idle with a real-work round → done alert; unrecovered errors → needs-attention. Delivered via the shared `notify.ts` dispatcher, channel per event from the `alerts` config section | No event stream — only `thatch_notify_user` when the agent chooses | No event stream — only `thatch_notify_user` when the agent chooses | **Gapped** — MCP hosts have no plugin event hook to watch; Claude Code's `Notification`/`Stop` hooks could feed the same classifier later |
|
|
@@ -78,28 +78,27 @@ and the nudge (fallback):
|
|
|
78
78
|
2. When the parent session goes idle (`session.status` idle event) with pending
|
|
79
79
|
buffer entries, the plugin calls `triggerExtraction`: creates a child session
|
|
80
80
|
via `client.session.create` (with `parentID`), then prompts it via
|
|
81
|
-
`client.session.promptAsync` (or `prompt`) with the extraction
|
|
82
|
-
|
|
81
|
+
`client.session.promptAsync` (or `prompt`) with the extraction prompt (the
|
|
82
|
+
parent's session ID is interpolated into the prompt by the plugin). The
|
|
83
|
+
`extracting` set gates re-triggering while the child runs.
|
|
83
84
|
3. The child session runs the `thatch-fact-extractor` skill, writes memories via
|
|
84
|
-
`thatch_memory_remember`.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
4. When the child goes idle, the handler
|
|
88
|
-
|
|
85
|
+
`thatch_memory_remember`. The child's payload fetch records its claim; its
|
|
86
|
+
memory write (or `extraction_done`) completes that claim - consuming only
|
|
87
|
+
the entries it received.
|
|
88
|
+
4. When the child goes idle, the handler finalizes the claim-scoped
|
|
89
|
+
completion, deletes the child session, and fires a toast via
|
|
89
90
|
`client.tui.showToast`.
|
|
90
91
|
|
|
91
|
-
**
|
|
92
|
+
**Failure recovery (no model-facing nudge exists on opencode):**
|
|
92
93
|
|
|
93
94
|
1. If `triggerExtraction` throws (session creation or prompt fails), the catch
|
|
94
|
-
block clears the `extracting` flag.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
(or a child sub-agent via the `childToParent` Map) or `thatch_extraction_done`
|
|
102
|
-
drains the buffer and resets the missed-nudge counter.
|
|
95
|
+
block clears the `extracting` flag. The pending buffer is untouched, and
|
|
96
|
+
the next idle re-triggers extraction - the plugin retries without any
|
|
97
|
+
model cooperation.
|
|
98
|
+
2. If the child errors or is deleted before completing, what it held is
|
|
99
|
+
requeued to pending. If its completion signal never comes at all, the
|
|
100
|
+
15-minute stale reaper returns accepted entries to pending. Either way
|
|
101
|
+
the next idle re-extracts - no interaction is silently lost.
|
|
103
102
|
|
|
104
103
|
Claude Code and Cursor use a **file-backed** queue (`src/extract-queue.ts`)
|
|
105
104
|
because hooks fire one-shot with no cross-call state:
|
|
@@ -25,7 +25,7 @@ code (`src/opencode/v1.ts`):
|
|
|
25
25
|
| `experimental.session.compacting` | `{ sessionID }` | `{ context: string[] }` | Marks the session as compacting, pushes re-familiarization context. |
|
|
26
26
|
| `experimental.compaction.autocontinue` | `{ sessionID }` | `{ enabled: boolean }` | Clears the compacting flag so `chat.message` nudges resume post-compaction. |
|
|
27
27
|
| `tool.execute.after` | `{ tool, sessionID, callID, args }` | `{ title, output, metadata }` | Buffers non-`thatch_`, non-`skill`, non-`task` tool calls into the in-memory extraction ring buffer (max 20). In child sessions, also tracks new/updated/deleted counts via `childMetrics` for the extraction toast. |
|
|
28
|
-
| `chat.message` | `{ sessionID, messageID }` | `{ message, parts }` |
|
|
28
|
+
| `chat.message` | `{ sessionID, messageID }` | `{ message, parts }` | Prompt-aware recall nudge (plus prediction/behavior auto-fire). No extraction nudge - extraction is plugin-driven at session idle. Suppressed while compacting or for task-dispatched sub-agent sessions (children that are not extraction children). |
|
|
29
29
|
| `event` | `{ event: { type, properties } }` | — | Dispatches on `event.type`: `session.created` (top-level) → `client.session.prompt` with reminder + hygiene heartbeat; `session.created` (child with `parentID`) → records `childToParent` + snapshots parent's pending buffer. `session.status` idle (parent, pending interactions) → `triggerExtraction` (direct child-session extraction); idle (child) → complete/drain, `showToast` with metrics, delete child. `session.error` (child) → requeue parent's accepted entries. `session.deleted` → requeue if child, drop accepted if parent, clean maps. `session.compacted` → clear compacting flag. |
|
|
30
30
|
| `dispose` | — | — | Closes the DB. |
|
|
31
31
|
|
package/docs/dev/skills.md
CHANGED
|
@@ -55,7 +55,14 @@ description: Extract durable project facts ... Use when ...
|
|
|
55
55
|
| `thatch-code-walkthrough` | Explain a feature, module, or workflow to the user as a teaching walkthrough: identify the code area (optionally from a branch or PR), research how it works, teach it with file:line citations and analogies, list the key files. |
|
|
56
56
|
| `thatch-session-reflection` | End-of-session memory recording (project, user, tools, self). |
|
|
57
57
|
| `thatch-coding-workflow` | Plan and execute code changes with a task-list-driven workflow. Use when implementing features, fixing bugs, or making multi-file changes. |
|
|
58
|
-
| `thatch-plan-refinement` | Refine a plan before implementing it: fresh-context reviewer rounds until consensus, with an optional deep-mode lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology). |
|
|
58
|
+
| `thatch-plan-refinement` | Refine a plan before implementing it: fresh-context reviewer rounds until consensus, with an optional deep-mode lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology). The generic core loop. |
|
|
59
|
+
| `thatch-refine` | Classify the project (audience, lifecycle, surface) and refine a plan under that type's requirements. Entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. |
|
|
60
|
+
| `thatch-refine-team-app` | Refinement deltas for multi-developer team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate. |
|
|
61
|
+
| `thatch-refine-personal` | Refinement deltas for solo projects with no downstream consumers: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. |
|
|
62
|
+
| `thatch-refine-shared-lib` | Refinement deltas for changes touching a library's API surface: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable. |
|
|
63
|
+
| `thatch-refine-spike` | Refinement deltas for throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on, state the question and the time box. |
|
|
64
|
+
| `thatch-refine-infra` | Refinement deltas for CI, Terraform, and k8s changes: blast radius across pipelines and environments, rollback plan and staged verification are mandatory, pattern-following is near-absolute. |
|
|
65
|
+
| `thatch-refine-oss-contribution` | Refinement deltas for PRs to repos you do not maintain: the maintainer's design authority governs, minimal diff, upstream discussion gates significant design work. |
|
|
59
66
|
| `thatch-pr-description` | Draft PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, project-context research, clarity checks, and bold+italic emphasis for scanning. |
|
|
60
67
|
| `thatch-ticket-description` | Draft ticket/issue descriptions with clear sections, project-context research, clarity checks, and bold+italic emphasis for scanning. |
|
|
61
68
|
| `thatch-split-overlarge-pr` | Split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
|
package/docs/user/README.md
CHANGED
|
@@ -328,6 +328,13 @@ response, walkthrough, and workflow skills that support the review pipeline:
|
|
|
328
328
|
| `thatch-code-walkthrough` | Explains a feature, module, or workflow to the user as a teaching walkthrough with file:line citations. Also used to draft high-level docs for new or undocumented features. |
|
|
329
329
|
| `thatch-coding-workflow` | Plans and executes code changes with a task-list-driven workflow: complexity triage, milestone planning, research before coding, post-coding verification. Pairs with `thatch-code-archaeology` (research first, then plan). |
|
|
330
330
|
| `thatch-plan-refinement` | Refines a plan before it is implemented: a fresh-context reviewer subagent re-checks the plan each round until consensus; deep mode adds specialist lenses (reuse, alternatives, hidden problems, safe-to-modify, intent archaeology). |
|
|
331
|
+
| `thatch-refine` | Classifies the project (audience, lifecycle, surface) and refines a plan under that type's requirements. The entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. |
|
|
332
|
+
| `thatch-refine-team-app` | Team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate. |
|
|
333
|
+
| `thatch-refine-personal` | Solo projects: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. |
|
|
334
|
+
| `thatch-refine-shared-lib` | Library API surfaces: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable. |
|
|
335
|
+
| `thatch-refine-spike` | Throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on. |
|
|
336
|
+
| `thatch-refine-infra` | CI, Terraform, and k8s changes: blast radius, mandatory rollback plan and staged verification, pattern-following is near-absolute. |
|
|
337
|
+
| `thatch-refine-oss-contribution` | PRs to repos you do not maintain: the maintainer's design authority governs, minimal diff, upstream discussion gates significant design work. |
|
|
331
338
|
|
|
332
339
|
### Writing skills
|
|
333
340
|
|
package/docs/user/commands.md
CHANGED
|
@@ -17,6 +17,13 @@ or you invoke it.
|
|
|
17
17
|
The agent asks before forgetting anything that may still matter.
|
|
18
18
|
- `/thatch/reflect` — run the session-reflection skill to persist what the
|
|
19
19
|
current session learned.
|
|
20
|
+
- `/thatch/refine` — refine a plan for a code change under the current
|
|
21
|
+
project type's requirements: the agent classifies the project (team,
|
|
22
|
+
personal, shared library, spike, infrastructure, or open source
|
|
23
|
+
contribution), confirms the pick, and runs the refinement loop with
|
|
24
|
+
that type's expectations applied. Text after the command can name the
|
|
25
|
+
type directly (for example, `/thatch/refine shared-lib`) to skip
|
|
26
|
+
classification.
|
|
20
27
|
- `/thatch/compact` and `/thatch/exit` — the wrap-up commands. They run a
|
|
21
28
|
pre-flight checklist (flush extraction, finish promised memory writes,
|
|
22
29
|
surface loose ends) before a compaction or an exit, gated by a greenlight
|
|
@@ -31,6 +38,7 @@ or you invoke it.
|
|
|
31
38
|
| `/thatch/extract` | yes | no | no |
|
|
32
39
|
| `/thatch/hygiene` | yes | yes | yes (as an MCP prompt) |
|
|
33
40
|
| `/thatch/reflect` | yes | yes | yes (as an MCP prompt) |
|
|
41
|
+
| `/thatch/refine` | yes | yes | yes (as an MCP prompt) |
|
|
34
42
|
| `/thatch/compact`, `/thatch/exit` | yes | no | no |
|
|
35
43
|
|
|
36
44
|
Why the gaps: `/thatch/extract` needs the invoking session's ID, which only
|
package/docs/user/skills.md
CHANGED
|
@@ -77,6 +77,18 @@ Skills are split into shared (all hosts) and opencode-only:
|
|
|
77
77
|
| `thatch-coding-workflow` | Plans and executes code changes with a task-list-driven workflow: complexity triage, milestone planning, research before coding, post-coding verification. Pairs with `thatch-code-archaeology` (research first, then plan). |
|
|
78
78
|
| `thatch-plan-refinement` | Refines a plan before it is implemented: a fresh-context reviewer subagent re-checks the plan each round until consensus; deep mode adds specialist lenses (reuse, alternatives, hidden problems, safe-to-modify, intent archaeology). |
|
|
79
79
|
|
|
80
|
+
### Plan refinement skills
|
|
81
|
+
|
|
82
|
+
| Skill | Purpose |
|
|
83
|
+
|-------|---------|
|
|
84
|
+
| `thatch-refine` | Classifies the project (audience, lifecycle, surface) and refines a plan under that type's requirements. The entry point for the `thatch-refine-*` family; also the `/thatch/refine` command. |
|
|
85
|
+
| `thatch-refine-team-app` | Team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate. |
|
|
86
|
+
| `thatch-refine-personal` | Solo projects: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest. |
|
|
87
|
+
| `thatch-refine-shared-lib` | Library API surfaces: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable. |
|
|
88
|
+
| `thatch-refine-spike` | Throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on. |
|
|
89
|
+
| `thatch-refine-infra` | CI, Terraform, and k8s changes: blast radius, mandatory rollback plan and staged verification, pattern-following is near-absolute. |
|
|
90
|
+
| `thatch-refine-oss-contribution` | PRs to repos you do not maintain: the maintainer's design authority governs, minimal diff, upstream discussion gates significant design work. |
|
|
91
|
+
|
|
80
92
|
## Stale skill cleanup
|
|
81
93
|
|
|
82
94
|
When skills are renamed or removed in a new release, thatch automatically
|
package/docs/user/watchers.md
CHANGED
|
@@ -16,6 +16,12 @@ baseline, then polls the GitHub API through the `gh` CLI once a
|
|
|
16
16
|
minute. When the target changes in a way the watcher cares about, the
|
|
17
17
|
plugin prompts the agent with a notification.
|
|
18
18
|
|
|
19
|
+
Register before you push. CI notifications fire when a check run goes
|
|
20
|
+
from running to completed, so a watcher created after a fast pipeline
|
|
21
|
+
has already finished will never fire for that commit. If every check on
|
|
22
|
+
the head is already done when the watcher is created, the response says
|
|
23
|
+
so and reports the results directly.
|
|
24
|
+
|
|
19
25
|
PR watched events:
|
|
20
26
|
|
|
21
27
|
- `pr_comment` - new top-level comments on the PR
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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
|
@@ -212,7 +212,7 @@ export type ChatHostKind = "opencode" | "mcp";
|
|
|
212
212
|
// Display names appear inside wake prompts and chat_list output, so they are
|
|
213
213
|
// capped tight. Bodies are capped so a runaway sender cannot turn a nudge
|
|
214
214
|
// into a context bomb; the reader fetches full content via chat_read anyway.
|
|
215
|
-
const MAX_BODY_LEN = 10_000;
|
|
215
|
+
export const MAX_BODY_LEN = 10_000;
|
|
216
216
|
|
|
217
217
|
// Assigned names are lowercase slugs of a pool name with a numeric counter
|
|
218
218
|
// suffix ("al-go-rithm-00001"), which satisfies NAME_CHARSET. The charset
|
package/src/commands.ts
CHANGED
|
@@ -100,6 +100,16 @@ After dispatching, call thatch_extraction_done to acknowledge.`,
|
|
|
100
100
|
description: "Tend the memory store: stale entries, orphans, pending dedup pairs",
|
|
101
101
|
body: hygieneCore(),
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
name: "refine",
|
|
105
|
+
description: "Refine a plan under the project type's requirements (classifies the project first)",
|
|
106
|
+
// A skill trigger, not a nudge behavior: the body points at the
|
|
107
|
+
// thatch-refine router skill, which classifies the project and loads
|
|
108
|
+
// the matching type deltas on top of the plan-refinement core loop.
|
|
109
|
+
body: `Load and follow the thatch-refine skill to refine a plan for the current project.
|
|
110
|
+
|
|
111
|
+
The user message may name a project type (team-app, personal, shared-lib, spike, infra, oss-contribution); if it does, skip classification and use that type. If no plan is drafted yet in this session, the skill's loop starts with drafting one.`,
|
|
112
|
+
},
|
|
103
113
|
{
|
|
104
114
|
name: "reflect",
|
|
105
115
|
description: "Persist what this session learned as memories",
|
|
@@ -108,10 +118,20 @@ After dispatching, call thatch_extraction_done to acknowledge.`,
|
|
|
108
118
|
];
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Renders a YAML double-quoted scalar. Plain scalars break on values
|
|
123
|
+
* containing ": " (invalid YAML - the mapping key would be ambiguous), and
|
|
124
|
+
* opencode's lenient fallback parser does not rescue every host path, so the
|
|
125
|
+
* description must always be quoted.
|
|
126
|
+
*/
|
|
127
|
+
function yamlQuote(value: string): string {
|
|
128
|
+
return JSON.stringify(value);
|
|
129
|
+
}
|
|
130
|
+
|
|
111
131
|
/** Renders one action as a full command markdown file. */
|
|
112
132
|
export function renderActionCommand(action: ActionDef): string {
|
|
113
133
|
return `---
|
|
114
|
-
description: ${action.description}
|
|
134
|
+
description: ${yamlQuote(action.description)}
|
|
115
135
|
---
|
|
116
136
|
${userMessageSection}
|
|
117
137
|
|
|
@@ -121,7 +141,7 @@ ${action.body}`;
|
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
const COMPACT_TEMPLATE = `---
|
|
124
|
-
description: Flush thatch persistence, check for loose ends, then compact if clear
|
|
144
|
+
description: ${yamlQuote("Flush thatch persistence, check for loose ends, then compact if clear")}
|
|
125
145
|
---
|
|
126
146
|
${userMessageSection}
|
|
127
147
|
|
|
@@ -138,7 +158,7 @@ ${COMPACT_READY_TOKEN}
|
|
|
138
158
|
If anything is outstanding, list the items concisely so the user can address them, and do NOT include the token. The session will only be compacted when the token is present.`;
|
|
139
159
|
|
|
140
160
|
const EXIT_TEMPLATE = `---
|
|
141
|
-
description: Flush thatch persistence, check for loose ends, then exit opencode if clear
|
|
161
|
+
description: ${yamlQuote("Flush thatch persistence, check for loose ends, then exit opencode if clear")}
|
|
142
162
|
---
|
|
143
163
|
${userMessageSection}
|
|
144
164
|
|
package/src/extract-queue.ts
CHANGED
|
@@ -56,10 +56,25 @@ export function appendBatch(sessionID: string, toolCalls: BatchToolCall[]): void
|
|
|
56
56
|
const additions: ToolInteraction[] = [];
|
|
57
57
|
for (const tc of toolCalls) {
|
|
58
58
|
const lower = tc.tool_name.toLowerCase();
|
|
59
|
-
if (lower === "mcp__thatch__memory_remember"
|
|
59
|
+
if (lower === "mcp__thatch__memory_remember") {
|
|
60
|
+
// The session that writes a memory has processed its queued
|
|
61
|
+
// interactions itself - drain. This is the parent's self-serve path.
|
|
60
62
|
resetMissedCount(sessionID);
|
|
61
63
|
consumeQueue(sessionID);
|
|
62
64
|
existing.length = 0;
|
|
65
|
+
} else if (lower === "mcp__thatch__extraction_done") {
|
|
66
|
+
// An extraction_done ack must NOT drain the queue. When the PARENT
|
|
67
|
+
// acks after dispatching (per the nudge), a drain here deletes the
|
|
68
|
+
// queue before the sub-agent's get_extraction_payload fetch - the
|
|
69
|
+
// accept-before-fetch race, which on this path is a hard delete
|
|
70
|
+
// (silent interaction loss). The queue is consumed instead by the
|
|
71
|
+
// extractor's completion: extraction_done with the parent's
|
|
72
|
+
// session_id (drainExtractionQueue in mcp.ts), or the parent's own
|
|
73
|
+
// memory_remember above. A no-drain ack only resets the escalation
|
|
74
|
+
// counter - the queue persists on disk (durable across
|
|
75
|
+
// interruption), and an extractor that dies without completing
|
|
76
|
+
// leaves it in place for the next nudge to re-dispatch.
|
|
77
|
+
resetMissedCount(sessionID);
|
|
63
78
|
}
|
|
64
79
|
if (lower.startsWith("mcp__thatch__")) continue;
|
|
65
80
|
if (lower === "skill" || lower === "task" || lower === "agent") continue;
|
package/src/extraction.ts
CHANGED
|
@@ -342,9 +342,10 @@ export class ExtractionPipeline {
|
|
|
342
342
|
* Requeues accepted entries that have waited longer than maxAgeMs for a
|
|
343
343
|
* completion signal that never came: the extractor sub-agent crashed
|
|
344
344
|
* without acking, or the completion ack was lost. Without this, accepted
|
|
345
|
-
* entries linger unprocessed
|
|
346
|
-
*
|
|
347
|
-
*
|
|
345
|
+
* entries linger unprocessed with nothing to re-extract them - silent
|
|
346
|
+
* loss. Returns the session ids that had entries requeued; on opencode
|
|
347
|
+
* the idle trigger re-extracts them, on MCP hosts the next flush-tools
|
|
348
|
+
* nudge picks them up.
|
|
348
349
|
*/
|
|
349
350
|
requeueStaleAccepted(maxAgeMs = ACCEPTED_STALE_MS): string[] {
|
|
350
351
|
const stale = [...this.#acceptedAt.entries()]
|
package/src/opencode/v2.ts
CHANGED
|
@@ -529,8 +529,9 @@ function buildCapabilities(context: V2Context, worktree: string, childSessions:
|
|
|
529
529
|
// returns the created SessionInfo directly.
|
|
530
530
|
const result = await session.create({ location: { directory: worktree }, title: input.title });
|
|
531
531
|
const id = result?.id;
|
|
532
|
-
// A shape mismatch must fail
|
|
533
|
-
//
|
|
532
|
+
// A shape mismatch must fail loudly: the runtime logs it and retries
|
|
533
|
+
// extraction at the next idle. It must not silently corrupt the
|
|
534
|
+
// child-session maps.
|
|
534
535
|
if (!id) throw new Error(`v2 session.create returned no id: ${JSON.stringify(result)?.slice(0, 200)}`);
|
|
535
536
|
// Register for event forwarding: the child's events carry the project
|
|
536
537
|
// directory, which the pump's directory filter would otherwise drop.
|
|
@@ -539,7 +540,7 @@ function buildCapabilities(context: V2Context, worktree: string, childSessions:
|
|
|
539
540
|
},
|
|
540
541
|
// No delete on the v2 SessionDomain: extraction child sessions are not
|
|
541
542
|
// cleaned up on v2 (documented gap; the bookkeeping maps still keep the
|
|
542
|
-
//
|
|
543
|
+
// claim/completion lifecycle consistent). Two user-visible consequences: the session
|
|
543
544
|
// picker accumulates one thatch-extraction entry per extraction, and
|
|
544
545
|
// "continue last session" (-c) logic that picks the newest top-level
|
|
545
546
|
// session will land in an extraction child after any session that
|
package/src/prompts.ts
CHANGED
|
@@ -192,6 +192,13 @@ workflows. The host auto-discovers them, but reach for them proactively:
|
|
|
192
192
|
- \`thatch-review-followup\` - alternate entrypoint for follow-up review rounds. Verifies whether the author's responses and code changes since your last review adequately addressed prior findings, offers to reply on resolved items, then optionally re-runs the full structured review.
|
|
193
193
|
- \`thatch-code-archaeology\` - investigate an existing feature, debug an unfamiliar area, or begin a new ticket. Explores the code base from multiple angles (data model, state flow, git history, sibling features) before proposing changes. The research skill; pair with \`thatch-coding-workflow\`.
|
|
194
194
|
- \`thatch-plan-refinement\` - refine a plan before implementing it: fresh-context reviewer rounds until consensus, with an optional deep-mode lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology). Use when a plan for a code change is about to be built or the user asks to stress-test a plan.
|
|
195
|
+
- \`thatch-refine\` - classify the project (audience, lifecycle, surface) and refine a plan under that type's requirements: team codebases owe coordination and intent breadcrumbs, personal projects allow cleverness, shared libraries owe API hygiene. Entry point for the thatch-refine-* family; also the /thatch/refine command.
|
|
196
|
+
- \`thatch-refine-team-app\` - refinement deltas for multi-developer team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate.
|
|
197
|
+
- \`thatch-refine-personal\` - refinement deltas for solo projects with no downstream consumers: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest.
|
|
198
|
+
- \`thatch-refine-shared-lib\` - refinement deltas for changes touching a library's API surface: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable.
|
|
199
|
+
- \`thatch-refine-spike\` - refinement deltas for throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on, state the question and the time box.
|
|
200
|
+
- \`thatch-refine-infra\` - refinement deltas for CI, Terraform, and k8s changes: blast radius across pipelines and environments, rollback plan and staged verification are mandatory, pattern-following is near-absolute.
|
|
201
|
+
- \`thatch-refine-oss-contribution\` - refinement deltas for PRs to repos you do not maintain: the maintainer's design authority governs, minimal diff, upstream discussion gates significant design work.
|
|
195
202
|
- \`thatch-change-walkthrough\` - produces a teaching walkthrough of a diff with a specific format (SYNOPSIS + per-workflow orient/mechanism/numbered-stages + change overlay mirroring the same numbers) and calibrated prose rules. Load the skill for the format and prose rules, not just for the research method.
|
|
196
203
|
- \`thatch-code-walkthrough\` - produces a teaching walkthrough for a feature/workflow as it stands today with a specific format (SYNOPSIS + per-workflow orient/mechanism/numbered-stages + Key files) and calibrated prose rules. Also use proactively to draft high-level docs for new or undocumented features (suitable for pkg/README.md or docs/features/<name>.md). Load the skill for the format and prose rules, not just for the research method.
|
|
197
204
|
- \`thatch-review-context\` - gather project context (PRs, tickets, TODOs, deferred work) before a review.
|
|
@@ -506,6 +513,13 @@ workflows. The host auto-discovers them, but reach for them proactively:
|
|
|
506
513
|
|
|
507
514
|
- \`thatch-code-archaeology\` - investigate an existing feature, debug an unfamiliar area, or begin a new ticket. Explores the code base from multiple angles (data model, state flow, git history, sibling features) before proposing changes. The research skill; pair with \`thatch-coding-workflow\`.
|
|
508
515
|
- \`thatch-plan-refinement\` - refine a plan before implementing it: fresh-context reviewer rounds until consensus, with an optional deep-mode lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology). Use when a plan for a code change is about to be built or the user asks to stress-test a plan.
|
|
516
|
+
- \`thatch-refine\` - classify the project (audience, lifecycle, surface) and refine a plan under that type's requirements: team codebases owe coordination and intent breadcrumbs, personal projects allow cleverness, shared libraries owe API hygiene. Entry point for the thatch-refine-* family; also the /thatch/refine command.
|
|
517
|
+
- \`thatch-refine-team-app\` - refinement deltas for multi-developer team codebases: consistency outranks cleverness, intent breadcrumbs are deliverables, pre-mortem and safe-to-modify lenses dominate.
|
|
518
|
+
- \`thatch-refine-personal\` - refinement deltas for solo projects with no downstream consumers: relaxed coordination, cleverness and experimentation welcome, premise verification stays honest.
|
|
519
|
+
- \`thatch-refine-shared-lib\` - refinement deltas for changes touching a library's API surface: flexible on input, strict on output, keep special cases out of the API, never foreclose caller options, docs are a deliverable.
|
|
520
|
+
- \`thatch-refine-spike\` - refinement deltas for throwaway prototypes: skip the loop, verify only the premises the experiment's conclusion depends on, state the question and the time box.
|
|
521
|
+
- \`thatch-refine-infra\` - refinement deltas for CI, Terraform, and k8s changes: blast radius across pipelines and environments, rollback plan and staged verification are mandatory, pattern-following is near-absolute.
|
|
522
|
+
- \`thatch-refine-oss-contribution\` - refinement deltas for PRs to repos you do not maintain: the maintainer's design authority governs, minimal diff, upstream discussion gates significant design work.
|
|
509
523
|
- \`thatch-change-walkthrough\` - produces a teaching walkthrough of a diff with a specific format (SYNOPSIS + per-workflow orient/mechanism/numbered-stages + change overlay mirroring the same numbers) and calibrated prose rules. Load the skill for the format and prose rules, not just for the research method.
|
|
510
524
|
- \`thatch-code-walkthrough\` - produces a teaching walkthrough for a feature/workflow as it stands today with a specific format (SYNOPSIS + per-workflow orient/mechanism/numbered-stages + Key files) and calibrated prose rules. Also use proactively to draft high-level docs for new or undocumented features (suitable for pkg/README.md or docs/features/<name>.md). Load the skill for the format and prose rules, not just for the research method.
|
|
511
525
|
- \`thatch-review-context\` - gather project context (PRs, tickets, TODOs, deferred work) before a review.
|
|
@@ -788,46 +802,25 @@ export function reflectCore(tool: ToolNamer): string {
|
|
|
788
802
|
}
|
|
789
803
|
|
|
790
804
|
/**
|
|
791
|
-
* Extraction nudge with escalation
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
805
|
+
* Extraction nudge with escalation - MCP hosts only (Claude Code / Cursor).
|
|
806
|
+
* There is deliberately no opencode variant: the opencode plugin extracts via
|
|
807
|
+
* its own child sessions (triggerExtraction at session idle) and no longer
|
|
808
|
+
* asks the model to run the handshake. Only hosts with no plugin lifecycle
|
|
809
|
+
* (each hook a fresh process, no session-creation API) still need a model-
|
|
810
|
+
* driven dispatch, and their file-backed queue (extract-queue.ts) is durable
|
|
811
|
+
* until the extractor's completion, so the handshake cannot lose entries
|
|
812
|
+
* there.
|
|
813
|
+
*
|
|
814
|
+
* The missedCount parameter tracks consecutive nudges delivered without any
|
|
815
|
+
* memory_remember call in between, escalating the tone from polite to
|
|
816
|
+
* insistent to shouting.
|
|
796
817
|
*
|
|
797
818
|
* The sessionID replaces the inline payload: the nudge tells the model to
|
|
798
819
|
* dispatch a sub-agent that calls get_extraction_payload to fetch the queued
|
|
799
820
|
* interactions as a tool response. This keeps the full payload out of the
|
|
800
821
|
* main session's context window.
|
|
801
|
-
*
|
|
802
|
-
* Call extractionNudgeOpencode for the opencode plugin path (task tool with
|
|
803
|
-
* background parameter) or extractionNudgeMcp for the Claude Code/Cursor
|
|
804
|
-
* CLI path (generic sub-agent wording).
|
|
805
822
|
*/
|
|
806
823
|
export function extractionNudge(
|
|
807
|
-
count: number,
|
|
808
|
-
missedCount: number,
|
|
809
|
-
toolName: string,
|
|
810
|
-
sessionID: string,
|
|
811
|
-
): string {
|
|
812
|
-
const isOpencode = toolName.startsWith("thatch_");
|
|
813
|
-
return isOpencode
|
|
814
|
-
? extractionNudgeOpencode(count, missedCount, sessionID)
|
|
815
|
-
: extractionNudgeMcp(count, missedCount, sessionID);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
function extractionNudgeOpencode(
|
|
819
|
-
count: number,
|
|
820
|
-
missedCount: number,
|
|
821
|
-
sessionID: string,
|
|
822
|
-
): string {
|
|
823
|
-
const plural = count === 1 ? "" : "s";
|
|
824
|
-
const dispatch = 'Dispatch a task with background: true and subagent_type: "general" (required for thatch tool access)';
|
|
825
|
-
const drainTool = "thatch_extraction_done";
|
|
826
|
-
const fetchTool = "thatch_get_extraction_payload";
|
|
827
|
-
return buildExtractionNudge(count, missedCount, plural, dispatch, drainTool, fetchTool, sessionID);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function extractionNudgeMcp(
|
|
831
824
|
count: number,
|
|
832
825
|
missedCount: number,
|
|
833
826
|
sessionID: string,
|