auditor-lambda 0.10.3 → 0.10.7
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-build.mjs +198 -0
- package/audit-code-wrapper-install-hosts.mjs +1140 -0
- package/audit-code-wrapper-io.mjs +155 -0
- package/audit-code-wrapper-legacy.mjs +125 -0
- package/audit-code-wrapper-lib.mjs +17 -1801
- package/audit-code-wrapper-opencode.mjs +256 -0
- package/dispatch/merge-results.mjs +5 -3
- package/dispatch/validate-result.mjs +2 -2
- package/dist/adapters/coverageSummary.js +6 -2
- package/dist/adapters/normalizeExternal.js +16 -1
- package/dist/adapters/npmAudit.js +20 -9
- package/dist/adapters/semgrep.js +26 -1
- package/dist/cli/advanceAuditCommand.d.ts +1 -0
- package/dist/cli/advanceAuditCommand.js +95 -0
- package/dist/cli/args.js +1 -2
- package/dist/cli/auditStep.js +2 -2
- package/dist/cli/cleanup.d.ts +11 -1
- package/dist/cli/cleanup.js +25 -5
- package/dist/cli/cleanupCommand.d.ts +1 -0
- package/dist/cli/cleanupCommand.js +24 -0
- package/dist/cli/dispatch.d.ts +55 -31
- package/dist/cli/dispatch.js +298 -241
- package/dist/cli/dispatchStatusCommand.d.ts +1 -0
- package/dist/cli/dispatchStatusCommand.js +68 -0
- package/dist/cli/explainTaskCommand.d.ts +1 -0
- package/dist/cli/explainTaskCommand.js +33 -0
- package/dist/cli/importExternalAnalyzerCommand.d.ts +1 -0
- package/dist/cli/importExternalAnalyzerCommand.js +20 -0
- package/dist/cli/ingestResultsCommand.d.ts +1 -0
- package/dist/cli/ingestResultsCommand.js +34 -0
- package/dist/cli/intakeCommand.d.ts +1 -0
- package/dist/cli/intakeCommand.js +17 -0
- package/dist/cli/lineIndex.js +19 -12
- package/dist/cli/nextStepCommand.d.ts +139 -0
- package/dist/cli/nextStepCommand.js +281 -232
- package/dist/cli/planCommand.d.ts +1 -0
- package/dist/cli/planCommand.js +16 -0
- package/dist/cli/prepareDispatchCommand.d.ts +1 -0
- package/dist/cli/prepareDispatchCommand.js +25 -0
- package/dist/cli/quotaCommand.d.ts +1 -0
- package/dist/cli/quotaCommand.js +56 -0
- package/dist/cli/requeueCommand.d.ts +1 -0
- package/dist/cli/requeueCommand.js +10 -0
- package/dist/cli/runToCompletion.js +451 -412
- package/dist/cli/sampleRunCommand.d.ts +1 -0
- package/dist/cli/sampleRunCommand.js +93 -0
- package/dist/cli/statusCommand.js +1 -1
- package/dist/cli/steps.js +4 -1
- package/dist/cli/submitPacketCommand.js +16 -15
- package/dist/cli/synthesizeCommand.d.ts +1 -0
- package/dist/cli/synthesizeCommand.js +15 -0
- package/dist/cli/updateRuntimeValidationCommand.d.ts +1 -0
- package/dist/cli/updateRuntimeValidationCommand.js +16 -0
- package/dist/cli/validateCommand.d.ts +1 -0
- package/dist/cli/validateCommand.js +41 -0
- package/dist/cli/validateResultCommand.d.ts +1 -0
- package/dist/cli/validateResultCommand.js +63 -0
- package/dist/cli/validateResultsCommand.d.ts +1 -0
- package/dist/cli/validateResultsCommand.js +31 -0
- package/dist/cli/workerRunCommand.d.ts +15 -1
- package/dist/cli/workerRunCommand.js +40 -4
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +21 -628
- package/dist/coverage.js +7 -3
- package/dist/extractors/analyzers/css.js +2 -2
- package/dist/extractors/analyzers/html.js +2 -2
- package/dist/extractors/analyzers/python.js +2 -2
- package/dist/extractors/analyzers/registry.js +17 -36
- package/dist/extractors/analyzers/treeSitter.d.ts +10 -1
- package/dist/extractors/analyzers/treeSitter.js +28 -6
- package/dist/extractors/analyzers/typescript.js +104 -85
- package/dist/extractors/browserExtension.js +4 -1
- package/dist/extractors/designAssessment.js +21 -21
- package/dist/extractors/fsIntake.js +34 -10
- package/dist/extractors/graph.js +17 -7
- package/dist/extractors/graphManifestEdges/cargo.d.ts +4 -0
- package/dist/extractors/graphManifestEdges/cargo.js +107 -0
- package/dist/extractors/graphManifestEdges/go.d.ts +5 -0
- package/dist/extractors/graphManifestEdges/go.js +151 -0
- package/dist/extractors/graphManifestEdges/index.d.ts +8 -0
- package/dist/extractors/graphManifestEdges/index.js +11 -0
- package/dist/extractors/graphManifestEdges/jsonc.d.ts +3 -0
- package/dist/extractors/graphManifestEdges/jsonc.js +97 -0
- package/dist/extractors/graphManifestEdges/maven.d.ts +3 -0
- package/dist/extractors/graphManifestEdges/maven.js +73 -0
- package/dist/extractors/graphManifestEdges/packageJson.d.ts +19 -0
- package/dist/extractors/graphManifestEdges/packageJson.js +204 -0
- package/dist/extractors/graphManifestEdges/pnpm.d.ts +2 -0
- package/dist/extractors/graphManifestEdges/pnpm.js +42 -0
- package/dist/extractors/graphManifestEdges/pyproject.d.ts +3 -0
- package/dist/extractors/graphManifestEdges/pyproject.js +83 -0
- package/dist/extractors/graphManifestEdges/toml.d.ts +4 -0
- package/dist/extractors/graphManifestEdges/toml.js +68 -0
- package/dist/extractors/graphManifestEdges/typescript.d.ts +3 -0
- package/dist/extractors/graphManifestEdges/typescript.js +56 -0
- package/dist/extractors/graphManifestEdges/workspace.d.ts +10 -0
- package/dist/extractors/graphManifestEdges/workspace.js +72 -0
- package/dist/extractors/graphManifestEdges/yaml.d.ts +3 -0
- package/dist/extractors/graphManifestEdges/yaml.js +59 -0
- package/dist/extractors/graphManifestEdges/yamlPaths.d.ts +4 -0
- package/dist/extractors/graphManifestEdges/yamlPaths.js +89 -0
- package/dist/extractors/graphPythonImports.js +4 -20
- package/dist/extractors/pathPatterns.js +3 -13
- package/dist/io/artifacts.d.ts +1 -1
- package/dist/io/artifacts.js +4 -1
- package/dist/io/runArtifacts.d.ts +8 -2
- package/dist/io/runArtifacts.js +103 -69
- package/dist/io/toolingManifest.js +2 -1
- package/dist/orchestrator/advance.js +36 -0
- package/dist/orchestrator/artifactFreshness.d.ts +1 -1
- package/dist/orchestrator/artifactFreshness.js +1 -1
- package/dist/orchestrator/artifactMetadata.js +5 -5
- package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
- package/dist/orchestrator/auditTaskUtils.js +8 -12
- package/dist/orchestrator/autoFixExecutor.js +40 -26
- package/dist/orchestrator/dependencyMap.js +1 -1
- package/dist/orchestrator/executorResult.d.ts +33 -0
- package/dist/orchestrator/executors.d.ts +7 -0
- package/dist/orchestrator/executors.js +24 -0
- package/dist/orchestrator/fileAnchors.js +42 -29
- package/dist/orchestrator/fileIntegrity.js +6 -1
- package/dist/orchestrator/flowCoverage.js +1 -2
- package/dist/orchestrator/flowPlanning.js +8 -4
- package/dist/orchestrator/graphEnrichmentExecutor.js +67 -45
- package/dist/orchestrator/ingestionExecutors.js +9 -1
- package/dist/orchestrator/intakeExecutors.d.ts +0 -4
- package/dist/orchestrator/intakeExecutors.js +24 -14
- package/dist/orchestrator/localCommands.d.ts +1 -0
- package/dist/orchestrator/localCommands.js +10 -17
- package/dist/orchestrator/nextStep.js +3 -1
- package/dist/orchestrator/requeueCommand.js +4 -0
- package/dist/orchestrator/reviewPacketGraph.js +50 -18
- package/dist/orchestrator/reviewPackets.js +10 -8
- package/dist/orchestrator/runtimeCommand.js +35 -7
- package/dist/orchestrator/runtimeValidationUpdate.js +6 -0
- package/dist/orchestrator/selectiveDeepening/highRiskClean.js +3 -2
- package/dist/orchestrator/selectiveDeepening/lensVerification.js +44 -18
- package/dist/orchestrator/staleness.js +3 -3
- package/dist/orchestrator/state.js +1 -1
- package/dist/orchestrator/syntaxResolutionExecutor.js +17 -24
- package/dist/orchestrator/synthesisExecutors.js +1 -0
- package/dist/orchestrator/taskBuilder.js +5 -4
- package/dist/providers/claudeCodeProvider.js +4 -1
- package/dist/providers/opencodeProvider.js +4 -1
- package/dist/quota/discoveredLimits.js +3 -3
- package/dist/quota/headerExtraction.js +5 -2
- package/dist/quota/headerExtractors/claudeCodeHeaderExtractor.js +3 -0
- package/dist/quota/headerExtractors/index.js +3 -3
- package/dist/quota/index.d.ts +3 -1
- package/dist/quota/index.js +3 -0
- package/dist/reporting/findingRanks.d.ts +3 -0
- package/dist/reporting/findingRanks.js +24 -0
- package/dist/reporting/mergeFindings.js +1 -24
- package/dist/reporting/synthesis.d.ts +3 -1
- package/dist/reporting/synthesis.js +30 -6
- package/dist/reporting/synthesisNarrativePrompt.js +3 -0
- package/dist/reporting/workBlocks.js +1 -14
- package/dist/supervisor/operatorHandoff.js +2 -6
- package/dist/supervisor/runLedger.js +30 -41
- package/dist/types/activeDispatch.d.ts +31 -0
- package/dist/types/activeDispatch.js +2 -0
- package/dist/types.d.ts +21 -4
- package/dist/types.js +24 -16
- package/dist/validation/artifacts.js +3 -0
- package/dist/validation/auditResults.js +8 -2
- package/package.json +2 -2
- package/schemas/audit_findings.schema.json +5 -1
- package/schemas/audit_plan_metrics.schema.json +1 -1
- package/schemas/audit_result.schema.json +5 -6
- package/schemas/audit_task.schema.json +1 -4
- package/schemas/blind_spot_register.schema.json +1 -1
- package/schemas/coverage_matrix.schema.json +2 -8
- package/schemas/finding.schema.json +1 -16
- package/schemas/flow_coverage.schema.json +2 -8
- package/schemas/graph_bundle.schema.json +31 -0
- package/schemas/lens.schema.json +7 -0
- package/schemas/review_packets.schema.json +6 -17
- package/schemas/step_contract.schema.json +8 -2
- package/schemas/unit_manifest.schema.json +1 -4
- package/scripts/postinstall.mjs +3 -1
- package/skills/audit-code/audit-code.prompt.md +2 -3
- package/dist/extractors/graphManifestEdges.d.ts +0 -12
- package/dist/extractors/graphManifestEdges.js +0 -1135
package/dist/coverage.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
function buildFileIndex(matrix) {
|
|
2
|
+
return new Map(matrix.files.map((f) => [f.path, f]));
|
|
3
|
+
}
|
|
1
4
|
export function createCoverageMatrix(paths) {
|
|
2
5
|
return {
|
|
3
6
|
files: paths.map((path) => ({
|
|
@@ -11,7 +14,7 @@ export function createCoverageMatrix(paths) {
|
|
|
11
14
|
};
|
|
12
15
|
}
|
|
13
16
|
export function markExcludedPath(matrix, path, classificationStatus) {
|
|
14
|
-
const record = matrix.
|
|
17
|
+
const record = buildFileIndex(matrix).get(path);
|
|
15
18
|
if (!record)
|
|
16
19
|
return;
|
|
17
20
|
record.classification_status = classificationStatus;
|
|
@@ -21,7 +24,7 @@ export function markExcludedPath(matrix, path, classificationStatus) {
|
|
|
21
24
|
record.unit_ids = [];
|
|
22
25
|
}
|
|
23
26
|
export function applyUnitCoverage(matrix, path, unitId, requiredLenses) {
|
|
24
|
-
const record = matrix.
|
|
27
|
+
const record = buildFileIndex(matrix).get(path);
|
|
25
28
|
if (!record || record.audit_status === "excluded")
|
|
26
29
|
return;
|
|
27
30
|
if (!record.unit_ids.includes(unitId)) {
|
|
@@ -33,8 +36,9 @@ export function applyUnitCoverage(matrix, path, unitId, requiredLenses) {
|
|
|
33
36
|
];
|
|
34
37
|
}
|
|
35
38
|
export function applyFileCoverage(matrix, fileCoverage) {
|
|
39
|
+
const index = buildFileIndex(matrix);
|
|
36
40
|
for (const coverage of fileCoverage) {
|
|
37
|
-
const record =
|
|
41
|
+
const record = index.get(coverage.path);
|
|
38
42
|
if (!record || record.audit_status === "excluded")
|
|
39
43
|
continue;
|
|
40
44
|
if (coverage.lens &&
|
|
@@ -87,8 +87,8 @@ async function analyze(files, context) {
|
|
|
87
87
|
const tree = parser.parse(content);
|
|
88
88
|
collectFileEdges(normalizeGraphPath(file), tree.rootNode, context.pathLookup, edges);
|
|
89
89
|
}
|
|
90
|
-
catch {
|
|
91
|
-
|
|
90
|
+
catch (e) {
|
|
91
|
+
process.stderr.write(`[audit-code] css-analyzer: parse failed for '${file}': ${e.message ?? String(e)}\n`);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
return { edges };
|
|
@@ -78,8 +78,8 @@ async function analyze(files, context) {
|
|
|
78
78
|
const tree = parser.parse(content);
|
|
79
79
|
collectFileEdges(normalizeGraphPath(file), tree.rootNode, context.pathLookup, edges);
|
|
80
80
|
}
|
|
81
|
-
catch {
|
|
82
|
-
|
|
81
|
+
catch (e) {
|
|
82
|
+
process.stderr.write(`[audit-code] html-analyzer: parse failed for '${file}': ${e.message ?? String(e)}\n`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
return { edges };
|
|
@@ -90,8 +90,8 @@ async function analyze(files, context) {
|
|
|
90
90
|
const tree = parser.parse(content);
|
|
91
91
|
collectFileEdges(normalizeGraphPath(file), tree.rootNode, context.pathLookup, edges);
|
|
92
92
|
}
|
|
93
|
-
catch {
|
|
94
|
-
|
|
93
|
+
catch (e) {
|
|
94
|
+
process.stderr.write(`[audit-code] python-analyzer: parse failed for '${file}': ${e.message ?? String(e)}\n`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
return { edges };
|
|
@@ -24,6 +24,18 @@ export function getAnalyzerById(id) {
|
|
|
24
24
|
function settingFor(analyzers, id) {
|
|
25
25
|
return analyzers?.[id] ?? "auto";
|
|
26
26
|
}
|
|
27
|
+
function makeEntry(analyzer, setting, supportedCount, resolution, path) {
|
|
28
|
+
const entry = {
|
|
29
|
+
id: analyzer.id,
|
|
30
|
+
dependency: analyzer.dependency,
|
|
31
|
+
setting,
|
|
32
|
+
resolution,
|
|
33
|
+
supportedCount,
|
|
34
|
+
};
|
|
35
|
+
if (path !== undefined)
|
|
36
|
+
entry.path = path;
|
|
37
|
+
return entry;
|
|
38
|
+
}
|
|
27
39
|
/**
|
|
28
40
|
* Deterministically resolve, without installing anything, how each registered
|
|
29
41
|
* analyzer would run for this repo. The conversation-first CLI uses this to
|
|
@@ -42,51 +54,20 @@ export function resolveAnalyzerPlan(root, analyzers, includedFiles, options = {}
|
|
|
42
54
|
const setting = settingFor(analyzers, analyzer.id);
|
|
43
55
|
const supportedCount = includedFiles.filter((file) => analyzer.supports(file)).length;
|
|
44
56
|
if (supportedCount === 0) {
|
|
45
|
-
return
|
|
46
|
-
id: analyzer.id,
|
|
47
|
-
dependency: analyzer.dependency,
|
|
48
|
-
setting,
|
|
49
|
-
resolution: "not_applicable",
|
|
50
|
-
supportedCount,
|
|
51
|
-
};
|
|
57
|
+
return makeEntry(analyzer, setting, supportedCount, "not_applicable");
|
|
52
58
|
}
|
|
53
59
|
if (setting === "skip") {
|
|
54
|
-
return
|
|
55
|
-
id: analyzer.id,
|
|
56
|
-
dependency: analyzer.dependency,
|
|
57
|
-
setting,
|
|
58
|
-
resolution: "skip",
|
|
59
|
-
supportedCount,
|
|
60
|
-
};
|
|
60
|
+
return makeEntry(analyzer, setting, supportedCount, "skip");
|
|
61
61
|
}
|
|
62
62
|
if (!analyzer.dependency) {
|
|
63
63
|
// No dependency required: always available.
|
|
64
|
-
return
|
|
65
|
-
id: analyzer.id,
|
|
66
|
-
dependency: analyzer.dependency,
|
|
67
|
-
setting,
|
|
68
|
-
resolution: "repo",
|
|
69
|
-
supportedCount,
|
|
70
|
-
};
|
|
64
|
+
return makeEntry(analyzer, setting, supportedCount, "repo");
|
|
71
65
|
}
|
|
72
66
|
const resolved = resolveAnalyzerDep(analyzer.dependency, root, depOptions);
|
|
73
67
|
if (resolved.via === "repo" || resolved.via === "cache") {
|
|
74
|
-
return
|
|
75
|
-
id: analyzer.id,
|
|
76
|
-
dependency: analyzer.dependency,
|
|
77
|
-
setting,
|
|
78
|
-
resolution: resolved.via,
|
|
79
|
-
path: resolved.path,
|
|
80
|
-
supportedCount,
|
|
81
|
-
};
|
|
68
|
+
return makeEntry(analyzer, setting, supportedCount, resolved.via, resolved.path);
|
|
82
69
|
}
|
|
83
|
-
return
|
|
84
|
-
id: analyzer.id,
|
|
85
|
-
dependency: analyzer.dependency,
|
|
86
|
-
setting,
|
|
87
|
-
resolution: "absent",
|
|
88
|
-
supportedCount,
|
|
89
|
-
};
|
|
70
|
+
return makeEntry(analyzer, setting, supportedCount, "absent");
|
|
90
71
|
});
|
|
91
72
|
}
|
|
92
73
|
/**
|
|
@@ -25,10 +25,19 @@ export interface TsParser {
|
|
|
25
25
|
setLanguage(language: TsLanguage): void;
|
|
26
26
|
parse(source: string): TsTree;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Return the number of times tree-sitter fell back to the regex floor since
|
|
30
|
+
* process start (or since the last `__resetTreeSitterForTests` call). Each
|
|
31
|
+
* distinct failure path (import failure, init failure, grammar load failure,
|
|
32
|
+
* parser instantiation failure) increments the counter once. A caller can
|
|
33
|
+
* snapshot this before and after a batch parse pass to detect systemic
|
|
34
|
+
* degradation without scraping stderr.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getTreeSitterDegradationCount(): number;
|
|
28
37
|
/**
|
|
29
38
|
* Obtain a parser bound to `grammar` (e.g. "python", "html", "css"), or
|
|
30
39
|
* `undefined` if web-tree-sitter or the grammar wasm cannot be loaded.
|
|
31
40
|
*/
|
|
32
41
|
export declare function getTreeSitterParser(grammar: string, dependencyPath?: string): Promise<TsParser | undefined>;
|
|
33
|
-
/** Test seam: reset the memoised runtime/grammar caches. */
|
|
42
|
+
/** Test seam: reset the memoised runtime/grammar caches and degradation counter. */
|
|
34
43
|
export declare function __resetTreeSitterForTests(): void;
|
|
@@ -2,6 +2,18 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
const requireFromHere = createRequire(import.meta.url);
|
|
5
|
+
let _degradationCount = 0;
|
|
6
|
+
/**
|
|
7
|
+
* Return the number of times tree-sitter fell back to the regex floor since
|
|
8
|
+
* process start (or since the last `__resetTreeSitterForTests` call). Each
|
|
9
|
+
* distinct failure path (import failure, init failure, grammar load failure,
|
|
10
|
+
* parser instantiation failure) increments the counter once. A caller can
|
|
11
|
+
* snapshot this before and after a batch parse pass to detect systemic
|
|
12
|
+
* degradation without scraping stderr.
|
|
13
|
+
*/
|
|
14
|
+
export function getTreeSitterDegradationCount() {
|
|
15
|
+
return _degradationCount;
|
|
16
|
+
}
|
|
5
17
|
// The parser module is resolved per `dependencyPath`: a call with a different
|
|
6
18
|
// dependencyPath must resolve its own module rather than reusing the first
|
|
7
19
|
// resolution. Keyed by `dependencyPath ?? ""` so the bare-specifier path
|
|
@@ -31,6 +43,7 @@ async function importParserModule(dependencyPath) {
|
|
|
31
43
|
}
|
|
32
44
|
}
|
|
33
45
|
catch (e) {
|
|
46
|
+
_degradationCount += 1;
|
|
34
47
|
process.stderr.write(`[audit-code] tree-sitter: failed to import '${specifier}': ${e.message ?? String(e)}\n`);
|
|
35
48
|
}
|
|
36
49
|
}
|
|
@@ -51,6 +64,7 @@ async function ensureInit(parserModule) {
|
|
|
51
64
|
cached = parserModule.Parser.init()
|
|
52
65
|
.then(() => true)
|
|
53
66
|
.catch((e) => {
|
|
67
|
+
_degradationCount += 1;
|
|
54
68
|
process.stderr.write(`[audit-code] tree-sitter: Parser.init() failed: ${e.message ?? String(e)}\n`);
|
|
55
69
|
return false;
|
|
56
70
|
});
|
|
@@ -75,22 +89,28 @@ function resolveGrammarPath(grammar) {
|
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
async function loadLanguage(parserModule, grammar) {
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
let moduleMap = languageCache.get(parserModule);
|
|
93
|
+
if (!moduleMap) {
|
|
94
|
+
moduleMap = new Map();
|
|
95
|
+
languageCache.set(parserModule, moduleMap);
|
|
96
|
+
}
|
|
97
|
+
if (moduleMap.has(grammar)) {
|
|
98
|
+
return moduleMap.get(grammar) ?? undefined;
|
|
80
99
|
}
|
|
81
100
|
const grammarPath = resolveGrammarPath(grammar);
|
|
82
101
|
if (!grammarPath) {
|
|
83
|
-
|
|
102
|
+
moduleMap.set(grammar, null);
|
|
84
103
|
return undefined;
|
|
85
104
|
}
|
|
86
105
|
try {
|
|
87
106
|
const language = await parserModule.Language.load(grammarPath);
|
|
88
|
-
|
|
107
|
+
moduleMap.set(grammar, language);
|
|
89
108
|
return language;
|
|
90
109
|
}
|
|
91
110
|
catch (e) {
|
|
111
|
+
_degradationCount += 1;
|
|
92
112
|
process.stderr.write(`[audit-code] tree-sitter: failed to load grammar '${grammar}' from '${grammarPath}': ${e.message ?? String(e)}\n`);
|
|
93
|
-
|
|
113
|
+
moduleMap.set(grammar, null);
|
|
94
114
|
return undefined;
|
|
95
115
|
}
|
|
96
116
|
}
|
|
@@ -113,13 +133,15 @@ export async function getTreeSitterParser(grammar, dependencyPath) {
|
|
|
113
133
|
return parser;
|
|
114
134
|
}
|
|
115
135
|
catch (e) {
|
|
136
|
+
_degradationCount += 1;
|
|
116
137
|
process.stderr.write(`[audit-code] tree-sitter: failed to instantiate parser for grammar '${grammar}': ${e.message ?? String(e)}\n`);
|
|
117
138
|
return undefined;
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
|
-
/** Test seam: reset the memoised runtime/grammar caches. */
|
|
141
|
+
/** Test seam: reset the memoised runtime/grammar caches and degradation counter. */
|
|
121
142
|
export function __resetTreeSitterForTests() {
|
|
122
143
|
moduleCache.clear();
|
|
123
144
|
initCache.clear();
|
|
124
145
|
languageCache.clear();
|
|
146
|
+
_degradationCount = 0;
|
|
125
147
|
}
|
|
@@ -112,104 +112,123 @@ function resolveSymbolToIncluded(state, symbol) {
|
|
|
112
112
|
}
|
|
113
113
|
return undefined;
|
|
114
114
|
}
|
|
115
|
-
function
|
|
115
|
+
function recordCallEdge(calls, callTargets, fromPath, target) {
|
|
116
|
+
if (!target || target === fromPath || callTargets.has(target))
|
|
117
|
+
return;
|
|
118
|
+
callTargets.add(target);
|
|
119
|
+
calls.push(graphEdge({
|
|
120
|
+
from: fromPath,
|
|
121
|
+
to: target,
|
|
122
|
+
kind: "ts-call",
|
|
123
|
+
confidence: TS_CALL_EDGE_CONFIDENCE,
|
|
124
|
+
reason: `TypeScript checker resolved a cross-file call into '${target}'.`,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
function visitHeritageNode(state, references, fromPath, node) {
|
|
116
128
|
const ts = state.ts;
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
130
|
+
const isExtends = clause.token === ts.SyntaxKind.ExtendsKeyword;
|
|
131
|
+
for (const typeNode of clause.types) {
|
|
132
|
+
const target = resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(typeNode.expression));
|
|
133
|
+
if (!target || target === fromPath)
|
|
134
|
+
continue;
|
|
135
|
+
references.push(graphEdge({
|
|
136
|
+
from: fromPath,
|
|
137
|
+
to: target,
|
|
138
|
+
kind: isExtends ? "ts-extends" : "ts-implements",
|
|
139
|
+
confidence: isExtends
|
|
140
|
+
? TS_EXTENDS_EDGE_CONFIDENCE
|
|
141
|
+
: TS_IMPLEMENTS_EDGE_CONFIDENCE,
|
|
142
|
+
reason: `TypeScript ${isExtends ? "extends" : "implements"} heritage resolves to '${target}'.`,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function visitImportDeclaration(state, imports, fromPath, node) {
|
|
148
|
+
if (!state.ts.isStringLiteral(node.moduleSpecifier))
|
|
149
|
+
return;
|
|
150
|
+
const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, node.getSourceFile().fileName);
|
|
151
|
+
if (target && target !== fromPath) {
|
|
152
|
+
imports.push(graphEdge({
|
|
123
153
|
from: fromPath,
|
|
124
154
|
to: target,
|
|
125
|
-
kind: "ts-
|
|
126
|
-
confidence:
|
|
127
|
-
reason: `TypeScript
|
|
155
|
+
kind: "ts-import",
|
|
156
|
+
confidence: TS_IMPORT_EDGE_CONFIDENCE,
|
|
157
|
+
reason: `TypeScript resolved import '${node.moduleSpecifier.text}' to '${target}'.`,
|
|
128
158
|
}));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function visitExportDeclaration(state, imports, fromPath, node) {
|
|
162
|
+
if (!node.moduleSpecifier || !state.ts.isStringLiteral(node.moduleSpecifier))
|
|
163
|
+
return;
|
|
164
|
+
const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, node.getSourceFile().fileName);
|
|
165
|
+
if (target && target !== fromPath) {
|
|
166
|
+
imports.push(graphEdge({
|
|
167
|
+
from: fromPath,
|
|
168
|
+
to: target,
|
|
169
|
+
kind: "ts-reexport",
|
|
170
|
+
confidence: TS_REEXPORT_EDGE_CONFIDENCE,
|
|
171
|
+
reason: `TypeScript resolved re-export '${node.moduleSpecifier.text}' to '${target}'.`,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function visitImportEqualsDeclaration(state, imports, fromPath, node) {
|
|
176
|
+
const ts = state.ts;
|
|
177
|
+
if (!ts.isExternalModuleReference(node.moduleReference) ||
|
|
178
|
+
!ts.isStringLiteral(node.moduleReference.expression)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const target = resolveSpecifierTarget(state, node.moduleReference.expression.text, node.getSourceFile().fileName);
|
|
182
|
+
if (target && target !== fromPath) {
|
|
183
|
+
imports.push(graphEdge({
|
|
184
|
+
from: fromPath,
|
|
185
|
+
to: target,
|
|
186
|
+
kind: "ts-import",
|
|
187
|
+
confidence: TS_IMPORT_EDGE_CONFIDENCE,
|
|
188
|
+
reason: `TypeScript resolved import-equals to '${target}'.`,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function visitCallExpression(state, imports, calls, callTargets, fromPath, node) {
|
|
193
|
+
const ts = state.ts;
|
|
194
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
195
|
+
node.arguments[0] &&
|
|
196
|
+
ts.isStringLiteral(node.arguments[0])) {
|
|
197
|
+
const target = resolveSpecifierTarget(state, node.arguments[0].text, node.getSourceFile().fileName);
|
|
198
|
+
if (target && target !== fromPath) {
|
|
199
|
+
imports.push(graphEdge({
|
|
200
|
+
from: fromPath,
|
|
201
|
+
to: target,
|
|
202
|
+
kind: "ts-import",
|
|
203
|
+
confidence: TS_IMPORT_EDGE_CONFIDENCE,
|
|
204
|
+
reason: `TypeScript resolved dynamic import to '${target}'.`,
|
|
205
|
+
}));
|
|
147
206
|
}
|
|
148
|
-
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
recordCallEdge(calls, callTargets, fromPath, resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(node.expression)));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function collectFileEdges(state, sourceFile, fromPath, imports, references, calls) {
|
|
213
|
+
const ts = state.ts;
|
|
214
|
+
const callTargets = new Set();
|
|
149
215
|
const visit = (node) => {
|
|
150
|
-
if (ts.isImportDeclaration(node)
|
|
151
|
-
|
|
152
|
-
if (target && target !== fromPath) {
|
|
153
|
-
imports.push(graphEdge({
|
|
154
|
-
from: fromPath,
|
|
155
|
-
to: target,
|
|
156
|
-
kind: "ts-import",
|
|
157
|
-
confidence: TS_IMPORT_EDGE_CONFIDENCE,
|
|
158
|
-
reason: `TypeScript resolved import '${node.moduleSpecifier.text}' to '${target}'.`,
|
|
159
|
-
}));
|
|
160
|
-
}
|
|
216
|
+
if (ts.isImportDeclaration(node)) {
|
|
217
|
+
visitImportDeclaration(state, imports, fromPath, node);
|
|
161
218
|
}
|
|
162
|
-
else if (ts.isExportDeclaration(node)
|
|
163
|
-
node
|
|
164
|
-
ts.isStringLiteral(node.moduleSpecifier)) {
|
|
165
|
-
const target = resolveSpecifierTarget(state, node.moduleSpecifier.text, sourceFile.fileName);
|
|
166
|
-
if (target && target !== fromPath) {
|
|
167
|
-
imports.push(graphEdge({
|
|
168
|
-
from: fromPath,
|
|
169
|
-
to: target,
|
|
170
|
-
kind: "ts-reexport",
|
|
171
|
-
confidence: TS_REEXPORT_EDGE_CONFIDENCE,
|
|
172
|
-
reason: `TypeScript resolved re-export '${node.moduleSpecifier.text}' to '${target}'.`,
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
219
|
+
else if (ts.isExportDeclaration(node)) {
|
|
220
|
+
visitExportDeclaration(state, imports, fromPath, node);
|
|
175
221
|
}
|
|
176
|
-
else if (ts.isImportEqualsDeclaration(node)
|
|
177
|
-
|
|
178
|
-
ts.isStringLiteral(node.moduleReference.expression)) {
|
|
179
|
-
const target = resolveSpecifierTarget(state, node.moduleReference.expression.text, sourceFile.fileName);
|
|
180
|
-
if (target && target !== fromPath) {
|
|
181
|
-
imports.push(graphEdge({
|
|
182
|
-
from: fromPath,
|
|
183
|
-
to: target,
|
|
184
|
-
kind: "ts-import",
|
|
185
|
-
confidence: TS_IMPORT_EDGE_CONFIDENCE,
|
|
186
|
-
reason: `TypeScript resolved import-equals to '${target}'.`,
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
222
|
+
else if (ts.isImportEqualsDeclaration(node)) {
|
|
223
|
+
visitImportEqualsDeclaration(state, imports, fromPath, node);
|
|
189
224
|
}
|
|
190
225
|
else if (ts.isCallExpression(node)) {
|
|
191
|
-
|
|
192
|
-
node.arguments[0] &&
|
|
193
|
-
ts.isStringLiteral(node.arguments[0])) {
|
|
194
|
-
const target = resolveSpecifierTarget(state, node.arguments[0].text, sourceFile.fileName);
|
|
195
|
-
if (target && target !== fromPath) {
|
|
196
|
-
imports.push(graphEdge({
|
|
197
|
-
from: fromPath,
|
|
198
|
-
to: target,
|
|
199
|
-
kind: "ts-import",
|
|
200
|
-
confidence: TS_IMPORT_EDGE_CONFIDENCE,
|
|
201
|
-
reason: `TypeScript resolved dynamic import to '${target}'.`,
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
recordCall(resolveSymbolToIncluded(state, state.checker.getSymbolAtLocation(node.expression)));
|
|
207
|
-
}
|
|
226
|
+
visitCallExpression(state, imports, calls, callTargets, fromPath, node);
|
|
208
227
|
}
|
|
209
228
|
else if (ts.isClassDeclaration(node) ||
|
|
210
229
|
ts.isClassExpression(node) ||
|
|
211
230
|
ts.isInterfaceDeclaration(node)) {
|
|
212
|
-
|
|
231
|
+
visitHeritageNode(state, references, fromPath, node);
|
|
213
232
|
}
|
|
214
233
|
ts.forEachChild(node, visit);
|
|
215
234
|
};
|
|
@@ -247,7 +266,7 @@ async function analyze(files, context) {
|
|
|
247
266
|
return { edges: [...imports, ...references, ...calls] };
|
|
248
267
|
}
|
|
249
268
|
catch (e) {
|
|
250
|
-
process.stderr.write(`[audit-code] typescript-analyzer: program analysis failed for ${files.length} file(s) under '${context.root}', degrading to regex floor: ${e.message ?? String(e)}\n`);
|
|
269
|
+
process.stderr.write(`[audit-code] typescript-analyzer: program analysis failed for ${files.length} file(s) under '${context.root}' — returning 0 edges (all ${files.length} file(s) edges lost), degrading to regex floor: ${e.message ?? String(e)}\n`);
|
|
251
270
|
return { edges: [] };
|
|
252
271
|
}
|
|
253
272
|
}
|
|
@@ -279,7 +279,10 @@ export function extractHtmlResourceEdges(fromPath, content, pathLookup) {
|
|
|
279
279
|
return edges;
|
|
280
280
|
}
|
|
281
281
|
export function hasBrowserExtensionManifestFile(repoManifest) {
|
|
282
|
-
|
|
282
|
+
// Detect a `manifest.json` anywhere in the tree (root or a subdirectory),
|
|
283
|
+
// using the same basename match as isBrowserExtensionManifestPath so the
|
|
284
|
+
// repo-level gate and the per-file classifier agree.
|
|
285
|
+
return repoManifest.files.some((file) => isBrowserExtensionManifestPath(file.path));
|
|
283
286
|
}
|
|
284
287
|
export function deriveBrowserExtensionLensesForPath(path) {
|
|
285
288
|
const normalized = normalizeGraphPath(path).toLowerCase();
|
|
@@ -18,6 +18,25 @@ function allEdges(graphBundle) {
|
|
|
18
18
|
}
|
|
19
19
|
return edges;
|
|
20
20
|
}
|
|
21
|
+
function dfsVisit(node, path, adjacency, visited, stack, cycles) {
|
|
22
|
+
if (stack.has(node)) {
|
|
23
|
+
const cycleStart = path.indexOf(node);
|
|
24
|
+
if (cycleStart >= 0) {
|
|
25
|
+
cycles.push(path.slice(cycleStart));
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (visited.has(node))
|
|
30
|
+
return;
|
|
31
|
+
visited.add(node);
|
|
32
|
+
stack.add(node);
|
|
33
|
+
path.push(node);
|
|
34
|
+
for (const neighbor of adjacency.get(node) ?? []) {
|
|
35
|
+
dfsVisit(neighbor, path, adjacency, visited, stack, cycles);
|
|
36
|
+
}
|
|
37
|
+
path.pop();
|
|
38
|
+
stack.delete(node);
|
|
39
|
+
}
|
|
21
40
|
function detectCycles(edges) {
|
|
22
41
|
const adjacency = new Map();
|
|
23
42
|
for (const edge of edges) {
|
|
@@ -28,27 +47,8 @@ function detectCycles(edges) {
|
|
|
28
47
|
const cycles = [];
|
|
29
48
|
const visited = new Set();
|
|
30
49
|
const stack = new Set();
|
|
31
|
-
function dfs(node, path) {
|
|
32
|
-
if (stack.has(node)) {
|
|
33
|
-
const cycleStart = path.indexOf(node);
|
|
34
|
-
if (cycleStart >= 0) {
|
|
35
|
-
cycles.push(path.slice(cycleStart));
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (visited.has(node))
|
|
40
|
-
return;
|
|
41
|
-
visited.add(node);
|
|
42
|
-
stack.add(node);
|
|
43
|
-
path.push(node);
|
|
44
|
-
for (const neighbor of adjacency.get(node) ?? []) {
|
|
45
|
-
dfs(neighbor, path);
|
|
46
|
-
}
|
|
47
|
-
path.pop();
|
|
48
|
-
stack.delete(node);
|
|
49
|
-
}
|
|
50
50
|
for (const node of adjacency.keys()) {
|
|
51
|
-
|
|
51
|
+
dfsVisit(node, [], adjacency, visited, stack, cycles);
|
|
52
52
|
}
|
|
53
53
|
return cycles;
|
|
54
54
|
}
|
|
@@ -195,7 +195,7 @@ function detectUnitSprawl(unitManifest, nextId) {
|
|
|
195
195
|
return [];
|
|
196
196
|
const fileCounts = unitManifest.units.map((u) => u.files.length);
|
|
197
197
|
const totalFiles = fileCounts.reduce((a, b) => a + b, 0);
|
|
198
|
-
const maxFiles =
|
|
198
|
+
const maxFiles = fileCounts.reduce((max, n) => n > max ? n : max, 0);
|
|
199
199
|
const findings = [];
|
|
200
200
|
const dominantUnit = unitManifest.units.find((u) => u.files.length === maxFiles);
|
|
201
201
|
if (dominantUnit && maxFiles > totalFiles * 0.5 && totalFiles > 10) {
|
|
@@ -35,25 +35,43 @@ async function maybeHashFile(path, enabled) {
|
|
|
35
35
|
const content = await readFile(path);
|
|
36
36
|
return createHash("sha256").update(content).digest("hex");
|
|
37
37
|
}
|
|
38
|
-
async function walk(
|
|
39
|
-
|
|
38
|
+
async function walk(ctx, current, results) {
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.warn(`[fsIntake] skipping unreadable directory: ${current} (${err.message})`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
40
47
|
for (const entry of entries) {
|
|
41
48
|
const absolutePath = join(current, entry.name);
|
|
42
|
-
const relativePath = normalizePath(relative(root, absolutePath));
|
|
43
|
-
if (!relativePath || shouldIgnore(relativePath, ignores)) {
|
|
49
|
+
const relativePath = normalizePath(relative(ctx.root, absolutePath));
|
|
50
|
+
if (!relativePath || shouldIgnore(relativePath, ctx.ignores)) {
|
|
44
51
|
continue;
|
|
45
52
|
}
|
|
46
53
|
if (entry.isDirectory()) {
|
|
47
|
-
await walk(
|
|
54
|
+
await walk(ctx, absolutePath, results);
|
|
48
55
|
continue;
|
|
49
56
|
}
|
|
50
57
|
if (!entry.isFile()) {
|
|
51
58
|
continue;
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
let info;
|
|
61
|
+
try {
|
|
62
|
+
info = await stat(absolutePath);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.warn(`[fsIntake] skipping unreadable file: ${relativePath} (${err.message})`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
let hash;
|
|
69
|
+
if (info.size <= ctx.maxFileSizeBytes) {
|
|
70
|
+
hash = await maybeHashFile(absolutePath, ctx.hashFiles);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.warn(`[fsIntake] skipping oversized file: ${relativePath} (${info.size} bytes > ${ctx.maxFileSizeBytes} limit)`);
|
|
74
|
+
}
|
|
57
75
|
results.push({
|
|
58
76
|
path: relativePath,
|
|
59
77
|
size_bytes: info.size,
|
|
@@ -65,6 +83,12 @@ export async function buildRepoManifestFromFs(options) {
|
|
|
65
83
|
const root = resolve(options.root);
|
|
66
84
|
const ignore = [...DEFAULT_IGNORES, ...(options.ignore ?? [])];
|
|
67
85
|
const files = [];
|
|
68
|
-
|
|
86
|
+
const ctx = {
|
|
87
|
+
root,
|
|
88
|
+
ignores: ignore,
|
|
89
|
+
hashFiles: options.hash_files ?? false,
|
|
90
|
+
maxFileSizeBytes: options.max_file_size_bytes ?? 1024 * 1024,
|
|
91
|
+
};
|
|
92
|
+
await walk(ctx, root, files);
|
|
69
93
|
return buildRepoManifest(root.split(/[\\/]/).pop() ?? "repo", files);
|
|
70
94
|
}
|