auditor-lambda 0.3.40 → 0.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/audit-code-wrapper-lib.mjs +20 -2
- package/dist/cli/args.d.ts +59 -0
- package/dist/cli/args.js +244 -0
- package/dist/cli/dispatch.d.ts +80 -0
- package/dist/cli/dispatch.js +532 -0
- package/dist/cli/prompts.d.ts +37 -0
- package/dist/cli/prompts.js +225 -0
- package/dist/cli/steps.d.ts +29 -0
- package/dist/cli/steps.js +30 -0
- package/dist/cli/waveManifest.d.ts +40 -0
- package/dist/cli/waveManifest.js +41 -0
- package/dist/cli/workerResult.d.ts +18 -0
- package/dist/cli/workerResult.js +42 -0
- package/dist/cli.d.ts +2 -22
- package/dist/cli.js +442 -975
- package/dist/extractors/analyzers/css.d.ts +2 -0
- package/dist/extractors/analyzers/css.js +101 -0
- package/dist/extractors/analyzers/html.d.ts +2 -0
- package/dist/extractors/analyzers/html.js +92 -0
- package/dist/extractors/analyzers/merge.d.ts +14 -0
- package/dist/extractors/analyzers/merge.js +85 -0
- package/dist/extractors/analyzers/python.d.ts +2 -0
- package/dist/extractors/analyzers/python.js +104 -0
- package/dist/extractors/analyzers/registry.d.ts +33 -0
- package/dist/extractors/analyzers/registry.js +100 -0
- package/dist/extractors/analyzers/resourceUrl.d.ts +7 -0
- package/dist/extractors/analyzers/resourceUrl.js +25 -0
- package/dist/extractors/analyzers/sql.d.ts +2 -0
- package/dist/extractors/analyzers/sql.js +19 -0
- package/dist/extractors/analyzers/treeSitter.d.ts +34 -0
- package/dist/extractors/analyzers/treeSitter.js +111 -0
- package/dist/extractors/analyzers/types.d.ts +53 -0
- package/dist/extractors/analyzers/typescript.d.ts +2 -0
- package/dist/extractors/analyzers/typescript.js +257 -0
- package/dist/extractors/browserExtension.d.ts +1 -3
- package/dist/extractors/browserExtension.js +2 -2
- package/dist/extractors/designAssessment.d.ts +1 -3
- package/dist/extractors/disposition.d.ts +2 -1
- package/dist/extractors/disposition.js +11 -1
- package/dist/extractors/flows.d.ts +1 -3
- package/dist/extractors/flows.js +2 -2
- package/dist/extractors/graph.d.ts +2 -2
- package/dist/extractors/graph.js +171 -327
- package/dist/extractors/graphManifestEdges.d.ts +1 -1
- package/dist/extractors/graphPathUtils.d.ts +1 -1
- package/dist/extractors/graphPythonImports.d.ts +18 -0
- package/dist/extractors/graphPythonImports.js +362 -0
- package/dist/extractors/pathPatterns.d.ts +6 -0
- package/dist/extractors/pathPatterns.js +8 -0
- package/dist/extractors/risk.d.ts +1 -2
- package/dist/extractors/surfaces.d.ts +1 -3
- package/dist/extractors/surfaces.js +2 -2
- package/dist/io/artifacts.d.ts +12 -5
- package/dist/io/artifacts.js +13 -1
- package/dist/io/runArtifacts.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/orchestrator/advance.d.ts +21 -0
- package/dist/orchestrator/advance.js +69 -7
- package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
- package/dist/orchestrator/auditTaskUtils.js +27 -0
- package/dist/orchestrator/dependencyMap.js +27 -0
- package/dist/orchestrator/edgeReasoning.d.ts +39 -0
- package/dist/orchestrator/edgeReasoning.js +125 -0
- package/dist/orchestrator/executors.js +11 -1
- package/dist/orchestrator/fileAnchors.d.ts +1 -1
- package/dist/orchestrator/fileIntegrity.d.ts +7 -0
- package/dist/orchestrator/fileIntegrity.js +41 -0
- package/dist/orchestrator/flowCoverage.d.ts +1 -1
- package/dist/orchestrator/flowPlanning.d.ts +1 -1
- package/dist/orchestrator/flowRequeue.d.ts +1 -1
- package/dist/orchestrator/graphEnrichmentExecutor.d.ts +29 -0
- package/dist/orchestrator/graphEnrichmentExecutor.js +196 -0
- package/dist/orchestrator/internalExecutors.d.ts +13 -2
- package/dist/orchestrator/internalExecutors.js +112 -16
- package/dist/orchestrator/localCommands.js +6 -25
- package/dist/orchestrator/nextStep.d.ts +2 -1
- package/dist/orchestrator/nextStep.js +3 -1
- package/dist/orchestrator/planning.d.ts +1 -1
- package/dist/orchestrator/requeueCommand.d.ts +1 -1
- package/dist/orchestrator/reviewPackets.d.ts +37 -4
- package/dist/orchestrator/reviewPackets.js +113 -158
- package/dist/orchestrator/runtimeValidation.d.ts +1 -1
- package/dist/orchestrator/runtimeValidation.js +4 -31
- package/dist/orchestrator/scope.d.ts +62 -0
- package/dist/orchestrator/scope.js +227 -0
- package/dist/orchestrator/state.js +2 -0
- package/dist/orchestrator/taskBuilder.d.ts +1 -1
- package/dist/orchestrator/taskBuilder.js +1 -12
- package/dist/orchestrator/unionFind.d.ts +7 -0
- package/dist/orchestrator/unionFind.js +32 -0
- package/dist/orchestrator/unitBuilder.d.ts +2 -2
- package/dist/orchestrator/unitBuilder.js +4 -18
- package/dist/prompts/renderWorkerPrompt.js +18 -1
- package/dist/providers/claudeCodeProvider.d.ts +4 -4
- package/dist/providers/claudeCodeProvider.js +9 -3
- package/dist/providers/constants.d.ts +1 -1
- package/dist/providers/constants.js +1 -1
- package/dist/providers/index.d.ts +1 -2
- package/dist/providers/index.js +5 -4
- package/dist/providers/localSubprocessProvider.d.ts +2 -2
- package/dist/providers/localSubprocessProvider.js +1 -1
- package/dist/providers/opencodeProvider.d.ts +4 -4
- package/dist/providers/opencodeProvider.js +7 -2
- package/dist/providers/spawnLoggedCommand.d.ts +3 -1
- package/dist/providers/spawnLoggedCommand.js +21 -0
- package/dist/providers/subprocessTemplateProvider.d.ts +4 -4
- package/dist/providers/subprocessTemplateProvider.js +8 -3
- package/dist/providers/vscodeTaskProvider.d.ts +3 -4
- package/dist/providers/vscodeTaskProvider.js +2 -2
- package/dist/quota/discoveredLimits.js +1 -1
- package/dist/quota/hostLimits.d.ts +1 -2
- package/dist/quota/hostLimits.js +4 -46
- package/dist/quota/index.d.ts +18 -15
- package/dist/quota/index.js +4 -9
- package/dist/quota/scheduler.d.ts +1 -3
- package/dist/quota/scheduler.js +1 -2
- package/dist/reporting/synthesis.d.ts +37 -3
- package/dist/reporting/synthesis.js +97 -16
- package/dist/reporting/synthesisNarrativePrompt.d.ts +7 -0
- package/dist/reporting/synthesisNarrativePrompt.js +60 -0
- package/dist/reporting/workBlocks.d.ts +2 -11
- package/dist/supervisor/operatorHandoff.js +1 -1
- package/dist/supervisor/runLedger.d.ts +1 -1
- package/dist/supervisor/runLedger.js +2 -2
- package/dist/supervisor/sessionConfig.d.ts +8 -1
- package/dist/supervisor/sessionConfig.js +22 -3
- package/dist/types/analyzerCapability.d.ts +16 -0
- package/dist/types/auditScope.d.ts +43 -0
- package/dist/types/auditScope.js +14 -0
- package/dist/types/reviewPlanning.d.ts +1 -1
- package/dist/types/synthesisNarrative.d.ts +7 -0
- package/dist/types/synthesisNarrative.js +5 -0
- package/dist/types/workerSession.d.ts +6 -0
- package/dist/types.d.ts +2 -19
- package/dist/validation/artifacts.d.ts +1 -1
- package/dist/validation/artifacts.js +10 -1
- package/dist/validation/auditResults.d.ts +1 -1
- package/dist/validation/auditResults.js +1 -1
- package/dist/validation/sessionConfig.d.ts +2 -3
- package/dist/validation/sessionConfig.js +25 -3
- package/package.json +7 -3
- package/schemas/analyzer_capability.schema.json +47 -0
- package/schemas/audit_findings.schema.json +141 -0
- package/schemas/finding.schema.json +2 -1
- package/schemas/graph_bundle.schema.json +5 -0
- package/schemas/scope.schema.json +46 -0
- package/scripts/postinstall.mjs +0 -1
- package/dist/io/json.d.ts +0 -10
- package/dist/io/json.js +0 -142
- package/dist/providers/types.d.ts +0 -33
- package/dist/quota/compositeQuotaSource.d.ts +0 -7
- package/dist/quota/compositeQuotaSource.js +0 -20
- package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +0 -6
- package/dist/quota/errorParsers/claudeCodeErrorParser.js +0 -39
- package/dist/quota/errorParsers/genericErrorParser.d.ts +0 -9
- package/dist/quota/errorParsers/genericErrorParser.js +0 -7
- package/dist/quota/errorParsers/index.d.ts +0 -5
- package/dist/quota/errorParsers/index.js +0 -12
- package/dist/quota/errorParsing.d.ts +0 -7
- package/dist/quota/errorParsing.js +0 -69
- package/dist/quota/fileLock.d.ts +0 -6
- package/dist/quota/fileLock.js +0 -64
- package/dist/quota/learnedQuotaSource.d.ts +0 -7
- package/dist/quota/learnedQuotaSource.js +0 -25
- package/dist/quota/limits.d.ts +0 -16
- package/dist/quota/limits.js +0 -77
- package/dist/quota/quotaSource.d.ts +0 -12
- package/dist/quota/slidingWindow.d.ts +0 -4
- package/dist/quota/slidingWindow.js +0 -28
- package/dist/quota/state.d.ts +0 -15
- package/dist/quota/state.js +0 -148
- package/dist/quota/types.d.ts +0 -67
- package/dist/quota/types.js +0 -1
- package/dist/reporting/rootCause.d.ts +0 -10
- package/dist/reporting/rootCause.js +0 -146
- package/dist/types/disposition.d.ts +0 -9
- package/dist/types/disposition.js +0 -1
- package/dist/types/flows.d.ts +0 -17
- package/dist/types/flows.js +0 -1
- package/dist/types/graph.d.ts +0 -22
- package/dist/types/graph.js +0 -1
- package/dist/types/risk.d.ts +0 -9
- package/dist/types/risk.js +0 -1
- package/dist/types/runLedger.d.ts +0 -17
- package/dist/types/runLedger.js +0 -6
- package/dist/types/sessionConfig.d.ts +0 -79
- package/dist/types/sessionConfig.js +0 -15
- package/dist/types/surfaces.d.ts +0 -15
- package/dist/types/surfaces.js +0 -1
- package/dist/validation/basic.d.ts +0 -13
- package/dist/validation/basic.js +0 -46
- /package/dist/{providers → extractors/analyzers}/types.js +0 -0
- /package/dist/{quota/quotaSource.js → types/analyzerCapability.js} +0 -0
package/dist/extractors/graph.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { posix } from "node:path";
|
|
4
|
-
import { isAuditExcludedStatus } from "./disposition.js";
|
|
4
|
+
import { buildDispositionMap, isAuditExcludedStatus } from "./disposition.js";
|
|
5
5
|
import { extractChromeExtensionManifestEdges, extractHtmlResourceEdges, } from "./browserExtension.js";
|
|
6
6
|
import { extractCargoWorkspaceMemberEdges, extractGoWorkspaceModuleEdges, extractMavenModuleEdges, extractPackageEntrypointEdges, extractPackageScriptEdges, extractPyprojectTestpathLinks, extractTypescriptProjectReferenceEdges, extractWorkspacePackageEdges, extractYamlPathReferenceEdges, isCargoManifestPath, isGoWorkspaceManifestPath, isMavenPomPath, isPyprojectPath, } from "./graphManifestEdges.js";
|
|
7
7
|
import { graphEdge, graphLookupKey, normalizeGraphPath, resolveCandidate, } from "./graphPathUtils.js";
|
|
8
|
+
import { extractPythonImportEdges, isPythonSourcePath, } from "./graphPythonImports.js";
|
|
8
9
|
import { isTestPath, normalizeExtractorPath } from "./pathPatterns.js";
|
|
9
10
|
const MAX_GRAPH_SOURCE_BYTES = 512 * 1024;
|
|
10
11
|
const SOURCE_LANGUAGES = new Set([
|
|
@@ -40,8 +41,6 @@ const SOURCE_EXTENSIONS = [
|
|
|
40
41
|
".java",
|
|
41
42
|
".cs",
|
|
42
43
|
];
|
|
43
|
-
const PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
|
|
44
|
-
const PYTHON_PACKAGE_INDEX_FILES = ["__init__.py", "__init__.pyi"];
|
|
45
44
|
const TYPESCRIPT_TYPE_CONTRACT_EXTENSIONS = [
|
|
46
45
|
".ts",
|
|
47
46
|
".tsx",
|
|
@@ -135,7 +134,7 @@ function shouldReadForGraph(file) {
|
|
|
135
134
|
isMavenPomPath(normalized) ||
|
|
136
135
|
isPyprojectPath(normalized)));
|
|
137
136
|
}
|
|
138
|
-
function buildPathLookup(repoManifest, dispositionMap) {
|
|
137
|
+
export function buildPathLookup(repoManifest, dispositionMap) {
|
|
139
138
|
return new Map(repoManifest.files
|
|
140
139
|
.filter((file) => {
|
|
141
140
|
const status = dispositionMap.get(file.path);
|
|
@@ -339,327 +338,6 @@ function extractImportBindings(fromPath, content, pathLookup) {
|
|
|
339
338
|
}
|
|
340
339
|
return bindings;
|
|
341
340
|
}
|
|
342
|
-
function isPythonSourcePath(path) {
|
|
343
|
-
const normalized = normalizeGraphPath(path).toLowerCase();
|
|
344
|
-
return PYTHON_SOURCE_EXTENSIONS.some((extension) => normalized.endsWith(extension));
|
|
345
|
-
}
|
|
346
|
-
function stripPythonLineComment(line) {
|
|
347
|
-
let quote;
|
|
348
|
-
let escaped = false;
|
|
349
|
-
for (let index = 0; index < line.length; index++) {
|
|
350
|
-
const char = line[index];
|
|
351
|
-
if (escaped) {
|
|
352
|
-
escaped = false;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (quote) {
|
|
356
|
-
if (char === "\\") {
|
|
357
|
-
escaped = true;
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
if (char === quote) {
|
|
361
|
-
quote = undefined;
|
|
362
|
-
}
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
if (char === "'" || char === '"') {
|
|
366
|
-
quote = char;
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
if (char === "#") {
|
|
370
|
-
return line.slice(0, index);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return line;
|
|
374
|
-
}
|
|
375
|
-
function pythonParenDelta(line) {
|
|
376
|
-
let quote;
|
|
377
|
-
let escaped = false;
|
|
378
|
-
let delta = 0;
|
|
379
|
-
for (const char of line) {
|
|
380
|
-
if (escaped) {
|
|
381
|
-
escaped = false;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (quote) {
|
|
385
|
-
if (char === "\\") {
|
|
386
|
-
escaped = true;
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (char === quote) {
|
|
390
|
-
quote = undefined;
|
|
391
|
-
}
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
if (char === "'" || char === '"') {
|
|
395
|
-
quote = char;
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
if (char === "(") {
|
|
399
|
-
delta += 1;
|
|
400
|
-
}
|
|
401
|
-
else if (char === ")") {
|
|
402
|
-
delta -= 1;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return delta;
|
|
406
|
-
}
|
|
407
|
-
function pythonLogicalLines(content) {
|
|
408
|
-
const logicalLines = [];
|
|
409
|
-
let pending = "";
|
|
410
|
-
let parenDepth = 0;
|
|
411
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
412
|
-
const stripped = stripPythonLineComment(rawLine).trim();
|
|
413
|
-
if (stripped.length === 0) {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
if (pending.length === 0 && !/^(?:import|from)\s+/i.test(stripped)) {
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
const continued = stripped.endsWith("\\");
|
|
420
|
-
const line = continued ? stripped.slice(0, -1).trimEnd() : stripped;
|
|
421
|
-
pending = pending.length > 0 ? `${pending} ${line}` : line;
|
|
422
|
-
parenDepth += pythonParenDelta(line);
|
|
423
|
-
if (!continued && parenDepth <= 0) {
|
|
424
|
-
logicalLines.push(pending.replace(/\s+/g, " ").trim());
|
|
425
|
-
pending = "";
|
|
426
|
-
parenDepth = 0;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (pending.length > 0) {
|
|
430
|
-
logicalLines.push(pending.replace(/\s+/g, " ").trim());
|
|
431
|
-
}
|
|
432
|
-
return logicalLines;
|
|
433
|
-
}
|
|
434
|
-
function unwrapPythonImportList(value) {
|
|
435
|
-
let trimmed = value.trim();
|
|
436
|
-
if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
|
|
437
|
-
trimmed = trimmed.slice(1, -1).trim();
|
|
438
|
-
}
|
|
439
|
-
return trimmed;
|
|
440
|
-
}
|
|
441
|
-
function splitPythonImportList(value) {
|
|
442
|
-
const items = [];
|
|
443
|
-
let current = "";
|
|
444
|
-
let quote;
|
|
445
|
-
let escaped = false;
|
|
446
|
-
let parenDepth = 0;
|
|
447
|
-
for (const char of unwrapPythonImportList(value)) {
|
|
448
|
-
if (escaped) {
|
|
449
|
-
current += char;
|
|
450
|
-
escaped = false;
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
|
-
if (quote) {
|
|
454
|
-
current += char;
|
|
455
|
-
if (char === "\\") {
|
|
456
|
-
escaped = true;
|
|
457
|
-
}
|
|
458
|
-
else if (char === quote) {
|
|
459
|
-
quote = undefined;
|
|
460
|
-
}
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
if (char === "'" || char === '"') {
|
|
464
|
-
current += char;
|
|
465
|
-
quote = char;
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
if (char === "(") {
|
|
469
|
-
parenDepth += 1;
|
|
470
|
-
current += char;
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
if (char === ")") {
|
|
474
|
-
parenDepth -= 1;
|
|
475
|
-
current += char;
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
if (char === "," && parenDepth === 0) {
|
|
479
|
-
const item = current.trim();
|
|
480
|
-
if (item.length > 0) {
|
|
481
|
-
items.push(item);
|
|
482
|
-
}
|
|
483
|
-
current = "";
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
current += char;
|
|
487
|
-
}
|
|
488
|
-
const item = current.trim();
|
|
489
|
-
if (item.length > 0) {
|
|
490
|
-
items.push(item);
|
|
491
|
-
}
|
|
492
|
-
return items;
|
|
493
|
-
}
|
|
494
|
-
function stripPythonAlias(value) {
|
|
495
|
-
return value.replace(/\s+as\s+[A-Za-z_]\w*$/i, "").trim();
|
|
496
|
-
}
|
|
497
|
-
function isPythonIdentifier(value) {
|
|
498
|
-
return /^[A-Za-z_]\w*$/.test(value);
|
|
499
|
-
}
|
|
500
|
-
function isPythonAbsoluteModuleSpecifier(value) {
|
|
501
|
-
return /^[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*$/.test(value);
|
|
502
|
-
}
|
|
503
|
-
function isPythonRelativeModuleSpecifier(value) {
|
|
504
|
-
return /^\.+(?:[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)?$/.test(value);
|
|
505
|
-
}
|
|
506
|
-
function isPythonModuleSpecifier(value) {
|
|
507
|
-
return (isPythonAbsoluteModuleSpecifier(value) ||
|
|
508
|
-
isPythonRelativeModuleSpecifier(value));
|
|
509
|
-
}
|
|
510
|
-
function pythonModulePath(specifier) {
|
|
511
|
-
return specifier.split(".").filter(Boolean).join("/");
|
|
512
|
-
}
|
|
513
|
-
function resolvePythonPathCandidate(candidate, pathLookup) {
|
|
514
|
-
const normalized = normalizeGraphPath(candidate).replace(/\/+$/, "");
|
|
515
|
-
if (normalized.length === 0 || normalized === "." || normalized === "..") {
|
|
516
|
-
return undefined;
|
|
517
|
-
}
|
|
518
|
-
return resolveCandidate(normalized, pathLookup);
|
|
519
|
-
}
|
|
520
|
-
function pythonPathMatchesModule(path, modulePath) {
|
|
521
|
-
const normalizedPath = normalizeGraphPath(path).toLowerCase();
|
|
522
|
-
const normalizedModulePath = normalizeGraphPath(modulePath).toLowerCase();
|
|
523
|
-
return (PYTHON_SOURCE_EXTENSIONS.some((extension) => {
|
|
524
|
-
const moduleFile = `${normalizedModulePath}${extension}`;
|
|
525
|
-
return (normalizedPath === moduleFile ||
|
|
526
|
-
normalizedPath.endsWith(`/${moduleFile}`));
|
|
527
|
-
}) ||
|
|
528
|
-
PYTHON_PACKAGE_INDEX_FILES.some((indexFile) => {
|
|
529
|
-
const packageFile = posix.join(normalizedModulePath, indexFile);
|
|
530
|
-
return (normalizedPath === packageFile ||
|
|
531
|
-
normalizedPath.endsWith(`/${packageFile}`));
|
|
532
|
-
}));
|
|
533
|
-
}
|
|
534
|
-
function commonDirectoryPrefixLength(left, right) {
|
|
535
|
-
const leftParts = normalizeGraphPath(left).split("/").filter(Boolean);
|
|
536
|
-
const rightParts = normalizeGraphPath(right).split("/").filter(Boolean);
|
|
537
|
-
let count = 0;
|
|
538
|
-
while (count < leftParts.length &&
|
|
539
|
-
count < rightParts.length &&
|
|
540
|
-
leftParts[count].toLowerCase() === rightParts[count].toLowerCase()) {
|
|
541
|
-
count += 1;
|
|
542
|
-
}
|
|
543
|
-
return count;
|
|
544
|
-
}
|
|
545
|
-
function resolvePythonAbsoluteModuleSpecifier(fromPath, specifier, pathLookup) {
|
|
546
|
-
const modulePath = pythonModulePath(specifier);
|
|
547
|
-
const direct = resolvePythonPathCandidate(modulePath, pathLookup);
|
|
548
|
-
if (direct) {
|
|
549
|
-
return direct;
|
|
550
|
-
}
|
|
551
|
-
const matches = [...new Set(pathLookup.values())].filter((path) => isPythonSourcePath(path) && pythonPathMatchesModule(path, modulePath));
|
|
552
|
-
if (matches.length === 1) {
|
|
553
|
-
return matches[0];
|
|
554
|
-
}
|
|
555
|
-
if (matches.length === 0) {
|
|
556
|
-
return undefined;
|
|
557
|
-
}
|
|
558
|
-
const fromDir = posix.dirname(normalizeGraphPath(fromPath));
|
|
559
|
-
const scored = matches
|
|
560
|
-
.map((target) => ({
|
|
561
|
-
target,
|
|
562
|
-
score: commonDirectoryPrefixLength(fromDir, posix.dirname(normalizeGraphPath(target))),
|
|
563
|
-
}))
|
|
564
|
-
.sort((a, b) => b.score - a.score || a.target.localeCompare(b.target));
|
|
565
|
-
const bestScore = scored[0]?.score ?? 0;
|
|
566
|
-
const bestMatches = scored.filter((item) => item.score === bestScore);
|
|
567
|
-
if (bestScore > 0 && bestMatches.length === 1) {
|
|
568
|
-
return bestMatches[0].target;
|
|
569
|
-
}
|
|
570
|
-
const srcMatches = matches.filter((target) => normalizeGraphPath(target).toLowerCase().startsWith("src/"));
|
|
571
|
-
return srcMatches.length === 1 ? srcMatches[0] : undefined;
|
|
572
|
-
}
|
|
573
|
-
function resolvePythonRelativeModuleSpecifier(fromPath, specifier, pathLookup) {
|
|
574
|
-
const match = /^(\.+)(.*)$/.exec(specifier);
|
|
575
|
-
if (!match) {
|
|
576
|
-
return undefined;
|
|
577
|
-
}
|
|
578
|
-
const level = match[1].length;
|
|
579
|
-
const remainder = match[2] ?? "";
|
|
580
|
-
let baseDir = posix.dirname(normalizeGraphPath(fromPath));
|
|
581
|
-
for (let index = 1; index < level; index++) {
|
|
582
|
-
const next = posix.dirname(baseDir);
|
|
583
|
-
if (next === baseDir) {
|
|
584
|
-
return undefined;
|
|
585
|
-
}
|
|
586
|
-
baseDir = next;
|
|
587
|
-
}
|
|
588
|
-
const modulePath = pythonModulePath(remainder);
|
|
589
|
-
const candidate = modulePath.length > 0 ? posix.join(baseDir, modulePath) : baseDir;
|
|
590
|
-
return resolvePythonPathCandidate(candidate, pathLookup);
|
|
591
|
-
}
|
|
592
|
-
function resolvePythonModuleSpecifier(fromPath, specifier, pathLookup) {
|
|
593
|
-
if (isPythonRelativeModuleSpecifier(specifier)) {
|
|
594
|
-
return resolvePythonRelativeModuleSpecifier(fromPath, specifier, pathLookup);
|
|
595
|
-
}
|
|
596
|
-
if (isPythonAbsoluteModuleSpecifier(specifier)) {
|
|
597
|
-
return resolvePythonAbsoluteModuleSpecifier(fromPath, specifier, pathLookup);
|
|
598
|
-
}
|
|
599
|
-
return undefined;
|
|
600
|
-
}
|
|
601
|
-
function appendPythonImportedSpecifier(moduleSpecifier, importedName) {
|
|
602
|
-
return moduleSpecifier.endsWith(".")
|
|
603
|
-
? `${moduleSpecifier}${importedName}`
|
|
604
|
-
: `${moduleSpecifier}.${importedName}`;
|
|
605
|
-
}
|
|
606
|
-
function addPythonImportEdge(edges, fromPath, target, kind, specifier) {
|
|
607
|
-
if (!target || target === fromPath) {
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
edges.push(graphEdge({
|
|
611
|
-
from: fromPath,
|
|
612
|
-
to: target,
|
|
613
|
-
kind,
|
|
614
|
-
confidence: IMPORT_EDGE_CONFIDENCE,
|
|
615
|
-
reason: `Resolved Python import specifier '${specifier}'.`,
|
|
616
|
-
}));
|
|
617
|
-
}
|
|
618
|
-
function extractPythonImportEdges(fromPath, content, pathLookup) {
|
|
619
|
-
if (!isPythonSourcePath(fromPath)) {
|
|
620
|
-
return [];
|
|
621
|
-
}
|
|
622
|
-
const edges = [];
|
|
623
|
-
for (const line of pythonLogicalLines(content)) {
|
|
624
|
-
const importMatch = /^import\s+(.+)$/i.exec(line);
|
|
625
|
-
if (importMatch) {
|
|
626
|
-
for (const rawSpecifier of splitPythonImportList(importMatch[1] ?? "")) {
|
|
627
|
-
const specifier = stripPythonAlias(rawSpecifier);
|
|
628
|
-
if (!isPythonAbsoluteModuleSpecifier(specifier)) {
|
|
629
|
-
continue;
|
|
630
|
-
}
|
|
631
|
-
addPythonImportEdge(edges, fromPath, resolvePythonModuleSpecifier(fromPath, specifier, pathLookup), "python-import", specifier);
|
|
632
|
-
}
|
|
633
|
-
continue;
|
|
634
|
-
}
|
|
635
|
-
const fromImportMatch = /^from\s+([.\w]+)\s+import\s+(.+)$/i.exec(line);
|
|
636
|
-
if (!fromImportMatch) {
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
const moduleSpecifier = fromImportMatch[1] ?? "";
|
|
640
|
-
if (!isPythonModuleSpecifier(moduleSpecifier)) {
|
|
641
|
-
continue;
|
|
642
|
-
}
|
|
643
|
-
const importedNames = splitPythonImportList(fromImportMatch[2] ?? "")
|
|
644
|
-
.map(stripPythonAlias)
|
|
645
|
-
.filter((name) => name !== "*" && isPythonIdentifier(name));
|
|
646
|
-
const submoduleTargets = importedNames
|
|
647
|
-
.map((name) => appendPythonImportedSpecifier(moduleSpecifier, name))
|
|
648
|
-
.map((specifier) => ({
|
|
649
|
-
specifier,
|
|
650
|
-
target: resolvePythonModuleSpecifier(fromPath, specifier, pathLookup),
|
|
651
|
-
}))
|
|
652
|
-
.filter((item) => item.target);
|
|
653
|
-
if (submoduleTargets.length > 0) {
|
|
654
|
-
for (const { specifier, target } of submoduleTargets) {
|
|
655
|
-
addPythonImportEdge(edges, fromPath, target, "python-from-import", specifier);
|
|
656
|
-
}
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
addPythonImportEdge(edges, fromPath, resolvePythonModuleSpecifier(fromPath, moduleSpecifier, pathLookup), "python-from-import", moduleSpecifier);
|
|
660
|
-
}
|
|
661
|
-
return edges;
|
|
662
|
-
}
|
|
663
341
|
function extractImportEdges(fromPath, content, pathLookup) {
|
|
664
342
|
const edges = [];
|
|
665
343
|
for (const { pattern, kind } of IMPORT_PATTERNS) {
|
|
@@ -1105,6 +783,169 @@ function extractConventionalRouteEvidence(fromPath, content) {
|
|
|
1105
783
|
}
|
|
1106
784
|
return routes.length > 0 ? routes : [{ path: routePath, handler: fromPath }];
|
|
1107
785
|
}
|
|
786
|
+
// ---- Phase 4A: decorator / framework route detection ----
|
|
787
|
+
// Deterministic route patterns for NestJS, FastAPI, Flask, and Angular. These
|
|
788
|
+
// emit only the existing RouteEdge / route-handler-link shapes — no new
|
|
789
|
+
// planning-topology edge kinds. Each branch is gated on a framework marker so
|
|
790
|
+
// the patterns do not fire on unrelated decorators or object literals. An
|
|
791
|
+
// AST-based version can later move behind the analyzer seam; this is the
|
|
792
|
+
// regex floor for these frameworks.
|
|
793
|
+
const NEST_CONTROLLER_PATTERN = /@Controller\s*\(([\s\S]{0,200}?)\)/g;
|
|
794
|
+
const NEST_METHOD_DECORATOR_PATTERN = /@(Get|Post|Put|Patch|Delete|Options|Head|All)\s*\(\s*(?:["'`]([^"'`]*)["'`])?/g;
|
|
795
|
+
const PY_DECORATOR_METHOD_PATTERN = /@\s*[A-Za-z_]\w*\s*\.\s*(get|post|put|patch|delete|options|head|trace|websocket)\s*\(\s*["']([^"']+)["']/g;
|
|
796
|
+
const PY_ROUTE_DECORATOR_PATTERN = /@\s*[A-Za-z_]\w*\s*\.\s*(api_route|route)\s*\(\s*["']([^"']+)["']([\s\S]{0,200}?)\)/g;
|
|
797
|
+
const PY_METHODS_LIST_PATTERN = /methods\s*=\s*\[([^\]]*)\]/;
|
|
798
|
+
const PY_METHOD_LITERAL_PATTERN = /["']([A-Za-z]+)["']/g;
|
|
799
|
+
const ANGULAR_FILE_MARKER_PATTERN = /\b(?:RouterModule|provideRouter|loadChildren|loadComponent)\b|:\s*Routes\b/;
|
|
800
|
+
const ANGULAR_ROUTE_OBJECT_PATTERN = /\{[^{}]*?\bpath\s*:\s*["'`]([^"'`]*)["'`][^{}]*?\}/g;
|
|
801
|
+
const ANGULAR_ROUTE_KEY_PATTERN = /\b(?:component|loadChildren|loadComponent|redirectTo)\s*:/;
|
|
802
|
+
const ANGULAR_COMPONENT_PATTERN = /\b(?:component|loadComponent)\s*:\s*([A-Za-z_$][\w$]*)/;
|
|
803
|
+
const ANGULAR_LAZY_IMPORT_PATTERN = /\b(?:loadChildren|loadComponent)\s*:[\s\S]*?import\s*\(\s*["']([^"']+)["']\s*\)/;
|
|
804
|
+
const TS_LIKE_EXTENSION_PATTERN = /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
805
|
+
/** Join route segments (controller prefix + method path) into one clean path. */
|
|
806
|
+
function joinRouteSegments(...segments) {
|
|
807
|
+
return segments
|
|
808
|
+
.map((segment) => segment.trim().replace(/^\/+|\/+$/g, ""))
|
|
809
|
+
.filter((segment) => segment.length > 0)
|
|
810
|
+
.join("/");
|
|
811
|
+
}
|
|
812
|
+
/** Controller prefixes in document order, so each method can take the nearest. */
|
|
813
|
+
function nestControllerPrefixes(content) {
|
|
814
|
+
const prefixes = [];
|
|
815
|
+
NEST_CONTROLLER_PATTERN.lastIndex = 0;
|
|
816
|
+
for (const match of content.matchAll(NEST_CONTROLLER_PATTERN)) {
|
|
817
|
+
const arg = match[1] ?? "";
|
|
818
|
+
const pathProp = arg.match(/\bpath\s*:\s*["'`]([^"'`]*)["'`]/);
|
|
819
|
+
const firstString = arg.match(/["'`]([^"'`]*)["'`]/);
|
|
820
|
+
const prefix = pathProp?.[1] ?? firstString?.[1] ?? "";
|
|
821
|
+
prefixes.push({ index: match.index ?? 0, prefix });
|
|
822
|
+
}
|
|
823
|
+
return prefixes;
|
|
824
|
+
}
|
|
825
|
+
function collectNestRoutes(fromPath, content, routes) {
|
|
826
|
+
if (!content.includes("@Controller")) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const controllers = nestControllerPrefixes(content);
|
|
830
|
+
if (controllers.length === 0) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
NEST_METHOD_DECORATOR_PATTERN.lastIndex = 0;
|
|
834
|
+
for (const match of content.matchAll(NEST_METHOD_DECORATOR_PATTERN)) {
|
|
835
|
+
const method = match[1];
|
|
836
|
+
if (!method)
|
|
837
|
+
continue;
|
|
838
|
+
const subPath = match[2] ?? "";
|
|
839
|
+
const at = match.index ?? 0;
|
|
840
|
+
let prefix = "";
|
|
841
|
+
for (const controller of controllers) {
|
|
842
|
+
if (controller.index <= at)
|
|
843
|
+
prefix = controller.prefix;
|
|
844
|
+
else
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
routes.push({
|
|
848
|
+
path: normalizeRoutePath(joinRouteSegments(prefix, subPath)),
|
|
849
|
+
handler: fromPath,
|
|
850
|
+
method: method.toUpperCase(),
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
function pythonRouteMethods(args) {
|
|
855
|
+
const listMatch = args.match(PY_METHODS_LIST_PATTERN);
|
|
856
|
+
if (!listMatch?.[1])
|
|
857
|
+
return [];
|
|
858
|
+
PY_METHOD_LITERAL_PATTERN.lastIndex = 0;
|
|
859
|
+
return [...listMatch[1].matchAll(PY_METHOD_LITERAL_PATTERN)].map((method) => method[1].toUpperCase());
|
|
860
|
+
}
|
|
861
|
+
function collectPythonFrameworkRoutes(fromPath, content, routes) {
|
|
862
|
+
// FastAPI / Starlette: @app.get("/x"), @router.post("/y"), @router.websocket("/ws")
|
|
863
|
+
PY_DECORATOR_METHOD_PATTERN.lastIndex = 0;
|
|
864
|
+
for (const match of content.matchAll(PY_DECORATOR_METHOD_PATTERN)) {
|
|
865
|
+
const verb = match[1];
|
|
866
|
+
const routePath = match[2];
|
|
867
|
+
if (!verb || !routePath)
|
|
868
|
+
continue;
|
|
869
|
+
const method = verb.toUpperCase();
|
|
870
|
+
routes.push({
|
|
871
|
+
path: normalizeRoutePath(routePath),
|
|
872
|
+
handler: fromPath,
|
|
873
|
+
method: method === "WEBSOCKET" ? "WS" : method,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
// FastAPI api_route + Flask route: @app.route("/x", methods=["GET","POST"])
|
|
877
|
+
PY_ROUTE_DECORATOR_PATTERN.lastIndex = 0;
|
|
878
|
+
for (const match of content.matchAll(PY_ROUTE_DECORATOR_PATTERN)) {
|
|
879
|
+
const routePath = match[2];
|
|
880
|
+
if (!routePath)
|
|
881
|
+
continue;
|
|
882
|
+
const methods = pythonRouteMethods(match[3] ?? "");
|
|
883
|
+
const path = normalizeRoutePath(routePath);
|
|
884
|
+
if (methods.length === 0) {
|
|
885
|
+
routes.push({ path, handler: fromPath, method: "GET" });
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
for (const method of methods) {
|
|
889
|
+
routes.push({ path, handler: fromPath, method });
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function collectAngularRoutes(fromPath, content, pathLookup, calls, routes) {
|
|
894
|
+
if (!ANGULAR_FILE_MARKER_PATTERN.test(content)) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const bindings = extractImportBindings(fromPath, content, pathLookup);
|
|
898
|
+
ANGULAR_ROUTE_OBJECT_PATTERN.lastIndex = 0;
|
|
899
|
+
for (const match of content.matchAll(ANGULAR_ROUTE_OBJECT_PATTERN)) {
|
|
900
|
+
const body = match[0];
|
|
901
|
+
if (!ANGULAR_ROUTE_KEY_PATTERN.test(body)) {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
const routePath = normalizeRoutePath(match[1] ?? "");
|
|
905
|
+
let handlerPath = fromPath;
|
|
906
|
+
let handlerExpression;
|
|
907
|
+
const lazyImport = body.match(ANGULAR_LAZY_IMPORT_PATTERN);
|
|
908
|
+
const component = body.match(ANGULAR_COMPONENT_PATTERN);
|
|
909
|
+
if (lazyImport?.[1]) {
|
|
910
|
+
const target = resolveSpecifier(fromPath, lazyImport[1], pathLookup) ??
|
|
911
|
+
resolveReferenceLiteral(fromPath, lazyImport[1], pathLookup);
|
|
912
|
+
if (target) {
|
|
913
|
+
handlerPath = target;
|
|
914
|
+
handlerExpression = lazyImport[1];
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
else if (component?.[1]) {
|
|
918
|
+
const binding = bindings.get(component[1]);
|
|
919
|
+
if (binding) {
|
|
920
|
+
handlerPath = binding.target;
|
|
921
|
+
handlerExpression = component[1];
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
routes.push({ path: routePath, handler: handlerPath });
|
|
925
|
+
if (handlerPath !== fromPath) {
|
|
926
|
+
calls.push(graphEdge({
|
|
927
|
+
from: fromPath,
|
|
928
|
+
to: handlerPath,
|
|
929
|
+
kind: "route-handler-link",
|
|
930
|
+
confidence: ROUTE_HANDLER_EDGE_CONFIDENCE,
|
|
931
|
+
reason: `Angular route '${routePath}' maps to '${handlerExpression ?? handlerPath}'.`,
|
|
932
|
+
}));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function extractFrameworkRouteEvidence(fromPath, content, pathLookup) {
|
|
937
|
+
const normalized = normalizeGraphPath(fromPath).toLowerCase();
|
|
938
|
+
const calls = [];
|
|
939
|
+
const routes = [];
|
|
940
|
+
if (normalized.endsWith(".py")) {
|
|
941
|
+
collectPythonFrameworkRoutes(fromPath, content, routes);
|
|
942
|
+
}
|
|
943
|
+
else if (TS_LIKE_EXTENSION_PATTERN.test(normalized)) {
|
|
944
|
+
collectNestRoutes(fromPath, content, routes);
|
|
945
|
+
collectAngularRoutes(fromPath, content, pathLookup, calls, routes);
|
|
946
|
+
}
|
|
947
|
+
return { calls, routes };
|
|
948
|
+
}
|
|
1108
949
|
function fallbackRouteEdge(filePath) {
|
|
1109
950
|
const normalized = filePath.toLowerCase();
|
|
1110
951
|
if (normalized.includes("api/") || normalized.includes("route")) {
|
|
@@ -1239,7 +1080,7 @@ function extractPytestConftestLinks(pathLookup) {
|
|
|
1239
1080
|
}
|
|
1240
1081
|
export async function buildGraphBundleFromFs(repoManifest, root, disposition, options = {}) {
|
|
1241
1082
|
const rootPath = resolve(root);
|
|
1242
|
-
const dispositionMap =
|
|
1083
|
+
const dispositionMap = buildDispositionMap(disposition);
|
|
1243
1084
|
const fileContents = {};
|
|
1244
1085
|
await Promise.all(repoManifest.files.map(async (file) => {
|
|
1245
1086
|
const status = dispositionMap.get(file.path);
|
|
@@ -1267,7 +1108,7 @@ export function buildGraphBundle(repoManifest, disposition, options = {}) {
|
|
|
1267
1108
|
const calls = [];
|
|
1268
1109
|
const references = [];
|
|
1269
1110
|
const routes = [];
|
|
1270
|
-
const dispositionMap =
|
|
1111
|
+
const dispositionMap = buildDispositionMap(disposition);
|
|
1271
1112
|
const pathLookup = buildPathLookup(repoManifest, dispositionMap);
|
|
1272
1113
|
for (const file of repoManifest.files) {
|
|
1273
1114
|
const status = dispositionMap.get(file.path);
|
|
@@ -1327,6 +1168,9 @@ export function buildGraphBundle(repoManifest, disposition, options = {}) {
|
|
|
1327
1168
|
const registeredRoutes = extractRegisteredRouteEvidence(file.path, content, pathLookup);
|
|
1328
1169
|
calls.push(...registeredRoutes.calls);
|
|
1329
1170
|
fileRoutes.push(...registeredRoutes.routes);
|
|
1171
|
+
const frameworkRoutes = extractFrameworkRouteEvidence(file.path, content, pathLookup);
|
|
1172
|
+
calls.push(...frameworkRoutes.calls);
|
|
1173
|
+
fileRoutes.push(...frameworkRoutes.routes);
|
|
1330
1174
|
}
|
|
1331
1175
|
fileRoutes.push(...extractConventionalRouteEvidence(file.path, content));
|
|
1332
1176
|
if (fileRoutes.length === 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GraphEdge } from "
|
|
1
|
+
import type { GraphEdge } from "@audit-tools/shared";
|
|
2
2
|
export declare function extractPackageEntrypointEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|
|
3
3
|
export declare function extractPackageScriptEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|
|
4
4
|
export declare function isGoWorkspaceManifestPath(path: string): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GraphEdge } from "
|
|
1
|
+
import type { GraphEdge } from "@audit-tools/shared";
|
|
2
2
|
export declare function normalizeGraphPath(path: string): string;
|
|
3
3
|
export declare function graphLookupKey(path: string): string;
|
|
4
4
|
export declare function resolveCandidate(candidate: string, pathLookup: Map<string, string>): string | undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GraphEdge } from "@audit-tools/shared";
|
|
2
|
+
export declare function isPythonSourcePath(path: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a single `import <spec>` module specifier to a repo file, or
|
|
5
|
+
* undefined. Shared with the tree-sitter Python analyzer so AST-extracted
|
|
6
|
+
* imports resolve to exactly the same targets as the regex floor.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePythonImportTarget(fromPath: string, specifier: string, pathLookup: Map<string, string>): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a `from <module> import <names>` statement to repo files. Mirrors the
|
|
11
|
+
* floor: prefer submodule files (`module.name`), else the module itself. Shared
|
|
12
|
+
* with the tree-sitter Python analyzer.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolvePythonFromImportTargets(fromPath: string, moduleSpecifier: string, importedNames: string[], pathLookup: Map<string, string>): Array<{
|
|
15
|
+
specifier: string;
|
|
16
|
+
target: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function extractPythonImportEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|