@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 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
- 2b427b6-mtj2vv29
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
- const cons = isPython ? (kids.of(i).find((k) => ast.nodes[k].type === 'block') ?? named[1]) : named[1];
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 (isPython) {
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
- const isParamList = ast.nodes[k].type === 'formal_parameters' || ast.nodes[k].type === 'parameters';
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
- return resolved.length >= 3
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 imports/inherits)` };
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.
@@ -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
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.1",
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",