@ashx-j/lunr 0.2.19 → 0.2.21
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/CHANGELOG.md +36 -1
- package/README.md +2 -2
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +0 -2
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-todos.d.ts +6 -5
- package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-todos.js +9 -7
- package/dist/builtin-extensions/lunr-todos.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts +6 -4
- package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/broker/spawn.js +114 -61
- package/dist/builtin-extensions/pi-intercom/broker/spawn.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +360 -351
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +8 -6
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +128 -57
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +18 -8
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +38 -13
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +24 -14
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +8 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +247 -18
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts +29 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js +179 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts +89 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js +541 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +11 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +38 -12
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +3 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +35 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +7 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +26 -4
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +55 -38
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +100 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +46 -31
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +3 -6
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +64 -5
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts +15 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js +14 -4
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +21 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +0 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +71 -25
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +75 -18
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +13 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +67 -11
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +7 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +13 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +5 -10
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +68 -11
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +12 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +21 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +21 -4
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +7 -0
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +11 -7
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +6 -0
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +46 -29
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +46 -4
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +407 -450
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts +2 -13
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js +5 -91
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js.map +1 -1
- package/dist/core/customize.d.ts +2 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +3 -0
- package/dist/core/customize.js.map +1 -1
- package/dist/core/permissions.d.ts +1 -0
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +8 -4
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +0 -5
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/runtime-bridges.d.ts.map +1 -1
- package/dist/core/runtime-bridges.js +0 -2
- package/dist/core/runtime-bridges.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-cancellation.d.ts +21 -0
- package/dist/core/subagent-cancellation.d.ts.map +1 -0
- package/dist/core/subagent-cancellation.js +53 -0
- package/dist/core/subagent-cancellation.js.map +1 -0
- package/dist/core/subagent-spinner-setting.d.ts +6 -0
- package/dist/core/subagent-spinner-setting.d.ts.map +1 -0
- package/dist/core/subagent-spinner-setting.js +12 -0
- package/dist/core/subagent-spinner-setting.js.map +1 -0
- package/dist/core/subagent-spinner.d.ts +4 -0
- package/dist/core/subagent-spinner.d.ts.map +1 -0
- package/dist/core/subagent-spinner.js +6 -0
- package/dist/core/subagent-spinner.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +12 -18
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +73 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/extension-widget-order.d.ts +8 -0
- package/dist/modes/interactive/extension-widget-order.d.ts.map +1 -0
- package/dist/modes/interactive/extension-widget-order.js +37 -0
- package/dist/modes/interactive/extension-widget-order.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +44 -22
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/node-runtime/{agent-session-services-35WNVGMR.js → agent-session-services-C743TYQE.js} +6 -5
- package/dist/node-runtime/{builtin-extensions-322XDDEM.js → builtin-extensions-JCOPAAB5.js} +3 -2
- package/dist/node-runtime/{chunk-XATUM2HK.js → chunk-25I2BHAA.js} +1 -1
- package/dist/node-runtime/{chunk-TXHKBRHZ.js → chunk-2BUAYZ7K.js} +136 -53
- package/dist/node-runtime/chunk-5ZH3ZFIZ.js +308 -0
- package/dist/node-runtime/{chunk-J26TSGDL.js → chunk-6N22HULO.js} +8 -5
- package/dist/node-runtime/chunk-A3KMVBZV.js +60 -0
- package/dist/node-runtime/{chunk-4FX25LWP.js → chunk-AWVD5IEY.js} +20 -421
- package/dist/node-runtime/{chunk-ZRCBN365.js → chunk-GB2MGFLI.js} +12 -12
- package/dist/node-runtime/chunk-LISUTZS3.js +19 -0
- package/dist/node-runtime/{chunk-PNJSG3IZ.js → chunk-LTYWTR3V.js} +1 -369
- package/dist/node-runtime/{chunk-TFBIJTM3.js → chunk-PDNXVXMD.js} +13 -13
- package/dist/node-runtime/{chunk-MCUNIRD5.js → chunk-SY2TFMLO.js} +532 -261
- package/dist/node-runtime/{chunk-HQCKFM2O.js → chunk-VGYZ3APQ.js} +3 -0
- package/dist/node-runtime/{chunk-4DJ6HEGH.js → chunk-VZR3NCM7.js} +3 -3
- package/dist/node-runtime/chunk-W3SILANP.js +24 -0
- package/dist/node-runtime/{chunk-CBGB57XT.js → chunk-WLLDXJRT.js} +10 -0
- package/dist/node-runtime/{chunk-ATGA2WLC.js → chunk-XI77KVKA.js} +399 -36
- package/dist/node-runtime/{chunk-4CSFRBCN.js → chunk-Y5KOEMBH.js} +1 -1
- package/dist/node-runtime/{chunk-AROOY4BB.js → chunk-YMEAYRBE.js} +1 -1
- package/dist/node-runtime/{command-DWUM5X3U.js → command-P4NYCJ2M.js} +1 -1
- package/dist/node-runtime/{customize-XEXL2DGQ.js → customize-LF5VST47.js} +1 -1
- package/dist/node-runtime/{gateway-UZXMFGWY.js → gateway-BM6NLOH4.js} +2 -2
- package/dist/node-runtime/index.js +16 -13
- package/dist/node-runtime/{interactive-mode-QT5IKGQ5.js → interactive-mode-EVVBA64D.js} +11 -8
- package/dist/node-runtime/{lunr-cron-QPBTKBGI.js → lunr-cron-4VHP4E4B.js} +2 -2
- package/dist/node-runtime/main.js +14 -12
- package/dist/node-runtime/{package-manager-cli-7RKSZGEN.js → package-manager-cli-N37546V5.js} +5 -4
- package/dist/node-runtime/{pi-intercom-EEMBGQID.js → pi-intercom-NV2LW6X7.js} +108 -87
- package/dist/node-runtime/{pi-subagents-7YJ3I4VE.js → pi-subagents-6MUIFNDM.js} +1262 -599
- package/dist/node-runtime/rpc-entry.js +14 -12
- package/dist/node-runtime/{runtime-bridges-3RKBDLNA.js → runtime-bridges-2VQTLPOZ.js} +3 -5
- package/dist/node-runtime/{settings-manager-GHPTUN7R.js → settings-manager-CUGAZWAB.js} +2 -1
- package/dist/node-runtime/{subagent-executor-5Q5A4HJ2.js → subagent-executor-MH4A7CCF.js} +181 -294
- package/dist/node-runtime/{update-cli-L3JIBH2U.js → update-cli-XTZMZGOL.js} +2 -1
- package/docs/features.md +22 -4
- package/docs/keybindings.md +3 -1
- package/docs/settings.md +3 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/builtin-extensions/lunr-settings-tools.d.ts +0 -4
- package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +0 -1
- package/dist/builtin-extensions/lunr-settings-tools.js +0 -120
- package/dist/builtin-extensions/lunr-settings-tools.js.map +0 -1
- package/dist/core/settings-tools-bridge.d.ts +0 -35
- package/dist/core/settings-tools-bridge.d.ts.map +0 -1
- package/dist/core/settings-tools-bridge.js +0 -56
- package/dist/core/settings-tools-bridge.js.map +0 -1
- package/dist/node-runtime/chunk-3NMWLTPV.js +0 -64
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AcceptanceConfig, AcceptanceInput, AcceptanceLedger, AcceptanceReport, AcceptanceRole, AcceptanceReviewResult, ResolvedAcceptanceConfig, SingleResult, SubagentRunMode } from "../../shared/types.ts";
|
|
2
2
|
export declare function normalizeAcceptanceInput(input: AcceptanceInput | undefined): AcceptanceConfig;
|
|
3
3
|
export declare function validateAcceptanceInput(input: unknown, pathLabel?: string): string[];
|
|
4
|
+
export declare function validatePersistedAcceptance(input: unknown, pathLabel?: string): string[];
|
|
5
|
+
export declare function restorePersistedAcceptance(input: unknown, pathLabel?: string): ResolvedAcceptanceConfig;
|
|
4
6
|
export declare function validateExecutionAcceptance(input: {
|
|
5
7
|
acceptance?: unknown;
|
|
6
8
|
tasks?: Array<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acceptance.d.ts","sourceRoot":"","sources":["../../../../../../src/builtin-extensions/pi-subagents/src/runs/shared/acceptance.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,gBAAgB,EAEhB,eAAe,EACf,gBAAgB,EAEhB,gBAAgB,EAChB,cAAc,EAGd,sBAAsB,EAGtB,wBAAwB,EAExB,YAAY,EACZ,eAAe,EACf,MAAM,uBAAuB,CAAC;AAqH/B,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,gBAAgB,CAK7F;AAMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAe,GAAG,MAAM,EAAE,CAyH1F;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC;QACb,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC,GAAG;YAAE,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;KACtE,CAAC,CAAC;CACH,GAAG,MAAM,EAAE,CAgBX;AAgBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,wBAAwB,CA6B3B;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,wBAAwB,GAAG,MAAM,CAiDnF;AAmOD,eAAO,MAAM,2BAA2B,4CAA4C,CAAC;AAErF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgDnG;AAuBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqB5D;AAuLD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAChD,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,gBAAgB,CA2BnB;AA4ED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC/C,UAAU,EAAE,wBAAwB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACxE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgF5B;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,wBAAwB,EAAE,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,CAY3I;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CASrF","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"node:child_process\";\nimport { spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tAcceptanceConfig,\n\tAcceptanceEvidenceKind,\n\tAcceptanceInput,\n\tAcceptanceLedger,\n\tAcceptanceLevel,\n\tAcceptanceReport,\n\tAcceptanceRole,\n\tAcceptanceRuntimeCheck,\n\tAcceptanceRuntimeCheckStatus,\n\tAcceptanceReviewResult,\n\tAcceptanceVerifyCommand,\n\tAcceptanceVerifyResult,\n\tResolvedAcceptanceConfig,\n\tResolvedAcceptanceGate,\n\tSingleResult,\n\tSubagentRunMode,\n} from \"../../shared/types.ts\";\nimport { classifyTaskMutationIntent, taskMayMutate } from \"./task-intent.ts\";\n\nconst LEVEL_RANK: Record<Exclude<AcceptanceLevel, \"auto\">, number> = {\n\tnone: 0,\n\tattested: 1,\n\tchecked: 2,\n\tverified: 3,\n\treviewed: 4,\n};\n\nconst VALID_LEVELS = new Set<AcceptanceLevel>([\"auto\", \"none\", \"attested\", \"checked\", \"verified\", \"reviewed\"]);\nconst VALID_EVIDENCE = new Set<AcceptanceEvidenceKind>([\n\t\"changed-files\",\n\t\"tests-added\",\n\t\"commands-run\",\n\t\"validation-output\",\n\t\"residual-risks\",\n\t\"no-staged-files\",\n\t\"diff-summary\",\n\t\"review-findings\",\n\t\"manual-notes\",\n]);\nconst ACCEPTANCE_CONFIG_KEYS = new Set([\"level\", \"criteria\", \"evidence\", \"verify\", \"review\", \"stopRules\", \"reason\"]);\nconst ACCEPTANCE_GATE_KEYS = new Set([\"id\", \"must\", \"evidence\", \"severity\"]);\nconst ACCEPTANCE_VERIFY_KEYS = new Set([\"id\", \"command\", \"timeoutMs\", \"cwd\", \"env\", \"allowFailure\"]);\nconst ACCEPTANCE_REVIEW_KEYS = new Set([\"agent\", \"focus\", \"required\"]);\nconst EXPLICIT_REVIEWED_UNAVAILABLE = \"cannot be requested explicitly because this run cannot supply an independent reviewer result; use checked/verified and orchestrate the reviewer separately, or omit acceptance for read-only review tasks.\";\n\nfunction normalizeLevel(level: AcceptanceLevel | undefined): Exclude<AcceptanceLevel, \"auto\"> | \"auto\" {\n\treturn level ?? \"auto\";\n}\n\nfunction unique<T>(items: T[]): T[] {\n\treturn [...new Set(items)];\n}\n\nfunction requiredEvidenceForLevel(level: Exclude<AcceptanceLevel, \"auto\">): AcceptanceEvidenceKind[] {\n\tswitch (level) {\n\t\tcase \"none\":\n\t\t\treturn [];\n\t\tcase \"attested\":\n\t\t\treturn [\"manual-notes\", \"residual-risks\"];\n\t\tcase \"checked\":\n\t\t\treturn [\"changed-files\", \"tests-added\", \"commands-run\", \"residual-risks\", \"no-staged-files\"];\n\t\tcase \"verified\":\n\t\tcase \"reviewed\":\n\t\t\treturn [\"changed-files\", \"tests-added\", \"commands-run\", \"validation-output\", \"residual-risks\", \"no-staged-files\"];\n\t}\n}\n\nfunction inferLevel(input: {\n\tagentName?: string;\n\tpermissions?: \"full\" | \"read-only\";\n\tacceptanceRole?: AcceptanceRole;\n\ttask?: string;\n\tmode?: SubagentRunMode;\n\tasync?: boolean;\n\tdynamic?: boolean;\n\tdynamicGroup?: boolean;\n}): { level: Exclude<AcceptanceLevel, \"auto\">; reasons: string[]; criteria: string[]; evidence: AcceptanceEvidenceKind[]; review?: { agent?: string; required?: boolean } } {\n\tconst permissions = input.permissions\n\t\t?? (input.acceptanceRole === \"read-only\" ? \"read-only\" : \"full\");\n\tconst task = input.task?.toLowerCase() ?? \"\";\n\tconst reasons: string[] = [];\n\tconst intent = classifyTaskMutationIntent(permissions, input.task ?? \"\");\n\tconst readOnlyPermission = permissions === \"read-only\";\n\tconst readOnlyTask = readOnlyPermission\n\t\t|| intent.kind === \"read-only\"\n\t\t|| (intent.kind === \"unknown\" && /\\b(?:read[- ]only|review[- ]only|no edits|without edits|inspect|summari[sz]e)\\b/.test(task));\n\tconst taskMayWrite = !readOnlyPermission && (taskMayMutate(input.task ?? \"\") || intent.kind === \"implementation\");\n\tconst writeTask = taskMayWrite && !readOnlyTask;\n\tconst inferredReadOnly = readOnlyPermission || readOnlyTask;\n\tconst risky = !inferredReadOnly && (\n\t\tBoolean(input.async && writeTask)\n\t\t|| Boolean(input.dynamic)\n\t\t|| Boolean(input.dynamicGroup)\n\t\t|| /\\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\\b/.test(task)\n\t);\n\n\tif (inferredReadOnly) {\n\t\treasons.push(readOnlyPermission ? \"read-only child permission\" : \"read-only task wording\");\n\t\treturn {\n\t\t\tlevel: \"attested\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Return concrete findings with file paths and severity when applicable\"],\n\t\t\tevidence: [\"review-findings\", \"residual-risks\"],\n\t\t};\n\t}\n\tif (risky) {\n\t\treasons.push(input.async ? \"async write-capable or risky run\" : \"risky write-capable run\");\n\t\tif (input.dynamic || input.dynamicGroup) reasons.push(\"dynamic fanout context\");\n\t\treturn {\n\t\t\tlevel: \"reviewed\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Implement the requested change without widening scope\", \"Return evidence sufficient for an independent acceptance review\"],\n\t\t\tevidence: requiredEvidenceForLevel(\"reviewed\"),\n\t\t};\n\t}\n\tif (writeTask) {\n\t\treasons.push(\"write-capable task with full child permission\");\n\t\treturn {\n\t\t\tlevel: \"checked\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Implement the requested change without widening scope\"],\n\t\t\tevidence: requiredEvidenceForLevel(\"checked\"),\n\t\t};\n\t}\n\treasons.push(\"default lightweight attestation\");\n\treturn {\n\t\tlevel: \"attested\",\n\t\treasons,\n\t\tcriteria: [\"Return a concise result and residual risks when applicable\"],\n\t\tevidence: [\"manual-notes\", \"residual-risks\"],\n\t};\n}\n\nexport function normalizeAcceptanceInput(input: AcceptanceInput | undefined): AcceptanceConfig {\n\tif (input === undefined || input === \"auto\") return { level: \"auto\" };\n\tif (input === false) return { level: \"none\", reason: \"disabled by deprecated false shorthand\" };\n\tif (typeof input === \"string\") return { level: input };\n\treturn { ...input };\n}\n\nfunction explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {\n\treturn explicit.level === \"none\" && typeof explicit.reason === \"string\" && explicit.reason.trim().length > 0;\n}\n\nexport function validateAcceptanceInput(input: unknown, pathLabel = \"acceptance\"): string[] {\n\tconst errors: string[] = [];\n\tif (input === undefined) return errors;\n\tif (input === false) return errors;\n\tif (typeof input === \"string\") {\n\t\tif (!VALID_LEVELS.has(input as AcceptanceLevel)) errors.push(`${pathLabel} has invalid level '${input}'.`);\n\t\telse if (input === \"none\") errors.push(`${pathLabel} level \"none\" requires a reason; use { level: \"none\", reason: \"...\" }.`);\n\t\telse if (input === \"reviewed\") errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);\n\t\treturn errors;\n\t}\n\tif (!input || typeof input !== \"object\" || Array.isArray(input)) {\n\t\terrors.push(`${pathLabel} must be a string level, false, or an object.`);\n\t\treturn errors;\n\t}\n\tconst value = input as Record<string, unknown>;\n\tfor (const key of Object.keys(value)) {\n\t\tif (!ACCEPTANCE_CONFIG_KEYS.has(key)) errors.push(`${pathLabel}.${key} is not supported.`);\n\t}\n\tif (value.level !== undefined && (typeof value.level !== \"string\" || !VALID_LEVELS.has(value.level as AcceptanceLevel))) {\n\t\terrors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified, reviewed.`);\n\t}\n\tif (value.level === \"none\" && (typeof value.reason !== \"string\" || !value.reason.trim())) {\n\t\terrors.push(`${pathLabel}.reason is required when level is none.`);\n\t}\n\tif (value.level === \"reviewed\") errors.push(`${pathLabel}.level ${EXPLICIT_REVIEWED_UNAVAILABLE}`);\n\tif (value.reason !== undefined && typeof value.reason !== \"string\") errors.push(`${pathLabel}.reason must be a string.`);\n\tif (value.criteria !== undefined && !Array.isArray(value.criteria)) errors.push(`${pathLabel}.criteria must be an array.`);\n\tif (Array.isArray(value.criteria)) {\n\t\tconst criterionIds = new Set<string>();\n\t\tfor (const [index, criterion] of value.criteria.entries()) {\n\t\t\tif (typeof criterion === \"string\") continue;\n\t\t\tconst criterionPath = `${pathLabel}.criteria[${index}]`;\n\t\t\tif (!criterion || typeof criterion !== \"object\" || Array.isArray(criterion)) {\n\t\t\t\terrors.push(`${criterionPath} must be a string or an object.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst gate = criterion as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(gate)) {\n\t\t\t\tif (!ACCEPTANCE_GATE_KEYS.has(key)) errors.push(`${criterionPath}.${key} is not supported.`);\n\t\t\t}\n\t\t\tif (typeof gate.id !== \"string\" || !gate.id.trim()) {\n\t\t\t\terrors.push(`${criterionPath}.id is required.`);\n\t\t\t} else {\n\t\t\t\tconst normalizedId = normalizedToken(gate.id);\n\t\t\t\tif (criterionIds.has(normalizedId)) errors.push(`${criterionPath}.id duplicates normalized criterion id '${normalizedId}'.`);\n\t\t\t\tcriterionIds.add(normalizedId);\n\t\t\t}\n\t\t\tif (typeof gate.must !== \"string\" || !gate.must.trim()) errors.push(`${criterionPath}.must is required.`);\n\t\t\tif (gate.evidence !== undefined && !Array.isArray(gate.evidence)) errors.push(`${criterionPath}.evidence must be an array.`);\n\t\t\tif (Array.isArray(gate.evidence)) {\n\t\t\t\tfor (const [evidenceIndex, item] of gate.evidence.entries()) {\n\t\t\t\t\tif (typeof item !== \"string\" || !VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)) {\n\t\t\t\t\t\terrors.push(`${criterionPath}.evidence[${evidenceIndex}] is not a supported evidence kind.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (gate.severity !== undefined && gate.severity !== \"required\" && gate.severity !== \"recommended\") {\n\t\t\t\terrors.push(`${criterionPath}.severity must be required or recommended.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (Array.isArray(value.evidence)) {\n\t\tfor (const [index, item] of value.evidence.entries()) {\n\t\t\tif (typeof item !== \"string\" || !VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)) {\n\t\t\t\terrors.push(`${pathLabel}.evidence[${index}] is not a supported evidence kind.`);\n\t\t\t}\n\t\t}\n\t} else if (value.evidence !== undefined) {\n\t\terrors.push(`${pathLabel}.evidence must be an array.`);\n\t}\n\tif (value.verify !== undefined && !Array.isArray(value.verify)) errors.push(`${pathLabel}.verify must be an array.`);\n\tif (Array.isArray(value.verify)) {\n\t\tfor (const [index, command] of value.verify.entries()) {\n\t\t\tif (!command || typeof command !== \"object\" || Array.isArray(command)) {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}] must be an object.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst cmd = command as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(cmd)) {\n\t\t\t\tif (!ACCEPTANCE_VERIFY_KEYS.has(key)) errors.push(`${pathLabel}.verify[${index}].${key} is not supported.`);\n\t\t\t}\n\t\t\tif (typeof cmd.id !== \"string\" || !cmd.id.trim()) errors.push(`${pathLabel}.verify[${index}].id is required.`);\n\t\t\tif (typeof cmd.command !== \"string\" || !cmd.command.trim()) errors.push(`${pathLabel}.verify[${index}].command is required.`);\n\t\t\tif (cmd.timeoutMs !== undefined && (typeof cmd.timeoutMs !== \"number\" || !Number.isInteger(cmd.timeoutMs) || cmd.timeoutMs < 1)) {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].timeoutMs must be an integer >= 1.`);\n\t\t\t}\n\t\t\tif (cmd.cwd !== undefined && typeof cmd.cwd !== \"string\") errors.push(`${pathLabel}.verify[${index}].cwd must be a string.`);\n\t\t\tif (cmd.env !== undefined) {\n\t\t\t\tif (!cmd.env || typeof cmd.env !== \"object\" || Array.isArray(cmd.env)) {\n\t\t\t\t\terrors.push(`${pathLabel}.verify[${index}].env must be an object.`);\n\t\t\t\t} else {\n\t\t\t\t\tfor (const [envKey, envValue] of Object.entries(cmd.env as Record<string, unknown>)) {\n\t\t\t\t\t\tif (typeof envValue !== \"string\") errors.push(`${pathLabel}.verify[${index}].env.${envKey} must be a string.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cmd.allowFailure !== undefined && typeof cmd.allowFailure !== \"boolean\") {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].allowFailure must be a boolean.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (value.review !== undefined && value.review !== false) {\n\t\tif (!value.review || typeof value.review !== \"object\" || Array.isArray(value.review)) {\n\t\t\terrors.push(`${pathLabel}.review must be false or an object.`);\n\t\t} else {\n\t\t\tconst review = value.review as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(review)) {\n\t\t\t\tif (!ACCEPTANCE_REVIEW_KEYS.has(key)) errors.push(`${pathLabel}.review.${key} is not supported.`);\n\t\t\t}\n\t\t\tif (review.agent !== undefined && typeof review.agent !== \"string\") errors.push(`${pathLabel}.review.agent must be a string.`);\n\t\t\tif (review.focus !== undefined && typeof review.focus !== \"string\") errors.push(`${pathLabel}.review.focus must be a string.`);\n\t\t\tif (review.required !== undefined && typeof review.required !== \"boolean\") errors.push(`${pathLabel}.review.required must be a boolean.`);\n\t\t}\n\t}\n\tif (value.stopRules !== undefined && !Array.isArray(value.stopRules)) errors.push(`${pathLabel}.stopRules must be an array.`);\n\tif (Array.isArray(value.stopRules)) {\n\t\tfor (const [index, item] of value.stopRules.entries()) {\n\t\t\tif (typeof item !== \"string\") errors.push(`${pathLabel}.stopRules[${index}] must be a string.`);\n\t\t}\n\t}\n\treturn errors;\n}\n\nexport function validateExecutionAcceptance(input: {\n\tacceptance?: unknown;\n\ttasks?: Array<{ acceptance?: unknown }>;\n\tchain?: Array<{\n\t\tacceptance?: unknown;\n\t\tparallel?: Array<{ acceptance?: unknown }> | { acceptance?: unknown };\n\t}>;\n}): string[] {\n\tconst errors = validateAcceptanceInput(input.acceptance, \"acceptance\");\n\tfor (const [index, task] of (input.tasks ?? []).entries()) {\n\t\terrors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));\n\t}\n\tfor (const [stepIndex, step] of (input.chain ?? []).entries()) {\n\t\terrors.push(...validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`));\n\t\tif (Array.isArray(step.parallel)) {\n\t\t\tfor (const [taskIndex, task] of step.parallel.entries()) {\n\t\t\t\terrors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));\n\t\t\t}\n\t\t} else if (step.parallel) {\n\t\t\terrors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));\n\t\t}\n\t}\n\treturn errors;\n}\n\nfunction normalizeCriteria(criteria: Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }> | undefined, evidence: AcceptanceEvidenceKind[]): ResolvedAcceptanceGate[] {\n\treturn (criteria ?? []).map((criterion, index) => {\n\t\tif (typeof criterion === \"string\") {\n\t\t\treturn { id: `criterion-${index + 1}`, must: criterion, evidence, severity: \"required\" };\n\t\t}\n\t\treturn {\n\t\t\tid: criterion.id?.trim() || `criterion-${index + 1}`,\n\t\t\tmust: criterion.must ?? \"\",\n\t\t\tevidence: criterion.evidence?.filter((item) => VALID_EVIDENCE.has(item)) ?? evidence,\n\t\t\tseverity: criterion.severity ?? \"required\",\n\t\t};\n\t}).filter((criterion) => criterion.must.trim());\n}\n\nexport function resolveEffectiveAcceptance(input: {\n\texplicit?: AcceptanceInput;\n\tagentName?: string;\n\tpermissions?: \"full\" | \"read-only\";\n\tacceptanceRole?: AcceptanceRole;\n\ttask?: string;\n\tmode?: SubagentRunMode;\n\tasync?: boolean;\n\tdynamic?: boolean;\n\tdynamicGroup?: boolean;\n}): ResolvedAcceptanceConfig {\n\tconst explicit = normalizeAcceptanceInput(input.explicit);\n\tconst inferred = inferLevel(input);\n\tconst explicitLevel = normalizeLevel(explicit.level);\n\tconst level = explicitAcceptanceCanDisable(explicit)\n\t\t? \"none\"\n\t\t: explicitLevel === \"auto\"\n\t\t\t? inferred.level\n\t\t\t: (LEVEL_RANK[explicitLevel] >= LEVEL_RANK[inferred.level] ? explicitLevel : inferred.level);\n\tconst evidence = unique([...(level === inferred.level ? inferred.evidence : requiredEvidenceForLevel(level)), ...(explicit.evidence ?? [])]);\n\tconst criteria = normalizeCriteria(\n\t\t(explicit.criteria?.length ? explicit.criteria : inferred.criteria) as Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }>,\n\t\tevidence,\n\t);\n\tlet review = explicit.review !== undefined ? explicit.review : inferred.review;\n\tif (level === \"reviewed\" && input.explicit !== undefined && explicitLevel !== \"reviewed\" && explicit.review === undefined && review && review !== false) {\n\t\treview = { ...review, required: false };\n\t}\n\treturn {\n\t\tlevel,\n\t\texplicit: input.explicit !== undefined,\n\t\tinferredReason: inferred.reasons,\n\t\tcriteria,\n\t\tevidence,\n\t\tverify: explicit.verify ?? [],\n\t\treview,\n\t\tstopRules: explicit.stopRules ?? [],\n\t\treason: explicit.reason,\n\t};\n}\n\nexport function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig): string {\n\tif (acceptance.level === \"none\") return \"\";\n\tconst lines = [\n\t\t\"\",\n\t\t\"## Acceptance Contract\",\n\t\t`Acceptance level: ${acceptance.level}`,\n\t\t\"Completion is not accepted from prose alone. End with a structured acceptance report.\",\n\t\t\"\",\n\t\t\"Criteria:\",\n\t\t...(acceptance.criteria.length ? acceptance.criteria.map((criterion) => `- ${criterion.id}: ${criterion.must}`) : [\"- Return the requested result.\"]),\n\t\t\"\",\n\t\t`Required evidence: ${acceptance.evidence.join(\", \") || \"none\"}`,\n\t];\n\tif (acceptance.verify.length > 0) {\n\t\tlines.push(\"\", \"Runtime verification commands configured by parent:\");\n\t\tfor (const command of acceptance.verify) lines.push(`- ${command.id}: ${command.command}`);\n\t}\n\tif (acceptance.review && acceptance.review !== false) {\n\t\tlines.push(\"\", `Review gate: ${acceptance.review.required === false ? \"optional\" : \"required\"}${acceptance.review.agent ? ` by ${acceptance.review.agent}` : \"\"}.`);\n\t\tif (acceptance.review.focus) lines.push(`Review focus: ${acceptance.review.focus}`);\n\t}\n\tif (acceptance.stopRules.length > 0) {\n\t\tlines.push(\"\", \"Stop rules:\", ...acceptance.stopRules.map((rule) => `- ${rule}`));\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"Finish with a fenced JSON block tagged `acceptance-report` in this shape:\",\n\t\t\"Use empty arrays when no items apply; array fields contain strings unless object entries are shown.\",\n\t\t\"`criteriaSatisfied[].status` must be exactly one of: satisfied, not-satisfied, not-applicable.\",\n\t\t\"`commandsRun[].result` must be exactly one of: passed, failed, not-run.\",\n\t\t\"`manualNotes` and `notes` are optional strings; an empty string means no note and does not satisfy `manual-notes` evidence.\",\n\t\t\"```acceptance-report\",\n\t\tJSON.stringify({\n\t\t\tcriteriaSatisfied: acceptance.criteria\n\t\t\t\t.filter((criterion) => criterion.severity !== \"recommended\")\n\t\t\t\t.map((criterion) => ({ id: criterion.id, status: \"satisfied\", evidence: \"specific proof\" })),\n\t\t\tchangedFiles: [\"src/file.ts\"],\n\t\t\ttestsAddedOrUpdated: [\"test/file.test.ts\"],\n\t\t\tcommandsRun: [{ command: \"command\", result: \"passed\", summary: \"short result\" }],\n\t\t\tvalidationOutput: [\"validation output or concise summary\"],\n\t\t\tresidualRisks: [\"none\"],\n\t\t\tnoStagedFiles: true,\n\t\t\tdiffSummary: \"short description of the diff\",\n\t\t\treviewFindings: [\"blocker: file.ts:12 - issue found, or no blockers\"],\n\t\t\tmanualNotes: \"anything else the parent should know\",\n\t\t}, null, 2),\n\t\t\"```\",\n\t);\n\treturn lines.join(\"\\n\");\n}\n\nfunction extractBalancedJson(text: string, start: number): string | undefined {\n\tlet depth = 0;\n\tlet inString = false;\n\tlet escaped = false;\n\tfor (let i = start; i < text.length; i++) {\n\t\tconst char = text[i]!;\n\t\tif (inString) {\n\t\t\tif (escaped) escaped = false;\n\t\t\telse if (char === \"\\\\\") escaped = true;\n\t\t\telse if (char === \"\\\"\") inString = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"\\\"\") {\n\t\t\tinString = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"{\") depth++;\n\t\tif (char === \"}\") {\n\t\t\tdepth--;\n\t\t\tif (depth === 0) return text.slice(start, i + 1);\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst ACCEPTANCE_REPORT_WRAPPERS = new Set([\"acceptance\", \"acceptance-report\", \"acceptance_report\", \"acceptanceReport\"]);\n\nconst ACCEPTANCE_REPORT_FIELDS: Record<string, keyof AcceptanceReport> = {\n\tcriteriaSatisfied: \"criteriaSatisfied\",\n\tcriteria_satisfied: \"criteriaSatisfied\",\n\tchangedFiles: \"changedFiles\",\n\tchanged_files: \"changedFiles\",\n\ttestsAddedOrUpdated: \"testsAddedOrUpdated\",\n\ttests_added_or_updated: \"testsAddedOrUpdated\",\n\tcommandsRun: \"commandsRun\",\n\tcommands_run: \"commandsRun\",\n\tvalidationOutput: \"validationOutput\",\n\tvalidation_output: \"validationOutput\",\n\tresidualRisks: \"residualRisks\",\n\tresidual_risks: \"residualRisks\",\n\tnoStagedFiles: \"noStagedFiles\",\n\tno_staged_files: \"noStagedFiles\",\n\tdiffSummary: \"diffSummary\",\n\tdiff_summary: \"diffSummary\",\n\treviewFindings: \"reviewFindings\",\n\treview_findings: \"reviewFindings\",\n\tmanualNotes: \"manualNotes\",\n\tmanual_notes: \"manualNotes\",\n\tnotes: \"notes\",\n};\n\nconst CRITERION_REPORT_FIELDS = new Set([\"id\", \"status\", \"evidence\"]);\nconst COMMAND_REPORT_FIELDS = new Set([\"command\", \"result\", \"summary\"]);\n\nfunction normalizedToken(value: string): string {\n\treturn value.trim().toLowerCase().replace(/[\\s_]+/g, \"-\").replace(/-+/g, \"-\");\n}\n\nfunction normalizeCriterionStatus(value: unknown): unknown {\n\tif (typeof value !== \"string\") return value;\n\tconst token = normalizedToken(value);\n\tif ([\"satisfied\", \"met\", \"complete\", \"completed\", \"done\", \"pass\", \"passed\", \"success\", \"succeeded\"].includes(token)) return \"satisfied\";\n\tif ([\"not-satisfied\", \"not-met\", \"unmet\", \"incomplete\", \"fail\", \"failed\"].includes(token)) return \"not-satisfied\";\n\tif ([\"not-applicable\", \"n-a\", \"na\", \"skip\", \"skipped\"].includes(token)) return \"not-applicable\";\n\treturn value;\n}\n\nfunction normalizeCommandResult(value: unknown): unknown {\n\tif (typeof value !== \"string\") return value;\n\tconst token = normalizedToken(value);\n\tif ([\"passed\", \"pass\", \"success\", \"successful\", \"succeeded\", \"ok\"].includes(token)) return \"passed\";\n\tif ([\"failed\", \"fail\", \"failure\", \"error\"].includes(token)) return \"failed\";\n\tif ([\"not-run\", \"not-executed\", \"skip\", \"skipped\"].includes(token)) return \"not-run\";\n\treturn value;\n}\n\nfunction normalizeCriterionReport(value: unknown, pathLabel: string, errors: string[]): unknown {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return value;\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!CRITERION_REPORT_FIELDS.has(key)) {\n\t\t\terrors.push(`${pathLabel}.${key}: unsupported acceptance criterion field`);\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized[key] = key === \"id\" && typeof fieldValue === \"string\"\n\t\t\t? normalizedToken(fieldValue)\n\t\t\t: key === \"status\"\n\t\t\t\t? normalizeCriterionStatus(fieldValue)\n\t\t\t\t: fieldValue;\n\t}\n\treturn normalized;\n}\n\nfunction normalizeCommandReport(value: unknown, pathLabel: string, errors: string[]): unknown {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return value;\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!COMMAND_REPORT_FIELDS.has(key)) {\n\t\t\terrors.push(`${pathLabel}.${key}: unsupported acceptance command field`);\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized[key] = key === \"result\" ? normalizeCommandResult(fieldValue) : fieldValue;\n\t}\n\treturn normalized;\n}\n\nfunction normalizeAcceptanceReportValue(value: unknown, pathLabel = \"\"): { value: unknown; pathLabel: string; errors: string[] } {\n\tconst errors: string[] = [];\n\tlet reportValue = value;\n\tlet reportPath = pathLabel;\n\tif (reportValue && typeof reportValue === \"object\" && !Array.isArray(reportValue)) {\n\t\tconst record = reportValue as Record<string, unknown>;\n\t\tconst wrapperKeys = Object.keys(record).filter((key) => ACCEPTANCE_REPORT_WRAPPERS.has(key));\n\t\tif (wrapperKeys.length > 0) {\n\t\t\tconst wrapperKey = wrapperKeys[0]!;\n\t\t\tif (wrapperKeys.length > 1) errors.push(`${pathLabel || \"acceptance-report\"}: multiple acceptance report wrappers are ambiguous`);\n\t\t\tfor (const key of Object.keys(record)) {\n\t\t\t\tif (key !== wrapperKey) errors.push(`${pathFor(pathLabel, key)}: unsupported alongside acceptance report wrapper '${wrapperKey}'`);\n\t\t\t}\n\t\t\treportValue = record[wrapperKey];\n\t\t\treportPath = pathFor(pathLabel, wrapperKey);\n\t\t}\n\t}\n\tif (!reportValue || typeof reportValue !== \"object\" || Array.isArray(reportValue)) return { value: reportValue, pathLabel: reportPath, errors };\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(reportValue as Record<string, unknown>)) {\n\t\tconst canonical = ACCEPTANCE_REPORT_FIELDS[key];\n\t\tif (!canonical) {\n\t\t\terrors.push(`${pathFor(reportPath, key)}: unsupported acceptance report field`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (Object.hasOwn(normalized, canonical)) {\n\t\t\terrors.push(`${pathFor(reportPath, key)}: duplicates normalized field '${canonical}'`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst fieldPath = pathFor(reportPath, canonical);\n\t\tswitch (canonical) {\n\t\t\tcase \"criteriaSatisfied\": {\n\t\t\t\tconst items = Array.isArray(fieldValue) ? fieldValue : fieldValue && typeof fieldValue === \"object\" ? [fieldValue] : fieldValue;\n\t\t\t\tnormalized[canonical] = Array.isArray(items)\n\t\t\t\t\t? items.map((item, index) => normalizeCriterionReport(item, `${fieldPath}[${index}]`, errors))\n\t\t\t\t\t: items;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"commandsRun\": {\n\t\t\t\tconst items = Array.isArray(fieldValue) ? fieldValue : fieldValue && typeof fieldValue === \"object\" ? [fieldValue] : fieldValue;\n\t\t\t\tnormalized[canonical] = Array.isArray(items)\n\t\t\t\t\t? items.map((item, index) => normalizeCommandReport(item, `${fieldPath}[${index}]`, errors))\n\t\t\t\t\t: items;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"changedFiles\":\n\t\t\tcase \"testsAddedOrUpdated\":\n\t\t\tcase \"validationOutput\":\n\t\t\tcase \"residualRisks\":\n\t\t\tcase \"reviewFindings\":\n\t\t\t\tnormalized[canonical] = typeof fieldValue === \"string\" ? [fieldValue] : fieldValue;\n\t\t\t\tbreak;\n\t\t\tcase \"noStagedFiles\": {\n\t\t\t\tconst token = typeof fieldValue === \"string\" ? fieldValue.trim().toLowerCase() : undefined;\n\t\t\t\tnormalized[canonical] = token === \"true\" ? true : token === \"false\" ? false : fieldValue;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tnormalized[canonical] = fieldValue;\n\t\t}\n\t}\n\treturn { value: normalized, pathLabel: reportPath, errors };\n}\n\nfunction hasGenericAcceptanceReportSignal(value: unknown): boolean {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\treturn \"criteriaSatisfied\" in record && [\n\t\t\"changedFiles\",\n\t\t\"testsAddedOrUpdated\",\n\t\t\"commandsRun\",\n\t\t\"validationOutput\",\n\t\t\"residualRisks\",\n\t\t\"noStagedFiles\",\n\t\t\"diffSummary\",\n\t\t\"reviewFindings\",\n\t\t\"manualNotes\",\n\t].some((key) => key in record);\n}\n\nfunction parseReportJson(body: string): unknown {\n\tconst trimmed = body.trim();\n\ttry {\n\t\treturn JSON.parse(trimmed) as unknown;\n\t} catch (error) {\n\t\tconst jsonStart = trimmed.indexOf(\"{\");\n\t\tif (jsonStart > 0) {\n\t\t\tconst json = extractBalancedJson(trimmed, jsonStart);\n\t\t\tif (json) return JSON.parse(json) as unknown;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction fencedBlocks(output: string, tag: string): string[] {\n\treturn [...output.matchAll(new RegExp(`\\`\\`\\`${tag}\\\\s*\\\\n([\\\\s\\\\S]*?)\\`\\`\\``, \"gi\"))]\n\t\t.map((match) => match[1]?.trim())\n\t\t.filter((value): value is string => Boolean(value));\n}\n\nfunction parseAcceptanceReportBody(body: string): { report?: AcceptanceReport; errors: string[] } {\n\treturn validateAcceptanceReport(parseReportJson(body));\n}\n\nfunction parseGenericJsonAcceptanceReportBody(body: string): { report?: AcceptanceReport; error?: string } {\n\tconst parsed = parseReportJson(body);\n\tconst normalized = normalizeAcceptanceReportValue(parsed);\n\tconst hasCriteriaMarker = normalized.value !== null\n\t\t&& typeof normalized.value === \"object\"\n\t\t&& !Array.isArray(normalized.value)\n\t\t&& \"criteriaSatisfied\" in normalized.value;\n\tif (!hasGenericAcceptanceReportSignal(normalized.value) && !(hasCriteriaMarker && normalized.errors.length > 0)) return {};\n\tconst validation = validateAcceptanceReport(parsed);\n\treturn validation.report\n\t\t? { report: validation.report }\n\t\t: { error: `Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n}\n\nexport const ACCEPTANCE_REPORT_NOT_FOUND = \"Structured acceptance report not found.\";\n\nexport function parseAcceptanceReport(output: string): { report?: AcceptanceReport; error?: string } {\n\tconst explicitFencePresent = /```acceptance[-_]report\\b/i.test(output);\n\tconst fenced = fencedBlocks(output, \"acceptance[-_]report\");\n\tconst parseErrors: string[] = [];\n\tfor (const body of fenced) {\n\t\ttry {\n\t\t\tconst validation = parseAcceptanceReportBody(body);\n\t\t\tif (validation.report) return { report: validation.report };\n\t\t\tparseErrors.push(`Invalid acceptance-report: ${validation.errors.join(\"; \")}`);\n\t\t} catch (error) {\n\t\t\tparseErrors.push(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\tif (parseErrors.length > 0) return { error: `Failed to parse acceptance-report: ${parseErrors.join(\"; \")}` };\n\tif (explicitFencePresent) {\n\t\treturn { error: \"Failed to parse acceptance-report: Empty or unterminated acceptance-report fence.\" };\n\t}\n\tfor (const body of fencedBlocks(output, \"(?:json|jsonc|json5)\")) {\n\t\ttry {\n\t\t\tconst parsed = parseGenericJsonAcceptanceReportBody(body);\n\t\t\tif (parsed.report) return { report: parsed.report };\n\t\t\tif (parsed.error) return { error: `Failed to parse acceptance-report: ${parsed.error}` };\n\t\t} catch {\n\t\t\t// Ignore unrelated malformed generic JSON. A recognizable report shape\n\t\t\t// returns exact validation errors above instead of being mistaken for prose.\n\t\t}\n\t}\n\tconst markerIndex = output.search(/ACCEPTANCE_REPORT\\s*:/i);\n\tif (markerIndex !== -1) {\n\t\tconst jsonStart = output.indexOf(\"{\", markerIndex);\n\t\tif (jsonStart === -1) {\n\t\t\treturn { error: \"Failed to parse acceptance-report: Expected a JSON object after ACCEPTANCE_REPORT:.\" };\n\t\t}\n\t\tconst json = extractBalancedJson(output, jsonStart);\n\t\tif (!json) {\n\t\t\treturn { error: \"Failed to parse acceptance-report: Unterminated JSON object after ACCEPTANCE_REPORT:.\" };\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(json) as unknown;\n\t\t\tconst validation = validateAcceptanceReport(parsed);\n\t\t\tif (validation.report) return { report: validation.report };\n\t\t\treturn { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn { error: `Failed to parse acceptance-report: ${message}` };\n\t\t}\n\t}\n\treturn { error: ACCEPTANCE_REPORT_NOT_FOUND };\n}\n\nfunction parseAcceptanceReportSources(\n\toutput: string,\n\tfileOutput: { content: string; path: string; authoritative?: boolean } | undefined,\n): { report?: AcceptanceReport; error?: string } {\n\tconst fromText = () => parseAcceptanceReport(output);\n\tconst fromFile = () => {\n\t\tif (!fileOutput) return { error: ACCEPTANCE_REPORT_NOT_FOUND };\n\t\tconst parsed = parseAcceptanceReport(fileOutput.content);\n\t\treturn parsed.report || parsed.error === ACCEPTANCE_REPORT_NOT_FOUND\n\t\t\t? parsed\n\t\t\t: { error: `${parsed.error} (in configured output ${fileOutput.path})` };\n\t};\n\tconst [primary, secondary] = fileOutput?.authoritative ? [fromFile, fromText] : [fromText, fromFile];\n\tconst first = primary();\n\t// A malformed report in the primary source is a defect to surface, not a\n\t// miss to paper over with the secondary source; only a genuinely absent\n\t// report falls through.\n\tif (first.report || first.error !== ACCEPTANCE_REPORT_NOT_FOUND) return first;\n\treturn secondary();\n}\n\nexport function stripAcceptanceReport(output: string): string {\n\tconst trailingFencePattern = /\\n?```(acceptance[-_]report|json|jsonc|json5)\\s*\\n([\\s\\S]*?)```\\s*/gi;\n\tlet trailingFence: { index: number; tag: string; body: string } | undefined;\n\tfor (const match of output.matchAll(trailingFencePattern)) {\n\t\tconst end = (match.index ?? 0) + match[0].length;\n\t\tif (output.slice(end).trim().length === 0 && match[1] && match[2]) {\n\t\t\ttrailingFence = { index: match.index ?? 0, tag: match[1].toLowerCase(), body: match[2] };\n\t\t}\n\t}\n\tif (trailingFence) {\n\t\tif (trailingFence.tag === \"acceptance-report\" || trailingFence.tag === \"acceptance_report\") return output.slice(0, trailingFence.index).trimEnd();\n\t\ttry {\n\t\t\tif (parseGenericJsonAcceptanceReportBody(trailingFence.body).report) return output.slice(0, trailingFence.index).trimEnd();\n\t\t} catch {\n\t\t\t// Leave unrelated or malformed generic JSON fences visible.\n\t\t}\n\t}\n\treturn output\n\t\t.replace(/\\n?```acceptance[-_]report\\s*\\n[\\s\\S]*?```\\s*$/i, \"\")\n\t\t.replace(/\\n?ACCEPTANCE_REPORT\\s*:\\s*\\{[\\s\\S]*\\}\\s*$/i, \"\")\n\t\t.trimEnd();\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n\treturn Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction pathFor(base: string, segment: string): string {\n\treturn base ? `${base}.${segment}` : segment;\n}\n\nfunction describeValidationValue(value: unknown): string {\n\tif (value === undefined) return \"missing\";\n\tif (value === null) return \"null\";\n\tif (Array.isArray(value)) return \"array\";\n\tif (typeof value === \"object\") return \"object\";\n\tif (typeof value === \"string\") {\n\t\tconst short = value.length > 80 ? `${value.slice(0, 77)}...` : value;\n\t\treturn JSON.stringify(short);\n\t}\n\treturn `${typeof value} ${String(value)}`;\n}\n\nfunction pushTypeError(errors: string[], pathLabel: string, expected: string, value: unknown): void {\n\terrors.push(`${pathLabel}: expected ${expected}; got ${describeValidationValue(value)}`);\n}\n\nfunction validateStringArrayField(errors: string[], value: unknown, pathLabel: string): void {\n\tif (!Array.isArray(value)) {\n\t\tpushTypeError(errors, pathLabel, \"string[]\", value);\n\t\treturn;\n\t}\n\tfor (const [index, item] of value.entries()) {\n\t\tif (typeof item !== \"string\" || !item.trim()) pushTypeError(errors, `${pathLabel}[${index}]`, \"non-empty string\", item);\n\t}\n}\n\nfunction validateAcceptanceReport(value: unknown, pathLabel = \"\"): { report?: AcceptanceReport; errors: string[] } {\n\tconst normalized = normalizeAcceptanceReportValue(value, pathLabel);\n\tvalue = normalized.value;\n\tpathLabel = normalized.pathLabel;\n\tconst errors = normalized.errors;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tpushTypeError(errors, pathLabel || \"acceptance-report\", \"object\", value);\n\t\treturn { errors };\n\t}\n\tconst report = value as AcceptanceReport;\n\tif (report.criteriaSatisfied !== undefined) {\n\t\tif (!Array.isArray(report.criteriaSatisfied)) {\n\t\t\tpushTypeError(errors, pathFor(pathLabel, \"criteriaSatisfied\"), \"array\", report.criteriaSatisfied);\n\t\t} else {\n\t\t\tconst criterionIds = new Set<string>();\n\t\t\tfor (const [index, item] of report.criteriaSatisfied.entries()) {\n\t\t\t\tconst itemPath = `${pathFor(pathLabel, \"criteriaSatisfied\")}[${index}]`;\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\tpushTypeError(errors, itemPath, \"object\", item);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst criterion = item as { id?: unknown; status?: unknown; evidence?: unknown };\n\t\t\t\tif (criterion.id !== undefined && typeof criterion.id !== \"string\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.id`, \"string\", criterion.id);\n\t\t\t\t} else if (typeof criterion.id === \"string\" && criterion.id) {\n\t\t\t\t\tif (criterionIds.has(criterion.id)) errors.push(`${itemPath}.id: duplicate normalized criterion id '${criterion.id}'`);\n\t\t\t\t\tcriterionIds.add(criterion.id);\n\t\t\t\t}\n\t\t\t\tif (criterion.status !== \"satisfied\" && criterion.status !== \"not-satisfied\" && criterion.status !== \"not-applicable\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.status`, \"one of \\\"satisfied\\\", \\\"not-satisfied\\\", \\\"not-applicable\\\"\", criterion.status);\n\t\t\t\t}\n\t\t\t\tif (typeof criterion.evidence !== \"string\" || !criterion.evidence.trim()) pushTypeError(errors, `${itemPath}.evidence`, \"non-empty string\", criterion.evidence);\n\t\t\t}\n\t\t}\n\t}\n\tif (report.changedFiles !== undefined) validateStringArrayField(errors, report.changedFiles, pathFor(pathLabel, \"changedFiles\"));\n\tif (report.testsAddedOrUpdated !== undefined) validateStringArrayField(errors, report.testsAddedOrUpdated, pathFor(pathLabel, \"testsAddedOrUpdated\"));\n\tif (report.commandsRun !== undefined) {\n\t\tif (!Array.isArray(report.commandsRun)) {\n\t\t\tpushTypeError(errors, pathFor(pathLabel, \"commandsRun\"), \"array\", report.commandsRun);\n\t\t} else {\n\t\t\tfor (const [index, item] of report.commandsRun.entries()) {\n\t\t\t\tconst itemPath = `${pathFor(pathLabel, \"commandsRun\")}[${index}]`;\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\tpushTypeError(errors, itemPath, \"object\", item);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst command = item as { command?: unknown; result?: unknown; summary?: unknown };\n\t\t\t\tif (typeof command.command !== \"string\" || !command.command.trim()) pushTypeError(errors, `${itemPath}.command`, \"non-empty string\", command.command);\n\t\t\t\tif (command.result !== \"passed\" && command.result !== \"failed\" && command.result !== \"not-run\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.result`, \"one of \\\"passed\\\", \\\"failed\\\", \\\"not-run\\\"\", command.result);\n\t\t\t\t}\n\t\t\t\tif (typeof command.summary !== \"string\" || !command.summary.trim()) pushTypeError(errors, `${itemPath}.summary`, \"non-empty string\", command.summary);\n\t\t\t}\n\t\t}\n\t}\n\tif (report.validationOutput !== undefined) validateStringArrayField(errors, report.validationOutput, pathFor(pathLabel, \"validationOutput\"));\n\tif (report.residualRisks !== undefined) validateStringArrayField(errors, report.residualRisks, pathFor(pathLabel, \"residualRisks\"));\n\tif (report.noStagedFiles !== undefined && typeof report.noStagedFiles !== \"boolean\") pushTypeError(errors, pathFor(pathLabel, \"noStagedFiles\"), \"boolean\", report.noStagedFiles);\n\tif (report.diffSummary !== undefined && (typeof report.diffSummary !== \"string\" || !report.diffSummary.trim())) pushTypeError(errors, pathFor(pathLabel, \"diffSummary\"), \"non-empty string\", report.diffSummary);\n\tif (report.reviewFindings !== undefined) validateStringArrayField(errors, report.reviewFindings, pathFor(pathLabel, \"reviewFindings\"));\n\tif (report.manualNotes !== undefined && typeof report.manualNotes !== \"string\") pushTypeError(errors, pathFor(pathLabel, \"manualNotes\"), \"string\", report.manualNotes);\n\tif (report.notes !== undefined && typeof report.notes !== \"string\") pushTypeError(errors, pathFor(pathLabel, \"notes\"), \"string\", report.notes);\n\tif (errors.length > 0) return { errors };\n\tconst hasReportField = report.criteriaSatisfied !== undefined\n\t\t|| report.changedFiles !== undefined\n\t\t|| report.testsAddedOrUpdated !== undefined\n\t\t|| report.commandsRun !== undefined\n\t\t|| report.validationOutput !== undefined\n\t\t|| report.residualRisks !== undefined\n\t\t|| report.noStagedFiles !== undefined\n\t\t|| report.diffSummary !== undefined\n\t\t|| report.manualNotes !== undefined\n\t\t|| report.notes !== undefined\n\t\t|| report.reviewFindings !== undefined;\n\treturn hasReportField\n\t\t? { report, errors }\n\t\t: { errors: [`${pathLabel || \"acceptance-report\"}: expected at least one acceptance report field`] };\n}\n\nfunction checkCriteriaSatisfied(criteria: ResolvedAcceptanceGate[], report: AcceptanceReport): AcceptanceRuntimeCheck[] {\n\tconst reports = new Map((report.criteriaSatisfied ?? []).filter((item) => item.id).map((item) => [normalizedToken(item.id!), item]));\n\treturn criteria.filter((criterion) => criterion.severity !== \"recommended\").map((criterion) => {\n\t\tconst item = reports.get(normalizedToken(criterion.id));\n\t\tif (!item) return { id: `criterion:${criterion.id}`, status: \"failed\", message: `Required criterion '${criterion.id}' was not reported.` };\n\t\tif (item.status !== \"satisfied\") return { id: `criterion:${criterion.id}`, status: \"failed\", message: `Required criterion '${criterion.id}' was reported as ${item.status}.` };\n\t\treturn { id: `criterion:${criterion.id}`, status: \"passed\", message: `Required criterion '${criterion.id}' satisfied.` };\n\t});\n}\n\nfunction reportEvidenceStatus(report: AcceptanceReport, kind: AcceptanceEvidenceKind): AcceptanceRuntimeCheckStatus {\n\tswitch (kind) {\n\t\tcase \"changed-files\":\n\t\t\tif (!isStringArray(report.changedFiles)) return \"failed\";\n\t\t\treturn report.changedFiles.length === 0 ? \"not-applicable\" : \"passed\";\n\t\tcase \"tests-added\":\n\t\t\tif (!isStringArray(report.testsAddedOrUpdated)) return \"failed\";\n\t\t\treturn report.testsAddedOrUpdated.length === 0 ? \"not-applicable\" : \"passed\";\n\t\tcase \"commands-run\": return Array.isArray(report.commandsRun) && report.commandsRun.length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"validation-output\": return isStringArray(report.validationOutput) && report.validationOutput.length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"residual-risks\": return isStringArray(report.residualRisks) ? \"passed\" : \"failed\";\n\t\tcase \"no-staged-files\": return report.noStagedFiles === true ? \"passed\" : \"failed\";\n\t\tcase \"diff-summary\": return typeof report.diffSummary === \"string\" && report.diffSummary.trim().length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"review-findings\": return isStringArray(report.reviewFindings) ? \"passed\" : \"failed\";\n\t\tcase \"manual-notes\": return Boolean((report.manualNotes ?? report.notes)?.trim()) ? \"passed\" : \"failed\";\n\t}\n}\n\nfunction checkNoStagedFiles(cwd: string): AcceptanceRuntimeCheck {\n\tconst result = spawnSync(\"git\", [\"status\", \"--short\"], { cwd, encoding: \"utf-8\" });\n\tif (result.status !== 0) {\n\t\treturn { id: \"no-staged-files\", status: \"not-applicable\", message: \"git status unavailable; no staged-files check skipped\" };\n\t}\n\tconst staged = result.stdout.split(/\\r?\\n/).filter((line) => line.length >= 2 && line[0] !== \" \" && line[0] !== \"?\");\n\treturn staged.length === 0\n\t\t? { id: \"no-staged-files\", status: \"passed\", message: \"No staged files detected.\" }\n\t\t: { id: \"no-staged-files\", status: \"failed\", message: `Staged files present: ${staged.join(\", \")}` };\n}\n\nfunction runStructuralChecks(acceptance: ResolvedAcceptanceConfig, report: AcceptanceReport, cwd: string): AcceptanceRuntimeCheck[] {\n\tconst checks: AcceptanceRuntimeCheck[] = [];\n\tfor (const kind of acceptance.evidence) {\n\t\tconst status = reportEvidenceStatus(report, kind);\n\t\tchecks.push({\n\t\t\tid: `evidence:${kind}`,\n\t\t\tstatus,\n\t\t\tmessage: status === \"passed\"\n\t\t\t\t? `${kind} evidence present.`\n\t\t\t\t: status === \"not-applicable\"\n\t\t\t\t\t? `${kind} evidence explicitly reported as not applicable.`\n\t\t\t\t\t: `${kind} evidence missing from child report.`,\n\t\t});\n\t}\n\tif (acceptance.evidence.includes(\"no-staged-files\")) checks.push(checkNoStagedFiles(cwd));\n\treturn checks;\n}\n\nfunction trimOutput(value: string): string | undefined {\n\tconst trimmed = value.trim();\n\tif (!trimmed) return undefined;\n\treturn trimmed.length > 12_000 ? `${trimmed.slice(0, 12_000)}\\n...[truncated]` : trimmed;\n}\n\nfunction uniqueStrings(items: Array<string | undefined>): string[] {\n\treturn unique(items.map((item) => item?.trim()).filter((item): item is string => Boolean(item)));\n}\n\nexport function aggregateAcceptanceReport(input: {\n\tresults: Array<Pick<SingleResult, \"agent\" | \"acceptance\" | \"error\" | \"exitCode\">>;\n\tnotes?: string;\n}): AcceptanceReport {\n\tconst childReports = input.results.map((result) => result.acceptance?.childReport).filter((report): report is AcceptanceReport => Boolean(report));\n\tconst blockers = input.results.filter((result) => result.exitCode !== 0 || result.acceptance?.status === \"rejected\");\n\tconst successfulChildren = input.results.length > 0 && blockers.length === 0;\n\treturn {\n\t\tcriteriaSatisfied: [\n\t\t\t{ id: \"criterion-1\", status: successfulChildren ? \"satisfied\" : \"not-satisfied\", evidence: successfulChildren ? `All ${input.results.length} dynamic child run(s) completed without child or acceptance blockers.` : \"Dynamic fanout produced no accepted child evidence.\" },\n\t\t\t{ id: \"criterion-2\", status: successfulChildren ? \"satisfied\" : \"not-satisfied\", evidence: successfulChildren ? \"Collected child acceptance evidence for aggregate review.\" : \"Dynamic fanout produced no aggregate review evidence.\" },\n\t\t\t...input.results.map((result, index) => ({\n\t\t\t\tid: `child-${index + 1}`,\n\t\t\t\tstatus: result.exitCode === 0 && result.acceptance?.status !== \"rejected\" ? \"satisfied\" : \"not-satisfied\",\n\t\t\t\tevidence: `${result.agent}: acceptance ${result.acceptance?.status ?? \"unreported\"}${result.error ? ` (${result.error})` : \"\"}`,\n\t\t\t})),\n\t\t],\n\t\tchangedFiles: uniqueStrings(childReports.flatMap((report) => report.changedFiles ?? [])),\n\t\ttestsAddedOrUpdated: uniqueStrings(childReports.flatMap((report) => report.testsAddedOrUpdated ?? [])),\n\t\tcommandsRun: childReports.flatMap((report) => report.commandsRun ?? []),\n\t\tvalidationOutput: uniqueStrings(childReports.flatMap((report) => report.validationOutput ?? [])),\n\t\tresidualRisks: uniqueStrings([\n\t\t\t...childReports.flatMap((report) => report.residualRisks ?? []),\n\t\t\t...blockers.map((result) => `${result.agent}: ${result.error ?? \"child or acceptance gate failed\"}`),\n\t\t]),\n\t\tnoStagedFiles: childReports.length > 0 && childReports.every((report) => report.noStagedFiles === true),\n\t\treviewFindings: uniqueStrings(childReports.flatMap((report) => report.reviewFindings ?? [])),\n\t\tmanualNotes: input.notes ?? `Aggregated acceptance evidence from ${input.results.length} dynamic fanout child run(s).`,\n\t\tnotes: input.notes,\n\t};\n}\n\nfunction runVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string, options: { signal?: AbortSignal; abortMessage?: string } = {}): Promise<AcceptanceVerifyResult> {\n\treturn new Promise((resolve) => {\n\t\tconst startedAt = Date.now();\n\t\tconst cwd = command.cwd ? path.resolve(defaultCwd, command.cwd) : defaultCwd;\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet timedOut = false;\n\t\tlet settled = false;\n\t\tlet hardKill: NodeJS.Timeout | undefined;\n\t\tconst child = spawn(command.command, {\n\t\t\tcwd,\n\t\t\tenv: { ...process.env, ...(command.env ?? {}) },\n\t\t\tshell: true,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst finish = (result: Omit<AcceptanceVerifyResult, \"id\" | \"command\" | \"cwd\" | \"durationMs\">) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\tif (hardKill) clearTimeout(hardKill);\n\t\t\toptions.signal?.removeEventListener(\"abort\", abortVerification);\n\t\t\tresolve({\n\t\t\t\tid: command.id,\n\t\t\t\tcommand: command.command,\n\t\t\t\tcwd,\n\t\t\t\tdurationMs: Date.now() - startedAt,\n\t\t\t\t...result,\n\t\t\t});\n\t\t};\n\t\tconst abortVerification = () => {\n\t\t\tif (settled || timedOut) return;\n\t\t\ttimedOut = true;\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\thardKill = setTimeout(() => {\n\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\tfinish({\n\t\t\t\t\texitCode: null,\n\t\t\t\t\tstatus: \"timed-out\",\n\t\t\t\t\tstdout: trimOutput(stdout),\n\t\t\t\t\tstderr: trimOutput(stderr || options.abortMessage || \"Acceptance verification timed out.\"),\n\t\t\t\t});\n\t\t\t}, 1000);\n\t\t\thardKill.unref?.();\n\t\t};\n\t\tconst timeout = setTimeout(abortVerification, command.timeoutMs ?? 120_000);\n\t\ttimeout.unref?.();\n\t\tif (options.signal?.aborted) abortVerification();\n\t\telse options.signal?.addEventListener(\"abort\", abortVerification, { once: true });\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on(\"close\", (exitCode) => {\n\t\t\tconst passed = exitCode === 0 && !timedOut;\n\t\t\tfinish({\n\t\t\t\texitCode,\n\t\t\t\tstatus: timedOut ? \"timed-out\" : passed ? \"passed\" : command.allowFailure ? \"allowed-failure\" : \"failed\",\n\t\t\t\tstdout: trimOutput(stdout),\n\t\t\t\tstderr: trimOutput(stderr || (timedOut ? options.abortMessage ?? \"\" : \"\")),\n\t\t\t});\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tfinish({\n\t\t\t\texitCode: timedOut ? null : 1,\n\t\t\t\tstatus: timedOut ? \"timed-out\" : command.allowFailure ? \"allowed-failure\" : \"failed\",\n\t\t\t\tstderr: timedOut ? trimOutput(stderr || options.abortMessage || \"Acceptance verification timed out.\") : error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t});\n\t});\n}\n\nexport async function evaluateAcceptance(input: {\n\tacceptance: ResolvedAcceptanceConfig;\n\toutput: string;\n\tcwd: string;\n\t/**\n\t * Content the child sent to its configured output file (from its own write\n\t * tool calls, not from disk, so a concurrent writer to the same path cannot\n\t * be misattributed). Searched for the acceptance report; searched before\n\t * the assistant output when `authoritative` (outputMode \"file-only\").\n\t */\n\tfileOutput?: { content: string; path: string; authoritative?: boolean };\n\treport?: AcceptanceReport;\n\treviewResult?: AcceptanceReviewResult;\n\tsignal?: AbortSignal;\n\tabortMessage?: string;\n}): Promise<AcceptanceLedger> {\n\tconst acceptance = input.acceptance;\n\tconst ledger: AcceptanceLedger = {\n\t\tstatus: acceptance.level === \"none\" ? \"not-required\" : \"claimed\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: [],\n\t\tverifyRuns: [],\n\t};\n\tif (acceptance.level === \"none\") return ledger;\n\n\tconst parsed = input.report\n\t\t? (() => {\n\t\t\tconst validation = validateAcceptanceReport(input.report);\n\t\t\treturn validation.report\n\t\t\t\t? { report: validation.report }\n\t\t\t\t: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n\t\t})()\n\t\t: parseAcceptanceReportSources(input.output, input.fileOutput);\n\tif (parsed.report) {\n\t\tledger.childReport = parsed.report;\n\t\tledger.status = \"attested\";\n\t} else {\n\t\tledger.childReportParseError = parsed.error;\n\t\tledger.runtimeChecks.push({ id: \"attestation\", status: \"failed\", message: parsed.error ?? \"Structured acceptance report missing.\" });\n\t\tledger.status = \"rejected\";\n\t\treturn ledger;\n\t}\n\n\tif (LEVEL_RANK[acceptance.level] >= LEVEL_RANK.checked) {\n\t\tledger.runtimeChecks = [\n\t\t\t...checkCriteriaSatisfied(acceptance.criteria, parsed.report),\n\t\t\t...runStructuralChecks(acceptance, parsed.report, input.cwd),\n\t\t];\n\t\tif (ledger.runtimeChecks.some((check) => check.status === \"failed\")) {\n\t\t\tledger.status = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.status = \"checked\";\n\t}\n\n\tif (LEVEL_RANK[acceptance.level] >= LEVEL_RANK.verified && (acceptance.level === \"verified\" || acceptance.verify.length > 0)) {\n\t\tif (acceptance.level === \"verified\" && acceptance.verify.length === 0) {\n\t\t\tledger.runtimeChecks.push({ id: \"verification-config\", status: \"failed\", message: \"verified acceptance requires runtime verify commands.\" });\n\t\t\tledger.status = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.verifyRuns = [];\n\t\tfor (const command of acceptance.verify) {\n\t\t\tledger.verifyRuns.push(await runVerifyCommand(command, input.cwd, { signal: input.signal, abortMessage: input.abortMessage }));\n\t\t\tif (input.signal?.aborted) break;\n\t\t}\n\t\tif (ledger.verifyRuns.some((run) => run.status === \"failed\" || run.status === \"timed-out\")) {\n\t\t\tledger.status = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.status = \"verified\";\n\t}\n\n\tif (acceptance.level === \"reviewed\") {\n\t\tif (input.reviewResult) {\n\t\t\tledger.reviewResult = input.reviewResult;\n\t\t\tledger.status = input.reviewResult.status === \"no-blockers\" ? \"reviewed\" : \"rejected\";\n\t\t} else {\n\t\t\tconst optionalReview = acceptance.review && acceptance.review !== false && acceptance.review.required === false;\n\t\t\tledger.reviewResult = {\n\t\t\t\tstatus: \"needs-parent-decision\",\n\t\t\t\tfindings: [{\n\t\t\t\t\tseverity: acceptance.explicit && !optionalReview ? \"blocker\" : \"non-blocking\",\n\t\t\t\t\tissue: \"Reviewed acceptance requires an independent reviewer result.\",\n\t\t\t\t\trationale: \"The run cannot be marked reviewed from child evidence alone.\",\n\t\t\t\t}],\n\t\t\t};\n\t\t\tif (acceptance.review === false || (acceptance.explicit && !optionalReview)) ledger.status = \"rejected\";\n\t\t}\n\t}\n\n\treturn ledger;\n}\n\nexport function buildSkippedAcceptanceLedger(acceptance: ResolvedAcceptanceConfig, input: { id: string; message: string }): AcceptanceLedger {\n\treturn {\n\t\tstatus: acceptance.level === \"none\" ? \"not-required\" : \"rejected\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: acceptance.level === \"none\"\n\t\t\t? []\n\t\t\t: [{ id: input.id, status: \"failed\", message: input.message }],\n\t\tverifyRuns: [],\n\t};\n}\n\nexport function acceptanceFailureMessage(ledger: AcceptanceLedger): string | undefined {\n\tif (ledger.status !== \"rejected\") return undefined;\n\tconst failedCheck = ledger.runtimeChecks.find((check) => check.status === \"failed\");\n\tif (failedCheck) return `Acceptance rejected: ${failedCheck.message}`;\n\tconst failedVerify = ledger.verifyRuns.find((run) => run.status === \"failed\" || run.status === \"timed-out\");\n\tif (failedVerify) return `Acceptance verification '${failedVerify.id}' ${failedVerify.status}.`;\n\tif (ledger.reviewResult?.status === \"needs-parent-decision\") return \"Acceptance review required but no automatic reviewer result is available.\";\n\tif (ledger.reviewResult?.status === \"blockers\") return \"Acceptance review found blockers.\";\n\treturn \"Acceptance rejected.\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"acceptance.d.ts","sourceRoot":"","sources":["../../../../../../src/builtin-extensions/pi-subagents/src/runs/shared/acceptance.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,gBAAgB,EAEhB,eAAe,EACf,gBAAgB,EAEhB,gBAAgB,EAChB,cAAc,EAGd,sBAAsB,EAGtB,wBAAwB,EAExB,YAAY,EACZ,eAAe,EACf,MAAM,uBAAuB,CAAC;AAuH/B,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,gBAAgB,CAK7F;AAyJD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAe,GAAG,MAAM,EAAE,CAE1F;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAe,GAAG,MAAM,EAAE,CAE9F;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,SAAe,GAAG,wBAAwB,CAyB7G;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC;QACb,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC,GAAG;YAAE,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;KACtE,CAAC,CAAC;CACH,GAAG,MAAM,EAAE,CAgBX;AAgBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,wBAAwB,CA8B3B;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,wBAAwB,GAAG,MAAM,CAiDnF;AAmOD,eAAO,MAAM,2BAA2B,4CAA4C,CAAC;AAErF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgDnG;AAuBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqB5D;AAuLD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAChD,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,gBAAgB,CA2BnB;AA4ED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC/C,UAAU,EAAE,wBAAwB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACxE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgF5B;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,wBAAwB,EAAE,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,gBAAgB,CAY3I;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CASrF","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"node:child_process\";\nimport { spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n\tAcceptanceConfig,\n\tAcceptanceEvidenceKind,\n\tAcceptanceInput,\n\tAcceptanceLedger,\n\tAcceptanceLevel,\n\tAcceptanceReport,\n\tAcceptanceRole,\n\tAcceptanceRuntimeCheck,\n\tAcceptanceRuntimeCheckStatus,\n\tAcceptanceReviewResult,\n\tAcceptanceVerifyCommand,\n\tAcceptanceVerifyResult,\n\tResolvedAcceptanceConfig,\n\tResolvedAcceptanceGate,\n\tSingleResult,\n\tSubagentRunMode,\n} from \"../../shared/types.ts\";\nimport { classifyTaskMutationIntent, taskMayMutate } from \"./task-intent.ts\";\n\nconst LEVEL_RANK: Record<Exclude<AcceptanceLevel, \"auto\">, number> = {\n\tnone: 0,\n\tattested: 1,\n\tchecked: 2,\n\tverified: 3,\n\treviewed: 4,\n};\n\nconst VALID_LEVELS = new Set<AcceptanceLevel>([\"auto\", \"none\", \"attested\", \"checked\", \"verified\", \"reviewed\"]);\nconst VALID_EVIDENCE = new Set<AcceptanceEvidenceKind>([\n\t\"changed-files\",\n\t\"tests-added\",\n\t\"commands-run\",\n\t\"validation-output\",\n\t\"residual-risks\",\n\t\"no-staged-files\",\n\t\"diff-summary\",\n\t\"review-findings\",\n\t\"manual-notes\",\n]);\nconst ACCEPTANCE_CONFIG_KEYS = new Set([\"level\", \"criteria\", \"evidence\", \"verify\", \"review\", \"stopRules\", \"reason\"]);\nconst PERSISTED_ACCEPTANCE_KEYS = new Set([...ACCEPTANCE_CONFIG_KEYS, \"explicit\", \"inferredReason\"]);\nconst ACCEPTANCE_GATE_KEYS = new Set([\"id\", \"must\", \"evidence\", \"severity\"]);\nconst ACCEPTANCE_VERIFY_KEYS = new Set([\"id\", \"command\", \"timeoutMs\", \"cwd\", \"env\", \"allowFailure\"]);\nconst ACCEPTANCE_REVIEW_KEYS = new Set([\"agent\", \"focus\", \"required\"]);\nconst EXPLICIT_REVIEWED_UNAVAILABLE = \"cannot be requested explicitly because this run cannot supply an independent reviewer result; use checked/verified and orchestrate the reviewer separately, or omit acceptance for read-only review tasks.\";\nconst RESOLVED_LEVELS = new Set<Exclude<AcceptanceLevel, \"auto\">>([\"none\", \"attested\", \"checked\", \"verified\", \"reviewed\"]);\n\nfunction normalizeLevel(level: AcceptanceLevel | undefined): Exclude<AcceptanceLevel, \"auto\"> | \"auto\" {\n\treturn level ?? \"auto\";\n}\n\nfunction unique<T>(items: T[]): T[] {\n\treturn [...new Set(items)];\n}\n\nfunction requiredEvidenceForLevel(level: Exclude<AcceptanceLevel, \"auto\">): AcceptanceEvidenceKind[] {\n\tswitch (level) {\n\t\tcase \"none\":\n\t\t\treturn [];\n\t\tcase \"attested\":\n\t\t\treturn [\"manual-notes\", \"residual-risks\"];\n\t\tcase \"checked\":\n\t\t\treturn [\"changed-files\", \"tests-added\", \"commands-run\", \"residual-risks\", \"no-staged-files\"];\n\t\tcase \"verified\":\n\t\tcase \"reviewed\":\n\t\t\treturn [\"changed-files\", \"tests-added\", \"commands-run\", \"validation-output\", \"residual-risks\", \"no-staged-files\"];\n\t}\n}\n\nfunction inferLevel(input: {\n\tagentName?: string;\n\tpermissions?: \"full\" | \"read-only\";\n\tacceptanceRole?: AcceptanceRole;\n\ttask?: string;\n\tmode?: SubagentRunMode;\n\tasync?: boolean;\n\tdynamic?: boolean;\n\tdynamicGroup?: boolean;\n}): { level: Exclude<AcceptanceLevel, \"auto\">; reasons: string[]; criteria: string[]; evidence: AcceptanceEvidenceKind[]; review?: { agent?: string; required?: boolean } } {\n\tconst permissions = input.permissions\n\t\t?? (input.acceptanceRole === \"read-only\" ? \"read-only\" : \"full\");\n\tconst task = input.task?.toLowerCase() ?? \"\";\n\tconst reasons: string[] = [];\n\tconst intent = classifyTaskMutationIntent(permissions, input.task ?? \"\");\n\tconst readOnlyPermission = permissions === \"read-only\";\n\tconst readOnlyTask = readOnlyPermission\n\t\t|| intent.kind === \"read-only\"\n\t\t|| (intent.kind === \"unknown\" && /\\b(?:read[- ]only|review[- ]only|no edits|without edits|inspect|summari[sz]e)\\b/.test(task));\n\tconst taskMayWrite = !readOnlyPermission && (taskMayMutate(input.task ?? \"\") || intent.kind === \"implementation\");\n\tconst writeTask = taskMayWrite && !readOnlyTask;\n\tconst inferredReadOnly = readOnlyPermission || readOnlyTask;\n\tconst risky = !inferredReadOnly && (\n\t\tBoolean(input.async && writeTask)\n\t\t|| Boolean(input.dynamic)\n\t\t|| Boolean(input.dynamicGroup)\n\t\t|| /\\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\\b/.test(task)\n\t);\n\n\tif (inferredReadOnly) {\n\t\treasons.push(readOnlyPermission ? \"read-only child permission\" : \"read-only task wording\");\n\t\treturn {\n\t\t\tlevel: \"attested\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Return concrete findings with file paths and severity when applicable\"],\n\t\t\tevidence: [\"review-findings\", \"residual-risks\"],\n\t\t};\n\t}\n\tif (risky) {\n\t\treasons.push(input.async ? \"async write-capable or risky run\" : \"risky write-capable run\");\n\t\tif (input.dynamic || input.dynamicGroup) reasons.push(\"dynamic fanout context\");\n\t\treturn {\n\t\t\tlevel: \"reviewed\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Implement the requested change without widening scope\", \"Return evidence sufficient for an independent acceptance review\"],\n\t\t\tevidence: requiredEvidenceForLevel(\"reviewed\"),\n\t\t};\n\t}\n\tif (writeTask) {\n\t\treasons.push(\"write-capable task with full child permission\");\n\t\treturn {\n\t\t\tlevel: \"checked\",\n\t\t\treasons,\n\t\t\tcriteria: [\"Implement the requested change without widening scope\"],\n\t\t\tevidence: requiredEvidenceForLevel(\"checked\"),\n\t\t};\n\t}\n\treasons.push(\"default lightweight attestation\");\n\treturn {\n\t\tlevel: \"attested\",\n\t\treasons,\n\t\tcriteria: [\"Return a concise result and residual risks when applicable\"],\n\t\tevidence: [\"manual-notes\", \"residual-risks\"],\n\t};\n}\n\nexport function normalizeAcceptanceInput(input: AcceptanceInput | undefined): AcceptanceConfig {\n\tif (input === undefined || input === \"auto\") return { level: \"auto\" };\n\tif (input === false) return { level: \"none\", reason: \"disabled by deprecated false shorthand\" };\n\tif (typeof input === \"string\") return { level: input };\n\treturn { ...input };\n}\n\nfunction explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {\n\treturn explicit.level === \"none\" && typeof explicit.reason === \"string\" && explicit.reason.trim().length > 0;\n}\n\ntype AcceptanceValidationMode = \"launch\" | \"persisted\";\n\nfunction validateAcceptanceValue(input: unknown, pathLabel: string, mode: AcceptanceValidationMode): string[] {\n\tconst errors: string[] = [];\n\tif (input === undefined) return errors;\n\tif (mode === \"persisted\") {\n\t\tif (!input || typeof input !== \"object\" || Array.isArray(input)) {\n\t\t\terrors.push(`${pathLabel} must be a resolved acceptance object.`);\n\t\t\treturn errors;\n\t\t}\n\t} else {\n\t\tif (input === false) return errors;\n\t\tif (typeof input === \"string\") {\n\t\t\tif (!VALID_LEVELS.has(input as AcceptanceLevel)) errors.push(`${pathLabel} has invalid level '${input}'.`);\n\t\t\telse if (input === \"none\") errors.push(`${pathLabel} level \"none\" requires a reason; use { level: \"none\", reason: \"...\" }.`);\n\t\t\telse if (input === \"reviewed\") errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);\n\t\t\treturn errors;\n\t\t}\n\t\tif (!input || typeof input !== \"object\" || Array.isArray(input)) {\n\t\t\terrors.push(`${pathLabel} must be a string level, false, or an object.`);\n\t\t\treturn errors;\n\t\t}\n\t}\n\tconst value = input as Record<string, unknown>;\n\tconst allowedKeys = mode === \"persisted\" ? PERSISTED_ACCEPTANCE_KEYS : ACCEPTANCE_CONFIG_KEYS;\n\tfor (const key of Object.keys(value)) {\n\t\tif (!allowedKeys.has(key)) errors.push(`${pathLabel}.${key} is not supported.`);\n\t}\n\tif (mode === \"persisted\") {\n\t\tif (typeof value.level !== \"string\" || !RESOLVED_LEVELS.has(value.level as Exclude<AcceptanceLevel, \"auto\">)) {\n\t\t\terrors.push(`${pathLabel}.level must be one of none, attested, checked, verified, reviewed.`);\n\t\t}\n\t} else if (value.level !== undefined && (typeof value.level !== \"string\" || !VALID_LEVELS.has(value.level as AcceptanceLevel))) {\n\t\terrors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified, reviewed.`);\n\t}\n\tif (value.level === \"none\" && (typeof value.reason !== \"string\" || !value.reason.trim())) {\n\t\terrors.push(`${pathLabel}.reason is required when level is none.`);\n\t}\n\tif (mode === \"launch\" && value.level === \"reviewed\") errors.push(`${pathLabel}.level ${EXPLICIT_REVIEWED_UNAVAILABLE}`);\n\tif (mode === \"persisted\") {\n\t\tif (value.explicit !== undefined && typeof value.explicit !== \"boolean\") {\n\t\t\terrors.push(`${pathLabel}.explicit must be a boolean.`);\n\t\t}\n\t\tif (value.inferredReason !== undefined) {\n\t\t\tif (!Array.isArray(value.inferredReason) || value.inferredReason.some((item) => typeof item !== \"string\")) {\n\t\t\t\terrors.push(`${pathLabel}.inferredReason must be an array of strings.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (value.reason !== undefined && typeof value.reason !== \"string\") errors.push(`${pathLabel}.reason must be a string.`);\n\tif (value.criteria !== undefined && !Array.isArray(value.criteria)) errors.push(`${pathLabel}.criteria must be an array.`);\n\tif (Array.isArray(value.criteria)) {\n\t\tconst criterionIds = new Set<string>();\n\t\tfor (const [index, criterion] of value.criteria.entries()) {\n\t\t\tif (typeof criterion === \"string\") continue;\n\t\t\tconst criterionPath = `${pathLabel}.criteria[${index}]`;\n\t\t\tif (!criterion || typeof criterion !== \"object\" || Array.isArray(criterion)) {\n\t\t\t\terrors.push(`${criterionPath} must be a string or an object.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst gate = criterion as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(gate)) {\n\t\t\t\tif (!ACCEPTANCE_GATE_KEYS.has(key)) errors.push(`${criterionPath}.${key} is not supported.`);\n\t\t\t}\n\t\t\tif (typeof gate.id !== \"string\" || !gate.id.trim()) {\n\t\t\t\terrors.push(`${criterionPath}.id is required.`);\n\t\t\t} else {\n\t\t\t\tconst normalizedId = normalizedToken(gate.id);\n\t\t\t\tif (criterionIds.has(normalizedId)) errors.push(`${criterionPath}.id duplicates normalized criterion id '${normalizedId}'.`);\n\t\t\t\tcriterionIds.add(normalizedId);\n\t\t\t}\n\t\t\tif (typeof gate.must !== \"string\" || !gate.must.trim()) errors.push(`${criterionPath}.must is required.`);\n\t\t\tif (gate.evidence !== undefined && !Array.isArray(gate.evidence)) errors.push(`${criterionPath}.evidence must be an array.`);\n\t\t\tif (Array.isArray(gate.evidence)) {\n\t\t\t\tfor (const [evidenceIndex, item] of gate.evidence.entries()) {\n\t\t\t\t\tif (typeof item !== \"string\" || !VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)) {\n\t\t\t\t\t\terrors.push(`${criterionPath}.evidence[${evidenceIndex}] is not a supported evidence kind.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (gate.severity !== undefined && gate.severity !== \"required\" && gate.severity !== \"recommended\") {\n\t\t\t\terrors.push(`${criterionPath}.severity must be required or recommended.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (Array.isArray(value.evidence)) {\n\t\tfor (const [index, item] of value.evidence.entries()) {\n\t\t\tif (typeof item !== \"string\" || !VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)) {\n\t\t\t\terrors.push(`${pathLabel}.evidence[${index}] is not a supported evidence kind.`);\n\t\t\t}\n\t\t}\n\t} else if (value.evidence !== undefined) {\n\t\terrors.push(`${pathLabel}.evidence must be an array.`);\n\t}\n\tif (value.verify !== undefined && !Array.isArray(value.verify)) errors.push(`${pathLabel}.verify must be an array.`);\n\tif (Array.isArray(value.verify)) {\n\t\tfor (const [index, command] of value.verify.entries()) {\n\t\t\tif (!command || typeof command !== \"object\" || Array.isArray(command)) {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}] must be an object.`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst cmd = command as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(cmd)) {\n\t\t\t\tif (!ACCEPTANCE_VERIFY_KEYS.has(key)) errors.push(`${pathLabel}.verify[${index}].${key} is not supported.`);\n\t\t\t}\n\t\t\tif (typeof cmd.id !== \"string\" || !cmd.id.trim()) errors.push(`${pathLabel}.verify[${index}].id is required.`);\n\t\t\tif (typeof cmd.command !== \"string\" || !cmd.command.trim()) errors.push(`${pathLabel}.verify[${index}].command is required.`);\n\t\t\tif (cmd.timeoutMs !== undefined && (typeof cmd.timeoutMs !== \"number\" || !Number.isInteger(cmd.timeoutMs) || cmd.timeoutMs < 1)) {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].timeoutMs must be an integer >= 1.`);\n\t\t\t}\n\t\t\tif (cmd.cwd !== undefined && typeof cmd.cwd !== \"string\") errors.push(`${pathLabel}.verify[${index}].cwd must be a string.`);\n\t\t\tif (cmd.env !== undefined) {\n\t\t\t\tif (!cmd.env || typeof cmd.env !== \"object\" || Array.isArray(cmd.env)) {\n\t\t\t\t\terrors.push(`${pathLabel}.verify[${index}].env must be an object.`);\n\t\t\t\t} else {\n\t\t\t\t\tfor (const [envKey, envValue] of Object.entries(cmd.env as Record<string, unknown>)) {\n\t\t\t\t\t\tif (typeof envValue !== \"string\") errors.push(`${pathLabel}.verify[${index}].env.${envKey} must be a string.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (cmd.allowFailure !== undefined && typeof cmd.allowFailure !== \"boolean\") {\n\t\t\t\terrors.push(`${pathLabel}.verify[${index}].allowFailure must be a boolean.`);\n\t\t\t}\n\t\t}\n\t}\n\tif (value.review !== undefined && value.review !== false) {\n\t\tif (!value.review || typeof value.review !== \"object\" || Array.isArray(value.review)) {\n\t\t\terrors.push(`${pathLabel}.review must be false or an object.`);\n\t\t} else {\n\t\t\tconst review = value.review as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(review)) {\n\t\t\t\tif (!ACCEPTANCE_REVIEW_KEYS.has(key)) errors.push(`${pathLabel}.review.${key} is not supported.`);\n\t\t\t}\n\t\t\tif (review.agent !== undefined && typeof review.agent !== \"string\") errors.push(`${pathLabel}.review.agent must be a string.`);\n\t\t\tif (review.focus !== undefined && typeof review.focus !== \"string\") errors.push(`${pathLabel}.review.focus must be a string.`);\n\t\t\tif (review.required !== undefined && typeof review.required !== \"boolean\") errors.push(`${pathLabel}.review.required must be a boolean.`);\n\t\t}\n\t}\n\tif (value.stopRules !== undefined && !Array.isArray(value.stopRules)) errors.push(`${pathLabel}.stopRules must be an array.`);\n\tif (Array.isArray(value.stopRules)) {\n\t\tfor (const [index, item] of value.stopRules.entries()) {\n\t\t\tif (typeof item !== \"string\") errors.push(`${pathLabel}.stopRules[${index}] must be a string.`);\n\t\t}\n\t}\n\treturn errors;\n}\n\nexport function validateAcceptanceInput(input: unknown, pathLabel = \"acceptance\"): string[] {\n\treturn validateAcceptanceValue(input, pathLabel, \"launch\");\n}\n\nexport function validatePersistedAcceptance(input: unknown, pathLabel = \"acceptance\"): string[] {\n\treturn validateAcceptanceValue(input, pathLabel, \"persisted\");\n}\n\nexport function restorePersistedAcceptance(input: unknown, pathLabel = \"acceptance\"): ResolvedAcceptanceConfig {\n\tconst errors = validatePersistedAcceptance(input, pathLabel);\n\tif (errors.length > 0) throw new Error(errors.join(\" \"));\n\tconst value = input as Record<string, unknown>;\n\tconst evidence = Array.isArray(value.evidence)\n\t\t? unique((value.evidence as string[]).filter((item): item is AcceptanceEvidenceKind => VALID_EVIDENCE.has(item as AcceptanceEvidenceKind)))\n\t\t: [];\n\treturn {\n\t\tlevel: value.level as Exclude<AcceptanceLevel, \"auto\">,\n\t\texplicit: value.explicit === true,\n\t\tinferredReason: Array.isArray(value.inferredReason)\n\t\t\t? (value.inferredReason as unknown[]).filter((item): item is string => typeof item === \"string\")\n\t\t\t: [],\n\t\tcriteria: normalizeCriteria(\n\t\t\tvalue.criteria as Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }> | undefined,\n\t\t\tevidence,\n\t\t),\n\t\tevidence,\n\t\tverify: Array.isArray(value.verify) ? value.verify as AcceptanceVerifyCommand[] : [],\n\t\treview: value.review as ResolvedAcceptanceConfig[\"review\"],\n\t\tstopRules: Array.isArray(value.stopRules)\n\t\t\t? (value.stopRules as unknown[]).filter((item): item is string => typeof item === \"string\")\n\t\t\t: [],\n\t\t...(typeof value.reason === \"string\" ? { reason: value.reason } : {}),\n\t};\n}\n\nexport function validateExecutionAcceptance(input: {\n\tacceptance?: unknown;\n\ttasks?: Array<{ acceptance?: unknown }>;\n\tchain?: Array<{\n\t\tacceptance?: unknown;\n\t\tparallel?: Array<{ acceptance?: unknown }> | { acceptance?: unknown };\n\t}>;\n}): string[] {\n\tconst errors = validateAcceptanceInput(input.acceptance, \"acceptance\");\n\tfor (const [index, task] of (input.tasks ?? []).entries()) {\n\t\terrors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));\n\t}\n\tfor (const [stepIndex, step] of (input.chain ?? []).entries()) {\n\t\terrors.push(...validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`));\n\t\tif (Array.isArray(step.parallel)) {\n\t\t\tfor (const [taskIndex, task] of step.parallel.entries()) {\n\t\t\t\terrors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));\n\t\t\t}\n\t\t} else if (step.parallel) {\n\t\t\terrors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));\n\t\t}\n\t}\n\treturn errors;\n}\n\nfunction normalizeCriteria(criteria: Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }> | undefined, evidence: AcceptanceEvidenceKind[]): ResolvedAcceptanceGate[] {\n\treturn (criteria ?? []).map((criterion, index) => {\n\t\tif (typeof criterion === \"string\") {\n\t\t\treturn { id: `criterion-${index + 1}`, must: criterion, evidence, severity: \"required\" };\n\t\t}\n\t\treturn {\n\t\t\tid: criterion.id?.trim() || `criterion-${index + 1}`,\n\t\t\tmust: criterion.must ?? \"\",\n\t\t\tevidence: criterion.evidence?.filter((item) => VALID_EVIDENCE.has(item)) ?? evidence,\n\t\t\tseverity: criterion.severity ?? \"required\",\n\t\t};\n\t}).filter((criterion) => criterion.must.trim());\n}\n\nexport function resolveEffectiveAcceptance(input: {\n\texplicit?: AcceptanceInput;\n\tagentName?: string;\n\tpermissions?: \"full\" | \"read-only\";\n\tacceptanceRole?: AcceptanceRole;\n\ttask?: string;\n\tmode?: SubagentRunMode;\n\tasync?: boolean;\n\tdynamic?: boolean;\n\tdynamicGroup?: boolean;\n}): ResolvedAcceptanceConfig {\n\tconst explicit = normalizeAcceptanceInput(input.explicit);\n\tconst inferred = inferLevel(input);\n\tconst explicitLevel = normalizeLevel(explicit.level);\n\tconst level = explicitAcceptanceCanDisable(explicit)\n\t\t? \"none\"\n\t\t: explicitLevel === \"auto\"\n\t\t\t? inferred.level\n\t\t\t: (LEVEL_RANK[explicitLevel] >= LEVEL_RANK[inferred.level] ? explicitLevel : inferred.level);\n\tconst evidence = unique([...(level === inferred.level ? inferred.evidence : requiredEvidenceForLevel(level)), ...(explicit.evidence ?? [])]);\n\tconst criteria = normalizeCriteria(\n\t\t(explicit.criteria?.length ? explicit.criteria : inferred.criteria) as Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: \"required\" | \"recommended\" }>,\n\t\tevidence,\n\t);\n\tlet review = explicit.review !== undefined ? explicit.review : inferred.review;\n\tif (level === \"reviewed\" && input.explicit !== undefined && explicitLevel !== \"reviewed\" && explicit.review === undefined && review && review !== false) {\n\t\treview = { ...review, required: false };\n\t}\n\tconst parentRequestedLevel = input.explicit === undefined || explicitLevel === \"auto\" ? undefined : explicitLevel;\n\treturn {\n\t\tlevel,\n\t\texplicit: parentRequestedLevel === level,\n\t\tinferredReason: inferred.reasons,\n\t\tcriteria,\n\t\tevidence,\n\t\tverify: explicit.verify ?? [],\n\t\treview,\n\t\tstopRules: explicit.stopRules ?? [],\n\t\treason: explicit.reason,\n\t};\n}\n\nexport function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig): string {\n\tif (acceptance.level === \"none\") return \"\";\n\tconst lines = [\n\t\t\"\",\n\t\t\"## Acceptance Contract\",\n\t\t`Acceptance level: ${acceptance.level}`,\n\t\t\"Completion is not accepted from prose alone. End with a structured acceptance report.\",\n\t\t\"\",\n\t\t\"Criteria:\",\n\t\t...(acceptance.criteria.length ? acceptance.criteria.map((criterion) => `- ${criterion.id}: ${criterion.must}`) : [\"- Return the requested result.\"]),\n\t\t\"\",\n\t\t`Required evidence: ${acceptance.evidence.join(\", \") || \"none\"}`,\n\t];\n\tif (acceptance.verify.length > 0) {\n\t\tlines.push(\"\", \"Runtime verification commands configured by parent:\");\n\t\tfor (const command of acceptance.verify) lines.push(`- ${command.id}: ${command.command}`);\n\t}\n\tif (acceptance.review && acceptance.review !== false) {\n\t\tlines.push(\"\", `Review gate: ${acceptance.review.required === false ? \"optional\" : \"required\"}${acceptance.review.agent ? ` by ${acceptance.review.agent}` : \"\"}.`);\n\t\tif (acceptance.review.focus) lines.push(`Review focus: ${acceptance.review.focus}`);\n\t}\n\tif (acceptance.stopRules.length > 0) {\n\t\tlines.push(\"\", \"Stop rules:\", ...acceptance.stopRules.map((rule) => `- ${rule}`));\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"Finish with a fenced JSON block tagged `acceptance-report` in this shape:\",\n\t\t\"Use empty arrays when no items apply; array fields contain strings unless object entries are shown.\",\n\t\t\"`criteriaSatisfied[].status` must be exactly one of: satisfied, not-satisfied, not-applicable.\",\n\t\t\"`commandsRun[].result` must be exactly one of: passed, failed, not-run.\",\n\t\t\"`manualNotes` and `notes` are optional strings; an empty string means no note and does not satisfy `manual-notes` evidence.\",\n\t\t\"```acceptance-report\",\n\t\tJSON.stringify({\n\t\t\tcriteriaSatisfied: acceptance.criteria\n\t\t\t\t.filter((criterion) => criterion.severity !== \"recommended\")\n\t\t\t\t.map((criterion) => ({ id: criterion.id, status: \"satisfied\", evidence: \"specific proof\" })),\n\t\t\tchangedFiles: [\"src/file.ts\"],\n\t\t\ttestsAddedOrUpdated: [\"test/file.test.ts\"],\n\t\t\tcommandsRun: [{ command: \"command\", result: \"passed\", summary: \"short result\" }],\n\t\t\tvalidationOutput: [\"validation output or concise summary\"],\n\t\t\tresidualRisks: [\"none\"],\n\t\t\tnoStagedFiles: true,\n\t\t\tdiffSummary: \"short description of the diff\",\n\t\t\treviewFindings: [\"blocker: file.ts:12 - issue found, or no blockers\"],\n\t\t\tmanualNotes: \"anything else the parent should know\",\n\t\t}, null, 2),\n\t\t\"```\",\n\t);\n\treturn lines.join(\"\\n\");\n}\n\nfunction extractBalancedJson(text: string, start: number): string | undefined {\n\tlet depth = 0;\n\tlet inString = false;\n\tlet escaped = false;\n\tfor (let i = start; i < text.length; i++) {\n\t\tconst char = text[i]!;\n\t\tif (inString) {\n\t\t\tif (escaped) escaped = false;\n\t\t\telse if (char === \"\\\\\") escaped = true;\n\t\t\telse if (char === \"\\\"\") inString = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"\\\"\") {\n\t\t\tinString = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"{\") depth++;\n\t\tif (char === \"}\") {\n\t\t\tdepth--;\n\t\t\tif (depth === 0) return text.slice(start, i + 1);\n\t\t}\n\t}\n\treturn undefined;\n}\n\nconst ACCEPTANCE_REPORT_WRAPPERS = new Set([\"acceptance\", \"acceptance-report\", \"acceptance_report\", \"acceptanceReport\"]);\n\nconst ACCEPTANCE_REPORT_FIELDS: Record<string, keyof AcceptanceReport> = {\n\tcriteriaSatisfied: \"criteriaSatisfied\",\n\tcriteria_satisfied: \"criteriaSatisfied\",\n\tchangedFiles: \"changedFiles\",\n\tchanged_files: \"changedFiles\",\n\ttestsAddedOrUpdated: \"testsAddedOrUpdated\",\n\ttests_added_or_updated: \"testsAddedOrUpdated\",\n\tcommandsRun: \"commandsRun\",\n\tcommands_run: \"commandsRun\",\n\tvalidationOutput: \"validationOutput\",\n\tvalidation_output: \"validationOutput\",\n\tresidualRisks: \"residualRisks\",\n\tresidual_risks: \"residualRisks\",\n\tnoStagedFiles: \"noStagedFiles\",\n\tno_staged_files: \"noStagedFiles\",\n\tdiffSummary: \"diffSummary\",\n\tdiff_summary: \"diffSummary\",\n\treviewFindings: \"reviewFindings\",\n\treview_findings: \"reviewFindings\",\n\tmanualNotes: \"manualNotes\",\n\tmanual_notes: \"manualNotes\",\n\tnotes: \"notes\",\n};\n\nconst CRITERION_REPORT_FIELDS = new Set([\"id\", \"status\", \"evidence\"]);\nconst COMMAND_REPORT_FIELDS = new Set([\"command\", \"result\", \"summary\"]);\n\nfunction normalizedToken(value: string): string {\n\treturn value.trim().toLowerCase().replace(/[\\s_]+/g, \"-\").replace(/-+/g, \"-\");\n}\n\nfunction normalizeCriterionStatus(value: unknown): unknown {\n\tif (typeof value !== \"string\") return value;\n\tconst token = normalizedToken(value);\n\tif ([\"satisfied\", \"met\", \"complete\", \"completed\", \"done\", \"pass\", \"passed\", \"success\", \"succeeded\"].includes(token)) return \"satisfied\";\n\tif ([\"not-satisfied\", \"not-met\", \"unmet\", \"incomplete\", \"fail\", \"failed\"].includes(token)) return \"not-satisfied\";\n\tif ([\"not-applicable\", \"n-a\", \"na\", \"skip\", \"skipped\"].includes(token)) return \"not-applicable\";\n\treturn value;\n}\n\nfunction normalizeCommandResult(value: unknown): unknown {\n\tif (typeof value !== \"string\") return value;\n\tconst token = normalizedToken(value);\n\tif ([\"passed\", \"pass\", \"success\", \"successful\", \"succeeded\", \"ok\"].includes(token)) return \"passed\";\n\tif ([\"failed\", \"fail\", \"failure\", \"error\"].includes(token)) return \"failed\";\n\tif ([\"not-run\", \"not-executed\", \"skip\", \"skipped\"].includes(token)) return \"not-run\";\n\treturn value;\n}\n\nfunction normalizeCriterionReport(value: unknown, pathLabel: string, errors: string[]): unknown {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return value;\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!CRITERION_REPORT_FIELDS.has(key)) {\n\t\t\terrors.push(`${pathLabel}.${key}: unsupported acceptance criterion field`);\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized[key] = key === \"id\" && typeof fieldValue === \"string\"\n\t\t\t? normalizedToken(fieldValue)\n\t\t\t: key === \"status\"\n\t\t\t\t? normalizeCriterionStatus(fieldValue)\n\t\t\t\t: fieldValue;\n\t}\n\treturn normalized;\n}\n\nfunction normalizeCommandReport(value: unknown, pathLabel: string, errors: string[]): unknown {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return value;\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (!COMMAND_REPORT_FIELDS.has(key)) {\n\t\t\terrors.push(`${pathLabel}.${key}: unsupported acceptance command field`);\n\t\t\tcontinue;\n\t\t}\n\t\tnormalized[key] = key === \"result\" ? normalizeCommandResult(fieldValue) : fieldValue;\n\t}\n\treturn normalized;\n}\n\nfunction normalizeAcceptanceReportValue(value: unknown, pathLabel = \"\"): { value: unknown; pathLabel: string; errors: string[] } {\n\tconst errors: string[] = [];\n\tlet reportValue = value;\n\tlet reportPath = pathLabel;\n\tif (reportValue && typeof reportValue === \"object\" && !Array.isArray(reportValue)) {\n\t\tconst record = reportValue as Record<string, unknown>;\n\t\tconst wrapperKeys = Object.keys(record).filter((key) => ACCEPTANCE_REPORT_WRAPPERS.has(key));\n\t\tif (wrapperKeys.length > 0) {\n\t\t\tconst wrapperKey = wrapperKeys[0]!;\n\t\t\tif (wrapperKeys.length > 1) errors.push(`${pathLabel || \"acceptance-report\"}: multiple acceptance report wrappers are ambiguous`);\n\t\t\tfor (const key of Object.keys(record)) {\n\t\t\t\tif (key !== wrapperKey) errors.push(`${pathFor(pathLabel, key)}: unsupported alongside acceptance report wrapper '${wrapperKey}'`);\n\t\t\t}\n\t\t\treportValue = record[wrapperKey];\n\t\t\treportPath = pathFor(pathLabel, wrapperKey);\n\t\t}\n\t}\n\tif (!reportValue || typeof reportValue !== \"object\" || Array.isArray(reportValue)) return { value: reportValue, pathLabel: reportPath, errors };\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, fieldValue] of Object.entries(reportValue as Record<string, unknown>)) {\n\t\tconst canonical = ACCEPTANCE_REPORT_FIELDS[key];\n\t\tif (!canonical) {\n\t\t\terrors.push(`${pathFor(reportPath, key)}: unsupported acceptance report field`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (Object.hasOwn(normalized, canonical)) {\n\t\t\terrors.push(`${pathFor(reportPath, key)}: duplicates normalized field '${canonical}'`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst fieldPath = pathFor(reportPath, canonical);\n\t\tswitch (canonical) {\n\t\t\tcase \"criteriaSatisfied\": {\n\t\t\t\tconst items = Array.isArray(fieldValue) ? fieldValue : fieldValue && typeof fieldValue === \"object\" ? [fieldValue] : fieldValue;\n\t\t\t\tnormalized[canonical] = Array.isArray(items)\n\t\t\t\t\t? items.map((item, index) => normalizeCriterionReport(item, `${fieldPath}[${index}]`, errors))\n\t\t\t\t\t: items;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"commandsRun\": {\n\t\t\t\tconst items = Array.isArray(fieldValue) ? fieldValue : fieldValue && typeof fieldValue === \"object\" ? [fieldValue] : fieldValue;\n\t\t\t\tnormalized[canonical] = Array.isArray(items)\n\t\t\t\t\t? items.map((item, index) => normalizeCommandReport(item, `${fieldPath}[${index}]`, errors))\n\t\t\t\t\t: items;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"changedFiles\":\n\t\t\tcase \"testsAddedOrUpdated\":\n\t\t\tcase \"validationOutput\":\n\t\t\tcase \"residualRisks\":\n\t\t\tcase \"reviewFindings\":\n\t\t\t\tnormalized[canonical] = typeof fieldValue === \"string\" ? [fieldValue] : fieldValue;\n\t\t\t\tbreak;\n\t\t\tcase \"noStagedFiles\": {\n\t\t\t\tconst token = typeof fieldValue === \"string\" ? fieldValue.trim().toLowerCase() : undefined;\n\t\t\t\tnormalized[canonical] = token === \"true\" ? true : token === \"false\" ? false : fieldValue;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tnormalized[canonical] = fieldValue;\n\t\t}\n\t}\n\treturn { value: normalized, pathLabel: reportPath, errors };\n}\n\nfunction hasGenericAcceptanceReportSignal(value: unknown): boolean {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst record = value as Record<string, unknown>;\n\treturn \"criteriaSatisfied\" in record && [\n\t\t\"changedFiles\",\n\t\t\"testsAddedOrUpdated\",\n\t\t\"commandsRun\",\n\t\t\"validationOutput\",\n\t\t\"residualRisks\",\n\t\t\"noStagedFiles\",\n\t\t\"diffSummary\",\n\t\t\"reviewFindings\",\n\t\t\"manualNotes\",\n\t].some((key) => key in record);\n}\n\nfunction parseReportJson(body: string): unknown {\n\tconst trimmed = body.trim();\n\ttry {\n\t\treturn JSON.parse(trimmed) as unknown;\n\t} catch (error) {\n\t\tconst jsonStart = trimmed.indexOf(\"{\");\n\t\tif (jsonStart > 0) {\n\t\t\tconst json = extractBalancedJson(trimmed, jsonStart);\n\t\t\tif (json) return JSON.parse(json) as unknown;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction fencedBlocks(output: string, tag: string): string[] {\n\treturn [...output.matchAll(new RegExp(`\\`\\`\\`${tag}\\\\s*\\\\n([\\\\s\\\\S]*?)\\`\\`\\``, \"gi\"))]\n\t\t.map((match) => match[1]?.trim())\n\t\t.filter((value): value is string => Boolean(value));\n}\n\nfunction parseAcceptanceReportBody(body: string): { report?: AcceptanceReport; errors: string[] } {\n\treturn validateAcceptanceReport(parseReportJson(body));\n}\n\nfunction parseGenericJsonAcceptanceReportBody(body: string): { report?: AcceptanceReport; error?: string } {\n\tconst parsed = parseReportJson(body);\n\tconst normalized = normalizeAcceptanceReportValue(parsed);\n\tconst hasCriteriaMarker = normalized.value !== null\n\t\t&& typeof normalized.value === \"object\"\n\t\t&& !Array.isArray(normalized.value)\n\t\t&& \"criteriaSatisfied\" in normalized.value;\n\tif (!hasGenericAcceptanceReportSignal(normalized.value) && !(hasCriteriaMarker && normalized.errors.length > 0)) return {};\n\tconst validation = validateAcceptanceReport(parsed);\n\treturn validation.report\n\t\t? { report: validation.report }\n\t\t: { error: `Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n}\n\nexport const ACCEPTANCE_REPORT_NOT_FOUND = \"Structured acceptance report not found.\";\n\nexport function parseAcceptanceReport(output: string): { report?: AcceptanceReport; error?: string } {\n\tconst explicitFencePresent = /```acceptance[-_]report\\b/i.test(output);\n\tconst fenced = fencedBlocks(output, \"acceptance[-_]report\");\n\tconst parseErrors: string[] = [];\n\tfor (const body of fenced) {\n\t\ttry {\n\t\t\tconst validation = parseAcceptanceReportBody(body);\n\t\t\tif (validation.report) return { report: validation.report };\n\t\t\tparseErrors.push(`Invalid acceptance-report: ${validation.errors.join(\"; \")}`);\n\t\t} catch (error) {\n\t\t\tparseErrors.push(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\tif (parseErrors.length > 0) return { error: `Failed to parse acceptance-report: ${parseErrors.join(\"; \")}` };\n\tif (explicitFencePresent) {\n\t\treturn { error: \"Failed to parse acceptance-report: Empty or unterminated acceptance-report fence.\" };\n\t}\n\tfor (const body of fencedBlocks(output, \"(?:json|jsonc|json5)\")) {\n\t\ttry {\n\t\t\tconst parsed = parseGenericJsonAcceptanceReportBody(body);\n\t\t\tif (parsed.report) return { report: parsed.report };\n\t\t\tif (parsed.error) return { error: `Failed to parse acceptance-report: ${parsed.error}` };\n\t\t} catch {\n\t\t\t// Ignore unrelated malformed generic JSON. A recognizable report shape\n\t\t\t// returns exact validation errors above instead of being mistaken for prose.\n\t\t}\n\t}\n\tconst markerIndex = output.search(/ACCEPTANCE_REPORT\\s*:/i);\n\tif (markerIndex !== -1) {\n\t\tconst jsonStart = output.indexOf(\"{\", markerIndex);\n\t\tif (jsonStart === -1) {\n\t\t\treturn { error: \"Failed to parse acceptance-report: Expected a JSON object after ACCEPTANCE_REPORT:.\" };\n\t\t}\n\t\tconst json = extractBalancedJson(output, jsonStart);\n\t\tif (!json) {\n\t\t\treturn { error: \"Failed to parse acceptance-report: Unterminated JSON object after ACCEPTANCE_REPORT:.\" };\n\t\t}\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(json) as unknown;\n\t\t\tconst validation = validateAcceptanceReport(parsed);\n\t\t\tif (validation.report) return { report: validation.report };\n\t\t\treturn { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn { error: `Failed to parse acceptance-report: ${message}` };\n\t\t}\n\t}\n\treturn { error: ACCEPTANCE_REPORT_NOT_FOUND };\n}\n\nfunction parseAcceptanceReportSources(\n\toutput: string,\n\tfileOutput: { content: string; path: string; authoritative?: boolean } | undefined,\n): { report?: AcceptanceReport; error?: string } {\n\tconst fromText = () => parseAcceptanceReport(output);\n\tconst fromFile = () => {\n\t\tif (!fileOutput) return { error: ACCEPTANCE_REPORT_NOT_FOUND };\n\t\tconst parsed = parseAcceptanceReport(fileOutput.content);\n\t\treturn parsed.report || parsed.error === ACCEPTANCE_REPORT_NOT_FOUND\n\t\t\t? parsed\n\t\t\t: { error: `${parsed.error} (in configured output ${fileOutput.path})` };\n\t};\n\tconst [primary, secondary] = fileOutput?.authoritative ? [fromFile, fromText] : [fromText, fromFile];\n\tconst first = primary();\n\t// A malformed report in the primary source is a defect to surface, not a\n\t// miss to paper over with the secondary source; only a genuinely absent\n\t// report falls through.\n\tif (first.report || first.error !== ACCEPTANCE_REPORT_NOT_FOUND) return first;\n\treturn secondary();\n}\n\nexport function stripAcceptanceReport(output: string): string {\n\tconst trailingFencePattern = /\\n?```(acceptance[-_]report|json|jsonc|json5)\\s*\\n([\\s\\S]*?)```\\s*/gi;\n\tlet trailingFence: { index: number; tag: string; body: string } | undefined;\n\tfor (const match of output.matchAll(trailingFencePattern)) {\n\t\tconst end = (match.index ?? 0) + match[0].length;\n\t\tif (output.slice(end).trim().length === 0 && match[1] && match[2]) {\n\t\t\ttrailingFence = { index: match.index ?? 0, tag: match[1].toLowerCase(), body: match[2] };\n\t\t}\n\t}\n\tif (trailingFence) {\n\t\tif (trailingFence.tag === \"acceptance-report\" || trailingFence.tag === \"acceptance_report\") return output.slice(0, trailingFence.index).trimEnd();\n\t\ttry {\n\t\t\tif (parseGenericJsonAcceptanceReportBody(trailingFence.body).report) return output.slice(0, trailingFence.index).trimEnd();\n\t\t} catch {\n\t\t\t// Leave unrelated or malformed generic JSON fences visible.\n\t\t}\n\t}\n\treturn output\n\t\t.replace(/\\n?```acceptance[-_]report\\s*\\n[\\s\\S]*?```\\s*$/i, \"\")\n\t\t.replace(/\\n?ACCEPTANCE_REPORT\\s*:\\s*\\{[\\s\\S]*\\}\\s*$/i, \"\")\n\t\t.trimEnd();\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n\treturn Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\nfunction pathFor(base: string, segment: string): string {\n\treturn base ? `${base}.${segment}` : segment;\n}\n\nfunction describeValidationValue(value: unknown): string {\n\tif (value === undefined) return \"missing\";\n\tif (value === null) return \"null\";\n\tif (Array.isArray(value)) return \"array\";\n\tif (typeof value === \"object\") return \"object\";\n\tif (typeof value === \"string\") {\n\t\tconst short = value.length > 80 ? `${value.slice(0, 77)}...` : value;\n\t\treturn JSON.stringify(short);\n\t}\n\treturn `${typeof value} ${String(value)}`;\n}\n\nfunction pushTypeError(errors: string[], pathLabel: string, expected: string, value: unknown): void {\n\terrors.push(`${pathLabel}: expected ${expected}; got ${describeValidationValue(value)}`);\n}\n\nfunction validateStringArrayField(errors: string[], value: unknown, pathLabel: string): void {\n\tif (!Array.isArray(value)) {\n\t\tpushTypeError(errors, pathLabel, \"string[]\", value);\n\t\treturn;\n\t}\n\tfor (const [index, item] of value.entries()) {\n\t\tif (typeof item !== \"string\" || !item.trim()) pushTypeError(errors, `${pathLabel}[${index}]`, \"non-empty string\", item);\n\t}\n}\n\nfunction validateAcceptanceReport(value: unknown, pathLabel = \"\"): { report?: AcceptanceReport; errors: string[] } {\n\tconst normalized = normalizeAcceptanceReportValue(value, pathLabel);\n\tvalue = normalized.value;\n\tpathLabel = normalized.pathLabel;\n\tconst errors = normalized.errors;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tpushTypeError(errors, pathLabel || \"acceptance-report\", \"object\", value);\n\t\treturn { errors };\n\t}\n\tconst report = value as AcceptanceReport;\n\tif (report.criteriaSatisfied !== undefined) {\n\t\tif (!Array.isArray(report.criteriaSatisfied)) {\n\t\t\tpushTypeError(errors, pathFor(pathLabel, \"criteriaSatisfied\"), \"array\", report.criteriaSatisfied);\n\t\t} else {\n\t\t\tconst criterionIds = new Set<string>();\n\t\t\tfor (const [index, item] of report.criteriaSatisfied.entries()) {\n\t\t\t\tconst itemPath = `${pathFor(pathLabel, \"criteriaSatisfied\")}[${index}]`;\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\tpushTypeError(errors, itemPath, \"object\", item);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst criterion = item as { id?: unknown; status?: unknown; evidence?: unknown };\n\t\t\t\tif (criterion.id !== undefined && typeof criterion.id !== \"string\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.id`, \"string\", criterion.id);\n\t\t\t\t} else if (typeof criterion.id === \"string\" && criterion.id) {\n\t\t\t\t\tif (criterionIds.has(criterion.id)) errors.push(`${itemPath}.id: duplicate normalized criterion id '${criterion.id}'`);\n\t\t\t\t\tcriterionIds.add(criterion.id);\n\t\t\t\t}\n\t\t\t\tif (criterion.status !== \"satisfied\" && criterion.status !== \"not-satisfied\" && criterion.status !== \"not-applicable\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.status`, \"one of \\\"satisfied\\\", \\\"not-satisfied\\\", \\\"not-applicable\\\"\", criterion.status);\n\t\t\t\t}\n\t\t\t\tif (typeof criterion.evidence !== \"string\" || !criterion.evidence.trim()) pushTypeError(errors, `${itemPath}.evidence`, \"non-empty string\", criterion.evidence);\n\t\t\t}\n\t\t}\n\t}\n\tif (report.changedFiles !== undefined) validateStringArrayField(errors, report.changedFiles, pathFor(pathLabel, \"changedFiles\"));\n\tif (report.testsAddedOrUpdated !== undefined) validateStringArrayField(errors, report.testsAddedOrUpdated, pathFor(pathLabel, \"testsAddedOrUpdated\"));\n\tif (report.commandsRun !== undefined) {\n\t\tif (!Array.isArray(report.commandsRun)) {\n\t\t\tpushTypeError(errors, pathFor(pathLabel, \"commandsRun\"), \"array\", report.commandsRun);\n\t\t} else {\n\t\t\tfor (const [index, item] of report.commandsRun.entries()) {\n\t\t\t\tconst itemPath = `${pathFor(pathLabel, \"commandsRun\")}[${index}]`;\n\t\t\t\tif (!item || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\tpushTypeError(errors, itemPath, \"object\", item);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst command = item as { command?: unknown; result?: unknown; summary?: unknown };\n\t\t\t\tif (typeof command.command !== \"string\" || !command.command.trim()) pushTypeError(errors, `${itemPath}.command`, \"non-empty string\", command.command);\n\t\t\t\tif (command.result !== \"passed\" && command.result !== \"failed\" && command.result !== \"not-run\") {\n\t\t\t\t\tpushTypeError(errors, `${itemPath}.result`, \"one of \\\"passed\\\", \\\"failed\\\", \\\"not-run\\\"\", command.result);\n\t\t\t\t}\n\t\t\t\tif (typeof command.summary !== \"string\" || !command.summary.trim()) pushTypeError(errors, `${itemPath}.summary`, \"non-empty string\", command.summary);\n\t\t\t}\n\t\t}\n\t}\n\tif (report.validationOutput !== undefined) validateStringArrayField(errors, report.validationOutput, pathFor(pathLabel, \"validationOutput\"));\n\tif (report.residualRisks !== undefined) validateStringArrayField(errors, report.residualRisks, pathFor(pathLabel, \"residualRisks\"));\n\tif (report.noStagedFiles !== undefined && typeof report.noStagedFiles !== \"boolean\") pushTypeError(errors, pathFor(pathLabel, \"noStagedFiles\"), \"boolean\", report.noStagedFiles);\n\tif (report.diffSummary !== undefined && (typeof report.diffSummary !== \"string\" || !report.diffSummary.trim())) pushTypeError(errors, pathFor(pathLabel, \"diffSummary\"), \"non-empty string\", report.diffSummary);\n\tif (report.reviewFindings !== undefined) validateStringArrayField(errors, report.reviewFindings, pathFor(pathLabel, \"reviewFindings\"));\n\tif (report.manualNotes !== undefined && typeof report.manualNotes !== \"string\") pushTypeError(errors, pathFor(pathLabel, \"manualNotes\"), \"string\", report.manualNotes);\n\tif (report.notes !== undefined && typeof report.notes !== \"string\") pushTypeError(errors, pathFor(pathLabel, \"notes\"), \"string\", report.notes);\n\tif (errors.length > 0) return { errors };\n\tconst hasReportField = report.criteriaSatisfied !== undefined\n\t\t|| report.changedFiles !== undefined\n\t\t|| report.testsAddedOrUpdated !== undefined\n\t\t|| report.commandsRun !== undefined\n\t\t|| report.validationOutput !== undefined\n\t\t|| report.residualRisks !== undefined\n\t\t|| report.noStagedFiles !== undefined\n\t\t|| report.diffSummary !== undefined\n\t\t|| report.manualNotes !== undefined\n\t\t|| report.notes !== undefined\n\t\t|| report.reviewFindings !== undefined;\n\treturn hasReportField\n\t\t? { report, errors }\n\t\t: { errors: [`${pathLabel || \"acceptance-report\"}: expected at least one acceptance report field`] };\n}\n\nfunction checkCriteriaSatisfied(criteria: ResolvedAcceptanceGate[], report: AcceptanceReport): AcceptanceRuntimeCheck[] {\n\tconst reports = new Map((report.criteriaSatisfied ?? []).filter((item) => item.id).map((item) => [normalizedToken(item.id!), item]));\n\treturn criteria.filter((criterion) => criterion.severity !== \"recommended\").map((criterion) => {\n\t\tconst item = reports.get(normalizedToken(criterion.id));\n\t\tif (!item) return { id: `criterion:${criterion.id}`, status: \"failed\", message: `Required criterion '${criterion.id}' was not reported.` };\n\t\tif (item.status !== \"satisfied\") return { id: `criterion:${criterion.id}`, status: \"failed\", message: `Required criterion '${criterion.id}' was reported as ${item.status}.` };\n\t\treturn { id: `criterion:${criterion.id}`, status: \"passed\", message: `Required criterion '${criterion.id}' satisfied.` };\n\t});\n}\n\nfunction reportEvidenceStatus(report: AcceptanceReport, kind: AcceptanceEvidenceKind): AcceptanceRuntimeCheckStatus {\n\tswitch (kind) {\n\t\tcase \"changed-files\":\n\t\t\tif (!isStringArray(report.changedFiles)) return \"failed\";\n\t\t\treturn report.changedFiles.length === 0 ? \"not-applicable\" : \"passed\";\n\t\tcase \"tests-added\":\n\t\t\tif (!isStringArray(report.testsAddedOrUpdated)) return \"failed\";\n\t\t\treturn report.testsAddedOrUpdated.length === 0 ? \"not-applicable\" : \"passed\";\n\t\tcase \"commands-run\": return Array.isArray(report.commandsRun) && report.commandsRun.length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"validation-output\": return isStringArray(report.validationOutput) && report.validationOutput.length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"residual-risks\": return isStringArray(report.residualRisks) ? \"passed\" : \"failed\";\n\t\tcase \"no-staged-files\": return report.noStagedFiles === true ? \"passed\" : \"failed\";\n\t\tcase \"diff-summary\": return typeof report.diffSummary === \"string\" && report.diffSummary.trim().length > 0 ? \"passed\" : \"failed\";\n\t\tcase \"review-findings\": return isStringArray(report.reviewFindings) ? \"passed\" : \"failed\";\n\t\tcase \"manual-notes\": return Boolean((report.manualNotes ?? report.notes)?.trim()) ? \"passed\" : \"failed\";\n\t}\n}\n\nfunction checkNoStagedFiles(cwd: string): AcceptanceRuntimeCheck {\n\tconst result = spawnSync(\"git\", [\"status\", \"--short\"], { cwd, encoding: \"utf-8\" });\n\tif (result.status !== 0) {\n\t\treturn { id: \"no-staged-files\", status: \"not-applicable\", message: \"git status unavailable; no staged-files check skipped\" };\n\t}\n\tconst staged = result.stdout.split(/\\r?\\n/).filter((line) => line.length >= 2 && line[0] !== \" \" && line[0] !== \"?\");\n\treturn staged.length === 0\n\t\t? { id: \"no-staged-files\", status: \"passed\", message: \"No staged files detected.\" }\n\t\t: { id: \"no-staged-files\", status: \"failed\", message: `Staged files present: ${staged.join(\", \")}` };\n}\n\nfunction runStructuralChecks(acceptance: ResolvedAcceptanceConfig, report: AcceptanceReport, cwd: string): AcceptanceRuntimeCheck[] {\n\tconst checks: AcceptanceRuntimeCheck[] = [];\n\tfor (const kind of acceptance.evidence) {\n\t\tconst status = reportEvidenceStatus(report, kind);\n\t\tchecks.push({\n\t\t\tid: `evidence:${kind}`,\n\t\t\tstatus,\n\t\t\tmessage: status === \"passed\"\n\t\t\t\t? `${kind} evidence present.`\n\t\t\t\t: status === \"not-applicable\"\n\t\t\t\t\t? `${kind} evidence explicitly reported as not applicable.`\n\t\t\t\t\t: `${kind} evidence missing from child report.`,\n\t\t});\n\t}\n\tif (acceptance.evidence.includes(\"no-staged-files\")) checks.push(checkNoStagedFiles(cwd));\n\treturn checks;\n}\n\nfunction trimOutput(value: string): string | undefined {\n\tconst trimmed = value.trim();\n\tif (!trimmed) return undefined;\n\treturn trimmed.length > 12_000 ? `${trimmed.slice(0, 12_000)}\\n...[truncated]` : trimmed;\n}\n\nfunction uniqueStrings(items: Array<string | undefined>): string[] {\n\treturn unique(items.map((item) => item?.trim()).filter((item): item is string => Boolean(item)));\n}\n\nexport function aggregateAcceptanceReport(input: {\n\tresults: Array<Pick<SingleResult, \"agent\" | \"acceptance\" | \"error\" | \"exitCode\">>;\n\tnotes?: string;\n}): AcceptanceReport {\n\tconst childReports = input.results.map((result) => result.acceptance?.childReport).filter((report): report is AcceptanceReport => Boolean(report));\n\tconst blockers = input.results.filter((result) => result.exitCode !== 0 || result.acceptance?.status === \"rejected\");\n\tconst successfulChildren = input.results.length > 0 && blockers.length === 0;\n\treturn {\n\t\tcriteriaSatisfied: [\n\t\t\t{ id: \"criterion-1\", status: successfulChildren ? \"satisfied\" : \"not-satisfied\", evidence: successfulChildren ? `All ${input.results.length} dynamic child run(s) completed without child or acceptance blockers.` : \"Dynamic fanout produced no accepted child evidence.\" },\n\t\t\t{ id: \"criterion-2\", status: successfulChildren ? \"satisfied\" : \"not-satisfied\", evidence: successfulChildren ? \"Collected child acceptance evidence for aggregate review.\" : \"Dynamic fanout produced no aggregate review evidence.\" },\n\t\t\t...input.results.map((result, index) => ({\n\t\t\t\tid: `child-${index + 1}`,\n\t\t\t\tstatus: result.exitCode === 0 && result.acceptance?.status !== \"rejected\" ? \"satisfied\" : \"not-satisfied\",\n\t\t\t\tevidence: `${result.agent}: acceptance ${result.acceptance?.status ?? \"unreported\"}${result.error ? ` (${result.error})` : \"\"}`,\n\t\t\t})),\n\t\t],\n\t\tchangedFiles: uniqueStrings(childReports.flatMap((report) => report.changedFiles ?? [])),\n\t\ttestsAddedOrUpdated: uniqueStrings(childReports.flatMap((report) => report.testsAddedOrUpdated ?? [])),\n\t\tcommandsRun: childReports.flatMap((report) => report.commandsRun ?? []),\n\t\tvalidationOutput: uniqueStrings(childReports.flatMap((report) => report.validationOutput ?? [])),\n\t\tresidualRisks: uniqueStrings([\n\t\t\t...childReports.flatMap((report) => report.residualRisks ?? []),\n\t\t\t...blockers.map((result) => `${result.agent}: ${result.error ?? \"child or acceptance gate failed\"}`),\n\t\t]),\n\t\tnoStagedFiles: childReports.length > 0 && childReports.every((report) => report.noStagedFiles === true),\n\t\treviewFindings: uniqueStrings(childReports.flatMap((report) => report.reviewFindings ?? [])),\n\t\tmanualNotes: input.notes ?? `Aggregated acceptance evidence from ${input.results.length} dynamic fanout child run(s).`,\n\t\tnotes: input.notes,\n\t};\n}\n\nfunction runVerifyCommand(command: AcceptanceVerifyCommand, defaultCwd: string, options: { signal?: AbortSignal; abortMessage?: string } = {}): Promise<AcceptanceVerifyResult> {\n\treturn new Promise((resolve) => {\n\t\tconst startedAt = Date.now();\n\t\tconst cwd = command.cwd ? path.resolve(defaultCwd, command.cwd) : defaultCwd;\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet timedOut = false;\n\t\tlet settled = false;\n\t\tlet hardKill: NodeJS.Timeout | undefined;\n\t\tconst child = spawn(command.command, {\n\t\t\tcwd,\n\t\t\tenv: { ...process.env, ...(command.env ?? {}) },\n\t\t\tshell: true,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst finish = (result: Omit<AcceptanceVerifyResult, \"id\" | \"command\" | \"cwd\" | \"durationMs\">) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\tif (hardKill) clearTimeout(hardKill);\n\t\t\toptions.signal?.removeEventListener(\"abort\", abortVerification);\n\t\t\tresolve({\n\t\t\t\tid: command.id,\n\t\t\t\tcommand: command.command,\n\t\t\t\tcwd,\n\t\t\t\tdurationMs: Date.now() - startedAt,\n\t\t\t\t...result,\n\t\t\t});\n\t\t};\n\t\tconst abortVerification = () => {\n\t\t\tif (settled || timedOut) return;\n\t\t\ttimedOut = true;\n\t\t\tchild.kill(\"SIGTERM\");\n\t\t\thardKill = setTimeout(() => {\n\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\tfinish({\n\t\t\t\t\texitCode: null,\n\t\t\t\t\tstatus: \"timed-out\",\n\t\t\t\t\tstdout: trimOutput(stdout),\n\t\t\t\t\tstderr: trimOutput(stderr || options.abortMessage || \"Acceptance verification timed out.\"),\n\t\t\t\t});\n\t\t\t}, 1000);\n\t\t\thardKill.unref?.();\n\t\t};\n\t\tconst timeout = setTimeout(abortVerification, command.timeoutMs ?? 120_000);\n\t\ttimeout.unref?.();\n\t\tif (options.signal?.aborted) abortVerification();\n\t\telse options.signal?.addEventListener(\"abort\", abortVerification, { once: true });\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on(\"close\", (exitCode) => {\n\t\t\tconst passed = exitCode === 0 && !timedOut;\n\t\t\tfinish({\n\t\t\t\texitCode,\n\t\t\t\tstatus: timedOut ? \"timed-out\" : passed ? \"passed\" : command.allowFailure ? \"allowed-failure\" : \"failed\",\n\t\t\t\tstdout: trimOutput(stdout),\n\t\t\t\tstderr: trimOutput(stderr || (timedOut ? options.abortMessage ?? \"\" : \"\")),\n\t\t\t});\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tfinish({\n\t\t\t\texitCode: timedOut ? null : 1,\n\t\t\t\tstatus: timedOut ? \"timed-out\" : command.allowFailure ? \"allowed-failure\" : \"failed\",\n\t\t\t\tstderr: timedOut ? trimOutput(stderr || options.abortMessage || \"Acceptance verification timed out.\") : error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t});\n\t});\n}\n\nexport async function evaluateAcceptance(input: {\n\tacceptance: ResolvedAcceptanceConfig;\n\toutput: string;\n\tcwd: string;\n\t/**\n\t * Content the child sent to its configured output file (from its own write\n\t * tool calls, not from disk, so a concurrent writer to the same path cannot\n\t * be misattributed). Searched for the acceptance report; searched before\n\t * the assistant output when `authoritative` (outputMode \"file-only\").\n\t */\n\tfileOutput?: { content: string; path: string; authoritative?: boolean };\n\treport?: AcceptanceReport;\n\treviewResult?: AcceptanceReviewResult;\n\tsignal?: AbortSignal;\n\tabortMessage?: string;\n}): Promise<AcceptanceLedger> {\n\tconst acceptance = input.acceptance;\n\tconst ledger: AcceptanceLedger = {\n\t\tstatus: acceptance.level === \"none\" ? \"not-required\" : \"claimed\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: [],\n\t\tverifyRuns: [],\n\t};\n\tif (acceptance.level === \"none\") return ledger;\n\n\tconst parsed = input.report\n\t\t? (() => {\n\t\t\tconst validation = validateAcceptanceReport(input.report);\n\t\t\treturn validation.report\n\t\t\t\t? { report: validation.report }\n\t\t\t\t: { error: `Failed to parse acceptance-report: Invalid acceptance-report: ${validation.errors.join(\"; \")}` };\n\t\t})()\n\t\t: parseAcceptanceReportSources(input.output, input.fileOutput);\n\tif (parsed.report) {\n\t\tledger.childReport = parsed.report;\n\t\tledger.status = \"attested\";\n\t} else {\n\t\tledger.childReportParseError = parsed.error;\n\t\tledger.runtimeChecks.push({ id: \"attestation\", status: \"failed\", message: parsed.error ?? \"Structured acceptance report missing.\" });\n\t\tledger.status = \"rejected\";\n\t\treturn ledger;\n\t}\n\n\tif (LEVEL_RANK[acceptance.level] >= LEVEL_RANK.checked) {\n\t\tledger.runtimeChecks = [\n\t\t\t...checkCriteriaSatisfied(acceptance.criteria, parsed.report),\n\t\t\t...runStructuralChecks(acceptance, parsed.report, input.cwd),\n\t\t];\n\t\tif (ledger.runtimeChecks.some((check) => check.status === \"failed\")) {\n\t\t\tledger.status = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.status = \"checked\";\n\t}\n\n\tif (LEVEL_RANK[acceptance.level] >= LEVEL_RANK.verified && (acceptance.level === \"verified\" || acceptance.verify.length > 0)) {\n\t\tif (acceptance.level === \"verified\" && acceptance.verify.length === 0) {\n\t\t\tledger.runtimeChecks.push({ id: \"verification-config\", status: \"failed\", message: \"verified acceptance requires runtime verify commands.\" });\n\t\t\tledger.status = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.verifyRuns = [];\n\t\tfor (const command of acceptance.verify) {\n\t\t\tledger.verifyRuns.push(await runVerifyCommand(command, input.cwd, { signal: input.signal, abortMessage: input.abortMessage }));\n\t\t\tif (input.signal?.aborted) break;\n\t\t}\n\t\tif (ledger.verifyRuns.some((run) => run.status === \"failed\" || run.status === \"timed-out\")) {\n\t\t\tledger.status = \"rejected\";\n\t\t\treturn ledger;\n\t\t}\n\t\tledger.status = \"verified\";\n\t}\n\n\tif (acceptance.level === \"reviewed\") {\n\t\tif (input.reviewResult) {\n\t\t\tledger.reviewResult = input.reviewResult;\n\t\t\tledger.status = input.reviewResult.status === \"no-blockers\" ? \"reviewed\" : \"rejected\";\n\t\t} else {\n\t\t\tconst optionalReview = acceptance.review && acceptance.review !== false && acceptance.review.required === false;\n\t\t\tledger.reviewResult = {\n\t\t\t\tstatus: \"needs-parent-decision\",\n\t\t\t\tfindings: [{\n\t\t\t\t\tseverity: acceptance.explicit && !optionalReview ? \"blocker\" : \"non-blocking\",\n\t\t\t\t\tissue: \"Reviewed acceptance requires an independent reviewer result.\",\n\t\t\t\t\trationale: \"The run cannot be marked reviewed from child evidence alone.\",\n\t\t\t\t}],\n\t\t\t};\n\t\t\tif (acceptance.review === false || (acceptance.explicit && !optionalReview)) ledger.status = \"rejected\";\n\t\t}\n\t}\n\n\treturn ledger;\n}\n\nexport function buildSkippedAcceptanceLedger(acceptance: ResolvedAcceptanceConfig, input: { id: string; message: string }): AcceptanceLedger {\n\treturn {\n\t\tstatus: acceptance.level === \"none\" ? \"not-required\" : \"rejected\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: acceptance.level === \"none\"\n\t\t\t? []\n\t\t\t: [{ id: input.id, status: \"failed\", message: input.message }],\n\t\tverifyRuns: [],\n\t};\n}\n\nexport function acceptanceFailureMessage(ledger: AcceptanceLedger): string | undefined {\n\tif (ledger.status !== \"rejected\") return undefined;\n\tconst failedCheck = ledger.runtimeChecks.find((check) => check.status === \"failed\");\n\tif (failedCheck) return `Acceptance rejected: ${failedCheck.message}`;\n\tconst failedVerify = ledger.verifyRuns.find((run) => run.status === \"failed\" || run.status === \"timed-out\");\n\tif (failedVerify) return `Acceptance verification '${failedVerify.id}' ${failedVerify.status}.`;\n\tif (ledger.reviewResult?.status === \"needs-parent-decision\") return \"Acceptance review required but no automatic reviewer result is available.\";\n\tif (ledger.reviewResult?.status === \"blockers\") return \"Acceptance review found blockers.\";\n\treturn \"Acceptance rejected.\";\n}\n"]}
|
|
@@ -23,10 +23,12 @@ const VALID_EVIDENCE = new Set([
|
|
|
23
23
|
"manual-notes",
|
|
24
24
|
]);
|
|
25
25
|
const ACCEPTANCE_CONFIG_KEYS = new Set(["level", "criteria", "evidence", "verify", "review", "stopRules", "reason"]);
|
|
26
|
+
const PERSISTED_ACCEPTANCE_KEYS = new Set([...ACCEPTANCE_CONFIG_KEYS, "explicit", "inferredReason"]);
|
|
26
27
|
const ACCEPTANCE_GATE_KEYS = new Set(["id", "must", "evidence", "severity"]);
|
|
27
28
|
const ACCEPTANCE_VERIFY_KEYS = new Set(["id", "command", "timeoutMs", "cwd", "env", "allowFailure"]);
|
|
28
29
|
const ACCEPTANCE_REVIEW_KEYS = new Set(["agent", "focus", "required"]);
|
|
29
30
|
const EXPLICIT_REVIEWED_UNAVAILABLE = "cannot be requested explicitly because this run cannot supply an independent reviewer result; use checked/verified and orchestrate the reviewer separately, or omit acceptance for read-only review tasks.";
|
|
31
|
+
const RESOLVED_LEVELS = new Set(["none", "attested", "checked", "verified", "reviewed"]);
|
|
30
32
|
function normalizeLevel(level) {
|
|
31
33
|
return level ?? "auto";
|
|
32
34
|
}
|
|
@@ -112,38 +114,62 @@ export function normalizeAcceptanceInput(input) {
|
|
|
112
114
|
function explicitAcceptanceCanDisable(explicit) {
|
|
113
115
|
return explicit.level === "none" && typeof explicit.reason === "string" && explicit.reason.trim().length > 0;
|
|
114
116
|
}
|
|
115
|
-
|
|
117
|
+
function validateAcceptanceValue(input, pathLabel, mode) {
|
|
116
118
|
const errors = [];
|
|
117
119
|
if (input === undefined)
|
|
118
120
|
return errors;
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
else if (input === "none")
|
|
125
|
-
errors.push(`${pathLabel} level "none" requires a reason; use { level: "none", reason: "..." }.`);
|
|
126
|
-
else if (input === "reviewed")
|
|
127
|
-
errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);
|
|
128
|
-
return errors;
|
|
121
|
+
if (mode === "persisted") {
|
|
122
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
123
|
+
errors.push(`${pathLabel} must be a resolved acceptance object.`);
|
|
124
|
+
return errors;
|
|
125
|
+
}
|
|
129
126
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
else {
|
|
128
|
+
if (input === false)
|
|
129
|
+
return errors;
|
|
130
|
+
if (typeof input === "string") {
|
|
131
|
+
if (!VALID_LEVELS.has(input))
|
|
132
|
+
errors.push(`${pathLabel} has invalid level '${input}'.`);
|
|
133
|
+
else if (input === "none")
|
|
134
|
+
errors.push(`${pathLabel} level "none" requires a reason; use { level: "none", reason: "..." }.`);
|
|
135
|
+
else if (input === "reviewed")
|
|
136
|
+
errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);
|
|
137
|
+
return errors;
|
|
138
|
+
}
|
|
139
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
140
|
+
errors.push(`${pathLabel} must be a string level, false, or an object.`);
|
|
141
|
+
return errors;
|
|
142
|
+
}
|
|
133
143
|
}
|
|
134
144
|
const value = input;
|
|
145
|
+
const allowedKeys = mode === "persisted" ? PERSISTED_ACCEPTANCE_KEYS : ACCEPTANCE_CONFIG_KEYS;
|
|
135
146
|
for (const key of Object.keys(value)) {
|
|
136
|
-
if (!
|
|
147
|
+
if (!allowedKeys.has(key))
|
|
137
148
|
errors.push(`${pathLabel}.${key} is not supported.`);
|
|
138
149
|
}
|
|
139
|
-
if (
|
|
150
|
+
if (mode === "persisted") {
|
|
151
|
+
if (typeof value.level !== "string" || !RESOLVED_LEVELS.has(value.level)) {
|
|
152
|
+
errors.push(`${pathLabel}.level must be one of none, attested, checked, verified, reviewed.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (value.level !== undefined && (typeof value.level !== "string" || !VALID_LEVELS.has(value.level))) {
|
|
140
156
|
errors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified, reviewed.`);
|
|
141
157
|
}
|
|
142
158
|
if (value.level === "none" && (typeof value.reason !== "string" || !value.reason.trim())) {
|
|
143
159
|
errors.push(`${pathLabel}.reason is required when level is none.`);
|
|
144
160
|
}
|
|
145
|
-
if (value.level === "reviewed")
|
|
161
|
+
if (mode === "launch" && value.level === "reviewed")
|
|
146
162
|
errors.push(`${pathLabel}.level ${EXPLICIT_REVIEWED_UNAVAILABLE}`);
|
|
163
|
+
if (mode === "persisted") {
|
|
164
|
+
if (value.explicit !== undefined && typeof value.explicit !== "boolean") {
|
|
165
|
+
errors.push(`${pathLabel}.explicit must be a boolean.`);
|
|
166
|
+
}
|
|
167
|
+
if (value.inferredReason !== undefined) {
|
|
168
|
+
if (!Array.isArray(value.inferredReason) || value.inferredReason.some((item) => typeof item !== "string")) {
|
|
169
|
+
errors.push(`${pathLabel}.inferredReason must be an array of strings.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
147
173
|
if (value.reason !== undefined && typeof value.reason !== "string")
|
|
148
174
|
errors.push(`${pathLabel}.reason must be a string.`);
|
|
149
175
|
if (value.criteria !== undefined && !Array.isArray(value.criteria))
|
|
@@ -264,6 +290,36 @@ export function validateAcceptanceInput(input, pathLabel = "acceptance") {
|
|
|
264
290
|
}
|
|
265
291
|
return errors;
|
|
266
292
|
}
|
|
293
|
+
export function validateAcceptanceInput(input, pathLabel = "acceptance") {
|
|
294
|
+
return validateAcceptanceValue(input, pathLabel, "launch");
|
|
295
|
+
}
|
|
296
|
+
export function validatePersistedAcceptance(input, pathLabel = "acceptance") {
|
|
297
|
+
return validateAcceptanceValue(input, pathLabel, "persisted");
|
|
298
|
+
}
|
|
299
|
+
export function restorePersistedAcceptance(input, pathLabel = "acceptance") {
|
|
300
|
+
const errors = validatePersistedAcceptance(input, pathLabel);
|
|
301
|
+
if (errors.length > 0)
|
|
302
|
+
throw new Error(errors.join(" "));
|
|
303
|
+
const value = input;
|
|
304
|
+
const evidence = Array.isArray(value.evidence)
|
|
305
|
+
? unique(value.evidence.filter((item) => VALID_EVIDENCE.has(item)))
|
|
306
|
+
: [];
|
|
307
|
+
return {
|
|
308
|
+
level: value.level,
|
|
309
|
+
explicit: value.explicit === true,
|
|
310
|
+
inferredReason: Array.isArray(value.inferredReason)
|
|
311
|
+
? value.inferredReason.filter((item) => typeof item === "string")
|
|
312
|
+
: [],
|
|
313
|
+
criteria: normalizeCriteria(value.criteria, evidence),
|
|
314
|
+
evidence,
|
|
315
|
+
verify: Array.isArray(value.verify) ? value.verify : [],
|
|
316
|
+
review: value.review,
|
|
317
|
+
stopRules: Array.isArray(value.stopRules)
|
|
318
|
+
? value.stopRules.filter((item) => typeof item === "string")
|
|
319
|
+
: [],
|
|
320
|
+
...(typeof value.reason === "string" ? { reason: value.reason } : {}),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
267
323
|
export function validateExecutionAcceptance(input) {
|
|
268
324
|
const errors = validateAcceptanceInput(input.acceptance, "acceptance");
|
|
269
325
|
for (const [index, task] of (input.tasks ?? []).entries()) {
|
|
@@ -310,9 +366,10 @@ export function resolveEffectiveAcceptance(input) {
|
|
|
310
366
|
if (level === "reviewed" && input.explicit !== undefined && explicitLevel !== "reviewed" && explicit.review === undefined && review && review !== false) {
|
|
311
367
|
review = { ...review, required: false };
|
|
312
368
|
}
|
|
369
|
+
const parentRequestedLevel = input.explicit === undefined || explicitLevel === "auto" ? undefined : explicitLevel;
|
|
313
370
|
return {
|
|
314
371
|
level,
|
|
315
|
-
explicit:
|
|
372
|
+
explicit: parentRequestedLevel === level,
|
|
316
373
|
inferredReason: inferred.reasons,
|
|
317
374
|
criteria,
|
|
318
375
|
evidence,
|