audit-tools 0.35.1 → 0.35.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  2. package/dist/audit/cli/nextStepHelpers.js +85 -10
  3. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  4. package/dist/audit/orchestrator/partitionTaskGraph.d.ts +11 -0
  5. package/dist/audit/orchestrator/partitionTaskGraph.d.ts.map +1 -1
  6. package/dist/audit/orchestrator/partitionTaskGraph.js +4 -1
  7. package/dist/audit/orchestrator/partitionTaskGraph.js.map +1 -1
  8. package/dist/audit/orchestrator/reviewPackets.d.ts +6 -0
  9. package/dist/audit/orchestrator/reviewPackets.d.ts.map +1 -1
  10. package/dist/audit/orchestrator/reviewPackets.js +1 -0
  11. package/dist/audit/orchestrator/reviewPackets.js.map +1 -1
  12. package/dist/audit/reporting/mergeFindings.d.ts.map +1 -1
  13. package/dist/audit/reporting/mergeFindings.js +3 -0
  14. package/dist/audit/reporting/mergeFindings.js.map +1 -1
  15. package/dist/audit/reporting/synthesis.d.ts +8 -5
  16. package/dist/audit/reporting/synthesis.d.ts.map +1 -1
  17. package/dist/audit/reporting/synthesis.js +21 -10
  18. package/dist/audit/reporting/synthesis.js.map +1 -1
  19. package/dist/audit/reporting/synthesisNarrativePrompt.js +1 -1
  20. package/dist/audit/reporting/synthesisNarrativePrompt.js.map +1 -1
  21. package/dist/remediate/dedup/crossLensDedup.d.ts +12 -0
  22. package/dist/remediate/dedup/crossLensDedup.d.ts.map +1 -1
  23. package/dist/remediate/dedup/crossLensDedup.js +29 -4
  24. package/dist/remediate/dedup/crossLensDedup.js.map +1 -1
  25. package/dist/remediate/intake.d.ts +1 -1
  26. package/dist/remediate/intake.d.ts.map +1 -1
  27. package/dist/remediate/intake.js +11 -1
  28. package/dist/remediate/intake.js.map +1 -1
  29. package/dist/remediate/phases/plan.d.ts +8 -4
  30. package/dist/remediate/phases/plan.d.ts.map +1 -1
  31. package/dist/remediate/phases/plan.js +10 -6
  32. package/dist/remediate/phases/plan.js.map +1 -1
  33. package/dist/remediate/phases/triage.d.ts.map +1 -1
  34. package/dist/remediate/phases/triage.js +1 -1
  35. package/dist/remediate/phases/triage.js.map +1 -1
  36. package/dist/remediate/review/reviewGate.d.ts +14 -0
  37. package/dist/remediate/review/reviewGate.d.ts.map +1 -1
  38. package/dist/remediate/review/reviewGate.js +37 -0
  39. package/dist/remediate/review/reviewGate.js.map +1 -1
  40. package/dist/remediate/steps/dispatch/implementPrompt.d.ts.map +1 -1
  41. package/dist/remediate/steps/dispatch/implementPrompt.js +3 -1
  42. package/dist/remediate/steps/dispatch/implementPrompt.js.map +1 -1
  43. package/dist/remediate/steps/dispatch/marshal.d.ts.map +1 -1
  44. package/dist/remediate/steps/dispatch/marshal.js +65 -31
  45. package/dist/remediate/steps/dispatch/marshal.js.map +1 -1
  46. package/dist/remediate/steps/dispatch/writeScope.d.ts +10 -17
  47. package/dist/remediate/steps/dispatch/writeScope.d.ts.map +1 -1
  48. package/dist/remediate/steps/dispatch/writeScope.js +16 -57
  49. package/dist/remediate/steps/dispatch/writeScope.js.map +1 -1
  50. package/dist/remediate/steps/dispatch.d.ts +1 -1
  51. package/dist/remediate/steps/dispatch.d.ts.map +1 -1
  52. package/dist/remediate/steps/dispatch.js +1 -1
  53. package/dist/remediate/steps/dispatch.js.map +1 -1
  54. package/dist/remediate/steps/intakeResolver.d.ts.map +1 -1
  55. package/dist/remediate/steps/intakeResolver.js +1 -1
  56. package/dist/remediate/steps/intakeResolver.js.map +1 -1
  57. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  58. package/dist/remediate/steps/nextStep.js +90 -18
  59. package/dist/remediate/steps/nextStep.js.map +1 -1
  60. package/dist/remediate/steps/prompts.d.ts +3 -3
  61. package/dist/remediate/steps/prompts.d.ts.map +1 -1
  62. package/dist/remediate/steps/prompts.js +43 -9
  63. package/dist/remediate/steps/prompts.js.map +1 -1
  64. package/dist/remediate/validation/remediationState.d.ts +1 -1
  65. package/dist/remediate/validation/remediationState.d.ts.map +1 -1
  66. package/dist/remediate/validation/remediationState.js +8 -1
  67. package/dist/remediate/validation/remediationState.js.map +1 -1
  68. package/dist/shared/decompose/modularity.d.ts.map +1 -1
  69. package/dist/shared/decompose/modularity.js +101 -37
  70. package/dist/shared/decompose/modularity.js.map +1 -1
  71. package/dist/shared/dispatch/providerLaunchFinalize.d.ts.map +1 -1
  72. package/dist/shared/dispatch/providerLaunchFinalize.js +23 -3
  73. package/dist/shared/dispatch/providerLaunchFinalize.js.map +1 -1
  74. package/dist/shared/findings/dedupe.d.ts +29 -0
  75. package/dist/shared/findings/dedupe.d.ts.map +1 -1
  76. package/dist/shared/findings/dedupe.js +74 -3
  77. package/dist/shared/findings/dedupe.js.map +1 -1
  78. package/dist/shared/index.d.ts +1 -1
  79. package/dist/shared/index.d.ts.map +1 -1
  80. package/dist/shared/index.js +1 -1
  81. package/dist/shared/index.js.map +1 -1
  82. package/dist/shared/validation/findingsReport.d.ts +25 -33
  83. package/dist/shared/validation/findingsReport.d.ts.map +1 -1
  84. package/dist/shared/validation/findingsReport.js +237 -57
  85. package/dist/shared/validation/findingsReport.js.map +1 -1
  86. package/dist/shared/validation/sessionConfig.d.ts.map +1 -1
  87. package/dist/shared/validation/sessionConfig.js +4 -1
  88. package/dist/shared/validation/sessionConfig.js.map +1 -1
  89. package/package.json +1 -1
@@ -37,7 +37,7 @@ import { resolveIntakeStep } from "./intakeResolver.js";
37
37
  import { runToolOwnedFinalGate, applyCoarseReblock, readFinalGateSidecar, writeFinalGateSidecar, } from "./finalGate.js";
38
38
  import { buildNextContractPipelineStep, shouldEnterContractPipeline, writePathASeedFromFindings, buildLeanExtractedPlan, } from "./contractPipeline.js";
39
39
  import { contractArtifactExists, contractPipelineDir, } from "../contractPipeline/artifactStore.js";
40
- import { buildReviewRequest, applyReviewResolution, isResolutionForRequest, REVIEW_REQUEST_SCHEMA_VERSION, } from "../review/reviewGate.js";
40
+ import { buildReviewRequest, applyReviewResolution, isResolutionForRequest, screenResolutionIds, REVIEW_REQUEST_SCHEMA_VERSION, } from "../review/reviewGate.js";
41
41
  import { buildAutonomousReviewDecision } from "../review/autonomousGate.js";
42
42
  import { runFindingFilterPass } from "../findingFilter.js";
43
43
  import { intakePaths, isIntakeReady, readIntakeArtifacts, resolveManifestSources, } from "../intake.js";
@@ -1074,13 +1074,11 @@ function stripPlanTimeBookkeeping(value) {
1074
1074
  function findingCarryForwardKey(finding) {
1075
1075
  return JSON.stringify(stripPlanTimeBookkeeping(finding));
1076
1076
  }
1077
- // FIRST-wins on multi-block membership: `fixupBlocksAfterDedup` can leave a
1078
- // dedup survivor in more than one block's items, and historically the two
1079
- // consumers disagreed (saveStateForPlan scanned blocks first-wins while this
1080
- // map overwrote last-wins), so the same plan could yield different block_ids
1081
- // per path. First-wins is pinned as the single semantic. The root question —
1082
- // whether a finding should ever belong to two blocks at all — is tracked in
1083
- // docs/backlog/open-bugs.md.
1077
+ // Single-block membership is enforced by `fixupBlocksAfterDedup` (each finding
1078
+ // id appears in exactly one block's items after fixup), so this scan is a plain
1079
+ // projection. First-wins in block order is kept as the tie-break for defense in
1080
+ // depth it is the same order fixup resolves ownership in, so the two can
1081
+ // never disagree even on a malformed plan.
1084
1082
  function blockIdsByFinding(plan) {
1085
1083
  const byFinding = new Map();
1086
1084
  for (const block of plan.blocks) {
@@ -2094,14 +2092,14 @@ function extractAuditFindings(parsed) {
2094
2092
  }
2095
2093
  return [];
2096
2094
  }
2097
- async function handleWaitingForReviewApproval(root, artifactsDir, request) {
2095
+ async function handleWaitingForReviewApproval(root, artifactsDir, request, refusal) {
2098
2096
  return writeCurrentStep({
2099
2097
  stepKind: "collect_review_approval",
2100
2098
  status: "blocked",
2101
2099
  runId: randomRunId("REVIEW"),
2102
2100
  repoRoot: root,
2103
2101
  artifactsDir,
2104
- prompt: reviewApprovalPrompt(request, reviewResolutionPath(artifactsDir)),
2102
+ prompt: reviewApprovalPrompt(request, reviewResolutionPath(artifactsDir), refusal),
2105
2103
  allowedCommands: [loaderCommand("next-step")],
2106
2104
  stopCondition: "Stop after presenting the findings for approval and collecting the user's approve/disapprove decision, unless the decision is already recorded and the prompt told you to continue.",
2107
2105
  artifactPaths: {
@@ -2110,6 +2108,31 @@ async function handleWaitingForReviewApproval(root, artifactsDir, request) {
2110
2108
  },
2111
2109
  });
2112
2110
  }
2111
+ /**
2112
+ * Uniform id-join contract pre-screen for the review gate (both paths): a
2113
+ * resolution naming an unknown finding id or tier is REFUSED whole — archived
2114
+ * (never applied) and the gate re-halts with the refusal and the valid set in
2115
+ * the re-prompt. The gate's default is approve, so the silent-drop alternative
2116
+ * turns a typo'd decline into an approval. Returns the re-halt step, or null
2117
+ * when the resolution's ids are clean. `applyReviewResolution` re-checks as the
2118
+ * mechanical backstop.
2119
+ */
2120
+ async function refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath) {
2121
+ const screen = screenResolutionIds(request, resolution);
2122
+ if (screen.unknown_finding_ids.length === 0 && screen.unknown_tiers.length === 0) {
2123
+ return null;
2124
+ }
2125
+ await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
2126
+ await writeJsonFile(requestPath, request);
2127
+ const parts = [];
2128
+ if (screen.unknown_finding_ids.length > 0) {
2129
+ parts.push(`finding id(s) not in the request: ${screen.unknown_finding_ids.map((i) => `\`${i}\``).join(", ")}`);
2130
+ }
2131
+ if (screen.unknown_tiers.length > 0) {
2132
+ parts.push(`unknown tier(s): ${screen.unknown_tiers.map((t) => `\`${t}\``).join(", ")}`);
2133
+ }
2134
+ return handleWaitingForReviewApproval(root, artifactsDir, request, parts.join("; "));
2135
+ }
2113
2136
  /**
2114
2137
  * Run the review-approval gate over the SURVIVOR finding set (already passed
2115
2138
  * through the single filter pass: deduped, evidence-bearing, path-grounded,
@@ -2186,6 +2209,9 @@ async function runReviewApprovalGate(root, artifactsDir, survivors, autonomous =
2186
2209
  step: await handleWaitingForReviewApproval(root, artifactsDir, request),
2187
2210
  };
2188
2211
  }
2212
+ const refusalStep = await refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath);
2213
+ if (refusalStep)
2214
+ return { kind: "halt", step: refusalStep };
2189
2215
  const decision = applyReviewResolution(request, resolution);
2190
2216
  const record = {
2191
2217
  schema_version: REVIEW_DECISION_SCHEMA_VERSION,
@@ -2765,11 +2791,25 @@ function applyClarificationActionToItem(item, res, now) {
2765
2791
  * Mirrors the triage resolution consume: reject_finding → terminal,
2766
2792
  * clarified → re-open (pending) for implement dispatch. Archives the file.
2767
2793
  */
2768
- async function applyPlanClarificationResolution(artifactsDir, state, store) {
2794
+ async function applyPlanClarificationResolution(root, artifactsDir, state, store) {
2769
2795
  if (!state.plan || !state.items)
2770
- return state;
2796
+ return { kind: "applied", state };
2771
2797
  const resolutionPath = join(artifactsDir, "clarification_resolution.json");
2772
2798
  const resolutions = normalizePlanClarificationResolutions(await readOptionalJsonFile(resolutionPath));
2799
+ // Uniform id-join contract: an unknown finding_id refuses the WHOLE
2800
+ // resolution (archived, nothing applied) and re-halts with the unknown ids
2801
+ // named — the silent-continue alternative drops the user's answer on a typo'd
2802
+ // id and force-closes its item as abandoned at the fall-through below.
2803
+ const unknownIds = resolutions
2804
+ .map((r) => r.finding_id)
2805
+ .filter((id) => !state.items?.[id]);
2806
+ if (unknownIds.length > 0) {
2807
+ await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
2808
+ return {
2809
+ kind: "refused",
2810
+ step: await handleWaitingForClarification(root, artifactsDir, state, `finding id(s) not in the plan: ${unknownIds.map((i) => `\`${i}\``).join(", ")}`),
2811
+ };
2812
+ }
2773
2813
  const now = new Date().toISOString();
2774
2814
  for (const res of resolutions) {
2775
2815
  const item = state.items[res.finding_id];
@@ -2795,9 +2835,9 @@ async function applyPlanClarificationResolution(artifactsDir, state, store) {
2795
2835
  state.clarifications = [];
2796
2836
  state.closing_plan ??= { action: "none" };
2797
2837
  await store.saveState(state);
2798
- return state;
2838
+ return { kind: "applied", state };
2799
2839
  }
2800
- async function handleWaitingForClarification(root, artifactsDir, state) {
2840
+ async function handleWaitingForClarification(root, artifactsDir, state, refusal) {
2801
2841
  const clarifications = state.clarifications ??
2802
2842
  (await readOptionalJsonFile(join(artifactsDir, "clarification_request.json"))) ??
2803
2843
  [];
@@ -2808,7 +2848,7 @@ async function handleWaitingForClarification(root, artifactsDir, state) {
2808
2848
  runId: stateRunId(state),
2809
2849
  repoRoot: root,
2810
2850
  artifactsDir,
2811
- prompt: clarificationPrompt(clarifications, resolutionPath),
2851
+ prompt: clarificationPrompt(clarifications, resolutionPath, refusal),
2812
2852
  allowedCommands: [loaderCommand("next-step")],
2813
2853
  stopCondition: "Stop after asking the user for clarification answers, unless the answers are already available and the prompt told you to continue.",
2814
2854
  artifactPaths: {
@@ -2877,6 +2917,9 @@ async function runPlanningReviewGate(root, artifactsDir, state, store) {
2877
2917
  await writeJsonFile(requestPath, request);
2878
2918
  return handleWaitingForReviewApproval(root, artifactsDir, request);
2879
2919
  }
2920
+ const refusalStep = await refuseUnknownIdResolution(root, artifactsDir, request, resolution, resolutionPath, requestPath);
2921
+ if (refusalStep)
2922
+ return refusalStep;
2880
2923
  const decision = applyReviewResolution(request, resolution);
2881
2924
  const record = {
2882
2925
  schema_version: REVIEW_DECISION_SCHEMA_VERSION,
@@ -2982,7 +3025,7 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
2982
3025
  runId: stateRunId(state),
2983
3026
  repoRoot: root,
2984
3027
  artifactsDir,
2985
- prompt: ambiguityReviewPrompt(candidates, resolutionPath),
3028
+ prompt: ambiguityReviewPrompt(candidates, resolutionPath, findings.map((f) => f.id)),
2986
3029
  allowedCommands: [loaderCommand("next-step")],
2987
3030
  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.",
2988
3031
  artifactPaths: {
@@ -2994,6 +3037,33 @@ async function runPlanAmbiguityGate(root, artifactsDir, state, store) {
2994
3037
  // Resolution present: apply it to items, mark the gate done, archive inputs so
2995
3038
  // it cannot re-halt.
2996
3039
  const resolutions = normalizePlanClarificationResolutions(await readOptionalJsonFile(resolutionPath));
3040
+ // Uniform id-join contract: a resolution naming a finding id outside the plan
3041
+ // is REFUSED whole (archived, nothing applied) and the gate re-halts with the
3042
+ // unknown ids named — the silent-continue alternative drops a host answer on a
3043
+ // typo'd id, leaving its item to fall to mid-run triage unexplained.
3044
+ const validIds = new Set(findings.map((f) => f.id));
3045
+ const unknownIds = resolutions
3046
+ .map((r) => r.finding_id)
3047
+ .filter((id) => !validIds.has(id));
3048
+ if (unknownIds.length > 0) {
3049
+ await withFsRetry(() => rename(resolutionPath, `${resolutionPath}.refused-${Date.now()}`));
3050
+ const candidates = (await readOptionalJsonFile(requestPath)) ??
3051
+ detectPlanAmbiguities(findings, state.items);
3052
+ return writeCurrentStep({
3053
+ stepKind: "collect_clarifications",
3054
+ status: "blocked",
3055
+ runId: stateRunId(state),
3056
+ repoRoot: root,
3057
+ artifactsDir,
3058
+ prompt: ambiguityReviewPrompt(candidates, resolutionPath, findings.map((f) => f.id), `finding id(s) not in the plan: ${unknownIds.map((i) => `\`${i}\``).join(", ")}`),
3059
+ allowedCommands: [loaderCommand("next-step")],
3060
+ stopCondition: "Stop after re-submitting a corrected ambiguity resolution, unless it is already written and the prompt told you to continue.",
3061
+ artifactPaths: {
3062
+ ambiguity_request: requestPath,
3063
+ ambiguity_resolution: resolutionPath,
3064
+ },
3065
+ });
3066
+ }
2997
3067
  const now = new Date().toISOString();
2998
3068
  let changed = false;
2999
3069
  for (const res of resolutions) {
@@ -3861,8 +3931,10 @@ function buildMainObligations(ctx) {
3861
3931
  execute: async (state) => {
3862
3932
  const s = requireState(state);
3863
3933
  if (existsSync(clarificationResolutionPath)) {
3864
- const next = await applyPlanClarificationResolution(artifactsDir, s, store);
3865
- return { kind: "transition", state: next };
3934
+ const outcome = await applyPlanClarificationResolution(root, artifactsDir, s, store);
3935
+ if (outcome.kind === "refused")
3936
+ return { kind: "emit", step: outcome.step };
3937
+ return { kind: "transition", state: outcome.state };
3866
3938
  }
3867
3939
  return {
3868
3940
  kind: "emit",