audit-tools 0.32.44 → 0.32.46
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/args.d.ts.map +1 -1
- package/dist/audit/cli/args.js +2 -4
- package/dist/audit/cli/args.js.map +1 -1
- package/dist/audit/cli/auditStep.d.ts +1 -1
- package/dist/audit/cli/dispatch.d.ts +1 -0
- package/dist/audit/cli/dispatch.d.ts.map +1 -1
- package/dist/audit/cli/dispatch.js +39 -18
- package/dist/audit/cli/dispatch.js.map +1 -1
- package/dist/audit/cli/mergeAndIngestCommand.d.ts +41 -0
- package/dist/audit/cli/mergeAndIngestCommand.d.ts.map +1 -1
- package/dist/audit/cli/mergeAndIngestCommand.js +105 -18
- package/dist/audit/cli/mergeAndIngestCommand.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +3 -3
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +23 -38
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +130 -89
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/ownerTokens.d.ts +21 -0
- package/dist/audit/cli/ownerTokens.d.ts.map +1 -0
- package/dist/audit/cli/ownerTokens.js +86 -0
- package/dist/audit/cli/ownerTokens.js.map +1 -0
- package/dist/audit/cli/resynthesizeCommand.d.ts.map +1 -1
- package/dist/audit/cli/resynthesizeCommand.js +8 -10
- package/dist/audit/cli/resynthesizeCommand.js.map +1 -1
- package/dist/audit/cli/rollingAuditDispatch.d.ts.map +1 -1
- package/dist/audit/cli/rollingAuditDispatch.js +9 -22
- package/dist/audit/cli/rollingAuditDispatch.js.map +1 -1
- package/dist/audit/contracts/wrapperResponse.d.ts +6 -6
- package/dist/audit/extractors/gitHistory.d.ts.map +1 -1
- package/dist/audit/extractors/gitHistory.js +3 -0
- package/dist/audit/extractors/gitHistory.js.map +1 -1
- package/dist/audit/orchestrator/advance.d.ts +4 -1
- package/dist/audit/orchestrator/advance.d.ts.map +1 -1
- package/dist/audit/orchestrator/advance.js +185 -36
- package/dist/audit/orchestrator/advance.js.map +1 -1
- package/dist/audit/orchestrator/fileIntegrity.d.ts.map +1 -1
- package/dist/audit/orchestrator/fileIntegrity.js +21 -32
- package/dist/audit/orchestrator/fileIntegrity.js.map +1 -1
- package/dist/audit/orchestrator/reviewPackets.d.ts.map +1 -1
- package/dist/audit/orchestrator/reviewPackets.js +31 -36
- package/dist/audit/orchestrator/reviewPackets.js.map +1 -1
- package/dist/audit/orchestrator/taskBuilder.d.ts.map +1 -1
- package/dist/audit/orchestrator/taskBuilder.js +12 -24
- package/dist/audit/orchestrator/taskBuilder.js.map +1 -1
- package/dist/audit/providers/opencodeProvider.d.ts +1 -4
- package/dist/audit/providers/opencodeProvider.d.ts.map +1 -1
- package/dist/audit/providers/opencodeProvider.js +6 -9
- package/dist/audit/providers/opencodeProvider.js.map +1 -1
- package/dist/audit/reporting/mergeFindings.d.ts.map +1 -1
- package/dist/audit/reporting/mergeFindings.js +1 -32
- package/dist/audit/reporting/mergeFindings.js.map +1 -1
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +1 -12
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.js +9 -12
- package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
- package/dist/audit/supervisor/sessionConfig.d.ts.map +1 -1
- package/dist/audit/supervisor/sessionConfig.js +25 -35
- package/dist/audit/supervisor/sessionConfig.js.map +1 -1
- package/dist/audit/types/auditState.d.ts +6 -6
- package/dist/remediate/dedup/crossLensDedup.d.ts +2 -1
- package/dist/remediate/dedup/crossLensDedup.d.ts.map +1 -1
- package/dist/remediate/dedup/crossLensDedup.js +4 -33
- package/dist/remediate/dedup/crossLensDedup.js.map +1 -1
- package/dist/remediate/index.d.ts +2 -2
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +5 -8
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/intake.d.ts.map +1 -1
- package/dist/remediate/intake.js +9 -5
- package/dist/remediate/intake.js.map +1 -1
- package/dist/remediate/phases/close.d.ts +59 -1
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +294 -29
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/phases/plan.d.ts +2 -2
- package/dist/remediate/phases/plan.d.ts.map +1 -1
- package/dist/remediate/phases/plan.js +31 -23
- package/dist/remediate/phases/plan.js.map +1 -1
- package/dist/remediate/providers/opencodeProvider.d.ts +1 -4
- package/dist/remediate/providers/opencodeProvider.d.ts.map +1 -1
- package/dist/remediate/providers/opencodeProvider.js +6 -9
- package/dist/remediate/providers/opencodeProvider.js.map +1 -1
- package/dist/remediate/state/store.d.ts +54 -11
- package/dist/remediate/state/store.d.ts.map +1 -1
- package/dist/remediate/state/store.js +37 -64
- package/dist/remediate/state/store.js.map +1 -1
- package/dist/remediate/state/types.d.ts +22 -0
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js +8 -0
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/dispatch/acceptNode.d.ts +38 -0
- package/dist/remediate/steps/dispatch/acceptNode.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/acceptNode.js +70 -24
- package/dist/remediate/steps/dispatch/acceptNode.js.map +1 -1
- package/dist/remediate/steps/dispatch/marshal.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/marshal.js +30 -0
- package/dist/remediate/steps/dispatch/marshal.js.map +1 -1
- package/dist/remediate/steps/dispatch/worktreeLifecycle.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/worktreeLifecycle.js +42 -8
- package/dist/remediate/steps/dispatch/worktreeLifecycle.js.map +1 -1
- package/dist/remediate/steps/finalGate.d.ts +2 -2
- package/dist/remediate/steps/finalGate.d.ts.map +1 -1
- package/dist/remediate/steps/finalGate.js +8 -9
- package/dist/remediate/steps/finalGate.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +12 -0
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +57 -35
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/rollingSession.d.ts.map +1 -1
- package/dist/remediate/steps/rollingSession.js +27 -1
- package/dist/remediate/steps/rollingSession.js.map +1 -1
- package/dist/remediate/utils/fileIntegrity.d.ts.map +1 -1
- package/dist/remediate/utils/fileIntegrity.js +29 -33
- package/dist/remediate/utils/fileIntegrity.js.map +1 -1
- package/dist/remediate/utils/hostAssets.d.ts +4 -1
- package/dist/remediate/utils/hostAssets.d.ts.map +1 -1
- package/dist/remediate/utils/hostAssets.js +4 -1
- package/dist/remediate/utils/hostAssets.js.map +1 -1
- package/dist/remediate/validation/artifacts.d.ts.map +1 -1
- package/dist/remediate/validation/artifacts.js +3 -2
- 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 +3 -1
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/shared/chunkByBudget.d.ts +58 -0
- package/dist/shared/chunkByBudget.d.ts.map +1 -0
- package/dist/shared/chunkByBudget.js +31 -0
- package/dist/shared/chunkByBudget.js.map +1 -0
- package/dist/shared/countBy.d.ts +19 -0
- package/dist/shared/countBy.d.ts.map +1 -0
- package/dist/shared/countBy.js +29 -0
- package/dist/shared/countBy.js.map +1 -0
- package/dist/shared/engine/obligationEngine.d.ts +2 -2
- package/dist/shared/fileIntegrity.d.ts +55 -0
- package/dist/shared/fileIntegrity.d.ts.map +1 -0
- package/dist/shared/fileIntegrity.js +50 -0
- package/dist/shared/fileIntegrity.js.map +1 -0
- package/dist/shared/findingSimilarity.d.ts +26 -0
- package/dist/shared/findingSimilarity.d.ts.map +1 -0
- package/dist/shared/findingSimilarity.js +54 -0
- package/dist/shared/findingSimilarity.js.map +1 -0
- package/dist/shared/friction/stepBoundaryCapture.d.ts +17 -0
- package/dist/shared/friction/stepBoundaryCapture.d.ts.map +1 -1
- package/dist/shared/friction/stepBoundaryCapture.js +21 -0
- package/dist/shared/friction/stepBoundaryCapture.js.map +1 -1
- package/dist/shared/git.d.ts +23 -0
- package/dist/shared/git.d.ts.map +1 -1
- package/dist/shared/git.js +26 -0
- package/dist/shared/git.js.map +1 -1
- package/dist/shared/index.d.ts +11 -4
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +22 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/auditToolsPaths.d.ts +7 -0
- package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
- package/dist/shared/io/auditToolsPaths.js +10 -0
- package/dist/shared/io/auditToolsPaths.js.map +1 -1
- package/dist/shared/io/lockedJsonStore.d.ts +66 -0
- package/dist/shared/io/lockedJsonStore.d.ts.map +1 -0
- package/dist/shared/io/lockedJsonStore.js +59 -0
- package/dist/shared/io/lockedJsonStore.js.map +1 -0
- package/dist/shared/loopCorePaths.d.ts.map +1 -1
- package/dist/shared/loopCorePaths.js +2 -0
- package/dist/shared/loopCorePaths.js.map +1 -1
- package/dist/shared/providers/openAiCompatibleProvider.d.ts.map +1 -1
- package/dist/shared/providers/openAiCompatibleProvider.js +2 -1
- package/dist/shared/providers/openAiCompatibleProvider.js.map +1 -1
- package/dist/shared/providers/opencodeProvider.d.ts +8 -0
- package/dist/shared/providers/opencodeProvider.d.ts.map +1 -1
- package/dist/shared/providers/opencodeProvider.js +10 -0
- package/dist/shared/providers/opencodeProvider.js.map +1 -1
- package/dist/shared/quota/claimRegistry.d.ts +9 -0
- package/dist/shared/quota/claimRegistry.d.ts.map +1 -1
- package/dist/shared/quota/claimRegistry.js +20 -0
- package/dist/shared/quota/claimRegistry.js.map +1 -1
- package/dist/shared/reporting/auditDeliverable.d.ts.map +1 -1
- package/dist/shared/reporting/auditDeliverable.js +14 -9
- package/dist/shared/reporting/auditDeliverable.js.map +1 -1
- package/dist/shared/tooling/exec.d.ts +42 -4
- package/dist/shared/tooling/exec.d.ts.map +1 -1
- package/dist/shared/tooling/exec.js +90 -12
- package/dist/shared/tooling/exec.js.map +1 -1
- package/dist/shared/types/contractPipeline/verification.d.ts +17 -3
- package/dist/shared/types/contractPipeline/verification.d.ts.map +1 -1
- package/dist/shared/types/sessionConfig.d.ts +28 -0
- package/dist/shared/types/sessionConfig.d.ts.map +1 -1
- package/dist/shared/types/sessionConfig.js +35 -0
- package/dist/shared/types/sessionConfig.js.map +1 -1
- package/opencode.json +2 -11
- package/package.json +2 -1
- package/scripts/audit/postinstall.mjs +57 -137
- package/scripts/remediate/postinstall.mjs +41 -169
- package/scripts/shared/install-host-assets.mjs +188 -0
- package/wrapper/audit-code-wrapper-lib.mjs +32 -4
- package/wrapper/audit-code-wrapper-opencode.mjs +41 -25
- package/wrapper/remediate-code-wrapper-opencode.mjs +43 -21
- package/dist/remediate/utils/commands.d.ts +0 -17
- package/dist/remediate/utils/commands.d.ts.map +0 -1
- package/dist/remediate/utils/commands.js +0 -29
- package/dist/remediate/utils/commands.js.map +0 -1
|
@@ -25,8 +25,66 @@ export interface ClosingResult {
|
|
|
25
25
|
action: ClosingAction;
|
|
26
26
|
status: "success" | "failed" | "skipped";
|
|
27
27
|
commands: ClosingCommandResult[];
|
|
28
|
+
/** See `ClosingActionPreviewSchema.leftover_files` — same untouched-dirt set, at execute time. */
|
|
29
|
+
leftover_files?: string[];
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
/** `collectStagingFiles`'s manifest-scoped result. */
|
|
32
|
+
export interface StagingSelection {
|
|
33
|
+
/** Manifest (or deliverable) files that are currently dirty — safe to stage. */
|
|
34
|
+
files: string[];
|
|
35
|
+
/**
|
|
36
|
+
* Currently-dirty files that are NEITHER in `manifest` nor a deliverable —
|
|
37
|
+
* pre-existing/unrelated dirt the run never touched. Never staged; surfaced
|
|
38
|
+
* so the host/user can see what was deliberately left alone.
|
|
39
|
+
*/
|
|
40
|
+
leftover: string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* INVARIANT (V2 fix): remediation close must never commit files the run
|
|
44
|
+
* didn't touch. This is the single chokepoint both the preview
|
|
45
|
+
* (`checkClosingPreview`) and the execute path (`executeClosingAction`) stage
|
|
46
|
+
* through — never a repo-wide `git diff`/`ls-files` sweep.
|
|
47
|
+
*
|
|
48
|
+
* Formula: `files = manifest ∩ currently-dirty` (plus `deliverables`, which
|
|
49
|
+
* are unioned into the effective manifest so they stage like any other
|
|
50
|
+
* manifest entry — see `toolDeliverablePaths`). Any currently-dirty path
|
|
51
|
+
* outside that set is `leftover`: reported, never staged, never committed —
|
|
52
|
+
* committing LESS than a dirty tree is always safe, so a leftover never blocks
|
|
53
|
+
* or aborts the close (a full-abort-on-unrelated-dirt policy would make the
|
|
54
|
+
* tool unusable against a routinely-dirty working tree).
|
|
55
|
+
*
|
|
56
|
+
* TOCTOU note: this recomputes "currently-dirty" fresh on every call (by
|
|
57
|
+
* design — a file the user touched between preview and execute must be
|
|
58
|
+
* re-observed). What it can NEVER do is widen `files` beyond `manifest ∪
|
|
59
|
+
* deliverables`: a newly-dirtied path outside the manifest can only ever land
|
|
60
|
+
* in `leftover`, never `files`. `pre_authorized: true` (see
|
|
61
|
+
* `checkClosingPreview`) only skips the interactive preview step; it does not
|
|
62
|
+
* — and structurally cannot — enlarge what this function is willing to stage.
|
|
63
|
+
*
|
|
64
|
+
* `.env*` is excluded even from an explicit manifest entry (defense-in-depth
|
|
65
|
+
* against ever committing a secret); `.audit-tools/` scratch is excluded
|
|
66
|
+
* unless the path is exactly a caller-supplied deliverable.
|
|
67
|
+
*
|
|
68
|
+
* Path comparison is TWO-TIER: an exact case-preserving key first
|
|
69
|
+
* (`repoPathExactKey` — forward-slash, `./`-stripped), then the canonical
|
|
70
|
+
* lowercased `normalizeRepoPath` key as a fallback ONLY when unambiguous
|
|
71
|
+
* (exactly one dirty path folds to it). The fold tier is what makes a
|
|
72
|
+
* declared `./Src/Foo.ts` match git's `src/Foo.ts` on win32; the exact tier +
|
|
73
|
+
* ambiguity guard is what stops a case-SIBLING pair on a case-sensitive
|
|
74
|
+
* checkout (`Foo.ts` real, `foo.ts` also real and user-dirty) from sweeping
|
|
75
|
+
* the user's file in through the fold. The STAGED output is always git's
|
|
76
|
+
* original-cased path string (never a normalized key — `git add` needs the
|
|
77
|
+
* real on-disk case), and the exclude regexes are tested against the folded
|
|
78
|
+
* key so `.ENV` / `.Audit-Tools/` casing games cannot bypass them.
|
|
79
|
+
*
|
|
80
|
+
* ACCEPTED RESIDUAL (inherent to declared surfaces): in the conversation-first
|
|
81
|
+
* flow a declared-but-never-edited file that the USER dirties DURING the run
|
|
82
|
+
* window (post-plan, pre-close) is indistinguishable from the run's own
|
|
83
|
+
* hand-applied edit — `run_start_dirty` only fences dirt that predates the
|
|
84
|
+
* run. Closing it fully requires per-edit git ground truth, which that flow
|
|
85
|
+
* does not have.
|
|
86
|
+
*/
|
|
87
|
+
export declare function collectStagingFiles(root: string, manifest: string[], deliverables?: string[]): StagingSelection;
|
|
30
88
|
export declare function executeClosingAction(state: RemediationState, options: OrchestratorOptions): ClosingResult;
|
|
31
89
|
export declare function runClosePhase(state: RemediationState, options: OrchestratorOptions, runLogger?: RunLogger): Promise<RemediationState>;
|
|
32
90
|
//# sourceMappingURL=close.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../../src/remediate/phases/close.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../../src/remediate/phases/close.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAsB1D,OAAO,KAAK,EAIV,yBAAyB,EACzB,SAAS,EAIV,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAOV,qBAAqB,EAItB,MAAM,mBAAmB,CAAC;AAiF3B,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,gBAAgB,EACvB,aAAa,EAAE,aAAa,GAC3B,yBAAyB,CA2J3B;AAsDD;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CA6B5C;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,wCAAwC,CAAC;IAC3D,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,kGAAkG;IAClG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA8JD,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAAE,EAClB,YAAY,GAAE,MAAM,EAAO,GAC1B,gBAAgB,CAyClB;AAmED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,mBAAmB,GAC3B,aAAa,CA4Gf;AA6qBD,wBAAsB,aAAa,CACjC,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,mBAAmB,EAC5B,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,gBAAgB,CAAC,CAmL3B"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { remediationBranchName, listQuarantinedCommits, readRemediationBaseBranch, } from "../steps/dispatch.js";
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { dirname, extname, isAbsolute, join, relative } from "node:path";
|
|
3
4
|
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
-
import { AGENT_FEEDBACK_FILENAME, parseReflectionsNdjson, readOptionalJsonFile, readOptionalTextFile, renderProcessFeedbackSection, stagedAndUntracked, writeJsonFile, writeTextFile, RemediationOutcomeStatusSchema, } from "audit-tools/shared";
|
|
5
|
+
import { AGENT_FEEDBACK_FILENAME, normalizeRepoPath, parseReflectionsNdjson, readOptionalJsonFile, readOptionalTextFile, renderProcessFeedbackSection, stagedAndUntracked, writeJsonFile, writeTextFile, RemediationOutcomeStatusSchema, countBy, } from "audit-tools/shared";
|
|
5
6
|
import { CONTRACT_PIPELINE_VERIFICATION_REPORT_VERSION } from "audit-tools/shared";
|
|
6
|
-
import {
|
|
7
|
+
import { runTracked } from "audit-tools/shared";
|
|
7
8
|
import { FAILURE_OUTPUT_TAIL_CHARS } from "./constants.js";
|
|
8
9
|
import { intakePaths } from "../intake.js";
|
|
9
10
|
import { isAuditFindingsReport } from "./plan.js";
|
|
@@ -129,12 +130,32 @@ export function buildRemediationOutcomesReport(state, closingResult) {
|
|
|
129
130
|
outcomes.push(enriched);
|
|
130
131
|
}
|
|
131
132
|
outcomes.sort((a, b) => a.finding_id.localeCompare(b.finding_id));
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
// Zero-filled so every outcome status appears even when unused (byOutcome),
|
|
134
|
+
// countBy(...) supplies the actual counts on top. Spreading the zero-filled
|
|
135
|
+
// base first, then counts, preserves OUTCOME_KEYS insertion order — spread
|
|
136
|
+
// only overwrites an existing key's value, never reorders or appends
|
|
137
|
+
// (outcome is a closed enum, so counts can't introduce an unseen key).
|
|
138
|
+
const byOutcome = {
|
|
139
|
+
...Object.fromEntries(OUTCOME_KEYS.map((key) => [key, 0])),
|
|
140
|
+
...countBy(outcomes, (entry) => entry.outcome),
|
|
141
|
+
};
|
|
142
|
+
// Group outcomes by lens (preserving first-appearance order), then countBy
|
|
143
|
+
// outcome status within each group — same per-lens/per-status counts and
|
|
144
|
+
// insertion order as the original single-pass nested reduce, since
|
|
145
|
+
// `by_lens` is only ever consumed key-sorted downstream (renderOutcomesSummary).
|
|
146
|
+
const lensGroups = new Map();
|
|
134
147
|
for (const entry of outcomes) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
const group = lensGroups.get(entry.lens);
|
|
149
|
+
if (group) {
|
|
150
|
+
group.push(entry);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
lensGroups.set(entry.lens, [entry]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const byLens = {};
|
|
157
|
+
for (const [lens, group] of lensGroups) {
|
|
158
|
+
byLens[lens] = countBy(group, (entry) => entry.outcome);
|
|
138
159
|
}
|
|
139
160
|
const startedEntries = outcomes
|
|
140
161
|
.map((outcome) => ({
|
|
@@ -261,7 +282,7 @@ function commandResult(command, result) {
|
|
|
261
282
|
};
|
|
262
283
|
}
|
|
263
284
|
function runTrackedCommand(root, command, args) {
|
|
264
|
-
const result =
|
|
285
|
+
const result = runTracked([command, ...args], {
|
|
265
286
|
cwd: root,
|
|
266
287
|
stdio: ["ignore", "pipe", "pipe"],
|
|
267
288
|
});
|
|
@@ -270,12 +291,215 @@ function runTrackedCommand(root, command, args) {
|
|
|
270
291
|
function isSuccess(result) {
|
|
271
292
|
return result.exit_code === 0;
|
|
272
293
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
294
|
+
// .env* is an absolute hard-exclude: never stageable under any circumstance
|
|
295
|
+
// (secrets safety net), regardless of manifest or deliverable membership.
|
|
296
|
+
const ENV_EXCLUDE_PATTERN = /^\.env($|\.)/;
|
|
297
|
+
// .audit-tools/ scratch (state.json, locks, per-run steps/results, quarantine
|
|
298
|
+
// refs) is excluded UNLESS the exact path is one of this run's own tool
|
|
299
|
+
// deliverables (see `toolDeliverablePaths`) — those are eligible for staging
|
|
300
|
+
// like any other manifest file.
|
|
301
|
+
const AUDIT_TOOLS_EXCLUDE_PATTERN = /^\.audit-tools\//;
|
|
302
|
+
/**
|
|
303
|
+
* The run's authoritative edit-surface manifest — the ONLY files
|
|
304
|
+
* `collectStagingFiles` may stage (invariant: "remediation close must never
|
|
305
|
+
* commit files the run didn't touch").
|
|
306
|
+
*
|
|
307
|
+
* Two sources, UNION'd (never priority-fallback — see below for why):
|
|
308
|
+
*
|
|
309
|
+
* 1. `state.applied_edit_surface` — ground truth. The union of files every
|
|
310
|
+
* accepted node ACTUALLY cherry-picked into the main tree, captured
|
|
311
|
+
* pre-merge from the node's own git branch diff in `acceptNodeWorktree`
|
|
312
|
+
* (never the worker's self-report) and persisted by
|
|
313
|
+
* `mergeImplementResultsIntoState` (src/remediate/steps/dispatch/marshal.ts).
|
|
314
|
+
* Only populated by the isolated-worktree accept lifecycle (the in-process
|
|
315
|
+
* and host-subagent rolling drivers, `accept-node` / `executeNodeInWorktree`).
|
|
316
|
+
*
|
|
317
|
+
* 2. Each `resolved` item's DECLARED surface — `item_spec.touched_files`
|
|
318
|
+
* unioned with the finding's `affected_files` — for items whose real edit
|
|
319
|
+
* was never captured as git ground truth. This is the only option for the
|
|
320
|
+
* conversation-first hand-driven flow (`remediate-code
|
|
321
|
+
* merge-implement-results` — a FIRST-CLASS dispatch mode, not legacy: the
|
|
322
|
+
* host edits directly in the main tree with no per-node worktree/commit to
|
|
323
|
+
* diff, so `acceptNodeWorktree` never runs and `applied_edit_surface` is
|
|
324
|
+
* never populated for that block).
|
|
325
|
+
*
|
|
326
|
+
* Why UNION and not "prefer (1), fall back to (2) only when (1) is entirely
|
|
327
|
+
* absent": a single run can mix dispatch modes across blocks (one block landed
|
|
328
|
+
* via worktree-accept, a sibling landed via a conversation-first hand-applied
|
|
329
|
+
* edit in the same close). Priority-fallback keyed on whether (1) is non-empty
|
|
330
|
+
* would silently drop the OTHER mode's real edits from the close commit the
|
|
331
|
+
* moment either mode contributed anything — under-staging the tool's own work,
|
|
332
|
+
* which is exactly the failure class this manifest exists to prevent, just in
|
|
333
|
+
* the opposite direction. Only items with `status === "resolved"` (a real
|
|
334
|
+
* edit, never `resolved_no_change`) contribute to source (2) — a `blocked`
|
|
335
|
+
* item's partial/reverted edits must never be treated as a legitimate surface.
|
|
336
|
+
*
|
|
337
|
+
* RUN-START-DIRTY GUARD on source (2) ONLY: declared surfaces are plan-time
|
|
338
|
+
* DECLARATIONS (write-access grants) / audit evidence — never verified against
|
|
339
|
+
* an actual diff. A declared file that was ALREADY dirty when the run started
|
|
340
|
+
* (`state.run_start_dirty`, captured in `runPlanPhase` before any remediation
|
|
341
|
+
* edit exists) cannot be the run's edit, so it is excluded here — otherwise a
|
|
342
|
+
* resolved item declaring a file the run never actually touched would sweep
|
|
343
|
+
* pre-existing user WIP into the closing commit (the exact over-inclusion class
|
|
344
|
+
* this manifest exists to close). Ground-truth entries from source (1) are
|
|
345
|
+
* NEVER excluded by the snapshot: git proved the run landed those paths, and a
|
|
346
|
+
* tool-merged edit to a file the user ALSO had dirty at run start is a path the
|
|
347
|
+
* tool legitimately owns. A state without `run_start_dirty` (pre-field, or a
|
|
348
|
+
* plan flow that bypasses `runPlanPhase`) means no exclusions.
|
|
349
|
+
*
|
|
350
|
+
* Both sources empty is legitimate (e.g. a run that only produced
|
|
351
|
+
* `resolved_no_change` / skipped items) and correctly yields an empty
|
|
352
|
+
* manifest: nothing but tool deliverables gets staged.
|
|
353
|
+
*
|
|
354
|
+
* Membership semantics: entries are compared via the canonical
|
|
355
|
+
* `normalizeRepoPath` key (forward-slash, `./`-stripped, lowercased — the
|
|
356
|
+
* repo's single path normalizer), so a declared `./Src/Foo.ts` matches git's
|
|
357
|
+
* `Src/Foo.ts`. The RETURNED strings keep their original casing; the
|
|
358
|
+
* normalized form is only ever a comparison key (see `collectStagingFiles`).
|
|
359
|
+
*/
|
|
360
|
+
function resolveEditSurfaceManifest(state) {
|
|
361
|
+
const files = new Set(state.applied_edit_surface ?? []);
|
|
362
|
+
const runStartDirtyKeys = new Set((state.run_start_dirty ?? []).map(normalizeRepoPath));
|
|
363
|
+
const addUnlessPreexistingDirt = (path) => {
|
|
364
|
+
if (!runStartDirtyKeys.has(normalizeRepoPath(path)))
|
|
365
|
+
files.add(path);
|
|
366
|
+
};
|
|
367
|
+
const findingsById = new Map((state.plan?.findings ?? []).map((finding) => [finding.id, finding]));
|
|
368
|
+
for (const item of Object.values(state.items ?? {})) {
|
|
369
|
+
if (item.status !== "resolved")
|
|
370
|
+
continue;
|
|
371
|
+
for (const f of item.item_spec?.touched_files ?? []) {
|
|
372
|
+
addUnlessPreexistingDirt(f);
|
|
373
|
+
}
|
|
374
|
+
const finding = findingsById.get(item.finding_id);
|
|
375
|
+
for (const affected of finding?.affected_files ?? []) {
|
|
376
|
+
addUnlessPreexistingDirt(affected.path);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return [...files];
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Repo-relative paths of the tool-emitted root deliverables `.gitignore`
|
|
383
|
+
* re-includes for tracking (`!.audit-tools/remediation-report.md`,
|
|
384
|
+
* `!.audit-tools/remediation-outcomes.json`) — the two files this close phase
|
|
385
|
+
* itself writes (see the end of `runClosePhase`) that are meant to be
|
|
386
|
+
* committed. Computed from `options` (never a hardcoded `.audit-tools/…`
|
|
387
|
+
* literal) so a non-default `--artifacts-dir` resolves correctly.
|
|
388
|
+
*
|
|
389
|
+
* Deliberately excludes `verification_report.json` / `remediation-state.
|
|
390
|
+
* complete.json`: `.gitignore` does NOT re-include those, so they are never
|
|
391
|
+
* visible to `stagedAndUntracked` (which honours `--exclude-standard`) and
|
|
392
|
+
* need no carve-out here.
|
|
393
|
+
*
|
|
394
|
+
* KNOWN ORDERING NOTE: `runClosePhase` calls `executeClosingAction` (which is
|
|
395
|
+
* where `collectStagingFiles` actually runs) BEFORE it writes these two
|
|
396
|
+
* deliverables to disk — their own content describes THIS closing action's
|
|
397
|
+
* outcome (status/commands), so they cannot exist yet at commit time without
|
|
398
|
+
* describing a commit that hasn't happened. Carving them out of the exclude
|
|
399
|
+
* pattern here does not retroactively stage them into the SAME commit; it
|
|
400
|
+
* means that if they are already dirty for any other reason when
|
|
401
|
+
* `collectStagingFiles` runs (re-included per `.gitignore`, tracked from a
|
|
402
|
+
* prior run, hand-edited, etc.), they are correctly treated as legitimate
|
|
403
|
+
* tool output rather than swept out by the blanket `.audit-tools/` exclude.
|
|
404
|
+
*/
|
|
405
|
+
function toolDeliverablePaths(options) {
|
|
406
|
+
const outputDir = dirname(options.artifactsDir);
|
|
407
|
+
return ["remediation-report.md", "remediation-outcomes.json"].map((name) => relative(options.root, join(outputDir, name)).replace(/\\/g, "/"));
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* INVARIANT (V2 fix): remediation close must never commit files the run
|
|
411
|
+
* didn't touch. This is the single chokepoint both the preview
|
|
412
|
+
* (`checkClosingPreview`) and the execute path (`executeClosingAction`) stage
|
|
413
|
+
* through — never a repo-wide `git diff`/`ls-files` sweep.
|
|
414
|
+
*
|
|
415
|
+
* Formula: `files = manifest ∩ currently-dirty` (plus `deliverables`, which
|
|
416
|
+
* are unioned into the effective manifest so they stage like any other
|
|
417
|
+
* manifest entry — see `toolDeliverablePaths`). Any currently-dirty path
|
|
418
|
+
* outside that set is `leftover`: reported, never staged, never committed —
|
|
419
|
+
* committing LESS than a dirty tree is always safe, so a leftover never blocks
|
|
420
|
+
* or aborts the close (a full-abort-on-unrelated-dirt policy would make the
|
|
421
|
+
* tool unusable against a routinely-dirty working tree).
|
|
422
|
+
*
|
|
423
|
+
* TOCTOU note: this recomputes "currently-dirty" fresh on every call (by
|
|
424
|
+
* design — a file the user touched between preview and execute must be
|
|
425
|
+
* re-observed). What it can NEVER do is widen `files` beyond `manifest ∪
|
|
426
|
+
* deliverables`: a newly-dirtied path outside the manifest can only ever land
|
|
427
|
+
* in `leftover`, never `files`. `pre_authorized: true` (see
|
|
428
|
+
* `checkClosingPreview`) only skips the interactive preview step; it does not
|
|
429
|
+
* — and structurally cannot — enlarge what this function is willing to stage.
|
|
430
|
+
*
|
|
431
|
+
* `.env*` is excluded even from an explicit manifest entry (defense-in-depth
|
|
432
|
+
* against ever committing a secret); `.audit-tools/` scratch is excluded
|
|
433
|
+
* unless the path is exactly a caller-supplied deliverable.
|
|
434
|
+
*
|
|
435
|
+
* Path comparison is TWO-TIER: an exact case-preserving key first
|
|
436
|
+
* (`repoPathExactKey` — forward-slash, `./`-stripped), then the canonical
|
|
437
|
+
* lowercased `normalizeRepoPath` key as a fallback ONLY when unambiguous
|
|
438
|
+
* (exactly one dirty path folds to it). The fold tier is what makes a
|
|
439
|
+
* declared `./Src/Foo.ts` match git's `src/Foo.ts` on win32; the exact tier +
|
|
440
|
+
* ambiguity guard is what stops a case-SIBLING pair on a case-sensitive
|
|
441
|
+
* checkout (`Foo.ts` real, `foo.ts` also real and user-dirty) from sweeping
|
|
442
|
+
* the user's file in through the fold. The STAGED output is always git's
|
|
443
|
+
* original-cased path string (never a normalized key — `git add` needs the
|
|
444
|
+
* real on-disk case), and the exclude regexes are tested against the folded
|
|
445
|
+
* key so `.ENV` / `.Audit-Tools/` casing games cannot bypass them.
|
|
446
|
+
*
|
|
447
|
+
* ACCEPTED RESIDUAL (inherent to declared surfaces): in the conversation-first
|
|
448
|
+
* flow a declared-but-never-edited file that the USER dirties DURING the run
|
|
449
|
+
* window (post-plan, pre-close) is indistinguishable from the run's own
|
|
450
|
+
* hand-applied edit — `run_start_dirty` only fences dirt that predates the
|
|
451
|
+
* run. Closing it fully requires per-edit git ground truth, which that flow
|
|
452
|
+
* does not have.
|
|
453
|
+
*/
|
|
454
|
+
export function collectStagingFiles(root, manifest, deliverables = []) {
|
|
455
|
+
const entries = [...manifest, ...deliverables];
|
|
456
|
+
// Two-tier membership: EXACT (case-preserving) match first; the lowercased
|
|
457
|
+
// normalizeRepoPath key only as a fallback, and only when it is UNAMBIGUOUS —
|
|
458
|
+
// i.e. exactly one currently-dirty path folds to that key. On a
|
|
459
|
+
// case-sensitive checkout two REAL files can differ only by case
|
|
460
|
+
// (Foo.ts / foo.ts); a fold-only match would admit the user's case-sibling
|
|
461
|
+
// of a manifest file into the commit — the exact over-inclusion this
|
|
462
|
+
// function exists to prevent. An ambiguous fold therefore stages only the
|
|
463
|
+
// exact-cased match (or none), never both.
|
|
464
|
+
const exactKeys = new Set(entries.map(repoPathExactKey));
|
|
465
|
+
const foldedKeys = new Set(entries.map(normalizeRepoPath));
|
|
466
|
+
const exactDeliverableKeys = new Set(deliverables.map(repoPathExactKey));
|
|
467
|
+
const foldedDeliverableKeys = new Set(deliverables.map(normalizeRepoPath));
|
|
468
|
+
const dirty = [...stagedAndUntracked(root)];
|
|
469
|
+
const foldedDirtyCount = new Map();
|
|
470
|
+
for (const f of dirty) {
|
|
471
|
+
const k = normalizeRepoPath(f);
|
|
472
|
+
foldedDirtyCount.set(k, (foldedDirtyCount.get(k) ?? 0) + 1);
|
|
473
|
+
}
|
|
474
|
+
const unambiguous = (foldedKey) => (foldedDirtyCount.get(foldedKey) ?? 0) === 1;
|
|
475
|
+
const files = [];
|
|
476
|
+
const leftover = [];
|
|
477
|
+
for (const f of dirty) {
|
|
478
|
+
const exact = repoPathExactKey(f);
|
|
479
|
+
const folded = normalizeRepoPath(f);
|
|
480
|
+
if (ENV_EXCLUDE_PATTERN.test(folded))
|
|
481
|
+
continue;
|
|
482
|
+
const isDeliverable = exactDeliverableKeys.has(exact) ||
|
|
483
|
+
(foldedDeliverableKeys.has(folded) && unambiguous(folded));
|
|
484
|
+
if (AUDIT_TOOLS_EXCLUDE_PATTERN.test(folded) && !isDeliverable)
|
|
485
|
+
continue;
|
|
486
|
+
const inManifest = exactKeys.has(exact) || (foldedKeys.has(folded) && unambiguous(folded));
|
|
487
|
+
if (inManifest) {
|
|
488
|
+
files.push(f); // original-cased git path — never a normalized key
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
leftover.push(f);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return { files: files.sort(), leftover: leftover.sort() };
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Case-PRESERVING repo-path comparison key (forward slashes, `./` stripped) —
|
|
498
|
+
* the exact-match tier of `collectStagingFiles`'s two-tier membership. The
|
|
499
|
+
* lowercased `normalizeRepoPath` remains the fold-fallback tier.
|
|
500
|
+
*/
|
|
501
|
+
function repoPathExactKey(path) {
|
|
502
|
+
return path.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
279
503
|
}
|
|
280
504
|
/**
|
|
281
505
|
* Generate a commit message derived from item summaries and finding titles.
|
|
@@ -315,9 +539,13 @@ function checkClosingPreview(state, options) {
|
|
|
315
539
|
return undefined;
|
|
316
540
|
if (!PREVIEW_ACTIONS.has(closingPlan.action))
|
|
317
541
|
return undefined;
|
|
318
|
-
const files = collectStagingFiles(options.root);
|
|
542
|
+
const { files, leftover } = collectStagingFiles(options.root, resolveEditSurfaceManifest(state), toolDeliverablePaths(options));
|
|
319
543
|
const commitMessage = generateCommitMessage(state);
|
|
320
|
-
return {
|
|
544
|
+
return {
|
|
545
|
+
files,
|
|
546
|
+
commit_message: commitMessage,
|
|
547
|
+
...(leftover.length > 0 ? { leftover_files: leftover } : {}),
|
|
548
|
+
};
|
|
321
549
|
}
|
|
322
550
|
export function executeClosingAction(state, options) {
|
|
323
551
|
const action = state.closing_plan.action;
|
|
@@ -335,8 +563,19 @@ export function executeClosingAction(state, options) {
|
|
|
335
563
|
commands.push(result);
|
|
336
564
|
return isSuccess(result);
|
|
337
565
|
};
|
|
566
|
+
// Populated only on the commit/push/open-pr branch below; threaded into
|
|
567
|
+
// every return of this function so the caller always sees the same
|
|
568
|
+
// untouched-user-dirt set the preview (checkClosingPreview) computed.
|
|
569
|
+
let leftoverFiles = [];
|
|
338
570
|
if (action === "commit" || action === "push" || action === "open-pr") {
|
|
339
|
-
|
|
571
|
+
// Recomputed here (not reused from the preview) so a file the user
|
|
572
|
+
// touched between preview and execute is re-observed — but ONLY within
|
|
573
|
+
// the same manifest ∪ deliverables the preview used, never a wider sweep
|
|
574
|
+
// (see collectStagingFiles's doc comment: this is the TOCTOU fix, not a
|
|
575
|
+
// TOCTOU reintroduction).
|
|
576
|
+
const staging = collectStagingFiles(options.root, resolveEditSurfaceManifest(state), toolDeliverablePaths(options));
|
|
577
|
+
leftoverFiles = staging.leftover;
|
|
578
|
+
const files = staging.files;
|
|
340
579
|
// Nothing to stage → vacuous success: no commit, push, or PR is attempted,
|
|
341
580
|
// so `commands` stays empty and the status is success.
|
|
342
581
|
if (files.length === 0) {
|
|
@@ -346,6 +585,7 @@ export function executeClosingAction(state, options) {
|
|
|
346
585
|
action,
|
|
347
586
|
status: "success",
|
|
348
587
|
commands: [],
|
|
588
|
+
...(leftoverFiles.length > 0 ? { leftover_files: leftoverFiles } : {}),
|
|
349
589
|
};
|
|
350
590
|
}
|
|
351
591
|
const commitMessage = state.closing_plan.closing_action_preview?.commit_message
|
|
@@ -410,6 +650,7 @@ export function executeClosingAction(state, options) {
|
|
|
410
650
|
action,
|
|
411
651
|
status: commands.every(isSuccess) ? "success" : "failed",
|
|
412
652
|
commands,
|
|
653
|
+
...(leftoverFiles.length > 0 ? { leftover_files: leftoverFiles } : {}),
|
|
413
654
|
};
|
|
414
655
|
}
|
|
415
656
|
/**
|
|
@@ -426,9 +667,13 @@ function runCombinedTestSuite(state, options) {
|
|
|
426
667
|
? state.plan.test_command.join(" ")
|
|
427
668
|
: state.plan.test_command;
|
|
428
669
|
const startedAt = Date.now();
|
|
429
|
-
|
|
670
|
+
// Windows-aware: force-hide the console window a windowless parent would
|
|
671
|
+
// otherwise pop for this shell child (former `runShellCommand` default).
|
|
672
|
+
const result = spawnSync(state.plan.test_command, {
|
|
430
673
|
cwd: options.root,
|
|
431
674
|
stdio: ["ignore", "pipe", "pipe"],
|
|
675
|
+
shell: true,
|
|
676
|
+
windowsHide: true,
|
|
432
677
|
});
|
|
433
678
|
const durationMs = Date.now() - startedAt;
|
|
434
679
|
if (result.status === 0) {
|
|
@@ -507,9 +752,13 @@ function runE2eTests(state, options) {
|
|
|
507
752
|
return { ran: false, passed: true, output: "" };
|
|
508
753
|
}
|
|
509
754
|
console.log("Running end-to-end tests on combined post-remediation state...");
|
|
510
|
-
|
|
755
|
+
// Windows-aware: force-hide the console window a windowless parent would
|
|
756
|
+
// otherwise pop for this shell child (former `runShellCommand` default).
|
|
757
|
+
const e2eResult = spawnSync(state.plan.e2e_command, {
|
|
511
758
|
cwd: options.root,
|
|
512
759
|
stdio: ["ignore", "pipe", "pipe"],
|
|
760
|
+
shell: true,
|
|
761
|
+
windowsHide: true,
|
|
513
762
|
});
|
|
514
763
|
const e2ePassed = e2eResult.status === 0;
|
|
515
764
|
if (!e2ePassed) {
|
|
@@ -666,6 +915,17 @@ function buildRemediationReportMarkdown(state, entries, closingResult, e2ePassed
|
|
|
666
915
|
}
|
|
667
916
|
reportContent += `\n## Closing Action\n\nAction: ${state.closing_plan.action}\n`;
|
|
668
917
|
reportContent += `Status: ${closingResult.status}\n`;
|
|
918
|
+
// V2 staging-manifest fix, finding 3: leftover (untouched user dirt) must be
|
|
919
|
+
// visible in the HUMAN report too — on a pre_authorized/autonomous run there
|
|
920
|
+
// is no interactive preview, so this section is the only place the user sees
|
|
921
|
+
// what the close deliberately did not commit.
|
|
922
|
+
if (closingResult.leftover_files?.length) {
|
|
923
|
+
reportContent += `\n## Files Left Untouched (not part of this run's edit surface)\n\n`;
|
|
924
|
+
reportContent += `${closingResult.leftover_files.length} dirty file(s) were NOT staged or committed because they are outside this run's edit-surface manifest (pre-existing or unrelated changes — yours to keep, commit, or discard):\n\n`;
|
|
925
|
+
for (const f of closingResult.leftover_files) {
|
|
926
|
+
reportContent += `- \`${f}\`\n`;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
669
929
|
if (e2ePassed !== undefined) {
|
|
670
930
|
reportContent += `\n## End-to-End Tests\n\nResult: ${e2ePassed ? "passed" : "failed"}\n`;
|
|
671
931
|
}
|
|
@@ -717,7 +977,7 @@ function buildRemediationReportMarkdown(state, entries, closingResult, e2ePassed
|
|
|
717
977
|
*/
|
|
718
978
|
async function cleanupTempBranchesAndArtifacts(options, completeState, combinedTest, e2eResult, closingResult, runLogger) {
|
|
719
979
|
try {
|
|
720
|
-
const branchResult =
|
|
980
|
+
const branchResult = runTracked(["git", "branch"], {
|
|
721
981
|
cwd: options.root,
|
|
722
982
|
encoding: "utf8",
|
|
723
983
|
});
|
|
@@ -725,7 +985,7 @@ async function cleanupTempBranchesAndArtifacts(options, completeState, combinedT
|
|
|
725
985
|
for (const branch of branches) {
|
|
726
986
|
const b = branch.replace("*", "").trim();
|
|
727
987
|
if (b.startsWith("remediator-block-")) {
|
|
728
|
-
|
|
988
|
+
runTracked(["git", "branch", "-D", b], { cwd: options.root });
|
|
729
989
|
}
|
|
730
990
|
}
|
|
731
991
|
}
|
|
@@ -817,13 +1077,13 @@ function buildVerificationReport(state, options, closingResult, combinedTest) {
|
|
|
817
1077
|
const traces = [];
|
|
818
1078
|
const itemPassed = isResolved && combinedTest.passed;
|
|
819
1079
|
if (isSkipped) {
|
|
820
|
-
// Ignored/inappropriate items are excluded from
|
|
821
|
-
// get a single
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
1080
|
+
// Ignored/inappropriate items are excluded from the run verdict — they
|
|
1081
|
+
// get a single trace recording the user's settled decision and a
|
|
1082
|
+
// first-class overall_status of "skipped" (see FindingVerificationTrace
|
|
1083
|
+
// doc comment in src/shared/types/contractPipeline/verification.ts).
|
|
1084
|
+
// The trace's own `status` stays "failed" because it did not verify
|
|
1085
|
+
// anything (there is no passing evidence); the finding-level
|
|
1086
|
+
// "skipped" is what excludes it from the report-level verdict below.
|
|
827
1087
|
traces.push({
|
|
828
1088
|
trace_id: `${item.finding_id}:skipped`,
|
|
829
1089
|
kind: "task",
|
|
@@ -1019,6 +1279,11 @@ export async function runClosePhase(state, options, runLogger) {
|
|
|
1019
1279
|
action: state.closing_plan.action,
|
|
1020
1280
|
status: closingResult.status,
|
|
1021
1281
|
commands: closingResult.commands,
|
|
1282
|
+
// Untouched-user-dirt set collectStagingFiles left alone (never staged) —
|
|
1283
|
+
// see ClosingResult.leftover_files / ClosingActionPreviewSchema.leftover_files.
|
|
1284
|
+
...(closingResult.leftover_files?.length
|
|
1285
|
+
? { leftover_files: closingResult.leftover_files }
|
|
1286
|
+
: {}),
|
|
1022
1287
|
},
|
|
1023
1288
|
...(outcomeCoverage ? { plan_coverage: outcomeCoverage } : {}),
|
|
1024
1289
|
};
|