gitnexus 1.4.10 → 1.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/README.md +6 -5
- package/dist/cli/ai-context.d.ts +4 -1
- package/dist/cli/ai-context.js +19 -11
- package/dist/cli/analyze.d.ts +6 -0
- package/dist/cli/analyze.js +105 -251
- package/dist/cli/eval-server.js +20 -11
- package/dist/cli/index-repo.js +20 -22
- package/dist/cli/index.js +8 -7
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/serve.js +29 -1
- package/dist/cli/setup.js +9 -9
- package/dist/cli/skill-gen.js +15 -9
- package/dist/cli/wiki.d.ts +2 -0
- package/dist/cli/wiki.js +141 -26
- package/dist/config/ignore-service.js +102 -22
- package/dist/config/supported-languages.d.ts +8 -42
- package/dist/config/supported-languages.js +8 -43
- package/dist/core/augmentation/engine.js +19 -7
- package/dist/core/embeddings/embedder.js +19 -15
- package/dist/core/embeddings/embedding-pipeline.js +6 -6
- package/dist/core/embeddings/http-client.js +3 -3
- package/dist/core/embeddings/text-generator.js +9 -24
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/embeddings/types.js +1 -7
- package/dist/core/graph/graph.js +6 -2
- package/dist/core/graph/types.d.ts +9 -59
- package/dist/core/ingestion/ast-cache.js +3 -3
- package/dist/core/ingestion/call-processor.d.ts +20 -2
- package/dist/core/ingestion/call-processor.js +347 -144
- package/dist/core/ingestion/call-routing.js +10 -4
- package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
- package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
- package/dist/core/ingestion/call-sites/java.d.ts +9 -0
- package/dist/core/ingestion/call-sites/java.js +30 -0
- package/dist/core/ingestion/cluster-enricher.js +6 -8
- package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
- package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
- package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
- package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
- package/dist/core/ingestion/cobol-processor.js +102 -56
- package/dist/core/ingestion/community-processor.js +21 -15
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
- package/dist/core/ingestion/entry-point-scoring.js +5 -6
- package/dist/core/ingestion/export-detection.js +32 -9
- package/dist/core/ingestion/field-extractor.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
- package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
- package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
- package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
- package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
- package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
- package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
- package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
- package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
- package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
- package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
- package/dist/core/ingestion/field-extractors/generic.js +6 -0
- package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/typescript.js +1 -1
- package/dist/core/ingestion/field-types.d.ts +4 -2
- package/dist/core/ingestion/filesystem-walker.js +3 -3
- package/dist/core/ingestion/framework-detection.d.ts +1 -1
- package/dist/core/ingestion/framework-detection.js +355 -85
- package/dist/core/ingestion/heritage-processor.d.ts +24 -0
- package/dist/core/ingestion/heritage-processor.js +99 -8
- package/dist/core/ingestion/import-processor.js +44 -15
- package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
- package/dist/core/ingestion/import-resolvers/dart.js +1 -1
- package/dist/core/ingestion/import-resolvers/go.js +4 -2
- package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
- package/dist/core/ingestion/import-resolvers/php.js +4 -4
- package/dist/core/ingestion/import-resolvers/python.js +1 -1
- package/dist/core/ingestion/import-resolvers/rust.js +9 -3
- package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/standard.js +6 -5
- package/dist/core/ingestion/import-resolvers/swift.js +2 -1
- package/dist/core/ingestion/import-resolvers/utils.js +26 -7
- package/dist/core/ingestion/language-config.js +5 -4
- package/dist/core/ingestion/language-provider.d.ts +7 -2
- package/dist/core/ingestion/languages/c-cpp.js +106 -21
- package/dist/core/ingestion/languages/cobol.js +1 -1
- package/dist/core/ingestion/languages/csharp.js +96 -19
- package/dist/core/ingestion/languages/dart.js +23 -7
- package/dist/core/ingestion/languages/go.js +1 -1
- package/dist/core/ingestion/languages/index.d.ts +1 -1
- package/dist/core/ingestion/languages/index.js +2 -3
- package/dist/core/ingestion/languages/java.js +4 -1
- package/dist/core/ingestion/languages/kotlin.js +60 -13
- package/dist/core/ingestion/languages/php.js +102 -25
- package/dist/core/ingestion/languages/python.js +28 -5
- package/dist/core/ingestion/languages/ruby.js +56 -14
- package/dist/core/ingestion/languages/rust.js +55 -11
- package/dist/core/ingestion/languages/swift.js +112 -27
- package/dist/core/ingestion/languages/typescript.js +95 -19
- package/dist/core/ingestion/markdown-processor.js +5 -5
- package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
- package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
- package/dist/core/ingestion/method-extractors/generic.js +137 -0
- package/dist/core/ingestion/method-types.d.ts +61 -0
- package/dist/core/ingestion/method-types.js +2 -0
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +12 -8
- package/dist/core/ingestion/named-binding-processor.js +2 -2
- package/dist/core/ingestion/named-bindings/rust.js +3 -1
- package/dist/core/ingestion/parsing-processor.js +74 -24
- package/dist/core/ingestion/pipeline.d.ts +2 -1
- package/dist/core/ingestion/pipeline.js +208 -102
- package/dist/core/ingestion/process-processor.js +12 -10
- package/dist/core/ingestion/resolution-context.js +3 -3
- package/dist/core/ingestion/route-extractors/middleware.js +31 -7
- package/dist/core/ingestion/route-extractors/php.js +2 -1
- package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
- package/dist/core/ingestion/structure-processor.d.ts +1 -1
- package/dist/core/ingestion/structure-processor.js +4 -4
- package/dist/core/ingestion/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/symbol-table.js +22 -6
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +1 -1
- package/dist/core/ingestion/type-env.d.ts +2 -2
- package/dist/core/ingestion/type-env.js +75 -50
- package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
- package/dist/core/ingestion/type-extractors/csharp.js +24 -14
- package/dist/core/ingestion/type-extractors/dart.js +6 -8
- package/dist/core/ingestion/type-extractors/go.js +7 -6
- package/dist/core/ingestion/type-extractors/jvm.js +10 -21
- package/dist/core/ingestion/type-extractors/php.js +26 -13
- package/dist/core/ingestion/type-extractors/python.js +11 -15
- package/dist/core/ingestion/type-extractors/ruby.js +8 -3
- package/dist/core/ingestion/type-extractors/rust.js +6 -8
- package/dist/core/ingestion/type-extractors/shared.js +134 -50
- package/dist/core/ingestion/type-extractors/swift.js +16 -13
- package/dist/core/ingestion/type-extractors/typescript.js +23 -15
- package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
- package/dist/core/ingestion/utils/ast-helpers.js +72 -35
- package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
- package/dist/core/ingestion/utils/call-analysis.js +96 -49
- package/dist/core/ingestion/utils/event-loop.js +1 -1
- package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
- package/dist/core/ingestion/workers/parse-worker.js +364 -84
- package/dist/core/ingestion/workers/worker-pool.js +5 -10
- package/dist/core/lbug/csv-generator.js +54 -15
- package/dist/core/lbug/lbug-adapter.d.ts +5 -0
- package/dist/core/lbug/lbug-adapter.js +86 -23
- package/dist/core/lbug/schema.d.ts +3 -6
- package/dist/core/lbug/schema.js +6 -30
- package/dist/core/run-analyze.d.ts +49 -0
- package/dist/core/run-analyze.js +257 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -1
- package/dist/core/wiki/cursor-client.js +2 -7
- package/dist/core/wiki/generator.js +38 -23
- package/dist/core/wiki/graph-queries.js +10 -10
- package/dist/core/wiki/html-viewer.js +7 -3
- package/dist/core/wiki/llm-client.d.ts +23 -2
- package/dist/core/wiki/llm-client.js +96 -26
- package/dist/core/wiki/prompts.js +7 -6
- package/dist/mcp/core/embedder.js +1 -1
- package/dist/mcp/core/lbug-adapter.d.ts +4 -1
- package/dist/mcp/core/lbug-adapter.js +17 -7
- package/dist/mcp/local/local-backend.js +247 -95
- package/dist/mcp/resources.js +14 -6
- package/dist/mcp/server.js +13 -5
- package/dist/mcp/staleness.js +5 -1
- package/dist/mcp/tools.js +100 -23
- package/dist/server/analyze-job.d.ts +53 -0
- package/dist/server/analyze-job.js +146 -0
- package/dist/server/analyze-worker.d.ts +13 -0
- package/dist/server/analyze-worker.js +59 -0
- package/dist/server/api.js +795 -44
- package/dist/server/git-clone.d.ts +25 -0
- package/dist/server/git-clone.js +91 -0
- package/dist/storage/git.js +1 -3
- package/dist/storage/repo-manager.d.ts +5 -2
- package/dist/storage/repo-manager.js +4 -4
- package/dist/types/pipeline.d.ts +1 -21
- package/dist/types/pipeline.js +1 -18
- package/hooks/claude/gitnexus-hook.cjs +52 -22
- package/package.json +3 -2
- package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
- package/dist/core/ingestion/utils/language-detection.js +0 -70
|
@@ -3,23 +3,23 @@ import { processStructure } from './structure-processor.js';
|
|
|
3
3
|
import { processMarkdown } from './markdown-processor.js';
|
|
4
4
|
import { processCobol, isCobolFile, isJclFile } from './cobol-processor.js';
|
|
5
5
|
import { processParsing } from './parsing-processor.js';
|
|
6
|
-
import { processImports, processImportsFromExtracted, buildImportResolutionContext } from './import-processor.js';
|
|
6
|
+
import { processImports, processImportsFromExtracted, buildImportResolutionContext, } from './import-processor.js';
|
|
7
7
|
import { EMPTY_INDEX } from './import-resolvers/utils.js';
|
|
8
|
-
import { processCalls, processCallsFromExtracted, processAssignmentsFromExtracted, processRoutesFromExtracted, processNextjsFetchRoutes, extractFetchCallsFromFiles, seedCrossFileReceiverTypes, buildImportedReturnTypes, buildImportedRawReturnTypes, buildExportedTypeMapFromGraph } from './call-processor.js';
|
|
8
|
+
import { processCalls, processCallsFromExtracted, processAssignmentsFromExtracted, processRoutesFromExtracted, processNextjsFetchRoutes, extractFetchCallsFromFiles, seedCrossFileReceiverTypes, buildImportedReturnTypes, buildImportedRawReturnTypes, buildExportedTypeMapFromGraph, buildImplementorMap, mergeImplementorMaps, } from './call-processor.js';
|
|
9
9
|
import { nextjsFileToRouteURL, normalizeFetchURL } from './route-extractors/nextjs.js';
|
|
10
10
|
import { expoFileToRouteURL } from './route-extractors/expo.js';
|
|
11
11
|
import { phpFileToRouteURL } from './route-extractors/php.js';
|
|
12
|
-
import { extractResponseShapes, extractPHPResponseShapes } from './route-extractors/response-shapes.js';
|
|
13
|
-
import { extractMiddlewareChain, extractNextjsMiddlewareConfig, compileMatcher, compiledMatcherMatchesRoute } from './route-extractors/middleware.js';
|
|
12
|
+
import { extractResponseShapes, extractPHPResponseShapes, } from './route-extractors/response-shapes.js';
|
|
13
|
+
import { extractMiddlewareChain, extractNextjsMiddlewareConfig, compileMatcher, compiledMatcherMatchesRoute, } from './route-extractors/middleware.js';
|
|
14
14
|
import { generateId } from '../../lib/utils.js';
|
|
15
|
-
import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
|
|
15
|
+
import { processHeritage, processHeritageFromExtracted, extractExtractedHeritageFromFiles, } from './heritage-processor.js';
|
|
16
16
|
import { computeMRO } from './mro-processor.js';
|
|
17
17
|
import { processCommunities } from './community-processor.js';
|
|
18
18
|
import { processProcesses } from './process-processor.js';
|
|
19
19
|
import { createResolutionContext } from './resolution-context.js';
|
|
20
20
|
import { createASTCache } from './ast-cache.js';
|
|
21
|
+
import { getLanguageFromFilename } from 'gitnexus-shared';
|
|
21
22
|
import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
|
|
22
|
-
import { getLanguageFromFilename } from './utils/language-detection.js';
|
|
23
23
|
import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
|
|
24
24
|
import { providers, getProviderForFile } from './languages/index.js';
|
|
25
25
|
import { createWorkerPool } from './workers/worker-pool.js';
|
|
@@ -57,9 +57,7 @@ export function topologicalLevelSort(importMap) {
|
|
|
57
57
|
}
|
|
58
58
|
// BFS from zero-in-degree nodes, grouping by level
|
|
59
59
|
const levels = [];
|
|
60
|
-
let currentLevel = [...inDegree.entries()]
|
|
61
|
-
.filter(([, d]) => d === 0)
|
|
62
|
-
.map(([f]) => f);
|
|
60
|
+
let currentLevel = [...inDegree.entries()].filter(([, d]) => d === 0).map(([f]) => f);
|
|
63
61
|
while (currentLevel.length > 0) {
|
|
64
62
|
levels.push(currentLevel);
|
|
65
63
|
const nextLevel = [];
|
|
@@ -74,9 +72,7 @@ export function topologicalLevelSort(importMap) {
|
|
|
74
72
|
currentLevel = nextLevel;
|
|
75
73
|
}
|
|
76
74
|
// Files still with positive in-degree are in cycles — add as final group
|
|
77
|
-
const cycleFiles = [...inDegree.entries()]
|
|
78
|
-
.filter(([, d]) => d > 0)
|
|
79
|
-
.map(([f]) => f);
|
|
75
|
+
const cycleFiles = [...inDegree.entries()].filter(([, d]) => d > 0).map(([f]) => f);
|
|
80
76
|
if (cycleFiles.length > 0) {
|
|
81
77
|
levels.push(cycleFiles);
|
|
82
78
|
}
|
|
@@ -97,15 +93,30 @@ const MAX_CROSS_FILE_REPROCESS = 2000;
|
|
|
97
93
|
* Excludes Method, Property, Constructor (accessed via receiver, not directly imported),
|
|
98
94
|
* and structural labels (File, Folder, Package, Module, Project, etc.). */
|
|
99
95
|
const IMPORTABLE_SYMBOL_LABELS = new Set([
|
|
100
|
-
'Function',
|
|
101
|
-
'
|
|
96
|
+
'Function',
|
|
97
|
+
'Class',
|
|
98
|
+
'Interface',
|
|
99
|
+
'Struct',
|
|
100
|
+
'Enum',
|
|
101
|
+
'Trait',
|
|
102
|
+
'TypeAlias',
|
|
103
|
+
'Const',
|
|
104
|
+
'Static',
|
|
105
|
+
'Record',
|
|
106
|
+
'Union',
|
|
107
|
+
'Typedef',
|
|
108
|
+
'Macro',
|
|
102
109
|
]);
|
|
103
110
|
/** Max synthetic bindings per importing file — prevents memory bloat for
|
|
104
111
|
* C/C++ files that include many large headers. */
|
|
105
112
|
const MAX_SYNTHETIC_BINDINGS_PER_FILE = 1000;
|
|
106
113
|
/** Pre-computed language sets derived from providers at module load. */
|
|
107
|
-
const WILDCARD_LANGUAGES = new Set(Object.values(providers)
|
|
108
|
-
|
|
114
|
+
const WILDCARD_LANGUAGES = new Set(Object.values(providers)
|
|
115
|
+
.filter((p) => p.importSemantics === 'wildcard')
|
|
116
|
+
.map((p) => p.id));
|
|
117
|
+
const SYNTHESIS_LANGUAGES = new Set(Object.values(providers)
|
|
118
|
+
.filter((p) => p.importSemantics !== 'named')
|
|
119
|
+
.map((p) => p.id));
|
|
109
120
|
/** Check if a language uses wildcard (whole-module) import semantics.
|
|
110
121
|
* Derived from LanguageProvider.importSemantics — no hardcoded set needed. */
|
|
111
122
|
function isWildcardImportLanguage(lang) {
|
|
@@ -124,7 +135,7 @@ function needsSynthesis(lang) {
|
|
|
124
135
|
function synthesizeWildcardImportBindings(graph, ctx) {
|
|
125
136
|
// Pre-compute exported symbols per file from graph (single pass)
|
|
126
137
|
const exportedSymbolsByFile = new Map();
|
|
127
|
-
graph.forEachNode(node => {
|
|
138
|
+
graph.forEachNode((node) => {
|
|
128
139
|
if (!node.properties?.isExported)
|
|
129
140
|
return;
|
|
130
141
|
if (!IMPORTABLE_SYMBOL_LABELS.has(node.label))
|
|
@@ -147,7 +158,7 @@ function synthesizeWildcardImportBindings(graph, ctx) {
|
|
|
147
158
|
// Collect graph-level IMPORTS edges for wildcard languages missing from ctx.importMap.
|
|
148
159
|
const FILE_PREFIX = 'File:';
|
|
149
160
|
const graphImports = new Map();
|
|
150
|
-
graph.forEachRelationship(rel => {
|
|
161
|
+
graph.forEachRelationship((rel) => {
|
|
151
162
|
if (rel.type !== 'IMPORTS')
|
|
152
163
|
return;
|
|
153
164
|
if (!rel.sourceId.startsWith(FILE_PREFIX) || !rel.targetId.startsWith(FILE_PREFIX))
|
|
@@ -292,7 +303,7 @@ async function runCrossFileBindingPropagation(graph, ctx, exportedTypeMap, allPa
|
|
|
292
303
|
});
|
|
293
304
|
let crossFileResolved = 0;
|
|
294
305
|
const crossFileStart = Date.now();
|
|
295
|
-
|
|
306
|
+
const astCache = createASTCache(AST_CACHE_CAP);
|
|
296
307
|
for (const level of levels) {
|
|
297
308
|
const levelCandidates = [];
|
|
298
309
|
for (const filePath of level) {
|
|
@@ -323,7 +334,7 @@ async function runCrossFileBindingPropagation(graph, ctx, exportedTypeMap, allPa
|
|
|
323
334
|
}
|
|
324
335
|
if (levelCandidates.length === 0)
|
|
325
336
|
continue;
|
|
326
|
-
const levelPaths = levelCandidates.map(c => c.filePath);
|
|
337
|
+
const levelPaths = levelCandidates.map((c) => c.filePath);
|
|
327
338
|
const contentMap = await readFileContents(repoPath, levelPaths);
|
|
328
339
|
for (const { filePath, seeded, importedReturns, importedRawReturns } of levelCandidates) {
|
|
329
340
|
const content = contentMap.get(filePath);
|
|
@@ -396,7 +407,7 @@ async function runScanAndStructure(repoPath, graph, onProgress) {
|
|
|
396
407
|
message: 'Analyzing project structure...',
|
|
397
408
|
stats: { filesProcessed: 0, totalFiles, nodesCreated: graph.nodeCount },
|
|
398
409
|
});
|
|
399
|
-
const allPaths = scannedFiles.map(f => f.path);
|
|
410
|
+
const allPaths = scannedFiles.map((f) => f.path);
|
|
400
411
|
processStructure(graph, allPaths);
|
|
401
412
|
onProgress({
|
|
402
413
|
phase: 'structure',
|
|
@@ -412,12 +423,12 @@ async function runScanAndStructure(repoPath, graph, onProgress) {
|
|
|
412
423
|
// To add a new language: create a new processor file, import it here,
|
|
413
424
|
// and add a filter-read-call-log block following the pattern below.
|
|
414
425
|
// ── Phase 2.5: Markdown processing (headings + cross-links) ────────
|
|
415
|
-
const mdScanned = scannedFiles.filter(f => f.path.endsWith('.md') || f.path.endsWith('.mdx'));
|
|
426
|
+
const mdScanned = scannedFiles.filter((f) => f.path.endsWith('.md') || f.path.endsWith('.mdx'));
|
|
416
427
|
if (mdScanned.length > 0) {
|
|
417
|
-
const mdContents = await readFileContents(repoPath, mdScanned.map(f => f.path));
|
|
428
|
+
const mdContents = await readFileContents(repoPath, mdScanned.map((f) => f.path));
|
|
418
429
|
const mdFiles = mdScanned
|
|
419
|
-
.filter(f => mdContents.has(f.path))
|
|
420
|
-
.map(f => ({ path: f.path, content: mdContents.get(f.path) }));
|
|
430
|
+
.filter((f) => mdContents.has(f.path))
|
|
431
|
+
.map((f) => ({ path: f.path, content: mdContents.get(f.path) }));
|
|
421
432
|
const allPathSet = new Set(allPaths);
|
|
422
433
|
const mdResult = processMarkdown(graph, mdFiles, allPathSet);
|
|
423
434
|
if (isDev) {
|
|
@@ -425,17 +436,19 @@ async function runScanAndStructure(repoPath, graph, onProgress) {
|
|
|
425
436
|
}
|
|
426
437
|
}
|
|
427
438
|
// ── Phase 2.6: COBOL processing (regex extraction, no tree-sitter) ──
|
|
428
|
-
const cobolScanned = scannedFiles.filter(f => isCobolFile(f.path) || isJclFile(f.path));
|
|
439
|
+
const cobolScanned = scannedFiles.filter((f) => isCobolFile(f.path) || isJclFile(f.path));
|
|
429
440
|
if (cobolScanned.length > 0) {
|
|
430
|
-
const cobolContents = await readFileContents(repoPath, cobolScanned.map(f => f.path));
|
|
441
|
+
const cobolContents = await readFileContents(repoPath, cobolScanned.map((f) => f.path));
|
|
431
442
|
const cobolFiles = cobolScanned
|
|
432
|
-
.filter(f => cobolContents.has(f.path))
|
|
433
|
-
.map(f => ({ path: f.path, content: cobolContents.get(f.path) }));
|
|
443
|
+
.filter((f) => cobolContents.has(f.path))
|
|
444
|
+
.map((f) => ({ path: f.path, content: cobolContents.get(f.path) }));
|
|
434
445
|
const allPathSet = new Set(allPaths);
|
|
435
446
|
const cobolResult = processCobol(graph, cobolFiles, allPathSet);
|
|
436
447
|
if (isDev) {
|
|
437
448
|
console.log(` COBOL: ${cobolResult.programs} programs, ${cobolResult.paragraphs} paragraphs, ${cobolResult.sections} sections from ${cobolFiles.length} files`);
|
|
438
|
-
if (cobolResult.execSqlBlocks > 0 ||
|
|
449
|
+
if (cobolResult.execSqlBlocks > 0 ||
|
|
450
|
+
cobolResult.execCicsBlocks > 0 ||
|
|
451
|
+
cobolResult.entryPoints > 0) {
|
|
439
452
|
console.log(` COBOL enriched: ${cobolResult.execSqlBlocks} SQL blocks, ${cobolResult.execCicsBlocks} CICS blocks, ${cobolResult.entryPoints} entry points, ${cobolResult.moves} moves, ${cobolResult.fileDeclarations} file declarations`);
|
|
440
453
|
}
|
|
441
454
|
if (cobolResult.jclJobs > 0) {
|
|
@@ -452,7 +465,8 @@ async function runScanAndStructure(repoPath, graph, onProgress) {
|
|
|
452
465
|
* 1. Parse via worker pool (or sequential fallback)
|
|
453
466
|
* 2. Resolve imports from extracted data
|
|
454
467
|
* 3. Synthesize wildcard import bindings (Go/Ruby/C++/Swift/Python)
|
|
455
|
-
* 4. Resolve
|
|
468
|
+
* 4. Resolve heritage + routes per chunk; defer worker CALLS until all chunks
|
|
469
|
+
* have contributed heritage so interface-dispatch implementor map is complete
|
|
456
470
|
* 5. Collect TypeEnv bindings for cross-file propagation
|
|
457
471
|
*
|
|
458
472
|
* State accumulated across chunks: symbolTable, importMap, namedImportMap,
|
|
@@ -462,10 +476,13 @@ async function runScanAndStructure(repoPath, graph, onProgress) {
|
|
|
462
476
|
* @reads allPaths (from scan phase)
|
|
463
477
|
* @writes graph (Symbol nodes, IMPORTS/CALLS/EXTENDS/IMPLEMENTS/ACCESSES edges)
|
|
464
478
|
* @writes ctx.symbolTable, ctx.importMap, ctx.namedImportMap, ctx.moduleAliasMap
|
|
479
|
+
*
|
|
480
|
+
* Follow-up from PR review: MethodExtractor (FieldExtractor parity) and optional
|
|
481
|
+
* METHOD_IMPLEMENTS graph edges to make dispatch queryable without an in-memory map.
|
|
465
482
|
*/
|
|
466
483
|
async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress) {
|
|
467
484
|
const symbolTable = ctx.symbols;
|
|
468
|
-
const parseableScanned = scannedFiles.filter(f => {
|
|
485
|
+
const parseableScanned = scannedFiles.filter((f) => {
|
|
469
486
|
const lang = getLanguageFromFilename(f.path);
|
|
470
487
|
return lang && isLanguageAvailable(lang);
|
|
471
488
|
});
|
|
@@ -547,14 +564,14 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
547
564
|
// Build import resolution context once — suffix index, file lists, resolve cache.
|
|
548
565
|
// Reused across all chunks to avoid rebuilding O(files × path_depth) structures.
|
|
549
566
|
const importCtx = buildImportResolutionContext(allPaths);
|
|
550
|
-
const allPathObjects = allPaths.map(p => ({ path: p }));
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
567
|
+
const allPathObjects = allPaths.map((p) => ({ path: p }));
|
|
568
|
+
// Worker path: parse + imports + heritage per chunk; buffer extracted calls and
|
|
569
|
+
// run processCallsFromExtracted once after all chunks so interface-dispatch uses a
|
|
570
|
+
// complete implementor map (heritage from every chunk). Costs peak RAM for buffered
|
|
571
|
+
// call rows vs streaming resolution per chunk.
|
|
555
572
|
const sequentialChunkPaths = [];
|
|
556
573
|
// Pre-compute which chunks need synthesis — O(1) lookup per chunk.
|
|
557
|
-
const chunkNeedsSynthesis = chunks.map(paths => paths.some(p => {
|
|
574
|
+
const chunkNeedsSynthesis = chunks.map((paths) => paths.some((p) => {
|
|
558
575
|
const lang = getLanguageFromFilename(p);
|
|
559
576
|
return lang != null && needsSynthesis(lang);
|
|
560
577
|
}));
|
|
@@ -571,27 +588,35 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
571
588
|
// Accumulate MCP/RPC tool definitions (@mcp.tool(), @app.tool(), etc.)
|
|
572
589
|
const allToolDefs = [];
|
|
573
590
|
const allORMQueries = [];
|
|
591
|
+
const deferredWorkerCalls = [];
|
|
592
|
+
const deferredWorkerHeritage = [];
|
|
593
|
+
const deferredConstructorBindings = [];
|
|
594
|
+
const deferredAssignments = [];
|
|
574
595
|
try {
|
|
575
596
|
for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
|
|
576
597
|
const chunkPaths = chunks[chunkIdx];
|
|
577
598
|
// Read content for this chunk only
|
|
578
599
|
const chunkContents = await readFileContents(repoPath, chunkPaths);
|
|
579
600
|
const chunkFiles = chunkPaths
|
|
580
|
-
.filter(p => chunkContents.has(p))
|
|
581
|
-
.map(p => ({ path: p, content: chunkContents.get(p) }));
|
|
601
|
+
.filter((p) => chunkContents.has(p))
|
|
602
|
+
.map((p) => ({ path: p, content: chunkContents.get(p) }));
|
|
582
603
|
// Parse this chunk (workers or sequential fallback)
|
|
583
604
|
const chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, (current, _total, filePath) => {
|
|
584
605
|
const globalCurrent = filesParsedSoFar + current;
|
|
585
|
-
const parsingProgress = 20 + (
|
|
606
|
+
const parsingProgress = 20 + (globalCurrent / totalParseable) * 62;
|
|
586
607
|
onProgress({
|
|
587
608
|
phase: 'parsing',
|
|
588
609
|
percent: Math.round(parsingProgress),
|
|
589
610
|
message: `Parsing chunk ${chunkIdx + 1}/${numChunks}...`,
|
|
590
611
|
detail: filePath,
|
|
591
|
-
stats: {
|
|
612
|
+
stats: {
|
|
613
|
+
filesProcessed: globalCurrent,
|
|
614
|
+
totalFiles: totalParseable,
|
|
615
|
+
nodesCreated: graph.nodeCount,
|
|
616
|
+
},
|
|
592
617
|
});
|
|
593
618
|
}, workerPool);
|
|
594
|
-
const chunkBasePercent = 20 + (
|
|
619
|
+
const chunkBasePercent = 20 + (filesParsedSoFar / totalParseable) * 62;
|
|
595
620
|
if (chunkWorkerData) {
|
|
596
621
|
// Imports
|
|
597
622
|
await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, ctx, (current, total) => {
|
|
@@ -600,7 +625,11 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
600
625
|
percent: Math.round(chunkBasePercent),
|
|
601
626
|
message: `Resolving imports (chunk ${chunkIdx + 1}/${numChunks})...`,
|
|
602
627
|
detail: `${current}/${total} files`,
|
|
603
|
-
stats: {
|
|
628
|
+
stats: {
|
|
629
|
+
filesProcessed: filesParsedSoFar,
|
|
630
|
+
totalFiles: totalParseable,
|
|
631
|
+
nodesCreated: graph.nodeCount,
|
|
632
|
+
},
|
|
604
633
|
});
|
|
605
634
|
}, repoPath, importCtx);
|
|
606
635
|
// ── Wildcard-import synthesis (Ruby / C/C++ / Swift / Go) + Python module aliases ─
|
|
@@ -620,26 +649,26 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
620
649
|
console.log(`🔗 E1: Seeded ${enrichedCount} cross-file receiver types (chunk ${chunkIdx + 1})`);
|
|
621
650
|
}
|
|
622
651
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
652
|
+
deferredWorkerCalls.push(...chunkWorkerData.calls);
|
|
653
|
+
deferredWorkerHeritage.push(...chunkWorkerData.heritage);
|
|
654
|
+
deferredConstructorBindings.push(...chunkWorkerData.constructorBindings);
|
|
655
|
+
if (chunkWorkerData.assignments?.length) {
|
|
656
|
+
deferredAssignments.push(...chunkWorkerData.assignments);
|
|
657
|
+
}
|
|
658
|
+
// Heritage + Routes — calls deferred until all chunks have contributed heritage
|
|
659
|
+
// (complete implementor map for interface dispatch).
|
|
626
660
|
await Promise.all([
|
|
627
|
-
processCallsFromExtracted(graph, chunkWorkerData.calls, ctx, (current, total) => {
|
|
628
|
-
onProgress({
|
|
629
|
-
phase: 'parsing',
|
|
630
|
-
percent: Math.round(chunkBasePercent),
|
|
631
|
-
message: `Resolving calls (chunk ${chunkIdx + 1}/${numChunks})...`,
|
|
632
|
-
detail: `${current}/${total} files`,
|
|
633
|
-
stats: { filesProcessed: filesParsedSoFar, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
|
|
634
|
-
});
|
|
635
|
-
}, chunkWorkerData.constructorBindings),
|
|
636
661
|
processHeritageFromExtracted(graph, chunkWorkerData.heritage, ctx, (current, total) => {
|
|
637
662
|
onProgress({
|
|
638
663
|
phase: 'parsing',
|
|
639
664
|
percent: Math.round(chunkBasePercent),
|
|
640
665
|
message: `Resolving heritage (chunk ${chunkIdx + 1}/${numChunks})...`,
|
|
641
666
|
detail: `${current}/${total} records`,
|
|
642
|
-
stats: {
|
|
667
|
+
stats: {
|
|
668
|
+
filesProcessed: filesParsedSoFar,
|
|
669
|
+
totalFiles: totalParseable,
|
|
670
|
+
nodesCreated: graph.nodeCount,
|
|
671
|
+
},
|
|
643
672
|
});
|
|
644
673
|
}),
|
|
645
674
|
processRoutesFromExtracted(graph, chunkWorkerData.routes ?? [], ctx, (current, total) => {
|
|
@@ -648,14 +677,14 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
648
677
|
percent: Math.round(chunkBasePercent),
|
|
649
678
|
message: `Resolving routes (chunk ${chunkIdx + 1}/${numChunks})...`,
|
|
650
679
|
detail: `${current}/${total} routes`,
|
|
651
|
-
stats: {
|
|
680
|
+
stats: {
|
|
681
|
+
filesProcessed: filesParsedSoFar,
|
|
682
|
+
totalFiles: totalParseable,
|
|
683
|
+
nodesCreated: graph.nodeCount,
|
|
684
|
+
},
|
|
652
685
|
});
|
|
653
686
|
}),
|
|
654
687
|
]);
|
|
655
|
-
// Process field write assignments (synchronous, runs after calls resolve)
|
|
656
|
-
if (chunkWorkerData.assignments?.length) {
|
|
657
|
-
processAssignmentsFromExtracted(graph, chunkWorkerData.assignments, ctx, chunkWorkerData.constructorBindings);
|
|
658
|
-
}
|
|
659
688
|
// Collect TypeEnv file-scope bindings for exported type enrichment
|
|
660
689
|
if (chunkWorkerData.typeEnvBindings?.length) {
|
|
661
690
|
workerTypeEnvBindings.push(...chunkWorkerData.typeEnvBindings);
|
|
@@ -686,6 +715,28 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
686
715
|
astCache.clear();
|
|
687
716
|
// chunkContents + chunkFiles + chunkWorkerData go out of scope → GC reclaims
|
|
688
717
|
}
|
|
718
|
+
// Complete implementor map from all worker heritage, then resolve CALLS once (interface dispatch).
|
|
719
|
+
const fullWorkerImplementorMap = deferredWorkerHeritage.length > 0
|
|
720
|
+
? buildImplementorMap(deferredWorkerHeritage, ctx)
|
|
721
|
+
: new Map();
|
|
722
|
+
if (deferredWorkerCalls.length > 0) {
|
|
723
|
+
await processCallsFromExtracted(graph, deferredWorkerCalls, ctx, (current, total) => {
|
|
724
|
+
onProgress({
|
|
725
|
+
phase: 'parsing',
|
|
726
|
+
percent: 82,
|
|
727
|
+
message: 'Resolving calls (all chunks)...',
|
|
728
|
+
detail: `${current}/${total} files`,
|
|
729
|
+
stats: {
|
|
730
|
+
filesProcessed: filesParsedSoFar,
|
|
731
|
+
totalFiles: totalParseable,
|
|
732
|
+
nodesCreated: graph.nodeCount,
|
|
733
|
+
},
|
|
734
|
+
});
|
|
735
|
+
}, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined, fullWorkerImplementorMap);
|
|
736
|
+
}
|
|
737
|
+
if (deferredAssignments.length > 0) {
|
|
738
|
+
processAssignmentsFromExtracted(graph, deferredAssignments, ctx, deferredConstructorBindings.length > 0 ? deferredConstructorBindings : undefined);
|
|
739
|
+
}
|
|
689
740
|
}
|
|
690
741
|
finally {
|
|
691
742
|
await workerPool?.terminate();
|
|
@@ -695,13 +746,18 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
695
746
|
// before any call resolution — same rationale as the worker-path inline synthesis.
|
|
696
747
|
if (sequentialChunkPaths.length > 0)
|
|
697
748
|
synthesizeWildcardImportBindings(graph, ctx);
|
|
749
|
+
// Merge implementor-map deltas per chunk (O(heritage per chunk)), not O(|edges|) graph scans
|
|
750
|
+
// per chunk — mirrors worker-path deferred heritage without re-iterating all relationships.
|
|
751
|
+
const sequentialImplementorMap = new Map();
|
|
698
752
|
for (const chunkPaths of sequentialChunkPaths) {
|
|
699
753
|
const chunkContents = await readFileContents(repoPath, chunkPaths);
|
|
700
754
|
const chunkFiles = chunkPaths
|
|
701
|
-
.filter(p => chunkContents.has(p))
|
|
702
|
-
.map(p => ({ path: p, content: chunkContents.get(p) }));
|
|
755
|
+
.filter((p) => chunkContents.has(p))
|
|
756
|
+
.map((p) => ({ path: p, content: chunkContents.get(p) }));
|
|
703
757
|
astCache = createASTCache(chunkFiles.length);
|
|
704
|
-
const
|
|
758
|
+
const sequentialHeritage = await extractExtractedHeritageFromFiles(chunkFiles, astCache);
|
|
759
|
+
mergeImplementorMaps(sequentialImplementorMap, buildImplementorMap(sequentialHeritage, ctx));
|
|
760
|
+
const rubyHeritage = await processCalls(graph, chunkFiles, astCache, ctx, undefined, exportedTypeMap, undefined, undefined, undefined, sequentialImplementorMap);
|
|
705
761
|
await processHeritage(graph, chunkFiles, astCache, ctx);
|
|
706
762
|
if (rubyHeritage.length > 0) {
|
|
707
763
|
await processHeritageFromExtracted(graph, rubyHeritage, ctx);
|
|
@@ -769,7 +825,14 @@ async function runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, tot
|
|
|
769
825
|
importCtx.resolveCache.clear();
|
|
770
826
|
importCtx.index = EMPTY_INDEX; // Release suffix index memory (~30MB for large repos)
|
|
771
827
|
importCtx.normalizedFileList = [];
|
|
772
|
-
return {
|
|
828
|
+
return {
|
|
829
|
+
exportedTypeMap,
|
|
830
|
+
allFetchCalls,
|
|
831
|
+
allExtractedRoutes,
|
|
832
|
+
allDecoratorRoutes,
|
|
833
|
+
allToolDefs,
|
|
834
|
+
allORMQueries,
|
|
835
|
+
};
|
|
773
836
|
}
|
|
774
837
|
/**
|
|
775
838
|
* Post-parse graph analysis: MRO, community detection, process extraction.
|
|
@@ -797,7 +860,7 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
|
|
|
797
860
|
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
|
|
798
861
|
});
|
|
799
862
|
const communityResult = await processCommunities(graph, (message, progress) => {
|
|
800
|
-
const communityProgress = 82 +
|
|
863
|
+
const communityProgress = 82 + progress * 0.1;
|
|
801
864
|
onProgress({
|
|
802
865
|
phase: 'communities',
|
|
803
866
|
percent: Math.round(communityProgress),
|
|
@@ -808,7 +871,7 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
|
|
|
808
871
|
if (isDev) {
|
|
809
872
|
console.log(`🏘️ Community detection: ${communityResult.stats.totalCommunities} communities found (modularity: ${communityResult.stats.modularity.toFixed(3)})`);
|
|
810
873
|
}
|
|
811
|
-
communityResult.communities.forEach(comm => {
|
|
874
|
+
communityResult.communities.forEach((comm) => {
|
|
812
875
|
graph.addNode({
|
|
813
876
|
id: comm.id,
|
|
814
877
|
label: 'Community',
|
|
@@ -818,10 +881,10 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
|
|
|
818
881
|
heuristicLabel: comm.heuristicLabel,
|
|
819
882
|
cohesion: comm.cohesion,
|
|
820
883
|
symbolCount: comm.symbolCount,
|
|
821
|
-
}
|
|
884
|
+
},
|
|
822
885
|
});
|
|
823
886
|
});
|
|
824
|
-
communityResult.memberships.forEach(membership => {
|
|
887
|
+
communityResult.memberships.forEach((membership) => {
|
|
825
888
|
graph.addRelationship({
|
|
826
889
|
id: `${membership.nodeId}_member_of_${membership.communityId}`,
|
|
827
890
|
type: 'MEMBER_OF',
|
|
@@ -839,11 +902,13 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
|
|
|
839
902
|
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
|
|
840
903
|
});
|
|
841
904
|
let symbolCount = 0;
|
|
842
|
-
graph.forEachNode(n => {
|
|
843
|
-
|
|
905
|
+
graph.forEachNode((n) => {
|
|
906
|
+
if (n.label !== 'File')
|
|
907
|
+
symbolCount++;
|
|
908
|
+
});
|
|
844
909
|
const dynamicMaxProcesses = Math.max(20, Math.min(300, Math.round(symbolCount / 10)));
|
|
845
910
|
const processResult = await processProcesses(graph, communityResult.memberships, (message, progress) => {
|
|
846
|
-
const processProgress = 94 +
|
|
911
|
+
const processProgress = 94 + progress * 0.05;
|
|
847
912
|
onProgress({
|
|
848
913
|
phase: 'processes',
|
|
849
914
|
percent: Math.round(processProgress),
|
|
@@ -854,7 +919,7 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
|
|
|
854
919
|
if (isDev) {
|
|
855
920
|
console.log(`🔄 Process detection: ${processResult.stats.totalProcesses} processes found (${processResult.stats.crossCommunityCount} cross-community)`);
|
|
856
921
|
}
|
|
857
|
-
processResult.processes.forEach(proc => {
|
|
922
|
+
processResult.processes.forEach((proc) => {
|
|
858
923
|
graph.addNode({
|
|
859
924
|
id: proc.id,
|
|
860
925
|
label: 'Process',
|
|
@@ -867,10 +932,10 @@ async function runGraphAnalysisPhases(graph, totalFiles, onProgress, routeRegist
|
|
|
867
932
|
communities: proc.communities,
|
|
868
933
|
entryPointId: proc.entryPointId,
|
|
869
934
|
terminalId: proc.terminalId,
|
|
870
|
-
}
|
|
935
|
+
},
|
|
871
936
|
});
|
|
872
937
|
});
|
|
873
|
-
processResult.steps.forEach(step => {
|
|
938
|
+
processResult.steps.forEach((step) => {
|
|
874
939
|
graph.addRelationship({
|
|
875
940
|
id: `${step.nodeId}_step_${step.step}_${step.processId}`,
|
|
876
941
|
type: 'STEP_IN_PROCESS',
|
|
@@ -962,7 +1027,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
962
1027
|
// Phase 1+2: Scan paths, build structure, process markdown
|
|
963
1028
|
const { scannedFiles, allPaths, totalFiles } = await runScanAndStructure(repoPath, graph, onProgress);
|
|
964
1029
|
// Phase 3+4: Chunked parse + resolve (imports, calls, heritage, routes)
|
|
965
|
-
const { exportedTypeMap, allFetchCalls, allExtractedRoutes, allDecoratorRoutes, allToolDefs, allORMQueries } = await runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress);
|
|
1030
|
+
const { exportedTypeMap, allFetchCalls, allExtractedRoutes, allDecoratorRoutes, allToolDefs, allORMQueries, } = await runChunkedParseAndResolve(graph, ctx, scannedFiles, allPaths, totalFiles, repoPath, pipelineStart, onProgress);
|
|
966
1031
|
const routeRegistry = new Map();
|
|
967
1032
|
// Detect Expo Router app/ roots vs Next.js app/ roots (monorepo-safe).
|
|
968
1033
|
const expoAppRoots = new Set();
|
|
@@ -1009,7 +1074,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1009
1074
|
}
|
|
1010
1075
|
}
|
|
1011
1076
|
}
|
|
1012
|
-
const ensureSlash = (path) => path.startsWith('/') ? path : '/' + path;
|
|
1077
|
+
const ensureSlash = (path) => (path.startsWith('/') ? path : '/' + path);
|
|
1013
1078
|
let duplicateRoutes = 0;
|
|
1014
1079
|
const addRoute = (url, entry) => {
|
|
1015
1080
|
if (routeRegistry.has(url)) {
|
|
@@ -1021,20 +1086,28 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1021
1086
|
for (const route of allExtractedRoutes) {
|
|
1022
1087
|
if (!route.routePath)
|
|
1023
1088
|
continue;
|
|
1024
|
-
addRoute(ensureSlash(route.routePath), {
|
|
1089
|
+
addRoute(ensureSlash(route.routePath), {
|
|
1090
|
+
filePath: route.filePath,
|
|
1091
|
+
source: 'framework-route',
|
|
1092
|
+
});
|
|
1025
1093
|
}
|
|
1026
1094
|
for (const dr of allDecoratorRoutes) {
|
|
1027
|
-
addRoute(ensureSlash(dr.routePath), {
|
|
1095
|
+
addRoute(ensureSlash(dr.routePath), {
|
|
1096
|
+
filePath: dr.filePath,
|
|
1097
|
+
source: `decorator-${dr.decoratorName}`,
|
|
1098
|
+
});
|
|
1028
1099
|
}
|
|
1029
1100
|
let handlerContents;
|
|
1030
1101
|
if (routeRegistry.size > 0) {
|
|
1031
|
-
const handlerPaths = [...routeRegistry.values()].map(e => e.filePath);
|
|
1102
|
+
const handlerPaths = [...routeRegistry.values()].map((e) => e.filePath);
|
|
1032
1103
|
handlerContents = await readFileContents(repoPath, handlerPaths);
|
|
1033
1104
|
for (const [routeURL, entry] of routeRegistry) {
|
|
1034
1105
|
const { filePath: handlerPath, source: routeSource } = entry;
|
|
1035
1106
|
const content = handlerContents.get(handlerPath);
|
|
1036
1107
|
const { responseKeys, errorKeys } = content
|
|
1037
|
-
?
|
|
1108
|
+
? handlerPath.endsWith('.php')
|
|
1109
|
+
? extractPHPResponseShapes(content)
|
|
1110
|
+
: extractResponseShapes(content)
|
|
1038
1111
|
: { responseKeys: undefined, errorKeys: undefined };
|
|
1039
1112
|
const mwResult = content ? extractMiddlewareChain(content) : undefined;
|
|
1040
1113
|
const middleware = mwResult?.chain;
|
|
@@ -1066,23 +1139,29 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1066
1139
|
}
|
|
1067
1140
|
// ── Phase 3.5b: Link Next.js project-level middleware.ts to routes ──
|
|
1068
1141
|
if (routeRegistry.size > 0) {
|
|
1069
|
-
const middlewareCandidates = allPaths.filter(p => p === 'middleware.ts' ||
|
|
1070
|
-
p === '
|
|
1142
|
+
const middlewareCandidates = allPaths.filter((p) => p === 'middleware.ts' ||
|
|
1143
|
+
p === 'middleware.js' ||
|
|
1144
|
+
p === 'middleware.tsx' ||
|
|
1145
|
+
p === 'middleware.jsx' ||
|
|
1146
|
+
p === 'src/middleware.ts' ||
|
|
1147
|
+
p === 'src/middleware.js' ||
|
|
1148
|
+
p === 'src/middleware.tsx' ||
|
|
1149
|
+
p === 'src/middleware.jsx');
|
|
1071
1150
|
if (middlewareCandidates.length > 0) {
|
|
1072
1151
|
const mwContents = await readFileContents(repoPath, middlewareCandidates);
|
|
1073
1152
|
for (const [mwPath, mwContent] of mwContents) {
|
|
1074
1153
|
const config = extractNextjsMiddlewareConfig(mwContent);
|
|
1075
1154
|
if (!config)
|
|
1076
1155
|
continue;
|
|
1077
|
-
const mwLabel = config.wrappedFunctions.length > 0
|
|
1078
|
-
? config.wrappedFunctions
|
|
1079
|
-
: [config.exportedName];
|
|
1156
|
+
const mwLabel = config.wrappedFunctions.length > 0 ? config.wrappedFunctions : [config.exportedName];
|
|
1080
1157
|
// Pre-compile matchers once per middleware file
|
|
1081
|
-
const compiled = config.matchers
|
|
1158
|
+
const compiled = config.matchers
|
|
1159
|
+
.map(compileMatcher)
|
|
1160
|
+
.filter((m) => m !== null);
|
|
1082
1161
|
let linkedCount = 0;
|
|
1083
1162
|
for (const [routeURL] of routeRegistry) {
|
|
1084
1163
|
const matches = compiled.length === 0 ||
|
|
1085
|
-
compiled.some(cm => compiledMatcherMatchesRoute(cm, routeURL));
|
|
1164
|
+
compiled.some((cm) => compiledMatcherMatchesRoute(cm, routeURL));
|
|
1086
1165
|
if (!matches)
|
|
1087
1166
|
continue;
|
|
1088
1167
|
const routeNodeId = generateId('Route', routeURL);
|
|
@@ -1091,7 +1170,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1091
1170
|
continue;
|
|
1092
1171
|
const currentMw = existing.properties.middleware ?? [];
|
|
1093
1172
|
// Prepend project-level middleware (runs before handler-level wrappers)
|
|
1094
|
-
existing.properties.middleware = [
|
|
1173
|
+
existing.properties.middleware = [
|
|
1174
|
+
...mwLabel,
|
|
1175
|
+
...currentMw.filter((m) => !mwLabel.includes(m)),
|
|
1176
|
+
];
|
|
1095
1177
|
linkedCount++;
|
|
1096
1178
|
}
|
|
1097
1179
|
if (isDev && linkedCount > 0) {
|
|
@@ -1103,8 +1185,11 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1103
1185
|
// Scan HTML/PHP/template files for <form action="/path"> and AJAX url patterns
|
|
1104
1186
|
// Scan HTML/template files for <form action="/path"> and AJAX url patterns
|
|
1105
1187
|
// Skip .php — already parsed by tree-sitter with http_client/fetch queries
|
|
1106
|
-
const htmlCandidates = allPaths.filter(p => p.endsWith('.html') ||
|
|
1107
|
-
p.endsWith('.
|
|
1188
|
+
const htmlCandidates = allPaths.filter((p) => p.endsWith('.html') ||
|
|
1189
|
+
p.endsWith('.htm') ||
|
|
1190
|
+
p.endsWith('.ejs') ||
|
|
1191
|
+
p.endsWith('.hbs') ||
|
|
1192
|
+
p.endsWith('.blade.php'));
|
|
1108
1193
|
if (htmlCandidates.length > 0 && routeRegistry.size > 0) {
|
|
1109
1194
|
const htmlContents = await readFileContents(repoPath, htmlCandidates);
|
|
1110
1195
|
const htmlPatterns = [/action=["']([^"']+)["']/g, /url:\s*["']([^"']+)["']/g];
|
|
@@ -1123,8 +1208,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1123
1208
|
}
|
|
1124
1209
|
// ── Phase 3.5c: Extract Expo Router navigation patterns ──
|
|
1125
1210
|
if (expoAppPaths.size > 0 && routeRegistry.size > 0) {
|
|
1126
|
-
const unreadExpoPaths = [...expoAppPaths].filter(p => !handlerContents?.has(p));
|
|
1127
|
-
const extraContents = unreadExpoPaths.length > 0
|
|
1211
|
+
const unreadExpoPaths = [...expoAppPaths].filter((p) => !handlerContents?.has(p));
|
|
1212
|
+
const extraContents = unreadExpoPaths.length > 0
|
|
1213
|
+
? await readFileContents(repoPath, unreadExpoPaths)
|
|
1214
|
+
: new Map();
|
|
1128
1215
|
const allExpoContents = new Map([...(handlerContents ?? new Map()), ...extraContents]);
|
|
1129
1216
|
for (const [filePath, content] of allExpoContents) {
|
|
1130
1217
|
if (!expoAppPaths.has(filePath))
|
|
@@ -1146,7 +1233,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1146
1233
|
for (const [url, entry] of routeRegistry)
|
|
1147
1234
|
routeURLToFile.set(url, entry.filePath);
|
|
1148
1235
|
// Read consumer file contents so we can extract property access patterns
|
|
1149
|
-
const consumerPaths = [...new Set(allFetchCalls.map(c => c.filePath))];
|
|
1236
|
+
const consumerPaths = [...new Set(allFetchCalls.map((c) => c.filePath))];
|
|
1150
1237
|
const consumerContents = await readFileContents(repoPath, consumerPaths);
|
|
1151
1238
|
processNextjsFetchRoutes(graph, allFetchCalls, routeURLToFile, consumerContents);
|
|
1152
1239
|
if (isDev) {
|
|
@@ -1163,8 +1250,11 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1163
1250
|
toolDefs.push({ name: td.toolName, filePath: td.filePath, description: td.description });
|
|
1164
1251
|
}
|
|
1165
1252
|
// TS tool definition arrays — require inputSchema nearby to distinguish from config objects
|
|
1166
|
-
const toolCandidatePaths = allPaths.filter(p => (p.endsWith('.ts') || p.endsWith('.js')) &&
|
|
1167
|
-
|
|
1253
|
+
const toolCandidatePaths = allPaths.filter((p) => (p.endsWith('.ts') || p.endsWith('.js')) &&
|
|
1254
|
+
p.toLowerCase().includes('tool') &&
|
|
1255
|
+
!p.includes('node_modules') &&
|
|
1256
|
+
!p.includes('test') &&
|
|
1257
|
+
!p.includes('__'));
|
|
1168
1258
|
if (toolCandidatePaths.length > 0) {
|
|
1169
1259
|
const toolContents = await readFileContents(repoPath, toolCandidatePaths);
|
|
1170
1260
|
for (const [filePath, content] of toolContents) {
|
|
@@ -1178,7 +1268,11 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1178
1268
|
if (seenToolNames.has(name))
|
|
1179
1269
|
continue;
|
|
1180
1270
|
seenToolNames.add(name);
|
|
1181
|
-
toolDefs.push({
|
|
1271
|
+
toolDefs.push({
|
|
1272
|
+
name,
|
|
1273
|
+
filePath,
|
|
1274
|
+
description: match[2].slice(0, 200).replace(/\n/g, ' ').trim(),
|
|
1275
|
+
});
|
|
1182
1276
|
}
|
|
1183
1277
|
}
|
|
1184
1278
|
}
|
|
@@ -1228,7 +1322,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
|
|
|
1228
1322
|
stats: {
|
|
1229
1323
|
filesProcessed: totalFiles,
|
|
1230
1324
|
totalFiles,
|
|
1231
|
-
nodesCreated: graph.nodeCount
|
|
1325
|
+
nodesCreated: graph.nodeCount,
|
|
1232
1326
|
},
|
|
1233
1327
|
});
|
|
1234
1328
|
return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult };
|
|
@@ -1253,14 +1347,26 @@ function extractORMQueriesInline(filePath, content, out) {
|
|
|
1253
1347
|
const model = m[1];
|
|
1254
1348
|
if (model.startsWith('$'))
|
|
1255
1349
|
continue;
|
|
1256
|
-
out.push({
|
|
1350
|
+
out.push({
|
|
1351
|
+
filePath,
|
|
1352
|
+
orm: 'prisma',
|
|
1353
|
+
model,
|
|
1354
|
+
method: m[2],
|
|
1355
|
+
lineNumber: content.substring(0, m.index).split('\n').length - 1,
|
|
1356
|
+
});
|
|
1257
1357
|
}
|
|
1258
1358
|
}
|
|
1259
1359
|
if (hasSupabase) {
|
|
1260
1360
|
SUPABASE_QUERY_RE.lastIndex = 0;
|
|
1261
1361
|
let m;
|
|
1262
1362
|
while ((m = SUPABASE_QUERY_RE.exec(content)) !== null) {
|
|
1263
|
-
out.push({
|
|
1363
|
+
out.push({
|
|
1364
|
+
filePath,
|
|
1365
|
+
orm: 'supabase',
|
|
1366
|
+
model: m[1],
|
|
1367
|
+
method: m[2],
|
|
1368
|
+
lineNumber: content.substring(0, m.index).split('\n').length - 1,
|
|
1369
|
+
});
|
|
1264
1370
|
}
|
|
1265
1371
|
}
|
|
1266
1372
|
}
|
|
@@ -1280,7 +1386,7 @@ function processORMQueries(graph, queries, isDev) {
|
|
|
1280
1386
|
generateId('Interface', `${q.model}`),
|
|
1281
1387
|
generateId('CodeElement', `${q.model}`),
|
|
1282
1388
|
];
|
|
1283
|
-
const existing = candidateIds.find(id => graph.getNode(id));
|
|
1389
|
+
const existing = candidateIds.find((id) => graph.getNode(id));
|
|
1284
1390
|
if (existing) {
|
|
1285
1391
|
modelNodeId = existing;
|
|
1286
1392
|
}
|