codeblast 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/SKILL.md +3 -2
- package/dist/bin.js +47 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,9 +94,10 @@ Replayed against 50 real commits: 42 correctly stayed silent, 87.5% of comments
|
|
|
94
94
|
|
|
95
95
|
- **TypeScript at function level: zero missed impact within statically analyzable scope.** Verified by mutation testing:
|
|
96
96
|
inject mutations into a real repo → run the full test suite to get the ground-truth impact set → compare against predictions.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
Two benchmarks, both hard gates in the weekly acceptance workflow: **tRPC** (vitest, 950 files) **28/28**
|
|
98
|
+
and **graphql-tools** (jest + npm workspaces, 353 files) **10/10** — 100% recall on each, average precision
|
|
99
|
+
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/).
|
|
100
101
|
- **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.
|
|
101
102
|
- **Python is file-level.** Dynamic typing makes function-level zero-miss guarantees impossible in principle, and we don't pretend otherwise.
|
|
102
103
|
|
package/SKILL.md
CHANGED
|
@@ -148,8 +148,9 @@ Replayed over 50 real commits: 42 stayed silent, 87.5% of the comments posted we
|
|
|
148
148
|
|
|
149
149
|
- TypeScript, function level: **zero missed impact within statically analyzable scope**, checked by
|
|
150
150
|
mutation testing (inject a fault, run the real test suite, compare failing tests to the prediction).
|
|
151
|
-
tRPC
|
|
152
|
-
|
|
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
|
|
153
|
+
workflow, which opens an issue if either drops below 100%.
|
|
153
154
|
- Conservative edges over-approximate on purpose (an interface method call fans out to every implementer).
|
|
154
155
|
- Python: file-level; typed calls (`b = Builder(); b.method()`, annotated parameters) are function-level;
|
|
155
156
|
untyped attribute chains fall back to file level and are recorded as blind spots.
|
package/dist/bin.js
CHANGED
|
@@ -1058,7 +1058,47 @@ var init_impact = __esm(() => {
|
|
|
1058
1058
|
|
|
1059
1059
|
// src/impact-cli.ts
|
|
1060
1060
|
var exports_impact_cli = {};
|
|
1061
|
-
|
|
1061
|
+
function printHuman() {
|
|
1062
|
+
const byLevel = { direct: 0, indirect: 0, tests: 0 };
|
|
1063
|
+
for (const it of result.items)
|
|
1064
|
+
byLevel[it.level]++;
|
|
1065
|
+
const callItems = result.items.filter((it) => it.channel === "call");
|
|
1066
|
+
const fileNamed = result.items.filter((it) => it.channel === "file" && !it.named_miss);
|
|
1067
|
+
const fileUnnamed = result.items.filter((it) => it.channel === "file" && it.named_miss);
|
|
1068
|
+
console.log(`target: ${result.target}`);
|
|
1069
|
+
console.log(`impact: ${result.items.length} nodes (direct=${byLevel.direct} indirect=${byLevel.indirect} tests=${byLevel.tests})${result.truncated ? " [TRUNCATED — wide impact, run the full suite]" : ""}`);
|
|
1070
|
+
console.log(` ├─ call-graph reachable (high confidence): ${callItems.length}`);
|
|
1071
|
+
console.log(` ├─ reachable via named import: ${fileNamed.length}`);
|
|
1072
|
+
console.log(` └─ reachable via unnamed import (execution closure, conservative — do not skip): ${fileUnnamed.length}`);
|
|
1073
|
+
if (result.blind_spot_count > 0)
|
|
1074
|
+
console.log(`blind spots in target file: ${result.blind_spot_count} (impact may be underestimated)`);
|
|
1075
|
+
if (result.co_change_hints.length > 0) {
|
|
1076
|
+
console.log(`co-change hints (no static edge, but historically changed together):`);
|
|
1077
|
+
for (const h of result.co_change_hints)
|
|
1078
|
+
console.log(` ~ ${h.file} (${h.co_commits} co-commits, ${h.evidence})`);
|
|
1079
|
+
}
|
|
1080
|
+
console.log(`query: ${ms}ms
|
|
1081
|
+
`);
|
|
1082
|
+
const testItems = result.items.filter((it) => it.level === "tests");
|
|
1083
|
+
const callNonTest = callItems.filter((it) => it.level !== "tests");
|
|
1084
|
+
const fileNonTest = [...fileNamed, ...fileUnnamed].filter((it) => it.level !== "tests");
|
|
1085
|
+
const shown = showAll ? [...callItems, ...fileNamed, ...fileUnnamed] : [...callNonTest, ...testItems];
|
|
1086
|
+
const LIMIT = 40;
|
|
1087
|
+
for (const it of shown.slice(0, LIMIT)) {
|
|
1088
|
+
const conf = it.confidence === "conservative" ? " ~" : "";
|
|
1089
|
+
const ch = it.channel === "file" ? it.named_miss ? " ·closure" : " ·import" : "";
|
|
1090
|
+
console.log(` [${it.level}${conf}${ch}] ${it.id} (${it.kind}, ${it.hops} hop, via ${it.via_file}:${it.via_line})`);
|
|
1091
|
+
}
|
|
1092
|
+
if (shown.length > LIMIT)
|
|
1093
|
+
console.log(` ... and ${shown.length - LIMIT} more`);
|
|
1094
|
+
if (!showAll && fileNonTest.length > 0) {
|
|
1095
|
+
const files = new Set(fileNonTest.map((it) => it.file)).size;
|
|
1096
|
+
console.log(`
|
|
1097
|
+
+ ${fileNonTest.length} conservative items across ${files} files reachable only via imports — --all to list them.`);
|
|
1098
|
+
console.log(` They are part of the no-false-negative promise: run the tests above, do not treat this list as noise.`);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
var dbPath2, query, maxFlag, maxNodes, showAll, db2, targetId, exact, t02, result, ms;
|
|
1062
1102
|
var init_impact_cli = __esm(() => {
|
|
1063
1103
|
init_db();
|
|
1064
1104
|
init_impact();
|
|
@@ -1069,6 +1109,7 @@ var init_impact_cli = __esm(() => {
|
|
|
1069
1109
|
}
|
|
1070
1110
|
maxFlag = process.argv.indexOf("--max");
|
|
1071
1111
|
maxNodes = maxFlag >= 0 ? Number(process.argv[maxFlag + 1]) : 500;
|
|
1112
|
+
showAll = process.argv.includes("--all");
|
|
1072
1113
|
db2 = openDatabase(dbPath2, { readonly: true });
|
|
1073
1114
|
targetId = query;
|
|
1074
1115
|
exact = db2.prepare("SELECT id FROM nodes WHERE id = ?").get(query);
|
|
@@ -1090,37 +1131,13 @@ var init_impact_cli = __esm(() => {
|
|
|
1090
1131
|
result = impact(db2, targetId, maxNodes);
|
|
1091
1132
|
ms = (performance.now() - t02).toFixed(0);
|
|
1092
1133
|
if (process.argv.includes("--json")) {
|
|
1093
|
-
|
|
1094
|
-
process.exit(0);
|
|
1095
|
-
}
|
|
1096
|
-
byLevel = { direct: 0, indirect: 0, tests: 0 };
|
|
1097
|
-
for (const it of result.items)
|
|
1098
|
-
byLevel[it.level]++;
|
|
1099
|
-
callItems = result.items.filter((it) => it.channel === "call");
|
|
1100
|
-
fileNamed = result.items.filter((it) => it.channel === "file" && !it.named_miss);
|
|
1101
|
-
fileUnnamed = result.items.filter((it) => it.channel === "file" && it.named_miss);
|
|
1102
|
-
console.log(`target: ${result.target}`);
|
|
1103
|
-
console.log(`impact: ${result.items.length} nodes (direct=${byLevel.direct} indirect=${byLevel.indirect} tests=${byLevel.tests})${result.truncated ? " [TRUNCATED — wide impact, run the full suite]" : ""}`);
|
|
1104
|
-
console.log(` ├─ call-graph reachable (high confidence): ${callItems.length}`);
|
|
1105
|
-
console.log(` ├─ reachable via named import: ${fileNamed.length}`);
|
|
1106
|
-
console.log(` └─ reachable via unnamed import (execution closure, conservative — do not skip): ${fileUnnamed.length}`);
|
|
1107
|
-
if (result.blind_spot_count > 0)
|
|
1108
|
-
console.log(`blind spots in target file: ${result.blind_spot_count} (impact may be underestimated)`);
|
|
1109
|
-
if (result.co_change_hints.length > 0) {
|
|
1110
|
-
console.log(`co-change hints (no static edge, but historically changed together):`);
|
|
1111
|
-
for (const h of result.co_change_hints)
|
|
1112
|
-
console.log(` ~ ${h.file} (${h.co_commits} co-commits, ${h.evidence})`);
|
|
1113
|
-
}
|
|
1114
|
-
console.log(`query: ${ms}ms
|
|
1134
|
+
process.stdout.write(JSON.stringify(result) + `
|
|
1115
1135
|
`);
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
console.log(` [${it.level}${conf}${ch}] ${it.id} (${it.kind}, ${it.hops} hop, via ${it.via_file}:${it.via_line})`);
|
|
1136
|
+
db2.close();
|
|
1137
|
+
process.exitCode = 0;
|
|
1138
|
+
} else {
|
|
1139
|
+
printHuman();
|
|
1121
1140
|
}
|
|
1122
|
-
if (ordered.length > 40)
|
|
1123
|
-
console.log(` ... and ${ordered.length - 40} more`);
|
|
1124
1141
|
});
|
|
1125
1142
|
|
|
1126
1143
|
// src/graph-diff.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeblast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|