auditor-lambda 0.2.11 → 0.2.13
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/dist/cli.js +138 -25
- package/dist/extractors/disposition.js +15 -1
- package/dist/extractors/pathPatterns.d.ts +2 -0
- package/dist/extractors/pathPatterns.js +6 -0
- package/dist/io/artifacts.d.ts +3 -0
- package/dist/io/artifacts.js +3 -0
- package/dist/io/runArtifacts.d.ts +13 -1
- package/dist/io/runArtifacts.js +53 -9
- package/dist/io/toolingManifest.d.ts +2 -0
- package/dist/io/toolingManifest.js +75 -0
- package/dist/orchestrator/advance.js +6 -2
- package/dist/orchestrator/artifactMetadata.d.ts +1 -1
- package/dist/orchestrator/artifactMetadata.js +15 -2
- package/dist/orchestrator/dependencyMap.js +3 -0
- package/dist/orchestrator/internalExecutors.js +16 -0
- package/dist/orchestrator/staleness.js +48 -1
- package/dist/supervisor/operatorHandoff.d.ts +13 -0
- package/dist/supervisor/operatorHandoff.js +48 -4
- package/dist/types/toolingManifest.d.ts +7 -0
- package/dist/types/toolingManifest.js +1 -0
- package/dist/validation/artifacts.js +3 -0
- package/package.json +1 -1
- package/schemas/audit-code-v1alpha1.schema.json +47 -0
- package/skills/audit-code/SKILL.md +9 -0
- package/skills/audit-code/audit-code.prompt.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./
|
|
|
24
24
|
import { appendRunLedgerEntry } from "./supervisor/runLedger.js";
|
|
25
25
|
import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./supervisor/operatorHandoff.js";
|
|
26
26
|
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
27
|
-
import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
27
|
+
import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
28
28
|
import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
|
|
29
29
|
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
30
30
|
import { runAuditCodeMcpServer } from "./mcp/server.js";
|
|
@@ -154,6 +154,7 @@ async function emitEnvelope(params) {
|
|
|
154
154
|
providerName: params.providerName,
|
|
155
155
|
progressSummary: params.progress_summary,
|
|
156
156
|
isConfigError: params.isConfigError,
|
|
157
|
+
activeReviewRun: params.activeReviewRun,
|
|
157
158
|
});
|
|
158
159
|
await writeAuditCodeHandoffArtifacts(handoff);
|
|
159
160
|
console.log(JSON.stringify(buildEnvelope({
|
|
@@ -169,7 +170,7 @@ async function emitEnvelope(params) {
|
|
|
169
170
|
}
|
|
170
171
|
function buildManualReviewBlocker(providerName) {
|
|
171
172
|
return providerName === LOCAL_SUBPROCESS_PROVIDER_NAME
|
|
172
|
-
? "Automatic backend steps are exhausted. Remaining semantic review now belongs to the active conversation agent. Review the dispatched files, write structured audit results
|
|
173
|
+
? "Automatic backend steps are exhausted. Remaining semantic review now belongs to the active conversation agent. Review the dispatched files, write structured audit results to the run-scoped audit_results_path, and execute the worker_command from current-task.json exactly as written. If you intentionally want a backend bridge instead, re-run audit-code with --provider auto, --provider claude-code, --provider opencode, --provider subprocess-template, or --provider vscode-task."
|
|
173
174
|
: "Automatic work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider.";
|
|
174
175
|
}
|
|
175
176
|
function shouldRunInlineExecutor(selectedExecutor) {
|
|
@@ -399,7 +400,7 @@ async function ingestBatchAuditResults(options) {
|
|
|
399
400
|
}
|
|
400
401
|
const bundle = lastStep?.updated_bundle ??
|
|
401
402
|
(await loadArtifactBundle(options.artifactsDir));
|
|
402
|
-
const state =
|
|
403
|
+
const state = deriveAuditState(bundle);
|
|
403
404
|
const decision = decideNextStep(bundle);
|
|
404
405
|
return {
|
|
405
406
|
batchFiles,
|
|
@@ -416,6 +417,51 @@ async function ingestBatchAuditResults(options) {
|
|
|
416
417
|
next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
|
|
417
418
|
};
|
|
418
419
|
}
|
|
420
|
+
function buildWorkerResult(params) {
|
|
421
|
+
return {
|
|
422
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
423
|
+
run_id: params.runId,
|
|
424
|
+
obligation_id: params.obligationId,
|
|
425
|
+
status: params.status,
|
|
426
|
+
progress_made: params.progressMade,
|
|
427
|
+
selected_executor: params.selectedExecutor,
|
|
428
|
+
artifacts_written: params.artifactsWritten,
|
|
429
|
+
summary: params.summary,
|
|
430
|
+
next_likely_step: params.nextLikelyStep,
|
|
431
|
+
errors: params.errors,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async function persistWorkerRunArtifacts(paths, workerResult, executionMode) {
|
|
435
|
+
await writeJsonFile(paths.resultPath, workerResult);
|
|
436
|
+
await writeJsonFile(paths.statusPath, {
|
|
437
|
+
run_id: workerResult.run_id,
|
|
438
|
+
status: workerResult.status,
|
|
439
|
+
execution_mode: executionMode,
|
|
440
|
+
result_path: paths.resultPath,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
async function persistConfigErrorHandoff(params) {
|
|
444
|
+
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
445
|
+
const blockedState = buildBlockedAuditState({
|
|
446
|
+
state: bundle.audit_state ?? deriveAuditState(bundle),
|
|
447
|
+
obligationId: null,
|
|
448
|
+
executor: null,
|
|
449
|
+
blocker: params.progressSummary,
|
|
450
|
+
});
|
|
451
|
+
await writeCoreArtifacts(params.artifactsDir, {
|
|
452
|
+
...bundle,
|
|
453
|
+
audit_state: blockedState,
|
|
454
|
+
});
|
|
455
|
+
const handoff = buildAuditCodeHandoff({
|
|
456
|
+
root: params.root,
|
|
457
|
+
artifactsDir: params.artifactsDir,
|
|
458
|
+
state: blockedState,
|
|
459
|
+
bundle: { ...bundle, audit_state: blockedState },
|
|
460
|
+
progressSummary: params.progressSummary,
|
|
461
|
+
isConfigError: true,
|
|
462
|
+
});
|
|
463
|
+
await writeAuditCodeHandoffArtifacts(handoff);
|
|
464
|
+
}
|
|
419
465
|
function isWorkerResult(value) {
|
|
420
466
|
return (typeof value === "object" &&
|
|
421
467
|
value !== null &&
|
|
@@ -499,7 +545,20 @@ export async function runSample(argv = process.argv) {
|
|
|
499
545
|
async function cmdAdvanceAudit(argv) {
|
|
500
546
|
const root = getRootDir(argv);
|
|
501
547
|
const artifactsDir = getArtifactsDir(argv);
|
|
502
|
-
|
|
548
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
549
|
+
await ensureSupervisorDirs(artifactsDir);
|
|
550
|
+
let sessionConfig;
|
|
551
|
+
try {
|
|
552
|
+
sessionConfig = await loadSessionConfig(artifactsDir);
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
await persistConfigErrorHandoff({
|
|
556
|
+
root,
|
|
557
|
+
artifactsDir,
|
|
558
|
+
progressSummary: error instanceof Error ? error.message : String(error),
|
|
559
|
+
});
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
503
562
|
const providerName = resolveRunProviderName(argv, sessionConfig);
|
|
504
563
|
const batchResultsDir = getBatchResultsDir(argv);
|
|
505
564
|
if (batchResultsDir && getFlag(argv, "--results")) {
|
|
@@ -565,7 +624,20 @@ async function cmdAdvanceAudit(argv) {
|
|
|
565
624
|
async function cmdRunToCompletion(argv) {
|
|
566
625
|
const root = getRootDir(argv);
|
|
567
626
|
const artifactsDir = getArtifactsDir(argv);
|
|
568
|
-
|
|
627
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
628
|
+
await ensureSupervisorDirs(artifactsDir);
|
|
629
|
+
let sessionConfig;
|
|
630
|
+
try {
|
|
631
|
+
sessionConfig = await loadSessionConfig(artifactsDir);
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
await persistConfigErrorHandoff({
|
|
635
|
+
root,
|
|
636
|
+
artifactsDir,
|
|
637
|
+
progressSummary: error instanceof Error ? error.message : String(error),
|
|
638
|
+
});
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
569
641
|
const explicitProvider = getExplicitProvider(argv);
|
|
570
642
|
const provider = createFreshSessionProvider(explicitProvider ?? LOCAL_SUBPROCESS_PROVIDER_NAME, sessionConfig);
|
|
571
643
|
const uiMode = getUiMode(argv, sessionConfig.ui_mode ?? "headless");
|
|
@@ -574,8 +646,6 @@ async function cmdRunToCompletion(argv) {
|
|
|
574
646
|
const parallelWorkers = getParallelWorkers(argv, sessionConfig);
|
|
575
647
|
const timeoutMs = getTimeoutMs(argv, sessionConfig);
|
|
576
648
|
const selfCliPath = resolve(argv[1] ?? process.argv[1] ?? "");
|
|
577
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
578
|
-
await ensureSupervisorDirs(artifactsDir);
|
|
579
649
|
const batchResultsDir = getBatchResultsDir(argv);
|
|
580
650
|
if (batchResultsDir && getFlag(argv, "--results")) {
|
|
581
651
|
throw new Error("Use either --results <file> or --batch-results <dir>, not both.");
|
|
@@ -650,7 +720,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
650
720
|
if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
|
|
651
721
|
const blocker = buildManualReviewBlocker(provider.name);
|
|
652
722
|
const blockedState = buildBlockedAuditState({
|
|
653
|
-
state:
|
|
723
|
+
state: decision.state,
|
|
654
724
|
obligationId,
|
|
655
725
|
executor: preferredExecutor,
|
|
656
726
|
blocker,
|
|
@@ -702,11 +772,19 @@ async function cmdRunToCompletion(argv) {
|
|
|
702
772
|
progress_summary: blocker,
|
|
703
773
|
next_likely_step: null,
|
|
704
774
|
providerName: provider.name,
|
|
775
|
+
activeReviewRun: {
|
|
776
|
+
run_id: blockRunId,
|
|
777
|
+
task_path: blockPaths.taskPath,
|
|
778
|
+
prompt_path: blockPaths.promptPath,
|
|
779
|
+
pending_audit_tasks_path: blockPendingTasksPath,
|
|
780
|
+
audit_results_path: blockAuditResultsPath,
|
|
781
|
+
worker_command: blockTask.worker_command,
|
|
782
|
+
},
|
|
705
783
|
});
|
|
706
784
|
return;
|
|
707
785
|
}
|
|
708
786
|
if (!preferredExecutor) {
|
|
709
|
-
const state =
|
|
787
|
+
const state = decision.state;
|
|
710
788
|
await clearDispatchFiles(artifactsDir);
|
|
711
789
|
await emitEnvelope({
|
|
712
790
|
root,
|
|
@@ -758,10 +836,19 @@ async function cmdRunToCompletion(argv) {
|
|
|
758
836
|
max_retries: 0,
|
|
759
837
|
};
|
|
760
838
|
const slotPrompt = renderWorkerPrompt(slotTask);
|
|
761
|
-
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group);
|
|
839
|
+
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
|
|
762
840
|
await writeJsonFile(slotPendingTasksPath, group);
|
|
763
841
|
workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
|
|
764
842
|
}
|
|
843
|
+
await writeDispatchBatchFiles(artifactsDir, workerSlots.map((slot) => ({
|
|
844
|
+
run_id: slot.runId,
|
|
845
|
+
task_path: slot.paths.taskPath,
|
|
846
|
+
prompt_path: slot.paths.promptPath,
|
|
847
|
+
result_path: slot.paths.resultPath,
|
|
848
|
+
status_path: slot.paths.statusPath,
|
|
849
|
+
audit_results_path: slot.auditResultsPath,
|
|
850
|
+
pending_audit_tasks_path: slot.pendingTasksPath,
|
|
851
|
+
})), workerSlots.flatMap((slot) => slot.group));
|
|
765
852
|
const parallelStartedAt = new Date().toISOString();
|
|
766
853
|
const launchResults = await Promise.allSettled(workerSlots.map((slot) => provider.launch({
|
|
767
854
|
repoRoot: root,
|
|
@@ -791,7 +878,17 @@ async function cmdRunToCompletion(argv) {
|
|
|
791
878
|
const batchErrors = [];
|
|
792
879
|
for (const slot of workerSlots) {
|
|
793
880
|
const parallelEndedAt = new Date().toISOString();
|
|
794
|
-
let
|
|
881
|
+
let workerResult = buildWorkerResult({
|
|
882
|
+
runId: slot.runId,
|
|
883
|
+
obligationId,
|
|
884
|
+
status: "no_progress",
|
|
885
|
+
progressMade: false,
|
|
886
|
+
selectedExecutor: "agent",
|
|
887
|
+
artifactsWritten: [],
|
|
888
|
+
summary: "Parallel worker batch made no progress.",
|
|
889
|
+
nextLikelyStep: obligationId,
|
|
890
|
+
errors: [],
|
|
891
|
+
});
|
|
795
892
|
try {
|
|
796
893
|
const launchError = launchErrorsByRunId.get(slot.runId);
|
|
797
894
|
if (launchError) {
|
|
@@ -822,7 +919,17 @@ async function cmdRunToCompletion(argv) {
|
|
|
822
919
|
preferredExecutor: "result_ingestion_executor",
|
|
823
920
|
auditResultsPath: slot.auditResultsPath,
|
|
824
921
|
});
|
|
825
|
-
|
|
922
|
+
workerResult = buildWorkerResult({
|
|
923
|
+
runId: slot.runId,
|
|
924
|
+
obligationId,
|
|
925
|
+
status: stepResult.progress_made ? "completed" : "no_progress",
|
|
926
|
+
progressMade: stepResult.progress_made,
|
|
927
|
+
selectedExecutor: stepResult.selected_executor,
|
|
928
|
+
artifactsWritten: stepResult.artifacts_written,
|
|
929
|
+
summary: stepResult.progress_summary,
|
|
930
|
+
nextLikelyStep: stepResult.next_likely_step,
|
|
931
|
+
errors: [],
|
|
932
|
+
});
|
|
826
933
|
batchProgress ||= stepResult.progress_made;
|
|
827
934
|
if (stepResult.progress_made)
|
|
828
935
|
anyProgress = true;
|
|
@@ -830,17 +937,28 @@ async function cmdRunToCompletion(argv) {
|
|
|
830
937
|
artifactsWritten.add(a);
|
|
831
938
|
}
|
|
832
939
|
catch (error) {
|
|
833
|
-
slotStatus = "failed";
|
|
834
940
|
const message = error instanceof Error ? error.message : String(error);
|
|
835
941
|
batchErrors.push(`${slot.runId}: ${message}`);
|
|
942
|
+
workerResult = buildWorkerResult({
|
|
943
|
+
runId: slot.runId,
|
|
944
|
+
obligationId,
|
|
945
|
+
status: "failed",
|
|
946
|
+
progressMade: false,
|
|
947
|
+
selectedExecutor: "agent",
|
|
948
|
+
artifactsWritten: [],
|
|
949
|
+
summary: `Worker failed for executor agent: ${message}`,
|
|
950
|
+
nextLikelyStep: obligationId,
|
|
951
|
+
errors: [message],
|
|
952
|
+
});
|
|
836
953
|
process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
|
|
837
954
|
}
|
|
955
|
+
await persistWorkerRunArtifacts(slot.paths, workerResult, "parallel-deferred-agent");
|
|
838
956
|
await appendRunLedgerEntry(artifactsDir, {
|
|
839
957
|
run_id: slot.runId,
|
|
840
958
|
provider: provider.name,
|
|
841
959
|
obligation_id: obligationId,
|
|
842
|
-
selected_executor:
|
|
843
|
-
status:
|
|
960
|
+
selected_executor: workerResult.selected_executor,
|
|
961
|
+
status: workerResult.status,
|
|
844
962
|
started_at: parallelStartedAt,
|
|
845
963
|
ended_at: parallelEndedAt,
|
|
846
964
|
result_path: slot.paths.resultPath,
|
|
@@ -940,12 +1058,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
940
1058
|
errors: [message],
|
|
941
1059
|
};
|
|
942
1060
|
}
|
|
943
|
-
await
|
|
944
|
-
await writeJsonFile(paths.statusPath, {
|
|
945
|
-
run_id: runId,
|
|
946
|
-
status: workerResult.status,
|
|
947
|
-
execution_mode: "inline",
|
|
948
|
-
});
|
|
1061
|
+
await persistWorkerRunArtifacts(paths, workerResult, "inline");
|
|
949
1062
|
await appendRunLedgerEntry(artifactsDir, {
|
|
950
1063
|
run_id: runId,
|
|
951
1064
|
provider: provider.name,
|
|
@@ -1098,7 +1211,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
1098
1211
|
next_likely_step: decision.selected_obligation,
|
|
1099
1212
|
errors: [message],
|
|
1100
1213
|
};
|
|
1101
|
-
await
|
|
1214
|
+
await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
|
|
1102
1215
|
}
|
|
1103
1216
|
await appendRunLedgerEntry(artifactsDir, {
|
|
1104
1217
|
run_id: runId,
|
|
@@ -1138,12 +1251,12 @@ async function cmdRunToCompletion(argv) {
|
|
|
1138
1251
|
const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
|
|
1139
1252
|
const state = shouldBlock
|
|
1140
1253
|
? buildBlockedAuditState({
|
|
1141
|
-
state:
|
|
1254
|
+
state: deriveAuditState(bundleAfter),
|
|
1142
1255
|
obligationId: workerResult.obligation_id,
|
|
1143
1256
|
executor: workerResult.selected_executor,
|
|
1144
1257
|
blocker: buildWorkerFailureBlocker(workerResult),
|
|
1145
1258
|
})
|
|
1146
|
-
:
|
|
1259
|
+
: deriveAuditState(bundleAfter);
|
|
1147
1260
|
if (shouldBlock) {
|
|
1148
1261
|
await writeCoreArtifacts(artifactsDir, {
|
|
1149
1262
|
...bundleAfter,
|
|
@@ -1172,7 +1285,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
1172
1285
|
}
|
|
1173
1286
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1174
1287
|
const decision = decideNextStep(bundle);
|
|
1175
|
-
const state =
|
|
1288
|
+
const state = decision.state;
|
|
1176
1289
|
if (state.status === "complete") {
|
|
1177
1290
|
await clearDispatchFiles(artifactsDir);
|
|
1178
1291
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, normalizeExtractorPath, } from "./pathPatterns.js";
|
|
1
|
+
import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isAuditArtifactPath, isGeneratedInstallArtifactPath, normalizeExtractorPath, } from "./pathPatterns.js";
|
|
2
2
|
function inferDisposition(path) {
|
|
3
3
|
const normalized = normalizeExtractorPath(path);
|
|
4
4
|
if (isNodeModulesOrGit(normalized)) {
|
|
@@ -26,9 +26,23 @@ function inferDisposition(path) {
|
|
|
26
26
|
if (isLockfilePath(normalized)) {
|
|
27
27
|
return { path, status: "generated", reason: "Lockfile excluded from code audit scope." };
|
|
28
28
|
}
|
|
29
|
+
if (isAuditArtifactPath(normalized)) {
|
|
30
|
+
return {
|
|
31
|
+
path,
|
|
32
|
+
status: "generated",
|
|
33
|
+
reason: "Generated audit artifact.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
29
36
|
if (isDocPath(normalized)) {
|
|
30
37
|
return { path, status: "doc_only", reason: "Documentation artifact." };
|
|
31
38
|
}
|
|
39
|
+
if (isGeneratedInstallArtifactPath(normalized)) {
|
|
40
|
+
return {
|
|
41
|
+
path,
|
|
42
|
+
status: "generated",
|
|
43
|
+
reason: "Generated install/bootstrap artifact.",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
32
46
|
return {
|
|
33
47
|
path,
|
|
34
48
|
status: "included",
|
|
@@ -13,6 +13,8 @@ export declare function isLogPath(normalized: string): boolean;
|
|
|
13
13
|
export declare function isLicensePath(normalized: string): boolean;
|
|
14
14
|
export declare function isLockfilePath(normalized: string): boolean;
|
|
15
15
|
export declare function isDocPath(normalized: string): boolean;
|
|
16
|
+
export declare function isGeneratedInstallArtifactPath(normalized: string): boolean;
|
|
17
|
+
export declare function isAuditArtifactPath(normalized: string): boolean;
|
|
16
18
|
export declare function isTestPath(normalized: string): boolean;
|
|
17
19
|
export declare function isInterfacePath(normalized: string): boolean;
|
|
18
20
|
export declare function isDataLayerPath(normalized: string): boolean;
|
|
@@ -100,6 +100,12 @@ export function isLockfilePath(normalized) {
|
|
|
100
100
|
export function isDocPath(normalized) {
|
|
101
101
|
return normalized.endsWith(".md") || hasSegment(normalized, "docs");
|
|
102
102
|
}
|
|
103
|
+
export function isGeneratedInstallArtifactPath(normalized) {
|
|
104
|
+
return normalized.startsWith(".audit-code/install/");
|
|
105
|
+
}
|
|
106
|
+
export function isAuditArtifactPath(normalized) {
|
|
107
|
+
return splitSegments(normalized).some((segment) => segment.startsWith(".audit-artifacts"));
|
|
108
|
+
}
|
|
103
109
|
export function isTestPath(normalized) {
|
|
104
110
|
return includesAny(normalized, TEST_KEYWORDS);
|
|
105
111
|
}
|
package/dist/io/artifacts.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { GraphBundle } from "../types/graph.js";
|
|
|
9
9
|
import type { RiskRegister } from "../types/risk.js";
|
|
10
10
|
import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
|
|
11
11
|
import type { SurfaceManifest } from "../types/surfaces.js";
|
|
12
|
+
import type { ToolingManifest } from "../types/toolingManifest.js";
|
|
12
13
|
type ArtifactPayloadMap = {
|
|
13
14
|
repo_manifest: RepoManifest;
|
|
14
15
|
file_disposition: FileDisposition;
|
|
@@ -29,6 +30,7 @@ type ArtifactPayloadMap = {
|
|
|
29
30
|
audit_report: string;
|
|
30
31
|
audit_state: AuditState;
|
|
31
32
|
artifact_metadata: ArtifactMetadataManifest;
|
|
33
|
+
tooling_manifest: ToolingManifest;
|
|
32
34
|
};
|
|
33
35
|
/**
|
|
34
36
|
* Audit artifacts accumulate phase-by-phase as the orchestrator advances.
|
|
@@ -63,6 +65,7 @@ export declare const ARTIFACT_DEFINITIONS: {
|
|
|
63
65
|
readonly audit_report: ArtifactDefinition<"audit_report">;
|
|
64
66
|
readonly audit_state: ArtifactDefinition<"audit_state">;
|
|
65
67
|
readonly artifact_metadata: ArtifactDefinition<"artifact_metadata">;
|
|
68
|
+
readonly tooling_manifest: ArtifactDefinition<"tooling_manifest">;
|
|
66
69
|
};
|
|
67
70
|
export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: Record<string, ArtifactBundleKey>;
|
|
68
71
|
export declare function getArtifactValue(bundle: ArtifactBundle, artifactName: string): unknown;
|
package/dist/io/artifacts.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cp, rm, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { isFileMissingError, readOptionalJsonFile, readOptionalNdjsonFile, readOptionalTextFile, writeJsonFile, writeNdjsonFile, writeTextFile, } from "./json.js";
|
|
4
|
+
import { buildToolingManifest } from "./toolingManifest.js";
|
|
4
5
|
function jsonArtifact(fileName, phase) {
|
|
5
6
|
return {
|
|
6
7
|
fileName,
|
|
@@ -45,6 +46,7 @@ export const ARTIFACT_DEFINITIONS = {
|
|
|
45
46
|
audit_report: textArtifact("audit-report.md", "reporting"),
|
|
46
47
|
audit_state: jsonArtifact("audit_state.json", "supervisor"),
|
|
47
48
|
artifact_metadata: jsonArtifact("artifact_metadata.json", "supervisor"),
|
|
49
|
+
tooling_manifest: jsonArtifact("tooling_manifest.json", "supervisor"),
|
|
48
50
|
};
|
|
49
51
|
const ARTIFACT_ENTRIES = Object.entries(ARTIFACT_DEFINITIONS);
|
|
50
52
|
export const ARTIFACT_FILE_TO_BUNDLE_KEY = Object.fromEntries(ARTIFACT_ENTRIES.map(([key, definition]) => [definition.fileName, key]));
|
|
@@ -62,6 +64,7 @@ export async function loadArtifactBundle(root) {
|
|
|
62
64
|
bundleRecord[key] = value;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
67
|
+
bundle.tooling_manifest = await buildToolingManifest();
|
|
65
68
|
return bundle;
|
|
66
69
|
}
|
|
67
70
|
export async function writeCoreArtifacts(root, bundle) {
|
|
@@ -9,8 +9,20 @@ export interface RunPaths {
|
|
|
9
9
|
stderrPath: string;
|
|
10
10
|
statusPath: string;
|
|
11
11
|
}
|
|
12
|
+
export interface DispatchBatchRun {
|
|
13
|
+
run_id: string;
|
|
14
|
+
task_path: string;
|
|
15
|
+
prompt_path: string;
|
|
16
|
+
result_path: string;
|
|
17
|
+
status_path: string;
|
|
18
|
+
audit_results_path?: string;
|
|
19
|
+
pending_audit_tasks_path?: string;
|
|
20
|
+
}
|
|
12
21
|
export declare function buildRunId(obligationId: string | null, index: number, now?: Date): string;
|
|
13
22
|
export declare function getRunPaths(artifactsDir: string, runId: string): RunPaths;
|
|
14
23
|
export declare function ensureSupervisorDirs(artifactsDir: string): Promise<void>;
|
|
15
|
-
export declare function writeWorkerTaskFiles(task: WorkerTask, prompt: string, paths: RunPaths, artifactsDir: string, currentTasks?: AuditTask[]
|
|
24
|
+
export declare function writeWorkerTaskFiles(task: WorkerTask, prompt: string, paths: RunPaths, artifactsDir: string, currentTasks?: AuditTask[], options?: {
|
|
25
|
+
updateDispatch?: boolean;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export declare function writeDispatchBatchFiles(artifactsDir: string, runs: DispatchBatchRun[], currentTasks: AuditTask[]): Promise<void>;
|
|
16
28
|
export declare function clearDispatchFiles(artifactsDir: string): Promise<void>;
|
package/dist/io/runArtifacts.js
CHANGED
|
@@ -5,6 +5,10 @@ import { writeJsonFile } from "./json.js";
|
|
|
5
5
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const packageRoot = resolve(moduleDir, "..", "..");
|
|
7
7
|
const auditResultSchemaPath = join(packageRoot, "schemas", "audit_result.schema.json");
|
|
8
|
+
const CURRENT_TASK_FILENAME = "current-task.json";
|
|
9
|
+
const CURRENT_PROMPT_FILENAME = "current-prompt.md";
|
|
10
|
+
const CURRENT_TASKS_FILENAME = "current-tasks.json";
|
|
11
|
+
const CURRENT_SCHEMA_FILENAME = "audit-result.schema.json";
|
|
8
12
|
function pad(value, size = 2) {
|
|
9
13
|
return String(value).padStart(size, "0");
|
|
10
14
|
}
|
|
@@ -49,7 +53,7 @@ export async function ensureSupervisorDirs(artifactsDir) {
|
|
|
49
53
|
await mkdir(join(artifactsDir, "dispatch"), { recursive: true });
|
|
50
54
|
await mkdir(join(artifactsDir, "runs"), { recursive: true });
|
|
51
55
|
}
|
|
52
|
-
export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, currentTasks) {
|
|
56
|
+
export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, currentTasks, options = {}) {
|
|
53
57
|
await mkdir(paths.runDir, { recursive: true });
|
|
54
58
|
await writeJsonFile(paths.taskPath, task);
|
|
55
59
|
await writeFile(paths.promptPath, prompt, "utf8");
|
|
@@ -57,17 +61,57 @@ export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, cu
|
|
|
57
61
|
run_id: task.run_id,
|
|
58
62
|
status: "dispatched",
|
|
59
63
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
if (options.updateDispatch === false) {
|
|
65
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_SCHEMA_FILENAME), await readFile(auditResultSchemaPath, "utf8"), "utf8");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASK_FILENAME), task);
|
|
69
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_PROMPT_FILENAME), prompt, "utf8");
|
|
70
|
+
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASKS_FILENAME), currentTasks ?? []);
|
|
71
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_SCHEMA_FILENAME), await readFile(auditResultSchemaPath, "utf8"), "utf8");
|
|
72
|
+
}
|
|
73
|
+
export async function writeDispatchBatchFiles(artifactsDir, runs, currentTasks) {
|
|
74
|
+
const summary = {
|
|
75
|
+
contract_version: "audit-code-dispatch/v1alpha1",
|
|
76
|
+
mode: "parallel-batch",
|
|
77
|
+
run_count: runs.length,
|
|
78
|
+
current_tasks_path: join(artifactsDir, "dispatch", CURRENT_TASKS_FILENAME),
|
|
79
|
+
runs,
|
|
80
|
+
};
|
|
81
|
+
const promptLines = [
|
|
82
|
+
"# audit-code parallel dispatch",
|
|
83
|
+
"",
|
|
84
|
+
`This batch launched ${runs.length} deferred review run(s).`,
|
|
85
|
+
"Each run keeps its own task.json, prompt.md, result.json, and status.json under .audit-artifacts/runs/<run_id>/.",
|
|
86
|
+
"Use current-tasks.json for the combined task list, then inspect the per-run files below when you need exact prompts or result paths.",
|
|
87
|
+
"",
|
|
88
|
+
"Runs:",
|
|
89
|
+
...runs.flatMap((run) => [
|
|
90
|
+
`- ${run.run_id}`,
|
|
91
|
+
` task: ${run.task_path}`,
|
|
92
|
+
` prompt: ${run.prompt_path}`,
|
|
93
|
+
` result: ${run.result_path}`,
|
|
94
|
+
` status: ${run.status_path}`,
|
|
95
|
+
...(run.audit_results_path
|
|
96
|
+
? [` audit results: ${run.audit_results_path}`]
|
|
97
|
+
: []),
|
|
98
|
+
...(run.pending_audit_tasks_path
|
|
99
|
+
? [` pending tasks: ${run.pending_audit_tasks_path}`]
|
|
100
|
+
: []),
|
|
101
|
+
]),
|
|
102
|
+
"",
|
|
103
|
+
];
|
|
104
|
+
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASK_FILENAME), summary);
|
|
105
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_PROMPT_FILENAME), promptLines.join("\n"), "utf8");
|
|
106
|
+
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASKS_FILENAME), currentTasks);
|
|
107
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_SCHEMA_FILENAME), await readFile(auditResultSchemaPath, "utf8"), "utf8");
|
|
64
108
|
}
|
|
65
109
|
export async function clearDispatchFiles(artifactsDir) {
|
|
66
110
|
const targets = [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
CURRENT_TASK_FILENAME,
|
|
112
|
+
CURRENT_PROMPT_FILENAME,
|
|
113
|
+
CURRENT_TASKS_FILENAME,
|
|
114
|
+
CURRENT_SCHEMA_FILENAME,
|
|
71
115
|
];
|
|
72
116
|
for (const name of targets) {
|
|
73
117
|
await rm(join(artifactsDir, "dispatch", name), { force: true });
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
6
|
+
const TOOLING_INPUTS = [
|
|
7
|
+
"audit-code.mjs",
|
|
8
|
+
"audit-code-wrapper-lib.mjs",
|
|
9
|
+
"package.json",
|
|
10
|
+
"dist",
|
|
11
|
+
"schemas",
|
|
12
|
+
"skills/audit-code",
|
|
13
|
+
];
|
|
14
|
+
async function pathExists(path) {
|
|
15
|
+
try {
|
|
16
|
+
await stat(path);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function collectFiles(path) {
|
|
24
|
+
const info = await stat(path);
|
|
25
|
+
if (info.isFile()) {
|
|
26
|
+
return [path];
|
|
27
|
+
}
|
|
28
|
+
if (!info.isDirectory()) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
32
|
+
const files = [];
|
|
33
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
34
|
+
files.push(...(await collectFiles(join(path, entry.name))));
|
|
35
|
+
}
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
async function readPackageVersion() {
|
|
39
|
+
const packageJsonPath = join(PACKAGE_ROOT, "package.json");
|
|
40
|
+
if (!(await pathExists(packageJsonPath))) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
45
|
+
return typeof packageJson.version === "string" ? packageJson.version : null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function buildToolingManifest() {
|
|
52
|
+
const hash = createHash("sha256");
|
|
53
|
+
const existingInputs = [];
|
|
54
|
+
for (const input of TOOLING_INPUTS) {
|
|
55
|
+
const absolute = join(PACKAGE_ROOT, input);
|
|
56
|
+
if (!(await pathExists(absolute))) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
existingInputs.push(input);
|
|
60
|
+
const files = await collectFiles(absolute);
|
|
61
|
+
for (const file of files.sort((a, b) => a.localeCompare(b))) {
|
|
62
|
+
hash.update(relative(PACKAGE_ROOT, file).replace(/\\/g, "/"));
|
|
63
|
+
hash.update("\n");
|
|
64
|
+
hash.update(await readFile(file));
|
|
65
|
+
hash.update("\n");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
generated_at: new Date().toISOString(),
|
|
70
|
+
package_root: PACKAGE_ROOT,
|
|
71
|
+
package_version: await readPackageVersion(),
|
|
72
|
+
implementation_hash: hash.digest("hex"),
|
|
73
|
+
inputs: existingInputs,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -108,8 +108,12 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
108
108
|
catch (error) {
|
|
109
109
|
throw formatExecutorFailure(selectedExecutor, selectedObligation, error);
|
|
110
110
|
}
|
|
111
|
-
const metadata = computeArtifactMetadata(run.updated, bundle.artifact_metadata);
|
|
112
|
-
const metadataBundle = {
|
|
111
|
+
const metadata = computeArtifactMetadata(run.updated, bundle.artifact_metadata, [...run.artifacts_written, "tooling_manifest.json"]);
|
|
112
|
+
const metadataBundle = {
|
|
113
|
+
...run.updated,
|
|
114
|
+
tooling_manifest: bundle.tooling_manifest,
|
|
115
|
+
artifact_metadata: metadata,
|
|
116
|
+
};
|
|
113
117
|
const updatedState = deriveAuditState(metadataBundle);
|
|
114
118
|
updatedState.last_executor = selectedExecutor;
|
|
115
119
|
updatedState.last_obligation = selectedObligation ?? undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ArtifactMetadataManifest } from "../types/artifactMetadata.js";
|
|
2
2
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
3
3
|
export declare function present(bundle: ArtifactBundle, artifactName: string): boolean;
|
|
4
|
-
export declare function computeArtifactMetadata(bundle: ArtifactBundle, previous?: ArtifactMetadataManifest): ArtifactMetadataManifest;
|
|
4
|
+
export declare function computeArtifactMetadata(bundle: ArtifactBundle, previous?: ArtifactMetadataManifest, updatedArtifacts?: Iterable<string>): ArtifactMetadataManifest;
|
|
@@ -28,6 +28,14 @@ function normalizeForMetadataHash(artifactName, value) {
|
|
|
28
28
|
const { generated_at: _generatedAt, ...rest } = record;
|
|
29
29
|
return rest;
|
|
30
30
|
}
|
|
31
|
+
if (artifactName === "tooling_manifest.json" &&
|
|
32
|
+
value &&
|
|
33
|
+
typeof value === "object" &&
|
|
34
|
+
!Array.isArray(value)) {
|
|
35
|
+
const record = value;
|
|
36
|
+
const { generated_at: _generatedAt, ...rest } = record;
|
|
37
|
+
return rest;
|
|
38
|
+
}
|
|
31
39
|
return value;
|
|
32
40
|
}
|
|
33
41
|
function buildReverseDependencyMap() {
|
|
@@ -72,8 +80,9 @@ export function present(bundle, artifactName) {
|
|
|
72
80
|
const value = getArtifactValue(bundle, artifactName);
|
|
73
81
|
return value !== undefined && value !== null;
|
|
74
82
|
}
|
|
75
|
-
export function computeArtifactMetadata(bundle, previous) {
|
|
83
|
+
export function computeArtifactMetadata(bundle, previous, updatedArtifacts = []) {
|
|
76
84
|
const artifacts = {};
|
|
85
|
+
const updated = new Set(updatedArtifacts);
|
|
77
86
|
const presentArtifacts = Object.keys(REVERSE_DEPENDENCY_MAP).filter((artifactName) => artifactName !== "artifact_metadata.json" &&
|
|
78
87
|
present(bundle, artifactName));
|
|
79
88
|
const orderedArtifacts = computeDependencyFirstOrder(presentArtifacts);
|
|
@@ -83,8 +92,12 @@ export function computeArtifactMetadata(bundle, previous) {
|
|
|
83
92
|
const value = getArtifactValue(bundle, artifactName);
|
|
84
93
|
if (value === undefined || value === null)
|
|
85
94
|
continue;
|
|
86
|
-
const contentHash = hashValue(normalizeForMetadataHash(artifactName, value));
|
|
87
95
|
const previousEntry = previous?.artifacts[artifactName];
|
|
96
|
+
if (previousEntry && !updated.has(artifactName)) {
|
|
97
|
+
artifacts[artifactName] = previousEntry;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const contentHash = hashValue(normalizeForMetadataHash(artifactName, value));
|
|
88
101
|
const dependencyRevisions = Object.fromEntries((REVERSE_DEPENDENCY_MAP[artifactName] ?? [])
|
|
89
102
|
.filter((dependencyName) => dependencyName !== "artifact_metadata.json")
|
|
90
103
|
.sort()
|
|
@@ -174,6 +174,18 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
174
174
|
const flowCoverage = bundle.critical_flows
|
|
175
175
|
? buildFlowCoverage(bundle.critical_flows, updatedCoverageMatrix)
|
|
176
176
|
: bundle.flow_coverage;
|
|
177
|
+
const runtimeCommand = bundle.runtime_validation_tasks?.tasks.find((task) => task.command && task.command.length > 0)?.command;
|
|
178
|
+
const runtimeValidationTasks = bundle.unit_manifest && flowCoverage
|
|
179
|
+
? buildRuntimeValidationTasks({
|
|
180
|
+
unitManifest: bundle.unit_manifest,
|
|
181
|
+
criticalFlows: bundle.critical_flows,
|
|
182
|
+
flowCoverage,
|
|
183
|
+
command: runtimeCommand,
|
|
184
|
+
})
|
|
185
|
+
: bundle.runtime_validation_tasks;
|
|
186
|
+
const runtimeValidationReport = runtimeValidationTasks
|
|
187
|
+
? mergeRuntimeValidationReport(runtimeValidationTasks, bundle.runtime_validation_report)
|
|
188
|
+
: bundle.runtime_validation_report;
|
|
177
189
|
const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, bundle.critical_flows, flowCoverage, bundle.external_analyzer_results);
|
|
178
190
|
const mergedResults = [...(bundle.audit_results ?? []), ...results];
|
|
179
191
|
const updatedAuditTasks = updateAuditTaskStatuses(bundle.audit_tasks, mergedResults);
|
|
@@ -182,6 +194,8 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
182
194
|
...bundle,
|
|
183
195
|
coverage_matrix: updatedCoverageMatrix,
|
|
184
196
|
flow_coverage: flowCoverage,
|
|
197
|
+
runtime_validation_tasks: runtimeValidationTasks,
|
|
198
|
+
runtime_validation_report: runtimeValidationReport,
|
|
185
199
|
audit_results: mergedResults,
|
|
186
200
|
audit_tasks: updatedAuditTasks,
|
|
187
201
|
requeue_tasks: requeuePayload.tasks,
|
|
@@ -190,6 +204,8 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
190
204
|
artifacts_written: [
|
|
191
205
|
"coverage_matrix.json",
|
|
192
206
|
"flow_coverage.json",
|
|
207
|
+
...(runtimeValidationTasks ? ["runtime_validation_tasks.json"] : []),
|
|
208
|
+
...(runtimeValidationReport ? ["runtime_validation_report.json"] : []),
|
|
193
209
|
"audit_results.jsonl",
|
|
194
210
|
"audit_tasks.json",
|
|
195
211
|
"requeue_tasks.json",
|
|
@@ -23,6 +23,14 @@ function normalizeForMetadataHash(artifactName, value) {
|
|
|
23
23
|
const { generated_at: _generatedAt, ...rest } = record;
|
|
24
24
|
return rest;
|
|
25
25
|
}
|
|
26
|
+
if (artifactName === "tooling_manifest.json" &&
|
|
27
|
+
value &&
|
|
28
|
+
typeof value === "object" &&
|
|
29
|
+
!Array.isArray(value)) {
|
|
30
|
+
const record = value;
|
|
31
|
+
const { generated_at: _generatedAt, ...rest } = record;
|
|
32
|
+
return rest;
|
|
33
|
+
}
|
|
26
34
|
return value;
|
|
27
35
|
}
|
|
28
36
|
function computeContentHash(artifactName, bundle) {
|
|
@@ -33,6 +41,18 @@ function computeContentHash(artifactName, bundle) {
|
|
|
33
41
|
.update(stableStringify(normalizeForMetadataHash(artifactName, value)))
|
|
34
42
|
.digest("hex");
|
|
35
43
|
}
|
|
44
|
+
function buildReverseDependencyMap() {
|
|
45
|
+
const reverse = {};
|
|
46
|
+
for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
|
|
47
|
+
reverse[upstream] ??= [];
|
|
48
|
+
for (const downstream of downstreamList) {
|
|
49
|
+
reverse[downstream] ??= [];
|
|
50
|
+
reverse[downstream].push(upstream);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return reverse;
|
|
54
|
+
}
|
|
55
|
+
const REVERSE_DEPENDENCY_MAP = buildReverseDependencyMap();
|
|
36
56
|
export function computeStaleArtifacts(bundle) {
|
|
37
57
|
const stale = new Set();
|
|
38
58
|
const metadata = bundle.artifact_metadata;
|
|
@@ -40,6 +60,15 @@ export function computeStaleArtifacts(bundle) {
|
|
|
40
60
|
for (const [artifactName, entry] of Object.entries(metadata.artifacts)) {
|
|
41
61
|
if (!present(bundle, artifactName))
|
|
42
62
|
continue;
|
|
63
|
+
const expectedDependencies = [...(REVERSE_DEPENDENCY_MAP[artifactName] ?? [])]
|
|
64
|
+
.filter((dependencyName) => dependencyName !== "artifact_metadata.json")
|
|
65
|
+
.sort();
|
|
66
|
+
const recordedDependencies = Object.keys(entry.dependency_revisions).sort();
|
|
67
|
+
if (stableStringify(expectedDependencies) !==
|
|
68
|
+
stableStringify(recordedDependencies)) {
|
|
69
|
+
stale.add(artifactName);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
43
72
|
let isStale = false;
|
|
44
73
|
for (const [dependencyName, recordedRevision] of Object.entries(entry.dependency_revisions)) {
|
|
45
74
|
if (!present(bundle, dependencyName)) {
|
|
@@ -51,7 +80,7 @@ export function computeStaleArtifacts(bundle) {
|
|
|
51
80
|
}
|
|
52
81
|
const dependencyEntry = metadata.artifacts[dependencyName];
|
|
53
82
|
if (!dependencyEntry) {
|
|
54
|
-
if (recordedRevision > 0) {
|
|
83
|
+
if (present(bundle, dependencyName) || recordedRevision > 0) {
|
|
55
84
|
isStale = true;
|
|
56
85
|
break;
|
|
57
86
|
}
|
|
@@ -70,6 +99,9 @@ export function computeStaleArtifacts(bundle) {
|
|
|
70
99
|
}
|
|
71
100
|
}
|
|
72
101
|
for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
|
|
102
|
+
if (upstream === "tooling_manifest.json" && !present(bundle, upstream)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
73
105
|
if (!present(bundle, upstream)) {
|
|
74
106
|
for (const downstream of downstreamList) {
|
|
75
107
|
const hasMetadataEntry = Boolean(metadata?.artifacts[downstream]);
|
|
@@ -79,5 +111,20 @@ export function computeStaleArtifacts(bundle) {
|
|
|
79
111
|
}
|
|
80
112
|
}
|
|
81
113
|
}
|
|
114
|
+
let changed = true;
|
|
115
|
+
while (changed) {
|
|
116
|
+
changed = false;
|
|
117
|
+
for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
|
|
118
|
+
if (!stale.has(upstream)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const downstream of downstreamList) {
|
|
122
|
+
if (present(bundle, downstream) && !stale.has(downstream)) {
|
|
123
|
+
stale.add(downstream);
|
|
124
|
+
changed = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
82
129
|
return stale;
|
|
83
130
|
}
|
|
@@ -11,9 +11,20 @@ export interface AuditCodeHandoffArtifactPaths {
|
|
|
11
11
|
operator_handoff_markdown: string;
|
|
12
12
|
session_config: string;
|
|
13
13
|
run_ledger: string;
|
|
14
|
+
current_task: string | null;
|
|
15
|
+
current_prompt: string | null;
|
|
16
|
+
current_tasks: string | null;
|
|
14
17
|
audit_tasks: string | null;
|
|
15
18
|
runtime_validation_tasks: string | null;
|
|
16
19
|
}
|
|
20
|
+
export interface ActiveReviewRun {
|
|
21
|
+
run_id: string;
|
|
22
|
+
task_path: string;
|
|
23
|
+
prompt_path: string;
|
|
24
|
+
pending_audit_tasks_path?: string;
|
|
25
|
+
audit_results_path: string;
|
|
26
|
+
worker_command: string[];
|
|
27
|
+
}
|
|
17
28
|
export declare const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
|
|
18
29
|
export interface AuditCodeHandoff {
|
|
19
30
|
status: AuditTopLevelStatus;
|
|
@@ -26,6 +37,7 @@ export interface AuditCodeHandoff {
|
|
|
26
37
|
suggested_commands: string[];
|
|
27
38
|
interactive_provider_hint: string | null;
|
|
28
39
|
artifact_paths: AuditCodeHandoffArtifactPaths;
|
|
40
|
+
active_review_run?: ActiveReviewRun;
|
|
29
41
|
}
|
|
30
42
|
export declare function buildAuditCodeHandoff(params: {
|
|
31
43
|
root: string;
|
|
@@ -35,5 +47,6 @@ export declare function buildAuditCodeHandoff(params: {
|
|
|
35
47
|
providerName?: string | null;
|
|
36
48
|
progressSummary: string;
|
|
37
49
|
isConfigError?: boolean;
|
|
50
|
+
activeReviewRun?: ActiveReviewRun;
|
|
38
51
|
}): AuditCodeHandoff;
|
|
39
52
|
export declare function writeAuditCodeHandoffArtifacts(handoff: AuditCodeHandoff): Promise<void>;
|
|
@@ -8,6 +8,9 @@ const OPERATOR_HANDOFF_JSON_FILENAME = "operator-handoff.json";
|
|
|
8
8
|
const OPERATOR_HANDOFF_MARKDOWN_FILENAME = "operator-handoff.md";
|
|
9
9
|
const SESSION_CONFIG_FILENAME = "session-config.json";
|
|
10
10
|
const RUN_LEDGER_FILENAME = "run-ledger.json";
|
|
11
|
+
const CURRENT_TASK_FILENAME = "current-task.json";
|
|
12
|
+
const CURRENT_PROMPT_FILENAME = "current-prompt.md";
|
|
13
|
+
const CURRENT_TASKS_FILENAME = "current-tasks.json";
|
|
11
14
|
const AUDIT_TASKS_FILENAME = "audit_tasks.json";
|
|
12
15
|
const RUNTIME_VALIDATION_TASKS_FILENAME = "runtime_validation_tasks.json";
|
|
13
16
|
const BLOCKED_STATUS = "blocked";
|
|
@@ -29,6 +32,12 @@ function quoteShellPath(filePath) {
|
|
|
29
32
|
// double-quote wrapping plus escaping embedded double quotes.
|
|
30
33
|
return `"${filePath.replace(/"/g, '\\"')}"`;
|
|
31
34
|
}
|
|
35
|
+
function quoteShellArg(value) {
|
|
36
|
+
return quoteShellPath(value);
|
|
37
|
+
}
|
|
38
|
+
function renderShellCommand(argv) {
|
|
39
|
+
return argv.map((item) => quoteShellArg(item)).join(" ");
|
|
40
|
+
}
|
|
32
41
|
function buildPendingObligations(state) {
|
|
33
42
|
return state.obligations
|
|
34
43
|
.filter((item) => !NON_PENDING_OBLIGATION_STATES.has(item.state))
|
|
@@ -55,10 +64,19 @@ function buildSummary(status, providerName, fallbackSummary) {
|
|
|
55
64
|
? `Automatic work can continue under ${providerName}. Re-run the same wrapper or inspect the listed artifacts if you need operator context.`
|
|
56
65
|
: "Automatic work can continue. Re-run the same wrapper or inspect the listed artifacts if you need operator context.";
|
|
57
66
|
}
|
|
58
|
-
function buildSuggestedInputs(artifactsDir, status, isConfigError) {
|
|
67
|
+
function buildSuggestedInputs(artifactsDir, status, isConfigError, activeReviewRun) {
|
|
59
68
|
if (status !== BLOCKED_STATUS || isConfigError) {
|
|
60
69
|
return [];
|
|
61
70
|
}
|
|
71
|
+
if (activeReviewRun) {
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
flag: "--results",
|
|
75
|
+
suggested_path: activeReviewRun.audit_results_path,
|
|
76
|
+
description: "Write structured audit-review results for the currently dispatched run, then execute the exact worker command below to ingest them.",
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
62
80
|
const incomingDir = join(artifactsDir, INCOMING_DIRNAME);
|
|
63
81
|
return [
|
|
64
82
|
{
|
|
@@ -83,10 +101,13 @@ function buildSuggestedInputs(artifactsDir, status, isConfigError) {
|
|
|
83
101
|
},
|
|
84
102
|
];
|
|
85
103
|
}
|
|
86
|
-
function buildSuggestedCommands(suggestedInputs, status) {
|
|
104
|
+
function buildSuggestedCommands(suggestedInputs, status, activeReviewRun) {
|
|
87
105
|
if (status !== BLOCKED_STATUS) {
|
|
88
106
|
return [];
|
|
89
107
|
}
|
|
108
|
+
if (activeReviewRun) {
|
|
109
|
+
return [renderShellCommand(activeReviewRun.worker_command)];
|
|
110
|
+
}
|
|
90
111
|
return suggestedInputs.map((item) => `audit-code ${item.flag} ${quoteShellPath(item.suggested_path)}`);
|
|
91
112
|
}
|
|
92
113
|
function buildInteractiveProviderHint(status, providerName, sessionConfigPath, isConfigError) {
|
|
@@ -126,6 +147,9 @@ function renderMarkdown(handoff) {
|
|
|
126
147
|
lines.push(`- incoming dir: ${handoff.artifact_paths.incoming_dir}`);
|
|
127
148
|
lines.push(`- session config: ${handoff.artifact_paths.session_config}`);
|
|
128
149
|
lines.push(`- run ledger: ${handoff.artifact_paths.run_ledger}`);
|
|
150
|
+
lines.push(`- current task: ${handoff.artifact_paths.current_task ?? "not available"}`);
|
|
151
|
+
lines.push(`- current prompt: ${handoff.artifact_paths.current_prompt ?? "not available"}`);
|
|
152
|
+
lines.push(`- current tasks: ${handoff.artifact_paths.current_tasks ?? "not available"}`);
|
|
129
153
|
lines.push(`- audit tasks: ${handoff.artifact_paths.audit_tasks ?? "not available yet"}`);
|
|
130
154
|
lines.push(`- runtime validation tasks: ${handoff.artifact_paths.runtime_validation_tasks ?? "not available yet"}`);
|
|
131
155
|
if (handoff.suggested_inputs.length > 0) {
|
|
@@ -141,6 +165,16 @@ function renderMarkdown(handoff) {
|
|
|
141
165
|
lines.push(`- ${command}`);
|
|
142
166
|
}
|
|
143
167
|
}
|
|
168
|
+
if (handoff.active_review_run) {
|
|
169
|
+
lines.push("", "Active review run:");
|
|
170
|
+
lines.push(`- run id: ${handoff.active_review_run.run_id}`);
|
|
171
|
+
lines.push(`- task file: ${handoff.active_review_run.task_path}`);
|
|
172
|
+
lines.push(`- prompt file: ${handoff.active_review_run.prompt_path}`);
|
|
173
|
+
if (handoff.active_review_run.pending_audit_tasks_path) {
|
|
174
|
+
lines.push(`- pending tasks: ${handoff.active_review_run.pending_audit_tasks_path}`);
|
|
175
|
+
}
|
|
176
|
+
lines.push(`- audit results: ${handoff.active_review_run.audit_results_path}`);
|
|
177
|
+
}
|
|
144
178
|
if (handoff.interactive_provider_hint) {
|
|
145
179
|
lines.push("", "Interactive provider hint:");
|
|
146
180
|
lines.push(`- ${handoff.interactive_provider_hint}`);
|
|
@@ -157,6 +191,15 @@ export function buildAuditCodeHandoff(params) {
|
|
|
157
191
|
operator_handoff_markdown: join(params.artifactsDir, OPERATOR_HANDOFF_MARKDOWN_FILENAME),
|
|
158
192
|
session_config: join(params.artifactsDir, SESSION_CONFIG_FILENAME),
|
|
159
193
|
run_ledger: join(params.artifactsDir, RUN_LEDGER_FILENAME),
|
|
194
|
+
current_task: params.state.status === BLOCKED_STATUS
|
|
195
|
+
? join(params.artifactsDir, "dispatch", CURRENT_TASK_FILENAME)
|
|
196
|
+
: null,
|
|
197
|
+
current_prompt: params.state.status === BLOCKED_STATUS
|
|
198
|
+
? join(params.artifactsDir, "dispatch", CURRENT_PROMPT_FILENAME)
|
|
199
|
+
: null,
|
|
200
|
+
current_tasks: params.state.status === BLOCKED_STATUS
|
|
201
|
+
? join(params.artifactsDir, "dispatch", CURRENT_TASKS_FILENAME)
|
|
202
|
+
: null,
|
|
160
203
|
audit_tasks: params.bundle.audit_tasks
|
|
161
204
|
? join(params.artifactsDir, AUDIT_TASKS_FILENAME)
|
|
162
205
|
: null,
|
|
@@ -164,7 +207,7 @@ export function buildAuditCodeHandoff(params) {
|
|
|
164
207
|
? join(params.artifactsDir, RUNTIME_VALIDATION_TASKS_FILENAME)
|
|
165
208
|
: null,
|
|
166
209
|
};
|
|
167
|
-
const suggestedInputs = buildSuggestedInputs(params.artifactsDir, params.state.status, isConfigError);
|
|
210
|
+
const suggestedInputs = buildSuggestedInputs(params.artifactsDir, params.state.status, isConfigError, params.activeReviewRun);
|
|
168
211
|
return {
|
|
169
212
|
status: params.state.status,
|
|
170
213
|
repo_root: params.root,
|
|
@@ -173,9 +216,10 @@ export function buildAuditCodeHandoff(params) {
|
|
|
173
216
|
summary: buildSummary(params.state.status, params.providerName ?? null, params.progressSummary),
|
|
174
217
|
pending_obligations: buildPendingObligations(params.state),
|
|
175
218
|
suggested_inputs: suggestedInputs,
|
|
176
|
-
suggested_commands: buildSuggestedCommands(suggestedInputs, params.state.status),
|
|
219
|
+
suggested_commands: buildSuggestedCommands(suggestedInputs, params.state.status, params.activeReviewRun),
|
|
177
220
|
interactive_provider_hint: buildInteractiveProviderHint(params.state.status, params.providerName ?? null, artifactPaths.session_config, isConfigError),
|
|
178
221
|
artifact_paths: artifactPaths,
|
|
222
|
+
active_review_run: params.activeReviewRun,
|
|
179
223
|
};
|
|
180
224
|
}
|
|
181
225
|
export async function writeAuditCodeHandoffArtifacts(handoff) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -40,6 +40,9 @@ export function validateArtifactBundle(bundle) {
|
|
|
40
40
|
if (bundle.external_analyzer_results) {
|
|
41
41
|
issues.push(...requireKeys(bundle.external_analyzer_results, "external_analyzer_results", ["tool", "results"]));
|
|
42
42
|
}
|
|
43
|
+
if (bundle.tooling_manifest) {
|
|
44
|
+
issues.push(...requireKeys(bundle.tooling_manifest, "tooling_manifest", ["generated_at", "package_root", "implementation_hash", "inputs"]));
|
|
45
|
+
}
|
|
43
46
|
const repoManifestFiles = asArray(bundle.repo_manifest?.files);
|
|
44
47
|
const fileDispositionEntries = asArray(bundle.file_disposition?.files);
|
|
45
48
|
const unitManifestUnits = asArray(bundle.unit_manifest?.units);
|
package/package.json
CHANGED
|
@@ -154,6 +154,41 @@
|
|
|
154
154
|
"interactive_provider_hint": {
|
|
155
155
|
"type": ["string", "null"]
|
|
156
156
|
},
|
|
157
|
+
"active_review_run": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"additionalProperties": false,
|
|
160
|
+
"required": [
|
|
161
|
+
"run_id",
|
|
162
|
+
"task_path",
|
|
163
|
+
"prompt_path",
|
|
164
|
+
"audit_results_path",
|
|
165
|
+
"worker_command"
|
|
166
|
+
],
|
|
167
|
+
"properties": {
|
|
168
|
+
"run_id": {
|
|
169
|
+
"type": "string"
|
|
170
|
+
},
|
|
171
|
+
"task_path": {
|
|
172
|
+
"type": "string"
|
|
173
|
+
},
|
|
174
|
+
"prompt_path": {
|
|
175
|
+
"type": "string"
|
|
176
|
+
},
|
|
177
|
+
"pending_audit_tasks_path": {
|
|
178
|
+
"type": "string"
|
|
179
|
+
},
|
|
180
|
+
"audit_results_path": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
},
|
|
183
|
+
"worker_command": {
|
|
184
|
+
"type": "array",
|
|
185
|
+
"minItems": 1,
|
|
186
|
+
"items": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
157
192
|
"artifact_paths": {
|
|
158
193
|
"type": "object",
|
|
159
194
|
"additionalProperties": false,
|
|
@@ -163,6 +198,9 @@
|
|
|
163
198
|
"operator_handoff_markdown",
|
|
164
199
|
"session_config",
|
|
165
200
|
"run_ledger",
|
|
201
|
+
"current_task",
|
|
202
|
+
"current_prompt",
|
|
203
|
+
"current_tasks",
|
|
166
204
|
"audit_tasks",
|
|
167
205
|
"runtime_validation_tasks"
|
|
168
206
|
],
|
|
@@ -182,6 +220,15 @@
|
|
|
182
220
|
"run_ledger": {
|
|
183
221
|
"type": "string"
|
|
184
222
|
},
|
|
223
|
+
"current_task": {
|
|
224
|
+
"type": ["string", "null"]
|
|
225
|
+
},
|
|
226
|
+
"current_prompt": {
|
|
227
|
+
"type": ["string", "null"]
|
|
228
|
+
},
|
|
229
|
+
"current_tasks": {
|
|
230
|
+
"type": ["string", "null"]
|
|
231
|
+
},
|
|
185
232
|
"audit_tasks": {
|
|
186
233
|
"type": ["string", "null"]
|
|
187
234
|
},
|
|
@@ -46,6 +46,15 @@ audit-code
|
|
|
46
46
|
|
|
47
47
|
from the target repository root.
|
|
48
48
|
|
|
49
|
+
When developing inside the `auditor-lambda` repository itself, prefer:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
node audit-code.mjs
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That keeps the run pinned to the local wrapper and local `dist/` output instead
|
|
56
|
+
of whichever global `audit-code` binary happens to be on `PATH`.
|
|
57
|
+
|
|
49
58
|
Debug one-step mode:
|
|
50
59
|
|
|
51
60
|
```bash
|
|
@@ -18,7 +18,7 @@ To move the state machine forward, execute the backend framework using your term
|
|
|
18
18
|
audit-code
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
_(If the wrapper is only available as a package dependency in the current repository, `npx audit-code` is equivalent. If developing
|
|
21
|
+
_(If the wrapper is only available as a package dependency in the current repository, `npx audit-code` is equivalent. If you are developing inside the `auditor-lambda` repository itself, prefer `node audit-code.mjs` so the run uses the local wrapper and local `dist/` output instead of a potentially stale global install.)_
|
|
22
22
|
|
|
23
23
|
## Step 2: Handle Blockages (The "Thinking" Phase)
|
|
24
24
|
|