gitnexus 1.6.0 → 1.6.2-rc.1
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 +73 -0
- package/dist/cli/analyze.js +50 -3
- package/dist/core/group/extractors/fs-utils.d.ts +10 -0
- package/dist/core/group/extractors/fs-utils.js +24 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +17 -8
- package/dist/core/group/extractors/grpc-extractor.js +328 -191
- package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
- package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
- package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
- package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
- package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
- package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
- package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
- package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
- package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/go.js +215 -0
- package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
- package/dist/core/group/extractors/http-patterns/index.js +44 -0
- package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/java.js +253 -0
- package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/http-patterns/node.js +354 -0
- package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/php.js +70 -0
- package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/python.js +133 -0
- package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
- package/dist/core/group/extractors/http-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +10 -13
- package/dist/core/group/extractors/http-route-extractor.js +231 -238
- package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
- package/dist/core/group/extractors/manifest-extractor.js +277 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +0 -1
- package/dist/core/group/extractors/topic-extractor.js +55 -192
- package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/go.js +120 -0
- package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
- package/dist/core/group/extractors/topic-patterns/index.js +38 -0
- package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/java.js +80 -0
- package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/topic-patterns/node.js +155 -0
- package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/python.js +116 -0
- package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
- package/dist/core/group/extractors/topic-patterns/types.js +10 -0
- package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
- package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +22 -17
- package/dist/core/ingestion/binding-accumulator.js +29 -25
- package/dist/core/ingestion/cobol-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +1 -1
- package/dist/core/ingestion/language-config.js +1 -1
- package/dist/core/ingestion/language-provider.d.ts +32 -5
- package/dist/core/ingestion/languages/c-cpp.js +2 -2
- package/dist/core/ingestion/languages/dart.d.ts +1 -1
- package/dist/core/ingestion/languages/dart.js +2 -2
- package/dist/core/ingestion/languages/go.d.ts +1 -1
- package/dist/core/ingestion/languages/go.js +2 -2
- package/dist/core/ingestion/languages/ruby.js +16 -1
- package/dist/core/ingestion/languages/swift.d.ts +1 -1
- package/dist/core/ingestion/languages/swift.js +2 -2
- package/dist/core/ingestion/markdown-processor.d.ts +1 -1
- package/dist/core/ingestion/method-extractors/configs/jvm.js +1 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +1 -0
- package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
- package/dist/core/ingestion/method-extractors/generic.js +48 -4
- package/dist/core/ingestion/method-types.d.ts +4 -0
- package/dist/core/ingestion/model/resolve.js +103 -48
- package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
- package/dist/core/ingestion/model/semantic-model.js +1 -1
- package/dist/core/ingestion/model/symbol-table.d.ts +7 -7
- package/dist/core/ingestion/model/symbol-table.js +7 -7
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +1 -1
- package/dist/core/ingestion/parsing-processor.js +54 -42
- package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
- package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/index.js +22 -0
- package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
- package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
- package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
- package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
- package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
- package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
- package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
- package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
- package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
- package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
- package/dist/core/ingestion/pipeline-phases/types.js +37 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +70 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +312 -0
- package/dist/core/ingestion/pipeline.d.ts +16 -10
- package/dist/core/ingestion/pipeline.js +66 -1534
- package/dist/core/ingestion/process-processor.js +1 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +69 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -3
- package/dist/core/ingestion/utils/ast-helpers.js +48 -21
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +10 -0
- package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
- package/dist/core/ingestion/utils/graph-sort.js +100 -0
- package/dist/core/ingestion/workers/parse-worker.js +12 -8
- package/dist/core/lbug/lbug-adapter.d.ts +28 -0
- package/dist/core/lbug/lbug-adapter.js +162 -57
- package/package.json +3 -3
- package/vendor/tree-sitter-proto/binding.gyp +30 -0
- package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
- package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-proto/package.json +18 -0
- package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
- package/vendor/tree-sitter-proto/src/parser.c +10149 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: processes
|
|
3
|
+
*
|
|
4
|
+
* Detects execution flows (processes) and creates Process nodes +
|
|
5
|
+
* STEP_IN_PROCESS edges. Also links Route/Tool nodes to processes.
|
|
6
|
+
*
|
|
7
|
+
* @deps communities, routes, tools
|
|
8
|
+
* @reads graph (all nodes and relationships), communityResult, routeRegistry, toolDefs
|
|
9
|
+
* @writes graph (Process nodes, STEP_IN_PROCESS edges, ENTRY_POINT_OF edges)
|
|
10
|
+
*/
|
|
11
|
+
import { getPhaseOutput } from './types.js';
|
|
12
|
+
import { processProcesses } from '../process-processor.js';
|
|
13
|
+
import { generateId } from '../../../lib/utils.js';
|
|
14
|
+
import { isDev } from '../utils/env.js';
|
|
15
|
+
export const processesPhase = {
|
|
16
|
+
name: 'processes',
|
|
17
|
+
// `structure` supplies `totalFiles` (progress counter) without the spurious
|
|
18
|
+
// structural data dependency on `parse`.
|
|
19
|
+
deps: ['communities', 'routes', 'tools', 'structure'],
|
|
20
|
+
async execute(ctx, deps) {
|
|
21
|
+
const { totalFiles } = getPhaseOutput(deps, 'structure');
|
|
22
|
+
const { communityResult } = getPhaseOutput(deps, 'communities');
|
|
23
|
+
const { routeRegistry } = getPhaseOutput(deps, 'routes');
|
|
24
|
+
const { toolDefs } = getPhaseOutput(deps, 'tools');
|
|
25
|
+
ctx.onProgress({
|
|
26
|
+
phase: 'processes',
|
|
27
|
+
percent: 94,
|
|
28
|
+
message: 'Detecting execution flows...',
|
|
29
|
+
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: ctx.graph.nodeCount },
|
|
30
|
+
});
|
|
31
|
+
let symbolCount = 0;
|
|
32
|
+
ctx.graph.forEachNode((n) => {
|
|
33
|
+
if (n.label !== 'File')
|
|
34
|
+
symbolCount++;
|
|
35
|
+
});
|
|
36
|
+
const dynamicMaxProcesses = Math.max(20, Math.min(300, Math.round(symbolCount / 10)));
|
|
37
|
+
const processResult = await processProcesses(ctx.graph, communityResult.memberships, (message, progress) => {
|
|
38
|
+
const processProgress = 94 + progress * 0.05;
|
|
39
|
+
ctx.onProgress({
|
|
40
|
+
phase: 'processes',
|
|
41
|
+
percent: Math.round(processProgress),
|
|
42
|
+
message,
|
|
43
|
+
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: ctx.graph.nodeCount },
|
|
44
|
+
});
|
|
45
|
+
}, { maxProcesses: dynamicMaxProcesses, minSteps: 3 });
|
|
46
|
+
if (isDev) {
|
|
47
|
+
console.log(`🔄 Process detection: ${processResult.stats.totalProcesses} processes found (${processResult.stats.crossCommunityCount} cross-community)`);
|
|
48
|
+
}
|
|
49
|
+
processResult.processes.forEach((proc) => {
|
|
50
|
+
ctx.graph.addNode({
|
|
51
|
+
id: proc.id,
|
|
52
|
+
label: 'Process',
|
|
53
|
+
properties: {
|
|
54
|
+
name: proc.label,
|
|
55
|
+
filePath: '',
|
|
56
|
+
heuristicLabel: proc.heuristicLabel,
|
|
57
|
+
processType: proc.processType,
|
|
58
|
+
stepCount: proc.stepCount,
|
|
59
|
+
communities: proc.communities,
|
|
60
|
+
entryPointId: proc.entryPointId,
|
|
61
|
+
terminalId: proc.terminalId,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
processResult.steps.forEach((step) => {
|
|
66
|
+
ctx.graph.addRelationship({
|
|
67
|
+
id: `${step.nodeId}_step_${step.step}_${step.processId}`,
|
|
68
|
+
type: 'STEP_IN_PROCESS',
|
|
69
|
+
sourceId: step.nodeId,
|
|
70
|
+
targetId: step.processId,
|
|
71
|
+
confidence: 1.0,
|
|
72
|
+
reason: 'trace-detection',
|
|
73
|
+
step: step.step,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// Link Route and Tool nodes to Processes
|
|
77
|
+
if (routeRegistry.size > 0 || toolDefs.length > 0) {
|
|
78
|
+
const routesByFile = new Map();
|
|
79
|
+
for (const [url, entry] of routeRegistry) {
|
|
80
|
+
let list = routesByFile.get(entry.filePath);
|
|
81
|
+
if (!list) {
|
|
82
|
+
list = [];
|
|
83
|
+
routesByFile.set(entry.filePath, list);
|
|
84
|
+
}
|
|
85
|
+
list.push(url);
|
|
86
|
+
}
|
|
87
|
+
const toolsByFile = new Map();
|
|
88
|
+
for (const td of toolDefs) {
|
|
89
|
+
let list = toolsByFile.get(td.filePath);
|
|
90
|
+
if (!list) {
|
|
91
|
+
list = [];
|
|
92
|
+
toolsByFile.set(td.filePath, list);
|
|
93
|
+
}
|
|
94
|
+
list.push(td.name);
|
|
95
|
+
}
|
|
96
|
+
let linked = 0;
|
|
97
|
+
for (const proc of processResult.processes) {
|
|
98
|
+
if (!proc.entryPointId)
|
|
99
|
+
continue;
|
|
100
|
+
const entryNode = ctx.graph.getNode(proc.entryPointId);
|
|
101
|
+
if (!entryNode)
|
|
102
|
+
continue;
|
|
103
|
+
const entryFile = entryNode.properties.filePath;
|
|
104
|
+
if (!entryFile)
|
|
105
|
+
continue;
|
|
106
|
+
const routeURLs = routesByFile.get(entryFile);
|
|
107
|
+
if (routeURLs) {
|
|
108
|
+
for (const routeURL of routeURLs) {
|
|
109
|
+
const routeNodeId = generateId('Route', routeURL);
|
|
110
|
+
ctx.graph.addRelationship({
|
|
111
|
+
id: generateId('ENTRY_POINT_OF', `${routeNodeId}->${proc.id}`),
|
|
112
|
+
sourceId: routeNodeId,
|
|
113
|
+
targetId: proc.id,
|
|
114
|
+
type: 'ENTRY_POINT_OF',
|
|
115
|
+
confidence: 0.85,
|
|
116
|
+
reason: 'route-handler-entry-point',
|
|
117
|
+
});
|
|
118
|
+
linked++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const toolNames = toolsByFile.get(entryFile);
|
|
122
|
+
if (toolNames) {
|
|
123
|
+
for (const toolName of toolNames) {
|
|
124
|
+
const toolNodeId = generateId('Tool', toolName);
|
|
125
|
+
ctx.graph.addRelationship({
|
|
126
|
+
id: generateId('ENTRY_POINT_OF', `${toolNodeId}->${proc.id}`),
|
|
127
|
+
sourceId: toolNodeId,
|
|
128
|
+
targetId: proc.id,
|
|
129
|
+
type: 'ENTRY_POINT_OF',
|
|
130
|
+
confidence: 0.85,
|
|
131
|
+
reason: 'tool-handler-entry-point',
|
|
132
|
+
});
|
|
133
|
+
linked++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (isDev && linked > 0) {
|
|
138
|
+
console.log(`🔗 Linked ${linked} Route/Tool nodes to execution flows`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { processResult };
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: routes
|
|
3
|
+
*
|
|
4
|
+
* Builds the route registry (Next.js, Expo, PHP, Laravel, decorator-based)
|
|
5
|
+
* and creates Route graph nodes + HANDLES_ROUTE edges.
|
|
6
|
+
* Also links middleware, processes fetch() calls, and scans HTML templates.
|
|
7
|
+
*
|
|
8
|
+
* @deps parse
|
|
9
|
+
* @reads allPaths, allExtractedRoutes, allDecoratorRoutes, allFetchCalls
|
|
10
|
+
* @writes graph (Route nodes, HANDLES_ROUTE, FETCHES_FROM edges)
|
|
11
|
+
* @output routeRegistry, handlerContents
|
|
12
|
+
*/
|
|
13
|
+
import type { PipelinePhase } from './types.js';
|
|
14
|
+
export interface RouteEntry {
|
|
15
|
+
filePath: string;
|
|
16
|
+
source: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RoutesOutput {
|
|
19
|
+
routeRegistry: Map<string, RouteEntry>;
|
|
20
|
+
}
|
|
21
|
+
export declare const routesPhase: PipelinePhase<RoutesOutput>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: routes
|
|
3
|
+
*
|
|
4
|
+
* Builds the route registry (Next.js, Expo, PHP, Laravel, decorator-based)
|
|
5
|
+
* and creates Route graph nodes + HANDLES_ROUTE edges.
|
|
6
|
+
* Also links middleware, processes fetch() calls, and scans HTML templates.
|
|
7
|
+
*
|
|
8
|
+
* @deps parse
|
|
9
|
+
* @reads allPaths, allExtractedRoutes, allDecoratorRoutes, allFetchCalls
|
|
10
|
+
* @writes graph (Route nodes, HANDLES_ROUTE, FETCHES_FROM edges)
|
|
11
|
+
* @output routeRegistry, handlerContents
|
|
12
|
+
*/
|
|
13
|
+
import { getPhaseOutput } from './types.js';
|
|
14
|
+
import { nextjsFileToRouteURL, normalizeFetchURL } from '../route-extractors/nextjs.js';
|
|
15
|
+
import { expoFileToRouteURL } from '../route-extractors/expo.js';
|
|
16
|
+
import { phpFileToRouteURL } from '../route-extractors/php.js';
|
|
17
|
+
import { extractResponseShapes, extractPHPResponseShapes, } from '../route-extractors/response-shapes.js';
|
|
18
|
+
import { extractMiddlewareChain, extractNextjsMiddlewareConfig, compileMatcher, compiledMatcherMatchesRoute, } from '../route-extractors/middleware.js';
|
|
19
|
+
import { processNextjsFetchRoutes } from '../call-processor.js';
|
|
20
|
+
import { generateId } from '../../../lib/utils.js';
|
|
21
|
+
import { readFileContents } from '../filesystem-walker.js';
|
|
22
|
+
import { isDev } from '../utils/env.js';
|
|
23
|
+
const EXPO_NAV_PATTERNS = [
|
|
24
|
+
/router\.(push|replace|navigate)\(\s*['"`]([^'"`]+)['"`]/g,
|
|
25
|
+
/<Link\s+[^>]*href=\s*['"`]([^'"`]+)['"`]/g,
|
|
26
|
+
];
|
|
27
|
+
export const routesPhase = {
|
|
28
|
+
name: 'routes',
|
|
29
|
+
deps: ['parse'],
|
|
30
|
+
async execute(ctx, deps) {
|
|
31
|
+
const { allPaths, allFetchCalls: parseFetchCalls, allExtractedRoutes, allDecoratorRoutes, } = getPhaseOutput(deps, 'parse');
|
|
32
|
+
// Local copy — routes phase must not mutate upstream ParseOutput
|
|
33
|
+
const allFetchCalls = [...parseFetchCalls];
|
|
34
|
+
const routeRegistry = new Map();
|
|
35
|
+
// Detect Expo Router app/ roots vs Next.js app/ roots (monorepo-safe)
|
|
36
|
+
const expoAppRoots = new Set();
|
|
37
|
+
const nextjsAppRoots = new Set();
|
|
38
|
+
const expoAppPaths = new Set();
|
|
39
|
+
for (const p of allPaths) {
|
|
40
|
+
const norm = p.replace(/\\/g, '/');
|
|
41
|
+
const appIdx = norm.lastIndexOf('app/');
|
|
42
|
+
if (appIdx < 0)
|
|
43
|
+
continue;
|
|
44
|
+
const root = norm.slice(0, appIdx + 4);
|
|
45
|
+
if (/\/_layout\.(tsx?|jsx?)$/.test(norm))
|
|
46
|
+
expoAppRoots.add(root);
|
|
47
|
+
if (/\/page\.(tsx?|jsx?)$/.test(norm))
|
|
48
|
+
nextjsAppRoots.add(root);
|
|
49
|
+
}
|
|
50
|
+
for (const root of nextjsAppRoots)
|
|
51
|
+
expoAppRoots.delete(root);
|
|
52
|
+
if (expoAppRoots.size > 0) {
|
|
53
|
+
for (const p of allPaths) {
|
|
54
|
+
const norm = p.replace(/\\/g, '/');
|
|
55
|
+
const appIdx = norm.lastIndexOf('app/');
|
|
56
|
+
if (appIdx >= 0 && expoAppRoots.has(norm.slice(0, appIdx + 4)))
|
|
57
|
+
expoAppPaths.add(p);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const p of allPaths) {
|
|
61
|
+
if (expoAppPaths.has(p)) {
|
|
62
|
+
const expoURL = expoFileToRouteURL(p);
|
|
63
|
+
if (expoURL && !routeRegistry.has(expoURL)) {
|
|
64
|
+
routeRegistry.set(expoURL, { filePath: p, source: 'expo-filesystem-route' });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const nextjsURL = nextjsFileToRouteURL(p);
|
|
69
|
+
if (nextjsURL && !routeRegistry.has(nextjsURL)) {
|
|
70
|
+
routeRegistry.set(nextjsURL, { filePath: p, source: 'nextjs-filesystem-route' });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (p.endsWith('.php')) {
|
|
74
|
+
const phpURL = phpFileToRouteURL(p);
|
|
75
|
+
if (phpURL && !routeRegistry.has(phpURL)) {
|
|
76
|
+
routeRegistry.set(phpURL, { filePath: p, source: 'php-file-route' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const ensureSlash = (path) => (path.startsWith('/') ? path : '/' + path);
|
|
81
|
+
let duplicateRoutes = 0;
|
|
82
|
+
const addRoute = (url, entry) => {
|
|
83
|
+
if (routeRegistry.has(url)) {
|
|
84
|
+
duplicateRoutes++;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
routeRegistry.set(url, entry);
|
|
88
|
+
};
|
|
89
|
+
for (const route of allExtractedRoutes) {
|
|
90
|
+
if (!route.routePath)
|
|
91
|
+
continue;
|
|
92
|
+
addRoute(ensureSlash(route.routePath), {
|
|
93
|
+
filePath: route.filePath,
|
|
94
|
+
source: 'framework-route',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
for (const dr of allDecoratorRoutes) {
|
|
98
|
+
addRoute(ensureSlash(dr.routePath), {
|
|
99
|
+
filePath: dr.filePath,
|
|
100
|
+
source: `decorator-${dr.decoratorName}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
let handlerContents;
|
|
104
|
+
if (routeRegistry.size > 0) {
|
|
105
|
+
const handlerPaths = [...routeRegistry.values()].map((e) => e.filePath);
|
|
106
|
+
handlerContents = await readFileContents(ctx.repoPath, handlerPaths);
|
|
107
|
+
for (const [routeURL, entry] of routeRegistry) {
|
|
108
|
+
const { filePath: handlerPath, source: routeSource } = entry;
|
|
109
|
+
const content = handlerContents.get(handlerPath);
|
|
110
|
+
const { responseKeys, errorKeys } = content
|
|
111
|
+
? handlerPath.endsWith('.php')
|
|
112
|
+
? extractPHPResponseShapes(content)
|
|
113
|
+
: extractResponseShapes(content)
|
|
114
|
+
: { responseKeys: undefined, errorKeys: undefined };
|
|
115
|
+
const mwResult = content ? extractMiddlewareChain(content) : undefined;
|
|
116
|
+
const middleware = mwResult?.chain;
|
|
117
|
+
const routeNodeId = generateId('Route', routeURL);
|
|
118
|
+
ctx.graph.addNode({
|
|
119
|
+
id: routeNodeId,
|
|
120
|
+
label: 'Route',
|
|
121
|
+
properties: {
|
|
122
|
+
name: routeURL,
|
|
123
|
+
filePath: handlerPath,
|
|
124
|
+
...(responseKeys ? { responseKeys } : {}),
|
|
125
|
+
...(errorKeys ? { errorKeys } : {}),
|
|
126
|
+
...(middleware && middleware.length > 0 ? { middleware } : {}),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
const handlerFileId = generateId('File', handlerPath);
|
|
130
|
+
ctx.graph.addRelationship({
|
|
131
|
+
id: generateId('HANDLES_ROUTE', `${handlerFileId}->${routeNodeId}`),
|
|
132
|
+
sourceId: handlerFileId,
|
|
133
|
+
targetId: routeNodeId,
|
|
134
|
+
type: 'HANDLES_ROUTE',
|
|
135
|
+
confidence: 1.0,
|
|
136
|
+
reason: routeSource,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (isDev) {
|
|
140
|
+
console.log(`🗺️ Route registry: ${routeRegistry.size} routes${duplicateRoutes > 0 ? ` (${duplicateRoutes} duplicate URLs skipped)` : ''}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ── Link Next.js project-level middleware.ts to routes ──
|
|
144
|
+
if (routeRegistry.size > 0) {
|
|
145
|
+
const middlewareCandidates = allPaths.filter((p) => p === 'middleware.ts' ||
|
|
146
|
+
p === 'middleware.js' ||
|
|
147
|
+
p === 'middleware.tsx' ||
|
|
148
|
+
p === 'middleware.jsx' ||
|
|
149
|
+
p === 'src/middleware.ts' ||
|
|
150
|
+
p === 'src/middleware.js' ||
|
|
151
|
+
p === 'src/middleware.tsx' ||
|
|
152
|
+
p === 'src/middleware.jsx');
|
|
153
|
+
if (middlewareCandidates.length > 0) {
|
|
154
|
+
const mwContents = await readFileContents(ctx.repoPath, middlewareCandidates);
|
|
155
|
+
for (const [mwPath, mwContent] of mwContents) {
|
|
156
|
+
const config = extractNextjsMiddlewareConfig(mwContent);
|
|
157
|
+
if (!config)
|
|
158
|
+
continue;
|
|
159
|
+
const mwLabel = config.wrappedFunctions.length > 0 ? config.wrappedFunctions : [config.exportedName];
|
|
160
|
+
const compiled = config.matchers
|
|
161
|
+
.map(compileMatcher)
|
|
162
|
+
.filter((m) => m !== null);
|
|
163
|
+
let linkedCount = 0;
|
|
164
|
+
for (const [routeURL] of routeRegistry) {
|
|
165
|
+
const matches = compiled.length === 0 ||
|
|
166
|
+
compiled.some((cm) => compiledMatcherMatchesRoute(cm, routeURL));
|
|
167
|
+
if (!matches)
|
|
168
|
+
continue;
|
|
169
|
+
const routeNodeId = generateId('Route', routeURL);
|
|
170
|
+
const existing = ctx.graph.getNode(routeNodeId);
|
|
171
|
+
if (!existing)
|
|
172
|
+
continue;
|
|
173
|
+
const currentMw = existing.properties.middleware ?? [];
|
|
174
|
+
existing.properties.middleware = [
|
|
175
|
+
...mwLabel,
|
|
176
|
+
...currentMw.filter((m) => !mwLabel.includes(m)),
|
|
177
|
+
];
|
|
178
|
+
linkedCount++;
|
|
179
|
+
}
|
|
180
|
+
if (isDev && linkedCount > 0) {
|
|
181
|
+
console.log(`🛡️ Linked ${mwPath} middleware [${mwLabel.join(', ')}] to ${linkedCount} routes`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Scan HTML/template files for form action and AJAX url patterns
|
|
187
|
+
const htmlCandidates = allPaths.filter((p) => p.endsWith('.html') ||
|
|
188
|
+
p.endsWith('.htm') ||
|
|
189
|
+
p.endsWith('.ejs') ||
|
|
190
|
+
p.endsWith('.hbs') ||
|
|
191
|
+
p.endsWith('.blade.php'));
|
|
192
|
+
if (htmlCandidates.length > 0 && routeRegistry.size > 0) {
|
|
193
|
+
const htmlContents = await readFileContents(ctx.repoPath, htmlCandidates);
|
|
194
|
+
const htmlPatterns = [/action=["']([^"']+)["']/g, /url:\s*["']([^"']+)["']/g];
|
|
195
|
+
for (const [filePath, content] of htmlContents) {
|
|
196
|
+
for (const pattern of htmlPatterns) {
|
|
197
|
+
pattern.lastIndex = 0;
|
|
198
|
+
let match;
|
|
199
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
200
|
+
const normalized = normalizeFetchURL(match[1]);
|
|
201
|
+
if (normalized) {
|
|
202
|
+
allFetchCalls.push({ filePath, fetchURL: normalized, lineNumber: 0 });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ── Extract Expo Router navigation patterns ──
|
|
209
|
+
if (expoAppPaths.size > 0 && routeRegistry.size > 0) {
|
|
210
|
+
const unreadExpoPaths = [...expoAppPaths].filter((p) => !handlerContents?.has(p));
|
|
211
|
+
const extraContents = unreadExpoPaths.length > 0
|
|
212
|
+
? await readFileContents(ctx.repoPath, unreadExpoPaths)
|
|
213
|
+
: new Map();
|
|
214
|
+
const allExpoContents = new Map([...(handlerContents ?? new Map()), ...extraContents]);
|
|
215
|
+
for (const [filePath, content] of allExpoContents) {
|
|
216
|
+
if (!expoAppPaths.has(filePath))
|
|
217
|
+
continue;
|
|
218
|
+
for (const pattern of EXPO_NAV_PATTERNS) {
|
|
219
|
+
pattern.lastIndex = 0;
|
|
220
|
+
let match;
|
|
221
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
222
|
+
const url = match[2] ?? match[1];
|
|
223
|
+
if (url && url.startsWith('/')) {
|
|
224
|
+
allFetchCalls.push({ filePath, fetchURL: url, lineNumber: 0 });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (routeRegistry.size > 0 && allFetchCalls.length > 0) {
|
|
231
|
+
const routeURLToFile = new Map();
|
|
232
|
+
for (const [url, entry] of routeRegistry)
|
|
233
|
+
routeURLToFile.set(url, entry.filePath);
|
|
234
|
+
const consumerPaths = [...new Set(allFetchCalls.map((c) => c.filePath))];
|
|
235
|
+
const consumerContents = await readFileContents(ctx.repoPath, consumerPaths);
|
|
236
|
+
processNextjsFetchRoutes(ctx.graph, allFetchCalls, routeURLToFile, consumerContents);
|
|
237
|
+
if (isDev) {
|
|
238
|
+
console.log(`🔗 Processed ${allFetchCalls.length} fetch() calls against ${routeRegistry.size} routes`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { routeRegistry };
|
|
242
|
+
},
|
|
243
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Phase Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes pipeline phases in dependency order using Kahn's topological sort.
|
|
5
|
+
* Each phase receives typed outputs from its upstream dependencies.
|
|
6
|
+
*
|
|
7
|
+
* The runner is intentionally simple:
|
|
8
|
+
* - No dynamic phase loading
|
|
9
|
+
* - No plugin system
|
|
10
|
+
* - Static phase graph, compile-time type safety
|
|
11
|
+
* - Sequential execution (parallel support is architecturally possible
|
|
12
|
+
* but most phases have linear dependencies)
|
|
13
|
+
*/
|
|
14
|
+
import type { PipelinePhase, PipelineContext, PhaseResult } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Execute a set of pipeline phases in dependency order.
|
|
17
|
+
*
|
|
18
|
+
* @param phases All phases to execute (order doesn't matter — sorted internally)
|
|
19
|
+
* @param ctx Shared pipeline context
|
|
20
|
+
* @returns Map of phase name → PhaseResult (all completed phases)
|
|
21
|
+
*/
|
|
22
|
+
export declare function runPipeline(phases: readonly PipelinePhase[], ctx: PipelineContext): Promise<ReadonlyMap<string, PhaseResult<unknown>>>;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Phase Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes pipeline phases in dependency order using Kahn's topological sort.
|
|
5
|
+
* Each phase receives typed outputs from its upstream dependencies.
|
|
6
|
+
*
|
|
7
|
+
* The runner is intentionally simple:
|
|
8
|
+
* - No dynamic phase loading
|
|
9
|
+
* - No plugin system
|
|
10
|
+
* - Static phase graph, compile-time type safety
|
|
11
|
+
* - Sequential execution (parallel support is architecturally possible
|
|
12
|
+
* but most phases have linear dependencies)
|
|
13
|
+
*/
|
|
14
|
+
import { isDev } from '../utils/env.js';
|
|
15
|
+
/**
|
|
16
|
+
* Validate that the phases form a valid dependency graph (no cycles, all deps present).
|
|
17
|
+
* Returns phases in topological execution order.
|
|
18
|
+
*/
|
|
19
|
+
function topologicalSort(phases) {
|
|
20
|
+
const phaseMap = new Map();
|
|
21
|
+
for (const phase of phases) {
|
|
22
|
+
if (phaseMap.has(phase.name)) {
|
|
23
|
+
throw new Error(`Duplicate phase name: '${phase.name}'`);
|
|
24
|
+
}
|
|
25
|
+
phaseMap.set(phase.name, phase);
|
|
26
|
+
}
|
|
27
|
+
// Validate all deps exist
|
|
28
|
+
for (const phase of phases) {
|
|
29
|
+
for (const dep of phase.deps) {
|
|
30
|
+
if (!phaseMap.has(dep)) {
|
|
31
|
+
throw new Error(`Phase '${phase.name}' depends on '${dep}', which is not registered`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Kahn's algorithm
|
|
36
|
+
const inDegree = new Map();
|
|
37
|
+
const reverseDeps = new Map();
|
|
38
|
+
for (const phase of phases) {
|
|
39
|
+
inDegree.set(phase.name, phase.deps.length);
|
|
40
|
+
for (const dep of phase.deps) {
|
|
41
|
+
let rev = reverseDeps.get(dep);
|
|
42
|
+
if (!rev) {
|
|
43
|
+
rev = [];
|
|
44
|
+
reverseDeps.set(dep, rev);
|
|
45
|
+
}
|
|
46
|
+
rev.push(phase.name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const sorted = [];
|
|
50
|
+
const queue = [...inDegree.entries()].filter(([, d]) => d === 0).map(([name]) => name);
|
|
51
|
+
while (queue.length > 0) {
|
|
52
|
+
const name = queue.shift();
|
|
53
|
+
sorted.push(phaseMap.get(name));
|
|
54
|
+
for (const dependent of reverseDeps.get(name) ?? []) {
|
|
55
|
+
const newDeg = (inDegree.get(dependent) ?? 1) - 1;
|
|
56
|
+
inDegree.set(dependent, newDeg);
|
|
57
|
+
if (newDeg === 0)
|
|
58
|
+
queue.push(dependent);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (sorted.length !== phases.length) {
|
|
62
|
+
const remaining = new Set([...inDegree.entries()].filter(([, d]) => d > 0).map(([name]) => name));
|
|
63
|
+
const cyclePath = findCyclePath(remaining, phaseMap);
|
|
64
|
+
const dependentsBlocked = remaining.size - new Set(cyclePath).size;
|
|
65
|
+
let message = `Cycle detected in pipeline phases: ${cyclePath.join(' -> ')}`;
|
|
66
|
+
if (dependentsBlocked > 0) {
|
|
67
|
+
message += ` (and ${dependentsBlocked} transitive dependent${dependentsBlocked === 1 ? '' : 's'} blocked)`;
|
|
68
|
+
}
|
|
69
|
+
throw new Error(message);
|
|
70
|
+
}
|
|
71
|
+
return sorted;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Find a concrete cycle path among the phases that Kahn's algorithm could not drain.
|
|
75
|
+
*
|
|
76
|
+
* Kahn's leftovers include both true cycle members AND phases transitively dependent
|
|
77
|
+
* on them. To produce an actionable error message, we DFS over the leftovers (using
|
|
78
|
+
* each leftover's `deps` as edges) until we hit a back-edge — that closes the cycle.
|
|
79
|
+
* The returned list is the cycle in order with the entry node repeated at the end:
|
|
80
|
+
* `[A, B, C, A]` for `A -> B -> C -> A`.
|
|
81
|
+
*
|
|
82
|
+
* Falls back to the raw remaining set (sorted) if no back-edge is found, which
|
|
83
|
+
* should be unreachable but keeps the error informative.
|
|
84
|
+
*/
|
|
85
|
+
function findCyclePath(remaining, phaseMap) {
|
|
86
|
+
for (const start of remaining) {
|
|
87
|
+
const stack = [];
|
|
88
|
+
const onStack = new Set();
|
|
89
|
+
const visited = new Set();
|
|
90
|
+
const dfs = (name) => {
|
|
91
|
+
stack.push(name);
|
|
92
|
+
onStack.add(name);
|
|
93
|
+
visited.add(name);
|
|
94
|
+
const phase = phaseMap.get(name);
|
|
95
|
+
if (phase) {
|
|
96
|
+
for (const dep of phase.deps) {
|
|
97
|
+
if (!remaining.has(dep))
|
|
98
|
+
continue; // dep already drained — not part of cycle
|
|
99
|
+
if (onStack.has(dep)) {
|
|
100
|
+
// Back-edge — slice from the first occurrence of `dep` and close the loop.
|
|
101
|
+
const cycleStart = stack.indexOf(dep);
|
|
102
|
+
return [...stack.slice(cycleStart), dep];
|
|
103
|
+
}
|
|
104
|
+
if (!visited.has(dep)) {
|
|
105
|
+
const found = dfs(dep);
|
|
106
|
+
if (found)
|
|
107
|
+
return found;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
stack.pop();
|
|
112
|
+
onStack.delete(name);
|
|
113
|
+
return null;
|
|
114
|
+
};
|
|
115
|
+
const cycle = dfs(start);
|
|
116
|
+
if (cycle)
|
|
117
|
+
return cycle;
|
|
118
|
+
}
|
|
119
|
+
// Unreachable in practice (Kahn proved a cycle exists), but stay defensive.
|
|
120
|
+
return [...remaining].sort();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Execute a set of pipeline phases in dependency order.
|
|
124
|
+
*
|
|
125
|
+
* @param phases All phases to execute (order doesn't matter — sorted internally)
|
|
126
|
+
* @param ctx Shared pipeline context
|
|
127
|
+
* @returns Map of phase name → PhaseResult (all completed phases)
|
|
128
|
+
*/
|
|
129
|
+
export async function runPipeline(phases, ctx) {
|
|
130
|
+
let sorted;
|
|
131
|
+
try {
|
|
132
|
+
sorted = topologicalSort(phases);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
// Emit a terminal 'error' progress event for graph-validation failures
|
|
136
|
+
// (cycle detected, duplicate phase, missing dep) so CLI/MCP consumers see
|
|
137
|
+
// the failure before the rejection propagates. Symmetric with the
|
|
138
|
+
// per-phase error path below. Best-effort: a throwing handler must not
|
|
139
|
+
// mask the underlying validation error.
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
try {
|
|
142
|
+
ctx.onProgress({
|
|
143
|
+
phase: 'error',
|
|
144
|
+
percent: 100,
|
|
145
|
+
message: 'Pipeline graph validation failed',
|
|
146
|
+
detail: message,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Swallow handler errors — preserving the original cause is more important.
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
const results = new Map();
|
|
155
|
+
for (const phase of sorted) {
|
|
156
|
+
const start = Date.now();
|
|
157
|
+
if (isDev) {
|
|
158
|
+
console.log(`▶ Phase: ${phase.name}`);
|
|
159
|
+
}
|
|
160
|
+
// Only expose declared dependencies — prevents hidden coupling to undeclared phases.
|
|
161
|
+
const declaredDeps = new Map();
|
|
162
|
+
for (const depName of phase.deps) {
|
|
163
|
+
const depResult = results.get(depName);
|
|
164
|
+
if (depResult)
|
|
165
|
+
declaredDeps.set(depName, depResult);
|
|
166
|
+
}
|
|
167
|
+
let output;
|
|
168
|
+
try {
|
|
169
|
+
output = await phase.execute(ctx, declaredDeps);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
173
|
+
const wrapped = new Error(`Phase '${phase.name}' failed: ${originalMessage}`, {
|
|
174
|
+
cause: err,
|
|
175
|
+
});
|
|
176
|
+
// Emit a terminal 'error' progress event so CLI/MCP consumers see the failure
|
|
177
|
+
// before the rejection propagates. Best-effort: a throwing handler must not
|
|
178
|
+
// mask the underlying phase error.
|
|
179
|
+
try {
|
|
180
|
+
ctx.onProgress({
|
|
181
|
+
phase: 'error',
|
|
182
|
+
percent: 100,
|
|
183
|
+
message: `Phase '${phase.name}' failed`,
|
|
184
|
+
detail: originalMessage,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Swallow handler errors — preserving the original cause is more important.
|
|
189
|
+
}
|
|
190
|
+
throw wrapped;
|
|
191
|
+
}
|
|
192
|
+
const durationMs = Date.now() - start;
|
|
193
|
+
results.set(phase.name, {
|
|
194
|
+
phaseName: phase.name,
|
|
195
|
+
output,
|
|
196
|
+
durationMs,
|
|
197
|
+
});
|
|
198
|
+
if (isDev) {
|
|
199
|
+
console.log(`✓ Phase: ${phase.name} (${durationMs}ms)`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: scan
|
|
3
|
+
*
|
|
4
|
+
* Walks the repository filesystem and collects file paths + sizes.
|
|
5
|
+
* Does NOT read file contents — that happens in downstream phases.
|
|
6
|
+
*
|
|
7
|
+
* @deps (none — this is the pipeline root)
|
|
8
|
+
* @reads repoPath (filesystem)
|
|
9
|
+
* @writes graph (nothing yet — just returns scanned paths)
|
|
10
|
+
* @output ScannedFile[], allPaths[], totalFiles
|
|
11
|
+
*/
|
|
12
|
+
import type { PipelinePhase } from './types.js';
|
|
13
|
+
export interface ScanOutput {
|
|
14
|
+
scannedFiles: {
|
|
15
|
+
path: string;
|
|
16
|
+
size: number;
|
|
17
|
+
}[];
|
|
18
|
+
allPaths: string[];
|
|
19
|
+
totalFiles: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const scanPhase: PipelinePhase<ScanOutput>;
|