audit-tools 0.52.0 → 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/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/reporting/synthesis.d.ts +16 -3
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +40 -11
- 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 +2 -2
- package/dist/remediate/phases/close.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 +28 -4
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +17 -1
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +100 -52
- 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 +315 -288
- 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/index.d.ts +14 -11
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +16 -9
- 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/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 +154 -15
- package/dist/shared/types/finding.d.ts.map +1 -1
- package/dist/shared/types/finding.js +41 -5
- package/dist/shared/types/finding.js.map +1 -1
- package/dist/shared/types/intentCheckpoint.d.ts +26 -40
- 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/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, auditReadOf, formatValidationIssues,
|
|
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";
|
|
@@ -35,13 +36,13 @@ import { renderGateAttribution, worktreeContentId, } from "./gateCommands.js";
|
|
|
35
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,
|
|
@@ -1490,7 +1477,7 @@ async function handleWaitingForReviewApproval(root, artifactsDir, request, refus
|
|
|
1490
1477
|
artifactsDir,
|
|
1491
1478
|
prompt: reviewApprovalPrompt(request, reviewResolutionPath(artifactsDir), refusal),
|
|
1492
1479
|
allowedCommands: [loaderCommand("next-step")],
|
|
1493
|
-
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.",
|
|
1494
1481
|
artifactPaths: {
|
|
1495
1482
|
review_request: reviewRequestPath(artifactsDir),
|
|
1496
1483
|
review_resolution: reviewResolutionPath(artifactsDir),
|
|
@@ -1498,29 +1485,25 @@ async function handleWaitingForReviewApproval(root, artifactsDir, request, refus
|
|
|
1498
1485
|
});
|
|
1499
1486
|
}
|
|
1500
1487
|
/**
|
|
1501
|
-
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
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.
|
|
1508
1496
|
*/
|
|
1509
|
-
async function
|
|
1510
|
-
const
|
|
1511
|
-
if (
|
|
1512
|
-
return
|
|
1513
|
-
|
|
1514
|
-
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()}`));
|
|
1515
1503
|
await writeJsonFile(requestPath, request);
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
if (screen.unknown_tiers.length > 0) {
|
|
1521
|
-
parts.push(`unknown tier(s): ${screen.unknown_tiers.map((t) => `\`${t}\``).join(", ")}`);
|
|
1522
|
-
}
|
|
1523
|
-
return handleWaitingForReviewApproval(root, artifactsDir, request, parts.join("; "));
|
|
1504
|
+
return {
|
|
1505
|
+
halt: await handleWaitingForReviewApproval(root, artifactsDir, request, parsed.kind === "refused" ? parsed.reason : undefined),
|
|
1506
|
+
};
|
|
1524
1507
|
}
|
|
1525
1508
|
/**
|
|
1526
1509
|
* Run the review-approval gate over the SURVIVOR finding set (already passed
|
|
@@ -1586,21 +1569,10 @@ sourceAuditRead, autonomous = false) {
|
|
|
1586
1569
|
// Consume the resolution into a durable, reasoned decision record.
|
|
1587
1570
|
// Regenerable: a stale-schema request is treated as absent and rebuilt below.
|
|
1588
1571
|
const request = discardOnSchemaVersionMismatch(await readOptionalJsonFile(requestPath), REVIEW_REQUEST_SCHEMA_VERSION) ?? buildReviewRequest(survivors, randomRunId("path-a-review"));
|
|
1589
|
-
const
|
|
1590
|
-
if (
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.stale-${Date.now()}`));
|
|
1594
|
-
await writeJsonFile(requestPath, request);
|
|
1595
|
-
return {
|
|
1596
|
-
kind: "halt",
|
|
1597
|
-
step: await handleWaitingForReviewApproval(root, artifactsDir, request),
|
|
1598
|
-
};
|
|
1599
|
-
}
|
|
1600
|
-
const refusalStep = await refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath);
|
|
1601
|
-
if (refusalStep)
|
|
1602
|
-
return { kind: "halt", step: refusalStep };
|
|
1603
|
-
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);
|
|
1604
1576
|
await writeReviewDecisionRecord(decisionPath, {
|
|
1605
1577
|
planId: request.plan_id,
|
|
1606
1578
|
approvedIds: decision.approved_ids,
|
|
@@ -1831,9 +1803,10 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
|
|
|
1831
1803
|
}
|
|
1832
1804
|
const paths = intakePaths(artifactsDir);
|
|
1833
1805
|
const sourcePaths = new Set();
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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);
|
|
1837
1810
|
for (const source of manifestSources) {
|
|
1838
1811
|
// Swap the raw audit-findings.json for the approved-only filtered file so a
|
|
1839
1812
|
// declined finding can never re-enter the pipeline as a source input.
|
|
@@ -1950,59 +1923,128 @@ Stop after presenting this choice. Do not advance the run until the user decides
|
|
|
1950
1923
|
},
|
|
1951
1924
|
});
|
|
1952
1925
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
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',
|
|
1984
1964
|
});
|
|
1985
1965
|
}
|
|
1986
|
-
if (
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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
|
+
});
|
|
1990
1972
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
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
|
+
};
|
|
1997
|
+
}
|
|
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
|
+
};
|
|
1993
2004
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
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);
|
|
2005
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;
|
|
2006
2048
|
}
|
|
2007
2049
|
/**
|
|
2008
2050
|
* Validate every `clarified` resolution's `scope_additions` BEFORE anything is
|
|
@@ -2059,6 +2101,8 @@ function applyClarificationScopeAdditions(root, state, res) {
|
|
|
2059
2101
|
* Never resurrects a terminal item.
|
|
2060
2102
|
*/
|
|
2061
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;
|
|
2062
2106
|
if (res.action === "reject_finding") {
|
|
2063
2107
|
item.status = "deemed_inappropriate";
|
|
2064
2108
|
item.failure_reason = res.rationale;
|
|
@@ -2087,32 +2131,22 @@ async function applyPlanClarificationResolution(root, artifactsDir, state, store
|
|
|
2087
2131
|
if (!state.plan || !state.items)
|
|
2088
2132
|
return { kind: "applied", state };
|
|
2089
2133
|
const resolutionPath = join(artifactsDir, "clarification_resolution.json");
|
|
2090
|
-
const
|
|
2091
|
-
// Uniform
|
|
2092
|
-
//
|
|
2093
|
-
//
|
|
2094
|
-
//
|
|
2095
|
-
|
|
2096
|
-
.map((r) => r.finding_id)
|
|
2097
|
-
.filter((id) => !state.items?.[id]);
|
|
2098
|
-
if (unknownIds.length > 0) {
|
|
2099
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2100
|
-
return {
|
|
2101
|
-
kind: "refused",
|
|
2102
|
-
step: await handleWaitingForClarification(root, artifactsDir, state, `finding id(s) not in the plan: ${unknownIds.map((i) => `\`${i}\``).join(", ")}`),
|
|
2103
|
-
};
|
|
2104
|
-
}
|
|
2105
|
-
// Scope deltas ride the same whole-file fail-closed contract as unknown ids:
|
|
2106
|
-
// 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
|
|
2107
2140
|
// half-applies (open-bugs.md:110).
|
|
2108
|
-
const
|
|
2109
|
-
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) {
|
|
2110
2143
|
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2111
2144
|
return {
|
|
2112
2145
|
kind: "refused",
|
|
2113
|
-
step: await handleWaitingForClarification(root, artifactsDir, state,
|
|
2146
|
+
step: await handleWaitingForClarification(root, artifactsDir, state, refusal ?? "the resolution could not be read"),
|
|
2114
2147
|
};
|
|
2115
2148
|
}
|
|
2149
|
+
const resolutions = parsed.resolutions;
|
|
2116
2150
|
const now = new Date().toISOString();
|
|
2117
2151
|
let appliedCount = 0;
|
|
2118
2152
|
for (const res of resolutions) {
|
|
@@ -2153,14 +2187,30 @@ async function applyPlanClarificationResolution(root, artifactsDir, state, store
|
|
|
2153
2187
|
: hasUnansweredClarification(state)
|
|
2154
2188
|
? "waiting_for_clarification"
|
|
2155
2189
|
: "implementing";
|
|
2156
|
-
state.clarifications = [];
|
|
2157
2190
|
await store.saveState(state);
|
|
2158
2191
|
return { kind: "applied", state };
|
|
2159
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
|
+
}
|
|
2160
2213
|
async function handleWaitingForClarification(root, artifactsDir, state, refusal) {
|
|
2161
|
-
const clarifications = state.clarifications ??
|
|
2162
|
-
(await readOptionalJsonFile(join(artifactsDir, "clarification_request.json"))) ??
|
|
2163
|
-
[];
|
|
2164
2214
|
const resolutionPath = join(artifactsDir, "clarification_resolution.json");
|
|
2165
2215
|
return writeCurrentStep({
|
|
2166
2216
|
stepKind: "collect_clarifications",
|
|
@@ -2168,11 +2218,10 @@ async function handleWaitingForClarification(root, artifactsDir, state, refusal)
|
|
|
2168
2218
|
runId: stateRunId(state),
|
|
2169
2219
|
repoRoot: root,
|
|
2170
2220
|
artifactsDir,
|
|
2171
|
-
prompt: clarificationPrompt(
|
|
2221
|
+
prompt: clarificationPrompt(pausedClarifications(state), resolutionPath, refusal),
|
|
2172
2222
|
allowedCommands: [loaderCommand("next-step")],
|
|
2173
2223
|
stopCondition: "Stop after asking the user for clarification answers, unless the answers are already available and the prompt told you to continue.",
|
|
2174
2224
|
artifactPaths: {
|
|
2175
|
-
clarification_request: join(artifactsDir, "clarification_request.json"),
|
|
2176
2225
|
clarification_resolution: resolutionPath,
|
|
2177
2226
|
},
|
|
2178
2227
|
});
|
|
@@ -2230,17 +2279,10 @@ async function runPlanningReviewGate(root, artifactsDir, state, store) {
|
|
|
2230
2279
|
// Resolution present: consume it into a durable, reasoned decision record.
|
|
2231
2280
|
// Regenerable: a stale-schema request is treated as absent and rebuilt below.
|
|
2232
2281
|
const request = discardOnSchemaVersionMismatch(await readOptionalJsonFile(requestPath), REVIEW_REQUEST_SCHEMA_VERSION) ?? buildReviewRequest(findings, reviewPlanId);
|
|
2233
|
-
const
|
|
2234
|
-
if (
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
await writeJsonFile(requestPath, request);
|
|
2238
|
-
return handleWaitingForReviewApproval(root, artifactsDir, request);
|
|
2239
|
-
}
|
|
2240
|
-
const refusalStep = await refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath);
|
|
2241
|
-
if (refusalStep)
|
|
2242
|
-
return refusalStep;
|
|
2243
|
-
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);
|
|
2244
2286
|
await writeReviewDecisionRecord(decisionPath, {
|
|
2245
2287
|
planId: request.plan_id,
|
|
2246
2288
|
approvedIds: decision.approved_ids,
|
|
@@ -2287,8 +2329,8 @@ function detectPlanAmbiguities(findings, items) {
|
|
|
2287
2329
|
out.push({
|
|
2288
2330
|
finding_id: f.id,
|
|
2289
2331
|
category: "scope_of_fix",
|
|
2290
|
-
description: `"${f.title}" is a ${lens}
|
|
2291
|
-
"
|
|
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.",
|
|
2292
2334
|
});
|
|
2293
2335
|
continue;
|
|
2294
2336
|
}
|
|
@@ -2296,7 +2338,7 @@ function detectPlanAmbiguities(findings, items) {
|
|
|
2296
2338
|
out.push({
|
|
2297
2339
|
finding_id: f.id,
|
|
2298
2340
|
category: "issue_appropriateness",
|
|
2299
|
-
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.`,
|
|
2300
2342
|
});
|
|
2301
2343
|
}
|
|
2302
2344
|
}
|
|
@@ -2308,8 +2350,8 @@ function detectPlanAmbiguities(findings, items) {
|
|
|
2308
2350
|
* ambiguity is asked as a single batched question up front rather than falling
|
|
2309
2351
|
* silently to triage mid-run. Deterministic heuristics seed CANDIDATES; the host
|
|
2310
2352
|
* reviews them with repo access, dismisses/adds, and batches one user round. Each
|
|
2311
|
-
* item is resolved as `clarified` (answered → re-opened), `
|
|
2312
|
-
* (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).
|
|
2313
2355
|
*
|
|
2314
2356
|
* Idempotent: once `ambiguity_decision.json` exists the gate is done and never
|
|
2315
2357
|
* re-halts. An empty resolution proceeds (the host found nothing to ask).
|
|
@@ -2348,16 +2390,16 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
|
2348
2390
|
}
|
|
2349
2391
|
// Resolution present: apply it to items, mark the gate done, archive inputs so
|
|
2350
2392
|
// it cannot re-halt.
|
|
2351
|
-
|
|
2352
|
-
//
|
|
2353
|
-
//
|
|
2354
|
-
//
|
|
2355
|
-
//
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
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) {
|
|
2361
2403
|
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2362
2404
|
const candidates = (await readOptionalJsonFile(requestPath)) ??
|
|
2363
2405
|
detectPlanAmbiguities(findings, state.items);
|
|
@@ -2367,30 +2409,7 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
|
2367
2409
|
runId: stateRunId(state),
|
|
2368
2410
|
repoRoot: root,
|
|
2369
2411
|
artifactsDir,
|
|
2370
|
-
prompt: ambiguityReviewPrompt(candidates, resolutionPath, findings.map((f) => f.id),
|
|
2371
|
-
allowedCommands: [loaderCommand("next-step")],
|
|
2372
|
-
stopCondition: "Stop after re-submitting a corrected ambiguity resolution, unless it is already written and the prompt told you to continue.",
|
|
2373
|
-
artifactPaths: {
|
|
2374
|
-
ambiguity_request: requestPath,
|
|
2375
|
-
ambiguity_resolution: resolutionPath,
|
|
2376
|
-
},
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
// Scope deltas: the same whole-file fail-closed contract as unknown ids
|
|
2380
|
-
// (open-bugs.md:110). At this up-front gate no workload binding exists yet,
|
|
2381
|
-
// so an applied widening simply flows into the first dispatch.
|
|
2382
|
-
const scopeRefusals = await validateClarificationScopeAdditions(root, state, resolutions);
|
|
2383
|
-
if (scopeRefusals.length > 0) {
|
|
2384
|
-
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
|
|
2385
|
-
const candidates = (await readOptionalJsonFile(requestPath)) ??
|
|
2386
|
-
detectPlanAmbiguities(findings, state.items);
|
|
2387
|
-
return writeCurrentStep({
|
|
2388
|
-
stepKind: "collect_clarifications",
|
|
2389
|
-
status: "blocked",
|
|
2390
|
-
runId: stateRunId(state),
|
|
2391
|
-
repoRoot: root,
|
|
2392
|
-
artifactsDir,
|
|
2393
|
-
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"),
|
|
2394
2413
|
allowedCommands: [loaderCommand("next-step")],
|
|
2395
2414
|
stopCondition: "Stop after re-submitting a corrected ambiguity resolution, unless it is already written and the prompt told you to continue.",
|
|
2396
2415
|
artifactPaths: {
|
|
@@ -2399,6 +2418,7 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
|
2399
2418
|
},
|
|
2400
2419
|
});
|
|
2401
2420
|
}
|
|
2421
|
+
const resolutions = parsed.resolutions;
|
|
2402
2422
|
const now = new Date().toISOString();
|
|
2403
2423
|
let changed = false;
|
|
2404
2424
|
for (const res of resolutions) {
|
|
@@ -2641,16 +2661,18 @@ async function emitFinalGateRedStep(ctx) {
|
|
|
2641
2661
|
// edit to a non-ignored file outside the run's own artifacts. This is the
|
|
2642
2662
|
// same carve-out `worktreeContentId` applies, said in the operator's terms.
|
|
2643
2663
|
const bindingBlock = ctx.tree === undefined
|
|
2644
|
-
? "
|
|
2645
|
-
"
|
|
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."
|
|
2646
2667
|
: ctx.tree === null
|
|
2647
|
-
? "
|
|
2648
|
-
"
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
"
|
|
2653
|
-
"
|
|
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.";
|
|
2654
2676
|
const nextCommand = loaderCommand("next-step");
|
|
2655
2677
|
return {
|
|
2656
2678
|
kind: "emit",
|
|
@@ -2663,39 +2685,27 @@ async function emitFinalGateRedStep(ctx) {
|
|
|
2663
2685
|
prompt: `
|
|
2664
2686
|
# Remediation paused — the repository suite is red
|
|
2665
2687
|
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
the status it had, the run stays in the same phase, and no work was discarded.
|
|
2669
|
-
|
|
2670
|
-
Failing command:
|
|
2688
|
+
${ctx.where}, the tool ran the repository's build, typecheck and tests.
|
|
2689
|
+
One command failed:
|
|
2671
2690
|
|
|
2672
2691
|
\`${failingCommand}\`
|
|
2673
2692
|
|
|
2674
|
-
The
|
|
2693
|
+
The output tail is in \`${recordPath}\`.
|
|
2675
2694
|
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
A
|
|
2679
|
-
it — it may not be this run's doing at all (a commit landed alongside the run is
|
|
2680
|
-
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.
|
|
2681
2698
|
|
|
2682
2699
|
${attributionBlock}
|
|
2683
2700
|
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
Fix the failing command — or confirm it was already broken independently of this
|
|
2687
|
-
run — then run:
|
|
2701
|
+
Do these steps:
|
|
2688
2702
|
|
|
2689
|
-
|
|
2703
|
+
1. Fix the failing command, or confirm that it was already broken before this run.
|
|
2704
|
+
2. Run \`${nextCommand}\`.
|
|
2690
2705
|
|
|
2691
|
-
|
|
2692
|
-
where it left off.
|
|
2706
|
+
When the suite is green, the run continues from where it stopped.
|
|
2693
2707
|
|
|
2694
|
-
|
|
2695
|
-
content of the tree, so a re-run with nothing changed serves the recorded verdict
|
|
2696
|
-
without spawning anything — and the moment you fix something the tree moves, the
|
|
2697
|
-
cache misses, and the whole floor (a full build plus the suite, minutes, holding
|
|
2698
|
-
the run's phase lock) runs against your change. Fix something first, then re-run.
|
|
2708
|
+
${bindingBlock}
|
|
2699
2709
|
`,
|
|
2700
2710
|
allowedCommands: [nextCommand],
|
|
2701
2711
|
stopCondition: "Stop. Make the repository suite green, then re-run next-step to resume the run.",
|
|
@@ -2730,6 +2740,7 @@ async function runPhaseBoundaryGate(ctx) {
|
|
|
2730
2740
|
if (phase == null)
|
|
2731
2741
|
return null;
|
|
2732
2742
|
const scope = `phase ${phase} boundary`;
|
|
2743
|
+
const where = `At the phase ${phase} boundary`;
|
|
2733
2744
|
const disabledReason = finalGateDisabledReason(options);
|
|
2734
2745
|
if (disabledReason !== null) {
|
|
2735
2746
|
await recordFinalGateOutcome({
|
|
@@ -2791,6 +2802,7 @@ async function runPhaseBoundaryGate(ctx) {
|
|
|
2791
2802
|
artifactsDir,
|
|
2792
2803
|
state,
|
|
2793
2804
|
scope,
|
|
2805
|
+
where,
|
|
2794
2806
|
gate: {
|
|
2795
2807
|
passed: false,
|
|
2796
2808
|
results: cached.results,
|
|
@@ -2844,6 +2856,7 @@ async function runPhaseBoundaryGate(ctx) {
|
|
|
2844
2856
|
artifactsDir,
|
|
2845
2857
|
state,
|
|
2846
2858
|
scope,
|
|
2859
|
+
where,
|
|
2847
2860
|
gate,
|
|
2848
2861
|
// The id taken BEFORE the floor ran, which is what writeFinalGateVerdict
|
|
2849
2862
|
// just recorded — so the prompt names the binding the cache will match on.
|
|
@@ -2901,6 +2914,7 @@ async function handleAllTerminalTransition(root, artifactsDir, state, store, opt
|
|
|
2901
2914
|
artifactsDir,
|
|
2902
2915
|
state,
|
|
2903
2916
|
scope,
|
|
2917
|
+
where: "Before the close phase",
|
|
2904
2918
|
gate,
|
|
2905
2919
|
runLogger,
|
|
2906
2920
|
});
|
|
@@ -3201,7 +3215,7 @@ async function buildConfirmIntentStep(ctx) {
|
|
|
3201
3215
|
const runId = stateRunId(state);
|
|
3202
3216
|
const nextCommand = loaderCommand("next-step");
|
|
3203
3217
|
const checkpointPath = join(artifactsDir, "intent_checkpoint.json");
|
|
3204
|
-
// Read the
|
|
3218
|
+
// Read a checkpoint the host already wrote, if one exists.
|
|
3205
3219
|
//
|
|
3206
3220
|
// This is the GATE, and it reads LENIENTLY on purpose: a checkpoint whose
|
|
3207
3221
|
// `closing_action` is outside the vocabulary must reach the refusal below,
|
|
@@ -3209,14 +3223,16 @@ async function buildConfirmIntentStep(ctx) {
|
|
|
3209
3223
|
// would replace that affordance with a zod error. Every field consumed here as
|
|
3210
3224
|
// a VALUE is still checked by name (`isClosingAction`, `customCommandOf`).
|
|
3211
3225
|
//
|
|
3212
|
-
// The REFUSED values come from `rejected`, never from `
|
|
3226
|
+
// The REFUSED values come from `rejected`, never from `existing`: the returned
|
|
3213
3227
|
// checkpoint is schema-valid, so an out-of-vocabulary key is ABSENT from it
|
|
3214
3228
|
// (see `parseIntentCheckpointLenient`). The value the refusal quotes is the one
|
|
3215
3229
|
// the host WROTE, which is exactly what `rejected` carries.
|
|
3216
|
-
const
|
|
3217
|
-
const
|
|
3218
|
-
const
|
|
3219
|
-
|
|
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;
|
|
3220
3236
|
// Closing action: DETECTED candidates, presented for the host to choose
|
|
3221
3237
|
// from; the tool never selects one (owner decision 92b0e2dd7cfdc06d).
|
|
3222
3238
|
const facts = await detectProjectFacts(root);
|
|
@@ -3226,46 +3242,42 @@ async function buildConfirmIntentStep(ctx) {
|
|
|
3226
3242
|
const closingSection = renderClosingActionSection(facts);
|
|
3227
3243
|
// A confirmed checkpoint whose closing_action is not in the vocabulary
|
|
3228
3244
|
// re-enters this step by name — a refusal, never a silent default.
|
|
3229
|
-
const rawChoice =
|
|
3230
|
-
? (
|
|
3245
|
+
const rawChoice = existing?.confirmed_by === "host"
|
|
3246
|
+
? (existing.closing_action ?? rejectedClosingAction?.value)
|
|
3231
3247
|
: undefined;
|
|
3232
3248
|
const refusal = rawChoice !== undefined && !isClosingAction(rawChoice)
|
|
3233
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`
|
|
3234
|
-
: rawChoice === "custom" && customCommandOf(
|
|
3250
|
+
: rawChoice === "custom" && customCommandOf(existing) === null
|
|
3235
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"
|
|
3236
3252
|
: "";
|
|
3237
3253
|
let prompt;
|
|
3238
|
-
if (
|
|
3239
|
-
// Build a consolidated single-stop proposal from the
|
|
3240
|
-
const
|
|
3241
|
-
const preDraftQuestions = Array.isArray(draftRaw.pre_draft_questions)
|
|
3242
|
-
? draftRaw.pre_draft_questions
|
|
3243
|
-
: [];
|
|
3254
|
+
if (summary) {
|
|
3255
|
+
// Build a consolidated single-stop proposal from the intake summary.
|
|
3256
|
+
const questions = summary.open_questions ?? [];
|
|
3244
3257
|
// INV-remediate-state-06: only explicit blocking===true is blocking.
|
|
3245
|
-
const blockingQs =
|
|
3246
|
-
const nonBlockingQs =
|
|
3247
|
-
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);
|
|
3248
3260
|
const questionLines = [
|
|
3249
3261
|
...blockingQs.map((q) => `- **[blocking] ${q.id}**: ${q.question}`),
|
|
3250
3262
|
...nonBlockingQs.map((q) => `- **[FYI] ${q.id}**: ${q.question}`),
|
|
3251
3263
|
].join("\n") || "- None";
|
|
3252
|
-
const filtersBlock =
|
|
3253
|
-
? `\`\`\`json\n${JSON.stringify(
|
|
3264
|
+
const filtersBlock = Object.keys(summary.filters).length > 0
|
|
3265
|
+
? `\`\`\`json\n${JSON.stringify(summary.filters, null, 2)}\n\`\`\``
|
|
3254
3266
|
: "(none — remediating all findings)";
|
|
3255
3267
|
prompt = `
|
|
3256
3268
|
${refusal}# Confirm Remediation Scope and Intent
|
|
3257
3269
|
|
|
3258
|
-
The
|
|
3270
|
+
The tool built the following proposal from the intake summary. Review each section
|
|
3259
3271
|
and adjust where needed, then confirm by writing the final \`intent_checkpoint.json\`.
|
|
3260
3272
|
|
|
3261
3273
|
## Proposed Scope
|
|
3262
3274
|
|
|
3263
|
-
${
|
|
3275
|
+
${summary.scope_summary}
|
|
3264
3276
|
|
|
3265
3277
|
## Proposed Intent
|
|
3266
3278
|
|
|
3267
|
-
${
|
|
3268
|
-
${
|
|
3279
|
+
${summary.intent_summary}
|
|
3280
|
+
${summary.intent_interpretation ? `\n**How free-form intent was interpreted:** ${summary.intent_interpretation}\n` : ""}
|
|
3269
3281
|
## Proposed Filters
|
|
3270
3282
|
|
|
3271
3283
|
${filtersBlock}
|
|
@@ -3287,10 +3299,10 @@ To confirm, write the final checkpoint to:
|
|
|
3287
3299
|
"schema_version": "intent-checkpoint/v1",
|
|
3288
3300
|
"confirmed_at": "<ISO-8601 timestamp>",
|
|
3289
3301
|
"confirmed_by": "host",
|
|
3290
|
-
"scope_summary":
|
|
3291
|
-
"intent_summary":
|
|
3302
|
+
"scope_summary": ${JSON.stringify(summary.scope_summary)},
|
|
3303
|
+
"intent_summary": ${JSON.stringify(summary.intent_summary)},
|
|
3292
3304
|
"free_form_intent": "<optional: additional guidance>",
|
|
3293
|
-
"filters": ${JSON.stringify(
|
|
3305
|
+
"filters": ${JSON.stringify(summary.filters, null, 2)},
|
|
3294
3306
|
"excluded_scope": [{ "path": "<path or prefix>", "reason": "<why>" }],
|
|
3295
3307
|
"must_not_touch": [],
|
|
3296
3308
|
"closing_action": "<one of the candidates above, or omit the field for none>",
|
|
@@ -3308,7 +3320,7 @@ Once written with \`"confirmed_by": "host"\`, run:
|
|
|
3308
3320
|
`;
|
|
3309
3321
|
}
|
|
3310
3322
|
else {
|
|
3311
|
-
// Fallback for when
|
|
3323
|
+
// Fallback for when no intake summary exists yet.
|
|
3312
3324
|
prompt = `
|
|
3313
3325
|
${refusal}# Confirm Remediation Scope and Intent
|
|
3314
3326
|
|
|
@@ -3595,11 +3607,11 @@ export function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
3595
3607
|
},
|
|
3596
3608
|
{
|
|
3597
3609
|
// Intent gate: fire when no confirmed checkpoint exists (no checkpoint + any
|
|
3598
|
-
// 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
|
|
3599
3612
|
// complete/closing — those already confirmed their checkpoint.
|
|
3600
3613
|
id: "confirm_intent",
|
|
3601
3614
|
derive: (state) => {
|
|
3602
|
-
const checkpointIsDraft = existingCheckpoint?.confirmed_by === "draft";
|
|
3603
3615
|
// A host-confirmed checkpoint carrying a closing_action outside the
|
|
3604
3616
|
// vocabulary is not a confirmation: the step re-emits naming the value
|
|
3605
3617
|
// (owner decision 92b0e2dd7cfdc06d — never a silent default).
|
|
@@ -3622,8 +3634,7 @@ export function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
3622
3634
|
state.status !== "pending" &&
|
|
3623
3635
|
state.status !== "complete" &&
|
|
3624
3636
|
state.status !== "closing";
|
|
3625
|
-
const fires =
|
|
3626
|
-
invalidClosingAction ||
|
|
3637
|
+
const fires = invalidClosingAction ||
|
|
3627
3638
|
(!existsSync(checkpointPath) &&
|
|
3628
3639
|
(existsSync(ip.summary) ||
|
|
3629
3640
|
existsSync(ip.extractedPlan) ||
|
|
@@ -3827,6 +3838,14 @@ export function buildMainObligations(ctx) {
|
|
|
3827
3838
|
return { kind: "emit", step: outcome.step };
|
|
3828
3839
|
return { kind: "transition", state: outcome.state };
|
|
3829
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
|
+
}
|
|
3830
3849
|
return {
|
|
3831
3850
|
kind: "emit",
|
|
3832
3851
|
step: await handleWaitingForClarification(root, artifactsDir, s),
|
|
@@ -4085,6 +4104,14 @@ async function advanceUnderPhaseLock(deps) {
|
|
|
4085
4104
|
// asking only the parsed value reads a refusal as "no answer given, so no
|
|
4086
4105
|
// question". Strict is not the alternative: it would replace the gate's named
|
|
4087
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
|
+
}
|
|
4088
4115
|
const checkpointRead = existsSync(checkpointPath)
|
|
4089
4116
|
? await readIntentCheckpointLenient(checkpointPath)
|
|
4090
4117
|
: undefined;
|
|
@@ -4093,7 +4120,7 @@ async function advanceUnderPhaseLock(deps) {
|
|
|
4093
4120
|
// Whether a supplied `--input` matches the input the existing run was built
|
|
4094
4121
|
// from — so re-passing the same `--input` (the loader does this each next-step)
|
|
4095
4122
|
// resumes rather than tripping the input_conflict gate.
|
|
4096
|
-
const suppliedInputUnchanged = suppliedInputMatchesRun(inputResolution, await
|
|
4123
|
+
const suppliedInputUnchanged = suppliedInputMatchesRun(inputResolution, await readSourceManifest(intakePaths(artifactsDir).sourceManifest));
|
|
4097
4124
|
// The linear pre-intake gates run as obligations through the shared advance
|
|
4098
4125
|
// loop. An emit returns to the host; a transition re-scans within this call;
|
|
4099
4126
|
// exhausting them (step === null) means the run is past intake and falls
|