oh-my-codex 0.18.0 → 0.18.2
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/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +45 -19
- package/crates/omx-api/src/lib.rs +66 -9
- package/crates/omx-sparkshell/src/exec.rs +125 -3
- package/crates/omx-sparkshell/src/main.rs +126 -36
- package/crates/omx-sparkshell/tests/execution.rs +225 -1
- package/dist/agents/__tests__/definitions.test.js +14 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +19 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +30 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +1 -0
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +4 -0
- package/dist/agents/native-config.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +4 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +15 -7
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +137 -8
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +203 -15
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
- package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
- package/dist/cli/__tests__/launch-fallback.test.js +163 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +29 -43
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +94 -35
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +20 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +227 -4
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +72 -1
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +5 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +13 -7
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +297 -17
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +9 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +465 -110
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +2 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +15 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +71 -11
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts +7 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +13 -3
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +184 -10
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +14 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +3 -0
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
- package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +22 -11
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
- package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
- package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
- package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
- package/dist/config/codex-feature-flags.d.ts +4 -0
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js +4 -0
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +1 -0
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +8 -10
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.d.ts +1 -0
- package/dist/config/commit-lore-guard.d.ts.map +1 -1
- package/dist/config/commit-lore-guard.js +29 -3
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +17 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +124 -11
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +4 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +50 -3
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -6
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +173 -17
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +33 -0
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +29 -14
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +36 -9
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +14 -2
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +36 -8
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +122 -11
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +84 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/hud/__tests__/state.test.js +51 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +69 -23
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/index.d.ts +2 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +17 -6
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +6 -3
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +26 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts +2 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +62 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +10 -3
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +60 -11
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +22 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
- package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
- package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
- package/dist/notifications/__tests__/verbosity.test.js +20 -0
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +6 -3
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/http-client.d.ts.map +1 -1
- package/dist/notifications/http-client.js +78 -27
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/types.d.ts +2 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts +7 -4
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +32 -69
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +128 -4
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +460 -9
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/index.d.ts +8 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +5 -2
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +5 -4
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +85 -17
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/code-review.d.ts +2 -2
- package/dist/pipeline/stages/code-review.d.ts.map +1 -1
- package/dist/pipeline/stages/code-review.js +5 -3
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/deep-interview.d.ts +15 -0
- package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
- package/dist/pipeline/stages/deep-interview.js +32 -0
- package/dist/pipeline/stages/deep-interview.js.map +1 -0
- package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
- package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js +2 -2
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +41 -6
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +19 -0
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
- package/dist/pipeline/stages/ultragoal.js +38 -0
- package/dist/pipeline/stages/ultragoal.js.map +1 -0
- package/dist/pipeline/stages/ultraqa.d.ts +30 -0
- package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
- package/dist/pipeline/stages/ultraqa.js +46 -0
- package/dist/pipeline/stages/ultraqa.js.map +1 -0
- package/dist/pipeline/types.d.ts +8 -6
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/pipeline/types.js +2 -2
- package/dist/question/__tests__/ui.test.js +43 -10
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/ui.d.ts +12 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +83 -46
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +200 -10
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +23 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -0
- package/dist/ralplan/consensus-gate.js +212 -0
- package/dist/ralplan/consensus-gate.js.map +1 -0
- package/dist/ralplan/runtime.d.ts +25 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +144 -8
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1358 -79
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +57 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +23 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +18 -3
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/cleanup-explore-harness.js +1 -0
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +372 -44
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +9 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +188 -4
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
- package/dist/scripts/notify-hook/process-runner.js +39 -17
- package/dist/scripts/notify-hook/process-runner.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +9 -5
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +7 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/run-test-files.js +13 -0
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +3 -0
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +99 -1
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +2 -2
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.js +2 -2
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/sidecar/index.d.ts +1 -1
- package/dist/sidecar/index.d.ts.map +1 -1
- package/dist/sidecar/index.js +29 -12
- package/dist/sidecar/index.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +6 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +11 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +7 -0
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +4 -3
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +36 -44
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +163 -15
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +10 -20
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +51 -21
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +764 -10
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +97 -2
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +837 -256
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
- package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
- package/dist/utils/__tests__/sleep-resource.test.js +39 -0
- package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js +17 -6
- package/dist/utils/sleep.js.map +1 -1
- package/package.json +2 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
- package/plugins/oh-my-codex/hooks/hooks.json +77 -0
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +92 -50
- package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +23 -12
- package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +22 -7
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
- package/prompts/planner.md +1 -1
- package/prompts/prometheus-strict-metis.md +274 -0
- package/prompts/prometheus-strict-momus.md +82 -0
- package/prompts/prometheus-strict-oracle.md +107 -0
- package/prompts/researcher.md +22 -3
- package/skills/autopilot/SKILL.md +92 -50
- package/skills/autoresearch/SKILL.md +4 -0
- package/skills/autoresearch-goal/SKILL.md +1 -1
- package/skills/best-practice-research/SKILL.md +1 -1
- package/skills/cancel/SKILL.md +2 -2
- package/skills/deep-interview/SKILL.md +8 -8
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/pipeline/SKILL.md +23 -12
- package/skills/plan/SKILL.md +8 -8
- package/skills/prometheus-strict/README.md +35 -0
- package/skills/prometheus-strict/SKILL.md +219 -0
- package/skills/ralph/SKILL.md +7 -0
- package/skills/ralplan/SKILL.md +22 -7
- package/skills/team/SKILL.md +1 -1
- package/skills/ultragoal/SKILL.md +38 -4
- package/skills/ultrawork/SKILL.md +1 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +1757 -210
- package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
- package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
- package/src/scripts/__tests__/run-test-files.test.ts +67 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +31 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +23 -3
- package/src/scripts/cleanup-explore-harness.ts +1 -0
- package/src/scripts/codex-native-hook.ts +393 -40
- package/src/scripts/codex-native-pre-post.ts +16 -1
- package/src/scripts/notify-dispatcher.ts +202 -4
- package/src/scripts/notify-hook/process-runner.ts +40 -16
- package/src/scripts/notify-hook/team-dispatch.ts +9 -5
- package/src/scripts/notify-hook/team-tmux-guard.ts +7 -0
- package/src/scripts/run-test-files.ts +13 -0
- package/src/scripts/smoke-packed-install.ts +105 -0
- package/src/scripts/sync-plugin-mirror.ts +3 -3
- package/src/scripts/verify-native-agents.ts +2 -2
- package/templates/catalog-manifest.json +22 -0
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { appendFile, mkdir, open, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
4
|
import { formatCodexGoalReconciliation, parseCodexGoalSnapshot, reconcileCodexGoalSnapshot, } from '../goal-workflows/codex-goal-snapshot.js';
|
|
5
5
|
export const ULTRAGOAL_DIR = '.omx/ultragoal';
|
|
6
6
|
export const ULTRAGOAL_BRIEF = 'brief.md';
|
|
7
7
|
export const ULTRAGOAL_GOALS = 'goals.json';
|
|
8
8
|
export const ULTRAGOAL_LEDGER = 'ledger.jsonl';
|
|
9
|
+
const ULTRAGOAL_MUTATION_LOCK = '.mutation.lock';
|
|
10
|
+
export const ULTRAGOAL_STEERING_MUTATION_KINDS = [
|
|
11
|
+
'add_subgoal',
|
|
12
|
+
'split_subgoal',
|
|
13
|
+
'reorder_pending',
|
|
14
|
+
'revise_pending_wording',
|
|
15
|
+
'annotate_ledger',
|
|
16
|
+
'mark_blocked_superseded',
|
|
17
|
+
];
|
|
18
|
+
export const ULTRAGOAL_STEERING_SOURCES = [
|
|
19
|
+
'user_prompt_submit',
|
|
20
|
+
'finding',
|
|
21
|
+
'cli',
|
|
22
|
+
];
|
|
9
23
|
export class UltragoalError extends Error {
|
|
10
24
|
}
|
|
11
25
|
function iso(now = new Date()) {
|
|
@@ -32,6 +46,51 @@ function cleanLine(line) {
|
|
|
32
46
|
function normalizeObjective(value) {
|
|
33
47
|
return value.replace(/\s+/g, ' ').trim();
|
|
34
48
|
}
|
|
49
|
+
function normalizeBlockerEvidence(value) {
|
|
50
|
+
return (value ?? '')
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/https?:\/\/\S+/g, ' ')
|
|
53
|
+
.replace(/[`"'()[\]{}:,;]/g, ' ')
|
|
54
|
+
.replace(/\s+/g, ' ')
|
|
55
|
+
.trim();
|
|
56
|
+
}
|
|
57
|
+
function classifyExternalAuthorizationBlocker(evidence) {
|
|
58
|
+
const normalized = normalizeBlockerEvidence(evidence);
|
|
59
|
+
if (!normalized)
|
|
60
|
+
return null;
|
|
61
|
+
const mentionsAuthorization = /\b(auth|authorization|credential|credentials|token|permission|permissions|scope|scopes|access|unauthorized|forbidden|401|403)\b/.test(normalized);
|
|
62
|
+
const mentionsMissingAuthority = /\b(unset|missing|required|requires|without|omit|omits|not set|not available|no read packages|read packages)\b/.test(normalized);
|
|
63
|
+
if (!mentionsAuthorization || !mentionsMissingAuthority)
|
|
64
|
+
return null;
|
|
65
|
+
const mentionsGhcr = /\b(ghcr|github container registry|read packages|imagepullsecret|package api|anonymous image|container image)\b/.test(normalized);
|
|
66
|
+
if (mentionsGhcr) {
|
|
67
|
+
const has401 = /\b(401|unauthorized|anonymous pull|authentication required)\b/.test(normalized);
|
|
68
|
+
const has403 = /\b(403|forbidden|read packages|package api)\b/.test(normalized);
|
|
69
|
+
const status = [has401 ? 'HTTP_401_ANONYMOUS' : null, has403 ? 'HTTP_403_NO_READ_PACKAGES' : null]
|
|
70
|
+
.filter((part) => Boolean(part))
|
|
71
|
+
.join('+') || 'AUTHORIZATION_REQUIRED';
|
|
72
|
+
return {
|
|
73
|
+
signature: `GHCR_PULL_ACCESS:${status}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`,
|
|
74
|
+
requiredDecision: 'make the GHCR package public, or provide/authorize a least-privilege read:packages credential and imagePullSecret/SOPS path',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
signature: 'EXTERNAL_AUTHORIZATION_REQUIRED',
|
|
79
|
+
requiredDecision: 'provide the missing external authorization/credential, or explicitly choose a different unblock path',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function sameBlockerOccurrences(entries, goalId, signature) {
|
|
83
|
+
return entries.filter((entry) => (entry.goalId === goalId
|
|
84
|
+
&& (entry.event === 'goal_failed' || entry.event === 'goal_needs_user_decision')
|
|
85
|
+
&& entry.blockerSignature === signature)).length;
|
|
86
|
+
}
|
|
87
|
+
function clearGoalBlockerFields(goal) {
|
|
88
|
+
goal.blockedReason = undefined;
|
|
89
|
+
goal.blockerSignature = undefined;
|
|
90
|
+
goal.blockerOccurrenceCount = undefined;
|
|
91
|
+
goal.requiredExternalDecision = undefined;
|
|
92
|
+
goal.nonRetriable = undefined;
|
|
93
|
+
}
|
|
35
94
|
function textMentionsUltragoalPlanArtifact(value) {
|
|
36
95
|
const normalized = (value ?? '').toLowerCase();
|
|
37
96
|
return normalized.includes(ULTRAGOAL_DIR.toLowerCase())
|
|
@@ -82,7 +141,14 @@ function buildCompletedLegacyGoalRemediation(goal) {
|
|
|
82
141
|
return [
|
|
83
142
|
'If get_goal returns a different completed legacy/thread objective, do not repeat --status complete in this thread.',
|
|
84
143
|
`Record a non-terminal blocker with: omx ultragoal checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<different completed get_goal JSON or path>".`,
|
|
85
|
-
'Then continue
|
|
144
|
+
'Then continue only from a Codex goal context with no active/completed conflicting goal, in the same repo/worktree, and create the intended goal there.',
|
|
145
|
+
].join(' ');
|
|
146
|
+
}
|
|
147
|
+
function buildUnavailableCodexGoalRemediation(goal) {
|
|
148
|
+
return [
|
|
149
|
+
'If get_goal itself is unavailable due to a Codex DB/schema/context error, such as "no such table: thread_goals", do not repeat --status complete or mark the Codex goal complete from shell state.',
|
|
150
|
+
`Record an auditable non-terminal blocker with: omx ultragoal checkpoint --goal-id ${goal.id} --status blocked --evidence "<get_goal unavailable due to Codex DB/schema/context error; safe recovery requires a working Codex goal context>" --codex-goal-json "<unavailable get_goal error JSON or path>".`,
|
|
151
|
+
'Then continue from a Codex goal context where get_goal works and strict completion reconciliation can be proven.',
|
|
86
152
|
].join(' ');
|
|
87
153
|
}
|
|
88
154
|
function codexGoalMode(plan) {
|
|
@@ -91,35 +157,78 @@ function codexGoalMode(plan) {
|
|
|
91
157
|
function isResolvedStatus(status) {
|
|
92
158
|
return status === 'complete' || status === 'review_blocked';
|
|
93
159
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (fallback.length <= 4000)
|
|
102
|
-
return fallback;
|
|
160
|
+
function isScheduleEligibleGoal(goal) {
|
|
161
|
+
return goal.steeringStatus !== 'superseded' && goal.steeringStatus !== 'blocked';
|
|
162
|
+
}
|
|
163
|
+
export const ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE = `Complete the durable ultragoal plan in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}, including later accepted/appended stories, under the original brief constraints; use ${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER} as the audit trail.`;
|
|
164
|
+
function aggregateCodexObjective(_goals) {
|
|
165
|
+
if (ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE.length <= 4000)
|
|
166
|
+
return ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE;
|
|
103
167
|
throw new UltragoalError('Generated aggregate Codex objective exceeds the 4,000 character goal limit.');
|
|
104
168
|
}
|
|
169
|
+
function isLegacyEnumeratedAggregateObjective(objective) {
|
|
170
|
+
if (!objective)
|
|
171
|
+
return false;
|
|
172
|
+
return (objective.startsWith(`Complete all ultragoal stories in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}: `)
|
|
173
|
+
|| objective === `Complete all ultragoal stories listed in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}. Use ${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER} as the durable audit trail.`);
|
|
174
|
+
}
|
|
175
|
+
function compatibleCodexObjectives(plan) {
|
|
176
|
+
return (plan.codexObjectiveAliases ?? [])
|
|
177
|
+
.filter((objective) => isLegacyEnumeratedAggregateObjective(objective));
|
|
178
|
+
}
|
|
105
179
|
function expectedCodexObjective(plan, goal) {
|
|
106
180
|
return codexGoalMode(plan) === 'aggregate'
|
|
107
181
|
? (plan.codexObjective ?? aggregateCodexObjective(plan.goals))
|
|
108
182
|
: goal.objective;
|
|
109
183
|
}
|
|
184
|
+
function isSupersededResolved(goal, plan) {
|
|
185
|
+
if (goal.steeringStatus !== 'superseded')
|
|
186
|
+
return false;
|
|
187
|
+
const replacements = goal.supersededBy ?? [];
|
|
188
|
+
if (replacements.length === 0)
|
|
189
|
+
return false;
|
|
190
|
+
return replacements.every((id) => {
|
|
191
|
+
const replacement = plan.goals.find((candidate) => candidate.id === id);
|
|
192
|
+
return replacement !== undefined && isResolvedStatus(replacement.status);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function isCompletionBlocking(goal, plan) {
|
|
196
|
+
if (goal.steeringStatus === 'superseded')
|
|
197
|
+
return !isSupersededResolved(goal, plan);
|
|
198
|
+
if (goal.steeringStatus === 'blocked')
|
|
199
|
+
return true;
|
|
200
|
+
return !isResolvedStatus(goal.status);
|
|
201
|
+
}
|
|
202
|
+
function isCompletionBlockingForFinalCandidate(candidate, finalCandidate, plan) {
|
|
203
|
+
if (candidate.id === finalCandidate.id)
|
|
204
|
+
return false;
|
|
205
|
+
if (candidate.steeringStatus === 'superseded') {
|
|
206
|
+
const replacements = candidate.supersededBy ?? [];
|
|
207
|
+
if (replacements.length === 0)
|
|
208
|
+
return true;
|
|
209
|
+
return !replacements.every((id) => {
|
|
210
|
+
if (id === finalCandidate.id)
|
|
211
|
+
return true;
|
|
212
|
+
const replacement = plan.goals.find((goal) => goal.id === id);
|
|
213
|
+
return replacement !== undefined && isResolvedStatus(replacement.status);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return isCompletionBlocking(candidate, plan);
|
|
217
|
+
}
|
|
218
|
+
function isScheduleEligible(goal) {
|
|
219
|
+
return goal.steeringStatus !== 'superseded' && goal.steeringStatus !== 'blocked';
|
|
220
|
+
}
|
|
110
221
|
export function isFinalRunCompletionCandidate(plan, goal) {
|
|
111
|
-
return plan.goals.every((candidate) => candidate
|
|
222
|
+
return plan.goals.every((candidate) => !isCompletionBlockingForFinalCandidate(candidate, goal, plan));
|
|
112
223
|
}
|
|
113
224
|
export function isUltragoalDone(plan) {
|
|
114
225
|
if (plan.aggregateCompletion?.status === 'complete')
|
|
115
226
|
return true;
|
|
116
227
|
if (plan.goals.length === 0)
|
|
117
228
|
return true;
|
|
118
|
-
if (plan.goals.some((goal) => goal
|
|
229
|
+
if (plan.goals.some((goal) => isCompletionBlocking(goal, plan)))
|
|
119
230
|
return false;
|
|
120
|
-
|
|
121
|
-
return false;
|
|
122
|
-
const latestNonReviewBlocked = [...plan.goals].reverse().find((goal) => goal.status !== 'review_blocked');
|
|
231
|
+
const latestNonReviewBlocked = [...plan.goals].reverse().find((goal) => goal.status !== 'review_blocked' && goal.steeringStatus !== 'superseded');
|
|
123
232
|
return latestNonReviewBlocked?.status === 'complete';
|
|
124
233
|
}
|
|
125
234
|
function titleFromObjective(objective, fallback) {
|
|
@@ -155,6 +264,37 @@ function normalizeGoalId(title, index) {
|
|
|
155
264
|
.replace(/-+$/g, '');
|
|
156
265
|
return `G${String(index + 1).padStart(3, '0')}${slug ? `-${slug}` : ''}`;
|
|
157
266
|
}
|
|
267
|
+
function sleep(ms) {
|
|
268
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
269
|
+
}
|
|
270
|
+
async function withUltragoalMutationLock(cwd, operation) {
|
|
271
|
+
await mkdir(ultragoalDir(cwd), { recursive: true });
|
|
272
|
+
const lockPath = join(ultragoalDir(cwd), ULTRAGOAL_MUTATION_LOCK);
|
|
273
|
+
let handle;
|
|
274
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
275
|
+
try {
|
|
276
|
+
handle = await open(lockPath, 'wx');
|
|
277
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: iso() }));
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const code = error.code;
|
|
282
|
+
if (code !== 'EEXIST')
|
|
283
|
+
throw error;
|
|
284
|
+
await sleep(Math.min(25 + attempt * 5, 250));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (!handle) {
|
|
288
|
+
throw new UltragoalError(`Timed out waiting for ultragoal mutation lock at ${repoRelative(cwd, lockPath)}.`);
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
return await operation();
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await handle.close().catch(() => undefined);
|
|
295
|
+
await rm(lockPath, { force: true }).catch(() => undefined);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
158
298
|
async function appendLedger(cwd, entry) {
|
|
159
299
|
await mkdir(ultragoalDir(cwd), { recursive: true });
|
|
160
300
|
const path = ultragoalLedgerPath(cwd);
|
|
@@ -173,49 +313,69 @@ export async function readUltragoalPlan(cwd) {
|
|
|
173
313
|
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
|
174
314
|
throw new UltragoalError(`Invalid ultragoal plan at ${repoRelative(cwd, path)}.`);
|
|
175
315
|
}
|
|
316
|
+
if (codexGoalMode(parsed) === 'aggregate' && isLegacyEnumeratedAggregateObjective(parsed.codexObjective)) {
|
|
317
|
+
const previousObjective = parsed.codexObjective;
|
|
318
|
+
const now = iso();
|
|
319
|
+
parsed.codexObjective = aggregateCodexObjective(parsed.goals);
|
|
320
|
+
parsed.codexObjectiveAliases = Array.from(new Set([...(parsed.codexObjectiveAliases ?? []), previousObjective].filter((value) => typeof value === 'string' && value.length > 0)));
|
|
321
|
+
parsed.updatedAt = now;
|
|
322
|
+
await writePlan(cwd, parsed);
|
|
323
|
+
await appendLedger(cwd, {
|
|
324
|
+
ts: now,
|
|
325
|
+
event: 'aggregate_objective_migrated',
|
|
326
|
+
message: 'Migrated legacy enumerated aggregate Codex objective to the stable pointer objective.',
|
|
327
|
+
before: { codexObjective: previousObjective },
|
|
328
|
+
after: { codexObjective: parsed.codexObjective },
|
|
329
|
+
});
|
|
330
|
+
}
|
|
176
331
|
return parsed;
|
|
177
332
|
}
|
|
178
333
|
async function writePlan(cwd, plan) {
|
|
179
334
|
await mkdir(ultragoalDir(cwd), { recursive: true });
|
|
180
|
-
|
|
335
|
+
const path = ultragoalGoalsPath(cwd);
|
|
336
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
337
|
+
await writeFile(tmpPath, `${JSON.stringify(plan, null, 2)}\n`);
|
|
338
|
+
await rename(tmpPath, path);
|
|
181
339
|
}
|
|
182
340
|
export async function createUltragoalPlan(cwd, options) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
plan.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
341
|
+
return withUltragoalMutationLock(cwd, async () => {
|
|
342
|
+
if (!options.force && existsSync(ultragoalGoalsPath(cwd))) {
|
|
343
|
+
throw new UltragoalError(`Refusing to overwrite existing ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}; pass --force to recreate it.`);
|
|
344
|
+
}
|
|
345
|
+
const now = iso(options.now);
|
|
346
|
+
const sourceGoals = options.goals?.length
|
|
347
|
+
? options.goals
|
|
348
|
+
: deriveGoalCandidates(options.brief);
|
|
349
|
+
const candidates = sourceGoals
|
|
350
|
+
.map((goal, index) => ({
|
|
351
|
+
id: normalizeGoalId(goal.title ?? titleFromObjective(goal.objective, `Goal ${index + 1}`), index),
|
|
352
|
+
title: goal.title ?? titleFromObjective(goal.objective, `Goal ${index + 1}`),
|
|
353
|
+
objective: goal.objective.trim(),
|
|
354
|
+
status: 'pending',
|
|
355
|
+
tokenBudget: goal.tokenBudget,
|
|
356
|
+
attempt: 0,
|
|
357
|
+
createdAt: now,
|
|
358
|
+
updatedAt: now,
|
|
359
|
+
}));
|
|
360
|
+
const plan = {
|
|
361
|
+
version: 1,
|
|
362
|
+
createdAt: now,
|
|
363
|
+
updatedAt: now,
|
|
364
|
+
briefPath: `${ULTRAGOAL_DIR}/${ULTRAGOAL_BRIEF}`,
|
|
365
|
+
goalsPath: `${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}`,
|
|
366
|
+
ledgerPath: `${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER}`,
|
|
367
|
+
codexGoalMode: options.codexGoalMode ?? 'aggregate',
|
|
368
|
+
goals: candidates,
|
|
369
|
+
};
|
|
370
|
+
if (plan.codexGoalMode === 'aggregate')
|
|
371
|
+
plan.codexObjective = aggregateCodexObjective(candidates);
|
|
372
|
+
await mkdir(ultragoalDir(cwd), { recursive: true });
|
|
373
|
+
await writeFile(ultragoalBriefPath(cwd), options.brief.endsWith('\n') ? options.brief : `${options.brief}\n`);
|
|
374
|
+
await writePlan(cwd, plan);
|
|
375
|
+
await writeFile(ultragoalLedgerPath(cwd), '');
|
|
376
|
+
await appendLedger(cwd, { ts: now, event: 'plan_created', message: `${candidates.length} goal(s) created` });
|
|
377
|
+
return plan;
|
|
378
|
+
});
|
|
219
379
|
}
|
|
220
380
|
export function summarizeUltragoalPlan(plan) {
|
|
221
381
|
return {
|
|
@@ -225,6 +385,9 @@ export function summarizeUltragoalPlan(plan) {
|
|
|
225
385
|
complete: plan.goals.filter((goal) => goal.status === 'complete').length,
|
|
226
386
|
failed: plan.goals.filter((goal) => goal.status === 'failed').length,
|
|
227
387
|
reviewBlocked: plan.goals.filter((goal) => goal.status === 'review_blocked').length,
|
|
388
|
+
needsUserDecision: plan.goals.filter((goal) => goal.status === 'needs_user_decision').length,
|
|
389
|
+
superseded: plan.goals.filter((goal) => goal.steeringStatus === 'superseded').length,
|
|
390
|
+
steeringBlocked: plan.goals.filter((goal) => goal.steeringStatus === 'blocked').length,
|
|
228
391
|
aggregateComplete: plan.aggregateCompletion?.status === 'complete',
|
|
229
392
|
activeGoalId: plan.activeGoalId,
|
|
230
393
|
};
|
|
@@ -235,7 +398,34 @@ function assertNonEmpty(value, label) {
|
|
|
235
398
|
throw new UltragoalError(`Missing ${label}.`);
|
|
236
399
|
return trimmed;
|
|
237
400
|
}
|
|
238
|
-
function
|
|
401
|
+
export function parseUltragoalSteeringDirective(raw) {
|
|
402
|
+
const trimmed = raw.trim();
|
|
403
|
+
if (!trimmed || trimmed.length < 5)
|
|
404
|
+
return null;
|
|
405
|
+
try {
|
|
406
|
+
const parsed = JSON.parse(trimmed);
|
|
407
|
+
if (!parsed || typeof parsed !== 'object')
|
|
408
|
+
return null;
|
|
409
|
+
if (!parsed.kind || typeof parsed.kind !== 'string')
|
|
410
|
+
return null;
|
|
411
|
+
if (!parsed.source || typeof parsed.source !== 'string')
|
|
412
|
+
return null;
|
|
413
|
+
if (!parsed.evidence || typeof parsed.evidence !== 'string')
|
|
414
|
+
return null;
|
|
415
|
+
if (!parsed.rationale || typeof parsed.rationale !== 'string')
|
|
416
|
+
return null;
|
|
417
|
+
if (!ULTRAGOAL_STEERING_MUTATION_KINDS.includes(parsed.kind))
|
|
418
|
+
return null;
|
|
419
|
+
if (!ULTRAGOAL_STEERING_SOURCES.includes(parsed.source))
|
|
420
|
+
return null;
|
|
421
|
+
return parsed;
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function appendGoalToPlan(plan, options, nowOverride) {
|
|
428
|
+
const now = nowOverride ?? iso(options.now);
|
|
239
429
|
const title = assertNonEmpty(options.title, '--title');
|
|
240
430
|
const objective = assertNonEmpty(options.objective, '--objective');
|
|
241
431
|
const goal = {
|
|
@@ -253,19 +443,360 @@ function appendGoalToPlan(plan, options, now) {
|
|
|
253
443
|
return goal;
|
|
254
444
|
}
|
|
255
445
|
export async function addUltragoalGoal(cwd, options) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
446
|
+
return withUltragoalMutationLock(cwd, async () => {
|
|
447
|
+
const plan = await readUltragoalPlan(cwd);
|
|
448
|
+
const now = iso(options.now);
|
|
449
|
+
const goal = appendGoalToPlan(plan, options);
|
|
450
|
+
await writePlan(cwd, plan);
|
|
451
|
+
await appendLedger(cwd, {
|
|
452
|
+
ts: now,
|
|
453
|
+
event: 'goal_added',
|
|
454
|
+
goalId: goal.id,
|
|
455
|
+
status: goal.status,
|
|
456
|
+
evidence: options.evidence,
|
|
457
|
+
message: goal.title,
|
|
458
|
+
});
|
|
459
|
+
return { plan, goal };
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function proposalTargetIds(proposal) {
|
|
463
|
+
return proposal.targetGoalIds?.length ? proposal.targetGoalIds : (proposal.targetGoalId ? [proposal.targetGoalId] : []);
|
|
464
|
+
}
|
|
465
|
+
function steeringTargets(plan, proposal) {
|
|
466
|
+
return proposalTargetIds(proposal).map((id) => {
|
|
467
|
+
const goal = plan.goals.find((candidate) => candidate.id === id);
|
|
468
|
+
if (!goal)
|
|
469
|
+
throw new UltragoalError(`Unknown ultragoal id: ${id}`);
|
|
470
|
+
return goal;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function mentionsWeakenedCompletion(...values) {
|
|
474
|
+
const normalized = values.filter(Boolean).join(' ').toLowerCase();
|
|
475
|
+
return /\b(skip|bypass|weaken|remove|omit|auto[-\s]?complete|mark complete|complete faster)\b/.test(normalized)
|
|
476
|
+
&& /\b(test|tests|verification|review|quality gate|complete|completion)\b/.test(normalized);
|
|
477
|
+
}
|
|
478
|
+
function hasProtectedSteeringPayload(value) {
|
|
479
|
+
if (!value || typeof value !== 'object')
|
|
480
|
+
return false;
|
|
481
|
+
const protectedKeys = new Set([
|
|
482
|
+
'aggregateCompletion',
|
|
483
|
+
'brief',
|
|
484
|
+
'briefPath',
|
|
485
|
+
'codexObjective',
|
|
486
|
+
'constraints',
|
|
487
|
+
'completedAt',
|
|
488
|
+
'qualityGate',
|
|
489
|
+
'status',
|
|
490
|
+
]);
|
|
491
|
+
const stack = [value];
|
|
492
|
+
while (stack.length > 0) {
|
|
493
|
+
const current = stack.pop();
|
|
494
|
+
if (!current || typeof current !== 'object')
|
|
495
|
+
continue;
|
|
496
|
+
for (const [key, child] of Object.entries(current)) {
|
|
497
|
+
if (protectedKeys.has(key))
|
|
498
|
+
return true;
|
|
499
|
+
if (key.toLowerCase().includes('complete'))
|
|
500
|
+
return true;
|
|
501
|
+
if (child && typeof child === 'object')
|
|
502
|
+
stack.push(child);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
function protectedIntentText(proposal) {
|
|
508
|
+
const after = proposal.after;
|
|
509
|
+
const childTexts = rawChildGoalsFromProposal(proposal).flatMap((child) => {
|
|
510
|
+
if (!child || typeof child !== 'object' || Array.isArray(child))
|
|
511
|
+
return [];
|
|
512
|
+
const candidate = child;
|
|
513
|
+
return [candidate.title, candidate.objective];
|
|
514
|
+
});
|
|
515
|
+
return [
|
|
516
|
+
proposal.title,
|
|
517
|
+
proposal.objective,
|
|
518
|
+
proposal.revisedTitle,
|
|
519
|
+
proposal.revisedObjective,
|
|
520
|
+
after?.title,
|
|
521
|
+
after?.objective,
|
|
522
|
+
proposal.rationale,
|
|
523
|
+
proposal.directiveText,
|
|
524
|
+
...childTexts,
|
|
525
|
+
]
|
|
526
|
+
.filter((value) => typeof value === 'string')
|
|
527
|
+
.join('\n')
|
|
528
|
+
.toLowerCase();
|
|
529
|
+
}
|
|
530
|
+
function rawChildGoalsFromProposal(proposal) {
|
|
531
|
+
if (Array.isArray(proposal.childGoals) && proposal.childGoals.length > 0)
|
|
532
|
+
return proposal.childGoals;
|
|
533
|
+
const after = proposal.after;
|
|
534
|
+
return Array.isArray(after?.children) ? after.children : [];
|
|
535
|
+
}
|
|
536
|
+
function isValidSteeringChildGoal(value) {
|
|
537
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
538
|
+
return false;
|
|
539
|
+
const candidate = value;
|
|
540
|
+
return typeof candidate.title === 'string'
|
|
541
|
+
&& candidate.title.trim().length > 0
|
|
542
|
+
&& typeof candidate.objective === 'string'
|
|
543
|
+
&& candidate.objective.trim().length > 0;
|
|
544
|
+
}
|
|
545
|
+
function childGoalsFromProposal(proposal) {
|
|
546
|
+
return rawChildGoalsFromProposal(proposal).filter(isValidSteeringChildGoal);
|
|
547
|
+
}
|
|
548
|
+
function pendingOrderFromProposal(proposal) {
|
|
549
|
+
if (proposal.pendingOrder?.length)
|
|
550
|
+
return proposal.pendingOrder;
|
|
551
|
+
const after = proposal.after;
|
|
552
|
+
return Array.isArray(after?.pendingGoalIds) ? after.pendingGoalIds : [];
|
|
553
|
+
}
|
|
554
|
+
function revisedTitleFromProposal(proposal) {
|
|
555
|
+
if (proposal.revisedTitle?.trim())
|
|
556
|
+
return proposal.revisedTitle;
|
|
557
|
+
const after = proposal.after;
|
|
558
|
+
return after?.title ?? proposal.title;
|
|
559
|
+
}
|
|
560
|
+
function revisedObjectiveFromProposal(proposal) {
|
|
561
|
+
if (proposal.revisedObjective?.trim())
|
|
562
|
+
return proposal.revisedObjective;
|
|
563
|
+
const after = proposal.after;
|
|
564
|
+
return after?.objective ?? proposal.objective;
|
|
565
|
+
}
|
|
566
|
+
export function validateUltragoalSteeringProposal(plan, proposal) {
|
|
567
|
+
const rejectedReasons = [];
|
|
568
|
+
const evidenceBackedNecessity = Boolean(proposal.evidence?.trim()) && Boolean(proposal.rationale?.trim());
|
|
569
|
+
if (!ULTRAGOAL_STEERING_MUTATION_KINDS.includes(proposal.kind))
|
|
570
|
+
rejectedReasons.push(`Invalid steering mutation kind: ${String(proposal.kind)}.`);
|
|
571
|
+
if (!ULTRAGOAL_STEERING_SOURCES.includes(proposal.source))
|
|
572
|
+
rejectedReasons.push(`Invalid steering source: ${String(proposal.source)}.`);
|
|
573
|
+
if (!evidenceBackedNecessity)
|
|
574
|
+
rejectedReasons.push('Steering requires non-empty evidence and rationale.');
|
|
575
|
+
if (hasProtectedSteeringPayload(proposal.after))
|
|
576
|
+
rejectedReasons.push('Steering payload must not edit protected objective, constraint, quality gate, or completion fields.');
|
|
577
|
+
if (/\b(?:skip|bypass|weaken|remove)\b.*\b(?:test|tests|review|verification|quality gate|complete|completion)\b|\bauto[- ]?complete\b/.test(protectedIntentText(proposal))) {
|
|
578
|
+
rejectedReasons.push('Steering must not weaken completion, quality gates, tests, reviews, or auto-complete work.');
|
|
579
|
+
}
|
|
580
|
+
if (plan.aggregateCompletion?.status === 'complete')
|
|
581
|
+
rejectedReasons.push('Cannot steer an already completed aggregate ultragoal plan.');
|
|
582
|
+
let targets = [];
|
|
583
|
+
try {
|
|
584
|
+
targets = steeringTargets(plan, proposal);
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
588
|
+
rejectedReasons.push(message.replace(/^Unknown ultragoal id:/, 'unknown ultragoal id:'));
|
|
589
|
+
}
|
|
590
|
+
const target = targets[0];
|
|
591
|
+
if ((proposal.kind === 'split_subgoal' || proposal.kind === 'revise_pending_wording' || proposal.kind === 'mark_blocked_superseded') && !target) {
|
|
592
|
+
rejectedReasons.push(`${proposal.kind} requires a target goal id.`);
|
|
593
|
+
}
|
|
594
|
+
if ((proposal.kind === 'split_subgoal' || proposal.kind === 'revise_pending_wording') && target?.status !== 'pending') {
|
|
595
|
+
rejectedReasons.push(`${proposal.kind} can only target a pending goal.`);
|
|
596
|
+
}
|
|
597
|
+
if (proposal.kind === 'add_subgoal') {
|
|
598
|
+
if (!proposal.title?.trim() || !proposal.objective?.trim())
|
|
599
|
+
rejectedReasons.push('add_subgoal requires title and objective.');
|
|
600
|
+
}
|
|
601
|
+
if (proposal.kind === 'split_subgoal') {
|
|
602
|
+
const rawChildren = rawChildGoalsFromProposal(proposal);
|
|
603
|
+
if (rawChildren.length === 0)
|
|
604
|
+
rejectedReasons.push('split_subgoal requires replacement child goals.');
|
|
605
|
+
if (rawChildren.some((child) => !isValidSteeringChildGoal(child)))
|
|
606
|
+
rejectedReasons.push('split_subgoal children require title and objective.');
|
|
607
|
+
}
|
|
608
|
+
if (proposal.kind === 'mark_blocked_superseded') {
|
|
609
|
+
const rawChildren = rawChildGoalsFromProposal(proposal);
|
|
610
|
+
if (rawChildren.some((child) => !isValidSteeringChildGoal(child)))
|
|
611
|
+
rejectedReasons.push('mark_blocked_superseded replacement children require title and objective.');
|
|
612
|
+
}
|
|
613
|
+
if (proposal.kind === 'reorder_pending') {
|
|
614
|
+
const requested = pendingOrderFromProposal(proposal);
|
|
615
|
+
const pending = plan.goals.filter((goal) => goal.status === 'pending' && isScheduleEligible(goal)).map((goal) => goal.id);
|
|
616
|
+
if (requested.length === 0)
|
|
617
|
+
rejectedReasons.push('reorder_pending requires at least one pending goal id.');
|
|
618
|
+
if (new Set(requested).size !== requested.length)
|
|
619
|
+
rejectedReasons.push('duplicate goal id in pendingOrder.');
|
|
620
|
+
if (requested.some((id) => !pending.includes(id)))
|
|
621
|
+
rejectedReasons.push('pendingOrder contains non-pending or unknown goal.');
|
|
622
|
+
}
|
|
623
|
+
if (proposal.kind === 'revise_pending_wording') {
|
|
624
|
+
if (!revisedTitleFromProposal(proposal)?.trim() && !revisedObjectiveFromProposal(proposal)?.trim())
|
|
625
|
+
rejectedReasons.push('revise_pending_wording requires title or objective.');
|
|
626
|
+
}
|
|
627
|
+
if (proposal.kind === 'annotate_ledger' && !proposal.evidence?.trim())
|
|
628
|
+
rejectedReasons.push('annotate_ledger requires evidence.');
|
|
629
|
+
const accepted = rejectedReasons.length === 0;
|
|
630
|
+
const noEasierCompletion = !mentionsWeakenedCompletion(protectedIntentText(proposal));
|
|
631
|
+
return {
|
|
632
|
+
structuralInvariantAccepted: accepted,
|
|
633
|
+
evidenceBackedNecessity,
|
|
634
|
+
noEasierCompletion,
|
|
635
|
+
accepted,
|
|
636
|
+
rejectedReasons,
|
|
637
|
+
reasons: rejectedReasons,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
export const validateSteeringProposal = validateUltragoalSteeringProposal;
|
|
641
|
+
async function readSteeringLedgerEntries(cwd) {
|
|
642
|
+
try {
|
|
643
|
+
const raw = await readFile(ultragoalLedgerPath(cwd), 'utf-8');
|
|
644
|
+
return raw.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
return [];
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function cloneForAudit(value) {
|
|
651
|
+
return JSON.parse(JSON.stringify(value));
|
|
652
|
+
}
|
|
653
|
+
function moveGoalsAfterTarget(plan, targetId, movedIds) {
|
|
654
|
+
const moved = movedIds.map((id) => plan.goals.find((goal) => goal.id === id)).filter((goal) => Boolean(goal));
|
|
655
|
+
if (moved.length === 0)
|
|
656
|
+
return;
|
|
657
|
+
plan.goals = plan.goals.filter((goal) => !movedIds.includes(goal.id));
|
|
658
|
+
const targetIndex = plan.goals.findIndex((goal) => goal.id === targetId);
|
|
659
|
+
plan.goals.splice(targetIndex >= 0 ? targetIndex + 1 : plan.goals.length, 0, ...moved);
|
|
660
|
+
}
|
|
661
|
+
function applySteeringMutation(plan, proposal, now) {
|
|
662
|
+
const targets = steeringTargets(plan, proposal);
|
|
663
|
+
const target = targets[0];
|
|
664
|
+
if (proposal.kind === 'add_subgoal') {
|
|
665
|
+
const goal = appendGoalToPlan(plan, { title: proposal.title ?? '', objective: proposal.objective ?? '', evidence: proposal.evidence, now: new Date(now) });
|
|
666
|
+
return { before: undefined, after: cloneForAudit(goal) };
|
|
667
|
+
}
|
|
668
|
+
if (proposal.kind === 'split_subgoal') {
|
|
669
|
+
const before = cloneForAudit(target);
|
|
670
|
+
const children = childGoalsFromProposal(proposal).map((child) => appendGoalToPlan(plan, { ...child, evidence: proposal.evidence, now: new Date(now) }));
|
|
671
|
+
target.steeringStatus = 'superseded';
|
|
672
|
+
target.supersededBy = children.map((child) => child.id);
|
|
673
|
+
moveGoalsAfterTarget(plan, target.id, children.map((child) => child.id));
|
|
674
|
+
target.steeringEvidence = proposal.evidence;
|
|
675
|
+
target.steeringRationale = proposal.rationale;
|
|
676
|
+
target.updatedAt = now;
|
|
677
|
+
for (const child of children)
|
|
678
|
+
child.supersedes = [target.id];
|
|
679
|
+
if (plan.activeGoalId === target.id)
|
|
680
|
+
plan.activeGoalId = undefined;
|
|
681
|
+
plan.updatedAt = now;
|
|
682
|
+
return { before, after: { target: cloneForAudit(target), children: cloneForAudit(children) } };
|
|
683
|
+
}
|
|
684
|
+
if (proposal.kind === 'reorder_pending') {
|
|
685
|
+
const before = plan.goals.map((goal) => goal.id);
|
|
686
|
+
const requested = pendingOrderFromProposal(proposal);
|
|
687
|
+
const requestedSet = new Set(requested);
|
|
688
|
+
const requestedGoals = requested.map((id) => plan.goals.find((goal) => goal.id === id)).filter((goal) => Boolean(goal));
|
|
689
|
+
const remaining = plan.goals.filter((goal) => !requestedSet.has(goal.id));
|
|
690
|
+
plan.goals = [...requestedGoals, ...remaining];
|
|
691
|
+
plan.updatedAt = now;
|
|
692
|
+
return { before, after: plan.goals.map((goal) => goal.id) };
|
|
693
|
+
}
|
|
694
|
+
if (proposal.kind === 'revise_pending_wording') {
|
|
695
|
+
const before = cloneForAudit(target);
|
|
696
|
+
const revisedTitle = revisedTitleFromProposal(proposal);
|
|
697
|
+
const revisedObjective = revisedObjectiveFromProposal(proposal);
|
|
698
|
+
if (revisedTitle?.trim())
|
|
699
|
+
target.title = revisedTitle.trim();
|
|
700
|
+
if (revisedObjective?.trim())
|
|
701
|
+
target.objective = revisedObjective.trim();
|
|
702
|
+
target.steeringEvidence = proposal.evidence;
|
|
703
|
+
target.steeringRationale = proposal.rationale;
|
|
704
|
+
target.updatedAt = now;
|
|
705
|
+
plan.updatedAt = now;
|
|
706
|
+
return { before, after: cloneForAudit(target) };
|
|
707
|
+
}
|
|
708
|
+
if (proposal.kind === 'annotate_ledger') {
|
|
709
|
+
return { before: undefined, after: { evidence: proposal.evidence, rationale: proposal.rationale } };
|
|
710
|
+
}
|
|
711
|
+
if (proposal.kind === 'mark_blocked_superseded') {
|
|
712
|
+
const before = cloneForAudit(target);
|
|
713
|
+
const children = childGoalsFromProposal(proposal);
|
|
714
|
+
if (children.length > 0) {
|
|
715
|
+
const replacements = children.map((child) => appendGoalToPlan(plan, { ...child, evidence: proposal.evidence, now: new Date(now) }));
|
|
716
|
+
target.steeringStatus = 'superseded';
|
|
717
|
+
target.supersededBy = replacements.map((child) => child.id);
|
|
718
|
+
moveGoalsAfterTarget(plan, target.id, replacements.map((child) => child.id));
|
|
719
|
+
target.steeringEvidence = proposal.evidence;
|
|
720
|
+
target.steeringRationale = proposal.rationale;
|
|
721
|
+
target.updatedAt = now;
|
|
722
|
+
for (const replacement of replacements)
|
|
723
|
+
replacement.supersedes = [target.id];
|
|
724
|
+
if (plan.activeGoalId === target.id)
|
|
725
|
+
plan.activeGoalId = undefined;
|
|
726
|
+
plan.updatedAt = now;
|
|
727
|
+
return { before, after: { target: cloneForAudit(target), children: cloneForAudit(replacements) } };
|
|
728
|
+
}
|
|
729
|
+
if (plan.activeGoalId === target.id)
|
|
730
|
+
delete plan.activeGoalId;
|
|
731
|
+
target.steeringStatus = 'blocked';
|
|
732
|
+
target.blockedReason = proposal.blockedReason ?? proposal.rationale;
|
|
733
|
+
target.steeringEvidence = proposal.evidence;
|
|
734
|
+
target.steeringRationale = proposal.rationale;
|
|
735
|
+
target.updatedAt = now;
|
|
736
|
+
if (plan.activeGoalId === target.id)
|
|
737
|
+
plan.activeGoalId = undefined;
|
|
738
|
+
plan.updatedAt = now;
|
|
739
|
+
return { before, after: cloneForAudit(target) };
|
|
740
|
+
}
|
|
741
|
+
return {};
|
|
742
|
+
}
|
|
743
|
+
export async function steerUltragoal(cwd, proposal, options = {}) {
|
|
744
|
+
return withUltragoalMutationLock(cwd, async () => {
|
|
745
|
+
const plan = await readUltragoalPlan(cwd);
|
|
746
|
+
const existing = proposal.idempotencyKey
|
|
747
|
+
? (await readSteeringLedgerEntries(cwd)).find((entry) => entry.event === 'steering_accepted' && (entry.idempotencyKey === proposal.idempotencyKey || entry.steering?.idempotencyKey === proposal.idempotencyKey) && entry.steering)
|
|
748
|
+
: undefined;
|
|
749
|
+
if (existing?.steering) {
|
|
750
|
+
return { plan, accepted: true, audit: { ...existing.steering, deduped: true }, rejectedReasons: [], deduped: true };
|
|
751
|
+
}
|
|
752
|
+
let invariant = validateUltragoalSteeringProposal(plan, proposal);
|
|
753
|
+
const now = iso(options.now ?? proposal.now);
|
|
754
|
+
const beforePlan = cloneForAudit(plan);
|
|
755
|
+
let mutation = {};
|
|
756
|
+
if (invariant.accepted) {
|
|
757
|
+
try {
|
|
758
|
+
mutation = applySteeringMutation(plan, proposal, now);
|
|
759
|
+
}
|
|
760
|
+
catch (error) {
|
|
761
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
762
|
+
const rejectedReasons = [...invariant.rejectedReasons, `Steering mutation failed: ${message}`];
|
|
763
|
+
invariant = {
|
|
764
|
+
...invariant,
|
|
765
|
+
accepted: false,
|
|
766
|
+
structuralInvariantAccepted: false,
|
|
767
|
+
rejectedReasons,
|
|
768
|
+
reasons: rejectedReasons,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const audit = {
|
|
773
|
+
kind: proposal.kind,
|
|
774
|
+
source: proposal.source,
|
|
775
|
+
targetGoalIds: proposalTargetIds(proposal),
|
|
776
|
+
before: mutation.before ?? beforePlan,
|
|
777
|
+
after: mutation.after,
|
|
778
|
+
evidence: proposal.evidence,
|
|
779
|
+
rationale: proposal.rationale,
|
|
780
|
+
invariant,
|
|
781
|
+
directiveText: options.directiveText ?? proposal.directiveText,
|
|
782
|
+
promptSignature: proposal.promptSignature,
|
|
783
|
+
idempotencyKey: proposal.idempotencyKey,
|
|
784
|
+
};
|
|
785
|
+
if (invariant.accepted)
|
|
786
|
+
await writePlan(cwd, plan);
|
|
787
|
+
await appendLedger(cwd, {
|
|
788
|
+
ts: now,
|
|
789
|
+
event: invariant.accepted ? 'steering_accepted' : 'steering_rejected',
|
|
790
|
+
goalId: proposalTargetIds(proposal)[0],
|
|
791
|
+
evidence: proposal.evidence,
|
|
792
|
+
message: proposal.rationale,
|
|
793
|
+
steering: audit,
|
|
794
|
+
mutationKind: proposal.kind,
|
|
795
|
+
before: audit.before,
|
|
796
|
+
after: audit.after,
|
|
797
|
+
});
|
|
798
|
+
return { plan, accepted: invariant.accepted, audit, rejectedReasons: invariant.rejectedReasons, deduped: false };
|
|
267
799
|
});
|
|
268
|
-
return { plan, goal };
|
|
269
800
|
}
|
|
270
801
|
function validateQualityGate(value) {
|
|
271
802
|
if (!value || typeof value !== 'object') {
|
|
@@ -301,230 +832,278 @@ function validateQualityGate(value) {
|
|
|
301
832
|
return gate;
|
|
302
833
|
}
|
|
303
834
|
export async function startNextUltragoal(cwd, options = {}) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
next
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
835
|
+
return withUltragoalMutationLock(cwd, async () => {
|
|
836
|
+
const plan = await readUltragoalPlan(cwd);
|
|
837
|
+
const now = iso(options.now);
|
|
838
|
+
if (plan.aggregateCompletion?.status === 'complete')
|
|
839
|
+
return { plan, goal: null, resumed: false, done: true };
|
|
840
|
+
const existing = plan.goals.find((goal) => goal.status === 'in_progress' && isScheduleEligibleGoal(goal));
|
|
841
|
+
if (existing) {
|
|
842
|
+
await appendLedger(cwd, { ts: now, event: 'goal_resumed', goalId: existing.id, status: existing.status, message: 'Resuming active ultragoal' });
|
|
843
|
+
return { plan, goal: existing, resumed: true, done: false };
|
|
844
|
+
}
|
|
845
|
+
let next = plan.goals.find((goal) => goal.status === 'pending' && isScheduleEligible(goal));
|
|
846
|
+
if (!next && options.retryFailed) {
|
|
847
|
+
next = plan.goals.find((goal) => goal.status === 'failed' && !goal.nonRetriable && isScheduleEligible(goal));
|
|
848
|
+
if (next)
|
|
849
|
+
await appendLedger(cwd, { ts: now, event: 'goal_retried', goalId: next.id, status: 'pending', message: next.failureReason });
|
|
850
|
+
}
|
|
851
|
+
if (!next)
|
|
852
|
+
return { plan, goal: null, resumed: false, done: isUltragoalDone(plan) };
|
|
853
|
+
next.status = 'in_progress';
|
|
854
|
+
next.attempt += 1;
|
|
855
|
+
next.startedAt = now;
|
|
856
|
+
next.failedAt = undefined;
|
|
857
|
+
next.failureReason = undefined;
|
|
858
|
+
clearGoalBlockerFields(next);
|
|
859
|
+
next.updatedAt = now;
|
|
860
|
+
plan.activeGoalId = next.id;
|
|
861
|
+
plan.updatedAt = now;
|
|
862
|
+
await writePlan(cwd, plan);
|
|
863
|
+
await appendLedger(cwd, { ts: now, event: 'goal_started', goalId: next.id, status: next.status, message: `Attempt ${next.attempt}` });
|
|
864
|
+
return { plan, goal: next, resumed: false, done: false };
|
|
865
|
+
});
|
|
332
866
|
}
|
|
333
867
|
export async function checkpointUltragoal(cwd, options) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
868
|
+
return withUltragoalMutationLock(cwd, async () => {
|
|
869
|
+
const plan = await readUltragoalPlan(cwd);
|
|
870
|
+
const goal = plan.goals.find((candidate) => candidate.id === options.goalId);
|
|
871
|
+
if (!goal)
|
|
872
|
+
throw new UltragoalError(`Unknown ultragoal id: ${options.goalId}`);
|
|
873
|
+
const now = iso(options.now);
|
|
874
|
+
if (options.status === 'blocked') {
|
|
875
|
+
if (goal.status !== 'in_progress') {
|
|
876
|
+
throw new UltragoalError(`Cannot record a blocked checkpoint for ${goal.id} while it is ${goal.status}; start or resume the ultragoal before recording a non-terminal blocker.`);
|
|
877
|
+
}
|
|
878
|
+
const snapshot = options.codexGoal === undefined ? null : parseCodexGoalSnapshot(options.codexGoal);
|
|
879
|
+
if (snapshot?.unavailableReason === 'db_schema_context_error') {
|
|
880
|
+
goal.updatedAt = now;
|
|
881
|
+
goal.failureReason = assertNonEmpty(options.evidence, '--evidence');
|
|
882
|
+
plan.activeGoalId = goal.id;
|
|
883
|
+
plan.updatedAt = now;
|
|
884
|
+
await writePlan(cwd, plan);
|
|
885
|
+
await appendLedger(cwd, {
|
|
886
|
+
ts: now,
|
|
887
|
+
event: 'goal_blocked',
|
|
888
|
+
goalId: goal.id,
|
|
889
|
+
status: goal.status,
|
|
890
|
+
evidence: options.evidence,
|
|
891
|
+
codexGoal: options.codexGoal,
|
|
892
|
+
message: 'Codex get_goal was unavailable due to a DB/schema/context error; strict completion reconciliation is deferred until get_goal works.',
|
|
893
|
+
});
|
|
894
|
+
return plan;
|
|
895
|
+
}
|
|
896
|
+
if (!snapshot?.available) {
|
|
897
|
+
throw new UltragoalError('Blocked ultragoal checkpoints require either a get_goal snapshot for the completed legacy Codex goal that blocked create_goal, or an unavailable get_goal error JSON for a Codex DB/schema/context failure; pass --codex-goal-json.');
|
|
898
|
+
}
|
|
899
|
+
if (snapshot.status !== 'complete') {
|
|
900
|
+
throw new UltragoalError(`Cannot record a blocked ultragoal checkpoint while the existing Codex goal is ${snapshot.status ?? 'unknown'}; strict objective mismatch protection remains required for active or incomplete goals.`);
|
|
901
|
+
}
|
|
902
|
+
if (!snapshot.objective) {
|
|
903
|
+
throw new UltragoalError('Blocked ultragoal checkpoint Codex snapshot is missing objective text.');
|
|
904
|
+
}
|
|
905
|
+
if (normalizeObjective(snapshot.objective) === normalizeObjective(expectedCodexObjective(plan, goal))) {
|
|
906
|
+
throw new UltragoalError('Blocked ultragoal checkpoint is only for a different completed legacy Codex goal; complete this ultragoal with --status complete after its audit passes.');
|
|
907
|
+
}
|
|
908
|
+
goal.updatedAt = now;
|
|
909
|
+
plan.activeGoalId = goal.id;
|
|
910
|
+
plan.updatedAt = now;
|
|
911
|
+
await writePlan(cwd, plan);
|
|
912
|
+
await appendLedger(cwd, {
|
|
913
|
+
ts: now,
|
|
914
|
+
event: 'goal_blocked',
|
|
915
|
+
goalId: goal.id,
|
|
916
|
+
status: goal.status,
|
|
917
|
+
evidence: options.evidence,
|
|
918
|
+
codexGoal: options.codexGoal,
|
|
919
|
+
});
|
|
920
|
+
return plan;
|
|
342
921
|
}
|
|
343
|
-
|
|
344
|
-
if (
|
|
345
|
-
|
|
922
|
+
let aggregateCompletion;
|
|
923
|
+
if (options.status === 'complete') {
|
|
924
|
+
const expectedObjective = expectedCodexObjective(plan, goal);
|
|
925
|
+
const aggregateMode = codexGoalMode(plan) === 'aggregate';
|
|
926
|
+
const finalRunCheckpoint = isFinalRunCompletionCandidate(plan, goal);
|
|
927
|
+
const snapshot = options.codexGoal === undefined ? null : parseCodexGoalSnapshot(options.codexGoal);
|
|
928
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
929
|
+
expectedObjective,
|
|
930
|
+
acceptedObjectives: aggregateMode ? compatibleCodexObjectives(plan) : undefined,
|
|
931
|
+
allowedStatuses: aggregateMode
|
|
932
|
+
? (finalRunCheckpoint && !options.allowActiveFinalCodexGoal ? ['complete'] : ['active'])
|
|
933
|
+
: ['complete'],
|
|
934
|
+
requireSnapshot: true,
|
|
935
|
+
requireComplete: !aggregateMode || (finalRunCheckpoint && !options.allowActiveFinalCodexGoal),
|
|
936
|
+
});
|
|
937
|
+
if (!reconciliation.ok) {
|
|
938
|
+
const completedTaskScopedAggregateSnapshot = snapshot?.available
|
|
939
|
+
&& snapshot.status === 'complete'
|
|
940
|
+
&& Boolean(snapshot.objective)
|
|
941
|
+
&& normalizeObjective(snapshot.objective ?? '') !== normalizeObjective(expectedObjective)
|
|
942
|
+
&& await canReconcileCompletedTaskScopedAggregateSnapshot(cwd, plan, goal, snapshot.objective ?? '', options.evidence);
|
|
943
|
+
if (completedTaskScopedAggregateSnapshot) {
|
|
944
|
+
aggregateCompletion = {
|
|
945
|
+
status: 'complete',
|
|
946
|
+
completedAt: now,
|
|
947
|
+
evidence: assertNonEmpty(options.evidence, '--evidence'),
|
|
948
|
+
codexGoal: options.codexGoal,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
const taskScopedRequirement = aggregateMode && snapshot?.status === 'complete' && Boolean(snapshot.objective)
|
|
953
|
+
? ' Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMX goal, evidence that names that active OMX goal id, names .omx/ultragoal/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ultragoal brief/artifact.'
|
|
954
|
+
: '';
|
|
955
|
+
const remediation = reconciliation.snapshot.available
|
|
956
|
+
&& reconciliation.snapshot.status === 'complete'
|
|
957
|
+
&& Boolean(reconciliation.snapshot.objective)
|
|
958
|
+
&& normalizeObjective(reconciliation.snapshot.objective ?? '') !== normalizeObjective(expectedObjective)
|
|
959
|
+
? ` ${buildCompletedLegacyGoalRemediation(goal)}`
|
|
960
|
+
: reconciliation.snapshot.unavailableReason === 'db_schema_context_error'
|
|
961
|
+
? ` ${buildUnavailableCodexGoalRemediation(goal)}`
|
|
962
|
+
: '';
|
|
963
|
+
throw new UltragoalError(`${formatCodexGoalReconciliation(reconciliation)}${taskScopedRequirement}${remediation}`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (finalRunCheckpoint && !options.allowActiveFinalCodexGoal)
|
|
967
|
+
goal.evidence = options.evidence;
|
|
346
968
|
}
|
|
347
|
-
|
|
348
|
-
|
|
969
|
+
const qualityGate = options.status === 'complete' && (aggregateCompletion !== undefined || (isFinalRunCompletionCandidate(plan, goal) && !options.allowActiveFinalCodexGoal))
|
|
970
|
+
? validateQualityGate(options.qualityGate)
|
|
971
|
+
: undefined;
|
|
972
|
+
if (aggregateCompletion) {
|
|
973
|
+
plan.aggregateCompletion = aggregateCompletion;
|
|
974
|
+
if (plan.activeGoalId === goal.id)
|
|
975
|
+
delete plan.activeGoalId;
|
|
976
|
+
plan.updatedAt = now;
|
|
977
|
+
await writePlan(cwd, plan);
|
|
978
|
+
await appendLedger(cwd, {
|
|
979
|
+
ts: now,
|
|
980
|
+
event: 'aggregate_completed',
|
|
981
|
+
goalId: goal.id,
|
|
982
|
+
status: goal.status,
|
|
983
|
+
evidence: options.evidence,
|
|
984
|
+
codexGoal: options.codexGoal,
|
|
985
|
+
qualityGate,
|
|
986
|
+
message: 'Aggregate ultragoal plan completed via task-scoped Codex goal snapshot; microgoal ledger progress remains independent.',
|
|
987
|
+
});
|
|
988
|
+
return plan;
|
|
349
989
|
}
|
|
350
|
-
|
|
351
|
-
|
|
990
|
+
goal.status = options.status;
|
|
991
|
+
goal.updatedAt = now;
|
|
992
|
+
if (options.status === 'complete') {
|
|
993
|
+
goal.completedAt = now;
|
|
994
|
+
goal.evidence = options.evidence;
|
|
995
|
+
goal.failureReason = undefined;
|
|
996
|
+
goal.failedAt = undefined;
|
|
997
|
+
clearGoalBlockerFields(goal);
|
|
998
|
+
if (plan.activeGoalId === goal.id)
|
|
999
|
+
delete plan.activeGoalId;
|
|
352
1000
|
}
|
|
353
|
-
|
|
354
|
-
|
|
1001
|
+
else {
|
|
1002
|
+
const blocker = classifyExternalAuthorizationBlocker(options.evidence);
|
|
1003
|
+
const previousEntries = blocker ? await readSteeringLedgerEntries(cwd) : [];
|
|
1004
|
+
const occurrenceCount = blocker ? sameBlockerOccurrences(previousEntries, goal.id, blocker.signature) + 1 : 0;
|
|
1005
|
+
const shouldCircuitBreak = blocker !== null && occurrenceCount >= 3;
|
|
1006
|
+
goal.failedAt = now;
|
|
1007
|
+
goal.failureReason = options.evidence;
|
|
1008
|
+
goal.blockerSignature = blocker?.signature;
|
|
1009
|
+
goal.blockerOccurrenceCount = blocker ? occurrenceCount : undefined;
|
|
1010
|
+
goal.requiredExternalDecision = blocker?.requiredDecision;
|
|
1011
|
+
goal.nonRetriable = shouldCircuitBreak || undefined;
|
|
1012
|
+
if (shouldCircuitBreak) {
|
|
1013
|
+
goal.status = 'needs_user_decision';
|
|
1014
|
+
goal.blockedReason = options.evidence;
|
|
1015
|
+
}
|
|
1016
|
+
if (plan.activeGoalId === goal.id)
|
|
1017
|
+
delete plan.activeGoalId;
|
|
355
1018
|
}
|
|
356
|
-
goal.updatedAt = now;
|
|
357
|
-
plan.activeGoalId = goal.id;
|
|
358
1019
|
plan.updatedAt = now;
|
|
359
1020
|
await writePlan(cwd, plan);
|
|
1021
|
+
const blockerEvent = goal.status === 'needs_user_decision';
|
|
360
1022
|
await appendLedger(cwd, {
|
|
361
1023
|
ts: now,
|
|
362
|
-
event: '
|
|
1024
|
+
event: options.status === 'complete' ? 'goal_completed' : blockerEvent ? 'goal_needs_user_decision' : 'goal_failed',
|
|
363
1025
|
goalId: goal.id,
|
|
364
1026
|
status: goal.status,
|
|
365
1027
|
evidence: options.evidence,
|
|
366
1028
|
codexGoal: options.codexGoal,
|
|
1029
|
+
qualityGate,
|
|
1030
|
+
blockerSignature: goal.blockerSignature,
|
|
1031
|
+
blockerOccurrenceCount: goal.blockerOccurrenceCount,
|
|
1032
|
+
requiredExternalDecision: goal.requiredExternalDecision,
|
|
1033
|
+
message: blockerEvent
|
|
1034
|
+
? `Blocked on repeated external authorization. Required decision: ${goal.requiredExternalDecision}.`
|
|
1035
|
+
: undefined,
|
|
367
1036
|
});
|
|
368
1037
|
return plan;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
export async function recordFinalReviewBlockers(cwd, options) {
|
|
1041
|
+
return withUltragoalMutationLock(cwd, async () => {
|
|
1042
|
+
const plan = await readUltragoalPlan(cwd);
|
|
1043
|
+
const goal = plan.goals.find((candidate) => candidate.id === options.goalId);
|
|
1044
|
+
if (!goal)
|
|
1045
|
+
throw new UltragoalError(`Unknown ultragoal id: ${options.goalId}`);
|
|
1046
|
+
assertNonEmpty(options.evidence, '--evidence');
|
|
1047
|
+
if (goal.status !== 'in_progress') {
|
|
1048
|
+
throw new UltragoalError(`Cannot record final review blockers for ${goal.id} while it is ${goal.status}; start or resume the ultragoal first.`);
|
|
1049
|
+
}
|
|
1050
|
+
if (!isFinalRunCompletionCandidate(plan, goal)) {
|
|
1051
|
+
throw new UltragoalError(`Cannot record final review blockers for ${goal.id}; it is not the only unresolved ultragoal story.`);
|
|
1052
|
+
}
|
|
1053
|
+
const now = iso(options.now);
|
|
372
1054
|
const expectedObjective = expectedCodexObjective(plan, goal);
|
|
373
1055
|
const aggregateMode = codexGoalMode(plan) === 'aggregate';
|
|
374
|
-
const
|
|
375
|
-
const snapshot = options.codexGoal === undefined ? null : parseCodexGoalSnapshot(options.codexGoal);
|
|
376
|
-
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
1056
|
+
const reconciliation = reconcileCodexGoalSnapshot(options.codexGoal === undefined ? null : parseCodexGoalSnapshot(options.codexGoal), {
|
|
377
1057
|
expectedObjective,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
: ['complete'],
|
|
1058
|
+
acceptedObjectives: aggregateMode ? compatibleCodexObjectives(plan) : undefined,
|
|
1059
|
+
allowedStatuses: ['active'],
|
|
381
1060
|
requireSnapshot: true,
|
|
382
|
-
requireComplete:
|
|
1061
|
+
requireComplete: false,
|
|
383
1062
|
});
|
|
384
1063
|
if (!reconciliation.ok) {
|
|
385
|
-
|
|
386
|
-
&& snapshot.status === 'complete'
|
|
387
|
-
&& Boolean(snapshot.objective)
|
|
388
|
-
&& normalizeObjective(snapshot.objective ?? '') !== normalizeObjective(expectedObjective)
|
|
389
|
-
&& await canReconcileCompletedTaskScopedAggregateSnapshot(cwd, plan, goal, snapshot.objective ?? '', options.evidence);
|
|
390
|
-
if (completedTaskScopedAggregateSnapshot) {
|
|
391
|
-
aggregateCompletion = {
|
|
392
|
-
status: 'complete',
|
|
393
|
-
completedAt: now,
|
|
394
|
-
evidence: assertNonEmpty(options.evidence, '--evidence'),
|
|
395
|
-
codexGoal: options.codexGoal,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
const taskScopedRequirement = aggregateMode && snapshot?.status === 'complete' && Boolean(snapshot.objective)
|
|
400
|
-
? ' Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMX goal, evidence that names that active OMX goal id, names .omx/ultragoal/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ultragoal brief/artifact.'
|
|
401
|
-
: '';
|
|
402
|
-
const remediation = reconciliation.snapshot.available
|
|
403
|
-
&& reconciliation.snapshot.status === 'complete'
|
|
404
|
-
&& Boolean(reconciliation.snapshot.objective)
|
|
405
|
-
&& normalizeObjective(reconciliation.snapshot.objective ?? '') !== normalizeObjective(expectedObjective)
|
|
406
|
-
? ` ${buildCompletedLegacyGoalRemediation(goal)}`
|
|
407
|
-
: '';
|
|
408
|
-
throw new UltragoalError(`${formatCodexGoalReconciliation(reconciliation)}${taskScopedRequirement}${remediation}`);
|
|
409
|
-
}
|
|
1064
|
+
throw new UltragoalError(formatCodexGoalReconciliation(reconciliation));
|
|
410
1065
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
1066
|
+
const addedGoal = appendGoalToPlan(plan, { ...options, now: options.now });
|
|
1067
|
+
goal.status = 'review_blocked';
|
|
1068
|
+
goal.reviewBlockedAt = now;
|
|
1069
|
+
goal.updatedAt = now;
|
|
1070
|
+
goal.completedAt = undefined;
|
|
1071
|
+
goal.failedAt = undefined;
|
|
1072
|
+
goal.failureReason = undefined;
|
|
1073
|
+
goal.evidence = options.evidence;
|
|
419
1074
|
if (plan.activeGoalId === goal.id)
|
|
420
1075
|
delete plan.activeGoalId;
|
|
421
1076
|
plan.updatedAt = now;
|
|
422
1077
|
await writePlan(cwd, plan);
|
|
423
1078
|
await appendLedger(cwd, {
|
|
424
1079
|
ts: now,
|
|
425
|
-
event: '
|
|
1080
|
+
event: 'final_review_failed',
|
|
426
1081
|
goalId: goal.id,
|
|
427
1082
|
status: goal.status,
|
|
428
1083
|
evidence: options.evidence,
|
|
429
1084
|
codexGoal: options.codexGoal,
|
|
430
|
-
|
|
431
|
-
|
|
1085
|
+
message: aggregateMode
|
|
1086
|
+
? 'Final aggregate code-review was not clean; blocker story was appended while Codex goal remains active.'
|
|
1087
|
+
: 'Final per-story code-review was not clean; blocker story was appended and may require an available Codex goal context.',
|
|
432
1088
|
});
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
plan.updatedAt = now;
|
|
452
|
-
await writePlan(cwd, plan);
|
|
453
|
-
await appendLedger(cwd, {
|
|
454
|
-
ts: now,
|
|
455
|
-
event: options.status === 'complete' ? 'goal_completed' : 'goal_failed',
|
|
456
|
-
goalId: goal.id,
|
|
457
|
-
status: goal.status,
|
|
458
|
-
evidence: options.evidence,
|
|
459
|
-
codexGoal: options.codexGoal,
|
|
460
|
-
qualityGate,
|
|
461
|
-
});
|
|
462
|
-
return plan;
|
|
463
|
-
}
|
|
464
|
-
export async function recordFinalReviewBlockers(cwd, options) {
|
|
465
|
-
const plan = await readUltragoalPlan(cwd);
|
|
466
|
-
const goal = plan.goals.find((candidate) => candidate.id === options.goalId);
|
|
467
|
-
if (!goal)
|
|
468
|
-
throw new UltragoalError(`Unknown ultragoal id: ${options.goalId}`);
|
|
469
|
-
assertNonEmpty(options.evidence, '--evidence');
|
|
470
|
-
if (goal.status !== 'in_progress') {
|
|
471
|
-
throw new UltragoalError(`Cannot record final review blockers for ${goal.id} while it is ${goal.status}; start or resume the ultragoal first.`);
|
|
472
|
-
}
|
|
473
|
-
if (!isFinalRunCompletionCandidate(plan, goal)) {
|
|
474
|
-
throw new UltragoalError(`Cannot record final review blockers for ${goal.id}; it is not the only unresolved ultragoal story.`);
|
|
475
|
-
}
|
|
476
|
-
const now = iso(options.now);
|
|
477
|
-
const expectedObjective = expectedCodexObjective(plan, goal);
|
|
478
|
-
const aggregateMode = codexGoalMode(plan) === 'aggregate';
|
|
479
|
-
const reconciliation = reconcileCodexGoalSnapshot(options.codexGoal === undefined ? null : parseCodexGoalSnapshot(options.codexGoal), {
|
|
480
|
-
expectedObjective,
|
|
481
|
-
allowedStatuses: ['active'],
|
|
482
|
-
requireSnapshot: true,
|
|
483
|
-
requireComplete: false,
|
|
484
|
-
});
|
|
485
|
-
if (!reconciliation.ok) {
|
|
486
|
-
throw new UltragoalError(formatCodexGoalReconciliation(reconciliation));
|
|
487
|
-
}
|
|
488
|
-
const addedGoal = appendGoalToPlan(plan, options, now);
|
|
489
|
-
goal.status = 'review_blocked';
|
|
490
|
-
goal.reviewBlockedAt = now;
|
|
491
|
-
goal.updatedAt = now;
|
|
492
|
-
goal.completedAt = undefined;
|
|
493
|
-
goal.failedAt = undefined;
|
|
494
|
-
goal.failureReason = undefined;
|
|
495
|
-
goal.evidence = options.evidence;
|
|
496
|
-
if (plan.activeGoalId === goal.id)
|
|
497
|
-
delete plan.activeGoalId;
|
|
498
|
-
plan.updatedAt = now;
|
|
499
|
-
await writePlan(cwd, plan);
|
|
500
|
-
await appendLedger(cwd, {
|
|
501
|
-
ts: now,
|
|
502
|
-
event: 'final_review_failed',
|
|
503
|
-
goalId: goal.id,
|
|
504
|
-
status: goal.status,
|
|
505
|
-
evidence: options.evidence,
|
|
506
|
-
codexGoal: options.codexGoal,
|
|
507
|
-
message: aggregateMode
|
|
508
|
-
? 'Final aggregate code-review was not clean; blocker story was appended while Codex goal remains active.'
|
|
509
|
-
: 'Final per-story code-review was not clean; blocker story was appended and may require a fresh/available Codex goal context.',
|
|
510
|
-
});
|
|
511
|
-
await appendLedger(cwd, {
|
|
512
|
-
ts: now,
|
|
513
|
-
event: 'goal_added',
|
|
514
|
-
goalId: addedGoal.id,
|
|
515
|
-
status: addedGoal.status,
|
|
516
|
-
evidence: options.evidence,
|
|
517
|
-
message: addedGoal.title,
|
|
518
|
-
});
|
|
519
|
-
await appendLedger(cwd, {
|
|
520
|
-
ts: now,
|
|
521
|
-
event: 'goal_review_blocked',
|
|
522
|
-
goalId: goal.id,
|
|
523
|
-
status: goal.status,
|
|
524
|
-
evidence: options.evidence,
|
|
525
|
-
codexGoal: options.codexGoal,
|
|
1089
|
+
await appendLedger(cwd, {
|
|
1090
|
+
ts: now,
|
|
1091
|
+
event: 'goal_added',
|
|
1092
|
+
goalId: addedGoal.id,
|
|
1093
|
+
status: addedGoal.status,
|
|
1094
|
+
evidence: options.evidence,
|
|
1095
|
+
message: addedGoal.title,
|
|
1096
|
+
});
|
|
1097
|
+
await appendLedger(cwd, {
|
|
1098
|
+
ts: now,
|
|
1099
|
+
event: 'goal_review_blocked',
|
|
1100
|
+
goalId: goal.id,
|
|
1101
|
+
status: goal.status,
|
|
1102
|
+
evidence: options.evidence,
|
|
1103
|
+
codexGoal: options.codexGoal,
|
|
1104
|
+
});
|
|
1105
|
+
return { plan, blockedGoal: goal, addedGoal };
|
|
526
1106
|
});
|
|
527
|
-
return { plan, blockedGoal: goal, addedGoal };
|
|
528
1107
|
}
|
|
529
1108
|
export function buildCodexGoalInstruction(goal, plan) {
|
|
530
1109
|
if (codexGoalMode(plan) === 'aggregate')
|
|
@@ -546,7 +1125,8 @@ function buildPerStoryCodexGoalInstruction(goal, plan) {
|
|
|
546
1125
|
'Codex goal integration constraints:',
|
|
547
1126
|
'- First call get_goal. If no active goal exists, call create_goal with the payload below.',
|
|
548
1127
|
'- If a different active Codex goal exists, finish/checkpoint that goal before starting this ultragoal.',
|
|
549
|
-
'-
|
|
1128
|
+
'- Ultragoal cannot call /goal clear from the model/shell tool surface. For another per-story goal in the same session/thread after a completed Codex goal, manually run /goal clear in the Codex UI before creating the next goal.',
|
|
1129
|
+
'- If get_goal returns a different completed legacy/thread goal and create_goal rejects because this thread already has a completed goal, continue only from a Codex goal context with no active/completed conflicting goal in the same repo/worktree and create the payload there.',
|
|
550
1130
|
`- To preserve the durable ledger before switching threads, record the non-terminal blocker without failing this goal: omx ultragoal checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<get_goal JSON or path>"`,
|
|
551
1131
|
'- Work only this goal until its completion audit passes.',
|
|
552
1132
|
finalStory
|
|
@@ -559,7 +1139,7 @@ function buildPerStoryCodexGoalInstruction(goal, plan) {
|
|
|
559
1139
|
? ` omx ultragoal record-review-blockers --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`
|
|
560
1140
|
: ` omx ultragoal checkpoint --goal-id ${goal.id} --status complete --evidence "<tests/files/PR evidence>" --codex-goal-json "<fresh get_goal JSON or path>"`,
|
|
561
1141
|
finalStory
|
|
562
|
-
? '- In legacy per-story mode, the blocker story may require
|
|
1142
|
+
? '- In legacy per-story mode, the blocker story may require an available Codex goal context because this story remains an active incomplete Codex goal; do not claim it is complete.'
|
|
563
1143
|
: null,
|
|
564
1144
|
finalStory
|
|
565
1145
|
? '- If final $code-review is clean (APPROVE + CLEAR), call update_goal({status: "complete"}), call get_goal again, then checkpoint with --quality-gate-json:'
|
|
@@ -592,6 +1172,7 @@ function buildAggregateCodexGoalInstruction(goal, plan) {
|
|
|
592
1172
|
'- First call get_goal. If no active goal exists, call create_goal with the aggregate payload below.',
|
|
593
1173
|
'- If get_goal reports the same aggregate objective as active, continue this OMX story without creating a new Codex goal.',
|
|
594
1174
|
'- If a different active or incomplete Codex goal exists, finish/checkpoint that goal before starting this ultragoal; do not replace hidden Codex state from the shell.',
|
|
1175
|
+
'- Ultragoal does not call /goal clear. After a completed aggregate run, manually run /goal clear in the Codex UI before starting another ultragoal run in the same session/thread.',
|
|
595
1176
|
finalStory
|
|
596
1177
|
? '- This is the final pending story: run the mandatory final ai-slop-cleaner pass, rerun verification, and run $code-review before any update_goal call.'
|
|
597
1178
|
: '- This is not the final story: do not call update_goal yet; the aggregate Codex goal must remain active while later OMX stories remain.',
|