audit-tools 0.45.0 → 0.48.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.
Files changed (288) hide show
  1. package/README.md +51 -18
  2. package/dispatch/merge-results.mjs +3 -3
  3. package/dispatch/validate-result.mjs +3 -3
  4. package/dist/audit/cli/charterExtractionPrompt.d.ts.map +1 -1
  5. package/dist/audit/cli/charterExtractionPrompt.js +11 -1
  6. package/dist/audit/cli/charterExtractionPrompt.js.map +1 -1
  7. package/dist/audit/cli/cleanupCommand.d.ts +25 -0
  8. package/dist/audit/cli/cleanupCommand.d.ts.map +1 -1
  9. package/dist/audit/cli/cleanupCommand.js +76 -1
  10. package/dist/audit/cli/cleanupCommand.js.map +1 -1
  11. package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
  12. package/dist/audit/cli/dispatch/hostHandoff.js +52 -76
  13. package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
  14. package/dist/audit/cli/dispatch/packetFilter.d.ts +1 -1
  15. package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
  16. package/dist/audit/cli/fanoutLanes.js +6 -1
  17. package/dist/audit/cli/fanoutLanes.js.map +1 -1
  18. package/dist/audit/cli/laneSubmissions.d.ts +13 -0
  19. package/dist/audit/cli/laneSubmissions.d.ts.map +1 -1
  20. package/dist/audit/cli/laneSubmissions.js +38 -27
  21. package/dist/audit/cli/laneSubmissions.js.map +1 -1
  22. package/dist/audit/cli/nextStepCommand.d.ts +12 -0
  23. package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
  24. package/dist/audit/cli/nextStepCommand.js +23 -12
  25. package/dist/audit/cli/nextStepCommand.js.map +1 -1
  26. package/dist/audit/cli/nextStepHelpers.d.ts +3 -2
  27. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  28. package/dist/audit/cli/nextStepHelpers.js +65 -37
  29. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  30. package/dist/audit/cli/reviewRun.d.ts +1 -1
  31. package/dist/audit/cli/reviewRun.d.ts.map +1 -1
  32. package/dist/audit/cli/reviewRun.js +2 -9
  33. package/dist/audit/cli/reviewRun.js.map +1 -1
  34. package/dist/audit/cli/sampleRunCommand.d.ts +215 -0
  35. package/dist/audit/cli/sampleRunCommand.d.ts.map +1 -1
  36. package/dist/audit/cli/sampleRunCommand.js +60 -3
  37. package/dist/audit/cli/sampleRunCommand.js.map +1 -1
  38. package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
  39. package/dist/audit/cli/semanticReviewStep.js +9 -1
  40. package/dist/audit/cli/semanticReviewStep.js.map +1 -1
  41. package/dist/audit/cli/steps.d.ts +6 -6
  42. package/dist/audit/contracts/workerSchemas.d.ts +36 -26
  43. package/dist/audit/contracts/workerSchemas.d.ts.map +1 -1
  44. package/dist/audit/contracts/workerSchemas.js +11 -1
  45. package/dist/audit/contracts/workerSchemas.js.map +1 -1
  46. package/dist/audit/contracts/wrapperResponse.d.ts +52 -28
  47. package/dist/audit/contracts/wrapperResponse.d.ts.map +1 -1
  48. package/dist/audit/contracts/wrapperResponse.js +8 -5
  49. package/dist/audit/contracts/wrapperResponse.js.map +1 -1
  50. package/dist/audit/decompose/sources.d.ts.map +1 -1
  51. package/dist/audit/decompose/sources.js +21 -5
  52. package/dist/audit/decompose/sources.js.map +1 -1
  53. package/dist/audit/extractors/analyzers/registry.d.ts +8 -5
  54. package/dist/audit/extractors/analyzers/registry.d.ts.map +1 -1
  55. package/dist/audit/extractors/analyzers/registry.js +28 -7
  56. package/dist/audit/extractors/analyzers/registry.js.map +1 -1
  57. package/dist/audit/extractors/designAssessment.js +1 -1
  58. package/dist/audit/extractors/gitHistory.d.ts +1 -1
  59. package/dist/audit/extractors/gitHistory.js +1 -1
  60. package/dist/audit/extractors/graph.d.ts +1 -1
  61. package/dist/audit/extractors/graph.js +1 -1
  62. package/dist/audit/extractors/risk.d.ts +1 -1
  63. package/dist/audit/extractors/risk.js +1 -1
  64. package/dist/audit/io/artifacts.d.ts.map +1 -1
  65. package/dist/audit/io/artifacts.js +5 -5
  66. package/dist/audit/io/artifacts.js.map +1 -1
  67. package/dist/audit/orchestrator/acquisitionExecutor.d.ts +12 -7
  68. package/dist/audit/orchestrator/acquisitionExecutor.d.ts.map +1 -1
  69. package/dist/audit/orchestrator/acquisitionExecutor.js +1 -1
  70. package/dist/audit/orchestrator/acquisitionExecutor.js.map +1 -1
  71. package/dist/audit/orchestrator/advance.d.ts +14 -10
  72. package/dist/audit/orchestrator/advance.d.ts.map +1 -1
  73. package/dist/audit/orchestrator/advance.js +49 -24
  74. package/dist/audit/orchestrator/advance.js.map +1 -1
  75. package/dist/audit/orchestrator/advanceTypes.d.ts +14 -0
  76. package/dist/audit/orchestrator/advanceTypes.d.ts.map +1 -1
  77. package/dist/audit/orchestrator/autoFixExecutor.d.ts +20 -1
  78. package/dist/audit/orchestrator/autoFixExecutor.d.ts.map +1 -1
  79. package/dist/audit/orchestrator/autoFixExecutor.js +77 -15
  80. package/dist/audit/orchestrator/autoFixExecutor.js.map +1 -1
  81. package/dist/audit/orchestrator/dependencyMap.js +1 -1
  82. package/dist/audit/orchestrator/dependencyMap.js.map +1 -1
  83. package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
  84. package/dist/audit/orchestrator/designReviewPrompt.js +7 -0
  85. package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
  86. package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
  87. package/dist/audit/orchestrator/executorRunners.js +18 -4
  88. package/dist/audit/orchestrator/executorRunners.js.map +1 -1
  89. package/dist/audit/orchestrator/executors.d.ts +54 -0
  90. package/dist/audit/orchestrator/executors.d.ts.map +1 -1
  91. package/dist/audit/orchestrator/executors.js +352 -0
  92. package/dist/audit/orchestrator/executors.js.map +1 -1
  93. package/dist/audit/orchestrator/hostInputPause.d.ts +28 -13
  94. package/dist/audit/orchestrator/hostInputPause.d.ts.map +1 -1
  95. package/dist/audit/orchestrator/hostInputPause.js +17 -11
  96. package/dist/audit/orchestrator/hostInputPause.js.map +1 -1
  97. package/dist/audit/orchestrator/intentInterpreter.d.ts +15 -18
  98. package/dist/audit/orchestrator/intentInterpreter.d.ts.map +1 -1
  99. package/dist/audit/orchestrator/intentInterpreter.js +12 -26
  100. package/dist/audit/orchestrator/intentInterpreter.js.map +1 -1
  101. package/dist/audit/orchestrator/localCommands.d.ts +24 -1
  102. package/dist/audit/orchestrator/localCommands.d.ts.map +1 -1
  103. package/dist/audit/orchestrator/localCommands.js +44 -9
  104. package/dist/audit/orchestrator/localCommands.js.map +1 -1
  105. package/dist/audit/orchestrator/nextStep.d.ts +13 -0
  106. package/dist/audit/orchestrator/nextStep.d.ts.map +1 -1
  107. package/dist/audit/orchestrator/nextStep.js +13 -0
  108. package/dist/audit/orchestrator/nextStep.js.map +1 -1
  109. package/dist/audit/orchestrator/requeueCommand.d.ts +1 -1
  110. package/dist/audit/orchestrator/runtimeCommand.d.ts +32 -1
  111. package/dist/audit/orchestrator/runtimeCommand.d.ts.map +1 -1
  112. package/dist/audit/orchestrator/runtimeCommand.js +90 -3
  113. package/dist/audit/orchestrator/runtimeCommand.js.map +1 -1
  114. package/dist/audit/orchestrator/structureExecutors.js +1 -1
  115. package/dist/audit/orchestrator/structureExecutors.js.map +1 -1
  116. package/dist/audit/orchestrator/syntaxResolutionExecutor.d.ts +11 -1
  117. package/dist/audit/orchestrator/syntaxResolutionExecutor.d.ts.map +1 -1
  118. package/dist/audit/orchestrator/syntaxResolutionExecutor.js +25 -9
  119. package/dist/audit/orchestrator/syntaxResolutionExecutor.js.map +1 -1
  120. package/dist/audit/supervisor/operatorHandoff.d.ts +0 -1
  121. package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
  122. package/dist/audit/supervisor/operatorHandoff.js +2 -1
  123. package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
  124. package/dist/audit/types.d.ts +18 -18
  125. package/dist/remediate/contractPipeline/derive.d.ts.map +1 -1
  126. package/dist/remediate/contractPipeline/derive.js +1 -0
  127. package/dist/remediate/contractPipeline/derive.js.map +1 -1
  128. package/dist/remediate/contractPipeline/semanticProjection.d.ts +5 -4
  129. package/dist/remediate/contractPipeline/semanticProjection.d.ts.map +1 -1
  130. package/dist/remediate/contractPipeline/semanticProjection.js +34 -19
  131. package/dist/remediate/contractPipeline/semanticProjection.js.map +1 -1
  132. package/dist/remediate/index.d.ts.map +1 -1
  133. package/dist/remediate/index.js +3 -2
  134. package/dist/remediate/index.js.map +1 -1
  135. package/dist/remediate/intake.js +1 -1
  136. package/dist/remediate/intake.js.map +1 -1
  137. package/dist/remediate/phases/close.d.ts +45 -2
  138. package/dist/remediate/phases/close.d.ts.map +1 -1
  139. package/dist/remediate/phases/close.js +111 -43
  140. package/dist/remediate/phases/close.js.map +1 -1
  141. package/dist/remediate/phases/closeVerifyAnalyzerLeads.js +2 -2
  142. package/dist/remediate/phases/closeVerifyAnalyzerLeads.js.map +1 -1
  143. package/dist/remediate/phases/triage.d.ts.map +1 -1
  144. package/dist/remediate/phases/triage.js +61 -19
  145. package/dist/remediate/phases/triage.js.map +1 -1
  146. package/dist/remediate/review/autonomousGate.d.ts +3 -7
  147. package/dist/remediate/review/autonomousGate.d.ts.map +1 -1
  148. package/dist/remediate/review/autonomousGate.js +10 -24
  149. package/dist/remediate/review/autonomousGate.js.map +1 -1
  150. package/dist/remediate/riskSignal.d.ts +8 -20
  151. package/dist/remediate/riskSignal.d.ts.map +1 -1
  152. package/dist/remediate/riskSignal.js +21 -76
  153. package/dist/remediate/riskSignal.js.map +1 -1
  154. package/dist/remediate/state/accessMemory.d.ts +2 -3
  155. package/dist/remediate/state/accessMemory.d.ts.map +1 -1
  156. package/dist/remediate/state/accessMemory.js +3 -6
  157. package/dist/remediate/state/accessMemory.js.map +1 -1
  158. package/dist/remediate/state/store.d.ts +6 -7
  159. package/dist/remediate/state/store.d.ts.map +1 -1
  160. package/dist/remediate/state/store.js.map +1 -1
  161. package/dist/remediate/state/types.d.ts +2 -84
  162. package/dist/remediate/state/types.d.ts.map +1 -1
  163. package/dist/remediate/state/types.js +1 -39
  164. package/dist/remediate/state/types.js.map +1 -1
  165. package/dist/remediate/steps/contractPipeline.d.ts +13 -19
  166. package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
  167. package/dist/remediate/steps/contractPipeline.js +157 -66
  168. package/dist/remediate/steps/contractPipeline.js.map +1 -1
  169. package/dist/remediate/steps/dispatch/hostHandoff.d.ts +1 -1
  170. package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
  171. package/dist/remediate/steps/dispatch/hostHandoff.js +297 -141
  172. package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
  173. package/dist/remediate/steps/nextStep.d.ts +16 -7
  174. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  175. package/dist/remediate/steps/nextStep.js +207 -216
  176. package/dist/remediate/steps/nextStep.js.map +1 -1
  177. package/dist/remediate/steps/stepUtils.d.ts +10 -2
  178. package/dist/remediate/steps/stepUtils.d.ts.map +1 -1
  179. package/dist/remediate/steps/stepUtils.js +13 -3
  180. package/dist/remediate/steps/stepUtils.js.map +1 -1
  181. package/dist/remediate/steps/types.d.ts +1 -1
  182. package/dist/remediate/steps/types.d.ts.map +1 -1
  183. package/dist/remediate/steps/types.js.map +1 -1
  184. package/dist/remediate/types/options.d.ts +1 -1
  185. package/dist/remediate/validation/artifacts.d.ts.map +1 -1
  186. package/dist/remediate/validation/artifacts.js +1 -16
  187. package/dist/remediate/validation/artifacts.js.map +1 -1
  188. package/dist/remediate/validation/remediationState.d.ts +0 -1
  189. package/dist/remediate/validation/remediationState.d.ts.map +1 -1
  190. package/dist/remediate/validation/remediationState.js +0 -51
  191. package/dist/remediate/validation/remediationState.js.map +1 -1
  192. package/dist/shared/analyzerPolicy.d.ts +18 -6
  193. package/dist/shared/analyzerPolicy.d.ts.map +1 -1
  194. package/dist/shared/analyzerPolicy.js +15 -3
  195. package/dist/shared/analyzerPolicy.js.map +1 -1
  196. package/dist/shared/analyzers/acquisitionEngine.d.ts +71 -32
  197. package/dist/shared/analyzers/acquisitionEngine.d.ts.map +1 -1
  198. package/dist/shared/analyzers/acquisitionEngine.js +86 -28
  199. package/dist/shared/analyzers/acquisitionEngine.js.map +1 -1
  200. package/dist/shared/analyzers/binaryAcquisition.d.ts +8 -2
  201. package/dist/shared/analyzers/binaryAcquisition.d.ts.map +1 -1
  202. package/dist/shared/analyzers/binaryAcquisition.js +4 -4
  203. package/dist/shared/analyzers/binaryAcquisition.js.map +1 -1
  204. package/dist/shared/constitutionalDocPaths.d.ts.map +1 -1
  205. package/dist/shared/constitutionalDocPaths.js +7 -0
  206. package/dist/shared/constitutionalDocPaths.js.map +1 -1
  207. package/dist/shared/engine/obligationEngine.d.ts +44 -3
  208. package/dist/shared/engine/obligationEngine.d.ts.map +1 -1
  209. package/dist/shared/engine/obligationEngine.js +53 -7
  210. package/dist/shared/engine/obligationEngine.js.map +1 -1
  211. package/dist/shared/findings/dedupe.d.ts.map +1 -1
  212. package/dist/shared/findings/dedupe.js +8 -0
  213. package/dist/shared/findings/dedupe.js.map +1 -1
  214. package/dist/shared/friction/frictionRecord.d.ts +42 -2
  215. package/dist/shared/friction/frictionRecord.d.ts.map +1 -1
  216. package/dist/shared/friction/frictionRecord.js +111 -3
  217. package/dist/shared/friction/frictionRecord.js.map +1 -1
  218. package/dist/shared/friction/triage.d.ts +15 -0
  219. package/dist/shared/friction/triage.d.ts.map +1 -1
  220. package/dist/shared/friction/triage.js +33 -2
  221. package/dist/shared/friction/triage.js.map +1 -1
  222. package/dist/shared/index.d.ts +15 -9
  223. package/dist/shared/index.d.ts.map +1 -1
  224. package/dist/shared/index.js +15 -7
  225. package/dist/shared/index.js.map +1 -1
  226. package/dist/shared/intent/constraintClauses.d.ts +31 -0
  227. package/dist/shared/intent/constraintClauses.d.ts.map +1 -0
  228. package/dist/shared/intent/constraintClauses.js +37 -0
  229. package/dist/shared/intent/constraintClauses.js.map +1 -0
  230. package/dist/shared/io/fileLock.d.ts.map +1 -1
  231. package/dist/shared/io/fileLock.js +22 -3
  232. package/dist/shared/io/fileLock.js.map +1 -1
  233. package/dist/shared/io/frictionCapture.d.ts +73 -19
  234. package/dist/shared/io/frictionCapture.d.ts.map +1 -1
  235. package/dist/shared/io/frictionCapture.js +21 -30
  236. package/dist/shared/io/frictionCapture.js.map +1 -1
  237. package/dist/shared/io/lockedJsonStore.d.ts +24 -3
  238. package/dist/shared/io/lockedJsonStore.d.ts.map +1 -1
  239. package/dist/shared/io/lockedJsonStore.js +35 -7
  240. package/dist/shared/io/lockedJsonStore.js.map +1 -1
  241. package/dist/shared/io/stepContractWriter.d.ts +3 -3
  242. package/dist/shared/io/stepContractWriter.d.ts.map +1 -1
  243. package/dist/shared/io/stepContractWriter.js +97 -13
  244. package/dist/shared/io/stepContractWriter.js.map +1 -1
  245. package/dist/shared/submission/expectedSubmissions.d.ts +18 -0
  246. package/dist/shared/submission/expectedSubmissions.d.ts.map +1 -1
  247. package/dist/shared/submission/expectedSubmissions.js +17 -0
  248. package/dist/shared/submission/expectedSubmissions.js.map +1 -1
  249. package/dist/shared/submission/handRecovery.d.ts.map +1 -1
  250. package/dist/shared/submission/handRecovery.js +9 -11
  251. package/dist/shared/submission/handRecovery.js.map +1 -1
  252. package/dist/shared/submission/hostHandoffCore.d.ts +155 -0
  253. package/dist/shared/submission/hostHandoffCore.d.ts.map +1 -0
  254. package/dist/shared/submission/hostHandoffCore.js +210 -0
  255. package/dist/shared/submission/hostHandoffCore.js.map +1 -0
  256. package/dist/shared/submission/submissionLedger.d.ts +5 -1
  257. package/dist/shared/submission/submissionLedger.d.ts.map +1 -1
  258. package/dist/shared/submission/submissionLedger.js +33 -3
  259. package/dist/shared/submission/submissionLedger.js.map +1 -1
  260. package/dist/shared/tooling/commandShape.d.ts +29 -0
  261. package/dist/shared/tooling/commandShape.d.ts.map +1 -1
  262. package/dist/shared/tooling/commandShape.js +82 -10
  263. package/dist/shared/tooling/commandShape.js.map +1 -1
  264. package/dist/shared/tooling/exec.d.ts +32 -0
  265. package/dist/shared/tooling/exec.d.ts.map +1 -1
  266. package/dist/shared/tooling/exec.js +189 -0
  267. package/dist/shared/tooling/exec.js.map +1 -1
  268. package/docs/audit-pkg/contracts.md +2 -1
  269. package/docs/audit-pkg/operator-guide.md +12 -6
  270. package/docs/audit-pkg/product.md +22 -6
  271. package/package.json +13 -6
  272. package/schemas/audit-code-v1alpha1.schema.json +297 -0
  273. package/schemas/audit_result.schema.json +4 -0
  274. package/schemas/audit_results.schema.json +4 -0
  275. package/scripts/audit/postinstall.mjs +4 -2
  276. package/scripts/postinstall.mjs +1 -1
  277. package/scripts/remediate/postinstall.mjs +3 -1
  278. package/scripts/shared/install-host-assets.mjs +15 -8
  279. package/skills/audit-code/SKILL.md +4 -2
  280. package/skills/audit-code/audit-code.prompt.md +5 -2
  281. package/skills/audit-code/opencode-command-template.txt +1 -1
  282. package/wrapper/audit-code-wrapper-build.mjs +3 -3
  283. package/wrapper/audit-code-wrapper-install-hosts.mjs +3 -3
  284. package/wrapper/audit-code-wrapper-lib.mjs +82 -16
  285. package/wrapper/installer-verb-help.mjs +14 -4
  286. package/wrapper/remediate-code-wrapper-install-hosts.mjs +3 -3
  287. package/docs/audit-pkg/development.md +0 -113
  288. package/docs/audit-pkg/release.md +0 -140
@@ -1,12 +1,12 @@
1
1
  import { loadRemediateSessionConfig } from "./sessionConfigLoad.js";
2
- import { existsSync, statSync } from "node:fs";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { StateStore } from "../state/store.js";
6
6
  // IO / validation / rendering helpers
7
7
  import { discardOnSchemaVersionMismatch, readOptionalJsonFile, stagedAndUntracked, writeJsonFile, writeTextFile, buildAuditDeliverablePair, formatValidationIssues, isRecord, withFsRetry, RunLogger, coerceJsonObjectArg, headCommit, projectAuditFindingsReportSubset,
8
8
  // obligation engine + intent
9
- interpretFreeFormIntent, advance, decideFrictionTriage, buildFrictionTriageBlock,
9
+ interpretFreeFormIntent, interpretIntent, unresolvedFromClauses, advance, decideFrictionTriage, buildFrictionTriageBlock, linkFrictionRunIds,
10
10
  // domain constants
11
11
  LENSES, SEVERITIES, } from "audit-tools/shared";
12
12
  import { applyPlanPipeline, buildCoverageLedger } from "../phases/plan.js";
@@ -27,13 +27,13 @@ import { checkAffectedFileIntegrity } from "../utils/fileIntegrity.js";
27
27
  import { applyIntentOrdering } from "../intent/intentOrdering.js";
28
28
  import { resolveIntakeStep } from "./intakeResolver.js";
29
29
  import { runToolOwnedFinalGate, writeFinalGateRedRecord, writeFinalGateOutcomeRecord, } from "./finalGate.js";
30
- import { buildNextContractPipelineStep, shouldEnterContractPipeline, writePathASeedFromFindings, buildLeanExtractedPlan, } from "./contractPipeline.js";
30
+ import { buildNextContractPipelineStep, shouldEnterContractPipeline, writePathASeedFromFindings, } from "./contractPipeline.js";
31
31
  import { contractArtifactExists, contractPipelineDir, } from "../contractPipeline/artifactStore.js";
32
32
  import { buildReviewRequest, applyReviewResolution, isResolutionForRequest, screenResolutionIds, REVIEW_REQUEST_SCHEMA_VERSION, } from "../review/reviewGate.js";
33
33
  import { buildAutonomousReviewDecision } from "../review/autonomousGate.js";
34
34
  import { runFindingFilterPass } from "../findingFilter.js";
35
35
  import { intakePaths, isIntakeReady, manifestIsInputBound, readIntakeArtifacts, resolveManifestSources, } from "../intake.js";
36
- import { ensureIntakeRiskSignal, readIntakeRiskSignal, writeIntakeRiskSignal, escalateRiskSignal, findingRiskEvidence, distinctAffectedFiles, interpretLeanLightReviewVerdict, LEAN_LIGHT_REVIEW_SCHEMA_VERSION, } from "../riskSignal.js";
36
+ import { ensureIntakeRiskSignal, readIntakeRiskSignal, writeIntakeRiskSignal, escalateRiskSignal, findingRiskEvidence, distinctAffectedFiles, } from "../riskSignal.js";
37
37
  import { ambiguityReviewPrompt, clarificationPrompt, collectIntakeClarificationsPrompt, collectStartingPointPrompt, loaderCommand, reviewApprovalPrompt, synthesizeIntakePrompt, triagePrompt, } from "./prompts.js";
38
38
  // Single-sourced prose renders of the canonical lens / severity vocabularies
39
39
  // (`audit-tools/shared` `LENSES` / `SEVERITIES`) for the intent-checkpoint
@@ -449,7 +449,7 @@ function stripPlanTimeBookkeeping(value) {
449
449
  * The re-plan carry-forward identity of a finding: canonical JSON with the
450
450
  * plan-time bookkeeping keys stripped, so a re-plan whose only delta is a
451
451
  * recomputed file hash or a re-evaluated grounding flag carries the prior item
452
- * (and its `item_spec`) forward, while a real change to the finding does not.
452
+ * forward, while a real change to the finding does not.
453
453
  *
454
454
  * EXPORTED so the invariant suite can call THIS function. It was module-internal,
455
455
  * and the suite claiming to cover the invariant declared its own copy of the key
@@ -488,14 +488,15 @@ function carryForwardMatchingItems(previous, replanned) {
488
488
  for (const finding of replanned.plan.findings) {
489
489
  const previousFinding = previousFindings.get(finding.id);
490
490
  const previousItem = previous.items[finding.id];
491
- // Skip items that were never documented (pending with no item_spec). Under
492
- // N-R13 (document phase dissolved), a pending item that already has an
493
- // item_spec from a prior planning/document pass should carry forward
494
- // together with its spec rather than being discarded.
491
+ // A still-pending item carries no work to preserve, so it is re-minted from
492
+ // the fresh plan rather than carried forward. (This test used to also admit
493
+ // a pending item that held an `item_spec`; the document phase that produced
494
+ // one was dissolved by N-R13 and the field is gone, so the second condition
495
+ // could never be true and is not restated here.)
495
496
  if (!previousFinding || !previousItem) {
496
497
  continue;
497
498
  }
498
- if (previousItem.status === "pending" && !previousItem.item_spec) {
499
+ if (previousItem.status === "pending") {
499
500
  continue;
500
501
  }
501
502
  if (findingCarryForwardKey(previousFinding) !== findingCarryForwardKey(finding)) {
@@ -740,6 +741,11 @@ async function buildImplementDispatchStep(ctx) {
740
741
  host_handoff: handoff.handoff_record,
741
742
  });
742
743
  }
744
+ // Name the runs this dispatch round relates to on the friction record (semantics:
745
+ // `FrictionRunLinks`). Each reference is sourced from the envelope that owns it — the
746
+ // persisted handoff record for the dispatch run, the step contract's own run id for
747
+ // the step — never synthesized from the other.
748
+ await linkFrictionRunIds(artifactsDir, stateRunId(state), { step_run_id: runId, dispatch_run_id: handoff.handoff_record.run_id }, "remediate-code");
743
749
  // Ingest issues reached the PROMPT and nothing else — a channel that survives
744
750
  // exactly as long as the host reads that one step. They are also the durable
745
751
  // record of which submitted results were rejected and why, so they are logged
@@ -1254,112 +1260,6 @@ async function runReviewApprovalGate(root, artifactsDir, survivors, autonomous =
1254
1260
  declined,
1255
1261
  };
1256
1262
  }
1257
- // ── T1 slice 3b — lean-path light adversarial review gate ───────────────────────
1258
- //
1259
- // Mirrors the review-approval gate's emit→resume→consume idiom, but the step is
1260
- // WORK (status "ready"), not a human decision — so it runs identically in
1261
- // attended and autonomous modes (the host agent executes the light pass like any
1262
- // phase). Fires (and at most once) only when the fast path is eligible.
1263
- function leanLightReviewVerdictPath(artifactsDir) {
1264
- return join(artifactsDir, "lean_light_review_verdict.json");
1265
- }
1266
- function leanLightReviewDecisionPath(artifactsDir) {
1267
- return join(artifactsDir, "lean_light_review_decision.json");
1268
- }
1269
- /** Render the bounded light-adversarial-review prompt over the approved findings. */
1270
- function leanLightReviewPrompt(approved, verdictPath) {
1271
- const findingLines = approved
1272
- .map((f) => {
1273
- const files = (f.affected_files ?? [])
1274
- .map((loc) => loc?.path)
1275
- .filter((p) => Boolean(p))
1276
- .join(", ");
1277
- return `- \`${f.id}\` — ${f.title ?? "(untitled)"}${files ? ` (${files})` : ""}`;
1278
- })
1279
- .join("\n");
1280
- const nextCommand = loaderCommand("next-step");
1281
- return `# Lean fast path — light adversarial review
1282
-
1283
- These findings cleared the simplicity gate and are headed for the lean fast path (straight to plan→implement, skipping the full contract pipeline). Before they are trusted, do ONE **lightweight adversarial pass** — the floor that replaces the full design loop here. This is proportionate, not an exhaustive counterexample search: remediation legitimately catches upstream audit errors, so nothing skips review entirely.
1284
-
1285
- ## Findings to review
1286
- ${findingLines || "_(none)_"}
1287
-
1288
- ## Your task
1289
- Adopt a brief adversarial stance and ask, across the set:
1290
- - Is any finding actually wrong, already-fixed, or not grounded in the cited code?
1291
- - Would the proposed fix break a caller, an invariant, or an adjacent behavior?
1292
- - Are any two of these coupled / sharing a file in a way that needs seam reconciliation (i.e. NOT really independent)?
1293
- - Is anything subtler or more architectural than the simplicity gate assumed?
1294
-
1295
- Write your verdict to exactly:
1296
-
1297
- \`${verdictPath}\`
1298
-
1299
- \`\`\`json
1300
- {
1301
- "schema_version": "${LEAN_LIGHT_REVIEW_SCHEMA_VERSION}",
1302
- "disposition": "clear | escalate",
1303
- "concerns": ["<required & non-empty when escalate; the concrete concern(s)>"],
1304
- "created_at": "<ISO-8601>"
1305
- }
1306
- \`\`\`
1307
-
1308
- - **clear** — the light pass surfaced no real concern; the lean path may proceed.
1309
- - **escalate** — a genuine concern surfaced. This is evidence the change is harder than assessed; the run escalates to the full contract pipeline (full independent review). When in doubt, escalate — a wrong call costs extra pipeline work, never a skipped review.
1310
-
1311
- After writing the verdict, run:
1312
-
1313
- \`${nextCommand}\`
1314
- `;
1315
- }
1316
- /**
1317
- * The lean light-review gate. Idempotent across next-step calls: once a decision
1318
- * is recorded it is consumed directly. Returns a halt step while awaiting the
1319
- * host's verdict, then `clear` (proceed to the lean plan) or `escalate` (route to
1320
- * the full pipeline with the concerns).
1321
- */
1322
- async function runLeanLightReviewGate(root, artifactsDir, approved) {
1323
- const decisionPath = leanLightReviewDecisionPath(artifactsDir);
1324
- const existing = await readOptionalJsonFile(decisionPath);
1325
- if (existing) {
1326
- return existing.disposition === "clear"
1327
- ? { kind: "clear" }
1328
- : { kind: "escalate", concerns: existing.concerns ?? [] };
1329
- }
1330
- const verdictPath = leanLightReviewVerdictPath(artifactsDir);
1331
- if (!existsSync(verdictPath)) {
1332
- const step = await writeCurrentStep({
1333
- stepKind: "lean_light_review",
1334
- status: "ready",
1335
- runId: randomRunId("LEANREVIEW"),
1336
- repoRoot: root,
1337
- artifactsDir,
1338
- prompt: leanLightReviewPrompt(approved, verdictPath),
1339
- allowedCommands: [loaderCommand("next-step")],
1340
- stopCondition: "Stop after writing the lean light-review verdict and running next-step.",
1341
- artifactPaths: { lean_light_review_verdict: verdictPath },
1342
- });
1343
- return { kind: "halt", step };
1344
- }
1345
- // Verdict present → interpret + record a durable decision, then archive the
1346
- // consumed verdict so the gate can never re-emit.
1347
- const raw = await readOptionalJsonFile(verdictPath);
1348
- const interp = interpretLeanLightReviewVerdict(raw);
1349
- const record = {
1350
- schema_version: LEAN_LIGHT_REVIEW_SCHEMA_VERSION,
1351
- disposition: interp.disposition,
1352
- concerns: interp.concerns,
1353
- created_at: new Date().toISOString(),
1354
- };
1355
- await writeJsonFile(decisionPath, record);
1356
- if (existsSync(verdictPath)) {
1357
- await withFsRetry(() => rename(verdictPath, `${verdictPath}.consumed-${Date.now()}`));
1358
- }
1359
- return interp.disposition === "clear"
1360
- ? { kind: "clear" }
1361
- : { kind: "escalate", concerns: interp.concerns };
1362
- }
1363
1263
  /**
1364
1264
  * Re-emit the autonomous-run leftovers (findings left LIVE, neither auto-fixed
1365
1265
  * nor durably rejected) as a standard, re-consumable audit deliverable pair:
@@ -1517,22 +1417,13 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
1517
1417
  }
1518
1418
  // Persist the filter dispositions so coverage is built over the originals.
1519
1419
  await persistReviewFilterDispositions(artifactsDir, originals, filter);
1520
- // Lean path = the `low` risk tier's realization (D-68 the standalone lean
1521
- // fast-path folded into the self-scaling dial; its two mechanisms now live in
1522
- // `riskSignal.ts` (light-review) + `contractPipeline.ts` (lean plan builder)).
1523
- // A run skips the heavy contract DESIGN loop and
1524
- // synthesizes the extracted plan directly IFF its effective risk tier is `low`;
1525
- // the plan→implement→close machinery (per-node verify-before-merge + the final
1526
- // whole-repo gate) is the retained safety net. Runs only here — on Path A
1527
- // (structured_audit), the only intake with a pre-existing finding set to judge.
1528
- //
1529
- // First fold the APPROVED set's finding-level risk (grounding / confidence /
1420
+ // Fold the APPROVED set's finding-level risk (grounding / confidence /
1530
1421
  // coupling / systemic / architecture / count — the finding-QUALITY dimension the
1531
1422
  // intake path/breadth/intent signal doesn't see) INTO the shared risk signal as
1532
- // escalate-on-evidence. This makes the tier the SINGLE classifier: there is no
1533
- // separate fast-path boolean that can DISAGREE with it (a grounded handful
1534
- // touching a risk subsystem stays `high` and takes the full pipeline, instead of
1535
- // bypassing it as the old parallel `evaluateFastPath` allowed).
1423
+ // escalate-on-evidence. The tier is the SINGLE classifier and the ONLY thing it
1424
+ // selects is DEPTH: every run enters the contract pipeline, and a `low` tier
1425
+ // traverses it shallowly (collapsed round-trips, light adversarial depth). There
1426
+ // is no second plan producer and no bypass — see COLLAPSE_GROUPS.
1536
1427
  const findingEvidence = findingRiskEvidence(gate.approved);
1537
1428
  let riskSignal = await readIntakeRiskSignal(artifactsDir);
1538
1429
  if (findingEvidence && riskSignal) {
@@ -1544,62 +1435,6 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
1544
1435
  await writeIntakeRiskSignal(artifactsDir, riskSignal);
1545
1436
  }
1546
1437
  }
1547
- // T1 slice 3b — the lean tier is NOT zero-scrutiny: a `low`-tier run first runs
1548
- // a bounded LIGHT adversarial review over the approved findings (the floor,
1549
- // never off — `adversarialDepthForTier("low") === "light"`). A clear verdict
1550
- // proceeds to the lean plan; a verdict that surfaces a real concern escalates
1551
- // the risk signal and routes to the full pipeline below.
1552
- if (gate.approved.length > 0 && riskSignal?.tier === "low") {
1553
- const review = await runLeanLightReviewGate(root, artifactsDir, gate.approved);
1554
- if (review.kind === "halt") {
1555
- return review.step;
1556
- }
1557
- if (review.kind === "escalate") {
1558
- // Escalate-on-evidence: raise the signal to at least `medium` so the
1559
- // full pipeline's adversarial depth is `full` (see slice 3a), then
1560
- // fall through to the full pipeline.
1561
- await writeIntakeRiskSignal(artifactsDir, escalateRiskSignal(riskSignal, {
1562
- tier: "medium",
1563
- reason: `lean light review surfaced a concern: ${review.concerns.join("; ")}`,
1564
- }));
1565
- runLogger.event({
1566
- phase: "next-step",
1567
- kind: "outcome",
1568
- obligation: "lean_fast_path",
1569
- note: `lean_fast_path_escalated concerns=${String(review.concerns.length)} ` +
1570
- `detail=${review.concerns.join("; ")}`,
1571
- });
1572
- process.stderr.write(`[remediate-code] Lean light review escalated (${review.concerns.join("; ")}); routing to the full contract pipeline.\n`);
1573
- }
1574
- else {
1575
- // Clear verdict → proceed with the lean plan.
1576
- const leanPlan = buildLeanExtractedPlan(gate.approved, randomRunId("LEAN"));
1577
- await writeJsonFile(intakePaths(artifactsDir).extractedPlan, leanPlan);
1578
- runLogger.event({
1579
- phase: "next-step",
1580
- kind: "outcome",
1581
- obligation: "lean_fast_path",
1582
- note: `lean_fast_path_routed findings=${String(gate.approved.length)} ` +
1583
- `rationale=${riskSignal.rationale.join("; ")}`,
1584
- });
1585
- process.stderr.write(`[remediate-code] Lean fast path (risk tier low): ${riskSignal.rationale.join("; ")}; light review clear. Routing to plan→implement.\n`);
1586
- const planned = await handlePendingExtractedPlan(root, artifactsDir, { status: "pending" }, leanPlan, runLogger);
1587
- if (planned) {
1588
- return planned;
1589
- }
1590
- // Defensive: a deterministically-built lean plan should always
1591
- // normalize. If it somehow didn't, handlePendingExtractedPlan removed
1592
- // the file; fall through to the full pipeline (the safety net) rather
1593
- // than stalling the run.
1594
- runLogger.event({
1595
- phase: "next-step",
1596
- kind: "outcome",
1597
- obligation: "lean_fast_path",
1598
- note: "lean_fast_path_fallback plan failed to materialize; routing to the contract pipeline",
1599
- });
1600
- process.stderr.write("[remediate-code] Lean fast-path plan failed to materialize; falling back to the contract pipeline.\n");
1601
- }
1602
- }
1603
1438
  // Seed the pipeline with the approved survivors only. When that set is
1604
1439
  // narrower than the originals (anything filtered or declined), route the
1605
1440
  // seed AND the pipeline's source inputs at a filtered file so a removed
@@ -1855,13 +1690,20 @@ async function applyPlanClarificationResolution(root, artifactsDir, state, store
1855
1690
  // their questions unanswered. Load-bearing now that the round runs at the
1856
1691
  // DRAINED end of the implement phase, where `remainingPending` is normally
1857
1692
  // false — the fall-through it guards is the common case, not a corner.
1693
+ // INV-RS-10 / OBL-seam-prep-remediate-core-inv-1: a resolution that disposes
1694
+ // of every remaining item does NOT write `closing` here. Writing it directly
1695
+ // satisfied the all_terminal derive (`status !== "closing"`) on the very next
1696
+ // scan, so the tool-owned final gate never ran for this arrival at close.
1697
+ // Landing in `implementing` lets the single all-terminal → closing funnel
1698
+ // (handleAllTerminalTransition) run the gate, exactly as every other closing
1699
+ // transition does; `closing_plan` is stamped by that funnel's successor, not
1700
+ // pre-stamped here (the gate-red pause must not leave a half-prepared close).
1858
1701
  state.status = remainingPending
1859
1702
  ? "implementing"
1860
1703
  : hasUnansweredClarification(state)
1861
1704
  ? "waiting_for_clarification"
1862
- : "closing";
1705
+ : "implementing";
1863
1706
  state.clarifications = [];
1864
- state.closing_plan ??= { action: "none" };
1865
1707
  await store.saveState(state);
1866
1708
  return { kind: "applied", state };
1867
1709
  }
@@ -2132,9 +1974,8 @@ async function handlePlanning(root, artifactsDir, state, store) {
2132
1974
  if (halt)
2133
1975
  return { kind: "emit", step: halt };
2134
1976
  }
2135
- // Document phase dissolved: planning transitions directly to implementing.
2136
- // The host workload reads item_spec from the plan DAG node when present, or
2137
- // uses finding context directly when absent.
1977
+ // Document phase dissolved (N-R13): planning transitions directly to
1978
+ // implementing, and the host workload reads finding context directly.
2138
1979
  const implementBlocks = implementableBlocks(state);
2139
1980
  if (implementBlocks.length > 0) {
2140
1981
  if (state.plan) {
@@ -2197,11 +2038,29 @@ async function handlePlanning(root, artifactsDir, state, store) {
2197
2038
  await store.saveState(state);
2198
2039
  return { kind: "transition", state };
2199
2040
  }
2200
- async function handleImplementing(root, artifactsDir, state, runLogger, store) {
2041
+ async function handleImplementing(root, artifactsDir, state, runLogger, store, options) {
2201
2042
  const triageStart = Date.now();
2202
2043
  runLogger.event({ phase: "next-step", kind: "executor_start", obligation: state.status, note: "triage" });
2203
2044
  const triaged = await runTriagePhase(state, { root, artifactsDir });
2204
2045
  runLogger.event({ phase: "next-step", kind: "executor_end", obligation: state.status, note: "triage", duration_ms: Date.now() - triageStart });
2046
+ // INV-RS-10 / OBL-seam-prep-remediate-core-inv-1..4 (the CP-NODE-3 review
2047
+ // finding): this is the ONE caller of runTriagePhase, shared by the
2048
+ // `implementing` and `triage` obligations, so it is also THE single seam
2049
+ // where a triage closing-intent crosses into close. Persisting
2050
+ // `status: "closing"` directly would satisfy the closing obligation on the
2051
+ // next scan and preempt the all-terminal → closing funnel's tool-owned final
2052
+ // gate — exactly the defect class the finding named. So the intent is
2053
+ // persisted under the NON-closing `triage` status first (keeping every
2054
+ // preparation runTriagePhase made — halted items already converted to
2055
+ // `abandoned`, `closing_context` stamped — so a gate-RED pause, which persists
2056
+ // nothing itself, cannot lose them: re-entering triage with zero blocked items
2057
+ // re-derives this exact intent instead of auto-retrying halted work), and only
2058
+ // then handed to the funnel, which runs the gate and owns the closing stamp.
2059
+ if (triaged.status === "closing") {
2060
+ const prepared = { ...triaged, status: "triage" };
2061
+ await store.saveState(prepared);
2062
+ return handleAllTerminalTransition(root, artifactsDir, prepared, store, options, runLogger);
2063
+ }
2205
2064
  await store.saveState(triaged);
2206
2065
  return { kind: "transition", state: triaged };
2207
2066
  }
@@ -2763,7 +2622,7 @@ To confirm, write the final checkpoint to:
2763
2622
  "intent_summary": "${draft.intent_summary ?? "<the goal>"}",
2764
2623
  "free_form_intent": "<optional: additional guidance>",
2765
2624
  "filters": ${JSON.stringify(draft.filters ?? {}, null, 2)},
2766
- "excluded_scope": [],
2625
+ "excluded_scope": [{ "path": "<path or prefix>", "reason": "<why>" }],
2767
2626
  "must_not_touch": []
2768
2627
  }
2769
2628
  \`\`\`
@@ -2846,7 +2705,11 @@ Once the file is written, run:
2846
2705
  // on an LLM-authored free-text `intent_interpretation`.
2847
2706
  /** Sidecar artifact recording the deterministic interpretation of free_form_intent. */
2848
2707
  export const INTENT_INTERPRETATION_FILENAME = "intent-interpretation.json";
2849
- export const INTENT_INTERPRETATION_SCHEMA_VERSION = "remediate-code-intent-interpretation/v1alpha1";
2708
+ // v1alpha2: unencodable_clauses carries identity-keyed records (clause_id +
2709
+ // checkpoint_question), not bare strings — the shape the blocking consumer
2710
+ // reads. A v1alpha1 sidecar (string[]) is stale and is repaired by
2711
+ // re-derivation; it had no readers, so no migration path is owed.
2712
+ export const INTENT_INTERPRETATION_SCHEMA_VERSION = "remediate-code-intent-interpretation/v1alpha2";
2850
2713
  /**
2851
2714
  * Interpret a confirmed checkpoint's `free_form_intent` via the shared
2852
2715
  * deterministic interpreter and persist the structured signals to a sidecar
@@ -2866,33 +2729,99 @@ runLogger) {
2866
2729
  if (typeof raw !== "string" || raw.trim().length === 0)
2867
2730
  return null;
2868
2731
  const interpreted = interpretFreeFormIntent(raw);
2732
+ // The clause pipeline (interpretIntent) owns identity + blocking questions;
2733
+ // the hint interpreter above owns lens/priority/scope signals. Both are
2734
+ // deterministic draws over the same input.
2735
+ const clauseResult = interpretIntent(raw);
2736
+ const unencodable_clauses = [];
2737
+ for (const clause of clauseResult.clauses) {
2738
+ if (clause.encodable || !clause.checkpoint_question)
2739
+ continue;
2740
+ unencodable_clauses.push({
2741
+ clause_id: clause.clause_id,
2742
+ text: clause.text,
2743
+ checkpoint_question: clause.checkpoint_question,
2744
+ });
2745
+ }
2869
2746
  const persisted = {
2870
2747
  schema_version: INTENT_INTERPRETATION_SCHEMA_VERSION,
2871
2748
  interpreted,
2872
- unencodable_clauses: interpreted.unencodableClauses,
2749
+ unencodable_clauses,
2873
2750
  created_at: new Date().toISOString(),
2874
2751
  };
2875
2752
  try {
2876
2753
  await writeJsonFile(join(artifactsDir, INTENT_INTERPRETATION_FILENAME), persisted);
2877
2754
  }
2878
2755
  catch {
2879
- // Best-effort sidecar: a write failure must never block the decide loop.
2756
+ // Best-effort WRITE: a write failure must never crash the decide loop.
2757
+ // Enforcement does not depend on it — the consumer re-derives when the
2758
+ // sidecar is missing (readOrRepairIntentInterpretation).
2880
2759
  }
2881
- if (interpreted.unencodableClauses.length > 0) {
2760
+ if (unencodable_clauses.length > 0) {
2761
+ const clauseTexts = unencodable_clauses.map((c) => c.text);
2882
2762
  runLogger?.event({
2883
2763
  phase: "next-step",
2884
2764
  kind: "outcome",
2885
2765
  obligation: "interpret_intent",
2886
- note: `intent_unencodable_clauses count=${String(interpreted.unencodableClauses.length)} ` +
2887
- `clauses=${interpreted.unencodableClauses.join("; ")}`,
2766
+ note: `intent_unencodable_clauses count=${String(unencodable_clauses.length)} ` +
2767
+ `clauses=${clauseTexts.join("; ")}`,
2888
2768
  });
2889
- process.stderr.write(`[remediate-code] free_form_intent: ${interpreted.unencodableClauses.length} ` +
2890
- `clause(s) could not be encoded as lens/priority/scope signals and are ` +
2891
- `surfaced for promotion to constraints: ` +
2892
- `${interpreted.unencodableClauses.join("; ")}\n`);
2769
+ process.stderr.write(`[remediate-code] free_form_intent: ${unencodable_clauses.length} ` +
2770
+ `clause(s) could not be encoded as lens/priority/scope signals and ` +
2771
+ `block planning until answered via constraint_clauses: ` +
2772
+ `${clauseTexts.join("; ")}\n`);
2893
2773
  }
2894
2774
  return persisted;
2895
2775
  }
2776
+ /**
2777
+ * Read the persisted intent interpretation — the LOAD-BEARING input to the
2778
+ * constraint-clause gate — repairing it by re-derivation when it is missing,
2779
+ * unparseable, or carries a stale schema_version. Returns null only when
2780
+ * there is nothing to interpret (no confirmed checkpoint / empty intent).
2781
+ */
2782
+ export async function readOrRepairIntentInterpretation(artifactsDir, checkpoint, runLogger) {
2783
+ if (!checkpoint || checkpoint.confirmed_by !== "host")
2784
+ return null;
2785
+ const raw = checkpoint.free_form_intent;
2786
+ if (typeof raw !== "string" || raw.trim().length === 0)
2787
+ return null;
2788
+ const sidecarPath = join(artifactsDir, INTENT_INTERPRETATION_FILENAME);
2789
+ try {
2790
+ const parsed = parsePersistedIntentInterpretation(JSON.parse(await readFile(sidecarPath, "utf8")));
2791
+ if (parsed)
2792
+ return parsed;
2793
+ }
2794
+ catch {
2795
+ // Missing or unreadable — fall through to repair.
2796
+ }
2797
+ return interpretConfirmedCheckpointIntent(artifactsDir, checkpoint, runLogger);
2798
+ }
2799
+ /** Pure shape gate for the sidecar: current version + record-shaped clauses, else null. */
2800
+ function parsePersistedIntentInterpretation(parsed) {
2801
+ if (isRecord(parsed) &&
2802
+ parsed.schema_version === INTENT_INTERPRETATION_SCHEMA_VERSION &&
2803
+ Array.isArray(parsed.unencodable_clauses) &&
2804
+ parsed.unencodable_clauses.every((c) => isRecord(c) &&
2805
+ typeof c.clause_id === "string" &&
2806
+ typeof c.text === "string" &&
2807
+ typeof c.checkpoint_question === "string")) {
2808
+ return parsed;
2809
+ }
2810
+ return null;
2811
+ }
2812
+ /**
2813
+ * Sync sidecar read for the obligation's derive scan. Returns the persisted
2814
+ * interpretation, or null when the sidecar is missing, unreadable, or stale —
2815
+ * the execute path repairs via {@link readOrRepairIntentInterpretation}.
2816
+ */
2817
+ function readPersistedIntentInterpretationSync(sidecarPath) {
2818
+ try {
2819
+ return parsePersistedIntentInterpretation(JSON.parse(readFileSync(sidecarPath, "utf8")));
2820
+ }
2821
+ catch {
2822
+ return null;
2823
+ }
2824
+ }
2896
2825
  /**
2897
2826
  * Narrow a nullable engine state to non-null inside an executor whose `derive`
2898
2827
  * only marks it actionable when the state is present — a violation is an engine
@@ -3020,19 +2949,81 @@ function buildPreIntakeObligations(ctx, snapshot) {
3020
2949
  },
3021
2950
  {
3022
2951
  // Past the intent gate: interpret the confirmed checkpoint's
3023
- // free_form_intent once (INV-S04) and persist the structured signals. A
3024
- // transition (state unchanged) — the re-scan skips it once the sidecar
3025
- // exists.
2952
+ // free_form_intent once (INV-S04), persist the structured signals, and
2953
+ // ENFORCE the unencodable-clause contract: an unanswered clause blocks
2954
+ // the decide loop until the host resolves it via a `constraint_clauses`
2955
+ // entry on the checkpoint (CE-004, identity-keyed — the shared matcher
2956
+ // in audit-tools/shared intent/constraintClauses.ts, the same core the
2957
+ // audit gate uses). The PERSISTED sidecar is the consumed input; a
2958
+ // missing or stale sidecar is repaired by re-derivation, never skipped.
3026
2959
  id: "interpret_intent",
3027
- derive: () => existingCheckpoint?.confirmed_by === "host" &&
3028
- typeof existingCheckpoint.free_form_intent === "string" &&
3029
- existingCheckpoint.free_form_intent.trim().length > 0 &&
3030
- !existsSync(interpretationPath)
3031
- ? "missing"
3032
- : "satisfied",
2960
+ derive: () => {
2961
+ if (existingCheckpoint?.confirmed_by !== "host" ||
2962
+ typeof existingCheckpoint.free_form_intent !== "string" ||
2963
+ existingCheckpoint.free_form_intent.trim().length === 0) {
2964
+ return "satisfied";
2965
+ }
2966
+ const sidecar = readPersistedIntentInterpretationSync(interpretationPath);
2967
+ if (sidecar === null)
2968
+ return "missing";
2969
+ return unresolvedFromClauses(sidecar.unencodable_clauses, existingCheckpoint)
2970
+ .length > 0
2971
+ ? "missing"
2972
+ : "satisfied";
2973
+ },
3033
2974
  execute: async (state, c) => {
3034
- await interpretConfirmedCheckpointIntent(artifactsDir, existingCheckpoint, c.runLogger);
3035
- return { kind: "transition", state };
2975
+ const persisted = await readOrRepairIntentInterpretation(artifactsDir, existingCheckpoint, c.runLogger);
2976
+ const unresolved = persisted
2977
+ ? unresolvedFromClauses(persisted.unencodable_clauses, existingCheckpoint)
2978
+ : [];
2979
+ if (unresolved.length === 0)
2980
+ return { kind: "transition", state };
2981
+ const nextCommand = loaderCommand("next-step");
2982
+ const checkpointPath = join(artifactsDir, "intent_checkpoint.json");
2983
+ const clauseLines = unresolved
2984
+ .map((clause) => `- **${clause.clause_id}** — "${clause.text}"\n Question: ${clause.checkpoint_question}`)
2985
+ .join("\n");
2986
+ const prompt = `
2987
+ # Resolve Free-Form Intent Constraints
2988
+
2989
+ ${unresolved.length} clause(s) of the confirmed checkpoint's \`free_form_intent\` could
2990
+ not be encoded as lens/priority/scope signals. Each needs an explicit answer
2991
+ before planning proceeds — an unanswered clause would otherwise be silently
2992
+ dropped.
2993
+
2994
+ ${clauseLines}
2995
+
2996
+ Answer each clause by adding a \`constraint_clauses\` entry to
2997
+ \`intent_checkpoint.json\` (keep the exact \`clause_id\` — answers are keyed on
2998
+ clause identity, not on the question text):
2999
+
3000
+ \`\`\`json
3001
+ "constraint_clauses": [
3002
+ { "clause_id": "<the clause_id above>", "text": "<the clause text>", "checkpoint_question": "<the question above>", "host_answer": "<how to apply this constraint>" }
3003
+ ]
3004
+ \`\`\`
3005
+
3006
+ Then run:
3007
+
3008
+ \`${nextCommand}\`
3009
+ `;
3010
+ return {
3011
+ kind: "emit",
3012
+ step: await writeCurrentStep({
3013
+ stepKind: "confirm_intent",
3014
+ status: "blocked",
3015
+ runId: stateRunId(state),
3016
+ repoRoot: c.root,
3017
+ artifactsDir,
3018
+ prompt,
3019
+ allowedCommands: [nextCommand],
3020
+ stopCondition: "Stop after adding constraint_clauses answers to intent_checkpoint.json and running next-step.",
3021
+ artifactPaths: {
3022
+ intent_checkpoint: checkpointPath,
3023
+ intent_interpretation: interpretationPath,
3024
+ },
3025
+ }),
3026
+ };
3036
3027
  },
3037
3028
  },
3038
3029
  {
@@ -3261,13 +3252,13 @@ function buildMainObligations(ctx) {
3261
3252
  return { kind: "transition", state: s };
3262
3253
  }
3263
3254
  }
3264
- return handleImplementing(root, artifactsDir, s, runLogger, store);
3255
+ return handleImplementing(root, artifactsDir, s, runLogger, store, options);
3265
3256
  },
3266
3257
  },
3267
3258
  {
3268
3259
  id: "triage",
3269
3260
  derive: (state) => (state?.status === "triage" ? "missing" : "satisfied"),
3270
- execute: async (state) => handleImplementing(root, artifactsDir, requireState(state), runLogger, store),
3261
+ execute: async (state) => handleImplementing(root, artifactsDir, requireState(state), runLogger, store, options),
3271
3262
  },
3272
3263
  {
3273
3264
  // planning with zero documentable findings is a user question, not a