chiasmus 0.1.10 → 0.1.12
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 +24 -0
- package/dist/formalize/validate.js +78 -11
- package/dist/formalize/validate.js.map +1 -1
- package/dist/graph/analyses.d.ts +1 -1
- package/dist/graph/analyses.d.ts.map +1 -1
- package/dist/graph/analyses.js +78 -8
- package/dist/graph/analyses.js.map +1 -1
- package/dist/graph/extractor.d.ts.map +1 -1
- package/dist/graph/extractor.js +108 -56
- package/dist/graph/extractor.js.map +1 -1
- package/dist/graph/facts.d.ts +2 -0
- package/dist/graph/facts.d.ts.map +1 -1
- package/dist/graph/facts.js +20 -6
- package/dist/graph/facts.js.map +1 -1
- package/dist/graph/mermaid.d.ts.map +1 -1
- package/dist/graph/mermaid.js +3 -7
- package/dist/graph/mermaid.js.map +1 -1
- package/dist/graph/parser.d.ts.map +1 -1
- package/dist/graph/parser.js +19 -5
- package/dist/graph/parser.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +2 -0
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/openai-compatible.d.ts.map +1 -1
- package/dist/llm/openai-compatible.js +2 -0
- package/dist/llm/openai-compatible.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +91 -3
- package/dist/mcp-server.js.map +1 -1
- package/dist/review.d.ts +43 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +338 -0
- package/dist/review.js.map +1 -0
- package/dist/skills/bm25.d.ts +4 -0
- package/dist/skills/bm25.d.ts.map +1 -1
- package/dist/skills/bm25.js +41 -0
- package/dist/skills/bm25.js.map +1 -1
- package/dist/skills/library.d.ts +1 -0
- package/dist/skills/library.d.ts.map +1 -1
- package/dist/skills/library.js +52 -21
- package/dist/skills/library.js.map +1 -1
- package/dist/skills/relationships.d.ts.map +1 -1
- package/dist/skills/relationships.js +25 -0
- package/dist/skills/relationships.js.map +1 -1
- package/dist/skills/starters.d.ts.map +1 -1
- package/dist/skills/starters.js +449 -0
- package/dist/skills/starters.js.map +1 -1
- package/dist/solvers/correction-loop.d.ts.map +1 -1
- package/dist/solvers/correction-loop.js +8 -16
- package/dist/solvers/correction-loop.js.map +1 -1
- package/dist/solvers/prolog-solver.d.ts.map +1 -1
- package/dist/solvers/prolog-solver.js +58 -46
- package/dist/solvers/prolog-solver.js.map +1 -1
- package/dist/solvers/types.d.ts +6 -0
- package/dist/solvers/types.d.ts.map +1 -1
- package/dist/solvers/z3-solver.d.ts +5 -0
- package/dist/solvers/z3-solver.d.ts.map +1 -1
- package/dist/solvers/z3-solver.js +30 -7
- package/dist/solvers/z3-solver.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ MCP server that gives LLMs access to formal verification via Z3 (SMT solver) and
|
|
|
11
11
|
- **"Does our workflow have dead-end or unreachable states?"** → Prolog checks reachability from the initial state, identifies orphaned and terminal nodes
|
|
12
12
|
- **"What's the dead code in this module?"** → tree-sitter parses source files, Prolog finds functions unreachable from any entry point
|
|
13
13
|
- **"What breaks if I change this function?"** → call graph impact analysis shows all transitive callers
|
|
14
|
+
- **"Do a full code review of these files"** → `chiasmus_review` returns a phased recipe of graph analyses + verification templates, and you execute it step-by-step
|
|
14
15
|
|
|
15
16
|
## Setup
|
|
16
17
|
|
|
@@ -142,6 +143,29 @@ chiasmus_craft name="api-rate-limit" domain="configuration" solver="z3"
|
|
|
142
143
|
|
|
143
144
|
After creation, the template appears in `chiasmus_skills` searches and `chiasmus_formalize`.
|
|
144
145
|
|
|
146
|
+
**`chiasmus_review`** — Returns a phased code-review recipe: which chiasmus tools and templates to run, in what order, and what to look for. No side effects — pure scaffolding. Execute phases sequentially using the named tools, then produce a final report per the `reporting` section.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
chiasmus_review files=["src/handler.ts", "src/db.ts"] focus="all"
|
|
150
|
+
→ {
|
|
151
|
+
phases: [
|
|
152
|
+
{ phase: "1. Structural overview", actions: [{tool: "chiasmus_graph", args: {analysis: "summary"}, interpret: "..."}] },
|
|
153
|
+
{ phase: "2. Architecture health", actions: [dead-code, cycles, layer-violation] },
|
|
154
|
+
{ phase: "3. Security — data flow and taint", actions: [facts + chiasmus_formalize taint-propagation] },
|
|
155
|
+
{ phase: "4. Resource safety", actions: [association-rule-check] },
|
|
156
|
+
{ phase: "5. Authorization", actions: [policy-contradiction] },
|
|
157
|
+
{ phase: "6. Correctness — invariants, boundaries, state machines", actions: [...] },
|
|
158
|
+
{ phase: "7. Impact analysis on flagged functions", actions: [chiasmus_graph impact] },
|
|
159
|
+
],
|
|
160
|
+
suggestedTemplates: [...],
|
|
161
|
+
reporting: { format: "Numbered issue list with severity", severityLevels: ["CRITICAL","HIGH","MEDIUM","LOW","INFO"] }
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Focus modes subset the phases: `all` (default, 7 phases), `quick` (overview + architecture), `architecture` (structural defects + impact), `security` (taint + resource pairing + auth), `correctness` (invariants + boundaries + state machines).
|
|
166
|
+
|
|
167
|
+
Each action carries an `interpret` field describing how to score the result. After all phases, emit a numbered issue list with severity labels and file:line references.
|
|
168
|
+
|
|
145
169
|
**`chiasmus_learn`** — Extract a reusable template from a verified solution. Candidates get promoted after 3+ successful reuses.
|
|
146
170
|
|
|
147
171
|
**`chiasmus_lint`** — Fast structural validation of specs without running the solver.
|
|
@@ -92,17 +92,12 @@ function lintSmtlib(spec, fixes, errors) {
|
|
|
92
92
|
}
|
|
93
93
|
return { spec: cleaned };
|
|
94
94
|
}
|
|
95
|
-
function lintProlog(spec,
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
const stripped = cleaned
|
|
101
|
-
.replace(/%.*$/gm, "")
|
|
102
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
103
|
-
.replace(/"[^"]*"/g, '""')
|
|
104
|
-
.replace(/'[^']*'/g, "''")
|
|
105
|
-
.trim();
|
|
95
|
+
function lintProlog(spec, _fixes, errors) {
|
|
96
|
+
const cleaned = spec;
|
|
97
|
+
// Context-aware strip of comments and quoted literals. Naive regex
|
|
98
|
+
// stripping misparses `%` inside atoms as line comments, and `''` inside
|
|
99
|
+
// atoms as quote open/close — both cause false-positive errors.
|
|
100
|
+
const stripped = stripPrologNoise(cleaned).trim();
|
|
106
101
|
if (!stripped)
|
|
107
102
|
return { spec: cleaned };
|
|
108
103
|
// Check: at least one clause ending with a period
|
|
@@ -126,4 +121,76 @@ function lintProlog(spec, fixes, errors) {
|
|
|
126
121
|
}
|
|
127
122
|
return { spec: cleaned };
|
|
128
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Remove Prolog comments and quoted literal contents without breaking inside
|
|
126
|
+
* strings/atoms. Replaces quoted literals with an empty placeholder (`''` or
|
|
127
|
+
* `""`) so paren-balance checks still work on the surrounding structure.
|
|
128
|
+
*/
|
|
129
|
+
function stripPrologNoise(src) {
|
|
130
|
+
let out = "";
|
|
131
|
+
let i = 0;
|
|
132
|
+
const n = src.length;
|
|
133
|
+
while (i < n) {
|
|
134
|
+
const ch = src[i];
|
|
135
|
+
// Line comment — only outside quotes
|
|
136
|
+
if (ch === "%") {
|
|
137
|
+
while (i < n && src[i] !== "\n")
|
|
138
|
+
i++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Block comment — only outside quotes
|
|
142
|
+
if (ch === "/" && src[i + 1] === "*") {
|
|
143
|
+
i += 2;
|
|
144
|
+
while (i < n && !(src[i] === "*" && src[i + 1] === "/"))
|
|
145
|
+
i++;
|
|
146
|
+
i += 2;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// Single-quoted atom
|
|
150
|
+
if (ch === "'") {
|
|
151
|
+
i++;
|
|
152
|
+
while (i < n) {
|
|
153
|
+
if (src[i] === "\\" && i + 1 < n) {
|
|
154
|
+
i += 2;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (src[i] === "'" && src[i + 1] === "'") {
|
|
158
|
+
i += 2;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (src[i] === "'") {
|
|
162
|
+
i++;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
i++;
|
|
166
|
+
}
|
|
167
|
+
out += "''";
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// Double-quoted string
|
|
171
|
+
if (ch === '"') {
|
|
172
|
+
i++;
|
|
173
|
+
while (i < n) {
|
|
174
|
+
if (src[i] === "\\" && i + 1 < n) {
|
|
175
|
+
i += 2;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (src[i] === '"' && src[i + 1] === '"') {
|
|
179
|
+
i += 2;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (src[i] === '"') {
|
|
183
|
+
i++;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
out += '""';
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
out += ch;
|
|
192
|
+
i++;
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
129
196
|
//# sourceMappingURL=validate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/formalize/validate.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/formalize/validate.ts"],"names":[],"mappings":"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"}
|
package/dist/graph/analyses.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CodeGraph } from "./types.js";
|
|
2
|
-
export type AnalysisType = "summary" | "callers" | "callees" | "reachability" | "dead-code" | "cycles" | "path" | "impact" | "facts";
|
|
2
|
+
export type AnalysisType = "summary" | "callers" | "callees" | "reachability" | "dead-code" | "cycles" | "path" | "impact" | "facts" | "layer-violation";
|
|
3
3
|
export interface AnalysisRequest {
|
|
4
4
|
analysis: AnalysisType;
|
|
5
5
|
target?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyses.d.ts","sourceRoot":"","sources":["../../src/graph/analyses.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"analyses.d.ts","sourceRoot":"","sources":["../../src/graph/analyses.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C,MAAM,MAAM,YAAY,GACpB,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAClD,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GACpD,iBAAiB,CAAC;AAEtB,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qDAAqD;AACrD,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAgDzB;AAED,yEAAyE;AACzE,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CA2BzB"}
|
package/dist/graph/analyses.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { extractGraph } from "./extractor.js";
|
|
3
3
|
import { graphToProlog, escapeAtom } from "./facts.js";
|
|
4
4
|
import { createPrologSolver } from "../solvers/prolog-solver.js";
|
|
5
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
6
|
+
// Graph analyses run system-generated Prolog (not user input) and walk
|
|
7
|
+
// cycle-safe reachability rules that are O(n²) per step. The default
|
|
8
|
+
// 100 000 inference budget gets exhausted on mid-size codebases (a few
|
|
9
|
+
// hundred functions), so we raise it for analyses.
|
|
10
|
+
const GRAPH_MAX_INFERENCES = 5_000_000;
|
|
5
11
|
/** Run a graph analysis on the given source files */
|
|
6
12
|
export async function runAnalysis(filePaths, request) {
|
|
7
|
-
// Read files from disk
|
|
8
|
-
const files =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
// Read files from disk with size check
|
|
14
|
+
const files = [];
|
|
15
|
+
for (const p of filePaths) {
|
|
16
|
+
try {
|
|
17
|
+
const stat = statSync(p);
|
|
18
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
19
|
+
continue; // skip oversized files
|
|
20
|
+
}
|
|
21
|
+
files.push({ path: p, content: readFileSync(p, "utf-8") });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
continue; // skip unreadable files
|
|
25
|
+
}
|
|
26
|
+
}
|
|
12
27
|
const graph = await extractGraph(files);
|
|
13
28
|
const program = graphToProlog(graph, request.entryPoints);
|
|
14
29
|
if (request.analysis === "facts") {
|
|
@@ -20,13 +35,19 @@ export async function runAnalysis(filePaths, request) {
|
|
|
20
35
|
result: buildSummary(graph),
|
|
21
36
|
};
|
|
22
37
|
}
|
|
38
|
+
if (request.analysis === "layer-violation") {
|
|
39
|
+
return {
|
|
40
|
+
analysis: "layer-violation",
|
|
41
|
+
result: findLayerViolations(graph),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
23
44
|
const query = buildQuery(request);
|
|
24
45
|
if (!query) {
|
|
25
46
|
return { analysis: request.analysis, result: { error: "Missing required parameters" } };
|
|
26
47
|
}
|
|
27
48
|
const solver = createPrologSolver();
|
|
28
49
|
try {
|
|
29
|
-
const solverResult = await solver.solve({ type: "prolog", program, query });
|
|
50
|
+
const solverResult = await solver.solve({ type: "prolog", program, query, maxInferences: GRAPH_MAX_INFERENCES });
|
|
30
51
|
return { analysis: request.analysis, result: formatResult(request.analysis, solverResult) };
|
|
31
52
|
}
|
|
32
53
|
finally {
|
|
@@ -42,19 +63,68 @@ export async function runAnalysisFromGraph(graph, request) {
|
|
|
42
63
|
if (request.analysis === "summary") {
|
|
43
64
|
return { analysis: "summary", result: buildSummary(graph) };
|
|
44
65
|
}
|
|
66
|
+
if (request.analysis === "layer-violation") {
|
|
67
|
+
return { analysis: "layer-violation", result: findLayerViolations(graph) };
|
|
68
|
+
}
|
|
45
69
|
const query = buildQuery(request);
|
|
46
70
|
if (!query) {
|
|
47
71
|
return { analysis: request.analysis, result: { error: "Missing required parameters" } };
|
|
48
72
|
}
|
|
49
73
|
const solver = createPrologSolver();
|
|
50
74
|
try {
|
|
51
|
-
const solverResult = await solver.solve({ type: "prolog", program, query });
|
|
75
|
+
const solverResult = await solver.solve({ type: "prolog", program, query, maxInferences: GRAPH_MAX_INFERENCES });
|
|
52
76
|
return { analysis: request.analysis, result: formatResult(request.analysis, solverResult) };
|
|
53
77
|
}
|
|
54
78
|
finally {
|
|
55
79
|
solver.dispose();
|
|
56
80
|
}
|
|
57
81
|
}
|
|
82
|
+
const LAYER_ORDER = {
|
|
83
|
+
handlers: 0,
|
|
84
|
+
routes: 0,
|
|
85
|
+
controllers: 0,
|
|
86
|
+
services: 1,
|
|
87
|
+
repositories: 2,
|
|
88
|
+
db: 3,
|
|
89
|
+
models: 3,
|
|
90
|
+
};
|
|
91
|
+
function extractLayer(filePath) {
|
|
92
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
93
|
+
const segments = normalized.split("/");
|
|
94
|
+
for (const seg of segments) {
|
|
95
|
+
if (seg in LAYER_ORDER)
|
|
96
|
+
return seg;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function findLayerViolations(graph) {
|
|
101
|
+
const funcLayers = new Map();
|
|
102
|
+
for (const d of graph.defines) {
|
|
103
|
+
const layer = extractLayer(d.file);
|
|
104
|
+
if (layer)
|
|
105
|
+
funcLayers.set(d.name, layer);
|
|
106
|
+
}
|
|
107
|
+
const violations = [];
|
|
108
|
+
for (const c of graph.calls) {
|
|
109
|
+
const callerLayer = funcLayers.get(c.caller);
|
|
110
|
+
const calleeLayer = funcLayers.get(c.callee);
|
|
111
|
+
if (!callerLayer || !calleeLayer)
|
|
112
|
+
continue;
|
|
113
|
+
if (callerLayer === calleeLayer)
|
|
114
|
+
continue;
|
|
115
|
+
const callerOrder = LAYER_ORDER[callerLayer] ?? 0;
|
|
116
|
+
const calleeOrder = LAYER_ORDER[calleeLayer] ?? 0;
|
|
117
|
+
if (calleeOrder - callerOrder > 1) {
|
|
118
|
+
violations.push({
|
|
119
|
+
caller: c.caller,
|
|
120
|
+
callee: c.callee,
|
|
121
|
+
callerLayer,
|
|
122
|
+
calleeLayer,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return violations;
|
|
127
|
+
}
|
|
58
128
|
function buildSummary(graph) {
|
|
59
129
|
const files = new Set(graph.defines.map((d) => d.file));
|
|
60
130
|
const functions = graph.defines.filter((d) => d.kind === "function" || d.kind === "method").length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyses.js","sourceRoot":"","sources":["../../src/graph/analyses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"analyses.js","sourceRoot":"","sources":["../../src/graph/analyses.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/graph/extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAkE,MAAM,YAAY,CAAC;AAE5G,8DAA8D;AAC9D,wBAAsB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/graph/extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAkE,MAAM,YAAY,CAAC;AAE5G,8DAA8D;AAC9D,wBAAsB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAkBtG"}
|
package/dist/graph/extractor.js
CHANGED
|
@@ -2,57 +2,80 @@ import { parseSource, parseSourceAsync, getLanguageForFile } from "./parser.js";
|
|
|
2
2
|
import { getAdapter } from "./adapter-registry.js";
|
|
3
3
|
/** Extract a unified call graph from multiple source files */
|
|
4
4
|
export async function extractGraph(files) {
|
|
5
|
+
const partials = await Promise.all(files.map((file) => extractFileGraph(file)));
|
|
5
6
|
const defines = [];
|
|
6
7
|
const calls = [];
|
|
7
8
|
const imports = [];
|
|
8
9
|
const exports = [];
|
|
9
10
|
const contains = [];
|
|
10
|
-
for (const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
11
|
+
for (const p of partials) {
|
|
12
|
+
defines.push(...p.defines);
|
|
13
|
+
calls.push(...p.calls);
|
|
14
|
+
imports.push(...p.imports);
|
|
15
|
+
exports.push(...p.exports);
|
|
16
|
+
contains.push(...p.contains);
|
|
17
|
+
}
|
|
18
|
+
return { defines, calls, imports, exports, contains };
|
|
19
|
+
}
|
|
20
|
+
async function extractFileGraph(file) {
|
|
21
|
+
const defines = [];
|
|
22
|
+
const calls = [];
|
|
23
|
+
const imports = [];
|
|
24
|
+
const exports = [];
|
|
25
|
+
const contains = [];
|
|
26
|
+
const callSet = new Set();
|
|
27
|
+
const lang = getLanguageForFile(file.path);
|
|
28
|
+
if (!lang)
|
|
29
|
+
return { defines, calls, imports, exports, contains };
|
|
30
|
+
const tree = parseSource(file.content, file.path)
|
|
31
|
+
?? await parseSourceAsync(file.content, file.path);
|
|
32
|
+
if (!tree)
|
|
33
|
+
return { defines, calls, imports, exports, contains };
|
|
34
|
+
try {
|
|
35
|
+
extractFromTree(tree, file.path, lang, defines, calls, imports, exports, contains, callSet);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
// web-tree-sitter (WASM) trees must be explicitly freed or WASM memory
|
|
39
|
+
// grows monotonically. Native tree-sitter trees have no delete method
|
|
40
|
+
// and are GC'd normally — guard with optional chaining.
|
|
41
|
+
tree.delete?.();
|
|
42
|
+
}
|
|
43
|
+
return { defines, calls, imports, exports, contains };
|
|
44
|
+
}
|
|
45
|
+
function extractFromTree(tree, filePath, lang, defines, calls, imports, exports, contains, callSet) {
|
|
46
|
+
const adapter = getAdapter(lang);
|
|
47
|
+
if (adapter) {
|
|
48
|
+
const partial = adapter.extract(tree.rootNode, filePath);
|
|
49
|
+
for (const d of partial.defines)
|
|
50
|
+
defines.push(d);
|
|
51
|
+
for (const c of partial.calls) {
|
|
52
|
+
const key = `${c.caller}->${c.callee}`;
|
|
53
|
+
if (!callSet.has(key)) {
|
|
54
|
+
callSet.add(key);
|
|
55
|
+
calls.push(c);
|
|
32
56
|
}
|
|
33
|
-
for (const i of partial.imports)
|
|
34
|
-
imports.push(i);
|
|
35
|
-
for (const e of partial.exports)
|
|
36
|
-
exports.push(e);
|
|
37
|
-
for (const c of partial.contains ?? [])
|
|
38
|
-
contains.push(c);
|
|
39
|
-
}
|
|
40
|
-
else if (lang === "clojure") {
|
|
41
|
-
walkClojure(tree.rootNode, file.path, defines, calls, imports, exports, callSet);
|
|
42
|
-
}
|
|
43
|
-
else if (lang === "python") {
|
|
44
|
-
const scopeStack = [];
|
|
45
|
-
walkPython(tree.rootNode, file.path, scopeStack, defines, calls, imports, exports, contains, callSet);
|
|
46
|
-
}
|
|
47
|
-
else if (lang === "go") {
|
|
48
|
-
walkGo(tree.rootNode, file.path, defines, calls, imports, exports, contains, callSet);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
const scopeStack = [];
|
|
52
|
-
walkNode(tree.rootNode, file.path, lang, scopeStack, defines, calls, imports, exports, contains, callSet);
|
|
53
57
|
}
|
|
58
|
+
for (const i of partial.imports)
|
|
59
|
+
imports.push(i);
|
|
60
|
+
for (const e of partial.exports)
|
|
61
|
+
exports.push(e);
|
|
62
|
+
for (const c of partial.contains ?? [])
|
|
63
|
+
contains.push(c);
|
|
64
|
+
}
|
|
65
|
+
else if (lang === "clojure") {
|
|
66
|
+
walkClojure(tree.rootNode, filePath, defines, calls, imports, exports, callSet);
|
|
67
|
+
}
|
|
68
|
+
else if (lang === "python") {
|
|
69
|
+
const scopeStack = [];
|
|
70
|
+
walkPython(tree.rootNode, filePath, scopeStack, defines, calls, imports, exports, contains, callSet);
|
|
71
|
+
}
|
|
72
|
+
else if (lang === "go") {
|
|
73
|
+
walkGo(tree.rootNode, filePath, defines, calls, imports, exports, contains, callSet);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const scopeStack = [];
|
|
77
|
+
walkNode(tree.rootNode, filePath, lang, scopeStack, defines, calls, imports, exports, contains, callSet);
|
|
54
78
|
}
|
|
55
|
-
return { defines, calls, imports, exports, contains };
|
|
56
79
|
}
|
|
57
80
|
function walkNode(node, filePath, language, scopeStack, defines, calls, imports, exports, contains, callSet) {
|
|
58
81
|
const type = node.type;
|
|
@@ -219,12 +242,12 @@ function resolveCallee(callNode) {
|
|
|
219
242
|
const property = fnNode.childForFieldName("property");
|
|
220
243
|
return property?.text ?? null;
|
|
221
244
|
}
|
|
222
|
-
// Dynamic calls like obj[x]() — not statically resolvable
|
|
223
|
-
case "subscript_expression":
|
|
224
|
-
return null;
|
|
225
245
|
default:
|
|
226
|
-
//
|
|
227
|
-
|
|
246
|
+
// Dynamic/compound calls (subscript, IIFE, logical-or, tagged template,
|
|
247
|
+
// parenthesized expressions) can't be resolved statically. Emitting the
|
|
248
|
+
// raw text like "(a || b)" or "() => 1" just produces noise in the
|
|
249
|
+
// downstream facts, so drop them.
|
|
250
|
+
return null;
|
|
228
251
|
}
|
|
229
252
|
}
|
|
230
253
|
/** Find the enclosing class name for a method node */
|
|
@@ -400,7 +423,7 @@ function resolvePythonCallee(callNode) {
|
|
|
400
423
|
return attr?.text ?? null;
|
|
401
424
|
}
|
|
402
425
|
default:
|
|
403
|
-
return
|
|
426
|
+
return null;
|
|
404
427
|
}
|
|
405
428
|
}
|
|
406
429
|
/** Find the enclosing class name for a Python method node */
|
|
@@ -534,7 +557,7 @@ function resolveGoCallee(callNode) {
|
|
|
534
557
|
return field?.text ?? null;
|
|
535
558
|
}
|
|
536
559
|
default:
|
|
537
|
-
return
|
|
560
|
+
return null;
|
|
538
561
|
}
|
|
539
562
|
}
|
|
540
563
|
/** Extract the receiver type name from a Go method receiver */
|
|
@@ -605,7 +628,7 @@ function cljDefnName(listNode) {
|
|
|
605
628
|
return null;
|
|
606
629
|
}
|
|
607
630
|
/** Extract ns form: (ns foo.bar (:require [baz.qux :as q] [x.y :refer [z]])) */
|
|
608
|
-
function cljExtractNs(listNode, filePath, imports
|
|
631
|
+
function cljExtractNs(listNode, filePath, imports) {
|
|
609
632
|
let symIdx = -1;
|
|
610
633
|
for (let i = 0; i < listNode.childCount; i++) {
|
|
611
634
|
if (listNode.child(i).type === "sym_lit") {
|
|
@@ -670,7 +693,7 @@ function walkClojure(rootNode, filePath, defines, calls, imports, exports, callS
|
|
|
670
693
|
if (child.type !== "list_lit")
|
|
671
694
|
continue;
|
|
672
695
|
// Check for ns form
|
|
673
|
-
const nsName = cljExtractNs(child, filePath, imports
|
|
696
|
+
const nsName = cljExtractNs(child, filePath, imports);
|
|
674
697
|
if (nsName)
|
|
675
698
|
continue;
|
|
676
699
|
// Check for defn/defn-
|
|
@@ -700,6 +723,33 @@ function walkClojure(rootNode, filePath, defines, calls, imports, exports, callS
|
|
|
700
723
|
cljExtractCalls(child, defn.name, calls, callSet);
|
|
701
724
|
}
|
|
702
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Clojure special forms and core macros that look like function calls in
|
|
728
|
+
* the AST (first position of a list_lit) but are not real call edges.
|
|
729
|
+
* Filtering these removes ~80% of the noise from cljExtractCalls output.
|
|
730
|
+
*/
|
|
731
|
+
const CLJ_SPECIAL_FORMS = new Set([
|
|
732
|
+
// Core special forms
|
|
733
|
+
"def", "do", "fn", "fn*", "if", "let", "let*", "letfn", "letfn*",
|
|
734
|
+
"loop", "loop*", "monitor-enter", "monitor-exit", "new", "quote",
|
|
735
|
+
"recur", "set!", "throw", "try", "catch", "finally", "var",
|
|
736
|
+
// Definition macros
|
|
737
|
+
"defn", "defn-", "defmacro", "defmulti", "defmethod", "defprotocol",
|
|
738
|
+
"defrecord", "deftype", "definterface", "defonce", "defstruct",
|
|
739
|
+
// Control-flow / binding macros
|
|
740
|
+
"when", "when-not", "when-let", "when-some", "when-first",
|
|
741
|
+
"if-let", "if-some", "if-not",
|
|
742
|
+
"cond", "condp", "case",
|
|
743
|
+
"and", "or", "not",
|
|
744
|
+
"do", "doto", "dotimes", "doseq", "dorun", "doall",
|
|
745
|
+
"for", "while",
|
|
746
|
+
// Threading macros
|
|
747
|
+
"->", "->>", "as->", "some->", "some->>", "cond->", "cond->>",
|
|
748
|
+
// Misc
|
|
749
|
+
"declare", "comment", "assert", "lazy-seq", "delay", "force",
|
|
750
|
+
"binding", "locking", "sync", "with-open", "with-local-vars",
|
|
751
|
+
"with-meta", "with-redefs", "with-redefs-fn",
|
|
752
|
+
]);
|
|
703
753
|
/** Recursively extract function calls from a Clojure form */
|
|
704
754
|
function cljExtractCalls(node, enclosingFn, calls, callSet) {
|
|
705
755
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -715,10 +765,12 @@ function cljExtractCalls(node, enclosingFn, calls, callSet) {
|
|
|
715
765
|
if (callee.includes("/")) {
|
|
716
766
|
callee = callee.split("/").pop();
|
|
717
767
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
callSet.
|
|
721
|
-
|
|
768
|
+
if (!CLJ_SPECIAL_FORMS.has(callee)) {
|
|
769
|
+
const key = `${enclosingFn}->${callee}`;
|
|
770
|
+
if (!callSet.has(key)) {
|
|
771
|
+
callSet.add(key);
|
|
772
|
+
calls.push({ caller: enclosingFn, callee });
|
|
773
|
+
}
|
|
722
774
|
}
|
|
723
775
|
}
|
|
724
776
|
break;
|