@holmes-lab/holmes-kit 0.4.1 → 0.5.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/CHANGELOG.md +35 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cpg/foundation/ast-store.d.ts +1 -1
- package/dist/holmes/cpg/foundation/ast-store.js +8 -0
- package/dist/holmes/cpg/foundation/cfg.js +137 -5
- package/dist/holmes/cpg/foundation/ddg.js +101 -3
- package/dist/holmes/cpg/foundation/language-matrix.js +9 -2
- package/grammars/manifest.json +30 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
<!-- @implements A-SPEC-209 -->
|
|
8
|
+
## [0.5.0] - 2026-09-02
|
|
9
|
+
|
|
10
|
+
The seven-language goal (GOAL-7langs) starts shipping: the L1 substrate now parses every
|
|
11
|
+
officially supported language, and Go becomes the third language with a full, corpus-validated
|
|
12
|
+
control- and data-flow story. Grades in the language matrix are still measurements, not goals —
|
|
13
|
+
a cell moves only when a real corpus proves it.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **L1 AST for all seven languages** (REQ-518): five more grammars join the vendored wasm
|
|
18
|
+
substrate — go 0.25.0, rust 0.24.0, java 0.23.5, c-sharp 0.23.5, cpp 0.23.4 — each ABI-probed
|
|
19
|
+
against web-tree-sitter 0.27.0 before pinning (5/5 load+parse). The expansion is five pins and
|
|
20
|
+
five extension mappings; no code changed, which was the design claim being tested. All 30
|
|
21
|
+
language fixtures parse with zero span violations. `.h` maps to the C++ grammar (near-superset;
|
|
22
|
+
a mis-parse surfaces as errorCount>0 and every layer above refuses on it).
|
|
23
|
+
- **Go CFG/DDG/CDG** (REQ-519): Go joins the shared lowering with its semantics pinned by intent,
|
|
24
|
+
written from a full statement census over two corpora (2,637 functions, 22 statement types)
|
|
25
|
+
rather than from the grammar docs. No implicit switch fallthrough (an explicit `fallthrough`
|
|
26
|
+
links to the next case); a bare `for {}` is INFINITE — no phantom exhaustion edge behind the
|
|
27
|
+
`for { select {…} }` daemon idiom; a `select` without `default` blocks; `defer` is a
|
|
28
|
+
registration-point statement (its arguments evaluate there, per the Go spec); `goto` refuses
|
|
29
|
+
the whole function, fail-closed. Receivers and NAMED RESULTS are parameters.
|
|
30
|
+
**Measured over gin+cobra: 2,637/2,637 functions lowered, zero refusals, zero dominator/DDG/CDG
|
|
31
|
+
invariant violations, 19,975 REACHING_DEF and 4,633 CDG edges (parse 337ms, build 1.5s).**
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **The language matrix could contradict itself** (REQ-517): the missing-edge list in a
|
|
36
|
+
relations cell was a hardcoded literal next to a derived present-list, so after REQ-511 the
|
|
37
|
+
generated doc read "calls, inherits (no imports/inherits)" — inherits present and absent in one
|
|
38
|
+
breath. Both the missing list and the full/partial judgement now derive from
|
|
39
|
+
EXTRACTABLE_RELATIONS by set difference, and a property test bans the contradiction SHAPE
|
|
40
|
+
itself (no edge may appear on both sides, any language, any future widening).
|
|
41
|
+
|
|
7
42
|
<!-- @implements A-SPEC-209 -->
|
|
8
43
|
## [0.4.1] - 2026-09-02
|
|
9
44
|
|
package/dist/.build-id
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8f00a9a-mtjc55io
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The honest language envelope: exactly what the vendored grammars cover — nothing more. */
|
|
2
2
|
export declare const AST_LANGUAGES: ReadonlyArray<{
|
|
3
3
|
ext: RegExp;
|
|
4
|
-
lang: 'typescript' | 'tsx' | 'python';
|
|
4
|
+
lang: 'typescript' | 'tsx' | 'python' | 'go' | 'rust' | 'java' | 'csharp' | 'cpp';
|
|
5
5
|
wasm: string;
|
|
6
6
|
}>;
|
|
7
7
|
export interface AstNodeRow {
|
|
@@ -67,6 +67,14 @@ exports.AST_LANGUAGES = [
|
|
|
67
67
|
{ ext: /\.(ts|mts|cts)$/, lang: 'typescript', wasm: 'tree-sitter-typescript.wasm' },
|
|
68
68
|
{ ext: /\.(js|mjs|cjs|jsx)$/, lang: 'tsx', wasm: 'tree-sitter-tsx.wasm' }, // TS grammar family parses JS (decision 3: no separate JS grammar)
|
|
69
69
|
{ ext: /\.py$/, lang: 'python', wasm: 'tree-sitter-python.wasm' },
|
|
70
|
+
// @implements A-SPEC-518.1 — the five languages the goal opens. `.h` goes to the C++ grammar on
|
|
71
|
+
// purpose (H-SPEC-518): C++ is a near-superset of C and a mis-parse surfaces as errorCount>0,
|
|
72
|
+
// which every layer above refuses on — revisited against the P2-C++ corpus.
|
|
73
|
+
{ ext: /\.go$/, lang: 'go', wasm: 'tree-sitter-go.wasm' },
|
|
74
|
+
{ ext: /\.rs$/, lang: 'rust', wasm: 'tree-sitter-rust.wasm' },
|
|
75
|
+
{ ext: /\.java$/, lang: 'java', wasm: 'tree-sitter-java.wasm' },
|
|
76
|
+
{ ext: /\.cs$/, lang: 'csharp', wasm: 'tree-sitter-c_sharp.wasm' },
|
|
77
|
+
{ ext: /\.(cpp|cc|cxx|hpp|h)$/, lang: 'cpp', wasm: 'tree-sitter-cpp.wasm' },
|
|
70
78
|
];
|
|
71
79
|
const GRAMMARS_DIR = (() => {
|
|
72
80
|
// src/holmes/cpg/foundation → three up is the package root; identical depth from dist.
|
|
@@ -10,6 +10,8 @@ exports.FUNCTION_TYPES = new Set([
|
|
|
10
10
|
'generator_function', 'arrow_function', 'method_definition', 'function',
|
|
11
11
|
// @implements A-SPEC-510.7 — Python's function node joins the same census.
|
|
12
12
|
'function_definition', 'lambda',
|
|
13
|
+
// @implements A-SPEC-519.1 — Go: methods and function literals (closures/goroutine bodies).
|
|
14
|
+
'method_declaration', 'func_literal',
|
|
13
15
|
]);
|
|
14
16
|
const TS_SIMPLE = new Set([
|
|
15
17
|
'expression_statement', 'lexical_declaration', 'variable_declaration', 'debugger_statement',
|
|
@@ -46,8 +48,29 @@ const PY_RULES = {
|
|
|
46
48
|
]),
|
|
47
49
|
block: 'block',
|
|
48
50
|
};
|
|
51
|
+
// @implements A-SPEC-519.1 — Go, classified from the FULL statement census over two corpora
|
|
52
|
+
// (2,637 functions, 22 types — evidence REQ-519), not from reading the grammar's docs.
|
|
53
|
+
// `defer`/`go` are pass-through statements (defer's args evaluate at registration per the Go
|
|
54
|
+
// spec; goroutine bodies are func_literals that functionsIn owns separately). `goto` is absent
|
|
55
|
+
// on purpose: zero corpus occurrences, and fail-closed refusal is the honest treatment.
|
|
56
|
+
const GO_SIMPLE = new Set([
|
|
57
|
+
'expression_statement', 'short_var_declaration', 'assignment_statement', 'var_declaration',
|
|
58
|
+
'const_declaration', 'type_declaration', 'inc_statement', 'dec_statement', 'send_statement',
|
|
59
|
+
'defer_statement', 'go_statement', 'empty_statement',
|
|
60
|
+
]);
|
|
61
|
+
const GO_RULES = {
|
|
62
|
+
simple: GO_SIMPLE,
|
|
63
|
+
handled: new Set([
|
|
64
|
+
...GO_SIMPLE,
|
|
65
|
+
'block', 'statement_list', 'if_statement', 'for_statement',
|
|
66
|
+
'expression_switch_statement', 'type_switch_statement', 'select_statement',
|
|
67
|
+
'labeled_statement', 'break_statement', 'continue_statement', 'return_statement',
|
|
68
|
+
'fallthrough_statement',
|
|
69
|
+
]),
|
|
70
|
+
block: 'block',
|
|
71
|
+
};
|
|
49
72
|
const LANG_RULES = {
|
|
50
|
-
typescript: TS_RULES, tsx: TS_RULES, python: PY_RULES,
|
|
73
|
+
typescript: TS_RULES, tsx: TS_RULES, python: PY_RULES, go: GO_RULES,
|
|
51
74
|
};
|
|
52
75
|
// @implements A-SPEC-510.6
|
|
53
76
|
/**
|
|
@@ -91,6 +114,7 @@ function cfgOf(ast, fn, source) {
|
|
|
91
114
|
}
|
|
92
115
|
const rules = LANG_RULES[ast.lang];
|
|
93
116
|
const isPython = ast.lang === 'python';
|
|
117
|
+
const isGo = ast.lang === 'go';
|
|
94
118
|
const kids = childrenIndex(ast);
|
|
95
119
|
// Points: one per lowered statement (block ids after coalescing). 0=ENTRY, 1=EXIT.
|
|
96
120
|
const stmtsOf = [[], []];
|
|
@@ -151,6 +175,8 @@ function cfgOf(ast, fn, source) {
|
|
|
151
175
|
switch (t) {
|
|
152
176
|
case 'statement_block':
|
|
153
177
|
case 'block':
|
|
178
|
+
case 'statement_list':
|
|
179
|
+
// Go's block carries one extra statement_list layer (probed shape) — both are containers.
|
|
154
180
|
return lowerSeq(stmtChildren(i), ctx);
|
|
155
181
|
case 'labeled_statement': {
|
|
156
182
|
const [labelNode, inner] = [kids.of(i)[0], stmtChildren(i).at(-1)];
|
|
@@ -164,9 +190,18 @@ function cfgOf(ast, fn, source) {
|
|
|
164
190
|
// @implements A-SPEC-510.7 — Python spells the arms as fields (`consequence`,
|
|
165
191
|
// `alternative` = elif_clause | else_clause), TS as positional children. Reading the
|
|
166
192
|
// fields where they exist keeps ONE lowering for both grammars.
|
|
167
|
-
|
|
193
|
+
// @implements A-SPEC-519.1 — Go: consequence is the FIRST block child; the alternative
|
|
194
|
+
// (an else block or an else-if chain's if_statement) is the last named child when it is
|
|
195
|
+
// distinct from the consequence. The header point owns the init (:=) and condition.
|
|
196
|
+
const goCons = isGo ? kids.of(i).find((k) => ast.nodes[k].type === 'block') : undefined;
|
|
197
|
+
const cons = isGo ? goCons : isPython ? (kids.of(i).find((k) => ast.nodes[k].type === 'block') ?? named[1]) : named[1];
|
|
168
198
|
let alt;
|
|
169
|
-
if (
|
|
199
|
+
if (isGo) {
|
|
200
|
+
const last = named.at(-1);
|
|
201
|
+
if (last !== goCons && ['block', 'if_statement'].includes(ast.nodes[last].type))
|
|
202
|
+
alt = last;
|
|
203
|
+
}
|
|
204
|
+
else if (isPython) {
|
|
170
205
|
const clause = kids.of(i).find((k) => ['elif_clause', 'else_clause'].includes(ast.nodes[k].type));
|
|
171
206
|
if (clause !== undefined) {
|
|
172
207
|
alt = ast.nodes[clause].type === 'elif_clause'
|
|
@@ -202,9 +237,14 @@ function cfgOf(ast, fn, source) {
|
|
|
202
237
|
? kids.of(i).filter((k) => ast.nodes[k].type === 'else_clause')
|
|
203
238
|
.flatMap((k) => stmtChildren(k)).find((k) => ast.nodes[k].type === 'block')
|
|
204
239
|
: undefined;
|
|
205
|
-
const bodyIdx = isPython
|
|
240
|
+
const bodyIdx = (isPython || isGo)
|
|
206
241
|
? kids.of(i).filter((k) => ast.nodes[k].type === 'block').at(0)
|
|
207
242
|
: stmtChildren(i).at(-1);
|
|
243
|
+
// @implements A-SPEC-519.1 — a bare Go `for {}` (no named non-block child) is INFINITE:
|
|
244
|
+
// no exhaustion edge. `for { select {…} }` is the daemon idiom, and a phantom false edge
|
|
245
|
+
// would hang spurious control dependence on everything after the loop. A for_clause with
|
|
246
|
+
// an empty condition keeps the edge (documented over-approximation).
|
|
247
|
+
const goInfinite = isGo && !kids.of(i).some((k) => ast.nodes[k].named && ast.nodes[k].type !== 'block' && ast.nodes[k].type !== 'comment');
|
|
208
248
|
const myBreak = { label, target: -1 }; // ONE owner: labeled and bare breaks share it
|
|
209
249
|
ctx.breakT.push({ label, owner: myBreak }, { label: null, owner: myBreak });
|
|
210
250
|
ctx.contT.push({ label, target: header }, { label: null, target: header });
|
|
@@ -220,7 +260,7 @@ function cfgOf(ast, fn, source) {
|
|
|
220
260
|
link(header, ef.entry, 'false'); // exhaustion path enters `else`
|
|
221
261
|
exits.push(...ef.exits);
|
|
222
262
|
}
|
|
223
|
-
else {
|
|
263
|
+
else if (!goInfinite) {
|
|
224
264
|
exits.push({ from: header, kind: 'false' });
|
|
225
265
|
}
|
|
226
266
|
// break targets resolved lazily: edges recorded with target -1 fixed by caller — instead
|
|
@@ -282,6 +322,98 @@ function cfgOf(ast, fn, source) {
|
|
|
282
322
|
throw new Unsupported('with_statement without a block');
|
|
283
323
|
return lowerStmt(body, ctx);
|
|
284
324
|
}
|
|
325
|
+
// @implements A-SPEC-519.1 — Go's two switch forms. NO implicit fallthrough (each case
|
|
326
|
+
// leaves the switch), an explicit fallthrough_statement links to the NEXT case's entry,
|
|
327
|
+
// and a missing default leaves a header escape (no case matched). The binding of a type
|
|
328
|
+
// switch (`switch v := x.(type)`) lives on the header point, where the DDG collects it.
|
|
329
|
+
case 'expression_switch_statement':
|
|
330
|
+
case 'type_switch_statement': {
|
|
331
|
+
const p = newPoint(i);
|
|
332
|
+
const myBreak = { label, target: -1 };
|
|
333
|
+
ctx.breakT.push({ label, owner: myBreak }, { label: null, owner: myBreak });
|
|
334
|
+
const caseNodes = kids.of(i).filter((k) => ['expression_case', 'type_case', 'default_case'].includes(ast.nodes[k].type));
|
|
335
|
+
let sawDefault = false;
|
|
336
|
+
const exits = [];
|
|
337
|
+
let pendingFalls = [];
|
|
338
|
+
caseNodes.forEach((c, ci) => {
|
|
339
|
+
if (ast.nodes[c].type === 'default_case')
|
|
340
|
+
sawDefault = true;
|
|
341
|
+
const bodyList = kids.of(c).find((k) => ast.nodes[k].type === 'statement_list');
|
|
342
|
+
const stmts = bodyList === undefined ? [] : stmtChildren(bodyList);
|
|
343
|
+
const fallIdx = stmts.filter((k) => ast.nodes[k].type === 'fallthrough_statement');
|
|
344
|
+
const rest = stmts.filter((k) => ast.nodes[k].type !== 'fallthrough_statement');
|
|
345
|
+
const cf = lowerSeq(rest, ctx);
|
|
346
|
+
link(p, cf.entry, ast.nodes[c].type === 'default_case' ? 'default' : 'case');
|
|
347
|
+
for (const f of pendingFalls)
|
|
348
|
+
link(f, cf.entry, 'seq');
|
|
349
|
+
pendingFalls = [];
|
|
350
|
+
if (fallIdx.length > 0) {
|
|
351
|
+
// The census shows fallthrough only as a case's LAST statement; a next case must
|
|
352
|
+
// exist (its absence is a Go compile error) — refuse honestly if it does not.
|
|
353
|
+
if (ci === caseNodes.length - 1)
|
|
354
|
+
throw new Unsupported('fallthrough in last case');
|
|
355
|
+
const fp = newPoint(fallIdx[0]);
|
|
356
|
+
for (const e of cf.exits)
|
|
357
|
+
link(e.from, fp, e.kind);
|
|
358
|
+
pendingFalls.push(fp);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
exits.push(...cf.exits);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
if (!sawDefault)
|
|
365
|
+
exits.push({ from: p, kind: 'default' });
|
|
366
|
+
ctx.breakT.splice(-2);
|
|
367
|
+
for (const b of claimBreaks(myBreak))
|
|
368
|
+
exits.push({ from: b.point, kind: 'break' });
|
|
369
|
+
return { entry: p, exits };
|
|
370
|
+
}
|
|
371
|
+
// @implements A-SPEC-519.1 — select: each communication case is a branch off the header;
|
|
372
|
+
// WITHOUT a default the statement BLOCKS until some case fires, so there is no "no arm
|
|
373
|
+
// matched" escape (the opposite of Python's match).
|
|
374
|
+
case 'select_statement': {
|
|
375
|
+
const p = newPoint(i);
|
|
376
|
+
const myBreak = { label, target: -1 };
|
|
377
|
+
ctx.breakT.push({ label, owner: myBreak }, { label: null, owner: myBreak });
|
|
378
|
+
let sawDefault = false;
|
|
379
|
+
const exits = [];
|
|
380
|
+
for (const c of kids.of(i)) {
|
|
381
|
+
const cty = ast.nodes[c].type;
|
|
382
|
+
if (cty !== 'communication_case' && cty !== 'default_case')
|
|
383
|
+
continue;
|
|
384
|
+
if (cty === 'default_case')
|
|
385
|
+
sawDefault = true;
|
|
386
|
+
const comm = kids.of(c).find((k) => ['receive_statement', 'send_statement', 'expression_statement'].includes(ast.nodes[k].type));
|
|
387
|
+
const bodyList = kids.of(c).find((k) => ast.nodes[k].type === 'statement_list');
|
|
388
|
+
const stmts = bodyList === undefined ? [] : stmtChildren(bodyList);
|
|
389
|
+
let entry;
|
|
390
|
+
let head;
|
|
391
|
+
if (comm !== undefined) {
|
|
392
|
+
const cp = newPoint(comm);
|
|
393
|
+
entry = cp;
|
|
394
|
+
head = { entry: cp, exits: [{ from: cp, kind: 'seq' }] };
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
head = undefined;
|
|
398
|
+
entry = -1;
|
|
399
|
+
}
|
|
400
|
+
const bf = lowerSeq(stmts, ctx);
|
|
401
|
+
if (head !== undefined) {
|
|
402
|
+
for (const e of head.exits)
|
|
403
|
+
link(e.from, bf.entry, e.kind);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
entry = bf.entry;
|
|
407
|
+
}
|
|
408
|
+
link(p, entry, cty === 'default_case' ? 'default' : 'case');
|
|
409
|
+
exits.push(...bf.exits);
|
|
410
|
+
}
|
|
411
|
+
if (sawDefault) { /* a ready default gives an immediate path — covered by its case edge */ }
|
|
412
|
+
ctx.breakT.splice(-2);
|
|
413
|
+
for (const b of claimBreaks(myBreak))
|
|
414
|
+
exits.push({ from: b.point, kind: 'break' });
|
|
415
|
+
return { entry: p, exits };
|
|
416
|
+
}
|
|
285
417
|
case 'raise_statement': {
|
|
286
418
|
const p = newPoint(i);
|
|
287
419
|
link(p, ctx.catchT.at(-1) ?? EXIT, 'throw');
|
|
@@ -6,12 +6,16 @@ exports.ddgViolations = ddgViolations;
|
|
|
6
6
|
const cfg_1 = require("./cfg");
|
|
7
7
|
/** Identifier positions that are DEF sites, keyed by the parent node type. */
|
|
8
8
|
const PATTERN_TYPES = new Set(['object_pattern', 'array_pattern', 'shorthand_property_identifier_pattern',
|
|
9
|
-
'pair_pattern', 'rest_pattern', 'assignment_pattern'
|
|
9
|
+
'pair_pattern', 'rest_pattern', 'assignment_pattern',
|
|
10
|
+
// @implements A-SPEC-519.1 — Go: an expression_list on a binding LHS carries the def context
|
|
11
|
+
// through to each identifier (`a, b := …`, `for i, v := range …`).
|
|
12
|
+
'expression_list']);
|
|
10
13
|
function defUseOf(ast, cfg, fn, source) {
|
|
11
14
|
const kids = (0, cfg_1.childrenIndex)(ast);
|
|
12
15
|
const text = (i) => source.slice(ast.nodes[i].start, ast.nodes[i].end);
|
|
13
16
|
// ── parameters: ENTRY defs (and part of the local namespace).
|
|
14
17
|
const isPython = ast.lang === 'python';
|
|
18
|
+
const isGo = ast.lang === 'go';
|
|
15
19
|
const params = new Map();
|
|
16
20
|
const bareArrow = ast.nodes[fn.nodeIndex].type === 'arrow_function'
|
|
17
21
|
&& !kids.of(fn.nodeIndex).some((k) => ast.nodes[k].type === 'formal_parameters');
|
|
@@ -19,7 +23,11 @@ function defUseOf(ast, cfg, fn, source) {
|
|
|
19
23
|
// Only formal_parameters — a declaration's NAME identifier is not a parameter (measured: the
|
|
20
24
|
// early break on the name swallowed every real parameter of named functions).
|
|
21
25
|
// @implements A-SPEC-510.7 — Python's parameter list node is `parameters`.
|
|
22
|
-
|
|
26
|
+
// @implements A-SPEC-519.1 — Go's list node is `parameter_list`, and it appears up to THREE
|
|
27
|
+
// times on one declaration: receiver, parameters, NAMED RESULTS. All of them bind names at
|
|
28
|
+
// ENTRY, so Go must not break after the first (the probe pinned the second-list shape).
|
|
29
|
+
const isParamList = ast.nodes[k].type === 'formal_parameters' || ast.nodes[k].type === 'parameters'
|
|
30
|
+
|| ast.nodes[k].type === 'parameter_list';
|
|
23
31
|
if (!isParamList && !(bareArrow && ast.nodes[k].type === 'identifier'))
|
|
24
32
|
continue;
|
|
25
33
|
const collect = (n) => {
|
|
@@ -32,7 +40,7 @@ function defUseOf(ast, cfg, fn, source) {
|
|
|
32
40
|
collect(c);
|
|
33
41
|
};
|
|
34
42
|
collect(k);
|
|
35
|
-
if (isParamList)
|
|
43
|
+
if (isParamList && !isGo)
|
|
36
44
|
break;
|
|
37
45
|
}
|
|
38
46
|
const defs = new Map();
|
|
@@ -65,7 +73,97 @@ function defUseOf(ast, cfg, fn, source) {
|
|
|
65
73
|
}
|
|
66
74
|
if (ty === 'property_identifier' || ty === 'statement_identifier' || ty === 'type_identifier')
|
|
67
75
|
return;
|
|
76
|
+
// @implements A-SPEC-519.1 — Go: a selector's field, a package qualifier and a label are
|
|
77
|
+
// names in other namespaces, never local value reads.
|
|
78
|
+
if (ty === 'field_identifier' || ty === 'package_identifier' || ty === 'label_name')
|
|
79
|
+
return;
|
|
68
80
|
switch (ty) {
|
|
81
|
+
// @implements A-SPEC-519.1 — Go's binding shapes, written from the probed grammar.
|
|
82
|
+
case 'short_var_declaration': {
|
|
83
|
+
// `a, b := e1, e2` — every LHS identifier is a def; the RHS list is uses.
|
|
84
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
85
|
+
if (named[0] !== undefined)
|
|
86
|
+
walk(named[0], true);
|
|
87
|
+
for (const r of named.slice(1))
|
|
88
|
+
walk(r, false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
case 'assignment_statement': {
|
|
92
|
+
// `x = e` defs x; `x += e` defs AND uses it. No fields survive persistence, so the
|
|
93
|
+
// operator is read from the unnamed token between the two expression_lists.
|
|
94
|
+
const all = kids.of(n);
|
|
95
|
+
const named = all.filter((c) => ast.nodes[c].named);
|
|
96
|
+
const op = all.find((c) => !ast.nodes[c].named && text(c).endsWith('='));
|
|
97
|
+
const compound = op !== undefined && text(op) !== '=';
|
|
98
|
+
const lhs = named[0];
|
|
99
|
+
if (lhs !== undefined) {
|
|
100
|
+
for (const el of ast.nodes[lhs].type === 'expression_list' ? kids.of(lhs).filter((c) => ast.nodes[c].named) : [lhs]) {
|
|
101
|
+
if (ast.nodes[el].type === 'identifier') {
|
|
102
|
+
add(defs, stmt, text(el));
|
|
103
|
+
if (compound)
|
|
104
|
+
add(uses, stmt, text(el));
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
walk(el, false); // a.b / a[i] mutation: uses only
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const r of named.slice(1))
|
|
111
|
+
walk(r, false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
case 'var_spec':
|
|
115
|
+
case 'const_spec': {
|
|
116
|
+
// `var x, y T = e1, e2` — identifier children are defs, the value list is uses.
|
|
117
|
+
for (const c of kids.of(n)) {
|
|
118
|
+
if (ast.nodes[c].type === 'identifier')
|
|
119
|
+
add(defs, stmt, text(c));
|
|
120
|
+
else
|
|
121
|
+
walk(c, false);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
case 'inc_statement':
|
|
126
|
+
case 'dec_statement': {
|
|
127
|
+
const arg = kids.of(n).find((c) => ast.nodes[c].named);
|
|
128
|
+
if (arg !== undefined && ast.nodes[arg].type === 'identifier') {
|
|
129
|
+
add(defs, stmt, text(arg));
|
|
130
|
+
add(uses, stmt, text(arg));
|
|
131
|
+
}
|
|
132
|
+
else if (arg !== undefined)
|
|
133
|
+
walk(arg, false);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case 'range_clause':
|
|
137
|
+
case 'receive_statement': {
|
|
138
|
+
// `for i, v := range xs` / `case v := <-ch` — LHS binds, RHS is read. With `=` instead
|
|
139
|
+
// of `:=` the LHS identifiers are still (re)defined; the same treatment holds.
|
|
140
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
141
|
+
if (named.length >= 2) {
|
|
142
|
+
walk(named[0], true);
|
|
143
|
+
for (const r of named.slice(1))
|
|
144
|
+
walk(r, false);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
for (const r of named)
|
|
148
|
+
walk(r, false); // bare `for range xs` / `<-ch`
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
case 'type_switch_statement': {
|
|
153
|
+
// `switch v := x.(type)` — v defs at the header; everything else is uses.
|
|
154
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
155
|
+
const hasBind = kids.of(n).some((c) => !ast.nodes[c].named && text(c) === ':=');
|
|
156
|
+
if (hasBind && named[0] !== undefined && ast.nodes[named[0]].type === 'expression_list') {
|
|
157
|
+
walk(named[0], true);
|
|
158
|
+
for (const r of named.slice(1))
|
|
159
|
+
walk(r, false);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
for (const r of named)
|
|
163
|
+
walk(r, false);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
69
167
|
// @implements A-SPEC-510.7 — Python's assignment shapes.
|
|
70
168
|
case 'assignment': {
|
|
71
169
|
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
@@ -43,9 +43,16 @@ function relationsCell(ext) {
|
|
|
43
43
|
if (resolved.length === 0)
|
|
44
44
|
return { support: 'none', basis: 'nothing reaches the graph' };
|
|
45
45
|
// A-SPEC-286: the claim is made in the graph-resolved column, never the scanner's raw output.
|
|
46
|
-
|
|
46
|
+
// @implements A-SPEC-517.1 — the MISSING list is derived from the same authority as the present
|
|
47
|
+
// list. It used to be the literal `(no imports/inherits)` next to a derived present-list, so when
|
|
48
|
+
// REQ-511 widened graphResolved the doc read "calls, inherits (no imports/inherits)" — inherits
|
|
49
|
+
// present and absent in one breath. The `>= 3` magic number was the same sin (a hardcoded "the
|
|
50
|
+
// full set has three kinds"); judging by set difference keeps both halves true if
|
|
51
|
+
// EXTRACTABLE_RELATIONS ever widens.
|
|
52
|
+
const missing = language_capability_1.EXTRACTABLE_RELATIONS.filter((r) => !resolved.includes(r));
|
|
53
|
+
return missing.length === 0
|
|
47
54
|
? { support: 'full', basis: `graph edges: ${resolved.join(', ')}` }
|
|
48
|
-
: { support: 'partial', basis: `graph edges: ${resolved.join(', ')} (no
|
|
55
|
+
: { support: 'partial', basis: `graph edges: ${resolved.join(', ')} (no ${missing.join('/')})` };
|
|
49
56
|
}
|
|
50
57
|
function astCell(astLang) {
|
|
51
58
|
// The TS grammar family covers tsx/js too — AST_LANGUAGES is the authority on which wasm applies.
|
package/grammars/manifest.json
CHANGED
|
@@ -18,6 +18,36 @@
|
|
|
18
18
|
"package": "tree-sitter-python",
|
|
19
19
|
"packageVersion": "0.25.0",
|
|
20
20
|
"sha256": "16108b50df4ee9a30168794252ab55e7c93bfc5765d7fa0aa3e335752c515f47"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "tree-sitter-go.wasm",
|
|
24
|
+
"package": "tree-sitter-go",
|
|
25
|
+
"packageVersion": "0.25.0",
|
|
26
|
+
"sha256": "9504573f352b20be7f2f1911754d710622aedc15afff16d5ed8fb5645681aee7"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"file": "tree-sitter-rust.wasm",
|
|
30
|
+
"package": "tree-sitter-rust",
|
|
31
|
+
"packageVersion": "0.24.0",
|
|
32
|
+
"sha256": "f65f354215611fd94ad34134b3427eb3d58cbb745df7b6509ba722184db73d57"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"file": "tree-sitter-java.wasm",
|
|
36
|
+
"package": "tree-sitter-java",
|
|
37
|
+
"packageVersion": "0.23.5",
|
|
38
|
+
"sha256": "4fdeac4ca6ca089f06c6f7e562abcac1733cd465728cc7031ebb73c2019122c4"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"file": "tree-sitter-c_sharp.wasm",
|
|
42
|
+
"package": "tree-sitter-c-sharp",
|
|
43
|
+
"packageVersion": "0.23.5",
|
|
44
|
+
"sha256": "6f69e1cae44e1c32c1eccc170dc5a9778fb94ff716f71113fe1f8c4299aa2f40"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"file": "tree-sitter-cpp.wasm",
|
|
48
|
+
"package": "tree-sitter-cpp",
|
|
49
|
+
"packageVersion": "0.23.4",
|
|
50
|
+
"sha256": "174eb0deb75b2ec7881bcacda9f995648d8e683956e5c2267e69ab6dc503fcbf"
|
|
21
51
|
}
|
|
22
52
|
]
|
|
23
53
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@implements A-SPEC-209",
|
|
3
3
|
"name": "@holmes-lab/holmes-kit",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
|
|
6
6
|
"main": "dist/holmes/mcp/server.js",
|
|
7
7
|
"types": "dist/holmes/mcp/server.d.ts",
|