@jeffober/thatch 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +7 -7
  2. package/artifacts/skills/common.md +1 -1
  3. package/artifacts/skills/thatch-change-walkthrough.md +2 -2
  4. package/artifacts/skills/thatch-code-archaeology.md +252 -0
  5. package/artifacts/skills/thatch-code-review.md +4 -4
  6. package/artifacts/skills/thatch-code-walkthrough.md +2 -2
  7. package/artifacts/skills/thatch-coding-workflow.md +79 -0
  8. package/artifacts/skills/{pr-description.md → thatch-pr-description.md} +1 -1
  9. package/artifacts/skills/thatch-review-context.md +9 -0
  10. package/artifacts/skills/thatch-review-followup.md +281 -0
  11. package/artifacts/skills/thatch-review-pedantic.md +16 -1
  12. package/artifacts/skills/thatch-review-response.md +365 -0
  13. package/artifacts/skills/thatch-review-synthesizer.md +92 -5
  14. package/artifacts/skills/{split-overlarge-pr.md → thatch-split-overlarge-pr.md} +1 -1
  15. package/artifacts/skills/{ticket-description.md → thatch-ticket-description.md} +3 -3
  16. package/bin/release +3 -3
  17. package/bin/thatch +8 -8
  18. package/docs/dev/README.md +1 -1
  19. package/docs/dev/skills.md +34 -9
  20. package/docs/plans/README.md +9 -6
  21. package/docs/plans/intuition-drives.md +147 -0
  22. package/docs/plans/nomenclater.md +89 -0
  23. package/docs/plans/prediction-consolidation.md +361 -0
  24. package/docs/qa/README.md +20 -3
  25. package/docs/user/README.md +33 -5
  26. package/package.json +1 -1
  27. package/src/db.ts +6 -6
  28. package/src/embeddings.ts +2 -2
  29. package/src/extract-queue.ts +4 -4
  30. package/src/extraction.ts +4 -4
  31. package/src/git.ts +1 -1
  32. package/src/hygiene.ts +1 -1
  33. package/src/index.ts +28 -28
  34. package/src/mcp.ts +5 -5
  35. package/src/prompts.ts +71 -65
  36. package/src/setup.ts +19 -19
  37. package/src/sideband.ts +3 -3
  38. package/src/skills.ts +105 -6
  39. package/src/tool-defs.ts +11 -11
  40. package/src/tools.ts +1 -1
  41. package/artifacts/skills/thatch-workflow-research.md +0 -91
  42. package/docs/in-progress/prediction-engine.md +0 -404
  43. package/docs/plans/001-memory-store.md +0 -106
  44. package/docs/plans/002-agent-driven-pipelines.md +0 -59
  45. package/docs/plans/003-memory-hygiene.md +0 -61
  46. package/docs/qa/use-cases/README.md +0 -51
  47. package/docs/qa/use-cases/UC-001-memory-roundtrip.md +0 -20
  48. package/docs/qa/use-cases/UC-002-dedup-cycle.md +0 -23
  49. package/docs/qa/use-cases/UC-003-extraction-nudge.md +0 -56
  50. package/docs/qa/use-cases/UC-004-cli-inspection.md +0 -22
  51. package/docs/qa/use-cases/UC-005-setup-install.md +0 -34
  52. package/docs/qa/use-cases/UC-006-cursor-hook-lifecycle.md +0 -32
  53. package/docs/qa/use-cases/UC-007-recall-nudge.md +0 -31
  54. package/docs/qa/use-cases/UC-008-hygiene-heartbeat.md +0 -26
  55. package/docs/qa/use-cases/UC-009-flush-tools-tiers.md +0 -33
  56. package/docs/qa/use-cases/UC-010-prime.md +0 -25
  57. package/docs/qa/use-cases/UC-011-write-time-similarity-warning.md +0 -24
  58. package/docs/qa/use-cases/UC-012-model-migration.md +0 -28
  59. package/docs/qa/use-cases/UC-013-compaction-context.md +0 -25
  60. package/docs/qa/use-cases/UC-014-skill-install-drift.md +0 -28
  61. package/docs/qa/use-cases/UC-015-env-override-matrix.md +0 -28
  62. package/docs/qa/use-cases/UC-016-concurrent-session-isolation.md +0 -37
  63. package/docs/qa/use-cases/UC-017-buffer-tool-vs-buffer-batch.md +0 -27
  64. package/docs/qa/use-cases/UC-018-mcp-startup-setup-detection.md +0 -38
  65. package/docs/qa/use-cases/UC-019-archived-memory-lifecycle.md +0 -36
  66. package/docs/qa/use-cases/UC-020-extraction-escalation.md +0 -52
  67. package/docs/qa/use-cases/UC-021-prediction-autofire.md +0 -21
  68. package/docs/qa/use-cases/UC-022-prediction-dedup.md +0 -19
  69. package/docs/qa/use-cases/UC-023-prediction-confidence-model.md +0 -23
  70. package/docs/qa/use-cases/UC-024-prediction-delete.md +0 -20
package/src/setup.ts CHANGED
@@ -22,7 +22,7 @@ interface SetupPaths {
22
22
 
23
23
  /**
24
24
  * Resolve the Claude config directory. `CLAUDE_CONFIG_DIR` overrides the
25
- * default `~/.claude` location — used by people running multiple accounts
25
+ * default `~/.claude` location - used by people running multiple accounts
26
26
  * side by side (per Claude Code env-vars docs). Settings, CLAUDE.md, and
27
27
  * skills are all stored under this path in global scope. Project-local
28
28
  * installs only use it for skills (which are always user-scoped); the
@@ -41,7 +41,7 @@ function resolvePaths(global: boolean, projectDir: string, homeDir: string): Set
41
41
  mcpConfigPath: null,
42
42
  // Per Claude Code settings docs, user-scope CLAUDE.md lives at
43
43
  // `~/.claude/CLAUDE.md` (or `$CLAUDE_CONFIG_DIR/CLAUDE.md` when the
44
- // env var is set) — NOT `~/CLAUDE.md`. Honoring CLAUDE_CONFIG_DIR
44
+ // env var is set) - NOT `~/CLAUDE.md`. Honoring CLAUDE_CONFIG_DIR
45
45
  // here also fixes a pre-existing bug where we wrote to `~/CLAUDE.md`.
46
46
  claudeMdPath: join(configDir, "CLAUDE.md"),
47
47
  settingsPath: join(configDir, "settings.json"),
@@ -55,14 +55,14 @@ function resolvePaths(global: boolean, projectDir: string, homeDir: string): Set
55
55
  claudeMdPath: join(projectDir, "CLAUDE.md"),
56
56
  settingsPath: join(projectDir, ".claude", "settings.json"),
57
57
  // Skills are always user-scoped, so they always live under the config
58
- // dir — even when settings and CLAUDE.md are project-local.
58
+ // dir - even when settings and CLAUDE.md are project-local.
59
59
  skillsDir,
60
60
  global: false,
61
61
  };
62
62
  }
63
63
 
64
64
  // ---------------------------------------------------------------------------
65
- // .mcp.json — MCP server registration
65
+ // .mcp.json - MCP server registration
66
66
  // ---------------------------------------------------------------------------
67
67
 
68
68
  function writeMcpConfig(path: string, thatchBin: string): void {
@@ -71,7 +71,7 @@ function writeMcpConfig(path: string, thatchBin: string): void {
71
71
  try {
72
72
  config = JSON.parse(readFileSync(path, "utf8"));
73
73
  } catch {
74
- // Corrupt or empty — start fresh.
74
+ // Corrupt or empty - start fresh.
75
75
  }
76
76
  }
77
77
  if (!config.mcpServers) config.mcpServers = {};
@@ -87,11 +87,11 @@ function writeMcpConfig(path: string, thatchBin: string): void {
87
87
  }
88
88
 
89
89
  // ---------------------------------------------------------------------------
90
- // CLAUDE.md — append thatch instructions (idempotent)
90
+ // CLAUDE.md - append thatch instructions (idempotent)
91
91
  // ---------------------------------------------------------------------------
92
92
 
93
93
  const THATCH_MARKER = "# Persistence\n\nThatch provides persistent memory across Claude Code sessions.";
94
- const THATCH_END_MARKER = '"Forget X" — `memory_recall` to find it, then `memory_forget`.';
94
+ const THATCH_END_MARKER = '"Forget X" - `memory_recall` to find it, then `memory_forget`.';
95
95
  const CURSOR_MARKER = "# Persistence\n\nThatch provides persistent memory across Cursor sessions.";
96
96
  const CURSOR_END_MARKER = THATCH_END_MARKER;
97
97
 
@@ -119,7 +119,7 @@ function appendBlock(
119
119
  return;
120
120
  }
121
121
  }
122
- // Marker found but block boundaries didn't parse — leave it alone.
122
+ // Marker found but block boundaries didn't parse - leave it alone.
123
123
  return;
124
124
  }
125
125
  // Append to existing file.
@@ -140,7 +140,7 @@ function appendCursorInstructions(path: string): void {
140
140
  }
141
141
 
142
142
  // ---------------------------------------------------------------------------
143
- // settings.json — hook installation
143
+ // settings.json - hook installation
144
144
  // ---------------------------------------------------------------------------
145
145
 
146
146
  interface HookEntry {
@@ -163,14 +163,14 @@ function writeHooks(path: string, thatchBin: string): void {
163
163
  try {
164
164
  settings = JSON.parse(readFileSync(path, "utf8"));
165
165
  } catch {
166
- // Corrupt — start fresh.
166
+ // Corrupt - start fresh.
167
167
  }
168
168
  }
169
169
  if (!settings.hooks) settings.hooks = {};
170
170
 
171
171
  const sessionStartCmd = `${thatchBin} reminder`;
172
172
  // PostToolBatch silently buffers tool interactions to the file-backed queue
173
- // (see src/extract-queue.ts). No stdout — the next model call must not block
173
+ // (see src/extract-queue.ts). No stdout - the next model call must not block
174
174
  // on a payload that should be invisible to the agent until UserPromptSubmit.
175
175
  const bufferCmd = `${thatchBin} buffer-batch`;
176
176
  // UserPromptSubmit peeks the queue. With buffered interactions, it prints
@@ -209,7 +209,7 @@ function replaceThatchHooks(
209
209
  }
210
210
 
211
211
  // ---------------------------------------------------------------------------
212
- // Skills — install to $CLAUDE_CONFIG_DIR/skills/ (or ~/.claude/skills/ by default)
212
+ // Skills - install to $CLAUDE_CONFIG_DIR/skills/ (or ~/.claude/skills/ by default)
213
213
  // ---------------------------------------------------------------------------
214
214
 
215
215
  function installClaudeSkills(skillsDir: string): SkillFile[] {
@@ -233,7 +233,7 @@ export interface SetupResult {
233
233
  /**
234
234
  * Installs thatch into Claude Code. Writes MCP config, appends instructions to
235
235
  * CLAUDE.md, installs SessionStart + PostToolBatch + UserPromptSubmit hooks,
236
- * and installs skill files. All operations are idempotent — re-running setup
236
+ * and installs skill files. All operations are idempotent - re-running setup
237
237
  * updates content that has drifted without clobbering unrelated configuration.
238
238
  *
239
239
  * Honors `CLAUDE_CONFIG_DIR` for all user-scoped paths (settings, CLAUDE.md,
@@ -276,7 +276,7 @@ export function setupClaudeCode(
276
276
  }
277
277
 
278
278
  // ---------------------------------------------------------------------------
279
- // Cursor setup — .cursor/mcp.json, AGENTS.md, .cursor/hooks.json, skills
279
+ // Cursor setup - .cursor/mcp.json, AGENTS.md, .cursor/hooks.json, skills
280
280
  // ---------------------------------------------------------------------------
281
281
 
282
282
  /**
@@ -303,7 +303,7 @@ function resolveCursorPaths(global: boolean, projectDir: string, homeDir: string
303
303
 
304
304
  if (global) {
305
305
  return {
306
- // Cursor's global MCP config is a simple file — no equivalent of
306
+ // Cursor's global MCP config is a simple file - no equivalent of
307
307
  // `claude mcp add --scope user`. Writing ~/.cursor/mcp.json is enough.
308
308
  mcpConfigPath: join(configDir, "mcp.json"),
309
309
  agentsMdPath: join(configDir, "AGENTS.md"),
@@ -340,7 +340,7 @@ function writeCursorHooks(path: string, thatchBin: string): void {
340
340
  try {
341
341
  config = JSON.parse(readFileSync(path, "utf8"));
342
342
  } catch {
343
- // Corrupt — start fresh.
343
+ // Corrupt - start fresh.
344
344
  }
345
345
  }
346
346
  if (!config.version) config.version = 1;
@@ -351,7 +351,7 @@ function writeCursorHooks(path: string, thatchBin: string): void {
351
351
  // Code style) and Cursor would not parse it.
352
352
  const sessionStartCmd = `${thatchBin} reminder --json`;
353
353
  // postToolUse fires per-tool (Cursor has no PostToolBatch equivalent).
354
- // Silent — no stdout — so the agent loop is not delayed.
354
+ // Silent - no stdout - so the agent loop is not delayed.
355
355
  const bufferCmd = `${thatchBin} buffer-tool`;
356
356
  // beforeSubmitPrompt is Cursor's UserPromptSubmit equivalent. Drains the
357
357
  // queue and prints the extraction nudge as JSON additional_context.
@@ -422,7 +422,7 @@ export function setupCursor(
422
422
  }
423
423
 
424
424
  // ---------------------------------------------------------------------------
425
- // Setup detection — check whether setup was completed for the current host
425
+ // Setup detection - check whether setup was completed for the current host
426
426
  // ---------------------------------------------------------------------------
427
427
 
428
428
  export type HostKind = "claude" | "cursor";
@@ -449,7 +449,7 @@ function checkInstructionsFile(path: string, startMarker: string, endMarker: str
449
449
  * CLAUDE_PROJECT_DIR for Claude Code) and checks whether `thatch setup` was
450
450
  * run for that host. Checks local instructions (in the project dir) first,
451
451
  * then global (in the config dir). Returns null if the host cannot be
452
- * determined (neither env var is set — e.g. manual `thatch mcp` invocation).
452
+ * determined (neither env var is set - e.g. manual `thatch mcp` invocation).
453
453
  */
454
454
  export function checkSetup(projectDir: string, homeDir?: string): SetupStatus | null {
455
455
  const home = homeDir ?? process.env.HOME ?? "/tmp";
package/src/sideband.ts CHANGED
@@ -66,7 +66,7 @@ export class SidebandServer {
66
66
  try {
67
67
  unlinkSync(this.#path);
68
68
  } catch {
69
- // No stale socket — expected on first run.
69
+ // No stale socket - expected on first run.
70
70
  }
71
71
 
72
72
  this.#server = createServer((socket) => {
@@ -103,7 +103,7 @@ export class SidebandServer {
103
103
  this.#handleRequest(line, socket);
104
104
  });
105
105
  socket.on("error", () => {
106
- // Client disconnected abruptly — nothing to do.
106
+ // Client disconnected abruptly - nothing to do.
107
107
  });
108
108
  }
109
109
 
@@ -155,7 +155,7 @@ export class SidebandServer {
155
155
 
156
156
  /**
157
157
  * Connect to the sideband server and request semantic matches for a prompt.
158
- * Returns null on any failure (server not running, stale socket, timeout) —
158
+ * Returns null on any failure (server not running, stale socket, timeout) -
159
159
  * callers must treat null as "skip the recall nudge" and fall back gracefully.
160
160
  */
161
161
  export function sidebandMatch(
package/src/skills.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import pkg from "../package.json";
3
4
 
4
5
  export interface SkillFile {
5
6
  name: string;
@@ -42,13 +43,16 @@ function loadSharedSkills(): SkillDef[] {
42
43
  "thatch-review-highlights",
43
44
  "thatch-review-synthesizer",
44
45
  "thatch-review-context",
45
- "thatch-workflow-research",
46
+ "thatch-code-archaeology",
47
+ "thatch-review-followup",
46
48
  "thatch-change-walkthrough",
47
49
  "thatch-code-walkthrough",
48
50
  "thatch-session-reflection",
49
- "pr-description",
50
- "ticket-description",
51
- "split-overlarge-pr",
51
+ "thatch-coding-workflow",
52
+ "thatch-pr-description",
53
+ "thatch-ticket-description",
54
+ "thatch-split-overlarge-pr",
55
+ "thatch-review-response",
52
56
  ];
53
57
  return names.map((name) => ({ name, content: loadSkillFile(name) }));
54
58
  }
@@ -62,11 +66,106 @@ function loadOpencodeOnlySkills(): SkillDef[] {
62
66
  export const SHARED_SKILLS: SkillDef[] = loadSharedSkills();
63
67
  export const OPENCODE_ONLY_SKILLS: SkillDef[] = loadOpencodeOnlySkills();
64
68
 
69
+ // ---------------------------------------------------------------------------
70
+ // Stale skill cleanup - runs before install on every skillsDir
71
+ // ---------------------------------------------------------------------------
72
+
73
+ // Skills renamed from non-prefixed to thatch-prefixed in v0.1.27. The old
74
+ // names are cleaned up during install for a limited version window because
75
+ // non-prefixed names could be adopted by third parties after we abandon them.
76
+ // After RENAME_MIGRATION_MAX_VERSION, the cleanup stops and leftover stale
77
+ // files are the user's responsibility.
78
+ const RENAMED_SKILLS = new Set([
79
+ "pr-description",
80
+ "ticket-description",
81
+ "split-overlarge-pr",
82
+ ]);
83
+
84
+ // Last version that ships the non-prefixed-to-prefixed migration cleanup.
85
+ // Users who upgrade to this version or earlier get automatic cleanup of the
86
+ // old skill directories. Users who skip past this version must manually
87
+ // delete the stale directories.
88
+ const RENAME_MIGRATION_MAX_VERSION = "0.1.35";
89
+
90
+ function compareVersions(a: string, b: string): number {
91
+ const [aMaj, aMin, aPatch] = a.split(".").map(Number);
92
+ const [bMaj, bMin, bPatch] = b.split(".").map(Number);
93
+ if (aMaj !== bMaj) return aMaj - bMaj;
94
+ if (aMin !== bMin) return aMin - bMin;
95
+ return aPatch - bPatch;
96
+ }
97
+
98
+ /**
99
+ * Remove stale skill directories before installing the current set.
100
+ *
101
+ * Two cleanup rules:
102
+ *
103
+ * 1. Always: delete any `thatch-*` skill directory that is not in the current
104
+ * install set. The `thatch-` prefix is our namespace - no third party should
105
+ * use it, so unconditional removal is safe. This catches skills we've
106
+ * renamed or removed in any release.
107
+ *
108
+ * 2. Version-gated: delete directories matching old non-prefixed skill names
109
+ * (pr-description, ticket-description, split-overlarge-pr) through
110
+ * RENAME_MIGRATION_MAX_VERSION. These names lack the thatch- prefix and
111
+ * could be adopted by third parties after we abandon them, so the cleanup
112
+ * is time-boxed. Users who upgrade past the window must manually clean up.
113
+ */
114
+ function cleanupStaleSkills(skillsDir: string, currentNames: string[]): void {
115
+ const currentSet = new Set(currentNames);
116
+ let entries;
117
+ try {
118
+ entries = readdirSync(skillsDir, { withFileTypes: true });
119
+ } catch {
120
+ return; // dir doesn't exist or isn't readable - nothing to clean
121
+ }
122
+
123
+ const withinMigrationWindow =
124
+ compareVersions(pkg.version, RENAME_MIGRATION_MAX_VERSION) <= 0;
125
+
126
+ for (const entry of entries) {
127
+ // isDirectory() returns false for symlinks (even symlinked dirs), so
128
+ // check isSymbolicLink() too - a symlinked skill dir is just as stale
129
+ // as a regular one.
130
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
131
+ const name = entry.name;
132
+ const fullPath = join(skillsDir, name);
133
+
134
+ // Remove a stale entry. For symlinks, unlink the symlink itself - never
135
+ // follow the link, which could delete the target (e.g. a shared skill
136
+ // directory the user still wants). For regular dirs, recursive rm.
137
+ const remove = () => {
138
+ try {
139
+ if (entry.isSymbolicLink()) {
140
+ unlinkSync(fullPath);
141
+ } else {
142
+ rmSync(fullPath, { recursive: true, force: true });
143
+ }
144
+ } catch {
145
+ // best-effort - don't block install on cleanup failure
146
+ }
147
+ };
148
+
149
+ // Rule 1: stale thatch-* dirs (our namespace, always safe to remove).
150
+ if (name.startsWith("thatch-") && !currentSet.has(name)) {
151
+ remove();
152
+ continue;
153
+ }
154
+
155
+ // Rule 2: renamed non-prefixed dirs (version-gated, third-party risk).
156
+ if (RENAMED_SKILLS.has(name) && withinMigrationWindow) {
157
+ remove();
158
+ }
159
+ }
160
+ }
161
+
65
162
  export function installSkills(
66
163
  skillsDir: string,
67
164
  skills: SkillDef[] = SHARED_SKILLS,
68
165
  ): SkillFile[] {
69
166
  mkdirSync(skillsDir, { recursive: true });
167
+ cleanupStaleSkills(skillsDir, skills.map((s) => s.name));
168
+
70
169
  const written: SkillFile[] = [];
71
170
 
72
171
  for (const skill of skills) {
@@ -77,7 +176,7 @@ export function installSkills(
77
176
  try {
78
177
  current = readFileSync(file, "utf8");
79
178
  } catch {
80
- // missing file — first install
179
+ // missing file - first install
81
180
  }
82
181
 
83
182
  if (current !== skill.content) {
package/src/tool-defs.ts CHANGED
@@ -14,7 +14,7 @@ const PREDICTION_DEDUP_COSINE = 0.85;
14
14
  const PREDICTION_QUERY_THRESHOLD = 0.45;
15
15
 
16
16
  /**
17
- * Shared context passed to every tool's execute function. Framework-agnostic —
17
+ * Shared context passed to every tool's execute function. Framework-agnostic -
18
18
  * neither opencode nor MCP specific. The plugin wires real defaults; tests and
19
19
  * the MCP server inject whatever they need.
20
20
  */
@@ -25,7 +25,7 @@ export interface CoreContext {
25
25
  }
26
26
 
27
27
  /**
28
- * A tool definition — the single source of truth shared by the opencode plugin
28
+ * A tool definition - the single source of truth shared by the opencode plugin
29
29
  * wrapper (tools.ts) and the MCP server (mcp.ts). The `args` field is a ZodRawShape
30
30
  * (a plain object of Zod types), which opencode's `tool()` accepts directly and
31
31
  * the MCP server wraps in `z.object()` for validation and `z.toJSONSchema()` for
@@ -39,7 +39,7 @@ export interface ToolDef {
39
39
  }
40
40
 
41
41
  // ---------------------------------------------------------------------------
42
- // Formatting helpers — shared by all tools that render entries
42
+ // Formatting helpers - shared by all tools that render entries
43
43
  // ---------------------------------------------------------------------------
44
44
 
45
45
  function formatEntry(
@@ -68,7 +68,7 @@ function formatRecallResult(entry: any): string {
68
68
  /**
69
69
  * Groups candidate pairs into connected components over the similarity graph,
70
70
  * so a topic fragmented across N entries reads as one cluster instead of
71
- * O(N²) pairs. Verdicts stay pairwise (markChecked) — this is presentation only.
71
+ * O(N²) pairs. Verdicts stay pairwise (markChecked) - this is presentation only.
72
72
  */
73
73
  function renderClusters(candidates: DedupCandidate[]): string {
74
74
  const parent = new Map<string, string>();
@@ -114,7 +114,7 @@ const rememberDef: ToolDef = {
114
114
  "reference material for a future instance of yourself with zero context.",
115
115
  args: {
116
116
  label: z.string().describe(
117
- "Short descriptive title. Used for deduplication — same label in the same store is the same entry.",
117
+ "Short descriptive title. Used for deduplication - same label in the same store is the same entry.",
118
118
  ),
119
119
  content: z.string().describe(
120
120
  "The information to remember. Self-contained, understandable without session context.",
@@ -133,7 +133,7 @@ const rememberDef: ToolDef = {
133
133
  ),
134
134
  archived: z.boolean().optional().describe(
135
135
  "Mark this memory as a stable, long-term record that should not trigger hygiene nudges. " +
136
- "When updating an already-archived memory, this param is REQUIRED — " +
136
+ "When updating an already-archived memory, this param is REQUIRED - " +
137
137
  "pass archived: true to keep it archived, or archived: false to unarchive.",
138
138
  ),
139
139
  },
@@ -189,7 +189,7 @@ const recallDef: ToolDef = {
189
189
  "Maximum number of results. Default 10.",
190
190
  ),
191
191
  includeArchived: z.boolean().optional().describe(
192
- "Include archived memories in results. Default false — archived memories are excluded.",
192
+ "Include archived memories in results. Default false - archived memories are excluded.",
193
193
  ),
194
194
  },
195
195
  async execute(args, ctx) {
@@ -299,7 +299,7 @@ const findDuplicatesDef: ToolDef = {
299
299
  description:
300
300
  "Find memories with unusually similar content that may be candidates " +
301
301
  "for consolidation. Uses cosine similarity on embeddings. Related pairs " +
302
- "are grouped into clusters — a cluster of three or more usually means " +
302
+ "are grouped into clusters - a cluster of three or more usually means " +
303
303
  "one topic fragmented across entries that should be consolidated into " +
304
304
  "a single memory. Pairs already reviewed via dedup_mark_checked " +
305
305
  "are skipped.",
@@ -357,13 +357,13 @@ const markCheckedDef: ToolDef = {
357
357
  * Called in a PARENT session after dispatching the fact-extractor, it accepts
358
358
  * the buffer: entries move to a holding area and the nudge quiets, but they
359
359
  * are not dropped until the extractor completes. Called in a CHILD extractor
360
- * at the end of its run, it completes the parent's accepted entries —
360
+ * at the end of its run, it completes the parent's accepted entries -
361
361
  * including no-save runs that write no memory. If the child errors or is
362
362
  * deleted before either signal, the host requeues the entries so the facts
363
363
  * are not lost.
364
364
  *
365
365
  * The actual state changes happen in the host's post-tool hook
366
- * (tool.execute.after for opencode, PostToolBatch/appendBatch for MCP) —
366
+ * (tool.execute.after for opencode, PostToolBatch/appendBatch for MCP) -
367
367
  * this tool's execute function is a no-op confirmation. The tool exists so
368
368
  * the model has a recognizable tool name to key on. In the MCP path the
369
369
  * file-backed queue is consumed on this call (or on any memory_remember),
@@ -502,7 +502,7 @@ const predictionUpdateDef: ToolDef = {
502
502
 
503
503
  // "create" on an existing prediction means the agent re-observed
504
504
  // the same preference in a new context. Link the edge and record
505
- // provenance, but do NOT adjust confidence — "create" is
505
+ // provenance, but do NOT adjust confidence - "create" is
506
506
  // confidence-neutral, not a disconfirm.
507
507
  if (signal === "create") {
508
508
  ctx.db.addProvenance(predictionId, "create", rationale);
package/src/tools.ts CHANGED
@@ -6,7 +6,7 @@ import { TOOL_DEFS, type CoreContext } from "./tool-defs";
6
6
  /**
7
7
  * Builds the opencode tool map from shared tool definitions. Each definition
8
8
  * in TOOL_DEFS is wrapped in opencode's `tool()` with a `thatch_` prefix
9
- * on the name — opencode uses the object key as the tool name, so the prefix
9
+ * on the name - opencode uses the object key as the tool name, so the prefix
10
10
  * lives here, not in the shared definitions.
11
11
  */
12
12
  export function createTools(
@@ -1,91 +0,0 @@
1
- ---
2
- name: thatch-workflow-research
3
- description: Research the code workflows and features affected by a change or planned change. Identifies affected workflows, reads code flows and comments, traces git history, recalls memories, and produces a guide to the code. Use before reviewing a change or planning new work in an unfamiliar area.
4
- ---
5
-
6
- You are a code workflow researcher. Your job is to identify the features and logical workflows affected by a change (or a planned change), do deep code archaeology on them, and produce a guide that gives other agents the context they need to review or plan effectively.
7
-
8
- ## Why this matters
9
-
10
- A specialist reviewer without code-level context will misidentify intentional behavior as bugs, miss known design constraints, and duplicate the same code-tracing work across every specialist lens. A planner without code-level context will propose changes that conflict with existing flows. This skill produces the code guide that prevents both.
11
-
12
- ## Relationship to thatch-review-context
13
-
14
- These two skills are complementary, not overlapping:
15
- - **thatch-review-context** gathers project metadata: tickets, PRs, TODO markers, deferred work, issue-tracker context. It answers "what is this change trying to do and what is intentionally left out?"
16
- - **thatch-workflow-research** (this skill) gathers code-level context: actual code flows, comments, git history, evolutions. It answers "how does the code being changed actually work, and how did it get that way?"
17
-
18
- Use both before a review. The coordinator runs thatch-review-context in Step 2 and this skill in Step 3.
19
-
20
- ## Two modes
21
-
22
- ### Review mode (existing change)
23
- Given a git range, branch, or PR:
24
- 1. Run `git diff --stat` on the resolved range to identify changed files.
25
- 2. For each changed file, read the diff to understand what is being modified.
26
- 3. From the changes, identify which features and business-logic workflows are touched. A single file may participate in multiple workflows; a single workflow may span multiple files.
27
-
28
- ### Planning mode (new change)
29
- Given a description of work to be done:
30
- 1. Identify which areas of the codebase the planned change would affect.
31
- 2. Use grep/glob to find the files and modules that implement the relevant features.
32
- 3. From those files, identify which existing workflows the change will interact with or modify.
33
-
34
- ## Research process
35
-
36
- For each identified workflow or feature, work through these steps. Not every step will yield information for every workflow. Skip steps that produce nothing rather than forcing a narrative.
37
-
38
- ### 1. Retrieve memories
39
- Call `thatch_memory_recall` with queries like:
40
- - The workflow or feature name
41
- - The primary files or modules involved
42
- - "architecture" or "design" or "convention" for the area
43
- - Any gotchas or known issues in the area
44
-
45
- ### 2. Read documentation
46
- - README, docs/, design docs, ADRs related to the workflow
47
- - CLAUDE.md, AGENTS.md, CONTRIBUTING.md for conventions
48
- - Any inline documentation at the module level
49
-
50
- ### 3. Read the code flow
51
- Trace each workflow through the codebase. Do not just read the changed lines; read the full flow:
52
- - Identify the entry point(s) for the workflow
53
- - Follow the data path through the functions and modules involved
54
- - Read the comments in the flow. Comments encode intention, rationale, and design decisions that are invisible from the code alone.
55
- - Note key contracts: what does each function or module assume about its inputs? What does it guarantee about its outputs?
56
- - Note implicit state machines: what states can the workflow be in? What transitions are guarded?
57
-
58
- ### 4. Read git history
59
- Trace the evolution of the workflow through git:
60
- - `git log --oneline -- <key files>` for the commit history of the files involved
61
- - `git log -S "<function name>"` to find when key functions were introduced or changed
62
- - `git blame` on critical sections to find when and why they were written
63
- - `git log --all --oneline --grep="<feature name>"` to find related work on other branches
64
- - Look for commit messages that explain design decisions, refactors, or bug fixes that shaped the workflow
65
-
66
- Focus on *why* the code evolved, not just *when*. Major evolutions are: creation, significant refactors, behavior changes, bug fixes that changed the flow, and additions of new paths or states.
67
-
68
- ## Output: code guide
69
-
70
- Produce a guide with one section per affected workflow or feature. Each section should be self-contained enough that a reviewer or planner can understand the workflow without reading the code themselves.
71
-
72
- ### Workflow: <name>
73
-
74
- - **Purpose**: What this workflow does and why it exists in the system
75
- - **Key files**: The primary files and entry points, with file:line references
76
- - **Data flow**: How data moves through the workflow (high-level, not line-by-line)
77
- - **Major evolutions**: Key changes from git history. When was it created? What refactors or behavior changes shaped it? What drove those changes? Cite commit hashes or dates.
78
- - **Constraints and design decisions**: From comments, memories, and docs. Known constraints, tradeoffs, intentional design choices, and gotchas.
79
- - **Current state**: Any open issues, TODOs, known limitations, or incomplete migrations
80
-
81
- ## Scope discipline
82
-
83
- Only research workflows that the change actually touches. Do not research every workflow in the codebase. If a changed file participates in three workflows but the change only affects one, research only that one.
84
-
85
- If the change is small and touches a single workflow, the guide may be one section. If the change is cross-cutting, the guide may have several sections. Let the change determine the scope.
86
-
87
- ## What this skill is NOT
88
-
89
- - It is not a code review. Do not evaluate the quality of the code or flag issues. That is the job of the review specialists.
90
- - It is not a project context builder. That is the job of thatch-review-context, which gathers project metadata (tickets, PRs, TODOs, deferred work). This skill gathers code-level context (flows, comments, git history, evolutions).
91
- - It is not a memory writer. Do not call thatch_memory_remember. If you discover durable facts, note them in the guide; the session-reflection or fact-extractor skills will persist them later.