auditor-lambda 0.5.0 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -187,9 +187,12 @@ export async function prepareDispatchArtifacts(params) {
187
187
  }
188
188
  const bundle = await loadArtifactBundle(artifactsDir);
189
189
  const tasksPath = join(runDir, "pending-audit-tasks.json");
190
- const tasks = await readJsonFile(tasksPath).catch((error) => {
191
- if (isFileMissingError(error))
192
- return buildPendingAuditTasks(bundle);
190
+ const tasks = await readJsonFile(tasksPath).catch(async (error) => {
191
+ if (isFileMissingError(error)) {
192
+ const generated = buildPendingAuditTasks(bundle);
193
+ await writeJsonFile(tasksPath, generated);
194
+ return generated;
195
+ }
193
196
  throw error;
194
197
  });
195
198
  const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
@@ -353,7 +356,7 @@ export async function prepareDispatchArtifacts(params) {
353
356
  "",
354
357
  ];
355
358
  });
356
- const submitCommand = `"${process.execPath}" "${join(params.packageRoot, "audit-code.mjs")}" submit-packet ` +
359
+ const submitCommand = `node packages/audit-code/audit-code.mjs submit-packet ` +
357
360
  `--run-id-b64 ${toBase64Url(runId)} ` +
358
361
  `--packet-id-b64 ${toBase64Url(packet.packet_id)} ` +
359
362
  `--artifacts-dir-b64 ${toBase64Url(artifactsDir)}`;
@@ -395,7 +398,7 @@ export async function prepareDispatchArtifacts(params) {
395
398
  " unit_id copy from the task metadata",
396
399
  " pass_id copy from the task metadata",
397
400
  " lens copy from the task metadata",
398
- " file_coverage [{path, total_lines}] - copy the exact template from each task section above",
401
+ " file_coverage [{path, total_lines}] - copy the exact template from each task section above. You MUST include total_lines. Do not omit or zero it out, as this will cause fatal validation errors.",
399
402
  " findings [] or array of finding objects",
400
403
  "",
401
404
  "Lens verification tasks:",
@@ -425,6 +428,7 @@ export async function prepareDispatchArtifacts(params) {
425
428
  "## Submit",
426
429
  "Pipe the JSON array on stdin to this command:",
427
430
  ` ${submitCommand}`,
431
+ " (If using Windows PowerShell, you MUST use `Get-Content <file> | & <command>` instead of the `<` operator.)",
428
432
  "",
429
433
  "The command validates and writes the packet-owned result files. Exit 0 means accepted.",
430
434
  "Non-zero: read the errors, fix the JSON, and run the same submit command again. Retry up to 3 times.",
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import { buildUnitManifest } from "./orchestrator/unitBuilder.js";
10
10
  import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
11
11
  import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
12
12
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
13
- import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
13
+ import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, AUDIT_REPORT_FILENAME, } from "./io/artifacts.js";
14
14
  import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues, RunLogger } from "@audit-tools/shared";
15
15
  import { validateArtifactBundle } from "./validation/artifacts.js";
16
16
  import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
@@ -630,8 +630,8 @@ async function runDeterministicForNextStep(params) {
630
630
  state,
631
631
  bundle,
632
632
  finalReportPath: promoted.promoted
633
- ? join(params.root, "audit-report.md")
634
- : join(params.artifactsDir, "audit-report.md"),
633
+ ? join(params.root, AUDIT_REPORT_FILENAME)
634
+ : join(params.artifactsDir, AUDIT_REPORT_FILENAME),
635
635
  };
636
636
  }
637
637
  if (index === 0 && bundle.repo_manifest) {
@@ -879,6 +879,89 @@ async function runDeterministicForNextStep(params) {
879
879
  reason: `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`,
880
880
  };
881
881
  }
882
+ // Renders the actionable semantic-review step (packet dispatch or single-task
883
+ // fallback) and writes steps/current-step.json. Shared by next-step and
884
+ // run-to-completion so the backend produces the actionable step itself rather
885
+ // than handing the host a second command. Host dispatch capability is resolved
886
+ // by the caller (flag -> session config -> env -> default true) and is never
887
+ // required from the host to make progress.
888
+ async function renderSemanticReviewStep(params) {
889
+ const { root, artifactsDir, activeReviewRun } = params;
890
+ if (!params.hostCanDispatch) {
891
+ const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
892
+ const workerCommand = renderCommand(activeReviewRun.worker_command);
893
+ return writeCurrentStep({
894
+ artifactsDir,
895
+ stepKind: "single_task_fallback",
896
+ status: "ready",
897
+ runId: activeReviewRun.run_id,
898
+ allowedCommands: [workerCommand],
899
+ stopCondition: "Run the exact worker_command after one result, then stop without looping.",
900
+ repoRoot: root,
901
+ artifactPaths: {
902
+ active_review_task: activeReviewRun.task_path,
903
+ active_review_prompt: activeReviewRun.prompt_path,
904
+ pending_audit_tasks: activeReviewRun.pending_audit_tasks_path ?? null,
905
+ audit_results: activeReviewRun.audit_results_path,
906
+ single_task_prompt: singleTaskPromptPath,
907
+ },
908
+ prompt: renderSingleTaskFallbackStepPrompt({
909
+ singleTaskPromptPath,
910
+ activeReviewRun,
911
+ }),
912
+ access: {
913
+ read_paths: [singleTaskPromptPath],
914
+ write_paths: [activeReviewRun.audit_results_path],
915
+ },
916
+ });
917
+ }
918
+ const dispatch = await prepareDispatchArtifacts({
919
+ packageRoot,
920
+ runId: activeReviewRun.run_id,
921
+ artifactsDir,
922
+ root,
923
+ hostActiveSubagentLimit: params.hostMaxActiveSubagents,
924
+ });
925
+ const mergeCommand = mergeAndIngestCommand(artifactsDir, activeReviewRun.run_id);
926
+ const continueCommand = nextStepCommand(root, artifactsDir);
927
+ return writeCurrentStep({
928
+ artifactsDir,
929
+ stepKind: "dispatch_review",
930
+ status: "ready",
931
+ runId: activeReviewRun.run_id,
932
+ allowedCommands: [
933
+ "auditor_merge_and_ingest",
934
+ "auditor_continue_audit",
935
+ mergeCommand,
936
+ continueCommand,
937
+ ],
938
+ stopCondition: "Dispatch every packet, run merge-and-ingest once, then run next-step.",
939
+ repoRoot: root,
940
+ artifactPaths: {
941
+ dispatch_plan: dispatch.dispatch_plan_path,
942
+ dispatch_quota: dispatch.dispatch_quota_path,
943
+ dispatch_warnings: dispatch.dispatch_warnings_path,
944
+ active_review_task: activeReviewRun.task_path,
945
+ pending_audit_tasks: activeReviewRun.pending_audit_tasks_path ?? null,
946
+ },
947
+ prompt: renderDispatchReviewPrompt({
948
+ root,
949
+ artifactsDir,
950
+ activeReviewRun,
951
+ dispatchPlanPath: dispatch.dispatch_plan_path,
952
+ dispatchQuotaPath: dispatch.dispatch_quota_path,
953
+ hostCanRestrictSubagentTools: params.hostCanRestrictSubagentTools,
954
+ hostCanSelectSubagentModel: params.hostCanSelectSubagentModel,
955
+ }),
956
+ access: {
957
+ read_paths: [
958
+ dispatch.dispatch_plan_path,
959
+ ...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
960
+ ],
961
+ write_paths: [],
962
+ },
963
+ });
964
+ }
882
965
  async function cmdNextStep(argv) {
883
966
  const root = getRootDir(argv);
884
967
  warnIfNotGitRepo(root);
@@ -1123,81 +1206,14 @@ async function cmdNextStep(argv) {
1123
1206
  console.log(JSON.stringify(step, null, 2));
1124
1207
  return;
1125
1208
  }
1126
- if (!hostCanDispatch) {
1127
- const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
1128
- const workerCommand = renderCommand(result.activeReviewRun.worker_command);
1129
- const step = await writeCurrentStep({
1130
- artifactsDir,
1131
- stepKind: "single_task_fallback",
1132
- status: "ready",
1133
- runId: result.activeReviewRun.run_id,
1134
- allowedCommands: [workerCommand],
1135
- stopCondition: "Run the exact worker_command after one result, then stop without looping.",
1136
- repoRoot: root,
1137
- artifactPaths: {
1138
- active_review_task: result.activeReviewRun.task_path,
1139
- active_review_prompt: result.activeReviewRun.prompt_path,
1140
- pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
1141
- audit_results: result.activeReviewRun.audit_results_path,
1142
- single_task_prompt: singleTaskPromptPath,
1143
- },
1144
- prompt: renderSingleTaskFallbackStepPrompt({
1145
- singleTaskPromptPath,
1146
- activeReviewRun: result.activeReviewRun,
1147
- }),
1148
- access: {
1149
- read_paths: [singleTaskPromptPath],
1150
- write_paths: [result.activeReviewRun.audit_results_path],
1151
- },
1152
- });
1153
- console.log(JSON.stringify(step, null, 2));
1154
- return;
1155
- }
1156
- const dispatch = await prepareDispatchArtifacts({
1157
- packageRoot,
1158
- runId: result.activeReviewRun.run_id,
1159
- artifactsDir,
1209
+ const step = await renderSemanticReviewStep({
1160
1210
  root,
1161
- hostActiveSubagentLimit: hostMaxActiveSubagents,
1162
- });
1163
- const mergeCommand = mergeAndIngestCommand(artifactsDir, result.activeReviewRun.run_id);
1164
- const continueCommand = nextStepCommand(root, artifactsDir);
1165
- const step = await writeCurrentStep({
1166
1211
  artifactsDir,
1167
- stepKind: "dispatch_review",
1168
- status: "ready",
1169
- runId: result.activeReviewRun.run_id,
1170
- allowedCommands: [
1171
- "auditor_merge_and_ingest",
1172
- "auditor_continue_audit",
1173
- mergeCommand,
1174
- continueCommand,
1175
- ],
1176
- stopCondition: "Dispatch every packet, run merge-and-ingest once, then run next-step.",
1177
- repoRoot: root,
1178
- artifactPaths: {
1179
- dispatch_plan: dispatch.dispatch_plan_path,
1180
- dispatch_quota: dispatch.dispatch_quota_path,
1181
- dispatch_warnings: dispatch.dispatch_warnings_path,
1182
- active_review_task: result.activeReviewRun.task_path,
1183
- pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
1184
- },
1185
- prompt: renderDispatchReviewPrompt({
1186
- root,
1187
- artifactsDir,
1188
- activeReviewRun: result.activeReviewRun,
1189
- dispatchPlanPath: dispatch.dispatch_plan_path,
1190
- dispatchQuotaPath: dispatch.dispatch_quota_path,
1191
- hostCanRestrictSubagentTools,
1192
- hostCanSelectSubagentModel,
1193
- }),
1194
- access: {
1195
- read_paths: [
1196
- dispatch.dispatch_plan_path,
1197
- ...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
1198
- ],
1199
- write_paths: [],
1200
- },
1212
+ activeReviewRun: result.activeReviewRun,
1213
+ hostCanDispatch,
1214
+ hostMaxActiveSubagents,
1215
+ hostCanRestrictSubagentTools,
1216
+ hostCanSelectSubagentModel,
1201
1217
  });
1202
1218
  console.log(JSON.stringify(step, null, 2));
1203
1219
  }
@@ -1365,6 +1381,37 @@ async function cmdRunToCompletion(argv) {
1365
1381
  const blockPrompt = renderWorkerPrompt(blockTask);
1366
1382
  await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
1367
1383
  await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
1384
+ const reviewRun = {
1385
+ run_id: blockRunId,
1386
+ task_path: blockPaths.taskPath,
1387
+ prompt_path: blockPaths.promptPath,
1388
+ pending_audit_tasks_path: blockPendingTasksPath,
1389
+ audit_results_path: blockAuditResultsPath,
1390
+ worker_command: blockTask.worker_command,
1391
+ };
1392
+ // Render the actionable dispatch / single-task step here instead of
1393
+ // leaving the host to issue next-step as a second command. Capability is
1394
+ // resolved from flags/config/env with a sane default, so nothing is
1395
+ // required from the host to make progress. If rendering fails we still
1396
+ // emit the hand-off below — run-to-completion is never worse than before,
1397
+ // and next-step will re-render and surface the error loudly.
1398
+ try {
1399
+ await renderSemanticReviewStep({
1400
+ root,
1401
+ artifactsDir,
1402
+ activeReviewRun: reviewRun,
1403
+ hostCanDispatch: resolveHostDispatchCapability({
1404
+ explicit: getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents"),
1405
+ sessionConfig,
1406
+ }),
1407
+ hostMaxActiveSubagents: getHostMaxActiveSubagents(argv),
1408
+ hostCanRestrictSubagentTools: getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ?? false,
1409
+ hostCanSelectSubagentModel: getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false,
1410
+ });
1411
+ }
1412
+ catch (stepError) {
1413
+ process.stderr.write(`[audit-code] Could not pre-render the review step; the operator hand-off points to next-step instead. ${stepError instanceof Error ? stepError.message : String(stepError)}\n`);
1414
+ }
1368
1415
  await emitEnvelope({
1369
1416
  root,
1370
1417
  artifactsDir,
@@ -1380,14 +1427,7 @@ async function cmdRunToCompletion(argv) {
1380
1427
  progress_summary: blocker,
1381
1428
  next_likely_step: null,
1382
1429
  providerName: provider.name,
1383
- activeReviewRun: {
1384
- run_id: blockRunId,
1385
- task_path: blockPaths.taskPath,
1386
- prompt_path: blockPaths.promptPath,
1387
- pending_audit_tasks_path: blockPendingTasksPath,
1388
- audit_results_path: blockAuditResultsPath,
1389
- worker_command: blockTask.worker_command,
1390
- },
1430
+ activeReviewRun: reviewRun,
1391
1431
  });
1392
1432
  return;
1393
1433
  }
@@ -55,6 +55,7 @@ interface ArtifactDefinition<K extends ArtifactBundleKey = ArtifactBundleKey> {
55
55
  read: (path: string) => Promise<ArtifactPayloadMap[K] | undefined>;
56
56
  write: (path: string, value: ArtifactPayloadMap[K]) => Promise<void>;
57
57
  }
58
+ export declare const AUDIT_REPORT_FILENAME = "audit-report.md";
58
59
  export declare const ARTIFACT_DEFINITIONS: {
59
60
  readonly repo_manifest: ArtifactDefinition<"repo_manifest">;
60
61
  readonly file_disposition: ArtifactDefinition<"file_disposition">;
@@ -2,6 +2,10 @@ 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 "@audit-tools/shared";
4
4
  import { buildToolingManifest } from "./toolingManifest.js";
5
+ // Canonical filename for the rendered findings report. Single source of truth
6
+ // for path construction. The dependency table below still lists it as plain
7
+ // data alongside its sibling artifact-name literals.
8
+ export const AUDIT_REPORT_FILENAME = "audit-report.md";
5
9
  function jsonArtifact(fileName, phase) {
6
10
  return {
7
11
  fileName,
@@ -49,7 +53,7 @@ export const ARTIFACT_DEFINITIONS = {
49
53
  audit_plan_metrics: jsonArtifact("audit_plan_metrics.json", "execution"),
50
54
  review_packets: jsonArtifact("review_packets.json", "execution"),
51
55
  requeue_tasks: jsonArtifact("requeue_tasks.json", "execution"),
52
- audit_report: textArtifact("audit-report.md", "reporting"),
56
+ audit_report: textArtifact(AUDIT_REPORT_FILENAME, "reporting"),
53
57
  audit_findings: jsonArtifact("audit-findings.json", "reporting"),
54
58
  synthesis_narrative: jsonArtifact("synthesis-narrative.json", "reporting"),
55
59
  audit_state: jsonArtifact("audit_state.json", "supervisor"),
@@ -103,8 +107,8 @@ export async function cleanupIntermediateArtifacts(root) {
103
107
  return deleted;
104
108
  }
105
109
  export async function promoteFinalAuditReport(params, options = {}) {
106
- const source = join(params.artifactsDir, "audit-report.md");
107
- const destination = join(params.repoRoot, "audit-report.md");
110
+ const source = join(params.artifactsDir, AUDIT_REPORT_FILENAME);
111
+ const destination = join(params.repoRoot, AUDIT_REPORT_FILENAME);
108
112
  const copy = options.copy ?? cp;
109
113
  const remove = options.remove ?? rm;
110
114
  const warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { spawn } from "node:child_process";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { loadArtifactBundle } from "../io/artifacts.js";
5
+ import { loadArtifactBundle, AUDIT_REPORT_FILENAME } from "../io/artifacts.js";
6
6
  import { readOptionalTextFile } from "@audit-tools/shared";
7
7
  import { deriveAuditState } from "../orchestrator/state.js";
8
8
  import { decideNextStep } from "../orchestrator/nextStep.js";
@@ -225,8 +225,8 @@ export const resourceRegistry = [
225
225
  description: "Current deterministic audit report if available.",
226
226
  mimeType: "text/markdown",
227
227
  async read(context) {
228
- const report = (await readOptionalTextFile(join(context.artifactsDir, "audit-report.md"))) ??
229
- (await readOptionalTextFile(join(context.root, "audit-report.md"))) ??
228
+ const report = (await readOptionalTextFile(join(context.artifactsDir, AUDIT_REPORT_FILENAME))) ??
229
+ (await readOptionalTextFile(join(context.root, AUDIT_REPORT_FILENAME))) ??
230
230
  "The audit report has not been rendered yet.";
231
231
  return { mimeType: this.mimeType, text: report };
232
232
  },
@@ -1,4 +1,4 @@
1
- import type { ArtifactBundle } from "../io/artifacts.js";
1
+ import { type ArtifactBundle } from "../io/artifacts.js";
2
2
  import type { AuditState, AuditTopLevelStatus } from "../types/auditState.js";
3
3
  export interface AuditCodeHandoffInput {
4
4
  flag: "--results" | "--batch-results" | "--updates" | "--external-analyzer-results";
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { writeJsonFile } from "@audit-tools/shared";
4
+ import { AUDIT_REPORT_FILENAME } from "../io/artifacts.js";
4
5
  import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "../providers/constants.js";
5
6
  export const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
6
7
  const INCOMING_DIRNAME = "incoming";
@@ -11,8 +12,6 @@ const RUN_LEDGER_FILENAME = "run-ledger.json";
11
12
  const CURRENT_TASK_FILENAME = "current-task.json";
12
13
  const CURRENT_PROMPT_FILENAME = "current-prompt.md";
13
14
  const CURRENT_TASKS_FILENAME = "current-tasks.json";
14
- const CURRENT_SINGLE_TASK_FILENAME = "current-single-task.json";
15
- const CURRENT_SINGLE_TASK_PROMPT_FILENAME = "current-single-task-prompt.md";
16
15
  const AUDIT_TASKS_FILENAME = "audit_tasks.json";
17
16
  const RUNTIME_VALIDATION_TASKS_FILENAME = "runtime_validation_tasks.json";
18
17
  const BLOCKED_STATUS = "blocked";
@@ -123,6 +122,26 @@ function buildInteractiveProviderHint(status, providerName, sessionConfigPath, i
123
122
  const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
124
123
  return `Provider: ${providerLabel}. This is a deterministic semantic-review handoff, not a failed audit. Use host subagents when the active toolset provides them; otherwise use the single-task fallback and stop after the worker command. For automatic LLM review, configure an interactive provider in ${sessionConfigPath}; that is only needed for backend-launched review.`;
125
124
  }
125
+ // Single source for which artifact paths render in the markdown handoff and how
126
+ // absent ones read. renderMarkdown and file_map both source paths from the
127
+ // artifact path model (AuditCodeHandoffArtifactPaths), so adding or renaming a
128
+ // handoff artifact is one edit here instead of coordinated edits across sites.
129
+ const ARTIFACT_PATH_RENDER_FIELDS = [
130
+ { label: "operator handoff json", key: "operator_handoff_json" },
131
+ { label: "operator handoff markdown", key: "operator_handoff_markdown" },
132
+ { label: "incoming dir", key: "incoming_dir" },
133
+ { label: "session config", key: "session_config" },
134
+ { label: "run ledger", key: "run_ledger" },
135
+ { label: "current task", key: "current_task", fallback: "not available" },
136
+ { label: "current prompt", key: "current_prompt", fallback: "not available" },
137
+ { label: "current tasks", key: "current_tasks", fallback: "not available" },
138
+ { label: "audit tasks", key: "audit_tasks", fallback: "not available yet" },
139
+ {
140
+ label: "runtime validation tasks",
141
+ key: "runtime_validation_tasks",
142
+ fallback: "not available yet",
143
+ },
144
+ ];
126
145
  function renderMarkdown(handoff) {
127
146
  const lines = [
128
147
  "# audit-code operator handoff",
@@ -145,16 +164,10 @@ function renderMarkdown(handoff) {
145
164
  }
146
165
  }
147
166
  lines.push("", "Useful artifact paths:");
148
- lines.push(`- operator handoff json: ${handoff.artifact_paths.operator_handoff_json}`);
149
- lines.push(`- operator handoff markdown: ${handoff.artifact_paths.operator_handoff_markdown}`);
150
- lines.push(`- incoming dir: ${handoff.artifact_paths.incoming_dir}`);
151
- lines.push(`- session config: ${handoff.artifact_paths.session_config}`);
152
- lines.push(`- run ledger: ${handoff.artifact_paths.run_ledger}`);
153
- lines.push(`- current task: ${handoff.artifact_paths.current_task ?? "not available"}`);
154
- lines.push(`- current prompt: ${handoff.artifact_paths.current_prompt ?? "not available"}`);
155
- lines.push(`- current tasks: ${handoff.artifact_paths.current_tasks ?? "not available"}`);
156
- lines.push(`- audit tasks: ${handoff.artifact_paths.audit_tasks ?? "not available yet"}`);
157
- lines.push(`- runtime validation tasks: ${handoff.artifact_paths.runtime_validation_tasks ?? "not available yet"}`);
167
+ for (const field of ARTIFACT_PATH_RENDER_FIELDS) {
168
+ const value = handoff.artifact_paths[field.key];
169
+ lines.push(`- ${field.label}: ${value ?? field.fallback ?? "not available"}`);
170
+ }
158
171
  if (handoff.suggested_inputs.length > 0) {
159
172
  lines.push("", "Suggested evidence inputs:");
160
173
  for (const item of handoff.suggested_inputs) {
@@ -238,11 +251,8 @@ export function buildAuditCodeHandoff(params) {
238
251
  handoff.file_map = {
239
252
  current_task: artifactPaths.current_task,
240
253
  current_prompt: artifactPaths.current_prompt,
241
- single_task: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_FILENAME),
242
- single_task_prompt: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_PROMPT_FILENAME),
243
- dispatch_plan: join(params.artifactsDir, "runs", params.activeReviewRun.run_id, "dispatch-plan.json"),
244
254
  audit_results: params.activeReviewRun.audit_results_path,
245
- final_report: join(params.root, "audit-report.md"),
255
+ final_report: join(params.root, AUDIT_REPORT_FILENAME),
246
256
  };
247
257
  }
248
258
  return handoff;
package/docs/contracts.md CHANGED
@@ -90,9 +90,16 @@ backend writes the current step contract to:
90
90
  includes `step_kind`, `prompt_path`, `status`, `run_id`, `allowed_commands`,
91
91
  `stop_condition`, `repo_root`, `artifacts_dir`, and relevant `artifact_paths`.
92
92
 
93
- When semantic review is blocked, `next-step` first emits a `capability_check`.
94
- After the host reports `--host-can-dispatch-subagents true|false`, the backend
95
- renders exactly one review path: packet dispatch or the single-task fallback.
93
+ When semantic review is reached, the backend resolves host dispatch capability
94
+ from `--host-can-dispatch-subagents true|false` (optional) → session config
95
+ (`host_can_dispatch_subagents`) `AUDIT_CODE_HOST_CAN_DISPATCH` default
96
+ `true`, then renders exactly one review path: packet dispatch (`dispatch_review`)
97
+ or the single-task fallback (`single_task_fallback`). No capability handshake is
98
+ required; pass the flag only to override the resolved default.
99
+
100
+ `run-to-completion` renders the same step when it reaches the semantic-review
101
+ boundary, so a host on the batch entrypoint can act on `steps/current-step.json`
102
+ directly instead of issuing a second `next-step`.
96
103
 
97
104
  ## Dispatch packets
98
105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.5.0",
3
+ "version": "0.6.3",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",