audit-tools 0.51.11 → 0.52.1
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/audit/cli/auditStep.d.ts.map +1 -1
- package/dist/audit/cli/auditStep.js +2 -2
- package/dist/audit/cli/auditStep.js.map +1 -1
- package/dist/audit/cli/charterClarificationPrompt.d.ts +16 -8
- package/dist/audit/cli/charterClarificationPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterClarificationPrompt.js +95 -42
- package/dist/audit/cli/charterClarificationPrompt.js.map +1 -1
- package/dist/audit/cli/charterComparisonPrompt.d.ts +16 -6
- package/dist/audit/cli/charterComparisonPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterComparisonPrompt.js +76 -29
- package/dist/audit/cli/charterComparisonPrompt.js.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.d.ts +5 -4
- package/dist/audit/cli/charterExtractionPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.js +64 -42
- package/dist/audit/cli/charterExtractionPrompt.js.map +1 -1
- package/dist/audit/cli/charterFidelityPrompt.d.ts +17 -4
- package/dist/audit/cli/charterFidelityPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterFidelityPrompt.js +72 -21
- package/dist/audit/cli/charterFidelityPrompt.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts +23 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +204 -41
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/dispatch/packetFilter.d.ts +8 -0
- package/dist/audit/cli/dispatch/packetFilter.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.js +20 -4
- package/dist/audit/cli/fanoutLanes.js.map +1 -1
- package/dist/audit/cli/laneValidators.d.ts +19 -7
- package/dist/audit/cli/laneValidators.d.ts.map +1 -1
- package/dist/audit/cli/laneValidators.js +73 -18
- package/dist/audit/cli/laneValidators.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +13 -6
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +25 -2
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +105 -35
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.js +89 -4
- package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -1
- package/dist/audit/cli/sampleRunCommand.d.ts +10 -0
- package/dist/audit/cli/sampleRunCommand.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.d.ts +4 -4
- package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.js +35 -71
- package/dist/audit/cli/semanticReviewStep.js.map +1 -1
- package/dist/audit/contracts/findingContractPrompt.d.ts.map +1 -1
- package/dist/audit/contracts/findingContractPrompt.js +7 -1
- package/dist/audit/contracts/findingContractPrompt.js.map +1 -1
- package/dist/audit/contracts/workerSchemas.d.ts +280 -4
- package/dist/audit/contracts/workerSchemas.d.ts.map +1 -1
- package/dist/audit/contracts/workerSchemas.js +24 -2
- package/dist/audit/contracts/workerSchemas.js.map +1 -1
- package/dist/audit/orchestrator/charterClarificationExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterClarificationExecutor.js +39 -0
- package/dist/audit/orchestrator/charterClarificationExecutor.js.map +1 -1
- package/dist/audit/orchestrator/charterExtractionExecutor.d.ts +12 -5
- package/dist/audit/orchestrator/charterExtractionExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterExtractionExecutor.js +39 -9
- package/dist/audit/orchestrator/charterExtractionExecutor.js.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.d.ts +7 -0
- package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.js +174 -19
- package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
- package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
- package/dist/audit/orchestrator/executorRunners.js +14 -3
- package/dist/audit/orchestrator/executorRunners.js.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointGate.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointGate.js +1 -5
- package/dist/audit/orchestrator/intentCheckpointGate.js.map +1 -1
- package/dist/audit/orchestrator/requeueCommand.d.ts +8 -0
- package/dist/audit/orchestrator/requeueCommand.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/lensVerification.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/lensVerification.js +94 -71
- package/dist/audit/orchestrator/selectiveDeepening/lensVerification.js.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts +0 -2
- package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/shared.js +9 -2
- package/dist/audit/orchestrator/selectiveDeepening/shared.js.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/stewardFollowup.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/stewardFollowup.js +28 -4
- package/dist/audit/orchestrator/selectiveDeepening/stewardFollowup.js.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.d.ts +10 -3
- package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.js +5 -4
- package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
- package/dist/audit/reporting/synthesis.d.ts +23 -6
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +53 -13
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/reporting/synthesisNarrativePrompt.d.ts +26 -1
- package/dist/audit/reporting/synthesisNarrativePrompt.d.ts.map +1 -1
- package/dist/audit/reporting/synthesisNarrativePrompt.js +53 -13
- package/dist/audit/reporting/synthesisNarrativePrompt.js.map +1 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.d.ts.map +1 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.js +67 -40
- package/dist/audit/systemic/secondOrderAdversaryPrompt.js.map +1 -1
- package/dist/audit/systemic/systemicChallengeLoop.d.ts.map +1 -1
- package/dist/audit/systemic/systemicChallengeLoop.js +13 -1
- package/dist/audit/systemic/systemicChallengeLoop.js.map +1 -1
- package/dist/audit/types/conceptualAdjudication.d.ts +26 -2
- package/dist/audit/types/conceptualAdjudication.d.ts.map +1 -1
- package/dist/audit/types/conceptualAdjudication.js +26 -7
- package/dist/audit/types/conceptualAdjudication.js.map +1 -1
- package/dist/audit/types.d.ts +279 -0
- package/dist/audit/types.d.ts.map +1 -1
- package/dist/audit/types.js +52 -0
- package/dist/audit/types.js.map +1 -1
- package/dist/audit/validation/auditResults.d.ts.map +1 -1
- package/dist/audit/validation/auditResults.js +51 -6
- package/dist/audit/validation/auditResults.js.map +1 -1
- package/dist/audit/validation/ingestIssueCodes.d.ts +3 -1
- package/dist/audit/validation/ingestIssueCodes.d.ts.map +1 -1
- package/dist/audit/validation/ingestIssueCodes.js +27 -15
- package/dist/audit/validation/ingestIssueCodes.js.map +1 -1
- package/dist/remediate/contractPipeline/sketchSource.d.ts +13 -3
- package/dist/remediate/contractPipeline/sketchSource.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/sketchSource.js +14 -8
- package/dist/remediate/contractPipeline/sketchSource.js.map +1 -1
- package/dist/remediate/intake.d.ts +140 -36
- package/dist/remediate/intake.d.ts.map +1 -1
- package/dist/remediate/intake.js +159 -38
- package/dist/remediate/intake.js.map +1 -1
- package/dist/remediate/intent/checkpointFilter.d.ts +1 -2
- package/dist/remediate/intent/checkpointFilter.d.ts.map +1 -1
- package/dist/remediate/intent/checkpointFilter.js +1 -5
- package/dist/remediate/intent/checkpointFilter.js.map +1 -1
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +8 -8
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/phases/closeVerifyHeadEvidence.d.ts +19 -35
- package/dist/remediate/phases/closeVerifyHeadEvidence.d.ts.map +1 -1
- package/dist/remediate/phases/closeVerifyHeadEvidence.js +38 -12
- package/dist/remediate/phases/closeVerifyHeadEvidence.js.map +1 -1
- package/dist/remediate/phases/plan.d.ts +1 -1
- package/dist/remediate/review/reviewGate.d.ts +68 -39
- package/dist/remediate/review/reviewGate.d.ts.map +1 -1
- package/dist/remediate/review/reviewGate.js +133 -50
- package/dist/remediate/review/reviewGate.js.map +1 -1
- package/dist/remediate/state/store.d.ts +1 -2
- package/dist/remediate/state/store.d.ts.map +1 -1
- package/dist/remediate/state/store.js +124 -3
- package/dist/remediate/state/store.js.map +1 -1
- package/dist/remediate/state/types.d.ts +59 -8
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js +12 -1
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +40 -2
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +147 -53
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.d.ts +32 -8
- package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.js +145 -131
- package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +6 -4
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +291 -155
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/intakeResolver.d.ts +1 -1
- package/dist/remediate/steps/intakeResolver.d.ts.map +1 -1
- package/dist/remediate/steps/intakeResolver.js +50 -49
- package/dist/remediate/steps/intakeResolver.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +337 -294
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/prompts.d.ts +10 -3
- package/dist/remediate/steps/prompts.d.ts.map +1 -1
- package/dist/remediate/steps/prompts.js +191 -228
- package/dist/remediate/steps/prompts.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +2 -2
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js +10 -3
- package/dist/remediate/steps/types.js.map +1 -1
- package/dist/remediate/validation/artifacts.d.ts.map +1 -1
- package/dist/remediate/validation/artifacts.js +5 -22
- package/dist/remediate/validation/artifacts.js.map +1 -1
- package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipeline.js +25 -8
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/remediate/validation/remediationState.d.ts +0 -1
- package/dist/remediate/validation/remediationState.d.ts.map +1 -1
- package/dist/remediate/validation/remediationState.js +1 -20
- package/dist/remediate/validation/remediationState.js.map +1 -1
- package/dist/shared/decompose/charterClarification.d.ts +7 -0
- package/dist/shared/decompose/charterClarification.d.ts.map +1 -1
- package/dist/shared/decompose/charterClarification.js +8 -1
- package/dist/shared/decompose/charterClarification.js.map +1 -1
- package/dist/shared/decompose/charterExtraction.d.ts +204 -16
- package/dist/shared/decompose/charterExtraction.d.ts.map +1 -1
- package/dist/shared/decompose/charterExtraction.js +70 -13
- package/dist/shared/decompose/charterExtraction.js.map +1 -1
- package/dist/shared/decompose/systemicChallenge.d.ts +42 -1
- package/dist/shared/decompose/systemicChallenge.d.ts.map +1 -1
- package/dist/shared/decompose/systemicChallenge.js +51 -0
- package/dist/shared/decompose/systemicChallenge.js.map +1 -1
- package/dist/shared/git.d.ts +31 -0
- package/dist/shared/git.d.ts.map +1 -1
- package/dist/shared/git.js +105 -0
- package/dist/shared/git.js.map +1 -1
- package/dist/shared/index.d.ts +17 -14
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +18 -11
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/stepContractWriter.d.ts.map +1 -1
- package/dist/shared/io/stepContractWriter.js +13 -4
- package/dist/shared/io/stepContractWriter.js.map +1 -1
- package/dist/shared/reporting/auditDeliverable.d.ts +8 -3
- package/dist/shared/reporting/auditDeliverable.d.ts.map +1 -1
- package/dist/shared/reporting/auditDeliverable.js +10 -3
- package/dist/shared/reporting/auditDeliverable.js.map +1 -1
- package/dist/shared/submission/hostHandoffCore.d.ts +7 -0
- package/dist/shared/submission/hostHandoffCore.d.ts.map +1 -1
- package/dist/shared/submission/hostHandoffCore.js +15 -2
- package/dist/shared/submission/hostHandoffCore.js.map +1 -1
- package/dist/shared/submission/hostResultOutcomes.d.ts +60 -10
- package/dist/shared/submission/hostResultOutcomes.d.ts.map +1 -1
- package/dist/shared/submission/hostResultOutcomes.js +42 -10
- package/dist/shared/submission/hostResultOutcomes.js.map +1 -1
- package/dist/shared/submission/ingestReport.d.ts +54 -0
- package/dist/shared/submission/ingestReport.d.ts.map +1 -0
- package/dist/shared/submission/ingestReport.js +44 -0
- package/dist/shared/submission/ingestReport.js.map +1 -0
- package/dist/shared/submission/ingestionChecks.d.ts +8 -13
- package/dist/shared/submission/ingestionChecks.d.ts.map +1 -1
- package/dist/shared/submission/ingestionChecks.js +9 -14
- package/dist/shared/submission/ingestionChecks.js.map +1 -1
- package/dist/shared/submission/submissionClassifier.d.ts +6 -0
- package/dist/shared/submission/submissionClassifier.d.ts.map +1 -1
- package/dist/shared/submission/submissionClassifier.js +16 -0
- package/dist/shared/submission/submissionClassifier.js.map +1 -1
- package/dist/shared/submission/workerPromptBinding.d.ts +18 -0
- package/dist/shared/submission/workerPromptBinding.d.ts.map +1 -0
- package/dist/shared/submission/workerPromptBinding.js +38 -0
- package/dist/shared/submission/workerPromptBinding.js.map +1 -0
- package/dist/shared/tooling/exec.d.ts +24 -0
- package/dist/shared/tooling/exec.d.ts.map +1 -1
- package/dist/shared/tooling/exec.js +30 -0
- package/dist/shared/tooling/exec.js.map +1 -1
- package/dist/shared/types/charter.d.ts +143 -7
- package/dist/shared/types/charter.d.ts.map +1 -1
- package/dist/shared/types/charter.js +63 -7
- package/dist/shared/types/charter.js.map +1 -1
- package/dist/shared/types/charterPacket.d.ts +13 -0
- package/dist/shared/types/charterPacket.d.ts.map +1 -1
- package/dist/shared/types/charterPacket.js.map +1 -1
- package/dist/shared/types/contractPipeline/obligations.d.ts +30 -11
- package/dist/shared/types/contractPipeline/obligations.d.ts.map +1 -1
- package/dist/shared/types/contractPipeline/obligations.js +6 -1
- package/dist/shared/types/contractPipeline/obligations.js.map +1 -1
- package/dist/shared/types/contractPropertyShapes.d.ts.map +1 -1
- package/dist/shared/types/contractPropertyShapes.js +13 -0
- package/dist/shared/types/contractPropertyShapes.js.map +1 -1
- package/dist/shared/types/finding.d.ts +210 -15
- package/dist/shared/types/finding.d.ts.map +1 -1
- package/dist/shared/types/finding.js +68 -5
- package/dist/shared/types/finding.js.map +1 -1
- package/dist/shared/types/intentCheckpoint.d.ts +28 -42
- package/dist/shared/types/intentCheckpoint.d.ts.map +1 -1
- package/dist/shared/types/intentCheckpoint.js +30 -33
- package/dist/shared/types/intentCheckpoint.js.map +1 -1
- package/dist/shared/types/remediationOutcome.d.ts +8 -8
- package/dist/shared/validation/citationGrounding.d.ts +29 -5
- package/dist/shared/validation/citationGrounding.d.ts.map +1 -1
- package/dist/shared/validation/citationGrounding.js +56 -9
- package/dist/shared/validation/citationGrounding.js.map +1 -1
- package/dist/shared/validation/designFindingGrounding.d.ts +30 -9
- package/dist/shared/validation/designFindingGrounding.d.ts.map +1 -1
- package/dist/shared/validation/designFindingGrounding.js +32 -12
- package/dist/shared/validation/designFindingGrounding.js.map +1 -1
- package/dist/shared/validation/findingsReport.d.ts +13 -2
- package/dist/shared/validation/findingsReport.d.ts.map +1 -1
- package/dist/shared/validation/findingsReport.js +19 -2
- package/dist/shared/validation/findingsReport.js.map +1 -1
- package/docs/audit-pkg/contracts.md +4 -5
- package/package.json +1 -1
- package/schemas/audit_result.schema.json +51 -0
- package/schemas/audit_results.schema.json +51 -0
- package/schemas/audit_task.schema.json +44 -0
- package/schemas/finding.schema.json +4 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// sites-pinned: tests/remediate/friction-capture-closeout.test.ts, tests/remediate/next-step-lifecycle.test.ts, tests/remediate/next-step-pipeline-dispatch.test.ts, tests/remediate/next-step-outcomes-contract.test.ts, tests/remediate/integration-pipeline.test.ts, tests/remediate/outcomes-roundtrip.test.ts, tests/remediate/phase-close.test.ts, tests/remediate/grounding.test.ts
|
|
1
|
+
// sites-pinned: tests/remediate/friction-capture-closeout.test.ts, tests/remediate/next-step-lifecycle.test.ts, tests/remediate/next-step-pipeline-dispatch.test.ts, tests/remediate/next-step-outcomes-contract.test.ts, tests/remediate/integration-pipeline.test.ts, tests/remediate/outcomes-roundtrip.test.ts, tests/remediate/phase-close.test.ts, tests/remediate/grounding.test.ts, tests/remediate/clarification-round-contract.test.ts, tests/remediate/next-step-review-gate.test.ts, tests/remediate/n-r04-intent-checkpoint.test.ts, tests/remediate/final-gate-red-pause.test.ts
|
|
2
2
|
// (the free-form branch's write
|
|
3
3
|
// scope is normalized — a backslash-spelled citation no longer wedges prepare)
|
|
4
4
|
import { AUDIT_TOOLS_DIRNAME } from "../../shared/io/auditToolsPaths.js";
|
|
5
5
|
import { loadRemediateSessionConfig } from "./sessionConfigLoad.js";
|
|
6
|
+
import { z } from "zod";
|
|
6
7
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
7
8
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
8
9
|
import { dirname, join, resolve } from "node:path";
|
|
9
10
|
import { StateStore } from "../state/store.js";
|
|
10
11
|
// IO / validation / rendering helpers
|
|
11
|
-
import { discardOnSchemaVersionMismatch, readOptionalJsonFile, stagedAndUntracked, writeJsonFile, writeTextFile, buildAuditDeliverablePair,
|
|
12
|
+
import { discardOnSchemaVersionMismatch, readOptionalJsonFile, stagedAndUntracked, writeJsonFile, writeTextFile, buildAuditDeliverablePair, auditReadOf, formatValidationIssues, isRecord, renderIngestReportLines, withFsRetry, RunLogger, coerceJsonObjectArg, renderPromptCommand, headCommit, projectAuditFindingsReportSubset,
|
|
12
13
|
// obligation engine + intent
|
|
13
14
|
interpretFreeFormIntent, interpretIntent, unresolvedFromClauses, advance, describeStoppedFold, decideFrictionTriage, buildFrictionTriageBlock, linkFrictionRunIds,
|
|
14
15
|
// domain constants
|
|
@@ -19,7 +20,7 @@ import { runTriagePhase } from "../phases/triage.js";
|
|
|
19
20
|
import { runClosePhase } from "../phases/close.js";
|
|
20
21
|
import { validateRemediationPlan } from "../validation/remediationState.js";
|
|
21
22
|
import { readExtractedPlanIfPresent, } from "./dispatch/marshal.js";
|
|
22
|
-
import { ingestRemediationHostResults, hostDependencyLevels, permanentlyDeadPendingBlocks, precomputeRecoveryTestVerdicts, prepareRemediationHostHandoff, workloadBindingIdentity, } from "./dispatch/hostHandoff.js";
|
|
23
|
+
import { ingestRemediationHostResults, hostDependencyLevels, permanentlyDeadPendingBlocks, precomputeRecoveryTestVerdicts, prepareRemediationHostHandoff, remediationIssueRemedy, workloadBindingIdentity, } from "./dispatch/hostHandoff.js";
|
|
23
24
|
import { FileLockTimeoutError, withFileLock, } from "../../shared/io/fileLock.js";
|
|
24
25
|
import { AUDIT_FINDINGS_FILENAME, AUDIT_REPORT_FILENAME, auditArtifactsDir, auditFindingsPath, auditReportPath, promotedAuditFindingsPath, promotedAuditReportPath, remediationArtifactsDir, } from "../../shared/io/auditToolsPaths.js";
|
|
25
26
|
import { callerWorkingDirectory, discoverRepoRoot, resolveRepoRoot, } from "../../shared/io/repoRoot.js";
|
|
@@ -32,16 +33,16 @@ import { resolveIntakeStep } from "./intakeResolver.js";
|
|
|
32
33
|
import { carriesGateVerdict } from "../../shared/types/remediationOutcome.js";
|
|
33
34
|
import { RUNTIME_RESIDUAL_DECLARATION, readFinalGateVerdict, runToolOwnedFinalGate, writeFinalGateRedRecord, writeFinalGateOutcomeRecord, writeFinalGateVerdict, } from "./finalGate.js";
|
|
34
35
|
import { renderGateAttribution, worktreeContentId, } from "./gateCommands.js";
|
|
35
|
-
import { buildNextContractPipelineStep, shouldEnterContractPipeline, writePathASeedFromFindings, normalizeBlockTouchedFiles, checkWriteScopePathsAgainstTrackedTree, } from "./contractPipeline.js";
|
|
36
|
+
import { buildNextContractPipelineStep, shouldEnterContractPipeline, readSeedAuditRead, writePathASeedFromFindings, normalizeBlockTouchedFiles, checkWriteScopePathsAgainstTrackedTree, } from "./contractPipeline.js";
|
|
36
37
|
import { compareCodeUnits } from "../../shared/compareCodeUnits.js";
|
|
37
38
|
import { contractArtifactExists, contractPipelineDir, } from "../contractPipeline/artifactStore.js";
|
|
38
|
-
import { buildReviewRequest, applyReviewResolution,
|
|
39
|
+
import { buildReviewRequest, applyReviewResolution, parseReviewResolution, REVIEW_REQUEST_SCHEMA_VERSION, } from "../review/reviewGate.js";
|
|
39
40
|
import { buildAutonomousReviewDecision } from "../review/autonomousGate.js";
|
|
40
41
|
import { runFindingFilterPass } from "../findingFilter.js";
|
|
41
42
|
import { droppedFindingsRecordPath, renderDroppedFindingsRecord, } from "../droppedFindingsRecord.js";
|
|
42
|
-
import { intakePaths, isIntakeReady, manifestIsInputBound, readIntakeArtifacts, readProjectFacts, writeProjectFacts, resolveManifestSources, } from "../intake.js";
|
|
43
|
+
import { intakePaths, isIntakeReady, manifestIsInputBound, readIntakeArtifacts, readProjectFacts, readSourceManifest, writeProjectFacts, resolveManifestSources, writeRemediationBrief, } from "../intake.js";
|
|
43
44
|
import { ensureIntakeRiskSignal, readIntakeRiskSignal, writeIntakeRiskSignal, escalateRiskSignal, findingRiskEvidence, distinctAffectedFiles, } from "../riskSignal.js";
|
|
44
|
-
import { readIntentCheckpoint, readIntentCheckpointLenient, } from "audit-tools/shared";
|
|
45
|
+
import { isLegacyDraftCheckpoint, readIntentCheckpoint, readIntentCheckpointLenient, } from "audit-tools/shared";
|
|
45
46
|
import { ambiguityReviewPrompt, clarificationPrompt, collectIntakeClarificationsPrompt, collectStartingPointPrompt, extractedPlanDiscardedPrompt, loaderCommand, reviewApprovalPrompt, synthesizeIntakePrompt, triagePrompt, } from "./prompts.js";
|
|
46
47
|
// Single-sourced prose renders of the canonical lens / severity vocabularies
|
|
47
48
|
// (`audit-tools/shared` `LENSES` / `SEVERITIES`) for the intent-checkpoint
|
|
@@ -950,34 +951,24 @@ export async function recoverIngestHostResults(options) {
|
|
|
950
951
|
return ingested;
|
|
951
952
|
}
|
|
952
953
|
/**
|
|
953
|
-
*
|
|
954
|
+
* The ingest report for the implement-dispatch prompt, as prompt lines.
|
|
954
955
|
*
|
|
955
|
-
* Takes the whole summary rather than just its issues, because the
|
|
956
|
-
* BOTH halves:
|
|
957
|
-
*
|
|
958
|
-
*
|
|
956
|
+
* Takes the whole summary rather than just its issues, because the report needs
|
|
957
|
+
* BOTH halves: the classified issues, and the items that landed without a
|
|
958
|
+
* result (from `work_item_outcomes`, where the run's corroborated commit for
|
|
959
|
+
* the item is what separates that case from plain unfinished work). The
|
|
960
|
+
* sections are the shared renderer's; only the remedy map is this draw's.
|
|
959
961
|
*/
|
|
960
|
-
function
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
.
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
`for it and its changed files are recorded — but no result file exists at its bound ` +
|
|
971
|
-
`\`result_path\`. This is PARTIAL PROGRESS, not absent work: write the result for the ` +
|
|
972
|
-
`commit that is already there rather than redoing the edit.`)) +
|
|
973
|
-
section("## Result status requiring attention", rejected.map(describe)) +
|
|
974
|
-
section("## Results not yet written", notYetWritten.map(describe));
|
|
975
|
-
if (body === "")
|
|
976
|
-
return "";
|
|
977
|
-
return (body +
|
|
978
|
-
"\nThe workload was restored from its tool-owned digest when necessary. Repair or\n" +
|
|
979
|
-
"complete only the named result files; do not rewrite the workload or its\n" +
|
|
980
|
-
"baseline.\n");
|
|
962
|
+
function remediationIngestReportLines(ingested) {
|
|
963
|
+
return renderIngestReportLines({
|
|
964
|
+
issues: ingested.issues,
|
|
965
|
+
remedy: remediationIssueRemedy,
|
|
966
|
+
workload: "named_above",
|
|
967
|
+
landedWithoutResult: [...ingested.work_item_outcomes]
|
|
968
|
+
.filter(([, outcome]) => outcome === "missing_result_with_commit")
|
|
969
|
+
.map(([id]) => id)
|
|
970
|
+
.sort(compareCodeUnits),
|
|
971
|
+
});
|
|
981
972
|
}
|
|
982
973
|
async function buildImplementDispatchStep(ctx) {
|
|
983
974
|
const { root, artifactsDir, state, store, runLogger } = ctx;
|
|
@@ -1053,17 +1044,29 @@ async function buildImplementDispatchStep(ctx) {
|
|
|
1053
1044
|
// RENDER — a channel that survives exactly as long as the host reads this one
|
|
1054
1045
|
// step, and that is reached only when nothing was accepted.
|
|
1055
1046
|
//
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
// friction: a host parser special-casing "no
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1061
|
-
// result FOR THE COMMIT THAT IS ALREADY THERE.
|
|
1062
|
-
// missing, no commit — nothing yet; write it when the work is done.
|
|
1063
|
-
// The classification is on CODES, never message text, so rewording a message
|
|
1064
|
-
// cannot move an item between sections.
|
|
1065
|
-
const diagnostics = remediationResultDiagnostics(ingested);
|
|
1047
|
+
// Each issue lands under the section its CODE's remedy names, never under one
|
|
1048
|
+
// its message text suggests, so rewording a message cannot move an item
|
|
1049
|
+
// between sections (the measured friction: a host parser special-casing "no
|
|
1050
|
+
// result file exists").
|
|
1051
|
+
const report = remediationIngestReportLines(ingested);
|
|
1066
1052
|
const nextCommand = loaderCommand("next-step");
|
|
1053
|
+
const promptLines = [
|
|
1054
|
+
"# Implement the remediation work items",
|
|
1055
|
+
"",
|
|
1056
|
+
"Read the workload file:",
|
|
1057
|
+
"",
|
|
1058
|
+
`\`${handoff.workload_path}\``,
|
|
1059
|
+
"",
|
|
1060
|
+
"The file lists every work item that is ready now. Give each item's prompt to a worker. You choose the order, the groups, and how many run at the same time.",
|
|
1061
|
+
"",
|
|
1062
|
+
...report,
|
|
1063
|
+
...(report.length > 0 ? ["Change only the result files named above.", ""] : []),
|
|
1064
|
+
"Do not edit the workload file.",
|
|
1065
|
+
"",
|
|
1066
|
+
"When each item has its commit on HEAD and its result file, run:",
|
|
1067
|
+
"",
|
|
1068
|
+
`\`${nextCommand}\``,
|
|
1069
|
+
];
|
|
1067
1070
|
return {
|
|
1068
1071
|
kind: "emit",
|
|
1069
1072
|
step: await writeCurrentStep({
|
|
@@ -1072,23 +1075,7 @@ async function buildImplementDispatchStep(ctx) {
|
|
|
1072
1075
|
runId,
|
|
1073
1076
|
repoRoot: root,
|
|
1074
1077
|
artifactsDir,
|
|
1075
|
-
prompt:
|
|
1076
|
-
# Implement the Eligible Remediation Workload
|
|
1077
|
-
|
|
1078
|
-
Read the generated workload at:
|
|
1079
|
-
|
|
1080
|
-
\`${handoff.workload_path}\`
|
|
1081
|
-
|
|
1082
|
-
It contains the complete, dependency-safe current frontier. Complete every work
|
|
1083
|
-
item and write its exact prompt-bound result contract to its \`result_path\`.
|
|
1084
|
-
The host owns execution choices, grouping, and concurrency; audit-tools performs
|
|
1085
|
-
no launch, routing, or quota decision. Do not start a later dependency level.
|
|
1086
|
-
${diagnostics}
|
|
1087
|
-
|
|
1088
|
-
After all completed changes are merged and their result files exist, run:
|
|
1089
|
-
|
|
1090
|
-
\`${nextCommand}\`
|
|
1091
|
-
`,
|
|
1078
|
+
prompt: `\n${promptLines.join("\n")}\n`,
|
|
1092
1079
|
allowedCommands: [
|
|
1093
1080
|
...new Set(handoff.workload.work_items.flatMap((item) => item.required_tests)),
|
|
1094
1081
|
nextCommand,
|
|
@@ -1333,6 +1320,16 @@ runLogger) {
|
|
|
1333
1320
|
};
|
|
1334
1321
|
}
|
|
1335
1322
|
// Past the recovery boundary: a failure below is a real failure and propagates.
|
|
1323
|
+
//
|
|
1324
|
+
// WHAT THE AUDIT READ, stamped by the tool. `normalizeExtractedPlan` builds
|
|
1325
|
+
// the plan field by field and deliberately has no `audit_read` line: the
|
|
1326
|
+
// extracted plan is host-writable, and this commit becomes terminal
|
|
1327
|
+
// dispositions at close. Only a plan the contract pipeline promoted from a
|
|
1328
|
+
// findings report has an audit-side source; every other plan states `null`.
|
|
1329
|
+
plan = {
|
|
1330
|
+
...plan,
|
|
1331
|
+
audit_read: plan.source === "contract_pipeline" ? await readSeedAuditRead(artifactsDir) : null,
|
|
1332
|
+
};
|
|
1336
1333
|
const pipelined = await applyPlanPipeline(plan, { root, artifactsDir });
|
|
1337
1334
|
// Run-start dirty snapshot for the V2 staging manifest, capture-once: the
|
|
1338
1335
|
// extracted-plan join runs at plan time (before any remediation edit), so
|
|
@@ -1480,7 +1477,7 @@ async function handleWaitingForReviewApproval(root, artifactsDir, request, refus
|
|
|
1480
1477
|
artifactsDir,
|
|
1481
1478
|
prompt: reviewApprovalPrompt(request, reviewResolutionPath(artifactsDir), refusal),
|
|
1482
1479
|
allowedCommands: [loaderCommand("next-step")],
|
|
1483
|
-
stopCondition: "Stop after presenting the findings for approval and collecting the user's approve/
|
|
1480
|
+
stopCondition: "Stop after presenting the findings for approval and collecting the user's approve/decline decision, unless the decision is already recorded and the prompt told you to continue.",
|
|
1484
1481
|
artifactPaths: {
|
|
1485
1482
|
review_request: reviewRequestPath(artifactsDir),
|
|
1486
1483
|
review_resolution: reviewResolutionPath(artifactsDir),
|
|
@@ -1488,29 +1485,25 @@ async function handleWaitingForReviewApproval(root, artifactsDir, request, refus
|
|
|
1488
1485
|
});
|
|
1489
1486
|
}
|
|
1490
1487
|
/**
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
*
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1488
|
+
* Read the review resolution for both gate paths, through the one strict
|
|
1489
|
+
* parser ({@link parseReviewResolution}). A refused file (bad JSON, an unknown
|
|
1490
|
+
* field, a wrong type, an id or tier outside the request) is archived as
|
|
1491
|
+
* `.refused-<ts>` — never applied — and the gate re-halts with each problem
|
|
1492
|
+
* named. The gate's default is approve, so reading past a bad file would turn
|
|
1493
|
+
* the user's decline into an approval. A stale file from another run is
|
|
1494
|
+
* archived as `.stale-<ts>` and the gate re-halts with no refusal. Returns the
|
|
1495
|
+
* re-halt step, or the resolution to apply.
|
|
1498
1496
|
*/
|
|
1499
|
-
async function
|
|
1500
|
-
const
|
|
1501
|
-
if (
|
|
1502
|
-
return
|
|
1503
|
-
|
|
1504
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}
|
|
1497
|
+
async function readReviewResolution(root, artifactsDir, request, resolutionPath, requestPath) {
|
|
1498
|
+
const parsed = parseReviewResolution(await readFile(resolutionPath, "utf8"), request);
|
|
1499
|
+
if (parsed.kind === "ok")
|
|
1500
|
+
return { resolution: parsed.resolution };
|
|
1501
|
+
const suffix = parsed.kind === "stale" ? "stale" : "refused";
|
|
1502
|
+
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.${suffix}-${Date.now()}`));
|
|
1505
1503
|
await writeJsonFile(requestPath, request);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
}
|
|
1510
|
-
if (screen.unknown_tiers.length > 0) {
|
|
1511
|
-
parts.push(`unknown tier(s): ${screen.unknown_tiers.map((t) => `\`${t}\``).join(", ")}`);
|
|
1512
|
-
}
|
|
1513
|
-
return handleWaitingForReviewApproval(root, artifactsDir, request, parts.join("; "));
|
|
1504
|
+
return {
|
|
1505
|
+
halt: await handleWaitingForReviewApproval(root, artifactsDir, request, parsed.kind === "refused" ? parsed.reason : undefined),
|
|
1506
|
+
};
|
|
1514
1507
|
}
|
|
1515
1508
|
/**
|
|
1516
1509
|
* Run the review-approval gate over the SURVIVOR finding set (already passed
|
|
@@ -1523,7 +1516,9 @@ async function refuseUnknownIdResolution(root, artifactsDir, request, resolution
|
|
|
1523
1516
|
* exists the gate consumes it directly and proceeds, so it fires (and halts) at
|
|
1524
1517
|
* most once per run. Empty survivors → nothing to review → approve-none/proceed.
|
|
1525
1518
|
*/
|
|
1526
|
-
async function runReviewApprovalGate(root, artifactsDir, survivors,
|
|
1519
|
+
async function runReviewApprovalGate(root, artifactsDir, survivors,
|
|
1520
|
+
/** The source report's `audit_read`, carried onto any leftover re-emit. */
|
|
1521
|
+
sourceAuditRead, autonomous = false) {
|
|
1527
1522
|
const decisionPath = reviewDecisionPath(artifactsDir);
|
|
1528
1523
|
// First crossing only: no decision yet AND the pipeline has not started.
|
|
1529
1524
|
const gateOpen = survivors.length > 0 &&
|
|
@@ -1550,7 +1545,7 @@ async function runReviewApprovalGate(root, artifactsDir, survivors, autonomous =
|
|
|
1550
1545
|
// so the next nightly run picks them up via defaultInputCandidates. Always
|
|
1551
1546
|
// on disk regardless of whether a git remote / PR is available.
|
|
1552
1547
|
const leftovers = survivors.filter((f) => !approvedSet.has(f.id));
|
|
1553
|
-
await emitAutonomousLeftoverDeliverable(root, artifactsDir, leftovers);
|
|
1548
|
+
await emitAutonomousLeftoverDeliverable(root, artifactsDir, leftovers, sourceAuditRead);
|
|
1554
1549
|
return {
|
|
1555
1550
|
kind: "proceed",
|
|
1556
1551
|
approved: survivors.filter((f) => approvedSet.has(f.id)),
|
|
@@ -1574,21 +1569,10 @@ async function runReviewApprovalGate(root, artifactsDir, survivors, autonomous =
|
|
|
1574
1569
|
// Consume the resolution into a durable, reasoned decision record.
|
|
1575
1570
|
// Regenerable: a stale-schema request is treated as absent and rebuilt below.
|
|
1576
1571
|
const request = discardOnSchemaVersionMismatch(await readOptionalJsonFile(requestPath), REVIEW_REQUEST_SCHEMA_VERSION) ?? buildReviewRequest(survivors, randomRunId("path-a-review"));
|
|
1577
|
-
const
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.stale-${Date.now()}`));
|
|
1582
|
-
await writeJsonFile(requestPath, request);
|
|
1583
|
-
return {
|
|
1584
|
-
kind: "halt",
|
|
1585
|
-
step: await handleWaitingForReviewApproval(root, artifactsDir, request),
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
const refusalStep = await refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath);
|
|
1589
|
-
if (refusalStep)
|
|
1590
|
-
return { kind: "halt", step: refusalStep };
|
|
1591
|
-
const decision = applyReviewResolution(request, resolution);
|
|
1572
|
+
const read = await readReviewResolution(root, artifactsDir, request, resolutionPath, requestPath);
|
|
1573
|
+
if ("halt" in read)
|
|
1574
|
+
return { kind: "halt", step: read.halt };
|
|
1575
|
+
const decision = applyReviewResolution(request, read.resolution);
|
|
1592
1576
|
await writeReviewDecisionRecord(decisionPath, {
|
|
1593
1577
|
planId: request.plan_id,
|
|
1594
1578
|
approvedIds: decision.approved_ids,
|
|
@@ -1627,8 +1611,12 @@ async function runReviewApprovalGate(root, artifactsDir, survivors, autonomous =
|
|
|
1627
1611
|
* Empty leftovers still emit an (empty) pair so a downstream "is there work?"
|
|
1628
1612
|
* probe sees a deterministic deliverable rather than a stale one.
|
|
1629
1613
|
*/
|
|
1630
|
-
async function emitAutonomousLeftoverDeliverable(root, artifactsDir, leftovers
|
|
1631
|
-
|
|
1614
|
+
async function emitAutonomousLeftoverDeliverable(root, artifactsDir, leftovers,
|
|
1615
|
+
// The leftovers were read by the ORIGINAL audit, so the pair re-states its
|
|
1616
|
+
// `audit_read`. The commit current now is a remediation-side commit — exactly
|
|
1617
|
+
// what the next run's evidence leg must never be handed as `B`.
|
|
1618
|
+
sourceAuditRead) {
|
|
1619
|
+
const pair = buildAuditDeliverablePair(leftovers, sourceAuditRead, {
|
|
1632
1620
|
title: "Audit Report — Autonomous Leftovers",
|
|
1633
1621
|
intro: "Findings left LIVE by an unattended (autonomous) remediation run: not on the " +
|
|
1634
1622
|
"fail-closed non-destructiveness allowlist (or not tier-safe), so not auto-fixed. " +
|
|
@@ -1771,7 +1759,7 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
|
|
|
1771
1759
|
checkpoint: checkpoint ?? undefined,
|
|
1772
1760
|
evidenceGrounding: true,
|
|
1773
1761
|
});
|
|
1774
|
-
const gate = await runReviewApprovalGate(root, artifactsDir, filter.survivors,
|
|
1762
|
+
const gate = await runReviewApprovalGate(root, artifactsDir, filter.survivors, auditReadOf(auditFindings),
|
|
1775
1763
|
// Autonomous review changes the approval policy only; it never grants
|
|
1776
1764
|
// implementation or process-execution authority.
|
|
1777
1765
|
canonicalIntent.review_mode === "autonomous");
|
|
@@ -1815,9 +1803,10 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
|
|
|
1815
1803
|
}
|
|
1816
1804
|
const paths = intakePaths(artifactsDir);
|
|
1817
1805
|
const sourcePaths = new Set();
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1806
|
+
// The brief is the tool's render of the ready summary; write it before it
|
|
1807
|
+
// becomes a pipeline source, so the source is never absent or stale.
|
|
1808
|
+
await writeRemediationBrief(artifactsDir, intake.summary);
|
|
1809
|
+
sourcePaths.add(paths.brief);
|
|
1821
1810
|
for (const source of manifestSources) {
|
|
1822
1811
|
// Swap the raw audit-findings.json for the approved-only filtered file so a
|
|
1823
1812
|
// declined finding can never re-enter the pipeline as a source input.
|
|
@@ -1934,59 +1923,128 @@ Stop after presenting this choice. Do not advance the run until the user decides
|
|
|
1934
1923
|
},
|
|
1935
1924
|
});
|
|
1936
1925
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1926
|
+
// Action tokens are deliberately unambiguous so a host CANNOT lose an
|
|
1927
|
+
// approved finding by a natural word choice at the ambiguity gate: "this
|
|
1928
|
+
// candidate ambiguity isn't genuine" reads as a comment on the AMBIGUITY, so it
|
|
1929
|
+
// must map to `clarified` (proceed with the finding), never to a drop. The
|
|
1930
|
+
// finding-dropping token is named `reject_finding` — it speaks about the
|
|
1931
|
+
// FINDING, not the ambiguity, and so can't be confused with "no ambiguity here."
|
|
1932
|
+
const PLAN_CLARIFICATION_ACTIONS = ["clarified", "reject_finding", "defer"];
|
|
1933
|
+
/**
|
|
1934
|
+
* One entry of a clarification resolution file. Strict: an unknown field is
|
|
1935
|
+
* refused, not ignored.
|
|
1936
|
+
*
|
|
1937
|
+
* - `rationale` is REQUIRED and non-empty on `clarified`: it becomes the item's
|
|
1938
|
+
* `clarification_context`, the answer the next worker reads. A `clarified`
|
|
1939
|
+
* entry without it re-opened the item with no answer attached.
|
|
1940
|
+
* - `scope_additions` lists the files the answer ADDS to the owning block's
|
|
1941
|
+
* write scope (open-bugs.md:110): every file the fix must create or edit
|
|
1942
|
+
* beyond the promoted scope — the test a node must write, the source a
|
|
1943
|
+
* generated artifact mirrors, a new shared module, a manifest. It is allowed
|
|
1944
|
+
* ONLY on `clarified`, the one action that re-opens the item; on
|
|
1945
|
+
* `reject_finding` or `defer` it used to be ignored without a word. Each path
|
|
1946
|
+
* is validated whole-file fail-closed BEFORE anything is applied
|
|
1947
|
+
* (`validateClarificationScopeAdditions`), so the host never edits the plan
|
|
1948
|
+
* by hand.
|
|
1949
|
+
*/
|
|
1950
|
+
const PlanClarificationResolutionSchema = z
|
|
1951
|
+
.object({
|
|
1952
|
+
finding_id: z.string().min(1, "must be a non-empty finding id"),
|
|
1953
|
+
action: z.enum(PLAN_CLARIFICATION_ACTIONS),
|
|
1954
|
+
rationale: z.string().optional(),
|
|
1955
|
+
scope_additions: z.array(z.string()).optional(),
|
|
1956
|
+
})
|
|
1957
|
+
.strict()
|
|
1958
|
+
.superRefine((entry, ctx) => {
|
|
1959
|
+
if (entry.action === "clarified" && (entry.rationale ?? "").trim().length === 0) {
|
|
1960
|
+
ctx.addIssue({
|
|
1961
|
+
code: z.ZodIssueCode.custom,
|
|
1962
|
+
path: ["rationale"],
|
|
1963
|
+
message: 'is required and must be non-empty when action is "clarified" — it carries the answer to the next worker',
|
|
1968
1964
|
});
|
|
1969
1965
|
}
|
|
1970
|
-
if (
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1966
|
+
if (entry.action !== "clarified" && entry.scope_additions !== undefined) {
|
|
1967
|
+
ctx.addIssue({
|
|
1968
|
+
code: z.ZodIssueCode.custom,
|
|
1969
|
+
path: ["scope_additions"],
|
|
1970
|
+
message: `is allowed only with action "clarified" — a ${entry.action} entry widens no write scope`,
|
|
1971
|
+
});
|
|
1974
1972
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1973
|
+
});
|
|
1974
|
+
/**
|
|
1975
|
+
* Read and validate a clarification resolution file — the mid-run
|
|
1976
|
+
* `clarification_resolution.json` and the up-front `ambiguity_resolution.json`
|
|
1977
|
+
* share this one parser.
|
|
1978
|
+
*
|
|
1979
|
+
* ONE shape is accepted: a bare JSON array of entries. Every entry is checked
|
|
1980
|
+
* against {@link PlanClarificationResolutionSchema}, and a second entry for the
|
|
1981
|
+
* same finding is refused. Any bad entry refuses the WHOLE file, and the reason
|
|
1982
|
+
* names the entry's index and field. The parser this replaced accepted four
|
|
1983
|
+
* shapes and dropped a bad entry in silence: an `"action": "approve"` lost the
|
|
1984
|
+
* user's answer, and the item then waited forever for a question the round no
|
|
1985
|
+
* longer showed.
|
|
1986
|
+
*/
|
|
1987
|
+
async function readPlanClarificationResolutions(path) {
|
|
1988
|
+
let value;
|
|
1989
|
+
try {
|
|
1990
|
+
value = JSON.parse(await readFile(path, "utf8"));
|
|
1991
|
+
}
|
|
1992
|
+
catch (error) {
|
|
1993
|
+
return {
|
|
1994
|
+
ok: false,
|
|
1995
|
+
reason: `the file is not valid JSON (${error instanceof Error ? error.message : String(error)})`,
|
|
1996
|
+
};
|
|
1977
1997
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1998
|
+
if (!Array.isArray(value)) {
|
|
1999
|
+
return {
|
|
2000
|
+
ok: false,
|
|
2001
|
+
reason: "the file must be a JSON array of entries — an object wrapper such as " +
|
|
2002
|
+
'`{"resolutions": [...]}` is not accepted',
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
const problems = [];
|
|
2006
|
+
const firstEntryFor = new Map();
|
|
2007
|
+
const resolutions = [];
|
|
2008
|
+
value.forEach((entry, index) => {
|
|
2009
|
+
const parsed = PlanClarificationResolutionSchema.safeParse(entry);
|
|
2010
|
+
if (!parsed.success) {
|
|
2011
|
+
for (const issue of parsed.error.issues) {
|
|
2012
|
+
const field = issue.path.length > 0 ? ` \`${issue.path.join(".")}\`` : "";
|
|
2013
|
+
problems.push(`entry [${index}]${field}: ${issue.message}`);
|
|
2014
|
+
}
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const prior = firstEntryFor.get(parsed.data.finding_id);
|
|
2018
|
+
if (prior !== undefined) {
|
|
2019
|
+
problems.push(`entry [${index}] \`finding_id\`: \`${parsed.data.finding_id}\` is already answered by entry [${prior}]`);
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
firstEntryFor.set(parsed.data.finding_id, index);
|
|
2023
|
+
resolutions.push(parsed.data);
|
|
1989
2024
|
});
|
|
2025
|
+
return problems.length > 0
|
|
2026
|
+
? { ok: false, reason: problems.join("; ") }
|
|
2027
|
+
: { ok: true, resolutions };
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Every whole-file refusal of a parsed resolution, in check order: the parse,
|
|
2031
|
+
* then the closed id set, then the scope additions. Null means the file may be
|
|
2032
|
+
* applied.
|
|
2033
|
+
*/
|
|
2034
|
+
async function planClarificationRefusal(root, state, parsed, validIds, outsideSetLabel) {
|
|
2035
|
+
if (!parsed.ok)
|
|
2036
|
+
return parsed.reason;
|
|
2037
|
+
const unknownIds = parsed.resolutions
|
|
2038
|
+
.map((r) => r.finding_id)
|
|
2039
|
+
.filter((id) => !validIds.has(id));
|
|
2040
|
+
if (unknownIds.length > 0) {
|
|
2041
|
+
return `finding id(s) ${outsideSetLabel}: ${unknownIds.map((i) => `\`${i}\``).join(", ")}`;
|
|
2042
|
+
}
|
|
2043
|
+
const scopeRefusals = await validateClarificationScopeAdditions(root, state, parsed.resolutions);
|
|
2044
|
+
if (scopeRefusals.length > 0) {
|
|
2045
|
+
return `invalid scope_additions — fix and re-submit: ${scopeRefusals.join(" | ")}`;
|
|
2046
|
+
}
|
|
2047
|
+
return null;
|
|
1990
2048
|
}
|
|
1991
2049
|
/**
|
|
1992
2050
|
* Validate every `clarified` resolution's `scope_additions` BEFORE anything is
|
|
@@ -2043,6 +2101,8 @@ function applyClarificationScopeAdditions(root, state, res) {
|
|
|
2043
2101
|
* Never resurrects a terminal item.
|
|
2044
2102
|
*/
|
|
2045
2103
|
function applyClarificationActionToItem(item, res, now) {
|
|
2104
|
+
// The question is answered (or the finding is closed): it leaves the item.
|
|
2105
|
+
delete item.clarification_question;
|
|
2046
2106
|
if (res.action === "reject_finding") {
|
|
2047
2107
|
item.status = "deemed_inappropriate";
|
|
2048
2108
|
item.failure_reason = res.rationale;
|
|
@@ -2071,32 +2131,22 @@ async function applyPlanClarificationResolution(root, artifactsDir, state, store
|
|
|
2071
2131
|
if (!state.plan || !state.items)
|
|
2072
2132
|
return { kind: "applied", state };
|
|
2073
2133
|
const resolutionPath = join(artifactsDir, "clarification_resolution.json");
|
|
2074
|
-
const
|
|
2075
|
-
// Uniform
|
|
2076
|
-
//
|
|
2077
|
-
//
|
|
2078
|
-
//
|
|
2079
|
-
|
|
2080
|
-
.map((r) => r.finding_id)
|
|
2081
|
-
.filter((id) => !state.items?.[id]);
|
|
2082
|
-
if (unknownIds.length > 0) {
|
|
2083
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2084
|
-
return {
|
|
2085
|
-
kind: "refused",
|
|
2086
|
-
step: await handleWaitingForClarification(root, artifactsDir, state, `finding id(s) not in the plan: ${unknownIds.map((i) => `\`${i}\``).join(", ")}`),
|
|
2087
|
-
};
|
|
2088
|
-
}
|
|
2089
|
-
// Scope deltas ride the same whole-file fail-closed contract as unknown ids:
|
|
2090
|
-
// an invalid addition refuses everything, so a decision record never
|
|
2134
|
+
const parsed = await readPlanClarificationResolutions(resolutionPath);
|
|
2135
|
+
// Uniform whole-file fail-closed contract: a malformed entry, an id outside
|
|
2136
|
+
// the paused set, or an invalid scope addition refuses the WHOLE resolution
|
|
2137
|
+
// (archived, nothing applied) and re-halts with the reason named. The
|
|
2138
|
+
// silent-continue alternative drops the user's answer and leaves its item
|
|
2139
|
+
// waiting on a question nobody is asked, so a decision record never
|
|
2091
2140
|
// half-applies (open-bugs.md:110).
|
|
2092
|
-
const
|
|
2093
|
-
if (
|
|
2141
|
+
const refusal = await planClarificationRefusal(root, state, parsed, new Set(pausedClarifications(state).map((q) => q.finding_id)), "not waiting for a clarification");
|
|
2142
|
+
if (refusal !== null || !parsed.ok) {
|
|
2094
2143
|
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2095
2144
|
return {
|
|
2096
2145
|
kind: "refused",
|
|
2097
|
-
step: await handleWaitingForClarification(root, artifactsDir, state,
|
|
2146
|
+
step: await handleWaitingForClarification(root, artifactsDir, state, refusal ?? "the resolution could not be read"),
|
|
2098
2147
|
};
|
|
2099
2148
|
}
|
|
2149
|
+
const resolutions = parsed.resolutions;
|
|
2100
2150
|
const now = new Date().toISOString();
|
|
2101
2151
|
let appliedCount = 0;
|
|
2102
2152
|
for (const res of resolutions) {
|
|
@@ -2137,14 +2187,30 @@ async function applyPlanClarificationResolution(root, artifactsDir, state, store
|
|
|
2137
2187
|
: hasUnansweredClarification(state)
|
|
2138
2188
|
? "waiting_for_clarification"
|
|
2139
2189
|
: "implementing";
|
|
2140
|
-
state.clarifications = [];
|
|
2141
2190
|
await store.saveState(state);
|
|
2142
2191
|
return { kind: "applied", state };
|
|
2143
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* The open worker questions: one per item paused as `needs_clarification`,
|
|
2195
|
+
* read from the item itself (its `clarification_question`), in content-stable
|
|
2196
|
+
* finding-id order. The item is the question's one home, so this list cannot
|
|
2197
|
+
* disagree with the item statuses the round waits on.
|
|
2198
|
+
*/
|
|
2199
|
+
function pausedClarifications(state) {
|
|
2200
|
+
return Object.values(state.items ?? {})
|
|
2201
|
+
.filter((item) => item.status === "needs_clarification")
|
|
2202
|
+
.sort((left, right) => compareCodeUnits(left.finding_id, right.finding_id))
|
|
2203
|
+
.map((item) => ({
|
|
2204
|
+
finding_id: item.finding_id,
|
|
2205
|
+
// The state store refuses a paused item without a question, so the
|
|
2206
|
+
// fallback is unreachable from a stored state; it keeps the render total.
|
|
2207
|
+
...(item.clarification_question ?? {
|
|
2208
|
+
category: "scope_of_fix",
|
|
2209
|
+
description: item.failure_reason ?? "(no question recorded)",
|
|
2210
|
+
}),
|
|
2211
|
+
}));
|
|
2212
|
+
}
|
|
2144
2213
|
async function handleWaitingForClarification(root, artifactsDir, state, refusal) {
|
|
2145
|
-
const clarifications = state.clarifications ??
|
|
2146
|
-
(await readOptionalJsonFile(join(artifactsDir, "clarification_request.json"))) ??
|
|
2147
|
-
[];
|
|
2148
2214
|
const resolutionPath = join(artifactsDir, "clarification_resolution.json");
|
|
2149
2215
|
return writeCurrentStep({
|
|
2150
2216
|
stepKind: "collect_clarifications",
|
|
@@ -2152,11 +2218,10 @@ async function handleWaitingForClarification(root, artifactsDir, state, refusal)
|
|
|
2152
2218
|
runId: stateRunId(state),
|
|
2153
2219
|
repoRoot: root,
|
|
2154
2220
|
artifactsDir,
|
|
2155
|
-
prompt: clarificationPrompt(
|
|
2221
|
+
prompt: clarificationPrompt(pausedClarifications(state), resolutionPath, refusal),
|
|
2156
2222
|
allowedCommands: [loaderCommand("next-step")],
|
|
2157
2223
|
stopCondition: "Stop after asking the user for clarification answers, unless the answers are already available and the prompt told you to continue.",
|
|
2158
2224
|
artifactPaths: {
|
|
2159
|
-
clarification_request: join(artifactsDir, "clarification_request.json"),
|
|
2160
2225
|
clarification_resolution: resolutionPath,
|
|
2161
2226
|
},
|
|
2162
2227
|
});
|
|
@@ -2214,17 +2279,10 @@ async function runPlanningReviewGate(root, artifactsDir, state, store) {
|
|
|
2214
2279
|
// Resolution present: consume it into a durable, reasoned decision record.
|
|
2215
2280
|
// Regenerable: a stale-schema request is treated as absent and rebuilt below.
|
|
2216
2281
|
const request = discardOnSchemaVersionMismatch(await readOptionalJsonFile(requestPath), REVIEW_REQUEST_SCHEMA_VERSION) ?? buildReviewRequest(findings, reviewPlanId);
|
|
2217
|
-
const
|
|
2218
|
-
if (
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
await writeJsonFile(requestPath, request);
|
|
2222
|
-
return handleWaitingForReviewApproval(root, artifactsDir, request);
|
|
2223
|
-
}
|
|
2224
|
-
const refusalStep = await refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath);
|
|
2225
|
-
if (refusalStep)
|
|
2226
|
-
return refusalStep;
|
|
2227
|
-
const decision = applyReviewResolution(request, resolution);
|
|
2282
|
+
const read = await readReviewResolution(root, artifactsDir, request, resolutionPath, requestPath);
|
|
2283
|
+
if ("halt" in read)
|
|
2284
|
+
return read.halt;
|
|
2285
|
+
const decision = applyReviewResolution(request, read.resolution);
|
|
2228
2286
|
await writeReviewDecisionRecord(decisionPath, {
|
|
2229
2287
|
planId: request.plan_id,
|
|
2230
2288
|
approvedIds: decision.approved_ids,
|
|
@@ -2271,8 +2329,8 @@ function detectPlanAmbiguities(findings, items) {
|
|
|
2271
2329
|
out.push({
|
|
2272
2330
|
finding_id: f.id,
|
|
2273
2331
|
category: "scope_of_fix",
|
|
2274
|
-
description: `"${f.title}" is a ${lens}
|
|
2275
|
-
"
|
|
2332
|
+
description: `"${f.title}" is a finding in the ${lens} lens with ${fileCount === 0 ? "no cited files" : `${fileCount} affected files`}. ` +
|
|
2333
|
+
"Confirm how far the fix should reach: a minimal local change, or a broader restructure.",
|
|
2276
2334
|
});
|
|
2277
2335
|
continue;
|
|
2278
2336
|
}
|
|
@@ -2280,7 +2338,7 @@ function detectPlanAmbiguities(findings, items) {
|
|
|
2280
2338
|
out.push({
|
|
2281
2339
|
finding_id: f.id,
|
|
2282
2340
|
category: "issue_appropriateness",
|
|
2283
|
-
description: `"${f.title}" is a low-confidence finding
|
|
2341
|
+
description: `"${f.title}" is a low-confidence finding. Confirm that it is a real issue to fix in this run.`,
|
|
2284
2342
|
});
|
|
2285
2343
|
}
|
|
2286
2344
|
}
|
|
@@ -2292,8 +2350,8 @@ function detectPlanAmbiguities(findings, items) {
|
|
|
2292
2350
|
* ambiguity is asked as a single batched question up front rather than falling
|
|
2293
2351
|
* silently to triage mid-run. Deterministic heuristics seed CANDIDATES; the host
|
|
2294
2352
|
* reviews them with repo access, dismisses/adds, and batches one user round. Each
|
|
2295
|
-
* item is resolved as `clarified` (answered → re-opened), `
|
|
2296
|
-
* (not a real issue), or `defer` (the user's explicit choice to skip this run).
|
|
2353
|
+
* item is resolved as `clarified` (answered → re-opened), `reject_finding`
|
|
2354
|
+
* (not a real issue → `deemed_inappropriate`), or `defer` (the user's explicit choice to skip this run).
|
|
2297
2355
|
*
|
|
2298
2356
|
* Idempotent: once `ambiguity_decision.json` exists the gate is done and never
|
|
2299
2357
|
* re-halts. An empty resolution proceeds (the host found nothing to ask).
|
|
@@ -2332,16 +2390,16 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
|
2332
2390
|
}
|
|
2333
2391
|
// Resolution present: apply it to items, mark the gate done, archive inputs so
|
|
2334
2392
|
// it cannot re-halt.
|
|
2335
|
-
|
|
2336
|
-
//
|
|
2337
|
-
//
|
|
2338
|
-
//
|
|
2339
|
-
//
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
if (
|
|
2393
|
+
// The same whole-file fail-closed contract as the mid-run round: a malformed
|
|
2394
|
+
// entry, a finding id outside the plan, or an invalid scope addition refuses
|
|
2395
|
+
// the WHOLE resolution (archived, nothing applied) and the gate re-halts with
|
|
2396
|
+
// the reason named — the silent-continue alternative drops a host answer,
|
|
2397
|
+
// leaving its item to fall to mid-run triage unexplained. At this up-front
|
|
2398
|
+
// gate no workload binding exists yet, so an applied scope widening simply
|
|
2399
|
+
// flows into the first dispatch.
|
|
2400
|
+
const parsed = await readPlanClarificationResolutions(resolutionPath);
|
|
2401
|
+
const refusal = await planClarificationRefusal(root, state, parsed, new Set(findings.map((f) => f.id)), "not in the plan");
|
|
2402
|
+
if (refusal !== null || !parsed.ok) {
|
|
2345
2403
|
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2346
2404
|
const candidates = (await readOptionalJsonFile(requestPath)) ??
|
|
2347
2405
|
detectPlanAmbiguities(findings, state.items);
|
|
@@ -2351,30 +2409,7 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
|
2351
2409
|
runId: stateRunId(state),
|
|
2352
2410
|
repoRoot: root,
|
|
2353
2411
|
artifactsDir,
|
|
2354
|
-
prompt: ambiguityReviewPrompt(candidates, resolutionPath, findings.map((f) => f.id),
|
|
2355
|
-
allowedCommands: [loaderCommand("next-step")],
|
|
2356
|
-
stopCondition: "Stop after re-submitting a corrected ambiguity resolution, unless it is already written and the prompt told you to continue.",
|
|
2357
|
-
artifactPaths: {
|
|
2358
|
-
ambiguity_request: requestPath,
|
|
2359
|
-
ambiguity_resolution: resolutionPath,
|
|
2360
|
-
},
|
|
2361
|
-
});
|
|
2362
|
-
}
|
|
2363
|
-
// Scope deltas: the same whole-file fail-closed contract as unknown ids
|
|
2364
|
-
// (open-bugs.md:110). At this up-front gate no workload binding exists yet,
|
|
2365
|
-
// so an applied widening simply flows into the first dispatch.
|
|
2366
|
-
const scopeRefusals = await validateClarificationScopeAdditions(root, state, resolutions);
|
|
2367
|
-
if (scopeRefusals.length > 0) {
|
|
2368
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2369
|
-
const candidates = (await readOptionalJsonFile(requestPath)) ??
|
|
2370
|
-
detectPlanAmbiguities(findings, state.items);
|
|
2371
|
-
return writeCurrentStep({
|
|
2372
|
-
stepKind: "collect_clarifications",
|
|
2373
|
-
status: "blocked",
|
|
2374
|
-
runId: stateRunId(state),
|
|
2375
|
-
repoRoot: root,
|
|
2376
|
-
artifactsDir,
|
|
2377
|
-
prompt: ambiguityReviewPrompt(candidates, resolutionPath, findings.map((f) => f.id), `invalid scope_additions — fix and re-submit: ${scopeRefusals.join(" | ")}`),
|
|
2412
|
+
prompt: ambiguityReviewPrompt(candidates, resolutionPath, findings.map((f) => f.id), refusal ?? "the resolution could not be read"),
|
|
2378
2413
|
allowedCommands: [loaderCommand("next-step")],
|
|
2379
2414
|
stopCondition: "Stop after re-submitting a corrected ambiguity resolution, unless it is already written and the prompt told you to continue.",
|
|
2380
2415
|
artifactPaths: {
|
|
@@ -2383,6 +2418,7 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
|
2383
2418
|
},
|
|
2384
2419
|
});
|
|
2385
2420
|
}
|
|
2421
|
+
const resolutions = parsed.resolutions;
|
|
2386
2422
|
const now = new Date().toISOString();
|
|
2387
2423
|
let changed = false;
|
|
2388
2424
|
for (const res of resolutions) {
|
|
@@ -2625,16 +2661,18 @@ async function emitFinalGateRedStep(ctx) {
|
|
|
2625
2661
|
// edit to a non-ignored file outside the run's own artifacts. This is the
|
|
2626
2662
|
// same carve-out `worktreeContentId` applies, said in the operator's terms.
|
|
2627
2663
|
const bindingBlock = ctx.tree === undefined
|
|
2628
|
-
? "
|
|
2629
|
-
"
|
|
2664
|
+
? "The tool keeps no copy of this result. Each next-step runs the full build\n" +
|
|
2665
|
+
"and suite again, which takes minutes. So fix something before you run\n" +
|
|
2666
|
+
"next-step again."
|
|
2630
2667
|
: ctx.tree === null
|
|
2631
|
-
? "
|
|
2632
|
-
"
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
"
|
|
2637
|
-
"
|
|
2668
|
+
? "The tool could not take a tree id, so it keeps no copy of this result.\n" +
|
|
2669
|
+
"Each next-step runs the full build and suite again, which takes minutes.\n" +
|
|
2670
|
+
"So fix something before you run next-step again."
|
|
2671
|
+
: `The tool keeps this result for tree \`${ctx.tree}\`. An edit to any file\n` +
|
|
2672
|
+
`outside \`${AUDIT_TOOLS_DIRNAME}/\` and outside git-ignored paths changes that tree.\n` +
|
|
2673
|
+
"The next next-step then runs the full build and suite again, which takes\n" +
|
|
2674
|
+
"minutes. With no edit, next-step shows this same red at once and runs\n" +
|
|
2675
|
+
"nothing. So fix something before you run next-step again.";
|
|
2638
2676
|
const nextCommand = loaderCommand("next-step");
|
|
2639
2677
|
return {
|
|
2640
2678
|
kind: "emit",
|
|
@@ -2647,39 +2685,27 @@ async function emitFinalGateRedStep(ctx) {
|
|
|
2647
2685
|
prompt: `
|
|
2648
2686
|
# Remediation paused — the repository suite is red
|
|
2649
2687
|
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
the status it had, the run stays in the same phase, and no work was discarded.
|
|
2653
|
-
|
|
2654
|
-
Failing command:
|
|
2688
|
+
${ctx.where}, the tool ran the repository's build, typecheck and tests.
|
|
2689
|
+
One command failed:
|
|
2655
2690
|
|
|
2656
2691
|
\`${failingCommand}\`
|
|
2657
2692
|
|
|
2658
|
-
The
|
|
2693
|
+
The output tail is in \`${recordPath}\`.
|
|
2659
2694
|
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
A
|
|
2663
|
-
it — it may not be this run's doing at all (a commit landed alongside the run is
|
|
2664
|
-
enough). So this is a PAUSE, not a verdict on the work.
|
|
2695
|
+
This pause changes nothing. Every item keeps its status, the run stays in its
|
|
2696
|
+
phase, and no work is lost. A red suite does not name the item that caused it.
|
|
2697
|
+
A commit made outside this run can also cause it.
|
|
2665
2698
|
|
|
2666
2699
|
${attributionBlock}
|
|
2667
2700
|
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
Fix the failing command — or confirm it was already broken independently of this
|
|
2671
|
-
run — then run:
|
|
2701
|
+
Do these steps:
|
|
2672
2702
|
|
|
2673
|
-
|
|
2703
|
+
1. Fix the failing command, or confirm that it was already broken before this run.
|
|
2704
|
+
2. Run \`${nextCommand}\`.
|
|
2674
2705
|
|
|
2675
|
-
|
|
2676
|
-
where it left off.
|
|
2706
|
+
When the suite is green, the run continues from where it stopped.
|
|
2677
2707
|
|
|
2678
|
-
|
|
2679
|
-
content of the tree, so a re-run with nothing changed serves the recorded verdict
|
|
2680
|
-
without spawning anything — and the moment you fix something the tree moves, the
|
|
2681
|
-
cache misses, and the whole floor (a full build plus the suite, minutes, holding
|
|
2682
|
-
the run's phase lock) runs against your change. Fix something first, then re-run.
|
|
2708
|
+
${bindingBlock}
|
|
2683
2709
|
`,
|
|
2684
2710
|
allowedCommands: [nextCommand],
|
|
2685
2711
|
stopCondition: "Stop. Make the repository suite green, then re-run next-step to resume the run.",
|
|
@@ -2714,6 +2740,7 @@ async function runPhaseBoundaryGate(ctx) {
|
|
|
2714
2740
|
if (phase == null)
|
|
2715
2741
|
return null;
|
|
2716
2742
|
const scope = `phase ${phase} boundary`;
|
|
2743
|
+
const where = `At the phase ${phase} boundary`;
|
|
2717
2744
|
const disabledReason = finalGateDisabledReason(options);
|
|
2718
2745
|
if (disabledReason !== null) {
|
|
2719
2746
|
await recordFinalGateOutcome({
|
|
@@ -2775,6 +2802,7 @@ async function runPhaseBoundaryGate(ctx) {
|
|
|
2775
2802
|
artifactsDir,
|
|
2776
2803
|
state,
|
|
2777
2804
|
scope,
|
|
2805
|
+
where,
|
|
2778
2806
|
gate: {
|
|
2779
2807
|
passed: false,
|
|
2780
2808
|
results: cached.results,
|
|
@@ -2828,6 +2856,7 @@ async function runPhaseBoundaryGate(ctx) {
|
|
|
2828
2856
|
artifactsDir,
|
|
2829
2857
|
state,
|
|
2830
2858
|
scope,
|
|
2859
|
+
where,
|
|
2831
2860
|
gate,
|
|
2832
2861
|
// The id taken BEFORE the floor ran, which is what writeFinalGateVerdict
|
|
2833
2862
|
// just recorded — so the prompt names the binding the cache will match on.
|
|
@@ -2885,6 +2914,7 @@ async function handleAllTerminalTransition(root, artifactsDir, state, store, opt
|
|
|
2885
2914
|
artifactsDir,
|
|
2886
2915
|
state,
|
|
2887
2916
|
scope,
|
|
2917
|
+
where: "Before the close phase",
|
|
2888
2918
|
gate,
|
|
2889
2919
|
runLogger,
|
|
2890
2920
|
});
|
|
@@ -3185,7 +3215,7 @@ async function buildConfirmIntentStep(ctx) {
|
|
|
3185
3215
|
const runId = stateRunId(state);
|
|
3186
3216
|
const nextCommand = loaderCommand("next-step");
|
|
3187
3217
|
const checkpointPath = join(artifactsDir, "intent_checkpoint.json");
|
|
3188
|
-
// Read the
|
|
3218
|
+
// Read a checkpoint the host already wrote, if one exists.
|
|
3189
3219
|
//
|
|
3190
3220
|
// This is the GATE, and it reads LENIENTLY on purpose: a checkpoint whose
|
|
3191
3221
|
// `closing_action` is outside the vocabulary must reach the refusal below,
|
|
@@ -3193,14 +3223,16 @@ async function buildConfirmIntentStep(ctx) {
|
|
|
3193
3223
|
// would replace that affordance with a zod error. Every field consumed here as
|
|
3194
3224
|
// a VALUE is still checked by name (`isClosingAction`, `customCommandOf`).
|
|
3195
3225
|
//
|
|
3196
|
-
// The REFUSED values come from `rejected`, never from `
|
|
3226
|
+
// The REFUSED values come from `rejected`, never from `existing`: the returned
|
|
3197
3227
|
// checkpoint is schema-valid, so an out-of-vocabulary key is ABSENT from it
|
|
3198
3228
|
// (see `parseIntentCheckpointLenient`). The value the refusal quotes is the one
|
|
3199
3229
|
// the host WROTE, which is exactly what `rejected` carries.
|
|
3200
|
-
const
|
|
3201
|
-
const
|
|
3202
|
-
const
|
|
3203
|
-
|
|
3230
|
+
const existingRead = await readIntentCheckpointLenient(checkpointPath);
|
|
3231
|
+
const existing = existingRead.checkpoint;
|
|
3232
|
+
const rejectedClosingAction = existingRead.rejected.find((field) => field.key === "closing_action");
|
|
3233
|
+
// The proposal comes from the intake summary — the one file the host wrote at
|
|
3234
|
+
// synthesis. There is no draft checkpoint: the facts live in one place.
|
|
3235
|
+
const summary = (await readIntakeArtifacts(artifactsDir)).summary;
|
|
3204
3236
|
// Closing action: DETECTED candidates, presented for the host to choose
|
|
3205
3237
|
// from; the tool never selects one (owner decision 92b0e2dd7cfdc06d).
|
|
3206
3238
|
const facts = await detectProjectFacts(root);
|
|
@@ -3210,46 +3242,42 @@ async function buildConfirmIntentStep(ctx) {
|
|
|
3210
3242
|
const closingSection = renderClosingActionSection(facts);
|
|
3211
3243
|
// A confirmed checkpoint whose closing_action is not in the vocabulary
|
|
3212
3244
|
// re-enters this step by name — a refusal, never a silent default.
|
|
3213
|
-
const rawChoice =
|
|
3214
|
-
? (
|
|
3245
|
+
const rawChoice = existing?.confirmed_by === "host"
|
|
3246
|
+
? (existing.closing_action ?? rejectedClosingAction?.value)
|
|
3215
3247
|
: undefined;
|
|
3216
3248
|
const refusal = rawChoice !== undefined && !isClosingAction(rawChoice)
|
|
3217
3249
|
? `> **Refused:** \`closing_action\` ${JSON.stringify(rawChoice)} is not one of ${CLOSING_ACTIONS.map((a) => `\`${a}\``).join(", ")}. Rewrite the checkpoint with a valid value, or omit the field for \`none\`.\n`
|
|
3218
|
-
: rawChoice === "custom" && customCommandOf(
|
|
3250
|
+
: rawChoice === "custom" && customCommandOf(existing) === null
|
|
3219
3251
|
? "> **Refused:** `closing_action` \"custom\" needs `closing_custom_command`, a non-empty argv array such as [\"npm\", \"run\", \"release\"]. Add it, or choose another action.\n"
|
|
3220
3252
|
: "";
|
|
3221
3253
|
let prompt;
|
|
3222
|
-
if (
|
|
3223
|
-
// Build a consolidated single-stop proposal from the
|
|
3224
|
-
const
|
|
3225
|
-
const preDraftQuestions = Array.isArray(draftRaw.pre_draft_questions)
|
|
3226
|
-
? draftRaw.pre_draft_questions
|
|
3227
|
-
: [];
|
|
3254
|
+
if (summary) {
|
|
3255
|
+
// Build a consolidated single-stop proposal from the intake summary.
|
|
3256
|
+
const questions = summary.open_questions ?? [];
|
|
3228
3257
|
// INV-remediate-state-06: only explicit blocking===true is blocking.
|
|
3229
|
-
const blockingQs =
|
|
3230
|
-
const nonBlockingQs =
|
|
3231
|
-
const intentInterpretation = typeof draftRaw.intent_interpretation === "string" ? draftRaw.intent_interpretation : undefined;
|
|
3258
|
+
const blockingQs = questions.filter((q) => q.blocking === true);
|
|
3259
|
+
const nonBlockingQs = questions.filter((q) => q.blocking !== true);
|
|
3232
3260
|
const questionLines = [
|
|
3233
3261
|
...blockingQs.map((q) => `- **[blocking] ${q.id}**: ${q.question}`),
|
|
3234
3262
|
...nonBlockingQs.map((q) => `- **[FYI] ${q.id}**: ${q.question}`),
|
|
3235
3263
|
].join("\n") || "- None";
|
|
3236
|
-
const filtersBlock =
|
|
3237
|
-
? `\`\`\`json\n${JSON.stringify(
|
|
3264
|
+
const filtersBlock = Object.keys(summary.filters).length > 0
|
|
3265
|
+
? `\`\`\`json\n${JSON.stringify(summary.filters, null, 2)}\n\`\`\``
|
|
3238
3266
|
: "(none — remediating all findings)";
|
|
3239
3267
|
prompt = `
|
|
3240
3268
|
${refusal}# Confirm Remediation Scope and Intent
|
|
3241
3269
|
|
|
3242
|
-
The
|
|
3270
|
+
The tool built the following proposal from the intake summary. Review each section
|
|
3243
3271
|
and adjust where needed, then confirm by writing the final \`intent_checkpoint.json\`.
|
|
3244
3272
|
|
|
3245
3273
|
## Proposed Scope
|
|
3246
3274
|
|
|
3247
|
-
${
|
|
3275
|
+
${summary.scope_summary}
|
|
3248
3276
|
|
|
3249
3277
|
## Proposed Intent
|
|
3250
3278
|
|
|
3251
|
-
${
|
|
3252
|
-
${
|
|
3279
|
+
${summary.intent_summary}
|
|
3280
|
+
${summary.intent_interpretation ? `\n**How free-form intent was interpreted:** ${summary.intent_interpretation}\n` : ""}
|
|
3253
3281
|
## Proposed Filters
|
|
3254
3282
|
|
|
3255
3283
|
${filtersBlock}
|
|
@@ -3271,10 +3299,10 @@ To confirm, write the final checkpoint to:
|
|
|
3271
3299
|
"schema_version": "intent-checkpoint/v1",
|
|
3272
3300
|
"confirmed_at": "<ISO-8601 timestamp>",
|
|
3273
3301
|
"confirmed_by": "host",
|
|
3274
|
-
"scope_summary":
|
|
3275
|
-
"intent_summary":
|
|
3302
|
+
"scope_summary": ${JSON.stringify(summary.scope_summary)},
|
|
3303
|
+
"intent_summary": ${JSON.stringify(summary.intent_summary)},
|
|
3276
3304
|
"free_form_intent": "<optional: additional guidance>",
|
|
3277
|
-
"filters": ${JSON.stringify(
|
|
3305
|
+
"filters": ${JSON.stringify(summary.filters, null, 2)},
|
|
3278
3306
|
"excluded_scope": [{ "path": "<path or prefix>", "reason": "<why>" }],
|
|
3279
3307
|
"must_not_touch": [],
|
|
3280
3308
|
"closing_action": "<one of the candidates above, or omit the field for none>",
|
|
@@ -3292,7 +3320,7 @@ Once written with \`"confirmed_by": "host"\`, run:
|
|
|
3292
3320
|
`;
|
|
3293
3321
|
}
|
|
3294
3322
|
else {
|
|
3295
|
-
// Fallback for when
|
|
3323
|
+
// Fallback for when no intake summary exists yet.
|
|
3296
3324
|
prompt = `
|
|
3297
3325
|
${refusal}# Confirm Remediation Scope and Intent
|
|
3298
3326
|
|
|
@@ -3579,11 +3607,11 @@ export function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
3579
3607
|
},
|
|
3580
3608
|
{
|
|
3581
3609
|
// Intent gate: fire when no confirmed checkpoint exists (no checkpoint + any
|
|
3582
|
-
// intake artifact or an active run
|
|
3610
|
+
// intake artifact or an active run). A legacy draft checkpoint was archived
|
|
3611
|
+
// at the snapshot read, so it counts as no checkpoint. Never for
|
|
3583
3612
|
// complete/closing — those already confirmed their checkpoint.
|
|
3584
3613
|
id: "confirm_intent",
|
|
3585
3614
|
derive: (state) => {
|
|
3586
|
-
const checkpointIsDraft = existingCheckpoint?.confirmed_by === "draft";
|
|
3587
3615
|
// A host-confirmed checkpoint carrying a closing_action outside the
|
|
3588
3616
|
// vocabulary is not a confirmation: the step re-emits naming the value
|
|
3589
3617
|
// (owner decision 92b0e2dd7cfdc06d — never a silent default).
|
|
@@ -3606,8 +3634,7 @@ export function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
3606
3634
|
state.status !== "pending" &&
|
|
3607
3635
|
state.status !== "complete" &&
|
|
3608
3636
|
state.status !== "closing";
|
|
3609
|
-
const fires =
|
|
3610
|
-
invalidClosingAction ||
|
|
3637
|
+
const fires = invalidClosingAction ||
|
|
3611
3638
|
(!existsSync(checkpointPath) &&
|
|
3612
3639
|
(existsSync(ip.summary) ||
|
|
3613
3640
|
existsSync(ip.extractedPlan) ||
|
|
@@ -3811,6 +3838,14 @@ export function buildMainObligations(ctx) {
|
|
|
3811
3838
|
return { kind: "emit", step: outcome.step };
|
|
3812
3839
|
return { kind: "transition", state: outcome.state };
|
|
3813
3840
|
}
|
|
3841
|
+
// A wait with no paused item has no question to ask: a round rendered
|
|
3842
|
+
// from it lists nothing, and no answer can satisfy it. Return to
|
|
3843
|
+
// `implementing`, whose own obligations route the run onward.
|
|
3844
|
+
if (!hasUnansweredClarification(s)) {
|
|
3845
|
+
s.status = "implementing";
|
|
3846
|
+
await store.saveState(s);
|
|
3847
|
+
return { kind: "transition", state: s };
|
|
3848
|
+
}
|
|
3814
3849
|
return {
|
|
3815
3850
|
kind: "emit",
|
|
3816
3851
|
step: await handleWaitingForClarification(root, artifactsDir, s),
|
|
@@ -4069,6 +4104,14 @@ async function advanceUnderPhaseLock(deps) {
|
|
|
4069
4104
|
// asking only the parsed value reads a refusal as "no answer given, so no
|
|
4070
4105
|
// question". Strict is not the alternative: it would replace the gate's named
|
|
4071
4106
|
// refusal with a thrown load error.
|
|
4107
|
+
//
|
|
4108
|
+
// A legacy DRAFT checkpoint (`confirmed_by: "draft"`, written by the retired
|
|
4109
|
+
// synthesis prompt) is not a confirmation. It is archived here, so that every
|
|
4110
|
+
// presence check on the checkpoint path below reads it as absent and the
|
|
4111
|
+
// confirm step asks again.
|
|
4112
|
+
if (isLegacyDraftCheckpoint(await readOptionalJsonFile(checkpointPath))) {
|
|
4113
|
+
await rename(checkpointPath, `${checkpointPath}.legacy-draft-${Date.now()}`);
|
|
4114
|
+
}
|
|
4072
4115
|
const checkpointRead = existsSync(checkpointPath)
|
|
4073
4116
|
? await readIntentCheckpointLenient(checkpointPath)
|
|
4074
4117
|
: undefined;
|
|
@@ -4077,7 +4120,7 @@ async function advanceUnderPhaseLock(deps) {
|
|
|
4077
4120
|
// Whether a supplied `--input` matches the input the existing run was built
|
|
4078
4121
|
// from — so re-passing the same `--input` (the loader does this each next-step)
|
|
4079
4122
|
// resumes rather than tripping the input_conflict gate.
|
|
4080
|
-
const suppliedInputUnchanged = suppliedInputMatchesRun(inputResolution, await
|
|
4123
|
+
const suppliedInputUnchanged = suppliedInputMatchesRun(inputResolution, await readSourceManifest(intakePaths(artifactsDir).sourceManifest));
|
|
4081
4124
|
// The linear pre-intake gates run as obligations through the shared advance
|
|
4082
4125
|
// loop. An emit returns to the host; a transition re-scans within this call;
|
|
4083
4126
|
// exhausting them (step === null) means the run is past intake and falls
|