@jeffober/thatch 0.1.26 → 0.1.28

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 (72) 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/thatch-fact-extractor.md +2 -2
  9. package/artifacts/skills/{pr-description.md → thatch-pr-description.md} +1 -1
  10. package/artifacts/skills/thatch-review-context.md +9 -0
  11. package/artifacts/skills/thatch-review-followup.md +29 -1
  12. package/artifacts/skills/thatch-review-pedantic.md +16 -1
  13. package/artifacts/skills/thatch-review-response.md +365 -0
  14. package/artifacts/skills/thatch-review-synthesizer.md +92 -5
  15. package/artifacts/skills/{split-overlarge-pr.md → thatch-split-overlarge-pr.md} +1 -1
  16. package/artifacts/skills/{ticket-description.md → thatch-ticket-description.md} +3 -3
  17. package/bin/release +3 -3
  18. package/bin/thatch +11 -10
  19. package/docs/dev/README.md +7 -6
  20. package/docs/dev/mcp-parity.md +1 -1
  21. package/docs/dev/skills.md +29 -9
  22. package/docs/plans/README.md +9 -6
  23. package/docs/plans/intuition-drives.md +147 -0
  24. package/docs/plans/nomenclater.md +89 -0
  25. package/docs/plans/prediction-consolidation.md +361 -0
  26. package/docs/qa/README.md +20 -3
  27. package/docs/user/README.md +19 -5
  28. package/package.json +1 -1
  29. package/src/db.ts +6 -6
  30. package/src/embeddings.ts +2 -2
  31. package/src/extract-queue.ts +4 -4
  32. package/src/extraction.ts +4 -4
  33. package/src/git.ts +1 -1
  34. package/src/hygiene.ts +1 -1
  35. package/src/index.ts +40 -34
  36. package/src/mcp.ts +34 -6
  37. package/src/prompts.ts +96 -85
  38. package/src/setup.ts +19 -19
  39. package/src/sideband.ts +3 -3
  40. package/src/skills.ts +104 -6
  41. package/src/tool-defs.ts +93 -14
  42. package/src/tools.ts +12 -2
  43. package/artifacts/skills/thatch-workflow-research.md +0 -91
  44. package/docs/in-progress/prediction-engine.md +0 -404
  45. package/docs/plans/001-memory-store.md +0 -106
  46. package/docs/plans/002-agent-driven-pipelines.md +0 -59
  47. package/docs/plans/003-memory-hygiene.md +0 -61
  48. package/docs/qa/use-cases/README.md +0 -51
  49. package/docs/qa/use-cases/UC-001-memory-roundtrip.md +0 -20
  50. package/docs/qa/use-cases/UC-002-dedup-cycle.md +0 -23
  51. package/docs/qa/use-cases/UC-003-extraction-nudge.md +0 -56
  52. package/docs/qa/use-cases/UC-004-cli-inspection.md +0 -22
  53. package/docs/qa/use-cases/UC-005-setup-install.md +0 -34
  54. package/docs/qa/use-cases/UC-006-cursor-hook-lifecycle.md +0 -32
  55. package/docs/qa/use-cases/UC-007-recall-nudge.md +0 -31
  56. package/docs/qa/use-cases/UC-008-hygiene-heartbeat.md +0 -26
  57. package/docs/qa/use-cases/UC-009-flush-tools-tiers.md +0 -33
  58. package/docs/qa/use-cases/UC-010-prime.md +0 -25
  59. package/docs/qa/use-cases/UC-011-write-time-similarity-warning.md +0 -24
  60. package/docs/qa/use-cases/UC-012-model-migration.md +0 -28
  61. package/docs/qa/use-cases/UC-013-compaction-context.md +0 -25
  62. package/docs/qa/use-cases/UC-014-skill-install-drift.md +0 -28
  63. package/docs/qa/use-cases/UC-015-env-override-matrix.md +0 -28
  64. package/docs/qa/use-cases/UC-016-concurrent-session-isolation.md +0 -37
  65. package/docs/qa/use-cases/UC-017-buffer-tool-vs-buffer-batch.md +0 -27
  66. package/docs/qa/use-cases/UC-018-mcp-startup-setup-detection.md +0 -38
  67. package/docs/qa/use-cases/UC-019-archived-memory-lifecycle.md +0 -36
  68. package/docs/qa/use-cases/UC-020-extraction-escalation.md +0 -52
  69. package/docs/qa/use-cases/UC-021-prediction-autofire.md +0 -21
  70. package/docs/qa/use-cases/UC-022-prediction-dedup.md +0 -19
  71. package/docs/qa/use-cases/UC-023-prediction-confidence-model.md +0 -23
  72. package/docs/qa/use-cases/UC-024-prediction-delete.md +0 -20
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,14 +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",
46
47
  "thatch-review-followup",
47
48
  "thatch-change-walkthrough",
48
49
  "thatch-code-walkthrough",
49
50
  "thatch-session-reflection",
50
- "pr-description",
51
- "ticket-description",
52
- "split-overlarge-pr",
51
+ "thatch-coding-workflow",
52
+ "thatch-pr-description",
53
+ "thatch-ticket-description",
54
+ "thatch-split-overlarge-pr",
55
+ "thatch-review-response",
53
56
  ];
54
57
  return names.map((name) => ({ name, content: loadSkillFile(name) }));
55
58
  }
@@ -63,11 +66,106 @@ function loadOpencodeOnlySkills(): SkillDef[] {
63
66
  export const SHARED_SKILLS: SkillDef[] = loadSharedSkills();
64
67
  export const OPENCODE_ONLY_SKILLS: SkillDef[] = loadOpencodeOnlySkills();
65
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
+
66
162
  export function installSkills(
67
163
  skillsDir: string,
68
164
  skills: SkillDef[] = SHARED_SKILLS,
69
165
  ): SkillFile[] {
70
166
  mkdirSync(skillsDir, { recursive: true });
167
+ cleanupStaleSkills(skillsDir, skills.map((s) => s.name));
168
+
71
169
  const written: SkillFile[] = [];
72
170
 
73
171
  for (const skill of skills) {
@@ -78,7 +176,7 @@ export function installSkills(
78
176
  try {
79
177
  current = readFileSync(file, "utf8");
80
178
  } catch {
81
- // missing file — first install
179
+ // missing file - first install
82
180
  }
83
181
 
84
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
  */
@@ -22,10 +22,22 @@ export interface CoreContext {
22
22
  db: ThatchDB;
23
23
  model: EmbeddingModel;
24
24
  defaultStore: string;
25
+ /** Peeks the extraction buffer for a session and returns the serialized
26
+ * JSON payload (same shape as buildExtractionPayload). Returns null when
27
+ * no interactions are queued. Used by the get_extraction_payload tool so
28
+ * the sub-agent fetches the payload as a tool response instead of
29
+ * receiving it inline in the nudge text. */
30
+ extractionPayloadProvider?: (sessionID: string) => string | null;
31
+ /** Drains a session's extraction queue. On the MCP path this deletes the
32
+ * file-backed queue and resets the missed-nudge counter. On the opencode
33
+ * path this is unused (the tool.execute.after hook handles drain via
34
+ * childToParent). Called by extraction_done when the sub-agent passes
35
+ * the parent's session_id. */
36
+ drainExtractionQueue?: (sessionID: string) => void;
25
37
  }
26
38
 
27
39
  /**
28
- * A tool definition — the single source of truth shared by the opencode plugin
40
+ * A tool definition - the single source of truth shared by the opencode plugin
29
41
  * wrapper (tools.ts) and the MCP server (mcp.ts). The `args` field is a ZodRawShape
30
42
  * (a plain object of Zod types), which opencode's `tool()` accepts directly and
31
43
  * the MCP server wraps in `z.object()` for validation and `z.toJSONSchema()` for
@@ -39,7 +51,7 @@ export interface ToolDef {
39
51
  }
40
52
 
41
53
  // ---------------------------------------------------------------------------
42
- // Formatting helpers — shared by all tools that render entries
54
+ // Formatting helpers - shared by all tools that render entries
43
55
  // ---------------------------------------------------------------------------
44
56
 
45
57
  function formatEntry(
@@ -68,7 +80,7 @@ function formatRecallResult(entry: any): string {
68
80
  /**
69
81
  * Groups candidate pairs into connected components over the similarity graph,
70
82
  * 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.
83
+ * O(N²) pairs. Verdicts stay pairwise (markChecked) - this is presentation only.
72
84
  */
73
85
  function renderClusters(candidates: DedupCandidate[]): string {
74
86
  const parent = new Map<string, string>();
@@ -114,7 +126,7 @@ const rememberDef: ToolDef = {
114
126
  "reference material for a future instance of yourself with zero context.",
115
127
  args: {
116
128
  label: z.string().describe(
117
- "Short descriptive title. Used for deduplication — same label in the same store is the same entry.",
129
+ "Short descriptive title. Used for deduplication - same label in the same store is the same entry.",
118
130
  ),
119
131
  content: z.string().describe(
120
132
  "The information to remember. Self-contained, understandable without session context.",
@@ -133,7 +145,7 @@ const rememberDef: ToolDef = {
133
145
  ),
134
146
  archived: z.boolean().optional().describe(
135
147
  "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 — " +
148
+ "When updating an already-archived memory, this param is REQUIRED - " +
137
149
  "pass archived: true to keep it archived, or archived: false to unarchive.",
138
150
  ),
139
151
  },
@@ -189,7 +201,7 @@ const recallDef: ToolDef = {
189
201
  "Maximum number of results. Default 10.",
190
202
  ),
191
203
  includeArchived: z.boolean().optional().describe(
192
- "Include archived memories in results. Default false — archived memories are excluded.",
204
+ "Include archived memories in results. Default false - archived memories are excluded.",
193
205
  ),
194
206
  },
195
207
  async execute(args, ctx) {
@@ -299,7 +311,7 @@ const findDuplicatesDef: ToolDef = {
299
311
  description:
300
312
  "Find memories with unusually similar content that may be candidates " +
301
313
  "for consolidation. Uses cosine similarity on embeddings. Related pairs " +
302
- "are grouped into clusters — a cluster of three or more usually means " +
314
+ "are grouped into clusters - a cluster of three or more usually means " +
303
315
  "one topic fragmented across entries that should be consolidated into " +
304
316
  "a single memory. Pairs already reviewed via dedup_mark_checked " +
305
317
  "are skipped.",
@@ -357,19 +369,74 @@ const markCheckedDef: ToolDef = {
357
369
  * Called in a PARENT session after dispatching the fact-extractor, it accepts
358
370
  * the buffer: entries move to a holding area and the nudge quiets, but they
359
371
  * 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 —
372
+ * at the end of its run, it completes the parent's accepted entries -
361
373
  * including no-save runs that write no memory. If the child errors or is
362
374
  * deleted before either signal, the host requeues the entries so the facts
363
375
  * are not lost.
364
376
  *
365
377
  * The actual state changes happen in the host's post-tool hook
366
- * (tool.execute.after for opencode, PostToolBatch/appendBatch for MCP) —
378
+ * (tool.execute.after for opencode, PostToolBatch/appendBatch for MCP) -
367
379
  * this tool's execute function is a no-op confirmation. The tool exists so
368
380
  * the model has a recognizable tool name to key on. In the MCP path the
369
381
  * file-backed queue is consumed on this call (or on any memory_remember),
370
382
  * which is durable across interruption because the queue persists on disk
371
383
  * until then.
372
384
  */
385
+ /**
386
+ * Fetches the queued extraction payload for a session. The sub-agent calls
387
+ * this with the parent's session ID to retrieve the tool interactions that
388
+ * need fact extraction, instead of receiving them inline in the nudge text.
389
+ * This keeps the full payload out of the main session's context window.
390
+ */
391
+ const getExtractionPayloadDef: ToolDef = {
392
+ name: "get_extraction_payload",
393
+ description:
394
+ "Retrieve the queued tool interactions for extraction. Call this with " +
395
+ "the session_id from the extraction nudge to get the JSON payload " +
396
+ "(interactions, projectStore, globalStore). Then run the " +
397
+ "thatch-fact-extractor skill on the returned payload.",
398
+ args: {
399
+ session_id: z.string().describe(
400
+ "The session ID from the extraction nudge. This is the parent session " +
401
+ "whose tool interactions are queued for extraction.",
402
+ ),
403
+ },
404
+ async execute(args, ctx) {
405
+ if (!ctx.extractionPayloadProvider) {
406
+ return "Extraction payload retrieval is not available in this host.";
407
+ }
408
+ const payload = ctx.extractionPayloadProvider(args.session_id as string);
409
+ if (!payload) {
410
+ return "No queued tool interactions found for this session.";
411
+ }
412
+ return payload;
413
+ },
414
+ };
415
+
416
+ /**
417
+ * Extraction-buffer acknowledgment, with AMQP-style accept/complete roles.
418
+ *
419
+ * Called in a PARENT session after dispatching the fact-extractor, it accepts
420
+ * the buffer: entries move to a holding area and the nudge quiets, but they
421
+ * are not dropped until the extractor completes. Called in a CHILD extractor
422
+ * at the end of its run, it completes the parent's accepted entries -
423
+ * including no-save runs that write no memory. If the child errors or is
424
+ * deleted before either signal, the host requeues the entries so the facts
425
+ * are not lost.
426
+ *
427
+ * The actual state changes happen in the host's post-tool hook
428
+ * (tool.execute.after for opencode, PostToolBatch/appendBatch for MCP) -
429
+ * this tool's execute function is a no-op confirmation. The tool exists so
430
+ * the model has a recognizable tool name to key on. In the MCP path the
431
+ * file-backed queue is consumed on this call (or on any memory_remember),
432
+ * which is durable across interruption because the queue persists on disk
433
+ * until then.
434
+ *
435
+ * The optional session_id parameter lets a sub-agent drain the PARENT's
436
+ * file-backed queue on the MCP path, where the sub-agent's session ID
437
+ * differs from the parent's and appendBatch's self-detection would drain
438
+ * the wrong (empty) queue.
439
+ */
373
440
  const extractionDoneDef: ToolDef = {
374
441
  name: "extraction_done",
375
442
  description:
@@ -377,9 +444,20 @@ const extractionDoneDef: ToolDef = {
377
444
  "dispatching the fact-extractor to a sub-agent: accepts the buffer " +
378
445
  "(quiets the nudge) while keeping entries until the extractor completes. " +
379
446
  "In the fact-extractor sub-agent, call at the end of the run to mark the " +
380
- "entries complete, even when nothing was worth saving.",
381
- args: {},
382
- async execute() {
447
+ "entries complete, even when nothing was worth saving. Pass session_id " +
448
+ "when running as a sub-agent to drain the parent session's queue.",
449
+ args: {
450
+ session_id: z.string().optional().describe(
451
+ "The parent session's ID, when called from a sub-agent on the MCP " +
452
+ "path. Drains the parent's file-backed queue. Omit when called from " +
453
+ "the parent session itself (opencode path handles drain via hooks).",
454
+ ),
455
+ },
456
+ async execute(args, ctx) {
457
+ const sessionID = args.session_id as string | undefined;
458
+ if (sessionID && ctx.drainExtractionQueue) {
459
+ ctx.drainExtractionQueue(sessionID);
460
+ }
383
461
  return "[acknowledged]";
384
462
  },
385
463
  };
@@ -502,7 +580,7 @@ const predictionUpdateDef: ToolDef = {
502
580
 
503
581
  // "create" on an existing prediction means the agent re-observed
504
582
  // the same preference in a new context. Link the edge and record
505
- // provenance, but do NOT adjust confidence — "create" is
583
+ // provenance, but do NOT adjust confidence - "create" is
506
584
  // confidence-neutral, not a disconfirm.
507
585
  if (signal === "create") {
508
586
  ctx.db.addProvenance(predictionId, "create", rationale);
@@ -587,6 +665,7 @@ export const TOOL_DEFS: ToolDef[] = [
587
665
  findDuplicatesDef,
588
666
  markCheckedDef,
589
667
  extractionDoneDef,
668
+ getExtractionPayloadDef,
590
669
  predictionQueryDef,
591
670
  predictionUpdateDef,
592
671
  predictionListDef,
package/src/tools.ts CHANGED
@@ -6,15 +6,25 @@ 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(
13
13
  db: ThatchDB,
14
14
  model: EmbeddingModel,
15
15
  defaultStore: string,
16
+ extensions?: {
17
+ extractionPayloadProvider?: CoreContext["extractionPayloadProvider"];
18
+ drainExtractionQueue?: CoreContext["drainExtractionQueue"];
19
+ },
16
20
  ): Record<string, ReturnType<typeof tool>> {
17
- const ctx: CoreContext = { db, model, defaultStore };
21
+ const ctx: CoreContext = {
22
+ db,
23
+ model,
24
+ defaultStore,
25
+ extractionPayloadProvider: extensions?.extractionPayloadProvider,
26
+ drainExtractionQueue: extensions?.drainExtractionQueue,
27
+ };
18
28
 
19
29
  const tools: Record<string, ReturnType<typeof tool>> = {};
20
30
  for (const def of TOOL_DEFS) {
@@ -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.