@jeffober/thatch 0.1.12 → 0.1.14
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 +11 -0
- package/artifacts/skills/common.md +61 -0
- package/artifacts/skills/thatch-code-review.md +125 -0
- package/artifacts/skills/thatch-dedup-classifier.md +40 -0
- package/artifacts/skills/thatch-fact-extractor.md +67 -0
- package/artifacts/skills/thatch-project-primer.md +62 -0
- package/artifacts/skills/thatch-review-acceptance.md +70 -0
- package/artifacts/skills/thatch-review-breadcrumbs.md +58 -0
- package/artifacts/skills/thatch-review-context.md +110 -0
- package/artifacts/skills/thatch-review-mark-and-sweep.md +125 -0
- package/artifacts/skills/thatch-review-no-slop.md +71 -0
- package/artifacts/skills/thatch-review-pedantic.md +57 -0
- package/artifacts/skills/thatch-review-state-flow.md +89 -0
- package/artifacts/skills/thatch-review-synthesizer.md +94 -0
- package/artifacts/skills/thatch-session-reflection.md +68 -0
- package/artifacts/skills/thatch-workflow-research.md +91 -0
- package/bin/thatch +9 -5
- package/docs/dev/skills.md +5 -8
- package/docs/user/README.md +11 -0
- package/package.json +3 -2
- package/src/extract-queue.ts +61 -10
- package/src/extraction.ts +12 -2
- package/src/index.ts +76 -9
- package/src/prompts.ts +56 -10
- package/src/skills.ts +41 -1282
- package/src/tool-defs.ts +28 -0
package/docs/dev/skills.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Skills
|
|
2
2
|
|
|
3
3
|
Thatch installs SKILL.md files that give the agent task-specific workflows. All
|
|
4
|
-
skill content is
|
|
4
|
+
skill content lives in `artifacts/skills/*.md` and is loaded by `src/skills.ts` and installed to a host-specific
|
|
5
5
|
directory. See `mcp-parity.md` for which skills each host gets.
|
|
6
6
|
|
|
7
7
|
## Skill file format
|
|
@@ -102,13 +102,10 @@ and `--cursor` pass only the shared set.
|
|
|
102
102
|
|
|
103
103
|
## Adding a skill
|
|
104
104
|
|
|
105
|
-
1. Write
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
3. If the skill name appears in a tool's expected workflow (e.g. the extraction
|
|
110
|
-
nudge references `thatch-fact-extractor`), update that prompt in `src/prompts.ts`.
|
|
111
|
-
4. The opencode plugin and `thatch setup` pick it up automatically on next init.
|
|
105
|
+
1. Write `artifacts/skills/thatch-<name>.md` with YAML frontmatter. If it's a review specialist, include `${REVIEW_COMMON}` (interpolated from `artifacts/skills/common.md`).
|
|
106
|
+
2. Add the skill name to the `names` array in `loadSharedSkills()` (or `loadOpencodeOnlySkills()` if sub-agents are needed). The loader in `src/skills.ts` reads `.md` files at init.
|
|
107
|
+
3. If the skill name appears in a tool's workflow (e.g. `thatch-fact-extractor`), update `src/prompts.ts`.
|
|
108
|
+
4. Run `mise run check` — tests verify counts; `installSkills` picks up new files on next init.
|
|
112
109
|
|
|
113
110
|
## Memory review skills in practice
|
|
114
111
|
|
package/docs/user/README.md
CHANGED
|
@@ -20,6 +20,17 @@ Publish to npm and add thatch to your opencode config:
|
|
|
20
20
|
|
|
21
21
|
OpenCode installs the plugin and its dependencies automatically on next start.
|
|
22
22
|
|
|
23
|
+
Set this environment variable so the extraction nudge can dispatch the
|
|
24
|
+
fact-extractor as a **background** sub-agent instead of blocking your turn:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Without it, opencode hides the task tool's `background` parameter and the
|
|
31
|
+
extraction nudge runs synchronously. Put it in your shell rc, `mise.toml`
|
|
32
|
+
`[env]`, or `.envrc`.
|
|
33
|
+
|
|
23
34
|
For local development before publishing, use a file path:
|
|
24
35
|
|
|
25
36
|
```jsonc
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"src",
|
|
17
17
|
"docs",
|
|
18
|
-
"bin"
|
|
18
|
+
"bin",
|
|
19
|
+
"artifacts"
|
|
19
20
|
],
|
|
20
21
|
"scripts": {
|
|
21
22
|
"test": "bun test",
|
package/src/extract-queue.ts
CHANGED
|
@@ -37,11 +37,16 @@ function queuePath(sessionID: string): string {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Buffer a batch of tool calls for later extraction. Filters out
|
|
41
|
-
* MCP tools (mcp__thatch__*) to avoid self-echo — extracting
|
|
42
|
-
* operations would just write the store back into
|
|
43
|
-
*
|
|
44
|
-
*
|
|
40
|
+
* Buffer a batch of tool calls for later extraction. Filters out:
|
|
41
|
+
* - thatch's own MCP tools (mcp__thatch__*) to avoid self-echo — extracting
|
|
42
|
+
* facts from memory operations would just write the store back into itself.
|
|
43
|
+
* - skill/task/agent meta-tools — these orchestrate agent behavior (loading
|
|
44
|
+
* skills, dispatching sub-agents) and buffering them creates a feedback
|
|
45
|
+
* loop where the extraction nudge triggers a skill load, which gets
|
|
46
|
+
* buffered, which triggers another nudge on the next turn.
|
|
47
|
+
* Memory writes reset the missed-nudge escalation counter.
|
|
48
|
+
* Each surviving call is mapped to a ToolInteraction (title synthesized since
|
|
49
|
+
* Claude Code doesn't supply one) and appended as one JSON line.
|
|
45
50
|
*/
|
|
46
51
|
export function appendBatch(sessionID: string, toolCalls: BatchToolCall[]): void {
|
|
47
52
|
const dir = queueDir();
|
|
@@ -50,7 +55,14 @@ export function appendBatch(sessionID: string, toolCalls: BatchToolCall[]): void
|
|
|
50
55
|
const existing = readQueueFile(path);
|
|
51
56
|
const additions: ToolInteraction[] = [];
|
|
52
57
|
for (const tc of toolCalls) {
|
|
53
|
-
|
|
58
|
+
const lower = tc.tool_name.toLowerCase();
|
|
59
|
+
if (lower === "mcp__thatch__memory_remember" || lower === "mcp__thatch__extraction_done") {
|
|
60
|
+
resetMissedCount(sessionID);
|
|
61
|
+
consumeQueue(sessionID);
|
|
62
|
+
existing.length = 0;
|
|
63
|
+
}
|
|
64
|
+
if (lower.startsWith("mcp__thatch__")) continue;
|
|
65
|
+
if (lower === "skill" || lower === "task" || lower === "agent") continue;
|
|
54
66
|
const response = typeof tc.tool_response === "string"
|
|
55
67
|
? tc.tool_response
|
|
56
68
|
: JSON.stringify(tc.tool_response).slice(0, 500);
|
|
@@ -70,8 +82,8 @@ export function appendBatch(sessionID: string, toolCalls: BatchToolCall[]): void
|
|
|
70
82
|
writeFileSync(path, all.map((ix) => JSON.stringify(ix)).join("\n") + "\n");
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
/** Read
|
|
74
|
-
export function
|
|
85
|
+
/** Read a session's queued interactions without removing them. */
|
|
86
|
+
export function peekQueue(sessionID: string): ToolInteraction[] {
|
|
75
87
|
let path: string;
|
|
76
88
|
try {
|
|
77
89
|
path = queuePath(sessionID);
|
|
@@ -79,8 +91,18 @@ export function flushQueue(sessionID: string): ToolInteraction[] {
|
|
|
79
91
|
return [];
|
|
80
92
|
}
|
|
81
93
|
if (!existsSync(path)) return [];
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
return readQueueFile(path);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Delete a session's queue file. Called when the agent writes a memory. */
|
|
98
|
+
export function consumeQueue(sessionID: string): void {
|
|
99
|
+
try { unlinkSync(queuePath(sessionID)); } catch { /* not present is fine */ }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Read and delete a session's queue. Returns the buffered interactions in order. */
|
|
103
|
+
export function flushQueue(sessionID: string): ToolInteraction[] {
|
|
104
|
+
const interactions = peekQueue(sessionID);
|
|
105
|
+
consumeQueue(sessionID);
|
|
84
106
|
return interactions;
|
|
85
107
|
}
|
|
86
108
|
|
|
@@ -98,4 +120,33 @@ function readQueueFile(path: string): ToolInteraction[] {
|
|
|
98
120
|
}
|
|
99
121
|
})
|
|
100
122
|
.filter((ix): ix is ToolInteraction => ix !== null);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Missed-nudge counter — file-backed per-session escalation state. Tracks
|
|
127
|
+
// consecutive extraction nudges delivered without any memory_remember call.
|
|
128
|
+
// Reset by appendBatch when the agent writes a memory. Read and incremented
|
|
129
|
+
// by flush-tools when the nudge is delivered.
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function countPath(sessionID: string): string {
|
|
133
|
+
return join(queueDir(), `${safeName(sessionID)}.count`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resetMissedCount(sessionID: string): void {
|
|
137
|
+
try { unlinkSync(countPath(sessionID)); } catch { /* not present is fine */ }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getMissedCount(sessionID: string): number {
|
|
141
|
+
try {
|
|
142
|
+
return parseInt(readFileSync(countPath(sessionID), "utf8"), 10) || 0;
|
|
143
|
+
} catch {
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function incrementMissedCount(sessionID: string): void {
|
|
149
|
+
const next = getMissedCount(sessionID) + 1;
|
|
150
|
+
mkdirSync(queueDir(), { recursive: true });
|
|
151
|
+
writeFileSync(countPath(sessionID), String(next));
|
|
101
152
|
}
|
package/src/extraction.ts
CHANGED
|
@@ -100,10 +100,20 @@ export class ExtractionPipeline {
|
|
|
100
100
|
this.#buffers.set(interaction.sessionID, buf.slice(-this.#maxBuffer));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/** Returns the session's buffered interactions without clearing them. */
|
|
104
|
+
peek(sessionID: string): ToolInteraction[] {
|
|
105
|
+
return this.#buffers.get(sessionID) ?? [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Clears the session's buffer. Called when the agent writes a memory. */
|
|
109
|
+
consume(sessionID: string): void {
|
|
110
|
+
this.#buffers.delete(sessionID);
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
/** Returns the session's buffered interactions and clears them. */
|
|
104
114
|
flush(sessionID: string): ToolInteraction[] {
|
|
105
|
-
const batch = this
|
|
106
|
-
this
|
|
115
|
+
const batch = this.peek(sessionID);
|
|
116
|
+
this.consume(sessionID);
|
|
107
117
|
return batch;
|
|
108
118
|
}
|
|
109
119
|
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
compactionContext,
|
|
10
10
|
sessionStartReminder,
|
|
11
11
|
recallNudge,
|
|
12
|
+
extractionNudge,
|
|
12
13
|
type NudgeMatch,
|
|
13
14
|
} from "./prompts";
|
|
14
15
|
import { ExtractionPipeline } from "./extraction";
|
|
@@ -49,6 +50,20 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
49
50
|
// session, but no crash).
|
|
50
51
|
const compacting = new Set<string>();
|
|
51
52
|
|
|
53
|
+
// Per-session count of consecutive extraction nudges delivered without any
|
|
54
|
+
// memory_remember call in between. Drives nudge escalation: the agent gets
|
|
55
|
+
// a couple of polite chances, then the tone shifts to directive, then to
|
|
56
|
+
// all-caps shouting. Reset to 0 whenever the agent writes a memory.
|
|
57
|
+
const missedNudges = new Map<string, number>();
|
|
58
|
+
|
|
59
|
+
// Parent-child session mapping for cross-session buffer drain. opencode
|
|
60
|
+
// creates real child sessions with their own IDs for sub-agents; without
|
|
61
|
+
// this map, a memory_remember call in a child (e.g. a background
|
|
62
|
+
// fact-extractor task) drains the child's empty buffer but leaves the
|
|
63
|
+
// parent's buffer untouched, causing the nudge to replay indefinitely.
|
|
64
|
+
// Populated from session.created events that carry a parentID.
|
|
65
|
+
const childToParent = new Map<string, string>();
|
|
66
|
+
|
|
52
67
|
// Skills always install to the global opencode config — installing into the
|
|
53
68
|
// worktree would mutate the user's repo (untracked files in git status).
|
|
54
69
|
// A failed install degrades the nudge workflow but must not kill the plugin.
|
|
@@ -86,11 +101,49 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
86
101
|
compacting.delete(input.sessionID);
|
|
87
102
|
},
|
|
88
103
|
|
|
89
|
-
// 3. Tool buffering — feeds the extraction nudge.
|
|
90
|
-
//
|
|
91
|
-
//
|
|
104
|
+
// 3. Tool buffering — feeds the extraction nudge. Excluded tools:
|
|
105
|
+
// - thatch_*: extracting facts from memory ops would echo the store
|
|
106
|
+
// - skill/task: meta-tools that orchestrate agent behavior (loading
|
|
107
|
+
// skills, dispatching sub-agents). Buffering them creates a feedback
|
|
108
|
+
// loop — the nudge triggers a skill load, which gets buffered, which
|
|
109
|
+
// triggers another nudge on the next turn.
|
|
110
|
+
// Memory writes consume the buffer and reset the missed-nudge counter.
|
|
111
|
+
// The buffer is NOT drained on nudge delivery — it persists until the
|
|
112
|
+
// agent writes a memory, so ignored nudges accumulate and the payload
|
|
113
|
+
// grows with each missed cycle.
|
|
114
|
+
//
|
|
115
|
+
// Fix A: a memory_remember call in a child session (sub-agent) also
|
|
116
|
+
// drains the parent's buffer. Without this, dispatching the
|
|
117
|
+
// fact-extractor as a background task writes memories in the child
|
|
118
|
+
// but never clears the parent's queue — the nudge replays every turn.
|
|
119
|
+
//
|
|
120
|
+
// Fix C: thatch_extraction_done is an explicit acknowledgment tool the
|
|
121
|
+
// model calls in the parent session after dispatching. Belt-and-
|
|
122
|
+
// suspenders for fix A: covers the case where the sub-agent errors out
|
|
123
|
+
// or the host doesn't expose parent-child session relationships.
|
|
92
124
|
"tool.execute.after": async (input, output) => {
|
|
93
|
-
if (input.tool
|
|
125
|
+
if (input.tool === "thatch_memory_remember") {
|
|
126
|
+
extraction.consume(input.sessionID);
|
|
127
|
+
missedNudges.delete(input.sessionID);
|
|
128
|
+
// Fix A: if this is a child session, also drain the parent so the
|
|
129
|
+
// parent's extraction nudge stops replaying after a sub-agent writes
|
|
130
|
+
// memories on its behalf.
|
|
131
|
+
const parentID = childToParent.get(input.sessionID);
|
|
132
|
+
if (parentID) {
|
|
133
|
+
extraction.consume(parentID);
|
|
134
|
+
missedNudges.delete(parentID);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Fix C: explicit acknowledgment tool drains the buffer without
|
|
139
|
+
// requiring a memory write. The model calls this after dispatching
|
|
140
|
+
// the fact-extractor to a sub-agent.
|
|
141
|
+
if (input.tool === "thatch_extraction_done") {
|
|
142
|
+
extraction.consume(input.sessionID);
|
|
143
|
+
missedNudges.delete(input.sessionID);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (input.tool.startsWith("thatch_") || input.tool === "skill" || input.tool === "task") return;
|
|
94
147
|
extraction.push({
|
|
95
148
|
tool: input.tool,
|
|
96
149
|
sessionID: input.sessionID,
|
|
@@ -113,12 +166,11 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
113
166
|
"chat.message": async (input, output) => {
|
|
114
167
|
if (compacting.has(input.sessionID)) return;
|
|
115
168
|
if (extraction.pending(input.sessionID)) {
|
|
116
|
-
const batch = extraction.
|
|
169
|
+
const batch = extraction.peek(input.sessionID);
|
|
117
170
|
const payload = extraction.buildPayload(batch, repo);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`to save any new durable facts from this payload:\n${payload}`;
|
|
171
|
+
const missed = missedNudges.get(input.sessionID) ?? 0;
|
|
172
|
+
const text = extractionNudge(batch.length, missed, "thatch_memory_remember", payload);
|
|
173
|
+
missedNudges.set(input.sessionID, missed + 1);
|
|
122
174
|
|
|
123
175
|
output.parts.push({
|
|
124
176
|
id: `prt_thatch_${Math.random().toString(36).slice(2)}`,
|
|
@@ -165,7 +217,22 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
165
217
|
|
|
166
218
|
// 5. Session-start reminder, carrying the hygiene heartbeat. Hygiene is
|
|
167
219
|
// best-effort: a failure there must not cost the reminder itself.
|
|
220
|
+
//
|
|
221
|
+
// Also tracks parent-child session relationships for fix A (cross-session
|
|
222
|
+
// buffer drain). session.created with a parentID records the mapping;
|
|
223
|
+
// session.deleted cleans it up to avoid unbounded growth.
|
|
168
224
|
event: async ({ event }) => {
|
|
225
|
+
if (event.type === "session.created") {
|
|
226
|
+
const info = event.properties.info;
|
|
227
|
+
if (info.parentID) {
|
|
228
|
+
childToParent.set(info.id, info.parentID);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (event.type === "session.deleted") {
|
|
232
|
+
childToParent.delete(event.properties.info.id);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
169
236
|
if (event.type !== "session.created") return;
|
|
170
237
|
const id = event.properties.info.id;
|
|
171
238
|
|
package/src/prompts.ts
CHANGED
|
@@ -136,7 +136,7 @@ Tools are prefixed in Claude Code: \`mcp__thatch__memory_remember\`,
|
|
|
136
136
|
\`mcp__thatch__memory_recall\`, \`mcp__thatch__memory_list\`,
|
|
137
137
|
\`mcp__thatch__memory_show\`, \`mcp__thatch__memory_forget\`,
|
|
138
138
|
\`mcp__thatch__store_list\`, \`mcp__thatch__find_duplicates\`,
|
|
139
|
-
\`mcp__thatch__dedup_mark_checked\`.
|
|
139
|
+
\`mcp__thatch__dedup_mark_checked\`, \`mcp__thatch__extraction_done\`.
|
|
140
140
|
Bare names used below for readability.
|
|
141
141
|
|
|
142
142
|
## Stores
|
|
@@ -255,16 +255,62 @@ export function claudeWriteNudge(): string {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
258
|
+
* Extraction nudge with escalation. Both the opencode plugin (src/index.ts)
|
|
259
|
+
* and the Claude Code/Cursor CLI (bin/thatch flush-tools) call this with
|
|
260
|
+
* their respective tool name prefix. The missedCount parameter tracks
|
|
261
|
+
* consecutive nudges delivered without any memory_remember call in between,
|
|
262
|
+
* escalating the tone from polite to insistent to shouting.
|
|
263
|
+
*/
|
|
264
|
+
export function extractionNudge(
|
|
265
|
+
count: number,
|
|
266
|
+
missedCount: number,
|
|
267
|
+
toolName: string,
|
|
268
|
+
payload: string,
|
|
269
|
+
): string {
|
|
270
|
+
const plural = count === 1 ? "" : "s";
|
|
271
|
+
// opencode's task tool has a `background` parameter (requires
|
|
272
|
+
// OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true) that launches the sub-agent
|
|
273
|
+
// asynchronously — the call returns immediately and the session is notified
|
|
274
|
+
// on completion. Naming the parameter gives the model a direct hit on the tool
|
|
275
|
+
// schema it sees. MCP hosts (Claude Code, Cursor) keep generic sub-agent
|
|
276
|
+
// wording because their background mechanisms differ and are not exposed as a
|
|
277
|
+
// per-call parameter in the same way.
|
|
278
|
+
const isOpencode = toolName.startsWith("thatch_");
|
|
279
|
+
const dispatch = isOpencode
|
|
280
|
+
? "Dispatch a task with background: true"
|
|
281
|
+
: "Spawn a background sub-agent";
|
|
282
|
+
const drainTool = isOpencode ? "thatch_extraction_done" : "mcp__thatch__extraction_done";
|
|
283
|
+
const acknowledge = `After dispatching, call ${drainTool} to acknowledge.`;
|
|
284
|
+
|
|
285
|
+
if (missedCount >= 3) {
|
|
286
|
+
return `[thatch] YOU ARE IGNORING EXTRACTION INSTRUCTIONS. ` +
|
|
287
|
+
`${count} TOOL INTERACTION${plural.toUpperCase()} ARE QUEUED AND YOU HAVE NOT PROCESSED ANY OF THEM. ` +
|
|
288
|
+
`THE USER INSTALLED THIS PLUGIN FOR A REASON. ` +
|
|
289
|
+
`${dispatch.toUpperCase()} NOW TO RUN THE thatch-fact-extractor SKILL. ` +
|
|
290
|
+
`${acknowledge.toUpperCase()} ` +
|
|
291
|
+
`USE ${toolName.toUpperCase()} TO SAVE DURABLE FACTS FROM THIS PAYLOAD:\n${payload}`;
|
|
292
|
+
}
|
|
293
|
+
if (missedCount >= 2) {
|
|
294
|
+
return `[thatch] YOU HAVE NOT PROCESSED YOUR PREVIOUS EXTRACTION PAYLOAD${plural.toUpperCase()}. ` +
|
|
295
|
+
`${count} more tool interaction${plural} are now queued. ` +
|
|
296
|
+
`${dispatch} to run the thatch-fact-extractor skill on this payload. ` +
|
|
297
|
+
`${acknowledge} ` +
|
|
298
|
+
`Use ${toolName} to save durable facts from this payload:\n${payload}`;
|
|
299
|
+
}
|
|
300
|
+
return `[thatch] ${dispatch} to run the thatch-fact-extractor skill ` +
|
|
301
|
+
`on ${count} queued tool interaction${plural}. ` +
|
|
302
|
+
`${acknowledge} ` +
|
|
303
|
+
`Then answer the user. ` +
|
|
304
|
+
`Use ${toolName} to save durable facts from this payload:\n${payload}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Backwards-compatible wrapper for the Claude Code/Cursor CLI path.
|
|
309
|
+
* Maintained for any external callers; bin/thatch should use extractionNudge
|
|
310
|
+
* directly with a missedCount from extract-queue.ts.
|
|
263
311
|
*/
|
|
264
312
|
export function claudeExtractionNudge(count: number, payload: string): string {
|
|
265
|
-
return
|
|
266
|
-
`Load the thatch-fact-extractor skill, then use mcp__thatch__memory_remember ` +
|
|
267
|
-
`to save any new durable facts from this payload:\n${payload}`;
|
|
313
|
+
return extractionNudge(count, 0, "mcp__thatch__memory_remember", payload);
|
|
268
314
|
}
|
|
269
315
|
|
|
270
316
|
/**
|
|
@@ -283,7 +329,7 @@ Tools are prefixed in Cursor: \`mcp__thatch__memory_remember\`,
|
|
|
283
329
|
\`mcp__thatch__memory_recall\`, \`mcp__thatch__memory_list\`,
|
|
284
330
|
\`mcp__thatch__memory_show\`, \`mcp__thatch__memory_forget\`,
|
|
285
331
|
\`mcp__thatch__store_list\`, \`mcp__thatch__find_duplicates\`,
|
|
286
|
-
\`mcp__thatch__dedup_mark_checked\`.
|
|
332
|
+
\`mcp__thatch__dedup_mark_checked\`, \`mcp__thatch__extraction_done\`.
|
|
287
333
|
Bare names used below for readability.
|
|
288
334
|
|
|
289
335
|
## Stores
|