@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/dist/unwire.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coldstart unwire — the reverse of `coldstart init`.
|
|
3
|
+
*
|
|
4
|
+
* `npm uninstall` cannot clean the per-repo artifacts init wrote (npm fires no
|
|
5
|
+
* pre/postuninstall reliably, and a global uninstall has no registry of which
|
|
6
|
+
* repos were inited). So, like husky, coldstart ships an explicit reverse
|
|
7
|
+
* command. It removes ONLY coldstart-owned markers from the files init touched —
|
|
8
|
+
* never clobbering user content in shared files — using the SAME detectors init
|
|
9
|
+
* uses for idempotent re-runs (isColdstartHookEntry / isKbHookEntry /
|
|
10
|
+
* isCodexHookEntry / isCursorHookEntry / stripCodexColdstartTable). Symmetry:
|
|
11
|
+
* init writes → unwire removes.
|
|
12
|
+
*
|
|
13
|
+
* Scope: all four clients are swept unconditionally (init doesn't record which
|
|
14
|
+
* client was wired), so unwire cleans whatever it finds and reports "absent" for
|
|
15
|
+
* the rest. The notebook (`.coldstart/notebook/`) is KEPT by default — it's
|
|
16
|
+
* committed/shared user data; `--purge` opts in to deleting it plus its git
|
|
17
|
+
* plumbing (the `.coldstart/` gitignore line + the merge=union gitattribute).
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import { IMPORT_LINE, AGENTS_START, AGENTS_END, isColdstartHookEntry, isKbHookEntry, isCodexHookEntry, isCursorHookEntry, stripCodexColdstartTable, } from './init.js';
|
|
22
|
+
const DIVIDER = '─'.repeat(60);
|
|
23
|
+
function out(msg) {
|
|
24
|
+
process.stderr.write(msg + '\n');
|
|
25
|
+
}
|
|
26
|
+
function label(res) {
|
|
27
|
+
if (typeof res === 'object')
|
|
28
|
+
return `NOT changed: ${res.error}`;
|
|
29
|
+
return res;
|
|
30
|
+
}
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Small filesystem helpers
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/** Delete a file if it exists. */
|
|
35
|
+
function removeFile(filePath) {
|
|
36
|
+
if (!fs.existsSync(filePath))
|
|
37
|
+
return 'absent';
|
|
38
|
+
fs.unlinkSync(filePath);
|
|
39
|
+
return 'removed';
|
|
40
|
+
}
|
|
41
|
+
/** Best-effort remove a directory only if it is now empty. Walks up the given
|
|
42
|
+
* chain (child → parent) so `.cursor/rules` then `.cursor` both go if empty. */
|
|
43
|
+
function rmEmptyDirs(...dirs) {
|
|
44
|
+
for (const dir of dirs) {
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0)
|
|
47
|
+
fs.rmdirSync(dir);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* best-effort — a non-empty or busy dir just stays */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Read a JSON config file. Distinguishes missing (existed:false) from invalid
|
|
55
|
+
* (error set) so we can leave a user-owned malformed file untouched. */
|
|
56
|
+
function readJson(filePath) {
|
|
57
|
+
if (!fs.existsSync(filePath))
|
|
58
|
+
return { config: {}, existed: false };
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
61
|
+
if (parsed && typeof parsed === 'object')
|
|
62
|
+
return { config: parsed, existed: true };
|
|
63
|
+
return { config: {}, existed: true };
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { config: {}, existed: true, error: `${filePath} is not valid JSON — left untouched` };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Strip coldstart-owned entries from a hooks-map config (Claude settings.json,
|
|
71
|
+
* Codex/Cursor hooks.json). For each listed event key, filter out entries `isOurs`
|
|
72
|
+
* matches; drop keys that become empty. Prune the hooks map, then any leftover
|
|
73
|
+
* keys named in `ownedTopKeys` (e.g. Cursor's `version`, which init sets) once the
|
|
74
|
+
* map is empty; delete the file if the whole config is then empty.
|
|
75
|
+
*
|
|
76
|
+
* Returns 'stripped' if we removed anything, 'absent' if there was nothing of
|
|
77
|
+
* ours, or an error for invalid JSON.
|
|
78
|
+
*/
|
|
79
|
+
function stripHooksConfig(filePath, hooksKey, events, isOurs, ownedTopKeys = []) {
|
|
80
|
+
const { config, existed, error } = readJson(filePath);
|
|
81
|
+
if (!existed)
|
|
82
|
+
return 'absent';
|
|
83
|
+
if (error)
|
|
84
|
+
return { error };
|
|
85
|
+
const hooksCfg = config[hooksKey] && typeof config[hooksKey] === 'object'
|
|
86
|
+
? config[hooksKey]
|
|
87
|
+
: null;
|
|
88
|
+
if (!hooksCfg)
|
|
89
|
+
return 'absent';
|
|
90
|
+
let changed = false;
|
|
91
|
+
for (const ev of events) {
|
|
92
|
+
const arr = hooksCfg[ev];
|
|
93
|
+
if (!Array.isArray(arr))
|
|
94
|
+
continue;
|
|
95
|
+
const kept = arr.filter((e) => !isOurs(e));
|
|
96
|
+
if (kept.length === arr.length)
|
|
97
|
+
continue;
|
|
98
|
+
changed = true;
|
|
99
|
+
if (kept.length === 0)
|
|
100
|
+
delete hooksCfg[ev];
|
|
101
|
+
else
|
|
102
|
+
hooksCfg[ev] = kept;
|
|
103
|
+
}
|
|
104
|
+
if (!changed)
|
|
105
|
+
return 'absent';
|
|
106
|
+
// Prune the now-empty hooks map, then any coldstart-owned top-level keys that
|
|
107
|
+
// only exist because we wrote the hooks (Cursor's `version`).
|
|
108
|
+
if (Object.keys(hooksCfg).length === 0) {
|
|
109
|
+
delete config[hooksKey];
|
|
110
|
+
for (const k of ownedTopKeys)
|
|
111
|
+
delete config[k];
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
config[hooksKey] = hooksCfg;
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(config).length === 0) {
|
|
117
|
+
fs.unlinkSync(filePath);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
121
|
+
}
|
|
122
|
+
return 'stripped';
|
|
123
|
+
}
|
|
124
|
+
/** Remove `mcpServers.coldstart` from a JSON MCP config, pruning empties and
|
|
125
|
+
* deleting the file if it becomes empty. */
|
|
126
|
+
function unwireJsonMcp(cwd, relFile) {
|
|
127
|
+
const filePath = path.join(cwd, relFile);
|
|
128
|
+
const { config, existed, error } = readJson(filePath);
|
|
129
|
+
if (!existed)
|
|
130
|
+
return 'absent';
|
|
131
|
+
if (error)
|
|
132
|
+
return { error };
|
|
133
|
+
const servers = config.mcpServers && typeof config.mcpServers === 'object'
|
|
134
|
+
? config.mcpServers
|
|
135
|
+
: null;
|
|
136
|
+
if (!servers || !('coldstart' in servers))
|
|
137
|
+
return 'absent';
|
|
138
|
+
delete servers.coldstart;
|
|
139
|
+
if (Object.keys(servers).length === 0)
|
|
140
|
+
delete config.mcpServers;
|
|
141
|
+
else
|
|
142
|
+
config.mcpServers = servers;
|
|
143
|
+
if (Object.keys(config).length === 0)
|
|
144
|
+
fs.unlinkSync(filePath);
|
|
145
|
+
else
|
|
146
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
147
|
+
return 'stripped';
|
|
148
|
+
}
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Per-artifact reversers
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
/** Remove the `@coldstart.md` import line from CLAUDE.md (leave the rest). */
|
|
153
|
+
function unwireClaudeImport(cwd) {
|
|
154
|
+
const filePath = path.join(cwd, 'CLAUDE.md');
|
|
155
|
+
if (!fs.existsSync(filePath))
|
|
156
|
+
return 'absent';
|
|
157
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
158
|
+
const lines = text.split('\n');
|
|
159
|
+
const kept = lines.filter((l) => l.trim() !== IMPORT_LINE);
|
|
160
|
+
if (kept.length === lines.length)
|
|
161
|
+
return 'absent';
|
|
162
|
+
// Collapse a blank-line run left behind, then trim trailing blanks.
|
|
163
|
+
const collapsed = [];
|
|
164
|
+
for (const l of kept) {
|
|
165
|
+
if (l.trim() === '' && collapsed.length > 0 && collapsed[collapsed.length - 1].trim() === '')
|
|
166
|
+
continue;
|
|
167
|
+
collapsed.push(l);
|
|
168
|
+
}
|
|
169
|
+
while (collapsed.length && collapsed[collapsed.length - 1].trim() === '')
|
|
170
|
+
collapsed.pop();
|
|
171
|
+
const next = collapsed.join('\n');
|
|
172
|
+
// If only init's own scaffold heading is left, init created the file — remove it.
|
|
173
|
+
if (next.trim() === '' || next.trim() === '# Project guidance')
|
|
174
|
+
fs.unlinkSync(filePath);
|
|
175
|
+
else
|
|
176
|
+
fs.writeFileSync(filePath, next + '\n');
|
|
177
|
+
return 'stripped';
|
|
178
|
+
}
|
|
179
|
+
/** Excise the `<!-- coldstart:start -->…<!-- coldstart:end -->` block from
|
|
180
|
+
* AGENTS.md, preserving the rest of the user's file. */
|
|
181
|
+
function unwireCodexAgents(cwd) {
|
|
182
|
+
const filePath = path.join(cwd, 'AGENTS.md');
|
|
183
|
+
if (!fs.existsSync(filePath))
|
|
184
|
+
return 'absent';
|
|
185
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
186
|
+
const start = text.indexOf(AGENTS_START);
|
|
187
|
+
if (start === -1)
|
|
188
|
+
return 'absent';
|
|
189
|
+
const end = text.indexOf(AGENTS_END, start);
|
|
190
|
+
if (end === -1)
|
|
191
|
+
return 'absent';
|
|
192
|
+
const before = text.slice(0, start).replace(/\n+$/, '\n');
|
|
193
|
+
const after = text.slice(end + AGENTS_END.length).replace(/^\n+/, '');
|
|
194
|
+
let next = before + (after ? '\n' + after : '');
|
|
195
|
+
if (next.trim() === '# AGENTS.md')
|
|
196
|
+
next = ''; // only our scaffold remained
|
|
197
|
+
if (next.trim() === '')
|
|
198
|
+
fs.unlinkSync(filePath);
|
|
199
|
+
else
|
|
200
|
+
fs.writeFileSync(filePath, next.endsWith('\n') ? next : next + '\n');
|
|
201
|
+
return 'stripped';
|
|
202
|
+
}
|
|
203
|
+
/** Strip `[mcp_servers.coldstart]` from .codex/config.toml. */
|
|
204
|
+
function unwireCodexMcp(cwd) {
|
|
205
|
+
const filePath = path.join(cwd, '.codex', 'config.toml');
|
|
206
|
+
if (!fs.existsSync(filePath))
|
|
207
|
+
return 'absent';
|
|
208
|
+
const before = fs.readFileSync(filePath, 'utf8');
|
|
209
|
+
const after = stripCodexColdstartTable(before).replace(/\n{3,}/g, '\n\n').replace(/^\n+/, '');
|
|
210
|
+
if (after.replace(/\s+/g, '') === before.replace(/\s+/g, ''))
|
|
211
|
+
return 'absent';
|
|
212
|
+
const trimmed = after.replace(/\n+$/, '');
|
|
213
|
+
if (trimmed === '')
|
|
214
|
+
fs.unlinkSync(filePath);
|
|
215
|
+
else
|
|
216
|
+
fs.writeFileSync(filePath, trimmed + '\n');
|
|
217
|
+
return 'stripped';
|
|
218
|
+
}
|
|
219
|
+
/** Drop the `.coldstart/` line from .gitignore (added by init's private default). */
|
|
220
|
+
function removeNotebookGitignore(cwd) {
|
|
221
|
+
const giPath = path.join(cwd, '.gitignore');
|
|
222
|
+
if (!fs.existsSync(giPath))
|
|
223
|
+
return 'absent';
|
|
224
|
+
const lines = fs.readFileSync(giPath, 'utf8').split('\n');
|
|
225
|
+
const kept = lines.filter((l) => { const t = l.trim(); return t !== '.coldstart/' && t !== '.coldstart'; });
|
|
226
|
+
if (kept.length === lines.length)
|
|
227
|
+
return 'absent';
|
|
228
|
+
fs.writeFileSync(giPath, kept.join('\n'));
|
|
229
|
+
return 'stripped';
|
|
230
|
+
}
|
|
231
|
+
/** Drop the notebook merge=union line from .gitattributes. */
|
|
232
|
+
function removeNotebookGitattributes(cwd) {
|
|
233
|
+
const gaPath = path.join(cwd, '.gitattributes');
|
|
234
|
+
if (!fs.existsSync(gaPath))
|
|
235
|
+
return 'absent';
|
|
236
|
+
const line = '.coldstart/notebook/.raw/*.jsonl merge=union';
|
|
237
|
+
const lines = fs.readFileSync(gaPath, 'utf8').split('\n');
|
|
238
|
+
const kept = lines.filter((l) => l.trim() !== line);
|
|
239
|
+
if (kept.length === lines.length)
|
|
240
|
+
return 'absent';
|
|
241
|
+
const next = kept.join('\n').replace(/\n+$/, '');
|
|
242
|
+
if (next.trim() === '')
|
|
243
|
+
fs.unlinkSync(gaPath);
|
|
244
|
+
else
|
|
245
|
+
fs.writeFileSync(gaPath, next + '\n');
|
|
246
|
+
return 'stripped';
|
|
247
|
+
}
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Entry point
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
const CLAUDE_HOOK_EVENTS = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'Stop', 'SubagentStop'];
|
|
252
|
+
const CURSOR_HOOK_EVENTS = ['preToolUse', 'postToolUse', 'beforeSubmitPrompt', 'stop', 'subagentStop'];
|
|
253
|
+
export async function runUnwire() {
|
|
254
|
+
const cwd = process.cwd();
|
|
255
|
+
const argv = process.argv.slice(3); // tokens after `unwire`
|
|
256
|
+
const purge = argv.includes('--purge');
|
|
257
|
+
out('');
|
|
258
|
+
out('coldstart unwire');
|
|
259
|
+
out(DIVIDER);
|
|
260
|
+
out(` Removing coldstart-owned wiring from ${cwd}`);
|
|
261
|
+
out('');
|
|
262
|
+
// Claude Code
|
|
263
|
+
out(' Claude Code');
|
|
264
|
+
out(` coldstart.md — ${label(removeFile(path.join(cwd, 'coldstart.md')))}`);
|
|
265
|
+
out(` CLAUDE.md — ${label(unwireClaudeImport(cwd))} @coldstart.md import`);
|
|
266
|
+
out(` settings.json — ${label(stripHooksConfig(path.join(cwd, '.claude', 'settings.json'), 'hooks', CLAUDE_HOOK_EVENTS, (e) => isColdstartHookEntry(e) || isKbHookEntry(e)))} find/gs + notebook hooks`);
|
|
267
|
+
out(` .mcp.json — ${label(unwireJsonMcp(cwd, '.mcp.json'))} MCP server`);
|
|
268
|
+
// Codex
|
|
269
|
+
out(' Codex');
|
|
270
|
+
out(` AGENTS.md — ${label(unwireCodexAgents(cwd))} coldstart block`);
|
|
271
|
+
out(` hooks.json — ${label(stripHooksConfig(path.join(cwd, '.codex', 'hooks.json'), 'hooks', CLAUDE_HOOK_EVENTS, isCodexHookEntry))} Codex hooks`);
|
|
272
|
+
out(` config.toml — ${label(unwireCodexMcp(cwd))} [mcp_servers.coldstart]`);
|
|
273
|
+
// Cursor
|
|
274
|
+
out(' Cursor');
|
|
275
|
+
out(` coldstart.mdc — ${label(removeFile(path.join(cwd, '.cursor', 'rules', 'coldstart.mdc')))}`);
|
|
276
|
+
out(` hooks.json — ${label(stripHooksConfig(path.join(cwd, '.cursor', 'hooks.json'), 'hooks', CURSOR_HOOK_EVENTS, isCursorHookEntry, ['version']))} Cursor hooks`);
|
|
277
|
+
out(` mcp.json — ${label(unwireJsonMcp(cwd, path.join('.cursor', 'mcp.json')))} MCP server`);
|
|
278
|
+
// Notebook
|
|
279
|
+
out(' Notebook');
|
|
280
|
+
if (purge) {
|
|
281
|
+
const nb = path.join(cwd, '.coldstart', 'notebook');
|
|
282
|
+
let nbRes = 'absent';
|
|
283
|
+
if (fs.existsSync(nb)) {
|
|
284
|
+
fs.rmSync(nb, { recursive: true, force: true });
|
|
285
|
+
nbRes = 'removed';
|
|
286
|
+
}
|
|
287
|
+
out(` .coldstart/notebook — ${label(nbRes)} (--purge: notes deleted)`);
|
|
288
|
+
out(` .gitignore — ${label(removeNotebookGitignore(cwd))} .coldstart/ line`);
|
|
289
|
+
out(` .gitattributes — ${label(removeNotebookGitattributes(cwd))} merge=union line`);
|
|
290
|
+
rmEmptyDirs(path.join(cwd, '.coldstart'));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
out(' .coldstart/notebook — KEPT (committed/shared notes; use --purge to delete)');
|
|
294
|
+
}
|
|
295
|
+
// Tidy up now-empty coldstart dirs (best-effort).
|
|
296
|
+
rmEmptyDirs(path.join(cwd, '.cursor', 'rules'), path.join(cwd, '.cursor'), path.join(cwd, '.claude'), path.join(cwd, '.codex'));
|
|
297
|
+
out('');
|
|
298
|
+
out(DIVIDER);
|
|
299
|
+
out('Unwired. Re-add anytime with: coldstart init');
|
|
300
|
+
if (!purge)
|
|
301
|
+
out('The notebook was kept — `coldstart unwire --purge` also deletes it.');
|
|
302
|
+
out('');
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=unwire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwire.js","sourceRoot":"","sources":["../src/unwire.ts"],"names":[],"mappings":"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"}
|
|
@@ -295,17 +295,20 @@ process.on("unhandledRejection", (e) => { log(`unhandled ${e?.stack || e}`); pro
|
|
|
295
295
|
if (input.stop_hook_active === true) { log("SKIP stop_hook_active"); process.exit(0); }
|
|
296
296
|
|
|
297
297
|
// Codex session_id identifies a thread; turn_id identifies one user turn.
|
|
298
|
-
// Capture
|
|
298
|
+
// Capture across the whole thread, incrementally — a resumed thread keeps
|
|
299
|
+
// contributing new knowledge.
|
|
299
300
|
const sid = String(input.session_id || "").replace(/[^A-Za-z0-9_-]/g, "");
|
|
300
301
|
if (!sid) { log("SKIP no-session-id"); process.exit(0); }
|
|
301
|
-
const tid = String(input.turn_id || sid).replace(/[^A-Za-z0-9_-]/g, "") || sid;
|
|
302
302
|
|
|
303
|
-
// Guard 2:
|
|
304
|
-
//
|
|
303
|
+
// Guard 2: capture across the session, but only files not yet offered. The
|
|
304
|
+
// old per-turn marker re-offered EVERY touched file on every turn; instead we
|
|
305
|
+
// remember which files were already offered and elicit only the new ones (the
|
|
306
|
+
// delta is computed below). Subagents share the parent session_id, so the
|
|
307
|
+
// record is scoped by agent too.
|
|
305
308
|
const aid = String(input.agent_id || "main").replace(/[^A-Za-z0-9_-]/g, "") || "main";
|
|
306
|
-
const marker = join(tmpdir(), `coldstart-codex-kb-${
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
+
const marker = join(tmpdir(), `coldstart-codex-kb-${sid}-${aid}.json`);
|
|
310
|
+
let offered = new Set();
|
|
311
|
+
try { offered = new Set(JSON.parse(readFileSync(marker, "utf8")).files || []); } catch { /* first Stop of this session */ }
|
|
309
312
|
|
|
310
313
|
// Codex supplies the child's own rollout as agent_transcript_path on
|
|
311
314
|
// SubagentStop. Its transcript_path is the parent's rollout at that event.
|
|
@@ -321,18 +324,25 @@ process.on("unhandledRejection", (e) => { log(`unhandled ${e?.stack || e}`); pro
|
|
|
321
324
|
// Ephemeral Codex runs deliberately expose no transcript path. Fail open:
|
|
322
325
|
// navigation/recall still work, but capture has no trustworthy evidence.
|
|
323
326
|
const files = transcriptPath ? touchedFiles(transcriptPath, root) : [];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
// Delta: only files not already offered on an earlier Stop this session.
|
|
328
|
+
const newFiles = files.filter((f) => !offered.has(f));
|
|
329
|
+
|
|
330
|
+
// FAST-EXIT when there is nothing NEW to capture — either no repo file was
|
|
331
|
+
// touched at all (pure orchestration / Q&A) or every touched file was already
|
|
332
|
+
// offered on a previous Stop. No record is written on this path, so an early
|
|
333
|
+
// no-op Stop can never burn the session's capture.
|
|
334
|
+
if (!newFiles.length) {
|
|
335
|
+
log(`FAST-EXIT no-new-files session=${sid} agent=${aid} touched=${files.length} event=${input.hook_event_name || "?"}`);
|
|
330
336
|
process.exit(0);
|
|
331
337
|
}
|
|
332
338
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
// Record everything now offered BEFORE returning the block, so the post-write
|
|
340
|
+
// re-Stop and every later Stop skip these files.
|
|
341
|
+
try { writeFileSync(marker, JSON.stringify({ files: [...offered, ...newFiles], ts: Date.now() })); } catch { /* best effort */ }
|
|
342
|
+
|
|
343
|
+
const prompt = buildCapturePrompt(root, filesBlock(root, newFiles), sid, input.hook_event_name === "SubagentStop");
|
|
344
|
+
logCaptureEvent(root, { event: "elicit", session: sid, agent: aid, touched: files.length, new: newFiles.length, hook: input.hook_event_name });
|
|
345
|
+
log(`ELICIT session=${sid} agent=${aid} new=${newFiles.length} touched=${files.length} promptBytes=${prompt.length} event=${input.hook_event_name || "?"}`);
|
|
336
346
|
process.stdout.write(JSON.stringify({ decision: "block", reason: prompt }));
|
|
337
347
|
} catch (e) {
|
|
338
348
|
log(`handler ${e?.stack || e}`); // fail-open: no stdout → stop allowed
|
|
@@ -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/hooks/kb-elicit.mjs
CHANGED
|
@@ -181,9 +181,17 @@ have firsthand knowledge of — never enumerate the rest.
|
|
|
181
181
|
The best facet/summary says: what it does that the name doesn't tell you, what to watch out \
|
|
182
182
|
for when changing it, and which tests or checks matter.
|
|
183
183
|
|
|
184
|
-
2. FLOW notes —
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
2. FLOW notes — RARE. Not "my task touched several files": that is most tasks, and its \
|
|
185
|
+
knowledge belongs in the file notes. A flow exists only when the RELATIONSHIP between files is \
|
|
186
|
+
itself the knowledge — such as an enforcement gap, an asymmetry or inconsistency between files, \
|
|
187
|
+
a dead/unreachable path, or an evolution that explains a surprising present shape. The gate: \
|
|
188
|
+
name, in one sentence, the fact a reader of ALL the file notes would still be missing; if you \
|
|
189
|
+
cannot, there is no flow — put the facts in file notes. That sentence is the FIRST sentence of \
|
|
190
|
+
the flow's summary. Never a flow: a feature's parts-list ("the model, the view, the route, the \
|
|
191
|
+
config, the tests" — an index, not knowledge); a mechanism living in one file (that file's note); a call-chain or \
|
|
192
|
+
version history with no gotcha at the end. Steps are the minimal chain carrying the missing \
|
|
193
|
+
fact — not a tour of every file the task visited. A step never restates what a file note \
|
|
194
|
+
already says — the detail lives in the file's facet; the flow links to it.
|
|
187
195
|
|
|
188
196
|
3. LESSON notes — rare. Only one thing qualifies:
|
|
189
197
|
- a confirmed ABSENCE ("there is no X in this repo"), with the search terms that proved it.
|
|
@@ -202,6 +210,10 @@ RULES:
|
|
|
202
210
|
- Codebase knowledge only — never the interaction, the user, or your own process.
|
|
203
211
|
- Firsthand only: if it arrived secondhand (e.g. a subagent's report) and you did not verify it \
|
|
204
212
|
yourself, do not store it.
|
|
213
|
+
- Say only what you verified in THIS file, this session: never assert a method, branch, or \
|
|
214
|
+
config key you did not confirm is present, and never describe what a DIFFERENT file does (that \
|
|
215
|
+
is a flow, or that file's own note). A confident whole-file claim from a partial read is the \
|
|
216
|
+
single most common bad note.
|
|
205
217
|
- If a future agent would not act differently for knowing it, do not store it.
|
|
206
218
|
- SEARCH BEFORE YOU WRITE a flow or lesson: run \`node ${CLI} kb search "<your task words>" \
|
|
207
219
|
--root ${root}\` once. If an existing flow already tells this mechanism's story, UPDATE it \
|
|
@@ -300,12 +312,16 @@ process.on("unhandledRejection", (e) => { log(`unhandled ${e?.stack || e}`); pro
|
|
|
300
312
|
const sid = String(input.session_id || "").replace(/[^A-Za-z0-9_-]/g, "");
|
|
301
313
|
if (!sid) { log("SKIP no-session-id"); process.exit(0); }
|
|
302
314
|
|
|
303
|
-
// Guard 2:
|
|
304
|
-
//
|
|
315
|
+
// Guard 2: capture across a long session, but only files not yet offered.
|
|
316
|
+
// A boolean once-per-(session,agent) marker silently dropped ALL knowledge
|
|
317
|
+
// after the first Stop; instead we remember which files were already offered
|
|
318
|
+
// for capture and elicit only the new ones (the delta is computed below,
|
|
319
|
+
// once the SubagentStop transcript is resolved). Subagents share the parent
|
|
320
|
+
// session_id, so the record is scoped by agent too.
|
|
305
321
|
const aid = String(input.agent_id || "main").replace(/[^A-Za-z0-9_-]/g, "") || "main";
|
|
306
|
-
const marker = join(tmpdir(), `coldstart-kb-${sid}-${aid}.
|
|
307
|
-
|
|
308
|
-
try {
|
|
322
|
+
const marker = join(tmpdir(), `coldstart-kb-${sid}-${aid}.json`);
|
|
323
|
+
let offered = new Set();
|
|
324
|
+
try { offered = new Set(JSON.parse(readFileSync(marker, "utf8")).files || []); } catch { /* first Stop of this session */ }
|
|
309
325
|
|
|
310
326
|
// On SubagentStop, transcript_path is the PARENT's transcript (confirmed:
|
|
311
327
|
// claude-code#11396) — scanning it would elicit the sub off the parent's
|
|
@@ -326,18 +342,27 @@ process.on("unhandledRejection", (e) => { log(`unhandled ${e?.stack || e}`); pro
|
|
|
326
342
|
transcriptPath = own;
|
|
327
343
|
}
|
|
328
344
|
const files = transcriptPath ? touchedFiles(transcriptPath, root) : [];
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
345
|
+
// Delta: only files not already offered on an earlier Stop this session.
|
|
346
|
+
const newFiles = files.filter((f) => !offered.has(f));
|
|
347
|
+
|
|
348
|
+
// FAST-EXIT when there is nothing NEW to capture — either no repo file was
|
|
349
|
+
// touched at all (pure orchestration / Q&A) or every touched file was already
|
|
350
|
+
// offered on a previous Stop. No record is written on this path, so an early
|
|
351
|
+
// no-op Stop can never burn the session's capture.
|
|
352
|
+
if (!newFiles.length) {
|
|
353
|
+
log(`FAST-EXIT no-new-files session=${sid} agent=${aid} touched=${files.length} event=${input.hook_event_name || "?"}`);
|
|
335
354
|
process.exit(0);
|
|
336
355
|
}
|
|
337
356
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
357
|
+
// Record everything now offered BEFORE returning the block, so the post-write
|
|
358
|
+
// re-Stop and every later Stop skip these files. stop_hook_active (Guard 1)
|
|
359
|
+
// still short-circuits the immediate re-Stop; this record survives across the
|
|
360
|
+
// natural turns of a long session.
|
|
361
|
+
try { writeFileSync(marker, JSON.stringify({ files: [...offered, ...newFiles], ts: Date.now() })); } catch { /* best effort */ }
|
|
362
|
+
|
|
363
|
+
const prompt = buildCapturePrompt(root, filesBlock(root, newFiles), sid, input.hook_event_name === "SubagentStop");
|
|
364
|
+
logCaptureEvent(root, { event: "elicit", session: sid, agent: aid, touched: files.length, new: newFiles.length, hook: input.hook_event_name });
|
|
365
|
+
log(`ELICIT session=${sid} agent=${aid} new=${newFiles.length} touched=${files.length} promptBytes=${prompt.length} event=${input.hook_event_name || "?"}`);
|
|
341
366
|
process.stdout.write(JSON.stringify({ decision: "block", reason: prompt }));
|
|
342
367
|
} catch (e) {
|
|
343
368
|
log(`handler ${e?.stack || e}`); // fail-open: no stdout → stop allowed
|