@jeffober/thatch 0.1.26 → 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.
- package/README.md +7 -7
- package/artifacts/skills/common.md +1 -1
- package/artifacts/skills/thatch-change-walkthrough.md +2 -2
- package/artifacts/skills/thatch-code-archaeology.md +252 -0
- package/artifacts/skills/thatch-code-review.md +4 -4
- package/artifacts/skills/thatch-code-walkthrough.md +2 -2
- package/artifacts/skills/thatch-coding-workflow.md +79 -0
- package/artifacts/skills/{pr-description.md → thatch-pr-description.md} +1 -1
- package/artifacts/skills/thatch-review-context.md +9 -0
- package/artifacts/skills/thatch-review-followup.md +29 -1
- package/artifacts/skills/thatch-review-pedantic.md +16 -1
- package/artifacts/skills/thatch-review-response.md +365 -0
- package/artifacts/skills/thatch-review-synthesizer.md +92 -5
- package/artifacts/skills/{split-overlarge-pr.md → thatch-split-overlarge-pr.md} +1 -1
- package/artifacts/skills/{ticket-description.md → thatch-ticket-description.md} +3 -3
- package/bin/release +3 -3
- package/bin/thatch +8 -8
- package/docs/dev/README.md +1 -1
- package/docs/dev/skills.md +29 -9
- package/docs/plans/README.md +9 -6
- package/docs/plans/intuition-drives.md +147 -0
- package/docs/plans/nomenclater.md +89 -0
- package/docs/plans/prediction-consolidation.md +361 -0
- package/docs/qa/README.md +20 -3
- package/docs/user/README.md +19 -5
- package/package.json +1 -1
- package/src/db.ts +6 -6
- package/src/embeddings.ts +2 -2
- package/src/extract-queue.ts +4 -4
- package/src/extraction.ts +4 -4
- package/src/git.ts +1 -1
- package/src/hygiene.ts +1 -1
- package/src/index.ts +28 -28
- package/src/mcp.ts +5 -5
- package/src/prompts.ts +71 -68
- package/src/setup.ts +19 -19
- package/src/sideband.ts +3 -3
- package/src/skills.ts +104 -6
- package/src/tool-defs.ts +11 -11
- package/src/tools.ts +1 -1
- package/artifacts/skills/thatch-workflow-research.md +0 -91
- package/docs/in-progress/prediction-engine.md +0 -404
- package/docs/plans/001-memory-store.md +0 -106
- package/docs/plans/002-agent-driven-pipelines.md +0 -59
- package/docs/plans/003-memory-hygiene.md +0 -61
- package/docs/qa/use-cases/README.md +0 -51
- package/docs/qa/use-cases/UC-001-memory-roundtrip.md +0 -20
- package/docs/qa/use-cases/UC-002-dedup-cycle.md +0 -23
- package/docs/qa/use-cases/UC-003-extraction-nudge.md +0 -56
- package/docs/qa/use-cases/UC-004-cli-inspection.md +0 -22
- package/docs/qa/use-cases/UC-005-setup-install.md +0 -34
- package/docs/qa/use-cases/UC-006-cursor-hook-lifecycle.md +0 -32
- package/docs/qa/use-cases/UC-007-recall-nudge.md +0 -31
- package/docs/qa/use-cases/UC-008-hygiene-heartbeat.md +0 -26
- package/docs/qa/use-cases/UC-009-flush-tools-tiers.md +0 -33
- package/docs/qa/use-cases/UC-010-prime.md +0 -25
- package/docs/qa/use-cases/UC-011-write-time-similarity-warning.md +0 -24
- package/docs/qa/use-cases/UC-012-model-migration.md +0 -28
- package/docs/qa/use-cases/UC-013-compaction-context.md +0 -25
- package/docs/qa/use-cases/UC-014-skill-install-drift.md +0 -28
- package/docs/qa/use-cases/UC-015-env-override-matrix.md +0 -28
- package/docs/qa/use-cases/UC-016-concurrent-session-isolation.md +0 -37
- package/docs/qa/use-cases/UC-017-buffer-tool-vs-buffer-batch.md +0 -27
- package/docs/qa/use-cases/UC-018-mcp-startup-setup-detection.md +0 -38
- package/docs/qa/use-cases/UC-019-archived-memory-lifecycle.md +0 -36
- package/docs/qa/use-cases/UC-020-extraction-escalation.md +0 -52
- package/docs/qa/use-cases/UC-021-prediction-autofire.md +0 -21
- package/docs/qa/use-cases/UC-022-prediction-dedup.md +0 -19
- package/docs/qa/use-cases/UC-023-prediction-confidence-model.md +0 -23
- 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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,14 +43,16 @@ function loadSharedSkills(): SkillDef[] {
|
|
|
42
43
|
"thatch-review-highlights",
|
|
43
44
|
"thatch-review-synthesizer",
|
|
44
45
|
"thatch-review-context",
|
|
45
|
-
"thatch-
|
|
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
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
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
|
|
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
|
*/
|
|
@@ -25,7 +25,7 @@ export interface CoreContext {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* A tool definition
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|