@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 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, memory verification,
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
- return await detectRepo();
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 "unknown";
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
- repo = await detectRepo();
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");
@@ -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. |
@@ -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" on failure. "global" is the shared store. "all" (search only) means project + global.
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 `thatch-qa` skill (`.opencode/skills/thatch-qa/SKILL.md`) instructs 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
@@ -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. |