oh-my-opencode 5.0.0-beta.6 → 5.0.0-beta.60
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 +59 -23
- 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 +59 -23
- 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 +59 -23
- package/.opencode/skills/pre-publish-review/SKILL.md +9 -12
- package/.opencode/skills/work-with-pr/SKILL.md +37 -23
- package/README.ja.md +65 -60
- package/README.ko.md +66 -61
- package/README.md +65 -72
- package/README.ru.md +62 -60
- package/README.zh-cn.md +61 -60
- 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/index.d.ts +2 -0
- package/dist/cli/doctor/checks/latest-version.d.ts +11 -0
- package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
- package/dist/cli/doctor/framework/constants.d.ts +5 -0
- package/dist/cli/doctor/framework/types.d.ts +2 -0
- package/dist/cli/fallback-lane-policy.d.ts +1 -0
- package/dist/cli/index.js +51738 -59690
- 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 +50900 -58829
- 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 +9 -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/background-agent/types.d.ts +4 -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/claude-tasks/storage.d.ts +7 -2
- package/dist/features/hook-message-injector/index.d.ts +1 -0
- package/dist/features/hook-message-injector/sdk-message-lookup.d.ts +1 -0
- 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 +9 -0
- package/dist/hooks/keyword-detector/ultrawork/index.d.ts +2 -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/hooks/unstable-agent-babysitter/task-message-analyzer.d.ts +1 -0
- package/dist/index.js +29256 -25391
- 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 +6 -4
- 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/stop-continuation.d.ts +3 -0
- package/dist/plugin/system-transform.d.ts +5 -1
- 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 +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/ATTRIBUTION.md +10 -0
- package/dist/skills/frontend/SKILL.md +15 -8
- package/dist/skills/frontend/references/design/README.md +10 -2
- package/dist/skills/frontend/references/design/_INDEX.md +2 -0
- package/dist/skills/frontend/references/design/ambience-skill.md +145 -0
- package/dist/skills/frontend/references/design/interaction-skill.md +2 -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 +4 -4
- package/dist/skills/remove-ai-slops/SKILL.md +1 -1
- package/dist/skills/review-work/SKILL.md +101 -419
- 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 +20 -19
- 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-plan/scripts/scaffold-plan.mjs +1 -1
- 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 +37048 -46599
- 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 +103 -6
- package/packages/lsp-core/src/lsp/client-wrapper.ts +50 -16
- 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-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/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/parameters.test.ts +51 -0
- package/packages/lsp-core/src/tools/types.ts +10 -0
- package/packages/lsp-daemon/dist/cli.js +902 -479
- package/packages/lsp-daemon/dist/client.d.ts +10 -0
- package/packages/lsp-daemon/dist/client.js +920 -493
- 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 +824 -402
- package/packages/lsp-daemon/dist/ownership.js +2 -2
- 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 +762 -341
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +272 -138
- package/packages/lsp-tools-mcp/dist/mcp.js +787 -368
- package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
- package/packages/lsp-tools-mcp/dist/tools.js +717 -302
- 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/dist/cli.js +1 -1
- package/packages/omo-codex/plugin/components/git-bash/dist/codex-hook.js +2 -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/git-bash/src/codex-hook.ts +2 -1
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +6 -7
- 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 +941 -540
- 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 +2407 -684
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2411 -689
- 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 +16 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +10 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +2 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -78
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +114 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.d.ts +7 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +50 -42
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +2 -2
- 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.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +40 -37
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +1396 -516
- 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 +2 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +24 -20
- 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.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +16 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +125 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +46 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +28 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +127 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.js +3 -0
- 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 +10 -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 +40 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +8 -6
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +5 -98
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +134 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +52 -73
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +10 -4
- 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 +52 -46
- 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 +34 -28
- 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 +29 -8
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +159 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +42 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +140 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk.ts +3 -0
- 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 +131 -82
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +147 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +16 -22
- 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 +31 -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 +27 -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 +52 -11
- 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 +67 -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/review-blockers.test.ts +9 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-contract.test.ts +137 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +148 -0
- 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 +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/ATTRIBUTION.md +10 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +15 -8
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +10 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/ambience-skill.md +145 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +2 -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 +8 -6
- 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 +29 -31
- package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +34 -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 +2987 -1370
- 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 +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/ATTRIBUTION.md +10 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +15 -8
- package/packages/shared-skills/skills/frontend/references/design/README.md +10 -2
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -0
- package/packages/shared-skills/skills/frontend/references/design/ambience-skill.md +145 -0
- package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +2 -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 +4 -4
- package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +1 -1
- package/packages/shared-skills/skills/review-work/SKILL.md +101 -419
- 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 +20 -19
- 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-plan/scripts/scaffold-plan.mjs +1 -1
- 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 -12149
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9747
- 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,120 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import { existsSync as existsSync3, statSync } from "node:fs";
|
|
5
|
-
import { readFile as
|
|
3
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
4
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
|
|
5
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
6
6
|
import { resolve as resolve3 } from "node:path";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// components/ulw-loop/src/codex-goal-snapshot.ts
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { resolve } from "node:path";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
class CodexGoalSnapshotError extends Error {
|
|
14
|
+
}
|
|
15
|
+
function safeObject(value) {
|
|
16
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
17
|
+
}
|
|
18
|
+
function safeString(value) {
|
|
19
|
+
return typeof value === "string" ? value : "";
|
|
20
|
+
}
|
|
21
|
+
function safeStatusString(value) {
|
|
22
|
+
return typeof value === "string" ? value.trim() : "";
|
|
23
|
+
}
|
|
24
|
+
function normalizeStatus(value) {
|
|
25
|
+
const status = safeStatusString(value).toLowerCase();
|
|
26
|
+
if (status === "complete" || status === "completed" || status === "done")
|
|
27
|
+
return "complete";
|
|
28
|
+
if (status === "cancelled" || status === "canceled")
|
|
29
|
+
return "cancelled";
|
|
30
|
+
if (status === "failed" || status === "failure")
|
|
31
|
+
return "failed";
|
|
32
|
+
if (status === "paused")
|
|
33
|
+
return "paused";
|
|
34
|
+
if (status === "usage_limited")
|
|
35
|
+
return "usage_limited";
|
|
36
|
+
if (status === "budget_limited")
|
|
37
|
+
return "budget_limited";
|
|
38
|
+
if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
|
|
39
|
+
return "active";
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
function normalizeObjective(value) {
|
|
43
|
+
return value.replace(/\s+/g, " ").trim();
|
|
44
|
+
}
|
|
45
|
+
function parseCodexGoalSnapshot(value) {
|
|
46
|
+
const root = safeObject(value);
|
|
47
|
+
const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
|
|
48
|
+
if (goalValue === null || goalValue === undefined || goalValue === false) {
|
|
49
|
+
return { available: false, raw: value };
|
|
50
|
+
}
|
|
51
|
+
const goal = safeObject(goalValue);
|
|
52
|
+
const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
|
|
53
|
+
const status = normalizeStatus(goal["status"] ?? root["status"]);
|
|
54
|
+
return {
|
|
55
|
+
available: Boolean(objective || status !== "unknown"),
|
|
56
|
+
...objective ? { objective } : {},
|
|
57
|
+
status,
|
|
58
|
+
raw: value
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
|
|
62
|
+
if (!raw?.trim())
|
|
63
|
+
return null;
|
|
64
|
+
const trimmed = raw.trim();
|
|
65
|
+
try {
|
|
66
|
+
return parseCodexGoalSnapshot(JSON.parse(trimmed));
|
|
67
|
+
} catch {
|
|
68
|
+
const path = resolve(cwd, trimmed);
|
|
69
|
+
if (!existsSync(path)) {
|
|
70
|
+
throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
80
|
+
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
81
|
+
const errors = [];
|
|
82
|
+
const warnings = [];
|
|
83
|
+
const expected = options.expectedObjective;
|
|
84
|
+
const normalizedExpected = normalizeObjective(expected);
|
|
85
|
+
if (!effectiveSnapshot.available) {
|
|
86
|
+
warnings.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
|
|
87
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
88
|
+
}
|
|
89
|
+
const accepted = new Set([
|
|
90
|
+
normalizedExpected,
|
|
91
|
+
...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))
|
|
92
|
+
].filter(Boolean));
|
|
93
|
+
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
94
|
+
if (actual && !accepted.has(normalizeObjective(actual))) {
|
|
95
|
+
warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
|
|
96
|
+
}
|
|
97
|
+
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
98
|
+
if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
|
|
99
|
+
warnings.push("/goal resume or raise the budget");
|
|
100
|
+
}
|
|
101
|
+
if (actualStatus === "complete")
|
|
102
|
+
warnings.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
|
|
103
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
104
|
+
}
|
|
105
|
+
function formatCodexGoalReconciliation(reconciliation) {
|
|
106
|
+
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
107
|
+
return parts.join(" ");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// components/ulw-loop/src/paths.ts
|
|
13
111
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
14
|
-
//
|
|
112
|
+
// components/ulw-loop/src/constants.ts
|
|
15
113
|
var ULW_LOOP_DIR = ".omo/ulw-loop";
|
|
16
114
|
var ULW_LOOP_BRIEF = "brief.md";
|
|
17
115
|
var ULW_LOOP_GOALS = "goals.json";
|
|
18
116
|
var ULW_LOOP_LEDGER = "ledger.jsonl";
|
|
117
|
+
var ULW_LOOP_STATE_LOCK = ".state.lock";
|
|
19
118
|
var ULW_LOOP_STEERING_MUTATION_KINDS = [
|
|
20
119
|
"add_subgoal",
|
|
21
120
|
"split_subgoal",
|
|
@@ -31,10 +130,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
|
|
|
31
130
|
"regression",
|
|
32
131
|
"adversarial"
|
|
33
132
|
];
|
|
34
|
-
//
|
|
133
|
+
// components/ulw-loop/src/runtime.ts
|
|
35
134
|
class UlwLoopError extends Error {
|
|
36
|
-
code;
|
|
37
|
-
details;
|
|
38
135
|
constructor(message, code, opts) {
|
|
39
136
|
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
|
|
40
137
|
this.name = "UlwLoopError";
|
|
@@ -47,8 +144,8 @@ class UlwLoopError extends Error {
|
|
|
47
144
|
function iso() {
|
|
48
145
|
return new Date().toISOString();
|
|
49
146
|
}
|
|
50
|
-
//
|
|
51
|
-
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"];
|
|
147
|
+
// components/ulw-loop/src/paths.ts
|
|
148
|
+
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
|
|
52
149
|
function normalizeUlwLoopSessionId(sessionId) {
|
|
53
150
|
const trimmed = sessionId?.trim();
|
|
54
151
|
if (!trimmed)
|
|
@@ -90,6 +187,9 @@ function ulwLoopGoalsPath(repoRoot, scope) {
|
|
|
90
187
|
function ulwLoopLedgerPath(repoRoot, scope) {
|
|
91
188
|
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_LEDGER);
|
|
92
189
|
}
|
|
190
|
+
function ulwLoopStateLockPath(repoRoot, scope) {
|
|
191
|
+
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_STATE_LOCK);
|
|
192
|
+
}
|
|
93
193
|
function repoRelative(absolutePath, repoRoot) {
|
|
94
194
|
const slashPrefix = `${repoRoot}/`;
|
|
95
195
|
const backslashPrefix = `${repoRoot}\\`;
|
|
@@ -100,7 +200,10 @@ function repoRelative(absolutePath, repoRoot) {
|
|
|
100
200
|
return absolutePath.split("\\").join("/");
|
|
101
201
|
}
|
|
102
202
|
function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
|
|
103
|
-
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId)
|
|
203
|
+
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
204
|
+
if (sessionId === null) {
|
|
205
|
+
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 } });
|
|
206
|
+
}
|
|
104
207
|
return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
|
|
105
208
|
}
|
|
106
209
|
var PLATFORM_PATH_API = { relative, isAbsolute, sep };
|
|
@@ -113,7 +216,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
|
|
|
113
216
|
return !pathApi.isAbsolute(relativePath);
|
|
114
217
|
}
|
|
115
218
|
|
|
116
|
-
//
|
|
219
|
+
// components/ulw-loop/src/goal-status.ts
|
|
117
220
|
var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
|
|
118
221
|
function aggregateCodexObjectiveForScope(scope) {
|
|
119
222
|
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,182 +299,265 @@ function hasEssentialCriteriaPass(goal) {
|
|
|
196
299
|
return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
|
|
197
300
|
}
|
|
198
301
|
|
|
199
|
-
//
|
|
200
|
-
function
|
|
201
|
-
|
|
302
|
+
// components/ulw-loop/src/checkpoint-codex-validation.ts
|
|
303
|
+
async function validateCheckpointCodexGoal(input) {
|
|
304
|
+
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
305
|
+
const expected = expectedCodexObjective(input.plan, input.goal);
|
|
306
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
307
|
+
expectedObjective: expected,
|
|
308
|
+
...codexGoalMode(input.plan) === "aggregate" ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {}
|
|
309
|
+
});
|
|
310
|
+
if (!reconciliation.ok)
|
|
311
|
+
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
312
|
+
return {
|
|
313
|
+
raw: snapshot?.raw,
|
|
314
|
+
nextActions: reconciliation.warnings,
|
|
315
|
+
warnings: reconciliation.warnings.filter((warning) => warning.startsWith("driver_objective_differs"))
|
|
316
|
+
};
|
|
202
317
|
}
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
318
|
+
function combineCheckpointValidationErrors(codexError, gateError) {
|
|
319
|
+
return new UlwLoopError(`${codexError.message}
|
|
320
|
+
${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
321
|
+
details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
|
|
322
|
+
});
|
|
206
323
|
}
|
|
207
|
-
|
|
208
|
-
|
|
324
|
+
|
|
325
|
+
// components/ulw-loop/src/plan-io.ts
|
|
326
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
327
|
+
import { createReadStream, readdirSync } from "node:fs";
|
|
328
|
+
import { appendFile, mkdir, readFile as readFile2, rename, writeFile } from "node:fs/promises";
|
|
329
|
+
import { createInterface } from "node:readline";
|
|
330
|
+
|
|
331
|
+
// components/ulw-loop/src/plan-missing-recovery.ts
|
|
332
|
+
var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
|
|
333
|
+
function planMissingRecovery(existingSessionIds) {
|
|
334
|
+
const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
|
|
335
|
+
if (existingSessionIds.length === 0)
|
|
336
|
+
return { message: lines.join(`
|
|
337
|
+
`) };
|
|
338
|
+
lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
|
|
339
|
+
return { message: lines.join(`
|
|
340
|
+
`), details: { existingSessionIds } };
|
|
341
|
+
}
|
|
342
|
+
function sessionScopeRequiredMessage(flag, existingSessionIds) {
|
|
343
|
+
const lines = [
|
|
344
|
+
"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.",
|
|
345
|
+
`Recovery: pass the scope explicitly: \`${flag} <id>\` (subprocess, eval, and hook contexts do not inherit the session env).`
|
|
346
|
+
];
|
|
347
|
+
if (existingSessionIds.length > 0) {
|
|
348
|
+
lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}.`);
|
|
349
|
+
}
|
|
350
|
+
return lines.join(`
|
|
351
|
+
`);
|
|
209
352
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
353
|
+
function sessionIdRequiredMessage(flag) {
|
|
354
|
+
return [
|
|
355
|
+
`${flag} requires a non-empty value.`,
|
|
356
|
+
"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),",
|
|
357
|
+
`so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
|
|
358
|
+
].join(`
|
|
359
|
+
`);
|
|
215
360
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
361
|
+
|
|
362
|
+
// components/ulw-loop/src/state-lock.ts
|
|
363
|
+
import { randomUUID } from "node:crypto";
|
|
364
|
+
import { closeSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
365
|
+
import { dirname } from "node:path";
|
|
366
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
367
|
+
var ULW_LOOP_LOCK_TIMEOUT_CODE = "ULW_LOOP_LOCK_TIMEOUT";
|
|
368
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
369
|
+
var DEFAULT_STALE_MS = 60000;
|
|
370
|
+
var MIN_DELAY_MS = 5;
|
|
371
|
+
var MAX_DELAY_MS = 100;
|
|
372
|
+
var SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
|
|
373
|
+
async function withStateLock(lockPath, fn, options = {}) {
|
|
374
|
+
const token = await acquireAsync(lockPath, options);
|
|
222
375
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (error instanceof Error)
|
|
227
|
-
return false;
|
|
228
|
-
throw error;
|
|
376
|
+
return await fn();
|
|
377
|
+
} finally {
|
|
378
|
+
release(lockPath, token);
|
|
229
379
|
}
|
|
230
380
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
381
|
+
function withStateLockSync(lockPath, fn, options = {}) {
|
|
382
|
+
const token = acquireSync(lockPath, options);
|
|
383
|
+
try {
|
|
384
|
+
return fn();
|
|
385
|
+
} finally {
|
|
386
|
+
release(lockPath, token);
|
|
238
387
|
}
|
|
239
|
-
if (!textMentionsUlwLoopPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id))
|
|
240
|
-
return false;
|
|
241
|
-
if (!textHasCompletionValidationEvidence(evidence))
|
|
242
|
-
return false;
|
|
243
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
244
|
-
}
|
|
245
|
-
async function canReconcileActiveFinalTaskScopedAggregateSnapshot(repoRoot, plan, goal, snapshotObjective, evidence, scope) {
|
|
246
|
-
if (codexGoalMode(plan) !== "aggregate")
|
|
247
|
-
return false;
|
|
248
|
-
if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id)
|
|
249
|
-
return false;
|
|
250
|
-
if (!isFinalRunCompletionCandidate(plan, goal))
|
|
251
|
-
return false;
|
|
252
|
-
if (!textHasCompletionValidationEvidence(evidence))
|
|
253
|
-
return false;
|
|
254
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
255
388
|
}
|
|
256
|
-
function
|
|
257
|
-
return
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
389
|
+
function isStateLockTimeout(error) {
|
|
390
|
+
return error instanceof UlwLoopError && error.code === ULW_LOOP_LOCK_TIMEOUT_CODE;
|
|
391
|
+
}
|
|
392
|
+
async function acquireAsync(lockPath, options) {
|
|
393
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
394
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
395
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
396
|
+
for (let attempt = 0;; ) {
|
|
397
|
+
const outcome = attemptOnce(lockPath, staleMs);
|
|
398
|
+
if (outcome.kind === "acquired")
|
|
399
|
+
return outcome.token;
|
|
400
|
+
if (outcome.kind === "retry")
|
|
401
|
+
continue;
|
|
402
|
+
if (Date.now() >= deadline)
|
|
403
|
+
throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
404
|
+
await sleep(backoffMs(attempt));
|
|
405
|
+
attempt += 1;
|
|
406
|
+
}
|
|
262
407
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
408
|
+
function acquireSync(lockPath, options) {
|
|
409
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
410
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
411
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
412
|
+
for (let attempt = 0;; ) {
|
|
413
|
+
const outcome = attemptOnce(lockPath, staleMs);
|
|
414
|
+
if (outcome.kind === "acquired")
|
|
415
|
+
return outcome.token;
|
|
416
|
+
if (outcome.kind === "retry")
|
|
417
|
+
continue;
|
|
418
|
+
if (Date.now() >= deadline)
|
|
419
|
+
throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
420
|
+
Atomics.wait(SLEEP_CELL, 0, 0, backoffMs(attempt));
|
|
421
|
+
attempt += 1;
|
|
266
422
|
}
|
|
267
|
-
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
423
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
424
|
+
function attemptOnce(lockPath, staleMs) {
|
|
425
|
+
try {
|
|
426
|
+
const token = tryCreate(lockPath);
|
|
427
|
+
if (token !== null)
|
|
428
|
+
return { kind: "acquired", token };
|
|
429
|
+
const snapshot = readSnapshot(lockPath);
|
|
430
|
+
if (snapshot === null)
|
|
431
|
+
return { kind: "retry" };
|
|
432
|
+
if (isStale(snapshot, staleMs) && reclaim(lockPath, snapshot.raw))
|
|
433
|
+
return { kind: "retry" };
|
|
434
|
+
return { kind: "wait" };
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (hasCode(error, "EINTR"))
|
|
437
|
+
return { kind: "wait" };
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
276
440
|
}
|
|
277
|
-
function
|
|
278
|
-
|
|
441
|
+
function tryCreate(lockPath) {
|
|
442
|
+
let fd;
|
|
443
|
+
try {
|
|
444
|
+
fd = openSync(lockPath, "wx");
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (hasCode(error, "EEXIST"))
|
|
447
|
+
return null;
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
const record = { pid: process.pid, createdAt: new Date().toISOString(), token: randomUUID() };
|
|
451
|
+
try {
|
|
452
|
+
writeSync(fd, JSON.stringify(record));
|
|
453
|
+
} catch (error) {
|
|
454
|
+
closeSync(fd);
|
|
455
|
+
try {
|
|
456
|
+
unlinkSync(lockPath);
|
|
457
|
+
} catch {}
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
closeSync(fd);
|
|
461
|
+
return record.token;
|
|
279
462
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
463
|
+
function readSnapshot(lockPath) {
|
|
464
|
+
try {
|
|
465
|
+
const raw = readFileSync(lockPath, "utf8");
|
|
466
|
+
const ageMs = Date.now() - statSync(lockPath).mtimeMs;
|
|
467
|
+
return { raw, record: parseRecord(raw), ageMs };
|
|
468
|
+
} catch (error) {
|
|
469
|
+
if (hasCode(error, "ENOENT"))
|
|
470
|
+
return null;
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
282
473
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
474
|
+
function parseRecord(raw) {
|
|
475
|
+
try {
|
|
476
|
+
const parsed = JSON.parse(raw);
|
|
477
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
478
|
+
return null;
|
|
479
|
+
const record = parsed;
|
|
480
|
+
const pid = record["pid"];
|
|
481
|
+
const createdAt = record["createdAt"];
|
|
482
|
+
const token = record["token"];
|
|
483
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof createdAt !== "string")
|
|
484
|
+
return null;
|
|
485
|
+
if (typeof token !== "string" || token.length === 0)
|
|
486
|
+
return null;
|
|
487
|
+
return { pid, createdAt, token };
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (error instanceof SyntaxError)
|
|
490
|
+
return null;
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
294
493
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
494
|
+
function isStale(snapshot, staleMs) {
|
|
495
|
+
if (snapshot.record === null)
|
|
496
|
+
return snapshot.ageMs > staleMs;
|
|
497
|
+
return !isProcessAlive(snapshot.record.pid);
|
|
297
498
|
}
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
499
|
+
function isProcessAlive(pid) {
|
|
500
|
+
try {
|
|
501
|
+
process.kill(pid, 0);
|
|
502
|
+
return true;
|
|
503
|
+
} catch (error) {
|
|
504
|
+
if (hasCode(error, "ESRCH"))
|
|
505
|
+
return false;
|
|
506
|
+
if (hasCode(error, "EPERM"))
|
|
507
|
+
return true;
|
|
508
|
+
throw error;
|
|
303
509
|
}
|
|
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
510
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
511
|
+
function reclaim(lockPath, expectedRaw) {
|
|
512
|
+
const current = readSnapshot(lockPath);
|
|
513
|
+
if (current === null)
|
|
514
|
+
return true;
|
|
515
|
+
if (current.raw !== expectedRaw)
|
|
516
|
+
return false;
|
|
318
517
|
try {
|
|
319
|
-
|
|
320
|
-
} catch {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
|
|
324
|
-
}
|
|
325
|
-
try {
|
|
326
|
-
return parseCodexGoalSnapshot(JSON.parse(await readFile2(path, "utf-8")));
|
|
327
|
-
} catch (error) {
|
|
328
|
-
throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
|
|
329
|
-
}
|
|
518
|
+
unlinkSync(lockPath);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
if (!hasCode(error, "ENOENT"))
|
|
521
|
+
throw error;
|
|
330
522
|
}
|
|
523
|
+
return true;
|
|
331
524
|
}
|
|
332
|
-
function
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
warnings.push(message);
|
|
342
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
343
|
-
}
|
|
344
|
-
const expected = normalizeObjective2(options.expectedObjective);
|
|
345
|
-
const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective2(objective))].filter(Boolean));
|
|
346
|
-
const actual = normalizeObjective2(effectiveSnapshot.objective ?? "");
|
|
347
|
-
if (!actual) {
|
|
348
|
-
errors.push("Codex goal snapshot is missing objective text.");
|
|
349
|
-
} else if (!accepted.has(actual)) {
|
|
350
|
-
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
|
|
351
|
-
}
|
|
352
|
-
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
|
|
353
|
-
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
354
|
-
if (!allowed.includes(actualStatus)) {
|
|
355
|
-
errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
|
|
356
|
-
}
|
|
357
|
-
if (options.requireComplete && actualStatus !== "complete") {
|
|
358
|
-
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.');
|
|
525
|
+
function release(lockPath, token) {
|
|
526
|
+
const current = readSnapshot(lockPath);
|
|
527
|
+
if (current === null || current.record?.token !== token)
|
|
528
|
+
return;
|
|
529
|
+
try {
|
|
530
|
+
unlinkSync(lockPath);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (!hasCode(error, "ENOENT"))
|
|
533
|
+
throw error;
|
|
359
534
|
}
|
|
360
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
361
535
|
}
|
|
362
|
-
function
|
|
363
|
-
const
|
|
364
|
-
return
|
|
536
|
+
function backoffMs(attempt) {
|
|
537
|
+
const exponential = Math.min(MAX_DELAY_MS, MIN_DELAY_MS * 2 ** attempt);
|
|
538
|
+
return exponential + Math.random() * MIN_DELAY_MS;
|
|
539
|
+
}
|
|
540
|
+
function lockTimeout(lockPath, timeoutMs) {
|
|
541
|
+
const holder = readSnapshot(lockPath)?.record;
|
|
542
|
+
const owner = holder === undefined || holder === null ? "another process" : `pid ${holder.pid}`;
|
|
543
|
+
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, {
|
|
544
|
+
details: {
|
|
545
|
+
lockPath,
|
|
546
|
+
timeoutMs,
|
|
547
|
+
...holder === undefined || holder === null ? {} : { holderPid: holder.pid }
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function hasCode(error, code) {
|
|
552
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
365
553
|
}
|
|
366
554
|
|
|
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";
|
|
555
|
+
// components/ulw-loop/src/plan-io.ts
|
|
371
556
|
var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
|
|
372
557
|
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
558
|
var locks = new Map;
|
|
374
|
-
|
|
559
|
+
var heldLocks = new AsyncLocalStorage;
|
|
560
|
+
function hasCode2(error, code) {
|
|
375
561
|
return error instanceof Error && "code" in error && error.code === code;
|
|
376
562
|
}
|
|
377
563
|
function isLegacyEnumeratedAggregateObjective(objective) {
|
|
@@ -386,8 +572,10 @@ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
|
386
572
|
if (fn === undefined)
|
|
387
573
|
throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
|
|
388
574
|
const lockKey = `${repoRoot}\x00${ulwLoopRelativeDir(scope)}`;
|
|
575
|
+
const lockPath = ulwLoopStateLockPath(repoRoot, scope);
|
|
576
|
+
const locked = () => withStateLock(lockPath, () => heldLocks.run(new Set([...heldLocks.getStore() ?? [], lockKey]), fn));
|
|
389
577
|
const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
|
|
390
|
-
const run = prior.then(
|
|
578
|
+
const run = prior.then(locked, locked);
|
|
391
579
|
const gate = run.then(() => {
|
|
392
580
|
return;
|
|
393
581
|
}, () => {
|
|
@@ -404,11 +592,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
404
592
|
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
405
593
|
let raw;
|
|
406
594
|
try {
|
|
407
|
-
raw = await
|
|
595
|
+
raw = await readFile2(path, "utf8");
|
|
408
596
|
} catch (error) {
|
|
409
|
-
if (!
|
|
597
|
+
if (!hasCode2(error, "ENOENT"))
|
|
410
598
|
throw error;
|
|
411
|
-
|
|
599
|
+
const recovery = planMissingRecovery(listUlwLoopSessionIds(repoRoot));
|
|
600
|
+
throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.
|
|
601
|
+
${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...recovery.details === undefined ? {} : { details: recovery.details } });
|
|
412
602
|
}
|
|
413
603
|
const parsed = JSON.parse(raw);
|
|
414
604
|
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
|
@@ -416,6 +606,9 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
416
606
|
}
|
|
417
607
|
const previousObjective = parsed.codexObjective;
|
|
418
608
|
if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && isLegacyEnumeratedAggregateObjective(previousObjective)) {
|
|
609
|
+
if (!(heldLocks.getStore()?.has(`${repoRoot}\x00${ulwLoopRelativeDir(scope)}`) ?? false)) {
|
|
610
|
+
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");
|
|
611
|
+
}
|
|
419
612
|
const now = iso();
|
|
420
613
|
parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
|
|
421
614
|
parsed.codexObjectiveAliases = [...new Set([...parsed.codexObjectiveAliases ?? [], previousObjective])];
|
|
@@ -431,6 +624,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
431
624
|
}
|
|
432
625
|
return parsed;
|
|
433
626
|
}
|
|
627
|
+
function listUlwLoopSessionIds(repoRoot) {
|
|
628
|
+
try {
|
|
629
|
+
return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
630
|
+
} catch {
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
}
|
|
434
634
|
async function writePlan(repoRoot, plan, scope) {
|
|
435
635
|
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
436
636
|
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
@@ -459,7 +659,7 @@ async function* ledgerLines(repoRoot, scope) {
|
|
|
459
659
|
yield line;
|
|
460
660
|
}
|
|
461
661
|
} catch (error) {
|
|
462
|
-
if (!
|
|
662
|
+
if (!hasCode2(error, "ENOENT"))
|
|
463
663
|
throw error;
|
|
464
664
|
} finally {
|
|
465
665
|
lines.close();
|
|
@@ -482,7 +682,7 @@ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
|
|
|
482
682
|
return;
|
|
483
683
|
}
|
|
484
684
|
|
|
485
|
-
//
|
|
685
|
+
// components/ulw-loop/src/evidence.ts
|
|
486
686
|
function ulwLoopFail(message, code, details) {
|
|
487
687
|
throw new UlwLoopError(message, code, { details });
|
|
488
688
|
}
|
|
@@ -588,57 +788,216 @@ function requireEssentialCriteriaPass(goal) {
|
|
|
588
788
|
});
|
|
589
789
|
}
|
|
590
790
|
|
|
591
|
-
//
|
|
791
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
592
792
|
import { resolve as resolve2 } from "node:path";
|
|
593
793
|
|
|
594
|
-
//
|
|
595
|
-
var PLACEHOLDER_PATTERN = /^(
|
|
794
|
+
// components/ulw-loop/src/quality-gate-fields.ts
|
|
795
|
+
var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
796
|
+
var activeCollector;
|
|
797
|
+
function withQualityGateCollector(operation) {
|
|
798
|
+
const previous = activeCollector;
|
|
799
|
+
const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
|
|
800
|
+
activeCollector = collector;
|
|
801
|
+
try {
|
|
802
|
+
const result = operation();
|
|
803
|
+
if (collector.defects.length === 0)
|
|
804
|
+
return result;
|
|
805
|
+
throwQualityGateDefects(collector.defects);
|
|
806
|
+
} finally {
|
|
807
|
+
activeCollector = previous;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function throwQualityGateDefects(defects) {
|
|
811
|
+
const uniqueDefects = [
|
|
812
|
+
...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
|
|
813
|
+
];
|
|
814
|
+
const fields = uniqueDefects.slice(0, 25);
|
|
815
|
+
const truncated = uniqueDefects.length > fields.length;
|
|
816
|
+
const message = [
|
|
817
|
+
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
818
|
+
...fields.map((item) => `- ${item.field}: ${item.message}`)
|
|
819
|
+
].join(`
|
|
820
|
+
`);
|
|
821
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
822
|
+
details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
|
|
823
|
+
});
|
|
824
|
+
}
|
|
596
825
|
function invalid(message, field) {
|
|
597
|
-
|
|
826
|
+
if (activeCollector === undefined)
|
|
827
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
|
|
828
|
+
activeCollector.defects.push({ field, message });
|
|
829
|
+
activeCollector.poisonedFields.add(field);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
function isPoisoned(field) {
|
|
833
|
+
return activeCollector?.poisonedFields.has(field) ?? false;
|
|
834
|
+
}
|
|
835
|
+
function markPoisonedArtifactKind(id) {
|
|
836
|
+
activeCollector?.poisonedArtifactKinds.add(id);
|
|
837
|
+
}
|
|
838
|
+
function isPoisonedArtifactKind(id) {
|
|
839
|
+
return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
|
|
598
840
|
}
|
|
599
841
|
function isRecord(value) {
|
|
600
842
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
601
843
|
}
|
|
602
844
|
function section(value, field) {
|
|
603
|
-
|
|
845
|
+
if (isRecord(value))
|
|
846
|
+
return value;
|
|
847
|
+
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);
|
|
848
|
+
return {};
|
|
604
849
|
}
|
|
605
850
|
function textField(value, field) {
|
|
606
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
851
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
607
852
|
invalid(`Final quality gate requires non-empty ${field}.`, field);
|
|
853
|
+
return "";
|
|
854
|
+
}
|
|
608
855
|
const trimmed = value.trim();
|
|
609
856
|
if (PLACEHOLDER_PATTERN.test(trimmed))
|
|
610
857
|
invalid(`Final quality gate rejects placeholder ${field}.`, field);
|
|
611
858
|
return trimmed;
|
|
612
859
|
}
|
|
613
860
|
function numberField(value, field) {
|
|
614
|
-
|
|
861
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
862
|
+
return value;
|
|
863
|
+
invalid(`Final quality gate requires numeric ${field}.`, field);
|
|
864
|
+
return 0;
|
|
615
865
|
}
|
|
616
866
|
function stringArray(value, field) {
|
|
617
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
618
|
-
|
|
867
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
868
|
+
invalid(`Final quality gate requires ${field}.`, field);
|
|
869
|
+
return [];
|
|
870
|
+
}
|
|
619
871
|
return value.map((item) => textField(item, field));
|
|
620
872
|
}
|
|
621
873
|
function emptyBlockers(value, field) {
|
|
622
874
|
if (Array.isArray(value) && value.length === 0)
|
|
623
875
|
return [];
|
|
624
876
|
invalid(`${field} must be empty.`, field);
|
|
877
|
+
return [];
|
|
625
878
|
}
|
|
626
879
|
function literal(value, expected, field) {
|
|
627
880
|
if (value === expected)
|
|
628
881
|
return expected;
|
|
629
882
|
invalid(`${field} must be ${String(expected)}.`, field);
|
|
883
|
+
return expected;
|
|
630
884
|
}
|
|
631
885
|
|
|
632
|
-
//
|
|
886
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
887
|
+
var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
|
|
888
|
+
var SUPPORTED_KINDS = [
|
|
889
|
+
"cli-transcript",
|
|
890
|
+
"log",
|
|
891
|
+
"screenshot",
|
|
892
|
+
"image",
|
|
893
|
+
"http-dump",
|
|
894
|
+
"data-diff"
|
|
895
|
+
];
|
|
896
|
+
var COMPATIBLE_KINDS = {
|
|
897
|
+
cli: ["cli-transcript", "log"],
|
|
898
|
+
tmux: ["cli-transcript", "log"],
|
|
899
|
+
http: ["http-dump"],
|
|
900
|
+
browser: ["screenshot", "image"],
|
|
901
|
+
gui: ["screenshot", "image"],
|
|
902
|
+
data: ["data-diff"]
|
|
903
|
+
};
|
|
904
|
+
function isSupportedSurface(value) {
|
|
905
|
+
return SUPPORTED_SURFACES.some((surface) => surface === value);
|
|
906
|
+
}
|
|
907
|
+
function isSupportedKind(value) {
|
|
908
|
+
return SUPPORTED_KINDS.some((kind) => kind === value);
|
|
909
|
+
}
|
|
910
|
+
function surfaceField(value, field) {
|
|
911
|
+
if (isSupportedSurface(value))
|
|
912
|
+
return value;
|
|
913
|
+
invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
|
|
914
|
+
return "cli";
|
|
915
|
+
}
|
|
916
|
+
function kindField(value, field) {
|
|
917
|
+
if (isSupportedKind(value))
|
|
918
|
+
return value;
|
|
919
|
+
invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
|
|
920
|
+
return "log";
|
|
921
|
+
}
|
|
922
|
+
function compatibleKindsFor(surface) {
|
|
923
|
+
return COMPATIBLE_KINDS[surface];
|
|
924
|
+
}
|
|
925
|
+
function artifactCompatible(surface, kind) {
|
|
926
|
+
return compatibleKindsFor(surface).includes(kind);
|
|
927
|
+
}
|
|
928
|
+
function checkFile(path, field, opts) {
|
|
929
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
930
|
+
return;
|
|
931
|
+
const absolute = resolve2(opts.repoRoot, path);
|
|
932
|
+
if (!opts.fs.existsSync(absolute)) {
|
|
933
|
+
invalid(`${field} must point to an existing artifact.`, field);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (opts.fs.statSync(absolute).size <= 0)
|
|
937
|
+
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
938
|
+
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve2(opts.repoRoot, opts.currentAttemptDir)))
|
|
939
|
+
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
940
|
+
}
|
|
941
|
+
function artifactMap(refs) {
|
|
942
|
+
const byId = new Map;
|
|
943
|
+
for (const ref of refs) {
|
|
944
|
+
if (byId.has(ref.id))
|
|
945
|
+
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
946
|
+
byId.set(ref.id, ref);
|
|
947
|
+
}
|
|
948
|
+
return byId;
|
|
949
|
+
}
|
|
950
|
+
function parseArtifactRefs(value, opts) {
|
|
951
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
952
|
+
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
return value.flatMap((item, index) => {
|
|
956
|
+
const prefix = `manualQa.artifactRefs[${index}]`;
|
|
957
|
+
const ref = section(item, prefix);
|
|
958
|
+
if (isPoisoned(prefix))
|
|
959
|
+
return [];
|
|
960
|
+
const pathField = `${prefix}.path`;
|
|
961
|
+
const idField = `${prefix}.id`;
|
|
962
|
+
const kindFieldName = `${prefix}.kind`;
|
|
963
|
+
const descriptionField = `${prefix}.description`;
|
|
964
|
+
const path = textField(ref["path"], pathField);
|
|
965
|
+
const id = textField(ref["id"], idField);
|
|
966
|
+
const kind = kindField(ref["kind"], kindFieldName);
|
|
967
|
+
if (isPoisoned(kindFieldName))
|
|
968
|
+
markPoisonedArtifactKind(id);
|
|
969
|
+
const description = textField(ref["description"], descriptionField);
|
|
970
|
+
checkFile(path, pathField, opts);
|
|
971
|
+
return [{ id, kind, description, path }];
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
function referencedArtifacts(value, field, byId) {
|
|
975
|
+
const ids = stringArray(value, field);
|
|
976
|
+
if (isPoisoned(field))
|
|
977
|
+
return [];
|
|
978
|
+
return ids.flatMap((id) => {
|
|
979
|
+
const artifact = byId.get(id);
|
|
980
|
+
if (artifact === undefined) {
|
|
981
|
+
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
982
|
+
return [];
|
|
983
|
+
}
|
|
984
|
+
return [artifact];
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// components/ulw-loop/src/quality-gate-verdicts.ts
|
|
633
989
|
function passedVerdict(value, field) {
|
|
634
|
-
if (value === "not_applicable")
|
|
990
|
+
if (value === "not_applicable") {
|
|
635
991
|
invalid(`${field} must not be not_applicable.`, field);
|
|
992
|
+
return "passed";
|
|
993
|
+
}
|
|
636
994
|
return literal(value, "passed", field);
|
|
637
995
|
}
|
|
638
996
|
function codeQualityStatusField(value, field) {
|
|
639
997
|
if (value === "CLEAR" || value === "WATCH")
|
|
640
998
|
return value;
|
|
641
999
|
invalid(`${field} must be CLEAR or WATCH.`, field);
|
|
1000
|
+
return "CLEAR";
|
|
642
1001
|
}
|
|
643
1002
|
function adversarialVerdict(row, field) {
|
|
644
1003
|
const value = row["verdict"];
|
|
@@ -647,10 +1006,83 @@ function adversarialVerdict(row, field) {
|
|
|
647
1006
|
if (value === "not_applicable") {
|
|
648
1007
|
return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
|
|
649
1008
|
}
|
|
650
|
-
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
1009
|
+
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
1010
|
+
return { verdict: "passed" };
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// components/ulw-loop/src/surface.ts
|
|
1014
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
1015
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
1016
|
+
import { fileURLToPath } from "node:url";
|
|
1017
|
+
var REVIEWER_ROLES_BY_SURFACE = {
|
|
1018
|
+
lazycodex: {
|
|
1019
|
+
codeReview: "lazycodex-code-reviewer",
|
|
1020
|
+
manualQa: "lazycodex-qa-executor",
|
|
1021
|
+
gateReview: "lazycodex-gate-reviewer"
|
|
1022
|
+
},
|
|
1023
|
+
"omo-senpi": {
|
|
1024
|
+
codeReview: "omo-senpi-code-reviewer",
|
|
1025
|
+
manualQa: "omo-senpi-qa-executor",
|
|
1026
|
+
gateReview: "omo-senpi-gate-reviewer"
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
|
|
1030
|
+
var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
|
|
1031
|
+
lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
|
|
1032
|
+
"omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
|
|
1033
|
+
};
|
|
1034
|
+
var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
|
|
1035
|
+
lazycodex: ["codeReview"],
|
|
1036
|
+
"omo-senpi": []
|
|
1037
|
+
};
|
|
1038
|
+
var GATE_SECTION_BY_ACCEPTOR = {
|
|
1039
|
+
lazycodex: {
|
|
1040
|
+
codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
|
|
1041
|
+
manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
|
|
1042
|
+
gateReview: [
|
|
1043
|
+
REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
|
|
1044
|
+
"category:deep",
|
|
1045
|
+
"category:unspecified-high",
|
|
1046
|
+
"category:unspecified-low",
|
|
1047
|
+
"main-session"
|
|
1048
|
+
]
|
|
1049
|
+
},
|
|
1050
|
+
"omo-senpi": {
|
|
1051
|
+
manualQa: ["main-session"],
|
|
1052
|
+
gateReview: ["category:deep", "category:unspecified-high", "category:unspecified-low"]
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
function reviewerRolesFor(surface) {
|
|
1056
|
+
return REVIEWER_ROLES_BY_SURFACE[surface];
|
|
1057
|
+
}
|
|
1058
|
+
var SURFACE_MARKER_FILENAME = "surface.json";
|
|
1059
|
+
var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
|
|
1060
|
+
function parseSurface(value) {
|
|
1061
|
+
return value === "lazycodex" || value === "omo-senpi" ? value : null;
|
|
1062
|
+
}
|
|
1063
|
+
function resolveToolkitSurface(options) {
|
|
1064
|
+
const env = options?.env ?? process.env;
|
|
1065
|
+
const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
|
|
1066
|
+
if (fromEnv !== null)
|
|
1067
|
+
return fromEnv;
|
|
1068
|
+
const entryDir = options?.entryDir ?? dirname2(fileURLToPath(import.meta.url));
|
|
1069
|
+
const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
|
|
1070
|
+
return readMarkerSurface(markerPath) ?? "lazycodex";
|
|
1071
|
+
}
|
|
1072
|
+
function readMarkerSurface(markerPath) {
|
|
1073
|
+
try {
|
|
1074
|
+
if (!existsSync2(markerPath))
|
|
1075
|
+
return null;
|
|
1076
|
+
const parsed = JSON.parse(readFileSync2(markerPath, "utf8"));
|
|
1077
|
+
return parseSurface(parsed["surface"]);
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
if (error instanceof Error)
|
|
1080
|
+
return null;
|
|
1081
|
+
throw error;
|
|
1082
|
+
}
|
|
651
1083
|
}
|
|
652
1084
|
|
|
653
|
-
//
|
|
1085
|
+
// components/ulw-loop/src/quality-gate-blockers.ts
|
|
654
1086
|
var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
655
1087
|
var URL_PATTERN = /https?:\/\/\S+/g;
|
|
656
1088
|
var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
|
|
@@ -692,131 +1124,68 @@ function clearGoalBlockerFields(goal) {
|
|
|
692
1124
|
Reflect.deleteProperty(goal, key);
|
|
693
1125
|
}
|
|
694
1126
|
|
|
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);
|
|
1127
|
+
// components/ulw-loop/src/quality-gate.ts
|
|
1128
|
+
function reviewerAcceptorField(value, surface, sectionName) {
|
|
1129
|
+
const field = `${sectionName}.by`;
|
|
1130
|
+
const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
|
|
1131
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1132
|
+
textField(value, field);
|
|
1133
|
+
return accepted?.[0] ?? "";
|
|
745
1134
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (byId.has(ref.id))
|
|
751
|
-
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
752
|
-
byId.set(ref.id, ref);
|
|
753
|
-
}
|
|
754
|
-
return byId;
|
|
755
|
-
}
|
|
756
|
-
function parseArtifactRefs(value, opts) {
|
|
757
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
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
|
-
});
|
|
1135
|
+
const actual = textField(value, field);
|
|
1136
|
+
if (accepted === undefined || !accepted.includes(actual))
|
|
1137
|
+
invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
|
|
1138
|
+
return actual;
|
|
778
1139
|
}
|
|
779
1140
|
function validateQualityGate(input, opts) {
|
|
780
|
-
|
|
781
|
-
|
|
1141
|
+
return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
|
|
1142
|
+
}
|
|
1143
|
+
function validateQualityGateUncollected(input, opts) {
|
|
1144
|
+
const surface = opts?.reviewerSurface ?? "lazycodex";
|
|
1145
|
+
const raw = input;
|
|
1146
|
+
const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
|
|
1147
|
+
for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
|
|
1148
|
+
section(gate[name], name);
|
|
1149
|
+
for (const name of Object.keys(gate)) {
|
|
1150
|
+
if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
|
|
1151
|
+
invalid("omo-senpi gate has no codeReview lane.", name);
|
|
1152
|
+
}
|
|
1153
|
+
if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
|
|
1154
|
+
invalid("omo-senpi gate has no codeReview lane.", "codeReview");
|
|
782
1155
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
783
1156
|
const gateReview = section(gate["gateReview"], "gateReview");
|
|
784
1157
|
const iteration = section(gate["iteration"], "iteration");
|
|
785
1158
|
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
|
|
1159
|
+
const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
|
|
1160
|
+
const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
|
|
1161
|
+
const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
|
|
1162
|
+
const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
|
|
1163
|
+
const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
|
|
786
1164
|
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
|
|
787
1165
|
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
|
|
788
|
-
if (passCount < totalCriteria)
|
|
1166
|
+
if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
|
|
789
1167
|
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
|
|
790
1168
|
const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
|
|
791
1169
|
const byId = artifactMap(artifactRefs);
|
|
792
1170
|
const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
|
|
793
1171
|
const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
|
|
794
|
-
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
795
1172
|
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
|
-
},
|
|
1173
|
+
if (!isPoisoned("gateReview.reportPath"))
|
|
1174
|
+
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
1175
|
+
const common = {
|
|
807
1176
|
manualQa: {
|
|
808
|
-
by:
|
|
1177
|
+
by: manualQaBy,
|
|
809
1178
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
810
|
-
evidence:
|
|
1179
|
+
evidence: manualQaEvidence,
|
|
811
1180
|
surfaceEvidence,
|
|
812
1181
|
adversarialCases,
|
|
813
1182
|
artifactRefs
|
|
814
1183
|
},
|
|
815
1184
|
gateReview: {
|
|
816
|
-
by:
|
|
1185
|
+
by: gateReviewBy,
|
|
817
1186
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
818
1187
|
reportPath: gateReportPath,
|
|
819
|
-
evidence:
|
|
1188
|
+
evidence: gateReviewEvidence,
|
|
820
1189
|
blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
|
|
821
1190
|
},
|
|
822
1191
|
iteration: {
|
|
@@ -834,17 +1203,41 @@ function validateQualityGate(input, opts) {
|
|
|
834
1203
|
adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
|
|
835
1204
|
}
|
|
836
1205
|
};
|
|
1206
|
+
if (surface === "omo-senpi")
|
|
1207
|
+
return { surface, ...common };
|
|
1208
|
+
if (codeReview === undefined)
|
|
1209
|
+
return { surface, ...common };
|
|
1210
|
+
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
1211
|
+
checkFile(codeReportPath, "codeReview.reportPath", opts);
|
|
1212
|
+
return {
|
|
1213
|
+
surface,
|
|
1214
|
+
...common,
|
|
1215
|
+
codeReview: {
|
|
1216
|
+
by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
|
|
1217
|
+
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
1218
|
+
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
1219
|
+
reportPath: codeReportPath,
|
|
1220
|
+
evidence: textField(codeReview["evidence"], "codeReview.evidence"),
|
|
1221
|
+
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
837
1224
|
}
|
|
838
1225
|
function parseSurfaceEvidence(value, byId) {
|
|
839
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1226
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
840
1227
|
invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
|
|
841
|
-
|
|
1228
|
+
return [];
|
|
1229
|
+
}
|
|
1230
|
+
return value.flatMap((item, index) => {
|
|
842
1231
|
const row = section(item, `manualQa.surfaceEvidence[${index}]`);
|
|
1232
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
|
|
1233
|
+
return [];
|
|
843
1234
|
const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
|
|
844
1235
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
|
|
845
1236
|
for (const artifact of artifacts) {
|
|
1237
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
|
|
1238
|
+
continue;
|
|
846
1239
|
if (!artifactCompatible(surface, artifact.kind)) {
|
|
847
|
-
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
|
|
1240
|
+
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
|
|
848
1241
|
}
|
|
849
1242
|
}
|
|
850
1243
|
return {
|
|
@@ -858,10 +1251,14 @@ function parseSurfaceEvidence(value, byId) {
|
|
|
858
1251
|
});
|
|
859
1252
|
}
|
|
860
1253
|
function parseAdversarialCases(value, byId) {
|
|
861
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1254
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
862
1255
|
invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
|
|
863
|
-
|
|
1256
|
+
return [];
|
|
1257
|
+
}
|
|
1258
|
+
return value.flatMap((item, index) => {
|
|
864
1259
|
const row = section(item, `manualQa.adversarialCases[${index}]`);
|
|
1260
|
+
if (isPoisoned(`manualQa.adversarialCases[${index}]`))
|
|
1261
|
+
return [];
|
|
865
1262
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
|
|
866
1263
|
const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
|
|
867
1264
|
return {
|
|
@@ -876,8 +1273,8 @@ function parseAdversarialCases(value, byId) {
|
|
|
876
1273
|
});
|
|
877
1274
|
}
|
|
878
1275
|
|
|
879
|
-
//
|
|
880
|
-
import { readFile as
|
|
1276
|
+
// components/ulw-loop/src/cli-arg-parser.ts
|
|
1277
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
881
1278
|
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
1279
|
var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
|
|
883
1280
|
function hasFlag(argv, flag) {
|
|
@@ -925,7 +1322,7 @@ async function readJsonInput(value) {
|
|
|
925
1322
|
if (value === undefined)
|
|
926
1323
|
return;
|
|
927
1324
|
try {
|
|
928
|
-
return JSON.parse(looksLikeJson(value) ? value : await
|
|
1325
|
+
return JSON.parse(looksLikeJson(value) ? value : await readFile3(value, "utf8"));
|
|
929
1326
|
} catch (error) {
|
|
930
1327
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
931
1328
|
throw new UlwLoopError(`Invalid JSON input: ${message}`, "ULW_LOOP_JSON_INPUT_INVALID", { cause: error });
|
|
@@ -934,13 +1331,13 @@ async function readJsonInput(value) {
|
|
|
934
1331
|
async function parseCodexGoalJson(value) {
|
|
935
1332
|
if (value === undefined)
|
|
936
1333
|
return;
|
|
937
|
-
const raw = looksLikeJson(value) ? value : await readFile4(value, "utf8");
|
|
938
1334
|
try {
|
|
1335
|
+
const raw = looksLikeJson(value) ? value : await readFile3(value, "utf8");
|
|
939
1336
|
JSON.parse(raw);
|
|
940
1337
|
return raw;
|
|
941
1338
|
} catch (error) {
|
|
942
1339
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
943
|
-
throw new UlwLoopError(`Invalid --codex-goal-json: ${message}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
|
|
1340
|
+
throw new UlwLoopError(`Invalid --codex-goal-json: ${looksLikeJson(value) ? message : "neither valid JSON nor a readable path"}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
|
|
944
1341
|
}
|
|
945
1342
|
}
|
|
946
1343
|
function required(argv, flag, code) {
|
|
@@ -967,7 +1364,7 @@ function parseRecordEvidenceArgs(argv) {
|
|
|
967
1364
|
return notes ? { ...result, notes } : result;
|
|
968
1365
|
}
|
|
969
1366
|
|
|
970
|
-
//
|
|
1367
|
+
// components/ulw-loop/src/validation-batch.ts
|
|
971
1368
|
function isObject(value) {
|
|
972
1369
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
973
1370
|
}
|
|
@@ -1085,14 +1482,11 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
|
1085
1482
|
throw new UlwLoopError(message, code);
|
|
1086
1483
|
}
|
|
1087
1484
|
|
|
1088
|
-
//
|
|
1089
|
-
var QUALITY_GATE_FS = { existsSync: existsSync3, statSync };
|
|
1485
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
1486
|
+
var QUALITY_GATE_FS = { existsSync: existsSync3, statSync: statSync2 };
|
|
1090
1487
|
function ulwLoopFail2(message, code) {
|
|
1091
1488
|
throw new UlwLoopError(message, code);
|
|
1092
1489
|
}
|
|
1093
|
-
function normalizeObjective3(value) {
|
|
1094
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1095
|
-
}
|
|
1096
1490
|
function nonEmptyEvidence2(value) {
|
|
1097
1491
|
const trimmed = value.trim();
|
|
1098
1492
|
return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
|
|
@@ -1115,7 +1509,7 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1115
1509
|
if (!existsSync3(path))
|
|
1116
1510
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1117
1511
|
try {
|
|
1118
|
-
return JSON.parse(await
|
|
1512
|
+
return JSON.parse(await readFile4(path, "utf8"));
|
|
1119
1513
|
} catch (error) {
|
|
1120
1514
|
return ulwLoopFail2(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ulw_loop_json_input_invalid");
|
|
1121
1515
|
}
|
|
@@ -1155,13 +1549,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
|
|
|
1155
1549
|
return status === "blocked" ? "goal_blocked" : "goal_failed";
|
|
1156
1550
|
}
|
|
1157
1551
|
function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
|
|
1158
|
-
const watch = qualityGate?.codeReview
|
|
1552
|
+
const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
|
|
1159
1553
|
const entry = {
|
|
1160
1554
|
at: now,
|
|
1161
1555
|
kind: ledgerKind2(args.status, goal, aggregateCompletion),
|
|
1162
1556
|
goalId: goal.id,
|
|
1163
1557
|
status: goal.status,
|
|
1164
|
-
evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview
|
|
1558
|
+
evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
|
|
1165
1559
|
};
|
|
1166
1560
|
if (codexGoal !== undefined)
|
|
1167
1561
|
entry.codexGoal = codexGoal;
|
|
@@ -1175,7 +1569,7 @@ function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletio
|
|
|
1175
1569
|
entry.requiredExternalDecision = goal.requiredExternalDecision;
|
|
1176
1570
|
return entry;
|
|
1177
1571
|
}
|
|
1178
|
-
async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
1572
|
+
async function checkpointUlwLoop(repoRoot, args, scope, dependencies) {
|
|
1179
1573
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1180
1574
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
1181
1575
|
const goal = findGoal2(plan, args.goalId);
|
|
@@ -1184,6 +1578,8 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1184
1578
|
let aggregateCompletion;
|
|
1185
1579
|
let qualityGate;
|
|
1186
1580
|
let codexGoal;
|
|
1581
|
+
let nextActions = [];
|
|
1582
|
+
let warnings = [];
|
|
1187
1583
|
if (args.status === "complete") {
|
|
1188
1584
|
const aggregate = codexGoalMode(plan) === "aggregate";
|
|
1189
1585
|
const final = isFinalRunCompletionCandidate(plan, goal);
|
|
@@ -1196,23 +1592,23 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1196
1592
|
requireEssentialCriteriaPass(goal);
|
|
1197
1593
|
else
|
|
1198
1594
|
requireAllCriteriaPass(goal);
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1595
|
+
let codexValidationError;
|
|
1596
|
+
try {
|
|
1597
|
+
const validation = await validateCheckpointCodexGoal({
|
|
1598
|
+
repoRoot,
|
|
1599
|
+
plan,
|
|
1600
|
+
goal,
|
|
1601
|
+
raw: args.codexGoalJson,
|
|
1602
|
+
evidence,
|
|
1603
|
+
...scope === undefined ? {} : { scope }
|
|
1604
|
+
});
|
|
1605
|
+
codexGoal = validation.raw;
|
|
1606
|
+
nextActions = validation.nextActions;
|
|
1607
|
+
warnings = validation.warnings;
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
if (!(error instanceof UlwLoopError))
|
|
1610
|
+
throw error;
|
|
1611
|
+
codexValidationError = error;
|
|
1216
1612
|
}
|
|
1217
1613
|
if (closesBatch)
|
|
1218
1614
|
requireBatchFinalReady(plan, goal);
|
|
@@ -1221,13 +1617,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1221
1617
|
if (final)
|
|
1222
1618
|
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
|
|
1223
1619
|
if (final || aggregateCompletion !== undefined || closesBatch) {
|
|
1224
|
-
|
|
1225
|
-
repoRoot,
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1620
|
+
try {
|
|
1621
|
+
qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
|
|
1622
|
+
repoRoot,
|
|
1623
|
+
fs: QUALITY_GATE_FS,
|
|
1624
|
+
reviewerSurface: dependencies?.surface ?? resolveToolkitSurface(),
|
|
1625
|
+
...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
|
|
1626
|
+
});
|
|
1627
|
+
requireBatchGate(plan, goal, qualityGate);
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
|
|
1630
|
+
throw error;
|
|
1631
|
+
throw combineCheckpointValidationErrors(codexValidationError, error);
|
|
1632
|
+
}
|
|
1230
1633
|
}
|
|
1634
|
+
if (codexValidationError !== undefined)
|
|
1635
|
+
throw codexValidationError;
|
|
1231
1636
|
goal.status = "complete";
|
|
1232
1637
|
goal.completedAt = now;
|
|
1233
1638
|
goal.evidence = evidence;
|
|
@@ -1241,6 +1646,8 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1241
1646
|
goal.updatedAt = now;
|
|
1242
1647
|
if (aggregateCompletion !== undefined)
|
|
1243
1648
|
plan.aggregateCompletion = aggregateCompletion;
|
|
1649
|
+
if (aggregateCompletion !== undefined)
|
|
1650
|
+
nextActions = [...nextActions, 'aggregate complete — now update_goal({status:"complete"})'];
|
|
1244
1651
|
plan.updatedAt = now;
|
|
1245
1652
|
await writePlan(repoRoot, plan, scope);
|
|
1246
1653
|
const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
|
|
@@ -1248,23 +1655,146 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1248
1655
|
const closedBatch = args.status === "complete" ? batchClosedBy(plan, goal.id) : undefined;
|
|
1249
1656
|
if (closedBatch !== undefined)
|
|
1250
1657
|
await appendLedger(repoRoot, { at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId }, scope);
|
|
1251
|
-
return aggregateCompletion === undefined ? { plan, goal, ledgerEntry } : { plan, goal, ledgerEntry, aggregateCompletion };
|
|
1658
|
+
return aggregateCompletion === undefined ? { plan, goal, ledgerEntry, nextActions, warnings } : { plan, goal, ledgerEntry, aggregateCompletion, nextActions, warnings };
|
|
1252
1659
|
});
|
|
1253
1660
|
}
|
|
1254
1661
|
|
|
1255
|
-
//
|
|
1662
|
+
// components/ulw-loop/src/checkpoint-template.ts
|
|
1663
|
+
function artifactPath(base, name) {
|
|
1664
|
+
return `${base}/${name}`;
|
|
1665
|
+
}
|
|
1666
|
+
function gateTemplate(surface, base) {
|
|
1667
|
+
const artifacts = [
|
|
1668
|
+
{
|
|
1669
|
+
id: "artifact-cli",
|
|
1670
|
+
kind: "cli-transcript",
|
|
1671
|
+
description: "<replace:artifact description>",
|
|
1672
|
+
path: artifactPath(base, "cli-transcript.txt")
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
id: "artifact-data",
|
|
1676
|
+
kind: "data-diff",
|
|
1677
|
+
description: "<replace:artifact description>",
|
|
1678
|
+
path: artifactPath(base, "data-diff.txt")
|
|
1679
|
+
}
|
|
1680
|
+
];
|
|
1681
|
+
const manualQa = {
|
|
1682
|
+
by: "main-session",
|
|
1683
|
+
status: "passed",
|
|
1684
|
+
evidence: "<replace:manual QA evidence>",
|
|
1685
|
+
surfaceEvidence: [
|
|
1686
|
+
{
|
|
1687
|
+
id: "surface-cli",
|
|
1688
|
+
criterionRef: "<replace:criterion id>",
|
|
1689
|
+
surface: "cli",
|
|
1690
|
+
invocation: "<replace:command>",
|
|
1691
|
+
verdict: "passed",
|
|
1692
|
+
artifactRefs: ["artifact-cli"]
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
id: "surface-data",
|
|
1696
|
+
criterionRef: "<replace:criterion id>",
|
|
1697
|
+
surface: "data",
|
|
1698
|
+
invocation: "<replace:command>",
|
|
1699
|
+
verdict: "passed",
|
|
1700
|
+
artifactRefs: ["artifact-data"]
|
|
1701
|
+
}
|
|
1702
|
+
],
|
|
1703
|
+
adversarialCases: [
|
|
1704
|
+
{
|
|
1705
|
+
id: "<replace:adversarial case id>",
|
|
1706
|
+
criterionRef: "<replace:criterion id>",
|
|
1707
|
+
scenario: "<replace:scenario>",
|
|
1708
|
+
expectedBehavior: "<replace:expected behavior>",
|
|
1709
|
+
verdict: "not_applicable",
|
|
1710
|
+
reason: "<replace:reason>",
|
|
1711
|
+
artifactRefs: ["artifact-cli"]
|
|
1712
|
+
}
|
|
1713
|
+
],
|
|
1714
|
+
artifactRefs: artifacts
|
|
1715
|
+
};
|
|
1716
|
+
const common = {
|
|
1717
|
+
manualQa,
|
|
1718
|
+
gateReview: {
|
|
1719
|
+
by: surface === "omo-senpi" ? "category:deep" : "main-session",
|
|
1720
|
+
recommendation: "APPROVE",
|
|
1721
|
+
reportPath: artifactPath(base, "gate-review.md"),
|
|
1722
|
+
evidence: "<replace:gate review evidence>",
|
|
1723
|
+
blockers: [],
|
|
1724
|
+
notes: []
|
|
1725
|
+
},
|
|
1726
|
+
iteration: {
|
|
1727
|
+
fullRerun: true,
|
|
1728
|
+
status: "passed",
|
|
1729
|
+
rerunCommands: ["<replace:verification command>"],
|
|
1730
|
+
evidence: "<replace:iteration evidence>"
|
|
1731
|
+
},
|
|
1732
|
+
criteriaCoverage: {
|
|
1733
|
+
totalCriteria: 0,
|
|
1734
|
+
passCount: 0,
|
|
1735
|
+
originalIntent: "<replace:original intent>",
|
|
1736
|
+
desiredOutcome: "<replace:desired outcome>",
|
|
1737
|
+
userOutcomeReview: "<replace:user outcome review>",
|
|
1738
|
+
adversarialClassesCovered: ["<replace:adversarial class>"]
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
return common;
|
|
1742
|
+
}
|
|
1743
|
+
async function checkpointTemplate(repoRoot, scope, goalId, dependencies) {
|
|
1744
|
+
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
1745
|
+
const surface = dependencies?.surface ?? resolveToolkitSurface();
|
|
1746
|
+
const targetId = goalId ?? plan.activeGoalId;
|
|
1747
|
+
const active = plan.goals.find((goal) => goal.id === targetId);
|
|
1748
|
+
if (goalId !== undefined && active === undefined)
|
|
1749
|
+
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
1750
|
+
const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
|
|
1751
|
+
const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
|
|
1752
|
+
const guidance = [
|
|
1753
|
+
"codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
|
|
1754
|
+
"Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
|
|
1755
|
+
'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
|
|
1756
|
+
'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.',
|
|
1757
|
+
...surface === "lazycodex" ? [
|
|
1758
|
+
"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."
|
|
1759
|
+
] : [],
|
|
1760
|
+
...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
|
|
1761
|
+
].join(" ");
|
|
1762
|
+
return {
|
|
1763
|
+
qualityGateTemplate: gateTemplate(surface, attemptDir),
|
|
1764
|
+
codexGoalTemplate: {
|
|
1765
|
+
goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
|
|
1766
|
+
},
|
|
1767
|
+
guidance,
|
|
1768
|
+
...hasAttempt ? { attemptDir } : {}
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// components/ulw-loop/src/cli-output.ts
|
|
1256
1773
|
var ULW_LOOP_HELP = `Usage:
|
|
1774
|
+
omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
|
|
1775
|
+
omo-agent-toolkit help | --help | -h (this message)
|
|
1776
|
+
omo-agent-toolkit ulw-loop help
|
|
1257
1777
|
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
1778
|
omo-agent-toolkit ulw-loop status [--json]
|
|
1259
1779
|
omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
|
|
1260
1780
|
omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
|
|
1261
1781
|
omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
|
|
1782
|
+
omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
|
|
1262
1783
|
omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
|
|
1263
1784
|
omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
|
|
1264
1785
|
omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
|
|
1265
1786
|
omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
|
|
1266
1787
|
|
|
1267
|
-
|
|
1788
|
+
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.
|
|
1789
|
+
Every subcommand accepts --help | -h to print its own usage line.`;
|
|
1790
|
+
function subcommandHelp(subcommand) {
|
|
1791
|
+
const lines = ULW_LOOP_HELP.split(`
|
|
1792
|
+
`).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
|
|
1793
|
+
if (lines.length === 0)
|
|
1794
|
+
return ULW_LOOP_HELP;
|
|
1795
|
+
return ["Usage:", ...lines].join(`
|
|
1796
|
+
`);
|
|
1797
|
+
}
|
|
1268
1798
|
function printJson(value) {
|
|
1269
1799
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
1270
1800
|
`);
|
|
@@ -1330,17 +1860,18 @@ function normalizeCodexGoalMode(value) {
|
|
|
1330
1860
|
throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
|
|
1331
1861
|
}
|
|
1332
1862
|
|
|
1333
|
-
//
|
|
1863
|
+
// components/ulw-loop/src/codex-goal-instruction.ts
|
|
1334
1864
|
function buildCodexGoalInstruction(args) {
|
|
1335
1865
|
const mode = codexGoalMode(args.plan);
|
|
1336
1866
|
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
|
|
1337
1867
|
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
|
|
1338
|
-
|
|
1868
|
+
const surface = args.surface ?? resolveToolkitSurface();
|
|
1869
|
+
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
|
|
1339
1870
|
}
|
|
1340
1871
|
function buildCreateGoalPayload(plan, goal) {
|
|
1341
1872
|
return { objective: expectedCodexObjective(plan, goal) };
|
|
1342
1873
|
}
|
|
1343
|
-
function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
1874
|
+
function buildText(mode, plan, goal, createGoal, isFinal, surface) {
|
|
1344
1875
|
return joinLines([
|
|
1345
1876
|
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
|
|
1346
1877
|
`Mode: ${mode}`,
|
|
@@ -1357,7 +1888,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
|
1357
1888
|
"- Goals are unlimited. Do not add numeric limits.",
|
|
1358
1889
|
...modeConstraintLines(mode, isFinal),
|
|
1359
1890
|
...evidenceLayoutLines(plan),
|
|
1360
|
-
finalSection(plan, goal, isFinal, mode === "aggregate"),
|
|
1891
|
+
finalSection(plan, goal, isFinal, mode === "aggregate", surface),
|
|
1361
1892
|
...checkpointLines(plan, mode),
|
|
1362
1893
|
"",
|
|
1363
1894
|
"create_goal payload:",
|
|
@@ -1409,19 +1940,21 @@ function evidenceLayoutLines(plan) {
|
|
|
1409
1940
|
"- 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
1941
|
];
|
|
1411
1942
|
}
|
|
1412
|
-
function finalSection(plan, goal, isFinal, aggregate) {
|
|
1943
|
+
function finalSection(plan, goal, isFinal, aggregate, surface) {
|
|
1944
|
+
const roles = reviewerRolesFor(surface);
|
|
1413
1945
|
if (!isFinal)
|
|
1414
1946
|
return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
|
|
1415
1947
|
const option = sessionOption(plan);
|
|
1948
|
+
if (surface === "omo-senpi")
|
|
1949
|
+
return senpiFinalSection(plan, goal, aggregate);
|
|
1416
1950
|
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
1951
|
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
1952
|
return joinLines([
|
|
1419
|
-
"Final story —
|
|
1953
|
+
"Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
|
|
1420
1954
|
"- Run targeted verification for changed behavior.",
|
|
1421
1955
|
"- 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.",
|
|
1956
|
+
`- 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".`,
|
|
1957
|
+
"- 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
1958
|
"- 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
1959
|
"- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
|
|
1427
1960
|
"- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
|
|
@@ -1430,6 +1963,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
|
|
|
1430
1963
|
` ${checkpointCommand}`
|
|
1431
1964
|
]);
|
|
1432
1965
|
}
|
|
1966
|
+
function senpiFinalSection(plan, goal, aggregate) {
|
|
1967
|
+
const option = sessionOption(plan);
|
|
1968
|
+
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)"`;
|
|
1969
|
+
return joinLines([
|
|
1970
|
+
"Final story — run the single-reviewer quality gate before update_goal:",
|
|
1971
|
+
'- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
|
|
1972
|
+
'- Spawn exactly one gate reviewer with task(category: "deep").',
|
|
1973
|
+
"- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
|
|
1974
|
+
"- Set gateReview.by to the exact category:<name> literal used for the successful task.",
|
|
1975
|
+
"- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
|
|
1976
|
+
'- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
|
|
1977
|
+
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}`
|
|
1978
|
+
]);
|
|
1979
|
+
}
|
|
1433
1980
|
function sessionOption(plan) {
|
|
1434
1981
|
const prefix = ".omo/ulw-loop/";
|
|
1435
1982
|
const suffix = "/goals.json";
|
|
@@ -1443,15 +1990,15 @@ function joinLines(lines) {
|
|
|
1443
1990
|
`);
|
|
1444
1991
|
}
|
|
1445
1992
|
|
|
1446
|
-
//
|
|
1993
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
1447
1994
|
import { existsSync as existsSync4 } from "node:fs";
|
|
1448
1995
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1449
1996
|
|
|
1450
|
-
//
|
|
1997
|
+
// components/ulw-loop/src/plan-goal-factory.ts
|
|
1451
1998
|
function cleanLine(line) {
|
|
1452
1999
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
1453
2000
|
}
|
|
1454
|
-
function
|
|
2001
|
+
function normalizeObjective2(value) {
|
|
1455
2002
|
return value.replace(/\s+/g, " ").trim();
|
|
1456
2003
|
}
|
|
1457
2004
|
function titleFromObjective(objective, fallback) {
|
|
@@ -1472,7 +2019,7 @@ function truncateObjective(objective) {
|
|
|
1472
2019
|
return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
|
|
1473
2020
|
}
|
|
1474
2021
|
function seedDefaultSuccessCriteria(goalIndex, objective) {
|
|
1475
|
-
const subject = truncateObjective(
|
|
2022
|
+
const subject = truncateObjective(normalizeObjective2(objective) || `Goal ${goalIndex + 1}`);
|
|
1476
2023
|
const rows = [
|
|
1477
2024
|
[
|
|
1478
2025
|
"C001",
|
|
@@ -1507,8 +2054,8 @@ function seedDefaultSuccessCriteria(goalIndex, objective) {
|
|
|
1507
2054
|
}));
|
|
1508
2055
|
}
|
|
1509
2056
|
function deriveGoalCandidates(brief) {
|
|
1510
|
-
const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned:
|
|
1511
|
-
const paragraphs = brief.split(/\n\s*\n/).map(
|
|
2057
|
+
const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective2(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
|
|
2058
|
+
const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective2).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
|
|
1512
2059
|
const selected = (bulletGoals.length > 0 ? bulletGoals : paragraphs).length > 0 ? bulletGoals.length > 0 ? bulletGoals : paragraphs : ["Complete the requested project objective."];
|
|
1513
2060
|
return selected.map((objective, index) => ({
|
|
1514
2061
|
title: titleFromObjective(objective, `Goal ${index + 1}`),
|
|
@@ -1536,7 +2083,7 @@ function appendGoalToPlan(plan, title, objective, now) {
|
|
|
1536
2083
|
return goal;
|
|
1537
2084
|
}
|
|
1538
2085
|
|
|
1539
|
-
//
|
|
2086
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
1540
2087
|
function isScheduleEligible(goal) {
|
|
1541
2088
|
return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
|
|
1542
2089
|
}
|
|
@@ -1663,7 +2210,7 @@ function summarizeUlwLoopPlan(plan) {
|
|
|
1663
2210
|
};
|
|
1664
2211
|
}
|
|
1665
2212
|
|
|
1666
|
-
//
|
|
2213
|
+
// components/ulw-loop/src/checkpoint-continuation.ts
|
|
1667
2214
|
async function checkpointAndContinue(repoRoot, args, scope) {
|
|
1668
2215
|
const result = await checkpointUlwLoop(repoRoot, args, scope);
|
|
1669
2216
|
if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
|
|
@@ -1675,13 +2222,18 @@ async function checkpointAndContinue(repoRoot, args, scope) {
|
|
|
1675
2222
|
return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
|
|
1676
2223
|
}
|
|
1677
2224
|
async function checkpoint(repoRoot, argv, json, scope) {
|
|
2225
|
+
if (hasFlag(argv, "--print-template")) {
|
|
2226
|
+
const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
|
|
2227
|
+
if (json)
|
|
2228
|
+
printJson({ ok: true, ...template });
|
|
2229
|
+
else
|
|
2230
|
+
printJson(template);
|
|
2231
|
+
return 0;
|
|
2232
|
+
}
|
|
1678
2233
|
const goalId = required2(argv, "--goal-id");
|
|
1679
2234
|
const statusValue = checkpointStatus(required2(argv, "--status"));
|
|
1680
2235
|
const evidence = required2(argv, "--evidence");
|
|
1681
|
-
const codexGoalJson = await parseCodexGoalJson(
|
|
1682
|
-
if (statusValue === "complete" && codexGoalJson === undefined) {
|
|
1683
|
-
throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
|
|
1684
|
-
}
|
|
2236
|
+
const codexGoalJson = await parseCodexGoalJson(readValue(argv, "--codex-goal-json"));
|
|
1685
2237
|
const qualityGateJson = readValue(argv, "--quality-gate-json");
|
|
1686
2238
|
const args = {
|
|
1687
2239
|
goalId,
|
|
@@ -1726,10 +2278,10 @@ function checkpointStatus(value) {
|
|
|
1726
2278
|
throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
|
|
1727
2279
|
}
|
|
1728
2280
|
|
|
1729
|
-
//
|
|
1730
|
-
import { readFile as
|
|
2281
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
2282
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1731
2283
|
|
|
1732
|
-
//
|
|
2284
|
+
// components/ulw-loop/src/cli-steering.ts
|
|
1733
2285
|
var SOURCES = ["user_prompt_submit", "finding", "cli"];
|
|
1734
2286
|
var STEERING_KIND_HELP = [
|
|
1735
2287
|
`Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
|
|
@@ -2036,7 +2588,7 @@ function printSteerBatchResult(result, json) {
|
|
|
2036
2588
|
printStatus(result.plan);
|
|
2037
2589
|
}
|
|
2038
2590
|
|
|
2039
|
-
//
|
|
2591
|
+
// components/ulw-loop/src/review-blockers.ts
|
|
2040
2592
|
var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
2041
2593
|
function ulwLoopError(message, code) {
|
|
2042
2594
|
throw new UlwLoopError(message, code);
|
|
@@ -2074,10 +2626,12 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2074
2626
|
if (!isFinalRunCompletionCandidate(plan, goal))
|
|
2075
2627
|
ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
|
|
2076
2628
|
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2629
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
2630
|
+
expectedObjective: expectedCodexObjective(plan, goal),
|
|
2631
|
+
acceptedObjectives: compatibleCodexObjectives(plan)
|
|
2632
|
+
});
|
|
2079
2633
|
if (!reconciliation.ok)
|
|
2080
|
-
|
|
2634
|
+
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
2081
2635
|
const now = iso();
|
|
2082
2636
|
for (const field of BLOCKER_FIELDS)
|
|
2083
2637
|
Reflect.deleteProperty(goal, field);
|
|
@@ -2089,7 +2643,7 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2089
2643
|
delete plan.activeGoalId;
|
|
2090
2644
|
const newGoal = appendBlockerGoal(plan, args, now);
|
|
2091
2645
|
plan.updatedAt = now;
|
|
2092
|
-
const codexGoal =
|
|
2646
|
+
const codexGoal = snapshot?.raw;
|
|
2093
2647
|
const blockedEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
|
|
2094
2648
|
const addedEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
|
|
2095
2649
|
const summaryEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
|
|
@@ -2098,18 +2652,54 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2098
2652
|
await writePlan(repoRoot, plan, scope);
|
|
2099
2653
|
for (const entry of ledgerEntries)
|
|
2100
2654
|
await appendLedger(repoRoot, entry, scope);
|
|
2101
|
-
return {
|
|
2655
|
+
return {
|
|
2656
|
+
plan,
|
|
2657
|
+
blockedGoal: goal,
|
|
2658
|
+
newGoal,
|
|
2659
|
+
ledgerEntries,
|
|
2660
|
+
nextActions: reconciliation.warnings,
|
|
2661
|
+
warnings: reconciliation.warnings.filter((warning) => warning.startsWith("driver_objective_differs"))
|
|
2662
|
+
};
|
|
2102
2663
|
});
|
|
2103
2664
|
}
|
|
2104
2665
|
|
|
2105
|
-
//
|
|
2666
|
+
// components/ulw-loop/src/status-next-actions.ts
|
|
2667
|
+
function statusNextActions(plan) {
|
|
2668
|
+
const actions = [];
|
|
2669
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
2670
|
+
if (plan.goals.length === 0)
|
|
2671
|
+
actions.push(`No goals yet: bootstrap with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`);
|
|
2672
|
+
else if (active !== undefined)
|
|
2673
|
+
actions.push(...activeGoalActions(plan, active));
|
|
2674
|
+
for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
|
|
2675
|
+
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>'\`.`);
|
|
2676
|
+
if (plan.evidenceLayoutVersion !== 2 || active === undefined)
|
|
2677
|
+
actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
|
|
2678
|
+
return actions;
|
|
2679
|
+
}
|
|
2680
|
+
function activeGoalActions(plan, active) {
|
|
2681
|
+
const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
|
|
2682
|
+
if (unresolved.length > 0)
|
|
2683
|
+
return [
|
|
2684
|
+
`${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>"\`.`
|
|
2685
|
+
];
|
|
2686
|
+
if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
|
|
2687
|
+
return [
|
|
2688
|
+
`${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.`
|
|
2689
|
+
];
|
|
2690
|
+
return [
|
|
2691
|
+
`${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.`
|
|
2692
|
+
];
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// components/ulw-loop/src/steering-mutations.ts
|
|
2106
2696
|
var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
2107
2697
|
var isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2108
2698
|
var text3 = (value, key) => {
|
|
2109
2699
|
const candidate = read2(value, key);
|
|
2110
2700
|
return isText(candidate) ? candidate.trim() : undefined;
|
|
2111
2701
|
};
|
|
2112
|
-
var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((
|
|
2702
|
+
var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2113
2703
|
var after = (proposal) => {
|
|
2114
2704
|
const candidate = read2(proposal, "after");
|
|
2115
2705
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
|
|
@@ -2184,36 +2774,36 @@ function reviseCriterion(plan, proposal, now) {
|
|
|
2184
2774
|
const current = target?.successCriteria[index];
|
|
2185
2775
|
if (target === undefined || current === undefined)
|
|
2186
2776
|
return;
|
|
2187
|
-
const
|
|
2188
|
-
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(
|
|
2777
|
+
const model = read2(proposal, "userModel");
|
|
2778
|
+
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model) ? model : current.userModel };
|
|
2189
2779
|
target.updatedAt = now;
|
|
2190
2780
|
}
|
|
2191
2781
|
|
|
2192
|
-
//
|
|
2782
|
+
// components/ulw-loop/src/steering-snapshot.ts
|
|
2193
2783
|
function buildSteeringPlanSnapshot(plan, changedGoalIds) {
|
|
2194
2784
|
const snapshot = {
|
|
2195
2785
|
updatedAt: plan.updatedAt,
|
|
2196
2786
|
goalCount: plan.goals.length,
|
|
2197
|
-
goalIds: plan.goals.map((
|
|
2198
|
-
goals: plan.goals.filter((
|
|
2787
|
+
goalIds: plan.goals.map((goal) => goal.id),
|
|
2788
|
+
goals: plan.goals.filter((goal) => changedGoalIds.has(goal.id))
|
|
2199
2789
|
};
|
|
2200
2790
|
return plan.activeGoalId === undefined ? snapshot : { ...snapshot, activeGoalId: plan.activeGoalId };
|
|
2201
2791
|
}
|
|
2202
|
-
function changedGoalIdsBetween(before,
|
|
2203
|
-
const beforeById = new Map(before.goals.map((
|
|
2792
|
+
function changedGoalIdsBetween(before, after) {
|
|
2793
|
+
const beforeById = new Map(before.goals.map((goal) => [goal.id, goal]));
|
|
2204
2794
|
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(
|
|
2795
|
+
for (const goal of after.goals) {
|
|
2796
|
+
const prior = beforeById.get(goal.id);
|
|
2797
|
+
if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal))
|
|
2798
|
+
changed.add(goal.id);
|
|
2799
|
+
beforeById.delete(goal.id);
|
|
2210
2800
|
}
|
|
2211
2801
|
for (const id of beforeById.keys())
|
|
2212
2802
|
changed.add(id);
|
|
2213
2803
|
return changed;
|
|
2214
2804
|
}
|
|
2215
2805
|
|
|
2216
|
-
//
|
|
2806
|
+
// components/ulw-loop/src/steering.ts
|
|
2217
2807
|
var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
|
|
2218
2808
|
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
2219
2809
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
@@ -2226,7 +2816,7 @@ var text4 = (value, key) => {
|
|
|
2226
2816
|
};
|
|
2227
2817
|
var isKind2 = (value) => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
|
|
2228
2818
|
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((
|
|
2819
|
+
var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2230
2820
|
var texts = (value, key) => {
|
|
2231
2821
|
const candidate = read3(value, key);
|
|
2232
2822
|
return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
|
|
@@ -2367,10 +2957,10 @@ function validateCriterion(plan, proposal, reasons) {
|
|
|
2367
2957
|
reasons.push("revise_criterion requires goalId");
|
|
2368
2958
|
else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
|
|
2369
2959
|
reasons.push("revise_criterion requires criterionId");
|
|
2370
|
-
const
|
|
2371
|
-
if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined &&
|
|
2960
|
+
const model = read3(proposal, "userModel");
|
|
2961
|
+
if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model === undefined)
|
|
2372
2962
|
reasons.push("revise_criterion requires update");
|
|
2373
|
-
if (
|
|
2963
|
+
if (model !== undefined && !isModel3(model))
|
|
2374
2964
|
reasons.push("invalid userModel");
|
|
2375
2965
|
}
|
|
2376
2966
|
function applySteeringMutation(plan, proposal, audit) {
|
|
@@ -2397,8 +2987,8 @@ function applySteeringMutation(plan, proposal, audit) {
|
|
|
2397
2987
|
function isProposal(value) {
|
|
2398
2988
|
return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
|
|
2399
2989
|
}
|
|
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(
|
|
2990
|
+
function parseUlwLoopSteeringDirective(text) {
|
|
2991
|
+
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
2992
|
if (match?.[1] === undefined)
|
|
2403
2993
|
return null;
|
|
2404
2994
|
try {
|
|
@@ -2450,7 +3040,130 @@ function ledgerEntry(proposal, audit, at) {
|
|
|
2450
3040
|
return entry;
|
|
2451
3041
|
}
|
|
2452
3042
|
|
|
2453
|
-
//
|
|
3043
|
+
// components/ulw-loop/src/sdk/manifest.ts
|
|
3044
|
+
var ULW_LOOP_MANIFEST = {
|
|
3045
|
+
version: 1,
|
|
3046
|
+
name: "ulw-loop",
|
|
3047
|
+
operations: [
|
|
3048
|
+
{ name: "help", mutating: false },
|
|
3049
|
+
{ name: "create-goals", mutating: true },
|
|
3050
|
+
{ name: "status", mutating: false },
|
|
3051
|
+
{ name: "complete-goals", mutating: true },
|
|
3052
|
+
{ name: "checkpoint", mutating: true },
|
|
3053
|
+
{ name: "steer", mutating: true },
|
|
3054
|
+
{ name: "add-goal", mutating: true },
|
|
3055
|
+
{ name: "criteria", mutating: false },
|
|
3056
|
+
{ name: "record-evidence", mutating: true },
|
|
3057
|
+
{ name: "record-review-blockers", mutating: true }
|
|
3058
|
+
]
|
|
3059
|
+
};
|
|
3060
|
+
|
|
3061
|
+
// components/ulw-loop/src/sdk/factory.ts
|
|
3062
|
+
function validateContext(context) {
|
|
3063
|
+
if (!context.cwd.trim())
|
|
3064
|
+
throw new UlwLoopError("cwd is required.", "ULW_LOOP_CWD_REQUIRED");
|
|
3065
|
+
if (!context.sessionId.trim())
|
|
3066
|
+
throw new UlwLoopError("ULW_LOOP_SESSION_ID_REQUIRED: sessionId is required.", "ULW_LOOP_SESSION_ID_REQUIRED");
|
|
3067
|
+
if (context.surface !== "omo-senpi" && context.surface !== "lazycodex")
|
|
3068
|
+
throw new UlwLoopError("surface must be omo-senpi or lazycodex.", "ULW_LOOP_SURFACE_INVALID");
|
|
3069
|
+
}
|
|
3070
|
+
function errorDetails(error) {
|
|
3071
|
+
const details = error.details === undefined ? undefined : Object.fromEntries(Object.entries(error.details).map(([key, value]) => [key, String(value)]));
|
|
3072
|
+
return details === undefined ? { code: error.code, message: error.message } : { code: error.code, message: error.message, details };
|
|
3073
|
+
}
|
|
3074
|
+
function failure(operation, error) {
|
|
3075
|
+
return { ok: false, operation, error: errorDetails(error) };
|
|
3076
|
+
}
|
|
3077
|
+
function caught(operation, error) {
|
|
3078
|
+
return failure(operation, error instanceof UlwLoopError ? error : new UlwLoopError(error.message, "ULW_LOOP_ERROR"));
|
|
3079
|
+
}
|
|
3080
|
+
function isKnownRequest(request) {
|
|
3081
|
+
return ULW_LOOP_MANIFEST.operations.some((operation) => operation.name === request.operation);
|
|
3082
|
+
}
|
|
3083
|
+
function unreachable(value) {
|
|
3084
|
+
throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
|
|
3085
|
+
}
|
|
3086
|
+
function nextActionsFrom(result) {
|
|
3087
|
+
if (!("nextActions" in result) || !Array.isArray(result.nextActions))
|
|
3088
|
+
return [];
|
|
3089
|
+
return result.nextActions.filter((action) => typeof action === "string").slice(0, 8);
|
|
3090
|
+
}
|
|
3091
|
+
function createAgentToolkit(context, deps = {}) {
|
|
3092
|
+
validateContext(context);
|
|
3093
|
+
const scope = { sessionId: context.sessionId };
|
|
3094
|
+
const notify = async (operation, response) => {
|
|
3095
|
+
if (deps.hooks?.onOperation !== undefined)
|
|
3096
|
+
await deps.hooks.onOperation({ operation, context, response });
|
|
3097
|
+
return response;
|
|
3098
|
+
};
|
|
3099
|
+
const invoke = async (operation, fn) => {
|
|
3100
|
+
try {
|
|
3101
|
+
const result = await fn();
|
|
3102
|
+
const nextActions = typeof result === "object" && result !== null ? nextActionsFrom(result) : [];
|
|
3103
|
+
return await notify(operation, { ok: true, operation, result, nextActions });
|
|
3104
|
+
} catch (error) {
|
|
3105
|
+
const response = caught(operation, error instanceof Error ? error : new Error("ULW_LOOP_ERROR"));
|
|
3106
|
+
return notify(operation, response);
|
|
3107
|
+
}
|
|
3108
|
+
};
|
|
3109
|
+
const toolkit = {
|
|
3110
|
+
dispatch: async (request) => {
|
|
3111
|
+
if (!isKnownRequest(request))
|
|
3112
|
+
return failure(request.operation, new UlwLoopError(`Unknown operation: ${request.operation}`, "ULW_LOOP_OPERATION_UNKNOWN"));
|
|
3113
|
+
switch (request.operation) {
|
|
3114
|
+
case "help":
|
|
3115
|
+
return toolkit.help();
|
|
3116
|
+
case "create-goals":
|
|
3117
|
+
return toolkit.createGoals(request.args);
|
|
3118
|
+
case "status":
|
|
3119
|
+
return toolkit.status();
|
|
3120
|
+
case "complete-goals":
|
|
3121
|
+
return toolkit.completeGoals(request.args);
|
|
3122
|
+
case "checkpoint":
|
|
3123
|
+
return toolkit.checkpoint(request.args);
|
|
3124
|
+
case "steer":
|
|
3125
|
+
return toolkit.steer(request.args);
|
|
3126
|
+
case "add-goal":
|
|
3127
|
+
return toolkit.addGoal(request.args);
|
|
3128
|
+
case "criteria":
|
|
3129
|
+
return toolkit.criteria(request.args);
|
|
3130
|
+
case "record-evidence":
|
|
3131
|
+
return toolkit.recordEvidence(request.args);
|
|
3132
|
+
case "record-review-blockers":
|
|
3133
|
+
return toolkit.recordReviewBlockers(request.args);
|
|
3134
|
+
default:
|
|
3135
|
+
return unreachable(request);
|
|
3136
|
+
}
|
|
3137
|
+
},
|
|
3138
|
+
help: () => invoke("help", async () => ULW_LOOP_MANIFEST),
|
|
3139
|
+
createGoals: (args) => invoke("create-goals", () => createUlwLoopPlan(context.cwd, args, scope)),
|
|
3140
|
+
status: () => invoke("status", async () => {
|
|
3141
|
+
const plan = await readUlwLoopPlan(context.cwd, scope);
|
|
3142
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
3143
|
+
return {
|
|
3144
|
+
plan,
|
|
3145
|
+
summary: summarizeUlwLoopPlan(plan),
|
|
3146
|
+
nextActions: statusNextActions(plan),
|
|
3147
|
+
...active === undefined || plan.evidenceLayoutVersion !== 2 ? {} : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }
|
|
3148
|
+
};
|
|
3149
|
+
}),
|
|
3150
|
+
completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
|
|
3151
|
+
checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface }) : checkpointUlwLoop(context.cwd, args, scope, { surface: context.surface })),
|
|
3152
|
+
steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope)),
|
|
3153
|
+
addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope)),
|
|
3154
|
+
criteria: (args) => invoke("criteria", async () => {
|
|
3155
|
+
const plan = await readUlwLoopPlan(context.cwd, scope);
|
|
3156
|
+
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
|
|
3157
|
+
if (goal === undefined)
|
|
3158
|
+
throw new UlwLoopError(`Unknown ulw-loop id: ${args.goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND");
|
|
3159
|
+
return { goalId: goal.id, criteria: goal.successCriteria };
|
|
3160
|
+
}),
|
|
3161
|
+
recordEvidence: (args) => invoke("record-evidence", () => recordEvidence(context.cwd, args, scope)),
|
|
3162
|
+
recordReviewBlockers: (args) => invoke("record-review-blockers", () => recordFinalReviewBlockers(context.cwd, args, scope))
|
|
3163
|
+
};
|
|
3164
|
+
return toolkit;
|
|
3165
|
+
}
|
|
3166
|
+
// components/ulw-loop/src/steering-batch.ts
|
|
2454
3167
|
async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
2455
3168
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
2456
3169
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
@@ -2483,16 +3196,16 @@ async function prepareBatch(repoRoot, plan, proposals, scope) {
|
|
|
2483
3196
|
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
2484
3197
|
const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
|
|
2485
3198
|
if (prior?.steering !== undefined) {
|
|
2486
|
-
const
|
|
2487
|
-
items.push({ kind: "deduped", result
|
|
2488
|
-
results.push(
|
|
3199
|
+
const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
|
|
3200
|
+
items.push({ kind: "deduped", result });
|
|
3201
|
+
results.push(result);
|
|
2489
3202
|
continue;
|
|
2490
3203
|
}
|
|
2491
3204
|
const audit = validateUlwLoopSteeringProposal(current, proposal);
|
|
2492
3205
|
if (!audit.invariant.accepted) {
|
|
2493
|
-
const
|
|
2494
|
-
items.push({ kind: "deduped", result
|
|
2495
|
-
results.push(
|
|
3206
|
+
const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
|
|
3207
|
+
items.push({ kind: "deduped", result });
|
|
3208
|
+
results.push(result);
|
|
2496
3209
|
continue;
|
|
2497
3210
|
}
|
|
2498
3211
|
const next = applySteeringMutation(current, proposal, audit);
|
|
@@ -2531,20 +3244,20 @@ function ledgerEntry2(proposal, audit, at) {
|
|
|
2531
3244
|
return entry;
|
|
2532
3245
|
}
|
|
2533
3246
|
|
|
2534
|
-
//
|
|
3247
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
2535
3248
|
async function createGoals(repoRoot, argv, json, scope) {
|
|
2536
3249
|
const briefFile = readValue(argv, "--brief-file");
|
|
2537
|
-
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await
|
|
3250
|
+
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile5(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
|
|
2538
3251
|
if (!brief.trim()) {
|
|
2539
3252
|
throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
|
|
2540
3253
|
}
|
|
2541
3254
|
const validationBatchesJson = readValue(argv, "--validation-batch-json");
|
|
2542
|
-
const plan = await
|
|
3255
|
+
const plan = unwrap(await toolkitFor(repoRoot, scope).createGoals({
|
|
2543
3256
|
brief,
|
|
2544
3257
|
codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")),
|
|
2545
3258
|
force: hasFlag(argv, "--force"),
|
|
2546
3259
|
...validationBatchesJson === undefined ? {} : { validationBatchesJson }
|
|
2547
|
-
}
|
|
3260
|
+
}));
|
|
2548
3261
|
if (json)
|
|
2549
3262
|
printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
|
|
2550
3263
|
else {
|
|
@@ -2557,22 +3270,15 @@ ledger: ${plan.ledgerPath}
|
|
|
2557
3270
|
return 0;
|
|
2558
3271
|
}
|
|
2559
3272
|
async function status(repoRoot, json, scope) {
|
|
2560
|
-
const
|
|
2561
|
-
if (json)
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
ok: true,
|
|
2566
|
-
plan,
|
|
2567
|
-
summary: summarizeUlwLoopPlan(plan),
|
|
2568
|
-
...currentAttemptDir === undefined ? {} : { currentAttemptDir }
|
|
2569
|
-
});
|
|
2570
|
-
} else
|
|
2571
|
-
printStatus(plan);
|
|
3273
|
+
const status = unwrap(await toolkitFor(repoRoot, scope).status());
|
|
3274
|
+
if (json)
|
|
3275
|
+
printJson({ ok: true, ...status });
|
|
3276
|
+
else
|
|
3277
|
+
printStatus(status.plan);
|
|
2572
3278
|
return 0;
|
|
2573
3279
|
}
|
|
2574
3280
|
async function completeGoals(repoRoot, argv, json, scope) {
|
|
2575
|
-
const result = await
|
|
3281
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).completeGoals({ retryFailed: hasFlag(argv, "--retry-failed") }));
|
|
2576
3282
|
if ("done" in result) {
|
|
2577
3283
|
const handoff = blockedDecisionHandoff(result.plan);
|
|
2578
3284
|
if (json) {
|
|
@@ -2601,16 +3307,19 @@ async function steer(repoRoot, argv, json, scope) {
|
|
|
2601
3307
|
const proposals = await parseSteeringProposals(argv);
|
|
2602
3308
|
const single = proposals[0];
|
|
2603
3309
|
if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
|
|
2604
|
-
const
|
|
2605
|
-
printSteerResult(
|
|
2606
|
-
return
|
|
3310
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).steer(single));
|
|
3311
|
+
printSteerResult(result, json);
|
|
3312
|
+
return result.accepted ? 0 : 1;
|
|
2607
3313
|
}
|
|
2608
3314
|
const result = await steerUlwLoopBatch(repoRoot, proposals, scope);
|
|
2609
3315
|
printSteerBatchResult(result, json);
|
|
2610
3316
|
return result.accepted ? 0 : 1;
|
|
2611
3317
|
}
|
|
2612
3318
|
async function addGoal(repoRoot, argv, json, scope) {
|
|
2613
|
-
const result = await
|
|
3319
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).addGoal({
|
|
3320
|
+
title: required4(argv, "--title"),
|
|
3321
|
+
objective: required4(argv, "--objective")
|
|
3322
|
+
}));
|
|
2614
3323
|
if (json)
|
|
2615
3324
|
printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
|
|
2616
3325
|
else {
|
|
@@ -2622,19 +3331,19 @@ async function addGoal(repoRoot, argv, json, scope) {
|
|
|
2622
3331
|
}
|
|
2623
3332
|
async function criteria(repoRoot, argv, json, scope) {
|
|
2624
3333
|
const goalId = required4(argv, "--goal-id");
|
|
2625
|
-
const
|
|
3334
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).criteria({ goalId }));
|
|
2626
3335
|
if (json)
|
|
2627
|
-
printJson({ ok: true, goalId:
|
|
3336
|
+
printJson({ ok: true, goalId: result.goalId, criteria: result.criteria });
|
|
2628
3337
|
else {
|
|
2629
|
-
process.stdout.write(`criteria for ${
|
|
2630
|
-
${
|
|
3338
|
+
process.stdout.write(`criteria for ${result.goalId}:
|
|
3339
|
+
${result.criteria.map(formatCriterionForCli).join(`
|
|
2631
3340
|
`)}
|
|
2632
3341
|
`);
|
|
2633
3342
|
}
|
|
2634
3343
|
return 0;
|
|
2635
3344
|
}
|
|
2636
3345
|
async function captureEvidence(repoRoot, argv, json, scope) {
|
|
2637
|
-
const result = await
|
|
3346
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).recordEvidence(parseRecordEvidenceArgs(argv)));
|
|
2638
3347
|
if (json)
|
|
2639
3348
|
printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
|
|
2640
3349
|
else {
|
|
@@ -2648,13 +3357,13 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
|
|
|
2648
3357
|
if (codexGoalJson === undefined) {
|
|
2649
3358
|
throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
|
|
2650
3359
|
}
|
|
2651
|
-
const result = await
|
|
3360
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).recordReviewBlockers({
|
|
2652
3361
|
goalId: required4(argv, "--goal-id"),
|
|
2653
3362
|
title: required4(argv, "--title"),
|
|
2654
3363
|
objective: required4(argv, "--objective"),
|
|
2655
3364
|
evidence: required4(argv, "--evidence"),
|
|
2656
3365
|
codexGoalJson
|
|
2657
|
-
}
|
|
3366
|
+
}));
|
|
2658
3367
|
if (json) {
|
|
2659
3368
|
printJson({
|
|
2660
3369
|
ok: true,
|
|
@@ -2662,6 +3371,8 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
|
|
|
2662
3371
|
blockedGoal: result.blockedGoal,
|
|
2663
3372
|
goal: result.newGoal,
|
|
2664
3373
|
ledgerEntries: result.ledgerEntries,
|
|
3374
|
+
nextActions: result.nextActions,
|
|
3375
|
+
warnings: result.warnings,
|
|
2665
3376
|
summary: summarizeUlwLoopPlan(result.plan)
|
|
2666
3377
|
});
|
|
2667
3378
|
} else {
|
|
@@ -2670,6 +3381,20 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
|
|
|
2670
3381
|
}
|
|
2671
3382
|
return 0;
|
|
2672
3383
|
}
|
|
3384
|
+
function toolkitFor(repoRoot, scope) {
|
|
3385
|
+
const sessionId = scope?.sessionId?.trim();
|
|
3386
|
+
if (sessionId === undefined || sessionId.length === 0) {
|
|
3387
|
+
throw new UlwLoopError("Missing --session-id.", "ULW_LOOP_SESSION_ID_REQUIRED", {
|
|
3388
|
+
details: { flag: "--session-id" }
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
return createAgentToolkit({ cwd: repoRoot, sessionId, surface: resolveToolkitSurface() });
|
|
3392
|
+
}
|
|
3393
|
+
function unwrap(response) {
|
|
3394
|
+
if (response.ok)
|
|
3395
|
+
return response.result;
|
|
3396
|
+
throw new UlwLoopError(response.error.message, response.error.code);
|
|
3397
|
+
}
|
|
2673
3398
|
function formatCriterionForCli(criterion) {
|
|
2674
3399
|
const marker = isEssentialCriterion(criterion) ? "essential" : "non-essential";
|
|
2675
3400
|
return `- ${criterion.id} [${criterion.status}] [${marker}] (${criterion.userModel}) ${criterion.scenario} evidence: ${criterion.capturedEvidence ?? "pending"}`;
|
|
@@ -2680,14 +3405,8 @@ function required4(argv, flag) {
|
|
|
2680
3405
|
return value;
|
|
2681
3406
|
throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
|
|
2682
3407
|
}
|
|
2683
|
-
function findGoal3(plan, goalId) {
|
|
2684
|
-
const goal3 = plan.goals.find((candidate) => candidate.id === goalId);
|
|
2685
|
-
if (goal3 !== undefined)
|
|
2686
|
-
return goal3;
|
|
2687
|
-
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
2688
|
-
}
|
|
2689
3408
|
|
|
2690
|
-
//
|
|
3409
|
+
// components/ulw-loop/src/cli-commands.ts
|
|
2691
3410
|
var ULW_LOOP_SUBCOMMANDS = [
|
|
2692
3411
|
"help",
|
|
2693
3412
|
"create-goals",
|
|
@@ -2710,7 +3429,6 @@ async function ulwLoopCommand(argv) {
|
|
|
2710
3429
|
const repoRoot = process.cwd();
|
|
2711
3430
|
const json = hasFlag(rest, "--json");
|
|
2712
3431
|
try {
|
|
2713
|
-
const scope = commandScope(rest);
|
|
2714
3432
|
if (!isUlwLoopSubcommand(command)) {
|
|
2715
3433
|
if (json) {
|
|
2716
3434
|
printJsonError(new UlwLoopError(`Unknown ulw-loop subcommand: ${command}.`, "ULW_LOOP_SUBCOMMAND_UNKNOWN", {
|
|
@@ -2722,11 +3440,18 @@ async function ulwLoopCommand(argv) {
|
|
|
2722
3440
|
`);
|
|
2723
3441
|
return 1;
|
|
2724
3442
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3443
|
+
if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
|
|
3444
|
+
process.stdout.write(`${subcommandHelp(command)}
|
|
3445
|
+
`);
|
|
3446
|
+
return 0;
|
|
3447
|
+
}
|
|
3448
|
+
if (command === "help") {
|
|
3449
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
2728
3450
|
`);
|
|
2729
|
-
|
|
3451
|
+
return 0;
|
|
3452
|
+
}
|
|
3453
|
+
const scope = commandScope(repoRoot, rest);
|
|
3454
|
+
switch (command) {
|
|
2730
3455
|
case "create-goals":
|
|
2731
3456
|
return await createGoals(repoRoot, rest, json, scope);
|
|
2732
3457
|
case "status":
|
|
@@ -2772,26 +3497,29 @@ var SESSION_ID_FLAG = "--session-id";
|
|
|
2772
3497
|
function sessionIdFlagPresent(argv) {
|
|
2773
3498
|
return hasFlag(argv, SESSION_ID_FLAG) || argv.some((arg) => arg.startsWith(`${SESSION_ID_FLAG}=`));
|
|
2774
3499
|
}
|
|
2775
|
-
function commandScope(argv) {
|
|
3500
|
+
function commandScope(repoRoot, argv) {
|
|
2776
3501
|
if (sessionIdFlagPresent(argv)) {
|
|
2777
|
-
const
|
|
2778
|
-
if (!
|
|
2779
|
-
throw new UlwLoopError(
|
|
3502
|
+
const sessionId = readValue(argv, SESSION_ID_FLAG)?.trim();
|
|
3503
|
+
if (!sessionId) {
|
|
3504
|
+
throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
|
|
2780
3505
|
details: { flag: SESSION_ID_FLAG }
|
|
2781
3506
|
});
|
|
2782
3507
|
}
|
|
2783
|
-
return { sessionId
|
|
3508
|
+
return { sessionId };
|
|
2784
3509
|
}
|
|
2785
3510
|
const sessionId = resolveUlwLoopSessionIdFromEnv();
|
|
2786
|
-
|
|
3511
|
+
if (sessionId !== null)
|
|
3512
|
+
return { sessionId };
|
|
3513
|
+
const existingSessionIds = listUlwLoopSessionIds(repoRoot);
|
|
3514
|
+
throw new UlwLoopError(sessionScopeRequiredMessage(SESSION_ID_FLAG, existingSessionIds), "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { flag: SESSION_ID_FLAG, existingSessionIds } });
|
|
2787
3515
|
}
|
|
2788
3516
|
|
|
2789
|
-
//
|
|
2790
|
-
import { readFileSync as
|
|
3517
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
3518
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
2791
3519
|
|
|
2792
|
-
//
|
|
2793
|
-
import { existsSync as existsSync5, readFileSync } from "node:fs";
|
|
2794
|
-
import { fileURLToPath } from "node:url";
|
|
3520
|
+
// components/ulw-loop/src/ultrawork-skill-pointer.ts
|
|
3521
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
|
|
3522
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2795
3523
|
var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
|
|
2796
3524
|
ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
|
|
2797
3525
|
|
|
@@ -2822,9 +3550,9 @@ Do not start the requested work until all three steps are complete.
|
|
|
2822
3550
|
`;
|
|
2823
3551
|
var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
|
|
2824
3552
|
var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
|
|
2825
|
-
var ULTRAWORK_DIRECTIVE =
|
|
3553
|
+
var ULTRAWORK_DIRECTIVE = readFileSync3(new URL("../directive.md", import.meta.url), "utf8");
|
|
2826
3554
|
function resolveUltraworkSkillFilePath() {
|
|
2827
|
-
return
|
|
3555
|
+
return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
|
|
2828
3556
|
}
|
|
2829
3557
|
function buildUltraworkSkillPointer(skillFilePath) {
|
|
2830
3558
|
return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
|
|
@@ -2837,7 +3565,7 @@ function buildUltraworkAdditionalContext(options = {}) {
|
|
|
2837
3565
|
return ULTRAWORK_DIRECTIVE;
|
|
2838
3566
|
}
|
|
2839
3567
|
|
|
2840
|
-
//
|
|
3568
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
2841
3569
|
var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
|
|
2842
3570
|
var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
|
|
2843
3571
|
var TRANSCRIPT_SEARCH_BYTES = 512000;
|
|
@@ -2885,7 +3613,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
|
|
|
2885
3613
|
return false;
|
|
2886
3614
|
}
|
|
2887
3615
|
function readTranscriptTail(transcriptPath) {
|
|
2888
|
-
const rawTranscript =
|
|
3616
|
+
const rawTranscript = readFileSync4(transcriptPath);
|
|
2889
3617
|
return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
|
|
2890
3618
|
}
|
|
2891
3619
|
function isUltraworkPrompt(prompt) {
|
|
@@ -2940,7 +3668,7 @@ function isRecord3(value) {
|
|
|
2940
3668
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2941
3669
|
}
|
|
2942
3670
|
|
|
2943
|
-
//
|
|
3671
|
+
// components/ulw-loop/src/codex-hook.ts
|
|
2944
3672
|
var CREATE_GOAL_TOOL_NAME = "create_goal";
|
|
2945
3673
|
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
3674
|
function parseUserPromptSubmitPayload(raw) {
|
|
@@ -3066,38 +3794,92 @@ function optionalString(value) {
|
|
|
3066
3794
|
return value === undefined || typeof value === "string";
|
|
3067
3795
|
}
|
|
3068
3796
|
function readAll(stdin) {
|
|
3069
|
-
return new Promise((
|
|
3797
|
+
return new Promise((resolve, reject) => {
|
|
3070
3798
|
let data = "";
|
|
3071
3799
|
stdin.setEncoding("utf8");
|
|
3072
3800
|
stdin.on("data", (chunk) => {
|
|
3073
3801
|
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
|
3074
3802
|
});
|
|
3075
3803
|
stdin.once("error", reject);
|
|
3076
|
-
stdin.once("end", () =>
|
|
3804
|
+
stdin.once("end", () => resolve(data));
|
|
3077
3805
|
});
|
|
3078
3806
|
}
|
|
3079
3807
|
|
|
3080
|
-
//
|
|
3081
|
-
import {
|
|
3082
|
-
import {
|
|
3808
|
+
// components/ulw-loop/src/spawn-guard.ts
|
|
3809
|
+
import { randomBytes } from "node:crypto";
|
|
3810
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, statSync as statSync3, writeFileSync } from "node:fs";
|
|
3811
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
3083
3812
|
var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
|
|
3084
|
-
var DEFAULT_FANOUT_LIMIT =
|
|
3085
|
-
var
|
|
3086
|
-
|
|
3813
|
+
var DEFAULT_FANOUT_LIMIT = 24;
|
|
3814
|
+
var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
|
|
3815
|
+
var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
|
|
3816
|
+
var REVIEW_AGENT_TYPES = [
|
|
3817
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
|
|
3818
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
|
|
3819
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
|
|
3820
|
+
];
|
|
3821
|
+
var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
|
|
3822
|
+
function applySpawnGuards(payload, options = {}) {
|
|
3087
3823
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
3088
3824
|
return "";
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3825
|
+
const breaker = readAdmissionBreaker(payload.session_id);
|
|
3826
|
+
if (breaker !== null)
|
|
3827
|
+
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.`);
|
|
3828
|
+
const scope = { sessionId: payload.session_id };
|
|
3829
|
+
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
3830
|
+
const plan = readPlan(join3(stateDir, "goals.json"));
|
|
3091
3831
|
if (plan === null)
|
|
3092
3832
|
return "";
|
|
3833
|
+
const lockOptions = options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs };
|
|
3834
|
+
try {
|
|
3835
|
+
return withStateLockSync(ulwLoopStateLockPath(payload.cwd, scope), () => evaluateGuards(payload, plan, stateDir), lockOptions);
|
|
3836
|
+
} catch (error) {
|
|
3837
|
+
if (isStateLockTimeout(error))
|
|
3838
|
+
return deny(`ulw-loop spawn guard could not take the session state lock: ${error.message}`);
|
|
3839
|
+
throw error;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
function evaluateGuards(payload, plan, stateDir) {
|
|
3843
|
+
const fanOutPeek = peekFanOutBudget(stateDir);
|
|
3844
|
+
if (fanOutPeek !== null)
|
|
3845
|
+
return deny(fanOutPeek);
|
|
3846
|
+
const missingArtifact = missingGateArtifact(payload, plan);
|
|
3847
|
+
if (missingArtifact !== null)
|
|
3848
|
+
return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
|
|
3849
|
+
const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
|
|
3850
|
+
if (reviewDenial !== null)
|
|
3851
|
+
return deny(reviewDenial);
|
|
3093
3852
|
const fanOutDenial = consumeFanOutBudget(stateDir);
|
|
3094
3853
|
if (fanOutDenial !== null)
|
|
3095
3854
|
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
3855
|
return "";
|
|
3100
3856
|
}
|
|
3857
|
+
async function runSpawnAdmissionRecorderCli(stdin, stdout) {
|
|
3858
|
+
const chunks = [];
|
|
3859
|
+
for await (const chunk of stdin)
|
|
3860
|
+
chunks.push(Buffer.from(chunk));
|
|
3861
|
+
try {
|
|
3862
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
3863
|
+
const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
|
|
3864
|
+
if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
|
|
3865
|
+
return;
|
|
3866
|
+
const dataDir = process.env["PLUGIN_DATA"];
|
|
3867
|
+
if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
|
|
3868
|
+
return;
|
|
3869
|
+
const markerDir = join3(dataDir, "spawn-breaker");
|
|
3870
|
+
try {
|
|
3871
|
+
mkdirSync2(markerDir, { recursive: true });
|
|
3872
|
+
atomicWriteJson(join3(markerDir, `${payload["session_id"]}.json`), {
|
|
3873
|
+
reason: response,
|
|
3874
|
+
at: new Date().toISOString()
|
|
3875
|
+
});
|
|
3876
|
+
} catch (error) {
|
|
3877
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3878
|
+
process.stderr.write(`[ulw-loop] spawn-guard: could not persist admission failure: ${message}
|
|
3879
|
+
`);
|
|
3880
|
+
}
|
|
3881
|
+
} catch {}
|
|
3882
|
+
}
|
|
3101
3883
|
async function runSpawnGuardCli(stdin, stdout) {
|
|
3102
3884
|
try {
|
|
3103
3885
|
const chunks = [];
|
|
@@ -3114,49 +3896,112 @@ async function runSpawnGuardCli(stdin, stdout) {
|
|
|
3114
3896
|
return;
|
|
3115
3897
|
}
|
|
3116
3898
|
}
|
|
3899
|
+
function peekFanOutBudget(stateDir) {
|
|
3900
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3901
|
+
const count = readCount(counterPath) + 1;
|
|
3902
|
+
const limit = fanOutLimit();
|
|
3903
|
+
if (count <= limit)
|
|
3904
|
+
return null;
|
|
3905
|
+
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.`;
|
|
3906
|
+
}
|
|
3117
3907
|
function consumeFanOutBudget(stateDir) {
|
|
3118
|
-
const counterPath =
|
|
3908
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3119
3909
|
const count = readCount(counterPath) + 1;
|
|
3120
|
-
|
|
3910
|
+
atomicWriteJson(counterPath, { count });
|
|
3121
3911
|
const limit = fanOutLimit();
|
|
3122
3912
|
if (count <= limit)
|
|
3123
3913
|
return null;
|
|
3124
3914
|
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
3915
|
}
|
|
3916
|
+
function consumeReviewSpawnBudget(payload, plan, stateDir) {
|
|
3917
|
+
const agentType = reviewAgentType(payload.tool_input);
|
|
3918
|
+
if (agentType === null)
|
|
3919
|
+
return null;
|
|
3920
|
+
const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
3921
|
+
if (goal === undefined)
|
|
3922
|
+
return null;
|
|
3923
|
+
const counterPath = join3(stateDir, "review-spawn-counts.json");
|
|
3924
|
+
const limit = reviewSpawnLimit();
|
|
3925
|
+
const counts = readCounts(counterPath);
|
|
3926
|
+
const key = `${agentType}:${goal.id}:a${goal.attempt}`;
|
|
3927
|
+
const count = (counts[key] ?? 0) + 1;
|
|
3928
|
+
if (count > limit)
|
|
3929
|
+
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.`;
|
|
3930
|
+
counts[key] = count;
|
|
3931
|
+
atomicWriteJson(counterPath, counts);
|
|
3932
|
+
return null;
|
|
3933
|
+
}
|
|
3126
3934
|
function missingGateArtifact(payload, plan) {
|
|
3127
3935
|
if (!isGateReviewerSpawn(payload.tool_input))
|
|
3128
3936
|
return null;
|
|
3129
|
-
const
|
|
3130
|
-
if (
|
|
3937
|
+
const goal = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
3938
|
+
if (goal === undefined || goal.status === "complete")
|
|
3131
3939
|
return null;
|
|
3132
|
-
if (!
|
|
3940
|
+
if (!goal.successCriteria.every((criterion) => criterion.status === "pass"))
|
|
3133
3941
|
return null;
|
|
3134
3942
|
const scope = { sessionId: payload.session_id };
|
|
3943
|
+
const requiredArtifacts = [`${goal.id}-manual-qa.md`];
|
|
3135
3944
|
if (plan.evidenceLayoutVersion === 2) {
|
|
3136
|
-
const attemptDir = ulwLoopAttemptEvidenceDir(
|
|
3137
|
-
for (const name of
|
|
3138
|
-
const
|
|
3139
|
-
if (!isNonEmptyFile(
|
|
3140
|
-
return
|
|
3945
|
+
const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
|
|
3946
|
+
for (const name of requiredArtifacts) {
|
|
3947
|
+
const relative = `${attemptDir}/${name}`;
|
|
3948
|
+
if (!isNonEmptyFile(join3(payload.cwd, relative)))
|
|
3949
|
+
return relative;
|
|
3141
3950
|
}
|
|
3142
3951
|
return null;
|
|
3143
3952
|
}
|
|
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;
|
|
3953
|
+
const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
|
|
3954
|
+
return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
|
|
3150
3955
|
}
|
|
3151
3956
|
function isGateReviewerSpawn(toolInput) {
|
|
3957
|
+
const agentType = reviewAgentType(toolInput);
|
|
3958
|
+
return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
|
|
3959
|
+
}
|
|
3960
|
+
function reviewAgentType(toolInput) {
|
|
3152
3961
|
if (typeof toolInput !== "object" || toolInput === null)
|
|
3153
|
-
return
|
|
3962
|
+
return null;
|
|
3154
3963
|
const record = toolInput;
|
|
3155
3964
|
const agentType = record["agent_type"];
|
|
3156
|
-
if (typeof agentType === "string")
|
|
3157
|
-
|
|
3965
|
+
if (typeof agentType === "string") {
|
|
3966
|
+
if (!REVIEW_AGENT_TYPE_SET.has(agentType))
|
|
3967
|
+
return null;
|
|
3968
|
+
return activeSurfaceReviewerAlias(agentType);
|
|
3969
|
+
}
|
|
3158
3970
|
const message = record["message"];
|
|
3159
|
-
|
|
3971
|
+
if (typeof message !== "string")
|
|
3972
|
+
return null;
|
|
3973
|
+
const normalizedMessage = message.toLowerCase();
|
|
3974
|
+
const allRoleNames = [...REVIEW_AGENT_TYPES];
|
|
3975
|
+
const explicitAssignment = allRoleNames.map((name) => ({
|
|
3976
|
+
name,
|
|
3977
|
+
index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
|
|
3978
|
+
})).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
|
|
3979
|
+
if (explicitAssignment !== undefined)
|
|
3980
|
+
return activeSurfaceReviewerAlias(explicitAssignment.name);
|
|
3981
|
+
const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
|
|
3982
|
+
if (nonReviewerActAs)
|
|
3983
|
+
return null;
|
|
3984
|
+
const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
|
|
3985
|
+
if (namedReviewer !== undefined)
|
|
3986
|
+
return activeSurfaceReviewerAlias(namedReviewer);
|
|
3987
|
+
return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
|
|
3988
|
+
}
|
|
3989
|
+
function activeSurfaceReviewerAlias(reviewer) {
|
|
3990
|
+
const activeRoles = reviewerRolesFor(resolveToolkitSurface());
|
|
3991
|
+
for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
|
|
3992
|
+
if (reviewer === roles.codeReview)
|
|
3993
|
+
return activeRoles.codeReview;
|
|
3994
|
+
if (reviewer === roles.manualQa)
|
|
3995
|
+
return activeRoles.manualQa;
|
|
3996
|
+
if (reviewer === roles.gateReview)
|
|
3997
|
+
return activeRoles.gateReview;
|
|
3998
|
+
}
|
|
3999
|
+
return reviewer;
|
|
4000
|
+
}
|
|
4001
|
+
function atomicWriteJson(targetPath, data) {
|
|
4002
|
+
const tmp = join3(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
|
|
4003
|
+
writeFileSync(tmp, JSON.stringify(data));
|
|
4004
|
+
renameSync(tmp, targetPath);
|
|
3160
4005
|
}
|
|
3161
4006
|
function deny(reason) {
|
|
3162
4007
|
return `${JSON.stringify({
|
|
@@ -3169,6 +4014,17 @@ function deny(reason) {
|
|
|
3169
4014
|
})}
|
|
3170
4015
|
`;
|
|
3171
4016
|
}
|
|
4017
|
+
function readAdmissionBreaker(sessionId) {
|
|
4018
|
+
const dataDir = process.env["PLUGIN_DATA"];
|
|
4019
|
+
if (typeof dataDir !== "string")
|
|
4020
|
+
return null;
|
|
4021
|
+
try {
|
|
4022
|
+
const value = JSON.parse(readFileSync5(join3(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
|
|
4023
|
+
return typeof value.reason === "string" ? value.reason : "capacity limit";
|
|
4024
|
+
} catch {
|
|
4025
|
+
return null;
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
3172
4028
|
function fanOutLimit() {
|
|
3173
4029
|
const raw = process.env["OMO_SPAWN_FANOUT_LIMIT"];
|
|
3174
4030
|
if (raw === undefined)
|
|
@@ -3176,37 +4032,52 @@ function fanOutLimit() {
|
|
|
3176
4032
|
const parsed = Number.parseInt(raw, 10);
|
|
3177
4033
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
|
|
3178
4034
|
}
|
|
4035
|
+
function reviewSpawnLimit() {
|
|
4036
|
+
const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
4037
|
+
if (raw === undefined)
|
|
4038
|
+
return DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
4039
|
+
const parsed = Number.parseInt(raw, 10);
|
|
4040
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
4041
|
+
}
|
|
3179
4042
|
function isNonEmptyFile(path) {
|
|
3180
4043
|
try {
|
|
3181
|
-
return existsSync6(path) &&
|
|
4044
|
+
return existsSync6(path) && statSync3(path).size > 0;
|
|
3182
4045
|
} catch (error) {
|
|
3183
4046
|
if (error instanceof Error)
|
|
3184
4047
|
return false;
|
|
3185
4048
|
throw error;
|
|
3186
4049
|
}
|
|
3187
4050
|
}
|
|
3188
|
-
function
|
|
4051
|
+
function readCount(counterPath) {
|
|
3189
4052
|
try {
|
|
3190
|
-
|
|
4053
|
+
const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
|
|
4054
|
+
return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
|
|
3191
4055
|
} catch (error) {
|
|
3192
4056
|
if (error instanceof Error)
|
|
3193
|
-
return
|
|
4057
|
+
return 0;
|
|
3194
4058
|
throw error;
|
|
3195
4059
|
}
|
|
3196
4060
|
}
|
|
3197
|
-
function
|
|
4061
|
+
function readCounts(counterPath) {
|
|
3198
4062
|
try {
|
|
3199
|
-
const parsed = JSON.parse(
|
|
3200
|
-
|
|
4063
|
+
const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
|
|
4064
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
4065
|
+
return {};
|
|
4066
|
+
const counts = {};
|
|
4067
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
4068
|
+
if (typeof value === "number" && value >= 0)
|
|
4069
|
+
counts[key] = value;
|
|
4070
|
+
}
|
|
4071
|
+
return counts;
|
|
3201
4072
|
} catch (error) {
|
|
3202
4073
|
if (error instanceof Error)
|
|
3203
|
-
return
|
|
4074
|
+
return {};
|
|
3204
4075
|
throw error;
|
|
3205
4076
|
}
|
|
3206
4077
|
}
|
|
3207
4078
|
function readPlan(goalsPath) {
|
|
3208
4079
|
try {
|
|
3209
|
-
return JSON.parse(
|
|
4080
|
+
return JSON.parse(readFileSync5(goalsPath, "utf8"));
|
|
3210
4081
|
} catch (error) {
|
|
3211
4082
|
if (error instanceof Error)
|
|
3212
4083
|
return null;
|
|
@@ -3214,9 +4085,9 @@ function readPlan(goalsPath) {
|
|
|
3214
4085
|
}
|
|
3215
4086
|
}
|
|
3216
4087
|
|
|
3217
|
-
//
|
|
3218
|
-
import { existsSync as existsSync7, readFileSync as
|
|
3219
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4088
|
+
// components/ulw-loop/src/stop-resume-hook.ts
|
|
4089
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4090
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
|
|
3220
4091
|
var RESUME_CAP = 2;
|
|
3221
4092
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
3222
4093
|
"context compacted",
|
|
@@ -3235,18 +4106,19 @@ function runStopResumeHook(input) {
|
|
|
3235
4106
|
return "";
|
|
3236
4107
|
if (boulderContinuationWillFire(payload.cwd, payload.session_id))
|
|
3237
4108
|
return "";
|
|
3238
|
-
const
|
|
3239
|
-
const
|
|
4109
|
+
const scope = { sessionId: payload.session_id };
|
|
4110
|
+
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
4111
|
+
const plan = readPlan2(join4(stateDir, "goals.json"));
|
|
3240
4112
|
if (plan === null || plan.aggregateCompletion?.status === "complete")
|
|
3241
4113
|
return "";
|
|
3242
|
-
const
|
|
3243
|
-
if (
|
|
4114
|
+
const goal = resumableGoal(plan);
|
|
4115
|
+
if (goal === undefined)
|
|
3244
4116
|
return "";
|
|
3245
|
-
if (!
|
|
4117
|
+
if (!consumeResumeBudgetLocked(ulwLoopStateLockPath(payload.cwd, scope), stateDir, goal.id))
|
|
3246
4118
|
return "";
|
|
3247
4119
|
const output = {
|
|
3248
4120
|
decision: "block",
|
|
3249
|
-
reason: renderResumeDirective(plan,
|
|
4121
|
+
reason: renderResumeDirective(plan, goal, payload.session_id)
|
|
3250
4122
|
};
|
|
3251
4123
|
return JSON.stringify(output);
|
|
3252
4124
|
}
|
|
@@ -3264,16 +4136,25 @@ async function runStopResumeHookCli(stdin, stdout) {
|
|
|
3264
4136
|
}
|
|
3265
4137
|
}
|
|
3266
4138
|
function resumableGoal(plan) {
|
|
3267
|
-
const active = plan.goals.find((
|
|
4139
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
3268
4140
|
if (active !== undefined && isResumableStatus(active.status))
|
|
3269
4141
|
return active;
|
|
3270
|
-
return plan.goals.find((
|
|
4142
|
+
return plan.goals.find((goal) => isResumableStatus(goal.status));
|
|
3271
4143
|
}
|
|
3272
|
-
function isResumableStatus(
|
|
3273
|
-
return
|
|
4144
|
+
function isResumableStatus(status) {
|
|
4145
|
+
return status === "pending" || status === "in_progress";
|
|
4146
|
+
}
|
|
4147
|
+
function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
|
|
4148
|
+
try {
|
|
4149
|
+
return withStateLockSync(lockPath, () => consumeResumeBudget(stateDir, goalId));
|
|
4150
|
+
} catch (error) {
|
|
4151
|
+
if (isStateLockTimeout(error))
|
|
4152
|
+
return false;
|
|
4153
|
+
throw error;
|
|
4154
|
+
}
|
|
3274
4155
|
}
|
|
3275
4156
|
function consumeResumeBudget(stateDir, goalId) {
|
|
3276
|
-
const ledgerLineCount = countLedgerLines(
|
|
4157
|
+
const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
|
|
3277
4158
|
const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
|
|
3278
4159
|
const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
|
|
3279
4160
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
@@ -3291,11 +4172,11 @@ function consumeResumeBudget(stateDir, goalId) {
|
|
|
3291
4172
|
function isInsideDir(dir, candidate) {
|
|
3292
4173
|
return candidate.startsWith(resolve4(dir) + sep2);
|
|
3293
4174
|
}
|
|
3294
|
-
function renderResumeDirective(plan,
|
|
4175
|
+
function renderResumeDirective(plan, goal, sessionId) {
|
|
3295
4176
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|
|
3296
4177
|
const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
|
|
3297
4178
|
return [
|
|
3298
|
-
`The ulw-loop run in this session still has unfinished goals (next: ${
|
|
4179
|
+
`The ulw-loop run in this session still has unfinished goals (next: ${goal.id} — ${goal.title}).`,
|
|
3299
4180
|
"The turn ended before the loop completed. Resume it now:",
|
|
3300
4181
|
`1. Run \`omo-agent-toolkit ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
|
|
3301
4182
|
"2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
|
|
@@ -3306,7 +4187,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
|
|
|
3306
4187
|
}
|
|
3307
4188
|
function readPlan2(goalsPath) {
|
|
3308
4189
|
try {
|
|
3309
|
-
return JSON.parse(
|
|
4190
|
+
return JSON.parse(readFileSync6(goalsPath, "utf8"));
|
|
3310
4191
|
} catch (error) {
|
|
3311
4192
|
if (error instanceof Error)
|
|
3312
4193
|
return null;
|
|
@@ -3315,7 +4196,7 @@ function readPlan2(goalsPath) {
|
|
|
3315
4196
|
}
|
|
3316
4197
|
function countLedgerLines(ledgerPath) {
|
|
3317
4198
|
try {
|
|
3318
|
-
return
|
|
4199
|
+
return readFileSync6(ledgerPath, "utf8").split(`
|
|
3319
4200
|
`).filter(Boolean).length;
|
|
3320
4201
|
} catch (error) {
|
|
3321
4202
|
if (error instanceof Error)
|
|
@@ -3327,7 +4208,7 @@ function readCounter(counterPath) {
|
|
|
3327
4208
|
try {
|
|
3328
4209
|
if (!existsSync7(counterPath))
|
|
3329
4210
|
return null;
|
|
3330
|
-
const parsed = JSON.parse(
|
|
4211
|
+
const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
|
|
3331
4212
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
3332
4213
|
return null;
|
|
3333
4214
|
return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
|
|
@@ -3339,7 +4220,7 @@ function readCounter(counterPath) {
|
|
|
3339
4220
|
}
|
|
3340
4221
|
function boulderContinuationWillFire(cwd, sessionId) {
|
|
3341
4222
|
try {
|
|
3342
|
-
const raw = JSON.parse(
|
|
4223
|
+
const raw = JSON.parse(readFileSync6(join4(cwd, ".omo", "boulder.json"), "utf8"));
|
|
3343
4224
|
const works = raw["works"];
|
|
3344
4225
|
const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
|
|
3345
4226
|
return entries.some((work) => {
|
|
@@ -3358,7 +4239,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
|
|
|
3358
4239
|
}
|
|
3359
4240
|
function transcriptShowsContextPressure(transcriptPath) {
|
|
3360
4241
|
try {
|
|
3361
|
-
const transcript =
|
|
4242
|
+
const transcript = readFileSync6(transcriptPath, "utf8").toLowerCase();
|
|
3362
4243
|
return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
|
|
3363
4244
|
} catch (error) {
|
|
3364
4245
|
if (error instanceof Error)
|
|
@@ -3370,12 +4251,12 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
3370
4251
|
const activePlan = entry["active_plan"];
|
|
3371
4252
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
3372
4253
|
return false;
|
|
3373
|
-
const planPath = isAbsolute2(activePlan) ? activePlan :
|
|
4254
|
+
const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
|
|
3374
4255
|
const worktree = entry["worktree_path"];
|
|
3375
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [
|
|
4256
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
|
|
3376
4257
|
for (const candidate of candidates) {
|
|
3377
4258
|
try {
|
|
3378
|
-
return
|
|
4259
|
+
return readFileSync6(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|
|
3379
4260
|
} catch (error) {
|
|
3380
4261
|
if (!(error instanceof Error))
|
|
3381
4262
|
throw error;
|
|
@@ -3399,19 +4280,13 @@ function parseStopPayload(value) {
|
|
|
3399
4280
|
};
|
|
3400
4281
|
}
|
|
3401
4282
|
|
|
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
|
-
`;
|
|
4283
|
+
// components/ulw-loop/src/cli.ts
|
|
3410
4284
|
async function main() {
|
|
3411
4285
|
const argv = process.argv.slice(2);
|
|
3412
4286
|
const command = argv[0];
|
|
3413
4287
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
3414
|
-
process.stdout.write(
|
|
4288
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
4289
|
+
`);
|
|
3415
4290
|
return 0;
|
|
3416
4291
|
}
|
|
3417
4292
|
if (command === "ulw-loop")
|
|
@@ -3432,6 +4307,10 @@ async function main() {
|
|
|
3432
4307
|
await runStopResumeHookCli(process.stdin, process.stdout);
|
|
3433
4308
|
return 0;
|
|
3434
4309
|
}
|
|
4310
|
+
if (sub === "post-tool-use-spawn") {
|
|
4311
|
+
await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
|
|
4312
|
+
return 0;
|
|
4313
|
+
}
|
|
3435
4314
|
if (sub === "pre-tool-use-spawn") {
|
|
3436
4315
|
await runSpawnGuardCli(process.stdin, process.stdout);
|
|
3437
4316
|
return 0;
|
|
@@ -3443,7 +4322,8 @@ async function main() {
|
|
|
3443
4322
|
if (isUlwLoopSubcommand(command))
|
|
3444
4323
|
return ulwLoopCommand(argv);
|
|
3445
4324
|
process.stderr.write(`[omo] unknown command: ${command}
|
|
3446
|
-
${
|
|
4325
|
+
${ULW_LOOP_HELP}
|
|
4326
|
+
`);
|
|
3447
4327
|
return 1;
|
|
3448
4328
|
}
|
|
3449
4329
|
main().then((code) => {
|