audit-tools 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -16
- package/dispatch/merge-results.mjs +3 -3
- package/dispatch/validate-result.mjs +3 -3
- package/dist/audit/cli/cleanupCommand.d.ts.map +1 -1
- package/dist/audit/cli/cleanupCommand.js +7 -1
- package/dist/audit/cli/cleanupCommand.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts +12 -0
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +15 -6
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +28 -2
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/sampleRunCommand.d.ts +215 -0
- package/dist/audit/cli/sampleRunCommand.d.ts.map +1 -1
- package/dist/audit/cli/sampleRunCommand.js +48 -2
- package/dist/audit/cli/sampleRunCommand.js.map +1 -1
- package/dist/audit/orchestrator/acquisitionExecutor.d.ts +2 -2
- package/dist/audit/orchestrator/acquisitionExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/acquisitionExecutor.js.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.js +7 -0
- package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
- package/dist/audit/orchestrator/hostInputPause.d.ts +8 -2
- package/dist/audit/orchestrator/hostInputPause.d.ts.map +1 -1
- package/dist/audit/orchestrator/hostInputPause.js.map +1 -1
- package/dist/audit/orchestrator/intentInterpreter.d.ts +15 -18
- package/dist/audit/orchestrator/intentInterpreter.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentInterpreter.js +12 -26
- package/dist/audit/orchestrator/intentInterpreter.js.map +1 -1
- package/dist/remediate/intake.js +1 -1
- package/dist/remediate/intake.js.map +1 -1
- package/dist/remediate/phases/close.d.ts +8 -0
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +11 -12
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/review/autonomousGate.d.ts +3 -7
- package/dist/remediate/review/autonomousGate.d.ts.map +1 -1
- package/dist/remediate/review/autonomousGate.js +10 -24
- package/dist/remediate/review/autonomousGate.js.map +1 -1
- package/dist/remediate/riskSignal.d.ts +8 -20
- package/dist/remediate/riskSignal.d.ts.map +1 -1
- package/dist/remediate/riskSignal.js +21 -76
- package/dist/remediate/riskSignal.js.map +1 -1
- package/dist/remediate/state/accessMemory.d.ts +2 -3
- package/dist/remediate/state/accessMemory.d.ts.map +1 -1
- package/dist/remediate/state/accessMemory.js +3 -6
- package/dist/remediate/state/accessMemory.js.map +1 -1
- package/dist/remediate/state/store.d.ts +6 -7
- package/dist/remediate/state/store.d.ts.map +1 -1
- package/dist/remediate/state/store.js.map +1 -1
- package/dist/remediate/state/types.d.ts +2 -84
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js +1 -39
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +0 -19
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +75 -63
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +0 -2
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +16 -7
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +171 -210
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/stepUtils.d.ts +10 -2
- package/dist/remediate/steps/stepUtils.d.ts.map +1 -1
- package/dist/remediate/steps/stepUtils.js +13 -3
- package/dist/remediate/steps/stepUtils.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +1 -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 +1 -16
- package/dist/remediate/validation/artifacts.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 +0 -51
- package/dist/remediate/validation/remediationState.js.map +1 -1
- package/dist/shared/analyzerPolicy.d.ts +18 -6
- package/dist/shared/analyzerPolicy.d.ts.map +1 -1
- package/dist/shared/analyzerPolicy.js +15 -3
- package/dist/shared/analyzerPolicy.js.map +1 -1
- package/dist/shared/analyzers/acquisitionEngine.d.ts +14 -8
- package/dist/shared/analyzers/acquisitionEngine.d.ts.map +1 -1
- package/dist/shared/analyzers/acquisitionEngine.js +3 -4
- package/dist/shared/analyzers/acquisitionEngine.js.map +1 -1
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/intent/constraintClauses.d.ts +31 -0
- package/dist/shared/intent/constraintClauses.d.ts.map +1 -0
- package/dist/shared/intent/constraintClauses.js +37 -0
- package/dist/shared/intent/constraintClauses.js.map +1 -0
- package/docs/audit-pkg/operator-guide.md +7 -4
- package/package.json +5 -2
- package/scripts/audit/postinstall.mjs +4 -2
- package/scripts/postinstall.mjs +1 -1
- package/scripts/remediate/postinstall.mjs +3 -1
- package/scripts/shared/install-host-assets.mjs +7 -3
- package/wrapper/audit-code-wrapper-build.mjs +3 -3
- package/wrapper/audit-code-wrapper-install-hosts.mjs +3 -3
- package/wrapper/audit-code-wrapper-lib.mjs +1 -0
- package/wrapper/remediate-code-wrapper-install-hosts.mjs +3 -3
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { loadRemediateSessionConfig } from "./sessionConfigLoad.js";
|
|
2
|
-
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { StateStore } from "../state/store.js";
|
|
6
6
|
// IO / validation / rendering helpers
|
|
7
7
|
import { discardOnSchemaVersionMismatch, readOptionalJsonFile, stagedAndUntracked, writeJsonFile, writeTextFile, buildAuditDeliverablePair, formatValidationIssues, isRecord, withFsRetry, RunLogger, coerceJsonObjectArg, headCommit, projectAuditFindingsReportSubset,
|
|
8
8
|
// obligation engine + intent
|
|
9
|
-
interpretFreeFormIntent, advance, decideFrictionTriage, buildFrictionTriageBlock, linkFrictionRunIds,
|
|
9
|
+
interpretFreeFormIntent, interpretIntent, unresolvedFromClauses, advance, decideFrictionTriage, buildFrictionTriageBlock, linkFrictionRunIds,
|
|
10
10
|
// domain constants
|
|
11
11
|
LENSES, SEVERITIES, } from "audit-tools/shared";
|
|
12
12
|
import { applyPlanPipeline, buildCoverageLedger } from "../phases/plan.js";
|
|
@@ -27,13 +27,13 @@ import { checkAffectedFileIntegrity } from "../utils/fileIntegrity.js";
|
|
|
27
27
|
import { applyIntentOrdering } from "../intent/intentOrdering.js";
|
|
28
28
|
import { resolveIntakeStep } from "./intakeResolver.js";
|
|
29
29
|
import { runToolOwnedFinalGate, writeFinalGateRedRecord, writeFinalGateOutcomeRecord, } from "./finalGate.js";
|
|
30
|
-
import { buildNextContractPipelineStep, shouldEnterContractPipeline, writePathASeedFromFindings,
|
|
30
|
+
import { buildNextContractPipelineStep, shouldEnterContractPipeline, writePathASeedFromFindings, } from "./contractPipeline.js";
|
|
31
31
|
import { contractArtifactExists, contractPipelineDir, } from "../contractPipeline/artifactStore.js";
|
|
32
32
|
import { buildReviewRequest, applyReviewResolution, isResolutionForRequest, screenResolutionIds, REVIEW_REQUEST_SCHEMA_VERSION, } from "../review/reviewGate.js";
|
|
33
33
|
import { buildAutonomousReviewDecision } from "../review/autonomousGate.js";
|
|
34
34
|
import { runFindingFilterPass } from "../findingFilter.js";
|
|
35
35
|
import { intakePaths, isIntakeReady, manifestIsInputBound, readIntakeArtifacts, resolveManifestSources, } from "../intake.js";
|
|
36
|
-
import { ensureIntakeRiskSignal, readIntakeRiskSignal, writeIntakeRiskSignal, escalateRiskSignal, findingRiskEvidence, distinctAffectedFiles,
|
|
36
|
+
import { ensureIntakeRiskSignal, readIntakeRiskSignal, writeIntakeRiskSignal, escalateRiskSignal, findingRiskEvidence, distinctAffectedFiles, } from "../riskSignal.js";
|
|
37
37
|
import { ambiguityReviewPrompt, clarificationPrompt, collectIntakeClarificationsPrompt, collectStartingPointPrompt, loaderCommand, reviewApprovalPrompt, synthesizeIntakePrompt, triagePrompt, } from "./prompts.js";
|
|
38
38
|
// Single-sourced prose renders of the canonical lens / severity vocabularies
|
|
39
39
|
// (`audit-tools/shared` `LENSES` / `SEVERITIES`) for the intent-checkpoint
|
|
@@ -449,7 +449,7 @@ function stripPlanTimeBookkeeping(value) {
|
|
|
449
449
|
* The re-plan carry-forward identity of a finding: canonical JSON with the
|
|
450
450
|
* plan-time bookkeeping keys stripped, so a re-plan whose only delta is a
|
|
451
451
|
* recomputed file hash or a re-evaluated grounding flag carries the prior item
|
|
452
|
-
*
|
|
452
|
+
* forward, while a real change to the finding does not.
|
|
453
453
|
*
|
|
454
454
|
* EXPORTED so the invariant suite can call THIS function. It was module-internal,
|
|
455
455
|
* and the suite claiming to cover the invariant declared its own copy of the key
|
|
@@ -488,14 +488,15 @@ function carryForwardMatchingItems(previous, replanned) {
|
|
|
488
488
|
for (const finding of replanned.plan.findings) {
|
|
489
489
|
const previousFinding = previousFindings.get(finding.id);
|
|
490
490
|
const previousItem = previous.items[finding.id];
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
491
|
+
// A still-pending item carries no work to preserve, so it is re-minted from
|
|
492
|
+
// the fresh plan rather than carried forward. (This test used to also admit
|
|
493
|
+
// a pending item that held an `item_spec`; the document phase that produced
|
|
494
|
+
// one was dissolved by N-R13 and the field is gone, so the second condition
|
|
495
|
+
// could never be true and is not restated here.)
|
|
495
496
|
if (!previousFinding || !previousItem) {
|
|
496
497
|
continue;
|
|
497
498
|
}
|
|
498
|
-
if (previousItem.status === "pending"
|
|
499
|
+
if (previousItem.status === "pending") {
|
|
499
500
|
continue;
|
|
500
501
|
}
|
|
501
502
|
if (findingCarryForwardKey(previousFinding) !== findingCarryForwardKey(finding)) {
|
|
@@ -1259,112 +1260,6 @@ async function runReviewApprovalGate(root, artifactsDir, survivors, autonomous =
|
|
|
1259
1260
|
declined,
|
|
1260
1261
|
};
|
|
1261
1262
|
}
|
|
1262
|
-
// ── T1 slice 3b — lean-path light adversarial review gate ───────────────────────
|
|
1263
|
-
//
|
|
1264
|
-
// Mirrors the review-approval gate's emit→resume→consume idiom, but the step is
|
|
1265
|
-
// WORK (status "ready"), not a human decision — so it runs identically in
|
|
1266
|
-
// attended and autonomous modes (the host agent executes the light pass like any
|
|
1267
|
-
// phase). Fires (and at most once) only when the fast path is eligible.
|
|
1268
|
-
function leanLightReviewVerdictPath(artifactsDir) {
|
|
1269
|
-
return join(artifactsDir, "lean_light_review_verdict.json");
|
|
1270
|
-
}
|
|
1271
|
-
function leanLightReviewDecisionPath(artifactsDir) {
|
|
1272
|
-
return join(artifactsDir, "lean_light_review_decision.json");
|
|
1273
|
-
}
|
|
1274
|
-
/** Render the bounded light-adversarial-review prompt over the approved findings. */
|
|
1275
|
-
function leanLightReviewPrompt(approved, verdictPath) {
|
|
1276
|
-
const findingLines = approved
|
|
1277
|
-
.map((f) => {
|
|
1278
|
-
const files = (f.affected_files ?? [])
|
|
1279
|
-
.map((loc) => loc?.path)
|
|
1280
|
-
.filter((p) => Boolean(p))
|
|
1281
|
-
.join(", ");
|
|
1282
|
-
return `- \`${f.id}\` — ${f.title ?? "(untitled)"}${files ? ` (${files})` : ""}`;
|
|
1283
|
-
})
|
|
1284
|
-
.join("\n");
|
|
1285
|
-
const nextCommand = loaderCommand("next-step");
|
|
1286
|
-
return `# Lean fast path — light adversarial review
|
|
1287
|
-
|
|
1288
|
-
These findings cleared the simplicity gate and are headed for the lean fast path (straight to plan→implement, skipping the full contract pipeline). Before they are trusted, do ONE **lightweight adversarial pass** — the floor that replaces the full design loop here. This is proportionate, not an exhaustive counterexample search: remediation legitimately catches upstream audit errors, so nothing skips review entirely.
|
|
1289
|
-
|
|
1290
|
-
## Findings to review
|
|
1291
|
-
${findingLines || "_(none)_"}
|
|
1292
|
-
|
|
1293
|
-
## Your task
|
|
1294
|
-
Adopt a brief adversarial stance and ask, across the set:
|
|
1295
|
-
- Is any finding actually wrong, already-fixed, or not grounded in the cited code?
|
|
1296
|
-
- Would the proposed fix break a caller, an invariant, or an adjacent behavior?
|
|
1297
|
-
- Are any two of these coupled / sharing a file in a way that needs seam reconciliation (i.e. NOT really independent)?
|
|
1298
|
-
- Is anything subtler or more architectural than the simplicity gate assumed?
|
|
1299
|
-
|
|
1300
|
-
Write your verdict to exactly:
|
|
1301
|
-
|
|
1302
|
-
\`${verdictPath}\`
|
|
1303
|
-
|
|
1304
|
-
\`\`\`json
|
|
1305
|
-
{
|
|
1306
|
-
"schema_version": "${LEAN_LIGHT_REVIEW_SCHEMA_VERSION}",
|
|
1307
|
-
"disposition": "clear | escalate",
|
|
1308
|
-
"concerns": ["<required & non-empty when escalate; the concrete concern(s)>"],
|
|
1309
|
-
"created_at": "<ISO-8601>"
|
|
1310
|
-
}
|
|
1311
|
-
\`\`\`
|
|
1312
|
-
|
|
1313
|
-
- **clear** — the light pass surfaced no real concern; the lean path may proceed.
|
|
1314
|
-
- **escalate** — a genuine concern surfaced. This is evidence the change is harder than assessed; the run escalates to the full contract pipeline (full independent review). When in doubt, escalate — a wrong call costs extra pipeline work, never a skipped review.
|
|
1315
|
-
|
|
1316
|
-
After writing the verdict, run:
|
|
1317
|
-
|
|
1318
|
-
\`${nextCommand}\`
|
|
1319
|
-
`;
|
|
1320
|
-
}
|
|
1321
|
-
/**
|
|
1322
|
-
* The lean light-review gate. Idempotent across next-step calls: once a decision
|
|
1323
|
-
* is recorded it is consumed directly. Returns a halt step while awaiting the
|
|
1324
|
-
* host's verdict, then `clear` (proceed to the lean plan) or `escalate` (route to
|
|
1325
|
-
* the full pipeline with the concerns).
|
|
1326
|
-
*/
|
|
1327
|
-
async function runLeanLightReviewGate(root, artifactsDir, approved) {
|
|
1328
|
-
const decisionPath = leanLightReviewDecisionPath(artifactsDir);
|
|
1329
|
-
const existing = await readOptionalJsonFile(decisionPath);
|
|
1330
|
-
if (existing) {
|
|
1331
|
-
return existing.disposition === "clear"
|
|
1332
|
-
? { kind: "clear" }
|
|
1333
|
-
: { kind: "escalate", concerns: existing.concerns ?? [] };
|
|
1334
|
-
}
|
|
1335
|
-
const verdictPath = leanLightReviewVerdictPath(artifactsDir);
|
|
1336
|
-
if (!existsSync(verdictPath)) {
|
|
1337
|
-
const step = await writeCurrentStep({
|
|
1338
|
-
stepKind: "lean_light_review",
|
|
1339
|
-
status: "ready",
|
|
1340
|
-
runId: randomRunId("LEANREVIEW"),
|
|
1341
|
-
repoRoot: root,
|
|
1342
|
-
artifactsDir,
|
|
1343
|
-
prompt: leanLightReviewPrompt(approved, verdictPath),
|
|
1344
|
-
allowedCommands: [loaderCommand("next-step")],
|
|
1345
|
-
stopCondition: "Stop after writing the lean light-review verdict and running next-step.",
|
|
1346
|
-
artifactPaths: { lean_light_review_verdict: verdictPath },
|
|
1347
|
-
});
|
|
1348
|
-
return { kind: "halt", step };
|
|
1349
|
-
}
|
|
1350
|
-
// Verdict present → interpret + record a durable decision, then archive the
|
|
1351
|
-
// consumed verdict so the gate can never re-emit.
|
|
1352
|
-
const raw = await readOptionalJsonFile(verdictPath);
|
|
1353
|
-
const interp = interpretLeanLightReviewVerdict(raw);
|
|
1354
|
-
const record = {
|
|
1355
|
-
schema_version: LEAN_LIGHT_REVIEW_SCHEMA_VERSION,
|
|
1356
|
-
disposition: interp.disposition,
|
|
1357
|
-
concerns: interp.concerns,
|
|
1358
|
-
created_at: new Date().toISOString(),
|
|
1359
|
-
};
|
|
1360
|
-
await writeJsonFile(decisionPath, record);
|
|
1361
|
-
if (existsSync(verdictPath)) {
|
|
1362
|
-
await withFsRetry(() => rename(verdictPath, `${verdictPath}.consumed-${Date.now()}`));
|
|
1363
|
-
}
|
|
1364
|
-
return interp.disposition === "clear"
|
|
1365
|
-
? { kind: "clear" }
|
|
1366
|
-
: { kind: "escalate", concerns: interp.concerns };
|
|
1367
|
-
}
|
|
1368
1263
|
/**
|
|
1369
1264
|
* Re-emit the autonomous-run leftovers (findings left LIVE, neither auto-fixed
|
|
1370
1265
|
* nor durably rejected) as a standard, re-consumable audit deliverable pair:
|
|
@@ -1522,22 +1417,13 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
|
|
|
1522
1417
|
}
|
|
1523
1418
|
// Persist the filter dispositions so coverage is built over the originals.
|
|
1524
1419
|
await persistReviewFilterDispositions(artifactsDir, originals, filter);
|
|
1525
|
-
//
|
|
1526
|
-
// fast-path folded into the self-scaling dial; its two mechanisms now live in
|
|
1527
|
-
// `riskSignal.ts` (light-review) + `contractPipeline.ts` (lean plan builder)).
|
|
1528
|
-
// A run skips the heavy contract DESIGN loop and
|
|
1529
|
-
// synthesizes the extracted plan directly IFF its effective risk tier is `low`;
|
|
1530
|
-
// the plan→implement→close machinery (per-node verify-before-merge + the final
|
|
1531
|
-
// whole-repo gate) is the retained safety net. Runs only here — on Path A
|
|
1532
|
-
// (structured_audit), the only intake with a pre-existing finding set to judge.
|
|
1533
|
-
//
|
|
1534
|
-
// First fold the APPROVED set's finding-level risk (grounding / confidence /
|
|
1420
|
+
// Fold the APPROVED set's finding-level risk (grounding / confidence /
|
|
1535
1421
|
// coupling / systemic / architecture / count — the finding-QUALITY dimension the
|
|
1536
1422
|
// intake path/breadth/intent signal doesn't see) INTO the shared risk signal as
|
|
1537
|
-
// escalate-on-evidence.
|
|
1538
|
-
//
|
|
1539
|
-
//
|
|
1540
|
-
//
|
|
1423
|
+
// escalate-on-evidence. The tier is the SINGLE classifier and the ONLY thing it
|
|
1424
|
+
// selects is DEPTH: every run enters the contract pipeline, and a `low` tier
|
|
1425
|
+
// traverses it shallowly (collapsed round-trips, light adversarial depth). There
|
|
1426
|
+
// is no second plan producer and no bypass — see COLLAPSE_GROUPS.
|
|
1541
1427
|
const findingEvidence = findingRiskEvidence(gate.approved);
|
|
1542
1428
|
let riskSignal = await readIntakeRiskSignal(artifactsDir);
|
|
1543
1429
|
if (findingEvidence && riskSignal) {
|
|
@@ -1549,62 +1435,6 @@ async function handleReadyIntakeContractPipeline(root, artifactsDir, options, ru
|
|
|
1549
1435
|
await writeIntakeRiskSignal(artifactsDir, riskSignal);
|
|
1550
1436
|
}
|
|
1551
1437
|
}
|
|
1552
|
-
// T1 slice 3b — the lean tier is NOT zero-scrutiny: a `low`-tier run first runs
|
|
1553
|
-
// a bounded LIGHT adversarial review over the approved findings (the floor,
|
|
1554
|
-
// never off — `adversarialDepthForTier("low") === "light"`). A clear verdict
|
|
1555
|
-
// proceeds to the lean plan; a verdict that surfaces a real concern escalates
|
|
1556
|
-
// the risk signal and routes to the full pipeline below.
|
|
1557
|
-
if (gate.approved.length > 0 && riskSignal?.tier === "low") {
|
|
1558
|
-
const review = await runLeanLightReviewGate(root, artifactsDir, gate.approved);
|
|
1559
|
-
if (review.kind === "halt") {
|
|
1560
|
-
return review.step;
|
|
1561
|
-
}
|
|
1562
|
-
if (review.kind === "escalate") {
|
|
1563
|
-
// Escalate-on-evidence: raise the signal to at least `medium` so the
|
|
1564
|
-
// full pipeline's adversarial depth is `full` (see slice 3a), then
|
|
1565
|
-
// fall through to the full pipeline.
|
|
1566
|
-
await writeIntakeRiskSignal(artifactsDir, escalateRiskSignal(riskSignal, {
|
|
1567
|
-
tier: "medium",
|
|
1568
|
-
reason: `lean light review surfaced a concern: ${review.concerns.join("; ")}`,
|
|
1569
|
-
}));
|
|
1570
|
-
runLogger.event({
|
|
1571
|
-
phase: "next-step",
|
|
1572
|
-
kind: "outcome",
|
|
1573
|
-
obligation: "lean_fast_path",
|
|
1574
|
-
note: `lean_fast_path_escalated concerns=${String(review.concerns.length)} ` +
|
|
1575
|
-
`detail=${review.concerns.join("; ")}`,
|
|
1576
|
-
});
|
|
1577
|
-
process.stderr.write(`[remediate-code] Lean light review escalated (${review.concerns.join("; ")}); routing to the full contract pipeline.\n`);
|
|
1578
|
-
}
|
|
1579
|
-
else {
|
|
1580
|
-
// Clear verdict → proceed with the lean plan.
|
|
1581
|
-
const leanPlan = buildLeanExtractedPlan(gate.approved, randomRunId("LEAN"));
|
|
1582
|
-
await writeJsonFile(intakePaths(artifactsDir).extractedPlan, leanPlan);
|
|
1583
|
-
runLogger.event({
|
|
1584
|
-
phase: "next-step",
|
|
1585
|
-
kind: "outcome",
|
|
1586
|
-
obligation: "lean_fast_path",
|
|
1587
|
-
note: `lean_fast_path_routed findings=${String(gate.approved.length)} ` +
|
|
1588
|
-
`rationale=${riskSignal.rationale.join("; ")}`,
|
|
1589
|
-
});
|
|
1590
|
-
process.stderr.write(`[remediate-code] Lean fast path (risk tier low): ${riskSignal.rationale.join("; ")}; light review clear. Routing to plan→implement.\n`);
|
|
1591
|
-
const planned = await handlePendingExtractedPlan(root, artifactsDir, { status: "pending" }, leanPlan, runLogger);
|
|
1592
|
-
if (planned) {
|
|
1593
|
-
return planned;
|
|
1594
|
-
}
|
|
1595
|
-
// Defensive: a deterministically-built lean plan should always
|
|
1596
|
-
// normalize. If it somehow didn't, handlePendingExtractedPlan removed
|
|
1597
|
-
// the file; fall through to the full pipeline (the safety net) rather
|
|
1598
|
-
// than stalling the run.
|
|
1599
|
-
runLogger.event({
|
|
1600
|
-
phase: "next-step",
|
|
1601
|
-
kind: "outcome",
|
|
1602
|
-
obligation: "lean_fast_path",
|
|
1603
|
-
note: "lean_fast_path_fallback plan failed to materialize; routing to the contract pipeline",
|
|
1604
|
-
});
|
|
1605
|
-
process.stderr.write("[remediate-code] Lean fast-path plan failed to materialize; falling back to the contract pipeline.\n");
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
1438
|
// Seed the pipeline with the approved survivors only. When that set is
|
|
1609
1439
|
// narrower than the originals (anything filtered or declined), route the
|
|
1610
1440
|
// seed AND the pipeline's source inputs at a filtered file so a removed
|
|
@@ -2144,9 +1974,8 @@ async function handlePlanning(root, artifactsDir, state, store) {
|
|
|
2144
1974
|
if (halt)
|
|
2145
1975
|
return { kind: "emit", step: halt };
|
|
2146
1976
|
}
|
|
2147
|
-
// Document phase dissolved: planning transitions directly to
|
|
2148
|
-
//
|
|
2149
|
-
// uses finding context directly when absent.
|
|
1977
|
+
// Document phase dissolved (N-R13): planning transitions directly to
|
|
1978
|
+
// implementing, and the host workload reads finding context directly.
|
|
2150
1979
|
const implementBlocks = implementableBlocks(state);
|
|
2151
1980
|
if (implementBlocks.length > 0) {
|
|
2152
1981
|
if (state.plan) {
|
|
@@ -2876,7 +2705,11 @@ Once the file is written, run:
|
|
|
2876
2705
|
// on an LLM-authored free-text `intent_interpretation`.
|
|
2877
2706
|
/** Sidecar artifact recording the deterministic interpretation of free_form_intent. */
|
|
2878
2707
|
export const INTENT_INTERPRETATION_FILENAME = "intent-interpretation.json";
|
|
2879
|
-
|
|
2708
|
+
// v1alpha2: unencodable_clauses carries identity-keyed records (clause_id +
|
|
2709
|
+
// checkpoint_question), not bare strings — the shape the blocking consumer
|
|
2710
|
+
// reads. A v1alpha1 sidecar (string[]) is stale and is repaired by
|
|
2711
|
+
// re-derivation; it had no readers, so no migration path is owed.
|
|
2712
|
+
export const INTENT_INTERPRETATION_SCHEMA_VERSION = "remediate-code-intent-interpretation/v1alpha2";
|
|
2880
2713
|
/**
|
|
2881
2714
|
* Interpret a confirmed checkpoint's `free_form_intent` via the shared
|
|
2882
2715
|
* deterministic interpreter and persist the structured signals to a sidecar
|
|
@@ -2896,33 +2729,99 @@ runLogger) {
|
|
|
2896
2729
|
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
2897
2730
|
return null;
|
|
2898
2731
|
const interpreted = interpretFreeFormIntent(raw);
|
|
2732
|
+
// The clause pipeline (interpretIntent) owns identity + blocking questions;
|
|
2733
|
+
// the hint interpreter above owns lens/priority/scope signals. Both are
|
|
2734
|
+
// deterministic draws over the same input.
|
|
2735
|
+
const clauseResult = interpretIntent(raw);
|
|
2736
|
+
const unencodable_clauses = [];
|
|
2737
|
+
for (const clause of clauseResult.clauses) {
|
|
2738
|
+
if (clause.encodable || !clause.checkpoint_question)
|
|
2739
|
+
continue;
|
|
2740
|
+
unencodable_clauses.push({
|
|
2741
|
+
clause_id: clause.clause_id,
|
|
2742
|
+
text: clause.text,
|
|
2743
|
+
checkpoint_question: clause.checkpoint_question,
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2899
2746
|
const persisted = {
|
|
2900
2747
|
schema_version: INTENT_INTERPRETATION_SCHEMA_VERSION,
|
|
2901
2748
|
interpreted,
|
|
2902
|
-
unencodable_clauses
|
|
2749
|
+
unencodable_clauses,
|
|
2903
2750
|
created_at: new Date().toISOString(),
|
|
2904
2751
|
};
|
|
2905
2752
|
try {
|
|
2906
2753
|
await writeJsonFile(join(artifactsDir, INTENT_INTERPRETATION_FILENAME), persisted);
|
|
2907
2754
|
}
|
|
2908
2755
|
catch {
|
|
2909
|
-
// Best-effort
|
|
2756
|
+
// Best-effort WRITE: a write failure must never crash the decide loop.
|
|
2757
|
+
// Enforcement does not depend on it — the consumer re-derives when the
|
|
2758
|
+
// sidecar is missing (readOrRepairIntentInterpretation).
|
|
2910
2759
|
}
|
|
2911
|
-
if (
|
|
2760
|
+
if (unencodable_clauses.length > 0) {
|
|
2761
|
+
const clauseTexts = unencodable_clauses.map((c) => c.text);
|
|
2912
2762
|
runLogger?.event({
|
|
2913
2763
|
phase: "next-step",
|
|
2914
2764
|
kind: "outcome",
|
|
2915
2765
|
obligation: "interpret_intent",
|
|
2916
|
-
note: `intent_unencodable_clauses count=${String(
|
|
2917
|
-
`clauses=${
|
|
2766
|
+
note: `intent_unencodable_clauses count=${String(unencodable_clauses.length)} ` +
|
|
2767
|
+
`clauses=${clauseTexts.join("; ")}`,
|
|
2918
2768
|
});
|
|
2919
|
-
process.stderr.write(`[remediate-code] free_form_intent: ${
|
|
2920
|
-
`clause(s) could not be encoded as lens/priority/scope signals and
|
|
2921
|
-
`
|
|
2922
|
-
`${
|
|
2769
|
+
process.stderr.write(`[remediate-code] free_form_intent: ${unencodable_clauses.length} ` +
|
|
2770
|
+
`clause(s) could not be encoded as lens/priority/scope signals and ` +
|
|
2771
|
+
`block planning until answered via constraint_clauses: ` +
|
|
2772
|
+
`${clauseTexts.join("; ")}\n`);
|
|
2923
2773
|
}
|
|
2924
2774
|
return persisted;
|
|
2925
2775
|
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Read the persisted intent interpretation — the LOAD-BEARING input to the
|
|
2778
|
+
* constraint-clause gate — repairing it by re-derivation when it is missing,
|
|
2779
|
+
* unparseable, or carries a stale schema_version. Returns null only when
|
|
2780
|
+
* there is nothing to interpret (no confirmed checkpoint / empty intent).
|
|
2781
|
+
*/
|
|
2782
|
+
export async function readOrRepairIntentInterpretation(artifactsDir, checkpoint, runLogger) {
|
|
2783
|
+
if (!checkpoint || checkpoint.confirmed_by !== "host")
|
|
2784
|
+
return null;
|
|
2785
|
+
const raw = checkpoint.free_form_intent;
|
|
2786
|
+
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
2787
|
+
return null;
|
|
2788
|
+
const sidecarPath = join(artifactsDir, INTENT_INTERPRETATION_FILENAME);
|
|
2789
|
+
try {
|
|
2790
|
+
const parsed = parsePersistedIntentInterpretation(JSON.parse(await readFile(sidecarPath, "utf8")));
|
|
2791
|
+
if (parsed)
|
|
2792
|
+
return parsed;
|
|
2793
|
+
}
|
|
2794
|
+
catch {
|
|
2795
|
+
// Missing or unreadable — fall through to repair.
|
|
2796
|
+
}
|
|
2797
|
+
return interpretConfirmedCheckpointIntent(artifactsDir, checkpoint, runLogger);
|
|
2798
|
+
}
|
|
2799
|
+
/** Pure shape gate for the sidecar: current version + record-shaped clauses, else null. */
|
|
2800
|
+
function parsePersistedIntentInterpretation(parsed) {
|
|
2801
|
+
if (isRecord(parsed) &&
|
|
2802
|
+
parsed.schema_version === INTENT_INTERPRETATION_SCHEMA_VERSION &&
|
|
2803
|
+
Array.isArray(parsed.unencodable_clauses) &&
|
|
2804
|
+
parsed.unencodable_clauses.every((c) => isRecord(c) &&
|
|
2805
|
+
typeof c.clause_id === "string" &&
|
|
2806
|
+
typeof c.text === "string" &&
|
|
2807
|
+
typeof c.checkpoint_question === "string")) {
|
|
2808
|
+
return parsed;
|
|
2809
|
+
}
|
|
2810
|
+
return null;
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Sync sidecar read for the obligation's derive scan. Returns the persisted
|
|
2814
|
+
* interpretation, or null when the sidecar is missing, unreadable, or stale —
|
|
2815
|
+
* the execute path repairs via {@link readOrRepairIntentInterpretation}.
|
|
2816
|
+
*/
|
|
2817
|
+
function readPersistedIntentInterpretationSync(sidecarPath) {
|
|
2818
|
+
try {
|
|
2819
|
+
return parsePersistedIntentInterpretation(JSON.parse(readFileSync(sidecarPath, "utf8")));
|
|
2820
|
+
}
|
|
2821
|
+
catch {
|
|
2822
|
+
return null;
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2926
2825
|
/**
|
|
2927
2826
|
* Narrow a nullable engine state to non-null inside an executor whose `derive`
|
|
2928
2827
|
* only marks it actionable when the state is present — a violation is an engine
|
|
@@ -3050,19 +2949,81 @@ function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
3050
2949
|
},
|
|
3051
2950
|
{
|
|
3052
2951
|
// Past the intent gate: interpret the confirmed checkpoint's
|
|
3053
|
-
// free_form_intent once (INV-S04)
|
|
3054
|
-
//
|
|
3055
|
-
//
|
|
2952
|
+
// free_form_intent once (INV-S04), persist the structured signals, and
|
|
2953
|
+
// ENFORCE the unencodable-clause contract: an unanswered clause blocks
|
|
2954
|
+
// the decide loop until the host resolves it via a `constraint_clauses`
|
|
2955
|
+
// entry on the checkpoint (CE-004, identity-keyed — the shared matcher
|
|
2956
|
+
// in audit-tools/shared intent/constraintClauses.ts, the same core the
|
|
2957
|
+
// audit gate uses). The PERSISTED sidecar is the consumed input; a
|
|
2958
|
+
// missing or stale sidecar is repaired by re-derivation, never skipped.
|
|
3056
2959
|
id: "interpret_intent",
|
|
3057
|
-
derive: () =>
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
2960
|
+
derive: () => {
|
|
2961
|
+
if (existingCheckpoint?.confirmed_by !== "host" ||
|
|
2962
|
+
typeof existingCheckpoint.free_form_intent !== "string" ||
|
|
2963
|
+
existingCheckpoint.free_form_intent.trim().length === 0) {
|
|
2964
|
+
return "satisfied";
|
|
2965
|
+
}
|
|
2966
|
+
const sidecar = readPersistedIntentInterpretationSync(interpretationPath);
|
|
2967
|
+
if (sidecar === null)
|
|
2968
|
+
return "missing";
|
|
2969
|
+
return unresolvedFromClauses(sidecar.unencodable_clauses, existingCheckpoint)
|
|
2970
|
+
.length > 0
|
|
2971
|
+
? "missing"
|
|
2972
|
+
: "satisfied";
|
|
2973
|
+
},
|
|
3063
2974
|
execute: async (state, c) => {
|
|
3064
|
-
await
|
|
3065
|
-
|
|
2975
|
+
const persisted = await readOrRepairIntentInterpretation(artifactsDir, existingCheckpoint, c.runLogger);
|
|
2976
|
+
const unresolved = persisted
|
|
2977
|
+
? unresolvedFromClauses(persisted.unencodable_clauses, existingCheckpoint)
|
|
2978
|
+
: [];
|
|
2979
|
+
if (unresolved.length === 0)
|
|
2980
|
+
return { kind: "transition", state };
|
|
2981
|
+
const nextCommand = loaderCommand("next-step");
|
|
2982
|
+
const checkpointPath = join(artifactsDir, "intent_checkpoint.json");
|
|
2983
|
+
const clauseLines = unresolved
|
|
2984
|
+
.map((clause) => `- **${clause.clause_id}** — "${clause.text}"\n Question: ${clause.checkpoint_question}`)
|
|
2985
|
+
.join("\n");
|
|
2986
|
+
const prompt = `
|
|
2987
|
+
# Resolve Free-Form Intent Constraints
|
|
2988
|
+
|
|
2989
|
+
${unresolved.length} clause(s) of the confirmed checkpoint's \`free_form_intent\` could
|
|
2990
|
+
not be encoded as lens/priority/scope signals. Each needs an explicit answer
|
|
2991
|
+
before planning proceeds — an unanswered clause would otherwise be silently
|
|
2992
|
+
dropped.
|
|
2993
|
+
|
|
2994
|
+
${clauseLines}
|
|
2995
|
+
|
|
2996
|
+
Answer each clause by adding a \`constraint_clauses\` entry to
|
|
2997
|
+
\`intent_checkpoint.json\` (keep the exact \`clause_id\` — answers are keyed on
|
|
2998
|
+
clause identity, not on the question text):
|
|
2999
|
+
|
|
3000
|
+
\`\`\`json
|
|
3001
|
+
"constraint_clauses": [
|
|
3002
|
+
{ "clause_id": "<the clause_id above>", "text": "<the clause text>", "checkpoint_question": "<the question above>", "host_answer": "<how to apply this constraint>" }
|
|
3003
|
+
]
|
|
3004
|
+
\`\`\`
|
|
3005
|
+
|
|
3006
|
+
Then run:
|
|
3007
|
+
|
|
3008
|
+
\`${nextCommand}\`
|
|
3009
|
+
`;
|
|
3010
|
+
return {
|
|
3011
|
+
kind: "emit",
|
|
3012
|
+
step: await writeCurrentStep({
|
|
3013
|
+
stepKind: "confirm_intent",
|
|
3014
|
+
status: "blocked",
|
|
3015
|
+
runId: stateRunId(state),
|
|
3016
|
+
repoRoot: c.root,
|
|
3017
|
+
artifactsDir,
|
|
3018
|
+
prompt,
|
|
3019
|
+
allowedCommands: [nextCommand],
|
|
3020
|
+
stopCondition: "Stop after adding constraint_clauses answers to intent_checkpoint.json and running next-step.",
|
|
3021
|
+
artifactPaths: {
|
|
3022
|
+
intent_checkpoint: checkpointPath,
|
|
3023
|
+
intent_interpretation: interpretationPath,
|
|
3024
|
+
},
|
|
3025
|
+
}),
|
|
3026
|
+
};
|
|
3066
3027
|
},
|
|
3067
3028
|
},
|
|
3068
3029
|
{
|