auditor-lambda 0.8.0 → 0.9.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 +149 -129
- package/dist/adapters/normalizeExternal.js +6 -3
- package/dist/cli/args.d.ts +0 -1
- package/dist/cli/args.js +0 -6
- package/dist/cli/dispatch.js +3 -2
- package/dist/cli/lineIndex.js +4 -1
- package/dist/cli/mergeAndIngestCommand.d.ts +1 -0
- package/dist/cli/mergeAndIngestCommand.js +219 -0
- package/dist/cli/nextStepCommand.js +5 -1
- package/dist/cli/runToCompletion.d.ts +9 -0
- package/dist/cli/runToCompletion.js +655 -480
- package/dist/cli/statusCommand.d.ts +1 -0
- package/dist/cli/statusCommand.js +113 -0
- package/dist/cli/submitPacketCommand.d.ts +1 -0
- package/dist/cli/submitPacketCommand.js +155 -0
- package/dist/cli/workerResult.d.ts +1 -1
- package/dist/cli/workerRunCommand.d.ts +1 -0
- package/dist/cli/workerRunCommand.js +88 -0
- package/dist/cli.js +14 -563
- package/dist/extractors/analyzers/sql.js +4 -1
- package/dist/extractors/analyzers/treeSitter.js +29 -15
- package/dist/extractors/analyzers/typescript.js +10 -8
- package/dist/extractors/designAssessment.js +43 -24
- package/dist/extractors/graph.js +139 -73
- package/dist/extractors/pathPatterns.js +17 -5
- package/dist/io/runArtifactTypes.d.ts +18 -0
- package/dist/io/runArtifactTypes.js +1 -0
- package/dist/io/runArtifacts.d.ts +2 -18
- package/dist/io/runArtifacts.js +14 -3
- package/dist/mcp/server.js +9 -0
- package/dist/orchestrator/advance.js +37 -22
- package/dist/orchestrator/artifactFreshness.js +2 -2
- package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
- package/dist/orchestrator/autoFixExecutor.js +16 -8
- package/dist/orchestrator/dependencyMap.d.ts +1 -1
- package/dist/orchestrator/dependencyMap.js +7 -1
- package/dist/orchestrator/fileAnchors.js +14 -3
- package/dist/orchestrator/flowCoverage.js +1 -0
- package/dist/orchestrator/flowRequeue.js +4 -1
- package/dist/orchestrator/{internalExecutors.d.ts → ingestionExecutors.d.ts} +0 -6
- package/dist/orchestrator/ingestionExecutors.js +237 -0
- package/dist/orchestrator/intakeExecutors.d.ts +3 -0
- package/dist/orchestrator/intakeExecutors.js +25 -0
- package/dist/orchestrator/planningExecutors.d.ts +4 -0
- package/dist/orchestrator/planningExecutors.js +95 -0
- package/dist/orchestrator/runtimeCommand.js +7 -15
- package/dist/orchestrator/selectiveDeepening/conflict.d.ts +8 -0
- package/dist/orchestrator/selectiveDeepening/conflict.js +71 -0
- package/dist/orchestrator/selectiveDeepening/findingFollowup.d.ts +10 -0
- package/dist/orchestrator/selectiveDeepening/findingFollowup.js +52 -0
- package/dist/orchestrator/selectiveDeepening/highRiskClean.d.ts +7 -0
- package/dist/orchestrator/selectiveDeepening/highRiskClean.js +44 -0
- package/dist/orchestrator/selectiveDeepening/index.d.ts +18 -0
- package/dist/orchestrator/selectiveDeepening/index.js +128 -0
- package/dist/orchestrator/selectiveDeepening/lensVerification.d.ts +12 -0
- package/dist/orchestrator/selectiveDeepening/lensVerification.js +242 -0
- package/dist/orchestrator/selectiveDeepening/runtimeValidation.d.ts +13 -0
- package/dist/orchestrator/selectiveDeepening/runtimeValidation.js +57 -0
- package/dist/orchestrator/selectiveDeepening/shared.d.ts +45 -0
- package/dist/orchestrator/selectiveDeepening/shared.js +128 -0
- package/dist/orchestrator/selectiveDeepening/stewardFollowup.d.ts +6 -0
- package/dist/orchestrator/selectiveDeepening/stewardFollowup.js +72 -0
- package/dist/orchestrator/selectiveDeepening.d.ts +2 -20
- package/dist/orchestrator/selectiveDeepening.js +6 -760
- package/dist/orchestrator/staleness.js +3 -3
- package/dist/orchestrator/structureExecutors.d.ts +5 -0
- package/dist/orchestrator/structureExecutors.js +94 -0
- package/dist/orchestrator/taskBuilder.d.ts +2 -2
- package/dist/orchestrator/taskBuilder.js +101 -82
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.js +14 -95
- package/dist/quota/discoveredLimits.d.ts +1 -0
- package/dist/quota/discoveredLimits.js +7 -1
- package/dist/quota/index.d.ts +0 -2
- package/dist/quota/index.js +1 -2
- package/dist/reporting/workBlocks.js +7 -4
- package/dist/types/reviewPlanning.d.ts +23 -16
- package/dist/validation/auditResults.js +97 -95
- package/dist/validation/sessionConfig.d.ts +2 -2
- package/dist/validation/sessionConfig.js +14 -7
- package/package.json +3 -2
- package/schemas/audit_findings.schema.json +3 -3
- package/schemas/critical_flows.schema.json +3 -2
- package/schemas/dispatch_quota.schema.json +1 -1
- package/schemas/graph_bundle.schema.json +1 -1
- package/schemas/review_packets.schema.json +1 -1
- package/schemas/step_contract.schema.json +80 -0
- package/scripts/postinstall.mjs +19 -2
- package/skills/audit-code/opencode-command-template.txt +3 -3
- package/dist/orchestrator/internalExecutors.js +0 -424
- package/dist/providers/localSubprocessProvider.d.ts +0 -9
- package/dist/providers/localSubprocessProvider.js +0 -18
- package/dist/providers/subprocessTemplateProvider.d.ts +0 -8
- package/dist/providers/subprocessTemplateProvider.js +0 -59
- package/dist/providers/vscodeTaskProvider.d.ts +0 -7
- package/dist/providers/vscodeTaskProvider.js +0 -14
- package/dist/quota/probe.d.ts +0 -10
- package/dist/quota/probe.js +0 -18
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile,
|
|
1
|
+
import { mkdir, readFile, rm, } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve, } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,29 +11,31 @@ import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
|
|
|
11
11
|
import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
|
|
12
12
|
import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
|
|
13
13
|
import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
|
|
14
|
-
import { isFileMissingError, readJsonFile,
|
|
14
|
+
import { isFileMissingError, readJsonFile, prefixValidationIssues, DEFAULT_EMPIRICAL_HALF_LIFE_HOURS, } from "@audit-tools/shared";
|
|
15
15
|
import { buildQuotaSource } from "@audit-tools/shared/quota/compositeQuotaSource";
|
|
16
16
|
import { validateArtifactBundle } from "./validation/artifacts.js";
|
|
17
|
-
import { validateAuditResults,
|
|
17
|
+
import { validateAuditResults, } from "./validation/auditResults.js";
|
|
18
18
|
import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
|
|
19
19
|
import { buildAuditReportModel, renderAuditReportMarkdown, } from "./reporting/synthesis.js";
|
|
20
20
|
import { deriveAuditState } from "./orchestrator/state.js";
|
|
21
21
|
import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
|
|
22
|
-
import { loadRunLedger, } from "./supervisor/runLedger.js";
|
|
23
22
|
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
24
23
|
import { clearDispatchFiles, ensureSupervisorDirs, } from "./io/runArtifacts.js";
|
|
25
24
|
import { runAuditCodeMcpServer } from "./mcp/server.js";
|
|
26
|
-
import { scheduleWave, buildProviderModelKey, readQuotaState, resolveLimits, resolveHostActiveSubagentLimit,
|
|
27
|
-
import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath,
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import { buildLineIndex, buildLineIndexForPaths, addFileLineCountHints, } from "./cli/lineIndex.js";
|
|
25
|
+
import { scheduleWave, buildProviderModelKey, readQuotaState, resolveLimits, resolveHostActiveSubagentLimit, computeMaxSafeConcurrency, getQuotaStatePath, lookupDiscoveredLimits, setQuotaStateDir, } from "./quota/index.js";
|
|
26
|
+
import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, countLines, } from "./cli/args.js";
|
|
27
|
+
import { ACTIVE_DISPATCH_FILENAME, loadDispatchResultMap, prepareDispatchArtifacts, } from "./cli/dispatch.js";
|
|
28
|
+
import { buildLineIndex, } from "./cli/lineIndex.js";
|
|
31
29
|
import { emitEnvelope, } from "./cli/envelope.js";
|
|
32
30
|
import { persistConfigErrorHandoff } from "./cli/reviewRun.js";
|
|
33
31
|
import { runAuditStep, ingestBatchAuditResults, } from "./cli/auditStep.js";
|
|
34
32
|
import { packageRoot } from "./cli/paths.js";
|
|
35
33
|
import { cmdNextStep } from "./cli/nextStepCommand.js";
|
|
36
34
|
import { cmdRunToCompletion } from "./cli/runToCompletion.js";
|
|
35
|
+
import { cmdWorkerRun } from "./cli/workerRunCommand.js";
|
|
36
|
+
import { cmdSubmitPacket } from "./cli/submitPacketCommand.js";
|
|
37
|
+
import { cmdMergeAndIngest } from "./cli/mergeAndIngestCommand.js";
|
|
38
|
+
import { cmdStatus } from "./cli/statusCommand.js";
|
|
37
39
|
import { cleanupStaleArtifactsDir } from "./cli/cleanup.js";
|
|
38
40
|
const SAMPLE_REPO_FILES = [
|
|
39
41
|
{ path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
|
|
@@ -218,88 +220,6 @@ async function cmdAdvanceAudit(argv) {
|
|
|
218
220
|
await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
|
-
async function cmdWorkerRun(argv) {
|
|
222
|
-
const taskPath = getFlag(argv, "--task");
|
|
223
|
-
if (!taskPath) {
|
|
224
|
-
throw new Error("worker-run requires --task <path>");
|
|
225
|
-
}
|
|
226
|
-
const task = await readJsonFile(taskPath);
|
|
227
|
-
let workerResult;
|
|
228
|
-
try {
|
|
229
|
-
if (looksLikeCliFlag(task.audit_results_path)) {
|
|
230
|
-
throw new Error(`task.audit_results_path resolved to '${task.audit_results_path}', which looks like a CLI flag instead of a file path.`);
|
|
231
|
-
}
|
|
232
|
-
if (task.preferred_executor === "agent" && !task.audit_results_path) {
|
|
233
|
-
throw new Error("agent worker-run requires audit_results_path so provider-assisted review can be ingested.");
|
|
234
|
-
}
|
|
235
|
-
if (task.preferred_executor === "agent" && task.audit_results_path) {
|
|
236
|
-
const pendingTasks = task.pending_audit_tasks_path
|
|
237
|
-
? await readJsonFile(task.pending_audit_tasks_path)
|
|
238
|
-
: [];
|
|
239
|
-
const auditResults = await readJsonFile(task.audit_results_path);
|
|
240
|
-
const pendingTaskIds = new Set(pendingTasks.map((item) => item.task_id));
|
|
241
|
-
const matchedResultCount = auditResults.filter((result) => pendingTaskIds.has(result.task_id)).length;
|
|
242
|
-
if (pendingTasks.length > 0 && matchedResultCount === 0) {
|
|
243
|
-
throw new Error("Provider-assisted review did not emit any audit results for the pending audit tasks.");
|
|
244
|
-
}
|
|
245
|
-
const issues = validateAuditResults(auditResults, pendingTasks, {
|
|
246
|
-
lineIndex: await buildLineIndexForPaths(task.repo_root, pendingTasks.flatMap((item) => item.file_paths)),
|
|
247
|
-
});
|
|
248
|
-
const errors = issues.filter((issue) => issue.severity === "error");
|
|
249
|
-
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
250
|
-
if (warnings.length > 0) {
|
|
251
|
-
process.stderr.write(`audit-results validation: ${warnings.length} warning(s):\n` +
|
|
252
|
-
formatAuditResultIssues(warnings) +
|
|
253
|
-
"\n");
|
|
254
|
-
}
|
|
255
|
-
if (errors.length > 0) {
|
|
256
|
-
throw new Error(formatAuditResultValidationError(errors));
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const preferredExecutor = task.preferred_executor === "agent"
|
|
260
|
-
? "result_ingestion_executor"
|
|
261
|
-
: task.preferred_executor;
|
|
262
|
-
const result = await runAuditStep({
|
|
263
|
-
root: task.repo_root,
|
|
264
|
-
artifactsDir: task.artifacts_dir,
|
|
265
|
-
preferredExecutor,
|
|
266
|
-
auditResultsPath: task.audit_results_path,
|
|
267
|
-
runtimeUpdatesPath: task.runtime_updates_path,
|
|
268
|
-
externalAnalyzerPath: task.external_analyzer_results_path,
|
|
269
|
-
});
|
|
270
|
-
workerResult = {
|
|
271
|
-
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
272
|
-
run_id: task.run_id,
|
|
273
|
-
obligation_id: task.obligation_id,
|
|
274
|
-
status: result.progress_made ? "completed" : "no_progress",
|
|
275
|
-
progress_made: result.progress_made,
|
|
276
|
-
selected_executor: result.selected_executor,
|
|
277
|
-
artifacts_written: result.artifacts_written,
|
|
278
|
-
summary: result.progress_summary,
|
|
279
|
-
next_likely_step: result.next_likely_step,
|
|
280
|
-
errors: [],
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
workerResult = {
|
|
285
|
-
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
286
|
-
run_id: task.run_id,
|
|
287
|
-
obligation_id: task.obligation_id,
|
|
288
|
-
status: "failed",
|
|
289
|
-
progress_made: false,
|
|
290
|
-
selected_executor: task.preferred_executor,
|
|
291
|
-
artifacts_written: [],
|
|
292
|
-
summary: `Worker failed for executor ${task.preferred_executor}: ${error instanceof Error ? error.message : String(error)}`,
|
|
293
|
-
next_likely_step: task.obligation_id,
|
|
294
|
-
errors: [error instanceof Error ? error.message : String(error)],
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
await writeJsonFile(task.result_path, workerResult);
|
|
298
|
-
console.log(JSON.stringify(workerResult, null, 2));
|
|
299
|
-
if (workerResult.status === "failed") {
|
|
300
|
-
process.exitCode = 1;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
223
|
async function cmdPrepareDispatch(argv) {
|
|
304
224
|
const runId = getFlag(argv, "--run-id");
|
|
305
225
|
if (!runId)
|
|
@@ -320,365 +240,6 @@ async function cmdPrepareDispatch(argv) {
|
|
|
320
240
|
});
|
|
321
241
|
console.log(JSON.stringify(result, null, 2));
|
|
322
242
|
}
|
|
323
|
-
async function cmdSubmitPacket(argv) {
|
|
324
|
-
const runId = resolveRunScopedArg(argv, "--run-id", "--run-id-b64");
|
|
325
|
-
const packetId = resolveRunScopedArg(argv, "--packet-id", "--packet-id-b64");
|
|
326
|
-
const artifactsDirB64 = getFlag(argv, "--artifacts-dir-b64");
|
|
327
|
-
const artifactsDir = artifactsDirB64
|
|
328
|
-
? resolve(fromBase64Url(artifactsDirB64))
|
|
329
|
-
: getArtifactsDir(argv);
|
|
330
|
-
if (!runId || !packetId) {
|
|
331
|
-
throw new Error("submit-packet requires --run-id and --packet-id (or --run-id-b64/--packet-id-b64)");
|
|
332
|
-
}
|
|
333
|
-
const runDir = join(artifactsDir, "runs", runId);
|
|
334
|
-
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
335
|
-
const resultMap = await loadDispatchResultMap(runDir);
|
|
336
|
-
if (!resultMap) {
|
|
337
|
-
throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
|
|
338
|
-
}
|
|
339
|
-
let packetEntries = resultMap.entries.filter((entry) => entry.packet_id === packetId);
|
|
340
|
-
let resolvedPacketId = packetId;
|
|
341
|
-
if (packetEntries.length === 0) {
|
|
342
|
-
const trimmed = packetId.trim();
|
|
343
|
-
packetEntries = resultMap.entries.filter((entry) => entry.packet_id.trim().toLowerCase() === trimmed.toLowerCase());
|
|
344
|
-
if (packetEntries.length > 0) {
|
|
345
|
-
resolvedPacketId = packetEntries[0].packet_id;
|
|
346
|
-
process.stderr.write(`[submit-packet] Resolved packet_id '${packetId}' → '${resolvedPacketId}' (case/whitespace normalization)\n`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (packetEntries.length === 0) {
|
|
350
|
-
const knownIds = [...new Set(resultMap.entries.map((e) => e.packet_id))];
|
|
351
|
-
throw new Error(`Unknown packet_id '${packetId}' for run ${runId}.\n` +
|
|
352
|
-
`Valid packet IDs: ${knownIds.join(", ")}`);
|
|
353
|
-
}
|
|
354
|
-
if (entriesByTaskId(packetEntries).size !== packetEntries.length) {
|
|
355
|
-
throw new Error(`Dispatch result map has duplicate task entries for packet '${resolvedPacketId}'.`);
|
|
356
|
-
}
|
|
357
|
-
const allTasks = await readJsonFile(tasksPath);
|
|
358
|
-
const taskById = new Map(allTasks.map((task) => [task.task_id, task]));
|
|
359
|
-
const packetTasks = packetEntries.map((entry) => taskById.get(entry.task_id));
|
|
360
|
-
const missingTask = packetEntries.find((entry, index) => !packetTasks[index]);
|
|
361
|
-
if (missingTask) {
|
|
362
|
-
throw new Error(`Dispatch result map references unknown task '${missingTask.task_id}'.`);
|
|
363
|
-
}
|
|
364
|
-
const tasks = packetTasks;
|
|
365
|
-
const expectedTaskIds = new Set(tasks.map((task) => task.task_id));
|
|
366
|
-
const lineIndex = Object.fromEntries(tasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
367
|
-
const encodedResults = getFlag(argv, "--results-b64");
|
|
368
|
-
const raw = encodedResults ? fromBase64Url(encodedResults) : await readStdinText();
|
|
369
|
-
if (raw.trim().length === 0) {
|
|
370
|
-
throw new Error("submit-packet requires an AuditResult[] JSON payload on stdin or --results-b64.");
|
|
371
|
-
}
|
|
372
|
-
let payload;
|
|
373
|
-
try {
|
|
374
|
-
payload = JSON.parse(raw);
|
|
375
|
-
}
|
|
376
|
-
catch (error) {
|
|
377
|
-
throw new Error(`Invalid submit-packet JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
378
|
-
}
|
|
379
|
-
const resultErrors = [];
|
|
380
|
-
const issues = validateAuditResults(payload, tasks, { lineIndex });
|
|
381
|
-
const validationErrors = issues.filter((issue) => issue.severity === "error");
|
|
382
|
-
const validationWarnings = issues.filter((issue) => issue.severity === "warning");
|
|
383
|
-
if (validationWarnings.length > 0) {
|
|
384
|
-
process.stderr.write(`audit-results validation: ${validationWarnings.length} warning(s):\n` +
|
|
385
|
-
formatAuditResultIssues(validationWarnings) +
|
|
386
|
-
"\n");
|
|
387
|
-
}
|
|
388
|
-
if (validationErrors.length > 0) {
|
|
389
|
-
resultErrors.push(formatAuditResultIssues(validationErrors));
|
|
390
|
-
}
|
|
391
|
-
if (Array.isArray(payload)) {
|
|
392
|
-
const seen = new Set();
|
|
393
|
-
for (const [index, result] of payload.entries()) {
|
|
394
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
const taskId = result.task_id;
|
|
398
|
-
if (typeof taskId !== "string" || taskId.trim().length === 0) {
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
if (seen.has(taskId)) {
|
|
402
|
-
resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
|
|
403
|
-
}
|
|
404
|
-
seen.add(taskId);
|
|
405
|
-
if (!expectedTaskIds.has(taskId)) {
|
|
406
|
-
resultErrors.push(`Result at index ${index} uses task_id '${taskId}', which is not assigned to packet '${resolvedPacketId}'.`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
for (const task of tasks) {
|
|
410
|
-
if (!seen.has(task.task_id)) {
|
|
411
|
-
resultErrors.push(`Missing audit result for assigned task '${task.task_id}'.`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (resultErrors.length > 0) {
|
|
416
|
-
throw new Error(`submit-packet rejected ${resolvedPacketId}:\n${resultErrors.join("\n")}`);
|
|
417
|
-
}
|
|
418
|
-
// Check for duplicate findings against already-submitted results in this run
|
|
419
|
-
const existingFindingKeys = new Set();
|
|
420
|
-
const otherEntries = resultMap.entries.filter((e) => e.packet_id !== resolvedPacketId);
|
|
421
|
-
for (const other of otherEntries) {
|
|
422
|
-
try {
|
|
423
|
-
const existing = JSON.parse(await readFile(other.result_path, "utf8"));
|
|
424
|
-
if (existing?.findings) {
|
|
425
|
-
for (const f of existing.findings) {
|
|
426
|
-
const key = [
|
|
427
|
-
(f.lens ?? "").trim().toLowerCase(),
|
|
428
|
-
(f.category ?? "").trim().toLowerCase(),
|
|
429
|
-
(f.title ?? "").trim().toLowerCase(),
|
|
430
|
-
f.affected_files?.[0]?.path ?? "",
|
|
431
|
-
].join("|");
|
|
432
|
-
existingFindingKeys.add(key);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
catch { /* file doesn't exist yet or invalid — skip */ }
|
|
437
|
-
}
|
|
438
|
-
let dupCount = 0;
|
|
439
|
-
for (const result of payload) {
|
|
440
|
-
for (const f of result.findings ?? []) {
|
|
441
|
-
const key = [
|
|
442
|
-
(f.lens ?? "").trim().toLowerCase(),
|
|
443
|
-
(f.category ?? "").trim().toLowerCase(),
|
|
444
|
-
(f.title ?? "").trim().toLowerCase(),
|
|
445
|
-
f.affected_files?.[0]?.path ?? "",
|
|
446
|
-
].join("|");
|
|
447
|
-
if (existingFindingKeys.has(key)) {
|
|
448
|
-
dupCount++;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if (dupCount > 0) {
|
|
453
|
-
process.stderr.write(`[submit-packet] Warning: ${dupCount} finding(s) appear to duplicate findings from other packets in this run.\n`);
|
|
454
|
-
}
|
|
455
|
-
const entryByTaskId = entriesByTaskId(packetEntries);
|
|
456
|
-
for (const result of payload) {
|
|
457
|
-
const entry = entryByTaskId.get(result.task_id);
|
|
458
|
-
if (!entry) {
|
|
459
|
-
throw new Error(`Internal error: no result path for accepted task '${result.task_id}'.`);
|
|
460
|
-
}
|
|
461
|
-
await writeJsonFile(entry.result_path, result);
|
|
462
|
-
}
|
|
463
|
-
const findingCount = payload.reduce((sum, result) => sum + result.findings.length, 0);
|
|
464
|
-
console.log(JSON.stringify({
|
|
465
|
-
run_id: runId,
|
|
466
|
-
packet_id: resolvedPacketId,
|
|
467
|
-
accepted_count: payload.length,
|
|
468
|
-
finding_count: findingCount,
|
|
469
|
-
...(dupCount > 0 ? { duplicate_warning_count: dupCount } : {}),
|
|
470
|
-
}, null, 2));
|
|
471
|
-
}
|
|
472
|
-
async function cmdMergeAndIngest(argv) {
|
|
473
|
-
const runId = getFlag(argv, "--run-id");
|
|
474
|
-
if (!runId)
|
|
475
|
-
throw new Error("merge-and-ingest requires --run-id <run_id>");
|
|
476
|
-
const artifactsDir = getArtifactsDir(argv);
|
|
477
|
-
const runDir = join(artifactsDir, "runs", runId);
|
|
478
|
-
const taskResultsDir = join(runDir, "task-results");
|
|
479
|
-
const auditResultsPath = join(runDir, "audit-results.json");
|
|
480
|
-
const taskPath = join(runDir, "task.json");
|
|
481
|
-
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
482
|
-
const workerTask = await readJsonFile(taskPath);
|
|
483
|
-
const resultMap = await loadDispatchResultMap(runDir);
|
|
484
|
-
if (!resultMap) {
|
|
485
|
-
throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
|
|
486
|
-
}
|
|
487
|
-
let allTasks = [];
|
|
488
|
-
try {
|
|
489
|
-
allTasks = await readJsonFile(tasksPath);
|
|
490
|
-
}
|
|
491
|
-
catch { /* may not exist */ }
|
|
492
|
-
const entryByTaskId = entriesByTaskId(resultMap.entries);
|
|
493
|
-
if (entryByTaskId.size !== resultMap.entries.length) {
|
|
494
|
-
throw new Error(`Dispatch result map for run ${runId} contains duplicate task entries.`);
|
|
495
|
-
}
|
|
496
|
-
const expectedPaths = new Set(resultMap.entries.map((entry) => resolve(entry.result_path)));
|
|
497
|
-
let files;
|
|
498
|
-
try {
|
|
499
|
-
files = (await readdir(taskResultsDir)).filter(f => f.endsWith(".json")).sort();
|
|
500
|
-
}
|
|
501
|
-
catch {
|
|
502
|
-
files = [];
|
|
503
|
-
}
|
|
504
|
-
const passing = [];
|
|
505
|
-
const failing = [];
|
|
506
|
-
const seenTaskIds = new Set();
|
|
507
|
-
let spuriousFileCount = 0;
|
|
508
|
-
const fallbackByTaskId = new Map();
|
|
509
|
-
for (const filename of files) {
|
|
510
|
-
const filePath = resolve(join(taskResultsDir, filename));
|
|
511
|
-
if (expectedPaths.has(filePath))
|
|
512
|
-
continue;
|
|
513
|
-
// Not part of this round's plan. Still read it so a current task can be
|
|
514
|
-
// recovered by task_id (e.g. a subagent wrote a valid result under a
|
|
515
|
-
// non-assigned name).
|
|
516
|
-
try {
|
|
517
|
-
const raw = await readFile(filePath, "utf8");
|
|
518
|
-
const parsed = JSON.parse(raw);
|
|
519
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
520
|
-
const tid = typeof parsed.task_id === "string"
|
|
521
|
-
? String(parsed.task_id) : undefined;
|
|
522
|
-
if (tid && !fallbackByTaskId.has(tid)) {
|
|
523
|
-
fallbackByTaskId.set(tid, parsed);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
catch { /* not parseable — skip */ }
|
|
528
|
-
// Only genuinely stray files are "spurious". Canonical per-task result files
|
|
529
|
-
// (<stem>_<digest>.json) left by prior deepening rounds in the same
|
|
530
|
-
// task-results/ dir are legitimate and must not inflate the count or bury
|
|
531
|
-
// the real stray-file signal (3 -> 191 over a run before this fix).
|
|
532
|
-
if (!isCanonicalResultFilename(filename)) {
|
|
533
|
-
spuriousFileCount++;
|
|
534
|
-
process.stderr.write(`[merge-and-ingest] Warning: unexpected file in task-results/: ${filename}\n`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
for (const task of allTasks) {
|
|
538
|
-
const entry = entryByTaskId.get(task.task_id);
|
|
539
|
-
if (!entry) {
|
|
540
|
-
failing.push({
|
|
541
|
-
task_id: task.task_id,
|
|
542
|
-
errors: ["Missing dispatch result-map entry for assigned task."],
|
|
543
|
-
});
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
const filePath = entry.result_path;
|
|
547
|
-
let obj;
|
|
548
|
-
try {
|
|
549
|
-
obj = JSON.parse(await readFile(filePath, "utf8"));
|
|
550
|
-
}
|
|
551
|
-
catch (e) {
|
|
552
|
-
if (isFileMissingError(e)) {
|
|
553
|
-
const fallback = fallbackByTaskId.get(task.task_id);
|
|
554
|
-
if (fallback) {
|
|
555
|
-
process.stderr.write(`[merge-and-ingest] Recovered result for '${task.task_id}' from unexpected file (matched by task_id)\n`);
|
|
556
|
-
obj = fallback;
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
failing.push({
|
|
560
|
-
task_id: task.task_id,
|
|
561
|
-
errors: ["Missing audit result for assigned task."],
|
|
562
|
-
});
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
failing.push({ task_id: task.task_id, errors: [`Invalid JSON: ${e.message}`] });
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const record = obj && typeof obj === "object" && !Array.isArray(obj)
|
|
572
|
-
? obj
|
|
573
|
-
: undefined;
|
|
574
|
-
const taskId = typeof record?.task_id === "string"
|
|
575
|
-
? String(record.task_id) : undefined;
|
|
576
|
-
const resultErrors = [];
|
|
577
|
-
if (taskId) {
|
|
578
|
-
if (seenTaskIds.has(taskId)) {
|
|
579
|
-
resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
seenTaskIds.add(taskId);
|
|
583
|
-
}
|
|
584
|
-
if (taskId !== task.task_id) {
|
|
585
|
-
resultErrors.push(`Result file is assigned to '${task.task_id}' but contains task_id '${taskId}'.`);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
const issues = validateAuditResults([obj], [task], { lineIndex: task.file_line_counts ?? {} });
|
|
589
|
-
resultErrors.push(...issues
|
|
590
|
-
.filter(i => i.severity === "error")
|
|
591
|
-
.map(i => i.message));
|
|
592
|
-
if (resultErrors.length === 0) {
|
|
593
|
-
passing.push(obj);
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
failing.push({ task_id: taskId ?? task.task_id, errors: resultErrors });
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
await writeJsonFile(auditResultsPath, passing);
|
|
600
|
-
const failedTasksPath = join(runDir, "failed-tasks.json");
|
|
601
|
-
if (failing.length > 0) {
|
|
602
|
-
await writeJsonFile(failedTasksPath, failing);
|
|
603
|
-
}
|
|
604
|
-
if (passing.length === 0 && failing.length > 0) {
|
|
605
|
-
throw new Error(`All ${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
|
|
606
|
-
}
|
|
607
|
-
const findingCount = passing.reduce((sum, result) => sum + result.findings.length, 0);
|
|
608
|
-
let result = null;
|
|
609
|
-
if (passing.length > 0) {
|
|
610
|
-
result = await runAuditStep({
|
|
611
|
-
root: workerTask.repo_root,
|
|
612
|
-
artifactsDir,
|
|
613
|
-
preferredExecutor: "result_ingestion_executor",
|
|
614
|
-
auditResultsPath,
|
|
615
|
-
});
|
|
616
|
-
const updatedPendingTasks = await addFileLineCountHints(workerTask.repo_root, buildPendingAuditTasks(result.updated_bundle));
|
|
617
|
-
await writeJsonFile(tasksPath, updatedPendingTasks);
|
|
618
|
-
}
|
|
619
|
-
const activeDispatchPath = join(artifactsDir, ACTIVE_DISPATCH_FILENAME);
|
|
620
|
-
try {
|
|
621
|
-
const dispatch = await readJsonFile(activeDispatchPath);
|
|
622
|
-
if (dispatch.run_id === runId) {
|
|
623
|
-
dispatch.status = failing.length > 0 ? "active" : "merged";
|
|
624
|
-
await writeJsonFile(activeDispatchPath, dispatch);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
catch { /* no active dispatch file — skip */ }
|
|
628
|
-
let retryDispatchPath = null;
|
|
629
|
-
if (failing.length > 0) {
|
|
630
|
-
const failedTaskIds = new Set(failing.map((f) => f.task_id));
|
|
631
|
-
const failedPacketIds = [
|
|
632
|
-
...new Set(resultMap.entries
|
|
633
|
-
.filter((e) => failedTaskIds.has(e.task_id))
|
|
634
|
-
.map((e) => e.packet_id)),
|
|
635
|
-
];
|
|
636
|
-
const retryDispatch = {
|
|
637
|
-
run_id: runId,
|
|
638
|
-
retry_packet_ids: failedPacketIds,
|
|
639
|
-
failed_task_count: failing.length,
|
|
640
|
-
accepted_task_count: passing.length,
|
|
641
|
-
};
|
|
642
|
-
retryDispatchPath = join(runDir, "retry-dispatch.json");
|
|
643
|
-
await writeJsonFile(retryDispatchPath, retryDispatch);
|
|
644
|
-
process.stderr.write(`[merge-and-ingest] ${passing.length} accepted, ${failing.length} failed. ` +
|
|
645
|
-
`Retry packets: ${failedPacketIds.join(", ")}\n`);
|
|
646
|
-
}
|
|
647
|
-
const status = failing.length > 0
|
|
648
|
-
? "partial"
|
|
649
|
-
: (result?.progress_made ? "completed" : "no_progress");
|
|
650
|
-
const workerResult = buildWorkerResult({
|
|
651
|
-
runId,
|
|
652
|
-
obligationId: workerTask.obligation_id,
|
|
653
|
-
status: failing.length > 0 ? "no_progress" : (result?.progress_made ? "completed" : "no_progress"),
|
|
654
|
-
progressMade: result?.progress_made ?? false,
|
|
655
|
-
selectedExecutor: result?.selected_executor ?? null,
|
|
656
|
-
artifactsWritten: result?.artifacts_written ?? [],
|
|
657
|
-
summary: result?.progress_summary ?? `${failing.length} task(s) failed`,
|
|
658
|
-
nextLikelyStep: result?.next_likely_step ?? null,
|
|
659
|
-
errors: [],
|
|
660
|
-
});
|
|
661
|
-
await writeJsonFile(workerTask.result_path, workerResult);
|
|
662
|
-
console.log(JSON.stringify({
|
|
663
|
-
run_id: runId,
|
|
664
|
-
status,
|
|
665
|
-
accepted_count: passing.length,
|
|
666
|
-
rejected_count: failing.length,
|
|
667
|
-
spurious_file_count: spuriousFileCount,
|
|
668
|
-
finding_count: findingCount,
|
|
669
|
-
audit_results_path: auditResultsPath,
|
|
670
|
-
...(retryDispatchPath ? { retry_dispatch_path: retryDispatchPath } : {}),
|
|
671
|
-
...(result ? {
|
|
672
|
-
selected_executor: workerResult.selected_executor,
|
|
673
|
-
progress_made: workerResult.progress_made,
|
|
674
|
-
progress_summary: workerResult.summary,
|
|
675
|
-
next_likely_step: workerResult.next_likely_step,
|
|
676
|
-
} : {}),
|
|
677
|
-
}, null, 2));
|
|
678
|
-
if (failing.length > 0) {
|
|
679
|
-
process.exitCode = 2;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
243
|
async function cmdValidateResult(argv) {
|
|
683
244
|
const rawRunId = getFlag(argv, "--run-id");
|
|
684
245
|
const runIdB64 = getFlag(argv, "--run-id-b64");
|
|
@@ -870,7 +431,7 @@ async function cmdValidate(argv) {
|
|
|
870
431
|
: prefixValidationIssues("session_config", validateSessionConfig(rawSessionConfig));
|
|
871
432
|
const providerIssues = rawSessionConfig === undefined || sessionConfigIssues.length > 0
|
|
872
433
|
? []
|
|
873
|
-
: prefixValidationIssues("session_config", validateConfiguredProviderEnvironment(rawSessionConfig));
|
|
434
|
+
: prefixValidationIssues("session_config", await validateConfiguredProviderEnvironment(rawSessionConfig));
|
|
874
435
|
const issues = [
|
|
875
436
|
...artifactIssues,
|
|
876
437
|
...sessionConfigIssues,
|
|
@@ -978,114 +539,6 @@ async function cmdCleanup(argv) {
|
|
|
978
539
|
dry_run: dryRun,
|
|
979
540
|
}, null, 2));
|
|
980
541
|
}
|
|
981
|
-
async function cmdStatus(argv) {
|
|
982
|
-
const artifactsDir = getArtifactsDir(argv);
|
|
983
|
-
const auditStatePath = join(artifactsDir, "audit_state.json");
|
|
984
|
-
// 1. Read audit_state.json
|
|
985
|
-
let auditState = null;
|
|
986
|
-
try {
|
|
987
|
-
auditState = await readJsonFile(auditStatePath);
|
|
988
|
-
}
|
|
989
|
-
catch (error) {
|
|
990
|
-
if (!isFileMissingError(error)) {
|
|
991
|
-
throw error;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
if (!auditState) {
|
|
995
|
-
console.error("No audit_state.json found; no active audit in this artifacts directory.");
|
|
996
|
-
process.exitCode = 1;
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
// Build obligations summary: count by state
|
|
1000
|
-
const obligationStates = {
|
|
1001
|
-
missing: 0,
|
|
1002
|
-
present: 0,
|
|
1003
|
-
stale: 0,
|
|
1004
|
-
blocked: 0,
|
|
1005
|
-
satisfied: 0,
|
|
1006
|
-
};
|
|
1007
|
-
for (const obligation of auditState.obligations ?? []) {
|
|
1008
|
-
const state = obligation.state;
|
|
1009
|
-
if (state in obligationStates) {
|
|
1010
|
-
obligationStates[state]++;
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
// 2. Read run ledger for last N entries
|
|
1014
|
-
const ledger = await loadRunLedger(artifactsDir);
|
|
1015
|
-
const RECENT_RUN_LIMIT = 5;
|
|
1016
|
-
const recentRuns = ledger.runs
|
|
1017
|
-
.slice(-RECENT_RUN_LIMIT)
|
|
1018
|
-
.reverse()
|
|
1019
|
-
.map((entry) => ({
|
|
1020
|
-
run_id: entry.run_id,
|
|
1021
|
-
obligation_id: entry.obligation_id,
|
|
1022
|
-
status: entry.status,
|
|
1023
|
-
started_at: entry.started_at,
|
|
1024
|
-
}));
|
|
1025
|
-
// 3. Find the most recent run directory and read pending-audit-tasks.json
|
|
1026
|
-
let pendingTasksSummary = null;
|
|
1027
|
-
const runsDir = join(artifactsDir, "runs");
|
|
1028
|
-
let runDirs = [];
|
|
1029
|
-
try {
|
|
1030
|
-
const entries = await readdir(runsDir, { withFileTypes: true });
|
|
1031
|
-
runDirs = entries
|
|
1032
|
-
.filter((e) => e.isDirectory())
|
|
1033
|
-
.map((e) => e.name)
|
|
1034
|
-
.sort()
|
|
1035
|
-
.reverse();
|
|
1036
|
-
}
|
|
1037
|
-
catch {
|
|
1038
|
-
// runs directory may not exist yet
|
|
1039
|
-
}
|
|
1040
|
-
for (const runDirName of runDirs) {
|
|
1041
|
-
const runDir = join(runsDir, runDirName);
|
|
1042
|
-
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1043
|
-
let tasks = null;
|
|
1044
|
-
try {
|
|
1045
|
-
tasks = await readJsonFile(tasksPath);
|
|
1046
|
-
}
|
|
1047
|
-
catch {
|
|
1048
|
-
continue; // no pending-audit-tasks.json in this run dir — try previous
|
|
1049
|
-
}
|
|
1050
|
-
if (!Array.isArray(tasks))
|
|
1051
|
-
continue;
|
|
1052
|
-
// Count remaining: tasks without status "complete"
|
|
1053
|
-
const total = tasks.length;
|
|
1054
|
-
const remaining = tasks.filter((t) => t.status !== "complete").length;
|
|
1055
|
-
pendingTasksSummary = {
|
|
1056
|
-
run_id: runDirName,
|
|
1057
|
-
total,
|
|
1058
|
-
remaining,
|
|
1059
|
-
};
|
|
1060
|
-
break;
|
|
1061
|
-
}
|
|
1062
|
-
// 4. Surface failed-tasks.json from the most recent run that has one
|
|
1063
|
-
let failedTasks = null;
|
|
1064
|
-
for (const runDirName of runDirs) {
|
|
1065
|
-
const failedTasksPath = join(runsDir, runDirName, "failed-tasks.json");
|
|
1066
|
-
try {
|
|
1067
|
-
const raw = await readJsonFile(failedTasksPath);
|
|
1068
|
-
if (Array.isArray(raw) && raw.length > 0) {
|
|
1069
|
-
failedTasks = raw;
|
|
1070
|
-
break;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
catch {
|
|
1074
|
-
// Not present in this run dir — keep looking
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
console.log(JSON.stringify({
|
|
1078
|
-
artifacts_dir: artifactsDir,
|
|
1079
|
-
status: auditState.status,
|
|
1080
|
-
last_obligation: auditState.last_obligation ?? null,
|
|
1081
|
-
last_executor: auditState.last_executor ?? null,
|
|
1082
|
-
blockers: auditState.blockers ?? [],
|
|
1083
|
-
obligations_summary: obligationStates,
|
|
1084
|
-
recent_runs: recentRuns,
|
|
1085
|
-
pending_tasks: pendingTasksSummary,
|
|
1086
|
-
failed_tasks: failedTasks,
|
|
1087
|
-
}, null, 2));
|
|
1088
|
-
}
|
|
1089
542
|
async function cmdMcp(argv) {
|
|
1090
543
|
await runAuditCodeMcpServer(argv.slice(3));
|
|
1091
544
|
}
|
|
@@ -1094,14 +547,13 @@ async function cmdQuota(argv) {
|
|
|
1094
547
|
const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
|
|
1095
548
|
const explicitProvider = getExplicitProvider(argv);
|
|
1096
549
|
const hostModel = getHostModel(argv);
|
|
1097
|
-
const probeMode = getQuotaProbeMode(argv, sessionConfig);
|
|
1098
550
|
const providerName = resolveFreshSessionProviderName(explicitProvider, sessionConfig);
|
|
1099
551
|
const providerModelKey = buildProviderModelKey(providerName, hostModel);
|
|
1100
552
|
const { limits, source, confidence } = resolveLimits({ providerName, sessionConfig, hostModel });
|
|
1101
|
-
const probeResult = await probeProvider(providerName, probeMode);
|
|
1102
553
|
const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
|
|
1103
554
|
const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
|
|
1104
|
-
const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ??
|
|
555
|
+
const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ??
|
|
556
|
+
DEFAULT_EMPIRICAL_HALF_LIFE_HOURS;
|
|
1105
557
|
const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
|
|
1106
558
|
explicitLimit: getHostMaxActiveSubagents(argv),
|
|
1107
559
|
sessionConfig,
|
|
@@ -1127,7 +579,6 @@ async function cmdQuota(argv) {
|
|
|
1127
579
|
confidence,
|
|
1128
580
|
source,
|
|
1129
581
|
host_concurrency_limit: hostConcurrencyLimit,
|
|
1130
|
-
probe: probeResult,
|
|
1131
582
|
learned_caps: quotaStateEntry
|
|
1132
583
|
? {
|
|
1133
584
|
max_safe_concurrency: computeMaxSafeConcurrency(quotaStateEntry, halfLifeHours),
|
|
@@ -9,7 +9,10 @@ import { normalizeGraphPath } from "../graphPathUtils.js";
|
|
|
9
9
|
function supports(file) {
|
|
10
10
|
return normalizeGraphPath(file).toLowerCase().endsWith(".sql");
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
// Signature matches the `LanguageAnalyzer.analyze` interface (the params the
|
|
13
|
+
// other analyzers receive) so the registry type stays uniform; the stub
|
|
14
|
+
// ignores them and emits no edges yet.
|
|
15
|
+
function analyze(_files, _context) {
|
|
13
16
|
return { edges: [] };
|
|
14
17
|
}
|
|
15
18
|
export const sqlAnalyzer = {
|