oh-my-opencode 4.18.2 → 4.19.1
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/.agents/command/omomomo.md +1 -1
- package/.agents/skills/omomomo/SKILL.md +1 -1
- package/.opencode/command/omomomo.md +1 -1
- package/README.ja.md +6 -6
- package/README.ko.md +6 -6
- package/README.md +8 -8
- package/README.ru.md +6 -6
- package/README.zh-cn.md +6 -6
- package/dist/agents/atlas/agent.d.ts +7 -6
- package/dist/agents/gpt-prompt-identity.d.ts +3 -0
- package/dist/agents/sisyphus/gpt-5-5.d.ts +1 -1
- package/dist/agents/sisyphus/gpt-task-system-guide.d.ts +1 -0
- package/dist/agents/sisyphus/index.d.ts +5 -0
- package/dist/agents/sisyphus/kimi-k3.d.ts +19 -0
- package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
- package/dist/agents/sisyphus-junior/agent.d.ts +8 -4
- package/dist/agents/sisyphus-junior/gpt-5-5.d.ts +2 -2
- package/dist/agents/sisyphus-junior/index.d.ts +2 -0
- package/dist/agents/sisyphus-junior/kimi-k3.d.ts +12 -0
- package/dist/agents/types.d.ts +2 -2
- package/dist/cli/index.js +781 -215
- package/dist/cli-node/index.js +781 -215
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/agent-overrides.d.ts +48 -48
- package/dist/config/schema/browser-automation.d.ts +1 -0
- package/dist/config/schema/categories.d.ts +6 -6
- package/dist/config/schema/codegraph.d.ts +2 -0
- package/dist/config/schema/commands.d.ts +1 -3
- package/dist/config/schema/default-mode.d.ts +1 -1
- package/dist/config/schema/fallback-models.d.ts +5 -5
- package/dist/config/schema/goal.d.ts +7 -0
- package/dist/config/schema/hooks.d.ts +1 -1
- package/dist/config/schema/oh-my-opencode-config.d.ts +56 -58
- package/dist/config/schema/ralph-loop.d.ts +1 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/create-hooks.d.ts +1 -1
- package/dist/features/builtin-commands/templates/goal.d.ts +1 -0
- package/dist/features/builtin-commands/templates/stop-continuation.d.ts +1 -1
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/hooks/auto-slash-command/executor.d.ts +1 -0
- package/dist/hooks/codegraph-bootstrap/hook.d.ts +3 -2
- package/dist/hooks/goal/command-arguments.d.ts +13 -0
- package/dist/hooks/goal/controller.d.ts +26 -0
- package/dist/hooks/goal/index.d.ts +23 -0
- package/dist/hooks/goal/prompt.d.ts +3 -0
- package/dist/hooks/goal/store.d.ts +8 -0
- package/dist/hooks/goal/tools.d.ts +10 -0
- package/dist/hooks/goal/types.d.ts +93 -0
- package/dist/hooks/goal/validation.d.ts +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/keyword-detector/hook.d.ts +1 -2
- package/dist/hooks/ralph-loop/types.d.ts +1 -1
- package/dist/hooks/sisyphus-junior-notepad/constants.d.ts +1 -1
- package/dist/hooks/start-work/notepad-scaffold.d.ts +10 -0
- package/dist/index.js +5398 -5109
- package/dist/oh-my-opencode.schema.json +34 -19
- package/dist/plugin/chat-message/loop-commands.d.ts +1 -1
- package/dist/plugin/chat-message/types.d.ts +23 -10
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +3 -2
- package/dist/plugin/hooks/create-transform-hooks.d.ts +0 -2
- package/dist/plugin/stop-continuation.d.ts +2 -2
- package/dist/shared/omo-process-sweep.d.ts +18 -0
- package/dist/shared/system-directive.d.ts +0 -1
- package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.test.ts +87 -0
- package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.ts +35 -5
- package/dist/skills/ulw-plan/SKILL.md +31 -8
- package/dist/skills/ulw-plan/references/full-workflow.md +117 -6
- package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/dist/skills/ulw-research/SKILL.md +3 -3
- package/dist/testing/create-plugin-module.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +0 -1
- package/dist/tui.js +138 -80
- package/package.json +14 -14
- package/packages/git-bash-mcp/dist/cli.js +54 -8
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +60 -0
- package/packages/lsp-core/src/lsp/client.ts +14 -1
- package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +2 -2
- package/packages/lsp-core/src/mcp.ts +9 -0
- package/packages/lsp-daemon/dist/cli.js +319 -51
- package/packages/lsp-daemon/dist/client.js +136 -48
- package/packages/lsp-daemon/dist/daemon-client.d.ts +0 -10
- package/packages/lsp-daemon/dist/daemon-client.js +4 -34
- package/packages/lsp-daemon/dist/daemon-failure-result.d.ts +3 -0
- package/packages/lsp-daemon/dist/daemon-failure-result.js +38 -0
- package/packages/lsp-daemon/dist/daemon-request-error.d.ts +14 -0
- package/packages/lsp-daemon/dist/daemon-request-error.js +26 -0
- package/packages/lsp-daemon/dist/daemon-server.js +5 -1
- package/packages/lsp-daemon/dist/index.js +137 -48
- package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
- package/packages/lsp-daemon/dist/proxy.js +1 -0
- package/packages/lsp-daemon/dist/version-reap.d.ts +26 -0
- package/packages/lsp-daemon/dist/version-reap.js +182 -0
- package/packages/lsp-tools-mcp/dist/cli.js +61 -10
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +6 -1
- package/packages/lsp-tools-mcp/dist/mcp.js +61 -10
- package/packages/lsp-tools-mcp/dist/tools.js +6 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +416 -25
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +22 -2
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +7 -6
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +638 -267
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +178 -89
- package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +3 -1
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +3 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +98 -32
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +5 -0
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +15 -6
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +6 -2
- package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +1 -0
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +148 -0
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +31 -2
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +65 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -7
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +57 -3
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +41 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +109 -8
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +2 -2
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +43 -0
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/AGENTS.md +1 -0
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +80 -40
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +11 -0
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +11 -0
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +60 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/AGENTS.md +8 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +147 -60
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +3 -71
- package/packages/omo-codex/plugin/components/start-work-continuation/src/plan-checklist.ts +171 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +198 -23
- package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +4 -3
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +6 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/test/fixtures/plan-scaffold.md +15 -0
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -10
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +31 -8
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +117 -6
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/packages/omo-codex/plugin/components/ultrawork/src/codex-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +42 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +6 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.d.ts +21 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +64 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +15 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.js +85 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +0 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +13 -33
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +998 -593
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/goal-status.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/goal-status.js +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +10 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +84 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +81 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +7 -59
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +12 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/validation-batch.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/validation-batch.js +122 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +8 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +84 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +13 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts +49 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +13 -45
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +4 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +20 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +125 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +59 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +6 -58
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +12 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts +122 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +76 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +111 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +67 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +57 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +152 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +29 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/validation-batch-checkpoint.test.ts +147 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/validation-batch.test.ts +73 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +2 -2
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +2 -2
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +2 -2
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +41 -41
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +32 -28
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +17 -45
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +455 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +3 -18
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +43 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +35 -5
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +8 -6
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +31 -8
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +117 -6
- package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +11 -11
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +26 -0
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +41 -0
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +6 -6
- package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +3 -3
- package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +62 -0
- package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +271 -14
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +3 -18
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +0 -5
- package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +293 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +10 -10
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +401 -22
- package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +39 -2
- package/packages/omo-codex/scripts/install-marketplace-cache.test.mjs +4 -1
- package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.test.ts +87 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +35 -5
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +31 -8
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +117 -6
- package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
- package/dist/features/builtin-commands/templates/ralph-loop.d.ts +0 -3
- package/dist/skills/ultraresearch/SKILL.md +0 -10
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +0 -273
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -12
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -107
- package/packages/shared-skills/skills/lcx-doctor/SKILL.md +0 -128
- package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +0 -11
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +0 -268
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +0 -11
- package/packages/shared-skills/skills/ultraresearch/SKILL.md +0 -10
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/SKILL.md +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/SKILL.md +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/agents/openai.yaml +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/SKILL.md +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/agents/openai.yaml +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all format: long agent-facing instruction strings are intentionally stable.
|
|
1
2
|
import { codexGoalMode, expectedCodexObjective, isEssentialCriterion, isFinalRunCompletionCandidate, } from "./goal-status.js";
|
|
2
3
|
export function buildCodexGoalInstruction(args) {
|
|
3
4
|
const mode = codexGoalMode(args.plan);
|
|
@@ -12,5 +12,5 @@ export declare const ULW_LOOP_SUCCESS_CRITERION_USER_MODELS: readonly ["happy",
|
|
|
12
12
|
export type UlwLoopSuccessCriterionUserModel = (typeof ULW_LOOP_SUCCESS_CRITERION_USER_MODELS)[number];
|
|
13
13
|
export declare const ULW_LOOP_CRITERION_STATUSES: readonly ["pending", "pass", "fail", "blocked"];
|
|
14
14
|
export type UlwLoopCriterionStatus = (typeof ULW_LOOP_CRITERION_STATUSES)[number];
|
|
15
|
-
export declare const ULW_LOOP_LEDGER_EVENT_KINDS: readonly ["plan_created", "goal_started", "goal_resumed", "goal_completed", "goal_blocked", "goal_failed", "goal_needs_user_decision", "goal_retried", "aggregate_completed", "aggregate_objective_migrated", "goal_added", "steering_accepted", "steering_rejected", "final_review_failed", "goal_review_blocked", "evidence_captured", "criterion_failed", "criterion_blocked", "criteria_revised"];
|
|
15
|
+
export declare const ULW_LOOP_LEDGER_EVENT_KINDS: readonly ["plan_created", "goal_started", "goal_resumed", "goal_completed", "goal_blocked", "goal_failed", "goal_needs_user_decision", "goal_retried", "aggregate_completed", "aggregate_objective_migrated", "goal_added", "steering_accepted", "steering_rejected", "final_review_failed", "goal_review_blocked", "evidence_captured", "criterion_failed", "criterion_blocked", "criteria_revised", "batch_closed", "batch_updated"];
|
|
16
16
|
export type UlwLoopLedgerEventKind = (typeof ULW_LOOP_LEDGER_EVENT_KINDS)[number];
|
|
@@ -43,6 +43,11 @@ export interface UlwLoopAggregateCompletion {
|
|
|
43
43
|
evidence: string;
|
|
44
44
|
codexGoal?: unknown;
|
|
45
45
|
}
|
|
46
|
+
export interface UlwLoopValidationBatch {
|
|
47
|
+
readonly batchId: string;
|
|
48
|
+
readonly memberIds: readonly string[];
|
|
49
|
+
readonly finalGoalId: string;
|
|
50
|
+
}
|
|
46
51
|
export interface UlwLoopPlan {
|
|
47
52
|
version: 1;
|
|
48
53
|
evidenceLayoutVersion?: 2;
|
|
@@ -56,6 +61,7 @@ export interface UlwLoopPlan {
|
|
|
56
61
|
codexObjectiveAliases?: string[];
|
|
57
62
|
aggregateCompletion?: UlwLoopAggregateCompletion;
|
|
58
63
|
activeGoalId?: string;
|
|
64
|
+
validationBatches?: readonly UlwLoopValidationBatch[];
|
|
59
65
|
goals: UlwLoopItem[];
|
|
60
66
|
}
|
|
61
67
|
export type UlwLoopManualQaSurface = "cli" | "http" | "tmux" | "browser" | "gui" | "data";
|
|
@@ -3,6 +3,7 @@ import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCrit
|
|
|
3
3
|
export declare const ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE: string;
|
|
4
4
|
export declare function aggregateCodexObjectiveForScope(scope?: UlwLoopScope): string;
|
|
5
5
|
export declare function codexGoalMode(plan: UlwLoopPlan): UlwLoopCodexGoalMode;
|
|
6
|
+
export declare function isMemberResolved(goal: UlwLoopItem, plan: UlwLoopPlan): boolean;
|
|
6
7
|
export declare function isUlwLoopDone(plan: UlwLoopPlan): boolean;
|
|
7
8
|
export declare function isFinalRunCompletionCandidate(plan: UlwLoopPlan, goal: UlwLoopItem): boolean;
|
|
8
9
|
export declare function aggregateCodexObjective(plan: UlwLoopPlan): string;
|
|
@@ -9,6 +9,9 @@ export function codexGoalMode(plan) {
|
|
|
9
9
|
function isResolvedStatus(status) {
|
|
10
10
|
return status === "complete";
|
|
11
11
|
}
|
|
12
|
+
export function isMemberResolved(goal, plan) {
|
|
13
|
+
return isResolvedStatus(goal.status) || isSupersededResolved(goal, plan);
|
|
14
|
+
}
|
|
12
15
|
function isSupersededResolved(goal, plan) {
|
|
13
16
|
if (goal.steeringStatus !== "superseded")
|
|
14
17
|
return false;
|
|
@@ -23,6 +23,7 @@ export declare function createUlwLoopPlan(repoRoot: string, args: {
|
|
|
23
23
|
brief: string;
|
|
24
24
|
codexGoalMode?: UlwLoopCodexGoalMode;
|
|
25
25
|
force?: boolean;
|
|
26
|
+
validationBatchesJson?: string;
|
|
26
27
|
}, scope?: UlwLoopScope): Promise<UlwLoopPlan>;
|
|
27
28
|
export declare function addUlwLoopGoal(repoRoot: string, args: {
|
|
28
29
|
title: string;
|
|
@@ -5,6 +5,7 @@ import { ulwLoopBriefPath, ulwLoopBriefRelativePath, ulwLoopDir, ulwLoopGoalsPat
|
|
|
5
5
|
import { appendGoalToPlan, deriveGoalCandidates, makeGoal } from "./plan-goal-factory.js";
|
|
6
6
|
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
|
|
7
7
|
import { iso, UlwLoopError } from "./types.js";
|
|
8
|
+
import { parseValidationBatches } from "./validation-batch.js";
|
|
8
9
|
export { deriveGoalCandidates, seedDefaultSuccessCriteria } from "./plan-goal-factory.js";
|
|
9
10
|
function isScheduleEligible(goal) {
|
|
10
11
|
return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
|
|
@@ -42,6 +43,9 @@ export async function createUlwLoopPlan(repoRoot, args, scope) {
|
|
|
42
43
|
codexGoalMode: args.codexGoalMode ?? "aggregate",
|
|
43
44
|
goals,
|
|
44
45
|
};
|
|
46
|
+
const validationBatches = await parseValidationBatches(args.validationBatchesJson, goals);
|
|
47
|
+
if (validationBatches !== undefined)
|
|
48
|
+
plan.validationBatches = validationBatches;
|
|
45
49
|
if (plan.codexGoalMode === "aggregate")
|
|
46
50
|
plan.codexObjective = aggregateCodexObjectiveForScope(scope);
|
|
47
51
|
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
@@ -5,6 +5,7 @@ export declare function withUlwLoopMutationLock<T>(repoRoot: string, scope: UlwL
|
|
|
5
5
|
export declare function readUlwLoopPlan(repoRoot: string, scope?: UlwLoopScope): Promise<UlwLoopPlan>;
|
|
6
6
|
export declare function writePlan(repoRoot: string, plan: UlwLoopPlan, scope?: UlwLoopScope): Promise<void>;
|
|
7
7
|
export declare function appendLedger(repoRoot: string, entry: UlwLoopLedgerEntry, scope?: UlwLoopScope): Promise<void>;
|
|
8
|
+
export declare function appendLedgerEntries(repoRoot: string, entries: readonly UlwLoopLedgerEntry[], scope?: UlwLoopScope): Promise<void>;
|
|
8
9
|
export declare function readSteeringLedgerEntries(repoRoot: string, scope?: UlwLoopScope): Promise<UlwLoopLedgerEntry[]>;
|
|
9
10
|
/**
|
|
10
11
|
* First accepted steering entry matching an idempotency key/prompt signature.
|
|
@@ -14,7 +14,10 @@ function isLegacyEnumeratedAggregateObjective(objective) {
|
|
|
14
14
|
return objective === LEGACY_OBJECTIVE || Boolean(objective?.startsWith(LEGACY_OBJECTIVE_PREFIX));
|
|
15
15
|
}
|
|
16
16
|
function isSteeringKind(value) {
|
|
17
|
-
return value === "steering_accepted" ||
|
|
17
|
+
return (value === "steering_accepted" ||
|
|
18
|
+
value === "steering_rejected" ||
|
|
19
|
+
value === "criteria_revised" ||
|
|
20
|
+
value === "batch_updated");
|
|
18
21
|
}
|
|
19
22
|
export async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
20
23
|
const scope = typeof scopeOrFn === "function" ? undefined : scopeOrFn;
|
|
@@ -76,8 +79,13 @@ export async function writePlan(repoRoot, plan, scope) {
|
|
|
76
79
|
await rename(tmpPath, path);
|
|
77
80
|
}
|
|
78
81
|
export async function appendLedger(repoRoot, entry, scope) {
|
|
82
|
+
await appendLedgerEntries(repoRoot, [entry], scope);
|
|
83
|
+
}
|
|
84
|
+
export async function appendLedgerEntries(repoRoot, entries, scope) {
|
|
85
|
+
if (entries.length === 0)
|
|
86
|
+
return;
|
|
79
87
|
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
80
|
-
await appendFile(ulwLoopLedgerPath(repoRoot, scope), `${JSON.stringify(entry)}\n`, "utf8");
|
|
88
|
+
await appendFile(ulwLoopLedgerPath(repoRoot, scope), `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf8");
|
|
81
89
|
}
|
|
82
90
|
/**
|
|
83
91
|
* Streams raw ledger lines without materializing the file. Real ledgers reach
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { invalid, literal, textField } from "./quality-gate-fields.js";
|
|
2
2
|
export function passedVerdict(value, field) {
|
|
3
3
|
if (value === "not_applicable")
|
|
4
4
|
invalid(`${field} must not be not_applicable.`, field);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { UlwLoopScope } from "./paths.js";
|
|
2
|
+
import type { UlwLoopPlan, UlwLoopSteeringAudit, UlwLoopSteeringProposal } from "./types.js";
|
|
3
|
+
export interface SteerUlwLoopBatchItemResult {
|
|
4
|
+
readonly accepted: boolean;
|
|
5
|
+
readonly deduped: boolean;
|
|
6
|
+
readonly audit: UlwLoopSteeringAudit;
|
|
7
|
+
readonly rejectedReasons: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export interface SteerUlwLoopBatchResult {
|
|
10
|
+
readonly plan: UlwLoopPlan;
|
|
11
|
+
readonly accepted: boolean;
|
|
12
|
+
readonly results: readonly SteerUlwLoopBatchItemResult[];
|
|
13
|
+
readonly rejectedReasons: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function steerUlwLoopBatch(repoRoot: string, proposals: readonly UlwLoopSteeringProposal[], scope?: UlwLoopScope): Promise<SteerUlwLoopBatchResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { appendLedger, appendLedgerEntries, findAcceptedSteeringLedgerEntry, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
|
|
2
|
+
import { applySteeringMutation, validateUlwLoopSteeringProposal } from "./steering.js";
|
|
3
|
+
import { buildSteeringPlanSnapshot, changedGoalIdsBetween } from "./steering-snapshot.js";
|
|
4
|
+
import { iso } from "./types.js";
|
|
5
|
+
import { batchUpdateLedgerEntry } from "./validation-batch.js";
|
|
6
|
+
export async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
7
|
+
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
8
|
+
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
9
|
+
const prepared = await prepareBatch(repoRoot, plan, proposals, scope);
|
|
10
|
+
const failed = prepared.results.find((item) => !item.accepted);
|
|
11
|
+
if (failed !== undefined) {
|
|
12
|
+
const entry = rejectedLedgerEntry(prepared.results);
|
|
13
|
+
await appendLedger(repoRoot, entry, scope);
|
|
14
|
+
return rejected(plan, prepared.results, failed.rejectedReasons);
|
|
15
|
+
}
|
|
16
|
+
let next = plan;
|
|
17
|
+
for (const item of prepared.items)
|
|
18
|
+
if (item.kind === "fresh")
|
|
19
|
+
next = item.prepared.next;
|
|
20
|
+
const fresh = prepared.items.filter((item) => item.kind === "fresh");
|
|
21
|
+
if (fresh.length > 0) {
|
|
22
|
+
await writePlan(repoRoot, next, scope);
|
|
23
|
+
const entries = fresh.map((item) => ledgerEntry(item.prepared.proposal, item.prepared.audit, item.prepared.proposal.now?.toISOString() ?? iso()));
|
|
24
|
+
const batchEntry = batchUpdateLedgerEntry(plan, next, iso());
|
|
25
|
+
await appendLedgerEntries(repoRoot, batchEntry === null ? entries : [...entries, batchEntry], scope);
|
|
26
|
+
}
|
|
27
|
+
return { plan: next, accepted: true, results: prepared.results, rejectedReasons: [] };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function prepareBatch(repoRoot, plan, proposals, scope) {
|
|
31
|
+
const items = [];
|
|
32
|
+
const results = [];
|
|
33
|
+
let current = plan;
|
|
34
|
+
for (const proposal of proposals) {
|
|
35
|
+
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
36
|
+
const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
|
|
37
|
+
if (prior?.steering !== undefined) {
|
|
38
|
+
const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
|
|
39
|
+
items.push({ kind: "deduped", result });
|
|
40
|
+
results.push(result);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const audit = validateUlwLoopSteeringProposal(current, proposal);
|
|
44
|
+
if (!audit.invariant.accepted) {
|
|
45
|
+
const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
|
|
46
|
+
items.push({ kind: "deduped", result });
|
|
47
|
+
results.push(result);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const next = applySteeringMutation(current, proposal, audit);
|
|
51
|
+
const changed = changedGoalIdsBetween(current, next);
|
|
52
|
+
const finalAudit = { ...audit, before: buildSteeringPlanSnapshot(current, changed), after: buildSteeringPlanSnapshot(next, changed) };
|
|
53
|
+
const result = { accepted: true, deduped: false, audit: finalAudit, rejectedReasons: [] };
|
|
54
|
+
items.push({ kind: "fresh", prepared: { proposal, audit: finalAudit, before: current, next } });
|
|
55
|
+
results.push(result);
|
|
56
|
+
current = next;
|
|
57
|
+
}
|
|
58
|
+
return { items, results };
|
|
59
|
+
}
|
|
60
|
+
function rejected(plan, results, rejectedReasons) {
|
|
61
|
+
return { plan, accepted: false, results, rejectedReasons };
|
|
62
|
+
}
|
|
63
|
+
function rejectedLedgerEntry(results) {
|
|
64
|
+
const rejectedItems = results.map((result, index) => ({ result, index })).filter((item) => !item.result.accepted);
|
|
65
|
+
return { at: iso(), kind: "steering_rejected", message: rejectedItems.map((item) => `index ${item.index}: ${item.result.rejectedReasons.join(", ")}`).join("; ") };
|
|
66
|
+
}
|
|
67
|
+
function ledgerEntry(proposal, audit, at) {
|
|
68
|
+
const entry = {
|
|
69
|
+
at,
|
|
70
|
+
kind: proposal.kind === "revise_criterion" ? "criteria_revised" : "steering_accepted",
|
|
71
|
+
evidence: proposal.evidence,
|
|
72
|
+
message: proposal.rationale,
|
|
73
|
+
steering: audit,
|
|
74
|
+
mutationKind: proposal.kind,
|
|
75
|
+
};
|
|
76
|
+
const goalId = audit.targetGoalIds[0];
|
|
77
|
+
if (goalId !== undefined)
|
|
78
|
+
entry.goalId = goalId;
|
|
79
|
+
if (proposal.criterionId !== undefined)
|
|
80
|
+
entry.criterionId = proposal.criterionId;
|
|
81
|
+
if (proposal.idempotencyKey !== undefined)
|
|
82
|
+
entry.idempotencyKey = proposal.idempotencyKey;
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSteeringChildGoal, UlwLoopSteeringProposal } from "./types.js";
|
|
2
|
+
export declare function makeGoal(plan: UlwLoopPlan, childGoal: UlwLoopSteeringChildGoal, evidence: string, now: string, offset: number): UlwLoopItem;
|
|
3
|
+
export declare function reviseWording(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void;
|
|
4
|
+
export declare function splitOrBlock(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void;
|
|
5
|
+
export declare function reviseCriterion(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// biome-ignore-all format: compact extracted steering mutations stay below the pure LOC budget.
|
|
2
|
+
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
|
|
3
|
+
import { ULW_LOOP_SUCCESS_CRITERION_USER_MODELS } from "./types.js";
|
|
4
|
+
import { updateBatchesAfterSupersede } from "./validation-batch.js";
|
|
5
|
+
const read = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
6
|
+
const isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
7
|
+
const text = (value, key) => { const candidate = read(value, key); return isText(candidate) ? candidate.trim() : undefined; };
|
|
8
|
+
const isModel = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
9
|
+
const after = (proposal) => { const candidate = read(proposal, "after"); return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined; };
|
|
10
|
+
const revised = (proposal, direct, nested) => text(proposal, direct) ?? text(after(proposal) ?? proposal, nested);
|
|
11
|
+
const targets = (proposal) => proposal.targetGoalIds ?? [proposal.targetGoalId ?? text(proposal, "goalId") ?? ""].filter(Boolean);
|
|
12
|
+
const child = (value) => {
|
|
13
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
14
|
+
return null;
|
|
15
|
+
const title = text(value, "title");
|
|
16
|
+
const objective = text(value, "objective");
|
|
17
|
+
return title === undefined || objective === undefined ? null : { title, objective };
|
|
18
|
+
};
|
|
19
|
+
const children = (proposal) => {
|
|
20
|
+
const direct = proposal.childGoals;
|
|
21
|
+
if (direct !== undefined && direct.length > 0)
|
|
22
|
+
return direct;
|
|
23
|
+
const nested = after(proposal);
|
|
24
|
+
const fromAfter = nested === undefined ? undefined : read(nested, "children");
|
|
25
|
+
return Array.isArray(fromAfter) ? fromAfter.map(child).filter((item) => item !== null) : [];
|
|
26
|
+
};
|
|
27
|
+
const goal = (plan, id) => id === undefined ? undefined : plan.goals.find((item) => item.id === id);
|
|
28
|
+
function nextId(plan, offset) {
|
|
29
|
+
const max = plan.goals.reduce((current, item) => { const digits = /^G(\d+)(?:-|$)/u.exec(item.id)?.[1]; return digits === undefined ? current : Math.max(current, Number(digits)); }, 0);
|
|
30
|
+
return `G${String(max + offset).padStart(3, "0")}`;
|
|
31
|
+
}
|
|
32
|
+
export function makeGoal(plan, childGoal, evidence, now, offset) {
|
|
33
|
+
const id = nextId(plan, offset);
|
|
34
|
+
const digits = /^G(\d+)/u.exec(id)?.[1];
|
|
35
|
+
const goalIndex = digits === undefined ? plan.goals.length + offset - 1 : Number(digits) - 1;
|
|
36
|
+
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective), attempt: 0, createdAt: now, updatedAt: now, evidence };
|
|
37
|
+
}
|
|
38
|
+
export function reviseWording(plan, proposal, now) {
|
|
39
|
+
const target = goal(plan, targets(proposal)[0]);
|
|
40
|
+
if (target === undefined)
|
|
41
|
+
return;
|
|
42
|
+
target.title = revised(proposal, "revisedTitle", "title") ?? target.title;
|
|
43
|
+
target.objective = revised(proposal, "revisedObjective", "objective") ?? target.objective;
|
|
44
|
+
target.steeringEvidence = proposal.evidence;
|
|
45
|
+
target.steeringRationale = proposal.rationale;
|
|
46
|
+
target.updatedAt = now;
|
|
47
|
+
}
|
|
48
|
+
export function splitOrBlock(plan, proposal, now) {
|
|
49
|
+
const target = goal(plan, targets(proposal)[0]);
|
|
50
|
+
if (target === undefined)
|
|
51
|
+
return;
|
|
52
|
+
const replacements = children(proposal).map((item, index) => makeGoal(plan, item, proposal.evidence, now, index + 1));
|
|
53
|
+
target.steeringEvidence = proposal.evidence;
|
|
54
|
+
target.steeringRationale = proposal.rationale;
|
|
55
|
+
target.updatedAt = now;
|
|
56
|
+
if (replacements.length === 0) {
|
|
57
|
+
target.status = "blocked";
|
|
58
|
+
target.steeringStatus = "blocked";
|
|
59
|
+
target.blockedReason = proposal.blockedReason ?? proposal.rationale;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
target.steeringStatus = "superseded";
|
|
63
|
+
target.supersededBy = replacements.map((item) => item.id);
|
|
64
|
+
for (const item of replacements)
|
|
65
|
+
item.supersedes = [target.id];
|
|
66
|
+
plan.goals.splice(plan.goals.indexOf(target) + 1, 0, ...replacements);
|
|
67
|
+
updateBatchesAfterSupersede(plan, target.id, replacements.map((item) => item.id));
|
|
68
|
+
}
|
|
69
|
+
if (plan.activeGoalId === target.id)
|
|
70
|
+
delete plan.activeGoalId;
|
|
71
|
+
}
|
|
72
|
+
export function reviseCriterion(plan, proposal, now) {
|
|
73
|
+
const target = goal(plan, targets(proposal)[0]);
|
|
74
|
+
const index = target?.successCriteria.findIndex((item) => item.id === proposal.criterionId) ?? -1;
|
|
75
|
+
const current = target?.successCriteria[index];
|
|
76
|
+
if (target === undefined || current === undefined)
|
|
77
|
+
return;
|
|
78
|
+
const model = read(proposal, "userModel");
|
|
79
|
+
target.successCriteria[index] = { ...current, scenario: text(proposal, "scenario") ?? current.scenario, expectedEvidence: text(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel(model) ? model : current.userModel };
|
|
80
|
+
target.updatedAt = now;
|
|
81
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// biome-ignore-all format: compact steering module must stay below the 240 pure-LOC budget
|
|
2
2
|
import { isUlwLoopDone } from "./goal-status.js";
|
|
3
|
-
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
|
|
4
3
|
import { appendLedger, findAcceptedSteeringLedgerEntry, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
|
|
4
|
+
import { makeGoal, reviseCriterion, reviseWording, splitOrBlock } from "./steering-mutations.js";
|
|
5
5
|
import { buildSteeringPlanSnapshot, changedGoalIdsBetween } from "./steering-snapshot.js";
|
|
6
6
|
import { iso, ULW_LOOP_STEERING_MUTATION_KINDS, ULW_LOOP_SUCCESS_CRITERION_USER_MODELS } from "./types.js";
|
|
7
|
+
import { batchUpdateLedgerEntry } from "./validation-batch.js";
|
|
7
8
|
const SOURCES = ["user_prompt_submit", "finding", "cli"];
|
|
8
9
|
const PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
9
10
|
const isObject = (value) => typeof value === "object" && value !== null;
|
|
@@ -48,7 +49,6 @@ function childValues(proposal) {
|
|
|
48
49
|
const fromAfter = nested === undefined ? undefined : read(nested, "children");
|
|
49
50
|
return Array.isArray(fromAfter) ? fromAfter : [];
|
|
50
51
|
}
|
|
51
|
-
const children = (proposal) => childValues(proposal).map(child).filter((item) => item !== null);
|
|
52
52
|
const pendingOrder = (proposal) => {
|
|
53
53
|
const direct = texts(proposal, "pendingOrder");
|
|
54
54
|
return direct.length > 0 ? direct : texts(after(proposal) ?? proposal, "pendingGoalIds");
|
|
@@ -163,19 +163,6 @@ function validateCriterion(plan, proposal, reasons) {
|
|
|
163
163
|
if (model !== undefined && !isModel(model))
|
|
164
164
|
reasons.push("invalid userModel");
|
|
165
165
|
}
|
|
166
|
-
function nextId(plan, offset) {
|
|
167
|
-
const max = plan.goals.reduce((current, item) => {
|
|
168
|
-
const digits = /^G(\d+)(?:-|$)/u.exec(item.id)?.[1];
|
|
169
|
-
return digits === undefined ? current : Math.max(current, Number(digits));
|
|
170
|
-
}, 0);
|
|
171
|
-
return `G${String(max + offset).padStart(3, "0")}`;
|
|
172
|
-
}
|
|
173
|
-
function makeGoal(plan, childGoal, evidence, now, offset) {
|
|
174
|
-
const id = nextId(plan, offset);
|
|
175
|
-
const digits = /^G(\d+)/u.exec(id)?.[1];
|
|
176
|
-
const goalIndex = digits === undefined ? plan.goals.length + offset - 1 : Number(digits) - 1;
|
|
177
|
-
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective), attempt: 0, createdAt: now, updatedAt: now, evidence };
|
|
178
|
-
}
|
|
179
166
|
export function applySteeringMutation(plan, proposal, audit) {
|
|
180
167
|
const next = structuredClone(plan);
|
|
181
168
|
if (!audit.invariant.accepted)
|
|
@@ -197,49 +184,6 @@ export function applySteeringMutation(plan, proposal, audit) {
|
|
|
197
184
|
next.updatedAt = now;
|
|
198
185
|
return next;
|
|
199
186
|
}
|
|
200
|
-
function reviseWording(plan, proposal, now) {
|
|
201
|
-
const target = goal(plan, targets(proposal)[0]);
|
|
202
|
-
if (target === undefined)
|
|
203
|
-
return;
|
|
204
|
-
target.title = revised(proposal, "revisedTitle", "title") ?? target.title;
|
|
205
|
-
target.objective = revised(proposal, "revisedObjective", "objective") ?? target.objective;
|
|
206
|
-
target.steeringEvidence = proposal.evidence;
|
|
207
|
-
target.steeringRationale = proposal.rationale;
|
|
208
|
-
target.updatedAt = now;
|
|
209
|
-
}
|
|
210
|
-
function splitOrBlock(plan, proposal, now) {
|
|
211
|
-
const target = goal(plan, targets(proposal)[0]);
|
|
212
|
-
if (target === undefined)
|
|
213
|
-
return;
|
|
214
|
-
const replacements = children(proposal).map((item, index) => makeGoal(plan, item, proposal.evidence, now, index + 1));
|
|
215
|
-
target.steeringEvidence = proposal.evidence;
|
|
216
|
-
target.steeringRationale = proposal.rationale;
|
|
217
|
-
target.updatedAt = now;
|
|
218
|
-
if (replacements.length === 0) {
|
|
219
|
-
target.status = "blocked";
|
|
220
|
-
target.steeringStatus = "blocked";
|
|
221
|
-
target.blockedReason = proposal.blockedReason ?? proposal.rationale;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
target.steeringStatus = "superseded";
|
|
225
|
-
target.supersededBy = replacements.map((item) => item.id);
|
|
226
|
-
for (const item of replacements)
|
|
227
|
-
item.supersedes = [target.id];
|
|
228
|
-
plan.goals.splice(plan.goals.indexOf(target) + 1, 0, ...replacements);
|
|
229
|
-
}
|
|
230
|
-
if (plan.activeGoalId === target.id)
|
|
231
|
-
delete plan.activeGoalId;
|
|
232
|
-
}
|
|
233
|
-
function reviseCriterion(plan, proposal, now) {
|
|
234
|
-
const target = goal(plan, targets(proposal)[0]);
|
|
235
|
-
const index = target?.successCriteria.findIndex((item) => item.id === proposal.criterionId) ?? -1;
|
|
236
|
-
const current = target?.successCriteria[index];
|
|
237
|
-
if (target === undefined || current === undefined)
|
|
238
|
-
return;
|
|
239
|
-
const model = read(proposal, "userModel");
|
|
240
|
-
target.successCriteria[index] = { ...current, scenario: text(proposal, "scenario") ?? current.scenario, expectedEvidence: text(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel(model) ? model : current.userModel };
|
|
241
|
-
target.updatedAt = now;
|
|
242
|
-
}
|
|
243
187
|
function isProposal(value) {
|
|
244
188
|
return isPlain(value) && isKind(read(value, "kind")) && isSource(read(value, "source")) && isText(read(value, "evidence")) && isText(read(value, "rationale"));
|
|
245
189
|
}
|
|
@@ -277,9 +221,13 @@ export async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
|
277
221
|
finalAudit.before = buildSteeringPlanSnapshot(plan, changed);
|
|
278
222
|
finalAudit.after = buildSteeringPlanSnapshot(next, changed);
|
|
279
223
|
}
|
|
224
|
+
const at = proposal.now?.toISOString() ?? iso();
|
|
225
|
+
const batchEntry = accepted ? batchUpdateLedgerEntry(plan, next, at) : null;
|
|
280
226
|
if (accepted)
|
|
281
227
|
await writePlan(repoRoot, next, scope);
|
|
282
|
-
await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit,
|
|
228
|
+
await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit, at), scope);
|
|
229
|
+
if (batchEntry !== null)
|
|
230
|
+
await appendLedger(repoRoot, batchEntry, scope);
|
|
283
231
|
return { plan: next, accepted, audit: finalAudit, rejectedReasons: audit.invariant.rejectedReasons, deduped: false };
|
|
284
232
|
});
|
|
285
233
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, join } from "node:path";
|
|
2
|
+
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
3
3
|
import { normalizeUlwLoopSessionId, ulwLoopDir } from "./paths.js";
|
|
4
4
|
// Turn-death recovery only: Codex emits Stop when a turn ends, so a run that
|
|
5
5
|
// dies mid-turn (crash, kill, context blowup before any Stop) never reaches
|
|
@@ -67,16 +67,24 @@ function isResumableStatus(status) {
|
|
|
67
67
|
// a separate file — a ledger append would change the count and self-reset.
|
|
68
68
|
function consumeResumeBudget(stateDir, goalId) {
|
|
69
69
|
const ledgerLineCount = countLedgerLines(join(stateDir, "ledger.jsonl"));
|
|
70
|
-
const counterPath =
|
|
70
|
+
const counterPath = resolve(stateDir, `auto-resume-${goalId}.json`);
|
|
71
|
+
const stuckPath = resolve(stateDir, `auto-resume-${goalId}.stuck`);
|
|
72
|
+
// goals.json is untrusted input: a crafted goal id (e.g. `../../x`) must
|
|
73
|
+
// never drive a write outside the session state dir. Deny the resume.
|
|
74
|
+
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
75
|
+
return false;
|
|
71
76
|
const previous = readCounter(counterPath);
|
|
72
77
|
const count = previous !== null && previous.ledgerLineCount === ledgerLineCount ? previous.count : 0;
|
|
73
78
|
if (count >= RESUME_CAP) {
|
|
74
|
-
writeFileSync(
|
|
79
|
+
writeFileSync(stuckPath, `no ledger progress after ${count} resumes\n`);
|
|
75
80
|
return false;
|
|
76
81
|
}
|
|
77
82
|
writeFileSync(counterPath, JSON.stringify({ count: count + 1, ledgerLineCount }));
|
|
78
83
|
return true;
|
|
79
84
|
}
|
|
85
|
+
function isInsideDir(dir, candidate) {
|
|
86
|
+
return candidate.startsWith(resolve(dir) + sep);
|
|
87
|
+
}
|
|
80
88
|
function renderResumeDirective(plan, goal, sessionId) {
|
|
81
89
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|
|
82
90
|
const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
|
|
@@ -85,7 +93,7 @@ function renderResumeDirective(plan, goal, sessionId) {
|
|
|
85
93
|
"The turn ended before the loop completed. Resume it now:",
|
|
86
94
|
`1. Run \`omo ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
|
|
87
95
|
"2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
|
|
88
|
-
`3. Checkpoint through \`omo ulw-loop checkpoint${option}\` when the goal's criteria are proven.`,
|
|
96
|
+
`3. Checkpoint through \`omo ulw-loop checkpoint${option}\` when the goal's criteria are proven; a complete checkpoint prints the next goal instruction.`,
|
|
89
97
|
"If the loop is genuinely blocked on the user, checkpoint the goal as blocked with the reason instead.",
|
|
90
98
|
].join("\n");
|
|
91
99
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopQualityGate, UlwLoopValidationBatch } from "./types.js";
|
|
2
|
+
export declare function parseValidationBatches(input: string | undefined, goals: readonly UlwLoopItem[]): Promise<readonly UlwLoopValidationBatch[] | undefined>;
|
|
3
|
+
export declare function updateBatchesAfterSupersede(plan: UlwLoopPlan, targetId: string, replacementIds: readonly string[]): void;
|
|
4
|
+
export declare function batchUpdateLedgerEntry(before: UlwLoopPlan, after: UlwLoopPlan, at: string): UlwLoopLedgerEntry | null;
|
|
5
|
+
export declare function batchOf(plan: UlwLoopPlan, goalId: string): UlwLoopValidationBatch | undefined;
|
|
6
|
+
export declare function batchClosedBy(plan: UlwLoopPlan, goalId: string): UlwLoopValidationBatch | undefined;
|
|
7
|
+
export declare function requireBatchFinalReady(plan: UlwLoopPlan, goal: UlwLoopItem): void;
|
|
8
|
+
export declare function requireAllValidationBatchesClosed(plan: UlwLoopPlan, closingGoalId?: string): void;
|
|
9
|
+
export declare function requireBatchGate(plan: UlwLoopPlan, goal: UlwLoopItem, gate: UlwLoopQualityGate): void;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// biome-ignore-all format: compact validation-batch helpers stay below the pure LOC budget.
|
|
2
|
+
import { readJsonInput } from "./cli-arg-parser.js";
|
|
3
|
+
import { isMemberResolved } from "./goal-status.js";
|
|
4
|
+
import { UlwLoopError } from "./types.js";
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function read(value, key) {
|
|
9
|
+
return Object.entries(value).find(([name]) => name === key)?.[1];
|
|
10
|
+
}
|
|
11
|
+
function text(value, key) {
|
|
12
|
+
const candidate = read(value, key);
|
|
13
|
+
return typeof candidate === "string" && candidate.trim().length > 0 ? candidate.trim() : undefined;
|
|
14
|
+
}
|
|
15
|
+
function strings(value, key) {
|
|
16
|
+
const candidate = read(value, key);
|
|
17
|
+
return Array.isArray(candidate) && candidate.every((item) => typeof item === "string" && item.trim().length > 0)
|
|
18
|
+
? candidate.map((item) => item.trim())
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
export async function parseValidationBatches(input, goals) {
|
|
22
|
+
const raw = await readJsonInput(input);
|
|
23
|
+
if (raw === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (!Array.isArray(raw))
|
|
26
|
+
fail("--validation-batch-json must be a JSON array.");
|
|
27
|
+
const batches = raw.map(batchFromObject);
|
|
28
|
+
validateBatches(batches, goals);
|
|
29
|
+
return batches;
|
|
30
|
+
}
|
|
31
|
+
function batchFromObject(value) {
|
|
32
|
+
if (!isObject(value))
|
|
33
|
+
fail("validation batch entries must be objects.");
|
|
34
|
+
const batchId = text(value, "batchId");
|
|
35
|
+
const memberIds = strings(value, "memberIds");
|
|
36
|
+
const finalGoalId = text(value, "finalGoalId");
|
|
37
|
+
if (batchId === undefined)
|
|
38
|
+
fail("validation batch requires batchId.");
|
|
39
|
+
if (memberIds === undefined || memberIds.length < 2)
|
|
40
|
+
fail("validation batch requires at least two memberIds.");
|
|
41
|
+
if (finalGoalId === undefined)
|
|
42
|
+
fail("validation batch requires finalGoalId.");
|
|
43
|
+
return { batchId, memberIds, finalGoalId };
|
|
44
|
+
}
|
|
45
|
+
function validateBatches(batches, goals) {
|
|
46
|
+
const goalIds = new Set(goals.map((goal) => goal.id));
|
|
47
|
+
const batchIds = new Set();
|
|
48
|
+
const members = new Set();
|
|
49
|
+
for (const batch of batches) {
|
|
50
|
+
if (batchIds.has(batch.batchId))
|
|
51
|
+
fail(`duplicate validation batch id: ${batch.batchId}.`);
|
|
52
|
+
batchIds.add(batch.batchId);
|
|
53
|
+
if (new Set(batch.memberIds).size !== batch.memberIds.length)
|
|
54
|
+
fail(`validation batch ${batch.batchId} has duplicate memberIds.`);
|
|
55
|
+
if (!batch.memberIds.includes(batch.finalGoalId))
|
|
56
|
+
fail(`validation batch ${batch.batchId} finalGoalId must be a member.`, "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER");
|
|
57
|
+
for (const memberId of batch.memberIds) {
|
|
58
|
+
if (!goalIds.has(memberId))
|
|
59
|
+
fail(`validation batch ${batch.batchId} references unknown goal: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_MEMBER_UNKNOWN");
|
|
60
|
+
if (members.has(memberId))
|
|
61
|
+
fail(`goal appears in multiple validation batches: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_OVERLAP");
|
|
62
|
+
members.add(memberId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function updateBatchesAfterSupersede(plan, targetId, replacementIds) {
|
|
67
|
+
if (replacementIds.length === 0 || plan.validationBatches === undefined)
|
|
68
|
+
return;
|
|
69
|
+
plan.validationBatches = plan.validationBatches.map((batch) => {
|
|
70
|
+
if (!batch.memberIds.includes(targetId))
|
|
71
|
+
return batch;
|
|
72
|
+
const memberIds = batch.memberIds.flatMap((id) => (id === targetId ? [...replacementIds] : [id]));
|
|
73
|
+
const replacementFinalGoalId = replacementIds[replacementIds.length - 1] ?? batch.finalGoalId;
|
|
74
|
+
const finalGoalId = batch.finalGoalId === targetId ? replacementFinalGoalId : batch.finalGoalId;
|
|
75
|
+
return { batchId: batch.batchId, memberIds, finalGoalId };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function batchUpdateLedgerEntry(before, after, at) {
|
|
79
|
+
if (JSON.stringify(before.validationBatches ?? []) === JSON.stringify(after.validationBatches ?? []))
|
|
80
|
+
return null;
|
|
81
|
+
return { at, kind: "batch_updated", before: before.validationBatches ?? [], after: after.validationBatches ?? [], message: "Validation batch membership updated after steering." };
|
|
82
|
+
}
|
|
83
|
+
export function batchOf(plan, goalId) {
|
|
84
|
+
return plan.validationBatches?.find((batch) => batch.memberIds.includes(goalId));
|
|
85
|
+
}
|
|
86
|
+
export function batchClosedBy(plan, goalId) {
|
|
87
|
+
const batch = batchOf(plan, goalId);
|
|
88
|
+
return batch?.finalGoalId === goalId ? batch : undefined;
|
|
89
|
+
}
|
|
90
|
+
export function requireBatchFinalReady(plan, goal) {
|
|
91
|
+
const batch = batchClosedBy(plan, goal.id);
|
|
92
|
+
if (batch === undefined)
|
|
93
|
+
return;
|
|
94
|
+
const open = batch.memberIds.filter((id) => id !== goal.id && !memberResolved(plan, id));
|
|
95
|
+
if (open.length > 0)
|
|
96
|
+
throw new UlwLoopError("Validation batch has unresolved members.", "ULW_LOOP_VALIDATION_BATCH_OPEN", { details: { batchId: batch.batchId, open } });
|
|
97
|
+
}
|
|
98
|
+
export function requireAllValidationBatchesClosed(plan, closingGoalId) {
|
|
99
|
+
const open = (plan.validationBatches ?? []).filter((batch) => batch.memberIds.some((id) => id !== closingGoalId && !memberResolved(plan, id)));
|
|
100
|
+
if (open.length > 0)
|
|
101
|
+
throw new UlwLoopError("Validation batches remain open.", "ULW_LOOP_VALIDATION_BATCH_OPEN", { details: { batchIds: open.map((batch) => batch.batchId) } });
|
|
102
|
+
}
|
|
103
|
+
export function requireBatchGate(plan, goal, gate) {
|
|
104
|
+
const batch = batchClosedBy(plan, goal.id);
|
|
105
|
+
if (batch === undefined)
|
|
106
|
+
return;
|
|
107
|
+
const members = batch.memberIds.map((id) => plan.goals.find((item) => item.id === id)).filter((item) => item !== undefined);
|
|
108
|
+
const pending = members.flatMap((member) => member.successCriteria.filter((criterion) => criterion.status !== "pass").map((criterion) => `${member.id}:${criterion.id}`));
|
|
109
|
+
if (pending.length > 0)
|
|
110
|
+
throw new UlwLoopError("Validation batch criteria remain pending.", "ULW_LOOP_VALIDATION_BATCH_CRITERIA_PENDING", { details: { batchId: batch.batchId, pending } });
|
|
111
|
+
const totalCriteria = members.reduce((sum, member) => sum + member.successCriteria.length, 0);
|
|
112
|
+
const passCount = members.reduce((sum, member) => sum + member.successCriteria.filter((criterion) => criterion.status === "pass").length, 0);
|
|
113
|
+
if (gate.criteriaCoverage.totalCriteria !== totalCriteria || gate.criteriaCoverage.passCount !== passCount)
|
|
114
|
+
throw new UlwLoopError("Validation batch gate coverage does not match member criteria.", "ULW_LOOP_VALIDATION_BATCH_GATE_MISMATCH", { details: { batchId: batch.batchId, expected: { totalCriteria, passCount }, actual: gate.criteriaCoverage } });
|
|
115
|
+
}
|
|
116
|
+
function memberResolved(plan, goalId) {
|
|
117
|
+
const goal = plan.goals.find((candidate) => candidate.id === goalId);
|
|
118
|
+
return goal !== undefined && isMemberResolved(goal, plan);
|
|
119
|
+
}
|
|
120
|
+
function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
121
|
+
throw new UlwLoopError(message, code);
|
|
122
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit --with-ultrawork",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 4.
|
|
10
|
+
"statusMessage": "(OmO 4.19.1) Checking Ulw-Loop Steering"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"type": "command",
|
|
21
21
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
|
|
22
22
|
"timeout": 5,
|
|
23
|
-
"statusMessage": "(OmO 4.
|
|
23
|
+
"statusMessage": "(OmO 4.19.1) Enforcing Unlimited Ulw-Loop Budget"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"type": "command",
|
|
32
32
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use-spawn",
|
|
33
33
|
"timeout": 5,
|
|
34
|
-
"statusMessage": "(OmO 4.
|
|
34
|
+
"statusMessage": "(OmO 4.19.1) Guarding Ulw-Loop Spawns"
|
|
35
35
|
}
|
|
36
36
|
]
|
|
37
37
|
}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "command",
|
|
44
44
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook stop",
|
|
45
45
|
"timeout": 10,
|
|
46
|
-
"statusMessage": "(OmO 4.
|
|
46
|
+
"statusMessage": "(OmO 4.19.1) Checking Ulw-Loop Resume"
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
}
|