musubix3 0.1.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.
Files changed (97) hide show
  1. package/.github/plugin/marketplace.json +16 -0
  2. package/.github/skills/sdd-change/SKILL.md +78 -0
  3. package/.github/skills/sdd-design/SKILL.md +26 -0
  4. package/.github/skills/sdd-formal-codegraph/SKILL.md +53 -0
  5. package/.github/skills/sdd-implementation/SKILL.md +54 -0
  6. package/.github/skills/sdd-knowledge/SKILL.md +24 -0
  7. package/.github/skills/sdd-quality/SKILL.md +69 -0
  8. package/.github/skills/sdd-requirements/SKILL.md +42 -0
  9. package/.github/skills/sdd-traceability/SKILL.md +28 -0
  10. package/CHANGELOG.md +152 -0
  11. package/LICENSE +21 -0
  12. package/README-ja.md +573 -0
  13. package/README.md +639 -0
  14. package/assets/ADR-0001.md +15 -0
  15. package/assets/constitution.md +17 -0
  16. package/assets/design.md +13 -0
  17. package/assets/requirements.md +15 -0
  18. package/dist/packages/analysis/src/adapters.d.ts +12 -0
  19. package/dist/packages/analysis/src/adapters.js +167 -0
  20. package/dist/packages/analysis/src/adapters.js.map +1 -0
  21. package/dist/packages/analysis/src/attestation.d.ts +50 -0
  22. package/dist/packages/analysis/src/attestation.js +474 -0
  23. package/dist/packages/analysis/src/attestation.js.map +1 -0
  24. package/dist/packages/analysis/src/change.d.ts +53 -0
  25. package/dist/packages/analysis/src/change.js +337 -0
  26. package/dist/packages/analysis/src/change.js.map +1 -0
  27. package/dist/packages/analysis/src/config.d.ts +104 -0
  28. package/dist/packages/analysis/src/config.js +456 -0
  29. package/dist/packages/analysis/src/config.js.map +1 -0
  30. package/dist/packages/analysis/src/files.d.ts +13 -0
  31. package/dist/packages/analysis/src/files.js +103 -0
  32. package/dist/packages/analysis/src/files.js.map +1 -0
  33. package/dist/packages/analysis/src/formal.d.ts +82 -0
  34. package/dist/packages/analysis/src/formal.js +579 -0
  35. package/dist/packages/analysis/src/formal.js.map +1 -0
  36. package/dist/packages/analysis/src/gate.d.ts +56 -0
  37. package/dist/packages/analysis/src/gate.js +484 -0
  38. package/dist/packages/analysis/src/gate.js.map +1 -0
  39. package/dist/packages/analysis/src/graph.d.ts +52 -0
  40. package/dist/packages/analysis/src/graph.js +737 -0
  41. package/dist/packages/analysis/src/graph.js.map +1 -0
  42. package/dist/packages/analysis/src/index.d.ts +17 -0
  43. package/dist/packages/analysis/src/index.js +18 -0
  44. package/dist/packages/analysis/src/index.js.map +1 -0
  45. package/dist/packages/analysis/src/knowledge.d.ts +29 -0
  46. package/dist/packages/analysis/src/knowledge.js +95 -0
  47. package/dist/packages/analysis/src/knowledge.js.map +1 -0
  48. package/dist/packages/analysis/src/model-correspondence.d.ts +45 -0
  49. package/dist/packages/analysis/src/model-correspondence.js +310 -0
  50. package/dist/packages/analysis/src/model-correspondence.js.map +1 -0
  51. package/dist/packages/analysis/src/mutation.d.ts +54 -0
  52. package/dist/packages/analysis/src/mutation.js +299 -0
  53. package/dist/packages/analysis/src/mutation.js.map +1 -0
  54. package/dist/packages/analysis/src/order.d.ts +23 -0
  55. package/dist/packages/analysis/src/order.js +89 -0
  56. package/dist/packages/analysis/src/order.js.map +1 -0
  57. package/dist/packages/analysis/src/performance.d.ts +65 -0
  58. package/dist/packages/analysis/src/performance.js +319 -0
  59. package/dist/packages/analysis/src/performance.js.map +1 -0
  60. package/dist/packages/analysis/src/process.d.ts +14 -0
  61. package/dist/packages/analysis/src/process.js +79 -0
  62. package/dist/packages/analysis/src/process.js.map +1 -0
  63. package/dist/packages/analysis/src/tdd.d.ts +65 -0
  64. package/dist/packages/analysis/src/tdd.js +353 -0
  65. package/dist/packages/analysis/src/tdd.js.map +1 -0
  66. package/dist/packages/analysis/src/trace.d.ts +45 -0
  67. package/dist/packages/analysis/src/trace.js +242 -0
  68. package/dist/packages/analysis/src/trace.js.map +1 -0
  69. package/dist/packages/analysis/src/workflow.d.ts +59 -0
  70. package/dist/packages/analysis/src/workflow.js +405 -0
  71. package/dist/packages/analysis/src/workflow.js.map +1 -0
  72. package/dist/packages/cli/src/install.d.ts +15 -0
  73. package/dist/packages/cli/src/install.js +71 -0
  74. package/dist/packages/cli/src/install.js.map +1 -0
  75. package/dist/packages/cli/src/main.d.ts +3 -0
  76. package/dist/packages/cli/src/main.js +364 -0
  77. package/dist/packages/cli/src/main.js.map +1 -0
  78. package/dist/packages/domain/src/constitution.d.ts +3 -0
  79. package/dist/packages/domain/src/constitution.js +46 -0
  80. package/dist/packages/domain/src/constitution.js.map +1 -0
  81. package/dist/packages/domain/src/design.d.ts +8 -0
  82. package/dist/packages/domain/src/design.js +59 -0
  83. package/dist/packages/domain/src/design.js.map +1 -0
  84. package/dist/packages/domain/src/index.d.ts +5 -0
  85. package/dist/packages/domain/src/index.js +6 -0
  86. package/dist/packages/domain/src/index.js.map +1 -0
  87. package/dist/packages/domain/src/markdown.d.ts +16 -0
  88. package/dist/packages/domain/src/markdown.js +72 -0
  89. package/dist/packages/domain/src/markdown.js.map +1 -0
  90. package/dist/packages/domain/src/requirements.d.ts +3 -0
  91. package/dist/packages/domain/src/requirements.js +195 -0
  92. package/dist/packages/domain/src/requirements.js.map +1 -0
  93. package/dist/packages/domain/src/types.d.ts +100 -0
  94. package/dist/packages/domain/src/types.js +14 -0
  95. package/dist/packages/domain/src/types.js.map +1 -0
  96. package/package.json +59 -0
  97. package/plugin.json +9 -0
@@ -0,0 +1,737 @@
1
+ import ts from 'typescript';
2
+ import { dirname, isAbsolute, posix, relative, resolve } from 'node:path';
3
+ import { error } from '../../domain/src/index.js';
4
+ import { files, isSource, isTraceSource, portable, readText, snapshot, writeJson } from './files.js';
5
+ export async function graphInputs(root) {
6
+ return (await files(root)).filter((p) => isTraceSource(p) || /(?:^|\/)(?:tsconfig[^/]*\.json|package\.json|go\.mod)$/.test(p));
7
+ }
8
+ export async function indexGraph(root, persist = true) {
9
+ const paths = await graphInputs(root);
10
+ const typedSources = paths.filter(isSource);
11
+ const rustSources = paths.filter((path) => path.endsWith('.rs'));
12
+ const pythonSources = paths.filter((path) => path.endsWith('.py'));
13
+ const goSources = paths.filter((path) => path.endsWith('.go'));
14
+ const javaSources = paths.filter((path) => path.endsWith('.java'));
15
+ const cppSources = paths.filter((path) => /\.(?:c|cc|cpp|h|hh|hpp)$/.test(path));
16
+ const csharpSources = paths.filter((path) => path.endsWith('.cs'));
17
+ const phpSources = paths.filter((path) => path.endsWith('.php'));
18
+ const rSources = paths.filter((path) => /\.(?:r|R)$/.test(path));
19
+ const juliaSources = paths.filter((path) => path.endsWith('.jl'));
20
+ const sources = [
21
+ ...typedSources, ...rustSources, ...pythonSources, ...goSources, ...javaSources,
22
+ ...cppSources, ...csharpSources, ...phpSources, ...rSources, ...juliaSources,
23
+ ].sort();
24
+ const supported = new Set(sources);
25
+ const unsupportedFiles = paths.filter((path) => isTraceSource(path) && !supported.has(path));
26
+ const known = new Set(sources);
27
+ const diagnostics = [];
28
+ if (unsupportedFiles.length) {
29
+ diagnostics.push({
30
+ code: 'GRAPH_UNSUPPORTED_LANGUAGE',
31
+ severity: 'warning',
32
+ message: `Dependency and call graph analysis is unavailable for ${unsupportedFiles.length} source file(s) in unsupported languages.`,
33
+ path: unsupportedFiles[0],
34
+ });
35
+ }
36
+ const configPaths = new Set(paths.filter((p) => /(?:^|\/)tsconfig\.json$/.test(p)));
37
+ const optionsCache = new Map();
38
+ function optionsFor(path) {
39
+ let directory = dirname(path);
40
+ let config;
41
+ while (true) {
42
+ const candidate = directory === '.' ? 'tsconfig.json' : `${directory}/tsconfig.json`;
43
+ if (configPaths.has(candidate)) {
44
+ config = candidate;
45
+ break;
46
+ }
47
+ if (directory === '.')
48
+ break;
49
+ directory = dirname(directory);
50
+ }
51
+ const key = config ?? '<default>';
52
+ const cached = optionsCache.get(key);
53
+ if (cached)
54
+ return cached;
55
+ let options = { allowJs: true, module: ts.ModuleKind.NodeNext, moduleResolution: ts.ModuleResolutionKind.NodeNext, target: ts.ScriptTarget.ES2022, noEmit: true };
56
+ if (config) {
57
+ const loaded = ts.readConfigFile(resolve(root, config), ts.sys.readFile);
58
+ if (loaded.error)
59
+ diagnostics.push(error('GRAPH_TSCONFIG', ts.flattenDiagnosticMessageText(loaded.error.messageText, '\n'), config));
60
+ else {
61
+ const parsed = ts.parseJsonConfigFileContent(loaded.config, ts.sys, resolve(root, dirname(config)));
62
+ for (const d of parsed.errors.filter((d) => d.code !== 18003))
63
+ diagnostics.push(error('GRAPH_TSCONFIG', ts.flattenDiagnosticMessageText(d.messageText, '\n'), config));
64
+ options = { ...options, ...parsed.options, noEmit: true };
65
+ }
66
+ }
67
+ optionsCache.set(key, options);
68
+ return options;
69
+ }
70
+ const program = ts.createProgram(typedSources.map((p) => resolve(root, p)), optionsFor('index.ts'));
71
+ const checker = program.getTypeChecker();
72
+ const graph = {
73
+ schemaVersion: 1,
74
+ generatedAt: new Date().toISOString(),
75
+ files: sources,
76
+ unsupportedFiles,
77
+ imports: [],
78
+ entrypoints: [],
79
+ symbols: [],
80
+ calls: [],
81
+ diagnostics,
82
+ fingerprints: await snapshot(root, paths),
83
+ };
84
+ for (const manifest of paths.filter((candidate) => candidate.endsWith('package.json'))) {
85
+ let value;
86
+ try {
87
+ value = JSON.parse(await readText(root, manifest));
88
+ }
89
+ catch {
90
+ diagnostics.push(error('GRAPH_MANIFEST', 'package.json is not valid JSON.', manifest));
91
+ continue;
92
+ }
93
+ const entries = [];
94
+ const collect = (field, candidate) => {
95
+ if (typeof candidate === 'string')
96
+ entries.push({ field, value: candidate });
97
+ else if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
98
+ for (const [key, nested] of Object.entries(candidate))
99
+ collect(`${field}.${key}`, nested);
100
+ }
101
+ };
102
+ for (const field of ['main', 'module', 'types', 'bin', 'exports'])
103
+ collect(field, value[field]);
104
+ for (const entry of entries) {
105
+ if (!entry.value.startsWith('.'))
106
+ continue;
107
+ const base = portable(relative(root, resolve(root, dirname(manifest), entry.value)));
108
+ const candidates = [
109
+ base,
110
+ base.replace(/\.[cm]?js$/, '.ts'),
111
+ base.replace(/\.[cm]?js$/, '.tsx'),
112
+ `${base}/index.ts`,
113
+ `${base}/index.js`,
114
+ ];
115
+ const target = candidates.find((candidate) => known.has(candidate));
116
+ if (target && !graph.entrypoints.some((candidate) => candidate.manifest === manifest && candidate.field === entry.field && candidate.path === target)) {
117
+ graph.entrypoints.push({ manifest, field: entry.field, path: target });
118
+ }
119
+ }
120
+ }
121
+ for (const path of typedSources) {
122
+ const source = program.getSourceFile(resolve(root, path));
123
+ if (!source)
124
+ continue;
125
+ const options = optionsFor(path);
126
+ const lineOf = (node) => source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
127
+ function staticSpecifier(expression, seen = new Set(), allowIdentifier = false) {
128
+ if (seen.has(expression))
129
+ return null;
130
+ seen.add(expression);
131
+ if (ts.isStringLiteralLike(expression))
132
+ return expression.text;
133
+ if (ts.isPropertyAccessExpression(expression) && expression.name.text === 'href')
134
+ return staticSpecifier(expression.expression, seen, allowIdentifier);
135
+ if (ts.isNewExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === 'URL') {
136
+ const first = expression.arguments?.[0];
137
+ const second = expression.arguments?.[1];
138
+ if (first && ts.isStringLiteralLike(first) && second?.getText(source) === 'import.meta.url')
139
+ return first.text;
140
+ }
141
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)
142
+ && ['pathToFileURL', 'fileURLToPath'].includes(expression.expression.text) && expression.arguments[0]) {
143
+ return staticSpecifier(expression.arguments[0], seen, allowIdentifier);
144
+ }
145
+ if (allowIdentifier && ts.isIdentifier(expression)) {
146
+ let symbol = checker.getSymbolAtLocation(expression);
147
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias)
148
+ symbol = checker.getAliasedSymbol(symbol);
149
+ const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
150
+ if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer)
151
+ return staticSpecifier(declaration.initializer, seen, true);
152
+ }
153
+ if (ts.isTemplateExpression(expression)) {
154
+ if (expression.head.text && /^[./]/.test(expression.head.text)) {
155
+ const query = expression.head.text.search(/[?#]/);
156
+ if (query > 0)
157
+ return expression.head.text.slice(0, query);
158
+ }
159
+ if (!expression.head.text && expression.templateSpans.length) {
160
+ const first = expression.templateSpans[0];
161
+ const base = staticSpecifier(first.expression, seen, true);
162
+ if (base && /^[?#]/.test(first.literal.text))
163
+ return base;
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+ function addImport(expression, kind) {
169
+ const staticValue = staticSpecifier(expression);
170
+ if (staticValue === null) {
171
+ diagnostics.push({ code: 'GRAPH_DYNAMIC', severity: 'warning', message: 'Nonliteral module loading is not statically resolved.', path, line: lineOf(expression) });
172
+ return;
173
+ }
174
+ const suffix = staticValue.search(/[?#]/);
175
+ const specifier = suffix > 0 ? staticValue.slice(0, suffix) : staticValue;
176
+ const mode = kind === 'require' ? ts.ModuleKind.CommonJS
177
+ : kind === 'dynamic' || !ts.isStringLiteralLike(expression) ? ts.ModuleKind.ESNext
178
+ : ts.getModeForUsageLocation(source, expression, options);
179
+ const resolved = ts.resolveModuleName(specifier, source.fileName, options, ts.sys, undefined, undefined, mode).resolvedModule;
180
+ const target = resolved ? portable(relative(root, resolved.resolvedFileName)) : '';
181
+ const local = target && !target.startsWith('../') && !isAbsolute(target) && !target.includes('node_modules/');
182
+ const external = !local;
183
+ if (!resolved && (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#') ||
184
+ Object.keys(options.paths ?? {}).some((pattern) => matchGlob(specifier, pattern.replace(/\*/g, '**'))))) {
185
+ diagnostics.push(error('GRAPH_UNRESOLVED', `Unresolved local import ${specifier}.`, path, lineOf(expression)));
186
+ }
187
+ else if (!resolved && !specifier.startsWith('node:')) {
188
+ diagnostics.push({ code: 'GRAPH_EXTERNAL_UNRESOLVED', severity: 'warning', message: `Unresolved package ${specifier}; treated as external.`, path, line: lineOf(expression) });
189
+ }
190
+ if (local && !known.has(target) && !/\.d\.[cm]?ts$/.test(target)) {
191
+ diagnostics.push(error('GRAPH_OUTSIDE_INDEX', `Local import ${specifier} resolves outside indexed sources (${target}).`, path, lineOf(expression)));
192
+ }
193
+ graph.imports.push({ from: path, to: local ? target : `npm:${specifier}`, specifier, kind, line: lineOf(expression), external });
194
+ }
195
+ function visit(node) {
196
+ if (ts.isImportDeclaration(node))
197
+ addImport(node.moduleSpecifier, 'import');
198
+ else if (ts.isExportDeclaration(node) && node.moduleSpecifier)
199
+ addImport(node.moduleSpecifier, 'export');
200
+ else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && node.moduleReference.expression)
201
+ addImport(node.moduleReference.expression, 'require');
202
+ else if (ts.isCallExpression(node)) {
203
+ const argument = node.arguments[0];
204
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword && argument)
205
+ addImport(argument, 'dynamic');
206
+ else if (ts.isIdentifier(node.expression) && node.expression.text === 'require' && argument)
207
+ addImport(argument, 'require');
208
+ else {
209
+ let symbol = checker.getSymbolAtLocation(ts.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression);
210
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias)
211
+ symbol = checker.getAliasedSymbol(symbol);
212
+ const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
213
+ const file = declaration?.getSourceFile();
214
+ const targetPath = file ? portable(relative(root, file.fileName)) : '';
215
+ const target = file && declaration && known.has(targetPath) ? `${targetPath}#${symbol?.name}@${file.getLineAndCharacterOfPosition(declaration.getStart(file)).line + 1}` : null;
216
+ graph.calls.push({ path, line: lineOf(node), expression: node.expression.getText(source), target });
217
+ }
218
+ }
219
+ if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) ||
220
+ ts.isEnumDeclaration(node) || ts.isVariableDeclaration(node) || ts.isMethodDeclaration(node)) && node.name) {
221
+ const name = node.name.getText(source);
222
+ graph.symbols.push({ id: `${path}#${name}@${lineOf(node)}`, name, path, line: lineOf(node), kind: ts.SyntaxKind[node.kind] });
223
+ }
224
+ ts.forEachChild(node, visit);
225
+ }
226
+ visit(source);
227
+ }
228
+ const rustTexts = new Map(await Promise.all(rustSources.map(async (path) => [path, await readText(root, path)])));
229
+ for (const [path, text] of rustTexts)
230
+ indexRustSymbols(path, text, graph);
231
+ for (const [path, text] of rustTexts)
232
+ indexRustRelations(path, text, known, graph);
233
+ const pythonTexts = new Map(await Promise.all(pythonSources.map(async (path) => [path, await readText(root, path)])));
234
+ for (const [path, text] of pythonTexts)
235
+ indexPythonSymbols(path, text, graph);
236
+ for (const [path, text] of pythonTexts)
237
+ indexPythonRelations(path, text, known, graph);
238
+ const goTexts = new Map(await Promise.all(goSources.map(async (path) => [path, await readText(root, path)])));
239
+ for (const [path, text] of goTexts)
240
+ indexGoSymbols(path, text, graph);
241
+ const goModule = paths.includes('go.mod') ? /^module\s+(\S+)/m.exec(await readText(root, 'go.mod'))?.[1] ?? null : null;
242
+ for (const [path, text] of goTexts)
243
+ indexGoRelations(path, text, known, graph, goModule);
244
+ const javaTexts = new Map(await Promise.all(javaSources.map(async (path) => [path, await readText(root, path)])));
245
+ const javaTypes = new Map();
246
+ for (const [path, text] of javaTexts)
247
+ indexJavaSymbols(path, text, graph, javaTypes);
248
+ for (const [path, text] of javaTexts)
249
+ indexJavaRelations(path, text, graph, javaTypes);
250
+ const cppTexts = new Map(await Promise.all(cppSources.map(async (path) => [path, await readText(root, path)])));
251
+ for (const [path, text] of cppTexts)
252
+ indexCppSymbols(path, text, graph);
253
+ for (const [path, text] of cppTexts)
254
+ indexCppRelations(path, text, known, graph);
255
+ const csharpTexts = new Map(await Promise.all(csharpSources.map(async (path) => [path, await readText(root, path)])));
256
+ const csharpTypes = new Map();
257
+ for (const [path, text] of csharpTexts)
258
+ indexCsharpSymbols(path, text, graph, csharpTypes);
259
+ for (const [path, text] of csharpTexts)
260
+ indexCsharpRelations(path, text, graph, csharpTypes);
261
+ const phpTexts = new Map(await Promise.all(phpSources.map(async (path) => [path, await readText(root, path)])));
262
+ const phpTypes = new Map();
263
+ for (const [path, text] of phpTexts)
264
+ indexPhpSymbols(path, text, graph, phpTypes);
265
+ for (const [path, text] of phpTexts)
266
+ indexPhpRelations(path, text, known, graph, phpTypes);
267
+ const rTexts = new Map(await Promise.all(rSources.map(async (path) => [path, await readText(root, path)])));
268
+ for (const [path, text] of rTexts)
269
+ indexRSymbols(path, text, graph);
270
+ for (const [path, text] of rTexts)
271
+ indexRRelations(path, text, known, graph);
272
+ const juliaTexts = new Map(await Promise.all(juliaSources.map(async (path) => [path, await readText(root, path)])));
273
+ const juliaModules = new Map();
274
+ for (const [path, text] of juliaTexts)
275
+ indexJuliaSymbols(path, text, graph, juliaModules);
276
+ for (const [path, text] of juliaTexts)
277
+ indexJuliaRelations(path, text, known, graph, juliaModules);
278
+ if (persist)
279
+ await writeJson(root, '.musubix/cache/codegraph.json', graph);
280
+ return graph;
281
+ }
282
+ function maskedRust(text) {
283
+ return text.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"/g, (value) => value.replace(/[^\r\n]/g, ' '));
284
+ }
285
+ function rustTarget(from, specifier, known) {
286
+ const directory = dirname(from);
287
+ const segments = specifier.split('::').filter(Boolean);
288
+ let base = directory;
289
+ if (segments[0] === 'crate') {
290
+ base = 'src';
291
+ segments.shift();
292
+ }
293
+ else {
294
+ while (segments[0] === 'super') {
295
+ base = dirname(base);
296
+ segments.shift();
297
+ }
298
+ if (segments[0] === 'self')
299
+ segments.shift();
300
+ }
301
+ for (let length = segments.length; length > 0; length -= 1) {
302
+ const modulePath = segments.slice(0, length).join('/');
303
+ for (const candidate of [`${base}/${modulePath}.rs`, `${base}/${modulePath}/mod.rs`]) {
304
+ if (known.has(candidate))
305
+ return candidate;
306
+ }
307
+ }
308
+ return null;
309
+ }
310
+ function indexRustSymbols(path, text, graph) {
311
+ const searchable = maskedRust(text);
312
+ const lineOf = (index) => searchable.slice(0, index).split(/\r?\n/).length;
313
+ for (const match of searchable.matchAll(/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(fn|struct|enum|trait|type|const|static)\s+([A-Za-z_]\w*)/gm)) {
314
+ const kind = match[1];
315
+ const name = match[2];
316
+ const line = lineOf(match.index);
317
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: `Rust${kind[0].toUpperCase()}${kind.slice(1)}` });
318
+ }
319
+ }
320
+ function indexRustRelations(path, text, known, graph) {
321
+ const searchable = maskedRust(text);
322
+ const sourceLine = (index) => lineOf(searchable, index);
323
+ for (const match of searchable.matchAll(/^\s*(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)\s*;/gm)) {
324
+ const specifier = match[1];
325
+ const target = rustTarget(path, `self::${specifier}`, known);
326
+ if (!target)
327
+ graph.diagnostics.push(error('GRAPH_UNRESOLVED', `Unresolved Rust module ${specifier}.`, path, sourceLine(match.index)));
328
+ graph.imports.push({ from: path, to: target ?? `crate:${specifier}`, specifier, kind: 'mod', line: sourceLine(match.index), external: !target });
329
+ }
330
+ for (const match of searchable.matchAll(/^\s*use\s+([^;{]+)(?:\{[^;]*\})?\s*;/gm)) {
331
+ const specifier = match[1].trim().replace(/::$/, '');
332
+ const target = /^(?:crate|self|super)::/.test(specifier) ? rustTarget(path, specifier, known) : null;
333
+ graph.imports.push({ from: path, to: target ?? `crate:${specifier}`, specifier, kind: 'use', line: sourceLine(match.index), external: !target });
334
+ }
335
+ const symbolsByName = new Map();
336
+ for (const symbol of graph.symbols) {
337
+ const entries = symbolsByName.get(symbol.name) ?? [];
338
+ entries.push(symbol);
339
+ symbolsByName.set(symbol.name, entries);
340
+ }
341
+ for (const match of searchable.matchAll(/\b([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)\s*\(/g)) {
342
+ const expression = match[1];
343
+ const prefix = searchable.slice(Math.max(0, match.index - 5), match.index);
344
+ if (/\bfn\s+$/.test(prefix) || ['if', 'while', 'for', 'match', 'loop'].includes(expression))
345
+ continue;
346
+ const name = expression.split('::').at(-1);
347
+ const candidates = symbolsByName.get(name) ?? [];
348
+ const target = candidates.length === 1 ? candidates[0].id : null;
349
+ graph.calls.push({ path, line: sourceLine(match.index), expression, target });
350
+ }
351
+ }
352
+ function lineOf(text, index) {
353
+ return text.slice(0, index).split(/\r?\n/).length;
354
+ }
355
+ function maskWithPatterns(text, patterns) {
356
+ let masked = text;
357
+ for (const pattern of patterns)
358
+ masked = masked.replace(pattern, (value) => value.replace(/[^\r\n]/g, ' '));
359
+ return masked;
360
+ }
361
+ function addCalls(path, searchable, graph, ignored) {
362
+ const symbols = new Map();
363
+ for (const symbol of graph.symbols) {
364
+ const values = symbols.get(symbol.name) ?? [];
365
+ values.push(symbol);
366
+ symbols.set(symbol.name, values);
367
+ }
368
+ for (const match of searchable.matchAll(/\b([A-Za-z_]\w*!?(?:[.:]{1,2}[A-Za-z_]\w*!?)*)\s*\(/g)) {
369
+ const expression = match[1];
370
+ const name = expression.split(/::|\./).at(-1);
371
+ const prefix = searchable.slice(Math.max(0, match.index - 24), match.index);
372
+ if (ignored.has(name) || /\b(?:def|class|func|function|fn|new)\s+$/.test(prefix))
373
+ continue;
374
+ const candidates = symbols.get(expression) ?? symbols.get(name) ?? [];
375
+ graph.calls.push({ path, line: lineOf(searchable, match.index), expression, target: candidates.length === 1 ? candidates[0].id : null });
376
+ }
377
+ }
378
+ function pythonTarget(from, specifier, known) {
379
+ let module = specifier;
380
+ let base = '';
381
+ if (module.startsWith('.')) {
382
+ const dots = module.match(/^\.+/)[0].length;
383
+ base = dirname(from);
384
+ for (let index = 1; index < dots; index += 1)
385
+ base = dirname(base);
386
+ module = module.slice(dots);
387
+ }
388
+ const relativeModule = module.replace(/\./g, '/');
389
+ return [
390
+ `${base ? `${base}/` : ''}${relativeModule}.py`,
391
+ `${base ? `${base}/` : ''}${relativeModule}/__init__.py`,
392
+ `src/${relativeModule}.py`,
393
+ `src/${relativeModule}/__init__.py`,
394
+ ].find((candidate) => known.has(candidate)) ?? null;
395
+ }
396
+ function indexPythonSymbols(path, text, graph) {
397
+ const searchable = maskWithPatterns(text, [/'''[\s\S]*?'''|"""[\s\S]*?"""/g, /#[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
398
+ for (const match of searchable.matchAll(/^\s*(?:async\s+)?(def|class)\s+([A-Za-z_]\w*)/gm)) {
399
+ const name = match[2];
400
+ const line = lineOf(searchable, match.index);
401
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: match[1] === 'class' ? 'PythonClass' : 'PythonFunction' });
402
+ }
403
+ }
404
+ function indexPythonRelations(path, text, known, graph) {
405
+ const searchable = maskWithPatterns(text, [/'''[\s\S]*?'''|"""[\s\S]*?"""/g, /#[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
406
+ const addImport = (specifier, index) => {
407
+ const target = pythonTarget(path, specifier, known);
408
+ graph.imports.push({ from: path, to: target ?? `python:${specifier}`, specifier, kind: 'import', line: lineOf(searchable, index), external: !target });
409
+ };
410
+ for (const match of searchable.matchAll(/^\s*import\s+([^\r\n]+)/gm)) {
411
+ for (const entry of match[1].split(','))
412
+ addImport(entry.trim().split(/\s+as\s+/)[0], match.index);
413
+ }
414
+ for (const match of searchable.matchAll(/^\s*from\s+([.\w]+)\s+import\s+/gm))
415
+ addImport(match[1], match.index);
416
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'while', 'def', 'class', 'return']));
417
+ }
418
+ function goTarget(specifier, moduleName, known) {
419
+ if (!moduleName || (specifier !== moduleName && !specifier.startsWith(`${moduleName}/`)))
420
+ return null;
421
+ const directory = specifier === moduleName ? '.' : specifier.slice(moduleName.length + 1);
422
+ return [...known].filter((path) => path.endsWith('.go') && dirname(path) === directory).sort()[0] ?? null;
423
+ }
424
+ function indexGoSymbols(path, text, graph) {
425
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g, /"(?:\\.|[^"\\])*"|`[\s\S]*?`/g]);
426
+ for (const match of searchable.matchAll(/^\s*(?:func\s+(?:\([^)]*\)\s*)?|type\s+|var\s+|const\s+)([A-Za-z_]\w*)/gm)) {
427
+ const name = match[1];
428
+ const line = lineOf(searchable, match.index);
429
+ const prefix = match[0].trimStart();
430
+ const kind = prefix.startsWith('func') ? 'GoFunction' : prefix.startsWith('type') ? 'GoType' : prefix.startsWith('const') ? 'GoConst' : 'GoVar';
431
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind });
432
+ }
433
+ }
434
+ function indexGoRelations(path, text, known, graph, moduleName) {
435
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g]);
436
+ const imports = [];
437
+ for (const match of searchable.matchAll(/^\s*import\s+(?:[A-Za-z_]\w*\s+)?["]([^"]+)["]/gm))
438
+ imports.push({ specifier: match[1], index: match.index });
439
+ for (const block of searchable.matchAll(/^\s*import\s*\(([\s\S]*?)^\s*\)/gm)) {
440
+ for (const match of block[1].matchAll(/(?:^|\n)\s*(?:[A-Za-z_]\w*\s+)?["]([^"]+)["]/g)) {
441
+ imports.push({ specifier: match[1], index: block.index + match.index });
442
+ }
443
+ }
444
+ for (const { specifier, index } of imports) {
445
+ const target = goTarget(specifier, moduleName, known);
446
+ graph.imports.push({ from: path, to: target ?? `go:${specifier}`, specifier, kind: 'import', line: lineOf(searchable, index), external: !target });
447
+ }
448
+ addCalls(path, maskWithPatterns(searchable, [/"(?:\\.|[^"\\])*"|`[\s\S]*?`/g]), graph, new Set(['if', 'for', 'switch', 'select', 'func', 'go', 'defer']));
449
+ }
450
+ function indexJavaSymbols(path, text, graph, types) {
451
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
452
+ const packageName = /^\s*package\s+([\w.]+)\s*;/m.exec(searchable)?.[1] ?? '';
453
+ for (const match of searchable.matchAll(/\b(class|interface|enum|record)\s+([A-Za-z_]\w*)/g)) {
454
+ const name = match[2];
455
+ const line = lineOf(searchable, match.index);
456
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: `Java${match[1][0].toUpperCase()}${match[1].slice(1)}` });
457
+ types.set(packageName ? `${packageName}.${name}` : name, path);
458
+ }
459
+ for (const match of searchable.matchAll(/\b(?:(?:public|protected|private|static|final|abstract|synchronized|native)\s+)*[\w<>\[\], ?]+\s+([A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:throws[^{]+)?\{/g)) {
460
+ const name = match[1];
461
+ const line = lineOf(searchable, match.index);
462
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'JavaMethod' });
463
+ }
464
+ }
465
+ function indexJavaRelations(path, text, graph, types) {
466
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
467
+ for (const match of searchable.matchAll(/^\s*import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?\s*;/gm)) {
468
+ const specifier = match[1];
469
+ const target = types.get(specifier) ?? null;
470
+ graph.imports.push({ from: path, to: target ?? `java:${specifier}`, specifier, kind: 'import', line: lineOf(searchable, match.index), external: !target });
471
+ }
472
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'while', 'switch', 'catch', 'synchronized', 'return', 'new']));
473
+ }
474
+ function relativeTarget(from, specifier, known) {
475
+ const target = posix.normalize(posix.join(posix.dirname(from), specifier.replaceAll('\\', '/')));
476
+ return known.has(target) ? target : null;
477
+ }
478
+ function indexCppSymbols(path, text, graph) {
479
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
480
+ for (const match of searchable.matchAll(/\b(class|struct|enum|union)\s+([A-Za-z_]\w*)/g)) {
481
+ const name = match[2];
482
+ const line = lineOf(searchable, match.index);
483
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: `Cpp${match[1][0].toUpperCase()}${match[1].slice(1)}` });
484
+ }
485
+ for (const match of searchable.matchAll(/(?:^|[;}]\s*)(?:template\s*<[^;{}]+>\s*)?(?:[\w:<>]+\s+)+[*&\s]*([A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:const\s*)?\{/gm)) {
486
+ const name = match[1];
487
+ const line = lineOf(searchable, match.index);
488
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'CppFunction' });
489
+ }
490
+ }
491
+ function indexCppRelations(path, text, known, graph) {
492
+ const commentsMasked = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g]);
493
+ for (const match of commentsMasked.matchAll(/^\s*#\s*include\s*([<"])([^>"]+)[>"]/gm)) {
494
+ const specifier = match[2];
495
+ const quoted = match[1] === '"';
496
+ const target = quoted ? relativeTarget(path, specifier, known) : null;
497
+ if (quoted && !target)
498
+ graph.diagnostics.push(error('GRAPH_UNRESOLVED', `Unresolved local include ${specifier}.`, path, lineOf(commentsMasked, match.index)));
499
+ graph.imports.push({ from: path, to: target ?? `cpp:${specifier}`, specifier, kind: 'include', line: lineOf(commentsMasked, match.index), external: !target });
500
+ }
501
+ const searchable = maskWithPatterns(commentsMasked, [/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
502
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'while', 'switch', 'catch', 'sizeof', 'alignof', 'decltype']));
503
+ }
504
+ function indexCsharpSymbols(path, text, graph, types) {
505
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g, /@"(?:""|[^"])*"|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
506
+ const namespace = /^\s*namespace\s+([\w.]+)\s*[;{]/m.exec(searchable)?.[1] ?? '';
507
+ for (const match of searchable.matchAll(/\b(class|interface|enum|record|struct)\s+([A-Za-z_]\w*)/g)) {
508
+ const name = match[2];
509
+ const line = lineOf(searchable, match.index);
510
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: `Csharp${match[1][0].toUpperCase()}${match[1].slice(1)}` });
511
+ types.set(namespace ? `${namespace}.${name}` : name, path);
512
+ }
513
+ for (const match of searchable.matchAll(/\b(?:(?:public|protected|private|internal|static|virtual|override|abstract|async|sealed|partial)\s+)+[\w<>\[\],.?]+\s+([A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:where[^{]+)?\{/g)) {
514
+ const name = match[1];
515
+ const line = lineOf(searchable, match.index);
516
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'CsharpMethod' });
517
+ }
518
+ }
519
+ function indexCsharpRelations(path, text, graph, types) {
520
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*/g, /@"(?:""|[^"])*"|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
521
+ for (const match of searchable.matchAll(/^\s*using\s+(?:static\s+)?(?:[A-Za-z_]\w*\s*=\s*)?([\w.]+)\s*;/gm)) {
522
+ const specifier = match[1];
523
+ const target = types.get(specifier) ?? [...types].find(([name]) => name.startsWith(`${specifier}.`))?.[1] ?? null;
524
+ graph.imports.push({ from: path, to: target ?? `csharp:${specifier}`, specifier, kind: 'using', line: lineOf(searchable, match.index), external: !target });
525
+ }
526
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'foreach', 'while', 'switch', 'catch', 'lock', 'using', 'nameof', 'typeof', 'checked', 'unchecked']));
527
+ }
528
+ function indexPhpSymbols(path, text, graph, types) {
529
+ const searchable = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*|#[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
530
+ const namespace = /^\s*namespace\s+([A-Za-z_][\w\\]*)\s*;/m.exec(searchable)?.[1] ?? '';
531
+ for (const match of searchable.matchAll(/\b(class|interface|trait|enum)\s+([A-Za-z_]\w*)/g)) {
532
+ const name = match[2];
533
+ const line = lineOf(searchable, match.index);
534
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: `Php${match[1][0].toUpperCase()}${match[1].slice(1)}` });
535
+ types.set(namespace ? `${namespace}\\${name}` : name, path);
536
+ }
537
+ for (const match of searchable.matchAll(/\bfunction\s+&?\s*([A-Za-z_]\w*)\s*\(/g)) {
538
+ const name = match[1];
539
+ const line = lineOf(searchable, match.index);
540
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'PhpFunction' });
541
+ }
542
+ }
543
+ function indexPhpRelations(path, text, known, graph, types) {
544
+ const commentsMasked = maskWithPatterns(text, [/\/\*[\s\S]*?\*\//g, /\/\/[^\r\n]*|#[^\r\n]*/g]);
545
+ for (const match of commentsMasked.matchAll(/\b(?:require|require_once|include|include_once)\s*(?:\(\s*)?['"]([^'"]+)['"]/g)) {
546
+ const specifier = match[1];
547
+ const target = relativeTarget(path, specifier, known);
548
+ if (!target)
549
+ graph.diagnostics.push(error('GRAPH_UNRESOLVED', `Unresolved local PHP include ${specifier}.`, path, lineOf(commentsMasked, match.index)));
550
+ graph.imports.push({ from: path, to: target ?? `php:${specifier}`, specifier, kind: 'include', line: lineOf(commentsMasked, match.index), external: !target });
551
+ }
552
+ const searchable = maskWithPatterns(commentsMasked, [/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
553
+ for (const match of searchable.matchAll(/^\s*use\s+([A-Za-z_][\w\\]*)(?:\s+as\s+\w+)?\s*;/gm)) {
554
+ const specifier = match[1];
555
+ const target = types.get(specifier) ?? null;
556
+ graph.imports.push({ from: path, to: target ?? `php:${specifier}`, specifier, kind: 'use', line: lineOf(searchable, match.index), external: !target });
557
+ }
558
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'foreach', 'while', 'switch', 'catch', 'isset', 'empty', 'echo', 'include', 'require']));
559
+ }
560
+ function indexRSymbols(path, text, graph) {
561
+ const searchable = maskWithPatterns(text, [/#[^\r\n]*/g, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
562
+ for (const match of searchable.matchAll(/^\s*([A-Za-z.][\w.]*)\s*(?:<-|=)\s*function\s*\(/gm)) {
563
+ const name = match[1];
564
+ const line = lineOf(searchable, match.index);
565
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'RFunction' });
566
+ }
567
+ }
568
+ function indexRRelations(path, text, known, graph) {
569
+ const commentsMasked = maskWithPatterns(text, [/#[^\r\n]*/g]);
570
+ for (const match of commentsMasked.matchAll(/\bsource\s*\(\s*["']([^"']+)["']/g)) {
571
+ const specifier = match[1];
572
+ const target = relativeTarget(path, specifier, known);
573
+ if (!target)
574
+ graph.diagnostics.push(error('GRAPH_UNRESOLVED', `Unresolved local R source ${specifier}.`, path, lineOf(commentsMasked, match.index)));
575
+ graph.imports.push({ from: path, to: target ?? `r:${specifier}`, specifier, kind: 'include', line: lineOf(commentsMasked, match.index), external: !target });
576
+ }
577
+ for (const match of commentsMasked.matchAll(/\b(?:library|require)\s*\(\s*(?:package\s*=\s*)?["']?([A-Za-z][\w.]*)/g)) {
578
+ const specifier = match[1];
579
+ graph.imports.push({ from: path, to: `r:${specifier}`, specifier, kind: 'import', line: lineOf(commentsMasked, match.index), external: true });
580
+ }
581
+ const searchable = maskWithPatterns(commentsMasked, [/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
582
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'while', 'function', 'library', 'require', 'source']));
583
+ }
584
+ function indexJuliaSymbols(path, text, graph, modules) {
585
+ const searchable = maskWithPatterns(text, [/#=[\s\S]*?=#/g, /#[^\r\n]*/g, /"""[\s\S]*?"""|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
586
+ for (const match of searchable.matchAll(/^\s*(?:baremodule|module)\s+([A-Za-z_]\w*)/gm)) {
587
+ const name = match[1];
588
+ const line = lineOf(searchable, match.index);
589
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'JuliaModule' });
590
+ modules.set(name, path);
591
+ }
592
+ for (const match of searchable.matchAll(/^\s*(?:(mutable)\s+)?(struct|abstract\s+type|primitive\s+type)\s+([A-Za-z_]\w*)/gm)) {
593
+ const name = match[3];
594
+ const line = lineOf(searchable, match.index);
595
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'JuliaType' });
596
+ }
597
+ for (const match of searchable.matchAll(/^\s*function\s+([A-Za-z_]\w*!?)/gm)) {
598
+ const name = match[1];
599
+ const line = lineOf(searchable, match.index);
600
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'JuliaFunction' });
601
+ }
602
+ for (const match of searchable.matchAll(/^\s*([A-Za-z_]\w*!?)\s*\([^=\r\n]*\)\s*=/gm)) {
603
+ const name = match[1];
604
+ const line = lineOf(searchable, match.index);
605
+ if (!graph.symbols.some((symbol) => symbol.path === path && symbol.name === name && symbol.line === line)) {
606
+ graph.symbols.push({ id: `${path}#${name}@${line}`, name, path, line, kind: 'JuliaFunction' });
607
+ }
608
+ }
609
+ }
610
+ function indexJuliaRelations(path, text, known, graph, modules) {
611
+ const commentsMasked = maskWithPatterns(text, [/#=[\s\S]*?=#/g, /#[^\r\n]*/g]);
612
+ for (const match of commentsMasked.matchAll(/\binclude\s*\(\s*["']([^"']+)["']/g)) {
613
+ const specifier = match[1];
614
+ const target = relativeTarget(path, specifier, known);
615
+ if (!target)
616
+ graph.diagnostics.push(error('GRAPH_UNRESOLVED', `Unresolved local Julia include ${specifier}.`, path, lineOf(commentsMasked, match.index)));
617
+ graph.imports.push({ from: path, to: target ?? `julia:${specifier}`, specifier, kind: 'include', line: lineOf(commentsMasked, match.index), external: !target });
618
+ }
619
+ const searchable = maskWithPatterns(commentsMasked, [/"""[\s\S]*?"""|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g]);
620
+ for (const match of searchable.matchAll(/^\s*(?:using|import)\s+([^\r\n]+)/gm)) {
621
+ for (const entry of match[1].split(',')) {
622
+ const specifier = entry.trim().replace(/^\.*/, '').split(':')[0].trim().split('.')[0];
623
+ const target = modules.get(specifier) ?? null;
624
+ graph.imports.push({ from: path, to: target ?? `julia:${specifier}`, specifier, kind: 'import', line: lineOf(searchable, match.index), external: !target });
625
+ }
626
+ }
627
+ addCalls(path, searchable, graph, new Set(['if', 'for', 'while', 'function', 'macro', 'include', 'using', 'import']));
628
+ }
629
+ export async function loadGraph(root) {
630
+ const graph = JSON.parse(await readText(root, '.musubix/cache/codegraph.json'));
631
+ if (graph.schemaVersion !== 1 || !Array.isArray(graph.files) || !Array.isArray(graph.imports) || !Array.isArray(graph.symbols) || !graph.fingerprints)
632
+ throw new Error('Invalid graph cache; run graph index.');
633
+ const current = await snapshot(root, await graphInputs(root));
634
+ if (JSON.stringify(current) !== JSON.stringify(graph.fingerprints))
635
+ throw new Error('Code graph is stale; run graph index.');
636
+ return graph;
637
+ }
638
+ export function graphImpact(graph, query) {
639
+ const roots = graph.files.includes(query) ? [query] : [...new Set(graph.symbols.filter((s) => s.name === query || s.id === query || `${s.path}#${s.name}` === query).map((s) => s.path))];
640
+ if (!roots.length)
641
+ throw new Error(`Code symbol or path not found: ${query}`);
642
+ const results = new Map(roots.map((path) => [path, { path, via: [path] }]));
643
+ const queue = [...results.values()];
644
+ for (let i = 0; i < queue.length; i++) {
645
+ const current = queue[i];
646
+ for (const edge of graph.imports.filter((edge) => !edge.external && edge.to === current.path)) {
647
+ if (results.has(edge.from))
648
+ continue;
649
+ const impact = { path: edge.from, via: [...current.via, edge.from] };
650
+ results.set(edge.from, impact);
651
+ queue.push(impact);
652
+ }
653
+ }
654
+ return [...results.values()].sort((a, b) => a.path.localeCompare(b.path));
655
+ }
656
+ export function cycles(graph) {
657
+ const adjacency = new Map(graph.files.map((file) => [file, graph.imports.filter((e) => e.from === file && !e.external).map((e) => e.to)]));
658
+ const index = new Map();
659
+ const low = new Map();
660
+ const stack = [];
661
+ const onStack = new Set();
662
+ const result = [];
663
+ let count = 0;
664
+ function visit(node) {
665
+ index.set(node, count);
666
+ low.set(node, count++);
667
+ stack.push(node);
668
+ onStack.add(node);
669
+ for (const next of adjacency.get(node) ?? []) {
670
+ if (!index.has(next)) {
671
+ visit(next);
672
+ low.set(node, Math.min(low.get(node), low.get(next)));
673
+ }
674
+ else if (onStack.has(next))
675
+ low.set(node, Math.min(low.get(node), index.get(next)));
676
+ }
677
+ if (low.get(node) === index.get(node)) {
678
+ const component = [];
679
+ let next;
680
+ do {
681
+ next = stack.pop();
682
+ onStack.delete(next);
683
+ component.push(next);
684
+ } while (next !== node);
685
+ if (component.length > 1 || adjacency.get(node)?.includes(node))
686
+ result.push(component.sort());
687
+ }
688
+ }
689
+ for (const file of graph.files)
690
+ if (!index.has(file))
691
+ visit(file);
692
+ return result.sort((a, b) => a.join().localeCompare(b.join()));
693
+ }
694
+ export function matchGlob(path, glob) {
695
+ let expression = '^';
696
+ for (let i = 0; i < glob.length; i++) {
697
+ const c = glob[i];
698
+ if (c === '*' && glob[i + 1] === '*') {
699
+ i++;
700
+ if (glob[i + 1] === '/') {
701
+ i++;
702
+ expression += '(?:.*/)?';
703
+ }
704
+ else
705
+ expression += '.*';
706
+ }
707
+ else if (c === '*')
708
+ expression += '[^/]*';
709
+ else if (c === '?')
710
+ expression += '[^/]';
711
+ else
712
+ expression += c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
713
+ }
714
+ return new RegExp(`${expression}$`).test(path);
715
+ }
716
+ export function graphGate(graph, config, codeGraph = { mode: 'compatible' }) {
717
+ const diagnostics = graph.diagnostics.map((diagnostic) => codeGraph.mode === 'strict' && diagnostic.code === 'GRAPH_DYNAMIC'
718
+ ? {
719
+ ...diagnostic,
720
+ severity: 'error',
721
+ message: `${diagnostic.message} Strict Code Graph mode requires statically resolvable module loading.`,
722
+ }
723
+ : diagnostic);
724
+ const components = cycles(graph);
725
+ if (config.forbidCycles) {
726
+ for (const component of components)
727
+ diagnostics.push(error('GRAPH_CYCLE', `Dependency cycle: ${component.join(' ↔ ')}.`));
728
+ }
729
+ for (const rule of config.rules) {
730
+ for (const edge of graph.imports) {
731
+ if (matchGlob(edge.from, rule.from) && rule.disallow.some((pattern) => matchGlob(edge.to, pattern)))
732
+ diagnostics.push(error('GRAPH_ARCHITECTURE', `${rule.name}: ${edge.from} must not depend on ${edge.to}.`, edge.from, edge.line));
733
+ }
734
+ }
735
+ return { valid: !diagnostics.some((d) => d.severity === 'error'), diagnostics, cycles: components };
736
+ }
737
+ //# sourceMappingURL=graph.js.map