weavatrix 0.2.12 → 0.2.14
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 +90 -21
- package/SECURITY.md +2 -2
- package/docs/releases/v0.2.14.md +93 -0
- package/package.json +2 -2
- package/skill/SKILL.md +108 -43
- package/src/analysis/allowed-test-runner.js +20 -9
- package/src/analysis/architecture/contract-graph.js +119 -0
- package/src/analysis/architecture/contract-schema.js +110 -0
- package/src/analysis/architecture/contract-storage.js +35 -0
- package/src/analysis/architecture/contract-verification.js +168 -0
- package/src/analysis/architecture-contract.js +16 -343
- package/src/analysis/change-classification/diff-parser.js +103 -0
- package/src/analysis/change-classification/options.js +40 -0
- package/src/analysis/change-classification/symbol-classifier.js +177 -0
- package/src/analysis/change-classification.js +120 -519
- package/src/analysis/dead-code-review/policy.js +23 -0
- package/src/analysis/dead-code-review.js +9 -19
- package/src/analysis/dep-check.js +10 -57
- package/src/analysis/dep-rules.js +10 -303
- package/src/analysis/dependency/scoped-dependencies.js +40 -0
- package/src/analysis/endpoints/common.js +62 -0
- package/src/analysis/endpoints/extract.js +107 -0
- package/src/analysis/endpoints/inventory.js +84 -0
- package/src/analysis/endpoints/mounts.js +129 -0
- package/src/analysis/endpoints-java.js +80 -3
- package/src/analysis/endpoints.js +3 -448
- package/src/analysis/git-history/analytics.js +177 -0
- package/src/analysis/git-history/collector.js +109 -0
- package/src/analysis/git-history/options.js +68 -0
- package/src/analysis/git-history.js +128 -577
- package/src/analysis/health-capabilities.js +82 -0
- package/src/analysis/http-contracts/analysis.js +169 -0
- package/src/analysis/http-contracts/client-call-detection.js +81 -0
- package/src/analysis/http-contracts/client-call-parser.js +255 -0
- package/src/analysis/http-contracts/graph-context.js +143 -0
- package/src/analysis/http-contracts/matching.js +61 -0
- package/src/analysis/http-contracts/shared.js +86 -0
- package/src/analysis/http-contracts.js +6 -822
- package/src/analysis/internal-audit/dependency-health.js +111 -0
- package/src/analysis/internal-audit/python-manifests.js +65 -0
- package/src/analysis/internal-audit/repo-files.js +55 -0
- package/src/analysis/internal-audit/supply-chain.js +132 -0
- package/src/analysis/internal-audit.collect.js +5 -106
- package/src/analysis/internal-audit.run.js +113 -200
- package/src/analysis/jvm-dependency-evidence.js +69 -0
- package/src/analysis/source-correctness/retry-patterns.js +93 -0
- package/src/analysis/source-correctness.js +225 -0
- package/src/analysis/structure/dependency-graph.js +156 -0
- package/src/analysis/structure/findings.js +142 -0
- package/src/graph/builder/go-receiver-resolution.js +112 -0
- package/src/graph/builder/js/queries.js +48 -0
- package/src/graph/builder/lang-go.js +89 -4
- package/src/graph/builder/lang-js.js +4 -44
- package/src/graph/builder/pass2-resolution.js +68 -0
- package/src/graph/freshness-probe.js +2 -1
- package/src/graph/incremental-refresh.js +3 -2
- package/src/graph/internal-builder.build.js +22 -183
- package/src/graph/internal-builder.pass2.js +161 -0
- package/src/graph/internal-builder.resolvers.js +2 -105
- package/src/graph/resolvers/rust.js +117 -0
- package/src/mcp/actions/advisories.mjs +18 -0
- package/src/mcp/actions/graph-lifecycle.mjs +148 -0
- package/src/mcp/actions/graph-sync.mjs +195 -0
- package/src/mcp/actions/hosted-architecture.mjs +57 -0
- package/src/mcp/architecture-bootstrap.mjs +168 -0
- package/src/mcp/architecture-starter.mjs +234 -0
- package/src/mcp/catalog.mjs +22 -6
- package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
- package/src/mcp/evidence/package-graph-common.mjs +115 -0
- package/src/mcp/evidence/package-lock-graph.mjs +92 -0
- package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
- package/src/mcp/graph/context-core.mjs +111 -0
- package/src/mcp/graph/context-seeds.mjs +208 -0
- package/src/mcp/graph/context-state.mjs +86 -0
- package/src/mcp/graph/tools-core.mjs +143 -0
- package/src/mcp/graph/tools-query.mjs +223 -0
- package/src/mcp/graph-context.mjs +4 -496
- package/src/mcp/health/audit-format.mjs +172 -0
- package/src/mcp/health/audit.mjs +171 -0
- package/src/mcp/health/dead-code.mjs +110 -0
- package/src/mcp/health/duplicates.mjs +83 -0
- package/src/mcp/health/endpoints.mjs +42 -0
- package/src/mcp/health/structure.mjs +219 -0
- package/src/mcp/runtime-version.mjs +32 -0
- package/src/mcp/server/auto-refresh.mjs +66 -0
- package/src/mcp/server/runtime-config.mjs +43 -0
- package/src/mcp/server/shutdown.mjs +40 -0
- package/src/mcp/sync/evidence-architecture.mjs +54 -0
- package/src/mcp/sync/evidence-common.mjs +88 -0
- package/src/mcp/sync/evidence-duplicates.mjs +100 -0
- package/src/mcp/sync/evidence-health.mjs +56 -0
- package/src/mcp/sync/evidence-packages.mjs +95 -0
- package/src/mcp/sync/payload-common.mjs +97 -0
- package/src/mcp/sync/payload-v2.mjs +53 -0
- package/src/mcp/sync/payload-v3.mjs +79 -0
- package/src/mcp/sync-evidence.mjs +7 -402
- package/src/mcp/sync-payload.mjs +10 -244
- package/src/mcp/tools-actions.mjs +18 -414
- package/src/mcp/tools-architecture.mjs +18 -70
- package/src/mcp/tools-context.mjs +56 -15
- package/src/mcp/tools-endpoints.mjs +4 -1
- package/src/mcp/tools-graph.mjs +17 -330
- package/src/mcp/tools-health.mjs +24 -705
- package/src/mcp/tools-verified-change.mjs +12 -4
- package/src/mcp-server.mjs +49 -146
- package/src/precision/lsp-client/constants.js +12 -0
- package/src/precision/lsp-client/environment.js +20 -0
- package/src/precision/lsp-client/errors.js +15 -0
- package/src/precision/lsp-client/lifecycle.js +127 -0
- package/src/precision/lsp-client/message-parser.js +81 -0
- package/src/precision/lsp-client/protocol.js +212 -0
- package/src/precision/lsp-client/registry.js +58 -0
- package/src/precision/lsp-client/repo-uri.js +60 -0
- package/src/precision/lsp-client/stdio-client.js +151 -0
- package/src/precision/lsp-client.js +10 -682
- package/src/precision/lsp-overlay/build.js +238 -0
- package/src/precision/lsp-overlay/contract.js +61 -0
- package/src/precision/lsp-overlay/reference-results.js +108 -0
- package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
- package/src/precision/lsp-overlay/source-session.js +134 -0
- package/src/precision/lsp-overlay/store.js +150 -0
- package/src/precision/lsp-overlay/target-index.js +147 -0
- package/src/precision/lsp-overlay/target-query.js +55 -0
- package/src/precision/lsp-overlay.js +11 -868
- package/src/precision/typescript-lsp-provider.js +12 -682
- package/src/precision/typescript-provider/client.js +69 -0
- package/src/precision/typescript-provider/discovery.js +124 -0
- package/src/precision/typescript-provider/project-host.js +249 -0
- package/src/precision/typescript-provider/project-safety.js +161 -0
- package/src/precision/typescript-provider/reference-usage.js +53 -0
- package/src/version.js +7 -0
- package/docs/releases/v0.2.12.md +0 -45
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const CALLABLE = /arrow_function|function|function_expression|generator_function/
|
|
2
|
+
|
|
3
|
+
export const FUNCS = `
|
|
4
|
+
(function_declaration name: (identifier) @fn)
|
|
5
|
+
(generator_function_declaration name: (identifier) @fn)
|
|
6
|
+
(class_declaration name: (_) @class)
|
|
7
|
+
(method_definition name: (_) @method)`
|
|
8
|
+
|
|
9
|
+
export const TOPVARS = `
|
|
10
|
+
(program (lexical_declaration (variable_declarator) @decl))
|
|
11
|
+
(program (variable_declaration (variable_declarator) @decl))
|
|
12
|
+
(program (export_statement (lexical_declaration (variable_declarator) @decl)))
|
|
13
|
+
(program (export_statement (variable_declaration (variable_declarator) @decl)))`
|
|
14
|
+
|
|
15
|
+
export const TYPES = `
|
|
16
|
+
(interface_declaration name: (_) @interface)
|
|
17
|
+
(type_alias_declaration name: (_) @type)
|
|
18
|
+
(enum_declaration name: (_) @enum)`
|
|
19
|
+
|
|
20
|
+
export const REQUIRE = `(variable_declarator name: (_) @lhs value: (call_expression function: (identifier) @req arguments: (arguments (string (string_fragment) @src))))`
|
|
21
|
+
|
|
22
|
+
export function parseExportSpecifiers(raw) {
|
|
23
|
+
return String(raw || '').split(',').map((part) => {
|
|
24
|
+
const text = part.trim()
|
|
25
|
+
const typeOnly = /^type\s+/.test(text)
|
|
26
|
+
const clean = text.replace(/^type\s+/, '').trim()
|
|
27
|
+
const match = clean.match(/^([A-Za-z_$][\w$]*|default)(?:\s+as\s+([A-Za-z_$][\w$]*|default))?$/)
|
|
28
|
+
return match ? {imported: match[1], exported: match[2] || match[1], typeOnly} : null
|
|
29
|
+
}).filter(Boolean)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function importTypeOnly(node) {
|
|
33
|
+
if (/^\s*import\s+type\b/.test(node.text)) return true
|
|
34
|
+
const clause = node.namedChildren.find((child) => child.type === 'import_clause')
|
|
35
|
+
if (!clause) return false
|
|
36
|
+
const parts = clause.namedChildren
|
|
37
|
+
if (parts.some((child) => child.type === 'identifier' || child.type === 'namespace_import')) return false
|
|
38
|
+
const named = parts.find((child) => child.type === 'named_imports')
|
|
39
|
+
const specifiers = named?.namedChildren.filter((child) => child.type === 'import_specifier') || []
|
|
40
|
+
return specifiers.length > 0 && specifiers.every((specifier) => /^\s*type\b/.test(specifier.text))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function reexportTypeOnly(node) {
|
|
44
|
+
if (/^\s*export\s+type\b/.test(node.text)) return true
|
|
45
|
+
const clause = node.namedChildren.find((child) => child.type === 'export_clause')
|
|
46
|
+
const specifiers = clause?.namedChildren.filter((child) => child.type === 'export_specifier') || []
|
|
47
|
+
return specifiers.length > 0 && specifiers.every((specifier) => /^\s*type\b/.test(specifier.text))
|
|
48
|
+
}
|
|
@@ -5,6 +5,61 @@
|
|
|
5
5
|
// in the orchestrator; cross-pkg `pkg.Func()` is a selector — see resolveGoPackage/pass2). No inheritance.
|
|
6
6
|
import { goSpecToPkg } from "./spec-pkg.js";
|
|
7
7
|
|
|
8
|
+
// Return the named Go type behind pointers/generics/parentheses. Qualified names are
|
|
9
|
+
// kept (`speaker.Speaker`) so pass 2 can route them through the import alias table.
|
|
10
|
+
export function goTypeName(node) {
|
|
11
|
+
if (!node) return null;
|
|
12
|
+
if (node.type === "type_identifier") return node.text;
|
|
13
|
+
if (node.type === "qualified_type") return node.text.replace(/\s+/g, "");
|
|
14
|
+
if (node.type === "generic_type") return goTypeName(node.childForFieldName?.("type") || node.namedChildren?.[0]);
|
|
15
|
+
if (["pointer_type", "parenthesized_type", "unary_expression"].includes(node.type)) {
|
|
16
|
+
return goTypeName(node.childForFieldName?.("type") || node.childForFieldName?.("operand") || node.namedChildren?.[0]);
|
|
17
|
+
}
|
|
18
|
+
if ((node.namedChildren?.length || 0) === 1) return goTypeName(node.namedChildren[0]);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const namedFieldChildren = (node, name) => {
|
|
23
|
+
const children = node?.childrenForFieldName?.(name);
|
|
24
|
+
if (Array.isArray(children) && children.length) return children;
|
|
25
|
+
const child = node?.childForFieldName?.(name);
|
|
26
|
+
return child ? [child] : [];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function goStructFields(typeNode) {
|
|
30
|
+
if (typeNode?.type !== "struct_type") return null;
|
|
31
|
+
const fields = {};
|
|
32
|
+
const visit = (node) => {
|
|
33
|
+
if (!node) return;
|
|
34
|
+
if (node.type === "field_declaration") {
|
|
35
|
+
const type = goTypeName(node.childForFieldName?.("type"));
|
|
36
|
+
if (!type) return;
|
|
37
|
+
const names = namedFieldChildren(node, "name");
|
|
38
|
+
if (names.length) for (const name of names) fields[name.text] = type;
|
|
39
|
+
else {
|
|
40
|
+
// An embedded field is addressed by the base type name (`h.Speaker`).
|
|
41
|
+
const embedded = type.split(".").pop();
|
|
42
|
+
if (embedded) fields[embedded] = type;
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const child of node.namedChildren || []) visit(child);
|
|
47
|
+
};
|
|
48
|
+
visit(typeNode);
|
|
49
|
+
return fields;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function goResultType(declaration) {
|
|
53
|
+
const result = declaration?.childForFieldName?.("result");
|
|
54
|
+
if (!result) return null;
|
|
55
|
+
if (result.type !== "parameter_list") return goTypeName(result);
|
|
56
|
+
const declarations = (result.namedChildren || []).filter((child) => child.type === "parameter_declaration");
|
|
57
|
+
// A call used in `value, err := New()` contributes its first result to the
|
|
58
|
+
// first binding. Preserve that receiver type instead of discarding every
|
|
59
|
+
// multi-result signature just because a trailing error is present.
|
|
60
|
+
return goTypeName(declarations[0]?.childForFieldName?.("type"));
|
|
61
|
+
}
|
|
62
|
+
|
|
8
63
|
export default {
|
|
9
64
|
family: "go",
|
|
10
65
|
grammars: ["go"],
|
|
@@ -20,10 +75,40 @@ export default {
|
|
|
20
75
|
const { grammar, tree, fileRel, caps, addSym, addImportEdge, addExternalImport, imports, resolveGoImport, dirFiles, goModule, goRequires } = ctx;
|
|
21
76
|
|
|
22
77
|
// ---- symbols ----
|
|
23
|
-
for (const cap of caps(grammar, `(function_declaration name: (identifier) @fn)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
78
|
+
for (const cap of caps(grammar, `(function_declaration name: (identifier) @fn)`, tree.rootNode)) {
|
|
79
|
+
const declaration = cap.node.parent;
|
|
80
|
+
addSym(cap.node.text, cap.node.startPosition.row + 1, true, {
|
|
81
|
+
sourceNode: declaration,
|
|
82
|
+
selectionNode: cap.node,
|
|
83
|
+
symbolKind: "function",
|
|
84
|
+
returnType: goResultType(declaration),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
for (const cap of caps(grammar, `(method_declaration name: (field_identifier) @method)`, tree.rootNode)) {
|
|
88
|
+
const declaration = cap.node.parent;
|
|
89
|
+
const receiver = declaration.childForFieldName?.("receiver");
|
|
90
|
+
const receiverDeclaration = receiver?.namedChildren?.find((child) => child.type === "parameter_declaration");
|
|
91
|
+
const receiverType = goTypeName(receiverDeclaration?.childForFieldName?.("type"));
|
|
92
|
+
addSym(cap.node.text, cap.node.startPosition.row + 1, true, {
|
|
93
|
+
sourceNode: declaration,
|
|
94
|
+
selectionNode: cap.node,
|
|
95
|
+
symbolKind: "method",
|
|
96
|
+
memberOf: receiverType || undefined,
|
|
97
|
+
receiverType: receiverType || undefined,
|
|
98
|
+
returnType: goResultType(declaration),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const cap of caps(grammar, `(source_file (type_declaration (type_spec name: (type_identifier) @type)))`, tree.rootNode)) {
|
|
102
|
+
const specification = cap.node.parent;
|
|
103
|
+
const typeNode = specification.childForFieldName?.("type");
|
|
104
|
+
addSym(cap.node.text, cap.node.startPosition.row + 1, false, {
|
|
105
|
+
sourceNode: specification,
|
|
106
|
+
selectionNode: cap.node,
|
|
107
|
+
symbolKind: typeNode?.type === "interface_type" ? "interface" : "type",
|
|
108
|
+
symbolSpace: "type",
|
|
109
|
+
fieldTypes: goStructFields(typeNode),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
27
112
|
for (const cap of caps(grammar, `(source_file (var_declaration (var_spec name: (identifier) @var))) (source_file (const_declaration (const_spec name: (identifier) @const)))`, tree.rootNode))
|
|
28
113
|
addSym(cap.node.text, cap.node.startPosition.row + 1, false, { sourceNode: cap.node.parent });
|
|
29
114
|
|
|
@@ -2,33 +2,10 @@
|
|
|
2
2
|
// Symbols: functions, generators, classes, methods, top-level const/let/var (data + arrow/fn consts).
|
|
3
3
|
// Imports: ESM `import`, CJS `require`, path-aliases (tsconfig/vite), and barrel re-exports (`export … from`).
|
|
4
4
|
// Calls: bare `foo()`. Heritage: `class X extends Y`. Everything runs through the shared ctx (see internal-builder.js).
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
(generator_function_declaration name: (identifier) @fn)
|
|
10
|
-
(class_declaration name: (_) @class)
|
|
11
|
-
(method_definition name: (_) @method)`;
|
|
12
|
-
const TOPVARS = `
|
|
13
|
-
(program (lexical_declaration (variable_declarator) @decl))
|
|
14
|
-
(program (variable_declaration (variable_declarator) @decl))
|
|
15
|
-
(program (export_statement (lexical_declaration (variable_declarator) @decl)))
|
|
16
|
-
(program (export_statement (variable_declaration (variable_declarator) @decl)))`;
|
|
17
|
-
const TYPES = `
|
|
18
|
-
(interface_declaration name: (_) @interface)
|
|
19
|
-
(type_alias_declaration name: (_) @type)
|
|
20
|
-
(enum_declaration name: (_) @enum)`;
|
|
21
|
-
const REQUIRE = `(variable_declarator name: (_) @lhs value: (call_expression function: (identifier) @req arguments: (arguments (string (string_fragment) @src))))`;
|
|
22
|
-
|
|
23
|
-
function parseExportSpecifiers(raw) {
|
|
24
|
-
return String(raw || "").split(",").map((part) => {
|
|
25
|
-
const text = part.trim();
|
|
26
|
-
const typeOnly = /^type\s+/.test(text);
|
|
27
|
-
const clean = text.replace(/^type\s+/, "").trim();
|
|
28
|
-
const match = clean.match(/^([A-Za-z_$][\w$]*|default)(?:\s+as\s+([A-Za-z_$][\w$]*|default))?$/);
|
|
29
|
-
return match ? { imported: match[1], exported: match[2] || match[1], typeOnly } : null;
|
|
30
|
-
}).filter(Boolean);
|
|
31
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
CALLABLE, FUNCS, TOPVARS, TYPES, REQUIRE, parseExportSpecifiers,
|
|
7
|
+
importTypeOnly, reexportTypeOnly,
|
|
8
|
+
} from './js/queries.js';
|
|
32
9
|
|
|
33
10
|
export default {
|
|
34
11
|
family: "js",
|
|
@@ -162,23 +139,6 @@ export default {
|
|
|
162
139
|
});
|
|
163
140
|
}
|
|
164
141
|
|
|
165
|
-
const importTypeOnly = (node) => {
|
|
166
|
-
if (/^\s*import\s+type\b/.test(node.text)) return true;
|
|
167
|
-
const clause = node.namedChildren.find((c) => c.type === "import_clause");
|
|
168
|
-
if (!clause) return false; // side-effect import executes the target module
|
|
169
|
-
const parts = clause.namedChildren;
|
|
170
|
-
if (parts.some((c) => c.type === "identifier" || c.type === "namespace_import")) return false;
|
|
171
|
-
const named = parts.find((c) => c.type === "named_imports");
|
|
172
|
-
const specs = named?.namedChildren.filter((c) => c.type === "import_specifier") || [];
|
|
173
|
-
return specs.length > 0 && specs.every((s) => /^\s*type\b/.test(s.text));
|
|
174
|
-
};
|
|
175
|
-
const reexportTypeOnly = (node) => {
|
|
176
|
-
if (/^\s*export\s+type\b/.test(node.text)) return true;
|
|
177
|
-
const clause = node.namedChildren.find((c) => c.type === "export_clause");
|
|
178
|
-
const specs = clause?.namedChildren.filter((c) => c.type === "export_specifier") || [];
|
|
179
|
-
return specs.length > 0 && specs.every((s) => /^\s*type\b/.test(s.text));
|
|
180
|
-
};
|
|
181
|
-
|
|
182
142
|
// ---- ESM imports ----
|
|
183
143
|
for (const cap of caps(grammar, `(import_statement) @imp`, tree.rootNode)) {
|
|
184
144
|
const node = cap.node; const srcNode = field(node, "source");
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function createPass2Resolution({symIdsByFileName, nodeById, importedLocals, symByFileName}) {
|
|
2
|
+
const dirSymbols = new Map(), dirMethods = new Map(), dirMethodsByName = new Map(), dirTypes = new Map()
|
|
3
|
+
const sharesDirScope = (file) => file.endsWith('.go') || file.endsWith('.cs') || file.endsWith('.rs')
|
|
4
|
+
for (const [file, names] of symIdsByFileName) {
|
|
5
|
+
if (!sharesDirScope(file)) continue
|
|
6
|
+
const dir = file.includes('/') ? file.slice(0, file.lastIndexOf('/')) : ''
|
|
7
|
+
let symbols = dirSymbols.get(dir)
|
|
8
|
+
if (!symbols) dirSymbols.set(dir, (symbols = new Map()))
|
|
9
|
+
for (const [name, ids] of names) for (const id of ids) {
|
|
10
|
+
const node = nodeById.get(id)
|
|
11
|
+
if (file.endsWith('.go') && node?.member_of && node?.symbol_kind === 'method') {
|
|
12
|
+
let receivers = dirMethods.get(dir)
|
|
13
|
+
if (!receivers) dirMethods.set(dir, (receivers = new Map()))
|
|
14
|
+
let methods = receivers.get(node.member_of)
|
|
15
|
+
if (!methods) receivers.set(node.member_of, (methods = new Map()))
|
|
16
|
+
const exact = methods.get(name) || []
|
|
17
|
+
exact.push(id); methods.set(name, exact)
|
|
18
|
+
let byName = dirMethodsByName.get(dir)
|
|
19
|
+
if (!byName) dirMethodsByName.set(dir, (byName = new Map()))
|
|
20
|
+
const candidates = byName.get(name) || []
|
|
21
|
+
candidates.push(id); byName.set(name, candidates)
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
if (!symbols.has(name)) symbols.set(name, id)
|
|
25
|
+
if (file.endsWith('.go') && node?.symbol_space === 'type') {
|
|
26
|
+
let types = dirTypes.get(dir)
|
|
27
|
+
if (!types) dirTypes.set(dir, (types = new Map()))
|
|
28
|
+
if (!types.has(name)) types.set(name, id)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const symbolSpace = (node) => {
|
|
33
|
+
const explicit = String(node?.symbol_space || '')
|
|
34
|
+
if (['value', 'type', 'both'].includes(explicit)) return explicit
|
|
35
|
+
const kind = String(node?.symbol_kind || '').toLowerCase()
|
|
36
|
+
if (['interface', 'type'].includes(kind)) return 'type'
|
|
37
|
+
return ['class', 'enum'].includes(kind) ? 'both' : 'value'
|
|
38
|
+
}
|
|
39
|
+
const resolveNamedSymbol = (file, name, space = 'value') => {
|
|
40
|
+
const ids = symIdsByFileName.get(file)?.get(name) || []
|
|
41
|
+
const exact = ids.find((id) => symbolSpace(nodeById.get(id)) === space)
|
|
42
|
+
return exact || ids.find((id) => ['both', space].includes(symbolSpace(nodeById.get(id)))) || null
|
|
43
|
+
}
|
|
44
|
+
const resolveCall = (name, file) => {
|
|
45
|
+
const local = resolveNamedSymbol(file, name, 'value')
|
|
46
|
+
if (local) return local
|
|
47
|
+
if (sharesDirScope(file)) {
|
|
48
|
+
const dir = file.includes('/') ? file.slice(0, file.lastIndexOf('/')) : ''
|
|
49
|
+
const symbols = dirSymbols.get(dir)
|
|
50
|
+
if (symbols?.has(name)) return symbols.get(name)
|
|
51
|
+
}
|
|
52
|
+
const imported = importedLocals.get(file)?.get(name)
|
|
53
|
+
if (!imported?.targetFile) return null
|
|
54
|
+
return resolveNamedSymbol(imported.originFile || imported.targetFile, imported.originName || imported.imported, 'value')
|
|
55
|
+
}
|
|
56
|
+
const javaTypeKinds = new Set(['class', 'interface', 'enum', 'record', 'annotation'])
|
|
57
|
+
const resolveJavaType = (name, file) => {
|
|
58
|
+
const imported = importedLocals.get(file)?.get(name)
|
|
59
|
+
if (imported?.targetFile) {
|
|
60
|
+
const symbols = symByFileName.get(imported.targetFile)
|
|
61
|
+
const target = symbols?.get(imported.imported) || symbols?.get(name)
|
|
62
|
+
if (target && javaTypeKinds.has(nodeById.get(target)?.symbol_kind)) return target
|
|
63
|
+
}
|
|
64
|
+
const target = symByFileName.get(file)?.get(name)
|
|
65
|
+
return target && javaTypeKinds.has(nodeById.get(target)?.symbol_kind) ? target : null
|
|
66
|
+
}
|
|
67
|
+
return {dirSymbols, dirMethods, dirMethodsByName, dirTypes, resolveNamedSymbol, resolveCall, resolveJavaType}
|
|
68
|
+
}
|
|
@@ -9,7 +9,7 @@ import {childProcessEnv} from '../child-env.js'
|
|
|
9
9
|
import {createRepoBoundary} from '../repo-path.js'
|
|
10
10
|
|
|
11
11
|
export const REPOSITORY_FRESHNESS_PROBE_V = 1
|
|
12
|
-
export const GRAPH_BUILDER_SCHEMA_V =
|
|
12
|
+
export const GRAPH_BUILDER_SCHEMA_V = 6
|
|
13
13
|
export const GRAPH_BUILDER_VERSION = (() => {
|
|
14
14
|
try { return String(createRequire(import.meta.url)('../../package.json').version) }
|
|
15
15
|
catch { return '0.0.0' }
|
|
@@ -23,6 +23,7 @@ const CURRENT_GRAPH_SCHEMA = Object.freeze({
|
|
|
23
23
|
edgeTypesV: 2,
|
|
24
24
|
edgeProvenanceV: 1,
|
|
25
25
|
complexityV: 2,
|
|
26
|
+
physicalFileLocV: 1,
|
|
26
27
|
repoBoundaryV: 1,
|
|
27
28
|
barrelResolutionV: 1,
|
|
28
29
|
reExportOccurrencesV: 1,
|
|
@@ -163,7 +163,7 @@ function mergeScopedGraph(base, scoped, affected, snapshot) {
|
|
|
163
163
|
controlHashes: snapshot.controlHashes,
|
|
164
164
|
graphRevision: snapshot.revision,
|
|
165
165
|
};
|
|
166
|
-
for (const key of ["extImportsV", "edgeTypesV", "edgeProvenanceV", "complexityV", "repoBoundaryV", "barrelResolutionV", "reExportOccurrencesV", "symbolSpacesV", "extractorSchemaV"]) {
|
|
166
|
+
for (const key of ["extImportsV", "edgeTypesV", "edgeProvenanceV", "complexityV", "physicalFileLocV", "repoBoundaryV", "barrelResolutionV", "reExportOccurrencesV", "symbolSpacesV", "extractorSchemaV"]) {
|
|
167
167
|
merged[key] = Math.max(Number(base[key]) || 0, Number(scoped[key]) || 0);
|
|
168
168
|
}
|
|
169
169
|
return merged;
|
|
@@ -184,7 +184,8 @@ export async function refreshGraphIncrementally(repoDir, existingGraph, {
|
|
|
184
184
|
|
|
185
185
|
if (!existingGraph || !existingGraph.fileHashes || !existingGraph.fileExportSignatures
|
|
186
186
|
|| !existingGraph.controlHashes || !existingGraph.jsExportRecords || Number(existingGraph.barrelResolutionV) < 1
|
|
187
|
-
|| Number(existingGraph.extractorSchemaV) < 5 || Number(existingGraph.
|
|
187
|
+
|| Number(existingGraph.extractorSchemaV) < 5 || Number(existingGraph.physicalFileLocV) < 1
|
|
188
|
+
|| Number(existingGraph.reExportOccurrencesV) < 1
|
|
188
189
|
|| Number(existingGraph.symbolSpacesV) < 1 || Number(existingGraph.edgeProvenanceV) < 1) {
|
|
189
190
|
return full("incremental-baseline-unavailable");
|
|
190
191
|
}
|
|
@@ -8,11 +8,17 @@ import { specToPkg } from "./builder/spec-pkg.js";
|
|
|
8
8
|
import { analyzeSyntaxComplexity } from "../analysis/source-complexity.js";
|
|
9
9
|
import { Parser, Query, GRAMMARS, LANGS, EXT_LANG, FAMILY, isDataFile, isDocFile, MAX_PARSE_BYTES, ensureParser, walk } from "./internal-builder.langs.js";
|
|
10
10
|
import { buildResolvers } from "./internal-builder.resolvers.js";
|
|
11
|
-
import { addJavaReferences } from "./internal-builder.java.js";
|
|
12
11
|
import { assignDeterministicCommunities } from "./community.js";
|
|
13
|
-
import { resolveJsBarrels } from "./internal-builder.barrels.js";
|
|
14
12
|
import { snapshotRepository } from "./incremental-refresh.js";
|
|
15
13
|
import { EDGE_PROVENANCE_V, stampEdgeProvenance } from "./edge-provenance.js";
|
|
14
|
+
import {runInternalGraphPass2} from './internal-builder.pass2.js'
|
|
15
|
+
|
|
16
|
+
function physicalLineCount(text) {
|
|
17
|
+
if (!text.length) return 0;
|
|
18
|
+
let lines = 1;
|
|
19
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) lines++;
|
|
20
|
+
return text.endsWith("\n") ? lines - 1 : lines;
|
|
21
|
+
}
|
|
16
22
|
|
|
17
23
|
// Parse a repo directory into a graph-builder-compatible { nodes, links } graph.
|
|
18
24
|
export async function buildInternalGraph(repoDir, opts = {}) {
|
|
@@ -78,10 +84,13 @@ export async function buildInternalGraph(repoDir, opts = {}) {
|
|
|
78
84
|
let _parsed = 0;
|
|
79
85
|
for (const abs of files) {
|
|
80
86
|
const fileRel = rel(abs); const ext = extname(abs);
|
|
81
|
-
|
|
82
|
-
if (isDataFile(fileRel) || isDocFile(fileRel)) {
|
|
87
|
+
const fileNode = { id: fileRel, label: fileRel.split("/").pop(), file_type: "code", source_file: fileRel, source_location: "L1" };
|
|
88
|
+
if (isDataFile(fileRel) || isDocFile(fileRel)) {
|
|
89
|
+
addNode(fileNode); perFileSymbols.set(fileRel, []); symByFileName.set(fileRel, new Map()); symIdsByFileName.set(fileRel, new Map()); continue;
|
|
90
|
+
} // config/infra/docs file-only node
|
|
91
|
+
let code; try { code = readFileSync(abs, "utf8"); } catch { addNode(fileNode); continue; }
|
|
92
|
+
addNode({ ...fileNode, physical_loc: physicalLineCount(code) });
|
|
83
93
|
const grammar = EXT_LANG[ext]; const lang = LANGS[FAMILY[grammar]]; if (!lang || !langs[grammar]) continue;
|
|
84
|
-
let code; try { code = readFileSync(abs, "utf8"); } catch { continue; }
|
|
85
94
|
// giant / generated / minified file → keep the file NODE but don't parse symbols (avoids a wedged parse)
|
|
86
95
|
if (code.length > MAX_PARSE_BYTES) { perFileSymbols.set(fileRel, []); symByFileName.set(fileRel, new Map()); symIdsByFileName.set(fileRel, new Map()); continue; }
|
|
87
96
|
if ((++_parsed % 24) === 0) await new Promise((r) => setImmediate(r)); // breathe: let the UI paint between chunks
|
|
@@ -144,7 +153,10 @@ export async function buildInternalGraph(repoDir, opts = {}) {
|
|
|
144
153
|
...(extra && extra.symbolSpace ? { symbol_space: extra.symbolSpace } : {}),
|
|
145
154
|
...(extra && extra.memberOf ? { member_of: extra.memberOf } : {}),
|
|
146
155
|
...(extra && extra.visibility ? { visibility: extra.visibility } : {}),
|
|
147
|
-
...(Number.isInteger(extra?.parameterCount) ? { parameter_count: extra.parameterCount } : {})
|
|
156
|
+
...(Number.isInteger(extra?.parameterCount) ? { parameter_count: extra.parameterCount } : {}),
|
|
157
|
+
...(extra?.receiverType ? { receiver_type: extra.receiverType } : {}),
|
|
158
|
+
...(extra?.returnType ? { return_type: extra.returnType } : {}),
|
|
159
|
+
...(extra?.fieldTypes && Object.keys(extra.fieldTypes).length ? { field_types: extra.fieldTypes } : {})
|
|
148
160
|
});
|
|
149
161
|
links.push({ source: fileRel, target: id, relation: "contains", confidence: "EXTRACTED" });
|
|
150
162
|
syms.push({
|
|
@@ -216,184 +228,10 @@ export async function buildInternalGraph(repoDir, opts = {}) {
|
|
|
216
228
|
// ---- pass 2: scope-aware calls + inheritance (Go package = whole dir → same-dir symbols share scope;
|
|
217
229
|
// C# gets the same treatment: one folder ≈ one namespace by convention, and `using` names namespaces,
|
|
218
230
|
// not files, so the folder map is the only reliable cross-file resolver) ----
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (!sharesDirScope(fr)) continue;
|
|
223
|
-
const d = fr.includes("/") ? fr.slice(0, fr.lastIndexOf("/")) : "";
|
|
224
|
-
let dm = goDirSymbols.get(d); if (!dm) goDirSymbols.set(d, (dm = new Map()));
|
|
225
|
-
for (const [n, id] of m) if (!dm.has(n)) dm.set(n, id);
|
|
226
|
-
}
|
|
227
|
-
const inferredSymbolSpace = (node) => {
|
|
228
|
-
const explicit = String(node?.symbol_space || "");
|
|
229
|
-
if (["value", "type", "both"].includes(explicit)) return explicit;
|
|
230
|
-
const kind = String(node?.symbol_kind || "").toLowerCase();
|
|
231
|
-
if (["interface", "type"].includes(kind)) return "type";
|
|
232
|
-
if (["class", "enum"].includes(kind)) return "both";
|
|
233
|
-
return "value";
|
|
234
|
-
};
|
|
235
|
-
const resolveNamedSymbol = (file, name, space = "value") => {
|
|
236
|
-
const ids = symIdsByFileName.get(file)?.get(name) || [];
|
|
237
|
-
const accepts = (id) => {
|
|
238
|
-
const symbolSpace = inferredSymbolSpace(nodeById.get(id));
|
|
239
|
-
return symbolSpace === "both" || symbolSpace === space;
|
|
240
|
-
};
|
|
241
|
-
const exact = ids.find((id) => inferredSymbolSpace(nodeById.get(id)) === space);
|
|
242
|
-
return exact || ids.find(accepts) || null;
|
|
243
|
-
};
|
|
244
|
-
const { resolveNamespaceMember, reExportOccurrences } = resolveJsBarrels({
|
|
245
|
-
jsExports, importedLocals, links,
|
|
246
|
-
resolveSymbol: (file, name, typeOnly) => resolveNamedSymbol(file, name, typeOnly ? "type" : "value"),
|
|
231
|
+
const {reExportOccurrences} = runInternalGraphPass2({
|
|
232
|
+
files, rel, langs, caps, field, links, nodeById, perFileSymbols, symByFileName,
|
|
233
|
+
symIdsByFileName, importedLocals, jsExports,
|
|
247
234
|
});
|
|
248
|
-
// Exact source ranges can overlap (a named function nested inside another function). Attribute a call
|
|
249
|
-
// to the innermost matching symbol, not whichever outer declaration happened to be added first.
|
|
250
|
-
const enclosing = (fileRel, line) => {
|
|
251
|
-
let best = null;
|
|
252
|
-
for (const s of perFileSymbols.get(fileRel) || []) {
|
|
253
|
-
if (line < s.start || line > s.end) continue;
|
|
254
|
-
if (!best || s.start > best.start || (s.start === best.start && s.end < best.end)) best = s;
|
|
255
|
-
}
|
|
256
|
-
return best;
|
|
257
|
-
};
|
|
258
|
-
const resolveCall = (name, fileRel) => {
|
|
259
|
-
const local = resolveNamedSymbol(fileRel, name, "value"); if (local) return local;
|
|
260
|
-
if (sharesDirScope(fileRel)) { const d = fileRel.includes("/") ? fileRel.slice(0, fileRel.lastIndexOf("/")) : ""; const dm = goDirSymbols.get(d); if (dm && dm.has(name)) return dm.get(name); }
|
|
261
|
-
const imp = importedLocals.get(fileRel) && importedLocals.get(fileRel).get(name);
|
|
262
|
-
if (imp && imp.targetFile) {
|
|
263
|
-
const targetFile = imp.originFile || imp.targetFile;
|
|
264
|
-
const importedName = imp.originName || imp.imported;
|
|
265
|
-
return resolveNamedSymbol(targetFile, importedName, "value");
|
|
266
|
-
}
|
|
267
|
-
return null;
|
|
268
|
-
};
|
|
269
|
-
const javaTypeKinds = new Set(["class", "interface", "enum", "record", "annotation"]);
|
|
270
|
-
const resolveJavaType = (name, fileRel) => {
|
|
271
|
-
const imp = importedLocals.get(fileRel)?.get(name);
|
|
272
|
-
if (imp?.targetFile) {
|
|
273
|
-
const symbols = symByFileName.get(imp.targetFile);
|
|
274
|
-
const target = symbols?.get(imp.imported) || symbols?.get(name);
|
|
275
|
-
if (target && javaTypeKinds.has(nodeById.get(target)?.symbol_kind)) return target;
|
|
276
|
-
}
|
|
277
|
-
const target = symByFileName.get(fileRel)?.get(name);
|
|
278
|
-
return target && javaTypeKinds.has(nodeById.get(target)?.symbol_kind) ? target : null;
|
|
279
|
-
};
|
|
280
|
-
for (const abs of files) {
|
|
281
|
-
const fileRel = rel(abs); const grammar = EXT_LANG[extname(abs)]; if (!grammar) continue;
|
|
282
|
-
const lang = LANGS[FAMILY[grammar]]; if (!lang || lang.isWeb || !langs[grammar]) continue;
|
|
283
|
-
let code; try { code = readFileSync(abs, "utf8"); } catch { continue; }
|
|
284
|
-
const parser = new Parser(); parser.setLanguage(langs[grammar]);
|
|
285
|
-
let tree; try { tree = parser.parse(code); } catch { continue; }
|
|
286
|
-
if (!lang.customCalls) for (const cap of caps(grammar, lang.calls, tree.rootNode)) {
|
|
287
|
-
const caller = enclosing(fileRel, cap.node.startPosition.row + 1); if (!caller) continue;
|
|
288
|
-
const target = resolveCall(cap.node.text, fileRel); if (!target || target === caller.id) continue;
|
|
289
|
-
links.push({ source: caller.id, target, relation: "calls", confidence: "INFERRED", line: cap.node.startPosition.row + 1 });
|
|
290
|
-
}
|
|
291
|
-
if (typeof lang.pass2 === "function") {
|
|
292
|
-
try {
|
|
293
|
-
lang.pass2({
|
|
294
|
-
grammar, tree, fileRel, code, caps, field, enclosing, links, nodeById,
|
|
295
|
-
perFileSymbols, symByFileName, symIdsByFileName, importedLocals, resolveCall, resolveJavaType,
|
|
296
|
-
});
|
|
297
|
-
} catch (e) { /* one language-specific resolver never sinks the graph */ void e; }
|
|
298
|
-
}
|
|
299
|
-
// qualified/selector calls (Go): `pkg.Func()` → the imported package's dir; else `receiver.Method()` → the
|
|
300
|
-
// SAME package (heuristic by method name — connects lifecycle methods like peer.Enable() that need type info).
|
|
301
|
-
if (lang.selectorCall) for (const cap of caps(grammar, lang.selectorCall, tree.rootNode)) {
|
|
302
|
-
const sel = cap.node; const operand = field(sel, "operand"), fld = field(sel, "field");
|
|
303
|
-
if (!operand || operand.type !== "identifier" || !fld) continue;
|
|
304
|
-
const caller = enclosing(fileRel, sel.startPosition.row + 1); if (!caller) continue;
|
|
305
|
-
const imp = importedLocals.get(fileRel) && importedLocals.get(fileRel).get(operand.text);
|
|
306
|
-
const dir = fileRel.includes("/") ? fileRel.slice(0, fileRel.lastIndexOf("/")) : "";
|
|
307
|
-
const dm = goDirSymbols.get(imp && imp.targetDir ? imp.targetDir : dir);
|
|
308
|
-
const target = dm && dm.get(fld.text);
|
|
309
|
-
if (target && target !== caller.id) links.push({ source: caller.id, target, relation: "calls", confidence: "INFERRED", line: cap.node.startPosition.row + 1 });
|
|
310
|
-
}
|
|
311
|
-
for (const heritageSpec of lang.heritage || []) {
|
|
312
|
-
const query = typeof heritageSpec === "string" ? heritageSpec : heritageSpec.query;
|
|
313
|
-
const relation = typeof heritageSpec === "string" ? "inherits" : (heritageSpec.relation || "inherits");
|
|
314
|
-
for (const cap of caps(grammar, query, tree.rootNode)) {
|
|
315
|
-
const cls = enclosing(fileRel, cap.node.startPosition.row + 1); if (!cls) continue;
|
|
316
|
-
const target = FAMILY[grammar] === "java" ? resolveJavaType(cap.node.text, fileRel) : resolveCall(cap.node.text, fileRel);
|
|
317
|
-
if (target && target !== cls.id) links.push({ source: cls.id, target, relation, confidence: "INFERRED" });
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// JSX is a real symbol use even though it is not a call_expression. Resolve imported components to their
|
|
321
|
-
// declaration so component fan-in and unused-export checks do not claim `<SettingsView />` is unreferenced.
|
|
322
|
-
if (FAMILY[grammar] === "js") {
|
|
323
|
-
// Namespace calls (`ui.run()`) need the member name before an export-star facade can be resolved.
|
|
324
|
-
for (const cap of caps(grammar, `(call_expression function: (member_expression) @memberCall)`, tree.rootNode)) {
|
|
325
|
-
const object = field(cap.node, "object"), property = field(cap.node, "property");
|
|
326
|
-
if (!object || object.type !== "identifier" || !property) continue;
|
|
327
|
-
const imp = importedLocals.get(fileRel)?.get(object.text);
|
|
328
|
-
if (!imp || !["*", "default"].includes(imp.imported) || imp.typeOnly) continue;
|
|
329
|
-
const origin = resolveNamespaceMember(fileRel, imp, property.text, "call");
|
|
330
|
-
if (origin.status !== "resolved") continue;
|
|
331
|
-
const target = resolveNamedSymbol(origin.origin.file, origin.origin.name, "value");
|
|
332
|
-
const caller = enclosing(fileRel, cap.node.startPosition.row + 1);
|
|
333
|
-
if (target && caller && target !== caller.id) links.push({ source: caller.id, target, relation: "calls", confidence: "INFERRED", line: cap.node.startPosition.row + 1 });
|
|
334
|
-
}
|
|
335
|
-
for (const cap of caps(grammar, `[
|
|
336
|
-
(jsx_opening_element name: (_) @jsx)
|
|
337
|
-
(jsx_self_closing_element name: (_) @jsx)
|
|
338
|
-
]`, tree.rootNode)) {
|
|
339
|
-
const jsxName = cap.node.text;
|
|
340
|
-
const parts = jsxName.split(".");
|
|
341
|
-
const localName = parts[0];
|
|
342
|
-
if (parts.length === 1 && !/^[A-Z_$]/.test(localName)) continue; // undotted lowercase tags are platform/intrinsic elements
|
|
343
|
-
const imp = importedLocals.get(fileRel)?.get(localName);
|
|
344
|
-
if (!imp || !imp.targetFile || imp.typeOnly) continue;
|
|
345
|
-
let targetFile = imp.originFile || imp.targetFile;
|
|
346
|
-
let importedName = imp.originName || imp.imported;
|
|
347
|
-
if (imp.imported === "*" && parts.length > 1) {
|
|
348
|
-
const origin = resolveNamespaceMember(fileRel, imp, parts[parts.length - 1], "jsx");
|
|
349
|
-
if (origin.status === "resolved") { targetFile = origin.origin.file; importedName = origin.origin.name; }
|
|
350
|
-
}
|
|
351
|
-
const target = resolveNamedSymbol(targetFile, importedName, "value") || resolveNamedSymbol(targetFile, localName, "value");
|
|
352
|
-
if (!target) continue;
|
|
353
|
-
const owner = enclosing(fileRel, cap.node.startPosition.row + 1);
|
|
354
|
-
links.push({ source: owner?.id || fileRel, target, relation: "references", confidence: "INFERRED", usage: "jsx", line: cap.node.startPosition.row + 1 });
|
|
355
|
-
}
|
|
356
|
-
// Type identifiers are a separate namespace in TypeScript. Resolve them to type/both-space
|
|
357
|
-
// declarations without creating runtime calls or keeping a same-named value symbol alive.
|
|
358
|
-
if (grammar !== "javascript") for (const cap of caps(grammar, `(type_identifier) @typeRef`, tree.rootNode)) {
|
|
359
|
-
const name = cap.node.text;
|
|
360
|
-
const imp = importedLocals.get(fileRel)?.get(name);
|
|
361
|
-
const targetFile = imp?.originFile || imp?.targetFile || fileRel;
|
|
362
|
-
const targetName = imp?.originName || imp?.imported || name;
|
|
363
|
-
const target = resolveNamedSymbol(targetFile, targetName, "type");
|
|
364
|
-
if (!target || String(target).startsWith(`${fileRel}#${name}@${cap.node.startPosition.row + 1}`)) continue;
|
|
365
|
-
const owner = enclosing(fileRel, cap.node.startPosition.row + 1);
|
|
366
|
-
const source = owner?.id || fileRel;
|
|
367
|
-
if (source === target) continue;
|
|
368
|
-
links.push({source, target, relation: "references", confidence: "EXTRACTED", provenance: "RESOLVED", typeOnly: true, line: cap.node.startPosition.row + 1, usage: "type"});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
// Go value references: a top-level const/var/type/func used BY NAME (bare `X`, or cross-package `pkg.X`) in
|
|
372
|
-
// another file/scope → a `references` edge, so used-but-never-called symbols (message-type consts, etc.) are
|
|
373
|
-
// not falsely DEAD. (Same-file usage is already covered by graph-builder-analysis localRefs.) Go-only for now.
|
|
374
|
-
if (FAMILY[grammar] === "go") {
|
|
375
|
-
const refSeen = new Set();
|
|
376
|
-
const emitRef = (src, target) => { if (!target || target === src) return; const k = src + ">" + target; if (refSeen.has(k)) return; refSeen.add(k); links.push({ source: src, target, relation: "references", confidence: "INFERRED" }); };
|
|
377
|
-
const dir = fileRel.includes("/") ? fileRel.slice(0, fileRel.lastIndexOf("/")) : "";
|
|
378
|
-
const dm = goDirSymbols.get(dir);
|
|
379
|
-
for (const cap of caps(grammar, `[(identifier) (type_identifier)] @id`, tree.rootNode)) { // type_identifier → struct/type usage
|
|
380
|
-
const target = dm && dm.get(cap.node.text); if (!target || target.slice(0, target.indexOf("#")) === fileRel) continue;
|
|
381
|
-
const caller = enclosing(fileRel, cap.node.startPosition.row + 1); emitRef(caller ? caller.id : fileRel, target);
|
|
382
|
-
}
|
|
383
|
-
for (const cap of caps(grammar, `(selector_expression) @sel`, tree.rootNode)) {
|
|
384
|
-
const sel = cap.node; const operand = field(sel, "operand"), fld = field(sel, "field");
|
|
385
|
-
if (!operand || operand.type !== "identifier" || !fld) continue;
|
|
386
|
-
const imp = importedLocals.get(fileRel) && importedLocals.get(fileRel).get(operand.text); if (!imp || !imp.targetDir) continue;
|
|
387
|
-
const tdm = goDirSymbols.get(imp.targetDir); const target = tdm && tdm.get(fld.text); if (!target) continue;
|
|
388
|
-
const caller = enclosing(fileRel, sel.startPosition.row + 1); emitRef(caller ? caller.id : fileRel, target);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
if (FAMILY[grammar] === "java") {
|
|
392
|
-
addJavaReferences({ grammar, tree, fileRel, caps, resolveJavaType, enclosing, links });
|
|
393
|
-
}
|
|
394
|
-
tree.delete();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
235
|
// HTML class/id usage → the CSS file(s) defining that selector: file-level reference edges (deduped per pair).
|
|
398
236
|
const htmlRefSeen = new Set();
|
|
399
237
|
for (const u of htmlUsages) {
|
|
@@ -422,6 +260,7 @@ export async function buildInternalGraph(repoDir, opts = {}) {
|
|
|
422
260
|
edgeTypesV: 2,
|
|
423
261
|
edgeProvenanceV: EDGE_PROVENANCE_V,
|
|
424
262
|
complexityV: 2,
|
|
263
|
+
physicalFileLocV: 1,
|
|
425
264
|
repoBoundaryV: 1,
|
|
426
265
|
barrelResolutionV: 1,
|
|
427
266
|
reExportOccurrencesV: 1,
|