@cstart/coldstart 2.0.3 → 2.1.0
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 +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer/extractors/cpp.d.ts.map +1 -1
- package/dist/indexer/extractors/cpp.js +1 -5
- package/dist/indexer/extractors/cpp.js.map +1 -1
- package/dist/indexer/extractors/csharp.d.ts.map +1 -1
- package/dist/indexer/extractors/csharp.js +1 -5
- package/dist/indexer/extractors/csharp.js.map +1 -1
- package/dist/indexer/extractors/go.d.ts.map +1 -1
- package/dist/indexer/extractors/go.js +1 -5
- package/dist/indexer/extractors/go.js.map +1 -1
- package/dist/indexer/extractors/groovy.d.ts.map +1 -1
- package/dist/indexer/extractors/groovy.js +1 -5
- package/dist/indexer/extractors/groovy.js.map +1 -1
- package/dist/indexer/extractors/java.d.ts +16 -0
- package/dist/indexer/extractors/java.d.ts.map +1 -1
- package/dist/indexer/extractors/java.js +10 -12
- package/dist/indexer/extractors/java.js.map +1 -1
- package/dist/indexer/extractors/kotlin.d.ts.map +1 -1
- package/dist/indexer/extractors/kotlin.js +1 -5
- package/dist/indexer/extractors/kotlin.js.map +1 -1
- package/dist/indexer/extractors/node-helpers.d.ts +11 -0
- package/dist/indexer/extractors/node-helpers.d.ts.map +1 -1
- package/dist/indexer/extractors/node-helpers.js +13 -0
- package/dist/indexer/extractors/node-helpers.js.map +1 -1
- package/dist/indexer/extractors/parser-factory.d.ts +25 -1
- package/dist/indexer/extractors/parser-factory.d.ts.map +1 -1
- package/dist/indexer/extractors/parser-factory.js +126 -16
- package/dist/indexer/extractors/parser-factory.js.map +1 -1
- package/dist/indexer/extractors/php.d.ts.map +1 -1
- package/dist/indexer/extractors/php.js +2 -7
- package/dist/indexer/extractors/php.js.map +1 -1
- package/dist/indexer/extractors/python.d.ts.map +1 -1
- package/dist/indexer/extractors/python.js +3 -7
- package/dist/indexer/extractors/python.js.map +1 -1
- package/dist/indexer/extractors/ruby.d.ts +23 -0
- package/dist/indexer/extractors/ruby.d.ts.map +1 -1
- package/dist/indexer/extractors/ruby.js +6 -17
- package/dist/indexer/extractors/ruby.js.map +1 -1
- package/dist/indexer/extractors/rust.d.ts.map +1 -1
- package/dist/indexer/extractors/rust.js +1 -5
- package/dist/indexer/extractors/rust.js.map +1 -1
- package/dist/indexer/extractors/toml.d.ts.map +1 -1
- package/dist/indexer/extractors/toml.js +1 -5
- package/dist/indexer/extractors/toml.js.map +1 -1
- package/dist/indexer/extractors/xml.d.ts.map +1 -1
- package/dist/indexer/extractors/xml.js +2 -6
- package/dist/indexer/extractors/xml.js.map +1 -1
- package/dist/indexer/extractors/yaml.d.ts.map +1 -1
- package/dist/indexer/extractors/yaml.js +1 -5
- package/dist/indexer/extractors/yaml.js.map +1 -1
- package/dist/indexer/parser.d.ts.map +1 -1
- package/dist/indexer/parser.js +2 -0
- package/dist/indexer/parser.js.map +1 -1
- package/dist/indexer/resolvers/ruby.d.ts.map +1 -1
- package/dist/indexer/resolvers/ruby.js +7 -1
- package/dist/indexer/resolvers/ruby.js.map +1 -1
- package/dist/indexer/ts-parser.d.ts +7 -0
- package/dist/indexer/ts-parser.d.ts.map +1 -1
- package/dist/indexer/ts-parser.js +6 -31
- package/dist/indexer/ts-parser.js.map +1 -1
- package/dist/init.d.ts +18 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +13 -10
- package/dist/init.js.map +1 -1
- package/dist/unwire.d.ts +20 -0
- package/dist/unwire.d.ts.map +1 -0
- package/dist/unwire.js +304 -0
- package/dist/unwire.js.map +1 -0
- package/hooks/codex-kb-elicit.mjs +26 -16
- package/hooks/codex-nudge-handler.mjs +79 -11
- package/hooks/kb-elicit.mjs +42 -17
- package/hooks/nudge-handler.mjs +79 -11
- package/package.json +4 -17
- package/vendor/wasm/tree-sitter-c-sharp.wasm +0 -0
- package/vendor/wasm/tree-sitter-cpp.wasm +0 -0
- package/vendor/wasm/tree-sitter-go.wasm +0 -0
- package/vendor/wasm/tree-sitter-groovy.wasm +0 -0
- package/vendor/wasm/tree-sitter-java.wasm +0 -0
- package/vendor/wasm/tree-sitter-kotlin.wasm +0 -0
- package/vendor/wasm/tree-sitter-php.wasm +0 -0
- package/vendor/wasm/tree-sitter-python.wasm +0 -0
- package/vendor/wasm/tree-sitter-ruby.wasm +0 -0
- package/vendor/wasm/tree-sitter-rust.wasm +0 -0
- package/vendor/wasm/tree-sitter-toml.wasm +0 -0
- package/vendor/wasm/tree-sitter-tsx.wasm +0 -0
- package/vendor/wasm/tree-sitter-typescript.wasm +0 -0
- package/vendor/wasm/tree-sitter-xml.wasm +0 -0
- package/vendor/wasm/tree-sitter-yaml.wasm +0 -0
package/hooks/nudge-handler.mjs
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
import { readFileSync, writeFileSync, renameSync, existsSync } from "node:fs";
|
|
39
39
|
import { execFileSync } from "node:child_process";
|
|
40
|
+
import { createHash } from "node:crypto";
|
|
40
41
|
import { join } from "node:path";
|
|
41
42
|
import { fileURLToPath } from "node:url";
|
|
42
43
|
import { canonicalFindKey } from "./canonical-find-key.mjs";
|
|
@@ -67,6 +68,48 @@ const SEARCH_RE =
|
|
|
67
68
|
/(^|[;&|]|\s)(grep|egrep|fgrep|rg)\b|git\s+grep|git\s+log|(^|[;&|]|\s)find\s|(^|[;&|]|\s)ls\b|(^|[;&|]|\s)cat\b/;
|
|
68
69
|
const GS_FILE_RE = /(?:coldstart|index\.js)\s+gs\s+(\S+)/;
|
|
69
70
|
|
|
71
|
+
// The checkpoint (detector 4) only makes sense when the agent may be SPINNING IN
|
|
72
|
+
// SEARCH — never mid-implementation. A write is the signal that it is producing,
|
|
73
|
+
// not spinning. Fast per-call detection: structured write tools + write-shaped
|
|
74
|
+
// shell (sed -i, redirections, cp/mv/rm, git apply/checkout…). This is the
|
|
75
|
+
// fallback; git ground-truth (below) is authoritative when the repo is a git repo.
|
|
76
|
+
const WRITE_TOOLS = new Set([
|
|
77
|
+
"Edit", "Write", "MultiEdit", "NotebookEdit", // Claude
|
|
78
|
+
"apply_patch", "edit_file", "write_file", "create_file", // Codex / Cursor / MCP writers
|
|
79
|
+
]);
|
|
80
|
+
const WRITE_CMD_RE =
|
|
81
|
+
/(^|[\s;&|(])(sed\s+-i|tee|dd|truncate|patch|apply_patch|cp|mv|rm|mkdir|touch|install)\b|>>?(?!\s*\/dev\/null)|git\s+(apply|checkout|restore|stash|reset|revert|commit|mv|rm)\b/;
|
|
82
|
+
|
|
83
|
+
// Genuine, tool-agnostic write detection: hash the working-tree state so ANY
|
|
84
|
+
// modification (Edit, sed -i, >>, an MCP writer, a new or deleted file) changes
|
|
85
|
+
// it, while pure reads leave it untouched. Empty string when git is unavailable
|
|
86
|
+
// (no repo / no commits / no git) — callers fall back to the fast signal above.
|
|
87
|
+
// porcelain catches added/removed/untracked files; shortstat catches further
|
|
88
|
+
// edits to an already-dirty file. Verified across write modalities 2026-07-10.
|
|
89
|
+
function gitFingerprint(root) {
|
|
90
|
+
try {
|
|
91
|
+
const opts = { cwd: root, encoding: "utf8", timeout: 3000, stdio: ["ignore", "pipe", "ignore"] };
|
|
92
|
+
const status = execFileSync("git", ["status", "--porcelain=v1"], opts);
|
|
93
|
+
const stat = execFileSync("git", ["diff", "HEAD", "--shortstat"], opts);
|
|
94
|
+
return createHash("sha1").update(status + " " + stat).digest("hex").slice(0, 16);
|
|
95
|
+
} catch {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Current HEAD (short sha), or "" when unavailable. The fingerprint above reads
|
|
101
|
+
// the working-tree DIFF against HEAD, so a HEAD move (branch switch / commit /
|
|
102
|
+
// rebase) can shift it without the agent writing anything — we compare HEAD
|
|
103
|
+
// across checkpoints and distrust the fingerprint whenever it moved.
|
|
104
|
+
function gitHead(root) {
|
|
105
|
+
try {
|
|
106
|
+
const opts = { cwd: root, encoding: "utf8", timeout: 3000, stdio: ["ignore", "pipe", "ignore"] };
|
|
107
|
+
return execFileSync("git", ["rev-parse", "HEAD"], opts).trim().slice(0, 12);
|
|
108
|
+
} catch {
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
70
113
|
// Evidence store regexes — paths printed in ANY output, and files named as ARGS.
|
|
71
114
|
const PATH_RE = /[\w./-]+\.(?:py|js|jsx|ts|tsx|htm|html|vue|json|scss|css|rb|java)/g;
|
|
72
115
|
const FILE_ARG_RE =
|
|
@@ -161,6 +204,9 @@ export default function handle(input) {
|
|
|
161
204
|
held_files: [],
|
|
162
205
|
redundant_fired: 0,
|
|
163
206
|
seen_find_queries: [],
|
|
207
|
+
wrote_since_ckpt: false, // (4) any write since the last checkpoint window
|
|
208
|
+
git_fp: "", // (4) working-tree fingerprint at the last checkpoint
|
|
209
|
+
git_head: "", // (4) HEAD at the last checkpoint — a move invalidates the fp diff
|
|
164
210
|
};
|
|
165
211
|
try {
|
|
166
212
|
Object.assign(st, JSON.parse(readFileSync(stateFile, "utf8")));
|
|
@@ -185,6 +231,8 @@ export default function handle(input) {
|
|
|
185
231
|
tool === "Glob" ||
|
|
186
232
|
(tool === "Bash" && !isFind && !isGs && !isKb && SEARCH_RE.test(cmd));
|
|
187
233
|
const isNonfindShell = isSearch;
|
|
234
|
+
// Producing, not spinning — used only to gate the checkpoint (detector 4).
|
|
235
|
+
const isWrite = WRITE_TOOLS.has(tool) || (tool === "Bash" && WRITE_CMD_RE.test(cmd));
|
|
188
236
|
|
|
189
237
|
const _ob = out !== null && out !== undefined ? out.trim().toLowerCase() : null;
|
|
190
238
|
const outEmpty =
|
|
@@ -195,6 +243,7 @@ export default function handle(input) {
|
|
|
195
243
|
_ob === "no files found");
|
|
196
244
|
|
|
197
245
|
st.total += 1;
|
|
246
|
+
if (isWrite) st.wrote_since_ckpt = true;
|
|
198
247
|
// seen_find gates the spiral detectors (3/3b): they stay silent for sessions that
|
|
199
248
|
// never touch coldstart at all (deliberate — this hook must not nag non-users).
|
|
200
249
|
// Any coldstart surface proves awareness: find (set below on the find branch),
|
|
@@ -329,19 +378,38 @@ export default function handle(input) {
|
|
|
329
378
|
st.last_gs_fallback = out && GS_FALLBACK_RE.test(out) ? gsFile : "";
|
|
330
379
|
}
|
|
331
380
|
|
|
332
|
-
// (4) checkpoint — periodic, lowest urgency
|
|
381
|
+
// (4) checkpoint — periodic, lowest urgency. Fires ONLY when the agent may be
|
|
382
|
+
// spinning in search: suppressed whenever a write happened in the window, so it
|
|
383
|
+
// never nags mid-implementation (where "stop searching and answer" is nonsense).
|
|
384
|
+
// git is authoritative when present (ground-truth over the whole window); the
|
|
385
|
+
// fast per-call signal covers the first window and non-git repos.
|
|
333
386
|
if (st.total - st.last_checkpoint >= CHECKPOINT_EVERY) {
|
|
334
387
|
st.last_checkpoint = st.total;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
388
|
+
const headNow = gitHead(input.cwd);
|
|
389
|
+
const fpNow = gitFingerprint(input.cwd);
|
|
390
|
+
// Trust the git working-tree diff only with a prior baseline AND an unmoved
|
|
391
|
+
// HEAD — a branch switch / commit / rebase shifts the whole tree and would
|
|
392
|
+
// read as a spurious write. Otherwise (first window, no git, or HEAD moved)
|
|
393
|
+
// the fast per-call signal governs: it tracks the agent's own writes and is
|
|
394
|
+
// immune to branch movement.
|
|
395
|
+
const gitUsable = !!fpNow && !!st.git_fp && !!headNow && headNow === st.git_head;
|
|
396
|
+
const wrote = gitUsable ? fpNow !== st.git_fp : st.wrote_since_ckpt;
|
|
397
|
+
if (fpNow) st.git_fp = fpNow;
|
|
398
|
+
if (headNow) st.git_head = headNow;
|
|
399
|
+
st.wrote_since_ckpt = false;
|
|
400
|
+
if (!wrote) {
|
|
401
|
+
msgs.push([
|
|
402
|
+
5,
|
|
403
|
+
`Checkpoint (${st.total} tool calls). If you are searching or investigating, stop and think ` +
|
|
404
|
+
"before the next call (if you are mid-implementation, disregard this):\n" +
|
|
405
|
+
"1. Restate the task in one sentence — what is the ONE thing it asks for?\n" +
|
|
406
|
+
"2. List the files you have already surfaced or read that bear on it.\n" +
|
|
407
|
+
"3. Can you answer (1) from (2)? If YES — write the answer now and stop searching; you almost " +
|
|
408
|
+
"certainly have enough.\n" +
|
|
409
|
+
"4. Only if NO — name the single specific fact still missing, and make the next call target ONLY " +
|
|
410
|
+
"that. Do not re-run a search whose results are already in your context.",
|
|
411
|
+
]);
|
|
412
|
+
}
|
|
345
413
|
}
|
|
346
414
|
|
|
347
415
|
// merge THIS call's evidence into the store (after novelty was computed above)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cstart/coldstart",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
16
|
"hooks",
|
|
17
|
-
"templates"
|
|
17
|
+
"templates",
|
|
18
|
+
"vendor/wasm"
|
|
18
19
|
],
|
|
19
20
|
"keywords": [
|
|
20
21
|
"cli",
|
|
@@ -45,22 +46,8 @@
|
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"@modelcontextprotocol/sdk": "latest",
|
|
48
|
-
"@tree-sitter-grammars/tree-sitter-toml": "^0.7.0",
|
|
49
|
-
"@tree-sitter-grammars/tree-sitter-xml": "^0.7.0",
|
|
50
|
-
"@tree-sitter-grammars/tree-sitter-yaml": "^0.7.1",
|
|
51
49
|
"@vscode/ripgrep": "^1.18.0",
|
|
52
|
-
"tree-sitter": "^0.
|
|
53
|
-
"tree-sitter-c-sharp": "^0.21.3",
|
|
54
|
-
"tree-sitter-cpp": "^0.23.4",
|
|
55
|
-
"tree-sitter-go": "^0.23.4",
|
|
56
|
-
"tree-sitter-groovy": "^0.1.2",
|
|
57
|
-
"tree-sitter-java": "^0.23.5",
|
|
58
|
-
"tree-sitter-kotlin": "^0.3.8",
|
|
59
|
-
"tree-sitter-php": "^0.23.12",
|
|
60
|
-
"tree-sitter-python": "^0.23.4",
|
|
61
|
-
"tree-sitter-ruby": "^0.23.1",
|
|
62
|
-
"tree-sitter-rust": "^0.23.1",
|
|
63
|
-
"tree-sitter-typescript": "^0.23.2"
|
|
50
|
+
"web-tree-sitter": "^0.26.10"
|
|
64
51
|
},
|
|
65
52
|
"devDependencies": {
|
|
66
53
|
"@types/node": "^22",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|