oh-my-opencode 5.0.0-beta.5 → 5.0.0-beta.51
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/publish.md +44 -16
- package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +1 -1
- package/.agents/skills/pre-publish-review/SKILL.md +9 -12
- package/.agents/skills/publish/SKILL.md +44 -16
- package/.agents/skills/senpi-qa/SKILL.md +80 -0
- package/.agents/skills/senpi-qa/scripts/resolve-evidence-dir.mjs +65 -0
- package/.agents/skills/senpi-qa/scripts/resolve-evidence-dir.test.mjs +171 -0
- package/.agents/skills/tech-debt-audit/SKILL.md +6 -76
- package/.agents/skills/work-with-pr/SKILL.md +37 -23
- package/.opencode/command/publish.md +44 -16
- package/.opencode/skills/pre-publish-review/SKILL.md +9 -12
- package/.opencode/skills/work-with-pr/SKILL.md +37 -23
- package/README.ja.md +58 -46
- package/README.ko.md +60 -48
- package/README.md +58 -58
- package/README.ru.md +55 -46
- package/README.zh-cn.md +54 -46
- package/THIRD-PARTY-NOTICES.md +27 -99
- package/dist/agents/atlas/agent.d.ts +0 -1
- package/dist/agents/gpt-prompt-identity.d.ts +1 -1
- package/dist/agents/sisyphus/grok-4.d.ts +20 -0
- package/dist/agents/sisyphus/index.d.ts +2 -0
- package/dist/agents/sisyphus-agent-config.d.ts +6 -0
- package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
- package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +15 -4
- package/dist/agents/types.d.ts +4 -2
- package/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
- package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
- package/dist/cli/fallback-lane-policy.d.ts +1 -0
- package/dist/cli/index.js +51551 -59608
- package/dist/cli/install-codex/install-codex-test-fixtures.d.ts +1 -1
- package/dist/cli/run/on-complete-hook.d.ts +2 -0
- package/dist/cli/runtime-commands.d.ts +1 -1
- package/dist/cli/worktree-sweep/classify.d.ts +16 -0
- package/dist/cli/worktree-sweep/format.d.ts +8 -0
- package/dist/cli/worktree-sweep/git.d.ts +19 -0
- package/dist/cli/worktree-sweep/index.d.ts +7 -0
- package/dist/cli/worktree-sweep/options.d.ts +2 -0
- package/dist/cli/worktree-sweep/parse-worktree-list.d.ts +8 -0
- package/dist/cli/worktree-sweep/sweep.d.ts +2 -0
- package/dist/cli/worktree-sweep/types.d.ts +65 -0
- package/dist/cli/worktree-sweep/worktree-sweep.d.ts +2 -0
- package/dist/cli-node/index.js +51569 -59603
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/commands.d.ts +1 -1
- package/dist/config/schema/hooks.d.ts +1 -2
- package/dist/config/schema/oh-my-opencode-config.d.ts +5 -11
- package/dist/config/schema/ulw-execute.d.ts +5 -0
- package/dist/config/schema.d.ts +0 -1
- package/dist/config/validate.d.ts +2 -0
- package/dist/create-hooks.d.ts +3 -2
- package/dist/features/background-agent/compaction-aware-message-resolver.d.ts +1 -0
- package/dist/features/background-agent/constants.d.ts +0 -7
- package/dist/features/background-agent/error-classifier.d.ts +1 -0
- package/dist/features/background-agent/manager.d.ts +2 -1
- package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
- package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
- package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
- package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
- package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
- package/dist/features/btw-side/btw-command-draft.d.ts +7 -0
- package/dist/features/btw-side/context-injector.d.ts +15 -0
- package/dist/features/btw-side/index.d.ts +8 -0
- package/dist/features/btw-side/metadata.d.ts +15 -0
- package/dist/features/btw-side/parent-context-budget.d.ts +9 -0
- package/dist/features/btw-side/server-session-registry.d.ts +8 -0
- package/dist/features/btw-side/tui-adoption-cache.d.ts +13 -0
- package/dist/features/btw-side/tui-adoption-guard.d.ts +5 -0
- package/dist/features/btw-side/tui-controller-types.d.ts +68 -0
- package/dist/features/btw-side/tui-controller.d.ts +19 -0
- package/dist/features/btw-side/tui-escape-return.d.ts +12 -0
- package/dist/features/btw-side/tui-keymap.d.ts +17 -0
- package/dist/features/btw-side/tui-parent-validator.d.ts +6 -0
- package/dist/features/btw-side/tui-picker-options.d.ts +20 -0
- package/dist/features/btw-side/tui-picker.d.ts +9 -0
- package/dist/features/btw-side/tui-prompt-queue.d.ts +8 -0
- package/dist/features/btw-side/tui-session-bridge.d.ts +11 -0
- package/dist/features/btw-side/tui-session-catalog.d.ts +35 -0
- package/dist/features/btw-side/tui-side-removal.d.ts +11 -0
- package/dist/features/btw-side/tui-side-start.d.ts +9 -0
- package/dist/features/btw-side/tui-wiring.d.ts +8 -0
- package/dist/features/builtin-commands/templates/ulw-execute.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/monitor/manager-internals.d.ts +1 -1
- package/dist/features/monitor/output-injector-types.d.ts +1 -1
- package/dist/features/opengateway-provider/index.d.ts +13 -0
- package/dist/features/task-toast-manager/manager.d.ts +0 -24
- package/dist/features/team-mode/tools/messaging-fallback-wake.d.ts +17 -0
- package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +3 -0
- package/dist/features/team-mode/tools/messaging-runtime.d.ts +7 -0
- package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
- package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
- package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
- package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
- package/dist/hooks/atlas/atlas-hook.d.ts +1 -0
- package/dist/hooks/atlas/atlas-lifecycle-store.d.ts +50 -0
- package/dist/hooks/atlas/event-handler.d.ts +1 -0
- package/dist/hooks/atlas/final-wave-approval-gate.test-support.d.ts +50 -0
- package/dist/hooks/atlas/system-reminder-templates.d.ts +0 -1
- package/dist/hooks/atlas/tool-execute-before.d.ts +3 -0
- package/dist/hooks/atlas/types.d.ts +1 -0
- package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
- package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
- package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
- package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
- package/dist/hooks/compaction-context-injector/validated-model.d.ts +2 -0
- package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
- package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
- package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
- package/dist/hooks/index.d.ts +1 -2
- package/dist/hooks/keyword-detector/hook.d.ts +7 -0
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
- package/dist/hooks/model-fallback/hook.d.ts +1 -1
- package/dist/hooks/prometheus-md-only/agent-resolution.d.ts +2 -2
- package/dist/hooks/runtime-fallback/chat-message-handler.d.ts +2 -0
- package/dist/hooks/runtime-fallback/retry-model-payload.d.ts +1 -0
- package/dist/hooks/runtime-fallback/runtime-model-settings.d.ts +6 -0
- package/dist/hooks/runtime-fallback/session-status-handler.d.ts +1 -1
- package/dist/hooks/runtime-fallback/types.d.ts +3 -1
- package/dist/hooks/sisyphus-junior-notepad/constants.d.ts +1 -1
- package/dist/hooks/todo-continuation-enforcer/types.d.ts +1 -0
- package/dist/hooks/{start-work → ulw-execute}/context-info-builder.d.ts +1 -1
- package/dist/hooks/{start-work → ulw-execute}/index.d.ts +1 -1
- package/dist/hooks/ulw-execute/ulw-execute-hook.d.ts +23 -0
- package/dist/index.js +28955 -25346
- package/dist/mcp/index.d.ts +0 -4
- package/dist/mcp/types.d.ts +0 -1
- package/dist/oh-my-opencode.schema.json +21 -47
- package/dist/plugin/chat-message/types.d.ts +3 -3
- package/dist/plugin/chat-message/ulw-execute-message.d.ts +5 -0
- package/dist/plugin/event-session-lifecycle.d.ts +1 -0
- package/dist/plugin/hooks/create-core-hooks.d.ts +2 -2
- package/dist/plugin/hooks/create-session-hooks.d.ts +2 -3
- package/dist/plugin/hooks/create-transform-hooks.d.ts +2 -0
- package/dist/plugin/messages-transform.d.ts +1 -0
- package/dist/plugin-dispose.d.ts +3 -0
- package/dist/shared/agent-tool-restrictions.d.ts +0 -1
- package/dist/shared/compaction-agent-config-checkpoint.d.ts +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/legacy-plugin-warning.d.ts +2 -1
- package/dist/shared/normalize-sdk-response.d.ts +1 -0
- package/dist/shared/omo-process-sweep.d.ts +2 -4
- package/dist/shared/plugin-entry-migrator.d.ts +4 -3
- package/dist/shared/plugin-entry-shape.d.ts +6 -0
- package/dist/shared/runtime-fallback-retry-marker.d.ts +16 -0
- package/dist/shared/shell-env.d.ts +1 -1
- package/dist/skills/ast-grep/AGENTS.md +51 -0
- package/dist/skills/ast-grep/SKILL.md +1 -1
- package/dist/skills/coding-agent-sessions/AGENTS.md +62 -0
- package/dist/skills/coding-agent-sessions/SKILL.md +3 -2
- package/dist/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/dist/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/dist/skills/data-scientist/SKILL.md +99 -239
- package/dist/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/dist/skills/data-scientist/references/placement.md +74 -0
- package/dist/skills/data-scientist/references/polars-lane.md +95 -0
- package/dist/skills/data-scientist/references/uv-setup.md +1 -1
- package/dist/skills/data-scientist/references/visualization.md +64 -0
- package/dist/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/dist/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/dist/skills/debugging/SKILL.md +3 -1
- package/dist/skills/debugging/references/methodology/00-setup.md +12 -0
- package/dist/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/dist/skills/debugging/references/runtimes/go.md +15 -1
- package/dist/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/dist/skills/debugging/references/runtimes/node.md +25 -0
- package/dist/skills/debugging/references/runtimes/python.md +13 -0
- package/dist/skills/debugging/references/runtimes/rust.md +41 -0
- package/dist/skills/debugging/references/scripts/dap.mjs +267 -0
- package/dist/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/dist/skills/debugging/references/tools/dap.md +103 -0
- package/dist/skills/debugging/references/tools/frida.md +193 -0
- package/dist/skills/frontend/SKILL.md +12 -8
- package/dist/skills/frontend/references/design/_INDEX.md +1 -0
- package/dist/skills/frontend/references/design/print-paged-media.md +76 -0
- package/dist/skills/frontend/references/design/stylegallery.md +80 -0
- package/dist/skills/frontend/references/designpowers/README.md +1 -1
- package/dist/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
- package/dist/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
- package/dist/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
- package/dist/skills/frontend/references/designpowers/orchestration.md +1 -1
- package/dist/skills/frontend/references/designpowers/routing.md +5 -5
- package/dist/skills/git-master/SKILL.md +1 -1
- package/dist/skills/init-deep/SKILL.md +14 -14
- package/dist/skills/lsp-setup/SKILL.md +1 -1
- package/dist/skills/programming/SKILL.md +1 -1
- package/dist/skills/refactor/SKILL.md +1 -1
- package/dist/skills/remove-ai-slops/SKILL.md +1 -1
- package/dist/skills/review-work/SKILL.md +100 -418
- package/dist/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
- package/dist/skills/ultimate-browsing/SKILL.md +26 -9
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/dist/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/dist/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/dist/skills/{start-work → ulw-execute}/SKILL.md +17 -16
- package/dist/skills/ulw-plan/SKILL.md +7 -8
- package/dist/skills/ulw-plan/references/full-workflow.md +31 -7
- package/dist/skills/ulw-plan/references/intent-clear.md +2 -1
- package/dist/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/dist/skills/ulw-research/SKILL.md +8 -3
- package/dist/skills/visual-qa/AGENTS.md +58 -0
- package/dist/skills/visual-qa/SKILL.md +4 -3
- package/dist/tools/delegate-task/builtin-categories.d.ts +3 -1
- package/dist/tools/delegate-task/builtin-category-definition.d.ts +2 -1
- package/dist/tools/delegate-task/constants.d.ts +2 -2
- package/dist/tools/delegate-task/openai-categories.d.ts +7 -2
- package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
- package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
- package/dist/tools/delegate-task/tool-description.d.ts +3 -1
- package/dist/tools/delegate-task/types.d.ts +4 -0
- package/dist/tools/session-manager/directory-filter.d.ts +2 -0
- package/dist/tui.d.ts +12 -0
- package/dist/tui.js +37066 -46704
- package/package.json +47 -41
- package/packages/git-bash-mcp/dist/cli.js +6 -1
- package/packages/git-bash-mcp/package.json +1 -1
- package/packages/lsp-core/src/index.ts +1 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +44 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +57 -48
- package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
- package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +163 -13
- package/packages/lsp-core/src/lsp/client-wrapper.ts +110 -23
- package/packages/lsp-core/src/lsp/client.ts +32 -3
- package/packages/lsp-core/src/lsp/connection.ts +16 -0
- package/packages/lsp-core/src/lsp/constants.ts +6 -0
- package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
- package/packages/lsp-core/src/lsp/errors.ts +15 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
- package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
- package/packages/lsp-core/src/lsp/format-document.ts +90 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection.ts +11 -6
- package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
- package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
- package/packages/lsp-core/src/lsp/manager.ts +138 -12
- package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
- package/packages/lsp-core/src/lsp/server-definitions.ts +29 -0
- package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
- package/packages/lsp-core/src/lsp/server-installation.ts +200 -27
- package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
- package/packages/lsp-core/src/lsp/server-resolution.ts +18 -5
- package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
- package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
- package/packages/lsp-core/src/lsp/transport.ts +16 -2
- package/packages/lsp-core/src/lsp/types.ts +8 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +29 -1
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
- package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +20 -1
- package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
- package/packages/lsp-core/src/mcp.ts +3 -0
- package/packages/lsp-core/src/request-context.test.ts +29 -0
- package/packages/lsp-core/src/request-context.ts +1 -1
- package/packages/lsp-core/src/tool-surface.test.ts +13 -1
- package/packages/lsp-core/src/tools/definitions.ts +14 -0
- package/packages/lsp-core/src/tools/diagnostics.ts +3 -3
- package/packages/lsp-core/src/tools/format.test.ts +56 -0
- package/packages/lsp-core/src/tools/format.ts +56 -0
- package/packages/lsp-core/src/tools/index.ts +1 -0
- package/packages/lsp-core/src/tools/navigation.ts +4 -2
- package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
- package/packages/lsp-core/src/tools/rename.ts +4 -2
- package/packages/lsp-core/src/tools/symbols.ts +1 -1
- package/packages/lsp-core/src/tools/types.ts +10 -0
- package/packages/lsp-daemon/dist/cli.js +961 -483
- package/packages/lsp-daemon/dist/client.d.ts +10 -0
- package/packages/lsp-daemon/dist/client.js +976 -495
- package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
- package/packages/lsp-daemon/dist/daemon-client.js +3 -0
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +6 -1
- package/packages/lsp-daemon/dist/ensure-daemon.js +10 -2
- package/packages/lsp-daemon/dist/index.js +881 -405
- package/packages/lsp-daemon/dist/proxy.js +3 -0
- package/packages/lsp-daemon/dist/version-reap.js +1 -1
- package/packages/lsp-daemon/package.json +1 -1
- package/packages/lsp-tools-mcp/dist/cli.js +820 -345
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +272 -138
- package/packages/lsp-tools-mcp/dist/mcp.js +845 -372
- package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
- package/packages/lsp-tools-mcp/dist/tools.js +775 -306
- package/packages/lsp-tools-mcp/package.json +1 -1
- package/packages/omo-codex/THIRD-PARTY-NOTICES.md +4 -21
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +4 -6
- package/packages/omo-codex/plugin/.mcp.json +0 -6
- package/packages/omo-codex/plugin/AGENTS.md +47 -0
- package/packages/omo-codex/plugin/README.md +6 -2
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +197 -148
- 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/hook.ts +1 -0
- package/packages/omo-codex/plugin/components/comment-checker/AGENTS.md +9 -0
- package/packages/omo-codex/plugin/components/comment-checker/biome.json +2 -2
- package/packages/omo-codex/plugin/components/comment-checker/dist/cli.js +2 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +5 -5
- package/packages/omo-codex/plugin/components/comment-checker/src/apply-patch.ts +2 -8
- package/packages/omo-codex/plugin/components/comment-checker/src/core.ts +1 -2
- package/packages/omo-codex/plugin/components/comment-checker/src/request-extractor.ts +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/src/runner.ts +1 -0
- package/packages/omo-codex/plugin/components/git-bash/AGENTS.md +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 +3 -4
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +2 -2
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/directive.md +2 -0
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/cli.js +36 -29
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/codex-hook.js +32 -26
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/types.d.ts +3 -0
- 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 +5 -5
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/codex-hook.ts +32 -28
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/types.ts +3 -0
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/cli.test.ts +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +137 -28
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +4 -2
- package/packages/omo-codex/plugin/components/lsp/AGENTS.md +30 -15
- package/packages/omo-codex/plugin/components/lsp/biome.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +999 -544
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +1 -5
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +6 -7
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +1 -2
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +11 -1
- package/packages/omo-codex/plugin/components/rules/AGENTS.md +12 -0
- package/packages/omo-codex/plugin/components/rules/README.md +2 -0
- package/packages/omo-codex/plugin/components/rules/biome.json +2 -2
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +123 -0
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +91 -68
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +7 -7
- package/packages/omo-codex/plugin/components/rules/src/config.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/src/persistent-cache.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +15 -5
- package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +1 -4
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -9
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +17 -25
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
- package/packages/omo-codex/plugin/components/rules/test/dynamic-target-fingerprints.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/engine.test.ts +7 -4
- package/packages/omo-codex/plugin/components/rules/test/finder.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +39 -72
- package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +38 -3
- package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/matcher.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +1 -1
- package/packages/omo-codex/plugin/components/rules/test/parser.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +29 -6
- package/packages/omo-codex/plugin/components/rules/test/rules-engine-consumption.test.ts +3 -6
- package/packages/omo-codex/plugin/components/rules/test/scanner.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/sources.test.ts +2 -5
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +2 -17
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +5 -5
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +4 -10
- package/packages/omo-codex/plugin/components/telemetry/AGENTS.md +8 -0
- package/packages/omo-codex/plugin/components/telemetry/biome.json +2 -2
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +2435 -700
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2439 -705
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +5 -5
- package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +1 -4
- package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -14
- package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +1 -1
- package/packages/omo-codex/plugin/components/telemetry/test/diagnostics.test.ts +2 -5
- package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -4
- package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -0
- package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.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-low.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/biome.json +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +490 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +5 -6
- package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +6 -7
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +31 -7
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
- package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +9 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
- package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/AGENTS.md +4 -4
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/NOTICE +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/README.md +6 -6
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/biome.json +2 -2
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/directive.md +10 -10
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/dist/cli.js +16 -16
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +16 -0
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/package.json +12 -12
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/boulder-reader.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/cli.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/codex-hook.ts +4 -4
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/directive.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/index.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/types.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/boulder-reader.test.ts +3 -3
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/cli.test.ts +4 -11
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/codex-hook.test.ts +24 -22
- package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/biome.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +26 -13
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +15 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +40 -39
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +21 -8
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +1270 -429
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +28 -8
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +15 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +13 -4
- 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 +34 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +14 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +29 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +104 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +65 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +72 -117
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +6 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +195 -29
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +227 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +20 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +75 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +17 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +6 -6
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +6 -4
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +108 -0
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +32 -8
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +31 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +128 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +38 -71
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +24 -8
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +14 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +8 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +35 -7
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +26 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
- package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +25 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +39 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +38 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +11 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +123 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +71 -15
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +92 -136
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +205 -28
- package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +239 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +19 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +107 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +132 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +147 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint.test.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-commands.test.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-complete-goals.test.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +35 -8
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +38 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-scope-required.test.ts +144 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +185 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +8 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +37 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/cli-session.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +9 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +5 -37
- package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io-cross-process.test.ts +106 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +36 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-fields-messages.test.ts +98 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-lazycodex-surface.test.ts +93 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +51 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +58 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +444 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +218 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +8 -5
- package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +13 -8
- package/packages/omo-codex/plugin/components/ulw-loop/vitest.config.ts +1 -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-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/post-tool-use-recording-spawn-admission.json +18 -0
- 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 +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- 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 +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +17 -0
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.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/model-catalog.json +16 -7
- package/packages/omo-codex/plugin/package-lock.json +676 -524
- package/packages/omo-codex/plugin/package.json +2 -3
- package/packages/omo-codex/plugin/scripts/AGENTS.md +39 -0
- package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +1 -2
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
- package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +16 -7
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +8 -4
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +19 -90
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +94 -55
- package/packages/omo-codex/plugin/shared/package.json +3 -4
- package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
- package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +51 -0
- package/packages/omo-codex/plugin/skills/ast-grep/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +62 -0
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +3 -1
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +12 -8
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +1 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/print-paged-media.md +76 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +80 -0
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/README.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/orchestration.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/routing.md +5 -5
- package/packages/omo-codex/plugin/skills/git-master/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +18 -16
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +4 -2
- package/packages/omo-codex/plugin/skills/lsp-setup/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +5 -3
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +5 -3
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +114 -422
- package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +26 -9
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +26 -13
- package/packages/omo-codex/plugin/skills/{start-work → ulw-execute}/SKILL.md +26 -28
- package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +30 -4
- package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +108 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +32 -8
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +6 -7
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +31 -7
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +12 -5
- package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +58 -0
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +8 -5
- package/packages/omo-codex/plugin/test/AGENTS.md +47 -0
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +16 -51
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -3
- package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
- package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +14 -4
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
- package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +4 -29
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
- package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
- package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -51
- package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +88 -2
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +5 -6
- package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +35 -35
- package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
- package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +107 -15
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +168 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +54 -24
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +18 -119
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
- package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
- package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
- package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
- package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
- package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -5
- package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
- package/packages/omo-codex/scripts/AGENTS.md +36 -0
- package/packages/omo-codex/scripts/check-model-catalog-parity.mjs +42 -0
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +1 -1
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +2 -1
- package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +5 -5
- package/packages/omo-codex/scripts/install-config.test.mjs +15 -17
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +3035 -1395
- package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +3 -4
- package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
- package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +7 -2
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +1 -6
- package/packages/{omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md → prompts-core/prompts/ultrawork/codex.md} +26 -20
- package/packages/shared-skills/package.json +7 -1
- package/packages/shared-skills/skill-source-filter.d.ts +16 -0
- package/packages/shared-skills/skill-source-filter.mjs +44 -0
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +51 -0
- package/packages/shared-skills/skills/ast-grep/SKILL.md +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +62 -0
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +3 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
- package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
- package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/shared-skills/skills/debugging/SKILL.md +3 -1
- package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
- package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +12 -8
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +1 -0
- package/packages/shared-skills/skills/frontend/references/design/print-paged-media.md +76 -0
- package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +80 -0
- package/packages/shared-skills/skills/frontend/references/designpowers/README.md +1 -1
- package/packages/shared-skills/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
- package/packages/shared-skills/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
- package/packages/shared-skills/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
- package/packages/shared-skills/skills/frontend/references/designpowers/orchestration.md +1 -1
- package/packages/shared-skills/skills/frontend/references/designpowers/routing.md +5 -5
- package/packages/shared-skills/skills/git-master/SKILL.md +1 -1
- package/packages/shared-skills/skills/init-deep/SKILL.md +14 -14
- package/packages/shared-skills/skills/lsp-setup/SKILL.md +1 -1
- package/packages/shared-skills/skills/programming/SKILL.md +1 -1
- package/packages/shared-skills/skills/refactor/SKILL.md +1 -1
- package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +1 -1
- package/packages/shared-skills/skills/review-work/SKILL.md +100 -418
- package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +26 -9
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/packages/shared-skills/skills/{start-work → ulw-execute}/SKILL.md +17 -16
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +7 -8
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +31 -7
- package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -1
- package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/packages/shared-skills/skills/ulw-research/SKILL.md +8 -3
- package/packages/shared-skills/skills/visual-qa/AGENTS.md +58 -0
- package/packages/shared-skills/skills/visual-qa/SKILL.md +4 -3
- package/postinstall.mjs +2 -0
- package/script/qa/web-terminal-visual-qa.mjs +2 -0
- package/script/qa/xterm-live-terminal.mjs +18 -2
- package/dist/config/schema/codegraph.d.ts +0 -12
- package/dist/config/schema/start-work.d.ts +0 -5
- package/dist/features/builtin-commands/templates/start-work.d.ts +0 -1
- package/dist/features/builtin-skills/index.d.ts +0 -1
- package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
- package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
- package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
- package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
- package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
- package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
- package/dist/features/builtin-skills/skills/index.d.ts +0 -1
- package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
- package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
- package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
- package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
- package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
- package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
- package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
- package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
- package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
- package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
- package/dist/features/builtin-skills/skills.d.ts +0 -1
- package/dist/features/builtin-skills/types.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
- package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
- package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
- package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
- package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
- package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
- package/dist/hooks/start-work/start-work-hook.d.ts +0 -23
- package/dist/mcp/codegraph.d.ts +0 -16
- package/dist/plugin/chat-message/start-work-message.d.ts +0 -5
- package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
- package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
- package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
- package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
- package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
- package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
- package/dist/shared/prompt-async-gate/types.d.ts +0 -1
- package/dist/skills/data-scientist/references/common-scenarios.md +0 -176
- package/dist/skills/data-scientist/references/execution-templates.md +0 -197
- package/dist/skills/data-scientist/references/integration-patterns.md +0 -153
- package/dist/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/dist/tools/call-omo-agent/background-agent-executor.d.ts +0 -5
- package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
- package/dist/tools/delegate-task/anthropic-categories.d.ts +0 -2
- package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
- package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
- package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12136
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9734
- package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
- package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -309
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -80
- package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -21
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
- package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
- package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
- package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
- package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
- package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
- package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
- package/packages/omo-codex/plugin/components/start-work-continuation/LICENSE +0 -21
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +0 -28
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -471
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +0 -17
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +0 -17
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
- package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +0 -2
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
- package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
- package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
- package/packages/omo-codex/plugin/test/ulw-plan-scope-contract.test.mjs +0 -24
- package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
- /package/dist/hooks/{start-work → ulw-execute}/context-info-formatters.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/explicit-plan-context.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/notepad-scaffold.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/parse-user-request.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/plan-discovery-context.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/plan-selection.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/session-plan-affinity.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/work-initializer.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/worktree-block.d.ts +0 -0
- /package/dist/hooks/{start-work → ulw-execute}/worktree-detector.d.ts +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/.gitattributes +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/CHANGELOG.md +0 -0
- /package/packages/omo-codex/plugin/components/{codegraph → ulw-execute-continuation}/LICENSE +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/plan-checklist.ts +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-completed.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-mixed-platforms.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-single-codex-work.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-all-done.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-scaffold.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-nested-checkboxes.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-unchecked.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.build.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/vitest.config.ts +0 -0
|
@@ -1,21 +1,129 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import { existsSync as
|
|
3
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
4
|
+
import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
|
|
5
5
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
6
6
|
import { resolve as resolve3 } from "node:path";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// components/ulw-loop/src/checkpoint-reconciliation.ts
|
|
9
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
10
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
// components/ulw-loop/src/codex-goal-snapshot.ts
|
|
9
13
|
import { existsSync } from "node:fs";
|
|
10
14
|
import { readFile } from "node:fs/promises";
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
class CodexGoalSnapshotError extends Error {
|
|
18
|
+
}
|
|
19
|
+
function safeObject(value) {
|
|
20
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21
|
+
}
|
|
22
|
+
function safeString(value) {
|
|
23
|
+
return typeof value === "string" ? value.trim() : "";
|
|
24
|
+
}
|
|
25
|
+
function normalizeStatus(value) {
|
|
26
|
+
const status = safeString(value).toLowerCase();
|
|
27
|
+
if (status === "complete" || status === "completed" || status === "done")
|
|
28
|
+
return "complete";
|
|
29
|
+
if (status === "cancelled" || status === "canceled")
|
|
30
|
+
return "cancelled";
|
|
31
|
+
if (status === "failed" || status === "failure")
|
|
32
|
+
return "failed";
|
|
33
|
+
if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
|
|
34
|
+
return "active";
|
|
35
|
+
return "unknown";
|
|
36
|
+
}
|
|
37
|
+
function normalizeObjective(value) {
|
|
38
|
+
return value.replace(/\s+/g, " ").trim();
|
|
39
|
+
}
|
|
40
|
+
function parseCodexGoalSnapshot(value) {
|
|
41
|
+
const root = safeObject(value);
|
|
42
|
+
const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
|
|
43
|
+
if (goalValue === null || goalValue === undefined || goalValue === false) {
|
|
44
|
+
return { available: false, raw: value };
|
|
45
|
+
}
|
|
46
|
+
const goal = safeObject(goalValue);
|
|
47
|
+
const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
|
|
48
|
+
const status = normalizeStatus(goal["status"] ?? root["status"]);
|
|
49
|
+
return {
|
|
50
|
+
available: Boolean(objective || status !== "unknown"),
|
|
51
|
+
...objective ? { objective } : {},
|
|
52
|
+
status,
|
|
53
|
+
raw: value
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
|
|
57
|
+
if (!raw?.trim())
|
|
58
|
+
return null;
|
|
59
|
+
const trimmed = raw.trim();
|
|
60
|
+
try {
|
|
61
|
+
return parseCodexGoalSnapshot(JSON.parse(trimmed));
|
|
62
|
+
} catch {
|
|
63
|
+
const path = resolve(cwd, trimmed);
|
|
64
|
+
if (!existsSync(path)) {
|
|
65
|
+
throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
75
|
+
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
76
|
+
const errors = [];
|
|
77
|
+
const warnings = [];
|
|
78
|
+
if (!effectiveSnapshot.available) {
|
|
79
|
+
const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
|
|
80
|
+
if (options.requireSnapshot)
|
|
81
|
+
errors.push(message);
|
|
82
|
+
else
|
|
83
|
+
warnings.push(message);
|
|
84
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
85
|
+
}
|
|
86
|
+
const expected = normalizeObjective(options.expectedObjective);
|
|
87
|
+
const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))].filter(Boolean));
|
|
88
|
+
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
89
|
+
if (!actual) {
|
|
90
|
+
errors.push("Codex goal snapshot is missing objective text.");
|
|
91
|
+
} else if (!accepted.has(actual)) {
|
|
92
|
+
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
|
|
93
|
+
}
|
|
94
|
+
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
|
|
95
|
+
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
96
|
+
if (!allowed.includes(actualStatus)) {
|
|
97
|
+
errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
|
|
98
|
+
}
|
|
99
|
+
if (options.requireComplete && actualStatus !== "complete") {
|
|
100
|
+
errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
|
|
101
|
+
}
|
|
102
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
103
|
+
}
|
|
104
|
+
function formatCodexGoalReconciliation(reconciliation) {
|
|
105
|
+
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
106
|
+
return parts.join(" ");
|
|
107
|
+
}
|
|
108
|
+
function codexGoalMismatchRecovery(expectedObjective, snapshot) {
|
|
109
|
+
const receivedObjective = snapshot?.objective ?? "";
|
|
110
|
+
const message = [
|
|
111
|
+
"Recovery: the Codex goal objective must equal the plan's codexObjective exactly — copy the expected value below into update_goal and re-run get_goal.",
|
|
112
|
+
`expected codexObjective: ${expectedObjective}`,
|
|
113
|
+
`received objective: ${receivedObjective || "(none)"}`
|
|
114
|
+
].join(`
|
|
115
|
+
`);
|
|
116
|
+
return { message, details: { expectedObjective, receivedObjective } };
|
|
117
|
+
}
|
|
11
118
|
|
|
12
|
-
//
|
|
119
|
+
// components/ulw-loop/src/paths.ts
|
|
13
120
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
14
|
-
//
|
|
121
|
+
// components/ulw-loop/src/constants.ts
|
|
15
122
|
var ULW_LOOP_DIR = ".omo/ulw-loop";
|
|
16
123
|
var ULW_LOOP_BRIEF = "brief.md";
|
|
17
124
|
var ULW_LOOP_GOALS = "goals.json";
|
|
18
125
|
var ULW_LOOP_LEDGER = "ledger.jsonl";
|
|
126
|
+
var ULW_LOOP_STATE_LOCK = ".state.lock";
|
|
19
127
|
var ULW_LOOP_STEERING_MUTATION_KINDS = [
|
|
20
128
|
"add_subgoal",
|
|
21
129
|
"split_subgoal",
|
|
@@ -31,10 +139,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
|
|
|
31
139
|
"regression",
|
|
32
140
|
"adversarial"
|
|
33
141
|
];
|
|
34
|
-
//
|
|
142
|
+
// components/ulw-loop/src/runtime.ts
|
|
35
143
|
class UlwLoopError extends Error {
|
|
36
|
-
code;
|
|
37
|
-
details;
|
|
38
144
|
constructor(message, code, opts) {
|
|
39
145
|
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
|
|
40
146
|
this.name = "UlwLoopError";
|
|
@@ -47,8 +153,8 @@ class UlwLoopError extends Error {
|
|
|
47
153
|
function iso() {
|
|
48
154
|
return new Date().toISOString();
|
|
49
155
|
}
|
|
50
|
-
//
|
|
51
|
-
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"];
|
|
156
|
+
// components/ulw-loop/src/paths.ts
|
|
157
|
+
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
|
|
52
158
|
function normalizeUlwLoopSessionId(sessionId) {
|
|
53
159
|
const trimmed = sessionId?.trim();
|
|
54
160
|
if (!trimmed)
|
|
@@ -90,6 +196,9 @@ function ulwLoopGoalsPath(repoRoot, scope) {
|
|
|
90
196
|
function ulwLoopLedgerPath(repoRoot, scope) {
|
|
91
197
|
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_LEDGER);
|
|
92
198
|
}
|
|
199
|
+
function ulwLoopStateLockPath(repoRoot, scope) {
|
|
200
|
+
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_STATE_LOCK);
|
|
201
|
+
}
|
|
93
202
|
function repoRelative(absolutePath, repoRoot) {
|
|
94
203
|
const slashPrefix = `${repoRoot}/`;
|
|
95
204
|
const backslashPrefix = `${repoRoot}\\`;
|
|
@@ -100,7 +209,10 @@ function repoRelative(absolutePath, repoRoot) {
|
|
|
100
209
|
return absolutePath.split("\\").join("/");
|
|
101
210
|
}
|
|
102
211
|
function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
|
|
103
|
-
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId)
|
|
212
|
+
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
213
|
+
if (sessionId === null) {
|
|
214
|
+
throw new UlwLoopError(`Evidence for ${goalId} attempt ${attempt} needs a session scope; pass --session-id <id> so the attempt directory lives under .omo/evidence/ulw/<id>/.`, "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { goalId, attempt } });
|
|
215
|
+
}
|
|
104
216
|
return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
|
|
105
217
|
}
|
|
106
218
|
var PLATFORM_PATH_API = { relative, isAbsolute, sep };
|
|
@@ -113,7 +225,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
|
|
|
113
225
|
return !pathApi.isAbsolute(relativePath);
|
|
114
226
|
}
|
|
115
227
|
|
|
116
|
-
//
|
|
228
|
+
// components/ulw-loop/src/goal-status.ts
|
|
117
229
|
var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
|
|
118
230
|
function aggregateCodexObjectiveForScope(scope) {
|
|
119
231
|
return `Complete the durable ulw-loop plan in ${ulwLoopGoalsRelativePath(scope)}, including later accepted/appended stories, under the original brief constraints; use ${ulwLoopLedgerRelativePath(scope)} as the audit trail.`;
|
|
@@ -196,8 +308,15 @@ function hasEssentialCriteriaPass(goal) {
|
|
|
196
308
|
return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
|
|
197
309
|
}
|
|
198
310
|
|
|
199
|
-
//
|
|
200
|
-
function
|
|
311
|
+
// components/ulw-loop/src/checkpoint-reconciliation.ts
|
|
312
|
+
function codexSnapshotMismatchError(input) {
|
|
313
|
+
const { reconciliation, snapshot, taskScopedHint } = input;
|
|
314
|
+
const hint = taskScopedHint?.aggregate === true && snapshot?.status === "complete" && snapshot.objective !== undefined ? buildTaskScopedAggregateReconciliationHint(taskScopedHint.goal, taskScopedHint.final) : "";
|
|
315
|
+
const recovery = codexGoalMismatchRecovery(input.expectedObjective, snapshot);
|
|
316
|
+
return new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${hint}
|
|
317
|
+
${recovery.message}`, "ulw_loop_codex_snapshot_mismatch", { details: recovery.details });
|
|
318
|
+
}
|
|
319
|
+
function normalizeObjective2(value) {
|
|
201
320
|
return value.replace(/\s+/g, " ").trim();
|
|
202
321
|
}
|
|
203
322
|
function textMentionsUlwLoopPlanArtifact(value) {
|
|
@@ -214,13 +333,13 @@ function textHasCompletionValidationEvidence(value) {
|
|
|
214
333
|
return done && verified;
|
|
215
334
|
}
|
|
216
335
|
async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope) {
|
|
217
|
-
const actual =
|
|
336
|
+
const actual = normalizeObjective2(snapshotObjective).toLowerCase();
|
|
218
337
|
if (textMentionsUlwLoopPlanArtifact(actual))
|
|
219
338
|
return true;
|
|
220
|
-
if (actual.length < 24 || !
|
|
339
|
+
if (actual.length < 24 || !existsSync2(ulwLoopBriefPath(repoRoot, scope)))
|
|
221
340
|
return false;
|
|
222
341
|
try {
|
|
223
|
-
const brief =
|
|
342
|
+
const brief = normalizeObjective2(await readFile2(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
|
|
224
343
|
return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
|
|
225
344
|
} catch (error) {
|
|
226
345
|
if (error instanceof Error)
|
|
@@ -267,111 +386,280 @@ function buildTaskScopedAggregateReconciliationHint(goal, final) {
|
|
|
267
386
|
return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
|
|
268
387
|
}
|
|
269
388
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
389
|
+
// components/ulw-loop/src/checkpoint-codex-validation.ts
|
|
390
|
+
function normalizeObjective3(value) {
|
|
391
|
+
return value.replace(/\s+/g, " ").trim();
|
|
392
|
+
}
|
|
393
|
+
async function validateCheckpointCodexGoal(input) {
|
|
394
|
+
const aggregate = codexGoalMode(input.plan) === "aggregate";
|
|
395
|
+
const final = isFinalRunCompletionCandidate(input.plan, input.goal);
|
|
396
|
+
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
397
|
+
const expectedObjective = expectedCodexObjective(input.plan, input.goal);
|
|
398
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
399
|
+
expectedObjective,
|
|
400
|
+
...aggregate ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {},
|
|
401
|
+
allowedStatuses: aggregate ? final ? ["complete"] : ["active"] : ["complete"],
|
|
402
|
+
requireSnapshot: true,
|
|
403
|
+
requireComplete: !aggregate || final
|
|
404
|
+
});
|
|
405
|
+
if (reconciliation.ok)
|
|
406
|
+
return reconciliation.snapshot.raw;
|
|
407
|
+
const objective = snapshot?.objective;
|
|
408
|
+
const mismatchedTaskObjective = snapshot?.available === true && objective !== undefined && normalizeObjective3(objective) !== normalizeObjective3(expectedObjective);
|
|
409
|
+
const completedTaskScoped = mismatchedTaskObjective && snapshot.status === "complete" && await canReconcileCompletedTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
|
|
410
|
+
const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
|
|
411
|
+
if (completedTaskScoped || activeFinalTaskScoped)
|
|
412
|
+
return reconciliation.snapshot.raw;
|
|
413
|
+
throw codexSnapshotMismatchError({
|
|
414
|
+
reconciliation,
|
|
415
|
+
snapshot,
|
|
416
|
+
expectedObjective,
|
|
417
|
+
taskScopedHint: { goal: input.goal, aggregate, final }
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
function combineCheckpointValidationErrors(codexError, gateError) {
|
|
421
|
+
return new UlwLoopError(`${codexError.message}
|
|
422
|
+
${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
423
|
+
details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
|
|
424
|
+
});
|
|
425
|
+
}
|
|
274
426
|
|
|
275
|
-
|
|
427
|
+
// components/ulw-loop/src/plan-io.ts
|
|
428
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
429
|
+
import { createReadStream, readdirSync } from "node:fs";
|
|
430
|
+
import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
|
|
431
|
+
import { createInterface } from "node:readline";
|
|
432
|
+
|
|
433
|
+
// components/ulw-loop/src/plan-missing-recovery.ts
|
|
434
|
+
var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
|
|
435
|
+
function planMissingRecovery(existingSessionIds) {
|
|
436
|
+
const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
|
|
437
|
+
if (existingSessionIds.length === 0)
|
|
438
|
+
return { message: lines.join(`
|
|
439
|
+
`) };
|
|
440
|
+
lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
|
|
441
|
+
return { message: lines.join(`
|
|
442
|
+
`), details: { existingSessionIds } };
|
|
443
|
+
}
|
|
444
|
+
function sessionScopeRequiredMessage(flag, existingSessionIds) {
|
|
445
|
+
const lines = [
|
|
446
|
+
"No ulw-loop session scope: neither the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID) nor the flag names this run, and the shared .omo/ulw-loop root is never used implicitly because every session in this directory would read and overwrite it.",
|
|
447
|
+
`Recovery: pass the scope explicitly: \`${flag} <id>\` (subprocess, eval, and hook contexts do not inherit the session env).`
|
|
448
|
+
];
|
|
449
|
+
if (existingSessionIds.length > 0) {
|
|
450
|
+
lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}.`);
|
|
451
|
+
}
|
|
452
|
+
return lines.join(`
|
|
453
|
+
`);
|
|
276
454
|
}
|
|
277
|
-
function
|
|
278
|
-
return
|
|
455
|
+
function sessionIdRequiredMessage(flag) {
|
|
456
|
+
return [
|
|
457
|
+
`${flag} requires a non-empty value.`,
|
|
458
|
+
"Recovery: subprocess, eval, and hook contexts do not inherit the ulw-loop session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID),",
|
|
459
|
+
`so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
|
|
460
|
+
].join(`
|
|
461
|
+
`);
|
|
279
462
|
}
|
|
280
|
-
|
|
281
|
-
|
|
463
|
+
|
|
464
|
+
// components/ulw-loop/src/state-lock.ts
|
|
465
|
+
import { randomUUID } from "node:crypto";
|
|
466
|
+
import { closeSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
467
|
+
import { dirname } from "node:path";
|
|
468
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
469
|
+
var ULW_LOOP_LOCK_TIMEOUT_CODE = "ULW_LOOP_LOCK_TIMEOUT";
|
|
470
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
471
|
+
var DEFAULT_STALE_MS = 60000;
|
|
472
|
+
var MIN_DELAY_MS = 5;
|
|
473
|
+
var MAX_DELAY_MS = 100;
|
|
474
|
+
var SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
|
|
475
|
+
async function withStateLock(lockPath, fn, options = {}) {
|
|
476
|
+
const token = await acquireAsync(lockPath, options);
|
|
477
|
+
try {
|
|
478
|
+
return await fn();
|
|
479
|
+
} finally {
|
|
480
|
+
release(lockPath, token);
|
|
481
|
+
}
|
|
282
482
|
}
|
|
283
|
-
function
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return "failed";
|
|
291
|
-
if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
|
|
292
|
-
return "active";
|
|
293
|
-
return "unknown";
|
|
483
|
+
function withStateLockSync(lockPath, fn, options = {}) {
|
|
484
|
+
const token = acquireSync(lockPath, options);
|
|
485
|
+
try {
|
|
486
|
+
return fn();
|
|
487
|
+
} finally {
|
|
488
|
+
release(lockPath, token);
|
|
489
|
+
}
|
|
294
490
|
}
|
|
295
|
-
function
|
|
296
|
-
return
|
|
491
|
+
function isStateLockTimeout(error) {
|
|
492
|
+
return error instanceof UlwLoopError && error.code === ULW_LOOP_LOCK_TIMEOUT_CODE;
|
|
493
|
+
}
|
|
494
|
+
async function acquireAsync(lockPath, options) {
|
|
495
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
496
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
497
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
498
|
+
for (let attempt = 0;; ) {
|
|
499
|
+
const outcome = attemptOnce(lockPath, staleMs);
|
|
500
|
+
if (outcome.kind === "acquired")
|
|
501
|
+
return outcome.token;
|
|
502
|
+
if (outcome.kind === "retry")
|
|
503
|
+
continue;
|
|
504
|
+
if (Date.now() >= deadline)
|
|
505
|
+
throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
506
|
+
await sleep(backoffMs(attempt));
|
|
507
|
+
attempt += 1;
|
|
508
|
+
}
|
|
297
509
|
}
|
|
298
|
-
function
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
510
|
+
function acquireSync(lockPath, options) {
|
|
511
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
512
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
513
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
514
|
+
for (let attempt = 0;; ) {
|
|
515
|
+
const outcome = attemptOnce(lockPath, staleMs);
|
|
516
|
+
if (outcome.kind === "acquired")
|
|
517
|
+
return outcome.token;
|
|
518
|
+
if (outcome.kind === "retry")
|
|
519
|
+
continue;
|
|
520
|
+
if (Date.now() >= deadline)
|
|
521
|
+
throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
522
|
+
Atomics.wait(SLEEP_CELL, 0, 0, backoffMs(attempt));
|
|
523
|
+
attempt += 1;
|
|
303
524
|
}
|
|
304
|
-
const goal = safeObject(goalValue);
|
|
305
|
-
const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? root["objective"]);
|
|
306
|
-
const status = normalizeStatus(goal["status"] ?? root["status"]);
|
|
307
|
-
return {
|
|
308
|
-
available: Boolean(objective || status !== "unknown"),
|
|
309
|
-
...objective ? { objective } : {},
|
|
310
|
-
status,
|
|
311
|
-
raw: value
|
|
312
|
-
};
|
|
313
525
|
}
|
|
314
|
-
|
|
315
|
-
if (!raw?.trim())
|
|
316
|
-
return null;
|
|
317
|
-
const trimmed = raw.trim();
|
|
526
|
+
function attemptOnce(lockPath, staleMs) {
|
|
318
527
|
try {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
528
|
+
const token = tryCreate(lockPath);
|
|
529
|
+
if (token !== null)
|
|
530
|
+
return { kind: "acquired", token };
|
|
531
|
+
const snapshot = readSnapshot(lockPath);
|
|
532
|
+
if (snapshot === null)
|
|
533
|
+
return { kind: "retry" };
|
|
534
|
+
if (isStale(snapshot, staleMs) && reclaim(lockPath, snapshot.raw))
|
|
535
|
+
return { kind: "retry" };
|
|
536
|
+
return { kind: "wait" };
|
|
537
|
+
} catch (error) {
|
|
538
|
+
if (hasCode(error, "EINTR"))
|
|
539
|
+
return { kind: "wait" };
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function tryCreate(lockPath) {
|
|
544
|
+
let fd;
|
|
545
|
+
try {
|
|
546
|
+
fd = openSync(lockPath, "wx");
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (hasCode(error, "EEXIST"))
|
|
549
|
+
return null;
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
const record = { pid: process.pid, createdAt: new Date().toISOString(), token: randomUUID() };
|
|
553
|
+
try {
|
|
554
|
+
writeSync(fd, JSON.stringify(record));
|
|
555
|
+
} catch (error) {
|
|
556
|
+
closeSync(fd);
|
|
325
557
|
try {
|
|
326
|
-
|
|
327
|
-
} catch
|
|
328
|
-
|
|
329
|
-
}
|
|
558
|
+
unlinkSync(lockPath);
|
|
559
|
+
} catch {}
|
|
560
|
+
throw error;
|
|
330
561
|
}
|
|
562
|
+
closeSync(fd);
|
|
563
|
+
return record.token;
|
|
331
564
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
warnings.push(message);
|
|
342
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
565
|
+
function readSnapshot(lockPath) {
|
|
566
|
+
try {
|
|
567
|
+
const raw = readFileSync(lockPath, "utf8");
|
|
568
|
+
const ageMs = Date.now() - statSync(lockPath).mtimeMs;
|
|
569
|
+
return { raw, record: parseRecord(raw), ageMs };
|
|
570
|
+
} catch (error) {
|
|
571
|
+
if (hasCode(error, "ENOENT"))
|
|
572
|
+
return null;
|
|
573
|
+
throw error;
|
|
343
574
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
575
|
+
}
|
|
576
|
+
function parseRecord(raw) {
|
|
577
|
+
try {
|
|
578
|
+
const parsed = JSON.parse(raw);
|
|
579
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
580
|
+
return null;
|
|
581
|
+
const record = parsed;
|
|
582
|
+
const pid = record["pid"];
|
|
583
|
+
const createdAt = record["createdAt"];
|
|
584
|
+
const token = record["token"];
|
|
585
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof createdAt !== "string")
|
|
586
|
+
return null;
|
|
587
|
+
if (typeof token !== "string" || token.length === 0)
|
|
588
|
+
return null;
|
|
589
|
+
return { pid, createdAt, token };
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (error instanceof SyntaxError)
|
|
592
|
+
return null;
|
|
593
|
+
throw error;
|
|
351
594
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
595
|
+
}
|
|
596
|
+
function isStale(snapshot, staleMs) {
|
|
597
|
+
if (snapshot.record === null)
|
|
598
|
+
return snapshot.ageMs > staleMs;
|
|
599
|
+
return !isProcessAlive(snapshot.record.pid);
|
|
600
|
+
}
|
|
601
|
+
function isProcessAlive(pid) {
|
|
602
|
+
try {
|
|
603
|
+
process.kill(pid, 0);
|
|
604
|
+
return true;
|
|
605
|
+
} catch (error) {
|
|
606
|
+
if (hasCode(error, "ESRCH"))
|
|
607
|
+
return false;
|
|
608
|
+
if (hasCode(error, "EPERM"))
|
|
609
|
+
return true;
|
|
610
|
+
throw error;
|
|
356
611
|
}
|
|
357
|
-
|
|
358
|
-
|
|
612
|
+
}
|
|
613
|
+
function reclaim(lockPath, expectedRaw) {
|
|
614
|
+
const current = readSnapshot(lockPath);
|
|
615
|
+
if (current === null)
|
|
616
|
+
return true;
|
|
617
|
+
if (current.raw !== expectedRaw)
|
|
618
|
+
return false;
|
|
619
|
+
try {
|
|
620
|
+
unlinkSync(lockPath);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (!hasCode(error, "ENOENT"))
|
|
623
|
+
throw error;
|
|
359
624
|
}
|
|
360
|
-
return
|
|
625
|
+
return true;
|
|
361
626
|
}
|
|
362
|
-
function
|
|
363
|
-
const
|
|
364
|
-
|
|
627
|
+
function release(lockPath, token) {
|
|
628
|
+
const current = readSnapshot(lockPath);
|
|
629
|
+
if (current === null || current.record?.token !== token)
|
|
630
|
+
return;
|
|
631
|
+
try {
|
|
632
|
+
unlinkSync(lockPath);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
if (!hasCode(error, "ENOENT"))
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function backoffMs(attempt) {
|
|
639
|
+
const exponential = Math.min(MAX_DELAY_MS, MIN_DELAY_MS * 2 ** attempt);
|
|
640
|
+
return exponential + Math.random() * MIN_DELAY_MS;
|
|
641
|
+
}
|
|
642
|
+
function lockTimeout(lockPath, timeoutMs) {
|
|
643
|
+
const holder = readSnapshot(lockPath)?.record;
|
|
644
|
+
const owner = holder === undefined || holder === null ? "another process" : `pid ${holder.pid}`;
|
|
645
|
+
return new UlwLoopError(`ulw-loop state lock ${lockPath} is held by ${owner} for more than ${timeoutMs}ms; retry once that process finishes, or delete the lock file if that process is gone.`, ULW_LOOP_LOCK_TIMEOUT_CODE, {
|
|
646
|
+
details: {
|
|
647
|
+
lockPath,
|
|
648
|
+
timeoutMs,
|
|
649
|
+
...holder === undefined || holder === null ? {} : { holderPid: holder.pid }
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
function hasCode(error, code) {
|
|
654
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
365
655
|
}
|
|
366
656
|
|
|
367
|
-
//
|
|
368
|
-
import { createReadStream } from "node:fs";
|
|
369
|
-
import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
|
|
370
|
-
import { createInterface } from "node:readline";
|
|
657
|
+
// components/ulw-loop/src/plan-io.ts
|
|
371
658
|
var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
|
|
372
659
|
var LEGACY_OBJECTIVE = `Complete all ulw-loop stories listed in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}. Use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the durable audit trail.`;
|
|
373
660
|
var locks = new Map;
|
|
374
|
-
|
|
661
|
+
var heldLocks = new AsyncLocalStorage;
|
|
662
|
+
function hasCode2(error, code) {
|
|
375
663
|
return error instanceof Error && "code" in error && error.code === code;
|
|
376
664
|
}
|
|
377
665
|
function isLegacyEnumeratedAggregateObjective(objective) {
|
|
@@ -386,8 +674,10 @@ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
|
386
674
|
if (fn === undefined)
|
|
387
675
|
throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
|
|
388
676
|
const lockKey = `${repoRoot}\x00${ulwLoopRelativeDir(scope)}`;
|
|
677
|
+
const lockPath = ulwLoopStateLockPath(repoRoot, scope);
|
|
678
|
+
const locked = () => withStateLock(lockPath, () => heldLocks.run(new Set([...heldLocks.getStore() ?? [], lockKey]), fn));
|
|
389
679
|
const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
|
|
390
|
-
const run = prior.then(
|
|
680
|
+
const run = prior.then(locked, locked);
|
|
391
681
|
const gate = run.then(() => {
|
|
392
682
|
return;
|
|
393
683
|
}, () => {
|
|
@@ -406,9 +696,11 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
406
696
|
try {
|
|
407
697
|
raw = await readFile3(path, "utf8");
|
|
408
698
|
} catch (error) {
|
|
409
|
-
if (!
|
|
699
|
+
if (!hasCode2(error, "ENOENT"))
|
|
410
700
|
throw error;
|
|
411
|
-
|
|
701
|
+
const recovery = planMissingRecovery(listUlwLoopSessionIds(repoRoot));
|
|
702
|
+
throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.
|
|
703
|
+
${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...recovery.details === undefined ? {} : { details: recovery.details } });
|
|
412
704
|
}
|
|
413
705
|
const parsed = JSON.parse(raw);
|
|
414
706
|
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
|
@@ -416,6 +708,9 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
416
708
|
}
|
|
417
709
|
const previousObjective = parsed.codexObjective;
|
|
418
710
|
if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && isLegacyEnumeratedAggregateObjective(previousObjective)) {
|
|
711
|
+
if (!(heldLocks.getStore()?.has(`${repoRoot}\x00${ulwLoopRelativeDir(scope)}`) ?? false)) {
|
|
712
|
+
throw new UlwLoopError(`The ulw-loop plan at ${repoRelative(path, repoRoot)} carries a legacy enumerated aggregate objective that must be migrated before reads continue. Run any state-mutating ulw-loop command once (e.g. \`record-evidence\`, \`steer\`, \`checkpoint\`) to migrate it under the state lock, then retry.`, "ULW_LOOP_MIGRATION_REQUIRED");
|
|
713
|
+
}
|
|
419
714
|
const now = iso();
|
|
420
715
|
parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
|
|
421
716
|
parsed.codexObjectiveAliases = [...new Set([...parsed.codexObjectiveAliases ?? [], previousObjective])];
|
|
@@ -431,6 +726,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
431
726
|
}
|
|
432
727
|
return parsed;
|
|
433
728
|
}
|
|
729
|
+
function listUlwLoopSessionIds(repoRoot) {
|
|
730
|
+
try {
|
|
731
|
+
return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
732
|
+
} catch {
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
434
736
|
async function writePlan(repoRoot, plan, scope) {
|
|
435
737
|
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
436
738
|
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
@@ -459,7 +761,7 @@ async function* ledgerLines(repoRoot, scope) {
|
|
|
459
761
|
yield line;
|
|
460
762
|
}
|
|
461
763
|
} catch (error) {
|
|
462
|
-
if (!
|
|
764
|
+
if (!hasCode2(error, "ENOENT"))
|
|
463
765
|
throw error;
|
|
464
766
|
} finally {
|
|
465
767
|
lines.close();
|
|
@@ -482,7 +784,7 @@ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
|
|
|
482
784
|
return;
|
|
483
785
|
}
|
|
484
786
|
|
|
485
|
-
//
|
|
787
|
+
// components/ulw-loop/src/evidence.ts
|
|
486
788
|
function ulwLoopFail(message, code, details) {
|
|
487
789
|
throw new UlwLoopError(message, code, { details });
|
|
488
790
|
}
|
|
@@ -588,57 +890,216 @@ function requireEssentialCriteriaPass(goal) {
|
|
|
588
890
|
});
|
|
589
891
|
}
|
|
590
892
|
|
|
591
|
-
//
|
|
893
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
592
894
|
import { resolve as resolve2 } from "node:path";
|
|
593
895
|
|
|
594
|
-
//
|
|
595
|
-
var PLACEHOLDER_PATTERN = /^(
|
|
896
|
+
// components/ulw-loop/src/quality-gate-fields.ts
|
|
897
|
+
var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
898
|
+
var activeCollector;
|
|
899
|
+
function withQualityGateCollector(operation) {
|
|
900
|
+
const previous = activeCollector;
|
|
901
|
+
const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
|
|
902
|
+
activeCollector = collector;
|
|
903
|
+
try {
|
|
904
|
+
const result = operation();
|
|
905
|
+
if (collector.defects.length === 0)
|
|
906
|
+
return result;
|
|
907
|
+
throwQualityGateDefects(collector.defects);
|
|
908
|
+
} finally {
|
|
909
|
+
activeCollector = previous;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function throwQualityGateDefects(defects) {
|
|
913
|
+
const uniqueDefects = [
|
|
914
|
+
...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
|
|
915
|
+
];
|
|
916
|
+
const fields = uniqueDefects.slice(0, 25);
|
|
917
|
+
const truncated = uniqueDefects.length > fields.length;
|
|
918
|
+
const message = [
|
|
919
|
+
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
920
|
+
...fields.map((item) => `- ${item.field}: ${item.message}`)
|
|
921
|
+
].join(`
|
|
922
|
+
`);
|
|
923
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
924
|
+
details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
|
|
925
|
+
});
|
|
926
|
+
}
|
|
596
927
|
function invalid(message, field) {
|
|
597
|
-
|
|
928
|
+
if (activeCollector === undefined)
|
|
929
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
|
|
930
|
+
activeCollector.defects.push({ field, message });
|
|
931
|
+
activeCollector.poisonedFields.add(field);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
function isPoisoned(field) {
|
|
935
|
+
return activeCollector?.poisonedFields.has(field) ?? false;
|
|
936
|
+
}
|
|
937
|
+
function markPoisonedArtifactKind(id) {
|
|
938
|
+
activeCollector?.poisonedArtifactKinds.add(id);
|
|
939
|
+
}
|
|
940
|
+
function isPoisonedArtifactKind(id) {
|
|
941
|
+
return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
|
|
598
942
|
}
|
|
599
943
|
function isRecord(value) {
|
|
600
944
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
601
945
|
}
|
|
602
946
|
function section(value, field) {
|
|
603
|
-
|
|
947
|
+
if (isRecord(value))
|
|
948
|
+
return value;
|
|
949
|
+
invalid(`Final quality gate is missing ${field} evidence (accepted input: a bare object with manualQa/gateReview/iteration/criteriaCoverage and optional codeReview, or the same object under a top-level "qualityGate" key).`, field);
|
|
950
|
+
return {};
|
|
604
951
|
}
|
|
605
952
|
function textField(value, field) {
|
|
606
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
953
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
607
954
|
invalid(`Final quality gate requires non-empty ${field}.`, field);
|
|
955
|
+
return "";
|
|
956
|
+
}
|
|
608
957
|
const trimmed = value.trim();
|
|
609
958
|
if (PLACEHOLDER_PATTERN.test(trimmed))
|
|
610
959
|
invalid(`Final quality gate rejects placeholder ${field}.`, field);
|
|
611
960
|
return trimmed;
|
|
612
961
|
}
|
|
613
|
-
function numberField(value, field) {
|
|
614
|
-
|
|
962
|
+
function numberField(value, field) {
|
|
963
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
964
|
+
return value;
|
|
965
|
+
invalid(`Final quality gate requires numeric ${field}.`, field);
|
|
966
|
+
return 0;
|
|
967
|
+
}
|
|
968
|
+
function stringArray(value, field) {
|
|
969
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
970
|
+
invalid(`Final quality gate requires ${field}.`, field);
|
|
971
|
+
return [];
|
|
972
|
+
}
|
|
973
|
+
return value.map((item) => textField(item, field));
|
|
974
|
+
}
|
|
975
|
+
function emptyBlockers(value, field) {
|
|
976
|
+
if (Array.isArray(value) && value.length === 0)
|
|
977
|
+
return [];
|
|
978
|
+
invalid(`${field} must be empty.`, field);
|
|
979
|
+
return [];
|
|
980
|
+
}
|
|
981
|
+
function literal(value, expected, field) {
|
|
982
|
+
if (value === expected)
|
|
983
|
+
return expected;
|
|
984
|
+
invalid(`${field} must be ${String(expected)}.`, field);
|
|
985
|
+
return expected;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
989
|
+
var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
|
|
990
|
+
var SUPPORTED_KINDS = [
|
|
991
|
+
"cli-transcript",
|
|
992
|
+
"log",
|
|
993
|
+
"screenshot",
|
|
994
|
+
"image",
|
|
995
|
+
"http-dump",
|
|
996
|
+
"data-diff"
|
|
997
|
+
];
|
|
998
|
+
var COMPATIBLE_KINDS = {
|
|
999
|
+
cli: ["cli-transcript", "log"],
|
|
1000
|
+
tmux: ["cli-transcript", "log"],
|
|
1001
|
+
http: ["http-dump"],
|
|
1002
|
+
browser: ["screenshot", "image"],
|
|
1003
|
+
gui: ["screenshot", "image"],
|
|
1004
|
+
data: ["data-diff"]
|
|
1005
|
+
};
|
|
1006
|
+
function isSupportedSurface(value) {
|
|
1007
|
+
return SUPPORTED_SURFACES.some((surface) => surface === value);
|
|
1008
|
+
}
|
|
1009
|
+
function isSupportedKind(value) {
|
|
1010
|
+
return SUPPORTED_KINDS.some((kind) => kind === value);
|
|
1011
|
+
}
|
|
1012
|
+
function surfaceField(value, field) {
|
|
1013
|
+
if (isSupportedSurface(value))
|
|
1014
|
+
return value;
|
|
1015
|
+
invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
|
|
1016
|
+
return "cli";
|
|
1017
|
+
}
|
|
1018
|
+
function kindField(value, field) {
|
|
1019
|
+
if (isSupportedKind(value))
|
|
1020
|
+
return value;
|
|
1021
|
+
invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
|
|
1022
|
+
return "log";
|
|
1023
|
+
}
|
|
1024
|
+
function compatibleKindsFor(surface) {
|
|
1025
|
+
return COMPATIBLE_KINDS[surface];
|
|
1026
|
+
}
|
|
1027
|
+
function artifactCompatible(surface, kind) {
|
|
1028
|
+
return compatibleKindsFor(surface).includes(kind);
|
|
1029
|
+
}
|
|
1030
|
+
function checkFile(path, field, opts) {
|
|
1031
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
1032
|
+
return;
|
|
1033
|
+
const absolute = resolve2(opts.repoRoot, path);
|
|
1034
|
+
if (!opts.fs.existsSync(absolute)) {
|
|
1035
|
+
invalid(`${field} must point to an existing artifact.`, field);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (opts.fs.statSync(absolute).size <= 0)
|
|
1039
|
+
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
1040
|
+
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve2(opts.repoRoot, opts.currentAttemptDir)))
|
|
1041
|
+
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
615
1042
|
}
|
|
616
|
-
function
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1043
|
+
function artifactMap(refs) {
|
|
1044
|
+
const byId = new Map;
|
|
1045
|
+
for (const ref of refs) {
|
|
1046
|
+
if (byId.has(ref.id))
|
|
1047
|
+
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
1048
|
+
byId.set(ref.id, ref);
|
|
1049
|
+
}
|
|
1050
|
+
return byId;
|
|
620
1051
|
}
|
|
621
|
-
function
|
|
622
|
-
if (Array.isArray(value)
|
|
1052
|
+
function parseArtifactRefs(value, opts) {
|
|
1053
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1054
|
+
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
623
1055
|
return [];
|
|
624
|
-
|
|
1056
|
+
}
|
|
1057
|
+
return value.flatMap((item, index) => {
|
|
1058
|
+
const prefix = `manualQa.artifactRefs[${index}]`;
|
|
1059
|
+
const ref = section(item, prefix);
|
|
1060
|
+
if (isPoisoned(prefix))
|
|
1061
|
+
return [];
|
|
1062
|
+
const pathField = `${prefix}.path`;
|
|
1063
|
+
const idField = `${prefix}.id`;
|
|
1064
|
+
const kindFieldName = `${prefix}.kind`;
|
|
1065
|
+
const descriptionField = `${prefix}.description`;
|
|
1066
|
+
const path = textField(ref["path"], pathField);
|
|
1067
|
+
const id = textField(ref["id"], idField);
|
|
1068
|
+
const kind = kindField(ref["kind"], kindFieldName);
|
|
1069
|
+
if (isPoisoned(kindFieldName))
|
|
1070
|
+
markPoisonedArtifactKind(id);
|
|
1071
|
+
const description = textField(ref["description"], descriptionField);
|
|
1072
|
+
checkFile(path, pathField, opts);
|
|
1073
|
+
return [{ id, kind, description, path }];
|
|
1074
|
+
});
|
|
625
1075
|
}
|
|
626
|
-
function
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1076
|
+
function referencedArtifacts(value, field, byId) {
|
|
1077
|
+
const ids = stringArray(value, field);
|
|
1078
|
+
if (isPoisoned(field))
|
|
1079
|
+
return [];
|
|
1080
|
+
return ids.flatMap((id) => {
|
|
1081
|
+
const artifact = byId.get(id);
|
|
1082
|
+
if (artifact === undefined) {
|
|
1083
|
+
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
1084
|
+
return [];
|
|
1085
|
+
}
|
|
1086
|
+
return [artifact];
|
|
1087
|
+
});
|
|
630
1088
|
}
|
|
631
1089
|
|
|
632
|
-
//
|
|
1090
|
+
// components/ulw-loop/src/quality-gate-verdicts.ts
|
|
633
1091
|
function passedVerdict(value, field) {
|
|
634
|
-
if (value === "not_applicable")
|
|
1092
|
+
if (value === "not_applicable") {
|
|
635
1093
|
invalid(`${field} must not be not_applicable.`, field);
|
|
1094
|
+
return "passed";
|
|
1095
|
+
}
|
|
636
1096
|
return literal(value, "passed", field);
|
|
637
1097
|
}
|
|
638
1098
|
function codeQualityStatusField(value, field) {
|
|
639
1099
|
if (value === "CLEAR" || value === "WATCH")
|
|
640
1100
|
return value;
|
|
641
1101
|
invalid(`${field} must be CLEAR or WATCH.`, field);
|
|
1102
|
+
return "CLEAR";
|
|
642
1103
|
}
|
|
643
1104
|
function adversarialVerdict(row, field) {
|
|
644
1105
|
const value = row["verdict"];
|
|
@@ -648,9 +1109,82 @@ function adversarialVerdict(row, field) {
|
|
|
648
1109
|
return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
|
|
649
1110
|
}
|
|
650
1111
|
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
1112
|
+
return { verdict: "passed" };
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// components/ulw-loop/src/surface.ts
|
|
1116
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
1117
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
1118
|
+
import { fileURLToPath } from "node:url";
|
|
1119
|
+
var REVIEWER_ROLES_BY_SURFACE = {
|
|
1120
|
+
lazycodex: {
|
|
1121
|
+
codeReview: "lazycodex-code-reviewer",
|
|
1122
|
+
manualQa: "lazycodex-qa-executor",
|
|
1123
|
+
gateReview: "lazycodex-gate-reviewer"
|
|
1124
|
+
},
|
|
1125
|
+
"omo-senpi": {
|
|
1126
|
+
codeReview: "omo-senpi-code-reviewer",
|
|
1127
|
+
manualQa: "omo-senpi-qa-executor",
|
|
1128
|
+
gateReview: "omo-senpi-gate-reviewer"
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
|
|
1132
|
+
var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
|
|
1133
|
+
lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
|
|
1134
|
+
"omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
|
|
1135
|
+
};
|
|
1136
|
+
var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
|
|
1137
|
+
lazycodex: ["codeReview"],
|
|
1138
|
+
"omo-senpi": []
|
|
1139
|
+
};
|
|
1140
|
+
var GATE_SECTION_BY_ACCEPTOR = {
|
|
1141
|
+
lazycodex: {
|
|
1142
|
+
codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
|
|
1143
|
+
manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
|
|
1144
|
+
gateReview: [
|
|
1145
|
+
REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
|
|
1146
|
+
"category:deep",
|
|
1147
|
+
"category:unspecified-high",
|
|
1148
|
+
"category:unspecified-low",
|
|
1149
|
+
"main-session"
|
|
1150
|
+
]
|
|
1151
|
+
},
|
|
1152
|
+
"omo-senpi": {
|
|
1153
|
+
manualQa: ["main-session"],
|
|
1154
|
+
gateReview: ["category:deep", "category:unspecified-high", "category:unspecified-low"]
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
function reviewerRolesFor(surface) {
|
|
1158
|
+
return REVIEWER_ROLES_BY_SURFACE[surface];
|
|
1159
|
+
}
|
|
1160
|
+
var SURFACE_MARKER_FILENAME = "surface.json";
|
|
1161
|
+
var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
|
|
1162
|
+
function parseSurface(value) {
|
|
1163
|
+
return value === "lazycodex" || value === "omo-senpi" ? value : null;
|
|
1164
|
+
}
|
|
1165
|
+
function resolveToolkitSurface(options) {
|
|
1166
|
+
const env = options?.env ?? process.env;
|
|
1167
|
+
const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
|
|
1168
|
+
if (fromEnv !== null)
|
|
1169
|
+
return fromEnv;
|
|
1170
|
+
const entryDir = options?.entryDir ?? dirname2(fileURLToPath(import.meta.url));
|
|
1171
|
+
const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
|
|
1172
|
+
return readMarkerSurface(markerPath) ?? "lazycodex";
|
|
1173
|
+
}
|
|
1174
|
+
function readMarkerSurface(markerPath) {
|
|
1175
|
+
try {
|
|
1176
|
+
if (!existsSync3(markerPath))
|
|
1177
|
+
return null;
|
|
1178
|
+
const parsed = JSON.parse(readFileSync2(markerPath, "utf8"));
|
|
1179
|
+
return parseSurface(parsed["surface"]);
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
if (error instanceof Error)
|
|
1182
|
+
return null;
|
|
1183
|
+
throw error;
|
|
1184
|
+
}
|
|
651
1185
|
}
|
|
652
1186
|
|
|
653
|
-
//
|
|
1187
|
+
// components/ulw-loop/src/quality-gate-blockers.ts
|
|
654
1188
|
var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
655
1189
|
var URL_PATTERN = /https?:\/\/\S+/g;
|
|
656
1190
|
var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
|
|
@@ -692,131 +1226,68 @@ function clearGoalBlockerFields(goal) {
|
|
|
692
1226
|
Reflect.deleteProperty(goal, key);
|
|
693
1227
|
}
|
|
694
1228
|
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const actual = textField(value, field);
|
|
703
|
-
if (actual !== expected)
|
|
704
|
-
invalid(`${field} must be ${expected}.`, field);
|
|
705
|
-
return expected;
|
|
706
|
-
}
|
|
707
|
-
function surfaceField(value, field) {
|
|
708
|
-
if (value === "cli" || value === "http" || value === "tmux" || value === "browser" || value === "gui" || value === "data")
|
|
709
|
-
return value;
|
|
710
|
-
invalid(`${field} must be a supported manual QA surface.`, field);
|
|
711
|
-
}
|
|
712
|
-
function kindField(value, field) {
|
|
713
|
-
if (value === "cli-transcript" || value === "log" || value === "screenshot" || value === "image" || value === "http-dump" || value === "data-diff")
|
|
714
|
-
return value;
|
|
715
|
-
invalid(`${field} must be a supported artifact kind.`, field);
|
|
716
|
-
}
|
|
717
|
-
function artifactCompatible(surface, kind) {
|
|
718
|
-
switch (surface) {
|
|
719
|
-
case "cli":
|
|
720
|
-
case "tmux":
|
|
721
|
-
return kind === "cli-transcript" || kind === "log";
|
|
722
|
-
case "http":
|
|
723
|
-
return kind === "http-dump";
|
|
724
|
-
case "browser":
|
|
725
|
-
case "gui":
|
|
726
|
-
return kind === "screenshot" || kind === "image";
|
|
727
|
-
case "data":
|
|
728
|
-
return kind === "data-diff";
|
|
729
|
-
default:
|
|
730
|
-
invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function checkFile(path, field, opts) {
|
|
734
|
-
if (opts === undefined)
|
|
735
|
-
return;
|
|
736
|
-
const absolute = resolve2(opts.repoRoot, path);
|
|
737
|
-
if (!opts.fs.existsSync(absolute))
|
|
738
|
-
invalid(`${field} must point to an existing artifact.`, field);
|
|
739
|
-
if (opts.fs.statSync(absolute).size <= 0)
|
|
740
|
-
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
741
|
-
if (opts.currentAttemptDir !== undefined) {
|
|
742
|
-
const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
|
|
743
|
-
if (!isWithinAttemptDir(absolute, attemptRoot))
|
|
744
|
-
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
function artifactMap(refs) {
|
|
748
|
-
const byId = new Map;
|
|
749
|
-
for (const ref of refs) {
|
|
750
|
-
if (byId.has(ref.id))
|
|
751
|
-
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
752
|
-
byId.set(ref.id, ref);
|
|
1229
|
+
// components/ulw-loop/src/quality-gate.ts
|
|
1230
|
+
function reviewerAcceptorField(value, surface, sectionName) {
|
|
1231
|
+
const field = `${sectionName}.by`;
|
|
1232
|
+
const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
|
|
1233
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1234
|
+
textField(value, field);
|
|
1235
|
+
return accepted?.[0] ?? "";
|
|
753
1236
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
759
|
-
return value.map((item, index) => {
|
|
760
|
-
const ref = section(item, `manualQa.artifactRefs[${index}]`);
|
|
761
|
-
const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
|
|
762
|
-
checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
|
|
763
|
-
return {
|
|
764
|
-
id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
|
|
765
|
-
kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
|
|
766
|
-
description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
|
|
767
|
-
path
|
|
768
|
-
};
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
function referencedArtifacts(value, field, byId) {
|
|
772
|
-
return stringArray(value, field).map((id) => {
|
|
773
|
-
const artifact = byId.get(id);
|
|
774
|
-
if (artifact === undefined)
|
|
775
|
-
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
776
|
-
return artifact;
|
|
777
|
-
});
|
|
1237
|
+
const actual = textField(value, field);
|
|
1238
|
+
if (accepted === undefined || !accepted.includes(actual))
|
|
1239
|
+
invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
|
|
1240
|
+
return actual;
|
|
778
1241
|
}
|
|
779
1242
|
function validateQualityGate(input, opts) {
|
|
780
|
-
|
|
781
|
-
|
|
1243
|
+
return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
|
|
1244
|
+
}
|
|
1245
|
+
function validateQualityGateUncollected(input, opts) {
|
|
1246
|
+
const surface = opts?.reviewerSurface ?? "lazycodex";
|
|
1247
|
+
const raw = input;
|
|
1248
|
+
const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
|
|
1249
|
+
for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
|
|
1250
|
+
section(gate[name], name);
|
|
1251
|
+
for (const name of Object.keys(gate)) {
|
|
1252
|
+
if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
|
|
1253
|
+
invalid("omo-senpi gate has no codeReview lane.", name);
|
|
1254
|
+
}
|
|
1255
|
+
if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
|
|
1256
|
+
invalid("omo-senpi gate has no codeReview lane.", "codeReview");
|
|
782
1257
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
783
1258
|
const gateReview = section(gate["gateReview"], "gateReview");
|
|
784
1259
|
const iteration = section(gate["iteration"], "iteration");
|
|
785
1260
|
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
|
|
1261
|
+
const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
|
|
1262
|
+
const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
|
|
1263
|
+
const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
|
|
1264
|
+
const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
|
|
1265
|
+
const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
|
|
786
1266
|
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
|
|
787
1267
|
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
|
|
788
|
-
if (passCount < totalCriteria)
|
|
1268
|
+
if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
|
|
789
1269
|
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
|
|
790
1270
|
const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
|
|
791
1271
|
const byId = artifactMap(artifactRefs);
|
|
792
1272
|
const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
|
|
793
1273
|
const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
|
|
794
|
-
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
795
1274
|
const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
codeReview: {
|
|
800
|
-
by: reviewerRoleField(codeReview["by"], REVIEWER_ROLES.codeReview, "codeReview.by"),
|
|
801
|
-
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
802
|
-
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
803
|
-
reportPath: codeReportPath,
|
|
804
|
-
evidence: textField(codeReview["evidence"], "codeReview.evidence"),
|
|
805
|
-
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
806
|
-
},
|
|
1275
|
+
if (!isPoisoned("gateReview.reportPath"))
|
|
1276
|
+
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
1277
|
+
const common = {
|
|
807
1278
|
manualQa: {
|
|
808
|
-
by:
|
|
1279
|
+
by: manualQaBy,
|
|
809
1280
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
810
|
-
evidence:
|
|
1281
|
+
evidence: manualQaEvidence,
|
|
811
1282
|
surfaceEvidence,
|
|
812
1283
|
adversarialCases,
|
|
813
1284
|
artifactRefs
|
|
814
1285
|
},
|
|
815
1286
|
gateReview: {
|
|
816
|
-
by:
|
|
1287
|
+
by: gateReviewBy,
|
|
817
1288
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
818
1289
|
reportPath: gateReportPath,
|
|
819
|
-
evidence:
|
|
1290
|
+
evidence: gateReviewEvidence,
|
|
820
1291
|
blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
|
|
821
1292
|
},
|
|
822
1293
|
iteration: {
|
|
@@ -834,17 +1305,41 @@ function validateQualityGate(input, opts) {
|
|
|
834
1305
|
adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
|
|
835
1306
|
}
|
|
836
1307
|
};
|
|
1308
|
+
if (surface === "omo-senpi")
|
|
1309
|
+
return { surface, ...common };
|
|
1310
|
+
if (codeReview === undefined)
|
|
1311
|
+
return { surface, ...common };
|
|
1312
|
+
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
1313
|
+
checkFile(codeReportPath, "codeReview.reportPath", opts);
|
|
1314
|
+
return {
|
|
1315
|
+
surface,
|
|
1316
|
+
...common,
|
|
1317
|
+
codeReview: {
|
|
1318
|
+
by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
|
|
1319
|
+
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
1320
|
+
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
1321
|
+
reportPath: codeReportPath,
|
|
1322
|
+
evidence: textField(codeReview["evidence"], "codeReview.evidence"),
|
|
1323
|
+
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
837
1326
|
}
|
|
838
1327
|
function parseSurfaceEvidence(value, byId) {
|
|
839
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1328
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
840
1329
|
invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
|
|
841
|
-
|
|
1330
|
+
return [];
|
|
1331
|
+
}
|
|
1332
|
+
return value.flatMap((item, index) => {
|
|
842
1333
|
const row = section(item, `manualQa.surfaceEvidence[${index}]`);
|
|
1334
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
|
|
1335
|
+
return [];
|
|
843
1336
|
const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
|
|
844
1337
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
|
|
845
1338
|
for (const artifact of artifacts) {
|
|
1339
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
|
|
1340
|
+
continue;
|
|
846
1341
|
if (!artifactCompatible(surface, artifact.kind)) {
|
|
847
|
-
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
|
|
1342
|
+
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
|
|
848
1343
|
}
|
|
849
1344
|
}
|
|
850
1345
|
return {
|
|
@@ -858,10 +1353,14 @@ function parseSurfaceEvidence(value, byId) {
|
|
|
858
1353
|
});
|
|
859
1354
|
}
|
|
860
1355
|
function parseAdversarialCases(value, byId) {
|
|
861
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1356
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
862
1357
|
invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
|
|
863
|
-
|
|
1358
|
+
return [];
|
|
1359
|
+
}
|
|
1360
|
+
return value.flatMap((item, index) => {
|
|
864
1361
|
const row = section(item, `manualQa.adversarialCases[${index}]`);
|
|
1362
|
+
if (isPoisoned(`manualQa.adversarialCases[${index}]`))
|
|
1363
|
+
return [];
|
|
865
1364
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
|
|
866
1365
|
const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
|
|
867
1366
|
return {
|
|
@@ -876,7 +1375,7 @@ function parseAdversarialCases(value, byId) {
|
|
|
876
1375
|
});
|
|
877
1376
|
}
|
|
878
1377
|
|
|
879
|
-
//
|
|
1378
|
+
// components/ulw-loop/src/cli-arg-parser.ts
|
|
880
1379
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
881
1380
|
var VALUE_FLAGS = new Set("--brief --brief-file --session-id --codex-goal-mode --validation-batch-json --goal --goal-id --criterion-id --status --evidence --notes --codex-goal-json --quality-gate-json --kind --rationale --title --objective --target-goal-id --source --after-json --directive-json --directive-file --idempotency-key --proposals-json".split(" "));
|
|
882
1381
|
var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
|
|
@@ -967,7 +1466,7 @@ function parseRecordEvidenceArgs(argv) {
|
|
|
967
1466
|
return notes ? { ...result, notes } : result;
|
|
968
1467
|
}
|
|
969
1468
|
|
|
970
|
-
//
|
|
1469
|
+
// components/ulw-loop/src/validation-batch.ts
|
|
971
1470
|
function isObject(value) {
|
|
972
1471
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
973
1472
|
}
|
|
@@ -1085,14 +1584,11 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
|
1085
1584
|
throw new UlwLoopError(message, code);
|
|
1086
1585
|
}
|
|
1087
1586
|
|
|
1088
|
-
//
|
|
1089
|
-
var QUALITY_GATE_FS = { existsSync:
|
|
1587
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
1588
|
+
var QUALITY_GATE_FS = { existsSync: existsSync4, statSync: statSync2 };
|
|
1090
1589
|
function ulwLoopFail2(message, code) {
|
|
1091
1590
|
throw new UlwLoopError(message, code);
|
|
1092
1591
|
}
|
|
1093
|
-
function normalizeObjective3(value) {
|
|
1094
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1095
|
-
}
|
|
1096
1592
|
function nonEmptyEvidence2(value) {
|
|
1097
1593
|
const trimmed = value.trim();
|
|
1098
1594
|
return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
|
|
@@ -1112,7 +1608,7 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1112
1608
|
throw error;
|
|
1113
1609
|
}
|
|
1114
1610
|
const path = resolve3(repoRoot, trimmed);
|
|
1115
|
-
if (!
|
|
1611
|
+
if (!existsSync4(path))
|
|
1116
1612
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1117
1613
|
try {
|
|
1118
1614
|
return JSON.parse(await readFile5(path, "utf8"));
|
|
@@ -1155,13 +1651,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
|
|
|
1155
1651
|
return status === "blocked" ? "goal_blocked" : "goal_failed";
|
|
1156
1652
|
}
|
|
1157
1653
|
function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
|
|
1158
|
-
const watch = qualityGate?.codeReview
|
|
1654
|
+
const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
|
|
1159
1655
|
const entry = {
|
|
1160
1656
|
at: now,
|
|
1161
1657
|
kind: ledgerKind2(args.status, goal, aggregateCompletion),
|
|
1162
1658
|
goalId: goal.id,
|
|
1163
1659
|
status: goal.status,
|
|
1164
|
-
evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview
|
|
1660
|
+
evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
|
|
1165
1661
|
};
|
|
1166
1662
|
if (codexGoal !== undefined)
|
|
1167
1663
|
entry.codexGoal = codexGoal;
|
|
@@ -1196,23 +1692,20 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1196
1692
|
requireEssentialCriteriaPass(goal);
|
|
1197
1693
|
else
|
|
1198
1694
|
requireAllCriteriaPass(goal);
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const taskScoped = completedTaskScoped || activeFinalTaskScoped;
|
|
1214
|
-
if (!taskScoped)
|
|
1215
|
-
throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
|
|
1695
|
+
let codexValidationError;
|
|
1696
|
+
try {
|
|
1697
|
+
codexGoal = await validateCheckpointCodexGoal({
|
|
1698
|
+
repoRoot,
|
|
1699
|
+
plan,
|
|
1700
|
+
goal,
|
|
1701
|
+
raw: args.codexGoalJson,
|
|
1702
|
+
evidence,
|
|
1703
|
+
...scope === undefined ? {} : { scope }
|
|
1704
|
+
});
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
if (!(error instanceof UlwLoopError))
|
|
1707
|
+
throw error;
|
|
1708
|
+
codexValidationError = error;
|
|
1216
1709
|
}
|
|
1217
1710
|
if (closesBatch)
|
|
1218
1711
|
requireBatchFinalReady(plan, goal);
|
|
@@ -1221,13 +1714,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1221
1714
|
if (final)
|
|
1222
1715
|
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
|
|
1223
1716
|
if (final || aggregateCompletion !== undefined || closesBatch) {
|
|
1224
|
-
|
|
1225
|
-
repoRoot,
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1717
|
+
try {
|
|
1718
|
+
qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
|
|
1719
|
+
repoRoot,
|
|
1720
|
+
fs: QUALITY_GATE_FS,
|
|
1721
|
+
reviewerSurface: resolveToolkitSurface(),
|
|
1722
|
+
...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
|
|
1723
|
+
});
|
|
1724
|
+
requireBatchGate(plan, goal, qualityGate);
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
|
|
1727
|
+
throw error;
|
|
1728
|
+
throw combineCheckpointValidationErrors(codexValidationError, error);
|
|
1729
|
+
}
|
|
1230
1730
|
}
|
|
1731
|
+
if (codexValidationError !== undefined)
|
|
1732
|
+
throw codexValidationError;
|
|
1231
1733
|
goal.status = "complete";
|
|
1232
1734
|
goal.completedAt = now;
|
|
1233
1735
|
goal.evidence = evidence;
|
|
@@ -1252,19 +1754,141 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1252
1754
|
});
|
|
1253
1755
|
}
|
|
1254
1756
|
|
|
1255
|
-
//
|
|
1757
|
+
// components/ulw-loop/src/checkpoint-template.ts
|
|
1758
|
+
function artifactPath(base, name) {
|
|
1759
|
+
return `${base}/${name}`;
|
|
1760
|
+
}
|
|
1761
|
+
function gateTemplate(surface, base) {
|
|
1762
|
+
const artifacts = [
|
|
1763
|
+
{
|
|
1764
|
+
id: "artifact-cli",
|
|
1765
|
+
kind: "cli-transcript",
|
|
1766
|
+
description: "<replace:artifact description>",
|
|
1767
|
+
path: artifactPath(base, "cli-transcript.txt")
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
id: "artifact-data",
|
|
1771
|
+
kind: "data-diff",
|
|
1772
|
+
description: "<replace:artifact description>",
|
|
1773
|
+
path: artifactPath(base, "data-diff.txt")
|
|
1774
|
+
}
|
|
1775
|
+
];
|
|
1776
|
+
const manualQa = {
|
|
1777
|
+
by: "main-session",
|
|
1778
|
+
status: "passed",
|
|
1779
|
+
evidence: "<replace:manual QA evidence>",
|
|
1780
|
+
surfaceEvidence: [
|
|
1781
|
+
{
|
|
1782
|
+
id: "surface-cli",
|
|
1783
|
+
criterionRef: "<replace:criterion id>",
|
|
1784
|
+
surface: "cli",
|
|
1785
|
+
invocation: "<replace:command>",
|
|
1786
|
+
verdict: "passed",
|
|
1787
|
+
artifactRefs: ["artifact-cli"]
|
|
1788
|
+
},
|
|
1789
|
+
{
|
|
1790
|
+
id: "surface-data",
|
|
1791
|
+
criterionRef: "<replace:criterion id>",
|
|
1792
|
+
surface: "data",
|
|
1793
|
+
invocation: "<replace:command>",
|
|
1794
|
+
verdict: "passed",
|
|
1795
|
+
artifactRefs: ["artifact-data"]
|
|
1796
|
+
}
|
|
1797
|
+
],
|
|
1798
|
+
adversarialCases: [
|
|
1799
|
+
{
|
|
1800
|
+
id: "<replace:adversarial case id>",
|
|
1801
|
+
criterionRef: "<replace:criterion id>",
|
|
1802
|
+
scenario: "<replace:scenario>",
|
|
1803
|
+
expectedBehavior: "<replace:expected behavior>",
|
|
1804
|
+
verdict: "not_applicable",
|
|
1805
|
+
reason: "<replace:reason>",
|
|
1806
|
+
artifactRefs: ["artifact-cli"]
|
|
1807
|
+
}
|
|
1808
|
+
],
|
|
1809
|
+
artifactRefs: artifacts
|
|
1810
|
+
};
|
|
1811
|
+
const common = {
|
|
1812
|
+
manualQa,
|
|
1813
|
+
gateReview: {
|
|
1814
|
+
by: surface === "omo-senpi" ? "category:deep" : "main-session",
|
|
1815
|
+
recommendation: "APPROVE",
|
|
1816
|
+
reportPath: artifactPath(base, "gate-review.md"),
|
|
1817
|
+
evidence: "<replace:gate review evidence>",
|
|
1818
|
+
blockers: [],
|
|
1819
|
+
notes: []
|
|
1820
|
+
},
|
|
1821
|
+
iteration: {
|
|
1822
|
+
fullRerun: true,
|
|
1823
|
+
status: "passed",
|
|
1824
|
+
rerunCommands: ["<replace:verification command>"],
|
|
1825
|
+
evidence: "<replace:iteration evidence>"
|
|
1826
|
+
},
|
|
1827
|
+
criteriaCoverage: {
|
|
1828
|
+
totalCriteria: 0,
|
|
1829
|
+
passCount: 0,
|
|
1830
|
+
originalIntent: "<replace:original intent>",
|
|
1831
|
+
desiredOutcome: "<replace:desired outcome>",
|
|
1832
|
+
userOutcomeReview: "<replace:user outcome review>",
|
|
1833
|
+
adversarialClassesCovered: ["<replace:adversarial class>"]
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
return common;
|
|
1837
|
+
}
|
|
1838
|
+
async function checkpointTemplate(repoRoot, scope, goalId) {
|
|
1839
|
+
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
1840
|
+
const targetId = goalId ?? plan.activeGoalId;
|
|
1841
|
+
const active = plan.goals.find((goal) => goal.id === targetId);
|
|
1842
|
+
if (goalId !== undefined && active === undefined)
|
|
1843
|
+
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
1844
|
+
const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
|
|
1845
|
+
const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
|
|
1846
|
+
const guidance = [
|
|
1847
|
+
"codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
|
|
1848
|
+
"Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
|
|
1849
|
+
'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
|
|
1850
|
+
'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
|
|
1851
|
+
...resolveToolkitSurface() === "lazycodex" ? [
|
|
1852
|
+
"Self-review defaults: manualQa.by and gateReview.by are main-session. Alternatives: manualQa.by accepts lazycodex-qa-executor; gateReview.by accepts lazycodex-gate-reviewer, category:deep, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session."
|
|
1853
|
+
] : [],
|
|
1854
|
+
...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
|
|
1855
|
+
].join(" ");
|
|
1856
|
+
return {
|
|
1857
|
+
qualityGateTemplate: gateTemplate(resolveToolkitSurface(), attemptDir),
|
|
1858
|
+
codexGoalTemplate: {
|
|
1859
|
+
goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
|
|
1860
|
+
},
|
|
1861
|
+
guidance,
|
|
1862
|
+
...hasAttempt ? { attemptDir } : {}
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// components/ulw-loop/src/cli-output.ts
|
|
1256
1867
|
var ULW_LOOP_HELP = `Usage:
|
|
1868
|
+
omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
|
|
1869
|
+
omo-agent-toolkit help | --help | -h (this message)
|
|
1870
|
+
omo-agent-toolkit ulw-loop help
|
|
1257
1871
|
omo-agent-toolkit ulw-loop create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json <json-or-path>] [--force] [--json]
|
|
1258
1872
|
omo-agent-toolkit ulw-loop status [--json]
|
|
1259
1873
|
omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
|
|
1260
1874
|
omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
|
|
1261
1875
|
omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
|
|
1876
|
+
omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
|
|
1262
1877
|
omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
|
|
1263
1878
|
omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
|
|
1264
1879
|
omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
|
|
1265
1880
|
omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
|
|
1266
1881
|
|
|
1267
|
-
|
|
1882
|
+
Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it.
|
|
1883
|
+
Every subcommand accepts --help | -h to print its own usage line.`;
|
|
1884
|
+
function subcommandHelp(subcommand) {
|
|
1885
|
+
const lines = ULW_LOOP_HELP.split(`
|
|
1886
|
+
`).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
|
|
1887
|
+
if (lines.length === 0)
|
|
1888
|
+
return ULW_LOOP_HELP;
|
|
1889
|
+
return ["Usage:", ...lines].join(`
|
|
1890
|
+
`);
|
|
1891
|
+
}
|
|
1268
1892
|
function printJson(value) {
|
|
1269
1893
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
1270
1894
|
`);
|
|
@@ -1330,17 +1954,18 @@ function normalizeCodexGoalMode(value) {
|
|
|
1330
1954
|
throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
|
|
1331
1955
|
}
|
|
1332
1956
|
|
|
1333
|
-
//
|
|
1957
|
+
// components/ulw-loop/src/codex-goal-instruction.ts
|
|
1334
1958
|
function buildCodexGoalInstruction(args) {
|
|
1335
1959
|
const mode = codexGoalMode(args.plan);
|
|
1336
1960
|
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
|
|
1337
1961
|
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
|
|
1338
|
-
|
|
1962
|
+
const surface = args.surface ?? resolveToolkitSurface();
|
|
1963
|
+
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
|
|
1339
1964
|
}
|
|
1340
1965
|
function buildCreateGoalPayload(plan, goal) {
|
|
1341
1966
|
return { objective: expectedCodexObjective(plan, goal) };
|
|
1342
1967
|
}
|
|
1343
|
-
function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
1968
|
+
function buildText(mode, plan, goal, createGoal, isFinal, surface) {
|
|
1344
1969
|
return joinLines([
|
|
1345
1970
|
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
|
|
1346
1971
|
`Mode: ${mode}`,
|
|
@@ -1357,7 +1982,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
|
1357
1982
|
"- Goals are unlimited. Do not add numeric limits.",
|
|
1358
1983
|
...modeConstraintLines(mode, isFinal),
|
|
1359
1984
|
...evidenceLayoutLines(plan),
|
|
1360
|
-
finalSection(plan, goal, isFinal, mode === "aggregate"),
|
|
1985
|
+
finalSection(plan, goal, isFinal, mode === "aggregate", surface),
|
|
1361
1986
|
...checkpointLines(plan, mode),
|
|
1362
1987
|
"",
|
|
1363
1988
|
"create_goal payload:",
|
|
@@ -1409,19 +2034,21 @@ function evidenceLayoutLines(plan) {
|
|
|
1409
2034
|
"- Evidence layout v2: write every artifact for the active goal (QA matrix, review reports, receipts) under the current attempt directory — read currentAttemptDir from `omo-agent-toolkit ulw-loop status --json` (.omo/evidence/ulw/<session>/<goalId>/a<attempt>). The final checkpoint rejects quality-gate artifacts outside that directory."
|
|
1410
2035
|
];
|
|
1411
2036
|
}
|
|
1412
|
-
function finalSection(plan, goal, isFinal, aggregate) {
|
|
2037
|
+
function finalSection(plan, goal, isFinal, aggregate, surface) {
|
|
2038
|
+
const roles = reviewerRolesFor(surface);
|
|
1413
2039
|
if (!isFinal)
|
|
1414
2040
|
return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
|
|
1415
2041
|
const option = sessionOption(plan);
|
|
2042
|
+
if (surface === "omo-senpi")
|
|
2043
|
+
return senpiFinalSection(plan, goal, aggregate);
|
|
1416
2044
|
const blockerCommand = `omo-agent-toolkit ulw-loop record-review-blockers${option} --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>"`;
|
|
1417
2045
|
const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<targeted verification/manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
|
|
1418
2046
|
return joinLines([
|
|
1419
|
-
"Final story —
|
|
2047
|
+
"Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
|
|
1420
2048
|
"- Run targeted verification for changed behavior.",
|
|
1421
2049
|
"- Confirm every manualQa artifact path exists and has non-zero size.",
|
|
1422
|
-
|
|
1423
|
-
"-
|
|
1424
|
-
"- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
|
|
2050
|
+
`- Run manual QA yourself and write its artifact under currentAttemptDir. Only if the user explicitly demands strict, rigorous, or high-accuracy review, spawn ${roles.gateReview}, optionally also ${roles.codeReview} and ${roles.manualQa}; otherwise set manualQa.by and gateReview.by to "main-session".`,
|
|
2051
|
+
"- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage; include codeReview only when strict review was requested. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
|
|
1425
2052
|
"- On a reviewer REJECT, fix only the cited blockers, rerun the affected verification/Manual-QA, and re-review the delta at most TWICE; if blockers remain, record them and surface to the user.",
|
|
1426
2053
|
"- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
|
|
1427
2054
|
"- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
|
|
@@ -1430,6 +2057,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
|
|
|
1430
2057
|
` ${checkpointCommand}`
|
|
1431
2058
|
]);
|
|
1432
2059
|
}
|
|
2060
|
+
function senpiFinalSection(plan, goal, aggregate) {
|
|
2061
|
+
const option = sessionOption(plan);
|
|
2062
|
+
const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "$(omo-agent-toolkit ulw-loop checkpoint${option} --print-template)"`;
|
|
2063
|
+
return joinLines([
|
|
2064
|
+
"Final story — run the single-reviewer quality gate before update_goal:",
|
|
2065
|
+
'- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
|
|
2066
|
+
'- Spawn exactly one gate reviewer with task(category: "deep").',
|
|
2067
|
+
"- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
|
|
2068
|
+
"- Set gateReview.by to the exact category:<name> literal used for the successful task.",
|
|
2069
|
+
"- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
|
|
2070
|
+
'- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
|
|
2071
|
+
aggregate ? `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint the aggregate story: ${checkpointCommand}` : `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint: ${checkpointCommand}`
|
|
2072
|
+
]);
|
|
2073
|
+
}
|
|
1433
2074
|
function sessionOption(plan) {
|
|
1434
2075
|
const prefix = ".omo/ulw-loop/";
|
|
1435
2076
|
const suffix = "/goals.json";
|
|
@@ -1443,11 +2084,11 @@ function joinLines(lines) {
|
|
|
1443
2084
|
`);
|
|
1444
2085
|
}
|
|
1445
2086
|
|
|
1446
|
-
//
|
|
1447
|
-
import { existsSync as
|
|
2087
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
2088
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1448
2089
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1449
2090
|
|
|
1450
|
-
//
|
|
2091
|
+
// components/ulw-loop/src/plan-goal-factory.ts
|
|
1451
2092
|
function cleanLine(line) {
|
|
1452
2093
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
1453
2094
|
}
|
|
@@ -1536,7 +2177,7 @@ function appendGoalToPlan(plan, title, objective, now) {
|
|
|
1536
2177
|
return goal;
|
|
1537
2178
|
}
|
|
1538
2179
|
|
|
1539
|
-
//
|
|
2180
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
1540
2181
|
function isScheduleEligible(goal) {
|
|
1541
2182
|
return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
|
|
1542
2183
|
}
|
|
@@ -1554,7 +2195,7 @@ function clearGoalBlockerFields2(goal) {
|
|
|
1554
2195
|
}
|
|
1555
2196
|
async function createUlwLoopPlan(repoRoot, args, scope) {
|
|
1556
2197
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1557
|
-
if (!args.force &&
|
|
2198
|
+
if (!args.force && existsSync5(ulwLoopGoalsPath(repoRoot, scope))) {
|
|
1558
2199
|
const existing = await readUlwLoopPlan(repoRoot, scope);
|
|
1559
2200
|
if (isUlwLoopDone(existing))
|
|
1560
2201
|
throw completedPlanExistsError(scope);
|
|
@@ -1663,7 +2304,7 @@ function summarizeUlwLoopPlan(plan) {
|
|
|
1663
2304
|
};
|
|
1664
2305
|
}
|
|
1665
2306
|
|
|
1666
|
-
//
|
|
2307
|
+
// components/ulw-loop/src/checkpoint-continuation.ts
|
|
1667
2308
|
async function checkpointAndContinue(repoRoot, args, scope) {
|
|
1668
2309
|
const result = await checkpointUlwLoop(repoRoot, args, scope);
|
|
1669
2310
|
if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
|
|
@@ -1675,6 +2316,14 @@ async function checkpointAndContinue(repoRoot, args, scope) {
|
|
|
1675
2316
|
return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
|
|
1676
2317
|
}
|
|
1677
2318
|
async function checkpoint(repoRoot, argv, json, scope) {
|
|
2319
|
+
if (hasFlag(argv, "--print-template")) {
|
|
2320
|
+
const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
|
|
2321
|
+
if (json)
|
|
2322
|
+
printJson({ ok: true, ...template });
|
|
2323
|
+
else
|
|
2324
|
+
printJson(template);
|
|
2325
|
+
return 0;
|
|
2326
|
+
}
|
|
1678
2327
|
const goalId = required2(argv, "--goal-id");
|
|
1679
2328
|
const statusValue = checkpointStatus(required2(argv, "--status"));
|
|
1680
2329
|
const evidence = required2(argv, "--evidence");
|
|
@@ -1726,10 +2375,10 @@ function checkpointStatus(value) {
|
|
|
1726
2375
|
throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
|
|
1727
2376
|
}
|
|
1728
2377
|
|
|
1729
|
-
//
|
|
2378
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
1730
2379
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
1731
2380
|
|
|
1732
|
-
//
|
|
2381
|
+
// components/ulw-loop/src/cli-steering.ts
|
|
1733
2382
|
var SOURCES = ["user_prompt_submit", "finding", "cli"];
|
|
1734
2383
|
var STEERING_KIND_HELP = [
|
|
1735
2384
|
`Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
|
|
@@ -2036,7 +2685,7 @@ function printSteerBatchResult(result, json) {
|
|
|
2036
2685
|
printStatus(result.plan);
|
|
2037
2686
|
}
|
|
2038
2687
|
|
|
2039
|
-
//
|
|
2688
|
+
// components/ulw-loop/src/review-blockers.ts
|
|
2040
2689
|
var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
2041
2690
|
function ulwLoopError(message, code) {
|
|
2042
2691
|
throw new UlwLoopError(message, code);
|
|
@@ -2075,9 +2724,10 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2075
2724
|
ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
|
|
2076
2725
|
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
|
|
2077
2726
|
const aggregate = codexGoalMode(plan) === "aggregate";
|
|
2078
|
-
const
|
|
2727
|
+
const expected = expectedCodexObjective(plan, goal);
|
|
2728
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expected, ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
|
|
2079
2729
|
if (!reconciliation.ok)
|
|
2080
|
-
|
|
2730
|
+
throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expected });
|
|
2081
2731
|
const now = iso();
|
|
2082
2732
|
for (const field of BLOCKER_FIELDS)
|
|
2083
2733
|
Reflect.deleteProperty(goal, field);
|
|
@@ -2102,14 +2752,43 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2102
2752
|
});
|
|
2103
2753
|
}
|
|
2104
2754
|
|
|
2105
|
-
//
|
|
2755
|
+
// components/ulw-loop/src/status-next-actions.ts
|
|
2756
|
+
function statusNextActions(plan) {
|
|
2757
|
+
const actions = [];
|
|
2758
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
2759
|
+
if (plan.goals.length === 0)
|
|
2760
|
+
actions.push(`No goals yet: bootstrap with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`);
|
|
2761
|
+
else if (active !== undefined)
|
|
2762
|
+
actions.push(...activeGoalActions(plan, active));
|
|
2763
|
+
for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
|
|
2764
|
+
actions.push(`${goal.id} is review_blocked: capture the reviewer verdict with \`omo-agent-toolkit ulw-loop record-review-blockers --goal-id ${goal.id} --title "<title>" --objective "<objective>" --evidence "<verdict>" --codex-goal-json '<get_goal json>'\`.`);
|
|
2765
|
+
if (plan.evidenceLayoutVersion !== 2 || active === undefined)
|
|
2766
|
+
actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
|
|
2767
|
+
return actions;
|
|
2768
|
+
}
|
|
2769
|
+
function activeGoalActions(plan, active) {
|
|
2770
|
+
const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
|
|
2771
|
+
if (unresolved.length > 0)
|
|
2772
|
+
return [
|
|
2773
|
+
`${active.id} has ${unresolved.length} unresolved criterion(s) (${unresolved.map((criterion) => criterion.id).join(", ")}): record proof with \`omo-agent-toolkit ulw-loop record-evidence --goal-id ${active.id} --criterion-id <id> --status pass --evidence "<observable proof>"\`.`
|
|
2774
|
+
];
|
|
2775
|
+
if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
|
|
2776
|
+
return [
|
|
2777
|
+
`${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate. Use status --json's currentAttemptDir for all quality-gate artifacts.`
|
|
2778
|
+
];
|
|
2779
|
+
return [
|
|
2780
|
+
`${active.id} passes every criterion: close it with \`omo-agent-toolkit ulw-loop checkpoint --goal-id ${active.id} --status complete --evidence "<proof>" --codex-goal-json '<get_goal json>'\`. Put quality-gate artifacts under the currentAttemptDir shown by status --json.`
|
|
2781
|
+
];
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
// components/ulw-loop/src/steering-mutations.ts
|
|
2106
2785
|
var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
2107
2786
|
var isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2108
2787
|
var text3 = (value, key) => {
|
|
2109
2788
|
const candidate = read2(value, key);
|
|
2110
2789
|
return isText(candidate) ? candidate.trim() : undefined;
|
|
2111
2790
|
};
|
|
2112
|
-
var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((
|
|
2791
|
+
var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2113
2792
|
var after = (proposal) => {
|
|
2114
2793
|
const candidate = read2(proposal, "after");
|
|
2115
2794
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
|
|
@@ -2184,36 +2863,36 @@ function reviseCriterion(plan, proposal, now) {
|
|
|
2184
2863
|
const current = target?.successCriteria[index];
|
|
2185
2864
|
if (target === undefined || current === undefined)
|
|
2186
2865
|
return;
|
|
2187
|
-
const
|
|
2188
|
-
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(
|
|
2866
|
+
const model = read2(proposal, "userModel");
|
|
2867
|
+
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model) ? model : current.userModel };
|
|
2189
2868
|
target.updatedAt = now;
|
|
2190
2869
|
}
|
|
2191
2870
|
|
|
2192
|
-
//
|
|
2871
|
+
// components/ulw-loop/src/steering-snapshot.ts
|
|
2193
2872
|
function buildSteeringPlanSnapshot(plan, changedGoalIds) {
|
|
2194
2873
|
const snapshot = {
|
|
2195
2874
|
updatedAt: plan.updatedAt,
|
|
2196
2875
|
goalCount: plan.goals.length,
|
|
2197
|
-
goalIds: plan.goals.map((
|
|
2198
|
-
goals: plan.goals.filter((
|
|
2876
|
+
goalIds: plan.goals.map((goal) => goal.id),
|
|
2877
|
+
goals: plan.goals.filter((goal) => changedGoalIds.has(goal.id))
|
|
2199
2878
|
};
|
|
2200
2879
|
return plan.activeGoalId === undefined ? snapshot : { ...snapshot, activeGoalId: plan.activeGoalId };
|
|
2201
2880
|
}
|
|
2202
|
-
function changedGoalIdsBetween(before,
|
|
2203
|
-
const beforeById = new Map(before.goals.map((
|
|
2881
|
+
function changedGoalIdsBetween(before, after) {
|
|
2882
|
+
const beforeById = new Map(before.goals.map((goal) => [goal.id, goal]));
|
|
2204
2883
|
const changed = new Set;
|
|
2205
|
-
for (const
|
|
2206
|
-
const prior = beforeById.get(
|
|
2207
|
-
if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(
|
|
2208
|
-
changed.add(
|
|
2209
|
-
beforeById.delete(
|
|
2884
|
+
for (const goal of after.goals) {
|
|
2885
|
+
const prior = beforeById.get(goal.id);
|
|
2886
|
+
if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal))
|
|
2887
|
+
changed.add(goal.id);
|
|
2888
|
+
beforeById.delete(goal.id);
|
|
2210
2889
|
}
|
|
2211
2890
|
for (const id of beforeById.keys())
|
|
2212
2891
|
changed.add(id);
|
|
2213
2892
|
return changed;
|
|
2214
2893
|
}
|
|
2215
2894
|
|
|
2216
|
-
//
|
|
2895
|
+
// components/ulw-loop/src/steering.ts
|
|
2217
2896
|
var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
|
|
2218
2897
|
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
2219
2898
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
@@ -2226,7 +2905,7 @@ var text4 = (value, key) => {
|
|
|
2226
2905
|
};
|
|
2227
2906
|
var isKind2 = (value) => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
|
|
2228
2907
|
var isSource2 = (value) => typeof value === "string" && SOURCES2.some((source) => source === value);
|
|
2229
|
-
var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((
|
|
2908
|
+
var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2230
2909
|
var texts = (value, key) => {
|
|
2231
2910
|
const candidate = read3(value, key);
|
|
2232
2911
|
return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
|
|
@@ -2367,10 +3046,10 @@ function validateCriterion(plan, proposal, reasons) {
|
|
|
2367
3046
|
reasons.push("revise_criterion requires goalId");
|
|
2368
3047
|
else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
|
|
2369
3048
|
reasons.push("revise_criterion requires criterionId");
|
|
2370
|
-
const
|
|
2371
|
-
if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined &&
|
|
3049
|
+
const model = read3(proposal, "userModel");
|
|
3050
|
+
if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model === undefined)
|
|
2372
3051
|
reasons.push("revise_criterion requires update");
|
|
2373
|
-
if (
|
|
3052
|
+
if (model !== undefined && !isModel3(model))
|
|
2374
3053
|
reasons.push("invalid userModel");
|
|
2375
3054
|
}
|
|
2376
3055
|
function applySteeringMutation(plan, proposal, audit) {
|
|
@@ -2397,8 +3076,8 @@ function applySteeringMutation(plan, proposal, audit) {
|
|
|
2397
3076
|
function isProposal(value) {
|
|
2398
3077
|
return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
|
|
2399
3078
|
}
|
|
2400
|
-
function parseUlwLoopSteeringDirective(
|
|
2401
|
-
const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(
|
|
3079
|
+
function parseUlwLoopSteeringDirective(text) {
|
|
3080
|
+
const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(text);
|
|
2402
3081
|
if (match?.[1] === undefined)
|
|
2403
3082
|
return null;
|
|
2404
3083
|
try {
|
|
@@ -2450,7 +3129,7 @@ function ledgerEntry(proposal, audit, at) {
|
|
|
2450
3129
|
return entry;
|
|
2451
3130
|
}
|
|
2452
3131
|
|
|
2453
|
-
//
|
|
3132
|
+
// components/ulw-loop/src/steering-batch.ts
|
|
2454
3133
|
async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
2455
3134
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
2456
3135
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
@@ -2483,16 +3162,16 @@ async function prepareBatch(repoRoot, plan, proposals, scope) {
|
|
|
2483
3162
|
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
2484
3163
|
const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
|
|
2485
3164
|
if (prior?.steering !== undefined) {
|
|
2486
|
-
const
|
|
2487
|
-
items.push({ kind: "deduped", result
|
|
2488
|
-
results.push(
|
|
3165
|
+
const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
|
|
3166
|
+
items.push({ kind: "deduped", result });
|
|
3167
|
+
results.push(result);
|
|
2489
3168
|
continue;
|
|
2490
3169
|
}
|
|
2491
3170
|
const audit = validateUlwLoopSteeringProposal(current, proposal);
|
|
2492
3171
|
if (!audit.invariant.accepted) {
|
|
2493
|
-
const
|
|
2494
|
-
items.push({ kind: "deduped", result
|
|
2495
|
-
results.push(
|
|
3172
|
+
const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
|
|
3173
|
+
items.push({ kind: "deduped", result });
|
|
3174
|
+
results.push(result);
|
|
2496
3175
|
continue;
|
|
2497
3176
|
}
|
|
2498
3177
|
const next = applySteeringMutation(current, proposal, audit);
|
|
@@ -2531,7 +3210,7 @@ function ledgerEntry2(proposal, audit, at) {
|
|
|
2531
3210
|
return entry;
|
|
2532
3211
|
}
|
|
2533
3212
|
|
|
2534
|
-
//
|
|
3213
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
2535
3214
|
async function createGoals(repoRoot, argv, json, scope) {
|
|
2536
3215
|
const briefFile = readValue(argv, "--brief-file");
|
|
2537
3216
|
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile6(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
|
|
@@ -2559,13 +3238,14 @@ ledger: ${plan.ledgerPath}
|
|
|
2559
3238
|
async function status(repoRoot, json, scope) {
|
|
2560
3239
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
2561
3240
|
if (json) {
|
|
2562
|
-
const active = plan.goals.find((
|
|
3241
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
2563
3242
|
const currentAttemptDir = plan.evidenceLayoutVersion === 2 && active ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : undefined;
|
|
2564
3243
|
printJson({
|
|
2565
3244
|
ok: true,
|
|
2566
3245
|
plan,
|
|
2567
3246
|
summary: summarizeUlwLoopPlan(plan),
|
|
2568
|
-
...currentAttemptDir === undefined ? {} : { currentAttemptDir }
|
|
3247
|
+
...currentAttemptDir === undefined ? {} : { currentAttemptDir },
|
|
3248
|
+
nextActions: statusNextActions(plan)
|
|
2569
3249
|
});
|
|
2570
3250
|
} else
|
|
2571
3251
|
printStatus(plan);
|
|
@@ -2601,9 +3281,9 @@ async function steer(repoRoot, argv, json, scope) {
|
|
|
2601
3281
|
const proposals = await parseSteeringProposals(argv);
|
|
2602
3282
|
const single = proposals[0];
|
|
2603
3283
|
if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
|
|
2604
|
-
const
|
|
2605
|
-
printSteerResult(
|
|
2606
|
-
return
|
|
3284
|
+
const result = await steerUlwLoop(repoRoot, single, scope);
|
|
3285
|
+
printSteerResult(result, json);
|
|
3286
|
+
return result.accepted ? 0 : 1;
|
|
2607
3287
|
}
|
|
2608
3288
|
const result = await steerUlwLoopBatch(repoRoot, proposals, scope);
|
|
2609
3289
|
printSteerBatchResult(result, json);
|
|
@@ -2622,12 +3302,12 @@ async function addGoal(repoRoot, argv, json, scope) {
|
|
|
2622
3302
|
}
|
|
2623
3303
|
async function criteria(repoRoot, argv, json, scope) {
|
|
2624
3304
|
const goalId = required4(argv, "--goal-id");
|
|
2625
|
-
const
|
|
3305
|
+
const goal = findGoal3(await readUlwLoopPlan(repoRoot, scope), goalId);
|
|
2626
3306
|
if (json)
|
|
2627
|
-
printJson({ ok: true, goalId:
|
|
3307
|
+
printJson({ ok: true, goalId: goal.id, criteria: goal.successCriteria });
|
|
2628
3308
|
else {
|
|
2629
|
-
process.stdout.write(`criteria for ${
|
|
2630
|
-
${
|
|
3309
|
+
process.stdout.write(`criteria for ${goal.id}:
|
|
3310
|
+
${goal.successCriteria.map(formatCriterionForCli).join(`
|
|
2631
3311
|
`)}
|
|
2632
3312
|
`);
|
|
2633
3313
|
}
|
|
@@ -2681,13 +3361,13 @@ function required4(argv, flag) {
|
|
|
2681
3361
|
throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
|
|
2682
3362
|
}
|
|
2683
3363
|
function findGoal3(plan, goalId) {
|
|
2684
|
-
const
|
|
2685
|
-
if (
|
|
2686
|
-
return
|
|
3364
|
+
const goal = plan.goals.find((candidate) => candidate.id === goalId);
|
|
3365
|
+
if (goal !== undefined)
|
|
3366
|
+
return goal;
|
|
2687
3367
|
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
2688
3368
|
}
|
|
2689
3369
|
|
|
2690
|
-
//
|
|
3370
|
+
// components/ulw-loop/src/cli-commands.ts
|
|
2691
3371
|
var ULW_LOOP_SUBCOMMANDS = [
|
|
2692
3372
|
"help",
|
|
2693
3373
|
"create-goals",
|
|
@@ -2710,7 +3390,6 @@ async function ulwLoopCommand(argv) {
|
|
|
2710
3390
|
const repoRoot = process.cwd();
|
|
2711
3391
|
const json = hasFlag(rest, "--json");
|
|
2712
3392
|
try {
|
|
2713
|
-
const scope = commandScope(rest);
|
|
2714
3393
|
if (!isUlwLoopSubcommand(command)) {
|
|
2715
3394
|
if (json) {
|
|
2716
3395
|
printJsonError(new UlwLoopError(`Unknown ulw-loop subcommand: ${command}.`, "ULW_LOOP_SUBCOMMAND_UNKNOWN", {
|
|
@@ -2722,11 +3401,18 @@ async function ulwLoopCommand(argv) {
|
|
|
2722
3401
|
`);
|
|
2723
3402
|
return 1;
|
|
2724
3403
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3404
|
+
if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
|
|
3405
|
+
process.stdout.write(`${subcommandHelp(command)}
|
|
3406
|
+
`);
|
|
3407
|
+
return 0;
|
|
3408
|
+
}
|
|
3409
|
+
if (command === "help") {
|
|
3410
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
2728
3411
|
`);
|
|
2729
|
-
|
|
3412
|
+
return 0;
|
|
3413
|
+
}
|
|
3414
|
+
const scope = commandScope(repoRoot, rest);
|
|
3415
|
+
switch (command) {
|
|
2730
3416
|
case "create-goals":
|
|
2731
3417
|
return await createGoals(repoRoot, rest, json, scope);
|
|
2732
3418
|
case "status":
|
|
@@ -2772,26 +3458,29 @@ var SESSION_ID_FLAG = "--session-id";
|
|
|
2772
3458
|
function sessionIdFlagPresent(argv) {
|
|
2773
3459
|
return hasFlag(argv, SESSION_ID_FLAG) || argv.some((arg) => arg.startsWith(`${SESSION_ID_FLAG}=`));
|
|
2774
3460
|
}
|
|
2775
|
-
function commandScope(argv) {
|
|
3461
|
+
function commandScope(repoRoot, argv) {
|
|
2776
3462
|
if (sessionIdFlagPresent(argv)) {
|
|
2777
|
-
const
|
|
2778
|
-
if (!
|
|
2779
|
-
throw new UlwLoopError(
|
|
3463
|
+
const sessionId = readValue(argv, SESSION_ID_FLAG)?.trim();
|
|
3464
|
+
if (!sessionId) {
|
|
3465
|
+
throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
|
|
2780
3466
|
details: { flag: SESSION_ID_FLAG }
|
|
2781
3467
|
});
|
|
2782
3468
|
}
|
|
2783
|
-
return { sessionId
|
|
3469
|
+
return { sessionId };
|
|
2784
3470
|
}
|
|
2785
3471
|
const sessionId = resolveUlwLoopSessionIdFromEnv();
|
|
2786
|
-
|
|
3472
|
+
if (sessionId !== null)
|
|
3473
|
+
return { sessionId };
|
|
3474
|
+
const existingSessionIds = listUlwLoopSessionIds(repoRoot);
|
|
3475
|
+
throw new UlwLoopError(sessionScopeRequiredMessage(SESSION_ID_FLAG, existingSessionIds), "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { flag: SESSION_ID_FLAG, existingSessionIds } });
|
|
2787
3476
|
}
|
|
2788
3477
|
|
|
2789
|
-
//
|
|
2790
|
-
import { readFileSync as
|
|
3478
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
3479
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
2791
3480
|
|
|
2792
|
-
//
|
|
2793
|
-
import { existsSync as
|
|
2794
|
-
import { fileURLToPath } from "node:url";
|
|
3481
|
+
// components/ulw-loop/src/ultrawork-skill-pointer.ts
|
|
3482
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
3483
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2795
3484
|
var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
|
|
2796
3485
|
ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
|
|
2797
3486
|
|
|
@@ -2822,22 +3511,22 @@ Do not start the requested work until all three steps are complete.
|
|
|
2822
3511
|
`;
|
|
2823
3512
|
var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
|
|
2824
3513
|
var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
|
|
2825
|
-
var ULTRAWORK_DIRECTIVE =
|
|
3514
|
+
var ULTRAWORK_DIRECTIVE = readFileSync3(new URL("../directive.md", import.meta.url), "utf8");
|
|
2826
3515
|
function resolveUltraworkSkillFilePath() {
|
|
2827
|
-
return
|
|
3516
|
+
return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
|
|
2828
3517
|
}
|
|
2829
3518
|
function buildUltraworkSkillPointer(skillFilePath) {
|
|
2830
3519
|
return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
|
|
2831
3520
|
}
|
|
2832
3521
|
function buildUltraworkAdditionalContext(options = {}) {
|
|
2833
3522
|
const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
|
|
2834
|
-
if (skillFilePath !== null &&
|
|
3523
|
+
if (skillFilePath !== null && existsSync6(skillFilePath)) {
|
|
2835
3524
|
return buildUltraworkSkillPointer(skillFilePath);
|
|
2836
3525
|
}
|
|
2837
3526
|
return ULTRAWORK_DIRECTIVE;
|
|
2838
3527
|
}
|
|
2839
3528
|
|
|
2840
|
-
//
|
|
3529
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
2841
3530
|
var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
|
|
2842
3531
|
var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
|
|
2843
3532
|
var TRANSCRIPT_SEARCH_BYTES = 512000;
|
|
@@ -2885,7 +3574,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
|
|
|
2885
3574
|
return false;
|
|
2886
3575
|
}
|
|
2887
3576
|
function readTranscriptTail(transcriptPath) {
|
|
2888
|
-
const rawTranscript =
|
|
3577
|
+
const rawTranscript = readFileSync4(transcriptPath);
|
|
2889
3578
|
return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
|
|
2890
3579
|
}
|
|
2891
3580
|
function isUltraworkPrompt(prompt) {
|
|
@@ -2940,7 +3629,7 @@ function isRecord3(value) {
|
|
|
2940
3629
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2941
3630
|
}
|
|
2942
3631
|
|
|
2943
|
-
//
|
|
3632
|
+
// components/ulw-loop/src/codex-hook.ts
|
|
2944
3633
|
var CREATE_GOAL_TOOL_NAME = "create_goal";
|
|
2945
3634
|
var CREATE_GOAL_PAYLOAD_WARNING = "Use create_goal with objective only. Omit token_budget so the goal stays unlimited, and put lifecycle status changes on update_goal.";
|
|
2946
3635
|
function parseUserPromptSubmitPayload(raw) {
|
|
@@ -3066,38 +3755,92 @@ function optionalString(value) {
|
|
|
3066
3755
|
return value === undefined || typeof value === "string";
|
|
3067
3756
|
}
|
|
3068
3757
|
function readAll(stdin) {
|
|
3069
|
-
return new Promise((
|
|
3758
|
+
return new Promise((resolve, reject) => {
|
|
3070
3759
|
let data = "";
|
|
3071
3760
|
stdin.setEncoding("utf8");
|
|
3072
3761
|
stdin.on("data", (chunk) => {
|
|
3073
3762
|
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
|
3074
3763
|
});
|
|
3075
3764
|
stdin.once("error", reject);
|
|
3076
|
-
stdin.once("end", () =>
|
|
3765
|
+
stdin.once("end", () => resolve(data));
|
|
3077
3766
|
});
|
|
3078
3767
|
}
|
|
3079
3768
|
|
|
3080
|
-
//
|
|
3081
|
-
import {
|
|
3082
|
-
import {
|
|
3769
|
+
// components/ulw-loop/src/spawn-guard.ts
|
|
3770
|
+
import { randomBytes } from "node:crypto";
|
|
3771
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, statSync as statSync3, writeFileSync } from "node:fs";
|
|
3772
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
3083
3773
|
var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
|
|
3084
|
-
var DEFAULT_FANOUT_LIMIT =
|
|
3085
|
-
var
|
|
3086
|
-
|
|
3774
|
+
var DEFAULT_FANOUT_LIMIT = 24;
|
|
3775
|
+
var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
|
|
3776
|
+
var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
|
|
3777
|
+
var REVIEW_AGENT_TYPES = [
|
|
3778
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
|
|
3779
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
|
|
3780
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
|
|
3781
|
+
];
|
|
3782
|
+
var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
|
|
3783
|
+
function applySpawnGuards(payload, options = {}) {
|
|
3087
3784
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
3088
3785
|
return "";
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3786
|
+
const breaker = readAdmissionBreaker(payload.session_id);
|
|
3787
|
+
if (breaker !== null)
|
|
3788
|
+
return deny(`Subagent admission failed earlier in this session (${breaker}). Do not spawn more workers or reviewers; report the capacity block and wait for the user.`);
|
|
3789
|
+
const scope = { sessionId: payload.session_id };
|
|
3790
|
+
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
3791
|
+
const plan = readPlan(join3(stateDir, "goals.json"));
|
|
3091
3792
|
if (plan === null)
|
|
3092
3793
|
return "";
|
|
3794
|
+
const lockOptions = options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs };
|
|
3795
|
+
try {
|
|
3796
|
+
return withStateLockSync(ulwLoopStateLockPath(payload.cwd, scope), () => evaluateGuards(payload, plan, stateDir), lockOptions);
|
|
3797
|
+
} catch (error) {
|
|
3798
|
+
if (isStateLockTimeout(error))
|
|
3799
|
+
return deny(`ulw-loop spawn guard could not take the session state lock: ${error.message}`);
|
|
3800
|
+
throw error;
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
function evaluateGuards(payload, plan, stateDir) {
|
|
3804
|
+
const fanOutPeek = peekFanOutBudget(stateDir);
|
|
3805
|
+
if (fanOutPeek !== null)
|
|
3806
|
+
return deny(fanOutPeek);
|
|
3807
|
+
const missingArtifact = missingGateArtifact(payload, plan);
|
|
3808
|
+
if (missingArtifact !== null)
|
|
3809
|
+
return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
|
|
3810
|
+
const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
|
|
3811
|
+
if (reviewDenial !== null)
|
|
3812
|
+
return deny(reviewDenial);
|
|
3093
3813
|
const fanOutDenial = consumeFanOutBudget(stateDir);
|
|
3094
3814
|
if (fanOutDenial !== null)
|
|
3095
3815
|
return deny(fanOutDenial);
|
|
3096
|
-
const missingArtifact = missingGateArtifact(payload, plan);
|
|
3097
|
-
if (missingArtifact !== null)
|
|
3098
|
-
return deny(`spawn code-review + QA first; gate audits their artifacts: missing ${missingArtifact}`);
|
|
3099
3816
|
return "";
|
|
3100
3817
|
}
|
|
3818
|
+
async function runSpawnAdmissionRecorderCli(stdin, stdout) {
|
|
3819
|
+
const chunks = [];
|
|
3820
|
+
for await (const chunk of stdin)
|
|
3821
|
+
chunks.push(Buffer.from(chunk));
|
|
3822
|
+
try {
|
|
3823
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
3824
|
+
const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
|
|
3825
|
+
if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
|
|
3826
|
+
return;
|
|
3827
|
+
const dataDir = process.env["PLUGIN_DATA"];
|
|
3828
|
+
if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
|
|
3829
|
+
return;
|
|
3830
|
+
const markerDir = join3(dataDir, "spawn-breaker");
|
|
3831
|
+
try {
|
|
3832
|
+
mkdirSync2(markerDir, { recursive: true });
|
|
3833
|
+
atomicWriteJson(join3(markerDir, `${payload["session_id"]}.json`), {
|
|
3834
|
+
reason: response,
|
|
3835
|
+
at: new Date().toISOString()
|
|
3836
|
+
});
|
|
3837
|
+
} catch (error) {
|
|
3838
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3839
|
+
process.stderr.write(`[ulw-loop] spawn-guard: could not persist admission failure: ${message}
|
|
3840
|
+
`);
|
|
3841
|
+
}
|
|
3842
|
+
} catch {}
|
|
3843
|
+
}
|
|
3101
3844
|
async function runSpawnGuardCli(stdin, stdout) {
|
|
3102
3845
|
try {
|
|
3103
3846
|
const chunks = [];
|
|
@@ -3114,49 +3857,112 @@ async function runSpawnGuardCli(stdin, stdout) {
|
|
|
3114
3857
|
return;
|
|
3115
3858
|
}
|
|
3116
3859
|
}
|
|
3860
|
+
function peekFanOutBudget(stateDir) {
|
|
3861
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3862
|
+
const count = readCount(counterPath) + 1;
|
|
3863
|
+
const limit = fanOutLimit();
|
|
3864
|
+
if (count <= limit)
|
|
3865
|
+
return null;
|
|
3866
|
+
return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
|
|
3867
|
+
}
|
|
3117
3868
|
function consumeFanOutBudget(stateDir) {
|
|
3118
|
-
const counterPath =
|
|
3869
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3119
3870
|
const count = readCount(counterPath) + 1;
|
|
3120
|
-
|
|
3871
|
+
atomicWriteJson(counterPath, { count });
|
|
3121
3872
|
const limit = fanOutLimit();
|
|
3122
3873
|
if (count <= limit)
|
|
3123
3874
|
return null;
|
|
3124
3875
|
return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
|
|
3125
3876
|
}
|
|
3877
|
+
function consumeReviewSpawnBudget(payload, plan, stateDir) {
|
|
3878
|
+
const agentType = reviewAgentType(payload.tool_input);
|
|
3879
|
+
if (agentType === null)
|
|
3880
|
+
return null;
|
|
3881
|
+
const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
3882
|
+
if (goal === undefined)
|
|
3883
|
+
return null;
|
|
3884
|
+
const counterPath = join3(stateDir, "review-spawn-counts.json");
|
|
3885
|
+
const limit = reviewSpawnLimit();
|
|
3886
|
+
const counts = readCounts(counterPath);
|
|
3887
|
+
const key = `${agentType}:${goal.id}:a${goal.attempt}`;
|
|
3888
|
+
const count = (counts[key] ?? 0) + 1;
|
|
3889
|
+
if (count > limit)
|
|
3890
|
+
return `ulw-loop reviewer no-progress cap reached (${agentType} ${count}/${limit}) for ${goal.id} attempt ${goal.attempt}. Consolidate existing review findings, or checkpoint and start a new attempt after concrete progress.`;
|
|
3891
|
+
counts[key] = count;
|
|
3892
|
+
atomicWriteJson(counterPath, counts);
|
|
3893
|
+
return null;
|
|
3894
|
+
}
|
|
3126
3895
|
function missingGateArtifact(payload, plan) {
|
|
3127
3896
|
if (!isGateReviewerSpawn(payload.tool_input))
|
|
3128
3897
|
return null;
|
|
3129
|
-
const
|
|
3130
|
-
if (
|
|
3898
|
+
const goal = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
3899
|
+
if (goal === undefined || goal.status === "complete")
|
|
3131
3900
|
return null;
|
|
3132
|
-
if (!
|
|
3901
|
+
if (!goal.successCriteria.every((criterion) => criterion.status === "pass"))
|
|
3133
3902
|
return null;
|
|
3134
3903
|
const scope = { sessionId: payload.session_id };
|
|
3904
|
+
const requiredArtifacts = [`${goal.id}-manual-qa.md`];
|
|
3135
3905
|
if (plan.evidenceLayoutVersion === 2) {
|
|
3136
|
-
const attemptDir = ulwLoopAttemptEvidenceDir(
|
|
3137
|
-
for (const name of
|
|
3138
|
-
const
|
|
3139
|
-
if (!isNonEmptyFile(
|
|
3140
|
-
return
|
|
3906
|
+
const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
|
|
3907
|
+
for (const name of requiredArtifacts) {
|
|
3908
|
+
const relative = `${attemptDir}/${name}`;
|
|
3909
|
+
if (!isNonEmptyFile(join3(payload.cwd, relative)))
|
|
3910
|
+
return relative;
|
|
3141
3911
|
}
|
|
3142
3912
|
return null;
|
|
3143
3913
|
}
|
|
3144
|
-
const
|
|
3145
|
-
|
|
3146
|
-
return flatReport;
|
|
3147
|
-
if (!hasOtherEvidenceFile(join2(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
|
|
3148
|
-
return `.omo/evidence/<any manual-QA artifact besides ${goal3.id}-code-review.md>`;
|
|
3149
|
-
return null;
|
|
3914
|
+
const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
|
|
3915
|
+
return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
|
|
3150
3916
|
}
|
|
3151
3917
|
function isGateReviewerSpawn(toolInput) {
|
|
3918
|
+
const agentType = reviewAgentType(toolInput);
|
|
3919
|
+
return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
|
|
3920
|
+
}
|
|
3921
|
+
function reviewAgentType(toolInput) {
|
|
3152
3922
|
if (typeof toolInput !== "object" || toolInput === null)
|
|
3153
|
-
return
|
|
3923
|
+
return null;
|
|
3154
3924
|
const record = toolInput;
|
|
3155
3925
|
const agentType = record["agent_type"];
|
|
3156
|
-
if (typeof agentType === "string")
|
|
3157
|
-
|
|
3926
|
+
if (typeof agentType === "string") {
|
|
3927
|
+
if (!REVIEW_AGENT_TYPE_SET.has(agentType))
|
|
3928
|
+
return null;
|
|
3929
|
+
return activeSurfaceReviewerAlias(agentType);
|
|
3930
|
+
}
|
|
3158
3931
|
const message = record["message"];
|
|
3159
|
-
|
|
3932
|
+
if (typeof message !== "string")
|
|
3933
|
+
return null;
|
|
3934
|
+
const normalizedMessage = message.toLowerCase();
|
|
3935
|
+
const allRoleNames = [...REVIEW_AGENT_TYPES];
|
|
3936
|
+
const explicitAssignment = allRoleNames.map((name) => ({
|
|
3937
|
+
name,
|
|
3938
|
+
index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
|
|
3939
|
+
})).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
|
|
3940
|
+
if (explicitAssignment !== undefined)
|
|
3941
|
+
return activeSurfaceReviewerAlias(explicitAssignment.name);
|
|
3942
|
+
const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
|
|
3943
|
+
if (nonReviewerActAs)
|
|
3944
|
+
return null;
|
|
3945
|
+
const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
|
|
3946
|
+
if (namedReviewer !== undefined)
|
|
3947
|
+
return activeSurfaceReviewerAlias(namedReviewer);
|
|
3948
|
+
return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
|
|
3949
|
+
}
|
|
3950
|
+
function activeSurfaceReviewerAlias(reviewer) {
|
|
3951
|
+
const activeRoles = reviewerRolesFor(resolveToolkitSurface());
|
|
3952
|
+
for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
|
|
3953
|
+
if (reviewer === roles.codeReview)
|
|
3954
|
+
return activeRoles.codeReview;
|
|
3955
|
+
if (reviewer === roles.manualQa)
|
|
3956
|
+
return activeRoles.manualQa;
|
|
3957
|
+
if (reviewer === roles.gateReview)
|
|
3958
|
+
return activeRoles.gateReview;
|
|
3959
|
+
}
|
|
3960
|
+
return reviewer;
|
|
3961
|
+
}
|
|
3962
|
+
function atomicWriteJson(targetPath, data) {
|
|
3963
|
+
const tmp = join3(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
|
|
3964
|
+
writeFileSync(tmp, JSON.stringify(data));
|
|
3965
|
+
renameSync(tmp, targetPath);
|
|
3160
3966
|
}
|
|
3161
3967
|
function deny(reason) {
|
|
3162
3968
|
return `${JSON.stringify({
|
|
@@ -3169,6 +3975,17 @@ function deny(reason) {
|
|
|
3169
3975
|
})}
|
|
3170
3976
|
`;
|
|
3171
3977
|
}
|
|
3978
|
+
function readAdmissionBreaker(sessionId) {
|
|
3979
|
+
const dataDir = process.env["PLUGIN_DATA"];
|
|
3980
|
+
if (typeof dataDir !== "string")
|
|
3981
|
+
return null;
|
|
3982
|
+
try {
|
|
3983
|
+
const value = JSON.parse(readFileSync5(join3(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
|
|
3984
|
+
return typeof value.reason === "string" ? value.reason : "capacity limit";
|
|
3985
|
+
} catch {
|
|
3986
|
+
return null;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3172
3989
|
function fanOutLimit() {
|
|
3173
3990
|
const raw = process.env["OMO_SPAWN_FANOUT_LIMIT"];
|
|
3174
3991
|
if (raw === undefined)
|
|
@@ -3176,37 +3993,52 @@ function fanOutLimit() {
|
|
|
3176
3993
|
const parsed = Number.parseInt(raw, 10);
|
|
3177
3994
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
|
|
3178
3995
|
}
|
|
3996
|
+
function reviewSpawnLimit() {
|
|
3997
|
+
const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
3998
|
+
if (raw === undefined)
|
|
3999
|
+
return DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
4000
|
+
const parsed = Number.parseInt(raw, 10);
|
|
4001
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
4002
|
+
}
|
|
3179
4003
|
function isNonEmptyFile(path) {
|
|
3180
4004
|
try {
|
|
3181
|
-
return
|
|
4005
|
+
return existsSync7(path) && statSync3(path).size > 0;
|
|
3182
4006
|
} catch (error) {
|
|
3183
4007
|
if (error instanceof Error)
|
|
3184
4008
|
return false;
|
|
3185
4009
|
throw error;
|
|
3186
4010
|
}
|
|
3187
4011
|
}
|
|
3188
|
-
function
|
|
4012
|
+
function readCount(counterPath) {
|
|
3189
4013
|
try {
|
|
3190
|
-
|
|
4014
|
+
const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
|
|
4015
|
+
return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
|
|
3191
4016
|
} catch (error) {
|
|
3192
4017
|
if (error instanceof Error)
|
|
3193
|
-
return
|
|
4018
|
+
return 0;
|
|
3194
4019
|
throw error;
|
|
3195
4020
|
}
|
|
3196
4021
|
}
|
|
3197
|
-
function
|
|
4022
|
+
function readCounts(counterPath) {
|
|
3198
4023
|
try {
|
|
3199
|
-
const parsed = JSON.parse(
|
|
3200
|
-
|
|
4024
|
+
const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
|
|
4025
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
4026
|
+
return {};
|
|
4027
|
+
const counts = {};
|
|
4028
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
4029
|
+
if (typeof value === "number" && value >= 0)
|
|
4030
|
+
counts[key] = value;
|
|
4031
|
+
}
|
|
4032
|
+
return counts;
|
|
3201
4033
|
} catch (error) {
|
|
3202
4034
|
if (error instanceof Error)
|
|
3203
|
-
return
|
|
4035
|
+
return {};
|
|
3204
4036
|
throw error;
|
|
3205
4037
|
}
|
|
3206
4038
|
}
|
|
3207
4039
|
function readPlan(goalsPath) {
|
|
3208
4040
|
try {
|
|
3209
|
-
return JSON.parse(
|
|
4041
|
+
return JSON.parse(readFileSync5(goalsPath, "utf8"));
|
|
3210
4042
|
} catch (error) {
|
|
3211
4043
|
if (error instanceof Error)
|
|
3212
4044
|
return null;
|
|
@@ -3214,9 +4046,9 @@ function readPlan(goalsPath) {
|
|
|
3214
4046
|
}
|
|
3215
4047
|
}
|
|
3216
4048
|
|
|
3217
|
-
//
|
|
3218
|
-
import { existsSync as
|
|
3219
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4049
|
+
// components/ulw-loop/src/stop-resume-hook.ts
|
|
4050
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4051
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
|
|
3220
4052
|
var RESUME_CAP = 2;
|
|
3221
4053
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
3222
4054
|
"context compacted",
|
|
@@ -3235,18 +4067,19 @@ function runStopResumeHook(input) {
|
|
|
3235
4067
|
return "";
|
|
3236
4068
|
if (boulderContinuationWillFire(payload.cwd, payload.session_id))
|
|
3237
4069
|
return "";
|
|
3238
|
-
const
|
|
3239
|
-
const
|
|
4070
|
+
const scope = { sessionId: payload.session_id };
|
|
4071
|
+
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
4072
|
+
const plan = readPlan2(join4(stateDir, "goals.json"));
|
|
3240
4073
|
if (plan === null || plan.aggregateCompletion?.status === "complete")
|
|
3241
4074
|
return "";
|
|
3242
|
-
const
|
|
3243
|
-
if (
|
|
4075
|
+
const goal = resumableGoal(plan);
|
|
4076
|
+
if (goal === undefined)
|
|
3244
4077
|
return "";
|
|
3245
|
-
if (!
|
|
4078
|
+
if (!consumeResumeBudgetLocked(ulwLoopStateLockPath(payload.cwd, scope), stateDir, goal.id))
|
|
3246
4079
|
return "";
|
|
3247
4080
|
const output = {
|
|
3248
4081
|
decision: "block",
|
|
3249
|
-
reason: renderResumeDirective(plan,
|
|
4082
|
+
reason: renderResumeDirective(plan, goal, payload.session_id)
|
|
3250
4083
|
};
|
|
3251
4084
|
return JSON.stringify(output);
|
|
3252
4085
|
}
|
|
@@ -3264,16 +4097,25 @@ async function runStopResumeHookCli(stdin, stdout) {
|
|
|
3264
4097
|
}
|
|
3265
4098
|
}
|
|
3266
4099
|
function resumableGoal(plan) {
|
|
3267
|
-
const active = plan.goals.find((
|
|
4100
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
3268
4101
|
if (active !== undefined && isResumableStatus(active.status))
|
|
3269
4102
|
return active;
|
|
3270
|
-
return plan.goals.find((
|
|
4103
|
+
return plan.goals.find((goal) => isResumableStatus(goal.status));
|
|
3271
4104
|
}
|
|
3272
|
-
function isResumableStatus(
|
|
3273
|
-
return
|
|
4105
|
+
function isResumableStatus(status) {
|
|
4106
|
+
return status === "pending" || status === "in_progress";
|
|
4107
|
+
}
|
|
4108
|
+
function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
|
|
4109
|
+
try {
|
|
4110
|
+
return withStateLockSync(lockPath, () => consumeResumeBudget(stateDir, goalId));
|
|
4111
|
+
} catch (error) {
|
|
4112
|
+
if (isStateLockTimeout(error))
|
|
4113
|
+
return false;
|
|
4114
|
+
throw error;
|
|
4115
|
+
}
|
|
3274
4116
|
}
|
|
3275
4117
|
function consumeResumeBudget(stateDir, goalId) {
|
|
3276
|
-
const ledgerLineCount = countLedgerLines(
|
|
4118
|
+
const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
|
|
3277
4119
|
const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
|
|
3278
4120
|
const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
|
|
3279
4121
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
@@ -3291,11 +4133,11 @@ function consumeResumeBudget(stateDir, goalId) {
|
|
|
3291
4133
|
function isInsideDir(dir, candidate) {
|
|
3292
4134
|
return candidate.startsWith(resolve4(dir) + sep2);
|
|
3293
4135
|
}
|
|
3294
|
-
function renderResumeDirective(plan,
|
|
4136
|
+
function renderResumeDirective(plan, goal, sessionId) {
|
|
3295
4137
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|
|
3296
4138
|
const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
|
|
3297
4139
|
return [
|
|
3298
|
-
`The ulw-loop run in this session still has unfinished goals (next: ${
|
|
4140
|
+
`The ulw-loop run in this session still has unfinished goals (next: ${goal.id} — ${goal.title}).`,
|
|
3299
4141
|
"The turn ended before the loop completed. Resume it now:",
|
|
3300
4142
|
`1. Run \`omo-agent-toolkit ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
|
|
3301
4143
|
"2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
|
|
@@ -3306,7 +4148,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
|
|
|
3306
4148
|
}
|
|
3307
4149
|
function readPlan2(goalsPath) {
|
|
3308
4150
|
try {
|
|
3309
|
-
return JSON.parse(
|
|
4151
|
+
return JSON.parse(readFileSync6(goalsPath, "utf8"));
|
|
3310
4152
|
} catch (error) {
|
|
3311
4153
|
if (error instanceof Error)
|
|
3312
4154
|
return null;
|
|
@@ -3315,7 +4157,7 @@ function readPlan2(goalsPath) {
|
|
|
3315
4157
|
}
|
|
3316
4158
|
function countLedgerLines(ledgerPath) {
|
|
3317
4159
|
try {
|
|
3318
|
-
return
|
|
4160
|
+
return readFileSync6(ledgerPath, "utf8").split(`
|
|
3319
4161
|
`).filter(Boolean).length;
|
|
3320
4162
|
} catch (error) {
|
|
3321
4163
|
if (error instanceof Error)
|
|
@@ -3325,9 +4167,9 @@ function countLedgerLines(ledgerPath) {
|
|
|
3325
4167
|
}
|
|
3326
4168
|
function readCounter(counterPath) {
|
|
3327
4169
|
try {
|
|
3328
|
-
if (!
|
|
4170
|
+
if (!existsSync8(counterPath))
|
|
3329
4171
|
return null;
|
|
3330
|
-
const parsed = JSON.parse(
|
|
4172
|
+
const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
|
|
3331
4173
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
3332
4174
|
return null;
|
|
3333
4175
|
return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
|
|
@@ -3339,7 +4181,7 @@ function readCounter(counterPath) {
|
|
|
3339
4181
|
}
|
|
3340
4182
|
function boulderContinuationWillFire(cwd, sessionId) {
|
|
3341
4183
|
try {
|
|
3342
|
-
const raw = JSON.parse(
|
|
4184
|
+
const raw = JSON.parse(readFileSync6(join4(cwd, ".omo", "boulder.json"), "utf8"));
|
|
3343
4185
|
const works = raw["works"];
|
|
3344
4186
|
const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
|
|
3345
4187
|
return entries.some((work) => {
|
|
@@ -3358,7 +4200,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
|
|
|
3358
4200
|
}
|
|
3359
4201
|
function transcriptShowsContextPressure(transcriptPath) {
|
|
3360
4202
|
try {
|
|
3361
|
-
const transcript =
|
|
4203
|
+
const transcript = readFileSync6(transcriptPath, "utf8").toLowerCase();
|
|
3362
4204
|
return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
|
|
3363
4205
|
} catch (error) {
|
|
3364
4206
|
if (error instanceof Error)
|
|
@@ -3370,12 +4212,12 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
3370
4212
|
const activePlan = entry["active_plan"];
|
|
3371
4213
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
3372
4214
|
return false;
|
|
3373
|
-
const planPath = isAbsolute2(activePlan) ? activePlan :
|
|
4215
|
+
const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
|
|
3374
4216
|
const worktree = entry["worktree_path"];
|
|
3375
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [
|
|
4217
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
|
|
3376
4218
|
for (const candidate of candidates) {
|
|
3377
4219
|
try {
|
|
3378
|
-
return
|
|
4220
|
+
return readFileSync6(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|
|
3379
4221
|
} catch (error) {
|
|
3380
4222
|
if (!(error instanceof Error))
|
|
3381
4223
|
throw error;
|
|
@@ -3399,19 +4241,13 @@ function parseStopPayload(value) {
|
|
|
3399
4241
|
};
|
|
3400
4242
|
}
|
|
3401
4243
|
|
|
3402
|
-
//
|
|
3403
|
-
var TOP_LEVEL_HELP = `Usage:
|
|
3404
|
-
omo-agent-toolkit ulw-loop <subcommand> [args]
|
|
3405
|
-
omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
|
|
3406
|
-
omo-agent-toolkit help | --help | -h (this message)
|
|
3407
|
-
|
|
3408
|
-
Run \`omo-agent-toolkit ulw-loop help\` for ulw-loop subcommands.
|
|
3409
|
-
`;
|
|
4244
|
+
// components/ulw-loop/src/cli.ts
|
|
3410
4245
|
async function main() {
|
|
3411
4246
|
const argv = process.argv.slice(2);
|
|
3412
4247
|
const command = argv[0];
|
|
3413
4248
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
3414
|
-
process.stdout.write(
|
|
4249
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
4250
|
+
`);
|
|
3415
4251
|
return 0;
|
|
3416
4252
|
}
|
|
3417
4253
|
if (command === "ulw-loop")
|
|
@@ -3432,6 +4268,10 @@ async function main() {
|
|
|
3432
4268
|
await runStopResumeHookCli(process.stdin, process.stdout);
|
|
3433
4269
|
return 0;
|
|
3434
4270
|
}
|
|
4271
|
+
if (sub === "post-tool-use-spawn") {
|
|
4272
|
+
await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
|
|
4273
|
+
return 0;
|
|
4274
|
+
}
|
|
3435
4275
|
if (sub === "pre-tool-use-spawn") {
|
|
3436
4276
|
await runSpawnGuardCli(process.stdin, process.stdout);
|
|
3437
4277
|
return 0;
|
|
@@ -3443,7 +4283,8 @@ async function main() {
|
|
|
3443
4283
|
if (isUlwLoopSubcommand(command))
|
|
3444
4284
|
return ulwLoopCommand(argv);
|
|
3445
4285
|
process.stderr.write(`[omo] unknown command: ${command}
|
|
3446
|
-
${
|
|
4286
|
+
${ULW_LOOP_HELP}
|
|
4287
|
+
`);
|
|
3447
4288
|
return 1;
|
|
3448
4289
|
}
|
|
3449
4290
|
main().then((code) => {
|