@psnext/lscg 0.1.4 → 0.1.6
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 +117 -15
- package/dist/bin/lscg.js +0 -0
- package/dist/src/cli-progress.d.ts +7 -0
- package/dist/src/cli-progress.js +59 -0
- package/dist/src/cli.js +62 -9
- package/dist/src/explore/sigma-provider.d.ts +27 -0
- package/dist/src/explore/sigma-provider.js +87 -0
- package/dist/src/explore/sigma-render.d.ts +18 -0
- package/dist/src/explore/sigma-render.js +67 -0
- package/dist/src/graph/attribution.d.ts +2 -2
- package/dist/src/graph/attribution.js +36 -13
- package/dist/src/graph/explore.d.ts +20 -0
- package/dist/src/graph/explore.js +200 -0
- package/dist/src/graph/repository.d.ts +36 -4
- package/dist/src/graph/repository.js +443 -159
- package/dist/src/graph/repositoryScanWorker.d.ts +21 -0
- package/dist/src/graph/repositoryScanWorker.js +45 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +5 -0
- package/dist/src/mcp/server.js +21 -3
- package/dist/src/parser/treeSitter.js +20 -1
- package/dist/src/scanner/artifactInventory.d.ts +35 -0
- package/dist/src/scanner/artifactInventory.js +139 -0
- package/dist/src/scanner/attributionPlugin.d.ts +5 -0
- package/dist/src/scanner/attributionPlugin.js +16 -0
- package/dist/src/scanner/discover.js +89 -16
- package/dist/src/scanner/fingerprint.js +5 -0
- package/dist/src/scanner/javaDependencyPlugin.d.ts +5 -0
- package/dist/src/scanner/javaDependencyPlugin.js +107 -0
- package/dist/src/scanner/javaPlugin.d.ts +5 -0
- package/dist/src/scanner/javaPlugin.js +199 -0
- package/dist/src/scanner/javaScanWorker.d.ts +2 -0
- package/dist/src/scanner/javaScanWorker.js +8 -0
- package/dist/src/scanner/packageParseWorker.d.ts +17 -0
- package/dist/src/scanner/packageParseWorker.js +30 -0
- package/dist/src/scanner/packagePlugin.js +83 -24
- package/dist/src/scanner/parallelScan.d.ts +2 -0
- package/dist/src/scanner/parallelScan.js +32 -0
- package/dist/src/scanner/plugins.d.ts +38 -4
- package/dist/src/scanner/plugins.js +58 -5
- package/dist/src/scanner/pythonPlugin.d.ts +5 -0
- package/dist/src/scanner/pythonPlugin.js +198 -0
- package/dist/src/scanner/pythonScanWorker.d.ts +2 -0
- package/dist/src/scanner/pythonScanWorker.js +8 -0
- package/dist/src/storage/connection.js +85 -0
- package/dist/src/storage/database.d.ts +1 -0
- package/dist/src/storage/database.js +1 -0
- package/dist/src/storage/explore-queries.d.ts +52 -0
- package/dist/src/storage/explore-queries.js +184 -0
- package/dist/src/storage/graph-writes.d.ts +22 -3
- package/dist/src/storage/graph-writes.js +167 -20
- package/dist/src/storage/manifest-inventory.d.ts +23 -0
- package/dist/src/storage/manifest-inventory.js +82 -0
- package/dist/src/storage/plugin-graph.js +3 -3
- package/dist/src/storage/queries.d.ts +10 -3
- package/dist/src/storage/queries.js +99 -24
- package/dist/src/storage/schema.d.ts +2 -2
- package/dist/src/storage/schema.js +48 -1
- package/dist/src/types.d.ts +110 -6
- package/dist/src/watch.d.ts +20 -2
- package/dist/src/watch.js +211 -46
- package/package.json +9 -3
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const JAVA_DEPENDENCY_PLUGIN_NAME = 'java-dependency-plugin';
|
|
2
|
+
const MAX_DECLARATIONS = 10_000;
|
|
3
|
+
export const JavaDependencyPlugin = {
|
|
4
|
+
name: JAVA_DEPENDENCY_PLUGIN_NAME,
|
|
5
|
+
apiVersion: 1,
|
|
6
|
+
capabilities: ['repository-analyzer'],
|
|
7
|
+
scan: (context) => scanJavaDependencies(context),
|
|
8
|
+
scanIncremental: (context, _delta, previous) => scanJavaDependenciesIncremental(context, previous)
|
|
9
|
+
};
|
|
10
|
+
export function scanJavaDependencies(context) {
|
|
11
|
+
const nodes = [];
|
|
12
|
+
const diagnostics = [];
|
|
13
|
+
for (const manifest of context.manifests) {
|
|
14
|
+
if (manifest.status !== 'observed' || manifest.content === null) {
|
|
15
|
+
if (manifest.status !== 'confirmed_deleted')
|
|
16
|
+
diagnostics.push(`${manifest.path}: manifest read unavailable (${manifest.status})`);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const parsed = manifest.kind === 'maven-pom' ? parseMaven(manifest) : parseGradle(manifest);
|
|
20
|
+
nodes.push(...parsed.nodes);
|
|
21
|
+
diagnostics.push(...parsed.diagnostics);
|
|
22
|
+
}
|
|
23
|
+
return { nodes, edges: [], diagnostics };
|
|
24
|
+
}
|
|
25
|
+
function scanJavaDependenciesIncremental(context, previous) {
|
|
26
|
+
const result = scanJavaDependencies(context);
|
|
27
|
+
// Repository facts are cheap and bounded, so each incremental run replaces
|
|
28
|
+
// the complete analyzer contribution. This also handles manifest-only edits
|
|
29
|
+
// and the final manifest deletion after reopening the database.
|
|
30
|
+
if (!previous || result.diagnostics?.length) {
|
|
31
|
+
// A bounded/read/parse failure is not a confirmed deletion. Keep the
|
|
32
|
+
// previous repository contribution while exposing diagnostics.
|
|
33
|
+
return { ...result, retractions: { nodes: [], edges: [] } };
|
|
34
|
+
}
|
|
35
|
+
return { ...result, retractions: { nodes: previous.nodes.map((node) => String(node.metadata.factKey ?? '')).filter(Boolean), edges: previous.edges.map((edge) => String(edge.metadata.factKey ?? '')).filter(Boolean) } };
|
|
36
|
+
}
|
|
37
|
+
function parseMaven(manifest) {
|
|
38
|
+
const source = manifest.content ?? '';
|
|
39
|
+
const diagnostics = [];
|
|
40
|
+
if (/<!DOCTYPE|<!ENTITY/iu.test(source))
|
|
41
|
+
return { nodes: [], diagnostics: ['pom.xml rejected DTD/entity expansion'] };
|
|
42
|
+
if (!/<project\b/iu.test(source) || !/<\/project\s*>/iu.test(source))
|
|
43
|
+
return { nodes: [], diagnostics: [`${manifest.path}: malformed or unsupported Maven XML`] };
|
|
44
|
+
const properties = new Map();
|
|
45
|
+
const propertyBlock = /<properties\b[^>]*>([\s\S]*?)<\/properties>/iu.exec(source)?.[1] ?? '';
|
|
46
|
+
for (const match of propertyBlock.matchAll(/<([\w.-]+)\s*>([^<]{0,1000})<\/\1\s*>/gu))
|
|
47
|
+
properties.set(match[1], match[2].trim());
|
|
48
|
+
const nodes = [];
|
|
49
|
+
let occurrence = 0;
|
|
50
|
+
for (const match of source.matchAll(/<dependency\b[^>]*>([\s\S]*?)<\/dependency\s*>/giu)) {
|
|
51
|
+
if (occurrence >= MAX_DECLARATIONS) {
|
|
52
|
+
diagnostics.push(`${manifest.path}: dependency declaration limit exceeded`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const block = match[1];
|
|
56
|
+
const group = xmlTag(block, 'groupId');
|
|
57
|
+
const artifact = xmlTag(block, 'artifactId');
|
|
58
|
+
const version = resolveProperty(xmlTag(block, 'version'), properties);
|
|
59
|
+
const scope = xmlTag(block, 'scope') || 'compile';
|
|
60
|
+
if (!group || !artifact || !version) {
|
|
61
|
+
diagnostics.push(`${manifest.path}: unsupported or unresolved Maven dependency at declaration ${occurrence}`);
|
|
62
|
+
occurrence += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const coordinate = `${group}:${artifact}:${version}`;
|
|
66
|
+
nodes.push(dependencyNode(manifest, `${manifest.path}\u0000${manifest.moduleIdentity}\u0000${coordinate}\u0000${scope}\u0000${occurrence}`, coordinate, group, artifact, version, scope, occurrence));
|
|
67
|
+
occurrence += 1;
|
|
68
|
+
}
|
|
69
|
+
return { nodes, diagnostics };
|
|
70
|
+
}
|
|
71
|
+
function parseGradle(manifest) {
|
|
72
|
+
const source = manifest.content ?? '';
|
|
73
|
+
const diagnostics = [];
|
|
74
|
+
const nodes = [];
|
|
75
|
+
let occurrence = 0;
|
|
76
|
+
// Literal Groovy/Kotlin dependency calls only. No Gradle expressions are evaluated.
|
|
77
|
+
const pattern = /\b(api|implementation|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|annotationProcessor|kapt|classpath)\b\s*(?:\(\s*)?['"]([^'"\n]+)['"]\s*\)?/gu;
|
|
78
|
+
for (const match of source.matchAll(pattern)) {
|
|
79
|
+
if (occurrence >= MAX_DECLARATIONS) {
|
|
80
|
+
diagnostics.push(`${manifest.path}: dependency declaration limit exceeded`);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const raw = match[2].trim();
|
|
84
|
+
const parts = raw.split(':');
|
|
85
|
+
const configuration = match[1].trim();
|
|
86
|
+
if (parts.length < 3 || parts.slice(0, 3).some((part) => !/^[\w.${}-]+$/u.test(part))) {
|
|
87
|
+
diagnostics.push(`${manifest.path}: unsupported Gradle dependency declaration ${raw.slice(0, 120)}`);
|
|
88
|
+
occurrence += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const [group, artifact, version] = parts;
|
|
92
|
+
const coordinate = `${group}:${artifact}:${version}`;
|
|
93
|
+
nodes.push(dependencyNode(manifest, `${manifest.path}\u0000${manifest.moduleIdentity}\u0000${coordinate}\u0000${configuration}\u0000${occurrence}`, coordinate, group, artifact, version, configuration, occurrence));
|
|
94
|
+
occurrence += 1;
|
|
95
|
+
}
|
|
96
|
+
if (/\b(?:project|platform|enforcedPlatform|libs\.|version\.catalog|\$\{)/u.test(source))
|
|
97
|
+
diagnostics.push(`${manifest.path}: dynamic or unsupported Gradle dependency expressions were ignored`);
|
|
98
|
+
return { nodes, diagnostics };
|
|
99
|
+
}
|
|
100
|
+
function dependencyNode(manifest, factKey, coordinate, group, artifact, version, scope, occurrence) {
|
|
101
|
+
return { factKey: `java-dependency:${factKey}`, kind: 'package', type: 'java-dependency', name: coordinate, metadata: { dependencyType: 'java-dependency', coordinate, group, artifact, version, scope, configuration: scope, declarationOccurrence: occurrence, manifestPath: manifest.path, manifestKind: manifest.kind, moduleIdentity: manifest.moduleIdentity, sourceHash: manifest.sourceHash } };
|
|
102
|
+
}
|
|
103
|
+
function xmlTag(source, tag) { return new RegExp(`<${tag}\\s*>([^<]{0,1000})<\\/${tag}\\s*>`, 'iu').exec(source)?.[1]?.trim(); }
|
|
104
|
+
function resolveProperty(value, properties) { if (!value)
|
|
105
|
+
return undefined; const match = /^\$\{([^}]+)\}$/u.exec(value); return match ? properties.get(match[1]) : value; }
|
|
106
|
+
function configurationAt(source, offset) { const line = source.slice(Math.max(0, source.lastIndexOf('\n', offset - 1) + 1), offset); return /\b(\w+)\s*$/u.exec(line)?.[1] ?? 'implementation'; }
|
|
107
|
+
//# sourceMappingURL=javaDependencyPlugin.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PluginScanResult, RepositoryScanFile, ScannerPlugin } from './plugins.js';
|
|
2
|
+
export declare const JAVA_SCANNER_PLUGIN_NAME = "java-scanner-plugin";
|
|
3
|
+
export declare function scanJavaFiles(files: readonly RepositoryScanFile[]): PluginScanResult;
|
|
4
|
+
export declare const JavaScannerPlugin: ScannerPlugin;
|
|
5
|
+
//# sourceMappingURL=javaPlugin.d.ts.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { parseSource } from '../parser/treeSitter.js';
|
|
2
|
+
import { runBatchedWorkers } from './parallelScan.js';
|
|
3
|
+
export const JAVA_SCANNER_PLUGIN_NAME = 'java-scanner-plugin';
|
|
4
|
+
const JAVA_EXTENSIONS = new Set(['.java']);
|
|
5
|
+
const TYPE_DECLARATIONS = new Set(['class_declaration', 'interface_declaration', 'enum_declaration', 'record_declaration', 'annotation_type_declaration', 'annotation_type_declaration']);
|
|
6
|
+
const MEMBER_DECLARATIONS = new Set(['method_declaration', 'constructor_declaration', 'field_declaration']);
|
|
7
|
+
const IMPORT_DECLARATIONS = new Set(['import_declaration']);
|
|
8
|
+
const CALL_DECLARATIONS = new Set(['method_invocation']);
|
|
9
|
+
const MAX_SNIPPET_BYTES = 240;
|
|
10
|
+
export function scanJavaFiles(files) {
|
|
11
|
+
const nodes = [];
|
|
12
|
+
const edges = [];
|
|
13
|
+
const failedPaths = [];
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
if (!isJava(file.path))
|
|
17
|
+
continue;
|
|
18
|
+
const parsed = parseSource(file.absolutePath, file.source);
|
|
19
|
+
if (!parsed || parsed.language !== 'java') {
|
|
20
|
+
failedPaths.push(file.path);
|
|
21
|
+
diagnostics.push(`${file.path}: Java parser failed; retaining prior facts`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (containsError(parsed.tree.rootNode)) {
|
|
25
|
+
failedPaths.push(file.path);
|
|
26
|
+
diagnostics.push(`${file.path}: Java source contains a parse error; retaining prior facts`);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const sourceBuffer = Buffer.from(file.source, 'utf8');
|
|
30
|
+
const fileKey = factKey(file.path, 'file', 0, sourceBuffer.length);
|
|
31
|
+
nodes.push(makeNode(file, fileKey, parsed, 'file', 'file', file.path, parsed.tree.rootNode, sourceBuffer, { language: 'java' }));
|
|
32
|
+
walk(parsed.tree.rootNode, [], [], fileKey, file, parsed, sourceBuffer, nodes, edges);
|
|
33
|
+
}
|
|
34
|
+
return { nodes, edges, failedPaths, diagnostics };
|
|
35
|
+
}
|
|
36
|
+
async function scanJavaFilesInBatches(files) {
|
|
37
|
+
const javaFiles = files.filter((file) => isJava(file.path));
|
|
38
|
+
const results = await runBatchedWorkers(javaFiles, new URL('./javaScanWorker.js', import.meta.url), (batch) => batch);
|
|
39
|
+
return {
|
|
40
|
+
nodes: results.flatMap((result) => result.nodes),
|
|
41
|
+
edges: results.flatMap((result) => result.edges),
|
|
42
|
+
failedPaths: results.flatMap((result) => result.failedPaths ?? []),
|
|
43
|
+
diagnostics: results.flatMap((result) => result.diagnostics ?? [])
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export const JavaScannerPlugin = {
|
|
47
|
+
name: JAVA_SCANNER_PLUGIN_NAME,
|
|
48
|
+
apiVersion: 1,
|
|
49
|
+
capabilities: ['file-scanner'],
|
|
50
|
+
scan: (context) => scanJavaFilesInBatches(context.files),
|
|
51
|
+
scanIncremental: (context, delta, previous) => scanJavaIncremental(context, delta, previous)
|
|
52
|
+
};
|
|
53
|
+
async function scanJavaIncremental(context, delta, previous) {
|
|
54
|
+
if (!previous)
|
|
55
|
+
return { ...(await scanJavaFilesInBatches(context.files)), retractions: { nodes: [], edges: [] } };
|
|
56
|
+
const changed = delta.full
|
|
57
|
+
? new Set(previous.nodes.flatMap((node) => typeof node.metadata.filePath === 'string' ? [node.metadata.filePath] : []).concat(context.files.filter((file) => isJava(file.path)).map((file) => file.path)))
|
|
58
|
+
: new Set([...delta.changedPaths, ...delta.deletedPaths].filter(isJava));
|
|
59
|
+
if (changed.size === 0)
|
|
60
|
+
return { nodes: [], edges: [], retractions: { nodes: [], edges: [] } };
|
|
61
|
+
const result = await scanJavaFilesInBatches(context.files.filter((file) => changed.has(file.path)));
|
|
62
|
+
const failed = new Set(result.failedPaths ?? []);
|
|
63
|
+
const retractions = { nodes: [], edges: [] };
|
|
64
|
+
for (const node of previous.nodes) {
|
|
65
|
+
const filePath = typeof node.metadata.filePath === 'string' ? node.metadata.filePath : undefined;
|
|
66
|
+
if (filePath && changed.has(filePath) && !failed.has(filePath))
|
|
67
|
+
retractions.nodes.push(String(node.metadata.factKey ?? ''));
|
|
68
|
+
}
|
|
69
|
+
for (const edge of previous.edges) {
|
|
70
|
+
const filePath = typeof edge.metadata.filePath === 'string' ? edge.metadata.filePath : undefined;
|
|
71
|
+
if (filePath && changed.has(filePath) && !failed.has(filePath))
|
|
72
|
+
retractions.edges.push(String(edge.metadata.factKey ?? ''));
|
|
73
|
+
}
|
|
74
|
+
return { ...result, retractions };
|
|
75
|
+
}
|
|
76
|
+
function walk(node, symbols, graph, fileKey, file, parsed, sourceBuffer, nodes, edges) {
|
|
77
|
+
// Java permits several variable declarators in one field declaration (for
|
|
78
|
+
// example, `int first, second`). Each declarator is a distinct graph field;
|
|
79
|
+
// the parent field_declaration is only a syntax container and must not cause
|
|
80
|
+
// the later declarators to be dropped.
|
|
81
|
+
if (node.type === 'field_declaration') {
|
|
82
|
+
const declarators = node.namedChildren.filter((child) => child.type === 'variable_declarator');
|
|
83
|
+
if (declarators.length > 0) {
|
|
84
|
+
for (const declarator of declarators) {
|
|
85
|
+
walkFieldDeclarator(declarator, node, symbols, graph, fileKey, file, parsed, sourceBuffer, nodes, edges);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const kind = kindFor(node);
|
|
91
|
+
let currentKey;
|
|
92
|
+
let nextSymbols = symbols;
|
|
93
|
+
let nextGraph = graph;
|
|
94
|
+
if (kind) {
|
|
95
|
+
const name = nameFor(node, sourceBuffer);
|
|
96
|
+
currentKey = factKey(file.path, kind, node.startIndex, node.endIndex);
|
|
97
|
+
const metadata = metadataFor(node, sourceBuffer, name);
|
|
98
|
+
nodes.push(makeNode(file, currentKey, parsed, kind, node.type, name, node, sourceBuffer, metadata));
|
|
99
|
+
const parent = graph.at(-1) ?? fileKey;
|
|
100
|
+
edges.push({ factKey: `contains:${file.path}:${node.startIndex}:${node.endIndex}`, sourceFactKey: parent, targetFactKey: currentKey, filePath: file.path, kind: kind === 'symbol' && symbols.length === 0 ? 'defines' : 'contains', metadata: { discoveredFrom: node.type } });
|
|
101
|
+
if (kind === 'import')
|
|
102
|
+
edges.push({ factKey: `imports:${file.path}:${node.startIndex}:${node.endIndex}`, sourceFactKey: fileKey, targetFactKey: currentKey, filePath: file.path, kind: 'imports', confidence: 0.9, metadata: { module: metadata.module, static: metadata.static, wildcard: metadata.wildcard } });
|
|
103
|
+
if (kind === 'call')
|
|
104
|
+
edges.push({ factKey: `calls:${file.path}:${node.startIndex}:${node.endIndex}`, sourceFactKey: symbols.at(-1) ?? fileKey, targetFactKey: currentKey, filePath: file.path, kind: 'calls', confidence: 0.65, metadata: { callee: name, syntaxOnly: true } });
|
|
105
|
+
if (kind === 'symbol') {
|
|
106
|
+
if (isPublic(node, sourceBuffer) && (symbols.length === 0 || containingTypesAccessible(node, sourceBuffer))) {
|
|
107
|
+
const exportKey = factKey(file.path, 'export', node.startIndex, node.endIndex);
|
|
108
|
+
nodes.push(makeNode(file, exportKey, parsed, 'export', 'java-public-export', name, node, sourceBuffer, { exportKind: 'java-public', symbol: name }));
|
|
109
|
+
edges.push({ factKey: `exports:${file.path}:${node.startIndex}:${node.endIndex}`, sourceFactKey: fileKey, targetFactKey: exportKey, filePath: file.path, kind: 'exports', metadata: { symbol: name, syntaxOnly: true } });
|
|
110
|
+
}
|
|
111
|
+
nextSymbols = [...symbols, currentKey];
|
|
112
|
+
}
|
|
113
|
+
nextGraph = [...graph, currentKey];
|
|
114
|
+
}
|
|
115
|
+
for (const child of node.namedChildren)
|
|
116
|
+
walk(child, nextSymbols, nextGraph, fileKey, file, parsed, sourceBuffer, nodes, edges);
|
|
117
|
+
}
|
|
118
|
+
function walkFieldDeclarator(declarator, declaration, symbols, graph, fileKey, file, parsed, sourceBuffer, nodes, edges) {
|
|
119
|
+
const currentKey = factKey(file.path, 'symbol', declarator.startIndex, declarator.endIndex);
|
|
120
|
+
const name = nameFor(declarator, sourceBuffer);
|
|
121
|
+
const metadata = metadataFor(declarator, sourceBuffer, name, declaration);
|
|
122
|
+
nodes.push(makeNode(file, currentKey, parsed, 'symbol', 'field_declaration', name, declarator, sourceBuffer, metadata));
|
|
123
|
+
const parent = graph.at(-1) ?? fileKey;
|
|
124
|
+
edges.push({ factKey: `contains:${file.path}:${declarator.startIndex}:${declarator.endIndex}`, sourceFactKey: parent, targetFactKey: currentKey, filePath: file.path, kind: symbols.length === 0 ? 'defines' : 'contains', metadata: { discoveredFrom: 'field_declaration' } });
|
|
125
|
+
if (isPublic(declaration, sourceBuffer) && (symbols.length === 0 || containingTypesAccessible(declaration, sourceBuffer))) {
|
|
126
|
+
const exportKey = factKey(file.path, 'export', declarator.startIndex, declarator.endIndex);
|
|
127
|
+
nodes.push(makeNode(file, exportKey, parsed, 'export', 'java-public-export', name, declarator, sourceBuffer, { exportKind: 'java-public', symbol: name }));
|
|
128
|
+
edges.push({ factKey: `exports:${file.path}:${declarator.startIndex}:${declarator.endIndex}`, sourceFactKey: fileKey, targetFactKey: exportKey, filePath: file.path, kind: 'exports', metadata: { symbol: name, syntaxOnly: true } });
|
|
129
|
+
}
|
|
130
|
+
for (const child of declarator.namedChildren)
|
|
131
|
+
walk(child, [...symbols, currentKey], [...graph, currentKey], fileKey, file, parsed, sourceBuffer, nodes, edges);
|
|
132
|
+
}
|
|
133
|
+
function kindFor(node) {
|
|
134
|
+
if (TYPE_DECLARATIONS.has(node.type) || MEMBER_DECLARATIONS.has(node.type))
|
|
135
|
+
return 'symbol';
|
|
136
|
+
if (IMPORT_DECLARATIONS.has(node.type))
|
|
137
|
+
return 'import';
|
|
138
|
+
if (CALL_DECLARATIONS.has(node.type))
|
|
139
|
+
return 'call';
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
function nameFor(node, source) {
|
|
143
|
+
const name = node.childForFieldName('name');
|
|
144
|
+
if (name)
|
|
145
|
+
return textFor(name, source).trim();
|
|
146
|
+
if (node.type === 'field_declaration') {
|
|
147
|
+
const declarator = node.childForFieldName('declarator') ?? node.namedChildren.find((child) => child.type === 'variable_declarator');
|
|
148
|
+
return declarator?.childForFieldName('name') ? textFor(declarator.childForFieldName('name'), source).trim() : null;
|
|
149
|
+
}
|
|
150
|
+
if (node.type === 'import_declaration')
|
|
151
|
+
return textFor(node, source).replace(/^import\s+(?:static\s+)?/u, '').replace(/;\s*$/u, '').trim();
|
|
152
|
+
if (node.type === 'method_invocation')
|
|
153
|
+
return textFor(name ?? node.childForFieldName('object') ?? node, source).trim().slice(0, 120);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function metadataFor(node, source, name, visibilityNode = node) {
|
|
157
|
+
const snippet = textFor(node, source).trim().slice(0, MAX_SNIPPET_BYTES);
|
|
158
|
+
if (node.type === 'import_declaration') {
|
|
159
|
+
const raw = textFor(node, source).trim().replace(/^import\s+/u, '').replace(/;\s*$/u, '');
|
|
160
|
+
const isStatic = raw.startsWith('static ');
|
|
161
|
+
const module = (isStatic ? raw.slice(7) : raw).replace(/\.\*$/u, '');
|
|
162
|
+
return { module, static: isStatic, wildcard: raw.endsWith('.*'), snippet };
|
|
163
|
+
}
|
|
164
|
+
if (node.type === 'method_invocation')
|
|
165
|
+
return { callee: name, snippet, syntaxOnly: true };
|
|
166
|
+
return { snippet, visibility: visibilityFor(visibilityNode, source) };
|
|
167
|
+
}
|
|
168
|
+
function isPublic(node, source) {
|
|
169
|
+
const modifiers = node.childForFieldName('modifiers') ?? node.namedChildren.find((child) => child.type === 'modifiers');
|
|
170
|
+
return Boolean(modifiers && /\bpublic\b/u.test(textFor(modifiers, source)));
|
|
171
|
+
}
|
|
172
|
+
function containingTypesAccessible(node, source) {
|
|
173
|
+
let parent = node.parent;
|
|
174
|
+
while (parent) {
|
|
175
|
+
if (TYPE_DECLARATIONS.has(parent.type) && !isPublic(parent, source))
|
|
176
|
+
return false;
|
|
177
|
+
parent = parent.parent;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
function visibilityFor(node, source) {
|
|
182
|
+
const modifiers = node.childForFieldName('modifiers') ?? node.namedChildren.find((child) => child.type === 'modifiers');
|
|
183
|
+
const value = modifiers ? textFor(modifiers, source) : '';
|
|
184
|
+
if (/\bpublic\b/u.test(value))
|
|
185
|
+
return 'public';
|
|
186
|
+
if (/\bprotected\b/u.test(value))
|
|
187
|
+
return 'protected';
|
|
188
|
+
if (/\bprivate\b/u.test(value))
|
|
189
|
+
return 'private';
|
|
190
|
+
return 'package';
|
|
191
|
+
}
|
|
192
|
+
function makeNode(file, key, parsed, kind, type, name, treeNode, source, metadata) {
|
|
193
|
+
return { factKey: key, filePath: file.path, kind, type, name, startByte: treeNode.startIndex, endByte: treeNode.endIndex, startPoint: treeNode.startPosition, endPoint: treeNode.endPosition, sourceHash: file.hash, parser: parsed.parser, parserVersion: parsed.parserVersion, metadata: { ...metadata, filePath: file.path, factKey: key, sourceBytes: source.length } };
|
|
194
|
+
}
|
|
195
|
+
function factKey(filePath, kind, startByte, endByte) { return `${kind}:${filePath}:${startByte}:${endByte}`; }
|
|
196
|
+
function textFor(node, source) { return source.subarray(node.startIndex, node.endIndex).toString('utf8'); }
|
|
197
|
+
function containsError(node) { return Boolean(node.hasError) || node.type === 'ERROR' || node.namedChildren.some(containsError); }
|
|
198
|
+
function isJava(filePath) { return JAVA_EXTENSIONS.has(filePath.slice(filePath.lastIndexOf('.')).toLowerCase()); }
|
|
199
|
+
//# sourceMappingURL=javaPlugin.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
import { scanJavaFiles } from './javaPlugin.js';
|
|
3
|
+
if (!parentPort)
|
|
4
|
+
throw new Error('Java scan worker requires a parent port');
|
|
5
|
+
parentPort.on('message', (files) => {
|
|
6
|
+
parentPort.postMessage(scanJavaFiles(files));
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=javaScanWorker.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface ParseFileTask {
|
|
2
|
+
path: string;
|
|
3
|
+
absolutePath: string;
|
|
4
|
+
source: string;
|
|
5
|
+
hash: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PackageParseTask {
|
|
8
|
+
repositoryId: string;
|
|
9
|
+
files: ParseFileTask[];
|
|
10
|
+
}
|
|
11
|
+
export interface PackageImport {
|
|
12
|
+
path: string;
|
|
13
|
+
id: string;
|
|
14
|
+
module: string;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=packageParseWorker.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
import { extractGraph, hashParts } from '../graph/extract.js';
|
|
3
|
+
import { parseSource } from '../parser/treeSitter.js';
|
|
4
|
+
if (!parentPort)
|
|
5
|
+
throw new Error('package parse worker requires a parent port');
|
|
6
|
+
parentPort.on('message', ({ repositoryId, files }) => {
|
|
7
|
+
const imports = [];
|
|
8
|
+
for (const file of files) {
|
|
9
|
+
const parsed = parseSource(file.absolutePath, file.source);
|
|
10
|
+
if (!parsed)
|
|
11
|
+
continue;
|
|
12
|
+
const extracted = extractGraph({
|
|
13
|
+
repositoryId,
|
|
14
|
+
fileId: hashParts([repositoryId, file.path]),
|
|
15
|
+
relativePath: file.path,
|
|
16
|
+
source: file.source,
|
|
17
|
+
sourceHash: file.hash,
|
|
18
|
+
parseResult: parsed
|
|
19
|
+
});
|
|
20
|
+
for (const node of extracted.nodes) {
|
|
21
|
+
if (node.kind !== 'import')
|
|
22
|
+
continue;
|
|
23
|
+
const module = typeof node.metadata.module === 'string' ? node.metadata.module : node.name;
|
|
24
|
+
if (module)
|
|
25
|
+
imports.push({ path: file.path, id: node.id, module });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
parentPort.postMessage(imports);
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=packageParseWorker.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { builtinModules } from 'node:module';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
3
|
+
import { Worker } from 'node:worker_threads';
|
|
4
|
+
import { hashParts } from '../graph/extract.js';
|
|
4
5
|
import { BUILTIN_GRAPH_PLUGIN } from './plugins.js';
|
|
5
6
|
// This plugin adds package-level relationships on top of the built-in source
|
|
6
7
|
// graph. Built-in extraction already creates file/import/symbol nodes; this
|
|
@@ -41,11 +42,46 @@ function packageRoot(specifier) {
|
|
|
41
42
|
function isManifest(value) {
|
|
42
43
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
43
44
|
}
|
|
45
|
+
const MAX_PARSE_WORKERS = 8;
|
|
46
|
+
async function parseImportsInBatches(repositoryId, files) {
|
|
47
|
+
const sourceFiles = files.filter((file) => !file.path.toLowerCase().endsWith('.java'));
|
|
48
|
+
if (sourceFiles.length === 0)
|
|
49
|
+
return [];
|
|
50
|
+
const workerCount = Math.min(Math.max(1, availableParallelism() - 1), MAX_PARSE_WORKERS, sourceFiles.length);
|
|
51
|
+
const batchSize = Math.ceil(sourceFiles.length / workerCount);
|
|
52
|
+
const batches = Array.from({ length: workerCount }, (_, index) => sourceFiles.slice(index * batchSize, (index + 1) * batchSize));
|
|
53
|
+
const results = await Promise.all(batches.map((batch) => runParseWorker({ repositoryId, files: batch })));
|
|
54
|
+
return results.flat();
|
|
55
|
+
}
|
|
56
|
+
function runParseWorker(task) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const worker = new Worker(new URL('./packageParseWorker.js', import.meta.url), {
|
|
59
|
+
// Test-runner and eval flags are not valid worker entry-point flags.
|
|
60
|
+
execArgv: [],
|
|
61
|
+
env: { ...process.env, NODE_OPTIONS: '' }
|
|
62
|
+
});
|
|
63
|
+
let settled = false;
|
|
64
|
+
const finish = (callback) => {
|
|
65
|
+
if (settled)
|
|
66
|
+
return;
|
|
67
|
+
settled = true;
|
|
68
|
+
callback();
|
|
69
|
+
void worker.terminate();
|
|
70
|
+
};
|
|
71
|
+
worker.once('message', (imports) => finish(() => resolve(imports)));
|
|
72
|
+
worker.once('error', (error) => finish(() => reject(error)));
|
|
73
|
+
worker.once('exit', (code) => {
|
|
74
|
+
if (code !== 0)
|
|
75
|
+
finish(() => reject(new Error(`package parse worker exited with code ${code}`)));
|
|
76
|
+
});
|
|
77
|
+
worker.postMessage(task);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
44
80
|
export const PackagePlugin = {
|
|
45
81
|
name: 'package-plugin',
|
|
46
82
|
apiVersion: 1,
|
|
47
83
|
capabilities: ['repository-analyzer'],
|
|
48
|
-
scan: (context) => {
|
|
84
|
+
scan: async (context) => {
|
|
49
85
|
let manifest;
|
|
50
86
|
try {
|
|
51
87
|
manifest = JSON.parse(context.readFile('package.json'));
|
|
@@ -81,28 +117,8 @@ export const PackagePlugin = {
|
|
|
81
117
|
// Re-run the same extraction used by the built-in scanner. The resulting
|
|
82
118
|
// import IDs are stable graph node IDs, so they can be used directly as
|
|
83
119
|
// targets when targetPlugin is BUILTIN_GRAPH_PLUGIN.
|
|
84
|
-
const imports = [];
|
|
85
120
|
const repositoryId = hashParts(['repository', context.repoPath]);
|
|
86
|
-
|
|
87
|
-
const parsed = parseSource(file.absolutePath, file.source);
|
|
88
|
-
if (!parsed)
|
|
89
|
-
continue;
|
|
90
|
-
const extracted = extractGraph({
|
|
91
|
-
repositoryId,
|
|
92
|
-
fileId: hashParts([repositoryId, file.path]),
|
|
93
|
-
relativePath: file.path,
|
|
94
|
-
source: file.source,
|
|
95
|
-
sourceHash: file.hash,
|
|
96
|
-
parseResult: parsed
|
|
97
|
-
});
|
|
98
|
-
for (const node of extracted.nodes) {
|
|
99
|
-
if (node.kind !== 'import')
|
|
100
|
-
continue;
|
|
101
|
-
const module = typeof node.metadata.module === 'string' ? node.metadata.module : node.name;
|
|
102
|
-
if (module)
|
|
103
|
-
imports.push({ path: file.path, id: node.id, module });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
121
|
+
const imports = await parseImportsInBatches(repositoryId, context.files);
|
|
106
122
|
// A package is represented once even when it is imported by many files or
|
|
107
123
|
// through many subpaths. `implied` records imports that are not declared in
|
|
108
124
|
// any dependency section of package.json.
|
|
@@ -163,6 +179,49 @@ export const PackagePlugin = {
|
|
|
163
179
|
}];
|
|
164
180
|
});
|
|
165
181
|
return { nodes, edges, diagnostics };
|
|
182
|
+
},
|
|
183
|
+
scanIncremental: async (context, delta, previous) => {
|
|
184
|
+
if (delta.full || !previous)
|
|
185
|
+
return await PackagePlugin.scan(context);
|
|
186
|
+
const changed = new Set(delta.changedPaths);
|
|
187
|
+
const removed = new Set(delta.deletedPaths);
|
|
188
|
+
const changedResult = await PackagePlugin.scan({ ...context, files: context.files.filter((file) => changed.has(file.path)) });
|
|
189
|
+
const retainedEdges = previous.edges.flatMap((edge) => {
|
|
190
|
+
const filePath = typeof edge.metadata.filePath === 'string' ? edge.metadata.filePath : undefined;
|
|
191
|
+
const factKey = typeof edge.metadata.factKey === 'string' ? edge.metadata.factKey : undefined;
|
|
192
|
+
if (!filePath || !factKey || changed.has(filePath) || removed.has(filePath))
|
|
193
|
+
return [];
|
|
194
|
+
return [{
|
|
195
|
+
factKey,
|
|
196
|
+
sourceFactKey: typeof edge.metadata.package === 'string' ? `package:${edge.metadata.package}` : edge.sourceId,
|
|
197
|
+
targetFactKey: edge.targetId,
|
|
198
|
+
targetPlugin: BUILTIN_GRAPH_PLUGIN,
|
|
199
|
+
filePath,
|
|
200
|
+
kind: edge.kind,
|
|
201
|
+
confidence: edge.confidence,
|
|
202
|
+
metadata: Object.fromEntries(Object.entries(edge.metadata).filter(([key]) => key !== 'factKey' && key !== 'plugin'))
|
|
203
|
+
}];
|
|
204
|
+
});
|
|
205
|
+
const edges = [...retainedEdges, ...changedResult.edges];
|
|
206
|
+
const packageNames = new Set(edges.flatMap((edge) => typeof edge.metadata?.package === 'string' ? [edge.metadata.package] : []));
|
|
207
|
+
const changedNodes = new Map(changedResult.nodes.map((node) => [node.factKey, node]));
|
|
208
|
+
const nodes = [...packageNames].sort().flatMap((name) => {
|
|
209
|
+
const factKey = `package:${name}`;
|
|
210
|
+
const current = changedNodes.get(factKey);
|
|
211
|
+
if (current)
|
|
212
|
+
return [current];
|
|
213
|
+
const previousNode = previous.nodes.find((node) => node.metadata.factKey === factKey);
|
|
214
|
+
if (!previousNode)
|
|
215
|
+
return [];
|
|
216
|
+
return [{
|
|
217
|
+
factKey,
|
|
218
|
+
kind: previousNode.kind,
|
|
219
|
+
type: previousNode.type,
|
|
220
|
+
name: previousNode.name,
|
|
221
|
+
metadata: Object.fromEntries(Object.entries(previousNode.metadata).filter(([key]) => key !== 'factKey' && key !== 'plugin'))
|
|
222
|
+
}];
|
|
223
|
+
});
|
|
224
|
+
return { nodes, edges, ...(changedResult.diagnostics ? { diagnostics: changedResult.diagnostics } : {}) };
|
|
166
225
|
}
|
|
167
226
|
};
|
|
168
227
|
//# sourceMappingURL=packagePlugin.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { availableParallelism } from 'node:os';
|
|
2
|
+
import { Worker } from 'node:worker_threads';
|
|
3
|
+
const MAX_SCAN_WORKERS = 8;
|
|
4
|
+
export async function runBatchedWorkers(items, workerUrl, createTask) {
|
|
5
|
+
if (items.length === 0)
|
|
6
|
+
return [];
|
|
7
|
+
const workerCount = Math.min(Math.max(1, availableParallelism() - 1), MAX_SCAN_WORKERS, items.length);
|
|
8
|
+
const batchSize = Math.ceil(items.length / workerCount);
|
|
9
|
+
const batches = Array.from({ length: workerCount }, (_, index) => items.slice(index * batchSize, (index + 1) * batchSize));
|
|
10
|
+
return Promise.all(batches.map((batch) => runWorker(workerUrl, createTask(batch))));
|
|
11
|
+
}
|
|
12
|
+
function runWorker(workerUrl, task) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const worker = new Worker(workerUrl, { execArgv: [], env: { ...process.env, NODE_OPTIONS: '' } });
|
|
15
|
+
let settled = false;
|
|
16
|
+
const finish = (callback) => {
|
|
17
|
+
if (settled)
|
|
18
|
+
return;
|
|
19
|
+
settled = true;
|
|
20
|
+
callback();
|
|
21
|
+
void worker.terminate();
|
|
22
|
+
};
|
|
23
|
+
worker.once('message', (result) => finish(() => resolve(result)));
|
|
24
|
+
worker.once('error', (error) => finish(() => reject(error)));
|
|
25
|
+
worker.once('exit', (code) => {
|
|
26
|
+
if (code !== 0)
|
|
27
|
+
finish(() => reject(new Error(`scanner worker exited with code ${code}`)));
|
|
28
|
+
});
|
|
29
|
+
worker.postMessage(task);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=parallelScan.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { FileRecord, GraphEdge, GraphNode, GraphExtractionResult, Point } from '../types.js';
|
|
1
|
+
import type { EnrichmentRunnerInput, AttributionOutcome, FileRecord, GraphEdge, GraphNode, GraphExtractionResult, Point } from '../types.js';
|
|
2
|
+
import { type RepositoryManifest } from './artifactInventory.js';
|
|
2
3
|
export declare const SCANNER_PLUGIN_API_VERSION: 1;
|
|
3
4
|
export type PluginConflictPolicy = 'merge' | 'replace' | 'reject';
|
|
4
|
-
export type ScannerCapability = 'file-scanner' | 'repository-analyzer';
|
|
5
|
+
export type ScannerCapability = 'file-scanner' | 'repository-analyzer' | 'enrichment';
|
|
5
6
|
export interface RepositoryScanFile extends FileRecord {
|
|
6
7
|
absolutePath: string;
|
|
7
8
|
source: string;
|
|
@@ -10,6 +11,8 @@ export interface RepositoryScanContext {
|
|
|
10
11
|
repoPath: string;
|
|
11
12
|
files: readonly RepositoryScanFile[];
|
|
12
13
|
discoveredPaths: readonly string[];
|
|
14
|
+
/** Bounded root/immediate-module Maven and Gradle inventory. */
|
|
15
|
+
manifests: readonly RepositoryManifest[];
|
|
13
16
|
readFile(relativePath: string): string;
|
|
14
17
|
}
|
|
15
18
|
export interface PluginNodeFact {
|
|
@@ -42,6 +45,7 @@ export interface PluginEdgeFact {
|
|
|
42
45
|
targetPlugin?: string;
|
|
43
46
|
filePath?: string;
|
|
44
47
|
kind: GraphEdge['kind'];
|
|
48
|
+
type?: string;
|
|
45
49
|
confidence?: number;
|
|
46
50
|
metadata?: Record<string, unknown>;
|
|
47
51
|
conflict?: PluginConflictPolicy;
|
|
@@ -50,6 +54,29 @@ export interface PluginScanResult {
|
|
|
50
54
|
nodes: PluginNodeFact[];
|
|
51
55
|
edges: PluginEdgeFact[];
|
|
52
56
|
diagnostics?: string[];
|
|
57
|
+
/** Paths whose previous contribution must be retained after a failed replacement. */
|
|
58
|
+
failedPaths?: string[];
|
|
59
|
+
}
|
|
60
|
+
/** Optional v1-compatible extension for plugins that can reconcile a repository delta. */
|
|
61
|
+
export interface RepositoryScanDelta {
|
|
62
|
+
changedPaths: readonly string[];
|
|
63
|
+
deletedPaths: readonly string[];
|
|
64
|
+
/** Additive repository-artifact delta fields; source-only plugins may ignore them. */
|
|
65
|
+
changedManifests?: readonly string[];
|
|
66
|
+
deletedManifests?: readonly string[];
|
|
67
|
+
manifestsComplete?: boolean;
|
|
68
|
+
full: boolean;
|
|
69
|
+
}
|
|
70
|
+
export interface PreviousPluginContribution {
|
|
71
|
+
nodes: readonly GraphNode[];
|
|
72
|
+
edges: readonly GraphEdge[];
|
|
73
|
+
}
|
|
74
|
+
export interface IncrementalPluginScanResult extends PluginScanResult {
|
|
75
|
+
/** Fact keys replaced by this delta when a plugin elects not to return a complete contribution. */
|
|
76
|
+
retractions?: {
|
|
77
|
+
nodes?: string[];
|
|
78
|
+
edges?: string[];
|
|
79
|
+
};
|
|
53
80
|
}
|
|
54
81
|
export interface PluginResourceLimits {
|
|
55
82
|
maxNodes: number;
|
|
@@ -71,16 +98,23 @@ export interface ScannerPlugin {
|
|
|
71
98
|
scan(context: RepositoryScanContext): PluginScanResult | Promise<PluginScanResult>;
|
|
72
99
|
finalize?(context: RepositoryScanContext): PluginScanResult | void | Promise<PluginScanResult | void>;
|
|
73
100
|
dispose?(context: RepositoryScanContext): void | Promise<void>;
|
|
101
|
+
/** Opt-in delta hook. Plugins without it retain the complete-repository v1 scan contract. */
|
|
102
|
+
scanIncremental?(context: RepositoryScanContext, delta: RepositoryScanDelta, previous?: PreviousPluginContribution): IncrementalPluginScanResult | Promise<IncrementalPluginScanResult>;
|
|
103
|
+
/** Optional deferred enrichment hook. The host supplies structural nodes after the file scan. */
|
|
104
|
+
enrich?(input: EnrichmentRunnerInput): AttributionOutcome | Promise<AttributionOutcome>;
|
|
74
105
|
}
|
|
75
106
|
export interface PluginRunResult extends GraphExtractionResult {
|
|
76
107
|
diagnostics: string[];
|
|
108
|
+
/** Per-file failures are degraded but do not make the complete plugin stale. */
|
|
109
|
+
failedPaths?: string[];
|
|
77
110
|
failedPlugins: string[];
|
|
78
111
|
successfulPlugins?: string[];
|
|
79
112
|
pluginContributions?: Map<string, {
|
|
80
113
|
nodes: GraphNode[];
|
|
81
114
|
edges: GraphEdge[];
|
|
82
115
|
}>;
|
|
116
|
+
enrichmentPlugins?: Map<string, ScannerPlugin>;
|
|
83
117
|
}
|
|
84
|
-
export declare function createRepositoryScanContext(repoPath: string): RepositoryScanContext;
|
|
85
|
-
export declare function runScannerPlugins(context: RepositoryScanContext, plugins: readonly ScannerPlugin[], resourceLimits?: Partial<PluginResourceLimits>): Promise<PluginRunResult>;
|
|
118
|
+
export declare function createRepositoryScanContext(repoPath: string, suppliedManifests?: readonly RepositoryManifest[]): RepositoryScanContext;
|
|
119
|
+
export declare function runScannerPlugins(context: RepositoryScanContext, plugins: readonly ScannerPlugin[], resourceLimits?: Partial<PluginResourceLimits>, delta?: RepositoryScanDelta, previousContributions?: ReadonlyMap<string, PreviousPluginContribution>): Promise<PluginRunResult>;
|
|
86
120
|
//# sourceMappingURL=plugins.d.ts.map
|