auditor-lambda 0.2.1 → 0.2.3
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 +229 -289
- package/dist/adapters/eslint.js +4 -2
- package/dist/adapters/npmAudit.js +1 -1
- package/dist/cli.js +82 -45
- package/dist/extractors/bucketing.js +14 -35
- package/dist/extractors/disposition.js +8 -9
- package/dist/extractors/fileInventory.js +0 -2
- package/dist/extractors/flows.js +14 -23
- package/dist/extractors/fsIntake.js +4 -1
- package/dist/extractors/pathPatterns.d.ts +19 -0
- package/dist/extractors/pathPatterns.js +91 -0
- package/dist/extractors/surfaces.js +2 -7
- package/dist/io/artifacts.d.ts +23 -1
- package/dist/io/artifacts.js +4 -3
- package/dist/io/runArtifacts.js +1 -1
- package/dist/orchestrator/advance.js +53 -71
- package/dist/orchestrator/flowCoverage.js +1 -2
- package/dist/orchestrator/internalExecutors.js +4 -6
- package/dist/orchestrator/planning.js +12 -20
- package/dist/orchestrator/resultIngestion.js +3 -2
- package/dist/orchestrator/runtimeValidation.js +5 -0
- package/dist/orchestrator/syntaxResolutionExecutor.js +10 -2
- package/dist/orchestrator/taskBuilder.js +15 -28
- package/dist/prompts/renderWorkerPrompt.js +2 -1
- package/dist/providers/claudeCodeProvider.js +1 -2
- package/dist/providers/constants.d.ts +1 -0
- package/dist/providers/constants.js +1 -0
- package/dist/providers/index.js +7 -3
- package/dist/providers/opencodeProvider.js +1 -6
- package/dist/providers/spawnLoggedCommand.js +4 -0
- package/dist/providers/types.d.ts +0 -1
- package/dist/supervisor/operatorHandoff.d.ts +2 -0
- package/dist/supervisor/operatorHandoff.js +21 -9
- package/dist/supervisor/runLedger.js +7 -8
- package/dist/supervisor/sessionConfig.js +1 -0
- package/dist/types/flowCoverage.d.ts +1 -1
- package/dist/types/runLedger.d.ts +1 -1
- package/dist/types/runtimeValidation.d.ts +2 -1
- package/dist/types/sessionConfig.d.ts +0 -6
- package/dist/types/surfaces.d.ts +2 -1
- package/dist/types/workerSession.d.ts +2 -0
- package/dist/types.d.ts +0 -1
- package/dist/validation/sessionConfig.js +1 -15
- package/package.json +1 -1
- package/schemas/audit-code-v1alpha1.schema.json +4 -0
- package/schemas/audit_result.schema.json +9 -3
- package/schemas/audit_state.schema.json +2 -2
- package/schemas/audit_task.schema.json +14 -3
- package/schemas/blind_spot_register.schema.json +13 -3
- package/schemas/coverage_matrix.schema.json +16 -4
- package/schemas/critical_flows.schema.json +6 -3
- package/schemas/external_analyzer_results.schema.json +10 -4
- package/schemas/finding.schema.json +31 -3
- package/schemas/flow_coverage.schema.json +12 -3
- package/schemas/graph_bundle.schema.json +12 -6
- package/schemas/merged_findings.schema.json +7 -2
- package/schemas/risk_register.schema.json +5 -1
- package/schemas/root_cause_clusters.schema.json +2 -1
- package/schemas/runtime_validation_tasks.schema.json +4 -1
- package/schemas/surface_manifest.schema.json +4 -1
- package/schemas/unit_manifest.schema.json +10 -3
- package/skills/audit-code/audit-code.prompt.md +0 -2
package/dist/adapters/eslint.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { normalizeGenericExternalResults } from "./normalizeExternal.js";
|
|
2
|
+
const ESLINT_SEVERITY_ERROR = 2;
|
|
3
|
+
const ESLINT_SEVERITY_WARNING = 1;
|
|
2
4
|
function mapSeverity(value) {
|
|
3
|
-
if (value ===
|
|
5
|
+
if (value === ESLINT_SEVERITY_ERROR)
|
|
4
6
|
return "medium";
|
|
5
|
-
if (value ===
|
|
7
|
+
if (value === ESLINT_SEVERITY_WARNING)
|
|
6
8
|
return "low";
|
|
7
9
|
return "info";
|
|
8
10
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { normalizeGenericExternalResults } from "./normalizeExternal.js";
|
|
2
2
|
export function normalizeNpmAuditJson(input) {
|
|
3
3
|
return normalizeGenericExternalResults("npm-audit", Object.entries(input.vulnerabilities ?? {}).map(([pkg, vuln], index) => ({
|
|
4
|
-
id: `npm-audit-${index
|
|
4
|
+
id: `npm-audit-${index}`,
|
|
5
5
|
category: "dependency_risk",
|
|
6
6
|
severity: vuln.severity ?? "unknown",
|
|
7
7
|
path: "package-lock.json",
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { buildRepoManifest } from "./extractors/fileInventory.js";
|
|
@@ -24,8 +24,11 @@ import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./superv
|
|
|
24
24
|
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
25
25
|
import { buildRunId, ensureSupervisorDirs, getRunPaths, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
26
26
|
import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
|
|
27
|
+
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
27
28
|
const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
|
|
28
29
|
const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
|
|
30
|
+
const DEFAULT_MAX_RUNS = 1000;
|
|
31
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
29
32
|
const sampleFiles = [
|
|
30
33
|
{ path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
|
|
31
34
|
{ path: "src/lib/session.ts", size_bytes: 980, hash: "def456" },
|
|
@@ -48,8 +51,8 @@ function getRootDir(argv) {
|
|
|
48
51
|
return resolve(getFlag(argv, "--root", "."));
|
|
49
52
|
}
|
|
50
53
|
function getMaxRuns(argv) {
|
|
51
|
-
const raw = Number(getFlag(argv, "--max-runs",
|
|
52
|
-
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) :
|
|
54
|
+
const raw = Number(getFlag(argv, "--max-runs", String(DEFAULT_MAX_RUNS)));
|
|
55
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_MAX_RUNS;
|
|
53
56
|
}
|
|
54
57
|
function getAgentBatchSize(argv, sessionConfig) {
|
|
55
58
|
const fromArg = getFlag(argv, "--agent-batch-size");
|
|
@@ -58,8 +61,7 @@ function getAgentBatchSize(argv, sessionConfig) {
|
|
|
58
61
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
59
62
|
return Math.floor(parsed);
|
|
60
63
|
}
|
|
61
|
-
if (typeof sessionConfig.agent_task_batch_size === "number" &&
|
|
62
|
-
sessionConfig.agent_task_batch_size > 0) {
|
|
64
|
+
if (typeof sessionConfig.agent_task_batch_size === "number" && sessionConfig.agent_task_batch_size > 0) {
|
|
63
65
|
return Math.floor(sessionConfig.agent_task_batch_size);
|
|
64
66
|
}
|
|
65
67
|
return 1;
|
|
@@ -71,8 +73,7 @@ function getParallelWorkers(argv, sessionConfig) {
|
|
|
71
73
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
72
74
|
return Math.floor(parsed);
|
|
73
75
|
}
|
|
74
|
-
if (typeof sessionConfig.parallel_workers === "number" &&
|
|
75
|
-
sessionConfig.parallel_workers > 0) {
|
|
76
|
+
if (typeof sessionConfig.parallel_workers === "number" && sessionConfig.parallel_workers > 0) {
|
|
76
77
|
return Math.floor(sessionConfig.parallel_workers);
|
|
77
78
|
}
|
|
78
79
|
return 1;
|
|
@@ -113,6 +114,7 @@ async function emitEnvelope(params) {
|
|
|
113
114
|
bundle: params.bundle,
|
|
114
115
|
providerName: params.providerName,
|
|
115
116
|
progressSummary: params.progress_summary,
|
|
117
|
+
isConfigError: params.isConfigError,
|
|
116
118
|
});
|
|
117
119
|
await writeAuditCodeHandoffArtifacts(handoff);
|
|
118
120
|
console.log(JSON.stringify(buildEnvelope({
|
|
@@ -127,7 +129,7 @@ async function emitEnvelope(params) {
|
|
|
127
129
|
}), null, 2));
|
|
128
130
|
}
|
|
129
131
|
function buildManualReviewBlocker(providerName) {
|
|
130
|
-
return providerName ===
|
|
132
|
+
return providerName === LOCAL_SUBPROCESS_PROVIDER_NAME
|
|
131
133
|
? "Automatic local-subprocess work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider such as claude-code, opencode, or subprocess-template."
|
|
132
134
|
: "Automatic work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider.";
|
|
133
135
|
}
|
|
@@ -159,8 +161,9 @@ function buildBlockedAuditState(params) {
|
|
|
159
161
|
}
|
|
160
162
|
async function countLines(path) {
|
|
161
163
|
return new Promise((resolve, reject) => {
|
|
162
|
-
let lines =
|
|
164
|
+
let lines = 0;
|
|
163
165
|
let byteCount = 0;
|
|
166
|
+
let lastByte = -1;
|
|
164
167
|
const stream = createReadStream(path);
|
|
165
168
|
stream.on("data", (chunk) => {
|
|
166
169
|
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
@@ -168,9 +171,15 @@ async function countLines(path) {
|
|
|
168
171
|
for (let i = 0; i < buffer.length; ++i) {
|
|
169
172
|
if (buffer[i] === 10)
|
|
170
173
|
lines++;
|
|
174
|
+
lastByte = buffer[i];
|
|
171
175
|
}
|
|
172
176
|
});
|
|
173
|
-
stream.on("end", () =>
|
|
177
|
+
stream.on("end", () => {
|
|
178
|
+
if (byteCount === 0)
|
|
179
|
+
return resolve(0);
|
|
180
|
+
// Files not ending with \n have one final line not counted above
|
|
181
|
+
resolve(lastByte !== 10 ? lines + 1 : lines);
|
|
182
|
+
});
|
|
174
183
|
stream.on("error", reject);
|
|
175
184
|
});
|
|
176
185
|
}
|
|
@@ -194,20 +203,29 @@ async function buildLineIndex(root, repoManifest) {
|
|
|
194
203
|
}
|
|
195
204
|
return Object.fromEntries(entries);
|
|
196
205
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
const PROJECT_SIGNALS = [
|
|
207
|
+
"package.json",
|
|
208
|
+
"go.mod",
|
|
209
|
+
"Cargo.toml",
|
|
210
|
+
"pom.xml",
|
|
211
|
+
"build.gradle",
|
|
212
|
+
"pyproject.toml",
|
|
213
|
+
"setup.py",
|
|
214
|
+
"setup.cfg",
|
|
215
|
+
"Makefile",
|
|
216
|
+
"CMakeLists.txt",
|
|
217
|
+
];
|
|
218
|
+
async function detectProjectRoot(root) {
|
|
219
|
+
for (const signal of PROJECT_SIGNALS) {
|
|
220
|
+
try {
|
|
221
|
+
await access(join(root, signal));
|
|
222
|
+
return signal;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// not found, try next
|
|
208
226
|
}
|
|
209
227
|
}
|
|
210
|
-
return
|
|
228
|
+
return null;
|
|
211
229
|
}
|
|
212
230
|
function buildPendingAuditTasks(bundle) {
|
|
213
231
|
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
@@ -337,10 +355,39 @@ async function cmdRunToCompletion(argv) {
|
|
|
337
355
|
const maxRuns = getMaxRuns(argv);
|
|
338
356
|
const agentBatchSize = getAgentBatchSize(argv, sessionConfig);
|
|
339
357
|
const parallelWorkers = getParallelWorkers(argv, sessionConfig);
|
|
340
|
-
const timeoutMs = sessionConfig.timeout_ms ??
|
|
358
|
+
const timeoutMs = sessionConfig.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
341
359
|
const selfCliPath = resolve(process.argv[1] ?? "");
|
|
342
360
|
await mkdir(artifactsDir, { recursive: true });
|
|
343
361
|
await ensureSupervisorDirs(artifactsDir);
|
|
362
|
+
const earlyBundle = await loadArtifactBundle(artifactsDir);
|
|
363
|
+
if (!earlyBundle.unit_manifest) {
|
|
364
|
+
const foundSignal = await detectProjectRoot(root);
|
|
365
|
+
if (!foundSignal) {
|
|
366
|
+
const blocker = `No recognisable project signals found in ${root}. Expected one of: ${PROJECT_SIGNALS.join(", ")}. Check that --root points to the repository root, not a subdirectory or an unrelated path.`;
|
|
367
|
+
const earlyState = deriveAuditState(earlyBundle);
|
|
368
|
+
const blockedState = buildBlockedAuditState({
|
|
369
|
+
state: earlyState,
|
|
370
|
+
obligationId: null,
|
|
371
|
+
executor: null,
|
|
372
|
+
blocker,
|
|
373
|
+
});
|
|
374
|
+
await emitEnvelope({
|
|
375
|
+
root,
|
|
376
|
+
artifactsDir,
|
|
377
|
+
bundle: { ...earlyBundle, audit_state: blockedState },
|
|
378
|
+
audit_state: blockedState,
|
|
379
|
+
selected_obligation: null,
|
|
380
|
+
selected_executor: null,
|
|
381
|
+
progress_made: false,
|
|
382
|
+
artifacts_written: [],
|
|
383
|
+
progress_summary: blocker,
|
|
384
|
+
next_likely_step: null,
|
|
385
|
+
providerName: provider.name,
|
|
386
|
+
isConfigError: true,
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
344
391
|
let pendingAuditResultsPath = getFlag(argv, "--results");
|
|
345
392
|
let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
|
|
346
393
|
let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
|
|
@@ -371,7 +418,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
371
418
|
obligationId = "runtime_validation_current";
|
|
372
419
|
runtimeUpdatesPath = pendingRuntimeUpdatesPath;
|
|
373
420
|
}
|
|
374
|
-
if (preferredExecutor === "agent" && provider.name ===
|
|
421
|
+
if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
|
|
375
422
|
const blocker = buildManualReviewBlocker(provider.name);
|
|
376
423
|
const blockedState = buildBlockedAuditState({
|
|
377
424
|
state: bundle.audit_state ?? decision.state,
|
|
@@ -468,13 +515,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
468
515
|
obligation_id: obligationId,
|
|
469
516
|
preferred_executor: "agent",
|
|
470
517
|
result_path: slotPaths.resultPath,
|
|
471
|
-
worker_command: [
|
|
472
|
-
process.execPath,
|
|
473
|
-
selfCliPath,
|
|
474
|
-
"worker-run",
|
|
475
|
-
"--task",
|
|
476
|
-
slotPaths.taskPath,
|
|
477
|
-
],
|
|
518
|
+
worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
|
|
478
519
|
audit_results_path: slotAuditResultsPath,
|
|
479
520
|
pending_audit_tasks_path: slotPendingTasksPath,
|
|
480
521
|
skip_worker_command: true,
|
|
@@ -482,13 +523,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
482
523
|
const slotPrompt = renderWorkerPrompt(slotTask);
|
|
483
524
|
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir);
|
|
484
525
|
await writeJsonFile(slotPendingTasksPath, group);
|
|
485
|
-
workerSlots.push({
|
|
486
|
-
runId: slotRunId,
|
|
487
|
-
paths: slotPaths,
|
|
488
|
-
auditResultsPath: slotAuditResultsPath,
|
|
489
|
-
pendingTasksPath: slotPendingTasksPath,
|
|
490
|
-
group,
|
|
491
|
-
});
|
|
526
|
+
workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
|
|
492
527
|
}
|
|
493
528
|
const parallelStartedAt = new Date().toISOString();
|
|
494
529
|
await Promise.allSettled(workerSlots.map((slot) => provider.launch({
|
|
@@ -502,8 +537,10 @@ async function cmdRunToCompletion(argv) {
|
|
|
502
537
|
stderrPath: slot.paths.stderrPath,
|
|
503
538
|
uiMode,
|
|
504
539
|
timeoutMs,
|
|
505
|
-
model: resolveModelForTasks(slot.group, sessionConfig),
|
|
506
540
|
})));
|
|
541
|
+
// Result ingestion is intentionally sequential even though agent launch
|
|
542
|
+
// was parallel. Writing to coverage_matrix.json is not atomic, so
|
|
543
|
+
// concurrent ingest calls would race and corrupt coverage state.
|
|
507
544
|
let batchProgress = false;
|
|
508
545
|
for (const slot of workerSlots) {
|
|
509
546
|
const parallelEndedAt = new Date().toISOString();
|
|
@@ -520,8 +557,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
520
557
|
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
521
558
|
if (warnings.length > 0) {
|
|
522
559
|
process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
|
|
523
|
-
formatAuditResultIssues(warnings) +
|
|
524
|
-
"\n");
|
|
560
|
+
formatAuditResultIssues(warnings) + "\n");
|
|
525
561
|
}
|
|
526
562
|
if (errors.length > 0) {
|
|
527
563
|
throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
|
|
@@ -626,9 +662,6 @@ async function cmdRunToCompletion(argv) {
|
|
|
626
662
|
stderrPath: paths.stderrPath,
|
|
627
663
|
uiMode,
|
|
628
664
|
timeoutMs,
|
|
629
|
-
model: pendingAuditTasks
|
|
630
|
-
? resolveModelForTasks(pendingAuditTasks, sessionConfig)
|
|
631
|
-
: undefined,
|
|
632
665
|
});
|
|
633
666
|
const candidate = await readJsonFile(paths.resultPath);
|
|
634
667
|
workerResult = isWorkerResult(candidate)
|
|
@@ -881,7 +914,11 @@ async function cmdValidate(argv) {
|
|
|
881
914
|
const providerIssues = rawSessionConfig === undefined || sessionConfigIssues.length > 0
|
|
882
915
|
? []
|
|
883
916
|
: prefixValidationIssues("session_config", validateConfiguredProviderEnvironment(rawSessionConfig));
|
|
884
|
-
const issues = [
|
|
917
|
+
const issues = [
|
|
918
|
+
...artifactIssues,
|
|
919
|
+
...sessionConfigIssues,
|
|
920
|
+
...providerIssues,
|
|
921
|
+
];
|
|
885
922
|
const resolvedProvider = rawSessionConfig === undefined
|
|
886
923
|
? "local-subprocess"
|
|
887
924
|
: sessionConfigIssues.length > 0
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNodeModulesOrGit, isTestPath, isInterfacePath, isDataLayerPath, isSecuritySensitivePath, isConcurrencyPath, isScriptPath, isDeploymentConfigPath, isDocPath, isGeneratedPath, } from "./pathPatterns.js";
|
|
1
2
|
function addBucket(buckets, rationale, bucket, reason) {
|
|
2
3
|
if (!buckets.has(bucket)) {
|
|
3
4
|
buckets.add(bucket);
|
|
@@ -8,57 +9,35 @@ export function bucketFile(path) {
|
|
|
8
9
|
const normalized = path.toLowerCase();
|
|
9
10
|
const buckets = new Set();
|
|
10
11
|
const rationale = [];
|
|
11
|
-
if (normalized
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (isNodeModulesOrGit(normalized)) {
|
|
13
|
+
addBucket(buckets, rationale, "generated_vendor", "node_modules or .git excluded by convention");
|
|
14
|
+
return { path, buckets: [...buckets], rationale };
|
|
15
|
+
}
|
|
16
|
+
if (isTestPath(normalized)) {
|
|
14
17
|
addBucket(buckets, rationale, "tests", "path suggests tests");
|
|
15
18
|
}
|
|
16
|
-
if (normalized
|
|
17
|
-
normalized.includes("controller") ||
|
|
18
|
-
normalized.includes("handler") ||
|
|
19
|
-
normalized.includes("api/")) {
|
|
19
|
+
if (isInterfacePath(normalized)) {
|
|
20
20
|
addBucket(buckets, rationale, "interface", "path suggests interface code");
|
|
21
21
|
}
|
|
22
|
-
if (normalized
|
|
23
|
-
normalized.includes("schema") ||
|
|
24
|
-
normalized.includes("migration") ||
|
|
25
|
-
normalized.includes("seed") ||
|
|
26
|
-
normalized.includes("db/")) {
|
|
22
|
+
if (isDataLayerPath(normalized)) {
|
|
27
23
|
addBucket(buckets, rationale, "data_layer", "path suggests data-layer code");
|
|
28
24
|
}
|
|
29
|
-
if (normalized
|
|
30
|
-
normalized.includes("secret") ||
|
|
31
|
-
normalized.includes("token") ||
|
|
32
|
-
normalized.includes("permission") ||
|
|
33
|
-
normalized.includes("session")) {
|
|
25
|
+
if (isSecuritySensitivePath(normalized)) {
|
|
34
26
|
addBucket(buckets, rationale, "security_sensitive", "path suggests security-sensitive code");
|
|
35
27
|
}
|
|
36
|
-
if (normalized
|
|
37
|
-
normalized.includes("worker") ||
|
|
38
|
-
normalized.includes("job") ||
|
|
39
|
-
normalized.includes("cache") ||
|
|
40
|
-
normalized.includes("retry") ||
|
|
41
|
-
normalized.includes("lock")) {
|
|
28
|
+
if (isConcurrencyPath(normalized)) {
|
|
42
29
|
addBucket(buckets, rationale, "concurrency_state", "path suggests concurrency or stateful behavior");
|
|
43
30
|
}
|
|
44
|
-
if (normalized
|
|
45
|
-
normalized.startsWith("scripts/") ||
|
|
46
|
-
normalized.startsWith("bin/")) {
|
|
31
|
+
if (isScriptPath(normalized)) {
|
|
47
32
|
addBucket(buckets, rationale, "tooling_scripts", "path suggests tooling or scripts");
|
|
48
33
|
}
|
|
49
|
-
if (normalized
|
|
50
|
-
normalized.includes("terraform") ||
|
|
51
|
-
normalized.includes("deploy") ||
|
|
52
|
-
normalized.includes("workflow") ||
|
|
53
|
-
normalized.includes("k8s") ||
|
|
54
|
-
normalized.endsWith(".yml") ||
|
|
55
|
-
normalized.endsWith(".yaml")) {
|
|
34
|
+
if (isDeploymentConfigPath(normalized)) {
|
|
56
35
|
addBucket(buckets, rationale, "config_deployment", "path suggests config or deployment artifact");
|
|
57
36
|
}
|
|
58
|
-
if (
|
|
37
|
+
if (isDocPath(normalized)) {
|
|
59
38
|
addBucket(buckets, rationale, "docs_specs", "path suggests documentation");
|
|
60
39
|
}
|
|
61
|
-
if (
|
|
40
|
+
if (isGeneratedPath(normalized)) {
|
|
62
41
|
addBucket(buckets, rationale, "generated_vendor", "path suggests generated or vendored content");
|
|
63
42
|
}
|
|
64
43
|
if (buckets.size === 0) {
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
+
import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isDocPath, } from "./pathPatterns.js";
|
|
1
2
|
function inferDisposition(path) {
|
|
2
3
|
const normalized = path.toLowerCase();
|
|
3
|
-
if (
|
|
4
|
+
if (isNodeModulesOrGit(normalized)) {
|
|
5
|
+
return { path, status: "excluded", reason: "node_modules or .git excluded by convention." };
|
|
6
|
+
}
|
|
7
|
+
if (isBuildOutput(normalized)) {
|
|
4
8
|
return { path, status: "generated", reason: "Build output path." };
|
|
5
9
|
}
|
|
6
|
-
if (
|
|
10
|
+
if (isVendorPath(normalized)) {
|
|
7
11
|
return { path, status: "vendor", reason: "Vendor or third-party path." };
|
|
8
12
|
}
|
|
9
|
-
if (normalized
|
|
10
|
-
normalized.endsWith(".jpg") ||
|
|
11
|
-
normalized.endsWith(".jpeg") ||
|
|
12
|
-
normalized.endsWith(".gif") ||
|
|
13
|
-
normalized.endsWith(".pdf") ||
|
|
14
|
-
normalized.endsWith(".zip")) {
|
|
13
|
+
if (isBinaryArtifact(normalized)) {
|
|
15
14
|
return {
|
|
16
15
|
path,
|
|
17
16
|
status: "binary",
|
|
18
17
|
reason: "Non-source binary-like artifact.",
|
|
19
18
|
};
|
|
20
19
|
}
|
|
21
|
-
if (
|
|
20
|
+
if (isDocPath(normalized)) {
|
|
22
21
|
return { path, status: "doc_only", reason: "Documentation artifact." };
|
|
23
22
|
}
|
|
24
23
|
return {
|
package/dist/extractors/flows.js
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
import { isAuditExcludedStatus } from "./disposition.js";
|
|
2
|
+
import { isSecuritySensitivePath, isDataLayerPath, isConcurrencyPath, isInterfacePath, isDeploymentConfigPath, } from "./pathPatterns.js";
|
|
2
3
|
function inferConcerns(paths) {
|
|
3
4
|
const concerns = new Set();
|
|
4
5
|
for (const path of paths) {
|
|
5
6
|
const normalized = path.toLowerCase();
|
|
6
|
-
if (normalized
|
|
7
|
-
normalized.includes("token") ||
|
|
8
|
-
normalized.includes("session"))
|
|
7
|
+
if (isSecuritySensitivePath(normalized))
|
|
9
8
|
concerns.add("security");
|
|
10
|
-
if (normalized
|
|
11
|
-
normalized.includes("model") ||
|
|
12
|
-
normalized.includes("schema") ||
|
|
13
|
-
normalized.includes("migration") ||
|
|
9
|
+
if (isDataLayerPath(normalized) ||
|
|
14
10
|
normalized.includes("invoice") ||
|
|
15
11
|
normalized.includes("payment"))
|
|
16
12
|
concerns.add("data_integrity");
|
|
17
|
-
if (normalized
|
|
18
|
-
normalized.includes("job") ||
|
|
19
|
-
normalized.includes("retry") ||
|
|
20
|
-
normalized.includes("queue"))
|
|
13
|
+
if (isConcurrencyPath(normalized))
|
|
21
14
|
concerns.add("reliability");
|
|
22
|
-
if (normalized
|
|
23
|
-
normalized.includes("route") ||
|
|
24
|
-
normalized.includes("controller"))
|
|
15
|
+
if (isInterfacePath(normalized))
|
|
25
16
|
concerns.add("correctness");
|
|
26
17
|
}
|
|
27
18
|
return concerns.size > 0 ? [...concerns] : ["correctness"];
|
|
@@ -33,13 +24,15 @@ function relatedPaths(entry, availablePaths) {
|
|
|
33
24
|
const lower = path.toLowerCase();
|
|
34
25
|
if (path === entry)
|
|
35
26
|
continue;
|
|
36
|
-
|
|
27
|
+
// Auth / session flows: link sibling auth, session, token, user paths
|
|
28
|
+
if (isSecuritySensitivePath(normalized) &&
|
|
37
29
|
(lower.includes("auth") ||
|
|
38
30
|
lower.includes("session") ||
|
|
39
31
|
lower.includes("token") ||
|
|
40
32
|
lower.includes("user"))) {
|
|
41
33
|
linked.add(path);
|
|
42
34
|
}
|
|
35
|
+
// Billing / payment flows: link ledger and subscription paths
|
|
43
36
|
if ((normalized.includes("billing") ||
|
|
44
37
|
normalized.includes("invoice") ||
|
|
45
38
|
normalized.includes("payment")) &&
|
|
@@ -50,9 +43,8 @@ function relatedPaths(entry, availablePaths) {
|
|
|
50
43
|
lower.includes("subscription"))) {
|
|
51
44
|
linked.add(path);
|
|
52
45
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
normalized.includes("queue")) &&
|
|
46
|
+
// Async / queue flows: link worker, job, retry, and task paths
|
|
47
|
+
if (isConcurrencyPath(normalized) &&
|
|
56
48
|
(lower.includes("queue") ||
|
|
57
49
|
lower.includes("retry") ||
|
|
58
50
|
lower.includes("worker") ||
|
|
@@ -60,7 +52,8 @@ function relatedPaths(entry, availablePaths) {
|
|
|
60
52
|
lower.includes("task"))) {
|
|
61
53
|
linked.add(path);
|
|
62
54
|
}
|
|
63
|
-
|
|
55
|
+
// Deployment / infra flows: link docker, k8s, terraform, workflow paths
|
|
56
|
+
if (isDeploymentConfigPath(normalized) &&
|
|
64
57
|
(lower.includes("deploy") ||
|
|
65
58
|
lower.includes("docker") ||
|
|
66
59
|
lower.includes("workflow") ||
|
|
@@ -75,7 +68,7 @@ function dedupeFlows(flows) {
|
|
|
75
68
|
const seen = new Set();
|
|
76
69
|
const deduped = [];
|
|
77
70
|
for (const flow of flows) {
|
|
78
|
-
const signature = `${flow.name}|${flow.paths.join(",")}|${flow.concerns.join(",")}`;
|
|
71
|
+
const signature = `${flow.name}|${[...flow.paths].sort().join(",")}|${[...flow.concerns].sort().join(",")}`;
|
|
79
72
|
if (seen.has(signature))
|
|
80
73
|
continue;
|
|
81
74
|
seen.add(signature);
|
|
@@ -108,9 +101,7 @@ export function buildCriticalFlowManifest(repoManifest, surfaceManifest, disposi
|
|
|
108
101
|
}
|
|
109
102
|
for (const path of availablePaths) {
|
|
110
103
|
const lower = path.toLowerCase();
|
|
111
|
-
if (lower.includes("
|
|
112
|
-
lower.includes("schema") ||
|
|
113
|
-
lower.includes("seed")) {
|
|
104
|
+
if (isDataLayerPath(lower) || lower.includes("seed")) {
|
|
114
105
|
flows.push({
|
|
115
106
|
id: `flow:data:${path.replace(/[^a-zA-Z0-9:_-]/g, "-")}`,
|
|
116
107
|
name: `data evolution flow for ${path}`,
|
|
@@ -22,7 +22,10 @@ function shouldIgnore(relativePath, ignores) {
|
|
|
22
22
|
const normalized = normalizePath(relativePath);
|
|
23
23
|
return ignores.some((ignore) => {
|
|
24
24
|
const value = normalizePath(ignore);
|
|
25
|
-
return normalized === value ||
|
|
25
|
+
return (normalized === value ||
|
|
26
|
+
normalized.startsWith(`${value}/`) ||
|
|
27
|
+
normalized.includes(`/${value}/`) ||
|
|
28
|
+
normalized.endsWith(`/${value}`));
|
|
26
29
|
});
|
|
27
30
|
}
|
|
28
31
|
async function maybeHashFile(path, enabled) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralised path-pattern predicates shared across disposition, bucketing,
|
|
3
|
+
* surfaces, and flows extractors. Every function operates on the
|
|
4
|
+
* already-lower-cased form of the path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isNodeModulesOrGit(normalized: string): boolean;
|
|
7
|
+
export declare function isBuildOutput(normalized: string): boolean;
|
|
8
|
+
export declare function isVendorPath(normalized: string): boolean;
|
|
9
|
+
export declare function isBinaryArtifact(normalized: string): boolean;
|
|
10
|
+
export declare function isDocPath(normalized: string): boolean;
|
|
11
|
+
export declare function isTestPath(normalized: string): boolean;
|
|
12
|
+
export declare function isInterfacePath(normalized: string): boolean;
|
|
13
|
+
export declare function isDataLayerPath(normalized: string): boolean;
|
|
14
|
+
export declare function isSecuritySensitivePath(normalized: string): boolean;
|
|
15
|
+
export declare function isConcurrencyPath(normalized: string): boolean;
|
|
16
|
+
export declare function isScriptPath(normalized: string): boolean;
|
|
17
|
+
export declare function isDeploymentConfigPath(normalized: string): boolean;
|
|
18
|
+
export declare function isGeneratedPath(normalized: string): boolean;
|
|
19
|
+
export declare function isSurfacePath(normalized: string): boolean;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralised path-pattern predicates shared across disposition, bucketing,
|
|
3
|
+
* surfaces, and flows extractors. Every function operates on the
|
|
4
|
+
* already-lower-cased form of the path.
|
|
5
|
+
*/
|
|
6
|
+
export function isNodeModulesOrGit(normalized) {
|
|
7
|
+
return (normalized === "node_modules" ||
|
|
8
|
+
normalized.startsWith("node_modules/") ||
|
|
9
|
+
normalized.includes("/node_modules/") ||
|
|
10
|
+
normalized.endsWith("/node_modules") ||
|
|
11
|
+
normalized === ".git" ||
|
|
12
|
+
normalized.startsWith(".git/") ||
|
|
13
|
+
normalized.includes("/.git/") ||
|
|
14
|
+
normalized.endsWith("/.git"));
|
|
15
|
+
}
|
|
16
|
+
export function isBuildOutput(normalized) {
|
|
17
|
+
return normalized.startsWith("dist/") || normalized.startsWith("build/");
|
|
18
|
+
}
|
|
19
|
+
export function isVendorPath(normalized) {
|
|
20
|
+
return normalized.includes("vendor") || normalized.includes("third_party");
|
|
21
|
+
}
|
|
22
|
+
export function isBinaryArtifact(normalized) {
|
|
23
|
+
return (normalized.endsWith(".png") ||
|
|
24
|
+
normalized.endsWith(".jpg") ||
|
|
25
|
+
normalized.endsWith(".jpeg") ||
|
|
26
|
+
normalized.endsWith(".gif") ||
|
|
27
|
+
normalized.endsWith(".pdf") ||
|
|
28
|
+
normalized.endsWith(".zip"));
|
|
29
|
+
}
|
|
30
|
+
export function isDocPath(normalized) {
|
|
31
|
+
return normalized.endsWith(".md") || normalized.startsWith("docs/");
|
|
32
|
+
}
|
|
33
|
+
export function isTestPath(normalized) {
|
|
34
|
+
return (normalized.includes("test") ||
|
|
35
|
+
normalized.includes("spec") ||
|
|
36
|
+
normalized.includes("__tests__"));
|
|
37
|
+
}
|
|
38
|
+
export function isInterfacePath(normalized) {
|
|
39
|
+
return (normalized.includes("route") ||
|
|
40
|
+
normalized.includes("controller") ||
|
|
41
|
+
normalized.includes("handler") ||
|
|
42
|
+
normalized.includes("api/"));
|
|
43
|
+
}
|
|
44
|
+
export function isDataLayerPath(normalized) {
|
|
45
|
+
return (normalized.includes("model") ||
|
|
46
|
+
normalized.includes("schema") ||
|
|
47
|
+
normalized.includes("migration") ||
|
|
48
|
+
normalized.includes("seed") ||
|
|
49
|
+
normalized.includes("db/"));
|
|
50
|
+
}
|
|
51
|
+
export function isSecuritySensitivePath(normalized) {
|
|
52
|
+
return (normalized.includes("auth") ||
|
|
53
|
+
normalized.includes("secret") ||
|
|
54
|
+
normalized.includes("token") ||
|
|
55
|
+
normalized.includes("permission") ||
|
|
56
|
+
normalized.includes("session"));
|
|
57
|
+
}
|
|
58
|
+
export function isConcurrencyPath(normalized) {
|
|
59
|
+
return (normalized.includes("queue") ||
|
|
60
|
+
normalized.includes("worker") ||
|
|
61
|
+
normalized.includes("job") ||
|
|
62
|
+
normalized.includes("cache") ||
|
|
63
|
+
normalized.includes("retry") ||
|
|
64
|
+
normalized.includes("lock"));
|
|
65
|
+
}
|
|
66
|
+
export function isScriptPath(normalized) {
|
|
67
|
+
return (normalized.includes("script") ||
|
|
68
|
+
normalized.startsWith("scripts/") ||
|
|
69
|
+
normalized.startsWith("bin/"));
|
|
70
|
+
}
|
|
71
|
+
export function isDeploymentConfigPath(normalized) {
|
|
72
|
+
return (normalized.includes("docker") ||
|
|
73
|
+
normalized.includes("terraform") ||
|
|
74
|
+
normalized.includes("deploy") ||
|
|
75
|
+
normalized.includes("workflow") ||
|
|
76
|
+
normalized.includes("k8s") ||
|
|
77
|
+
normalized.endsWith(".yml") ||
|
|
78
|
+
normalized.endsWith(".yaml"));
|
|
79
|
+
}
|
|
80
|
+
export function isGeneratedPath(normalized) {
|
|
81
|
+
return normalized.includes("vendor") || normalized.includes("generated");
|
|
82
|
+
}
|
|
83
|
+
export function isSurfacePath(normalized) {
|
|
84
|
+
return (normalized.includes("api/") ||
|
|
85
|
+
normalized.includes("route") ||
|
|
86
|
+
normalized.includes("controller") ||
|
|
87
|
+
normalized.includes("worker") ||
|
|
88
|
+
normalized.includes("job") ||
|
|
89
|
+
normalized.includes("command") ||
|
|
90
|
+
normalized.includes("cli"));
|
|
91
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAuditExcludedStatus } from "./disposition.js";
|
|
2
|
+
import { isSurfacePath } from "./pathPatterns.js";
|
|
2
3
|
function methodsForPath(path) {
|
|
3
4
|
const normalized = path.toLowerCase();
|
|
4
5
|
if (normalized.includes("api") || normalized.includes("route")) {
|
|
@@ -15,13 +16,7 @@ export function buildSurfaceManifest(repoManifest, disposition) {
|
|
|
15
16
|
continue;
|
|
16
17
|
}
|
|
17
18
|
const normalized = file.path.toLowerCase();
|
|
18
|
-
if (normalized
|
|
19
|
-
normalized.includes("route") ||
|
|
20
|
-
normalized.includes("controller") ||
|
|
21
|
-
normalized.includes("worker") ||
|
|
22
|
-
normalized.includes("job") ||
|
|
23
|
-
normalized.includes("command") ||
|
|
24
|
-
normalized.includes("cli")) {
|
|
19
|
+
if (isSurfacePath(normalized)) {
|
|
25
20
|
surfaces.push({
|
|
26
21
|
id: `surface:${file.path}`,
|
|
27
22
|
kind: normalized.includes("worker") || normalized.includes("job")
|