@sdsrs/code-graph 0.161.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.
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // FIRST statement, before the other requires — same reason as pre-edit-guide.js.
4
+ if (require.main === module) require('./hook-fail-open').installHookFailOpen('SubagentStart');
5
+
6
+ // SubagentStart hook (P1 #3a), registered by lifecycle.js with the matcher
7
+ // `Explore|Plan|general-purpose`.
8
+ //
9
+ // Explore and Plan subagents do not load CLAUDE.md, so the adopt block that
10
+ // tells the main session about the index never reaches them, and they fall
11
+ // back to multi-round grep/Read for structural questions. This hands them the
12
+ // same facts at spawn: the repo has an index, how big and how fresh, and the
13
+ // three CLI commands that answer structural questions from it.
14
+ //
15
+ // Facts only — no "you must". The hooks reference says imperative text framed
16
+ // as out-of-band instructions can trip Claude's prompt-injection defenses
17
+ // (https://code.claude.com/docs/en/hooks, "Add context for Claude").
18
+ //
19
+ // Silent when: no index up the tree, no binary (the commands would not run),
20
+ // the health check fails or reports 0 files, or CODE_GRAPH_QUIET_HOOKS=1 (the
21
+ // same switch that skips the SessionStart project-map injection).
22
+ const { execFileSync } = require('child_process');
23
+ const fs = require('fs');
24
+ const { hidden } = require('./proc-opts');
25
+
26
+ // Hard ceiling on the injected text, in characters (spec: ≤400). Every
27
+ // subagent spawn pays it in context, and a subagent's context is small.
28
+ const MAX_CONTEXT_CHARS = 400;
29
+
30
+ /**
31
+ * The text a subagent receives, or null when there is nothing true to say.
32
+ * @param {{files?: number, index_age?: string, index_version_stale?: boolean}} report
33
+ * `health-check --format json` output
34
+ * @param {number} [maxChars] ceiling; a parameter only so tests can reach the
35
+ * check — the builder's longest output (389) is below the real one
36
+ * @returns {string|null}
37
+ */
38
+ function buildSubagentContext(report, maxChars = MAX_CONTEXT_CHARS) {
39
+ if (!report || typeof report !== 'object') return null;
40
+ const files = Number(report.files);
41
+ if (!Number.isFinite(files) || files <= 0) return null;
42
+ const age = typeof report.index_age === 'string' && /^[\w .-]{1,24}$/.test(report.index_age)
43
+ ? report.index_age : null;
44
+ let text = `This repository has a code-graph AST index of ${files} files`;
45
+ text += age ? `, last updated ${age}.` : '.';
46
+ if (report.index_version_stale === true) text += ' A rebuild for a newer extractor is pending.';
47
+ text += ' Structural questions are answered from it through Bash:' +
48
+ ' `code-graph-mcp callgraph <fn>` (callers and callees),' +
49
+ ' `code-graph-mcp show <fn>` (a symbol\'s source and signature),' +
50
+ ' `code-graph-mcp overview <dir>` (symbols of a module grouped by file).';
51
+ return text.length <= maxChars ? text : null;
52
+ }
53
+
54
+ function readStdinJson() {
55
+ try {
56
+ // fd 0, not '/dev/stdin' (ENXIO on socketpair stdin) — as the other hooks.
57
+ return JSON.parse(fs.readFileSync(0, 'utf8'));
58
+ } catch { return null; }
59
+ }
60
+
61
+ function runMain() {
62
+ if (process.env.CODE_GRAPH_QUIET_HOOKS === '1') return;
63
+ const input = readStdinJson();
64
+ if (!input) return;
65
+
66
+ const { resolveProjectRoot } = require('./project-root');
67
+ const root = resolveProjectRoot(typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd());
68
+ if (root === null) return;
69
+
70
+ const binary = require('./find-binary').findBinary();
71
+ if (!binary) return;
72
+
73
+ // Spend the registered budget, not a literal (hooks.test.js "armed must spend").
74
+ const budget = require('./hook-fail-open').remainingMs(1500);
75
+ if (budget === null) return;
76
+ let report;
77
+ try {
78
+ report = JSON.parse(execFileSync(binary, ['health-check', '--format', 'json'], hidden({
79
+ cwd: root,
80
+ timeout: budget,
81
+ encoding: 'utf8',
82
+ stdio: ['ignore', 'pipe', 'ignore'],
83
+ env: { ...process.env, CODE_GRAPH_INTERNAL: '1' },
84
+ })));
85
+ } catch { return; }
86
+
87
+ const text = buildSubagentContext(report);
88
+ if (!text) return;
89
+ const { emitEventContext } = require('./hook-emit');
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
+ });
99
+ }
100
+
101
+ if (require.main === module) runMain();
102
+
103
+ module.exports = { buildSubagentContext, MAX_CONTEXT_CHARS };
@@ -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.161.0 code-graph-mcp snapshot create --out snapshot.db
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.161.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.161.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.161.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.161.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.161.0",
42
- "@sdsrs/code-graph-win32-x64": "0.161.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
  }