@sdsrs/code-graph 0.162.0 → 0.163.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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/session-edits.js +9 -2
- package/claude-plugin/scripts/stop-impact.js +73 -8
- package/claude-plugin/scripts/subagent-start.js +8 -0
- package/claude-plugin/templates/code-graph-snapshot.yml +1 -1
- package/package.json +6 -6
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
//
|
|
10
10
|
// .cg-edits-<cwdHash>-<sid>.jsonl one line per Edit call: {ts, file, symbol[, sigs]}
|
|
11
11
|
// .cg-stop-<cwdHash>-<sid>.json written by the Stop hook only:
|
|
12
|
-
// {lastStopAt, reported: ["file#symbol", …]
|
|
12
|
+
// {lastStopAt, reported: ["file#symbol", …],
|
|
13
|
+
// pending?: {at, files: […]}} — the last
|
|
14
|
+
// report's caller files, until its follow-up
|
|
13
15
|
//
|
|
14
16
|
// The edit log is APPEND-only: parallel Edit calls each run their own hook
|
|
15
17
|
// process, and a read-modify-write JSON file would drop one of two concurrent
|
|
@@ -108,10 +110,15 @@ function readStopState(root, sessionId) {
|
|
|
108
110
|
if (!p) return empty;
|
|
109
111
|
try {
|
|
110
112
|
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
111
|
-
|
|
113
|
+
const out = {
|
|
112
114
|
lastStopAt: typeof s.lastStopAt === 'number' ? s.lastStopAt : null,
|
|
113
115
|
reported: Array.isArray(s.reported) ? s.reported.filter((x) => typeof x === 'string') : [],
|
|
114
116
|
};
|
|
117
|
+
const p2 = s.pending;
|
|
118
|
+
if (p2 && typeof p2.at === 'number' && Array.isArray(p2.files)) {
|
|
119
|
+
out.pending = { at: p2.at, files: p2.files.filter((x) => typeof x === 'string') };
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
115
122
|
} catch { return empty; }
|
|
116
123
|
}
|
|
117
124
|
|
|
@@ -291,6 +291,7 @@ function computeStopReport({ edits, state, now, workText, callers, mtimeMs }) {
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
const lines = [];
|
|
294
|
+
const listed = new Set();
|
|
294
295
|
for (const c of changed.slice(0, MAX_SYMBOLS_CHECKED)) {
|
|
295
296
|
const refs = callers(c.symbol, c.file);
|
|
296
297
|
if (!refs || refs.length === 0) continue;
|
|
@@ -318,6 +319,7 @@ function computeStopReport({ edits, state, now, workText, callers, mtimeMs }) {
|
|
|
318
319
|
}
|
|
319
320
|
lines.push(line);
|
|
320
321
|
next.reported.push(c.key);
|
|
322
|
+
for (const r of untouched) listed.add(r.file);
|
|
321
323
|
}
|
|
322
324
|
// Changed beyond the query cap: named, not dropped in silence (review M1).
|
|
323
325
|
// Not marked reported, and a later turn does not see them as changed again
|
|
@@ -327,7 +329,54 @@ function computeStopReport({ edits, state, now, workText, callers, mtimeMs }) {
|
|
|
327
329
|
lines.push(' ' + unchecked.length + ' more changed, callers not checked: ' +
|
|
328
330
|
unchecked.map((c) => shellQuoteArg(c.symbol) + '() in ' + shellQuoteArg(c.file)).join(', '));
|
|
329
331
|
}
|
|
330
|
-
|
|
332
|
+
// `symbols` / `files`: what the report names, for its telemetry and its
|
|
333
|
+
// follow-up (D#163). Not part of `state`.
|
|
334
|
+
return { lines, state: next, symbols: next.reported.length - state.reported.length, files: [...listed].sort() };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// The follow-up remembers at most this many of a report's caller files.
|
|
338
|
+
const MAX_FOLLOWUP_FILES = 64;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* What a report leaves behind (D#163): the `stop_check` record — for every
|
|
342
|
+
* report the model is shown, the one made only of the "more changed, callers
|
|
343
|
+
* not checked" line included — and the follow-up to judge at the next Stop,
|
|
344
|
+
* only when the report named caller files. `callers` counts every caller file
|
|
345
|
+
* the report found, the ones past the text's per-symbol cap included.
|
|
346
|
+
* @param {{lines:string[], state:{lastStopAt:number}, symbols?:number, files?:string[]}} report
|
|
347
|
+
* @returns {{check: object|null, pending: {at:number, files:string[]}|null}}
|
|
348
|
+
*/
|
|
349
|
+
function reportRecords(report) {
|
|
350
|
+
if (!report.lines.length) return { check: null, pending: null };
|
|
351
|
+
const files = report.files || [];
|
|
352
|
+
return {
|
|
353
|
+
check: { hook: 'stop', action: 'stop_check', symbols: report.symbols || 0, callers: files.length },
|
|
354
|
+
pending: files.length ? { at: report.state.lastStopAt, files: files.slice(0, MAX_FOLLOWUP_FILES) } : null,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Whether a report was acted on: a caller file it listed was edited after it —
|
|
360
|
+
* an Edit logged since `pending.at`, or an mtime at or after it (Write, `sed
|
|
361
|
+
* -i`, formatters; the same reading as "touched this turn"). Judged at the
|
|
362
|
+
* next Stop, which is the end of the continuation the feedback starts.
|
|
363
|
+
* @param {{at:number, files:string[]}} pending left in the Stop state by the report
|
|
364
|
+
* @param {{ts:number, file:string}[]} edits the session's edit log
|
|
365
|
+
* @param {(file:string)=>number|null} mtimeMs
|
|
366
|
+
* @returns {{adopted:boolean, listed:number, edited:number}}
|
|
367
|
+
*/
|
|
368
|
+
function followUpOf(pending, edits, mtimeMs) {
|
|
369
|
+
const listed = new Set(pending.files);
|
|
370
|
+
const edited = new Set();
|
|
371
|
+
for (const e of edits) {
|
|
372
|
+
if (e.ts >= pending.at && listed.has(e.file)) edited.add(e.file);
|
|
373
|
+
}
|
|
374
|
+
for (const f of listed) {
|
|
375
|
+
if (edited.has(f)) continue;
|
|
376
|
+
const m = mtimeMs(f);
|
|
377
|
+
if (m !== null && m >= pending.at) edited.add(f);
|
|
378
|
+
}
|
|
379
|
+
return { adopted: edited.size > 0, listed: listed.size, edited: edited.size };
|
|
331
380
|
}
|
|
332
381
|
|
|
333
382
|
function formatStopContext(lines) {
|
|
@@ -350,17 +399,32 @@ function readStdinJson() {
|
|
|
350
399
|
function runMain() {
|
|
351
400
|
if (process.env.CODE_GRAPH_QUIET_HOOKS === '1') return;
|
|
352
401
|
const input = readStdinJson();
|
|
353
|
-
if (!input || input.
|
|
354
|
-
if (!input.session_id) return;
|
|
402
|
+
if (!input || !input.session_id) return;
|
|
355
403
|
|
|
356
404
|
const { resolveProjectRoot } = require('./project-root');
|
|
357
405
|
const root = resolveProjectRoot(typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd());
|
|
358
406
|
if (root === null) return;
|
|
359
407
|
|
|
360
408
|
const sessionEdits = require('./session-edits');
|
|
409
|
+
const { recordRecommendation } = require('./recommendation-log');
|
|
410
|
+
const mtimeMs = (file) => {
|
|
411
|
+
try { return fs.statSync(path.join(root, file)).mtimeMs; } catch { return null; }
|
|
412
|
+
};
|
|
413
|
+
const state = sessionEdits.readStopState(root, input.session_id);
|
|
414
|
+
// The previous report's follow-up (D#163), at every Stop — the one ending
|
|
415
|
+
// the continuation that report started included. It only records: nothing
|
|
416
|
+
// is emitted, so a continuation cannot loop through it.
|
|
417
|
+
if (state.pending) {
|
|
418
|
+
const f = followUpOf(state.pending, sessionEdits.readEdits(root, input.session_id), mtimeMs);
|
|
419
|
+
recordRecommendation(root, { hook: 'stop', action: 'stop_followup', ...f });
|
|
420
|
+
delete state.pending;
|
|
421
|
+
sessionEdits.writeStopState(root, input.session_id, state);
|
|
422
|
+
}
|
|
423
|
+
// A continuation caused by a Stop hook is never re-checked.
|
|
424
|
+
if (input.stop_hook_active === true) return;
|
|
425
|
+
|
|
361
426
|
const edits = sessionEdits.readEdits(root, input.session_id);
|
|
362
427
|
if (edits.length === 0) return; // no log → nothing to check, and nothing written
|
|
363
|
-
const state = sessionEdits.readStopState(root, input.session_id);
|
|
364
428
|
|
|
365
429
|
const { remainingMs } = require('./hook-fail-open');
|
|
366
430
|
const run = (cmd, args, defaultMs) => {
|
|
@@ -410,11 +474,12 @@ function runMain() {
|
|
|
410
474
|
return { file: r.file_path, name: r.name, line: findCallSiteLine(text, symbol, r.start_line) };
|
|
411
475
|
});
|
|
412
476
|
},
|
|
413
|
-
mtimeMs
|
|
414
|
-
try { return fs.statSync(path.join(root, file)).mtimeMs; } catch { return null; }
|
|
415
|
-
},
|
|
477
|
+
mtimeMs,
|
|
416
478
|
});
|
|
417
479
|
}
|
|
480
|
+
const rec = reportRecords(report);
|
|
481
|
+
if (rec.check) recordRecommendation(root, rec.check);
|
|
482
|
+
if (rec.pending) report.state.pending = rec.pending;
|
|
418
483
|
sessionEdits.writeStopState(root, input.session_id, report.state);
|
|
419
484
|
|
|
420
485
|
const text = formatStopContext(report.lines);
|
|
@@ -427,5 +492,5 @@ if (require.main === module) runMain();
|
|
|
427
492
|
|
|
428
493
|
module.exports = {
|
|
429
494
|
extractSignatures, signatureChanged, findCallSiteLine, computeStopReport, formatStopContext,
|
|
430
|
-
MAX_SYMBOLS_CHECKED, MAX_CALLERS_LISTED,
|
|
495
|
+
followUpOf, reportRecords, MAX_SYMBOLS_CHECKED, MAX_CALLERS_LISTED, MAX_FOLLOWUP_FILES,
|
|
431
496
|
};
|
|
@@ -88,6 +88,14 @@ function runMain() {
|
|
|
88
88
|
if (!text) return;
|
|
89
89
|
const { emitEventContext } = require('./hook-emit');
|
|
90
90
|
process.stdout.write(emitEventContext('SubagentStart', text) + '\n');
|
|
91
|
+
// D#163: how many subagents were handed the facts. Whether they then used
|
|
92
|
+
// the index is in their transcripts, not here (scripts/subagent_share.py).
|
|
93
|
+
// The agent type is harness input; only a plain name is kept.
|
|
94
|
+
const agent = typeof input.agent_type === 'string' && /^[\w:.-]{1,64}$/.test(input.agent_type)
|
|
95
|
+
? input.agent_type : null;
|
|
96
|
+
require('./recommendation-log').recordRecommendation(root, {
|
|
97
|
+
hook: 'subagent', action: 'subagent_context', ...(agent ? { agent } : {}),
|
|
98
|
+
});
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
if (require.main === module) runMain();
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
node-version: '20'
|
|
36
36
|
- name: Build snapshot
|
|
37
37
|
run: |
|
|
38
|
-
npx -y -p @sdsrs/code-graph@0.
|
|
38
|
+
npx -y -p @sdsrs/code-graph@0.163.0 code-graph-mcp snapshot create --out snapshot.db
|
|
39
39
|
zstd -9 snapshot.db -o snapshot.db.zst
|
|
40
40
|
mv snapshot.db.zst "code-graph-snapshot-${GITHUB_SHA:0:7}.db.zst"
|
|
41
41
|
- name: Upload to release
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.163.0",
|
|
4
4
|
"description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"node": ">=16"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
|
-
"@sdsrs/code-graph-linux-x64": "0.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.163.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.163.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.163.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.163.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.163.0"
|
|
43
43
|
}
|
|
44
44
|
}
|