codeblast 0.3.0 โ†’ 0.3.2

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
@@ -1,3 +1,5 @@
1
+ **codeblast** is a deterministic code-graph CLI for TypeScript and Python repositories that tells developers and AI agents what breaks before a change is merged.
2
+
1
3
  <p align="center">
2
4
  <img src="assets/readme/hero.svg" width="100%" alt="codeblast โ€” deterministic code graph: know what breaks before you merge"/>
3
5
  </p>
@@ -13,6 +15,8 @@
13
15
  <img src="https://img.shields.io/badge/license-MIT-8b949e?style=flat-square" alt="MIT"/>
14
16
  </p>
15
17
 
18
+ ## What it is
19
+
16
20
  **codeblast parses your repository into a deterministic code graph and answers the three most expensive questions around any code change:**
17
21
  > ๐Ÿ”— **[Live interactive demo](https://alloevil.github.io/codeblast/)** โ€” real architecture maps of tRPC / Tabby / sgp, with three-level drill-down
18
22
 
@@ -35,19 +39,47 @@ Built for humans (CLI / interactive HTML / PR comments) and for AI agents ([SKIL
35
39
  <a href="https://alloevil.github.io/codeblast/tabby-arch.html">
36
40
  <img src="assets/readme/arch-hover.png" alt="Tabby architecture map with hover highlighting: all edges into tabby-core lit up"/>
37
41
  </a>
38
- <p align="center"><sub><b>Architecture map</b> โ€” hover lights the dependency fan-in (Tabby, 60kโ˜… ยท <a href="https://alloevil.github.io/codeblast/tabby-arch.html">live โ†—</a>)</sub></p>
42
+ <p align="center"><sub><b>Architecture map</b> โ€” hover lights the dependency fan-in (Tabby, 70kโ˜… ยท <a href="https://alloevil.github.io/codeblast/tabby-arch.html">live โ†—</a>)</sub></p>
39
43
  </td>
40
44
  </tr>
41
45
  </table>
42
46
 
47
+ ## Install
48
+
43
49
  ```bash
44
50
  npx codeblast demo # build a graph of the current repo, run one impact query, emit the map
45
51
  npm i -g codeblast # or install globally; needs Node โ‰ฅ 22.13 (built-in sqlite) or Bun
52
+ # npm serves 0.3.1
46
53
 
47
54
  # Install as an agent skill (Claude Code, Codex, Cursor, and 14 more harnesses)
48
55
  npx skills add alloevil/codeblast
49
56
  ```
50
57
 
58
+ ### As a GitHub Action (one line)
59
+
60
+ ```yaml
61
+ # .github/workflows/codeblast.yml
62
+ name: codeblast
63
+ on: pull_request
64
+ permissions:
65
+ contents: read
66
+ pull-requests: write
67
+ jobs:
68
+ analyze:
69
+ runs-on: ubuntu-latest
70
+ steps:
71
+ - uses: actions/checkout@v4
72
+ with: { fetch-depth: 0 } # the analyzer compares base and head commits
73
+ - uses: alloevil/codeblast@v0.3.1
74
+ ```
75
+
76
+ The action builds the analyzer from the ref you pinned (not from npm, which can lag),
77
+ posts one sticky comment per PR and updates it in place, and stays silent when the diff
78
+ has no structural change. Inputs: `base`, `head`, `repo-url`, `comment` (set to `false`
79
+ to only produce the file); outputs: `has_comment`, `comment_path`. If you prefer to own
80
+ the commenting step, copy [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml)
81
+ instead โ€” it runs the same command with `npx`.
82
+
51
83
  ## Why not yet another LLM diagram tool
52
84
 
53
85
  ```
@@ -55,14 +87,18 @@ LLM diagrams: code โ†’ model reads it โ†’ hand-drawn graph โ†’ render graph
55
87
  codeblast: code โ†’ deterministic tsc/AST parse โ†’ graph โ†’ project graph = checkable facts
56
88
  ```
57
89
 
58
- **Every node, every edge, every claim carries `file:line` evidence** you can open and verify.
90
+ **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).
59
91
  The LLM does exactly one job in the pipeline: giving modules human-readable names โ€” node membership and edges always come from static analysis.
60
92
 
61
93
  ## The three queries
62
94
 
95
+ <p align="center">
96
+ <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"/>
97
+ </p>
98
+
63
99
  ```bash
64
100
  # Build the graph: auto-detects TS monorepos / Python, hash-based incremental updates
65
- # (full build of tRPC, 950 files, in ~20s)
101
+ # (full build of tRPC: 957 files, 6248 nodes, 17072 edges โ€” the sample run in SKILL.md)
66
102
  codeblast index <repo> --db graph.db
67
103
 
68
104
  # โ‘  Impact โ€” check the blast radius before you change anything
@@ -77,7 +113,8 @@ codeblast change <repo> main~5 main --json
77
113
 
78
114
  # โ‘ข Architecture Map โ€” interactive HTML: module โ†’ file โ†’ symbol drill-down,
79
115
  # symbols link to source lines
80
- codeblast archmap graph.db --out arch.html --repo-url <github-url>
116
+ codeblast archmap graph.db --out arch.html --repo-url <github-url> \
117
+ --site-url https://example.github.io/repo --og-image <share-image-url>
81
118
 
82
119
  # Optional: mine git co-change coupling (protocol pairs, config + consumers โ€”
83
120
  # edges static analysis can't see)
@@ -88,19 +125,53 @@ codeblast cochange <repo> graph.db
88
125
 
89
126
  Copy [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) into your repo (it runs `npx codeblast pr-comment`, no other setup):
90
127
  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**.
91
- Replayed against 50 real commits: 42 correctly stayed silent, 87.5% of comments were useful.
128
+ Replayed against 50 real commits: 42 correctly stayed silent. Comment usefulness is the honest weak spot โ€”
129
+ 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
130
+ authoring agent rated its own comments; both numbers and the fixes that followed each round are logged in
131
+ [intent.md](intent.md).
92
132
 
93
133
  ## The precision promise (bounded, and evidence-backed)
94
134
 
95
135
  - **TypeScript at function level: zero missed impact within statically analyzable scope.** Verified by mutation testing:
96
136
  inject mutations into a real repo โ†’ run the full test suite to get the ground-truth impact set โ†’ compare against predictions.
97
- Two benchmarks, both hard gates in the weekly acceptance workflow: **tRPC** (vitest, 950 files) **28/28**
137
+ Two benchmarks, both hard gates in the weekly acceptance workflow: **tRPC** (vitest, 957 files) **28/28**
98
138
  and **graphql-tools** (jest + npm workspaces, 353 files) **10/10** โ€” 100% recall on each, average precision
99
139
  0.33โ€“0.36. Favoring false positives over false negatives is a deliberate trade: in a controlled experiment,
100
- dropping the conservative edges raises precision to 0.70 but recall collapses to 14%. Data lives in [`eval/`](eval/).
140
+ restricting the result to the call channel (items whose path is call-graph only) raises precision to 0.70 but recall collapses to 14%. Data lives in [`eval/`](eval/).
101
141
  - **Blind spots are explicitly flagged.** A blind spot is any call or import that static analysis cannot resolve to an in-repo target โ€” dynamic calls, unresolved calls, failed external-dependency resolution, subprocess boundaries, test-framework globals โ€” not just dynamic calls; each is recorded in `blind_spots` with an "impact may be underestimated" warning, never silently dropped.
102
142
  - **Python is file-level.** Dynamic typing makes function-level zero-miss guarantees impossible in principle, and we don't pretend otherwise.
103
143
 
144
+ ## When to use it
145
+
146
+ - You are about to change an exported TypeScript symbol in a monorepo and want the callsite list and the test list *before* you edit, not after CI fails.
147
+ - You are an AI agent editing code: `impact --json` before the edit puts the callsites in context, `change --json` after it catches scope creep and accidental deletions.
148
+ - You are reviewing a PR and want the structural delta โ€” edges added, symbols renamed, new symbols with no test coverage โ€” separated from formatting noise.
149
+ - You just inherited an unfamiliar TypeScript or Python repo and want a map whose every box and arrow can be opened at the source line that justifies it.
150
+ - You need the answer to be checkable by someone who does not trust the tool.
151
+
152
+ ## When NOT to use it
153
+
154
+ - **You want function-level guarantees on Python.** Python is file-level with typed-call upgrades; duck typing makes a zero-miss promise impossible in principle and we don't pretend otherwise.
155
+ - **You want a short, precise impact list.** Mean precision is 0.33โ€“0.36 overall; the call channel alone runs 0.702 (tRPC ablation) to 0.918 (graphql-tools) โ€” a derived range, the min and max of the two per-benchmark means in [`eval/`](eval/), not one measured figure. The engine over-approximates on purpose โ€” read the `call` channel first, treat the full list as the test set.
156
+ - **Your dependencies run through what static analysis cannot resolve** โ€” dynamic `require`, `eval`, subprocess boundaries, uninstalled `node_modules`, test-framework globals. Those are reported as `blind_spots`, never silently dropped, but such a repo gets a thin graph.
157
+ - **You want a presentation diagram or a collaborative canvas.** `archmap` emits facts for navigation; feed its JSON to a rendering tool if you need something pretty.
158
+ - **You want cross-service / cross-repo edges, or Java.** The graph model reserves the node types, v1 does not fill those edges, and Java is explicitly not implemented ([intent.md](intent.md)).
159
+ - **You want the PR bot to replace a reviewer.** It is a structural-change signal whose usefulness measured between 20% and 75% depending on the review round.
160
+
161
+
162
+ ### Unified change safety check
163
+
164
+ For agents and CI that need one decision instead of composing `change` and `impact`, run:
165
+
166
+ ```bash
167
+ codeblast check-change <repo> <base-sha> <head-sha> --json
168
+ ```
169
+
170
+ The output contains `decision` (`safe-to-review`, `targeted-review`, or `review`), `risk`, reasons,
171
+ recommended actions, structural facts, predicted affected-test count, blind-spot count, truncation,
172
+ and the complete graph diff. It is a conservative routing decision, not a claim that the change is
173
+ safe to merge. `review` is required when API surface contracts, removed symbols, or incomplete impact
174
+ results are detected.
104
175
  ## For AI agents
105
176
 
106
177
  ```
@@ -111,8 +182,28 @@ after editing: change HEAD~1 HEAD --json โ†’ self-check for scope creep and ac
111
182
  The full contract and interpretation discipline (including "never pretend the blind-spot list is complete") is in [SKILL.md](SKILL.md).
112
183
  Agent conventions: [AGENTS.md](AGENTS.md).
113
184
 
185
+ ## FAQ
186
+
187
+ **What exactly does the zero-miss promise cover?** Within the statically analyzable scope of an in-repo TypeScript codebase, the predicted set of affected test files is a superset of the tests that actually fail. That is verified by mutation testing on two independent repos โ€” trpc/trpc 28/28 killed mutants, ardatan/graphql-tools 10/10 โ€” and enforced by the weekly [`acceptance`](.github/workflows/acceptance.yml) workflow, which fails the run and opens an issue if recall drops below 100%. Anything static analysis cannot resolve is listed as a blind spot, and unqualified "zero-miss" wording is banned by project rule.
188
+
189
+ **Why is precision so low, and is that a bug?** No, it is a measured trade. Mean precision is 0.358 on the 28-mutant tRPC run and 0.331 on the 10-mutant graphql-tools run, so most predicted affected tests do not fail. A controlled ablation that restricted the result to the call channel โ€” a filter on the combined run (`predicted_call` in [`eval/mutation_check.py`](eval/mutation_check.py)) โ€” raised call-channel precision to 0.702 and collapsed recall to 2 of 14 killed mutants. A later attempt to raise precision by pruning pure re-export barrels and narrowing interface fan-out was rejected by its own data because it would have produced guaranteed misses, so precision is no longer an optimization target.
190
+
191
+ **Does it work on Python?** Yes, at file level, with typed-call upgrades that resolve named-import calls and constructor/annotation-derived method calls, which is enough for the architecture map and a file-level change map. The function-level zero-miss promise stays TypeScript-only. The Python example is the [sgp map](https://alloevil.github.io/codeblast/sgp-arch.html), including a detected `sgp_utils โ‡„ solver_transfer` cycle.
192
+
193
+ **How do I drive it from an AI coding agent?** Install it as a skill with `npx skills add alloevil/codeblast`, then run `codeblast impact <db> "<symbol>" --json` before editing and `codeblast change <repo> HEAD~1 HEAD --json` after. [SKILL.md](SKILL.md) carries the interpretation rules that matter: never present the impact list as complete while `blind_spot_count > 0`, never drop the `file` channel to shorten it, never claim function-level precision on Python, treat `truncated: true` as "run the full suite", and never report `co_change_hints` as impact.
194
+
195
+ **Where are the numbers I can check?** Machine-readable claims with metric, method, repro command and evidence path are published at [claims.json](https://alloevil.github.io/codeblast/claims.json); the raw mutation and PR-replay runs are archived under [`eval/`](eval/) as records of runs that were made (the harness writes to `/tmp`, so the files are manual copies with no commit or version pin, and `mutation_check.py` picks candidates with `ORDER BY RANDOM()` โ€” read them as archived runs, not as one-command regenerations), and the acceptance log with every downgrade and rejected optimization is [intent.md](intent.md).
196
+
197
+ ### PR bot feedback
198
+
199
+ Feedback is intentionally issue-based and privacy-safe. Report only the public PR URL, the affected
200
+ decision category, and a redacted explanation. Do not include source code, secrets, proprietary diffs,
201
+ or full repository contents. The useful labels are false positive, missed impact, noise, incorrect
202
+ silence, or incorrect recommendation. Feedback is qualitative calibration data; it is not telemetry
203
+ and codeblast does not require access to customer repositories.
204
+
114
205
  ## Status & roadmap
115
206
 
116
- M0 graph engine โ†’ M1 Impact โ†’ M3 architecture map โ†’ M4 graph diff + PR bot โ†’ M5 precision extensions โ€” **all milestones accepted** (each with a reproducible acceptance script). Single source of truth for design and acceptance criteria: [intent.md](intent.md).
207
+ M0 graph engine โ†’ M1 Impact โ†’ M3 architecture map โ†’ M4 graph diff + PR bot โ†’ M5 precision extensions โ€” **all shipped**; two acceptance steps were downgraded rather than passed (M2's verification method, and M3's original "10 minutes, 5 questions" test which was never run โ€” โš ๏ธ in [intent.md](intent.md)), and the SemArc alignment check was dropped. Single source of truth for design and acceptance criteria: [intent.md](intent.md).
117
208
 
118
209
  MIT ยฉ 2026
package/README.zh-CN.md CHANGED
@@ -1,3 +1,5 @@
1
+ **codeblast** ๆ˜ฏไธ€ไธช็กฎๅฎšๆ€งไปฃ็ ๅ›พ่ฐฑ CLI๏ผŒ้ขๅ‘ TypeScript / Python ไป“ๅบ“๏ผŒ่ฎฉๅผ€ๅ‘่€…ๅ’Œ AI agent ๅœจๅˆๅนถไน‹ๅ‰ๅฐฑ็Ÿฅ้“ๆ”นๅŠจไผš็‚ธๅˆฐๅ“ช้‡Œใ€‚
2
+
1
3
  <p align="center">
2
4
  <img src="assets/readme/hero.svg" width="100%" alt="codeblast โ€” deterministic code graph: know what breaks before you merge"/>
3
5
  </p>
@@ -13,6 +15,8 @@
13
15
  <img src="https://img.shields.io/badge/license-MIT-8b949e?style=flat-square" alt="MIT"/>
14
16
  </p>
15
17
 
18
+ ## ๆ˜ฏไป€ไนˆ
19
+
16
20
  **codeblast ๆŠŠไป“ๅบ“่งฃๆžๆˆไธ€ไปฝ็กฎๅฎšๆ€งไปฃ็ ๅ›พ่ฐฑ๏ผŒๅ›ž็ญ”ๆ”นไปฃ็ ๅ‰ๅŽๆœ€่ดต็š„ไธ‰ไธช้—ฎ้ข˜๏ผš**
17
21
  > ๐Ÿ”— **[ๅœจ็บฟไบคไบ’ๆผ”็คบ](https://alloevil.github.io/codeblast/)** โ€” tRPC / Tabby / sgp ็š„ๅฎžๆ—ถๆžถๆž„ๅ›พ,็‚นๅผ€ๅณๅฏไธ‰ๅฑ‚ไธ‹้’ป
18
22
 
@@ -24,6 +28,17 @@
24
28
 
25
29
  ็ป™ไบบ็œ‹๏ผˆCLI / ไบคไบ’ HTML / PR ่ฏ„่ฎบ๏ผ‰๏ผŒไนŸ็ป™ AI agent ็”จ๏ผˆ[SKILL.md](SKILL.md)๏ผ‰โ€”โ€”ๅŒไธ€ไปฝๅ›พ่ฐฑ๏ผŒไธคไธชๅ‡บๅฃใ€‚
26
30
 
31
+ ## ๅฎ‰่ฃ…
32
+
33
+ ```bash
34
+ npx codeblast demo # ็ป™ๅฝ“ๅ‰ไป“ๅบ“ๅปบๅ›พใ€่ท‘ไธ€ๆฌก impact ๆŸฅ่ฏขใ€ๅฏผๅ‡บๆžถๆž„ๅ›พ
35
+ npm i -g codeblast # ๆˆ–ๅ…จๅฑ€ๅฎ‰่ฃ…๏ผ›้œ€่ฆ Node โ‰ฅ 22.13๏ผˆๅ†…็ฝฎ sqlite๏ผ‰ๆˆ– Bun
36
+ # npm ไธŠ็š„็‰ˆๆœฌๆ˜ฏ 0.3.1๏ผŒไธŽๆœฌไป“ๅบ“ไธ€่‡ด
37
+
38
+ # ไฝœไธบ agent skill ๅฎ‰่ฃ…๏ผˆClaude Codeใ€Codexใ€Cursor ็ญ‰๏ผ‰
39
+ npx skills add alloevil/codeblast
40
+ ```
41
+
27
42
  ## ไธบไป€ไนˆไธๆ˜ฏๅˆไธ€ไธช LLM ็”ปๅ›พๅทฅๅ…ท
28
43
 
29
44
  ```
@@ -31,13 +46,13 @@ LLM ็”ปๅ›พ: ไปฃ็  โ†’ ๆจกๅž‹้˜…่ฏป็†่งฃ โ†’ ๆ‰‹ๅ†™ๅ›พ โ†’ ๆธฒๆŸ“ ๅ›พ
31
46
  codeblast: ไปฃ็  โ†’ tsc/AST ็กฎๅฎšๆ€ง่งฃๆž โ†’ ๅ›พ่ฐฑ โ†’ ๆŠ•ๅฝฑ ๅ›พ = ๅฏ้ชŒ่ฏ็š„ไบ‹ๅฎž
32
47
  ```
33
48
 
34
- **ๆฏไธช่Š‚็‚นใ€ๆฏๆก่พนใ€ๆฏๅฅ็ป“่ฎบ้ƒฝๅธฆ `file:line` ่ฏๆฎ**๏ผŒๅฏ็›ดๆŽฅๆ‰“ๅผ€ๆ ธๅฏนใ€‚
49
+ **ๆฏไธช่Š‚็‚นๅ’Œๆฏๆก้™ๆ€ๅˆ†ๆž่พน้ƒฝๅธฆ `file:line` ่ฏๆฎ**๏ผŒๅฏ็›ดๆŽฅๆ‰“ๅผ€ๆ ธๅฏน๏ผˆ`co_change` ่พนๅœจ่ฏฅๅญ—ๆฎตๅญ˜็š„ๆ˜ฏๅ…ฑๆไบคๆฌกๆ•ฐ่€Œ้žๆบ็ ่กŒ๏ผ›ๆ–‡ไปถ็บง่Š‚็‚น่กŒๅทไธบ 1๏ผ‰ใ€‚
35
50
  LLM ๅœจ็ฎก็บฟ้‡Œๅชๅšไธ€ไปถไบ‹๏ผš็ป™ๆจกๅ—่ตทไบบ่ฏๅๅญ—โ€”โ€”่Š‚็‚นๅฝ’ๅฑžๅ’Œ่พนๆฐธ่ฟœๆฅ่‡ช้™ๆ€ๅˆ†ๆžใ€‚
36
51
 
37
52
  ## ไธ‰ไธชๆŸฅ่ฏข
38
53
 
39
54
  ```bash
40
- # ๅปบๅ›พ๏ผšTS monorepo / Python ่‡ชๅŠจ่ฏ†ๅˆซ๏ผŒhash ๅขž้‡ๆ›ดๆ–ฐ๏ผˆtRPC 950 ๆ–‡ไปถๅ…จ้‡ ~20s๏ผ‰
55
+ # ๅปบๅ›พ๏ผšTS monorepo / Python ่‡ชๅŠจ่ฏ†ๅˆซ๏ผŒhash ๅขž้‡ๆ›ดๆ–ฐ๏ผˆtRPC 957 ๆ–‡ไปถ๏ผš6248 ่Š‚็‚น / 17072 ่พน๏ผŒๆ ทไพ‹่ฟ่กŒ่ง SKILL.md๏ผ‰
41
56
  codeblast index <repo> --db graph.db
42
57
 
43
58
  # โ‘  Impact โ€”โ€” ๆ”นๅŠจๅ‰ๆŸฅๅฝฑๅ“ๅŠๅพ„
@@ -60,18 +75,36 @@ codeblast cochange <repo> graph.db
60
75
 
61
76
  ๅคๅˆถ [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) ๅˆฐ็›ฎๆ ‡ไป“ๅบ“๏ผš
62
77
  ๆฏไธช PR ่‡ชๅŠจ่ฏ„่ฎบ็ป“ๆž„ๅ˜ๅŒ– + ๅฝฑๅ“ๅŠๅพ„ + ๆ— ๆต‹่ฏ•่ฆ†็›–็š„ๆ–ฐๅขž็ฌฆๅท๏ผ›**ๆ— ็ป“ๆž„ๅ˜ๅŒ–็š„ PR ้›ถ่ฏ„่ฎบ**ใ€‚
63
- 50 ไธช็œŸๅฎžๆไบคๅ›žๆ”พ๏ผš42 ไธชๆญฃ็กฎ้™้ป˜ใ€่ฏ„่ฎบๆœ‰ๆ•ˆ็އ 87.5%ใ€‚
78
+ 50 ไธช็œŸๅฎžๆไบคๅ›žๆ”พ๏ผš42 ไธชๆญฃ็กฎ้™้ป˜ใ€‚่ฏ„่ฎบๆœ‰ๆ•ˆ็އๆ˜ฏ่ฏšๅฎž็š„ๅผฑ้กนโ€”โ€”ๅ››่ฝฎ่ฏ„ๅฎก๏ผˆ1โ€“3 ่ฝฎไธบ็‹ฌ็ซ‹็›ฒ่ฏ„๏ผŒ็ฌฌ 4 ่ฝฎ็”ฑ็Žฐๆจกๅž‹ๆ‹…ไปป่ฏ„ๅฎกไบบ๏ผ‰ไธบ 25% / 75% / 57% / 20%๏ผŒ
79
+ ่€Œไฝœ่€… agent ่‡ช่ฏ„ๅŒไธ€ๆ‰น 8 ๆก่ฏ„่ฎบไธบ 7/8 = 87.5%๏ผ›ไธคไธชๆ•ฐๅญ—ไธŽๆฏ่ฝฎไน‹ๅŽ็š„ไฟฎๅค้ƒฝ่ฎฐๅœจ [intent.md](intent.md)ใ€‚
64
80
 
65
81
  ## ็ฒพๅบฆๆ‰ฟ่ฏบ๏ผˆๆœ‰่พน็•Œ๏ผŒๆœ‰่ฏๆฎ๏ผ‰
66
82
 
67
83
  - **TypeScript ๅ‡ฝๆ•ฐ็บง๏ผŒ้™ๆ€ๅฏๅˆ†ๆž่Œƒๅ›ดๅ†…้›ถๆผๆŠฅใ€‚** ้ชŒๆ”ถๆ–นๆณ•๏ผšๅ˜ๅผ‚ๆต‹่ฏ•ๅฏน็…ง
68
- ๏ผˆ็œŸๅฎžไป“ๅบ“ๆณจๅ…ฅๅ˜ๅผ‚ โ†’ ๅ…จ้‡ๆต‹่ฏ•ๅพ—็œŸๅฎžๅฝฑๅ“้›† โ†’ ๅฏนๆฏ”้ข„ๆต‹๏ผ‰ใ€‚ๅฝ“ๅ‰ๅŸบๅ‡†๏ผˆtRPC๏ผŒ950 ๆ–‡ไปถ๏ผ‰๏ผš
84
+ ๏ผˆ็œŸๅฎžไป“ๅบ“ๆณจๅ…ฅๅ˜ๅผ‚ โ†’ ๅ…จ้‡ๆต‹่ฏ•ๅพ—็œŸๅฎžๅฝฑๅ“้›† โ†’ ๅฏนๆฏ”้ข„ๆต‹๏ผ‰ใ€‚ๅฝ“ๅ‰ๅŸบๅ‡†๏ผˆtRPC๏ผŒ957 ๆ–‡ไปถ๏ผ‰๏ผš
69
85
  **28/28 ๅ˜ๅผ‚ๅฌๅ›ž็އ 100%**๏ผŒๅนณๅ‡็ฒพ็กฎ็އ 0.36โ€”โ€”ๅฎ่ฏฏๆŠฅไธๆผๆŠฅๆ˜ฏๅˆปๆ„ไบคๆข๏ผš
70
- ๅฏน็…งๅฎž้ชŒไธญ็ ๆމไฟๅฎˆ่พนๅฏๅฐ†็ฒพ็กฎ็އๆๅˆฐ 0.70๏ผŒไฝ†ๅฌๅ›ž็އ่ทŒ่‡ณ 14%ใ€‚ๆ•ฐๆฎๅœจ [`eval/`](eval/)ใ€‚
86
+ ๅฏน็…งๅฎž้ชŒไธญๆŠŠ็ป“ๆžœ้™ๅˆถๅœจ่ฐƒ็”จ้“พ้€š้“๏ผˆๅฏนๅˆๅนถ็ป“ๆžœๅš channel ่ฟ‡ๆปค๏ผŒ่ง `eval/mutation_check.py` ็š„ `predicted_call`๏ผ‰ๅฏๅฐ†็ฒพ็กฎ็އๆๅˆฐ 0.70๏ผŒไฝ†ๅฌๅ›ž็އ่ทŒ่‡ณ 14%ใ€‚ๆ•ฐๆฎๅœจ [`eval/`](eval/)ใ€‚
71
87
  - **็›ฒๅŒบๆ˜พๅผๆ ‡ๆณจใ€‚** ็›ฒๅŒบ = ้™ๆ€ๆ— ๆณ•่งฃๆžๅˆฐไป“ๅ†…็›ฎๆ ‡็š„่ฐƒ็”จ/ๅฏผๅ…ฅ๏ผˆๅซๅŠจๆ€่ฐƒ็”จใ€ๆœช่งฃๆž่ฐƒ็”จใ€ๅค–้ƒจไพ่ต–่งฃๆžๅคฑ่ดฅใ€ๅญ่ฟ›็จ‹่พน็•Œใ€ๆต‹่ฏ•ๆก†ๆžถๅ…จๅฑ€๏ผ‰๏ผŒๅนถ้žๅชๆœ‰ๅŠจๆ€่ฐƒ็”จ๏ผ›
72
88
  ไธ€ๅพ‹่ฎฐๅ…ฅ blind_spots ๅนถๆ็คบ"ๅฝฑๅ“ๅฏ่ƒฝ่ขซไฝŽไผฐ"๏ผŒ็ปไธ้™้ป˜ไธขๅผƒใ€‚
73
89
  - **Python ไธบๆ–‡ไปถ็บงใ€‚** ๅŠจๆ€็ฑปๅž‹ไฝฟๅ‡ฝๆ•ฐ็บง้›ถๆผๆŠฅๅŽŸ็†ๆ€งไธๆˆ็ซ‹๏ผŒไธๅ‡่ฃ…ๅšๅˆฐใ€‚
74
90
 
91
+ ## ไป€ไนˆๆ—ถๅ€™็”จๅฎƒ
92
+
93
+ - ไฝ ่ฆๆ”น monorepo ้‡ŒๆŸไธชๅฏผๅ‡บ็ฌฆๅท๏ผŒๆƒณๅœจๅŠจๆ‰‹**ไน‹ๅ‰**ๆ‹ฟๅˆฐ callsite ๆธ…ๅ•ๅ’Œๅฟ…้กป่ท‘็š„ๆต‹่ฏ•ๆธ…ๅ•๏ผŒ่€Œไธๆ˜ฏ็ญ‰ CI ็บขใ€‚
94
+ - ไฝ ๆ˜ฏๅœจๆ”นไปฃ็ ็š„ AI agent๏ผšๆ”นๅ‰ `impact --json` ๆŠŠ callsite ้€่ฟ›ไธŠไธ‹ๆ–‡๏ผŒๆ”นๅŽ `change --json` ่‡ชๆŸฅ่ถŠ็•ŒไธŽ่ฏฏๅˆ ใ€‚
95
+ - ไฝ ๅœจ่ฏ„ๅฎก PR๏ผŒๆƒณๆŠŠ็ป“ๆž„ๅ˜ๅŒ–๏ผˆๆ–ฐๅขž่พนใ€้‡ๅ‘ฝๅ็ฌฆๅทใ€ๆ— ๆต‹่ฏ•่ฆ†็›–็š„ๆ–ฐ็ฌฆๅท๏ผ‰ไปŽๆ ผๅผๅ™ช้Ÿณ้‡Œๅˆ†็ฆปๅ‡บๆฅใ€‚
96
+ - ไฝ ๅˆšๆŽฅๆ‰‹ไธ€ไธช้™Œ็”Ÿ็š„ TS / Python ไป“ๅบ“๏ผŒๆƒณ่ฆไธ€ๅผ ๆฏไธชๆก†ๆฏๆก่พน้ƒฝ่ƒฝ็‚นๅผ€ๆ ธๅฏนๆบ็ ่กŒ็š„ๅœฐๅ›พใ€‚
97
+ - ไฝ ้œ€่ฆ็ป“่ฎบ่ƒฝ่ขซไธไฟกไปปๆœฌๅทฅๅ…ท็š„ไบบๅคๆ ธใ€‚
98
+
99
+ ## ไป€ไนˆๆ—ถๅ€™ไธ่ฆ็”จๅฎƒ
100
+
101
+ - **ไฝ ่ฆ Python ็š„ๅ‡ฝๆ•ฐ็บงไฟ่ฏใ€‚** Python ๆ˜ฏๆ–‡ไปถ็บง + ็ฑปๅž‹ๅŒ–่ฐƒ็”จๅขžๅผบ๏ผ›้ธญๅญ็ฑปๅž‹ไฝฟๅ‡ฝๆ•ฐ็บง้›ถๆผๆŠฅๅŽŸ็†ๆ€งไธๆˆ็ซ‹๏ผŒๆˆ‘ไปฌไธๅ‡่ฃ…ๅšๅˆฐใ€‚
102
+ - **ไฝ ่ฆไธ€ไปฝ็Ÿญ่€Œๅ‡†็š„ๅฝฑๅ“ๆธ…ๅ•ใ€‚** ๅ…จ้‡ๅนณๅ‡็ฒพ็กฎ็އ 0.33โ€“0.36๏ผ›ๅ•็œ‹ call ้€š้“ไธบ 0.702๏ผˆtRPC ๅฏน็…งๅฎž้ชŒ๏ผ‰ๅˆฐ 0.918๏ผˆgraphql-tools๏ผ‰โ€”โ€”่ฟ™ๆ˜ฏๆดพ็”ŸๅŒบ้—ด๏ผˆไธคไธชๅŸบๅ‡†ๅ‡ๅ€ผ็š„ min/max๏ผŒๆ•ฐๆฎๅœจ [`eval/`](eval/)๏ผ‰๏ผŒไธๆ˜ฏๅ•ๆฌกๅฎžๆต‹ใ€‚ๅผ•ๆ“Žๅˆปๆ„่ฟ‡่ฟ‘ไผผ๏ผš`call` ้€š้“ไผ˜ๅ…ˆ่ฏป๏ผŒๅ…จ้‡ๆธ…ๅ•ๅฝ“ไฝœ"่ฆ่ท‘็š„ๆต‹่ฏ•"ใ€‚
103
+ - **ไฝ ็š„ไพ่ต–ไธป่ฆ่ตฐ้™ๆ€ๅˆ†ๆž็œ‹ไธ่ง็š„่ทฏๅพ„** โ€”โ€” ๅŠจๆ€ `require`ใ€`eval`ใ€ๅญ่ฟ›็จ‹่พน็•Œใ€ๆœชๅฎ‰่ฃ…็š„ `node_modules`ใ€ๆต‹่ฏ•ๆก†ๆžถๅ…จๅฑ€ใ€‚่ฟ™ไบ›ไธ€ๅพ‹่ฎฐๅ…ฅ `blind_spots` ไธ้™้ป˜ไธขๅผƒ๏ผŒไฝ†่ฟ™ๆ ท็š„ไป“ๅบ“ๅชไผšๅพ—ๅˆฐไธ€ๅผ ็จ€็–็š„ๅ›พใ€‚
104
+ - **ไฝ ่ฆ็š„ๆ˜ฏๆผ”็คบ็”จๅ›พๆˆ–ๅไฝœ็”ปๅธƒใ€‚** `archmap` ่พ“ๅ‡บ็š„ๆ˜ฏ็”จไบŽๅฏผ่ˆช็š„ไบ‹ๅฎž๏ผ›่ฆๅฅฝ็œ‹็š„ๅ›พ่ฏทๆŠŠๅฎƒ็š„ JSON ๅ–‚็ป™ๆธฒๆŸ“ๅทฅๅ…ทใ€‚
105
+ - **ไฝ ่ฆ่ทจๆœๅŠก / ่ทจไป“ๅบ“่พน๏ผŒๆˆ–่€… Javaใ€‚** ๅ›พๆจกๅž‹้ข„็•™ไบ†่Š‚็‚น็ฑปๅž‹ไฝ† v1 ไธๅกซ่ฟ™ไบ›่พน๏ผŒJava ๆ˜Ž็กฎๆœชๅฎž็Žฐ๏ผˆ่ง [intent.md](intent.md)๏ผ‰ใ€‚
106
+ - **ไฝ ๆƒณ่ฎฉ PR bot ไปฃๆ›ฟ่ฏ„ๅฎกไบบใ€‚** ๅฎƒๆ˜ฏ็ป“ๆž„ๅ˜ๅŒ–ไฟกๅท๏ผŒๅ››่ฝฎ่ฏ„ๅฎก๏ผˆ1โ€“3 ่ฝฎ็‹ฌ็ซ‹็›ฒ่ฏ„๏ผŒ็ฌฌ 4 ่ฝฎ็Žฐๆจกๅž‹่‡ช่ฏ„๏ผ‰็š„ๆœ‰ๆ•ˆ็އๅœจ 20%โ€“75% ไน‹้—ดๆณขๅŠจใ€‚
107
+
75
108
  ## ็ป™ AI Agent ็”จ
76
109
 
77
110
  ```
@@ -82,8 +115,20 @@ codeblast cochange <repo> graph.db
82
115
  ๅฎŒๆ•ดๅฅ‘็บฆไธŽ่งฃ่ฏป็บชๅพ‹๏ผˆๅซ"็ฆๆญขๅ‡่ฃ…็›ฒๅŒบๆธ…ๅ•ๅฎŒๆ•ด"๏ผ‰่ง [SKILL.md](SKILL.md)ใ€‚
83
116
  Agent ่ง„่Œƒๅฆ่ง [AGENTS.md](AGENTS.md)ใ€‚
84
117
 
118
+ ## ๅธธ่ง้—ฎ้ข˜
119
+
120
+ **้›ถๆผๆŠฅๆ‰ฟ่ฏบ็š„่พน็•Œๅˆฐๅบ•ๆ˜ฏไป€ไนˆ๏ผŸ** ๅœจ"ไป“ๅ†…้™ๆ€ๅฏๅˆ†ๆž็š„ TypeScript"่Œƒๅ›ดๅ†…๏ผŒ้ข„ๆต‹็š„ๅ—ๅฝฑๅ“ๆต‹่ฏ•ๆ–‡ไปถ้›†ๆ˜ฏ็œŸๅฎžๅคฑ่ดฅๆต‹่ฏ•้›†็š„่ถ…้›†ใ€‚้ชŒๆ”ถๆ–นๅผๆ˜ฏไธคไธช็‹ฌ็ซ‹ไป“ๅบ“ไธŠ็š„ๅ˜ๅผ‚ๆต‹่ฏ•โ€”โ€”trpc/trpc 28/28 ่ขซๆ€ๆญปๅ˜ๅผ‚ใ€ardatan/graphql-tools 10/10โ€”โ€”ๅนถ็”ฑๆฏๅ‘จ็š„ [`acceptance`](.github/workflows/acceptance.yml) ๅทฅไฝœๆตๆŠŠๅฎˆ๏ผšๅฌๅ›ž็އไฝŽไบŽ 100% ๅณๅคฑ่ดฅๅนถ่‡ชๅŠจๅผ€ issueใ€‚ไปปไฝ•้™ๆ€ๆ— ๆณ•่งฃๆž็š„ไธœ่ฅฟ้ƒฝ่ฎฐๅ…ฅ็›ฒๅŒบ๏ผŒไธ”้กน็›ฎ่ง„ๅˆ™็ฆๆญขไฝฟ็”จๆ— ้™ๅฎš็š„"้›ถๆผๆŠฅ"่กจ่ฟฐใ€‚
121
+
122
+ **็ฒพ็กฎ็އ่ฟ™ไนˆไฝŽๆ˜ฏ bug ๅ—๏ผŸ** ไธๆ˜ฏ๏ผŒๆ˜ฏ่ขซๆต‹้‡่ฟ‡็š„ๅ–่ˆใ€‚tRPC 30 ๅ˜ๅผ‚้›†ไธŠๅนณๅ‡็ฒพ็กฎ็އ 0.358๏ผŒgraphql-tools 10 ๅ˜ๅผ‚้›†ไธŠ 0.331๏ผŒๅณๅคšๆ•ฐ่ขซ้ข„ๆต‹็š„ๆต‹่ฏ•ๅนถไธไผšๅคฑ่ดฅใ€‚ๅฏน็…งๅฎž้ชŒไธญๆŠŠ็ป“ๆžœ้™ๅˆถๅœจ call ้€š้“๏ผˆๅฏนๅˆๅนถ็ป“ๆžœๅš่ฟ‡ๆปค๏ผŒ่ง `eval/mutation_check.py` ็š„ `predicted_call`๏ผ‰ๅฏๆŠŠ call ้€š้“็ฒพ็กฎ็އๆๅˆฐ 0.702๏ผŒไฝ†ๅฌๅ›ž็އ่ทŒๅˆฐ 14 ไธช่ขซๆ€ๆญปๅ˜ๅผ‚ไธญ็š„ 2 ไธชใ€‚ๅŽๆฅไธ€ๆฌก"ๅ‰ช็บฏ re-export barrel + ๆ”ถ็ช„ๆŽฅๅฃๆ‰‡ๅ‡บ"็š„็ฒพ็กฎ็އไผ˜ๅŒ–่ขซ่‡ชๅทฑ็š„ๆ•ฐๆฎๅฆๅ†ณ๏ผˆๅฟ…็„ถไบง็”ŸๆผๆŠฅ๏ผ‰๏ผŒๅ› ๆญค็ฒพ็กฎ็އไธๅ†ไฝœไธบไผ˜ๅŒ–็›ฎๆ ‡ใ€‚
123
+
124
+ **ๆ”ฏๆŒ Python ๅ—๏ผŸ** ๆ”ฏๆŒ๏ผŒๆ–‡ไปถ็บง๏ผŒๅนถๅธฆ็ฑปๅž‹ๅŒ–่ฐƒ็”จๅขžๅผบ๏ผˆๅ…ทๅๅฏผๅ…ฅ่ฐƒ็”จใ€ๆž„้€ ่ต‹ๅ€ผ/ๆณจ่งฃๆŽจๆ–ญๅ‡บ็š„ๆ–นๆณ•่ฐƒ็”จ๏ผ‰๏ผŒ่ถณไปฅๆ”ฏๆ’‘ๆžถๆž„ๅ›พไธŽๆ–‡ไปถ็บง Change Mapใ€‚ๅ‡ฝๆ•ฐ็บง้›ถๆผๆŠฅๆ‰ฟ่ฏบไปไธบ TypeScript ็‹ฌๆœ‰ใ€‚Python ๅฎžไพ‹่ง [sgp ๆžถๆž„ๅ›พ](https://alloevil.github.io/codeblast/sgp-arch.html)๏ผŒๅ…ถไธญๆฃ€ๅ‡บไบ† `sgp_utils โ‡„ solver_transfer` ๅพช็Žฏไพ่ต–ใ€‚
125
+
126
+ **ๆ€Žไนˆ่ฎฉ AI agent ็”จๅฎƒ๏ผŸ** ็”จ `npx skills add alloevil/codeblast` ่ฃ…ๆˆ skill๏ผŒๆ”นๅ‰่ท‘ `codeblast impact <db> "<symbol>" --json`๏ผŒๆ”นๅŽ่ท‘ `codeblast change <repo> HEAD~1 HEAD --json`ใ€‚[SKILL.md](SKILL.md) ๅ†™ๆ˜Žไบ†ๅ…ณ้”ฎ่งฃ่ฏป็บชๅพ‹๏ผš`blind_spot_count > 0` ๆ—ถ็ฆๆญขๅฃฐ็งฐๆธ…ๅ•ๅฎŒๆ•ดใ€็ฆๆญขไธบไบ†็ผฉ็Ÿญๆธ…ๅ•็ ๆމ `file` ้€š้“ใ€็ฆๆญขๅฏน Python ๅฃฐ็งฐๅ‡ฝๆ•ฐ็บง็ฒพๅบฆใ€`truncated: true` ่ฆๅปบ่ฎฎ่ท‘ๅ…จ้‡ๆต‹่ฏ•ใ€`co_change_hints` ไธ็ญ‰ไบŽๅฝฑๅ“ใ€‚
127
+
128
+ **ๅฏๆ ธๅฏน็š„ๆ•ฐๅญ—ๅœจๅ“ช้‡Œ๏ผŸ** ๅธฆๆŒ‡ๆ ‡ใ€ๆ–นๆณ•ใ€ๅค็Žฐๅ‘ฝไปคๅ’Œ่ฏๆฎ่ทฏๅพ„็š„ๆœบๅ™จๅฏ่ฏปๆธ…ๅ•ๅ‘ๅธƒๅœจ [claims.json](https://alloevil.github.io/codeblast/claims.json)๏ผ›ๅŽŸๅง‹ๅ˜ๅผ‚ไธŽ PR ๅ›žๆ”พๆ•ฐๆฎไฝœไธบ**่ฟ่กŒๅญ˜ๆกฃ**ๆ”พๅœจ [`eval/`](eval/)๏ผˆharness ๅ†™็š„ๆ˜ฏ `/tmp`๏ผŒ่ฟ™ไบ›ๆ–‡ไปถๆ˜ฏไบบๅทฅๆ‹ท่ดใ€ไธๅธฆ commit ๆˆ–็‰ˆๆœฌๆ ‡ๆณจ๏ผŒๅ€™้€‰็”จ `ORDER BY RANDOM()` ๆŠฝๅ–โ€”โ€”ๅฝ“ๅญ˜ๆกฃ่ฏป๏ผŒๅˆซๅฝ“ไฝœไธ€ๆกๅ‘ฝไปคๅฐฑ่ƒฝ้‡่ท‘ๅ‡บๆฅ็š„ไบง็‰ฉ๏ผ‰๏ผ›ๅŒ…ๅซๆฏๆฌก้™็บงไธŽ่ขซๅฆๅ†ณไผ˜ๅŒ–็š„้ชŒๆ”ถๅฐ่ดฆๆ˜ฏ [intent.md](intent.md)ใ€‚
129
+
85
130
  ## ็Šถๆ€ไธŽ่ทฏ็บฟ
86
131
 
87
- M0 ๅ›พ่ฐฑๅผ•ๆ“Ž โ†’ M1 Impact โ†’ M3 ๆžถๆž„ๅ›พ โ†’ M4 ๅ›พ diff + PR bot โ†’ M5 ็ฒพๅบฆๆ‰ฉๅฑ•๏ผŒ**ๅ…จ้ƒจ้ชŒๆ”ถ้€š่ฟ‡**๏ผˆๆฏ้กนๅซๅฏๅค็Žฐ้ชŒๆ”ถ่„šๆœฌ๏ผ‰ใ€‚ๆ–นๆกˆไธŽ้ชŒๆ”ถๆ ‡ๅ‡†็š„ๅ•ไธ€ไบ‹ๅฎžๆบ๏ผš[intent.md](intent.md)ใ€‚
132
+ M0 ๅ›พ่ฐฑๅผ•ๆ“Ž โ†’ M1 Impact โ†’ M3 ๆžถๆž„ๅ›พ โ†’ M4 ๅ›พ diff + PR bot โ†’ M5 ็ฒพๅบฆๆ‰ฉๅฑ•๏ผŒ**ๅ…จ้ƒจไบคไป˜**๏ผ›ๅ…ถไธญไธค้กน้ชŒๆ”ถๆ˜ฏ้™็บง่€Œ้ž้€š่ฟ‡๏ผˆM2 ็š„้ชŒ่ฏๆ–นๅผใ€M3 ๅŽŸๅฎš็š„"้™Œ็”Ÿๅทฅ็จ‹ๅธˆ 10 ๅˆ†้’Ÿ 5 ้—ฎ"ไปŽๆœชๆ‰ง่กŒโ€”โ€”่ง intent.md ็š„ โš ๏ธ๏ผ‰๏ผŒSemArc ๅฏน้ฝๆฃ€ๆŸฅๅทฒไฝœๅบŸใ€‚ๆ–นๆกˆไธŽ้ชŒๆ”ถๆ ‡ๅ‡†็š„ๅ•ไธ€ไบ‹ๅฎžๆบ๏ผš[intent.md](intent.md)ใ€‚
88
133
 
89
134
  MIT ยฉ 2026
package/SKILL.md CHANGED
@@ -52,13 +52,17 @@ codeblast index <repo-root> --db /tmp/graph.db
52
52
  ```
53
53
 
54
54
  Auto-discovers every package `tsconfig.json` in a monorepo and ingests Python via AST. Re-running only
55
- processes files whose content hash changed. Stdout is one JSON object:
55
+ processes files whose content hash changed. Stdout is one JSON object โ€” this is a real run of
56
+ `codeblast index` against [tRPC](https://github.com/trpc/trpc) at commit `66d0544` with the historical codeblast 0.3.0 benchmark
56
57
 
57
58
  ```json
58
- { "db": "...", "seconds": 19.4, "tsconfigs": 12, "files_indexed": 950, "files_skipped": 0,
59
- "nodes": 14200, "edges": 31800, "blind_spots": 412, "failures": 0 }
59
+ { "db": "/tmp/graph.db", "seconds": 5.2, "tsconfigs": 34, "files_indexed": 957, "files_skipped": 0,
60
+ "nodes": 6248, "edges": 17072, "blind_spots": 14725, "failures": 0 }
60
61
  ```
61
62
 
63
+ Note that `blind_spots` is routinely large on a real TypeScript monorepo โ€” it counts unresolved and
64
+ dynamic references, not errors. Judge graph health by `failures`, not by `blind_spots`.
65
+
62
66
  Non-zero exit with `failures > 0` means the graph is incomplete โ€” do not query it; report the failure.
63
67
 
64
68
  ## 2. Impact โ€” what breaks if I change this
@@ -88,9 +92,21 @@ Output (`--json`):
88
92
  via_file: string; via_line: number; // where the dependency occurs (rule 6)
89
93
  }>;
90
94
  co_change_hints: Array<{ file: string; co_commits: number; evidence: string }>; // rule 5
95
+ guidance: {
96
+ review_first: string[]; // call-channel non-test nodes
97
+ run_tests: string[]; // test file paths, de-duplicate before running
98
+ conservative: string[]; // non-test file-channel nodes; never discard
99
+ warnings: string[]; // truncation and blind-spot warnings
100
+ };
91
101
  }
92
102
  ```
93
103
 
104
+ The `guidance` object is a convenience projection of `items`; it does not add analysis results.
105
+ Use `review_first` for the initial callsite checklist, `run_tests` for the affected test files,
106
+ and `conservative` as the import/re-export safety net. `warnings` is non-empty when the result is
107
+ incomplete or the target file contains unresolved analysis. Always retain `via_file` and `via_line`
108
+ when reporting a dependency.
109
+
94
110
  How to use it: `items.filter(level === "direct")` is the callsite checklist. `items.filter(level ===
95
111
  "tests")` de-duplicated by `file` is the test set to run. Test-directory fixtures are included
96
112
  conservatively; estimate test cost by distinct files, not item count.
@@ -148,8 +164,9 @@ Replayed over 50 real commits: 42 stayed silent, 87.5% of the comments posted we
148
164
 
149
165
  - TypeScript, function level: **zero missed impact within statically analyzable scope**, checked by
150
166
  mutation testing (inject a fault, run the real test suite, compare failing tests to the prediction).
151
- Two benchmarks: tRPC (vitest, 950 files) 28/28, graphql-tools (jest, 353 files) 10/10 โ€” precision
152
- 0.33โ€“0.36 overall, โ‰ˆ 0.70โ€“0.92 on the call channel. Both are hard gates in the weekly acceptance
167
+ Two benchmarks: tRPC (vitest, 957 files) 28/28, graphql-tools (jest, 353 files) 10/10 โ€” precision
168
+ 0.33โ€“0.36 overall; the call channel alone is 0.702โ€“0.918 (derived: min/max of the two per-benchmark
169
+ means, see eval/). Both are hard gates in the weekly acceptance
153
170
  workflow, which opens an issue if either drops below 100%.
154
171
  - Conservative edges over-approximate on purpose (an interface method call fans out to every implementer).
155
172
  - Python: file-level; typed calls (`b = Builder(); b.method()`, annotated parameters) are function-level;
package/dist/bin.js CHANGED
@@ -1131,7 +1131,16 @@ var init_impact_cli = __esm(() => {
1131
1131
  result = impact(db2, targetId, maxNodes);
1132
1132
  ms = (performance.now() - t02).toFixed(0);
1133
1133
  if (process.argv.includes("--json")) {
1134
- process.stdout.write(JSON.stringify(result) + `
1134
+ const guidance = {
1135
+ review_first: result.items.filter((it) => it.channel === "call" && it.level !== "tests").map((it) => it.id),
1136
+ run_tests: result.items.filter((it) => it.level === "tests").map((it) => it.file),
1137
+ conservative: result.items.filter((it) => it.channel === "file" && it.level !== "tests").map((it) => it.id),
1138
+ warnings: [
1139
+ ...result.truncated ? ["impact_truncated_run_full_test_suite"] : [],
1140
+ ...result.blind_spot_count > 0 ? ["blind_spots_may_underestimate_impact"] : []
1141
+ ]
1142
+ };
1143
+ process.stdout.write(JSON.stringify({ ...result, guidance }) + `
1135
1144
  `);
1136
1145
  db2.close();
1137
1146
  process.exitCode = 0;
@@ -1436,7 +1445,7 @@ async function layoutGraph(nodes, edges) {
1436
1445
  }))
1437
1446
  };
1438
1447
  }
1439
- var CLIENT_JS, dbPath3, outFlag2, outPath2, overlayFlag, overlayPath, repoFlag, repoUrl, impactFlag, impactTarget, diffFlag, diffBase, db3, overlay, TEST_RE, moduleOf = (file) => {
1448
+ var CLIENT_JS, dbPath3, outFlag2, outPath2, overlayFlag, overlayPath, repoFlag, repoUrl, impactFlag, impactTarget, diffFlag, diffBase, siteFlag, siteUrl, ogImageFlag, ogImage, repoLabel, pageUrl, pageKind, pageTitle, pageDesc, escAttr = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"), headMeta, db3, overlay, TEST_RE, moduleOf = (file) => {
1440
1449
  if (TEST_RE.test(file))
1441
1450
  return "tests";
1442
1451
  const ix = file.indexOf("/");
@@ -1460,6 +1469,51 @@ var init_archmap_html = __esm(async () => {
1460
1469
  impactTarget = impactFlag >= 0 ? process.argv[impactFlag + 1] : undefined;
1461
1470
  diffFlag = process.argv.indexOf("--diff");
1462
1471
  diffBase = diffFlag >= 0 ? process.argv[diffFlag + 1] : undefined;
1472
+ siteFlag = process.argv.indexOf("--site-url");
1473
+ siteUrl = siteFlag >= 0 ? process.argv[siteFlag + 1].replace(/\/+$/, "") : undefined;
1474
+ ogImageFlag = process.argv.indexOf("--og-image");
1475
+ ogImage = ogImageFlag >= 0 ? process.argv[ogImageFlag + 1] : undefined;
1476
+ repoLabel = (() => {
1477
+ const m = repoUrl?.match(/github\.com\/([^/]+\/[^/]+)/);
1478
+ if (m)
1479
+ return m[1].replace(/\.git$/, "");
1480
+ const base = outPath2.split("/").pop() ?? outPath2;
1481
+ return base.replace(/\.html$/, "");
1482
+ })();
1483
+ pageUrl = siteUrl ? `${siteUrl}/${outPath2.split("/").pop()}` : undefined;
1484
+ pageKind = impactTarget ? "blast-radius map" : diffBase ? "change map" : "architecture map";
1485
+ pageTitle = `${repoLabel} โ€” ${pageKind}`;
1486
+ pageDesc = `Interactive architecture map of ${repoLabel} generated by codeblast: a module-level` + ` dependency graph where hovering a node lights up its fan-in and clicking it shows its files,` + ` symbols and import edges.` + (impactTarget ? ` Includes the blast-radius overlay for ${impactTarget}.` : "") + (overlayPath ? " Includes the change overlay." : "");
1487
+ headMeta = [
1488
+ `<title>${escAttr(pageTitle)}</title>`,
1489
+ `<meta name="description" content="${escAttr(pageDesc)}">`,
1490
+ ...pageUrl ? [`<link rel="canonical" href="${escAttr(pageUrl)}">`] : [],
1491
+ `<meta property="og:type" content="website">`,
1492
+ `<meta property="og:title" content="${escAttr(pageTitle)}">`,
1493
+ `<meta property="og:description" content="${escAttr(pageDesc)}">`,
1494
+ ...pageUrl ? [`<meta property="og:url" content="${escAttr(pageUrl)}">`] : [],
1495
+ `<meta property="og:site_name" content="codeblast">`,
1496
+ ...ogImage ? [`<meta property="og:image" content="${escAttr(ogImage)}">`] : [],
1497
+ `<meta name="twitter:card" content="${ogImage ? "summary_large_image" : "summary"}">`,
1498
+ `<meta name="twitter:title" content="${escAttr(pageTitle)}">`,
1499
+ `<meta name="twitter:description" content="${escAttr(pageDesc)}">`,
1500
+ ...ogImage ? [`<meta name="twitter:image" content="${escAttr(ogImage)}">`] : [],
1501
+ `<script type="application/ld+json">`,
1502
+ JSON.stringify({
1503
+ "@context": "https://schema.org",
1504
+ "@type": "WebPage",
1505
+ name: pageTitle,
1506
+ description: pageDesc,
1507
+ inLanguage: "zh-CN",
1508
+ ...pageUrl ? { url: pageUrl } : {},
1509
+ ...repoUrl ? {
1510
+ isBasedOn: repoUrl,
1511
+ about: { "@type": "SoftwareSourceCode", name: repoLabel, codeRepository: repoUrl }
1512
+ } : {}
1513
+ }, null, 2),
1514
+ `</script>`
1515
+ ].join(`
1516
+ `);
1463
1517
  if (!dbPath3) {
1464
1518
  console.error("usage: codeblast archmap <graph.db> --out arch.html");
1465
1519
  process.exit(1);
@@ -1629,10 +1683,10 @@ var init_archmap_html = __esm(async () => {
1629
1683
  fileEdges: importRows
1630
1684
  };
1631
1685
  html = `<!DOCTYPE html>
1632
- <html lang="zh">
1686
+ <html lang="zh-CN">
1633
1687
  <head>
1634
1688
  <meta charset="utf-8">
1635
- <title>codeblast ยท Architecture Map</title>
1689
+ ${headMeta}
1636
1690
  <style>
1637
1691
  :root { --bg:#0d1117; --panel:#161b22; --border:#30363d; --fg:#e6edf3; --dim:#8b949e;
1638
1692
  --accent:#58a6ff; --warn:#f85149; --ok:#3fb950; }
@@ -1671,6 +1725,11 @@ var init_archmap_html = __esm(async () => {
1671
1725
  .edge.cyclic { stroke: var(--warn); stroke-dasharray: 5 3; stroke-width: 2;
1672
1726
  animation: cycflow 1.2s linear infinite; }
1673
1727
  @keyframes cycflow { to { stroke-dashoffset: -16; } }
1728
+ /* ๅพช็Žฏไพ่ต–็”จ่™š็บฟ้™ๆญข่กจ็คบๅณๅฏ โ€”โ€” ่™š็บฟไธŽ้ขœ่‰ฒๆ‰ฟ่ฝฝไฟกๆฏ๏ผŒๆตๅŠจๅชๆ˜ฏ่ฎฉๅฎƒๆ›ดๆ˜พ็œผใ€‚
1729
+ reduced-motion ไธ‹ๅœๆމๅŠจ็”ป๏ผŒ่€Œไธๆ˜ฏๅŽปๆމ่™š็บฟใ€‚ */
1730
+ @media (prefers-reduced-motion: reduce) {
1731
+ .edge.cyclic { animation: none; }
1732
+ }
1674
1733
  .edge.hi { stroke: #d29922 !important; stroke-width: 2.6; stroke-opacity: 1; }
1675
1734
  .edge.dim { stroke-opacity: 0.1; }
1676
1735
  .edge.faint { stroke-opacity: 0.06; }
@@ -1955,6 +2014,54 @@ var init_pr_silence = __esm(() => {
1955
2014
  AUX_RE = /^(www|docs|examples)\//;
1956
2015
  });
1957
2016
 
2017
+ // src/pr-decision.ts
2018
+ function reviewDecision(input) {
2019
+ const { diff, prodNodesAdded, bodyChanged, affectedTests, truncated, blindSpotCount } = input;
2020
+ const apiContractions = diff.visibilityChanged.filter((v) => !v.nowExported).length;
2021
+ const apiChanges = apiContractions + diff.signatureChanged.length;
2022
+ const removals = diff.nodesRemoved.length;
2023
+ const behaviorChanges = bodyChanged.length;
2024
+ const reasons = [];
2025
+ const recommendedActions = [];
2026
+ let risk = "low";
2027
+ if (apiContractions > 0 || removals > 0 || truncated)
2028
+ risk = "high";
2029
+ else if (apiChanges > 0 || behaviorChanges > 0 || affectedTests > 0 || blindSpotCount > 0)
2030
+ risk = "medium";
2031
+ if (apiContractions > 0)
2032
+ reasons.push(`${apiContractions} exported symbol${apiContractions === 1 ? " is" : "s are"} no longer public`);
2033
+ if (removals > 0)
2034
+ reasons.push(`${removals} symbol${removals === 1 ? " was" : "s were"} removed`);
2035
+ if (diff.signatureChanged.length > 0)
2036
+ reasons.push(`${diff.signatureChanged.length} exported signature${diff.signatureChanged.length === 1 ? " changed" : "s changed"}`);
2037
+ if (behaviorChanges > 0)
2038
+ reasons.push(`${behaviorChanges} function bod${behaviorChanges === 1 ? "y" : "ies"} changed`);
2039
+ if (affectedTests > 0)
2040
+ reasons.push(`${affectedTests} test file${affectedTests === 1 ? " is" : "s are"} in the predicted impact set`);
2041
+ if (truncated)
2042
+ reasons.push("the impact set exceeded the reporting limit");
2043
+ if (blindSpotCount > 0)
2044
+ reasons.push(`${blindSpotCount} static-analysis blind spot${blindSpotCount === 1 ? "" : "s"} may hide impact`);
2045
+ if (reasons.length === 0 && prodNodesAdded.length > 0)
2046
+ reasons.push(`${prodNodesAdded.length} production symbol${prodNodesAdded.length === 1 ? " was" : "s were"} added`);
2047
+ if (reasons.length === 0)
2048
+ reasons.push("only low-risk structural additions were found");
2049
+ if (apiChanges > 0 || removals > 0)
2050
+ recommendedActions.push("Review the public API changes and migration impact.");
2051
+ if (truncated)
2052
+ recommendedActions.push("Run the full test suite; the reported impact list is truncated.");
2053
+ else if (affectedTests > 0)
2054
+ recommendedActions.push(`Run the ${affectedTests} predicted affected test file${affectedTests === 1 ? "" : "s"}.`);
2055
+ if (blindSpotCount > 0)
2056
+ recommendedActions.push("Inspect the reported blind spots before treating the impact set as complete.");
2057
+ if (behaviorChanges > 0)
2058
+ recommendedActions.push("Review the changed function bodies even where the exported shape is unchanged.");
2059
+ if (recommendedActions.length === 0)
2060
+ recommendedActions.push("Review the named structural additions; no existing API contraction was detected.");
2061
+ const summary = risk === "high" ? "Review before merge: the change removes API surface, deletes symbols, or has an incomplete impact set." : risk === "medium" ? "Targeted review recommended: behavior, API shape, tests, or blind spots changed." : "Low structural risk: only additive changes with no predicted affected tests were found.";
2062
+ return { risk, summary, reasons, recommendedActions };
2063
+ }
2064
+
1958
2065
  // src/pr-comment.ts
1959
2066
  var exports_pr_comment = {};
1960
2067
  import fs7 from "node:fs";
@@ -1993,7 +2100,7 @@ var args3, repo2, baseSha, headSha, urlFlag, repoUrl2, dbPathA = "/tmp/codeblast
1993
2100
  const wrap = s.kind === "interface" || s.kind === "const" ? (t) => t : (t) => `(${t})`;
1994
2101
  const what = s.kind === "interface" ? "ๆˆๅ‘˜ๅ˜ๅŒ–" : s.kind === "const" ? "็ฑปๅž‹ๅ˜ๅŒ–" : "";
1995
2102
  return `- \`${s.name}\`${what ? ` ${what}` : ""}: \`${wrap(clip(s.from))}\` โ†’ \`${wrap(clip(s.to))}\` ๏ผˆ${link(s.file, s.line)}๏ผ‰`;
1996
- }, apiSig, testSig, uncovered, impactRows, prodNodesAdded, diffLineCount, bodySignal, coreNamed;
2103
+ }, apiSig, testSig, uncovered, impactRows, affectedTestCount = 0, anyImpactTruncated = false, totalBlindSpots = 0, prodNodesAdded, diffLineCount, bodySignal, coreNamed, decision;
1997
2104
  var init_pr_comment = __esm(async () => {
1998
2105
  init_db();
1999
2106
  init_proc();
@@ -2106,6 +2213,9 @@ var init_pr_comment = __esm(async () => {
2106
2213
  const r = impact(dbB2, n.id, 2000);
2107
2214
  const callItems = r.items.filter((i) => i.channel === "call");
2108
2215
  const tests = callItems.filter((i) => i.level === "tests").length;
2216
+ affectedTestCount += tests;
2217
+ anyImpactTruncated ||= r.truncated;
2218
+ totalBlindSpots += r.blind_spot_count;
2109
2219
  impactRows.push(`| \`${n.name}\` | ${n.kind} | ${callItems.length} | ${tests} | ${link(n.file, n.line)} |`);
2110
2220
  const blind = r.blind_spot_count > 0;
2111
2221
  if (tests === 0 && n.kind !== "interface" && n.kind !== "const") {
@@ -2153,16 +2263,101 @@ var init_pr_comment = __esm(async () => {
2153
2263
  if (total2 === 0 && bodyChanged.length > 0) {
2154
2264
  lines3[2] = `**ๆ— ็ป“ๆž„ๅ˜ๆ›ด**,ไฝ†ๆœ‰ ${bodyChanged.length} ไธชๅ‡ฝๆ•ฐไฝ“ๅ†…ๆ”นๅŠจ๏ผˆ่งไธ‹๏ผ‰`;
2155
2265
  }
2266
+ decision = reviewDecision({
2267
+ diff: diff2,
2268
+ prodNodesAdded,
2269
+ bodyChanged,
2270
+ affectedTests: affectedTestCount,
2271
+ truncated: anyImpactTruncated,
2272
+ blindSpotCount: totalBlindSpots
2273
+ });
2274
+ lines3.splice(3, 0, `> **Review decision ยท ${decision.risk.toUpperCase()}** โ€” ${decision.summary}`, `> ${decision.reasons.join("; ")}.`, ``, `### Recommended checks`, ``, ...decision.recommendedActions.map((action) => `- ${action}`), ``);
2156
2275
  lines3.push(`<sub>็”ฑ [codeblast](https://github.com/alloevil/codeblast) ็”Ÿๆˆ ยท ๆฏๆก็ป“่ฎบๅŸบไบŽ้™ๆ€ๅˆ†ๆž,ๅซ่ฏๆฎ้“พๆŽฅ ยท ๅŠจๆ€่ฐƒ็”จ็›ฒๅŒบไธๅœจๆœฌๆŠฅๅ‘Šๅ†… ยท ่ฏ„่ฎบไธๅ‡†?[30 ็ง’ๅ้ฆˆ](https://github.com/alloevil/codeblast/issues/new?template=bot-feedback.yml&title=${encodeURIComponent(`[feedback] ${baseSha.slice(0, 7)}..${headSha.slice(0, 7)}`)})</sub>`);
2157
2276
  console.log(lines3.join(`
2158
2277
  `));
2159
2278
  });
2160
2279
 
2280
+ // src/check-change.ts
2281
+ var exports_check_change = {};
2282
+ import fs8 from "node:fs";
2283
+ function buildGraphAt3(ref, db) {
2284
+ const wt = `/tmp/codeblast-check-${ref.replace(/[^\w]/g, "_")}`;
2285
+ spawnSync(["git", "worktree", "remove", "--force", wt], { cwd: repo3 });
2286
+ const add = spawnSync(["git", "worktree", "add", "--detach", wt, ref], { cwd: repo3 });
2287
+ if (add.exitCode !== 0)
2288
+ throw new Error(add.stderr.slice(0, 300));
2289
+ try {
2290
+ const built = spawnSync(selfCommand("index", wt, "--db", db));
2291
+ if (built.exitCode !== 0)
2292
+ throw new Error(built.stderr.slice(0, 500));
2293
+ } finally {
2294
+ spawnSync(["git", "worktree", "remove", "--force", wt], { cwd: repo3 });
2295
+ }
2296
+ }
2297
+ var repo3, baseSha2, headSha2, dbAPath = "/tmp/codeblast-check-base.db", dbBPath = "/tmp/codeblast-check-head.db", dbA3, dbB3, diff3, prodNodesAdded2, bodyChanged2, total3, affectedTests = 0, truncated = false, blindSpots = 0, diffLineCount2, decision2, output;
2298
+ var init_check_change = __esm(() => {
2299
+ init_db();
2300
+ init_proc();
2301
+ init_impact();
2302
+ init_pr_silence();
2303
+ [repo3, baseSha2, headSha2] = process.argv.slice(2);
2304
+ if (!repo3 || !baseSha2 || !headSha2) {
2305
+ console.error("usage: codeblast check-change <repo> <base-sha> <head-sha> --json");
2306
+ process.exit(1);
2307
+ }
2308
+ for (const f of [dbAPath, dbBPath])
2309
+ for (const suffix of ["", "-wal", "-shm"])
2310
+ fs8.rmSync(f + suffix, { force: true });
2311
+ buildGraphAt3(baseSha2, dbAPath);
2312
+ buildGraphAt3(headSha2, dbBPath);
2313
+ dbA3 = openDatabase(dbAPath, { readonly: true });
2314
+ dbB3 = openDatabase(dbBPath, { readonly: true });
2315
+ diff3 = graphDiff(dbA3, dbB3);
2316
+ prodNodesAdded2 = diff3.nodesAdded.filter((n) => !TEST_RE3.test(n.file));
2317
+ bodyChanged2 = [];
2318
+ total3 = structuralTotal(diff3);
2319
+ 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)) {
2320
+ try {
2321
+ const result = impact(dbB3, node.id, 2000);
2322
+ affectedTests += new Set(result.items.filter((item) => item.level === "tests").map((item) => item.file)).size;
2323
+ truncated ||= result.truncated;
2324
+ blindSpots += result.blind_spot_count;
2325
+ } catch {}
2326
+ }
2327
+ diffLineCount2 = spawnSync(["git", "diff", "--numstat", baseSha2, headSha2], { cwd: repo3 }).stdout.split(`
2328
+ `).reduce((sum, line) => {
2329
+ const match = line.match(/^(\d+)\t(\d+)\t/);
2330
+ return sum + (match ? Number(match[1]) + Number(match[2]) : 0);
2331
+ }, 0);
2332
+ decision2 = reviewDecision({ diff: diff3, prodNodesAdded: prodNodesAdded2, bodyChanged: bodyChanged2, affectedTests, truncated, blindSpotCount: blindSpots });
2333
+ output = {
2334
+ range: `${baseSha2}..${headSha2}`,
2335
+ decision: decision2.risk === "high" ? "review" : decision2.risk === "medium" ? "targeted-review" : "safe-to-review",
2336
+ risk: decision2.risk,
2337
+ summary: decision2.summary,
2338
+ reasons: decision2.reasons,
2339
+ recommended_actions: decision2.recommendedActions,
2340
+ structural_changes: total3,
2341
+ affected_test_files: affectedTests,
2342
+ blind_spot_count: blindSpots,
2343
+ truncated,
2344
+ signals: {
2345
+ core_named: coreNamedCount(diff3, prodNodesAdded2),
2346
+ body: bodySignalCount(bodyChanged2, diffLineCount2, () => false),
2347
+ aux_only: total3 > 0 && coreNamedCount(diff3, prodNodesAdded2) === 0 && bodyChanged2.length === 0
2348
+ },
2349
+ diff: diff3
2350
+ };
2351
+ console.log(JSON.stringify(output));
2352
+ dbA3.close();
2353
+ dbB3.close();
2354
+ });
2355
+
2161
2356
  // src/demo.ts
2162
2357
  var exports_demo = {};
2163
- import fs8 from "node:fs";
2358
+ import fs9 from "node:fs";
2164
2359
  import path3 from "node:path";
2165
- var repo3, db6 = "/tmp/codeblast-demo.db", out = "/tmp/codeblast-demo-arch.html", run = (label, args) => {
2360
+ var repo4, db6 = "/tmp/codeblast-demo.db", out = "/tmp/codeblast-demo-arch.html", run = (label, args) => {
2166
2361
  console.log(`
2167
2362
  \x1B[36mโ–ธ ${label}\x1B[0m`);
2168
2363
  console.log(` $ codeblast ${args.slice(2).join(" ")}`);
@@ -2182,12 +2377,12 @@ var repo3, db6 = "/tmp/codeblast-demo.db", out = "/tmp/codeblast-demo-arch.html"
2182
2377
  var init_demo = __esm(() => {
2183
2378
  init_db();
2184
2379
  init_proc();
2185
- repo3 = path3.resolve(process.argv[2] ?? path3.join(import.meta.dirname, ".."));
2186
- console.log(`codeblast demo โ€” target: ${repo3}`);
2380
+ repo4 = path3.resolve(process.argv[2] ?? path3.join(import.meta.dirname, ".."));
2381
+ console.log(`codeblast demo โ€” target: ${repo4}`);
2187
2382
  for (const s of ["", "-wal", "-shm"])
2188
- fs8.rmSync(db6 + s, { force: true });
2189
- run("1/4 build graph", selfCommand("index", repo3, "--db", db6));
2190
- run("2/4 incremental rerun (should skip everything)", selfCommand("index", repo3, "--db", db6));
2383
+ fs9.rmSync(db6 + s, { force: true });
2384
+ run("1/4 build graph", selfCommand("index", repo4, "--db", db6));
2385
+ run("2/4 incremental rerun (should skip everything)", selfCommand("index", repo4, "--db", db6));
2191
2386
  conn = openDatabase(db6, { readonly: true });
2192
2387
  pick = conn.prepare(`SELECT n.id, COUNT(DISTINCT e.src) c FROM nodes n
2193
2388
  JOIN edges e ON e.dst = n.id AND e.kind = 'calls'
@@ -2212,7 +2407,7 @@ var init_demo = __esm(() => {
2212
2407
 
2213
2408
  // src/name-modules.ts
2214
2409
  var exports_name_modules = {};
2215
- import fs9 from "node:fs";
2410
+ import fs10 from "node:fs";
2216
2411
  var dbPath6, overlayFlag2, overlayPath2, db7, TEST_RE4, files3, byModule, symStmt, evidence, prompt, cmd;
2217
2412
  var init_name_modules = __esm(async () => {
2218
2413
  init_db();
@@ -2250,7 +2445,7 @@ ${JSON.stringify(evidence, null, 1)}`;
2250
2445
  if (!cmd) {
2251
2446
  const applyFlag = process.argv.indexOf("--apply");
2252
2447
  if (applyFlag >= 0) {
2253
- const namesJson = JSON.parse(fs9.readFileSync(process.argv[applyFlag + 1], "utf8"));
2448
+ const namesJson = JSON.parse(fs10.readFileSync(process.argv[applyFlag + 1], "utf8"));
2254
2449
  const overlay = await loadOverlay(overlayPath2);
2255
2450
  for (const [mod, v] of Object.entries(namesJson)) {
2256
2451
  const existing = overlay.modules[mod];
@@ -2259,7 +2454,7 @@ ${JSON.stringify(evidence, null, 1)}`;
2259
2454
  overlay.modules[mod] = { ...existing, name: `${v.name}`, ...v.desc ? {} : {} };
2260
2455
  overlay.modules[mod].desc = v.desc;
2261
2456
  }
2262
- fs9.writeFileSync(overlayPath2, JSON.stringify(overlay, null, 2));
2457
+ fs10.writeFileSync(overlayPath2, JSON.stringify(overlay, null, 2));
2263
2458
  console.error(`overlay written: ${overlayPath2}`);
2264
2459
  } else {
2265
2460
  console.log(prompt);
@@ -2276,7 +2471,7 @@ ${JSON.stringify(evidence, null, 1)}`;
2276
2471
  overlay.modules[mod] = { ...overlay.modules[mod], name: v.name };
2277
2472
  overlay.modules[mod].desc = v.desc;
2278
2473
  }
2279
- fs9.writeFileSync(overlayPath2, JSON.stringify(overlay, null, 2));
2474
+ fs10.writeFileSync(overlayPath2, JSON.stringify(overlay, null, 2));
2280
2475
  console.error(`overlay written: ${overlayPath2}`);
2281
2476
  }
2282
2477
  });
@@ -2290,6 +2485,7 @@ var ROUTES = {
2290
2485
  mermaid: () => Promise.resolve().then(() => (init_archmap(), exports_archmap)),
2291
2486
  cochange: () => Promise.resolve().then(() => (init_cochange(), exports_cochange)),
2292
2487
  "pr-comment": () => init_pr_comment().then(() => exports_pr_comment),
2488
+ "check-change": () => Promise.resolve().then(() => (init_check_change(), exports_check_change)),
2293
2489
  demo: () => Promise.resolve().then(() => (init_demo(), exports_demo)),
2294
2490
  "name-modules": () => init_name_modules().then(() => exports_name_modules)
2295
2491
  };
@@ -2304,9 +2500,12 @@ usage: codeblast <command> [args]
2304
2500
  change <repo> <ref-a> <ref-b> [--json] structural diff between two refs
2305
2501
  archmap <graph.db> --out arch.html interactive architecture map
2306
2502
  [--impact <sym>] [--diff <base.db>] ...with impact / change overlay
2503
+ [--repo-url <url>] names the page and its JSON-LD (already used above)
2504
+ [--site-url <base>] [--og-image <u>] canonical/og for the published page (optional)
2307
2505
  mermaid <graph.db> module map as mermaid
2308
2506
  cochange <repo> <graph.db> mine git history coupling
2309
2507
  pr-comment <repo> <base-sha> <head-sha> PR review comment (silent if no change)
2508
+ check-change <repo> <base-sha> <head-sha> machine-readable merge safety decision
2310
2509
  demo [repo] build + query + map in one shot
2311
2510
 
2312
2511
  docs: https://github.com/alloevil/codeblast ยท demos: https://alloevil.github.io/codeblast/`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeblast",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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",