codeblast 0.3.4 → 0.3.5

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="#the-three-queries"><img src="https://img.shields.io/badge/TypeScript-function--level-3178c6?style=flat-square" alt="TypeScript function-level"/></a>
12
+ <a href="#the-four-views"><img src="https://img.shields.io/badge/TypeScript-function--level-3178c6?style=flat-square" alt="TypeScript function-level"/></a>
13
13
  <a href="#the-precision-promise-bounded-and-evidence-backed"><img src="https://img.shields.io/badge/recall-28%2F28_%3D_100%25-3fb950?style=flat-square" alt="mutation-tested recall 100%"/></a>
14
14
  <a href="SKILL.md"><img src="https://img.shields.io/badge/Agent-Skill-7c3aed?style=flat-square" alt="agent skill"/></a>
15
15
  <img src="https://img.shields.io/badge/license-MIT-8b949e?style=flat-square" alt="MIT"/>
@@ -31,7 +31,7 @@ jobs:
31
31
  steps:
32
32
  - uses: actions/checkout@v4
33
33
  with: { fetch-depth: 0 }
34
- - uses: alloevil/codeblast@v0.3.3
34
+ - uses: alloevil/codeblast@v0.3.5
35
35
  ```
36
36
 
37
37
  On a structural PR, codeblast posts a bounded review decision, affected tests, `file:line` evidence,
@@ -40,14 +40,15 @@ locally in the runner; source is not uploaded to a codeblast service.
40
40
 
41
41
  ## What it is
42
42
 
43
- **codeblast parses your repository into a deterministic code graph and answers the three most expensive questions around any code change:**
43
+ **codeblast parses your repository into a deterministic code graph and exposes four views of a change:**
44
44
  > 🔗 **[Live interactive demo](https://alloevil.github.io/codeblast/)** — real architecture maps of tRPC / Tabby / sgp, with three-level drill-down
45
45
 
46
46
  | | Question | Command |
47
47
  |---|---|---|
48
- | 🎯 | **What breaks if I change this?** | `impact` — direct / transitive / affected-tests, in three tiers |
49
- | 🔍 | **What did this PR structurally change?** | `change` — symbols and dependency edges added, removed, renamed |
50
- | 🗺️ | **What does this project look like?** | `archmap` — collapsible module map + circular-dependency detection |
48
+ | 🎯 | **What breaks if I change this?** | `impact` — direct, transitive and affected-test reachability |
49
+ | 🛡️ | **Should this change receive targeted review?** | `check-change` — risk, graph health, affected tests and warnings |
50
+ | 🔍 | **What structurally changed?** | `change` — symbols and dependency edges added, removed or renamed |
51
+ | 🗺️ | **What does this project look like?** | `archmap` — module/file/symbol drill-down and cycle detection |
51
52
 
52
53
  Built for humans (CLI / interactive HTML / PR comments) and for AI agents ([SKILL.md](SKILL.md)) — one graph, two front-ends.
53
54
  <table>
@@ -72,7 +73,7 @@ Built for humans (CLI / interactive HTML / PR comments) and for AI agents ([SKIL
72
73
  ```bash
73
74
  npx codeblast demo # build a graph of the current repo, run one impact query, emit the map
74
75
  npm i -g codeblast # or install globally; needs Node ≥ 22.13 (built-in sqlite) or Bun
75
- # npm serves 0.3.3
76
+ # npm serves 0.3.5
76
77
 
77
78
  # Install as an agent skill (Claude Code, Codex, Cursor, and 14 more harnesses)
78
79
  npx skills add alloevil/codeblast
@@ -95,12 +96,12 @@ jobs:
95
96
  steps:
96
97
  - uses: actions/checkout@v4
97
98
  with: { fetch-depth: 0 }
98
- - uses: alloevil/codeblast@v0.3.3
99
+ - uses: alloevil/codeblast@v0.3.5
99
100
  ```
100
101
 
101
- The action builds the analyzer from the ref you pinned, posts one sticky comment per PR, and stays
102
- silent when the diff has no structural change. For the full input/output contract, use the
103
- [workflow template](.github/workflows-template/codeblast.yml).
102
+ The action builds the analyzer from the ref you pinned, posts one sticky comment when a PR contains a
103
+ meaningful structural or tested behavior signal, and stays silent for docs-only, test-only, or
104
+ auxiliary-only noise. For the full input/output contract, use the [workflow template](.github/workflows-template/codeblast.yml).
104
105
 
105
106
 
106
107
  ## Why not yet another LLM diagram tool
@@ -113,10 +114,10 @@ codeblast: code → deterministic tsc/AST parse → graph → project graph
113
114
  **Every node and every static-analysis edge carries `file:line` evidence** you can open and verify (`co_change` edges put a co-commit count in that field instead of a source line; file-level nodes carry line 1).
114
115
  The LLM does exactly one job in the pipeline: giving modules human-readable names — node membership and edges always come from static analysis.
115
116
 
116
- ## The three queries
117
+ ## The four views
117
118
 
118
119
  <p align="center">
119
- <img src="assets/readme/three-queries.svg" width="100%" alt="The three codeblast queries — impact: check the blast radius first; change: structural diff between two refs; archmap: module, file and symbol drill-down"/>
120
+ <img src="assets/readme/three-queries.svg" width="100%" alt="codeblast views — impact, change and architecture; check-change adds the machine-readable pre-merge safety decision"/>
120
121
  </p>
121
122
 
122
123
  ```bash
@@ -130,11 +131,15 @@ codeblast impact graph.db "createOrder" --json
130
131
  # → two channels: call-graph reachable (precision ~0.70, read first)
131
132
  # + import reachable (conservative supplement, don't skip)
132
133
 
133
- # ② Change Map — structural diff between two refs
134
+ # ② Safety — one machine-readable pre-merge decision
135
+ codeblast check-change <repo> <base> <head> --json
136
+ # → decision / risk / graph_health / affected_test_files / warnings
137
+
138
+ # ③ Change Map — structural diff between two refs
134
139
  codeblast change <repo> main~5 main --json
135
140
  # → unexpected edges_added = a signal the change is out of scope
136
141
 
137
- # ③ Architecture Map — interactive HTML: module → file → symbol drill-down,
142
+ # ④ Architecture Map — interactive HTML: module → file → symbol drill-down,
138
143
  # symbols link to source lines
139
144
  codeblast archmap graph.db --out arch.html --repo-url <github-url> \
140
145
  --site-url https://example.github.io/repo --og-image <share-image-url>
@@ -159,12 +164,11 @@ The self-pilot record is [`eval/pilot-2026-09-24.json`](eval/pilot-2026-09-24.js
159
164
  package indexed this repository with 0 extraction failures and returned separate review-first and test
160
165
  guidance. It is evidence that the workflow runs, not a claim of universal accuracy.
161
166
 
162
- Copy [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) into your repo (it runs `npx codeblast pr-comment`, no other setup):
163
- every PR gets an automatic comment with structural changes + blast radius + new symbols with no test coverage; **PRs with no structural change get zero comments**.
164
- Replayed against 50 real commits: 42 correctly stayed silent. Comment usefulness is the honest weak spot —
165
- four review rounds — rounds 1–3 independent blind review, round 4 by the current model — scored 25% / 75% / 57% / 20% useful, against 7/8 = 87.5% when the
166
- authoring agent rated its own comments; both numbers and the fixes that followed each round are logged in
167
- [intent.md](intent.md).
167
+ Copy [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) into your repo.
168
+ PR comments contain structural changes, tested behavior signals, affected tests and evidence links;
169
+ docs-only, test-only and auxiliary-only changes stay silent. The deterministic offline replay matrix
170
+ guards these routing rules. Earlier historical replays and their human-review limitations remain logged
171
+ in [intent.md](intent.md).
168
172
  ## Evidence you can rerun
169
173
 
170
174
  The headline promise is bounded: TypeScript, within the statically analyzable scope, and measured by
@@ -180,6 +184,28 @@ and every published figure has a machine-readable receipt in [`docs/claims.json`
180
184
  The pilot is not a benchmark and does not establish a universal accuracy rate. It is a reproducible
181
185
  smoke run of the published package against this repository.
182
186
 
187
+ ### Current automated evidence
188
+
189
+ The current main branch is guarded by one command:
190
+
191
+ ```bash
192
+ bun run evolution-check
193
+ ```
194
+
195
+ It runs build, tests, typecheck, deterministic replay, full/incremental graph equivalence and the
196
+ published-claims gate. Current committed evidence:
197
+
198
+ | Evidence | Current result |
199
+ |---|---:|
200
+ | Offline safety replay | 12/12, 0 incorrect silence, 0 invalid evidence, 0 nondeterministic output |
201
+ | Full vs incremental graph | nodes, edges and blind spots equal; deleted/renamed rows removed |
202
+ | Star-project graph completeness | n8n, Excalidraw and OpenCode: 0 extraction failures on pinned commits |
203
+ | Pinned mutation recall | tRPC 28/28; graphql-tools 10/10 |
204
+ | Modern compatibility radar | tRPC bounded sample 3/3; informational, not a release gate |
205
+
206
+ Literal `import.meta.glob("./fixtures/**/*.tsx")` patterns are represented as conservative imports.
207
+ Non-literal loaders, subprocess boundaries and unresolved runtime dispatch remain explicit blind spots.
208
+
183
209
  ### A reviewer's decision, not a diagram
184
210
 
185
211
  For agents and CI, `check-change --json` returns a routing decision plus evidence:
package/README.zh-CN.md CHANGED
@@ -1,4 +1,4 @@
1
- **codeblast** 是一个确定性代码图谱 CLI,面向 TypeScript / Python 仓库,让开发者和 AI agent 在合并之前就知道改动会炸到哪里。
1
+ **codeblast** 是面向 TypeScript monorepo 和 AI coding agent 的合并前变更安全检查器:告诉你该看哪里、该跑哪些测试,以及图谱是否完整。
2
2
 
3
3
  <p align="center">
4
4
  <img src="assets/readme/hero.svg" width="100%" alt="codeblast — deterministic code graph: know what breaks before you merge"/>
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="#三个查询"><img src="https://img.shields.io/badge/TypeScript-函数级-3178c6?style=flat-square" alt="TypeScript function-level"/></a>
12
+ <a href="#四个视图"><img src="https://img.shields.io/badge/TypeScript-函数级-3178c6?style=flat-square" alt="TypeScript function-level"/></a>
13
13
  <a href="#精度承诺有边界有证据"><img src="https://img.shields.io/badge/recall-28%2F28_%3D_100%25-3fb950?style=flat-square" alt="mutation-tested recall 100%"/></a>
14
14
  <a href="SKILL.md"><img src="https://img.shields.io/badge/Agent-Skill-7c3aed?style=flat-square" alt="agent skill"/></a>
15
15
  <img src="https://img.shields.io/badge/license-MIT-8b949e?style=flat-square" alt="MIT"/>
@@ -17,14 +17,15 @@
17
17
 
18
18
  ## 是什么
19
19
 
20
- **codeblast 把仓库解析成一份确定性代码图谱,回答改代码前后最贵的三个问题:**
21
- > 🔗 **[在线交互演示](https://alloevil.github.io/codeblast/)** — tRPC / Tabby / sgp 的实时架构图,点开即可三层下钻
20
+ **codeblast 把仓库解析成确定性代码图谱,为一次改动提供四个视图:**
21
+ > 🔗 **[在线交互演示](https://alloevil.github.io/codeblast/)** — tRPC / Tabby / sgp 的真实架构图,可下钻到源码行
22
22
 
23
23
  | | 问题 | 命令 |
24
24
  |---|---|---|
25
- | 🎯 | **改这个会炸哪里?** | `impact` — 直接/传递/受影响测试三级清单 |
26
- | 🔍 | **这个 PR 在结构上改了什么?** | `change` — 符号与依赖边的增删/重命名 |
27
- | 🗺️ | **这个项目长什么样?** | `archmap` — 模块折叠图 + 循环依赖检测 |
25
+ | 🎯 | **改这个会影响哪里?** | `impact` — 直接、传递与受影响测试 |
26
+ | 🛡️ | **这个改动是否需要重点评审?** | `check-change` — 风险、图健康度、测试与警告 |
27
+ | 🔍 | **结构上改了什么?** | `change` — 符号和依赖边的新增、删除与重命名 |
28
+ | 🗺️ | **项目长什么样?** | `archmap` — 模块/文件/符号下钻与循环依赖 |
28
29
 
29
30
  给人看(CLI / 交互 HTML / PR 评论),也给 AI agent 用([SKILL.md](SKILL.md))——同一份图谱,两个出口。
30
31
 
@@ -33,7 +34,7 @@
33
34
  ```bash
34
35
  npx codeblast demo # 给当前仓库建图、跑一次 impact 查询、导出架构图
35
36
  npm i -g codeblast # 或全局安装;需要 Node ≥ 22.13(内置 sqlite)或 Bun
36
- # npm 上的版本是 0.3.3,与本仓库一致
37
+ # npm 当前版本为 0.3.5
37
38
 
38
39
  # 作为 agent skill 安装(Claude Code、Codex、Cursor 等)
39
40
  npx skills add alloevil/codeblast
@@ -49,7 +50,7 @@ codeblast: 代码 → tsc/AST 确定性解析 → 图谱 → 投影 图 =
49
50
  **每个节点和每条静态分析边都带 `file:line` 证据**,可直接打开核对(`co_change` 边在该字段存的是共提交次数而非源码行;文件级节点行号为 1)。
50
51
  LLM 在管线里只做一件事:给模块起人话名字——节点归属和边永远来自静态分析。
51
52
 
52
- ## 三个查询
53
+ ## 四个视图
53
54
 
54
55
  ```bash
55
56
  # 建图:TS monorepo / Python 自动识别,hash 增量更新(tRPC 957 文件:6248 节点 / 17072 边,样例运行见 SKILL.md)
@@ -60,23 +61,45 @@ codeblast impact graph.db "createOrder" --json
60
61
  # → direct 清单 = 必须检查的 callsite;tests 清单 = 必须跑的测试
61
62
  # → 双通道:调用链可达(精确率 ~0.70,优先看)+ import 可达(保守补充,勿跳过)
62
63
 
63
- # ② Change Map —— 两个 ref 之间的结构 diff
64
+ # ② Safety —— 合并前机器判断
65
+ codeblast check-change <repo> <base> <head> --json
66
+ # → decision / risk / graph_health / affected_test_files / warnings
67
+
68
+ # ③ Change Map —— 两个 ref 之间的结构 diff
64
69
  codeblast change <repo> main~5 main --json
65
70
  # → 意料之外的 edges_added = 改动越界信号
66
71
 
67
- # ③ Architecture Map —— 交互 HTML:模块→文件→符号三层下钻,符号跳源码行
72
+ # ④ Architecture Map —— 交互 HTML:模块→文件→符号三层下钻,符号跳源码行
68
73
  codeblast archmap graph.db --out arch.html --repo-url <github-url>
69
74
 
70
75
  # 可选:git 历史耦合挖掘(协议两端、配置与消费者——静态分析看不见的边)
71
76
  codeblast cochange <repo> graph.db
72
77
  ```
73
78
 
74
- ### PR bot(CI 内跑,宁静默不刷屏)
79
+ ### PR bot(CI 内运行,宁静默不刷屏)
80
+
81
+ 复制 [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) 到目标仓库。
82
+ 有意义的结构变化或“生产函数体 + 同 PR 测试”变化会得到 review decision、测试建议和 `file:line` 证据;
83
+ docs-only、test-only、auxiliary-only 噪音默认静默。离线 replay 会自动防止错误静默和无效证据回归。
84
+
85
+ ## 当前自动证据
75
86
 
76
- 复制 [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) 到目标仓库:
77
- 每个 PR 自动评论结构变化 + 影响半径 + 无测试覆盖的新增符号;**无结构变化的 PR 零评论**。
78
- 50 个真实提交回放:42 个正确静默。评论有效率是诚实的弱项——四轮评审(1–3 轮为独立盲评,第 4 轮由现模型担任评审人)为 25% / 75% / 57% / 20%,
79
- 而作者 agent 自评同一批 8 条评论为 7/8 = 87.5%;两个数字与每轮之后的修复都记在 [intent.md](intent.md)。
87
+ ```bash
88
+ bun run evolution-check
89
+ ```
90
+
91
+ 统一运行 build、测试、类型检查、离线 replay、full/incremental 图等价和 claims gate。当前结果:
92
+
93
+ | 证据 | 结果 |
94
+ |---|---:|
95
+ | 离线安全 replay | 12/12;错误静默、无效证据、非确定输出均为 0 |
96
+ | 全量与增量图 | nodes / edges / blind spots 相等;删除与重命名后的旧记录已清理 |
97
+ | 明星项目图完整性 | n8n、Excalidraw、OpenCode 固定 commit 均为 0 extraction failure |
98
+ | 固定 mutation gate | tRPC 28/28;graphql-tools 10/10 |
99
+ | modern 兼容性雷达 | tRPC 小样本 3/3;仅 informational,不是 release gate |
100
+
101
+ 字面量 `import.meta.glob("./fixtures/**/*.tsx")` 会展开成保守 import 边;非字面量 loader、
102
+ 子进程边界和无法解析的运行时派发仍作为显式 blind spot。
80
103
 
81
104
  ## 精度承诺(有边界,有证据)
82
105
 
@@ -107,13 +130,13 @@ codeblast cochange <repo> graph.db
107
130
 
108
131
  ## 给 AI Agent 用
109
132
 
110
- ```
111
- 改前: impact "symbol" --json → callsite 清单进上下文,防漏改
112
- 改后: change HEAD~1 HEAD --json → 自查结构越界与意外删除
133
+ ```text
134
+ 改前:impact "symbol" --json → review_first / run_tests / conservative / warnings
135
+ 改后:check-change repo base head --json → decision / risk / graph_health / affected tests
113
136
  ```
114
137
 
115
- 完整契约与解读纪律(含"禁止假装盲区清单完整")见 [SKILL.md](SKILL.md)。
116
- Agent 规范另见 [AGENTS.md](AGENTS.md)。
138
+ `safe-to-review` 只是注意力路由,不等于“可以安全合并”。完整契约、schema version、exit code
139
+ 和解读纪律见 [SKILL.md](SKILL.md);Agent 规范另见 [AGENTS.md](AGENTS.md)。
117
140
 
118
141
  ## 常见问题
119
142
 
package/dist/bin.js CHANGED
@@ -467,21 +467,24 @@ class Extractor {
467
467
  enclosing.push(id);
468
468
  if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
469
469
  const caller = [...enclosing].reverse().find(Boolean) ?? relPath;
470
- if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
470
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "glob" && node.expression.expression.getText(sf) === "import.meta") {
471
+ const arg = node.arguments[0];
472
+ const patterns = arg && ts.isStringLiteral(arg) ? [arg.text] : arg && ts.isArrayLiteralExpression(arg) && arg.elements.every(ts.isStringLiteral) ? arg.elements.map((element) => element.text) : null;
473
+ if (patterns) {
474
+ const excluded = new Set(patterns.filter((pattern) => pattern.startsWith("!")).flatMap((pattern) => this.expandGlob(pattern.slice(1), sf.fileName)));
475
+ const matches = new Set(patterns.filter((pattern) => !pattern.startsWith("!")).flatMap((pattern) => this.expandGlob(pattern, sf.fileName)));
476
+ for (const match of matches)
477
+ if (!excluded.has(match))
478
+ edges.push({ src: relPath, dst: this.rel(match), kind: "imports", file: relPath, line: lineOf(node), confidence: "conservative", src_file: relPath });
479
+ } else {
480
+ blindSpots.push({ file: relPath, line: lineOf(node), reason: `dynamic import.meta.glob: ${(arg?.getText() ?? "").slice(0, 80)}`, src_file: relPath });
481
+ }
482
+ } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
471
483
  const arg = node.arguments[0];
472
484
  if (arg && ts.isStringLiteral(arg)) {
473
485
  const resolved = this.resolveModule(arg.text, sf.fileName);
474
- if (resolved) {
475
- edges.push({
476
- src: relPath,
477
- dst: this.rel(resolved),
478
- kind: "imports",
479
- file: relPath,
480
- line: lineOf(node),
481
- confidence: "exact",
482
- src_file: relPath
483
- });
484
- }
486
+ if (resolved)
487
+ edges.push({ src: relPath, dst: this.rel(resolved), kind: "imports", file: relPath, line: lineOf(node), confidence: "exact", src_file: relPath });
485
488
  } else {
486
489
  blindSpots.push({ file: relPath, line: lineOf(node), reason: `dynamic import: ${(arg?.getText() ?? "").slice(0, 80)}`, src_file: relPath });
487
490
  }
@@ -659,6 +662,28 @@ class Extractor {
659
662
  isExported(node) {
660
663
  return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0;
661
664
  }
665
+ expandGlob(pattern, fromFile) {
666
+ if (!pattern.startsWith("."))
667
+ return [];
668
+ const base = path.resolve(path.dirname(fromFile), pattern.split("*")[0]);
669
+ if (!fs.existsSync(base))
670
+ return [];
671
+ const rawSuffix = pattern.slice(pattern.indexOf("*"));
672
+ const suffix = rawSuffix.split("**").map((part) => part.split("*").map((piece) => piece.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")).join(".*");
673
+ const re = new RegExp(`^${suffix}$`);
674
+ const out = [];
675
+ const walk = (dir) => {
676
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
677
+ const file = path.join(dir, entry.name);
678
+ if (entry.isDirectory())
679
+ walk(file);
680
+ else if (re.test(path.relative(base, file).replaceAll(path.sep, "/")))
681
+ out.push(file);
682
+ }
683
+ };
684
+ walk(base);
685
+ return out;
686
+ }
662
687
  }
663
688
  var TEST_FILE_RE;
664
689
  var init_extract = __esm(() => {
@@ -879,25 +904,33 @@ var init_cli = __esm(() => {
879
904
  }
880
905
  pyProbe = spawnSync(["python3", path2.join(import.meta.dirname, "py_extract.py"), repoRoot]);
881
906
  if (pyProbe.exitCode === 0) {
882
- const payload = JSON.parse(pyProbe.stdout);
883
- for (const f of payload.files) {
884
- if (seenFiles.has(f.path))
885
- continue;
886
- seenFiles.add(f.path);
887
- const existing = getHash.get(f.path);
888
- if (existing?.hash === f.hash && f.hash !== "") {
889
- skipped++;
890
- continue;
907
+ try {
908
+ const payload = JSON.parse(pyProbe.stdout);
909
+ for (const f of payload.files) {
910
+ if (seenFiles.has(f.path))
911
+ continue;
912
+ seenFiles.add(f.path);
913
+ const existing = getHash.get(f.path);
914
+ if (existing?.hash === f.hash && f.hash !== "") {
915
+ skipped++;
916
+ continue;
917
+ }
918
+ writeBatch(f.path, f.hash, f.nodes, f.edges, f.blind_spots);
919
+ indexed++;
920
+ nodeCount += f.nodes.length;
921
+ edgeCount += f.edges.length;
922
+ blindCount += f.blind_spots.length;
891
923
  }
892
- writeBatch(f.path, f.hash, f.nodes, f.edges, f.blind_spots);
893
- indexed++;
894
- nodeCount += f.nodes.length;
895
- edgeCount += f.edges.length;
896
- blindCount += f.blind_spots.length;
924
+ const pyFiles = payload.files.length;
925
+ if (pyFiles > 0)
926
+ console.error(`python files ingested: ${pyFiles}`);
927
+ } catch (err) {
928
+ failures++;
929
+ console.error(`PYTHON INGEST FAILED: ${err instanceof Error ? err.message : err}`);
897
930
  }
898
- const pyFiles = payload.files.length;
899
- if (pyFiles > 0)
900
- console.error(`python files ingested: ${pyFiles}`);
931
+ } else {
932
+ failures++;
933
+ console.error(`PYTHON INGEST FAILED: ${pyProbe.stderr.slice(0, 1000)}`);
901
934
  }
902
935
  if (failures === 0) {
903
936
  const stale = db.prepare("SELECT path FROM files").all().filter((row) => !seenFiles.has(row.path));
@@ -2348,7 +2381,7 @@ var SCHEMA_VERSION = "1", EXIT_OK = 0, EXIT_REVIEW = 1, EXIT_ERROR = 2, readVers
2348
2381
  nodes: Number(db.prepare("SELECT COUNT(*) c FROM nodes").get().c),
2349
2382
  edges: Number(db.prepare("SELECT COUNT(*) c FROM edges").get().c),
2350
2383
  blind_spots: Number(db.prepare("SELECT COUNT(*) c FROM blind_spots").get().c)
2351
- }), healthBase, healthHead, diff3, healthWarnings, prodNodesAdded2, total3, affectedTests = 0, truncated = false, blindSpots = 0, bodyChanged2, structuralIds2, diffText, currentFile = "", diffLineCount2, changedFiles2, auxOnly, decision2, output;
2384
+ }), healthBase, healthHead, diff3, healthWarnings, prodNodesAdded2, total3, affectedTestFiles, truncated = false, blindSpots = 0, affectedTests, bodyChanged2, structuralIds2, diffText, currentFile = "", diffLineCount2, changedFiles2, auxOnly, decision2, output;
2352
2385
  var init_check_change = __esm(() => {
2353
2386
  init_db();
2354
2387
  init_proc();
@@ -2384,14 +2417,18 @@ var init_check_change = __esm(() => {
2384
2417
  ];
2385
2418
  prodNodesAdded2 = diff3.nodesAdded.filter((n) => !TEST_RE3.test(n.file));
2386
2419
  total3 = structuralTotal(diff3);
2420
+ affectedTestFiles = new Set;
2387
2421
  for (const node of [...diff3.nodesAdded, ...diff3.renamed.map((r) => ({ id: `${r.file}#${r.to}`, kind: r.kind, name: r.to, file: r.file, line: 0 }))].slice(0, 15)) {
2388
2422
  try {
2389
2423
  const result = impact(dbB3, node.id, 2000);
2390
- affectedTests += new Set(result.items.filter((item) => item.level === "tests").map((item) => item.file)).size;
2424
+ for (const item of result.items)
2425
+ if (item.level === "tests")
2426
+ affectedTestFiles.add(item.file);
2391
2427
  truncated ||= result.truncated;
2392
2428
  blindSpots += result.blind_spot_count;
2393
2429
  } catch {}
2394
2430
  }
2431
+ affectedTests = affectedTestFiles.size;
2395
2432
  bodyChanged2 = [];
2396
2433
  structuralIds2 = new Set([
2397
2434
  ...diff3.nodesAdded.map((node) => node.id),
@@ -2442,6 +2479,7 @@ var init_check_change = __esm(() => {
2442
2479
  recommended_actions: decision2.recommendedActions,
2443
2480
  structural_changes: total3,
2444
2481
  affected_test_files: affectedTests,
2482
+ affected_test_file_paths: [...affectedTestFiles].sort((a, b) => a.localeCompare(b)),
2445
2483
  blind_spot_count: blindSpots,
2446
2484
  graph_health: { base: healthBase, head: healthHead, warnings: healthWarnings },
2447
2485
  truncated,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeblast",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Know what breaks before you merge — mutation-tested code graph with architecture, change & impact maps. Evidence on every edge. For humans and AI agents.",
5
5
  "keywords": [
6
6
  "impact-analysis",
@@ -39,6 +39,7 @@
39
39
  "prepack": "bun run build",
40
40
  "typecheck": "tsc -p .",
41
41
  "agent-smoke": "bun run build && node eval/agent-workflow-smoke.mjs",
42
+ "evolution-check": "PYTHONPATH=. node eval/evolution-check.mjs",
42
43
  "guidance-stability": "node eval/guidance-stability.mjs",
43
44
  "incremental-equivalence": "bun run build && node eval/incremental-equivalence.mjs",
44
45
  "offline-replay": "bun run build && node eval/offline-replay.mjs",