@lannguyensi/harness 0.63.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +46 -42
- package/dist/cli/add/index.js +17 -21
- package/dist/cli/add/index.js.map +1 -1
- package/dist/cli/adopt/derive.d.ts +2 -2
- package/dist/cli/adopt/derive.js +2 -19
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/adopt/index.d.ts +1 -1
- package/dist/cli/adopt/index.js +1 -12
- package/dist/cli/adopt/index.js.map +1 -1
- package/dist/cli/apply/apply.d.ts +20 -35
- package/dist/cli/apply/apply.js +61 -136
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.js +10 -58
- package/dist/cli/apply/generate-codex-config.js.map +1 -1
- package/dist/cli/apply/generate-memory-index.js +1 -2
- package/dist/cli/apply/generate-memory-index.js.map +1 -1
- package/dist/cli/apply/generate-opencode-config.d.ts +2 -10
- package/dist/cli/apply/generate-opencode-config.js +74 -113
- package/dist/cli/apply/generate-opencode-config.js.map +1 -1
- package/dist/cli/apply/generate-settings.d.ts +11 -64
- package/dist/cli/apply/generate-settings.js +27 -132
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/apply/index.d.ts +1 -1
- package/dist/cli/apply/index.js +1 -1
- package/dist/cli/apply/index.js.map +1 -1
- package/dist/cli/apply/install-codex-config.d.ts +153 -1
- package/dist/cli/apply/install-codex-config.js +445 -70
- package/dist/cli/apply/install-codex-config.js.map +1 -1
- package/dist/cli/apply/next-steps.d.ts +7 -0
- package/dist/cli/apply/next-steps.js +13 -1
- package/dist/cli/apply/next-steps.js.map +1 -1
- package/dist/cli/bounded-stdin.d.ts +0 -23
- package/dist/cli/bounded-stdin.js +2 -49
- package/dist/cli/bounded-stdin.js.map +1 -1
- package/dist/cli/describe.d.ts +1 -1
- package/dist/cli/describe.js +0 -3
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/diff/engine.d.ts +0 -11
- package/dist/cli/diff/engine.js +1 -17
- package/dist/cli/diff/engine.js.map +1 -1
- package/dist/cli/diff/index.js +14 -25
- package/dist/cli/diff/index.js.map +1 -1
- package/dist/cli/doctor/claude-mcp.d.ts +4 -19
- package/dist/cli/doctor/claude-mcp.js +11 -22
- package/dist/cli/doctor/claude-mcp.js.map +1 -1
- package/dist/cli/doctor/codex.d.ts +12 -14
- package/dist/cli/doctor/codex.js +31 -144
- package/dist/cli/doctor/codex.js.map +1 -1
- package/dist/cli/doctor/format.js +23 -338
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/hook-liveness.d.ts +56 -0
- package/dist/cli/doctor/hook-liveness.js +455 -0
- package/dist/cli/doctor/hook-liveness.js.map +1 -0
- package/dist/cli/doctor/index.d.ts +13 -87
- package/dist/cli/doctor/index.js +57 -705
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/opencode.d.ts +2 -1
- package/dist/cli/doctor/opencode.js.map +1 -1
- package/dist/cli/doctor/types.d.ts +21 -378
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/drain-stdio.d.ts +31 -0
- package/dist/cli/drain-stdio.js +76 -0
- package/dist/cli/drain-stdio.js.map +1 -0
- package/dist/cli/export.d.ts +3 -1
- package/dist/cli/export.js +7 -16
- package/dist/cli/export.js.map +1 -1
- package/dist/cli/gate/codex-disable.d.ts +29 -0
- package/dist/cli/gate/codex-disable.js +195 -0
- package/dist/cli/gate/codex-disable.js.map +1 -0
- package/dist/cli/gate/codex-enable.d.ts +41 -0
- package/dist/cli/gate/codex-enable.js +127 -0
- package/dist/cli/gate/codex-enable.js.map +1 -0
- package/dist/cli/gate/codex-snapshot.d.ts +208 -0
- package/dist/cli/gate/codex-snapshot.js +402 -0
- package/dist/cli/gate/codex-snapshot.js.map +1 -0
- package/dist/cli/gate/disable.d.ts +9 -0
- package/dist/cli/gate/disable.js +263 -28
- package/dist/cli/gate/disable.js.map +1 -1
- package/dist/cli/gate/enable.d.ts +26 -6
- package/dist/cli/gate/enable.js +152 -80
- package/dist/cli/gate/enable.js.map +1 -1
- package/dist/cli/gate/link-chain.d.ts +53 -0
- package/dist/cli/gate/link-chain.js +122 -0
- package/dist/cli/gate/link-chain.js.map +1 -0
- package/dist/cli/gate/snapshot.d.ts +289 -12
- package/dist/cli/gate/snapshot.js +481 -22
- package/dist/cli/gate/snapshot.js.map +1 -1
- package/dist/cli/index.d.ts +0 -9
- package/dist/cli/index.js +15 -2607
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.d.ts +3 -6
- package/dist/cli/init/composer.js +6 -370
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/dependencies.d.ts +1 -2
- package/dist/cli/init/dependencies.js +4 -80
- package/dist/cli/init/dependencies.js.map +1 -1
- package/dist/cli/init/detect.js +5 -4
- package/dist/cli/init/detect.js.map +1 -1
- package/dist/cli/init/interactive.d.ts +1 -23
- package/dist/cli/init/interactive.js +33 -247
- package/dist/cli/init/interactive.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +51 -243
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -37
- package/dist/cli/init/templates.js +25 -1237
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/list.d.ts +1 -1
- package/dist/cli/list.js +1 -38
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/loader.d.ts +15 -1
- package/dist/cli/loader.js +56 -39
- package/dist/cli/loader.js.map +1 -1
- package/dist/cli/main.js +7 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/migrate-home/index.js +2 -2
- package/dist/cli/migrate-home/index.js.map +1 -1
- package/dist/cli/pack/add.js +1 -1
- package/dist/cli/pack/add.js.map +1 -1
- package/dist/cli/pack/hook-bootstrap.d.ts +5 -164
- package/dist/cli/pack/hook-bootstrap.js +15 -291
- package/dist/cli/pack/hook-bootstrap.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.d.ts +28 -21
- package/dist/cli/pack/hook-branch-protection.js +365 -340
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/index.d.ts +0 -1
- package/dist/cli/pack/index.js +0 -1
- package/dist/cli/pack/index.js.map +1 -1
- package/dist/cli/pack/list.js +1 -6
- package/dist/cli/pack/list.js.map +1 -1
- package/dist/cli/pack/mutate.d.ts +7 -9
- package/dist/cli/pack/mutate.js +6 -9
- package/dist/cli/pack/mutate.js.map +1 -1
- package/dist/cli/pack/reseed.d.ts +2 -2
- package/dist/cli/pack/reseed.js +16 -22
- package/dist/cli/pack/reseed.js.map +1 -1
- package/dist/cli/register-gate-group.d.ts +5 -0
- package/dist/cli/register-gate-group.js +348 -0
- package/dist/cli/register-gate-group.js.map +1 -0
- package/dist/cli/register-inspect-group.d.ts +6 -0
- package/dist/cli/register-inspect-group.js +189 -0
- package/dist/cli/register-inspect-group.js.map +1 -0
- package/dist/cli/register-operator-lifecycle.d.ts +5 -0
- package/dist/cli/register-operator-lifecycle.js +20 -0
- package/dist/cli/register-operator-lifecycle.js.map +1 -0
- package/dist/cli/register-pack-group.d.ts +5 -0
- package/dist/cli/register-pack-group.js +134 -0
- package/dist/cli/register-pack-group.js.map +1 -0
- package/dist/cli/register-setup-group.d.ts +5 -0
- package/dist/cli/register-setup-group.js +495 -0
- package/dist/cli/register-setup-group.js.map +1 -0
- package/dist/cli/remove/index.d.ts +0 -14
- package/dist/cli/remove/index.js +2 -115
- package/dist/cli/remove/index.js.map +1 -1
- package/dist/cli/remove/mutate.d.ts +0 -2
- package/dist/cli/remove/mutate.js +1 -19
- package/dist/cli/remove/mutate.js.map +1 -1
- package/dist/cli/remove/register.d.ts +5 -0
- package/dist/cli/remove/register.js +25 -0
- package/dist/cli/remove/register.js.map +1 -0
- package/dist/cli/shell-quote.d.ts +7 -0
- package/dist/cli/shell-quote.js +17 -0
- package/dist/cli/shell-quote.js.map +1 -0
- package/dist/cli/uninstall/index.d.ts +4 -2
- package/dist/cli/uninstall/index.js +15 -7
- package/dist/cli/uninstall/index.js.map +1 -1
- package/dist/cli/uninstall/register.d.ts +5 -0
- package/dist/cli/uninstall/register.js +171 -0
- package/dist/cli/uninstall/register.js.map +1 -0
- package/dist/cli/uninstall/snapshot.d.ts +15 -0
- package/dist/cli/uninstall/snapshot.js +38 -0
- package/dist/cli/uninstall/snapshot.js.map +1 -1
- package/dist/cli/validate/checks.d.ts +0 -77
- package/dist/cli/validate/checks.js +3 -1049
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/cli/validate/index.js +19 -15
- package/dist/cli/validate/index.js.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/io/claude-mcp.d.ts +1 -1
- package/dist/io/claude-mcp.js +27 -14
- package/dist/io/claude-mcp.js.map +1 -1
- package/dist/io/harness-lock.js +2 -2
- package/dist/io/harness-lock.js.map +1 -1
- package/dist/io/lock.d.ts +0 -26
- package/dist/io/lock.js +10 -27
- package/dist/io/lock.js.map +1 -1
- package/dist/io/merge-settings.js +6 -4
- package/dist/io/merge-settings.js.map +1 -1
- package/dist/io/project-name.d.ts +22 -39
- package/dist/io/project-name.js +25 -42
- package/dist/io/project-name.js.map +1 -1
- package/dist/io/read-regular-file.d.ts +103 -40
- package/dist/io/read-regular-file.js +196 -35
- package/dist/io/read-regular-file.js.map +1 -1
- package/dist/io/restart-hints.js +3 -6
- package/dist/io/restart-hints.js.map +1 -1
- package/dist/io/version-compare.d.ts +1 -3
- package/dist/io/version-compare.js +1 -3
- package/dist/io/version-compare.js.map +1 -1
- package/dist/io/write-regular-file.d.ts +30 -0
- package/dist/io/write-regular-file.js +85 -0
- package/dist/io/write-regular-file.js.map +1 -0
- package/dist/overrides/index.d.ts +1 -0
- package/dist/overrides/index.js +1 -0
- package/dist/overrides/index.js.map +1 -1
- package/dist/overrides/merge-layers.d.ts +43 -0
- package/dist/overrides/merge-layers.js +76 -0
- package/dist/overrides/merge-layers.js.map +1 -0
- package/dist/overrides/merge.d.ts +16 -0
- package/dist/overrides/merge.js +21 -8
- package/dist/overrides/merge.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -69
- package/dist/policy-packs/builtin/branch-protection-runtime.js +12 -84
- package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.d.ts +7 -30
- package/dist/policy-packs/builtin/branch-protection.js +56 -89
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/config-check.d.ts +7 -5
- package/dist/policy-packs/config-check.js +2 -2
- package/dist/policy-packs/config-check.js.map +1 -1
- package/dist/policy-packs/expand.d.ts +1 -3
- package/dist/policy-packs/expand.js +5 -42
- package/dist/policy-packs/expand.js.map +1 -1
- package/dist/policy-packs/index.d.ts +3 -3
- package/dist/policy-packs/index.js +2 -2
- package/dist/policy-packs/index.js.map +1 -1
- package/dist/policy-packs/registry.d.ts +9 -38
- package/dist/policy-packs/registry.js +5 -65
- package/dist/policy-packs/registry.js.map +1 -1
- package/dist/policy-packs/runtime.d.ts +0 -4
- package/dist/policy-packs/runtime.js +9 -22
- package/dist/policy-packs/runtime.js.map +1 -1
- package/dist/policy-packs/types.d.ts +0 -19
- package/dist/policy-packs/ux-compare.d.ts +1 -11
- package/dist/policy-packs/ux-compare.js +6 -42
- package/dist/policy-packs/ux-compare.js.map +1 -1
- package/dist/policy-packs/ux-drift-check.d.ts +5 -5
- package/dist/policy-packs/ux-drift-check.js +17 -22
- package/dist/policy-packs/ux-drift-check.js.map +1 -1
- package/dist/policy-packs/version-check.d.ts +5 -26
- package/dist/policy-packs/version-check.js +19 -74
- package/dist/policy-packs/version-check.js.map +1 -1
- package/dist/probes/memory.d.ts +1 -1
- package/dist/probes/memory.js +1 -1
- package/dist/probes/memory.js.map +1 -1
- package/dist/runtime/agent-facing.d.ts +9 -0
- package/dist/runtime/agent-facing.js +28 -14
- package/dist/runtime/agent-facing.js.map +1 -1
- package/dist/runtime/bounded-text.d.ts +15 -0
- package/dist/runtime/bounded-text.js +27 -0
- package/dist/runtime/bounded-text.js.map +1 -0
- package/dist/runtime/git-branch.d.ts +125 -0
- package/dist/runtime/git-branch.js +381 -0
- package/dist/runtime/git-branch.js.map +1 -0
- package/dist/runtime/hermetic-spawn-guard.d.ts +2 -2
- package/dist/runtime/hermetic-spawn-guard.js +24 -34
- package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
- package/dist/runtime/home-dir.js +3 -2
- package/dist/runtime/home-dir.js.map +1 -1
- package/dist/runtime/tool-name-aliases.d.ts +0 -6
- package/dist/runtime/tool-name-aliases.js +5 -18
- package/dist/runtime/tool-name-aliases.js.map +1 -1
- package/dist/schema/grounding.d.ts +7 -54
- package/dist/schema/grounding.js +10 -12
- package/dist/schema/grounding.js.map +1 -1
- package/dist/schema/hooks.d.ts +39 -136
- package/dist/schema/hooks.js +3 -6
- package/dist/schema/hooks.js.map +1 -1
- package/dist/schema/index.d.ts +114 -3828
- package/dist/schema/index.js +41 -80
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/memory.d.ts +54 -131
- package/dist/schema/memory.js +2 -2
- package/dist/schema/memory.js.map +1 -1
- package/dist/schema/policy-packs.d.ts +3 -45
- package/dist/schema/policy-packs.js +7 -13
- package/dist/schema/policy-packs.js.map +1 -1
- package/dist/schema/policy-ux.d.ts +7 -0
- package/dist/schema/policy-ux.js +18 -0
- package/dist/schema/policy-ux.js.map +1 -0
- package/dist/schema/removed-keys.d.ts +185 -0
- package/dist/schema/removed-keys.js +531 -0
- package/dist/schema/removed-keys.js.map +1 -0
- package/dist/schema/tools.d.ts +30 -268
- package/dist/schema/tools.js +4 -4
- package/dist/schema/tools.js.map +1 -1
- package/package.json +5 -16
- package/dist/cli/approve/branch-protection.d.ts +0 -69
- package/dist/cli/approve/branch-protection.js +0 -144
- package/dist/cli/approve/branch-protection.js.map +0 -1
- package/dist/cli/approve/risk.d.ts +0 -112
- package/dist/cli/approve/risk.js +0 -197
- package/dist/cli/approve/risk.js.map +0 -1
- package/dist/cli/approve/stdin-report.d.ts +0 -57
- package/dist/cli/approve/stdin-report.js +0 -145
- package/dist/cli/approve/stdin-report.js.map +0 -1
- package/dist/cli/approve/understanding.d.ts +0 -410
- package/dist/cli/approve/understanding.js +0 -996
- package/dist/cli/approve/understanding.js.map +0 -1
- package/dist/cli/audit.d.ts +0 -80
- package/dist/cli/audit.js +0 -252
- package/dist/cli/audit.js.map +0 -1
- package/dist/cli/delegate/index.d.ts +0 -76
- package/dist/cli/delegate/index.js +0 -443
- package/dist/cli/delegate/index.js.map +0 -1
- package/dist/cli/doctor/auto-approve-mode.d.ts +0 -37
- package/dist/cli/doctor/auto-approve-mode.js +0 -80
- package/dist/cli/doctor/auto-approve-mode.js.map +0 -1
- package/dist/cli/doctor/bypass-without-auto-approve.d.ts +0 -38
- package/dist/cli/doctor/bypass-without-auto-approve.js +0 -95
- package/dist/cli/doctor/bypass-without-auto-approve.js.map +0 -1
- package/dist/cli/doctor/codex-config-drift.d.ts +0 -28
- package/dist/cli/doctor/codex-config-drift.js +0 -240
- package/dist/cli/doctor/codex-config-drift.js.map +0 -1
- package/dist/cli/doctor/rogue-ledger.d.ts +0 -53
- package/dist/cli/doctor/rogue-ledger.js +0 -153
- package/dist/cli/doctor/rogue-ledger.js.map +0 -1
- package/dist/cli/doctor/session-start-preflight-setup-version.d.ts +0 -83
- package/dist/cli/doctor/session-start-preflight-setup-version.js +0 -114
- package/dist/cli/doctor/session-start-preflight-setup-version.js.map +0 -1
- package/dist/cli/doctor/settings-drift.d.ts +0 -25
- package/dist/cli/doctor/settings-drift.js +0 -213
- package/dist/cli/doctor/settings-drift.js.map +0 -1
- package/dist/cli/doctor/toolchain-parity.d.ts +0 -63
- package/dist/cli/doctor/toolchain-parity.js +0 -154
- package/dist/cli/doctor/toolchain-parity.js.map +0 -1
- package/dist/cli/doctor/ug-auto-approvals.d.ts +0 -40
- package/dist/cli/doctor/ug-auto-approvals.js +0 -91
- package/dist/cli/doctor/ug-auto-approvals.js.map +0 -1
- package/dist/cli/doctor/ug-delegations.d.ts +0 -26
- package/dist/cli/doctor/ug-delegations.js +0 -121
- package/dist/cli/doctor/ug-delegations.js.map +0 -1
- package/dist/cli/doctor/ug-inflight.d.ts +0 -35
- package/dist/cli/doctor/ug-inflight.js +0 -57
- package/dist/cli/doctor/ug-inflight.js.map +0 -1
- package/dist/cli/doctor/understanding-mode-env.d.ts +0 -51
- package/dist/cli/doctor/understanding-mode-env.js +0 -98
- package/dist/cli/doctor/understanding-mode-env.js.map +0 -1
- package/dist/cli/dry-run.d.ts +0 -46
- package/dist/cli/dry-run.js +0 -250
- package/dist/cli/dry-run.js.map +0 -1
- package/dist/cli/event-input.d.ts +0 -28
- package/dist/cli/event-input.js +0 -73
- package/dist/cli/event-input.js.map +0 -1
- package/dist/cli/explain-action.d.ts +0 -20
- package/dist/cli/explain-action.js +0 -27
- package/dist/cli/explain-action.js.map +0 -1
- package/dist/cli/explain-policy.d.ts +0 -135
- package/dist/cli/explain-policy.js +0 -266
- package/dist/cli/explain-policy.js.map +0 -1
- package/dist/cli/explain.d.ts +0 -31
- package/dist/cli/explain.js +0 -159
- package/dist/cli/explain.js.map +0 -1
- package/dist/cli/gc/index.d.ts +0 -63
- package/dist/cli/gc/index.js +0 -566
- package/dist/cli/gc/index.js.map +0 -1
- package/dist/cli/pack/approve-escape.d.ts +0 -19
- package/dist/cli/pack/approve-escape.js +0 -190
- package/dist/cli/pack/approve-escape.js.map +0 -1
- package/dist/cli/pack/auto-approve-path.d.ts +0 -146
- package/dist/cli/pack/auto-approve-path.js +0 -462
- package/dist/cli/pack/auto-approve-path.js.map +0 -1
- package/dist/cli/pack/hook-codex-post-tool-use.d.ts +0 -25
- package/dist/cli/pack/hook-codex-post-tool-use.js +0 -160
- package/dist/cli/pack/hook-codex-post-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +0 -48
- package/dist/cli/pack/hook-codex-pre-tool-use.js +0 -474
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-codex-stop.d.ts +0 -35
- package/dist/cli/pack/hook-codex-stop.js +0 -329
- package/dist/cli/pack/hook-codex-stop.js.map +0 -1
- package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +0 -24
- package/dist/cli/pack/hook-codex-user-prompt-submit.js +0 -93
- package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +0 -1
- package/dist/cli/pack/hook-post-merge-gate-record.d.ts +0 -37
- package/dist/cli/pack/hook-post-merge-gate-record.js +0 -190
- package/dist/cli/pack/hook-post-merge-gate-record.js.map +0 -1
- package/dist/cli/pack/hook-post-merge-gate.d.ts +0 -34
- package/dist/cli/pack/hook-post-merge-gate.js +0 -277
- package/dist/cli/pack/hook-post-merge-gate.js.map +0 -1
- package/dist/cli/pack/hook-post-tool-use.d.ts +0 -45
- package/dist/cli/pack/hook-post-tool-use.js +0 -137
- package/dist/cli/pack/hook-post-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-pre-tool-use.d.ts +0 -92
- package/dist/cli/pack/hook-pre-tool-use.js +0 -1328
- package/dist/cli/pack/hook-pre-tool-use.js.map +0 -1
- package/dist/cli/pack/hook-runtime-reality.d.ts +0 -63
- package/dist/cli/pack/hook-runtime-reality.js +0 -210
- package/dist/cli/pack/hook-runtime-reality.js.map +0 -1
- package/dist/cli/pack/hook-solution-acceptance-writeguard.d.ts +0 -32
- package/dist/cli/pack/hook-solution-acceptance-writeguard.js +0 -337
- package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +0 -1
- package/dist/cli/pack/hook-solution-acceptance.d.ts +0 -157
- package/dist/cli/pack/hook-solution-acceptance.js +0 -530
- package/dist/cli/pack/hook-solution-acceptance.js.map +0 -1
- package/dist/cli/pack/hook-stay-in-scope.d.ts +0 -37
- package/dist/cli/pack/hook-stay-in-scope.js +0 -128
- package/dist/cli/pack/hook-stay-in-scope.js.map +0 -1
- package/dist/cli/pack/hook-subagent-start.d.ts +0 -23
- package/dist/cli/pack/hook-subagent-start.js +0 -88
- package/dist/cli/pack/hook-subagent-start.js.map +0 -1
- package/dist/cli/pack/hook-subagent-stop.d.ts +0 -19
- package/dist/cli/pack/hook-subagent-stop.js +0 -41
- package/dist/cli/pack/hook-subagent-stop.js.map +0 -1
- package/dist/cli/pack/hook-track-active-claim.d.ts +0 -27
- package/dist/cli/pack/hook-track-active-claim.js +0 -275
- package/dist/cli/pack/hook-track-active-claim.js.map +0 -1
- package/dist/cli/pack/transcript-report-scan.d.ts +0 -71
- package/dist/cli/pack/transcript-report-scan.js +0 -359
- package/dist/cli/pack/transcript-report-scan.js.map +0 -1
- package/dist/cli/pack/understanding-report-schema-hint.d.ts +0 -26
- package/dist/cli/pack/understanding-report-schema-hint.js +0 -120
- package/dist/cli/pack/understanding-report-schema-hint.js.map +0 -1
- package/dist/cli/pack/upgrade.d.ts +0 -32
- package/dist/cli/pack/upgrade.js +0 -273
- package/dist/cli/pack/upgrade.js.map +0 -1
- package/dist/cli/pause/index.d.ts +0 -113
- package/dist/cli/pause/index.js +0 -251
- package/dist/cli/pause/index.js.map +0 -1
- package/dist/cli/pause-check.d.ts +0 -22
- package/dist/cli/pause-check.js +0 -54
- package/dist/cli/pause-check.js.map +0 -1
- package/dist/cli/policy/intercept.d.ts +0 -233
- package/dist/cli/policy/intercept.js +0 -1020
- package/dist/cli/policy/intercept.js.map +0 -1
- package/dist/cli/policy/risk-envelope-enrichment.d.ts +0 -40
- package/dist/cli/policy/risk-envelope-enrichment.js +0 -108
- package/dist/cli/policy/risk-envelope-enrichment.js.map +0 -1
- package/dist/cli/record/index.d.ts +0 -81
- package/dist/cli/record/index.js +0 -348
- package/dist/cli/record/index.js.map +0 -1
- package/dist/cli/resolve-env.d.ts +0 -32
- package/dist/cli/resolve-env.js +0 -47
- package/dist/cli/resolve-env.js.map +0 -1
- package/dist/cli/session-export/index.d.ts +0 -46
- package/dist/cli/session-export/index.js +0 -169
- package/dist/cli/session-export/index.js.map +0 -1
- package/dist/cli/session-export/redact.d.ts +0 -22
- package/dist/cli/session-export/redact.js +0 -47
- package/dist/cli/session-export/redact.js.map +0 -1
- package/dist/cli/session-export/transcript.d.ts +0 -24
- package/dist/cli/session-export/transcript.js +0 -164
- package/dist/cli/session-export/transcript.js.map +0 -1
- package/dist/cli/session-start/branch-check.d.ts +0 -18
- package/dist/cli/session-start/branch-check.js +0 -140
- package/dist/cli/session-start/branch-check.js.map +0 -1
- package/dist/cli/session-start/index.d.ts +0 -121
- package/dist/cli/session-start/index.js +0 -591
- package/dist/cli/session-start/index.js.map +0 -1
- package/dist/cli/session-start/shared-options.d.ts +0 -103
- package/dist/cli/session-start/shared-options.js +0 -107
- package/dist/cli/session-start/shared-options.js.map +0 -1
- package/dist/cli/session-start/stale-base-check.d.ts +0 -46
- package/dist/cli/session-start/stale-base-check.js +0 -352
- package/dist/cli/session-start/stale-base-check.js.map +0 -1
- package/dist/cli/session-start/toolchain-parity.d.ts +0 -267
- package/dist/cli/session-start/toolchain-parity.js +0 -797
- package/dist/cli/session-start/toolchain-parity.js.map +0 -1
- package/dist/cli/smoke/assertions.d.ts +0 -46
- package/dist/cli/smoke/assertions.js +0 -170
- package/dist/cli/smoke/assertions.js.map +0 -1
- package/dist/cli/smoke/index.d.ts +0 -76
- package/dist/cli/smoke/index.js +0 -245
- package/dist/cli/smoke/index.js.map +0 -1
- package/dist/cli/smoke/runner.d.ts +0 -55
- package/dist/cli/smoke/runner.js +0 -134
- package/dist/cli/smoke/runner.js.map +0 -1
- package/dist/cli/smoke/stream-parser.d.ts +0 -65
- package/dist/cli/smoke/stream-parser.js +0 -115
- package/dist/cli/smoke/stream-parser.js.map +0 -1
- package/dist/cli/test-risk.d.ts +0 -26
- package/dist/cli/test-risk.js +0 -34
- package/dist/cli/test-risk.js.map +0 -1
- package/dist/io/display-path.d.ts +0 -11
- package/dist/io/display-path.js +0 -29
- package/dist/io/display-path.js.map +0 -1
- package/dist/io/duration.d.ts +0 -5
- package/dist/io/duration.js +0 -50
- package/dist/io/duration.js.map +0 -1
- package/dist/io/extract.d.ts +0 -79
- package/dist/io/extract.js +0 -226
- package/dist/io/extract.js.map +0 -1
- package/dist/io/ledger-record.d.ts +0 -83
- package/dist/io/ledger-record.js +0 -301
- package/dist/io/ledger-record.js.map +0 -1
- package/dist/io/read-json-dir-entries.d.ts +0 -36
- package/dist/io/read-json-dir-entries.js +0 -48
- package/dist/io/read-json-dir-entries.js.map +0 -1
- package/dist/io/safe-json-parse.d.ts +0 -5
- package/dist/io/safe-json-parse.js +0 -21
- package/dist/io/safe-json-parse.js.map +0 -1
- package/dist/policies/index.d.ts +0 -5
- package/dist/policies/index.js +0 -6
- package/dist/policies/index.js.map +0 -1
- package/dist/policies/ledger-client.d.ts +0 -113
- package/dist/policies/ledger-client.js +0 -500
- package/dist/policies/ledger-client.js.map +0 -1
- package/dist/policies/producers.d.ts +0 -12
- package/dist/policies/producers.js +0 -61
- package/dist/policies/producers.js.map +0 -1
- package/dist/policies/requires.d.ts +0 -75
- package/dist/policies/requires.js +0 -240
- package/dist/policies/requires.js.map +0 -1
- package/dist/policies/timestamp.d.ts +0 -14
- package/dist/policies/timestamp.js +0 -36
- package/dist/policies/timestamp.js.map +0 -1
- package/dist/policy-packs/builtin/permission-profiles.d.ts +0 -11
- package/dist/policy-packs/builtin/permission-profiles.js +0 -74
- package/dist/policy-packs/builtin/permission-profiles.js.map +0 -1
- package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +0 -286
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js +0 -397
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +0 -1
- package/dist/policy-packs/builtin/post-merge-gate.d.ts +0 -56
- package/dist/policy-packs/builtin/post-merge-gate.js +0 -347
- package/dist/policy-packs/builtin/post-merge-gate.js.map +0 -1
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.d.ts +0 -50
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.js +0 -151
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.js.map +0 -1
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +0 -323
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +0 -672
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +0 -1
- package/dist/policy-packs/builtin/solution-acceptance.d.ts +0 -45
- package/dist/policy-packs/builtin/solution-acceptance.js +0 -226
- package/dist/policy-packs/builtin/solution-acceptance.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +0 -25
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +0 -94
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.d.ts +0 -35
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js +0 -86
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +0 -210
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +0 -361
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +0 -304
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +0 -734
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.d.ts +0 -36
- package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js +0 -73
- package/dist/policy-packs/builtin/understanding-before-execution/expiry-reason.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +0 -19
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +0 -83
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.d.ts +0 -135
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +0 -618
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +0 -25
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +0 -56
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +0 -20
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +0 -93
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +0 -126
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js +0 -245
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.d.ts +0 -29
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js +0 -82
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.d.ts +0 -71
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +0 -175
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +0 -620
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +0 -1047
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +0 -128
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +0 -213
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.d.ts +0 -129
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js +0 -166
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +0 -164
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +0 -275
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +0 -10
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +0 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +0 -526
- package/dist/policy-packs/builtin/understanding-before-execution.js +0 -1074
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +0 -1
- package/dist/policy-packs/permission-translator.d.ts +0 -9
- package/dist/policy-packs/permission-translator.js +0 -76
- package/dist/policy-packs/permission-translator.js.map +0 -1
- package/dist/runtime/action-envelope.d.ts +0 -64
- package/dist/runtime/action-envelope.js +0 -46
- package/dist/runtime/action-envelope.js.map +0 -1
- package/dist/runtime/approval-signing.d.ts +0 -111
- package/dist/runtime/approval-signing.js +0 -384
- package/dist/runtime/approval-signing.js.map +0 -1
- package/dist/runtime/bash-match-facts.d.ts +0 -60
- package/dist/runtime/bash-match-facts.js +0 -196
- package/dist/runtime/bash-match-facts.js.map +0 -1
- package/dist/runtime/bash-match-registry.d.ts +0 -112
- package/dist/runtime/bash-match-registry.js +0 -406
- package/dist/runtime/bash-match-registry.js.map +0 -1
- package/dist/runtime/bash-prefix-parse.d.ts +0 -31
- package/dist/runtime/bash-prefix-parse.js +0 -576
- package/dist/runtime/bash-prefix-parse.js.map +0 -1
- package/dist/runtime/command-normalize.d.ts +0 -658
- package/dist/runtime/command-normalize.js +0 -2410
- package/dist/runtime/command-normalize.js.map +0 -1
- package/dist/runtime/deletion-target-resolve.d.ts +0 -28
- package/dist/runtime/deletion-target-resolve.js +0 -1078
- package/dist/runtime/deletion-target-resolve.js.map +0 -1
- package/dist/runtime/destructive-shell-floor.d.ts +0 -19
- package/dist/runtime/destructive-shell-floor.js +0 -611
- package/dist/runtime/destructive-shell-floor.js.map +0 -1
- package/dist/runtime/environment-resolver.d.ts +0 -36
- package/dist/runtime/environment-resolver.js +0 -138
- package/dist/runtime/environment-resolver.js.map +0 -1
- package/dist/runtime/git-context.d.ts +0 -201
- package/dist/runtime/git-context.js +0 -422
- package/dist/runtime/git-context.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -12
- package/dist/runtime/index.js +0 -13
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/intercept.d.ts +0 -494
- package/dist/runtime/intercept.js +0 -1313
- package/dist/runtime/intercept.js.map +0 -1
- package/dist/runtime/kube-context.d.ts +0 -16
- package/dist/runtime/kube-context.js +0 -63
- package/dist/runtime/kube-context.js.map +0 -1
- package/dist/runtime/kubectl-target-parse.d.ts +0 -31
- package/dist/runtime/kubectl-target-parse.js +0 -249
- package/dist/runtime/kubectl-target-parse.js.map +0 -1
- package/dist/runtime/ledger-add.d.ts +0 -16
- package/dist/runtime/ledger-add.js +0 -150
- package/dist/runtime/ledger-add.js.map +0 -1
- package/dist/runtime/ledger-writer.d.ts +0 -27
- package/dist/runtime/ledger-writer.js +0 -46
- package/dist/runtime/ledger-writer.js.map +0 -1
- package/dist/runtime/pause-sentinel.d.ts +0 -60
- package/dist/runtime/pause-sentinel.js +0 -156
- package/dist/runtime/pause-sentinel.js.map +0 -1
- package/dist/runtime/pending-approval.d.ts +0 -23
- package/dist/runtime/pending-approval.js +0 -67
- package/dist/runtime/pending-approval.js.map +0 -1
- package/dist/runtime/read-only-bash.d.ts +0 -141
- package/dist/runtime/read-only-bash.js +0 -2204
- package/dist/runtime/read-only-bash.js.map +0 -1
- package/dist/runtime/recovery-git-commit.d.ts +0 -22
- package/dist/runtime/recovery-git-commit.js +0 -284
- package/dist/runtime/recovery-git-commit.js.map +0 -1
- package/dist/runtime/reject-malformed-session-id.d.ts +0 -8
- package/dist/runtime/reject-malformed-session-id.js +0 -16
- package/dist/runtime/reject-malformed-session-id.js.map +0 -1
- package/dist/runtime/risk-classifier.d.ts +0 -38
- package/dist/runtime/risk-classifier.js +0 -311
- package/dist/runtime/risk-classifier.js.map +0 -1
- package/dist/runtime/session-id.d.ts +0 -113
- package/dist/runtime/session-id.js +0 -193
- package/dist/runtime/session-id.js.map +0 -1
- package/dist/runtime/shell-word.d.ts +0 -62
- package/dist/runtime/shell-word.js +0 -410
- package/dist/runtime/shell-word.js.map +0 -1
- package/dist/runtime/task-providers/agent-tasks.d.ts +0 -103
- package/dist/runtime/task-providers/agent-tasks.js +0 -245
- package/dist/runtime/task-providers/agent-tasks.js.map +0 -1
- package/dist/runtime/when-eval.d.ts +0 -62
- package/dist/runtime/when-eval.js +0 -212
- package/dist/runtime/when-eval.js.map +0 -1
- package/dist/runtime/workflow-policies.d.ts +0 -207
- package/dist/runtime/workflow-policies.js +0 -596
- package/dist/runtime/workflow-policies.js.map +0 -1
- package/dist/schema/audit.d.ts +0 -71
- package/dist/schema/audit.js +0 -32
- package/dist/schema/audit.js.map +0 -1
- package/dist/schema/doctor.d.ts +0 -25
- package/dist/schema/doctor.js +0 -23
- package/dist/schema/doctor.js.map +0 -1
- package/dist/schema/environments.d.ts +0 -215
- package/dist/schema/environments.js +0 -101
- package/dist/schema/environments.js.map +0 -1
- package/dist/schema/extract.d.ts +0 -9
- package/dist/schema/extract.js +0 -86
- package/dist/schema/extract.js.map +0 -1
- package/dist/schema/permission-profiles.d.ts +0 -2292
- package/dist/schema/permission-profiles.js +0 -60
- package/dist/schema/permission-profiles.js.map +0 -1
- package/dist/schema/policies.d.ts +0 -974
- package/dist/schema/policies.js +0 -298
- package/dist/schema/policies.js.map +0 -1
- package/dist/schema/requires.d.ts +0 -131
- package/dist/schema/requires.js +0 -88
- package/dist/schema/requires.js.map +0 -1
- package/dist/schema/risk.d.ts +0 -144
- package/dist/schema/risk.js +0 -194
- package/dist/schema/risk.js.map +0 -1
- package/dist/schema/session-start-preflight.d.ts +0 -10
- package/dist/schema/session-start-preflight.js +0 -106
- package/dist/schema/session-start-preflight.js.map +0 -1
- package/dist/schema/stale-base-check.d.ts +0 -18
- package/dist/schema/stale-base-check.js +0 -37
- package/dist/schema/stale-base-check.js.map +0 -1
- package/dist/schema/toolchain-parity.d.ts +0 -21
- package/dist/schema/toolchain-parity.js +0 -48
- package/dist/schema/toolchain-parity.js.map +0 -1
- package/dist/schema/workflows.d.ts +0 -519
- package/dist/schema/workflows.js +0 -81
- package/dist/schema/workflows.js.map +0 -1
- package/scripts/runtime-reality-docker-probe.mjs +0 -71
|
@@ -1,996 +0,0 @@
|
|
|
1
|
-
// Phase 6 #4 — `harness approve understanding [--session <id>]` CLI verb.
|
|
2
|
-
//
|
|
3
|
-
// Round-trips both approval sources for the
|
|
4
|
-
// `understanding-before-execution` pack:
|
|
5
|
-
//
|
|
6
|
-
// 1. Writes the evidence-ledger tag `understanding-approved:${SESSION_ID}`
|
|
7
|
-
// via `grounding-mcp`'s `ledger_add` (best-effort: degraded ledger
|
|
8
|
-
// surfaces as a warning, not a hard failure).
|
|
9
|
-
// 2. Flips `approvalStatus: "approved"` on the latest persisted JSON
|
|
10
|
-
// report under `.understanding-gate/reports/`. Atomic rewrite.
|
|
11
|
-
//
|
|
12
|
-
// Rationale for writing both: harnessed sessions consult the ledger as
|
|
13
|
-
// canonical, but a solo `@lannguyensi/understanding-gate` user without
|
|
14
|
-
// `grounding-mcp` wired only sees the persisted JSON. Round-tripping
|
|
15
|
-
// both means switching between the two stacks doesn't lose history.
|
|
16
|
-
import * as fs from "node:fs";
|
|
17
|
-
import * as os from "node:os";
|
|
18
|
-
import * as path from "node:path";
|
|
19
|
-
import { atomicWriteFile } from "../../io/atomic-write.js";
|
|
20
|
-
import { escapeForDisplay } from "../../io/display-path.js";
|
|
21
|
-
import { approvedLedgerTagFor, defaultReportsDir, hashReportFile, listDirNamesBounded, listPersistedReportsBoundedWithSkips, MAX_HASHED_REPORT_BYTES, readReportFileBounded, readActiveClaim, selectReportForSession, TOLERANT_FALLBACK_MAX_AGE_MS, writeApprovalMarker, writeTaskApprovalMarker, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
|
|
22
|
-
import { PACK_NAME as UNDERSTANDING_PACK_NAME, resolveMode, toPackageMode, } from "../../policy-packs/builtin/understanding-before-execution.js";
|
|
23
|
-
import { addLedgerFact } from "../../runtime/ledger-add.js";
|
|
24
|
-
import { clearPendingApproval, resolveGeneratedDir, } from "../../runtime/pending-approval.js";
|
|
25
|
-
import { resolveApprovalSessionId } from "../../runtime/session-id.js";
|
|
26
|
-
import { EX_FAIL, HarnessExitError } from "../exit-codes.js";
|
|
27
|
-
import { loadManifest, resolvePaths } from "../loader.js";
|
|
28
|
-
import { describeSectionKey } from "../pack/understanding-report-schema-hint.js";
|
|
29
|
-
import { persistStdinReport } from "./stdin-report.js";
|
|
30
|
-
/**
|
|
31
|
-
* Human-scale age for operator messages: minutes up to two hours,
|
|
32
|
-
* hours up to two days, then days. A 17-day-old report should read
|
|
33
|
-
* "17d", not "24893m" (review finding on harness-discovery C1).
|
|
34
|
-
*/
|
|
35
|
-
function formatAge(ms) {
|
|
36
|
-
const min = Math.round(ms / 60_000);
|
|
37
|
-
if (Math.abs(min) < 120)
|
|
38
|
-
return `${min}m`;
|
|
39
|
-
const hours = Math.round(min / 60);
|
|
40
|
-
if (Math.abs(hours) < 48)
|
|
41
|
-
return `${hours}h`;
|
|
42
|
-
return `${Math.round(hours / 24)}d`;
|
|
43
|
-
}
|
|
44
|
-
const DEFAULT_APPROVED_BY = "harness-approve-cli";
|
|
45
|
-
function findGroundingMcp(manifest) {
|
|
46
|
-
return manifest.tools.mcp.find((m) => m.name === "grounding-mcp") ?? null;
|
|
47
|
-
}
|
|
48
|
-
function expandHomePath(p) {
|
|
49
|
-
if (p === "~")
|
|
50
|
-
return process.env.HOME ?? os.homedir();
|
|
51
|
-
if (p.startsWith("~/"))
|
|
52
|
-
return path.join(process.env.HOME ?? os.homedir(), p.slice(2));
|
|
53
|
-
return p;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Load the manifest (best-effort, never throws) and resolve the
|
|
57
|
-
* `understanding-before-execution` pack's declared config entry from it.
|
|
58
|
-
* Extracted (agent-tasks 37ad0b05) so
|
|
59
|
-
* `approveUnderstanding` and `issueDelegation` (`src/cli/delegate/index.ts`)
|
|
60
|
-
* share one copy of this manifest-load-plus-pack-lookup shape instead of
|
|
61
|
-
* each carrying its own byte-identical block
|
|
62
|
-
* (`npm run check:duplication` was flagging the pair as a clone). Pure
|
|
63
|
-
* extraction: the try/catch and the `declaredPack` fallback are unchanged
|
|
64
|
-
* from what each call site did inline before this change.
|
|
65
|
-
*/
|
|
66
|
-
export function loadDeclaredUnderstandingPack(opts) {
|
|
67
|
-
let manifest = null;
|
|
68
|
-
let manifestLoadError = null;
|
|
69
|
-
try {
|
|
70
|
-
manifest = opts.manifest ?? loadManifest(opts).manifest;
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
manifestLoadError = err instanceof Error ? err.message : String(err);
|
|
74
|
-
}
|
|
75
|
-
const declaredPack = manifest?.policy_packs.find((p) => p.name === UNDERSTANDING_PACK_NAME && p.enabled !== false) ?? { name: UNDERSTANDING_PACK_NAME, config: {} };
|
|
76
|
-
return { manifest, manifestLoadError, declaredPack };
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Exported (agent-tasks 37ad0b05, Slice 3 of the auto-mode-approval ADR)
|
|
80
|
-
* with an additive `source` parameter so `harness delegate` can reuse
|
|
81
|
-
* this writer verbatim instead of re-implementing the same
|
|
82
|
-
* findGroundingMcp + addLedgerFact shape a third time in this codebase
|
|
83
|
-
* (branch-protection.ts already carries its own independent copy for
|
|
84
|
-
* its own `harness-approve-branch-protection` source). `source` defaults
|
|
85
|
-
* to this file's own literal, so `approveUnderstanding`'s own call below
|
|
86
|
-
* (which does not pass it) is byte-identical to before this change: pure
|
|
87
|
-
* additive parameterization, not a behaviour change for that caller.
|
|
88
|
-
*/
|
|
89
|
-
export async function writeLedgerTag(manifest, sessionId, content, opts, source = "harness-approve-understanding") {
|
|
90
|
-
if (opts.ledgerAdd)
|
|
91
|
-
return opts.ledgerAdd(sessionId, content);
|
|
92
|
-
const server = findGroundingMcp(manifest);
|
|
93
|
-
if (!server) {
|
|
94
|
-
return { ok: false, reason: "grounding-mcp not declared in manifest" };
|
|
95
|
-
}
|
|
96
|
-
const command = Array.isArray(server.command)
|
|
97
|
-
? server.command.map(expandHomePath)
|
|
98
|
-
: server.command.trim().split(/\s+/).map(expandHomePath);
|
|
99
|
-
return addLedgerFact({
|
|
100
|
-
mcpCommand: command,
|
|
101
|
-
...(server.env && { mcpEnv: server.env }),
|
|
102
|
-
timeoutMs: server.health?.timeout_ms ?? 5_000,
|
|
103
|
-
sessionId,
|
|
104
|
-
content,
|
|
105
|
-
source,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Find the freshest parse-error log under `<dir>` (the
|
|
110
|
-
* `<reports-dir>/../parse-errors/` location the standalone Stop hook
|
|
111
|
-
* writes to when `parseReport` rejects the agent's last message). Used
|
|
112
|
-
* to upgrade `approve understanding`'s "no reports found" diagnostic
|
|
113
|
-
* from a silent dead end to a "hook fired but parse failed because X"
|
|
114
|
-
* pointer. Best-effort: any I/O error is swallowed and we report no
|
|
115
|
-
* parse-error, mirroring the listPersistedReports contract.
|
|
116
|
-
*
|
|
117
|
-
* `sessionId` filter (agent-tasks/b13205b2): each parse-error log's JSON
|
|
118
|
-
* header carries the `sessionId` of the session that produced it. The
|
|
119
|
-
* lookup used to return the directory-newest log regardless of whose
|
|
120
|
-
* session wrote it, so a stale parse-error from a previous session would
|
|
121
|
-
* surface in the current operator's approve output and read like a
|
|
122
|
-
* failure of THEIR session. Logs whose header sessionId does not match
|
|
123
|
-
* `sessionId` are now skipped entirely. Logs missing a `sessionId` field
|
|
124
|
-
* (or whose header is not JSON) are also skipped, since we cannot
|
|
125
|
-
* attribute them.
|
|
126
|
-
*
|
|
127
|
-
* `malformedSections` (task 823837fd, follow-up to 7e29e5d7): when the
|
|
128
|
-
* log's JSON header carries a non-empty `malformedSections` array (a
|
|
129
|
-
* (list) section that was present but not a markdown list), the returned
|
|
130
|
-
* `summary` names those sections explicitly so the CLI's one-line reason
|
|
131
|
-
* does not read as a bare "missing" when the agent actually wrote
|
|
132
|
-
* *something* under the heading. Exported so the PreToolUse hooks
|
|
133
|
-
* (hook-pre-tool-use.ts, hook-codex-pre-tool-use.ts) can reuse the same
|
|
134
|
-
* lookup + attribution logic rather than re-implementing the header scan.
|
|
135
|
-
*
|
|
136
|
-
* Both hooks pass `maxEntries` (`MAX_HOOK_LISTING_ENTRIES`): a directory with
|
|
137
|
-
* more `*.log` entries than that is read not at all and yields no parse error,
|
|
138
|
-
* and the logs read are charged against the hash scan's byte budget (the
|
|
139
|
-
* larger of the `fstat` size and the per-entry floor, newest first), so a
|
|
140
|
-
* planted directory cannot run the hook past its time budget. Once the budget
|
|
141
|
-
* is spent the lookup yields no parse error.
|
|
142
|
-
*/
|
|
143
|
-
export function findLatestParseError(dir, sessionId, opts = {}) {
|
|
144
|
-
// `maxEntries` is set by the PreToolUse hooks (the directory is agent-writable
|
|
145
|
-
// and the lookup stats and reads its logs): past it nothing is opened and the
|
|
146
|
-
// answer is no parse error (the listing of a too large directory is empty),
|
|
147
|
-
// and the reads below spend a byte budget. The operator command leaves it unset.
|
|
148
|
-
const listed = listDirNamesBounded(dir, ".log", opts.maxEntries ?? Number.POSITIVE_INFINITY);
|
|
149
|
-
const budget = opts.maxEntries === undefined ? undefined : { spent: 0 };
|
|
150
|
-
const candidates = [];
|
|
151
|
-
for (const name of listed.names) {
|
|
152
|
-
const full = path.join(dir, name);
|
|
153
|
-
let stat;
|
|
154
|
-
try {
|
|
155
|
-
stat = fs.statSync(full);
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
candidates.push({ filePath: full, mtimeMs: stat.mtimeMs });
|
|
161
|
-
}
|
|
162
|
-
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
163
|
-
for (const cand of candidates) {
|
|
164
|
-
// Agent-writable dir, reached by both hooks on the no-marker path: the stat
|
|
165
|
-
// above only orders the candidates; the bounded reader's descriptor decides
|
|
166
|
-
// type and size, so a FIFO, a huge or a growing log is skipped like an
|
|
167
|
-
// unreadable one.
|
|
168
|
-
const bounded = readReportFileBounded(cand.filePath, { budget });
|
|
169
|
-
if (!bounded.ok && bounded.reason === "over-budget")
|
|
170
|
-
return null;
|
|
171
|
-
if (!bounded.ok)
|
|
172
|
-
continue;
|
|
173
|
-
const raw = bounded.raw;
|
|
174
|
-
// The standalone package writes a JSON header followed by `--- raw ---`
|
|
175
|
-
// and the original assistant text. Read the header for a `message`,
|
|
176
|
-
// `reason`, or `missing` field; fall back to the first line if the
|
|
177
|
-
// schema is unfamiliar so a future format change still surfaces
|
|
178
|
-
// *something* rather than going silent.
|
|
179
|
-
const header = raw.split("\n--- raw ---")[0] ?? raw;
|
|
180
|
-
let summary = (header.split("\n")[0] ?? "").trim();
|
|
181
|
-
let headerSessionId = null;
|
|
182
|
-
let malformedSections = [];
|
|
183
|
-
// Tracks whether `summary` was taken verbatim from the package's own
|
|
184
|
-
// `message` field: `@lannguyensi/understanding-gate` >= 0.4.10 already
|
|
185
|
-
// annotates each malformed (list) section inline in that string
|
|
186
|
-
// ("Prior Art (list) (present but not a markdown list -- use '- ' or
|
|
187
|
-
// '1.' items)"). Appending the same names again below would duplicate
|
|
188
|
-
// them in one line; the append is only useful when `summary` came from
|
|
189
|
-
// the `reason` fallback or the raw first-line fallback, neither of
|
|
190
|
-
// which mentions the malformed sections on its own.
|
|
191
|
-
let summaryFromMessage = false;
|
|
192
|
-
try {
|
|
193
|
-
const parsed = JSON.parse(header);
|
|
194
|
-
if (typeof parsed["sessionId"] === "string") {
|
|
195
|
-
headerSessionId = parsed["sessionId"];
|
|
196
|
-
}
|
|
197
|
-
if (Array.isArray(parsed["malformedSections"])) {
|
|
198
|
-
malformedSections = parsed["malformedSections"].filter((m) => typeof m === "string");
|
|
199
|
-
}
|
|
200
|
-
if (typeof parsed["message"] === "string" && parsed["message"].length > 0) {
|
|
201
|
-
summary = parsed["message"];
|
|
202
|
-
summaryFromMessage = true;
|
|
203
|
-
}
|
|
204
|
-
else if (typeof parsed["reason"] === "string") {
|
|
205
|
-
const missing = Array.isArray(parsed["missing"])
|
|
206
|
-
? ` (missing: ${parsed["missing"].filter((m) => typeof m === "string").join(", ")})`
|
|
207
|
-
: "";
|
|
208
|
-
summary = `${parsed["reason"]}${missing}`;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
/* keep the first-line fallback; headerSessionId stays null */
|
|
213
|
-
}
|
|
214
|
-
if (headerSessionId !== sessionId)
|
|
215
|
-
continue;
|
|
216
|
-
if (malformedSections.length > 0 && !summaryFromMessage) {
|
|
217
|
-
summary = `${summary}; malformed (present but not a markdown list): ${malformedSections.join(", ")}`;
|
|
218
|
-
}
|
|
219
|
-
return { filePath: cand.filePath, summary, malformedSections };
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Render the agent-facing "your previous report had malformed sections"
|
|
225
|
-
* sentence from a `ParseErrorSummary.malformedSections` list, or `null`
|
|
226
|
-
* when there is nothing to say. Shared by both PreToolUse hooks
|
|
227
|
-
* (hook-pre-tool-use.ts, hook-codex-pre-tool-use.ts, task 823837fd
|
|
228
|
-
* review) so the sentence is defined once instead of duplicated
|
|
229
|
-
* byte-identically in each runtime; same extraction shape as
|
|
230
|
-
* `checkOperatorApprovalMarkers` (understanding-before-execution-runtime.ts),
|
|
231
|
-
* a marker-check helper both hooks already call instead of
|
|
232
|
-
* re-implementing.
|
|
233
|
-
*
|
|
234
|
-
* Section keys are mapped to their display name via `describeSectionKey`
|
|
235
|
-
* (`Prior Art (priorArt)` rather than the bare camelCase key) so the
|
|
236
|
-
* sentence reads consistently with `renderReportSchemaHint`'s section
|
|
237
|
-
* bullets in the same block reason, instead of mixing two vocabularies
|
|
238
|
-
* for the same section. The CLI's own one-line summary
|
|
239
|
-
* (`findLatestParseError` above) intentionally stays on raw keys; it is
|
|
240
|
-
* operator-facing and matches the parse-error log verbatim.
|
|
241
|
-
*/
|
|
242
|
-
export function renderMalformedSectionsNotice(sections) {
|
|
243
|
-
if (sections.length === 0)
|
|
244
|
-
return null;
|
|
245
|
-
const named = sections.map((s) => describeSectionKey(s)).join(", ");
|
|
246
|
-
return (`Your previous Understanding Report attempt had malformed sections ` +
|
|
247
|
-
`(present but not a markdown list): ${named}.`);
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Exported (agent-tasks/74b4b17d) so the PreToolUse hook's auto-approval
|
|
251
|
-
* path applies the SAME approve-time content validation this CLI does,
|
|
252
|
-
* rather than a second, drifting copy: the ADR
|
|
253
|
-
* (docs/decisions/2026-08-27-ug-auto-mode-approval.md, Option A
|
|
254
|
-
* condition 3) requires the auto path's report precondition to be the
|
|
255
|
-
* approve CLI's own check. Export only; the logic is unchanged.
|
|
256
|
-
*/
|
|
257
|
-
export function validatePersistedReport(parsed) {
|
|
258
|
-
const mode = typeof parsed["mode"] === "string" ? parsed["mode"] : null;
|
|
259
|
-
if (mode !== "grill_me")
|
|
260
|
-
return { ok: true, mode };
|
|
261
|
-
const priorArt = parsed["priorArt"];
|
|
262
|
-
if (!Array.isArray(priorArt) || priorArt.length === 0) {
|
|
263
|
-
return {
|
|
264
|
-
ok: false,
|
|
265
|
-
field: "priorArt",
|
|
266
|
-
reason: "grill_me report is missing required Section 10 (Prior Art). " +
|
|
267
|
-
"The schema requires a non-empty list naming the channels searched " +
|
|
268
|
-
"and the closest existing solution.",
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
// Mirror the schema's `items: { type: "string", minLength: 1 }`.
|
|
272
|
-
for (const item of priorArt) {
|
|
273
|
-
if (typeof item !== "string" || item.trim().length === 0) {
|
|
274
|
-
return {
|
|
275
|
-
ok: false,
|
|
276
|
-
field: "priorArt",
|
|
277
|
-
reason: "grill_me report's priorArt contains a non-string or empty item; " +
|
|
278
|
-
"every entry must be a non-empty string.",
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
// Deterrent for the literal `- None` / `None` placeholder. The parser
|
|
283
|
-
// intentionally accepts these structurally; approve-time is where the
|
|
284
|
-
// prompt's "do not write `- None`" rule turns into a hard refusal.
|
|
285
|
-
// Only blocks when EVERY item is the None literal; a mixed list with
|
|
286
|
-
// real content is accepted.
|
|
287
|
-
const isNone = (x) => typeof x === "string" && ["none", "- none"].includes(x.trim().toLowerCase());
|
|
288
|
-
if (priorArt.every(isNone)) {
|
|
289
|
-
return {
|
|
290
|
-
ok: false,
|
|
291
|
-
field: "priorArt",
|
|
292
|
-
reason: "grill_me report's priorArt is entirely literal `None` / `- None`. " +
|
|
293
|
-
"The section exists to force a written answer to 'should this be " +
|
|
294
|
-
"built at all'; name the channels searched and the closest existing tool.",
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
return { ok: true, mode };
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Exported (agent-tasks/74b4b17d) so the PreToolUse hook's auto-approval path
|
|
301
|
-
* CONSUMES the report it used through the identical rewrite this CLI performs
|
|
302
|
-
* (ADR Option A condition 5). `boundedRaw`: the text that path already read
|
|
303
|
-
* through the bounded reader, so the consume never re-reads the agent-writable
|
|
304
|
-
* path (a retargeted symlink); `harness approve understanding` reads by path
|
|
305
|
-
* through the same bounded reader.
|
|
306
|
-
*/
|
|
307
|
-
export function rewriteReportApproved(filePath, approvedAt, approvedBy, sessionId, boundedRaw) {
|
|
308
|
-
let raw = boundedRaw;
|
|
309
|
-
if (raw === undefined) {
|
|
310
|
-
// Bounded like every other read of this agent-writable directory: the path
|
|
311
|
-
// can be swapped for an oversized file or a FIFO after the listing.
|
|
312
|
-
const read = readReportFileBounded(filePath, { noFollow: true });
|
|
313
|
-
if (!read.ok)
|
|
314
|
-
throw new Error(`report ${read.detail}`);
|
|
315
|
-
raw = read.raw;
|
|
316
|
-
}
|
|
317
|
-
const { text, previousStatus, sessionIdStamped } = renderApprovedReport(raw, approvedAt, approvedBy, sessionId);
|
|
318
|
-
// Temp file plus rename: the rename replaces the directory entry (a symlink
|
|
319
|
-
// included) and never opens whatever a retargeted symlink points at.
|
|
320
|
-
atomicWriteFile(filePath, text);
|
|
321
|
-
return { previousStatus, sessionIdStamped };
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Thrown by {@link renderApprovedReport} when the approved report, once
|
|
325
|
-
* pretty-printed with its approval fields, is over `MAX_HASHED_REPORT_BYTES`.
|
|
326
|
-
* The gate-read scan reads no file over that cap, so a marker signed for such
|
|
327
|
-
* a report would deny on every call and neither re-approval nor `--force`
|
|
328
|
-
* could clear it.
|
|
329
|
-
*/
|
|
330
|
-
export class ApprovedReportTooLargeError extends Error {
|
|
331
|
-
bytes;
|
|
332
|
-
constructor(bytes) {
|
|
333
|
-
super(`the approved report would be ${bytes} bytes, over the ${MAX_HASHED_REPORT_BYTES}-byte cap ` +
|
|
334
|
-
"for hashing its content, so the gate could not match it");
|
|
335
|
-
this.bytes = bytes;
|
|
336
|
-
this.name = "ApprovedReportTooLargeError";
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* The text `rewriteReportApproved` writes: the report with its approval
|
|
341
|
-
* fields stamped and pretty-printed. Pure, so a producer can check the size
|
|
342
|
-
* of the rewritten bytes before it signs a marker or changes any file. Throws
|
|
343
|
-
* {@link ApprovedReportTooLargeError} when those bytes exceed
|
|
344
|
-
* `MAX_HASHED_REPORT_BYTES`: a report accepted just under the cap can cross
|
|
345
|
-
* it through the pretty-print and the added fields.
|
|
346
|
-
*/
|
|
347
|
-
export function renderApprovedReport(raw, approvedAt, approvedBy, sessionId) {
|
|
348
|
-
const parsed = JSON.parse(raw);
|
|
349
|
-
const previousStatus = typeof parsed["approvalStatus"] === "string" ? parsed["approvalStatus"] : null;
|
|
350
|
-
parsed["approvalStatus"] = "approved";
|
|
351
|
-
parsed["approvedAt"] = approvedAt;
|
|
352
|
-
parsed["approvedBy"] = approvedBy;
|
|
353
|
-
// `expiredAt` and `expiredBy` are `expirePersistedReport`'s stamp for how a
|
|
354
|
-
// report reached "expired" (understanding-before-execution-runtime.ts); they
|
|
355
|
-
// describe that specific past state, not the report's current one.
|
|
356
|
-
// Approving a report that was previously expired must not let that stale
|
|
357
|
-
// timestamp survive into the new snapshot, or the persisted record becomes
|
|
358
|
-
// self-contradictory: {approvalStatus: "approved", expiredAt: ...}. Mirrors
|
|
359
|
-
// the same idiom in the standalone package's `withApprovalStatus`
|
|
360
|
-
// (agent-grounding PR #173, from the 5120938c review). `delete` (not
|
|
361
|
-
// `= undefined`) so `JSON.stringify` below drops the key entirely instead
|
|
362
|
-
// of serializing `"expiredAt": null`-adjacent noise.
|
|
363
|
-
delete parsed["expiredAt"];
|
|
364
|
-
delete parsed["expiredBy"];
|
|
365
|
-
// Stamp the session id when the report lacks one (older Stop-hook package
|
|
366
|
-
// versions write reports without a `sessionId` field). This binds the report
|
|
367
|
-
// to the session that approved it, so every later lookup strict-matches it and
|
|
368
|
-
// the sessionId-null fallback can never re-adopt it for a different session
|
|
369
|
-
// (harness/0dce3880 friction #1). An existing sessionId is left untouched.
|
|
370
|
-
let sessionIdStamped = false;
|
|
371
|
-
const existing = parsed["sessionId"];
|
|
372
|
-
if (typeof existing !== "string" || existing.length === 0) {
|
|
373
|
-
parsed["sessionId"] = sessionId;
|
|
374
|
-
sessionIdStamped = true;
|
|
375
|
-
}
|
|
376
|
-
const text = `${JSON.stringify(parsed, null, 2)}\n`;
|
|
377
|
-
const bytes = Buffer.byteLength(text);
|
|
378
|
-
if (bytes > MAX_HASHED_REPORT_BYTES)
|
|
379
|
-
throw new ApprovedReportTooLargeError(bytes);
|
|
380
|
-
return { text, previousStatus, sessionIdStamped };
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Normalise a list of task ids supplied via `opts.tasks` (or the CLI's
|
|
384
|
-
* variadic `--task`). Each entry is comma-split (so `--task a,b` and
|
|
385
|
-
* `--task a b` are equivalent), trimmed, blank entries dropped, and the
|
|
386
|
-
* result de-duplicated while preserving first-seen order.
|
|
387
|
-
*/
|
|
388
|
-
export function dedupeTaskIds(raw) {
|
|
389
|
-
const seen = new Set();
|
|
390
|
-
const out = [];
|
|
391
|
-
for (const entry of raw) {
|
|
392
|
-
for (const part of entry.split(",")) {
|
|
393
|
-
const id = part.trim();
|
|
394
|
-
if (id.length === 0 || seen.has(id))
|
|
395
|
-
continue;
|
|
396
|
-
seen.add(id);
|
|
397
|
-
out.push(id);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
return out;
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* A reason `hashReportFile` gives for a report whose CONTENT cannot be hashed
|
|
404
|
-
* (`--force` may sign a marker without a content binding over it), as opposed
|
|
405
|
-
* to one that could not be read within the bound (oversized, not a regular
|
|
406
|
-
* file, a link, unreadable, growing), which `--force` never overrides.
|
|
407
|
-
*/
|
|
408
|
-
function isContentReason(reason) {
|
|
409
|
-
return reason === "too-deep" || reason === "not-json-object";
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* The reports directory is writable by the gated agent, so `harness approve
|
|
413
|
-
* understanding` lists it through the bounded reader (one `O_NOFOLLOW` open
|
|
414
|
-
* and `fstat` per entry, never a read past the size cap) and refuses while any `*.json`
|
|
415
|
-
* entry cannot be read as a report: oversized, a FIFO or other non-regular
|
|
416
|
-
* file, a symbolic link, unreadable, or growing. Refusing, rather than
|
|
417
|
-
* leaving the entry out, keeps the approval from binding an older report
|
|
418
|
-
* while a newer one is hidden from it. Thrown before any side effect, so
|
|
419
|
-
* no marker, ledger tag or report flip is written; `--force` does not
|
|
420
|
-
* override it (it overrides report content validation only).
|
|
421
|
-
*/
|
|
422
|
-
function refuseSkippedReports(skipped, reportsDir) {
|
|
423
|
-
if (skipped.length === 0)
|
|
424
|
-
return;
|
|
425
|
-
const lines = skipped.map((s) => ` ${escapeForDisplay(s.filePath)}: ${s.detail}`);
|
|
426
|
-
throw new HarnessExitError([
|
|
427
|
-
`refusing to approve: ${skipped.length === 1 ? "an entry" : `${skipped.length} entries`} in the reports directory ` +
|
|
428
|
-
`cannot be read as a report, so approval cannot tell which report it would bind:`,
|
|
429
|
-
...lines,
|
|
430
|
-
"",
|
|
431
|
-
`Remove or move ${skipped.length === 1 ? "that file" : "those files"} out of ${reportsDir}, then re-run ` +
|
|
432
|
-
"`harness approve understanding`. No approval marker, ledger tag or report change was written.",
|
|
433
|
-
"(`harness gc` lists such entries too and leaves them in place.)",
|
|
434
|
-
].join("\n"), EX_FAIL);
|
|
435
|
-
}
|
|
436
|
-
export async function approveUnderstanding(opts = {}) {
|
|
437
|
-
// harness.generated/ holds the `.pending-approval` staging file (the
|
|
438
|
-
// third resolution tier below) and is the directory this command cleans
|
|
439
|
-
// up after consuming a staged id. `resolvePaths` only computes paths —
|
|
440
|
-
// it does not read or parse the manifest — so the lookup still works
|
|
441
|
-
// even when the manifest itself is unparseable.
|
|
442
|
-
const generatedDir = opts.generatedDir ??
|
|
443
|
-
resolveGeneratedDir({
|
|
444
|
-
...(opts.homeDir !== undefined ? { homeDir: opts.homeDir } : {}),
|
|
445
|
-
manifestPath: resolvePaths(opts).base,
|
|
446
|
-
});
|
|
447
|
-
// Session id resolution, in precedence order:
|
|
448
|
-
// 1. explicit --session flag
|
|
449
|
-
// 2. $CLAUDE_CODE_SESSION_ID — the canonical Claude Code env, the
|
|
450
|
-
// variable Claude Code itself exports into the agent shell. Read
|
|
451
|
-
// first so the runtime's id wins over a manually exported legacy
|
|
452
|
-
// $CLAUDE_SESSION_ID that may not match.
|
|
453
|
-
// 3. $CLAUDE_SESSION_ID — legacy / docs-name peer, kept for the
|
|
454
|
-
// Codex pre-tool-use hook's own fallback chain and for operators
|
|
455
|
-
// who set it by hand in older `!`-shell recipes.
|
|
456
|
-
// 4. $CODEX_SESSION_ID (set inside a live Codex session — symmetric
|
|
457
|
-
// with the Codex pre-tool-use hook's own env fallback).
|
|
458
|
-
// 5. the `.pending-approval` file the gate hook staged on its last
|
|
459
|
-
// block — this is what makes an arg-less `harness approve` work
|
|
460
|
-
// from the operator's `!`-shell, where neither of the above is set.
|
|
461
|
-
// 6. the freshest persisted Understanding Report under <reportsDir>
|
|
462
|
-
// whose JSON `sessionId` field is non-null. Runtime-neutral
|
|
463
|
-
// fallback when the gate has not blocked yet (e.g. arg-less
|
|
464
|
-
// approval right after the agent produced an Understanding Report,
|
|
465
|
-
// before any tool call hit the gate to stage `.pending-approval`).
|
|
466
|
-
//
|
|
467
|
-
// Reports-dir resolution mirrors the downstream persisted-report write
|
|
468
|
-
// path: explicit opts.reportsDir wins (test injection), then
|
|
469
|
-
// UNDERSTANDING_GATE_REPORT_DIR env (honoured by defaultReportsDir),
|
|
470
|
-
// then manifest-anchored fallback via resolvePaths. resolvePaths is
|
|
471
|
-
// evaluated lazily so a test that injects opts.reportsDir does not
|
|
472
|
-
// also need to inject homeDir/configPath to satisfy the
|
|
473
|
-
// HARNESS_ALLOW_REAL_GENERATED_DIR loader guard.
|
|
474
|
-
const reportsDir = opts.reportsDir ??
|
|
475
|
-
defaultReportsDir(path.dirname(resolvePaths(opts).base));
|
|
476
|
-
// Refuse a planted entry before anything else: the session-id fallback
|
|
477
|
-
// below and the report lookup further down both list this directory.
|
|
478
|
-
refuseSkippedReports(listPersistedReportsBoundedWithSkips(reportsDir, { refuseSymlinks: true }).skipped, reportsDir);
|
|
479
|
-
const resolved = resolveApprovalSessionId({
|
|
480
|
-
session: opts.session,
|
|
481
|
-
generatedDir,
|
|
482
|
-
// Tier 6: guess the session from the freshest persisted report.
|
|
483
|
-
// Restricted to `pending` reports. An `approved` / `expired`
|
|
484
|
-
// report belongs to a finished gate cycle (often a different
|
|
485
|
-
// session days ago); adopting its sessionId silently approves an
|
|
486
|
-
// unrelated session while the live one stays gated
|
|
487
|
-
// (harness/56f51f2b). A `pending` report is one the Stop hook
|
|
488
|
-
// just produced that no approval has consumed yet, so it is far
|
|
489
|
-
// more likely to be the current session's. This mirrors the
|
|
490
|
-
// `tolerantFallback: "uncompleted"` restriction PR #218 applied
|
|
491
|
-
// to the report-flip path below. The residual case — a stale
|
|
492
|
-
// session left a never-approved `pending` report — is caught by
|
|
493
|
-
// the loud newest-report warning the CLI prints, which names the report
|
|
494
|
-
// file so the operator can verify before trusting the marker.
|
|
495
|
-
newestReportFallback: () => {
|
|
496
|
-
const newest = listPersistedReportsBoundedWithSkips(reportsDir, {
|
|
497
|
-
refuseSymlinks: true,
|
|
498
|
-
}).reports.find((r) => r.sessionId !== null && r.approvalStatus === "pending");
|
|
499
|
-
if (newest && newest.sessionId !== null) {
|
|
500
|
-
return { sessionId: newest.sessionId, filePath: newest.filePath };
|
|
501
|
-
}
|
|
502
|
-
return null;
|
|
503
|
-
},
|
|
504
|
-
});
|
|
505
|
-
const sessionId = resolved.sessionId;
|
|
506
|
-
const sessionSource = resolved.sessionSource;
|
|
507
|
-
const newestReportPath = resolved.newestReportPath;
|
|
508
|
-
if (sessionId === "") {
|
|
509
|
-
// Reaching here means: no --session flag, no $CLAUDE_CODE_SESSION_ID
|
|
510
|
-
// / $CLAUDE_SESSION_ID / $CODEX_SESSION_ID env, no staged
|
|
511
|
-
// `.pending-approval`, AND no `pending` persisted Understanding
|
|
512
|
-
// Report under <reportsDir> carries a sessionId field. Either the
|
|
513
|
-
// gate has never blocked this session and the agent never produced a
|
|
514
|
-
// report, or every report is already approved/expired or
|
|
515
|
-
// sessionId-null (tier 6 only adopts a fresh `pending` report).
|
|
516
|
-
// Spell out the retrieval paths so the operator does not have to
|
|
517
|
-
// dig through docs.
|
|
518
|
-
throw new HarnessExitError([
|
|
519
|
-
"no session id available. Pass --session <id>, or set one of",
|
|
520
|
-
"$CLAUDE_CODE_SESSION_ID (Claude Code) / $CLAUDE_SESSION_ID (legacy) /",
|
|
521
|
-
"$CODEX_SESSION_ID (Codex).",
|
|
522
|
-
"",
|
|
523
|
-
`Both the understanding-gate PreToolUse hook AND \`harness session-start preflight\``,
|
|
524
|
-
"stage the session id in",
|
|
525
|
-
` ${generatedDir}/.pending-approval`,
|
|
526
|
-
"(the hook on block, the preflight on every run with a resolved id) so an",
|
|
527
|
-
"arg-less `harness approve` works after either event. An empty result here means",
|
|
528
|
-
"neither has fired for the current session yet, or the staging file was already consumed.",
|
|
529
|
-
"",
|
|
530
|
-
"Fastest fix: run `harness preflight` once, then re-run `harness approve",
|
|
531
|
-
"understanding`. The preflight stages the staging file as a side effect.",
|
|
532
|
-
"",
|
|
533
|
-
"Other runtime-neutral recovery paths:",
|
|
534
|
-
` • Read the JSON \`sessionId\` field from the newest report under`,
|
|
535
|
-
` ${reportsDir}`,
|
|
536
|
-
" The agent writes one report per Understanding Report it produces,",
|
|
537
|
-
" so this is the canonical session-id source for both Claude Code",
|
|
538
|
-
" and Codex runtimes regardless of cwd.",
|
|
539
|
-
" • From inside the running agent: ask it to print its session id",
|
|
540
|
-
" (Claude Code exposes $CLAUDE_CODE_SESSION_ID; Codex exposes",
|
|
541
|
-
" $CODEX_SESSION_ID and also prints it in `codex doctor --json`).",
|
|
542
|
-
"",
|
|
543
|
-
"If approve writes the tag but the gate still blocks, the running",
|
|
544
|
-
"session is using a different session id than the report you picked.",
|
|
545
|
-
"In that case ask the agent to read its own session id and pass",
|
|
546
|
-
"that exact value to --session.",
|
|
547
|
-
].join("\n"), EX_FAIL);
|
|
548
|
-
}
|
|
549
|
-
// Manifest is required for the ledger write path; if it can't load,
|
|
550
|
-
// we still try to flip the persisted report so a solo user benefits.
|
|
551
|
-
// Not blocking: a solo user still benefits from the report flip below
|
|
552
|
-
// even with no manifest. `manifestLoadError` captures the cause so the
|
|
553
|
-
// degraded-ok ledger result names WHY the write was skipped instead of
|
|
554
|
-
// leaving the operator with a bare `ledger: ok false` (no-silent-errors,
|
|
555
|
-
// M8).
|
|
556
|
-
// Gap-fill mode for a stdin report that declares none of its own
|
|
557
|
-
// (harness task 5d73d78d): resolve via `resolveMode` (Env >
|
|
558
|
-
// config.mode > DEFAULT_MODE — this IS a live runtime consumer, unlike
|
|
559
|
-
// `resolve()`/`buildHooks`'s generation path; see `MODE_ENV`'s doc
|
|
560
|
-
// comment in understanding-before-execution.ts for why the two differ),
|
|
561
|
-
// from whatever manifest is available (`loadDeclaredUnderstandingPack`
|
|
562
|
-
// above), so a report captured via this path is validated against the
|
|
563
|
-
// operator's actually-configured mode instead of a hardcoded
|
|
564
|
-
// "fast_confirm" regardless of harness.yaml. A missing manifest, or a
|
|
565
|
-
// manifest without the pack declared, resolves the same as an empty
|
|
566
|
-
// `config:` block, resolveMode still applies Env > DEFAULT_MODE in
|
|
567
|
-
// that case.
|
|
568
|
-
//
|
|
569
|
-
// A pack entry that IS declared but `enabled: false` is treated the
|
|
570
|
-
// SAME as undeclared (task 5d73d78d review LOW-10): a disabled pack's
|
|
571
|
-
// hooks never ran, so its `config.mode` is not "the operator's live
|
|
572
|
-
// mode" in any sense this gap-fill should honor. The synthesized
|
|
573
|
-
// fallback only needs `name` + `config` (resolveMode's minimal
|
|
574
|
-
// `ModeConfigSource` shape) — `source`/`enabled` would otherwise have
|
|
575
|
-
// to be invented for a pack entry that, by construction, was never
|
|
576
|
-
// matched. `loadDeclaredUnderstandingPack` applies that same rule.
|
|
577
|
-
const { manifest, manifestLoadError, declaredPack } = loadDeclaredUnderstandingPack(opts);
|
|
578
|
-
// Task 5d73d78d review LOW (fix-round-3): `resolveMode`'s own warning
|
|
579
|
-
// (an invalid `config.mode` or `UNDERSTANDING_GATE_MODE` value) used to
|
|
580
|
-
// be dropped on the floor here — only `.mode` was read. The Codex
|
|
581
|
-
// `UserPromptSubmit` injector (hook-codex-user-prompt-submit.ts)
|
|
582
|
-
// already surfaces the identical warning shape to stderr on this same
|
|
583
|
-
// `resolveMode` call; this path silently swallowed it, so an operator
|
|
584
|
-
// with a typo'd `config.mode` got no signal at all from `harness
|
|
585
|
-
// approve understanding`. Surfaced via `modeWarning` on the result so
|
|
586
|
-
// the CLI can print it the same way the marker/ledger/report lines are
|
|
587
|
-
// printed.
|
|
588
|
-
const modeResolution = resolveMode(declaredPack);
|
|
589
|
-
const resolvedMode = toPackageMode(modeResolution.mode);
|
|
590
|
-
// Report capture from stdin (task 61fd36db): persist BEFORE the
|
|
591
|
-
// lookup below so this very approve run selects, validates, and flips
|
|
592
|
-
// the report the agent just attached. Session-bound via the resolved
|
|
593
|
-
// sessionId, so the strict match wins over any stale leftovers.
|
|
594
|
-
let stdinReport;
|
|
595
|
-
if (typeof opts.reportMarkdown === "string" && opts.reportMarkdown.trim().length > 0) {
|
|
596
|
-
stdinReport = persistStdinReport({
|
|
597
|
-
markdown: opts.reportMarkdown,
|
|
598
|
-
reportsDir,
|
|
599
|
-
sessionId,
|
|
600
|
-
now: opts.now ?? new Date(),
|
|
601
|
-
mode: resolvedMode,
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
// Approve-time validation. Resolve the report we would flip BEFORE
|
|
605
|
-
// writing the marker, so a validation failure can short-circuit every
|
|
606
|
-
// downstream write. The `latest` + `reports` values are reused later;
|
|
607
|
-
// we do not list / find twice. The fallback age limit is what stops a
|
|
608
|
-
// weeks-old sessionId-less leftover from being adopted, validated,
|
|
609
|
-
// and stamped as if it were the live session's report when the
|
|
610
|
-
// producer Stop hook silently failed (harness-discovery C1).
|
|
611
|
-
// Listed again here (the stdin capture above may have written a report);
|
|
612
|
-
// a planted entry that appeared since the first listing is refused too.
|
|
613
|
-
const listing = listPersistedReportsBoundedWithSkips(reportsDir, { refuseSymlinks: true });
|
|
614
|
-
refuseSkippedReports(listing.skipped, reportsDir);
|
|
615
|
-
const reports = listing.reports;
|
|
616
|
-
const selection = selectReportForSession(reports, sessionId, {
|
|
617
|
-
tolerantFallback: "uncompleted",
|
|
618
|
-
maxFallbackAgeMs: TOLERANT_FALLBACK_MAX_AGE_MS,
|
|
619
|
-
now: opts.now,
|
|
620
|
-
});
|
|
621
|
-
const latest = selection.report;
|
|
622
|
-
let validation;
|
|
623
|
-
if (!latest) {
|
|
624
|
-
validation = { skipped: true };
|
|
625
|
-
}
|
|
626
|
-
else {
|
|
627
|
-
let parsed = null;
|
|
628
|
-
// Bounded read, not following a symbolic link: the listing above saw the
|
|
629
|
-
// file within the cap, but the path can be swapped for an oversized file,
|
|
630
|
-
// a FIFO or a link since.
|
|
631
|
-
const reread = readReportFileBounded(latest.filePath, { noFollow: true });
|
|
632
|
-
if (reread.ok) {
|
|
633
|
-
try {
|
|
634
|
-
parsed = JSON.parse(reread.raw);
|
|
635
|
-
}
|
|
636
|
-
catch {
|
|
637
|
-
parsed = null;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (!reread.ok) {
|
|
641
|
-
// Enforced even under --force: a report that cannot be read within the
|
|
642
|
-
// bound has no content to bind, and --force never signs a marker over
|
|
643
|
-
// one (it overrides content validation only).
|
|
644
|
-
validation = {
|
|
645
|
-
ok: false,
|
|
646
|
-
field: "report",
|
|
647
|
-
reason: `${reread.detail}, so the approval could not bind it`,
|
|
648
|
-
enforced: true,
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
else if (!parsed) {
|
|
652
|
-
validation = { skipped: true };
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
const v = validatePersistedReport(parsed);
|
|
656
|
-
validation = v.ok ? v : { ...v, enforced: !opts.force };
|
|
657
|
-
// A report the canonical hash cannot cover (larger than the size cap
|
|
658
|
-
// the gate reads, not a regular file, nested too deeply) would sign a
|
|
659
|
-
// marker whose `reportContentHash` is null, i.e. one the gate-read
|
|
660
|
-
// content check never applies to, so it fails validation instead.
|
|
661
|
-
// `--force` overrides only a content reason (too deep, not a JSON
|
|
662
|
-
// object); a size, type or read failure is enforced whatever the flag
|
|
663
|
-
// says. `hashReportFile` reads the file exactly as the gate-read scan
|
|
664
|
-
// does, except that it does not follow a symbolic link.
|
|
665
|
-
if (validation.ok) {
|
|
666
|
-
const hashed = hashReportFile(latest.filePath, { noFollow: true });
|
|
667
|
-
if (!hashed.ok) {
|
|
668
|
-
validation = {
|
|
669
|
-
ok: false,
|
|
670
|
-
field: "report",
|
|
671
|
-
reason: `${hashed.detail}, so the approval could not bind it`,
|
|
672
|
-
enforced: !isContentReason(hashed.reason) || !opts.force,
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
// Task 5d73d78d review HIGH-2: a stdin report that was REJECTED (failed
|
|
679
|
-
// to parse, e.g. because it does not meet the resolved mode's schema —
|
|
680
|
-
// typically a fast_confirm-shaped report submitted against a
|
|
681
|
-
// grill_me-configured host, see `resolvedMode` above) AND left no
|
|
682
|
-
// persisted report to fall back on must not fall through to `{ skipped:
|
|
683
|
-
// true }` above. "skipped" means "nothing was submitted to check"; here
|
|
684
|
-
// something WAS submitted and it was refused. Before this fix, that
|
|
685
|
-
// case still landed on `{ skipped: true }` (no `latest` report exists
|
|
686
|
-
// yet, since persistStdinReport never wrote one), which is not an `ok:
|
|
687
|
-
// false` validation outcome, so the enforced short-circuit below never
|
|
688
|
-
// triggered and the marker was written anyway — silently approving a
|
|
689
|
-
// session whose only report attempt was rejected.
|
|
690
|
-
//
|
|
691
|
-
// Fix-round-3 HIGH (5d73d78d review, verification round 2): the
|
|
692
|
-
// original guard checked only `!latest`, but `latest` can also be a
|
|
693
|
-
// sessionId-null report ADOPTED via `selectReportForSession`'s
|
|
694
|
-
// `tolerantFallback: "uncompleted"` mode above (any pending, sessionId-
|
|
695
|
-
// less report younger than `TOLERANT_FALLBACK_MAX_AGE_MS`). A rejected
|
|
696
|
-
// stdin submission with such a leftover sitting on disk used to sail
|
|
697
|
-
// through unconstrained: `latest` was truthy so this override never
|
|
698
|
-
// fired, the ADOPTED (unrelated) report's own — already-valid — content
|
|
699
|
-
// passed `validatePersistedReport` instead, and `rewriteReportApproved`
|
|
700
|
-
// below then stamped the live session's id onto that foreign report and
|
|
701
|
-
// wrote the marker: a session whose actual submission was refused got
|
|
702
|
-
// silently approved anyway. The condition now also treats a fallback-
|
|
703
|
-
// adopted `latest` as "nothing THIS session can claim":
|
|
704
|
-
// `selection.fallbackAdopted` is `true` exactly when the match was NOT
|
|
705
|
-
// a strict sessionId match.
|
|
706
|
-
//
|
|
707
|
-
// The deliberate carve-out for a genuine EARLIER same-session
|
|
708
|
-
// submission stays: when `latest` is a STRICT sessionId match
|
|
709
|
-
// (`selection.fallbackAdopted === false`), that report's own content
|
|
710
|
-
// validation above already governs the outcome and this block does not
|
|
711
|
-
// override it.
|
|
712
|
-
if (typeof opts.reportMarkdown === "string" &&
|
|
713
|
-
opts.reportMarkdown.trim().length > 0 &&
|
|
714
|
-
stdinReport !== undefined &&
|
|
715
|
-
stdinReport.ok === false &&
|
|
716
|
-
(!latest || selection.fallbackAdopted)) {
|
|
717
|
-
validation = {
|
|
718
|
-
ok: false,
|
|
719
|
-
field: "stdinReport",
|
|
720
|
-
reason: stdinReport.reason,
|
|
721
|
-
enforced: !opts.force,
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
// Short-circuit before any side effect if validation rejects and the
|
|
725
|
-
// operator did not pass --force. No marker, no ledger tag, no report
|
|
726
|
-
// flip — the gate stays closed and the audit trail records no
|
|
727
|
-
// approval. The CLI surfaces this as a hard failure so the operator
|
|
728
|
-
// does not believe they approved when they didn't.
|
|
729
|
-
if ("ok" in validation && validation.ok === false && validation.enforced) {
|
|
730
|
-
// Any --task / --tasks ids the operator passed are dropped on the
|
|
731
|
-
// floor: pre-approving a batch alongside a session whose own report
|
|
732
|
-
// failed validation would write task markers for a session that the
|
|
733
|
-
// gate is still going to refuse. The CLI surfaces this empty array
|
|
734
|
-
// alongside the rejection so an operator who batched several tasks
|
|
735
|
-
// can see the batch was NOT pre-approved.
|
|
736
|
-
return {
|
|
737
|
-
sessionId,
|
|
738
|
-
sessionSource,
|
|
739
|
-
...(newestReportPath !== undefined ? { newestReportPath } : {}),
|
|
740
|
-
...(modeResolution.warning !== null ? { modeWarning: modeResolution.warning } : {}),
|
|
741
|
-
marker: {
|
|
742
|
-
ok: false,
|
|
743
|
-
reason: `validation failed (${validation.field}): ${validation.reason}`,
|
|
744
|
-
},
|
|
745
|
-
taskMarkers: [],
|
|
746
|
-
...(stdinReport !== undefined ? { stdinReport } : {}),
|
|
747
|
-
ledger: {
|
|
748
|
-
ok: false,
|
|
749
|
-
tag: approvedLedgerTagFor(sessionId),
|
|
750
|
-
reason: `skipped: ${validation.field} validation failed`,
|
|
751
|
-
},
|
|
752
|
-
persistedReport: {
|
|
753
|
-
ok: false,
|
|
754
|
-
reason: `skipped: ${validation.field} validation failed`,
|
|
755
|
-
},
|
|
756
|
-
validation,
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
// Report-content hash the marker's signature binds to (harness/f9485cc7):
|
|
760
|
-
// the canonical hash of the persisted report AT APPROVAL TIME, computed
|
|
761
|
-
// BEFORE `rewriteReportApproved` below flips its status/approvedAt/
|
|
762
|
-
// approvedBy fields, so it covers what the operator actually reviewed.
|
|
763
|
-
// `canonicalReportHash` leaves the lifecycle fields out, which is why the
|
|
764
|
-
// value survives that flip and a later `expirePersistedReport` rewrite;
|
|
765
|
-
// both PreToolUse hooks look for a report file with that hash at gate-read
|
|
766
|
-
// time (`verifyApprovedReportHash`, task fa423e9b) and refuse the marker
|
|
767
|
-
// when none is left. null when no persisted report was resolved or it
|
|
768
|
-
// cannot be read as a JSON object (validation skipped above, nothing to
|
|
769
|
-
// bind), or when its content cannot be hashed: nested too deeply or not a
|
|
770
|
-
// JSON object (reachable only under --force: the validation above refuses
|
|
771
|
-
// the first otherwise). A report larger than the size cap, not a regular
|
|
772
|
-
// file or a link never gets here: the validation above enforces that
|
|
773
|
-
// refusal under --force too, and the check below repeats it for a swap
|
|
774
|
-
// since.
|
|
775
|
-
let reportContentHash = null;
|
|
776
|
-
if (latest) {
|
|
777
|
-
const hashed = hashReportFile(latest.filePath, { noFollow: true });
|
|
778
|
-
if (hashed.ok) {
|
|
779
|
-
reportContentHash = hashed.hash;
|
|
780
|
-
}
|
|
781
|
-
else if (!isContentReason(hashed.reason)) {
|
|
782
|
-
// Swapped for an oversized file, a FIFO or a link after the validation
|
|
783
|
-
// above read it: refuse before any marker, ledger tag or report flip is
|
|
784
|
-
// written, `--force` or not (the validation above enforces the same).
|
|
785
|
-
throw new HarnessExitError(`refusing to approve: report ${escapeForDisplay(latest.filePath)} ${hashed.detail} ` +
|
|
786
|
-
"since it was validated, so the approval cannot bind it. No approval marker, " +
|
|
787
|
-
"ledger tag or report change was written; re-run `harness approve understanding`.", EX_FAIL);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
// Write the canonical approval marker first. The gate consults this
|
|
791
|
-
// file (not the ledger) since agent-tasks/88ca4bb3 closed the self-
|
|
792
|
-
// approval backdoor: the agent has direct MCP access to the ledger,
|
|
793
|
-
// but no path to write a file under harness.generated/.
|
|
794
|
-
const approvedAtMarker = (opts.now ?? new Date()).toISOString();
|
|
795
|
-
const approvedByMarker = opts.approvedBy ?? DEFAULT_APPROVED_BY;
|
|
796
|
-
// The rewrite below pretty-prints the report and adds its approval fields,
|
|
797
|
-
// which can carry a report accepted just under the size cap over it. The
|
|
798
|
-
// gate-read scan reads no file over the cap, so the marker signed here would
|
|
799
|
-
// deny on every call, and neither re-approval nor --force could clear it.
|
|
800
|
-
// Render the rewrite first and refuse on its bytes, before any marker,
|
|
801
|
-
// ledger tag or report change is written, `--force` or not.
|
|
802
|
-
if (latest) {
|
|
803
|
-
const current = readReportFileBounded(latest.filePath, { noFollow: true });
|
|
804
|
-
if (current.ok) {
|
|
805
|
-
try {
|
|
806
|
-
renderApprovedReport(current.raw, approvedAtMarker, approvedByMarker, sessionId);
|
|
807
|
-
}
|
|
808
|
-
catch (err) {
|
|
809
|
-
if (err instanceof ApprovedReportTooLargeError) {
|
|
810
|
-
throw new HarnessExitError(`refusing to approve: report ${escapeForDisplay(latest.filePath)}: ${err.message}. ` +
|
|
811
|
-
"Shorten the report and re-run `harness approve understanding`. No approval marker, " +
|
|
812
|
-
"ledger tag or report change was written.", EX_FAIL);
|
|
813
|
-
}
|
|
814
|
-
// Any other failure (unparseable text) keeps its existing handling below.
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
let markerResult;
|
|
819
|
-
try {
|
|
820
|
-
const filePath = writeApprovalMarker(generatedDir, sessionId, {
|
|
821
|
-
approvedAt: approvedAtMarker,
|
|
822
|
-
approvedBy: approvedByMarker,
|
|
823
|
-
reportContentHash,
|
|
824
|
-
});
|
|
825
|
-
markerResult = { ok: true, filePath, approvedAt: approvedAtMarker };
|
|
826
|
-
}
|
|
827
|
-
catch (err) {
|
|
828
|
-
markerResult = {
|
|
829
|
-
ok: false,
|
|
830
|
-
reason: `failed to write approval marker: ${err.message}`,
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
// Task-scoped markers (harness/1ee26e77 + v2 auto-resolve in
|
|
834
|
-
// harness/494fd1e5 + multi-task batch in harness/0dce3880). Written
|
|
835
|
-
// alongside the session marker. Resolution precedence:
|
|
836
|
-
// - opts.tasks (explicit, multi) — pre-approve a whole batch, OR
|
|
837
|
-
// - opts.task (explicit, single, back-compat), OR
|
|
838
|
-
// - the active-claim file (source: "active-claim").
|
|
839
|
-
// A failure on any one id is surfaced loudly but does not abort the
|
|
840
|
-
// approve flow; the session marker still satisfies the gate as a
|
|
841
|
-
// fallback, and the other ids still get their markers.
|
|
842
|
-
let resolvedTaskIds = [];
|
|
843
|
-
let taskSource = "flag";
|
|
844
|
-
if (opts.tasks && opts.tasks.length > 0) {
|
|
845
|
-
resolvedTaskIds = dedupeTaskIds(opts.tasks);
|
|
846
|
-
taskSource = "flag";
|
|
847
|
-
}
|
|
848
|
-
else if (typeof opts.task === "string" && opts.task.length > 0) {
|
|
849
|
-
resolvedTaskIds = [opts.task];
|
|
850
|
-
taskSource = "flag";
|
|
851
|
-
}
|
|
852
|
-
else {
|
|
853
|
-
const fromFile = readActiveClaim(generatedDir);
|
|
854
|
-
if (fromFile !== null) {
|
|
855
|
-
resolvedTaskIds = [fromFile];
|
|
856
|
-
taskSource = "active-claim";
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
const taskMarkers = [];
|
|
860
|
-
for (const taskId of resolvedTaskIds) {
|
|
861
|
-
try {
|
|
862
|
-
const filePath = writeTaskApprovalMarker(generatedDir, taskId, {
|
|
863
|
-
approvedAt: approvedAtMarker,
|
|
864
|
-
approvedBy: approvedByMarker,
|
|
865
|
-
reportContentHash,
|
|
866
|
-
});
|
|
867
|
-
taskMarkers.push({
|
|
868
|
-
ok: true,
|
|
869
|
-
taskId,
|
|
870
|
-
filePath,
|
|
871
|
-
approvedAt: approvedAtMarker,
|
|
872
|
-
source: taskSource,
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
catch (err) {
|
|
876
|
-
taskMarkers.push({
|
|
877
|
-
ok: false,
|
|
878
|
-
taskId,
|
|
879
|
-
reason: `failed to write task marker: ${err.message}`,
|
|
880
|
-
source: taskSource,
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
// Ledger tag content: a `--force`-bypass of a validation failure
|
|
885
|
-
// stamps the bypass into the tag so audit can distinguish a clean
|
|
886
|
-
// approval from one that overrode a structural rule. The base tag
|
|
887
|
-
// shape (`understanding-approved:<session>`) stays intact so the gate
|
|
888
|
-
// matcher still recognises it; the `:forced:<field>` suffix is
|
|
889
|
-
// additive metadata.
|
|
890
|
-
const tag = "ok" in validation && validation.ok === false
|
|
891
|
-
? `${approvedLedgerTagFor(sessionId)}:forced:${validation.field}`
|
|
892
|
-
: approvedLedgerTagFor(sessionId);
|
|
893
|
-
const ledgerResult = manifest
|
|
894
|
-
? await writeLedgerTag(manifest, sessionId, tag, opts)
|
|
895
|
-
: {
|
|
896
|
-
ok: false,
|
|
897
|
-
reason: manifestLoadError
|
|
898
|
-
? `manifest unreadable (${manifestLoadError}); skipped ledger write`
|
|
899
|
-
: "manifest unreadable; skipped ledger write",
|
|
900
|
-
};
|
|
901
|
-
// Persisted report: flip the latest matching one. `reports` + `latest`
|
|
902
|
-
// were resolved up front (alongside validation) so both paths agree
|
|
903
|
-
// on the same artefact.
|
|
904
|
-
let persistedReport;
|
|
905
|
-
if (!latest) {
|
|
906
|
-
// When no matching report exists, look at the sibling parse-errors
|
|
907
|
-
// directory (`<dir-of-reports>/../parse-errors/`, the path the
|
|
908
|
-
// standalone @lannguyensi/understanding-gate Stop hook writes to
|
|
909
|
-
// when its parser rejects the agent's last message). Surface the
|
|
910
|
-
// newest entry's reason so "no reports found" becomes "the hook
|
|
911
|
-
// fired but the parser rejected the report — here is why", rather
|
|
912
|
-
// than a silent dead end.
|
|
913
|
-
const parseErrorsDir = path.join(path.dirname(reportsDir), "parse-errors");
|
|
914
|
-
const latestParseError = findLatestParseError(parseErrorsDir, sessionId);
|
|
915
|
-
let reason;
|
|
916
|
-
if (reports.length === 0) {
|
|
917
|
-
reason = `no reports found at ${reportsDir}`;
|
|
918
|
-
if (latestParseError) {
|
|
919
|
-
reason += `; latest parse-error at ${escapeForDisplay(latestParseError.filePath)}: ${escapeForDisplay(latestParseError.summary)}`;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
else if (selection.staleRejected.length > 0) {
|
|
923
|
-
// A sessionId-less pending report existed but exceeded the
|
|
924
|
-
// fallback age limit. This is its own failure mode, distinct from
|
|
925
|
-
// "no report": it usually means the live session's report was
|
|
926
|
-
// never persisted and only an unrelated leftover was on disk —
|
|
927
|
-
// exactly the artefact the limit refuses to bind (C1).
|
|
928
|
-
const newest = selection.staleRejected[0];
|
|
929
|
-
const age = formatAge((opts.now ?? new Date()).getTime() - newest.createdAtMs);
|
|
930
|
-
const maxMin = Math.round(TOLERANT_FALLBACK_MAX_AGE_MS / 60_000);
|
|
931
|
-
reason =
|
|
932
|
-
`no report matched session_id=${escapeForDisplay(sessionId)}; rejected ${selection.staleRejected.length} ` +
|
|
933
|
-
`stale sessionId-less candidate(s) for age (newest: ${escapeForDisplay(newest.filePath)}, ` +
|
|
934
|
-
`created ${escapeForDisplay(newest.createdAt ?? "<unknown>")}, age ${age} > max ${maxMin}m). ` +
|
|
935
|
-
`If the agent just wrote an Understanding Report, the Stop hook likely failed to ` +
|
|
936
|
-
`persist it; check ${parseErrorsDir}`;
|
|
937
|
-
if (latestParseError) {
|
|
938
|
-
reason += `; latest parse-error at ${escapeForDisplay(latestParseError.filePath)}: ${escapeForDisplay(latestParseError.summary)}`;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
reason = `no report matched session_id=${escapeForDisplay(sessionId)} (${reports.length} report(s) for other sessions)`;
|
|
943
|
-
}
|
|
944
|
-
persistedReport = { ok: false, reason };
|
|
945
|
-
}
|
|
946
|
-
else {
|
|
947
|
-
const approvedAt = (opts.now ?? new Date()).toISOString();
|
|
948
|
-
const approvedBy = opts.approvedBy ?? DEFAULT_APPROVED_BY;
|
|
949
|
-
try {
|
|
950
|
-
const { previousStatus, sessionIdStamped } = rewriteReportApproved(latest.filePath, approvedAt, approvedBy, sessionId);
|
|
951
|
-
persistedReport = {
|
|
952
|
-
ok: true,
|
|
953
|
-
filePath: latest.filePath,
|
|
954
|
-
previousStatus,
|
|
955
|
-
approvedAt,
|
|
956
|
-
sessionIdStamped,
|
|
957
|
-
...(selection.fallbackAdopted
|
|
958
|
-
? {
|
|
959
|
-
fallbackAdopted: {
|
|
960
|
-
createdAt: latest.createdAt,
|
|
961
|
-
ageMinutes: Math.round(((opts.now ?? new Date()).getTime() - latest.createdAtMs) / 60_000),
|
|
962
|
-
},
|
|
963
|
-
}
|
|
964
|
-
: {}),
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
catch (err) {
|
|
968
|
-
persistedReport = {
|
|
969
|
-
ok: false,
|
|
970
|
-
reason: `failed to rewrite ${escapeForDisplay(latest.filePath)}: ${escapeForDisplay(err.message)}`,
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
// Drop the staging file once we have consumed it AND the canonical
|
|
975
|
-
// (marker) write landed, so a later arg-less `harness approve` cannot
|
|
976
|
-
// revive this id. A failed marker write keeps the file so the operator
|
|
977
|
-
// can retry; an id supplied by flag/env was never "ours" to clean up.
|
|
978
|
-
if (sessionSource === "pending-approval" && markerResult.ok) {
|
|
979
|
-
clearPendingApproval(generatedDir);
|
|
980
|
-
}
|
|
981
|
-
return {
|
|
982
|
-
sessionId,
|
|
983
|
-
sessionSource,
|
|
984
|
-
...(newestReportPath !== undefined ? { newestReportPath } : {}),
|
|
985
|
-
...(modeResolution.warning !== null ? { modeWarning: modeResolution.warning } : {}),
|
|
986
|
-
marker: markerResult,
|
|
987
|
-
taskMarkers,
|
|
988
|
-
...(stdinReport !== undefined ? { stdinReport } : {}),
|
|
989
|
-
ledger: ledgerResult.ok
|
|
990
|
-
? { ok: true, tag }
|
|
991
|
-
: { ok: false, tag, reason: ledgerResult.reason },
|
|
992
|
-
persistedReport,
|
|
993
|
-
validation,
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
//# sourceMappingURL=understanding.js.map
|