@sdsrs/code-graph 0.116.0 → 0.117.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.
@@ -609,7 +609,36 @@ function runSessionInit({ source } = {}) {
609
609
  // 上下文感知默认:插件模式下首次 SessionStart 自动安装(创建/注入 CLAUDE.md 块 +
610
610
  // .claude/ detail 文件),并清理旧 memory-dir 制品(升级自动迁移)。shipped 漂移
611
611
  // 时刷新。三种情况发一次 stderr 提示,让用户知道发生了什么 + 如何回退。
612
- const autoAdopt = isRelic ? { attempted: false, result: null } : maybeAutoAdopt({ scriptPath: __dirname });
612
+ // Adoption is OPTIONAL; the rest of this hook is not. It touches files the
613
+ // user owns (CLAUDE.md, .claude/) which can be unreadable, a directory, or on
614
+ // a read-only mount — and a throw here used to abort every remaining step
615
+ // (map injection, recent impact, consistency check, both hook canaries) with a
616
+ // raw stack trace (audit 2026-08-16 P1-16). adopt() now returns reasons rather
617
+ // than throwing; this is the belt to that suspenders, so a future unguarded
618
+ // read inside it cannot take the session down again.
619
+ let autoAdopt = { attempted: false, result: null };
620
+ if (!isRelic) {
621
+ try {
622
+ autoAdopt = maybeAutoAdopt({ scriptPath: __dirname });
623
+ } catch (e) {
624
+ autoAdopt = { attempted: true, reason: 'threw', result: null, error: (e && e.message) || String(e) };
625
+ process.stderr.write(
626
+ `[code-graph] Skipped CLAUDE.md adoption for this project (${(e && e.code) || (e && e.message) || 'unknown error'}).\n` +
627
+ ' Everything else in this session start continues normally.\n'
628
+ );
629
+ }
630
+ }
631
+ if (autoAdopt.result && autoAdopt.result.ok === false &&
632
+ (autoAdopt.result.reason === 'claude-md-unreadable' || autoAdopt.result.reason === 'claude-md-unwritable' ||
633
+ autoAdopt.result.reason === 'detail-unwritable')) {
634
+ // A refusal is not a silent no-op: the user's steering block is NOT
635
+ // installed/refreshed, and only this line says so.
636
+ process.stderr.write(
637
+ `[code-graph] Could not install the CLAUDE.md steering block (${autoAdopt.result.reason}: ` +
638
+ `${autoAdopt.result.error || 'unknown'}). Nothing was changed.\n` +
639
+ ' Opt out permanently: CODE_GRAPH_NO_AUTO_ADOPT=1\n'
640
+ );
641
+ }
613
642
  const migrated = autoAdopt.migrated || {};
614
643
  if (migrated.memoryIndexPruned || migrated.legacyDetailRemoved) {
615
644
  process.stderr.write(
@@ -889,5 +918,18 @@ if (require.main === module) {
889
918
  source = JSON.parse(fs.readFileSync(0, 'utf8')).source;
890
919
  }
891
920
  } catch { /* no/garbled stdin → treat as unknown source */ }
892
- runSessionInit({ source });
921
+ // Hooks FAIL OPEN. This one had no wrapper at all, so anything unhandled
922
+ // anywhere in the sequence surfaced as a node stack trace plus a non-zero exit
923
+ // in the user's session start — for work that is entirely optional
924
+ // housekeeping (audit 2026-08-16 P1-16). One `[code-graph]` line, exit 0.
925
+ try {
926
+ runSessionInit({ source });
927
+ } catch (e) {
928
+ process.stderr.write(
929
+ `[code-graph] SessionStart hook error (${(e && e.code) || (e && e.name) || 'Error'}): ` +
930
+ `${(e && e.message) || String(e)}\n` +
931
+ ' The session continues; run `code-graph-mcp doctor` if this repeats.\n'
932
+ );
933
+ process.exit(0);
934
+ }
893
935
  }
@@ -16,7 +16,21 @@
16
16
  // (this plugin's own provider). Third parties should use stable ids like
17
17
  // "gsd", "claude-mem", etc.
18
18
 
19
- const { readRegistry, registerStatuslineProvider, unregisterStatuslineProvider } = require('./lifecycle');
19
+ const {
20
+ readRegistry, readRegistryForWrite, registerStatuslineProvider, unregisterStatuslineProvider,
21
+ } = require('./lifecycle');
22
+
23
+ // A registry that EXISTS but cannot be read is a refusal, not a no-op: the
24
+ // mutation functions leave it alone (they must — it holds the user's previous
25
+ // statusline and other plugins' entries), which would otherwise surface here as
26
+ // the cheerful "unchanged <id>" / "not-found <id>" at exit 0. A third-party
27
+ // installer reading that exit code would believe it is wired in.
28
+ function bailIfRegistryUnusable(action) {
29
+ const { refuse, why } = readRegistryForWrite();
30
+ if (!refuse) return;
31
+ process.stderr.write(`error: ${why} — nothing ${action}. Repair or move that file aside and retry.\n`);
32
+ process.exit(2);
33
+ }
20
34
 
21
35
  function usage(code = 1) {
22
36
  process.stderr.write(
@@ -34,12 +48,14 @@ function runRegister(id, command, needsStdin) {
34
48
  process.exit(2);
35
49
  }
36
50
  if (!id || !command) usage();
51
+ bailIfRegistryUnusable('registered');
37
52
  const changed = registerStatuslineProvider(id, command, needsStdin);
38
53
  process.stdout.write(changed ? `registered ${id}\n` : `unchanged ${id}\n`);
39
54
  }
40
55
 
41
56
  function runUnregister(id) {
42
57
  if (!id) usage();
58
+ bailIfRegistryUnusable('unregistered');
43
59
  const changed = unregisterStatuslineProvider(id);
44
60
  process.stdout.write(changed ? `unregistered ${id}\n` : `not-found ${id}\n`);
45
61
  }
@@ -89,6 +89,9 @@ function runProvider(command, needsStdin, stdin) {
89
89
 
90
90
  const out = execFileSync(argv[0], argv.slice(1), hidden({
91
91
  timeout: 3000,
92
+ // SIGKILL, not the SIGTERM default: a provider that traps SIGTERM makes
93
+ // Node's timeout unreachable and hangs every render (audit P1-17).
94
+ killSignal: 'SIGKILL',
92
95
  stdio: ['pipe', 'pipe', 'pipe'],
93
96
  input: needsStdin ? stdin : '',
94
97
  env,
@@ -187,6 +187,9 @@ try {
187
187
  // "slow health-check" into a rendered "offline"/"updating" instead of a blank.
188
188
  report = parseReport(execFileSync(bin, ['health-check', '--format', 'json'], hidden({
189
189
  timeout: 1500,
190
+ // Render hot path: a wedged binary ignoring SIGTERM must not outlive the
191
+ // budget (same reasoning as the composite's provider spawn, audit P1-17).
192
+ killSignal: 'SIGKILL',
190
193
  stdio: ['pipe', 'pipe', 'pipe'],
191
194
  // Run the binary FROM the resolved root so its own project-root resolution
192
195
  // lands on the same DB the gate above picked (a subdir cwd would otherwise
@@ -22,15 +22,20 @@ jobs:
22
22
  permissions:
23
23
  contents: write
24
24
  steps:
25
- - uses: actions/checkout@v4
25
+ # SHA-pinned, not `@v4`: a floating major tag is mutable, so anyone who can
26
+ # move it runs their code in a job that holds `contents: write` on your
27
+ # repo. These are the same commits code-graph-mcp's own workflows pin.
28
+ # Update by copying a new SHA from the action's release page, not by
29
+ # switching back to a tag.
30
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
26
31
  with:
27
32
  fetch-depth: 0
28
- - uses: actions/setup-node@v4
33
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
29
34
  with:
30
35
  node-version: '20'
31
36
  - name: Build snapshot
32
37
  run: |
33
- npx -y -p @sdsrs/code-graph@0.116.0 code-graph-mcp snapshot create --out snapshot.db
38
+ npx -y -p @sdsrs/code-graph@0.117.0 code-graph-mcp snapshot create --out snapshot.db
34
39
  zstd -9 snapshot.db -o snapshot.db.zst
35
40
  mv snapshot.db.zst "code-graph-snapshot-${GITHUB_SHA:0:7}.db.zst"
36
41
  - name: Upload to release
@@ -35,8 +35,12 @@ type: reference
35
35
  > - `module_overview include_deps=true` / `include_dead=true`
36
36
  > - `get_call_graph route_path="GET /api/x"`
37
37
  >
38
- > 旧名(`impact_analysis` 等)仍作为向后兼容 dispatcher 别名可调(raw JSON-RPC /
39
- > SDK 脚本场景),但 Claude Code 内强烈建议用上面的新 flag 形式。CLI 子命令
38
+ > `impact_analysis` **已删除**——dispatcher 里没有这个 arm,按名调用返回
39
+ > `Unknown tool`。改用 `get_ast_node include_impact=true`,或 CLI `impact`。
40
+ > 其余旧名(`read_snippet` / `dependency_graph` / `find_similar_code` /
41
+ > `find_dead_code` / `trace_http_chain` + alias `find_http_route`)仍是向后兼容
42
+ > dispatcher 别名(raw JSON-RPC / SDK 脚本场景),但 Claude Code 内一律用上面的
43
+ > 新 flag 形式。CLI 子命令
40
44
  > (`code-graph-mcp impact|similar|deps|dead-code|trace`)保持不变,给 Bash 工作流。
41
45
 
42
46
  ## 何时调用 MCP/CLI(替代多步 Grep/Read)
@@ -73,10 +77,14 @@ type: reference
73
77
  ### 旧名兼容 + CLI 速查(v0.18.4 fold 后)
74
78
 
75
79
  v0.18.4 把原"进阶 5"折叠进核心 7 的 flag。**Claude Code 内**首选上表的新 flag 形式。
76
- 旧名(`impact_analysis` / `find_similar_code` / `dependency_graph` / `find_dead_code` /
77
- `trace_http_chain` + alias `find_http_route`)作为 dispatcher 向后兼容**仍可调用**
78
- (raw JSON-RPC / MCP SDK / 既有脚本不破),但 Claude Code 的 ToolSearch 仍然不为
79
- hidden 5 加载 schema —— 实操中走新 flag。CLI 子命令保持原样:
80
+
81
+ 可按名调用的只有这 7 个:`project_map` / `semantic_code_search` / `get_call_graph` /
82
+ `get_ast_node` / `module_overview` / `ast_search` / `find_references`。
83
+ `impact_analysis` **不在其列,已从 dispatcher 删除**——按名调用返回 `Unknown tool`。
84
+ 隐藏旧名 `read_snippet` / `dependency_graph` / `find_similar_code` / `find_dead_code` /
85
+ `trace_http_chain`(+ alias `find_http_route`)仍有 dispatcher arm,raw JSON-RPC / MCP
86
+ SDK / 既有脚本不破;但它们不在 `tools/list`,Claude Code 的 ToolSearch 不为其加载
87
+ schema —— 实操中一律走新 flag。CLI 子命令保持原样:
80
88
 
81
89
  | 意图 | CLI(Bash 工作流) | 等价 MCP 新形式 |
82
90
  |------|--------------------|------------------|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdsrs/code-graph",
3
- "version": "0.116.0",
3
+ "version": "0.117.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.116.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.116.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.116.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.116.0",
42
- "@sdsrs/code-graph-win32-x64": "0.116.0"
38
+ "@sdsrs/code-graph-linux-x64": "0.117.0",
39
+ "@sdsrs/code-graph-linux-arm64": "0.117.0",
40
+ "@sdsrs/code-graph-darwin-x64": "0.117.0",
41
+ "@sdsrs/code-graph-darwin-arm64": "0.117.0",
42
+ "@sdsrs/code-graph-win32-x64": "0.117.0"
43
43
  }
44
44
  }