@cstart/coldstart 2.2.15 → 2.2.16
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/dist/init.d.ts +16 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +49 -7
- package/dist/init.js.map +1 -1
- package/dist/kb/cli.d.ts.map +1 -1
- package/dist/kb/cli.js +4 -1
- package/dist/kb/cli.js.map +1 -1
- package/dist/kb/durable-worklist.d.ts +8 -6
- package/dist/kb/durable-worklist.d.ts.map +1 -1
- package/dist/kb/durable-worklist.js +76 -22
- package/dist/kb/durable-worklist.js.map +1 -1
- package/dist/kb/store.d.ts.map +1 -1
- package/dist/kb/store.js +12 -5
- package/dist/kb/store.js.map +1 -1
- package/dist/kb/write-batch.js +1 -1
- package/dist/kb/write-batch.js.map +1 -1
- package/dist/kb/write-guide.d.ts.map +1 -1
- package/dist/kb/write-guide.js +6 -4
- package/dist/kb/write-guide.js.map +1 -1
- package/dist/kb/write.d.ts +4 -0
- package/dist/kb/write.d.ts.map +1 -1
- package/dist/kb/write.js.map +1 -1
- package/dist/server/mcp.d.ts +4 -0
- package/dist/server/mcp.d.ts.map +1 -1
- package/dist/server/mcp.js +5 -0
- package/dist/server/mcp.js.map +1 -1
- package/hooks/capture-payload.mjs +204 -88
- package/hooks/capture-sentinel.d.mts +4 -0
- package/hooks/capture-sentinel.mjs +10 -0
- package/hooks/codex-kb-elicit.mjs +6 -2
- package/hooks/codex-kb-recall.mjs +54 -5
- package/hooks/cursor-kb-elicit.mjs +8 -4
- package/hooks/cursor-kb-recall.mjs +34 -1
- package/hooks/kb-elicit.mjs +81 -34
- package/package.json +1 -1
package/hooks/kb-elicit.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import { existsSync, writeFileSync, appendFileSync, readFileSync, readdirSync, s
|
|
|
36
36
|
import { extractEvidence, segmentStats } from "./evidence.mjs";
|
|
37
37
|
import { initialState, step } from "./trigger.mjs";
|
|
38
38
|
import { loadIgnore } from "./ignore.mjs";
|
|
39
|
-
import { buildCapturePayload } from "./capture-payload.mjs";
|
|
39
|
+
import { buildCapturePayload, worklistJsonPath } from "./capture-payload.mjs";
|
|
40
40
|
import {
|
|
41
41
|
worklistEntries, freshNotedSet, gitHead, logCaptureEvent, writePendingCapture, MAX_WORKLIST,
|
|
42
42
|
} from "./elicit-core.mjs";
|
|
@@ -129,10 +129,12 @@ process.on("unhandledRejection", (e) => { log(`unhandled ${e?.stack || e}`); pro
|
|
|
129
129
|
|
|
130
130
|
// --- Manual capture (`--manual --root <dir>`) --------------------------------
|
|
131
131
|
// The `/capture-notes` command runs this to fire capture ON DEMAND, bypassing
|
|
132
|
-
// the trigger score gate. There is no hook stdin, so
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
132
|
+
// the trigger score gate. There is no hook stdin, so there is no session id to
|
|
133
|
+
// trust by default. When the host names it (Claude passes `--session
|
|
134
|
+
// ${CLAUDE_SESSION_ID}`), that session's marker is resolved exactly, never
|
|
135
|
+
// guessed among several. Hosts with no session-id variable on their command
|
|
136
|
+
// surface (Cursor, Codex) pass none; soleMarkerUnderRoot below covers the
|
|
137
|
+
// common single-session case for them without ever guessing wrong.
|
|
136
138
|
//
|
|
137
139
|
// It marks the files it LISTED as captured (and nothing else). Without that, a
|
|
138
140
|
// manual capture left every file uncaptured, so the next automatic fire re-asked
|
|
@@ -144,41 +146,77 @@ function argValue(name) {
|
|
|
144
146
|
const i = process.argv.indexOf(name);
|
|
145
147
|
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
146
148
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
149
|
+
// The main-agent evidence marker for a KNOWN session id. Returns null when the
|
|
150
|
+
// sid is empty or its marker is absent/foreign (files don't resolve under root).
|
|
151
|
+
function markerForSession(root, sid) {
|
|
152
|
+
if (!sid) return null;
|
|
153
|
+
const p = join(tmpdir(), `coldstart-kb-${sid}-main.json`);
|
|
154
|
+
let state;
|
|
155
|
+
try { state = JSON.parse(readFileSync(p, "utf8")); } catch { return null; }
|
|
156
|
+
const files = state && state.files ? Object.keys(state.files) : [];
|
|
157
|
+
if (!files.length || !files.some((rel) => existsSync(join(root, rel)))) return null;
|
|
158
|
+
return { sid, state, path: p };
|
|
159
|
+
}
|
|
160
|
+
// Hosts with no verified session-id variable on their command surface (Cursor,
|
|
161
|
+
// Codex — their commands are plain prompt templates the agent itself runs, with
|
|
162
|
+
// no `!`-style substitution to carry a session id into the invocation) can't name
|
|
163
|
+
// a session at all. Guessing among several is the wrong-session risk `--session`
|
|
164
|
+
// exists to avoid, but when exactly ONE main-agent marker under this root exists
|
|
165
|
+
// there is nothing to disambiguate — restore that single-session case instead of
|
|
166
|
+
// refusing outright. `-main.json` only: subagent markers never own a worklist.
|
|
167
|
+
function soleMarkerUnderRoot(root) {
|
|
168
|
+
const re = /^coldstart-kb-(.+)-main\.json$/;
|
|
169
|
+
let entries;
|
|
170
|
+
try { entries = readdirSync(tmpdir()); } catch { return { marker: null, ambiguous: false }; }
|
|
171
|
+
const candidates = [];
|
|
172
|
+
for (const name of entries) {
|
|
173
|
+
const m = re.exec(name);
|
|
174
|
+
if (!m) continue;
|
|
175
|
+
const found = markerForSession(root, m[1]);
|
|
176
|
+
if (found) candidates.push(found);
|
|
165
177
|
}
|
|
166
|
-
return
|
|
178
|
+
if (candidates.length === 1) return { marker: candidates[0], ambiguous: false };
|
|
179
|
+
if (candidates.length > 1) return { marker: null, ambiguous: true };
|
|
180
|
+
return { marker: null, ambiguous: false };
|
|
167
181
|
}
|
|
168
182
|
if (process.argv.includes("--manual")) {
|
|
169
183
|
try {
|
|
170
184
|
const rootArg = argValue("--root");
|
|
171
185
|
const root = rootArg ? resolve(rootArg) : process.cwd();
|
|
172
186
|
setLogRoot(root);
|
|
173
|
-
const
|
|
174
|
-
|
|
187
|
+
const sidArg = String(argValue("--session") || "").replace(/[^A-Za-z0-9_-]/g, "");
|
|
188
|
+
let found = sidArg ? markerForSession(root, sidArg) : null;
|
|
189
|
+
if (!sidArg) {
|
|
190
|
+
const sole = soleMarkerUnderRoot(root);
|
|
191
|
+
if (sole.ambiguous) {
|
|
192
|
+
process.stdout.write(
|
|
193
|
+
"Multiple sessions have notebook-capture evidence in this repo, and this host can't tell\n" +
|
|
194
|
+
"/capture-notes which one you mean — pass --session <id> if your host exposes one, or close\n" +
|
|
195
|
+
"the other sessions working here so only one remains.\n",
|
|
196
|
+
);
|
|
197
|
+
log(`MANUAL ambiguous-no-session root=${root}`);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
found = sole.marker;
|
|
201
|
+
if (found) log(`MANUAL sole-marker-fallback sid=${found.sid} root=${root}`);
|
|
202
|
+
}
|
|
203
|
+
if (!sidArg && !found) {
|
|
175
204
|
process.stdout.write(
|
|
176
205
|
"No notebook-capture evidence for this repo yet — it accrues as you read and edit files.\n" +
|
|
177
206
|
"Do a turn or two of real work here, then run /capture-notes again.\n",
|
|
178
207
|
);
|
|
179
|
-
log(`MANUAL no-
|
|
208
|
+
log(`MANUAL no-session root=${root}`);
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
if (!found) {
|
|
212
|
+
process.stdout.write(
|
|
213
|
+
"No notebook-capture evidence for THIS session in this repo yet — it accrues as you read\n" +
|
|
214
|
+
"and edit files. Do a turn or two of real work here, then run /capture-notes again.\n",
|
|
215
|
+
);
|
|
216
|
+
log(`MANUAL no-marker-for-session sid=${sidArg} root=${root}`);
|
|
180
217
|
process.exit(0);
|
|
181
218
|
}
|
|
219
|
+
log(`MANUAL session sid=${found.sid} root=${root}`);
|
|
182
220
|
const { sid, state, path: markerPath } = found;
|
|
183
221
|
// A manual /capture-notes is an EXPLICIT request to write up THIS session's
|
|
184
222
|
// work, so its scope is everything worked on — not merely what an automatic
|
|
@@ -189,10 +227,17 @@ if (process.argv.includes("--manual")) {
|
|
|
189
227
|
// note unwritten and a now-stale note unfixed (the Bug-1 report). So:
|
|
190
228
|
// include every edited file regardless of the flag, and drop only READ-ONLY
|
|
191
229
|
// files that were already offered (re-listing those on demand is just
|
|
192
|
-
// noise)
|
|
193
|
-
//
|
|
230
|
+
// noise) AND whose worklist is still live. A captured read-only file whose
|
|
231
|
+
// durable worklist pair is gone (write failure, or hand-deleted since) has
|
|
232
|
+
// no live checklist anywhere naming it — re-including it here is the only
|
|
233
|
+
// way it is ever offered again, and it can't double-nag since there is
|
|
234
|
+
// nothing else currently showing it. Rank most-worked first (edits →
|
|
235
|
+
// retouches → reads) so new/edited files lead; the per-file annotations
|
|
236
|
+
// flag when a fresh note needs nothing.
|
|
237
|
+
const worklistLost = !existsSync(worklistJsonPath(root, sid, "main"));
|
|
238
|
+
if (worklistLost) log(`MANUAL worklist-artifact-missing session=${sid} — recovering captured read-only files too`);
|
|
194
239
|
const files = Object.entries(state.files)
|
|
195
|
-
.filter(([, f]) => (f.reads + f.edits + f.gs) > 0 && (f.edits > 0 || !f.captured))
|
|
240
|
+
.filter(([, f]) => (f.reads + f.edits + f.gs) > 0 && (f.edits > 0 || !f.captured || worklistLost))
|
|
196
241
|
.sort((a, b) => (b[1].edits - a[1].edits)
|
|
197
242
|
|| ((b[1].retouches || 0) - (a[1].retouches || 0))
|
|
198
243
|
|| (b[1].reads - a[1].reads))
|
|
@@ -203,7 +248,9 @@ if (process.argv.includes("--manual")) {
|
|
|
203
248
|
process.exit(0);
|
|
204
249
|
}
|
|
205
250
|
const entries = worklistEntries(CLI, root, files, state.files, log);
|
|
206
|
-
|
|
251
|
+
// Manual capture resolves the invoking session's MAIN marker (markerForSession
|
|
252
|
+
// targets `-main.json`), so its worklist is the main agent's.
|
|
253
|
+
const payload = buildCapturePayload({ root, cli: CLI, sid, aid: "main", entries, envelope: "manual" });
|
|
207
254
|
// Mark ONLY the files actually LISTED (the worklist caps its display to
|
|
208
255
|
// MAX_WORKLIST) captured — any overflow stays uncaptured so it rolls into
|
|
209
256
|
// the next capture rather than being marked done but never shown. Re-read
|
|
@@ -345,7 +392,7 @@ if (process.argv.includes("--manual")) {
|
|
|
345
392
|
writeFileSync(marker, JSON.stringify(state));
|
|
346
393
|
if (!fresh.length) { log(`FAST-EXIT subagent no-new-files session=${sid} agent=${aid}`); process.exit(0); }
|
|
347
394
|
const entries = worklistEntries(CLI, root, fresh, Object.fromEntries(fresh.map((rel) => [rel, delta.get(rel)])), log);
|
|
348
|
-
const payload = buildCapturePayload({ root, cli: CLI, sid, entries, envelope: "subagent" });
|
|
395
|
+
const payload = buildCapturePayload({ root, cli: CLI, sid, aid, entries, envelope: "subagent" });
|
|
349
396
|
logCaptureEvent(root, { event: "fire", reason: "subagent", session: sid, agent: aid, files: fresh.length });
|
|
350
397
|
log(`FIRE subagent session=${sid} agent=${aid} files=${fresh.length}`);
|
|
351
398
|
process.stdout.write(JSON.stringify({ decision: "block", reason: payload }));
|
|
@@ -381,11 +428,11 @@ if (process.argv.includes("--manual")) {
|
|
|
381
428
|
log(`FIRE ${decision.fire} mode=${decision.mode} session=${sid} score=${decision.score} files=${decision.files.length}`);
|
|
382
429
|
|
|
383
430
|
if (decision.mode === "block") {
|
|
384
|
-
const payload = buildCapturePayload({ root, cli: CLI, sid, entries, envelope: "block" });
|
|
431
|
+
const payload = buildCapturePayload({ root, cli: CLI, sid, aid, entries, envelope: "block" });
|
|
385
432
|
process.stdout.write(JSON.stringify({ decision: "block", reason: payload }));
|
|
386
433
|
} else {
|
|
387
434
|
// Non-blocking: kb-recall delivers this with the user's next prompt.
|
|
388
|
-
const payload = buildCapturePayload({ root, cli: CLI, sid, entries, envelope: "inject" });
|
|
435
|
+
const payload = buildCapturePayload({ root, cli: CLI, sid, aid, entries, envelope: "inject" });
|
|
389
436
|
writePendingCapture(sid, decision.fire, payload);
|
|
390
437
|
}
|
|
391
438
|
} catch (e) {
|