@jeffober/thatch 0.1.46 → 0.1.47
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 +2 -2
- package/artifacts/skills/thatch-plan-refinement.md +80 -0
- package/bin/thatch +17 -8
- package/docs/dev/README.md +3 -3
- package/docs/dev/features/cli.md +1 -1
- package/docs/dev/features/database.md +25 -0
- package/docs/dev/features/multi-host.md +3 -1
- package/docs/dev/features/qa-system.md +1 -1
- package/docs/dev/features/repo-identity.md +110 -0
- package/docs/dev/features/watchers.md +8 -0
- package/docs/dev/skills.md +1 -0
- package/docs/plans/worktree-deletion-recovery.md +246 -0
- package/docs/user/README.md +2 -1
- package/docs/user/skills.md +1 -0
- package/docs/user/watchers.md +7 -2
- package/package.json +1 -1
- package/src/db.ts +57 -0
- package/src/git.ts +198 -17
- package/src/hygiene.ts +6 -2
- package/src/index.ts +27 -9
- package/src/mcp.ts +4 -2
- package/src/prompts.ts +2 -0
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +11 -4
- package/src/watchers.ts +26 -0
package/README.md
CHANGED
|
@@ -175,8 +175,8 @@ Thatch gives your agent:
|
|
|
175
175
|
or quiet notifications entirely.
|
|
176
176
|
|
|
177
177
|
Plus **skills** for memory workflows, structured multi-specialist code
|
|
178
|
-
review, review response, change and feature walkthroughs,
|
|
179
|
-
knowledge export, and writing tasks
|
|
178
|
+
review, review response, plan refinement, change and feature walkthroughs,
|
|
179
|
+
memory verification, knowledge export, and writing tasks
|
|
180
180
|
(PR descriptions, tickets, PR splitting).
|
|
181
181
|
|
|
182
182
|
See the [user guide](https://github.com/sysread/thatch/wiki/Guide%3A-Overview) for the full tool list, CLI
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: thatch-plan-refinement
|
|
3
|
+
description: Refine a plan for a code change by dispatching a fresh-context reviewer subagent to hunt for gaps, unverified premises, and unexpected side effects, then revising and re-reviewing until consensus. Use when a plan is about to be implemented, when the user asks to refine, stress-test, or double-check a plan, or after a plan revision that incorporated prior findings. Quick mode is one fresh reviewer per round; deep mode adds a specialist lens fan-out (reuse, alternatives, hidden problems, safe-to-modify, archaeology).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are refining a plan before code exists. A plan written by the same context that researched the problem inherits that context's blind spots. A reviewer with no stake in the plan and no memory of writing it finds what the author cannot. Refinement is the loop: draft, review, revise, repeat until consensus. Each pass tightens the plan; a round with no findings means the plan is done, not that the loop failed.
|
|
7
|
+
|
|
8
|
+
This is the plan-stage counterpart to code review: code review applies multi-lens scrutiny to a diff that exists; refinement applies it to a plan, where findings cost nothing to fix.
|
|
9
|
+
|
|
10
|
+
## Mode triage
|
|
11
|
+
|
|
12
|
+
Pick the mode before starting, and say which you picked.
|
|
13
|
+
|
|
14
|
+
**Skip refinement** for single-file mechanical changes, typo or docs-only changes, and changes that follow an established pattern with no design decisions. A single self-review pass is enough; running this loop on a five-line change is waste.
|
|
15
|
+
|
|
16
|
+
**Quick mode (default)** — one fresh-context reviewer per round. Use for everything that is not trivial and not deep-mode material.
|
|
17
|
+
|
|
18
|
+
**Deep mode** — adds a one-time specialist lens fan-out (see below) before the consensus rounds. Use when the plan: touches shared or persistent state (database tables, files, config) that other consumers read; spans many files or multiple hosts/processes; introduces a new abstraction, table, or contract; or when the user asks for depth. Deep mode runs the lenses ONCE, in the first round; the remaining rounds are quick-mode consensus on the revised plan.
|
|
19
|
+
|
|
20
|
+
## The loop
|
|
21
|
+
|
|
22
|
+
1. **Draft the plan.** Every factual premise carries a `file:line` citation you personally verified. A premise you did not check is a guess wearing a citation; verify it or mark it unverified.
|
|
23
|
+
2. **Dispatch one reviewer** (read-only). The prompt must instruct it to:
|
|
24
|
+
- Verify each premise against the actual code, with `file:line` evidence.
|
|
25
|
+
- Give each plan step a verdict: CORRECT, NEEDS ADJUSTMENT (how), or WRONG (why).
|
|
26
|
+
- Hunt for gaps by severity (blocker / should-fix / nice-to-have), unexpected side effects, blast radius, and the question "how does this fail in production?" — concurrency, stale state, multi-host interactions, test flakiness, and behavior changes for existing callers.
|
|
27
|
+
- Say explicitly when it cannot verify something, rather than guessing.
|
|
28
|
+
3. **Revise the plan** from the findings. Fix what is wrong, adjust what needs adjusting, and record what you rejected and why.
|
|
29
|
+
4. **Dispatch a NEW reviewer with fresh context** on the revised plan. Never continue the prior reviewer's session, and never tell it what previous reviewers said — prior findings anchor the next reviewer, and it will inherit the earlier reviewer's blind spots instead of finding its own. Present the revised plan as if it were the original.
|
|
30
|
+
5. **Consensus** is a round where the reviewer reports no blockers, no unaddressed should-fixes, and the plan's premises all verify. Two consecutive quiet rounds is strong consensus; one is enough to build.
|
|
31
|
+
6. **Cap at three review rounds.** If the plan has not converged by then, stop looping and put the remaining disagreements in front of the user — continued revision usually means the design problem, not the plan, is unresolved.
|
|
32
|
+
|
|
33
|
+
## Deep mode: the lenses
|
|
34
|
+
|
|
35
|
+
Run each lens as its own fresh read-only agent, in parallel where the host allows. The lenses see the plan plus the problem context — never prior review findings. Before the fan-out, run `thatch_memory_recall` for the plan's topic and include what comes back as a "prior knowledge from project memory" block in every lens prompt: lens agents are plain read-only sub-agents WITHOUT thatch MCP tools, so the coordinator is the only side of the fan-out that can reach project memory — a lens told to "consult project memory" will report a spurious unverifiable-input hole instead (observed in the first dogfood run). Mark the block as unverified context, not established fact; the archaeology lens treats it as a lead to check, not a source to cite. Collect the results, synthesize (dedupe by root cause, severity-group, keep only verified findings), revise the plan, then continue with quick-mode rounds.
|
|
36
|
+
|
|
37
|
+
**Pattern and reuse.** Find existing patterns, helpers, and abstractions in the codebase that the plan could reuse or extend instead of adding new surface. The verification gate: the lens must READ the code it cites and show the contract fits the plan's need — a pattern claim from memory or a name match is not evidence, and a wrong "reuse X" sends the design down a bad path. Output: reusable candidates with `file:line` and fit analysis, or an explicit "none found".
|
|
38
|
+
|
|
39
|
+
**Alternatives.** Research materially different solutions to the same problem and judge the juice-to-squeeze ratio against the plan's actual constraint set. Budget: at most two alternatives, each argued in a paragraph — this lens is a filter, not a survey. "No better alternative found" is a valid, expected, honest output; padding with straw men is worse than an empty result.
|
|
40
|
+
|
|
41
|
+
**Hidden problems (pre-mortem).** Enumerate every consumer of each symbol, table, file, and config value the plan touches (grep, not memory). Enumerate state the plan writes, moves, or invalidates, and who reads it after the change — including other hosts, processes, and hook paths sharing the same database or files. Then mentally apply the change and diff the failure modes: what breaks downstream, what silently changes meaning. This lens owns the question "who knows about this state that the plan does not?"
|
|
42
|
+
|
|
43
|
+
**Safe-to-modify audit.** For each code section the plan must touch: is there test coverage that would catch a regression (name the test), and is the section in shape to modify without a rewrite? Verdict per section: **safe**, **refactor-first** (the plan gains an explicit dependency: refactor before the change), or **stop-and-ask** (structural problem — surface to the user before building). Audit only sections the plan touches; auditing the whole repo is out of scope. A missing-coverage section is refactor-first or stop-and-ask, never "safe, we'll add tests later" — coverage comes before the change, not after.
|
|
44
|
+
|
|
45
|
+
**Archaeology (intent).** For the code in the change's path, find out WHY it is the way it is: git history (commits, PR descriptions and review discussions, merge messages), ticket history, code comments, and docs — all reachable from a read-only agent via git and the gh CLI. Project memory arrives only via the coordinator-injected prior-knowledge block in your prompt (you have no memory tools); treat it as a lead to verify, not a citable source. Every intent claim cites its evidence. Where the evidence runs out, record exactly that — "unknown, not enough detail available" — and stop. NEVER construct a plausible intent from how the code looks: unstated intent is treated as unknown, and the plan must not build on assumed intent. The deliverable is a map of evidenced intent plus an explicit unknowns list, so the plan can ask the user about the unknowns instead of hallucinating past them.
|
|
46
|
+
|
|
47
|
+
## Dispatching across hosts
|
|
48
|
+
|
|
49
|
+
The fan-out needs sub-agents, and every host has them — couch the instructions per host, the same way code review does:
|
|
50
|
+
|
|
51
|
+
- **opencode**: Task tool, read-only explore-type agent, one agent per lens.
|
|
52
|
+
- **Claude Code**: Agent tool; the skill runs inline, each lens is one Agent dispatch.
|
|
53
|
+
- **Cursor**: background subagents, one per lens.
|
|
54
|
+
|
|
55
|
+
Single level of dispatch only — lenses never dispatch further agents. If the host or session cannot dispatch sub-agents, degrade honestly: run each lens as a separate focused self-review pass in sequence, and tell the user the review is weaker than the real thing (same-context review misses what fresh-context review catches).
|
|
56
|
+
|
|
57
|
+
## When the user insists on proceeding with open findings
|
|
58
|
+
|
|
59
|
+
Do not argue, and do not silently drop the findings. Reduce the risk of building with known holes:
|
|
60
|
+
|
|
61
|
+
- For each open finding, attach an explicit mitigation or mark it as an accepted risk. A mitigation is one of: a test that guards the risky behavior, a rollout or feature-flag plan, a log or assertion that makes the failure loud when it happens, or a documented rollback path.
|
|
62
|
+
- "We discussed it and the user said go" is not a mitigation. Name what will catch the failure if the reviewer was right.
|
|
63
|
+
- Present the list of accepted risks to the user in one place before implementation starts, and keep it with the plan so a future session can see what was knowingly left open.
|
|
64
|
+
|
|
65
|
+
## Reviewer prompt rules
|
|
66
|
+
|
|
67
|
+
- Give the reviewer the problem context and the plan. Give it neither your research transcript nor earlier review findings.
|
|
68
|
+
- Demand `file:line` evidence for every claim, and explicit "unverified" where it cannot check.
|
|
69
|
+
- Ask it to state whether the plan's own list of affected surfaces is complete or over-broad. Plan authors over-scope and under-scope in equal measure.
|
|
70
|
+
- Tell it the repo is read-only for this task.
|
|
71
|
+
|
|
72
|
+
## Cost control
|
|
73
|
+
|
|
74
|
+
One reviewer per round in quick mode; one lens fan-out total in deep mode, then quick rounds. Foreground dispatches — the loop is sequential by design, and the fresh-context requirement, not reviewer count, is what buys independence. Parallel reviewers multiply context spend without adding much signal at plan stage.
|
|
75
|
+
|
|
76
|
+
## Relationship to other skills
|
|
77
|
+
|
|
78
|
+
- **`thatch-code-archaeology`** — the research skill for understanding the code; its output is often the plan's input. The archaeology lens is a targeted, plan-scoped re-run of its git-history step, not a replacement for it.
|
|
79
|
+
- **`thatch-coding-workflow`** — the procedure skill for executing the refined plan. Refinement ends where coding-workflow begins.
|
|
80
|
+
- **`thatch-code-review`** — the post-implementation multi-lens pipeline. Same machinery, different artifact and stage. Findings from code review that reveal a plan flaw are a signal to refine the plan for the NEXT change, not to loop backwards.
|
package/bin/thatch
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import pkg from "../package.json" with { type: "json" };
|
|
6
|
-
import { ThatchDB } from "../src/db";
|
|
6
|
+
import { ThatchDB, repoPathCache } from "../src/db";
|
|
7
7
|
import { detectRepo } from "../src/git";
|
|
8
8
|
import { BgeEmbeddingModel } from "../src/embeddings";
|
|
9
9
|
import { hygieneReport } from "../src/hygiene";
|
|
@@ -75,9 +75,13 @@ global store. Use "all" with search to search project + global together.`);
|
|
|
75
75
|
|
|
76
76
|
async function defaultStore(): Promise<string> {
|
|
77
77
|
try {
|
|
78
|
-
|
|
78
|
+
// The cache recovers identity when the project directory is gone
|
|
79
|
+
// (deleted worktree). Same unknown-to-global mapping the tool layer
|
|
80
|
+
// applies, so the CLI never lands on a literal "unknown" store.
|
|
81
|
+
const repo = await detectRepo(undefined, repoPathCache(db));
|
|
82
|
+
return repo === "unknown" ? "global" : repo;
|
|
79
83
|
} catch {
|
|
80
|
-
return "
|
|
84
|
+
return "global";
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
|
|
@@ -97,7 +101,10 @@ async function chatHookLine(hookSessionID?: string, transcriptPath?: string): Pr
|
|
|
97
101
|
if (!chatEnabled(loadConfig(dbPath).config)) return "";
|
|
98
102
|
let repo: string | null = null;
|
|
99
103
|
try {
|
|
100
|
-
|
|
104
|
+
const detected = await detectRepo(undefined, repoPathCache(db));
|
|
105
|
+
// unknown-to-global: chat registration must follow the same store the
|
|
106
|
+
// tools would read, even when the project directory is gone.
|
|
107
|
+
repo = detected === "unknown" ? "global" : detected;
|
|
101
108
|
} catch {
|
|
102
109
|
return "";
|
|
103
110
|
}
|
|
@@ -730,7 +737,7 @@ switch (command) {
|
|
|
730
737
|
const worktree = projectDir();
|
|
731
738
|
let hygiene: string | null = null;
|
|
732
739
|
try {
|
|
733
|
-
hygiene = await hygieneReport(db, repo, worktree);
|
|
740
|
+
hygiene = await hygieneReport(db, repo, worktree, repoPathCache(db));
|
|
734
741
|
} catch (err) {
|
|
735
742
|
console.error(`[thatch] hygiene report failed: ${err}`);
|
|
736
743
|
}
|
|
@@ -768,7 +775,7 @@ switch (command) {
|
|
|
768
775
|
case "hygiene": {
|
|
769
776
|
const repo = await defaultStore();
|
|
770
777
|
const worktree = projectDir();
|
|
771
|
-
const hygiene = await hygieneReport(db, repo, worktree);
|
|
778
|
+
const hygiene = await hygieneReport(db, repo, worktree, repoPathCache(db));
|
|
772
779
|
console.log(hygiene ?? "Store is healthy.");
|
|
773
780
|
break;
|
|
774
781
|
}
|
|
@@ -1003,7 +1010,8 @@ switch (command) {
|
|
|
1003
1010
|
const { detectSkew, readNpmCacheForUpdate } = await import("../src/version-check");
|
|
1004
1011
|
|
|
1005
1012
|
let repo = "unknown";
|
|
1006
|
-
try { repo = await detectRepo(); } catch {}
|
|
1013
|
+
try { repo = await detectRepo(undefined, repoPathCache(db)); } catch {}
|
|
1014
|
+
if (repo === "unknown") repo = "global";
|
|
1007
1015
|
|
|
1008
1016
|
// Version checks (best-effort, no network, instant):
|
|
1009
1017
|
// - Skew: the running MCP server's version file differs from this
|
|
@@ -1214,7 +1222,8 @@ switch (command) {
|
|
|
1214
1222
|
}
|
|
1215
1223
|
|
|
1216
1224
|
let repo = "unknown";
|
|
1217
|
-
try { repo = await detectRepo(); } catch {}
|
|
1225
|
+
try { repo = await detectRepo(undefined, repoPathCache(db)); } catch {}
|
|
1226
|
+
if (repo === "unknown") repo = "global";
|
|
1218
1227
|
|
|
1219
1228
|
const { sidebandPredictions, sidebandSocketPath } = await import("../src/sideband");
|
|
1220
1229
|
const { predictionNudge } = await import("../src/prompts");
|
package/docs/dev/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Shared core
|
|
|
22
22
|
├── tool-defs.ts → single source of truth: zod schemas + execute logic
|
|
23
23
|
├── db.ts → SQLite CRUD, cosine search (recall + search), dedup verdicts
|
|
24
24
|
├── embeddings.ts → embedding model via transformers.js
|
|
25
|
-
├── git.ts → detect repo identity (store name)
|
|
25
|
+
├── git.ts → detect repo identity (store name) + worktree-deletion recovery (repo_paths cache)
|
|
26
26
|
├── hygiene.ts → hygiene report (pending dedups, stale, orphaned branches)
|
|
27
27
|
├── prompts.ts → system prompt, compaction, reminders, recall/prediction/behavior nudges, prompt cores, CLAUDE.md instructions
|
|
28
28
|
├── commands.ts → /thatch slash-command registry: actions from prompt cores + wrap-ups, per-host sync
|
|
@@ -72,7 +72,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
72
72
|
| `index.ts` | OpenCode plugin entry. Wires DB, model, extraction; registers tools and hooks; installs skills. Internal state beyond the extraction pipeline: `extracting` set (parent IDs with an active direct-extraction child), `childMetrics` map (new/updated/deleted counts per child session), and `triggerExtraction(parentID)` (creates a child session via the SDK client and prompts it with the extraction payload). |
|
|
73
73
|
| `setup.ts` | `thatch setup --claude` / `--cursor` installer. Writes MCP config (`.mcp.json` / `.cursor/mcp.json`), appends to CLAUDE.md / AGENTS.md (idempotent), installs hooks in settings.json / hooks.json, installs skills. |
|
|
74
74
|
| `hygiene.ts` | Hygiene report: pending dedup pairs, stale count, orphaned branch memories. Shared by the plugin's session-start hook and the CLI's `thatch reminder` command. |
|
|
75
|
-
| `git.ts` | Parse `owner/repo` from git remote. Worktree-safe fallback chain. |
|
|
75
|
+
| `git.ts` | Parse `owner/repo` from git remote. Worktree-safe fallback chain, plus recovery from the `repo_paths` cache when the directory is deleted, and the spawn-cwd fallback. See [features/repo-identity.md](features/repo-identity.md). |
|
|
76
76
|
| `db.ts` | SQLite schema, CRUD for entries/stores, brute-force cosine search (`search` = pure scoring, `recall` = search + telemetry stamping), dedup-pair verdict tracking. Prediction tables: matchers, predictions, edges, provenance. `scorePredictionNudge` is the shared auto-fire entry point for both host paths. Behavior tables: same four-table shape (matchers, behaviors, edges, provenance) with `scoreBehaviorNudge` as the shared entry point. |
|
|
77
77
|
| `embeddings.ts` | Lazy-load the embedding model. Expose `queryEmbed`/`passageEmbed` and the model `name` (stored as an informational tag). `MockEmbeddingModel` for tests. |
|
|
78
78
|
| `extraction.ts` | Per-session in-memory ring buffer (cap 20) that buffers non-thatch tool interactions and serializes them into the JSON payload the `get_extraction_payload` tool returns. The in-memory pipeline is opencode-only, but the payload builders (`buildExtractionPayload`, `deriveTitle`) are shared by both paths — `extract-queue.ts` imports `deriveTitle`, `mcp.ts` imports `buildExtractionPayload` for the extraction payload provider. `summarizeArgs` is used internally by `buildExtractionPayload`. |
|
|
@@ -86,7 +86,7 @@ bin/thatch → CLI: stores|list|show|forget|search|mcp|reminder|hygi
|
|
|
86
86
|
| `sideband.ts` | Unix domain socket server + client. The MCP server (long-lived, warm model) runs `SidebandServer` so one-shot hook processes can ask it to embed a prompt and search for matches without loading the model themselves. Handles three methods: `match` (recall nudge), `predictions` (prediction auto-fire), and `behaviors` (behavior auto-fire). Socket path is a hash of the DB path — both processes compute it independently. |
|
|
87
87
|
| `prompts.ts` | Text constants: opencode system prompt, compaction context, session-start reminder, prompt-aware recall nudge (`recallNudge` / `claudeRecallNudge`), prediction nudge (`predictionNudge`), behavior nudge (`behaviorNudge`), prediction verb selection (`predictionVerb`), watcher notification (`watcherNotificationNudge`), version warning (`versionWarningNudge`), chat wake notification (`chatNotificationNudge`), chat transcript echo text and echo recognizer (`chatEchoText` / `isChatEchoParts`), Claude Code CLAUDE.md instructions, Cursor AGENTS.md instructions, Claude Code hook text. Also the prompt cores (`defragCore`, `hygieneCore`, `reflectCore`, `extractionCore`) - host-agnostic behavior instruction bodies shared by nudges and `/thatch` commands, parameterized by a `ToolNamer` for per-host tool spellings. See [features/commands.md](features/commands.md). |
|
|
88
88
|
| `commands.ts` | Slash-command registry and sync. `actionDefs(tool)` renders the on-demand actions (defrag, extract, hygiene, reflect) from the prompt cores; the wrap-up commands (compact, exit) are bespoke greenlight-gated templates. Renderers produce full command files for opencode (`installOpencodeCommands`, synced by the plugin at init) and Claude Code (`installClaudeCommands`, synced by setup); `compilePrompts` in `mcp.ts` exposes the same actions as MCP prompts for Cursor. A parity-guard test pins the three sets to the documented exclusions. See [features/commands.md](features/commands.md). |
|
|
89
|
-
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (fact-extractor, dedup-classifier, project-primer, the review specialists, review synthesizer, review context, code archaeology, review followup, review response, change walkthrough, code walkthrough, session reflection, coding-workflow, thatch-pr-description, thatch-ticket-description, thatch-split-overlarge-pr, memory-verify, knowledge-export — work on all three hosts) and `OPENCODE_ONLY_SKILLS` (code-review coordinator — requires sub-agent support, not installed for Claude Code or Cursor). `installSkills(dir, skills)` defaults to `SHARED_SKILLS`; the opencode plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`. |
|
|
89
|
+
| `skills.ts` | `SKILL.md` content for all thatch skills, plus the installer. Skills are split into `SHARED_SKILLS` (fact-extractor, dedup-classifier, project-primer, the review specialists, review synthesizer, review context, code archaeology, review followup, review response, change walkthrough, code walkthrough, session reflection, coding-workflow, plan-refinement, thatch-pr-description, thatch-ticket-description, thatch-split-overlarge-pr, memory-verify, knowledge-export — work on all three hosts) and `OPENCODE_ONLY_SKILLS` (code-review coordinator — requires sub-agent support, not installed for Claude Code or Cursor). `installSkills(dir, skills)` defaults to `SHARED_SKILLS`; the opencode plugin passes `[...SHARED_SKILLS, ...OPENCODE_ONLY_SKILLS]`. |
|
|
90
90
|
| `scoring-engine.ts` | Generic four-table scoring engine with Bayesian confidence. Shared base for prediction and behavior engines — each wraps it with table-specific names. |
|
|
91
91
|
| `prediction.ts` | Thin wrapper around `ScoringEngine` with prediction-specific table names. |
|
|
92
92
|
| `behavior.ts` | Thin wrapper around `ScoringEngine` with behavior-specific table names. |
|
package/docs/dev/features/cli.md
CHANGED
|
@@ -31,7 +31,7 @@ The thatch CLI is a Bun script at `bin/thatch`. It provides memory inspection, M
|
|
|
31
31
|
- Unknown command or missing required arg calls `usage()` and exits 1.
|
|
32
32
|
- Args are positional, parsed by hand from `process.argv.slice(2)`. No arg-parsing library.
|
|
33
33
|
- DB opened once at startup, closed at end (except `mcp` which closes early).
|
|
34
|
-
- Stores default to git remote detected by `detectRepo()`; "unknown"
|
|
34
|
+
- Stores default to git remote detected by `detectRepo()`; "unknown" (no git repo, or a deleted project directory with no cache entry) degrades to "global". "global" is the shared store. "all" (search only) means project + global.
|
|
35
35
|
|
|
36
36
|
## prime
|
|
37
37
|
|
|
@@ -18,6 +18,7 @@ The tables fall into these groups:
|
|
|
18
18
|
| Prediction engine | `prediction_matchers`, `predictions`, `prediction_edges`, `prediction_provenance` | User decision model |
|
|
19
19
|
| Behavior engine | `behavior_matchers`, `behaviors`, `behavior_edges`, `behavior_provenance` | LLM self-discipline rules |
|
|
20
20
|
| Cross-session chat | `chat_sessions`, `chat_messages` | Session directory + message inbox (shared across opencode processes) |
|
|
21
|
+
| Repo identity | `repo_paths` | Worktree-to-main-checkout cache for identity recovery after deletion |
|
|
21
22
|
|
|
22
23
|
The prediction and behavior engines share the same four-table shape (matchers, items, edges, provenance) with different table names. They are separate because the semantics differ: predictions model what the user wants; behaviors model what the LLM should do.
|
|
23
24
|
|
|
@@ -264,6 +265,30 @@ chat_messages(
|
|
|
264
265
|
|
|
265
266
|
See [cross-session-chat.md](cross-session-chat.md) for the delivery model.
|
|
266
267
|
|
|
268
|
+
### repo_paths
|
|
269
|
+
|
|
270
|
+
```sql
|
|
271
|
+
repo_paths(
|
|
272
|
+
worktree_path TEXT PRIMARY KEY,
|
|
273
|
+
main_path TEXT NOT NULL,
|
|
274
|
+
repo_slug TEXT NOT NULL,
|
|
275
|
+
resolved_at INTEGER NOT NULL
|
|
276
|
+
)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- Records, per worktree directory (realpath-canonicalized key), the main
|
|
280
|
+
checkout path and the repo identity that `detectRepo` resolved while the
|
|
281
|
+
directory was alive. Read after the directory is deleted -- the recovery
|
|
282
|
+
and validation rules live in [repo-identity.md](repo-identity.md).
|
|
283
|
+
- Written by `detectRepo` on successful remote or common-dir resolution,
|
|
284
|
+
never from the basename fallback. Rows are evicted only on definitive
|
|
285
|
+
invalidation (the main checkout exists but is no longer the recorded
|
|
286
|
+
repo); a missing main checkout keeps the row, since a re-clone may be in
|
|
287
|
+
progress.
|
|
288
|
+
|
|
289
|
+
See [repo-identity.md](repo-identity.md) for the resolution and eviction
|
|
290
|
+
rules.
|
|
291
|
+
|
|
267
292
|
## Schema migration
|
|
268
293
|
|
|
269
294
|
`recall_count`, `last_recalled_at`, and `archived` are added to pre-existing databases by an idempotent column migration at init. The migration uses `PRAGMA table_info` to check for the column's existence, then `ALTER TABLE ADD COLUMN` if missing. This handles databases created before these columns existed.
|
|
@@ -42,7 +42,9 @@ startup.
|
|
|
42
42
|
`experimental.chat.system.transform` hook. The hook calls `systemPrompt(repo)`
|
|
43
43
|
and pushes the result into `output.system` every turn. No files are written.
|
|
44
44
|
The repo name is baked in at runtime, so the prompt always reflects the current
|
|
45
|
-
worktree.
|
|
45
|
+
worktree. Identity detection reads the session's own directory (not just the
|
|
46
|
+
framework worktree) and recovers from the `repo_paths` cache when that
|
|
47
|
+
directory was deleted - see [repo-identity.md](repo-identity.md).
|
|
46
48
|
|
|
47
49
|
**Tools**: registered through opencode's `tool()` wrapper in `src/tools.ts`,
|
|
48
50
|
which adds the `thatch_` prefix. The agent sees `thatch_memory_remember`,
|
|
@@ -126,7 +126,7 @@ with isolated `HOME`, `XDG_CONFIG_HOME`, `THATCH_DB_PATH`, and
|
|
|
126
126
|
`THATCH_QUEUE_DIR` environment variables. The repo is read-only during QA —
|
|
127
127
|
sessions run inside `/tmp/thatch-qa/<uc-name>/` copies.
|
|
128
128
|
|
|
129
|
-
The `
|
|
129
|
+
The `qa` skill (`.opencode/skills/qa/SKILL.md`) instructs the
|
|
130
130
|
LLM on how to execute a single use case in the isolated directory. The skill's
|
|
131
131
|
safety contract is intentionally fixed text — do not paraphrase it.
|
|
132
132
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Repo identity
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Every thatch surface keys memory on a repo identity string (the "project
|
|
6
|
+
store"). Identity is resolved once per process at init by `detectRepo()`,
|
|
7
|
+
then threaded as the default store to tools, prompts, nudges, and chat
|
|
8
|
+
registration. This feature covers how identity is resolved, and how it
|
|
9
|
+
survives the deletion of the directory identity was resolved from -- the
|
|
10
|
+
worktree-merged-and-cleaned-up case.
|
|
11
|
+
|
|
12
|
+
The same resolution also decides where git subprocesses spawn: watchers
|
|
13
|
+
and branch listing run `git`/`bash` with `cwd` set to the project
|
|
14
|
+
directory, which stops working when that directory is deleted.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
### Resolution chain (`detectRepo`, src/git.ts)
|
|
19
|
+
|
|
20
|
+
1. Parse `owner/repo` from `git remote get-url origin`.
|
|
21
|
+
2. Fall back to the basename of the main checkout, resolved from
|
|
22
|
+
`git rev-parse --path-format=absolute --git-common-dir`. The absolute
|
|
23
|
+
flag makes the output uniform: a linked worktree reports the MAIN
|
|
24
|
+
checkout's `.git`, a root checkout reports its own -- so the basename
|
|
25
|
+
is the repo, never the worktree/branch directory.
|
|
26
|
+
3. Fall back to the directory basename (existing, non-git directories).
|
|
27
|
+
4. When the directory no longer exists, steps 1-2 cannot run (spawning
|
|
28
|
+
with a deleted cwd rejects), so the `repo_paths` cache is consulted;
|
|
29
|
+
a miss returns `"unknown"` -- never the basename, which for a worktree
|
|
30
|
+
is the branch name and would mint a plausible-but-wrong store.
|
|
31
|
+
|
|
32
|
+
Callers treat `"unknown"` as unknown identity: the tool layer maps it to
|
|
33
|
+
the global store, and the CLI's `defaultStore()` applies the same mapping.
|
|
34
|
+
|
|
35
|
+
### The `repo_paths` cache (src/db.ts)
|
|
36
|
+
|
|
37
|
+
One row per worktree directory (realpath-canonicalized key): the main
|
|
38
|
+
checkout path and the repo slug recorded while the directory was alive.
|
|
39
|
+
Writes happen only on successful resolution via steps 1-2, never from the
|
|
40
|
+
basename fallback. Access is injected (`RepoPathCache` interface, adapted
|
|
41
|
+
by `repoPathCache(db)` in src/db.ts) so src/git.ts stays database-free.
|
|
42
|
+
|
|
43
|
+
Path-form handling matters on macOS, where `/var/...` and
|
|
44
|
+
`/private/var/...` are the same directory in different forms. Writes
|
|
45
|
+
canonicalize with `realpathSync` (the directory exists at write time);
|
|
46
|
+
reads try the raw path first, then the parent-realpath form, because a
|
|
47
|
+
deleted directory cannot itself be realpathed.
|
|
48
|
+
|
|
49
|
+
### Recovery and eviction rules
|
|
50
|
+
|
|
51
|
+
A cache hit is validated against the CURRENT state of its main checkout
|
|
52
|
+
before it is trusted:
|
|
53
|
+
|
|
54
|
+
| Observation | Verdict |
|
|
55
|
+
|-------------|---------|
|
|
56
|
+
| Main checkout missing (`statSync` throws) | Keep row, degrade to `"unknown"` -- a re-clone may be in progress |
|
|
57
|
+
| Spawn failure against main checkout | Keep row (transient) |
|
|
58
|
+
| Main checkout live but `git rev-parse` exits nonzero | Evict (definitive: no longer a repo) |
|
|
59
|
+
| `git remote get-url origin` parses to a different slug | Evict (definitive: path recycled or re-cloned from elsewhere) |
|
|
60
|
+
| Remote matches (or, with no remote, the slug still equals the main checkout's basename) | Valid -- identity recovered |
|
|
61
|
+
|
|
62
|
+
Eviction is definitive-only. A wrongly evicted row permanently downgrades
|
|
63
|
+
identity to `"unknown"`, which is worse than serving a stale row for one
|
|
64
|
+
more call.
|
|
65
|
+
|
|
66
|
+
### The spawn-cwd fallback (`resolveSpawnCwd`, src/git.ts)
|
|
67
|
+
|
|
68
|
+
`resolveSpawnCwd(dir, cache)` returns the directory when it exists, else
|
|
69
|
+
the validated main checkout from the cache, else null. Worktrees share
|
|
70
|
+
`refs/heads` and config with the main checkout -- only HEAD and the index
|
|
71
|
+
are per-worktree -- so git reads are equivalent from either. Consumers:
|
|
72
|
+
|
|
73
|
+
- `listBranches` falls back to the main checkout instead of returning
|
|
74
|
+
`[]` (which made hygiene skip the orphaned-branch check).
|
|
75
|
+
- The opencode plugin wraps its watcher `commandRunner` with
|
|
76
|
+
`withCwdFallback` (src/watchers.ts): the spawn cwd is resolved per poll,
|
|
77
|
+
so a watcher whose worktree dies mid-watch recovers instead of spinning
|
|
78
|
+
ENOENT errors until the TTL. The fallback is logged once per dead
|
|
79
|
+
directory.
|
|
80
|
+
- `watch_command_create` accepts a `cd` override, validated at
|
|
81
|
+
registration by the baseline run (a nonexistent path is rejected).
|
|
82
|
+
|
|
83
|
+
## Key invariants
|
|
84
|
+
|
|
85
|
+
- Identity resolution returns a correct slug or `"unknown"` -- never a
|
|
86
|
+
plausible wrong name. A wrong-but-plausible name defeats the
|
|
87
|
+
degrade-to-global safety net.
|
|
88
|
+
- The cache is only written from live resolutions, and only via the
|
|
89
|
+
remote or common-dir probes.
|
|
90
|
+
- Eviction is definitive-only.
|
|
91
|
+
- The cache key is the realpath at write time; reads try raw and
|
|
92
|
+
parent-realpath forms.
|
|
93
|
+
|
|
94
|
+
## Interactions with other features
|
|
95
|
+
|
|
96
|
+
- Database ([database.md](database.md)): the `repo_paths` table.
|
|
97
|
+
- Watchers ([watchers.md](watchers.md)): the per-poll cwd fallback wraps
|
|
98
|
+
the watcher command runner; `watch_command_create` takes `cd`.
|
|
99
|
+
- Hygiene ([hygiene.md](hygiene.md)): `listBranches` falls back to the
|
|
100
|
+
cached main checkout, so the orphaned-branch check survives worktree
|
|
101
|
+
deletion.
|
|
102
|
+
|
|
103
|
+
## Source files
|
|
104
|
+
|
|
105
|
+
- `src/git.ts` -- resolution chain, cache validation, spawn-cwd fallback
|
|
106
|
+
- `src/db.ts` -- `repo_paths` table, accessors, `repoPathCache` adapter
|
|
107
|
+
- `src/index.ts` -- plugin init order (DB before detection), identity from
|
|
108
|
+
the session directory, watcher runner wrapper
|
|
109
|
+
- `src/mcp.ts` -- MCP server init order
|
|
110
|
+
- `bin/thatch` -- CLI `defaultStore()` and hook-line identity resolution
|
|
@@ -138,6 +138,14 @@ The command source stretches the fetch/diff recipe in two ways:
|
|
|
138
138
|
holding the pipe write-ends past the kill - without the race, the
|
|
139
139
|
drains never resolve and the shared poll cycle stalls forever.
|
|
140
140
|
|
|
141
|
+
The spawn cwd is resolved per poll by the plugin's command runner
|
|
142
|
+
(`withCwdFallback`): the watcher's directory when it still exists, else
|
|
143
|
+
the cached main checkout (see [repo-identity.md](repo-identity.md)), so a
|
|
144
|
+
watcher whose worktree is deleted mid-watch recovers instead of spinning
|
|
145
|
+
ENOENT errors until the TTL. `watch_command_create` also accepts a `cd`
|
|
146
|
+
override for conditions that must be checked somewhere other than the
|
|
147
|
+
project directory; it is validated by the registration-time baseline run.
|
|
148
|
+
|
|
141
149
|
`createCommand()` runs the command once at registration as baseline
|
|
142
150
|
validation, like `createPr()`'s baseline fetch: exit 0 at baseline
|
|
143
151
|
means the condition is already met (refused, so the caller proceeds
|
package/docs/dev/skills.md
CHANGED
|
@@ -55,6 +55,7 @@ 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
59
|
| `thatch-pr-description` | Draft PR descriptions with SYNOPSIS / PURPOSE / DESCRIPTION / WALK-THROUGH / NOTES, project-context research, clarity checks, and bold+italic emphasis for scanning. |
|
|
59
60
|
| `thatch-ticket-description` | Draft ticket/issue descriptions with clear sections, project-context research, clarity checks, and bold+italic emphasis for scanning. |
|
|
60
61
|
| `thatch-split-overlarge-pr` | Split already-completed work from an overlarge PR into human-reviewable, release-safe PRs targeting main. |
|