codeblast 0.3.2 → 0.3.4
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 +103 -16
- package/README.zh-CN.md +1 -1
- package/SKILL.md +64 -3
- package/dist/bin.js +143 -41
- package/package.json +17 -5
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
**codeblast** is
|
|
1
|
+
**codeblast** is the pre-merge blast-radius check for TypeScript monorepos and AI coding agents. It tells you what to review, which tests to run, and when the graph is incomplete — with a source line for every reported edge.
|
|
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"/>
|
|
@@ -15,6 +15,29 @@
|
|
|
15
15
|
<img src="https://img.shields.io/badge/license-MIT-8b949e?style=flat-square" alt="MIT"/>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
+
## Get value in one pull request
|
|
19
|
+
|
|
20
|
+
Copy this workflow into `.github/workflows/codeblast.yml`:
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
name: codeblast
|
|
24
|
+
on: pull_request
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
pull-requests: write
|
|
28
|
+
jobs:
|
|
29
|
+
codeblast:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
with: { fetch-depth: 0 }
|
|
34
|
+
- uses: alloevil/codeblast@v0.3.3
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
On a structural PR, codeblast posts a bounded review decision, affected tests, `file:line` evidence,
|
|
38
|
+
and explicit blind-spot warnings. On a docs-only or otherwise irrelevant PR, it stays silent. It runs
|
|
39
|
+
locally in the runner; source is not uploaded to a codeblast service.
|
|
40
|
+
|
|
18
41
|
## What it is
|
|
19
42
|
|
|
20
43
|
**codeblast parses your repository into a deterministic code graph and answers the three most expensive questions around any code change:**
|
|
@@ -49,7 +72,7 @@ Built for humans (CLI / interactive HTML / PR comments) and for AI agents ([SKIL
|
|
|
49
72
|
```bash
|
|
50
73
|
npx codeblast demo # build a graph of the current repo, run one impact query, emit the map
|
|
51
74
|
npm i -g codeblast # or install globally; needs Node ≥ 22.13 (built-in sqlite) or Bun
|
|
52
|
-
# npm serves 0.3.
|
|
75
|
+
# npm serves 0.3.3
|
|
53
76
|
|
|
54
77
|
# Install as an agent skill (Claude Code, Codex, Cursor, and 14 more harnesses)
|
|
55
78
|
npx skills add alloevil/codeblast
|
|
@@ -57,28 +80,28 @@ npx skills add alloevil/codeblast
|
|
|
57
80
|
|
|
58
81
|
### As a GitHub Action (one line)
|
|
59
82
|
|
|
83
|
+
The smallest useful installation is a PR workflow. Pin the release tag, or pin the commit when your
|
|
84
|
+
repository requires immutable third-party actions:
|
|
85
|
+
|
|
60
86
|
```yaml
|
|
61
|
-
# .github/workflows/codeblast.yml
|
|
62
87
|
name: codeblast
|
|
63
88
|
on: pull_request
|
|
64
89
|
permissions:
|
|
65
90
|
contents: read
|
|
66
91
|
pull-requests: write
|
|
67
92
|
jobs:
|
|
68
|
-
|
|
93
|
+
codeblast:
|
|
69
94
|
runs-on: ubuntu-latest
|
|
70
95
|
steps:
|
|
71
96
|
- uses: actions/checkout@v4
|
|
72
|
-
with: { fetch-depth: 0 }
|
|
73
|
-
- uses: alloevil/codeblast@v0.3.
|
|
97
|
+
with: { fetch-depth: 0 }
|
|
98
|
+
- uses: alloevil/codeblast@v0.3.3
|
|
74
99
|
```
|
|
75
100
|
|
|
76
|
-
The action builds the analyzer from the ref you pinned
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
the commenting step, copy [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml)
|
|
81
|
-
instead — it runs the same command with `npx`.
|
|
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).
|
|
104
|
+
|
|
82
105
|
|
|
83
106
|
## Why not yet another LLM diagram tool
|
|
84
107
|
|
|
@@ -122,6 +145,19 @@ codeblast cochange <repo> graph.db
|
|
|
122
145
|
```
|
|
123
146
|
|
|
124
147
|
### PR bot (runs in CI, stays quiet by default)
|
|
148
|
+
### Reproducible PR cases
|
|
149
|
+
|
|
150
|
+
These are not synthetic diagrams; each case is a committed replay or pilot artifact:
|
|
151
|
+
|
|
152
|
+
| Case | Run it | Reviewer takeaway |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| Function-body behavior change | `codeblast pr-comment <repo> <base> <head>` | A symbol can keep the same shape while its callers still need review. |
|
|
155
|
+
| Exported signature change | `codeblast check-change <repo> <base> <head> --json` | API contraction or signature changes route to targeted review or review. |
|
|
156
|
+
| Incomplete static graph | `codeblast impact <db> <symbol> --json` | `warnings` and `blind_spot_count` prevent an apparently complete answer. |
|
|
157
|
+
|
|
158
|
+
The self-pilot record is [`eval/pilot-2026-09-24.json`](eval/pilot-2026-09-24.json): the published
|
|
159
|
+
package indexed this repository with 0 extraction failures and returned separate review-first and test
|
|
160
|
+
guidance. It is evidence that the workflow runs, not a claim of universal accuracy.
|
|
125
161
|
|
|
126
162
|
Copy [`.github/workflows-template/codeblast.yml`](.github/workflows-template/codeblast.yml) into your repo (it runs `npx codeblast pr-comment`, no other setup):
|
|
127
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**.
|
|
@@ -129,6 +165,48 @@ Replayed against 50 real commits: 42 correctly stayed silent. Comment usefulness
|
|
|
129
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
|
|
130
166
|
authoring agent rated its own comments; both numbers and the fixes that followed each round are logged in
|
|
131
167
|
[intent.md](intent.md).
|
|
168
|
+
## Evidence you can rerun
|
|
169
|
+
|
|
170
|
+
The headline promise is bounded: TypeScript, within the statically analyzable scope, and measured by
|
|
171
|
+
mutation testing against the real test suite. The committed runs are inspectable under [`eval/`](eval/)
|
|
172
|
+
and every published figure has a machine-readable receipt in [`docs/claims.json`](docs/claims.json).
|
|
173
|
+
|
|
174
|
+
| Scenario | Evidence | What it proves |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| tRPC, 30 injected mutations | [`mutation-2026-08-28-trpc-n30.json`](eval/mutation-2026-08-28-trpc-n30.json) | 28/28 killed mutants recalled; 2 were not killed by the suite |
|
|
177
|
+
| graphql-tools, Jest | [`mutation-2026-09-07-graphql-tools-n10.json`](eval/mutation-2026-09-07-graphql-tools-n10.json) | 10/10 killed mutants recalled across a second workspace layout |
|
|
178
|
+
| Real package pilot | [`pilot-2026-09-24.json`](eval/pilot-2026-09-24.json) | 32 files indexed, 0 extraction failures, guidance separated from repository-wide blind spots |
|
|
179
|
+
|
|
180
|
+
The pilot is not a benchmark and does not establish a universal accuracy rate. It is a reproducible
|
|
181
|
+
smoke run of the published package against this repository.
|
|
182
|
+
|
|
183
|
+
### A reviewer's decision, not a diagram
|
|
184
|
+
|
|
185
|
+
For agents and CI, `check-change --json` returns a routing decision plus evidence:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"decision": "targeted-review",
|
|
190
|
+
"risk": "medium",
|
|
191
|
+
"affected_test_files": 3,
|
|
192
|
+
"blind_spot_count": 0,
|
|
193
|
+
"graph_health": {"warnings": []}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### What the numbers do and do not mean
|
|
198
|
+
|
|
199
|
+
The 28/28 and 10/10 figures are mutation-testing recall on two pinned repositories. They mean every
|
|
200
|
+
test file that failed for each killed mutation was present in the predicted set **within the measured
|
|
201
|
+
static-analysis boundary**. They do not mean codeblast catches every production regression, understands
|
|
202
|
+
dynamic runtime behavior, or provides function-level guarantees for Python.
|
|
203
|
+
|
|
204
|
+
The conservative import/file channel is deliberately retained: a controlled call-only ablation reached
|
|
205
|
+
better precision but recalled only 2/14 killed mutations. Treat `call` items as the first reading list,
|
|
206
|
+
the complete result as the test safety net, and every blind spot as an explicit limit.
|
|
207
|
+
`review` means inspect before merge; it does not mean the tool has proven the change unsafe. A nonzero
|
|
208
|
+
graph failure or an incomplete impact result is a reason to stop and inspect, not a reason to hide the
|
|
209
|
+
uncertainty.
|
|
132
210
|
|
|
133
211
|
## The precision promise (bounded, and evidence-backed)
|
|
134
212
|
|
|
@@ -174,13 +252,22 @@ safe to merge. `review` is required when API surface contracts, removed symbols,
|
|
|
174
252
|
results are detected.
|
|
175
253
|
## For AI agents
|
|
176
254
|
|
|
177
|
-
```
|
|
255
|
+
```text
|
|
178
256
|
before editing: impact "symbol" --json → callsite list into context, so nothing gets missed
|
|
179
|
-
after editing: change
|
|
257
|
+
after editing: check-change repo base head --json → risk, graph health, affected tests and warnings
|
|
180
258
|
```
|
|
181
259
|
|
|
182
|
-
The full contract and interpretation discipline (including
|
|
183
|
-
Agent conventions: [AGENTS.md](AGENTS.md).
|
|
260
|
+
The full contract and interpretation discipline (including “never pretend the impact list is complete”)
|
|
261
|
+
is in [SKILL.md](SKILL.md). Agent conventions: [AGENTS.md](AGENTS.md).
|
|
262
|
+
|
|
263
|
+
## Help improve the reviewer
|
|
264
|
+
|
|
265
|
+
Found a false positive, missed impact, noisy comment, or wrong silence decision? Open a
|
|
266
|
+
[privacy-safe bot feedback issue](https://github.com/alloevil/codeblast/issues/new?template=bot-feedback.yml).
|
|
267
|
+
Share the public PR URL and a redacted explanation; never paste source code, secrets, private diffs,
|
|
268
|
+
or full repository contents. Reproducible benchmark results and pilot evidence belong in [`eval/`](eval/),
|
|
269
|
+
not in issue comments.
|
|
270
|
+
|
|
184
271
|
|
|
185
272
|
## FAQ
|
|
186
273
|
|
package/README.zh-CN.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
```bash
|
|
34
34
|
npx codeblast demo # 给当前仓库建图、跑一次 impact 查询、导出架构图
|
|
35
35
|
npm i -g codeblast # 或全局安装;需要 Node ≥ 22.13(内置 sqlite)或 Bun
|
|
36
|
-
# npm 上的版本是 0.3.
|
|
36
|
+
# npm 上的版本是 0.3.3,与本仓库一致
|
|
37
37
|
|
|
38
38
|
# 作为 agent skill 安装(Claude Code、Codex、Cursor 等)
|
|
39
39
|
npx skills add alloevil/codeblast
|
package/SKILL.md
CHANGED
|
@@ -9,18 +9,79 @@ Three deterministic queries over a graph built by `tsc` (TypeScript, function-le
|
|
|
9
9
|
Python AST (file-level with typed-call upgrades). Every result carries the `file:line` where the
|
|
10
10
|
dependency actually occurs. The graph comes from the code, not from a model's reading of it.
|
|
11
11
|
|
|
12
|
-
## When to run it
|
|
13
|
-
|
|
14
12
|
| Situation | Command | What you get back |
|
|
15
13
|
|---|---|---|
|
|
16
|
-
| About to edit an exported symbol | `codeblast impact <db> "<symbol>" --json` |
|
|
14
|
+
| About to edit an exported symbol | `codeblast impact <db> "<symbol>" --json` | `guidance.review_first`, `guidance.run_tests`, conservative items and warnings |
|
|
17
15
|
| Finished a multi-file change; verifying scope | `codeblast change <repo> HEAD~1 HEAD --json` | Symbols and dependency edges added / removed / renamed |
|
|
16
|
+
| Need one merge-safety decision | `codeblast check-change <repo> <base> <head> --json` | Risk, decision, graph health, affected tests and recommended actions |
|
|
18
17
|
| Need to understand an unfamiliar repo | `codeblast archmap <db> --out arch.html` | Module → file → symbol map with cycle detection |
|
|
19
18
|
| Reviewing a PR | `codeblast pr-comment <repo> <base> <head>` | Markdown review comment; empty output when nothing structural changed |
|
|
20
19
|
|
|
21
20
|
Prerequisites: Node ≥ 22.13 or Bun ≥ 1.0 (`npx codeblast` works with no install); `python3` for Python
|
|
22
21
|
repos; the target repo's dependencies installed (missing `node_modules` turns external calls into blind spots).
|
|
23
22
|
|
|
23
|
+
|
|
24
|
+
Recommended agent loop:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
1. index the repository and check `failures == 0`
|
|
28
|
+
2. before editing: read `impact --json` and start with `guidance.review_first`
|
|
29
|
+
3. make the change
|
|
30
|
+
4. run `check-change --json` against base and head
|
|
31
|
+
5. run distinct files from `guidance.run_tests` / `affected_test_files`
|
|
32
|
+
6. report `warnings`, blind spots, and every `via_file:via_line` evidence location
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Never turn a `safe-to-review` routing result into “safe to merge”; the command routes attention and
|
|
36
|
+
|
|
37
|
+
`check-change --json` has `schema_version: "1"` and `engine_version`. Its exit codes are stable:
|
|
38
|
+
|
|
39
|
+
- `0`: analysis completed with no high-risk or graph-health warning;
|
|
40
|
+
- `1`: analysis completed but routes the change to review (`high` risk or incomplete graph);
|
|
41
|
+
- `2`: command, repository, worktree, or graph construction error.
|
|
42
|
+
|
|
43
|
+
`impact --json` also has `schema_version: "1"`; every guidance array is de-duplicated and sorted for
|
|
44
|
+
stable agent diffs. Treat the schema version as a compatibility boundary, not as a prose suggestion.
|
|
45
|
+
|
|
46
|
+
Machine-readable compatibility contract:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
impact --json: schema_version=1, guidance arrays sorted and de-duplicated
|
|
50
|
+
check-change --json: schema_version=1, engine_version, decision, risk, graph_health
|
|
51
|
+
exit 0: result complete and no high-risk routing
|
|
52
|
+
exit 1: result complete but review routing is required
|
|
53
|
+
exit 2: analysis or repository error; do not consume the result as evidence
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
When a future schema version appears, stop and read its contract before making decisions from fields
|
|
57
|
+
that are not explicitly understood. Do not silently fall back to prose parsing.
|
|
58
|
+
|
|
59
|
+
The formal schemas are committed at [`eval/check-change.schema.json`](eval/check-change.schema.json)
|
|
60
|
+
and [`eval/impact.schema.json`](eval/impact.schema.json). Version policy: within schema version `1`,
|
|
61
|
+
new optional fields may be added, but existing field meanings, enum values, array ordering, and exit
|
|
62
|
+
codes remain stable. A breaking field removal, type change, or enum change requires a new schema
|
|
63
|
+
version and an explicit migration note. Consumers must reject unknown schema versions rather than
|
|
64
|
+
guessing.
|
|
65
|
+
|
|
66
|
+
The supported agent-facing artifacts are the two version-one JSON contracts and the smoke command:
|
|
67
|
+
`bun run agent-smoke` (or `node eval/agent-workflow-smoke.mjs` after building). A consumer integration
|
|
68
|
+
should fail closed when the schema version is unknown, and should preserve the complete JSON artifact
|
|
69
|
+
alongside its human summary for later review.
|
|
70
|
+
|
|
71
|
+
Continuous evolution outputs:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bun run release-smoke # package install and executable surface
|
|
75
|
+
bun run agent-smoke # end-to-end safety JSON contract
|
|
76
|
+
bun run pilot-summary # committed pilot graph/impact summary
|
|
77
|
+
bun run guidance-stability # deterministic guidance sample
|
|
78
|
+
bun run validate-compatibility # compatibility sample boundary check
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Each command should either produce a small machine-readable result or fail. Keep the result with the
|
|
82
|
+
release or compatibility evidence; a green test suite without a current evidence sample is not a
|
|
83
|
+
complete evolution cycle.
|
|
84
|
+
tests. The graph health and warning fields are part of the contract.
|
|
24
85
|
## Interpretation rules — read before running
|
|
25
86
|
|
|
26
87
|
These are the mistakes an agent makes with this tool. Each one has produced a wrong answer in practice.
|
package/dist/bin.js
CHANGED
|
@@ -238,15 +238,30 @@ class Extractor {
|
|
|
238
238
|
rel(fileName) {
|
|
239
239
|
return path.relative(this.rootDir, fileName);
|
|
240
240
|
}
|
|
241
|
+
safeSymbolAt(node) {
|
|
242
|
+
try {
|
|
243
|
+
return this.checker.getSymbolAtLocation(node);
|
|
244
|
+
} catch {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
safeAliased(symbol) {
|
|
249
|
+
if (!(symbol.flags & ts.SymbolFlags.Alias))
|
|
250
|
+
return symbol;
|
|
251
|
+
try {
|
|
252
|
+
return this.checker.getAliasedSymbol(symbol);
|
|
253
|
+
} catch {
|
|
254
|
+
return symbol;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
241
257
|
collectImplementers() {
|
|
242
258
|
for (const sf of this.sourceFiles()) {
|
|
243
259
|
const visit = (node) => {
|
|
244
260
|
if (ts.isClassDeclaration(node) && node.heritageClauses) {
|
|
245
261
|
for (const clause of node.heritageClauses) {
|
|
246
262
|
for (const typeNode of clause.types) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
sym = this.checker.getAliasedSymbol(sym);
|
|
263
|
+
const raw = this.safeSymbolAt(typeNode.expression);
|
|
264
|
+
const sym = raw ? this.safeAliased(raw) : undefined;
|
|
250
265
|
const decl = sym?.declarations?.[0];
|
|
251
266
|
if (!decl)
|
|
252
267
|
continue;
|
|
@@ -289,18 +304,27 @@ class Extractor {
|
|
|
289
304
|
if (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt)) {
|
|
290
305
|
const spec = stmt.moduleSpecifier;
|
|
291
306
|
if (spec && ts.isStringLiteral(spec)) {
|
|
292
|
-
|
|
307
|
+
let resolved;
|
|
308
|
+
try {
|
|
309
|
+
resolved = this.resolveModule(spec.text, sf.fileName);
|
|
310
|
+
} catch {
|
|
311
|
+
blindSpots.push({ file: relPath, line: lineOf(stmt), reason: `module resolution failed: ${spec.text}`, src_file: relPath });
|
|
312
|
+
}
|
|
293
313
|
if (!resolved) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
try {
|
|
315
|
+
for (const entry of this.externalReentry(spec.text, sf.fileName)) {
|
|
316
|
+
edges.push({
|
|
317
|
+
src: relPath,
|
|
318
|
+
dst: this.rel(entry),
|
|
319
|
+
kind: "imports",
|
|
320
|
+
file: relPath,
|
|
321
|
+
line: lineOf(stmt),
|
|
322
|
+
confidence: "conservative",
|
|
323
|
+
src_file: relPath
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
blindSpots.push({ file: relPath, line: lineOf(stmt), reason: `external reentry resolution failed: ${spec.text}`, src_file: relPath });
|
|
304
328
|
}
|
|
305
329
|
} else {
|
|
306
330
|
edges.push({
|
|
@@ -429,9 +453,8 @@ class Extractor {
|
|
|
429
453
|
for (const clause of node.heritageClauses) {
|
|
430
454
|
const ek = clause.token === ts.SyntaxKind.ImplementsKeyword ? "implements" : "extends";
|
|
431
455
|
for (const t of clause.types) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
sym = this.checker.getAliasedSymbol(sym);
|
|
456
|
+
const raw = this.safeSymbolAt(t.expression);
|
|
457
|
+
const sym = raw ? this.safeAliased(raw) : undefined;
|
|
435
458
|
const decl = sym?.declarations?.[0];
|
|
436
459
|
const dst = decl ? this.nodeIdOfDecl(decl) : undefined;
|
|
437
460
|
if (dst)
|
|
@@ -489,9 +512,8 @@ class Extractor {
|
|
|
489
512
|
}
|
|
490
513
|
resolveCall(call, caller, relPath, line, edges, blindSpots) {
|
|
491
514
|
const expr = call.expression;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
sym = this.checker.getAliasedSymbol(sym);
|
|
515
|
+
const raw = this.safeSymbolAt(expr);
|
|
516
|
+
const sym = raw ? this.safeAliased(raw) : undefined;
|
|
495
517
|
const decl = sym?.valueDeclaration ?? sym?.declarations?.[0];
|
|
496
518
|
if (!decl) {
|
|
497
519
|
const structural = ts.isElementAccessExpression(expr) || ts.isPropertyAccessExpression(expr) && ["call", "apply", "bind"].includes(expr.name.text);
|
|
@@ -709,7 +731,11 @@ function indexProgram(extractor) {
|
|
|
709
731
|
} catch (err) {
|
|
710
732
|
failures++;
|
|
711
733
|
seenFiles.delete(relPath);
|
|
712
|
-
|
|
734
|
+
const detail = err instanceof Error ? `${err.name}: ${err.message}${err.stack ? `
|
|
735
|
+
${err.stack.split(`
|
|
736
|
+
`).slice(1, 12).join(`
|
|
737
|
+
`)}` : ""}` : String(err);
|
|
738
|
+
console.error(`EXTRACT FAILED ${relPath}: ${detail}`);
|
|
713
739
|
}
|
|
714
740
|
}
|
|
715
741
|
}
|
|
@@ -873,6 +899,13 @@ var init_cli = __esm(() => {
|
|
|
873
899
|
if (pyFiles > 0)
|
|
874
900
|
console.error(`python files ingested: ${pyFiles}`);
|
|
875
901
|
}
|
|
902
|
+
if (failures === 0) {
|
|
903
|
+
const stale = db.prepare("SELECT path FROM files").all().filter((row) => !seenFiles.has(row.path));
|
|
904
|
+
transaction(db, () => {
|
|
905
|
+
for (const row of stale)
|
|
906
|
+
invalidateFile(db, row.path);
|
|
907
|
+
})();
|
|
908
|
+
}
|
|
876
909
|
dt = ((performance.now() - t0) / 1000).toFixed(1);
|
|
877
910
|
console.log(JSON.stringify({
|
|
878
911
|
db: dbPath,
|
|
@@ -1131,16 +1164,17 @@ var init_impact_cli = __esm(() => {
|
|
|
1131
1164
|
result = impact(db2, targetId, maxNodes);
|
|
1132
1165
|
ms = (performance.now() - t02).toFixed(0);
|
|
1133
1166
|
if (process.argv.includes("--json")) {
|
|
1167
|
+
const uniqueSorted = (values) => [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
1134
1168
|
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),
|
|
1169
|
+
review_first: uniqueSorted(result.items.filter((it) => it.channel === "call" && it.level !== "tests").map((it) => it.id)),
|
|
1170
|
+
run_tests: uniqueSorted(result.items.filter((it) => it.level === "tests").map((it) => it.file)),
|
|
1171
|
+
conservative: uniqueSorted(result.items.filter((it) => it.channel === "file" && it.level !== "tests").map((it) => it.id)),
|
|
1138
1172
|
warnings: [
|
|
1139
1173
|
...result.truncated ? ["impact_truncated_run_full_test_suite"] : [],
|
|
1140
1174
|
...result.blind_spot_count > 0 ? ["blind_spots_may_underestimate_impact"] : []
|
|
1141
1175
|
]
|
|
1142
1176
|
};
|
|
1143
|
-
process.stdout.write(JSON.stringify({ ...result, guidance }) + `
|
|
1177
|
+
process.stdout.write(JSON.stringify({ schema_version: "1", ...result, guidance }) + `
|
|
1144
1178
|
`);
|
|
1145
1179
|
db2.close();
|
|
1146
1180
|
process.exitCode = 0;
|
|
@@ -2006,7 +2040,7 @@ function bodySignalCount(bodyChanged, diffLineCount, hasCallImpact) {
|
|
|
2006
2040
|
return n;
|
|
2007
2041
|
}
|
|
2008
2042
|
function coreNamedCount(diff, prodNodesAdded) {
|
|
2009
|
-
return diff.edgesAdded.filter((e) => !AUX_RE.test(e.file)).length + prodNodesAdded.filter((n) => !AUX_RE.test(n.file)).length + diff.renamed.filter((r) => !AUX_RE.test(r.file)).length + diff.visibilityChanged.filter((v) => !AUX_RE.test(v.file)).length + diff.signatureChanged.filter((s) => !AUX_RE.test(s.file)).length;
|
|
2043
|
+
return diff.edgesAdded.filter((e) => !AUX_RE.test(e.file)).length + diff.edgesRemoved.filter((e) => !AUX_RE.test(e.file)).length + prodNodesAdded.filter((n) => !AUX_RE.test(n.file)).length + diff.nodesRemoved.filter((n) => !AUX_RE.test(n.file) && !TEST_RE3.test(n.file)).length + diff.renamed.filter((r) => !AUX_RE.test(r.file)).length + diff.visibilityChanged.filter((v) => !AUX_RE.test(v.file)).length + diff.signatureChanged.filter((s) => !AUX_RE.test(s.file)).length;
|
|
2010
2044
|
}
|
|
2011
2045
|
var TEST_RE3, AUX_RE, BIG_DIFF_LINES = 40;
|
|
2012
2046
|
var init_pr_silence = __esm(() => {
|
|
@@ -2019,6 +2053,7 @@ function reviewDecision(input) {
|
|
|
2019
2053
|
const { diff, prodNodesAdded, bodyChanged, affectedTests, truncated, blindSpotCount } = input;
|
|
2020
2054
|
const apiContractions = diff.visibilityChanged.filter((v) => !v.nowExported).length;
|
|
2021
2055
|
const apiChanges = apiContractions + diff.signatureChanged.length;
|
|
2056
|
+
const renames = diff.renamed.length;
|
|
2022
2057
|
const removals = diff.nodesRemoved.length;
|
|
2023
2058
|
const behaviorChanges = bodyChanged.length;
|
|
2024
2059
|
const reasons = [];
|
|
@@ -2026,7 +2061,7 @@ function reviewDecision(input) {
|
|
|
2026
2061
|
let risk = "low";
|
|
2027
2062
|
if (apiContractions > 0 || removals > 0 || truncated)
|
|
2028
2063
|
risk = "high";
|
|
2029
|
-
else if (apiChanges > 0 || behaviorChanges > 0 || affectedTests > 0 || blindSpotCount > 0)
|
|
2064
|
+
else if (apiChanges > 0 || renames > 0 || behaviorChanges > 0 || affectedTests > 0 || blindSpotCount > 0)
|
|
2030
2065
|
risk = "medium";
|
|
2031
2066
|
if (apiContractions > 0)
|
|
2032
2067
|
reasons.push(`${apiContractions} exported symbol${apiContractions === 1 ? " is" : "s are"} no longer public`);
|
|
@@ -2034,10 +2069,10 @@ function reviewDecision(input) {
|
|
|
2034
2069
|
reasons.push(`${removals} symbol${removals === 1 ? " was" : "s were"} removed`);
|
|
2035
2070
|
if (diff.signatureChanged.length > 0)
|
|
2036
2071
|
reasons.push(`${diff.signatureChanged.length} exported signature${diff.signatureChanged.length === 1 ? " changed" : "s changed"}`);
|
|
2072
|
+
if (renames > 0)
|
|
2073
|
+
reasons.push(`${renames} symbol${renames === 1 ? " was" : "s were"} renamed`);
|
|
2037
2074
|
if (behaviorChanges > 0)
|
|
2038
2075
|
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
2076
|
if (truncated)
|
|
2042
2077
|
reasons.push("the impact set exceeded the reporting limit");
|
|
2043
2078
|
if (blindSpotCount > 0)
|
|
@@ -2100,7 +2135,7 @@ var args3, repo2, baseSha, headSha, urlFlag, repoUrl2, dbPathA = "/tmp/codeblast
|
|
|
2100
2135
|
const wrap = s.kind === "interface" || s.kind === "const" ? (t) => t : (t) => `(${t})`;
|
|
2101
2136
|
const what = s.kind === "interface" ? "成员变化" : s.kind === "const" ? "类型变化" : "";
|
|
2102
2137
|
return `- \`${s.name}\`${what ? ` ${what}` : ""}: \`${wrap(clip(s.from))}\` → \`${wrap(clip(s.to))}\` (${link(s.file, s.line)})`;
|
|
2103
|
-
}, apiSig, testSig, uncovered, impactRows, affectedTestCount = 0, anyImpactTruncated = false, totalBlindSpots = 0, prodNodesAdded, diffLineCount, bodySignal, coreNamed, decision;
|
|
2138
|
+
}, apiSig, testSig, uncovered, impactRows, affectedTestCount = 0, anyImpactTruncated = false, totalBlindSpots = 0, prodNodesAdded, diffLineCount, bodySignal, coreNamed, changedFiles, changedTests, testedBodySignal, decision;
|
|
2104
2139
|
var init_pr_comment = __esm(async () => {
|
|
2105
2140
|
init_db();
|
|
2106
2141
|
init_proc();
|
|
@@ -2170,7 +2205,9 @@ var init_pr_comment = __esm(async () => {
|
|
|
2170
2205
|
if (diff2.renamed.length > 0) {
|
|
2171
2206
|
lines3.push(`### 重命名`, ``);
|
|
2172
2207
|
for (const r of diff2.renamed.slice(0, 10)) {
|
|
2173
|
-
|
|
2208
|
+
const targetName = r.to.includes("#") ? r.to.split("#").pop() : r.to;
|
|
2209
|
+
const target = dbB2.prepare("SELECT line FROM nodes WHERE file = ? AND name = ? ORDER BY line LIMIT 1").get(r.file, targetName);
|
|
2210
|
+
lines3.push(`- \`${r.from}\` → \`${r.to}\` (${r.kind})${target ? ` (${link(r.file, target.line)})` : ""}`);
|
|
2174
2211
|
}
|
|
2175
2212
|
if (diff2.renamed.length > 10)
|
|
2176
2213
|
lines3.push(`- …及另外 ${diff2.renamed.length - 10} 项`);
|
|
@@ -2242,7 +2279,11 @@ var init_pr_comment = __esm(async () => {
|
|
|
2242
2279
|
}
|
|
2243
2280
|
});
|
|
2244
2281
|
coreNamed = coreNamedCount(diff2, prodNodesAdded);
|
|
2245
|
-
|
|
2282
|
+
changedFiles = spawnSync(["git", "diff", "--name-only", baseSha, headSha], { cwd: repo2 }).stdout.split(`
|
|
2283
|
+
`).filter(Boolean);
|
|
2284
|
+
changedTests = changedFiles.some((file) => TEST_RE3.test(file));
|
|
2285
|
+
testedBodySignal = bodyChanged.length > 0 && changedTests ? 1 : 0;
|
|
2286
|
+
if (coreNamed + bodySignal + testedBodySignal === 0)
|
|
2246
2287
|
process.exit(0);
|
|
2247
2288
|
if (bodyChanged.length > 0) {
|
|
2248
2289
|
const rows = [];
|
|
@@ -2280,6 +2321,7 @@ var init_pr_comment = __esm(async () => {
|
|
|
2280
2321
|
// src/check-change.ts
|
|
2281
2322
|
var exports_check_change = {};
|
|
2282
2323
|
import fs8 from "node:fs";
|
|
2324
|
+
import path3 from "node:path";
|
|
2283
2325
|
function buildGraphAt3(ref, db) {
|
|
2284
2326
|
const wt = `/tmp/codeblast-check-${ref.replace(/[^\w]/g, "_")}`;
|
|
2285
2327
|
spawnSync(["git", "worktree", "remove", "--force", wt], { cwd: repo3 });
|
|
@@ -2294,12 +2336,33 @@ function buildGraphAt3(ref, db) {
|
|
|
2294
2336
|
spawnSync(["git", "worktree", "remove", "--force", wt], { cwd: repo3 });
|
|
2295
2337
|
}
|
|
2296
2338
|
}
|
|
2297
|
-
var
|
|
2339
|
+
var SCHEMA_VERSION = "1", EXIT_OK = 0, EXIT_REVIEW = 1, EXIT_ERROR = 2, readVersion = () => {
|
|
2340
|
+
try {
|
|
2341
|
+
const parsed = JSON.parse(fs8.readFileSync(path3.join(import.meta.dirname, "..", "package.json"), "utf8"));
|
|
2342
|
+
if (parsed && typeof parsed === "object" && "version" in parsed && typeof parsed.version === "string")
|
|
2343
|
+
return parsed.version;
|
|
2344
|
+
} catch {}
|
|
2345
|
+
return "unknown";
|
|
2346
|
+
}, ENGINE_VERSION, repo3, baseSha2, headSha2, dbAPath = "/tmp/codeblast-check-base.db", dbBPath = "/tmp/codeblast-check-head.db", dbA3, dbB3, graphHealth = (db) => ({
|
|
2347
|
+
files: Number(db.prepare("SELECT COUNT(*) c FROM files").get().c),
|
|
2348
|
+
nodes: Number(db.prepare("SELECT COUNT(*) c FROM nodes").get().c),
|
|
2349
|
+
edges: Number(db.prepare("SELECT COUNT(*) c FROM edges").get().c),
|
|
2350
|
+
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;
|
|
2298
2352
|
var init_check_change = __esm(() => {
|
|
2299
2353
|
init_db();
|
|
2300
2354
|
init_proc();
|
|
2301
2355
|
init_impact();
|
|
2302
2356
|
init_pr_silence();
|
|
2357
|
+
ENGINE_VERSION = readVersion();
|
|
2358
|
+
process.on("uncaughtException", (error) => {
|
|
2359
|
+
console.error(`check-change analysis error: ${error instanceof Error ? error.message : error}`);
|
|
2360
|
+
process.exitCode = EXIT_ERROR;
|
|
2361
|
+
});
|
|
2362
|
+
process.on("unhandledRejection", (reason) => {
|
|
2363
|
+
console.error(`check-change analysis error: ${reason instanceof Error ? reason.message : reason}`);
|
|
2364
|
+
process.exitCode = EXIT_ERROR;
|
|
2365
|
+
});
|
|
2303
2366
|
[repo3, baseSha2, headSha2] = process.argv.slice(2);
|
|
2304
2367
|
if (!repo3 || !baseSha2 || !headSha2) {
|
|
2305
2368
|
console.error("usage: codeblast check-change <repo> <base-sha> <head-sha> --json");
|
|
@@ -2312,9 +2375,14 @@ var init_check_change = __esm(() => {
|
|
|
2312
2375
|
buildGraphAt3(headSha2, dbBPath);
|
|
2313
2376
|
dbA3 = openDatabase(dbAPath, { readonly: true });
|
|
2314
2377
|
dbB3 = openDatabase(dbBPath, { readonly: true });
|
|
2378
|
+
healthBase = graphHealth(dbA3);
|
|
2379
|
+
healthHead = graphHealth(dbB3);
|
|
2315
2380
|
diff3 = graphDiff(dbA3, dbB3);
|
|
2381
|
+
healthWarnings = [
|
|
2382
|
+
...healthHead.files === 0 || healthHead.nodes === 0 ? ["graph_empty"] : [],
|
|
2383
|
+
...healthHead.nodes < healthBase.nodes / 2 ? ["graph_node_count_dropped_sharply"] : []
|
|
2384
|
+
];
|
|
2316
2385
|
prodNodesAdded2 = diff3.nodesAdded.filter((n) => !TEST_RE3.test(n.file));
|
|
2317
|
-
bodyChanged2 = [];
|
|
2318
2386
|
total3 = structuralTotal(diff3);
|
|
2319
2387
|
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
2388
|
try {
|
|
@@ -2324,13 +2392,48 @@ var init_check_change = __esm(() => {
|
|
|
2324
2392
|
blindSpots += result.blind_spot_count;
|
|
2325
2393
|
} catch {}
|
|
2326
2394
|
}
|
|
2395
|
+
bodyChanged2 = [];
|
|
2396
|
+
structuralIds2 = new Set([
|
|
2397
|
+
...diff3.nodesAdded.map((node) => node.id),
|
|
2398
|
+
...diff3.renamed.map((rename) => `${rename.file}#${rename.to}`)
|
|
2399
|
+
]);
|
|
2400
|
+
diffText = spawnSync(["git", "diff", "--unified=0", baseSha2, headSha2, "--", "*.ts", "*.tsx"], { cwd: repo3 }).stdout;
|
|
2401
|
+
for (const line of diffText.split(`
|
|
2402
|
+
`)) {
|
|
2403
|
+
const file = line.match(/^\+\+\+ b\/(.+)$/);
|
|
2404
|
+
if (file) {
|
|
2405
|
+
currentFile = file[1];
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
2409
|
+
if (!hunk || !currentFile || TEST_RE3.test(currentFile) || AUX_RE.test(currentFile))
|
|
2410
|
+
continue;
|
|
2411
|
+
const row = dbB3.prepare("SELECT id, name, kind, file, line FROM nodes WHERE file = ? AND kind IN ('function','method') AND line <= ? AND end_line >= ? ORDER BY (end_line - line) ASC LIMIT 1").get(currentFile, Number(hunk[1]), Number(hunk[1]));
|
|
2412
|
+
if (row && !structuralIds2.has(row.id) && !bodyChanged2.some((item) => item.id === row.id))
|
|
2413
|
+
bodyChanged2.push(row);
|
|
2414
|
+
}
|
|
2327
2415
|
diffLineCount2 = spawnSync(["git", "diff", "--numstat", baseSha2, headSha2], { cwd: repo3 }).stdout.split(`
|
|
2328
2416
|
`).reduce((sum, line) => {
|
|
2329
2417
|
const match = line.match(/^(\d+)\t(\d+)\t/);
|
|
2330
2418
|
return sum + (match ? Number(match[1]) + Number(match[2]) : 0);
|
|
2331
2419
|
}, 0);
|
|
2420
|
+
changedFiles2 = spawnSync(["git", "diff", "--name-only", baseSha2, headSha2], { cwd: repo3 }).stdout.split(`
|
|
2421
|
+
`).filter(Boolean);
|
|
2422
|
+
auxOnly = changedFiles2.length > 0 && changedFiles2.every((file) => AUX_RE.test(file));
|
|
2332
2423
|
decision2 = reviewDecision({ diff: diff3, prodNodesAdded: prodNodesAdded2, bodyChanged: bodyChanged2, affectedTests, truncated, blindSpotCount: blindSpots });
|
|
2424
|
+
if (healthWarnings.length > 0) {
|
|
2425
|
+
decision2.risk = "high";
|
|
2426
|
+
decision2.reasons.push(...healthWarnings);
|
|
2427
|
+
decision2.recommendedActions.unshift("Rebuild or inspect the graph before relying on this decision.");
|
|
2428
|
+
} else if (auxOnly) {
|
|
2429
|
+
decision2.risk = "low";
|
|
2430
|
+
decision2.summary = "Low structural risk: only auxiliary-directory changes were found.";
|
|
2431
|
+
decision2.reasons = ["only auxiliary-directory changes were found"];
|
|
2432
|
+
decision2.recommendedActions = ["No code review signal; inspect the auxiliary change directly if needed."];
|
|
2433
|
+
}
|
|
2333
2434
|
output = {
|
|
2435
|
+
schema_version: SCHEMA_VERSION,
|
|
2436
|
+
engine_version: ENGINE_VERSION,
|
|
2334
2437
|
range: `${baseSha2}..${headSha2}`,
|
|
2335
2438
|
decision: decision2.risk === "high" ? "review" : decision2.risk === "medium" ? "targeted-review" : "safe-to-review",
|
|
2336
2439
|
risk: decision2.risk,
|
|
@@ -2340,23 +2443,25 @@ var init_check_change = __esm(() => {
|
|
|
2340
2443
|
structural_changes: total3,
|
|
2341
2444
|
affected_test_files: affectedTests,
|
|
2342
2445
|
blind_spot_count: blindSpots,
|
|
2446
|
+
graph_health: { base: healthBase, head: healthHead, warnings: healthWarnings },
|
|
2343
2447
|
truncated,
|
|
2344
2448
|
signals: {
|
|
2345
2449
|
core_named: coreNamedCount(diff3, prodNodesAdded2),
|
|
2346
2450
|
body: bodySignalCount(bodyChanged2, diffLineCount2, () => false),
|
|
2347
|
-
aux_only:
|
|
2451
|
+
aux_only: auxOnly
|
|
2348
2452
|
},
|
|
2349
2453
|
diff: diff3
|
|
2350
2454
|
};
|
|
2351
2455
|
console.log(JSON.stringify(output));
|
|
2352
2456
|
dbA3.close();
|
|
2353
2457
|
dbB3.close();
|
|
2458
|
+
process.exitCode = healthWarnings.length > 0 || decision2.risk === "high" ? EXIT_REVIEW : EXIT_OK;
|
|
2354
2459
|
});
|
|
2355
2460
|
|
|
2356
2461
|
// src/demo.ts
|
|
2357
2462
|
var exports_demo = {};
|
|
2358
2463
|
import fs9 from "node:fs";
|
|
2359
|
-
import
|
|
2464
|
+
import path4 from "node:path";
|
|
2360
2465
|
var repo4, db6 = "/tmp/codeblast-demo.db", out = "/tmp/codeblast-demo-arch.html", run = (label, args) => {
|
|
2361
2466
|
console.log(`
|
|
2362
2467
|
\x1B[36m▸ ${label}\x1B[0m`);
|
|
@@ -2377,7 +2482,7 @@ var repo4, db6 = "/tmp/codeblast-demo.db", out = "/tmp/codeblast-demo-arch.html"
|
|
|
2377
2482
|
var init_demo = __esm(() => {
|
|
2378
2483
|
init_db();
|
|
2379
2484
|
init_proc();
|
|
2380
|
-
repo4 =
|
|
2485
|
+
repo4 = path4.resolve(process.argv[2] ?? path4.join(import.meta.dirname, ".."));
|
|
2381
2486
|
console.log(`codeblast demo — target: ${repo4}`);
|
|
2382
2487
|
for (const s of ["", "-wal", "-shm"])
|
|
2383
2488
|
fs9.rmSync(db6 + s, { force: true });
|
|
@@ -2501,15 +2606,12 @@ usage: codeblast <command> [args]
|
|
|
2501
2606
|
archmap <graph.db> --out arch.html interactive architecture map
|
|
2502
2607
|
[--impact <sym>] [--diff <base.db>] ...with impact / change overlay
|
|
2503
2608
|
[--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)
|
|
2505
|
-
mermaid <graph.db> module map as mermaid
|
|
2506
|
-
cochange <repo> <graph.db> mine git history coupling
|
|
2507
2609
|
pr-comment <repo> <base-sha> <head-sha> PR review comment (silent if no change)
|
|
2508
2610
|
check-change <repo> <base-sha> <head-sha> machine-readable merge safety decision
|
|
2509
2611
|
demo [repo] build + query + map in one shot
|
|
2510
2612
|
|
|
2511
2613
|
docs: https://github.com/alloevil/codeblast · demos: https://alloevil.github.io/codeblast/`);
|
|
2512
|
-
process.exit(cmd2
|
|
2614
|
+
process.exit(cmd2 === "--help" || cmd2 === "-h" || !cmd2 ? 0 : 1);
|
|
2513
2615
|
}
|
|
2514
2616
|
process.argv.splice(2, 1);
|
|
2515
2617
|
await ROUTES[cmd2]();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeblast",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"type": "module",
|
|
28
28
|
"bin": {
|
|
29
|
-
"codeblast": "
|
|
29
|
+
"codeblast": "dist/bin.js"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
@@ -38,8 +38,14 @@
|
|
|
38
38
|
"build": "bun build src/bin.ts --target=node --outdir=dist --entry-naming=bin.js --external typescript --external @dagrejs/dagre && cp src/archmap-client.js src/py_extract.py dist/",
|
|
39
39
|
"prepack": "bun run build",
|
|
40
40
|
"typecheck": "tsc -p .",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
41
|
+
"agent-smoke": "bun run build && node eval/agent-workflow-smoke.mjs",
|
|
42
|
+
"guidance-stability": "node eval/guidance-stability.mjs",
|
|
43
|
+
"incremental-equivalence": "bun run build && node eval/incremental-equivalence.mjs",
|
|
44
|
+
"offline-replay": "bun run build && node eval/offline-replay.mjs",
|
|
45
|
+
"pilot-summary": "node eval/pilot-summary.mjs",
|
|
46
|
+
"release-smoke": "node eval/release-smoke.mjs",
|
|
47
|
+
"validate-compatibility": "node eval/validate-compatibility.mjs",
|
|
48
|
+
"validate-safety": "node eval/validate-safety.mjs",
|
|
43
49
|
"verify": "python3 eval/mutation_check.py"
|
|
44
50
|
},
|
|
45
51
|
"engines": {
|
|
@@ -52,5 +58,11 @@
|
|
|
52
58
|
"devDependencies": {
|
|
53
59
|
"@types/node": "^26.4.0",
|
|
54
60
|
"bun-types": "^1.4.0"
|
|
55
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"main": "index.js",
|
|
63
|
+
"directories": {
|
|
64
|
+
"doc": "docs",
|
|
65
|
+
"test": "test"
|
|
66
|
+
},
|
|
67
|
+
"author": ""
|
|
56
68
|
}
|