audit-tools 0.28.2 → 0.28.4
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 +12 -1
- package/dist/audit/cli/auditStep.d.ts.map +1 -1
- package/dist/audit/cli/auditStep.js +1 -0
- package/dist/audit/cli/auditStep.js.map +1 -1
- package/dist/audit/cli/confirmIntentStep.d.ts.map +1 -1
- package/dist/audit/cli/confirmIntentStep.js +46 -35
- package/dist/audit/cli/confirmIntentStep.js.map +1 -1
- package/dist/audit/cli/dispatch/packetFilter.d.ts +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +5 -1
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/steps.d.ts +2 -2
- package/dist/audit/contracts/workerSchemas.d.ts +14 -14
- package/dist/audit/contracts/wrapperResponse.d.ts +4 -4
- package/dist/audit/extractors/analyzers/types.d.ts +3 -1
- package/dist/audit/extractors/analyzers/types.d.ts.map +1 -1
- package/dist/audit/extractors/analyzers/types.js +10 -1
- package/dist/audit/extractors/analyzers/types.js.map +1 -1
- package/dist/audit/orchestrator/advanceTypes.d.ts +8 -0
- package/dist/audit/orchestrator/advanceTypes.d.ts.map +1 -1
- package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
- package/dist/audit/orchestrator/executorRunners.js +1 -1
- package/dist/audit/orchestrator/executorRunners.js.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointExecutor.d.ts +19 -8
- package/dist/audit/orchestrator/intentCheckpointExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointExecutor.js +64 -40
- package/dist/audit/orchestrator/intentCheckpointExecutor.js.map +1 -1
- package/dist/audit/orchestrator/requeueCommand.d.ts +1 -1
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +15 -27
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/types/analyzerCapability.d.ts +69 -12
- package/dist/audit/types/analyzerCapability.d.ts.map +1 -1
- package/dist/audit/types/analyzerCapability.js +25 -1
- package/dist/audit/types/analyzerCapability.js.map +1 -1
- package/dist/audit/types/auditScope.d.ts +51 -12
- package/dist/audit/types/auditScope.d.ts.map +1 -1
- package/dist/audit/types/auditScope.js +49 -1
- package/dist/audit/types/auditScope.js.map +1 -1
- package/dist/audit/types/auditState.d.ts +60 -11
- package/dist/audit/types/auditState.d.ts.map +1 -1
- package/dist/audit/types/auditState.js +24 -1
- package/dist/audit/types/auditState.js.map +1 -1
- package/dist/audit/types/flowCoverage.d.ts +65 -8
- package/dist/audit/types/flowCoverage.d.ts.map +1 -1
- package/dist/audit/types/flowCoverage.js +21 -0
- package/dist/audit/types/flowCoverage.js.map +1 -1
- package/dist/audit/types/reviewPlanning.d.ts +2 -2
- package/dist/audit/types.d.ts +12 -12
- package/dist/remediate/contractPipeline/semanticProjection.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/semanticProjection.js +36 -14
- package/dist/remediate/contractPipeline/semanticProjection.js.map +1 -1
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +27 -8
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/state/itemStatus.d.ts +5 -3
- package/dist/remediate/state/itemStatus.d.ts.map +1 -1
- package/dist/remediate/state/itemStatus.js +7 -3
- package/dist/remediate/state/itemStatus.js.map +1 -1
- package/dist/remediate/state/types.d.ts +7 -2
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js +7 -0
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/dispatch.d.ts +109 -0
- package/dist/remediate/steps/dispatch.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch.js +348 -55
- package/dist/remediate/steps/dispatch.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +215 -21
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/prompts.d.ts +12 -0
- package/dist/remediate/steps/prompts.d.ts.map +1 -1
- package/dist/remediate/steps/prompts.js +83 -12
- package/dist/remediate/steps/prompts.js.map +1 -1
- package/dist/remediate/steps/rollingSession.d.ts.map +1 -1
- package/dist/remediate/steps/rollingSession.js +52 -5
- package/dist/remediate/steps/rollingSession.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +10 -1
- package/dist/remediate/steps/types.d.ts.map +1 -1
- 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 -2
- package/dist/remediate/validation/artifacts.js.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.js +25 -7
- package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
- package/dist/shared/engine/obligationEngine.d.ts +16 -5
- package/dist/shared/engine/obligationEngine.d.ts.map +1 -1
- package/dist/shared/engine/obligationEngine.js +21 -0
- package/dist/shared/engine/obligationEngine.js.map +1 -1
- package/dist/shared/index.d.ts +6 -2
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +5 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/intent/freeFormIntentInterpreter.d.ts.map +1 -1
- package/dist/shared/intent/freeFormIntentInterpreter.js +5 -1
- package/dist/shared/intent/freeFormIntentInterpreter.js.map +1 -1
- package/dist/shared/reporting/findingDisplay.d.ts +71 -0
- package/dist/shared/reporting/findingDisplay.d.ts.map +1 -0
- package/dist/shared/reporting/findingDisplay.js +133 -0
- package/dist/shared/reporting/findingDisplay.js.map +1 -0
- package/dist/shared/types/intentCheckpoint.d.ts +151 -34
- package/dist/shared/types/intentCheckpoint.d.ts.map +1 -1
- package/dist/shared/types/intentCheckpoint.js +113 -1
- package/dist/shared/types/intentCheckpoint.js.map +1 -1
- package/dist/shared/types/remediationOutcome.d.ts +203 -24
- package/dist/shared/types/remediationOutcome.d.ts.map +1 -1
- package/dist/shared/types/remediationOutcome.js +65 -1
- package/dist/shared/types/remediationOutcome.js.map +1 -1
- package/dist/shared/types/sessionConfig.d.ts +3 -1
- package/dist/shared/types/sessionConfig.d.ts.map +1 -1
- package/dist/shared/types/sessionConfig.js +2 -0
- package/dist/shared/types/sessionConfig.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { groundExtractedFindings } from "../phases/grounding.js";
|
|
|
8
8
|
import { runTriagePhase } from "../phases/triage.js";
|
|
9
9
|
import { runClosePhase } from "../phases/close.js";
|
|
10
10
|
import { validateRemediationPlan } from "../validation/remediationState.js";
|
|
11
|
-
import { mergeImplementResults, prepareImplementDispatch, readExtractedPlanIfPresent, buildConfirmedPools, createWorktree, removeWorktree, resetNodeWorktreeAndBranch, acceptNodeWorktree, recordNodeAcceptOutcome, ensureWorktreeNodeModules, worktreePath, worktreeBranchForBlock, } from "./dispatch.js";
|
|
11
|
+
import { mergeImplementResults, prepareImplementDispatch, readExtractedPlanIfPresent, buildConfirmedPools, createWorktree, removeWorktree, resetNodeWorktreeAndBranch, acceptNodeWorktree, recordNodeAcceptOutcome, ensureWorktreeNodeModules, seedUntrackedDeclaredPaths, worktreePath, worktreeBranchForBlock, blockScopesFromPlan, } from "./dispatch.js";
|
|
12
12
|
import { makeProviderNodeDispatcher } from "./providerNodeDispatch.js";
|
|
13
13
|
import { prepareHostRollingDispatch } from "./rollingSession.js";
|
|
14
14
|
import { writeCurrentStep } from "./stepWriter.js";
|
|
@@ -24,7 +24,7 @@ import { contractArtifactExists, contractPipelineDir, } from "../contractPipelin
|
|
|
24
24
|
import { buildReviewRequest, applyReviewResolution, } from "../review/reviewGate.js";
|
|
25
25
|
import { runFindingFilterPass } from "../findingFilter.js";
|
|
26
26
|
import { intakePaths, isIntakeReady, readIntakeArtifacts, resolveManifestSources, } from "../intake.js";
|
|
27
|
-
import { clarificationPrompt, collectIntakeClarificationsPrompt, collectStartingPointPrompt, loaderCommand, reviewApprovalPrompt, synthesizeIntakePrompt, triagePrompt, } from "./prompts.js";
|
|
27
|
+
import { ambiguityReviewPrompt, clarificationPrompt, collectIntakeClarificationsPrompt, collectStartingPointPrompt, loaderCommand, reviewApprovalPrompt, synthesizeIntakePrompt, triagePrompt, } from "./prompts.js";
|
|
28
28
|
export function resolveHostDispatchCapability(options) {
|
|
29
29
|
if (options.hostCanDispatchSubagents !== undefined) {
|
|
30
30
|
return options.hostCanDispatchSubagents;
|
|
@@ -141,6 +141,30 @@ function resolveInputPaths(root, input) {
|
|
|
141
141
|
checked,
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* True when a supplied `--input` is the SAME input the existing run was already
|
|
146
|
+
* built from (its recorded intake source manifest, `created_from: "input"`, with
|
|
147
|
+
* a path set equal to the supplied paths). The `/remediate-code` loader re-passes
|
|
148
|
+
* the same `--input` on every `next-step`; treating that unchanged input as a
|
|
149
|
+
* RESUME — not an `input_conflict` — spares the host a needless resume/restart ack
|
|
150
|
+
* dance, while a genuinely DIFFERENT input still trips the conflict gate. Enforced
|
|
151
|
+
* in the tool, never by asking the loader to remember to drop the flag (a needed
|
|
152
|
+
* manual flag is a bug signal).
|
|
153
|
+
*/
|
|
154
|
+
function suppliedInputMatchesRun(inputResolution, manifest) {
|
|
155
|
+
if (!inputResolution.supplied)
|
|
156
|
+
return false;
|
|
157
|
+
if (!manifest || manifest.created_from !== "input")
|
|
158
|
+
return false;
|
|
159
|
+
const supplied = new Set(inputResolution.checked.map((p) => resolve(p)));
|
|
160
|
+
const recorded = new Set(manifest.sources.map((s) => resolve(s.path)));
|
|
161
|
+
if (supplied.size === 0 || supplied.size !== recorded.size)
|
|
162
|
+
return false;
|
|
163
|
+
for (const p of supplied)
|
|
164
|
+
if (!recorded.has(p))
|
|
165
|
+
return false;
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
144
168
|
export { NO_CHANGE_RE, dependenciesSatisfied, dependencyVerifiedComplete, specIndicatesNoChange, classifyFindingRisk, } from "./stepUtils.js";
|
|
145
169
|
export { isTerminalStatus, isVerifiedCompleteStatus };
|
|
146
170
|
function documentableFindings(state) {
|
|
@@ -377,6 +401,10 @@ export async function driveRollingImplementDispatch(options) {
|
|
|
377
401
|
const resultPathByBlock = new Map(plan.items
|
|
378
402
|
.filter((i) => typeof i.block_id === "string")
|
|
379
403
|
.map((i) => [i.block_id, i.result_path]));
|
|
404
|
+
// Every block's declared write scope, for the accept-time write-scope gate's
|
|
405
|
+
// amendment ownership adjudication (so an amended path owned by a sibling block
|
|
406
|
+
// is a seam conflict, not a silent grant). Built once from the in-memory plan.
|
|
407
|
+
const allBlockScopes = blockScopesFromPlan(plan);
|
|
380
408
|
// Map each planned block id to its worktree-rooted prompt path so the
|
|
381
409
|
// provider-backed dispatcher launches the worker with the right prompt.
|
|
382
410
|
const promptPathByBlock = new Map(plan.items
|
|
@@ -431,6 +459,11 @@ export async function driveRollingImplementDispatch(options) {
|
|
|
431
459
|
// A fresh worktree has no node_modules (gitignored); link the main checkout's
|
|
432
460
|
// so this node's verify commands can run.
|
|
433
461
|
ensureWorktreeNodeModules(root, wt);
|
|
462
|
+
// Bring in declared targets that are untracked/ignored in the main tree so a
|
|
463
|
+
// committed-files-only worktree can still see this node's own targets.
|
|
464
|
+
// `touched_files` is the block's authoritative declared write set (the same
|
|
465
|
+
// source the dispatch plan's write scope is derived from).
|
|
466
|
+
seedUntrackedDeclaredPaths(root, wt, block.touched_files ?? []);
|
|
434
467
|
const result = await dispatchNode({
|
|
435
468
|
block,
|
|
436
469
|
slot,
|
|
@@ -445,6 +478,9 @@ export async function driveRollingImplementDispatch(options) {
|
|
|
445
478
|
const finding = state.plan?.findings.find((f) => f.id === id);
|
|
446
479
|
return finding?.targeted_commands ?? [];
|
|
447
480
|
}));
|
|
481
|
+
// The worker's self-reported amendments, adjudicated (never trusted as the
|
|
482
|
+
// gate input) by the accept-time write-scope gate before the cherry-pick.
|
|
483
|
+
const workerResult = await readOptionalJsonFile(resultPath);
|
|
448
484
|
const accept = acceptNodeWorktree({
|
|
449
485
|
root,
|
|
450
486
|
runId,
|
|
@@ -453,6 +489,7 @@ export async function driveRollingImplementDispatch(options) {
|
|
|
453
489
|
branch,
|
|
454
490
|
workerOutcome: result.outcome,
|
|
455
491
|
targetedCommands: targeted,
|
|
492
|
+
scope: { allBlockScopes, amendedFiles: workerResult?.amended_files ?? [] },
|
|
456
493
|
});
|
|
457
494
|
// Persist the tool-owned verify/merge outcome so finalization blocks a node
|
|
458
495
|
// that self-reported resolved but never actually landed (OBL-DS-06). Parity
|
|
@@ -949,7 +986,7 @@ async function buildImplementDispatchStep(ctx) {
|
|
|
949
986
|
}
|
|
950
987
|
const rollMerge = loaderCommand(`merge-implement-results --run-id ${runId}`);
|
|
951
988
|
const rollNext = loaderCommand("next-step");
|
|
952
|
-
const acceptCmd = loaderCommand(
|
|
989
|
+
const acceptCmd = loaderCommand(`accept-node --id <BLOCK_ID> --run-id ${runId}`);
|
|
953
990
|
const nodeLines = rolling.initial
|
|
954
991
|
.map((n) => `- \`${n.block_id}\` — prompt: \`${n.prompt_path}\` — worktree (subagent cwd): \`${n.worktree_root}\``)
|
|
955
992
|
.join("\n");
|
|
@@ -1221,6 +1258,17 @@ function reviewResolutionPath(artifactsDir) {
|
|
|
1221
1258
|
function reviewDecisionPath(artifactsDir) {
|
|
1222
1259
|
return join(artifactsDir, "review_decision.json");
|
|
1223
1260
|
}
|
|
1261
|
+
// Up-front ambiguity gate (note 3, part A) — its own request/resolution/decision
|
|
1262
|
+
// files, mirroring the review gate so it fires (and halts) at most once per run.
|
|
1263
|
+
function ambiguityRequestPath(artifactsDir) {
|
|
1264
|
+
return join(artifactsDir, "ambiguity_request.json");
|
|
1265
|
+
}
|
|
1266
|
+
function ambiguityResolutionPath(artifactsDir) {
|
|
1267
|
+
return join(artifactsDir, "ambiguity_resolution.json");
|
|
1268
|
+
}
|
|
1269
|
+
function ambiguityDecisionPath(artifactsDir) {
|
|
1270
|
+
return join(artifactsDir, "ambiguity_decision.json");
|
|
1271
|
+
}
|
|
1224
1272
|
/** Pull the Finding[] out of a parsed audit-findings.json payload. */
|
|
1225
1273
|
function extractAuditFindings(parsed) {
|
|
1226
1274
|
if (isRecord(parsed) && Array.isArray(parsed.findings)) {
|
|
@@ -1534,11 +1582,19 @@ Stop after presenting this choice. Do not advance the run until the user decides
|
|
|
1534
1582
|
},
|
|
1535
1583
|
});
|
|
1536
1584
|
}
|
|
1585
|
+
const PLAN_CLARIFICATION_ACTIONS = [
|
|
1586
|
+
"clarified",
|
|
1587
|
+
"deemed_inappropriate",
|
|
1588
|
+
"defer",
|
|
1589
|
+
];
|
|
1590
|
+
function isPlanClarificationAction(value) {
|
|
1591
|
+
return (typeof value === "string" &&
|
|
1592
|
+
PLAN_CLARIFICATION_ACTIONS.includes(value));
|
|
1593
|
+
}
|
|
1537
1594
|
function normalizePlanClarificationResolutions(value) {
|
|
1538
1595
|
if (Array.isArray(value)) {
|
|
1539
1596
|
return value.filter(isRecord).flatMap((entry) => {
|
|
1540
|
-
if (typeof entry.finding_id === "string" &&
|
|
1541
|
-
(entry.action === "clarified" || entry.action === "deemed_inappropriate")) {
|
|
1597
|
+
if (typeof entry.finding_id === "string" && isPlanClarificationAction(entry.action)) {
|
|
1542
1598
|
return [
|
|
1543
1599
|
{
|
|
1544
1600
|
finding_id: entry.finding_id,
|
|
@@ -1561,7 +1617,7 @@ function normalizePlanClarificationResolutions(value) {
|
|
|
1561
1617
|
return Object.entries(value).flatMap(([findingId, entry]) => {
|
|
1562
1618
|
if (!isRecord(entry))
|
|
1563
1619
|
return [];
|
|
1564
|
-
if (entry.action
|
|
1620
|
+
if (!isPlanClarificationAction(entry.action))
|
|
1565
1621
|
return [];
|
|
1566
1622
|
return [{
|
|
1567
1623
|
finding_id: typeof entry.finding_id === "string" ? entry.finding_id : findingId,
|
|
@@ -1570,6 +1626,33 @@ function normalizePlanClarificationResolutions(value) {
|
|
|
1570
1626
|
}];
|
|
1571
1627
|
});
|
|
1572
1628
|
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Apply one clarification resolution to its item. Single-sourced so the up-front
|
|
1631
|
+
* ambiguity gate (part A) and the mid-run clarification round (part B) settle an
|
|
1632
|
+
* item identically: `clarified` re-opens it (pending) with the answer as context,
|
|
1633
|
+
* `deemed_inappropriate` closes it as not-a-real-issue, and `defer` closes it as
|
|
1634
|
+
* an explicit user deferral for this run. Never resurrects a terminal item.
|
|
1635
|
+
*/
|
|
1636
|
+
function applyClarificationActionToItem(item, res, now) {
|
|
1637
|
+
if (res.action === "deemed_inappropriate") {
|
|
1638
|
+
item.status = "deemed_inappropriate";
|
|
1639
|
+
item.failure_reason = res.rationale;
|
|
1640
|
+
item.started_at ??= now;
|
|
1641
|
+
item.completed_at = now;
|
|
1642
|
+
}
|
|
1643
|
+
else if (res.action === "defer") {
|
|
1644
|
+
item.status = "ignored";
|
|
1645
|
+
item.failure_reason = res.rationale
|
|
1646
|
+
? `User-deferred for this run: ${res.rationale}`
|
|
1647
|
+
: "User-deferred for this run.";
|
|
1648
|
+
item.started_at ??= now;
|
|
1649
|
+
item.completed_at = now;
|
|
1650
|
+
}
|
|
1651
|
+
else {
|
|
1652
|
+
item.status = "pending";
|
|
1653
|
+
item.clarification_context = res.rationale;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1573
1656
|
/**
|
|
1574
1657
|
* Consume clarification_resolution.json for plan-phase clarifications.
|
|
1575
1658
|
* Mirrors the triage resolution consume: deemed_inappropriate → terminal,
|
|
@@ -1583,18 +1666,9 @@ async function applyPlanClarificationResolution(artifactsDir, state, store) {
|
|
|
1583
1666
|
const now = new Date().toISOString();
|
|
1584
1667
|
for (const res of resolutions) {
|
|
1585
1668
|
const item = state.items[res.finding_id];
|
|
1586
|
-
if (!item)
|
|
1669
|
+
if (!item || isTerminalStatus(item.status))
|
|
1587
1670
|
continue;
|
|
1588
|
-
|
|
1589
|
-
item.status = "deemed_inappropriate";
|
|
1590
|
-
item.failure_reason = res.rationale;
|
|
1591
|
-
item.started_at ??= now;
|
|
1592
|
-
item.completed_at = now;
|
|
1593
|
-
}
|
|
1594
|
-
else {
|
|
1595
|
-
item.status = "pending";
|
|
1596
|
-
item.clarification_context = res.rationale;
|
|
1597
|
-
}
|
|
1671
|
+
applyClarificationActionToItem(item, res, now);
|
|
1598
1672
|
}
|
|
1599
1673
|
if (existsSync(resolutionPath)) {
|
|
1600
1674
|
await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.consumed-${Date.now()}`));
|
|
@@ -1710,6 +1784,110 @@ async function runPlanningReviewGate(root, artifactsDir, state, store) {
|
|
|
1710
1784
|
await store.saveState(state);
|
|
1711
1785
|
return null;
|
|
1712
1786
|
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Deterministic first pass (note 3, part A): scan the plan's non-terminal
|
|
1789
|
+
* findings for scoping/judgment ambiguity, classified into the canonical
|
|
1790
|
+
* clarification categories. These are CANDIDATES — the host reviews them against
|
|
1791
|
+
* the repo, dismisses false positives, and adds any it finds, before batching one
|
|
1792
|
+
* user round. Conservative by design: a candidate the host dismisses costs one
|
|
1793
|
+
* read; a real scoping question that falls silently to mid-run triage is the bug
|
|
1794
|
+
* this gate exists to prevent.
|
|
1795
|
+
*/
|
|
1796
|
+
function detectPlanAmbiguities(findings, items) {
|
|
1797
|
+
const out = [];
|
|
1798
|
+
for (const f of findings) {
|
|
1799
|
+
const item = items?.[f.id];
|
|
1800
|
+
if (item && isTerminalStatus(item.status))
|
|
1801
|
+
continue;
|
|
1802
|
+
const lens = (f.lens ?? "").toLowerCase();
|
|
1803
|
+
const fileCount = f.affected_files?.length ?? 0;
|
|
1804
|
+
const broadScope = (lens === "architecture" || lens === "maintainability") &&
|
|
1805
|
+
(fileCount === 0 || fileCount >= 5);
|
|
1806
|
+
if (broadScope) {
|
|
1807
|
+
out.push({
|
|
1808
|
+
finding_id: f.id,
|
|
1809
|
+
category: "scope_of_fix",
|
|
1810
|
+
description: `"${f.title}" is a ${lens} finding with ${fileCount === 0 ? "no cited files" : `${fileCount} affected files`}; ` +
|
|
1811
|
+
"confirm how far the fix should reach (minimal local change vs. broader restructuring).",
|
|
1812
|
+
});
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
if (f.confidence === "low") {
|
|
1816
|
+
out.push({
|
|
1817
|
+
finding_id: f.id,
|
|
1818
|
+
category: "issue_appropriateness",
|
|
1819
|
+
description: `"${f.title}" is a low-confidence finding; confirm it is a real issue worth fixing in this run.`,
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return out;
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Up-front ambiguity gate (note 3, part A). Mirrors {@link runPlanningReviewGate}:
|
|
1827
|
+
* it fires once at planning, BEFORE any implement dispatch, so scoping/judgment
|
|
1828
|
+
* ambiguity is asked as a single batched question up front rather than falling
|
|
1829
|
+
* silently to triage mid-run. Deterministic heuristics seed CANDIDATES; the host
|
|
1830
|
+
* reviews them with repo access, dismisses/adds, and batches one user round. Each
|
|
1831
|
+
* item is resolved as `clarified` (answered → re-opened), `deemed_inappropriate`
|
|
1832
|
+
* (not a real issue), or `defer` (the user's explicit choice to skip this run).
|
|
1833
|
+
*
|
|
1834
|
+
* Idempotent: once `ambiguity_decision.json` exists the gate is done and never
|
|
1835
|
+
* re-halts. An empty resolution proceeds (the host found nothing to ask).
|
|
1836
|
+
*/
|
|
1837
|
+
async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
|
|
1838
|
+
const findings = state.plan?.findings ?? [];
|
|
1839
|
+
if (findings.length === 0)
|
|
1840
|
+
return null;
|
|
1841
|
+
const requestPath = ambiguityRequestPath(artifactsDir);
|
|
1842
|
+
const resolutionPath = ambiguityResolutionPath(artifactsDir);
|
|
1843
|
+
const decisionPath = ambiguityDecisionPath(artifactsDir);
|
|
1844
|
+
if (!existsSync(resolutionPath)) {
|
|
1845
|
+
// Deterministic detection is the gate trigger: with zero candidates there is
|
|
1846
|
+
// nothing for the host to review, so the plan proceeds without a round. Any
|
|
1847
|
+
// ambiguity the heuristics miss is still caught by the mid-run escape hatch
|
|
1848
|
+
// (part B). When candidates exist, halt for the host's review + the user's
|
|
1849
|
+
// batched answers.
|
|
1850
|
+
const candidates = detectPlanAmbiguities(findings, state.items);
|
|
1851
|
+
if (candidates.length === 0)
|
|
1852
|
+
return null;
|
|
1853
|
+
await writeJsonFile(requestPath, candidates);
|
|
1854
|
+
return writeCurrentStep({
|
|
1855
|
+
stepKind: "collect_clarifications",
|
|
1856
|
+
status: "blocked",
|
|
1857
|
+
runId: stateRunId(state),
|
|
1858
|
+
repoRoot: root,
|
|
1859
|
+
artifactsDir,
|
|
1860
|
+
prompt: ambiguityReviewPrompt(candidates, resolutionPath),
|
|
1861
|
+
allowedCommands: [loaderCommand("next-step")],
|
|
1862
|
+
stopCondition: "Stop after reviewing the candidate ambiguities (and asking the user any genuine ones), unless the resolution is already written and the prompt told you to continue.",
|
|
1863
|
+
artifactPaths: {
|
|
1864
|
+
ambiguity_request: requestPath,
|
|
1865
|
+
ambiguity_resolution: resolutionPath,
|
|
1866
|
+
},
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
// Resolution present: apply it to items, mark the gate done, archive inputs so
|
|
1870
|
+
// it cannot re-halt.
|
|
1871
|
+
const resolutions = normalizePlanClarificationResolutions(await readOptionalJsonFile(resolutionPath));
|
|
1872
|
+
const now = new Date().toISOString();
|
|
1873
|
+
let changed = false;
|
|
1874
|
+
for (const res of resolutions) {
|
|
1875
|
+
const item = state.items?.[res.finding_id];
|
|
1876
|
+
if (!item || isTerminalStatus(item.status))
|
|
1877
|
+
continue;
|
|
1878
|
+
applyClarificationActionToItem(item, res, now);
|
|
1879
|
+
changed = true;
|
|
1880
|
+
}
|
|
1881
|
+
await writeJsonFile(decisionPath, { resolved_at: now, resolution_count: resolutions.length });
|
|
1882
|
+
for (const p of [resolutionPath, requestPath]) {
|
|
1883
|
+
if (existsSync(p)) {
|
|
1884
|
+
await withFsRetry(() => rename(p, `${p}.consumed-${Date.now()}`));
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (changed)
|
|
1888
|
+
await store.saveState(state);
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1713
1891
|
async function handlePlanning(root, artifactsDir, state, store) {
|
|
1714
1892
|
// Review-necessity gate (Path B). Path A records its review decision at intake,
|
|
1715
1893
|
// over the ORIGINAL findings, before the contract pipeline collapses them into
|
|
@@ -1723,6 +1901,14 @@ async function handlePlanning(root, artifactsDir, state, store) {
|
|
|
1723
1901
|
if (halt)
|
|
1724
1902
|
return { kind: "emit", step: halt };
|
|
1725
1903
|
}
|
|
1904
|
+
// Up-front ambiguity gate (note 3, part A): resolve every scoping/judgment
|
|
1905
|
+
// ambiguity in ONE batched round here, before any implement dispatch, so a
|
|
1906
|
+
// question never falls silently to mid-run triage. Fires at most once per run.
|
|
1907
|
+
if (state.plan && !existsSync(ambiguityDecisionPath(artifactsDir))) {
|
|
1908
|
+
const halt = await runPlanAmbiguityGate(root, artifactsDir, state, store);
|
|
1909
|
+
if (halt)
|
|
1910
|
+
return { kind: "emit", step: halt };
|
|
1911
|
+
}
|
|
1726
1912
|
// Document phase dissolved: planning transitions directly to implementing.
|
|
1727
1913
|
// The rolling implement dispatch reads item_spec from the plan DAG node when
|
|
1728
1914
|
// present, or uses finding context directly when absent.
|
|
@@ -2264,7 +2450,7 @@ const PRE_INTAKE_PRIORITY = [
|
|
|
2264
2450
|
*/
|
|
2265
2451
|
function buildPreIntakeObligations(ctx, snapshot) {
|
|
2266
2452
|
const { artifactsDir, inputResolution } = ctx;
|
|
2267
|
-
const { existingCheckpoint, resumeAck, entryState } = snapshot;
|
|
2453
|
+
const { existingCheckpoint, resumeAck, entryState, suppliedInputUnchanged } = snapshot;
|
|
2268
2454
|
const ip = intakePaths(artifactsDir);
|
|
2269
2455
|
const checkpointPath = join(artifactsDir, "intent_checkpoint.json");
|
|
2270
2456
|
const ackPath = join(artifactsDir, "confirm_resume_ack.json");
|
|
@@ -2276,11 +2462,14 @@ function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
2276
2462
|
const warned = { value: false };
|
|
2277
2463
|
return [
|
|
2278
2464
|
{
|
|
2279
|
-
// A new --input against a run already past intake must not
|
|
2280
|
-
// the old plan; require an explicit resume-vs-restart
|
|
2281
|
-
//
|
|
2465
|
+
// A new, DIFFERENT --input against a run already past intake must not
|
|
2466
|
+
// silently resume the old plan; require an explicit resume-vs-restart
|
|
2467
|
+
// choice. The SAME --input re-passed (the loader does this every next-step)
|
|
2468
|
+
// is an unchanged input → a resume, not a conflict. Derives from the frozen
|
|
2469
|
+
// entry state — never an intake-created one.
|
|
2282
2470
|
id: "input_conflict",
|
|
2283
2471
|
derive: () => inputResolution.supplied &&
|
|
2472
|
+
!suppliedInputUnchanged &&
|
|
2284
2473
|
entryState != null &&
|
|
2285
2474
|
entryState.status !== "pending"
|
|
2286
2475
|
? "missing"
|
|
@@ -2680,6 +2869,10 @@ async function decideNextStepLoop(options, runLogger) {
|
|
|
2680
2869
|
? await readOptionalJsonFile(checkpointPath)
|
|
2681
2870
|
: undefined;
|
|
2682
2871
|
const resumeAck = await readOptionalJsonFile(join(artifactsDir, "confirm_resume_ack.json"));
|
|
2872
|
+
// Whether a supplied `--input` matches the input the existing run was built
|
|
2873
|
+
// from — so re-passing the same `--input` (the loader does this each next-step)
|
|
2874
|
+
// resumes rather than tripping the input_conflict gate.
|
|
2875
|
+
const suppliedInputUnchanged = suppliedInputMatchesRun(inputResolution, await readOptionalJsonFile(intakePaths(artifactsDir).sourceManifest));
|
|
2683
2876
|
// The linear pre-intake gates run as obligations through the shared advance
|
|
2684
2877
|
// loop. An emit returns to the host; a transition re-scans within this call;
|
|
2685
2878
|
// exhausting them (step === null) means the run is past intake and falls
|
|
@@ -2699,6 +2892,7 @@ async function decideNextStepLoop(options, runLogger) {
|
|
|
2699
2892
|
existingCheckpoint,
|
|
2700
2893
|
resumeAck,
|
|
2701
2894
|
entryState: state,
|
|
2895
|
+
suppliedInputUnchanged,
|
|
2702
2896
|
}),
|
|
2703
2897
|
}, state, ctx);
|
|
2704
2898
|
if (preIntake.step)
|