oh-my-opencode 5.0.0-beta.8 → 5.0.0-beta.81
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/command/remove-deadcode.md +1 -1
- package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
- package/.agents/skills/hyperplan/SKILL.md +5 -5
- 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/remove-deadcode/SKILL.md +1 -1
- package/.agents/skills/security-research/SKILL.md +4 -4
- 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/.opencode/command/publish.md +59 -23
- package/.opencode/skills/pre-publish-review/SKILL.md +9 -12
- package/README.ja.md +66 -61
- package/README.ko.md +67 -62
- package/README.md +108 -182
- package/README.ru.md +63 -61
- package/README.zh-cn.md +62 -61
- package/THIRD-PARTY-NOTICES.md +35 -105
- package/dist/agents/atlas/agent.d.ts +0 -1
- package/dist/agents/gpt-prompt-identity.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/browser-provider.d.ts +2 -0
- 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 +59118 -63486
- 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 +58841 -63186
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/agent-names.d.ts +0 -1
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/browser-automation.d.ts +0 -2
- 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 -12
- 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/config-migration/category-deep-split.d.ts +3 -0
- package/dist/config-migration/migration-plans.d.ts +1 -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/boulder-state/storage.d.ts +1 -1
- 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/hyperplan.d.ts +1 -1
- package/dist/features/builtin-commands/templates/refactor-sections/team-mode-addendum.d.ts +1 -1
- package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
- 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/lifecycle-inline-spec.d.ts +1 -1
- 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/comment-checker/__fixtures__/download-probe.fixture.d.ts +1 -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/plan-format-validator/hook.d.ts +19 -0
- 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 +34199 -28037
- package/dist/mcp/index.d.ts +0 -4
- package/dist/mcp/types.d.ts +0 -1
- package/dist/oh-my-opencode.schema.json +28 -59
- 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-config/omo-config-chain.d.ts +1 -0
- package/dist/plugin-dispose.d.ts +3 -0
- package/dist/shared/agent-tool-restrictions.d.ts +0 -1
- package/dist/shared/compaction-agent-config-checkpoint.d.ts +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/legacy-plugin-warning.d.ts +2 -1
- package/dist/shared/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 +13 -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 +26 -5
- 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/debugging/references/tools/playwright-cli.md +79 -161
- package/dist/skills/frontend/ATTRIBUTION.md +10 -0
- package/dist/skills/frontend/SKILL.md +8 -3
- package/dist/skills/frontend/references/design/README.md +13 -4
- package/dist/skills/frontend/references/design/_INDEX.md +1 -0
- package/dist/skills/frontend/references/design/ambience-skill.md +145 -0
- package/dist/skills/frontend/references/design/aside.md +1 -1
- package/dist/skills/frontend/references/design/clone-from-url.md +1 -1
- package/dist/skills/frontend/references/design/design-system-architecture.md +1 -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/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/frontend/references/perfection/README.md +1 -0
- package/dist/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
- 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 +5 -5
- package/dist/skills/remove-ai-slops/SKILL.md +5 -5
- package/dist/skills/review-work/SKILL.md +101 -419
- package/dist/skills/ultimate-browsing/ATTRIBUTION.md +32 -86
- package/dist/skills/ultimate-browsing/SKILL.md +32 -27
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +111 -0
- package/dist/skills/ultimate-browsing/engine/templates/package.json +2 -2
- package/dist/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
- package/dist/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
- package/dist/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
- package/dist/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
- package/dist/skills/ultimate-browsing/references/chrome-stealth.md +86 -95
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +18 -5
- package/dist/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
- package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
- package/dist/skills/{start-work → ulw-execute}/SKILL.md +24 -22
- package/dist/skills/ulw-plan/SKILL.md +5 -6
- package/dist/skills/ulw-plan/references/full-workflow.md +6 -5
- package/dist/skills/ulw-plan/references/intent-unclear.md +2 -2
- package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +2 -1
- package/dist/skills/ulw-research/SKILL.md +10 -5
- package/dist/skills/visual-qa/AGENTS.md +58 -0
- package/dist/skills/visual-qa/SKILL.md +4 -3
- package/dist/skills/visual-qa/references/browser-setup.md +75 -0
- 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 +10 -3
- 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 +22186 -30577
- package/package.json +51 -50
- 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 +9 -1
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +100 -28
- 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 +90 -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 +35 -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 +1421 -960
- package/packages/lsp-daemon/dist/client.d.ts +10 -0
- package/packages/lsp-daemon/dist/client.js +956 -498
- package/packages/lsp-daemon/dist/daemon-client.d.ts +3 -0
- package/packages/lsp-daemon/dist/daemon-client.js +23 -3
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +6 -1
- package/packages/lsp-daemon/dist/ensure-daemon.js +27 -5
- package/packages/lsp-daemon/dist/index.js +860 -407
- 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 +5 -4
- package/packages/lsp-tools-mcp/dist/cli.js +764 -341
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +274 -138
- package/packages/lsp-tools-mcp/dist/mcp.js +789 -368
- package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
- package/packages/lsp-tools-mcp/dist/tools.js +719 -302
- package/packages/lsp-tools-mcp/package.json +5 -4
- 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 +8402 -408
- 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/bootstrap/src/setup.ts +7 -1
- 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 +6 -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 +6 -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 +977 -545
- 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 +6 -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 +8 -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 +6 -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 +5328 -1382
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +5524 -1579
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +6 -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 +4 -4
- package/packages/omo-codex/plugin/components/ultrawork/biome.json +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +500 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +6 -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 +4 -5
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +5 -5
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -0
- 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 +11 -11
- 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 +13 -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 +17 -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 +44 -27
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +30 -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 +57 -46
- 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 +2191 -737
- 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 +8 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +35 -23
- 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 +25 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.d.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.js +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.js +37 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.js +24 -12
- package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.d.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.js +39 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +19 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.d.ts +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.js +108 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +6 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +54 -25
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.d.ts +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.js +38 -31
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +10 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +121 -100
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.d.ts +16 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.js +115 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +41 -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 +25 -13
- 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 +157 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +381 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +127 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +130 -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-budget-io.d.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.js +67 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +7 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +188 -59
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.js +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +259 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +41 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-types.d.ts +9 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +11 -11
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +28 -23
- package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.js +41 -0
- 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 +83 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +18 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +7 -6
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +7 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +33 -10
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +52 -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 +59 -77
- 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 +51 -33
- 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 +27 -9
- package/packages/omo-codex/plugin/components/ulw-loop/src/driver-objective-ack.ts +20 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/evidence-artifacts.ts +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts +23 -16
- package/packages/omo-codex/plugin/components/ulw-loop/src/ledger.ts +38 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +32 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-commit.ts +132 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +67 -54
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts +63 -31
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +140 -119
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-log.ts +112 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +61 -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 +38 -13
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +218 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +156 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +143 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-budget-io.ts +59 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +196 -52
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-role-guard.ts +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +257 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +5 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-types.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +12 -10
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +27 -22
- package/packages/omo-codex/plugin/components/ulw-loop/src/success-criteria-input.ts +56 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +115 -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 +88 -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/barrier.ts +13 -0
- 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/commit-child.ts +65 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-cli-qa.mjs +76 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-crash.ts +23 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +58 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-writer.ts +65 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/lease-clock.ts +23 -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-commit-log.test.ts +252 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-recovery.test.ts +190 -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 +74 -25
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-legacy-projection.test.ts +228 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-newest.test.ts +72 -0
- 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-completed-plan.test.ts +43 -0
- 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-driver-states.test.ts +174 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-dx.test.ts +219 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +169 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +450 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-matrix.test.ts +113 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +310 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/status-next-actions-surface.test.ts +133 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +12 -12
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +12 -6
- 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 +992 -681
- package/packages/omo-codex/plugin/package.json +6 -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.d.mts +4 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +111 -64
- 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 +13 -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 +26 -5
- 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/debugging/references/tools/playwright-cli.md +79 -161
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +10 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +8 -3
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +13 -4
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +1 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/ambience-skill.md +145 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/aside.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +1 -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/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/frontend/references/perfection/README.md +1 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
- package/packages/omo-codex/plugin/skills/git-master/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +20 -18
- 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 +11 -9
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +11 -9
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +116 -424
- package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +32 -86
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +32 -27
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +111 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +86 -95
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +18 -5
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
- package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +44 -27
- package/packages/omo-codex/plugin/skills/{start-work → ulw-execute}/SKILL.md +35 -36
- package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +31 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +33 -10
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +4 -5
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +5 -5
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -0
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +16 -9
- package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +58 -0
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +10 -7
- package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +75 -0
- 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-default-role.test.mjs +66 -0
- 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 +100 -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 +16149 -4304
- 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} +44 -34
- 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 +13 -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 +26 -5
- 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/debugging/references/tools/playwright-cli.md +79 -161
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +10 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +8 -3
- package/packages/shared-skills/skills/frontend/references/design/README.md +13 -4
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +1 -0
- package/packages/shared-skills/skills/frontend/references/design/ambience-skill.md +145 -0
- package/packages/shared-skills/skills/frontend/references/design/aside.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +1 -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/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/frontend/references/perfection/README.md +1 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
- 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 +5 -5
- package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +5 -5
- package/packages/shared-skills/skills/review-work/SKILL.md +101 -419
- package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +32 -86
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +32 -27
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +111 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
- package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
- package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +86 -95
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +18 -5
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
- package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
- package/packages/shared-skills/skills/{start-work → ulw-execute}/SKILL.md +24 -22
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +5 -6
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +6 -5
- package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +2 -2
- package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +2 -1
- package/packages/shared-skills/skills/ulw-research/SKILL.md +10 -5
- package/packages/shared-skills/skills/visual-qa/AGENTS.md +58 -0
- package/packages/shared-skills/skills/visual-qa/SKILL.md +4 -3
- package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +75 -0
- 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/.agents/skills/work-with-pr-workspace/evals/evals.json +0 -76
- package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.json +0 -138
- package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.md +0 -42
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +0 -57
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +0 -15
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +0 -454
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +0 -136
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +0 -47
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +0 -163
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +0 -15
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +0 -615
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +0 -99
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +0 -50
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +0 -111
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +0 -37
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +0 -11
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +0 -205
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +0 -78
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +0 -42
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +0 -87
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +0 -11
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +0 -334
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +0 -86
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +0 -23
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +0 -119
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +0 -32
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +0 -10
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +0 -221
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +0 -104
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +0 -41
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +0 -84
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +0 -10
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +0 -342
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +0 -131
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +0 -39
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +0 -128
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +0 -32
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +0 -10
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +0 -143
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +0 -82
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +0 -51
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +0 -69
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +0 -10
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +0 -252
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +0 -83
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +0 -33
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +0 -101
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +0 -32
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +0 -10
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +0 -387
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +0 -112
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +0 -51
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +0 -75
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +0 -10
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +0 -529
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +0 -127
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +0 -42
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +0 -120
- package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +0 -1
- package/.agents/skills/work-with-pr-workspace/iteration-1/review.html +0 -1326
- package/.opencode/skills/work-with-pr-workspace/evals/evals.json +0 -76
- package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.json +0 -138
- package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.md +0 -42
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +0 -57
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +0 -15
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +0 -454
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +0 -136
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +0 -47
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +0 -163
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +0 -15
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +0 -615
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +0 -99
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +0 -50
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +0 -111
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +0 -37
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +0 -11
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +0 -205
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +0 -78
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +0 -42
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +0 -87
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +0 -11
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +0 -334
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +0 -86
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +0 -23
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +0 -119
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +0 -32
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +0 -10
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +0 -221
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +0 -104
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +0 -41
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +0 -84
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +0 -10
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +0 -342
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +0 -131
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +0 -39
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +0 -128
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +0 -32
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +0 -10
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +0 -143
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +0 -82
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +0 -51
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +0 -69
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +0 -10
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +0 -252
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +0 -83
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +0 -33
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +0 -101
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +0 -32
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +0 -10
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +0 -387
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +0 -112
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +0 -51
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +0 -75
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +0 -10
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +0 -529
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +0 -127
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +0 -42
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +0 -120
- package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +0 -1
- package/.opencode/skills/work-with-pr-workspace/iteration-1/review.html +0 -1326
- 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/skills/visual-qa/references/agent-browser-setup.md +0 -45
- 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 -12244
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9842
- 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 -477
- 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/skills/visual-qa/references/agent-browser-setup.md +0 -45
- 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/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +0 -45
- /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,145 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { readFile as
|
|
6
|
-
import { resolve as
|
|
3
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
4
|
+
import { existsSync as existsSync5, statSync as statSync2 } from "node:fs";
|
|
5
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
6
|
+
import { resolve as resolve4 } 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";
|
|
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 nextActions = [];
|
|
84
|
+
const expected = options.expectedObjective;
|
|
85
|
+
if (!effectiveSnapshot.available) {
|
|
86
|
+
nextActions.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
|
|
87
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, nextActions, errors };
|
|
88
|
+
}
|
|
89
|
+
const normalized = (objectives) => new Set(objectives.map(normalizeObjective).filter(Boolean));
|
|
90
|
+
const accepted = normalized([expected, ...options.acceptedObjectives ?? []]);
|
|
91
|
+
const acknowledged = normalized(options.acknowledgedObjectives ?? []);
|
|
92
|
+
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
93
|
+
let unacknowledgedObjective;
|
|
94
|
+
if (actual && !accepted.has(actual) && !acknowledged.has(actual)) {
|
|
95
|
+
warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
|
|
96
|
+
unacknowledgedObjective = actual;
|
|
97
|
+
}
|
|
98
|
+
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
99
|
+
if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
|
|
100
|
+
nextActions.push("/goal resume or raise the budget");
|
|
101
|
+
}
|
|
102
|
+
if (actualStatus === "complete")
|
|
103
|
+
nextActions.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
|
|
104
|
+
return {
|
|
105
|
+
ok: errors.length === 0,
|
|
106
|
+
snapshot: effectiveSnapshot,
|
|
107
|
+
warnings,
|
|
108
|
+
nextActions,
|
|
109
|
+
errors,
|
|
110
|
+
...unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective }
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function formatCodexGoalReconciliation(reconciliation) {
|
|
114
|
+
const parts = [...reconciliation.errors, ...reconciliation.nextActions, ...reconciliation.warnings];
|
|
115
|
+
return parts.join(" ");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// components/ulw-loop/src/driver-objective-ack.ts
|
|
119
|
+
function normalizeDriverObjective(value) {
|
|
120
|
+
return value.replace(/\s+/g, " ").trim();
|
|
121
|
+
}
|
|
122
|
+
function acknowledgedDriverObjectives(plan) {
|
|
123
|
+
return plan.acknowledgedDriverObjectives ?? [];
|
|
124
|
+
}
|
|
125
|
+
function acknowledgeDriverObjective(plan, objective) {
|
|
126
|
+
if (objective === undefined)
|
|
127
|
+
return false;
|
|
128
|
+
const normalized = normalizeDriverObjective(objective);
|
|
129
|
+
if (!normalized || acknowledgedDriverObjectives(plan).includes(normalized))
|
|
130
|
+
return false;
|
|
131
|
+
plan.acknowledgedDriverObjectives = [...acknowledgedDriverObjectives(plan), normalized];
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
11
134
|
|
|
12
|
-
//
|
|
135
|
+
// components/ulw-loop/src/paths.ts
|
|
13
136
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
14
|
-
//
|
|
137
|
+
// components/ulw-loop/src/constants.ts
|
|
15
138
|
var ULW_LOOP_DIR = ".omo/ulw-loop";
|
|
16
139
|
var ULW_LOOP_BRIEF = "brief.md";
|
|
17
140
|
var ULW_LOOP_GOALS = "goals.json";
|
|
18
141
|
var ULW_LOOP_LEDGER = "ledger.jsonl";
|
|
142
|
+
var ULW_LOOP_STATE_LOCK = ".state.lock";
|
|
19
143
|
var ULW_LOOP_STEERING_MUTATION_KINDS = [
|
|
20
144
|
"add_subgoal",
|
|
21
145
|
"split_subgoal",
|
|
@@ -31,10 +155,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
|
|
|
31
155
|
"regression",
|
|
32
156
|
"adversarial"
|
|
33
157
|
];
|
|
34
|
-
//
|
|
158
|
+
// components/ulw-loop/src/runtime.ts
|
|
35
159
|
class UlwLoopError extends Error {
|
|
36
|
-
code;
|
|
37
|
-
details;
|
|
38
160
|
constructor(message, code, opts) {
|
|
39
161
|
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
|
|
40
162
|
this.name = "UlwLoopError";
|
|
@@ -47,8 +169,8 @@ class UlwLoopError extends Error {
|
|
|
47
169
|
function iso() {
|
|
48
170
|
return new Date().toISOString();
|
|
49
171
|
}
|
|
50
|
-
//
|
|
51
|
-
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"];
|
|
172
|
+
// components/ulw-loop/src/paths.ts
|
|
173
|
+
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
|
|
52
174
|
function normalizeUlwLoopSessionId(sessionId) {
|
|
53
175
|
const trimmed = sessionId?.trim();
|
|
54
176
|
if (!trimmed)
|
|
@@ -81,14 +203,11 @@ function ulwLoopGoalsRelativePath(scope) {
|
|
|
81
203
|
function ulwLoopLedgerRelativePath(scope) {
|
|
82
204
|
return `${ulwLoopRelativeDir(scope)}/${ULW_LOOP_LEDGER}`;
|
|
83
205
|
}
|
|
84
|
-
function ulwLoopBriefPath(repoRoot, scope) {
|
|
85
|
-
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_BRIEF);
|
|
86
|
-
}
|
|
87
206
|
function ulwLoopGoalsPath(repoRoot, scope) {
|
|
88
207
|
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_GOALS);
|
|
89
208
|
}
|
|
90
|
-
function
|
|
91
|
-
return join(ulwLoopDir(repoRoot, scope),
|
|
209
|
+
function ulwLoopStateLockPath(repoRoot, scope) {
|
|
210
|
+
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_STATE_LOCK);
|
|
92
211
|
}
|
|
93
212
|
function repoRelative(absolutePath, repoRoot) {
|
|
94
213
|
const slashPrefix = `${repoRoot}/`;
|
|
@@ -99,8 +218,15 @@ function repoRelative(absolutePath, repoRoot) {
|
|
|
99
218
|
return absolutePath.slice(backslashPrefix.length).split("\\").join("/");
|
|
100
219
|
return absolutePath.split("\\").join("/");
|
|
101
220
|
}
|
|
221
|
+
function ulwLoopEvidenceRoot(scope) {
|
|
222
|
+
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
223
|
+
return sessionId === null ? ".omo/evidence" : `.omo/evidence/ulw/${sessionId}`;
|
|
224
|
+
}
|
|
102
225
|
function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
|
|
103
|
-
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId)
|
|
226
|
+
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
|
|
227
|
+
if (sessionId === null) {
|
|
228
|
+
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 } });
|
|
229
|
+
}
|
|
104
230
|
return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
|
|
105
231
|
}
|
|
106
232
|
var PLATFORM_PATH_API = { relative, isAbsolute, sep };
|
|
@@ -113,7 +239,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
|
|
|
113
239
|
return !pathApi.isAbsolute(relativePath);
|
|
114
240
|
}
|
|
115
241
|
|
|
116
|
-
//
|
|
242
|
+
// components/ulw-loop/src/goal-status.ts
|
|
117
243
|
var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
|
|
118
244
|
function aggregateCodexObjectiveForScope(scope) {
|
|
119
245
|
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,198 +322,553 @@ function hasEssentialCriteriaPass(goal) {
|
|
|
196
322
|
return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
|
|
197
323
|
}
|
|
198
324
|
|
|
199
|
-
//
|
|
200
|
-
function
|
|
201
|
-
|
|
325
|
+
// components/ulw-loop/src/checkpoint-codex-validation.ts
|
|
326
|
+
async function validateCheckpointCodexGoal(input) {
|
|
327
|
+
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
328
|
+
const expected = expectedCodexObjective(input.plan, input.goal);
|
|
329
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
330
|
+
expectedObjective: expected,
|
|
331
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(input.plan),
|
|
332
|
+
...codexGoalMode(input.plan) === "aggregate" ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {}
|
|
333
|
+
});
|
|
334
|
+
if (!reconciliation.ok)
|
|
335
|
+
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
336
|
+
return {
|
|
337
|
+
raw: snapshot?.raw,
|
|
338
|
+
nextActions: reconciliation.nextActions,
|
|
339
|
+
warnings: reconciliation.warnings,
|
|
340
|
+
...reconciliation.unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective: reconciliation.unacknowledgedObjective }
|
|
341
|
+
};
|
|
202
342
|
}
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
343
|
+
function combineCheckpointValidationErrors(codexError, gateError) {
|
|
344
|
+
return new UlwLoopError(`${codexError.message}
|
|
345
|
+
${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
346
|
+
details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
|
|
347
|
+
});
|
|
206
348
|
}
|
|
207
|
-
|
|
208
|
-
|
|
349
|
+
|
|
350
|
+
// components/ulw-loop/src/evidence-artifacts.ts
|
|
351
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
352
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
353
|
+
function fail(message, code, details) {
|
|
354
|
+
throw new UlwLoopError(message, code, { details });
|
|
209
355
|
}
|
|
210
|
-
function
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
return done && verified;
|
|
356
|
+
function stored(repoRoot, absolute) {
|
|
357
|
+
const rel = relative2(repoRoot, absolute);
|
|
358
|
+
const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
359
|
+
return inside ? rel.split(sep2).join("/") : absolute;
|
|
215
360
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
361
|
+
function resolveEvidenceArtifacts(repoRoot, artifacts) {
|
|
362
|
+
if (artifacts === undefined)
|
|
363
|
+
return;
|
|
364
|
+
const seen = new Set;
|
|
365
|
+
const resolved = [];
|
|
366
|
+
for (const [index, candidate] of artifacts.entries()) {
|
|
367
|
+
const path = typeof candidate === "string" ? candidate.trim() : "";
|
|
368
|
+
if (!path)
|
|
369
|
+
fail(`Artifact ${index + 1} must be a non-empty path.`, "ULW_LOOP_ARGUMENT_INVALID", { index });
|
|
370
|
+
const absolute = resolve2(repoRoot, path);
|
|
371
|
+
if (!existsSync2(absolute))
|
|
372
|
+
fail(`Evidence artifact does not exist: ${path} (resolved to ${absolute}).`, "ULW_LOOP_EVIDENCE_ARTIFACT_MISSING", {
|
|
373
|
+
path,
|
|
374
|
+
resolved: absolute
|
|
375
|
+
});
|
|
376
|
+
const value = stored(repoRoot, absolute);
|
|
377
|
+
if (!seen.has(value)) {
|
|
378
|
+
seen.add(value);
|
|
379
|
+
resolved.push(value);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return resolved;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// components/ulw-loop/src/plan-commit.ts
|
|
386
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
387
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
388
|
+
import { linkSync, mkdirSync as mkdirSync2, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
389
|
+
import { rename, writeFile } from "node:fs/promises";
|
|
390
|
+
import { join as join4 } from "node:path";
|
|
391
|
+
|
|
392
|
+
// components/ulw-loop/src/ledger.ts
|
|
393
|
+
import { join as join3 } from "node:path";
|
|
394
|
+
|
|
395
|
+
// components/ulw-loop/src/plan-log.ts
|
|
396
|
+
import { existsSync as existsSync3, readdirSync, readFileSync } from "node:fs";
|
|
397
|
+
import { join as join2 } from "node:path";
|
|
398
|
+
function hasCode(error, code) {
|
|
399
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
400
|
+
}
|
|
401
|
+
function readOptional(path) {
|
|
222
402
|
try {
|
|
223
|
-
|
|
224
|
-
return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
|
|
403
|
+
return readFileSync(path, "utf8");
|
|
225
404
|
} catch (error) {
|
|
226
|
-
if (error
|
|
227
|
-
return
|
|
405
|
+
if (hasCode(error, "ENOENT"))
|
|
406
|
+
return;
|
|
228
407
|
throw error;
|
|
229
408
|
}
|
|
230
409
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
410
|
+
function logNames(dir) {
|
|
411
|
+
try {
|
|
412
|
+
return readdirSync(join2(dir, "revisions")).filter((name) => /^\d{8,}\.json$/.test(name)).sort();
|
|
413
|
+
} catch (error) {
|
|
414
|
+
if (hasCode(error, "ENOENT"))
|
|
415
|
+
return [];
|
|
416
|
+
throw error;
|
|
238
417
|
}
|
|
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
418
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
|
|
419
|
+
function readRecord(dir, name) {
|
|
420
|
+
try {
|
|
421
|
+
const record = JSON.parse(readFileSync(join2(dir, "revisions", name), "utf8"));
|
|
422
|
+
return record.version === 1 && Number.isInteger(record.revision) && record.plan?.version === 1 && Array.isArray(record.plan.goals) && Array.isArray(record.ledger) ? record : undefined;
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (!(error instanceof SyntaxError))
|
|
425
|
+
throw error;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
255
428
|
}
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
429
|
+
function readRecords(dir) {
|
|
430
|
+
const records = [];
|
|
431
|
+
for (const name of logNames(dir)) {
|
|
432
|
+
const record = readRecord(dir, name);
|
|
433
|
+
if (record !== undefined)
|
|
434
|
+
records.push(record);
|
|
435
|
+
}
|
|
436
|
+
return records.sort((a, b) => a.revision - b.revision);
|
|
437
|
+
}
|
|
438
|
+
function readNewestRecord(dir) {
|
|
439
|
+
const names = logNames(dir).sort((a, b) => Number.parseInt(b, 10) - Number.parseInt(a, 10));
|
|
440
|
+
for (const name of names) {
|
|
441
|
+
const record = readRecord(dir, name);
|
|
442
|
+
if (record !== undefined)
|
|
443
|
+
return record;
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
262
446
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
447
|
+
function reconcilePlan(dir) {
|
|
448
|
+
const raw = readOptional(join2(dir, "goals.json"));
|
|
449
|
+
let cached;
|
|
450
|
+
if (raw !== undefined) {
|
|
451
|
+
try {
|
|
452
|
+
cached = JSON.parse(raw);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
if (!(error instanceof SyntaxError))
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const latest = readNewestRecord(dir);
|
|
459
|
+
if (latest === undefined)
|
|
460
|
+
return cached;
|
|
461
|
+
if (cached === undefined)
|
|
462
|
+
return latest.plan;
|
|
463
|
+
if ((cached.revision ?? 0) > latest.revision)
|
|
464
|
+
return cached;
|
|
465
|
+
if (cached.revision === undefined || cached.revision === latest.revision) {
|
|
466
|
+
const published = new Set(latest.plan.goals.map((goal) => goal.id));
|
|
467
|
+
if (Array.isArray(cached.goals) && cached.goals.some((goal) => !published.has(goal.id)))
|
|
468
|
+
return { ...cached, revision: latest.revision };
|
|
266
469
|
}
|
|
267
|
-
return
|
|
470
|
+
return latest.plan;
|
|
471
|
+
}
|
|
472
|
+
function assertProjectionComplete(plan, ledger) {
|
|
473
|
+
const present = new Set(plan.goals.map((goal) => goal.id));
|
|
474
|
+
const missing = new Set;
|
|
475
|
+
for (const entry of ledger)
|
|
476
|
+
if (entry.kind === "goal_added" && entry.goalId !== undefined && !present.has(entry.goalId))
|
|
477
|
+
missing.add(entry.goalId);
|
|
478
|
+
if (missing.size === 0)
|
|
479
|
+
return;
|
|
480
|
+
throw new UlwLoopError(`Refusing to rewrite goals.json: the ledger records goals the plan projection lacks (${[...missing].join(", ")}). Restore those goals into the newest revisions/ record (keeping plan.revision equal to its file number) before mutating this run.`, "ULW_LOOP_PROJECTION_TRUNCATED", { details: { missingGoalIds: [...missing] } });
|
|
481
|
+
}
|
|
482
|
+
function planExists(repoRoot, scope) {
|
|
483
|
+
const dir = ulwLoopDir(repoRoot, scope);
|
|
484
|
+
return existsSync3(join2(dir, "goals.json")) || logNames(dir).length > 0;
|
|
268
485
|
}
|
|
269
486
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
487
|
+
// components/ulw-loop/src/ledger.ts
|
|
488
|
+
function readLedgerAt(dir) {
|
|
489
|
+
const entries = new Map;
|
|
490
|
+
const lines = (readOptional(join3(dir, "ledger.jsonl")) ?? "").split(/\r?\n/);
|
|
491
|
+
let reached = 0;
|
|
492
|
+
for (const [index, line] of lines.entries()) {
|
|
493
|
+
if (line.trim().length === 0)
|
|
494
|
+
continue;
|
|
495
|
+
try {
|
|
496
|
+
const entry = JSON.parse(line);
|
|
497
|
+
entry.revision = entry.revision || reached;
|
|
498
|
+
reached = Math.max(reached, entry.revision);
|
|
499
|
+
entry.id ??= `legacy-${index + 1}`;
|
|
500
|
+
entries.set(entry.id, entry);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
if (!(error instanceof SyntaxError))
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (const record of readRecords(dir))
|
|
507
|
+
for (const [seq, entry] of record.ledger.entries()) {
|
|
508
|
+
const id = entry.id ?? `${record.revision}-${seq}`;
|
|
509
|
+
entries.set(id, { ...entry, revision: record.revision, id });
|
|
510
|
+
}
|
|
511
|
+
const reset = reconcilePlan(dir)?.ledgerResetRevision ?? 0;
|
|
512
|
+
const sequence = (entry) => Number(entry.id?.split("-").at(-1) ?? 0);
|
|
513
|
+
return [...entries.values()].filter((entry) => (entry.revision ?? 0) >= reset).sort((a, b) => (a.revision ?? 0) - (b.revision ?? 0) || sequence(a) - sequence(b));
|
|
276
514
|
}
|
|
277
|
-
function
|
|
278
|
-
return
|
|
515
|
+
function readLedger(repoRoot, scope) {
|
|
516
|
+
return readLedgerAt(ulwLoopDir(repoRoot, scope));
|
|
279
517
|
}
|
|
280
|
-
|
|
281
|
-
|
|
518
|
+
|
|
519
|
+
// components/ulw-loop/src/plan-io.ts
|
|
520
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
521
|
+
import { readdirSync as readdirSync2 } from "node:fs";
|
|
522
|
+
|
|
523
|
+
// components/ulw-loop/src/plan-missing-recovery.ts
|
|
524
|
+
var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
|
|
525
|
+
function createGoalsAction(surface) {
|
|
526
|
+
return surface === "omo-senpi" ? 'agentToolkit.createGoals({ brief: "<brief>" })' : ULW_LOOP_CREATE_GOALS_COMMAND;
|
|
527
|
+
}
|
|
528
|
+
function planMissingRecovery(existingSessionIds, surface = "lazycodex") {
|
|
529
|
+
const lines = [`Recovery: bootstrap the plan with \`${createGoalsAction(surface)}\`.`];
|
|
530
|
+
if (existingSessionIds.length === 0)
|
|
531
|
+
return { message: lines.join(`
|
|
532
|
+
`) };
|
|
533
|
+
lines.push(surface === "omo-senpi" ? `Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. The SDK is bound to the current session; resume the owning session to target its plan.` : `Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
|
|
534
|
+
return { message: lines.join(`
|
|
535
|
+
`), details: { existingSessionIds } };
|
|
536
|
+
}
|
|
537
|
+
function planMissingError(planPath, existingSessionIds, surface = "lazycodex") {
|
|
538
|
+
const recovery = planMissingRecovery(existingSessionIds, surface);
|
|
539
|
+
return new UlwLoopError(`No ulw-loop plan found at ${planPath}.
|
|
540
|
+
${recovery.message}`, "ULW_LOOP_PLAN_MISSING", {
|
|
541
|
+
...recovery.details === undefined ? {} : { details: recovery.details }
|
|
542
|
+
});
|
|
282
543
|
}
|
|
283
|
-
function
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return "unknown";
|
|
544
|
+
function sessionScopeRequiredMessage(flag, existingSessionIds) {
|
|
545
|
+
const lines = [
|
|
546
|
+
"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.",
|
|
547
|
+
`Recovery: pass the scope explicitly: \`${flag} <id>\` (subprocess, eval, and hook contexts do not inherit the session env).`
|
|
548
|
+
];
|
|
549
|
+
if (existingSessionIds.length > 0) {
|
|
550
|
+
lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}.`);
|
|
551
|
+
}
|
|
552
|
+
return lines.join(`
|
|
553
|
+
`);
|
|
294
554
|
}
|
|
295
|
-
function
|
|
296
|
-
return
|
|
555
|
+
function sessionIdRequiredMessage(flag) {
|
|
556
|
+
return [
|
|
557
|
+
`${flag} requires a non-empty value.`,
|
|
558
|
+
"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),",
|
|
559
|
+
`so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
|
|
560
|
+
].join(`
|
|
561
|
+
`);
|
|
297
562
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
563
|
+
|
|
564
|
+
// components/ulw-loop/src/state-lock.ts
|
|
565
|
+
import { randomUUID } from "node:crypto";
|
|
566
|
+
import { closeSync, ftruncateSync, mkdirSync, openSync, readFileSync as readFileSync2, statSync, unlinkSync, writeSync } from "node:fs";
|
|
567
|
+
import { dirname } from "node:path";
|
|
568
|
+
var ULW_LOOP_LOCK_TIMEOUT_CODE = "ULW_LOOP_LOCK_TIMEOUT";
|
|
569
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
570
|
+
var DEFAULT_STALE_MS = 60000;
|
|
571
|
+
var DEFAULT_LEASE_MS = 30000;
|
|
572
|
+
var SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
|
|
573
|
+
var systemClock = {
|
|
574
|
+
now: Date.now,
|
|
575
|
+
schedule(fn, ms) {
|
|
576
|
+
const timer = setTimeout(fn, ms);
|
|
577
|
+
return {
|
|
578
|
+
unref: () => {
|
|
579
|
+
timer.unref();
|
|
580
|
+
},
|
|
581
|
+
cancel: () => clearTimeout(timer)
|
|
582
|
+
};
|
|
303
583
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
584
|
+
};
|
|
585
|
+
async function withStateLock(lockPath, fn, options = {}) {
|
|
586
|
+
const holder = await acquireAsync(lockPath, options);
|
|
587
|
+
const clock = options.clock ?? systemClock;
|
|
588
|
+
let timer;
|
|
589
|
+
let heartbeatError;
|
|
590
|
+
const beat = () => {
|
|
591
|
+
try {
|
|
592
|
+
holder.record.leaseUntil = clock.now() + (options.leaseMs ?? DEFAULT_LEASE_MS);
|
|
593
|
+
writeRecord(holder);
|
|
594
|
+
schedule();
|
|
595
|
+
} catch (error) {
|
|
596
|
+
heartbeatError = error;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
const schedule = () => {
|
|
600
|
+
if (options.heartbeatMs === 0)
|
|
601
|
+
return;
|
|
602
|
+
timer = clock.schedule(beat, options.heartbeatMs ?? (options.leaseMs ?? DEFAULT_LEASE_MS) / 3);
|
|
603
|
+
timer.unref();
|
|
312
604
|
};
|
|
605
|
+
schedule();
|
|
606
|
+
try {
|
|
607
|
+
const result = await fn(holder.record.token);
|
|
608
|
+
if (heartbeatError !== undefined)
|
|
609
|
+
throw heartbeatError;
|
|
610
|
+
return result;
|
|
611
|
+
} finally {
|
|
612
|
+
timer?.cancel();
|
|
613
|
+
closeSync(holder.fd);
|
|
614
|
+
release(lockPath, holder.record.token);
|
|
615
|
+
}
|
|
313
616
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return null;
|
|
317
|
-
const trimmed = raw.trim();
|
|
617
|
+
function withStateLockSync(lockPath, fn, options = {}) {
|
|
618
|
+
const holder = acquireSync(lockPath, options);
|
|
318
619
|
try {
|
|
319
|
-
return
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
620
|
+
return fn();
|
|
621
|
+
} finally {
|
|
622
|
+
closeSync(holder.fd);
|
|
623
|
+
release(lockPath, holder.record.token);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function isStateLockTimeout(error) {
|
|
627
|
+
return error instanceof UlwLoopError && error.code === ULW_LOOP_LOCK_TIMEOUT_CODE;
|
|
628
|
+
}
|
|
629
|
+
async function acquireAsync(lockPath, options) {
|
|
630
|
+
const clock = options.clock ?? systemClock;
|
|
631
|
+
const deadline = clock.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
632
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
633
|
+
for (let attempt = 0;; ) {
|
|
634
|
+
const outcome = attemptOnce(lockPath, options, clock.now(), options.leaseMs ?? DEFAULT_LEASE_MS);
|
|
635
|
+
if (outcome.kind === "acquired")
|
|
636
|
+
return outcome.holder;
|
|
637
|
+
if (outcome.kind === "retry")
|
|
638
|
+
continue;
|
|
639
|
+
if (clock.now() >= deadline)
|
|
640
|
+
throw lockTimeout(lockPath, options);
|
|
641
|
+
await new Promise((resolve) => clock.schedule(resolve, backoffMs(attempt)));
|
|
642
|
+
attempt += 1;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function acquireSync(lockPath, options) {
|
|
646
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
647
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
648
|
+
for (let attempt = 0;; ) {
|
|
649
|
+
const outcome = attemptOnce(lockPath, options, Date.now());
|
|
650
|
+
if (outcome.kind === "acquired")
|
|
651
|
+
return outcome.holder;
|
|
652
|
+
if (outcome.kind === "retry")
|
|
653
|
+
continue;
|
|
654
|
+
if (Date.now() >= deadline)
|
|
655
|
+
throw lockTimeout(lockPath, options);
|
|
656
|
+
Atomics.wait(SLEEP_CELL, 0, 0, backoffMs(attempt));
|
|
657
|
+
attempt += 1;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function attemptOnce(lockPath, options, now, leaseMs) {
|
|
661
|
+
try {
|
|
662
|
+
const holder = tryCreate(lockPath, now, leaseMs);
|
|
663
|
+
if (holder !== null)
|
|
664
|
+
return { kind: "acquired", holder };
|
|
665
|
+
const snapshot = readSnapshot(lockPath, now);
|
|
666
|
+
if (snapshot === null)
|
|
667
|
+
return { kind: "retry" };
|
|
668
|
+
const record = snapshot.record;
|
|
669
|
+
const stale = record === null ? snapshot.ageMs > (options.staleMs ?? DEFAULT_STALE_MS) : !isProcessAlive(record.pid) || record.leaseUntil !== undefined && now > record.leaseUntil;
|
|
670
|
+
if (stale && reclaim(lockPath, snapshot.raw))
|
|
671
|
+
return { kind: "retry" };
|
|
672
|
+
return { kind: "wait" };
|
|
673
|
+
} catch (error) {
|
|
674
|
+
if (hasCode2(error, "EINTR"))
|
|
675
|
+
return { kind: "wait" };
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function writeRecord(holder) {
|
|
680
|
+
const buf = Buffer.from(JSON.stringify(holder.record));
|
|
681
|
+
ftruncateSync(holder.fd, 0);
|
|
682
|
+
let offset = 0;
|
|
683
|
+
while (offset < buf.length) {
|
|
684
|
+
const written = writeSync(holder.fd, buf, offset, buf.length - offset, offset);
|
|
685
|
+
if (written === 0)
|
|
686
|
+
throw new Error("State lock write made no progress.");
|
|
687
|
+
offset += written;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function tryCreate(lockPath, now, leaseMs) {
|
|
691
|
+
let fd;
|
|
692
|
+
try {
|
|
693
|
+
fd = openSync(lockPath, "wx");
|
|
694
|
+
} catch (error) {
|
|
695
|
+
if (hasCode2(error, "EEXIST"))
|
|
696
|
+
return null;
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
const record = {
|
|
700
|
+
pid: process.pid,
|
|
701
|
+
createdAt: new Date(now).toISOString(),
|
|
702
|
+
token: randomUUID(),
|
|
703
|
+
...leaseMs === undefined ? {} : { leaseUntil: now + leaseMs }
|
|
704
|
+
};
|
|
705
|
+
const holder = { fd, record };
|
|
706
|
+
try {
|
|
707
|
+
writeRecord(holder);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
closeSync(fd);
|
|
325
710
|
try {
|
|
326
|
-
|
|
327
|
-
} catch (
|
|
328
|
-
|
|
711
|
+
unlinkSync(lockPath);
|
|
712
|
+
} catch (cleanup) {
|
|
713
|
+
if (!hasCode2(cleanup, "ENOENT"))
|
|
714
|
+
throw cleanup;
|
|
329
715
|
}
|
|
716
|
+
throw error;
|
|
330
717
|
}
|
|
718
|
+
return holder;
|
|
331
719
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
720
|
+
function readSnapshot(lockPath, now) {
|
|
721
|
+
try {
|
|
722
|
+
const raw = readFileSync2(lockPath, "utf8");
|
|
723
|
+
return { raw, record: parseRecord(raw), ageMs: now - statSync(lockPath).mtimeMs };
|
|
724
|
+
} catch (error) {
|
|
725
|
+
if (hasCode2(error, "ENOENT"))
|
|
726
|
+
return null;
|
|
727
|
+
throw error;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function parseRecord(raw) {
|
|
731
|
+
try {
|
|
732
|
+
const record = JSON.parse(raw);
|
|
733
|
+
if (typeof record !== "object" || record === null)
|
|
734
|
+
return null;
|
|
735
|
+
if (!("pid" in record) || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0)
|
|
736
|
+
return null;
|
|
737
|
+
if (!("createdAt" in record) || typeof record.createdAt !== "string")
|
|
738
|
+
return null;
|
|
739
|
+
if (!("token" in record) || typeof record.token !== "string" || record.token.length === 0)
|
|
740
|
+
return null;
|
|
741
|
+
return {
|
|
742
|
+
pid: record.pid,
|
|
743
|
+
createdAt: record.createdAt,
|
|
744
|
+
token: record.token,
|
|
745
|
+
..."leaseUntil" in record && typeof record.leaseUntil === "number" ? { leaseUntil: record.leaseUntil } : {}
|
|
746
|
+
};
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (error instanceof SyntaxError)
|
|
749
|
+
return null;
|
|
750
|
+
throw error;
|
|
356
751
|
}
|
|
357
|
-
|
|
358
|
-
|
|
752
|
+
}
|
|
753
|
+
function isProcessAlive(pid) {
|
|
754
|
+
try {
|
|
755
|
+
process.kill(pid, 0);
|
|
756
|
+
return true;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
if (hasCode2(error, "ESRCH"))
|
|
759
|
+
return false;
|
|
760
|
+
if (hasCode2(error, "EPERM"))
|
|
761
|
+
return true;
|
|
762
|
+
throw error;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function reclaim(lockPath, expectedRaw) {
|
|
766
|
+
const current = readSnapshot(lockPath, Date.now());
|
|
767
|
+
if (current === null)
|
|
768
|
+
return true;
|
|
769
|
+
if (current.raw !== expectedRaw)
|
|
770
|
+
return false;
|
|
771
|
+
try {
|
|
772
|
+
unlinkSync(lockPath);
|
|
773
|
+
} catch (error) {
|
|
774
|
+
if (hasCode2(error, "EPERM") || hasCode2(error, "EACCES"))
|
|
775
|
+
return false;
|
|
776
|
+
if (!hasCode2(error, "ENOENT"))
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
function release(lockPath, token) {
|
|
782
|
+
if (readSnapshot(lockPath, Date.now())?.record?.token !== token)
|
|
783
|
+
return;
|
|
784
|
+
try {
|
|
785
|
+
unlinkSync(lockPath);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (!hasCode2(error, "ENOENT"))
|
|
788
|
+
throw error;
|
|
359
789
|
}
|
|
360
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
361
790
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
791
|
+
function backoffMs(attempt) {
|
|
792
|
+
return Math.min(100, 5 * 2 ** attempt) + Math.random() * 5;
|
|
793
|
+
}
|
|
794
|
+
function lockTimeout(lockPath, options) {
|
|
795
|
+
const holder = readSnapshot(lockPath, Date.now())?.record;
|
|
796
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
797
|
+
const owner = holder == null ? "another process" : `pid ${holder.pid}`;
|
|
798
|
+
return new UlwLoopError(`ulw-loop state lock ${lockPath} is held by ${owner} for more than ${timeoutMs}ms. The lock owner is still alive. If a JS eval kernel was interrupted while writing, wait for its lease to expire (${options.leaseMs ?? DEFAULT_LEASE_MS} ms) or restart the owning senpi process; never delete a lock owned by a live process.`, ULW_LOOP_LOCK_TIMEOUT_CODE, { details: { lockPath, timeoutMs, ...holder == null ? {} : { holderPid: holder.pid } } });
|
|
799
|
+
}
|
|
800
|
+
function hasCode2(error, code) {
|
|
801
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
365
802
|
}
|
|
366
803
|
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
804
|
+
// components/ulw-loop/src/plan-io.ts
|
|
805
|
+
function readLedger2(repoRoot, scope) {
|
|
806
|
+
const lockPath = ulwLoopStateLockPath(repoRoot, scope);
|
|
807
|
+
if (heldLocks.getStore()?.has(lockPath)) {
|
|
808
|
+
assertStateLockOwned(lockPath);
|
|
809
|
+
materializeSync(ulwLoopDir(repoRoot, scope));
|
|
810
|
+
}
|
|
811
|
+
return readLedger(repoRoot, scope);
|
|
812
|
+
}
|
|
371
813
|
var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
|
|
372
814
|
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
815
|
var locks = new Map;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
816
|
+
var heldLocks = new AsyncLocalStorage;
|
|
817
|
+
var lockOptions = new AsyncLocalStorage;
|
|
818
|
+
function tokenAt(lockPath) {
|
|
819
|
+
const raw = readOptional(lockPath);
|
|
820
|
+
if (raw === undefined)
|
|
821
|
+
return;
|
|
822
|
+
try {
|
|
823
|
+
const record = JSON.parse(raw);
|
|
824
|
+
return typeof record === "object" && record !== null && "token" in record && typeof record.token === "string" ? record.token : undefined;
|
|
825
|
+
} catch (error) {
|
|
826
|
+
if (error instanceof SyntaxError)
|
|
827
|
+
return;
|
|
828
|
+
throw error;
|
|
829
|
+
}
|
|
379
830
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
831
|
+
function assertStateLockOwned(lockPath) {
|
|
832
|
+
const context = heldLocks.getStore()?.get(lockPath);
|
|
833
|
+
if (context === undefined)
|
|
834
|
+
return;
|
|
835
|
+
if (tokenAt(lockPath) !== context.token)
|
|
836
|
+
throw new UlwLoopError("The ulw-loop mutation lock changed owners.", "ULW_LOOP_LOCK_LOST");
|
|
837
|
+
}
|
|
838
|
+
function migrationEntries(plan) {
|
|
839
|
+
for (const context of heldLocks.getStore()?.values() ?? []) {
|
|
840
|
+
const entries = context.migrations.get(plan.goalsPath);
|
|
841
|
+
if (entries !== undefined) {
|
|
842
|
+
context.migrations.delete(plan.goalsPath);
|
|
843
|
+
return entries;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return [];
|
|
382
847
|
}
|
|
383
|
-
async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
848
|
+
async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn, options = {}) {
|
|
384
849
|
const scope = typeof scopeOrFn === "function" ? undefined : scopeOrFn;
|
|
385
850
|
const fn = typeof scopeOrFn === "function" ? scopeOrFn : maybeFn;
|
|
386
851
|
if (fn === undefined)
|
|
387
852
|
throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
|
|
388
853
|
const lockKey = `${repoRoot}\x00${ulwLoopRelativeDir(scope)}`;
|
|
854
|
+
const lockPath = ulwLoopStateLockPath(repoRoot, scope);
|
|
855
|
+
const locked = () => withStateLock(lockPath, async (token) => {
|
|
856
|
+
return heldLocks.run(new Map([...heldLocks.getStore() ?? [], [lockPath, { token, migrations: new Map }]]), async () => {
|
|
857
|
+
for (let attempt = 0;; attempt += 1) {
|
|
858
|
+
try {
|
|
859
|
+
return await fn();
|
|
860
|
+
} catch (error) {
|
|
861
|
+
if (!(error instanceof UlwLoopError) || error.code !== "ULW_LOOP_PUBLISH_CONFLICT")
|
|
862
|
+
throw error;
|
|
863
|
+
assertStateLockOwned(lockPath);
|
|
864
|
+
if (attempt !== 0)
|
|
865
|
+
throw error;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}, { ...lockOptions.getStore(), ...options });
|
|
389
870
|
const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
|
|
390
|
-
const run = prior.then(
|
|
871
|
+
const run = prior.then(locked, locked);
|
|
391
872
|
const gate = run.then(() => {
|
|
392
873
|
return;
|
|
393
874
|
}, () => {
|
|
@@ -400,89 +881,144 @@ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
|
400
881
|
});
|
|
401
882
|
return run;
|
|
402
883
|
}
|
|
403
|
-
|
|
884
|
+
function readUlwLoopPlanSync(repoRoot, scope) {
|
|
404
885
|
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (!hasCode(error, "ENOENT"))
|
|
410
|
-
throw error;
|
|
411
|
-
throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}. Run \`omo-agent-toolkit ulw-loop create-goals ...\` first.`, "ULW_LOOP_PLAN_MISSING", { cause: error });
|
|
412
|
-
}
|
|
413
|
-
const parsed = JSON.parse(raw);
|
|
414
|
-
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
|
886
|
+
const parsed = reconcilePlan(ulwLoopDir(repoRoot, scope));
|
|
887
|
+
if (parsed === undefined)
|
|
888
|
+
throw planMissingError(repoRelative(path, repoRoot), listUlwLoopSessionIds(repoRoot));
|
|
889
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.goals))
|
|
415
890
|
throw new UlwLoopError(`Invalid ulw-loop plan at ${repoRelative(path, repoRoot)}.`, "ULW_LOOP_PLAN_INVALID");
|
|
416
|
-
}
|
|
417
891
|
const previousObjective = parsed.codexObjective;
|
|
418
|
-
if ((parsed.codexGoalMode ?? "per_story") === "aggregate" &&
|
|
419
|
-
const now = iso();
|
|
892
|
+
if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && previousObjective !== undefined && (previousObjective === LEGACY_OBJECTIVE || previousObjective.startsWith(LEGACY_OBJECTIVE_PREFIX))) {
|
|
420
893
|
parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
|
|
421
894
|
parsed.codexObjectiveAliases = [...new Set([...parsed.codexObjectiveAliases ?? [], previousObjective])];
|
|
422
|
-
parsed.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}, scope);
|
|
895
|
+
heldLocks.getStore()?.get(ulwLoopStateLockPath(repoRoot, scope))?.migrations.set(parsed.goalsPath, [
|
|
896
|
+
{
|
|
897
|
+
at: iso(),
|
|
898
|
+
kind: "aggregate_objective_migrated",
|
|
899
|
+
before: { codexObjective: previousObjective },
|
|
900
|
+
after: { codexObjective: parsed.codexObjective }
|
|
901
|
+
}
|
|
902
|
+
]);
|
|
431
903
|
}
|
|
432
904
|
return parsed;
|
|
433
905
|
}
|
|
434
|
-
async function
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
906
|
+
async function readUlwLoopPlan(repoRoot, scope) {
|
|
907
|
+
if (heldLocks.getStore()?.has(ulwLoopStateLockPath(repoRoot, scope))) {
|
|
908
|
+
assertStateLockOwned(ulwLoopStateLockPath(repoRoot, scope));
|
|
909
|
+
await materialize(ulwLoopDir(repoRoot, scope));
|
|
910
|
+
}
|
|
911
|
+
return readUlwLoopPlanSync(repoRoot, scope);
|
|
912
|
+
}
|
|
913
|
+
function listUlwLoopSessionIds(repoRoot) {
|
|
914
|
+
try {
|
|
915
|
+
return readdirSync2(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
916
|
+
} catch {
|
|
917
|
+
return [];
|
|
918
|
+
}
|
|
441
919
|
}
|
|
442
|
-
|
|
443
|
-
|
|
920
|
+
function isSteeringKind(value) {
|
|
921
|
+
return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised" || value === "batch_updated";
|
|
444
922
|
}
|
|
445
|
-
async function
|
|
446
|
-
|
|
447
|
-
return;
|
|
448
|
-
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
449
|
-
await appendFile(ulwLoopLedgerPath(repoRoot, scope), `${entries.map((entry) => JSON.stringify(entry)).join(`
|
|
450
|
-
`)}
|
|
451
|
-
`, "utf8");
|
|
923
|
+
async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
|
|
924
|
+
return readLedger2(repoRoot, scope).find((entry) => isSteeringKind(entry.kind) && entry.steering?.invariant.accepted === true && (entry.idempotencyKey === key || entry.steering.idempotencyKey === key || entry.steering.promptSignature === key));
|
|
452
925
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
926
|
+
|
|
927
|
+
// components/ulw-loop/src/plan-commit.ts
|
|
928
|
+
var hooks = new AsyncLocalStorage2;
|
|
929
|
+
async function beforePlanMutation() {
|
|
930
|
+
await hooks.getStore()?.beforeMutation?.();
|
|
931
|
+
}
|
|
932
|
+
async function writeViewFile(path, content) {
|
|
933
|
+
await (hooks.getStore()?.writeView ?? writeFile)(path, content);
|
|
934
|
+
}
|
|
935
|
+
async function commit(repoRoot, scope, mutation) {
|
|
936
|
+
const { plan } = mutation;
|
|
937
|
+
const dir = ulwLoopDir(repoRoot, scope);
|
|
938
|
+
assertStateLockOwned(ulwLoopStateLockPath(repoRoot, scope));
|
|
939
|
+
await hooks.getStore()?.beforeCommit?.();
|
|
940
|
+
const next = (plan.revision ?? 0) + 1;
|
|
941
|
+
const brief = plan.brief ?? readOptional(join4(dir, "brief.md")) ?? "";
|
|
942
|
+
const entries = [...migrationEntries(plan), ...mutation.entries].map((entry, seq) => ({
|
|
943
|
+
...entry,
|
|
944
|
+
revision: next,
|
|
945
|
+
id: `${next}-${seq}`
|
|
946
|
+
}));
|
|
947
|
+
const record = {
|
|
948
|
+
version: 1,
|
|
949
|
+
revision: next,
|
|
950
|
+
plan: { ...plan, revision: next, brief },
|
|
951
|
+
ledger: entries
|
|
952
|
+
};
|
|
953
|
+
mkdirSync2(join4(dir, "revisions"), { recursive: true });
|
|
954
|
+
mkdirSync2(join4(dir, "tmp"), { recursive: true });
|
|
955
|
+
const temp = join4(dir, "tmp", `${process.pid}-${randomUUID2()}.json`);
|
|
456
956
|
try {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
957
|
+
writeFileSync(temp, `${JSON.stringify(record)}
|
|
958
|
+
`, { flag: "wx" });
|
|
959
|
+
try {
|
|
960
|
+
(hooks.getStore()?.link ?? linkSync)(temp, join4(dir, "revisions", `${String(next).padStart(8, "0")}.json`));
|
|
961
|
+
} catch (error) {
|
|
962
|
+
if (hasCode(error, "EEXIST"))
|
|
963
|
+
throw new UlwLoopError("Another writer published the read revision.", "ULW_LOOP_PUBLISH_CONFLICT", {
|
|
964
|
+
cause: error
|
|
965
|
+
});
|
|
966
|
+
if (["EPERM", "ENOTSUP", "EXDEV"].some((code) => hasCode(error, code)))
|
|
967
|
+
throw new UlwLoopError("Immutable ulw-loop publication requires atomic hard links.", "ULW_LOOP_PUBLISH_UNSUPPORTED_FS", { cause: error });
|
|
463
968
|
throw error;
|
|
969
|
+
}
|
|
464
970
|
} finally {
|
|
465
|
-
|
|
466
|
-
stream.destroy();
|
|
971
|
+
rmSync(temp, { force: true });
|
|
467
972
|
}
|
|
973
|
+
plan.revision = next;
|
|
974
|
+
plan.brief = brief;
|
|
975
|
+
for (const [index, entry] of mutation.entries.entries())
|
|
976
|
+
Object.assign(entry, entries[index + entries.length - mutation.entries.length]);
|
|
977
|
+
await hooks.getStore()?.afterCommit?.();
|
|
978
|
+
await materialize(dir);
|
|
979
|
+
}
|
|
980
|
+
function viewContents(dir) {
|
|
981
|
+
const plan = reconcilePlan(dir);
|
|
982
|
+
if (plan === undefined || readRecords(dir).length === 0)
|
|
983
|
+
return [];
|
|
984
|
+
const ledger = readLedgerAt(dir);
|
|
985
|
+
assertProjectionComplete(plan, ledger);
|
|
986
|
+
const views = [
|
|
987
|
+
["goals.json", `${JSON.stringify(plan, null, 2)}
|
|
988
|
+
`],
|
|
989
|
+
["ledger.jsonl", ledger.length === 0 ? "" : `${ledger.map((entry) => JSON.stringify(entry)).join(`
|
|
990
|
+
`)}
|
|
991
|
+
`],
|
|
992
|
+
["brief.md", plan.brief ?? readOptional(join4(dir, "brief.md")) ?? ""]
|
|
993
|
+
];
|
|
994
|
+
return views.filter(([name, content]) => readOptional(join4(dir, name)) !== content);
|
|
468
995
|
}
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
996
|
+
function materializeSync(dir) {
|
|
997
|
+
for (const [name, content] of viewContents(dir)) {
|
|
998
|
+
mkdirSync2(join4(dir, "tmp"), { recursive: true });
|
|
999
|
+
const temp = join4(dir, "tmp", `${process.pid}-${randomUUID2()}-${name}`);
|
|
1000
|
+
try {
|
|
1001
|
+
writeFileSync(temp, content, { flag: "wx" });
|
|
1002
|
+
renameSync(temp, join4(dir, name));
|
|
1003
|
+
} finally {
|
|
1004
|
+
rmSync(temp, { force: true });
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async function materialize(dir) {
|
|
1009
|
+
for (const [name, content] of viewContents(dir)) {
|
|
1010
|
+
mkdirSync2(join4(dir, "tmp"), { recursive: true });
|
|
1011
|
+
const temp = join4(dir, "tmp", `${process.pid}-${randomUUID2()}-${name}`);
|
|
1012
|
+
try {
|
|
1013
|
+
await writeViewFile(temp, content);
|
|
1014
|
+
await rename(temp, join4(dir, name));
|
|
1015
|
+
} finally {
|
|
1016
|
+
rmSync(temp, { force: true });
|
|
1017
|
+
}
|
|
481
1018
|
}
|
|
482
|
-
return;
|
|
483
1019
|
}
|
|
484
1020
|
|
|
485
|
-
//
|
|
1021
|
+
// components/ulw-loop/src/evidence.ts
|
|
486
1022
|
function ulwLoopFail(message, code, details) {
|
|
487
1023
|
throw new UlwLoopError(message, code, { details });
|
|
488
1024
|
}
|
|
@@ -519,6 +1055,7 @@ async function recordEvidence(repoRoot, args, scope) {
|
|
|
519
1055
|
const goal = findGoal(plan, args.goalId);
|
|
520
1056
|
const criterion = findCriterion(goal, args.criterionId);
|
|
521
1057
|
const evidence = nonEmptyEvidence(args.evidence);
|
|
1058
|
+
const artifacts = resolveEvidenceArtifacts(repoRoot, args.artifacts);
|
|
522
1059
|
const kind = ledgerKind(args.status);
|
|
523
1060
|
const prevStatus = criterion.status;
|
|
524
1061
|
const capturedAt = iso();
|
|
@@ -527,9 +1064,12 @@ async function recordEvidence(repoRoot, args, scope) {
|
|
|
527
1064
|
criterion.capturedAt = capturedAt;
|
|
528
1065
|
if (args.notes !== undefined)
|
|
529
1066
|
criterion.notes = args.notes;
|
|
1067
|
+
if (artifacts !== undefined)
|
|
1068
|
+
criterion.artifacts = artifacts;
|
|
1069
|
+
else
|
|
1070
|
+
delete criterion.artifacts;
|
|
530
1071
|
goal.updatedAt = capturedAt;
|
|
531
1072
|
plan.updatedAt = capturedAt;
|
|
532
|
-
await writePlan(repoRoot, plan, scope);
|
|
533
1073
|
const ledgerEntry = {
|
|
534
1074
|
at: capturedAt,
|
|
535
1075
|
kind,
|
|
@@ -538,10 +1078,11 @@ async function recordEvidence(repoRoot, args, scope) {
|
|
|
538
1078
|
criterionStatus: args.status,
|
|
539
1079
|
evidence,
|
|
540
1080
|
capturedEvidence: evidence,
|
|
1081
|
+
...artifacts === undefined ? {} : { artifacts },
|
|
541
1082
|
before: { status: prevStatus },
|
|
542
1083
|
after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus }
|
|
543
1084
|
};
|
|
544
|
-
await
|
|
1085
|
+
await commit(repoRoot, scope, { plan, entries: [ledgerEntry] });
|
|
545
1086
|
return { plan, goal, criterion, ledgerEntry };
|
|
546
1087
|
});
|
|
547
1088
|
}
|
|
@@ -588,57 +1129,216 @@ function requireEssentialCriteriaPass(goal) {
|
|
|
588
1129
|
});
|
|
589
1130
|
}
|
|
590
1131
|
|
|
591
|
-
//
|
|
592
|
-
import { resolve as
|
|
1132
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
1133
|
+
import { resolve as resolve3 } from "node:path";
|
|
593
1134
|
|
|
594
|
-
//
|
|
595
|
-
var PLACEHOLDER_PATTERN = /^(
|
|
1135
|
+
// components/ulw-loop/src/quality-gate-fields.ts
|
|
1136
|
+
var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
1137
|
+
var activeCollector;
|
|
1138
|
+
function withQualityGateCollector(operation) {
|
|
1139
|
+
const previous = activeCollector;
|
|
1140
|
+
const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
|
|
1141
|
+
activeCollector = collector;
|
|
1142
|
+
try {
|
|
1143
|
+
const result = operation();
|
|
1144
|
+
if (collector.defects.length === 0)
|
|
1145
|
+
return result;
|
|
1146
|
+
throwQualityGateDefects(collector.defects);
|
|
1147
|
+
} finally {
|
|
1148
|
+
activeCollector = previous;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
function throwQualityGateDefects(defects) {
|
|
1152
|
+
const uniqueDefects = [
|
|
1153
|
+
...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
|
|
1154
|
+
];
|
|
1155
|
+
const fields = uniqueDefects.slice(0, 25);
|
|
1156
|
+
const truncated = uniqueDefects.length > fields.length;
|
|
1157
|
+
const message = [
|
|
1158
|
+
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
1159
|
+
...fields.map((item) => `- ${item.field}: ${item.message}`)
|
|
1160
|
+
].join(`
|
|
1161
|
+
`);
|
|
1162
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
1163
|
+
details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
596
1166
|
function invalid(message, field) {
|
|
597
|
-
|
|
1167
|
+
if (activeCollector === undefined)
|
|
1168
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
|
|
1169
|
+
activeCollector.defects.push({ field, message });
|
|
1170
|
+
activeCollector.poisonedFields.add(field);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
function isPoisoned(field) {
|
|
1174
|
+
return activeCollector?.poisonedFields.has(field) ?? false;
|
|
1175
|
+
}
|
|
1176
|
+
function markPoisonedArtifactKind(id) {
|
|
1177
|
+
activeCollector?.poisonedArtifactKinds.add(id);
|
|
1178
|
+
}
|
|
1179
|
+
function isPoisonedArtifactKind(id) {
|
|
1180
|
+
return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
|
|
598
1181
|
}
|
|
599
1182
|
function isRecord(value) {
|
|
600
1183
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
601
1184
|
}
|
|
602
1185
|
function section(value, field) {
|
|
603
|
-
|
|
1186
|
+
if (isRecord(value))
|
|
1187
|
+
return value;
|
|
1188
|
+
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);
|
|
1189
|
+
return {};
|
|
604
1190
|
}
|
|
605
1191
|
function textField(value, field) {
|
|
606
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
1192
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
607
1193
|
invalid(`Final quality gate requires non-empty ${field}.`, field);
|
|
1194
|
+
return "";
|
|
1195
|
+
}
|
|
608
1196
|
const trimmed = value.trim();
|
|
609
1197
|
if (PLACEHOLDER_PATTERN.test(trimmed))
|
|
610
1198
|
invalid(`Final quality gate rejects placeholder ${field}.`, field);
|
|
611
1199
|
return trimmed;
|
|
612
1200
|
}
|
|
613
1201
|
function numberField(value, field) {
|
|
614
|
-
|
|
1202
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
1203
|
+
return value;
|
|
1204
|
+
invalid(`Final quality gate requires numeric ${field}.`, field);
|
|
1205
|
+
return 0;
|
|
615
1206
|
}
|
|
616
1207
|
function stringArray(value, field) {
|
|
617
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
618
|
-
|
|
1208
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1209
|
+
invalid(`Final quality gate requires ${field}.`, field);
|
|
1210
|
+
return [];
|
|
1211
|
+
}
|
|
619
1212
|
return value.map((item) => textField(item, field));
|
|
620
1213
|
}
|
|
621
1214
|
function emptyBlockers(value, field) {
|
|
622
1215
|
if (Array.isArray(value) && value.length === 0)
|
|
623
1216
|
return [];
|
|
624
1217
|
invalid(`${field} must be empty.`, field);
|
|
1218
|
+
return [];
|
|
625
1219
|
}
|
|
626
1220
|
function literal(value, expected, field) {
|
|
627
1221
|
if (value === expected)
|
|
628
1222
|
return expected;
|
|
629
1223
|
invalid(`${field} must be ${String(expected)}.`, field);
|
|
1224
|
+
return expected;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
1228
|
+
var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
|
|
1229
|
+
var SUPPORTED_KINDS = [
|
|
1230
|
+
"cli-transcript",
|
|
1231
|
+
"log",
|
|
1232
|
+
"screenshot",
|
|
1233
|
+
"image",
|
|
1234
|
+
"http-dump",
|
|
1235
|
+
"data-diff"
|
|
1236
|
+
];
|
|
1237
|
+
var COMPATIBLE_KINDS = {
|
|
1238
|
+
cli: ["cli-transcript", "log"],
|
|
1239
|
+
tmux: ["cli-transcript", "log"],
|
|
1240
|
+
http: ["http-dump"],
|
|
1241
|
+
browser: ["screenshot", "image"],
|
|
1242
|
+
gui: ["screenshot", "image"],
|
|
1243
|
+
data: ["data-diff"]
|
|
1244
|
+
};
|
|
1245
|
+
function isSupportedSurface(value) {
|
|
1246
|
+
return SUPPORTED_SURFACES.some((surface) => surface === value);
|
|
1247
|
+
}
|
|
1248
|
+
function isSupportedKind(value) {
|
|
1249
|
+
return SUPPORTED_KINDS.some((kind) => kind === value);
|
|
1250
|
+
}
|
|
1251
|
+
function surfaceField(value, field) {
|
|
1252
|
+
if (isSupportedSurface(value))
|
|
1253
|
+
return value;
|
|
1254
|
+
invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
|
|
1255
|
+
return "cli";
|
|
1256
|
+
}
|
|
1257
|
+
function kindField(value, field) {
|
|
1258
|
+
if (isSupportedKind(value))
|
|
1259
|
+
return value;
|
|
1260
|
+
invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
|
|
1261
|
+
return "log";
|
|
1262
|
+
}
|
|
1263
|
+
function compatibleKindsFor(surface) {
|
|
1264
|
+
return COMPATIBLE_KINDS[surface];
|
|
1265
|
+
}
|
|
1266
|
+
function artifactCompatible(surface, kind) {
|
|
1267
|
+
return compatibleKindsFor(surface).includes(kind);
|
|
1268
|
+
}
|
|
1269
|
+
function checkFile(path, field, opts) {
|
|
1270
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
1271
|
+
return;
|
|
1272
|
+
const absolute = resolve3(opts.repoRoot, path);
|
|
1273
|
+
if (!opts.fs.existsSync(absolute)) {
|
|
1274
|
+
invalid(`${field} must point to an existing artifact.`, field);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (opts.fs.statSync(absolute).size <= 0)
|
|
1278
|
+
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
1279
|
+
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve3(opts.repoRoot, opts.currentAttemptDir)))
|
|
1280
|
+
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
1281
|
+
}
|
|
1282
|
+
function artifactMap(refs) {
|
|
1283
|
+
const byId = new Map;
|
|
1284
|
+
for (const ref of refs) {
|
|
1285
|
+
if (byId.has(ref.id))
|
|
1286
|
+
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
1287
|
+
byId.set(ref.id, ref);
|
|
1288
|
+
}
|
|
1289
|
+
return byId;
|
|
1290
|
+
}
|
|
1291
|
+
function parseArtifactRefs(value, opts) {
|
|
1292
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1293
|
+
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
1294
|
+
return [];
|
|
1295
|
+
}
|
|
1296
|
+
return value.flatMap((item, index) => {
|
|
1297
|
+
const prefix = `manualQa.artifactRefs[${index}]`;
|
|
1298
|
+
const ref = section(item, prefix);
|
|
1299
|
+
if (isPoisoned(prefix))
|
|
1300
|
+
return [];
|
|
1301
|
+
const pathField = `${prefix}.path`;
|
|
1302
|
+
const idField = `${prefix}.id`;
|
|
1303
|
+
const kindFieldName = `${prefix}.kind`;
|
|
1304
|
+
const descriptionField = `${prefix}.description`;
|
|
1305
|
+
const path = textField(ref["path"], pathField);
|
|
1306
|
+
const id = textField(ref["id"], idField);
|
|
1307
|
+
const kind = kindField(ref["kind"], kindFieldName);
|
|
1308
|
+
if (isPoisoned(kindFieldName))
|
|
1309
|
+
markPoisonedArtifactKind(id);
|
|
1310
|
+
const description = textField(ref["description"], descriptionField);
|
|
1311
|
+
checkFile(path, pathField, opts);
|
|
1312
|
+
return [{ id, kind, description, path }];
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
function referencedArtifacts(value, field, byId) {
|
|
1316
|
+
const ids = stringArray(value, field);
|
|
1317
|
+
if (isPoisoned(field))
|
|
1318
|
+
return [];
|
|
1319
|
+
return ids.flatMap((id) => {
|
|
1320
|
+
const artifact = byId.get(id);
|
|
1321
|
+
if (artifact === undefined) {
|
|
1322
|
+
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
1323
|
+
return [];
|
|
1324
|
+
}
|
|
1325
|
+
return [artifact];
|
|
1326
|
+
});
|
|
630
1327
|
}
|
|
631
1328
|
|
|
632
|
-
//
|
|
1329
|
+
// components/ulw-loop/src/quality-gate-verdicts.ts
|
|
633
1330
|
function passedVerdict(value, field) {
|
|
634
|
-
if (value === "not_applicable")
|
|
1331
|
+
if (value === "not_applicable") {
|
|
635
1332
|
invalid(`${field} must not be not_applicable.`, field);
|
|
1333
|
+
return "passed";
|
|
1334
|
+
}
|
|
636
1335
|
return literal(value, "passed", field);
|
|
637
1336
|
}
|
|
638
1337
|
function codeQualityStatusField(value, field) {
|
|
639
1338
|
if (value === "CLEAR" || value === "WATCH")
|
|
640
1339
|
return value;
|
|
641
1340
|
invalid(`${field} must be CLEAR or WATCH.`, field);
|
|
1341
|
+
return "CLEAR";
|
|
642
1342
|
}
|
|
643
1343
|
function adversarialVerdict(row, field) {
|
|
644
1344
|
const value = row["verdict"];
|
|
@@ -648,9 +1348,90 @@ function adversarialVerdict(row, field) {
|
|
|
648
1348
|
return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
|
|
649
1349
|
}
|
|
650
1350
|
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
1351
|
+
return { verdict: "passed" };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// components/ulw-loop/src/surface.ts
|
|
1355
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
1356
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
1357
|
+
import { fileURLToPath } from "node:url";
|
|
1358
|
+
var REVIEWER_ROLES_BY_SURFACE = {
|
|
1359
|
+
lazycodex: {
|
|
1360
|
+
codeReview: "lazycodex-code-reviewer",
|
|
1361
|
+
manualQa: "lazycodex-qa-executor",
|
|
1362
|
+
gateReview: "lazycodex-gate-reviewer"
|
|
1363
|
+
},
|
|
1364
|
+
"omo-senpi": {
|
|
1365
|
+
codeReview: "omo-senpi-code-reviewer",
|
|
1366
|
+
manualQa: "omo-senpi-qa-executor",
|
|
1367
|
+
gateReview: "omo-senpi-gate-reviewer"
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
|
|
1371
|
+
var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
|
|
1372
|
+
lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
|
|
1373
|
+
"omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
|
|
1374
|
+
};
|
|
1375
|
+
var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
|
|
1376
|
+
lazycodex: ["codeReview"],
|
|
1377
|
+
"omo-senpi": []
|
|
1378
|
+
};
|
|
1379
|
+
var GATE_SECTION_BY_ACCEPTOR = {
|
|
1380
|
+
lazycodex: {
|
|
1381
|
+
codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
|
|
1382
|
+
manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
|
|
1383
|
+
gateReview: [
|
|
1384
|
+
REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
|
|
1385
|
+
"category:deep-high",
|
|
1386
|
+
"category:deep-low",
|
|
1387
|
+
"category:deep",
|
|
1388
|
+
"category:unspecified-high",
|
|
1389
|
+
"category:unspecified-low",
|
|
1390
|
+
"main-session"
|
|
1391
|
+
]
|
|
1392
|
+
},
|
|
1393
|
+
"omo-senpi": {
|
|
1394
|
+
manualQa: ["main-session"],
|
|
1395
|
+
gateReview: [
|
|
1396
|
+
"category:deep-high",
|
|
1397
|
+
"category:deep-low",
|
|
1398
|
+
"category:deep",
|
|
1399
|
+
"category:unspecified-high",
|
|
1400
|
+
"category:unspecified-low"
|
|
1401
|
+
]
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
function reviewerRolesFor(surface) {
|
|
1405
|
+
return REVIEWER_ROLES_BY_SURFACE[surface];
|
|
1406
|
+
}
|
|
1407
|
+
var SURFACE_MARKER_FILENAME = "surface.json";
|
|
1408
|
+
var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
|
|
1409
|
+
function parseSurface(value) {
|
|
1410
|
+
return value === "lazycodex" || value === "omo-senpi" ? value : null;
|
|
1411
|
+
}
|
|
1412
|
+
function resolveToolkitSurface(options) {
|
|
1413
|
+
const env = options?.env ?? process.env;
|
|
1414
|
+
const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
|
|
1415
|
+
if (fromEnv !== null)
|
|
1416
|
+
return fromEnv;
|
|
1417
|
+
const entryDir = options?.entryDir ?? dirname2(fileURLToPath(import.meta.url));
|
|
1418
|
+
const markerPath = join5(entryDir, SURFACE_MARKER_FILENAME);
|
|
1419
|
+
return readMarkerSurface(markerPath) ?? "lazycodex";
|
|
1420
|
+
}
|
|
1421
|
+
function readMarkerSurface(markerPath) {
|
|
1422
|
+
try {
|
|
1423
|
+
if (!existsSync4(markerPath))
|
|
1424
|
+
return null;
|
|
1425
|
+
const parsed = JSON.parse(readFileSync3(markerPath, "utf8"));
|
|
1426
|
+
return parseSurface(parsed["surface"]);
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
if (error instanceof Error)
|
|
1429
|
+
return null;
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
651
1432
|
}
|
|
652
1433
|
|
|
653
|
-
//
|
|
1434
|
+
// components/ulw-loop/src/quality-gate-blockers.ts
|
|
654
1435
|
var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
655
1436
|
var URL_PATTERN = /https?:\/\/\S+/g;
|
|
656
1437
|
var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
|
|
@@ -692,131 +1473,68 @@ function clearGoalBlockerFields(goal) {
|
|
|
692
1473
|
Reflect.deleteProperty(goal, key);
|
|
693
1474
|
}
|
|
694
1475
|
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const actual = textField(value, field);
|
|
703
|
-
if (actual !== expected)
|
|
704
|
-
invalid(`${field} must be ${expected}.`, field);
|
|
705
|
-
return expected;
|
|
706
|
-
}
|
|
707
|
-
function surfaceField(value, field) {
|
|
708
|
-
if (value === "cli" || value === "http" || value === "tmux" || value === "browser" || value === "gui" || value === "data")
|
|
709
|
-
return value;
|
|
710
|
-
invalid(`${field} must be a supported manual QA surface.`, field);
|
|
711
|
-
}
|
|
712
|
-
function kindField(value, field) {
|
|
713
|
-
if (value === "cli-transcript" || value === "log" || value === "screenshot" || value === "image" || value === "http-dump" || value === "data-diff")
|
|
714
|
-
return value;
|
|
715
|
-
invalid(`${field} must be a supported artifact kind.`, field);
|
|
716
|
-
}
|
|
717
|
-
function artifactCompatible(surface, kind) {
|
|
718
|
-
switch (surface) {
|
|
719
|
-
case "cli":
|
|
720
|
-
case "tmux":
|
|
721
|
-
return kind === "cli-transcript" || kind === "log";
|
|
722
|
-
case "http":
|
|
723
|
-
return kind === "http-dump";
|
|
724
|
-
case "browser":
|
|
725
|
-
case "gui":
|
|
726
|
-
return kind === "screenshot" || kind === "image";
|
|
727
|
-
case "data":
|
|
728
|
-
return kind === "data-diff";
|
|
729
|
-
default:
|
|
730
|
-
invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function checkFile(path, field, opts) {
|
|
734
|
-
if (opts === undefined)
|
|
735
|
-
return;
|
|
736
|
-
const absolute = resolve2(opts.repoRoot, path);
|
|
737
|
-
if (!opts.fs.existsSync(absolute))
|
|
738
|
-
invalid(`${field} must point to an existing artifact.`, field);
|
|
739
|
-
if (opts.fs.statSync(absolute).size <= 0)
|
|
740
|
-
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
741
|
-
if (opts.currentAttemptDir !== undefined) {
|
|
742
|
-
const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
|
|
743
|
-
if (!isWithinAttemptDir(absolute, attemptRoot))
|
|
744
|
-
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
function artifactMap(refs) {
|
|
748
|
-
const byId = new Map;
|
|
749
|
-
for (const ref of refs) {
|
|
750
|
-
if (byId.has(ref.id))
|
|
751
|
-
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
752
|
-
byId.set(ref.id, ref);
|
|
1476
|
+
// components/ulw-loop/src/quality-gate.ts
|
|
1477
|
+
function reviewerAcceptorField(value, surface, sectionName) {
|
|
1478
|
+
const field = `${sectionName}.by`;
|
|
1479
|
+
const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
|
|
1480
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1481
|
+
textField(value, field);
|
|
1482
|
+
return accepted?.[0] ?? "";
|
|
753
1483
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
759
|
-
return value.map((item, index) => {
|
|
760
|
-
const ref = section(item, `manualQa.artifactRefs[${index}]`);
|
|
761
|
-
const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
|
|
762
|
-
checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
|
|
763
|
-
return {
|
|
764
|
-
id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
|
|
765
|
-
kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
|
|
766
|
-
description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
|
|
767
|
-
path
|
|
768
|
-
};
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
function referencedArtifacts(value, field, byId) {
|
|
772
|
-
return stringArray(value, field).map((id) => {
|
|
773
|
-
const artifact = byId.get(id);
|
|
774
|
-
if (artifact === undefined)
|
|
775
|
-
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
776
|
-
return artifact;
|
|
777
|
-
});
|
|
1484
|
+
const actual = textField(value, field);
|
|
1485
|
+
if (accepted === undefined || !accepted.includes(actual))
|
|
1486
|
+
invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
|
|
1487
|
+
return actual;
|
|
778
1488
|
}
|
|
779
1489
|
function validateQualityGate(input, opts) {
|
|
780
|
-
|
|
781
|
-
|
|
1490
|
+
return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
|
|
1491
|
+
}
|
|
1492
|
+
function validateQualityGateUncollected(input, opts) {
|
|
1493
|
+
const surface = opts?.reviewerSurface ?? "lazycodex";
|
|
1494
|
+
const raw = input;
|
|
1495
|
+
const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
|
|
1496
|
+
for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
|
|
1497
|
+
section(gate[name], name);
|
|
1498
|
+
for (const name of Object.keys(gate)) {
|
|
1499
|
+
if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
|
|
1500
|
+
invalid("omo-senpi gate has no codeReview lane.", name);
|
|
1501
|
+
}
|
|
1502
|
+
if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
|
|
1503
|
+
invalid("omo-senpi gate has no codeReview lane.", "codeReview");
|
|
782
1504
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
783
1505
|
const gateReview = section(gate["gateReview"], "gateReview");
|
|
784
1506
|
const iteration = section(gate["iteration"], "iteration");
|
|
785
1507
|
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
|
|
1508
|
+
const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
|
|
1509
|
+
const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
|
|
1510
|
+
const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
|
|
1511
|
+
const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
|
|
1512
|
+
const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
|
|
786
1513
|
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
|
|
787
1514
|
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
|
|
788
|
-
if (passCount < totalCriteria)
|
|
1515
|
+
if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
|
|
789
1516
|
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
|
|
790
1517
|
const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
|
|
791
1518
|
const byId = artifactMap(artifactRefs);
|
|
792
1519
|
const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
|
|
793
1520
|
const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
|
|
794
|
-
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
795
1521
|
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
|
-
},
|
|
1522
|
+
if (!isPoisoned("gateReview.reportPath"))
|
|
1523
|
+
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
1524
|
+
const common = {
|
|
807
1525
|
manualQa: {
|
|
808
|
-
by:
|
|
1526
|
+
by: manualQaBy,
|
|
809
1527
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
810
|
-
evidence:
|
|
1528
|
+
evidence: manualQaEvidence,
|
|
811
1529
|
surfaceEvidence,
|
|
812
1530
|
adversarialCases,
|
|
813
1531
|
artifactRefs
|
|
814
1532
|
},
|
|
815
1533
|
gateReview: {
|
|
816
|
-
by:
|
|
1534
|
+
by: gateReviewBy,
|
|
817
1535
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
818
1536
|
reportPath: gateReportPath,
|
|
819
|
-
evidence:
|
|
1537
|
+
evidence: gateReviewEvidence,
|
|
820
1538
|
blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
|
|
821
1539
|
},
|
|
822
1540
|
iteration: {
|
|
@@ -834,17 +1552,41 @@ function validateQualityGate(input, opts) {
|
|
|
834
1552
|
adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
|
|
835
1553
|
}
|
|
836
1554
|
};
|
|
1555
|
+
if (surface === "omo-senpi")
|
|
1556
|
+
return { surface, ...common };
|
|
1557
|
+
if (codeReview === undefined)
|
|
1558
|
+
return { surface, ...common };
|
|
1559
|
+
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
1560
|
+
checkFile(codeReportPath, "codeReview.reportPath", opts);
|
|
1561
|
+
return {
|
|
1562
|
+
surface,
|
|
1563
|
+
...common,
|
|
1564
|
+
codeReview: {
|
|
1565
|
+
by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
|
|
1566
|
+
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
1567
|
+
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
1568
|
+
reportPath: codeReportPath,
|
|
1569
|
+
evidence: textField(codeReview["evidence"], "codeReview.evidence"),
|
|
1570
|
+
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
837
1573
|
}
|
|
838
1574
|
function parseSurfaceEvidence(value, byId) {
|
|
839
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1575
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
840
1576
|
invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
|
|
841
|
-
|
|
1577
|
+
return [];
|
|
1578
|
+
}
|
|
1579
|
+
return value.flatMap((item, index) => {
|
|
842
1580
|
const row = section(item, `manualQa.surfaceEvidence[${index}]`);
|
|
1581
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
|
|
1582
|
+
return [];
|
|
843
1583
|
const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
|
|
844
1584
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
|
|
845
1585
|
for (const artifact of artifacts) {
|
|
1586
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
|
|
1587
|
+
continue;
|
|
846
1588
|
if (!artifactCompatible(surface, artifact.kind)) {
|
|
847
|
-
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
|
|
1589
|
+
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
|
|
848
1590
|
}
|
|
849
1591
|
}
|
|
850
1592
|
return {
|
|
@@ -858,10 +1600,14 @@ function parseSurfaceEvidence(value, byId) {
|
|
|
858
1600
|
});
|
|
859
1601
|
}
|
|
860
1602
|
function parseAdversarialCases(value, byId) {
|
|
861
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1603
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
862
1604
|
invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
|
|
863
|
-
|
|
1605
|
+
return [];
|
|
1606
|
+
}
|
|
1607
|
+
return value.flatMap((item, index) => {
|
|
864
1608
|
const row = section(item, `manualQa.adversarialCases[${index}]`);
|
|
1609
|
+
if (isPoisoned(`manualQa.adversarialCases[${index}]`))
|
|
1610
|
+
return [];
|
|
865
1611
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
|
|
866
1612
|
const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
|
|
867
1613
|
return {
|
|
@@ -876,8 +1622,8 @@ function parseAdversarialCases(value, byId) {
|
|
|
876
1622
|
});
|
|
877
1623
|
}
|
|
878
1624
|
|
|
879
|
-
//
|
|
880
|
-
import { readFile as
|
|
1625
|
+
// components/ulw-loop/src/cli-arg-parser.ts
|
|
1626
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
881
1627
|
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
1628
|
var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
|
|
883
1629
|
function hasFlag(argv, flag) {
|
|
@@ -925,7 +1671,7 @@ async function readJsonInput(value) {
|
|
|
925
1671
|
if (value === undefined)
|
|
926
1672
|
return;
|
|
927
1673
|
try {
|
|
928
|
-
return JSON.parse(looksLikeJson(value) ? value : await
|
|
1674
|
+
return JSON.parse(looksLikeJson(value) ? value : await readFile2(value, "utf8"));
|
|
929
1675
|
} catch (error) {
|
|
930
1676
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
931
1677
|
throw new UlwLoopError(`Invalid JSON input: ${message}`, "ULW_LOOP_JSON_INPUT_INVALID", { cause: error });
|
|
@@ -934,13 +1680,13 @@ async function readJsonInput(value) {
|
|
|
934
1680
|
async function parseCodexGoalJson(value) {
|
|
935
1681
|
if (value === undefined)
|
|
936
1682
|
return;
|
|
937
|
-
const raw = looksLikeJson(value) ? value : await readFile4(value, "utf8");
|
|
938
1683
|
try {
|
|
1684
|
+
const raw = looksLikeJson(value) ? value : await readFile2(value, "utf8");
|
|
939
1685
|
JSON.parse(raw);
|
|
940
1686
|
return raw;
|
|
941
1687
|
} catch (error) {
|
|
942
1688
|
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 });
|
|
1689
|
+
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
1690
|
}
|
|
945
1691
|
}
|
|
946
1692
|
function required(argv, flag, code) {
|
|
@@ -967,7 +1713,7 @@ function parseRecordEvidenceArgs(argv) {
|
|
|
967
1713
|
return notes ? { ...result, notes } : result;
|
|
968
1714
|
}
|
|
969
1715
|
|
|
970
|
-
//
|
|
1716
|
+
// components/ulw-loop/src/validation-batch.ts
|
|
971
1717
|
function isObject(value) {
|
|
972
1718
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
973
1719
|
}
|
|
@@ -987,23 +1733,23 @@ async function parseValidationBatches(input, goals) {
|
|
|
987
1733
|
if (raw === undefined)
|
|
988
1734
|
return;
|
|
989
1735
|
if (!Array.isArray(raw))
|
|
990
|
-
|
|
1736
|
+
fail2("--validation-batch-json must be a JSON array.");
|
|
991
1737
|
const batches = raw.map(batchFromObject);
|
|
992
1738
|
validateBatches(batches, goals);
|
|
993
1739
|
return batches;
|
|
994
1740
|
}
|
|
995
1741
|
function batchFromObject(value) {
|
|
996
1742
|
if (!isObject(value))
|
|
997
|
-
|
|
1743
|
+
fail2("validation batch entries must be objects.");
|
|
998
1744
|
const batchId = text(value, "batchId");
|
|
999
1745
|
const memberIds = strings(value, "memberIds");
|
|
1000
1746
|
const finalGoalId = text(value, "finalGoalId");
|
|
1001
1747
|
if (batchId === undefined)
|
|
1002
|
-
|
|
1748
|
+
fail2("validation batch requires batchId.");
|
|
1003
1749
|
if (memberIds === undefined || memberIds.length < 2)
|
|
1004
|
-
|
|
1750
|
+
fail2("validation batch requires at least two memberIds.");
|
|
1005
1751
|
if (finalGoalId === undefined)
|
|
1006
|
-
|
|
1752
|
+
fail2("validation batch requires finalGoalId.");
|
|
1007
1753
|
return { batchId, memberIds, finalGoalId };
|
|
1008
1754
|
}
|
|
1009
1755
|
function validateBatches(batches, goals) {
|
|
@@ -1012,17 +1758,17 @@ function validateBatches(batches, goals) {
|
|
|
1012
1758
|
const members = new Set;
|
|
1013
1759
|
for (const batch of batches) {
|
|
1014
1760
|
if (batchIds.has(batch.batchId))
|
|
1015
|
-
|
|
1761
|
+
fail2(`duplicate validation batch id: ${batch.batchId}.`);
|
|
1016
1762
|
batchIds.add(batch.batchId);
|
|
1017
1763
|
if (new Set(batch.memberIds).size !== batch.memberIds.length)
|
|
1018
|
-
|
|
1764
|
+
fail2(`validation batch ${batch.batchId} has duplicate memberIds.`);
|
|
1019
1765
|
if (!batch.memberIds.includes(batch.finalGoalId))
|
|
1020
|
-
|
|
1766
|
+
fail2(`validation batch ${batch.batchId} finalGoalId must be a member.`, "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER");
|
|
1021
1767
|
for (const memberId of batch.memberIds) {
|
|
1022
1768
|
if (!goalIds.has(memberId))
|
|
1023
|
-
|
|
1769
|
+
fail2(`validation batch ${batch.batchId} references unknown goal: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_MEMBER_UNKNOWN");
|
|
1024
1770
|
if (members.has(memberId))
|
|
1025
|
-
|
|
1771
|
+
fail2(`goal appears in multiple validation batches: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_OVERLAP");
|
|
1026
1772
|
members.add(memberId);
|
|
1027
1773
|
}
|
|
1028
1774
|
}
|
|
@@ -1081,18 +1827,15 @@ function memberResolved(plan, goalId) {
|
|
|
1081
1827
|
const goal = plan.goals.find((candidate) => candidate.id === goalId);
|
|
1082
1828
|
return goal !== undefined && isMemberResolved(goal, plan);
|
|
1083
1829
|
}
|
|
1084
|
-
function
|
|
1830
|
+
function fail2(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
1085
1831
|
throw new UlwLoopError(message, code);
|
|
1086
1832
|
}
|
|
1087
1833
|
|
|
1088
|
-
//
|
|
1089
|
-
var QUALITY_GATE_FS = { existsSync:
|
|
1834
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
1835
|
+
var QUALITY_GATE_FS = { existsSync: existsSync5, statSync: statSync2 };
|
|
1090
1836
|
function ulwLoopFail2(message, code) {
|
|
1091
1837
|
throw new UlwLoopError(message, code);
|
|
1092
1838
|
}
|
|
1093
|
-
function normalizeObjective3(value) {
|
|
1094
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1095
|
-
}
|
|
1096
1839
|
function nonEmptyEvidence2(value) {
|
|
1097
1840
|
const trimmed = value.trim();
|
|
1098
1841
|
return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
|
|
@@ -1111,11 +1854,11 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1111
1854
|
if (!(error instanceof SyntaxError))
|
|
1112
1855
|
throw error;
|
|
1113
1856
|
}
|
|
1114
|
-
const path =
|
|
1115
|
-
if (!
|
|
1857
|
+
const path = resolve4(repoRoot, trimmed);
|
|
1858
|
+
if (!existsSync5(path))
|
|
1116
1859
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1117
1860
|
try {
|
|
1118
|
-
return JSON.parse(await
|
|
1861
|
+
return JSON.parse(await readFile3(path, "utf8"));
|
|
1119
1862
|
} catch (error) {
|
|
1120
1863
|
return ulwLoopFail2(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ulw_loop_json_input_invalid");
|
|
1121
1864
|
}
|
|
@@ -1155,13 +1898,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
|
|
|
1155
1898
|
return status === "blocked" ? "goal_blocked" : "goal_failed";
|
|
1156
1899
|
}
|
|
1157
1900
|
function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
|
|
1158
|
-
const watch = qualityGate?.codeReview
|
|
1901
|
+
const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
|
|
1159
1902
|
const entry = {
|
|
1160
1903
|
at: now,
|
|
1161
1904
|
kind: ledgerKind2(args.status, goal, aggregateCompletion),
|
|
1162
1905
|
goalId: goal.id,
|
|
1163
1906
|
status: goal.status,
|
|
1164
|
-
evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview
|
|
1907
|
+
evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
|
|
1165
1908
|
};
|
|
1166
1909
|
if (codexGoal !== undefined)
|
|
1167
1910
|
entry.codexGoal = codexGoal;
|
|
@@ -1175,7 +1918,7 @@ function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletio
|
|
|
1175
1918
|
entry.requiredExternalDecision = goal.requiredExternalDecision;
|
|
1176
1919
|
return entry;
|
|
1177
1920
|
}
|
|
1178
|
-
async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
1921
|
+
async function checkpointUlwLoop(repoRoot, args, scope, dependencies) {
|
|
1179
1922
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1180
1923
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
1181
1924
|
const goal = findGoal2(plan, args.goalId);
|
|
@@ -1184,6 +1927,8 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1184
1927
|
let aggregateCompletion;
|
|
1185
1928
|
let qualityGate;
|
|
1186
1929
|
let codexGoal;
|
|
1930
|
+
let nextActions = [];
|
|
1931
|
+
let warnings = [];
|
|
1187
1932
|
if (args.status === "complete") {
|
|
1188
1933
|
const aggregate = codexGoalMode(plan) === "aggregate";
|
|
1189
1934
|
const final = isFinalRunCompletionCandidate(plan, goal);
|
|
@@ -1196,23 +1941,24 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1196
1941
|
requireEssentialCriteriaPass(goal);
|
|
1197
1942
|
else
|
|
1198
1943
|
requireAllCriteriaPass(goal);
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if (!
|
|
1215
|
-
throw
|
|
1944
|
+
let codexValidationError;
|
|
1945
|
+
try {
|
|
1946
|
+
const validation = await validateCheckpointCodexGoal({
|
|
1947
|
+
repoRoot,
|
|
1948
|
+
plan,
|
|
1949
|
+
goal,
|
|
1950
|
+
raw: args.codexGoalJson,
|
|
1951
|
+
evidence,
|
|
1952
|
+
...scope === undefined ? {} : { scope }
|
|
1953
|
+
});
|
|
1954
|
+
codexGoal = validation.raw;
|
|
1955
|
+
nextActions = validation.nextActions;
|
|
1956
|
+
warnings = validation.warnings;
|
|
1957
|
+
acknowledgeDriverObjective(plan, validation.unacknowledgedObjective);
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
if (!(error instanceof UlwLoopError))
|
|
1960
|
+
throw error;
|
|
1961
|
+
codexValidationError = error;
|
|
1216
1962
|
}
|
|
1217
1963
|
if (closesBatch)
|
|
1218
1964
|
requireBatchFinalReady(plan, goal);
|
|
@@ -1221,13 +1967,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1221
1967
|
if (final)
|
|
1222
1968
|
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
|
|
1223
1969
|
if (final || aggregateCompletion !== undefined || closesBatch) {
|
|
1224
|
-
|
|
1225
|
-
repoRoot,
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1970
|
+
try {
|
|
1971
|
+
qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
|
|
1972
|
+
repoRoot,
|
|
1973
|
+
fs: QUALITY_GATE_FS,
|
|
1974
|
+
reviewerSurface: dependencies?.surface ?? resolveToolkitSurface(),
|
|
1975
|
+
...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
|
|
1976
|
+
});
|
|
1977
|
+
requireBatchGate(plan, goal, qualityGate);
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
|
|
1980
|
+
throw error;
|
|
1981
|
+
throw combineCheckpointValidationErrors(codexValidationError, error);
|
|
1982
|
+
}
|
|
1230
1983
|
}
|
|
1984
|
+
if (codexValidationError !== undefined)
|
|
1985
|
+
throw codexValidationError;
|
|
1231
1986
|
goal.status = "complete";
|
|
1232
1987
|
goal.completedAt = now;
|
|
1233
1988
|
goal.evidence = evidence;
|
|
@@ -1241,30 +1996,155 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1241
1996
|
goal.updatedAt = now;
|
|
1242
1997
|
if (aggregateCompletion !== undefined)
|
|
1243
1998
|
plan.aggregateCompletion = aggregateCompletion;
|
|
1999
|
+
if (aggregateCompletion !== undefined)
|
|
2000
|
+
nextActions = [...nextActions, 'aggregate complete — now update_goal({status:"complete"})'];
|
|
1244
2001
|
plan.updatedAt = now;
|
|
1245
|
-
await writePlan(repoRoot, plan, scope);
|
|
1246
2002
|
const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
|
|
1247
|
-
|
|
2003
|
+
const entries = [ledgerEntry];
|
|
1248
2004
|
const closedBatch = args.status === "complete" ? batchClosedBy(plan, goal.id) : undefined;
|
|
1249
2005
|
if (closedBatch !== undefined)
|
|
1250
|
-
|
|
1251
|
-
|
|
2006
|
+
entries.push({ at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId });
|
|
2007
|
+
await commit(repoRoot, scope, { plan, entries });
|
|
2008
|
+
return aggregateCompletion === undefined ? { plan, goal, ledgerEntry, nextActions, warnings } : { plan, goal, ledgerEntry, aggregateCompletion, nextActions, warnings };
|
|
1252
2009
|
});
|
|
1253
2010
|
}
|
|
1254
2011
|
|
|
1255
|
-
//
|
|
2012
|
+
// components/ulw-loop/src/checkpoint-template.ts
|
|
2013
|
+
function artifactPath(base, name) {
|
|
2014
|
+
return `${base}/${name}`;
|
|
2015
|
+
}
|
|
2016
|
+
function gateTemplate(surface, base) {
|
|
2017
|
+
const artifacts = [
|
|
2018
|
+
{
|
|
2019
|
+
id: "artifact-cli",
|
|
2020
|
+
kind: "cli-transcript",
|
|
2021
|
+
description: "<replace:artifact description>",
|
|
2022
|
+
path: artifactPath(base, "cli-transcript.txt")
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
id: "artifact-data",
|
|
2026
|
+
kind: "data-diff",
|
|
2027
|
+
description: "<replace:artifact description>",
|
|
2028
|
+
path: artifactPath(base, "data-diff.txt")
|
|
2029
|
+
}
|
|
2030
|
+
];
|
|
2031
|
+
const manualQa = {
|
|
2032
|
+
by: "main-session",
|
|
2033
|
+
status: "passed",
|
|
2034
|
+
evidence: "<replace:manual QA evidence>",
|
|
2035
|
+
surfaceEvidence: [
|
|
2036
|
+
{
|
|
2037
|
+
id: "surface-cli",
|
|
2038
|
+
criterionRef: "<replace:criterion id>",
|
|
2039
|
+
surface: "cli",
|
|
2040
|
+
invocation: "<replace:command>",
|
|
2041
|
+
verdict: "passed",
|
|
2042
|
+
artifactRefs: ["artifact-cli"]
|
|
2043
|
+
},
|
|
2044
|
+
{
|
|
2045
|
+
id: "surface-data",
|
|
2046
|
+
criterionRef: "<replace:criterion id>",
|
|
2047
|
+
surface: "data",
|
|
2048
|
+
invocation: "<replace:command>",
|
|
2049
|
+
verdict: "passed",
|
|
2050
|
+
artifactRefs: ["artifact-data"]
|
|
2051
|
+
}
|
|
2052
|
+
],
|
|
2053
|
+
adversarialCases: [
|
|
2054
|
+
{
|
|
2055
|
+
id: "<replace:adversarial case id>",
|
|
2056
|
+
criterionRef: "<replace:criterion id>",
|
|
2057
|
+
scenario: "<replace:scenario>",
|
|
2058
|
+
expectedBehavior: "<replace:expected behavior>",
|
|
2059
|
+
verdict: "not_applicable",
|
|
2060
|
+
reason: "<replace:reason>",
|
|
2061
|
+
artifactRefs: ["artifact-cli"]
|
|
2062
|
+
}
|
|
2063
|
+
],
|
|
2064
|
+
artifactRefs: artifacts
|
|
2065
|
+
};
|
|
2066
|
+
const common = {
|
|
2067
|
+
manualQa,
|
|
2068
|
+
gateReview: {
|
|
2069
|
+
by: surface === "omo-senpi" ? "category:deep-high" : "main-session",
|
|
2070
|
+
recommendation: "APPROVE",
|
|
2071
|
+
reportPath: artifactPath(base, "gate-review.md"),
|
|
2072
|
+
evidence: "<replace:gate review evidence>",
|
|
2073
|
+
blockers: [],
|
|
2074
|
+
notes: []
|
|
2075
|
+
},
|
|
2076
|
+
iteration: {
|
|
2077
|
+
fullRerun: true,
|
|
2078
|
+
status: "passed",
|
|
2079
|
+
rerunCommands: ["<replace:verification command>"],
|
|
2080
|
+
evidence: "<replace:iteration evidence>"
|
|
2081
|
+
},
|
|
2082
|
+
criteriaCoverage: {
|
|
2083
|
+
totalCriteria: 0,
|
|
2084
|
+
passCount: 0,
|
|
2085
|
+
originalIntent: "<replace:original intent>",
|
|
2086
|
+
desiredOutcome: "<replace:desired outcome>",
|
|
2087
|
+
userOutcomeReview: "<replace:user outcome review>",
|
|
2088
|
+
adversarialClassesCovered: ["<replace:adversarial class>"]
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
return common;
|
|
2092
|
+
}
|
|
2093
|
+
async function checkpointTemplate(repoRoot, scope, goalId, dependencies) {
|
|
2094
|
+
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
2095
|
+
const surface = dependencies?.surface ?? resolveToolkitSurface();
|
|
2096
|
+
const targetId = goalId ?? plan.activeGoalId;
|
|
2097
|
+
const active = plan.goals.find((goal) => goal.id === targetId);
|
|
2098
|
+
if (goalId !== undefined && active === undefined)
|
|
2099
|
+
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
2100
|
+
const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
|
|
2101
|
+
const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
|
|
2102
|
+
const guidance = [
|
|
2103
|
+
"codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
|
|
2104
|
+
"Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
|
|
2105
|
+
'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
|
|
2106
|
+
'Passing quality-gate-json example requires gateReview {"by":"category:deep-high","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
|
|
2107
|
+
...surface === "lazycodex" ? [
|
|
2108
|
+
"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-high, category:deep-low, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session."
|
|
2109
|
+
] : [],
|
|
2110
|
+
...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
|
|
2111
|
+
].join(" ");
|
|
2112
|
+
return {
|
|
2113
|
+
qualityGateTemplate: gateTemplate(surface, attemptDir),
|
|
2114
|
+
codexGoalTemplate: {
|
|
2115
|
+
goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
|
|
2116
|
+
},
|
|
2117
|
+
guidance,
|
|
2118
|
+
...hasAttempt ? { attemptDir } : {}
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// components/ulw-loop/src/cli-output.ts
|
|
1256
2123
|
var ULW_LOOP_HELP = `Usage:
|
|
2124
|
+
omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
|
|
2125
|
+
omo-agent-toolkit help | --help | -h (this message)
|
|
2126
|
+
omo-agent-toolkit ulw-loop help
|
|
1257
2127
|
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
2128
|
omo-agent-toolkit ulw-loop status [--json]
|
|
1259
2129
|
omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
|
|
1260
2130
|
omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
|
|
1261
2131
|
omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
|
|
2132
|
+
omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
|
|
1262
2133
|
omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
|
|
1263
2134
|
omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
|
|
1264
2135
|
omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
|
|
1265
2136
|
omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
|
|
1266
2137
|
|
|
1267
|
-
|
|
2138
|
+
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.
|
|
2139
|
+
Every subcommand accepts --help | -h to print its own usage line.`;
|
|
2140
|
+
function subcommandHelp(subcommand) {
|
|
2141
|
+
const lines = ULW_LOOP_HELP.split(`
|
|
2142
|
+
`).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
|
|
2143
|
+
if (lines.length === 0)
|
|
2144
|
+
return ULW_LOOP_HELP;
|
|
2145
|
+
return ["Usage:", ...lines].join(`
|
|
2146
|
+
`);
|
|
2147
|
+
}
|
|
1268
2148
|
function printJson(value) {
|
|
1269
2149
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
1270
2150
|
`);
|
|
@@ -1330,17 +2210,18 @@ function normalizeCodexGoalMode(value) {
|
|
|
1330
2210
|
throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
|
|
1331
2211
|
}
|
|
1332
2212
|
|
|
1333
|
-
//
|
|
2213
|
+
// components/ulw-loop/src/codex-goal-instruction.ts
|
|
1334
2214
|
function buildCodexGoalInstruction(args) {
|
|
1335
2215
|
const mode = codexGoalMode(args.plan);
|
|
1336
2216
|
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
|
|
1337
2217
|
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
|
|
1338
|
-
|
|
2218
|
+
const surface = args.surface ?? resolveToolkitSurface();
|
|
2219
|
+
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
|
|
1339
2220
|
}
|
|
1340
2221
|
function buildCreateGoalPayload(plan, goal) {
|
|
1341
2222
|
return { objective: expectedCodexObjective(plan, goal) };
|
|
1342
2223
|
}
|
|
1343
|
-
function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
2224
|
+
function buildText(mode, plan, goal, createGoal, isFinal, surface) {
|
|
1344
2225
|
return joinLines([
|
|
1345
2226
|
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
|
|
1346
2227
|
`Mode: ${mode}`,
|
|
@@ -1357,7 +2238,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
|
1357
2238
|
"- Goals are unlimited. Do not add numeric limits.",
|
|
1358
2239
|
...modeConstraintLines(mode, isFinal),
|
|
1359
2240
|
...evidenceLayoutLines(plan),
|
|
1360
|
-
finalSection(plan, goal, isFinal, mode === "aggregate"),
|
|
2241
|
+
finalSection(plan, goal, isFinal, mode === "aggregate", surface),
|
|
1361
2242
|
...checkpointLines(plan, mode),
|
|
1362
2243
|
"",
|
|
1363
2244
|
"create_goal payload:",
|
|
@@ -1409,19 +2290,21 @@ function evidenceLayoutLines(plan) {
|
|
|
1409
2290
|
"- 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
2291
|
];
|
|
1411
2292
|
}
|
|
1412
|
-
function finalSection(plan, goal, isFinal, aggregate) {
|
|
2293
|
+
function finalSection(plan, goal, isFinal, aggregate, surface) {
|
|
2294
|
+
const roles = reviewerRolesFor(surface);
|
|
1413
2295
|
if (!isFinal)
|
|
1414
2296
|
return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
|
|
1415
2297
|
const option = sessionOption(plan);
|
|
2298
|
+
if (surface === "omo-senpi")
|
|
2299
|
+
return senpiFinalSection(plan, goal, aggregate);
|
|
1416
2300
|
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
2301
|
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
2302
|
return joinLines([
|
|
1419
|
-
"Final story —
|
|
2303
|
+
"Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
|
|
1420
2304
|
"- Run targeted verification for changed behavior.",
|
|
1421
2305
|
"- 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.",
|
|
2306
|
+
`- 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".`,
|
|
2307
|
+
"- 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
2308
|
"- 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
2309
|
"- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
|
|
1427
2310
|
"- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
|
|
@@ -1430,6 +2313,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
|
|
|
1430
2313
|
` ${checkpointCommand}`
|
|
1431
2314
|
]);
|
|
1432
2315
|
}
|
|
2316
|
+
function senpiFinalSection(plan, goal, aggregate) {
|
|
2317
|
+
const option = sessionOption(plan);
|
|
2318
|
+
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)"`;
|
|
2319
|
+
return joinLines([
|
|
2320
|
+
"Final story — run the single-reviewer quality gate before update_goal:",
|
|
2321
|
+
'- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
|
|
2322
|
+
'- Spawn exactly one gate reviewer with task(category: "deep-high").',
|
|
2323
|
+
"- If that task fails with any model_unavailable failure, retry with category:deep-low, then category:unspecified-high, then category:unspecified-low.",
|
|
2324
|
+
"- Set gateReview.by to the exact category:<name> literal used for the successful task.",
|
|
2325
|
+
"- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
|
|
2326
|
+
'- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
|
|
2327
|
+
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}`
|
|
2328
|
+
]);
|
|
2329
|
+
}
|
|
1433
2330
|
function sessionOption(plan) {
|
|
1434
2331
|
const prefix = ".omo/ulw-loop/";
|
|
1435
2332
|
const suffix = "/goals.json";
|
|
@@ -1443,15 +2340,53 @@ function joinLines(lines) {
|
|
|
1443
2340
|
`);
|
|
1444
2341
|
}
|
|
1445
2342
|
|
|
1446
|
-
//
|
|
1447
|
-
|
|
1448
|
-
|
|
2343
|
+
// components/ulw-loop/src/success-criteria-input.ts
|
|
2344
|
+
function invalid2(message, details) {
|
|
2345
|
+
throw new UlwLoopError(`Invalid successCriteria: ${message}`, "ULW_LOOP_ARGUMENT_INVALID", { details });
|
|
2346
|
+
}
|
|
2347
|
+
function isUserModel(value) {
|
|
2348
|
+
return typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2349
|
+
}
|
|
2350
|
+
function requireText(value, field, index) {
|
|
2351
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
2352
|
+
return trimmed || invalid2(`entry ${index + 1} needs a non-empty ${field}.`, { index, field });
|
|
2353
|
+
}
|
|
2354
|
+
function criterionId(index) {
|
|
2355
|
+
return `C${String(index + 1).padStart(3, "0")}`;
|
|
2356
|
+
}
|
|
2357
|
+
function criteriaFromInput(input) {
|
|
2358
|
+
if (input.length === 0)
|
|
2359
|
+
invalid2("provide at least one criterion or omit the field for placeholders.", { count: 0 });
|
|
2360
|
+
return input.map((entry, index) => {
|
|
2361
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
2362
|
+
return invalid2(`entry ${index + 1} must be an object.`, { index });
|
|
2363
|
+
const record = { ...entry };
|
|
2364
|
+
const userModel = record["userModel"] ?? "happy";
|
|
2365
|
+
if (!isUserModel(userModel))
|
|
2366
|
+
return invalid2(`entry ${index + 1} userModel must be one of ${ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.join(", ")}.`, {
|
|
2367
|
+
index,
|
|
2368
|
+
userModel: String(userModel)
|
|
2369
|
+
});
|
|
2370
|
+
const essential = record["essential"];
|
|
2371
|
+
if (essential !== undefined && typeof essential !== "boolean")
|
|
2372
|
+
return invalid2(`entry ${index + 1} essential must be a boolean.`, { index });
|
|
2373
|
+
return {
|
|
2374
|
+
id: criterionId(index),
|
|
2375
|
+
scenario: requireText(record["scenario"], "scenario", index),
|
|
2376
|
+
userModel,
|
|
2377
|
+
expectedEvidence: requireText(record["expectedEvidence"], "expectedEvidence", index),
|
|
2378
|
+
essential: essential ?? true,
|
|
2379
|
+
capturedEvidence: null,
|
|
2380
|
+
status: "pending"
|
|
2381
|
+
};
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
1449
2384
|
|
|
1450
|
-
//
|
|
2385
|
+
// components/ulw-loop/src/plan-goal-factory.ts
|
|
1451
2386
|
function cleanLine(line) {
|
|
1452
2387
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
1453
2388
|
}
|
|
1454
|
-
function
|
|
2389
|
+
function normalizeObjective2(value) {
|
|
1455
2390
|
return value.replace(/\s+/g, " ").trim();
|
|
1456
2391
|
}
|
|
1457
2392
|
function titleFromObjective(objective, fallback) {
|
|
@@ -1471,72 +2406,72 @@ function assertNonEmpty(value, label) {
|
|
|
1471
2406
|
function truncateObjective(objective) {
|
|
1472
2407
|
return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
|
|
1473
2408
|
}
|
|
1474
|
-
function
|
|
1475
|
-
|
|
2409
|
+
function replaceVia(goalId, id, surface) {
|
|
2410
|
+
return surface === "omo-senpi" ? `Replace via agentToolkit.steer({ kind: "revise_criterion", source: "finding", goalId: "${goalId}", criterionId: "${id}", scenario, expectedEvidence, evidence, rationale }) (or pass successCriteria to addGoal)` : `Replace via omo-agent-toolkit ulw-loop steer --kind revise_criterion --goal-id ${goalId} --criterion-id ${id} --scenario "<scenario>" --expected-evidence "<proof>" --evidence "<why>" --rationale "<why>"`;
|
|
2411
|
+
}
|
|
2412
|
+
function seedDefaultSuccessCriteria(goalIndex, objective, options = {}) {
|
|
2413
|
+
const subject = truncateObjective(normalizeObjective2(objective) || `Goal ${goalIndex + 1}`);
|
|
2414
|
+
const goalId = options.goalId ?? `G${String(goalIndex + 1).padStart(3, "0")}`;
|
|
2415
|
+
const surface = options.surface ?? "lazycodex";
|
|
1476
2416
|
const rows = [
|
|
2417
|
+
["happy", `happy path for: ${subject}`, `observable happy-path proof for goal ${goalIndex + 1}`, true],
|
|
2418
|
+
["edge", "edge case (boundary/empty/malformed)", `boundary or malformed-input proof for: ${subject}`, true],
|
|
1477
2419
|
[
|
|
1478
|
-
"C001",
|
|
1479
|
-
"happy",
|
|
1480
|
-
`happy path for: ${subject}`,
|
|
1481
|
-
`Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`,
|
|
1482
|
-
true
|
|
1483
|
-
],
|
|
1484
|
-
[
|
|
1485
|
-
"C002",
|
|
1486
|
-
"edge",
|
|
1487
|
-
"edge case (boundary/empty/malformed)",
|
|
1488
|
-
`Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`,
|
|
1489
|
-
true
|
|
1490
|
-
],
|
|
1491
|
-
[
|
|
1492
|
-
"C003",
|
|
1493
2420
|
"regression",
|
|
1494
2421
|
"regression: adjacent surface still works",
|
|
1495
|
-
`
|
|
2422
|
+
`regression proof for neighboring behavior after: ${subject}`,
|
|
1496
2423
|
false
|
|
1497
2424
|
]
|
|
1498
2425
|
];
|
|
1499
|
-
return rows.map(([
|
|
1500
|
-
id
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
2426
|
+
return rows.map(([userModel, scenario, proof, essential], index) => {
|
|
2427
|
+
const id = criterionId(index);
|
|
2428
|
+
return {
|
|
2429
|
+
id,
|
|
2430
|
+
scenario,
|
|
2431
|
+
userModel,
|
|
2432
|
+
expectedEvidence: `${replaceVia(goalId, id, surface)} with ${proof}.`,
|
|
2433
|
+
essential,
|
|
2434
|
+
capturedEvidence: null,
|
|
2435
|
+
status: "pending"
|
|
2436
|
+
};
|
|
2437
|
+
});
|
|
1508
2438
|
}
|
|
1509
2439
|
function deriveGoalCandidates(brief) {
|
|
1510
|
-
const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned:
|
|
1511
|
-
const paragraphs = brief.split(/\n\s*\n/).map(
|
|
2440
|
+
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);
|
|
2441
|
+
const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective2).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
|
|
1512
2442
|
const selected = (bulletGoals.length > 0 ? bulletGoals : paragraphs).length > 0 ? bulletGoals.length > 0 ? bulletGoals : paragraphs : ["Complete the requested project objective."];
|
|
1513
2443
|
return selected.map((objective, index) => ({
|
|
1514
2444
|
title: titleFromObjective(objective, `Goal ${index + 1}`),
|
|
1515
2445
|
objective
|
|
1516
2446
|
}));
|
|
1517
2447
|
}
|
|
1518
|
-
function makeGoal(title, objective, index, now) {
|
|
2448
|
+
function makeGoal(title, objective, index, now, options = {}) {
|
|
1519
2449
|
const cleanTitle = assertNonEmpty(title, "title");
|
|
1520
2450
|
const cleanObjective = assertNonEmpty(objective, "objective");
|
|
2451
|
+
const id = normalizeGoalId(cleanTitle, index);
|
|
2452
|
+
const successCriteria = options.successCriteria === undefined ? seedDefaultSuccessCriteria(index, cleanObjective, {
|
|
2453
|
+
goalId: id,
|
|
2454
|
+
...options.surface === undefined ? {} : { surface: options.surface }
|
|
2455
|
+
}) : criteriaFromInput(options.successCriteria);
|
|
1521
2456
|
return {
|
|
1522
|
-
id
|
|
2457
|
+
id,
|
|
1523
2458
|
title: cleanTitle,
|
|
1524
2459
|
objective: cleanObjective,
|
|
1525
2460
|
status: "pending",
|
|
1526
|
-
successCriteria
|
|
2461
|
+
successCriteria,
|
|
1527
2462
|
attempt: 0,
|
|
1528
2463
|
createdAt: now,
|
|
1529
2464
|
updatedAt: now
|
|
1530
2465
|
};
|
|
1531
2466
|
}
|
|
1532
|
-
function appendGoalToPlan(plan, title, objective, now) {
|
|
1533
|
-
const goal = makeGoal(title, objective, plan.goals.length, now);
|
|
2467
|
+
function appendGoalToPlan(plan, title, objective, now, options = {}) {
|
|
2468
|
+
const goal = makeGoal(title, objective, plan.goals.length, now, options);
|
|
1534
2469
|
plan.goals.push(goal);
|
|
1535
2470
|
plan.updatedAt = now;
|
|
1536
2471
|
return goal;
|
|
1537
2472
|
}
|
|
1538
2473
|
|
|
1539
|
-
//
|
|
2474
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
1540
2475
|
function isScheduleEligible(goal) {
|
|
1541
2476
|
return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
|
|
1542
2477
|
}
|
|
@@ -1552,18 +2487,31 @@ function clearGoalBlockerFields2(goal) {
|
|
|
1552
2487
|
])
|
|
1553
2488
|
delete goal[key];
|
|
1554
2489
|
}
|
|
1555
|
-
async function createUlwLoopPlan(repoRoot, args, scope) {
|
|
2490
|
+
async function createUlwLoopPlan(repoRoot, args, scope, surface = "lazycodex") {
|
|
1556
2491
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1557
|
-
|
|
1558
|
-
|
|
2492
|
+
let existing;
|
|
2493
|
+
if (planExists(repoRoot, scope)) {
|
|
2494
|
+
try {
|
|
2495
|
+
existing = await readUlwLoopPlan(repoRoot, scope);
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
if (!args.force)
|
|
2498
|
+
throw error;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
if (!args.force && existing !== undefined) {
|
|
1559
2502
|
if (isUlwLoopDone(existing))
|
|
1560
|
-
throw completedPlanExistsError(scope);
|
|
2503
|
+
throw completedPlanExistsError(scope, surface);
|
|
1561
2504
|
throw new UlwLoopError(`Refusing to overwrite existing ${ulwLoopGoalsRelativePath(scope)}; pass --force to recreate it.`, "ULW_LOOP_PLAN_EXISTS");
|
|
1562
2505
|
}
|
|
1563
2506
|
const now = iso();
|
|
1564
|
-
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
|
|
2507
|
+
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now, { surface }));
|
|
1565
2508
|
const plan = {
|
|
1566
2509
|
version: 1,
|
|
2510
|
+
revision: existing?.revision ?? 0,
|
|
2511
|
+
ledgerResetRevision: (existing?.revision ?? 0) + 1,
|
|
2512
|
+
brief: args.brief.endsWith(`
|
|
2513
|
+
`) ? args.brief : `${args.brief}
|
|
2514
|
+
`,
|
|
1567
2515
|
evidenceLayoutVersion: 2,
|
|
1568
2516
|
createdAt: now,
|
|
1569
2517
|
updatedAt: now,
|
|
@@ -1578,30 +2526,37 @@ async function createUlwLoopPlan(repoRoot, args, scope) {
|
|
|
1578
2526
|
plan.validationBatches = validationBatches;
|
|
1579
2527
|
if (plan.codexGoalMode === "aggregate")
|
|
1580
2528
|
plan.codexObjective = aggregateCodexObjectiveForScope(scope);
|
|
1581
|
-
await
|
|
1582
|
-
await
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
await writeFile2(ulwLoopLedgerPath(repoRoot, scope), "", "utf8");
|
|
1587
|
-
await appendLedger(repoRoot, { at: now, kind: "plan_created", message: `${goals.length} goal(s) created` }, scope);
|
|
2529
|
+
await beforePlanMutation();
|
|
2530
|
+
await commit(repoRoot, scope, {
|
|
2531
|
+
plan,
|
|
2532
|
+
entries: [{ at: now, kind: "plan_created", message: `${goals.length} goal(s) created` }]
|
|
2533
|
+
});
|
|
1588
2534
|
return plan;
|
|
1589
2535
|
});
|
|
1590
2536
|
}
|
|
1591
|
-
function completedPlanExistsError(scope) {
|
|
2537
|
+
function completedPlanExistsError(scope, surface) {
|
|
1592
2538
|
return new UlwLoopError([
|
|
1593
2539
|
`Existing ulw-loop aggregate is already complete at ${ulwLoopGoalsRelativePath(scope)}.`,
|
|
1594
|
-
|
|
1595
|
-
|
|
2540
|
+
...surface === "omo-senpi" ? [
|
|
2541
|
+
"Start a new run under a fresh session id (a new senpi session) or call agentToolkit.createGoals({ brief, force: true }) to recreate."
|
|
2542
|
+
] : [
|
|
2543
|
+
"Start a new run with `omo-agent-toolkit ulw-loop create-goals --session-id <new-id> ...` to isolate fresh state.",
|
|
2544
|
+
"Use --force only when you intentionally want to overwrite the completed evidence."
|
|
2545
|
+
]
|
|
1596
2546
|
].join(" "), "ULW_LOOP_PLAN_EXISTS_COMPLETE");
|
|
1597
2547
|
}
|
|
1598
|
-
async function addUlwLoopGoal(repoRoot, args, scope) {
|
|
2548
|
+
async function addUlwLoopGoal(repoRoot, args, scope, surface = "lazycodex") {
|
|
1599
2549
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1600
2550
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
1601
2551
|
const now = iso();
|
|
1602
|
-
const goal = appendGoalToPlan(plan, args.title, args.objective, now
|
|
1603
|
-
|
|
1604
|
-
|
|
2552
|
+
const goal = appendGoalToPlan(plan, args.title, args.objective, now, {
|
|
2553
|
+
surface,
|
|
2554
|
+
...args.successCriteria === undefined ? {} : { successCriteria: args.successCriteria }
|
|
2555
|
+
});
|
|
2556
|
+
await commit(repoRoot, scope, {
|
|
2557
|
+
plan,
|
|
2558
|
+
entries: [{ at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }]
|
|
2559
|
+
});
|
|
1605
2560
|
return { plan, goal };
|
|
1606
2561
|
});
|
|
1607
2562
|
}
|
|
@@ -1614,17 +2569,18 @@ async function startNextUlwLoop(repoRoot, args = {}, scope) {
|
|
|
1614
2569
|
const existing = plan.goals.find((goal) => goal.status === "in_progress" && isScheduleEligible(goal));
|
|
1615
2570
|
if (existing)
|
|
1616
2571
|
return { plan, goal: existing, resumed: true };
|
|
2572
|
+
const entries = [];
|
|
1617
2573
|
let next = plan.goals.find((goal) => goal.status === "pending" && isScheduleEligible(goal));
|
|
1618
2574
|
if (!next && args.retryFailed) {
|
|
1619
2575
|
next = plan.goals.find((goal) => goal.status === "failed" && !goal.nonRetriable && isScheduleEligible(goal));
|
|
1620
2576
|
if (next)
|
|
1621
|
-
|
|
2577
|
+
entries.push({
|
|
1622
2578
|
at: now,
|
|
1623
2579
|
kind: "goal_retried",
|
|
1624
2580
|
goalId: next.id,
|
|
1625
2581
|
status: "pending",
|
|
1626
2582
|
...next.failureReason ? { message: next.failureReason } : {}
|
|
1627
|
-
}
|
|
2583
|
+
});
|
|
1628
2584
|
}
|
|
1629
2585
|
if (!next)
|
|
1630
2586
|
return { done: true, plan };
|
|
@@ -1635,8 +2591,14 @@ async function startNextUlwLoop(repoRoot, args = {}, scope) {
|
|
|
1635
2591
|
next.updatedAt = now;
|
|
1636
2592
|
plan.activeGoalId = next.id;
|
|
1637
2593
|
plan.updatedAt = now;
|
|
1638
|
-
|
|
1639
|
-
|
|
2594
|
+
entries.push({
|
|
2595
|
+
at: now,
|
|
2596
|
+
kind: "goal_started",
|
|
2597
|
+
goalId: next.id,
|
|
2598
|
+
status: next.status,
|
|
2599
|
+
message: `Attempt ${next.attempt}`
|
|
2600
|
+
});
|
|
2601
|
+
await commit(repoRoot, scope, { plan, entries });
|
|
1640
2602
|
return { plan, goal: next, resumed: false };
|
|
1641
2603
|
});
|
|
1642
2604
|
}
|
|
@@ -1663,7 +2625,7 @@ function summarizeUlwLoopPlan(plan) {
|
|
|
1663
2625
|
};
|
|
1664
2626
|
}
|
|
1665
2627
|
|
|
1666
|
-
//
|
|
2628
|
+
// components/ulw-loop/src/checkpoint-continuation.ts
|
|
1667
2629
|
async function checkpointAndContinue(repoRoot, args, scope) {
|
|
1668
2630
|
const result = await checkpointUlwLoop(repoRoot, args, scope);
|
|
1669
2631
|
if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
|
|
@@ -1675,13 +2637,18 @@ async function checkpointAndContinue(repoRoot, args, scope) {
|
|
|
1675
2637
|
return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
|
|
1676
2638
|
}
|
|
1677
2639
|
async function checkpoint(repoRoot, argv, json, scope) {
|
|
2640
|
+
if (hasFlag(argv, "--print-template")) {
|
|
2641
|
+
const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
|
|
2642
|
+
if (json)
|
|
2643
|
+
printJson({ ok: true, ...template });
|
|
2644
|
+
else
|
|
2645
|
+
printJson(template);
|
|
2646
|
+
return 0;
|
|
2647
|
+
}
|
|
1678
2648
|
const goalId = required2(argv, "--goal-id");
|
|
1679
2649
|
const statusValue = checkpointStatus(required2(argv, "--status"));
|
|
1680
2650
|
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
|
-
}
|
|
2651
|
+
const codexGoalJson = await parseCodexGoalJson(readValue(argv, "--codex-goal-json"));
|
|
1685
2652
|
const qualityGateJson = readValue(argv, "--quality-gate-json");
|
|
1686
2653
|
const args = {
|
|
1687
2654
|
goalId,
|
|
@@ -1726,10 +2693,10 @@ function checkpointStatus(value) {
|
|
|
1726
2693
|
throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
|
|
1727
2694
|
}
|
|
1728
2695
|
|
|
1729
|
-
//
|
|
1730
|
-
import { readFile as
|
|
2696
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
2697
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1731
2698
|
|
|
1732
|
-
//
|
|
2699
|
+
// components/ulw-loop/src/cli-steering.ts
|
|
1733
2700
|
var SOURCES = ["user_prompt_submit", "finding", "cli"];
|
|
1734
2701
|
var STEERING_KIND_HELP = [
|
|
1735
2702
|
`Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
|
|
@@ -1753,7 +2720,7 @@ function isSource(value) {
|
|
|
1753
2720
|
function isModel(value) {
|
|
1754
2721
|
return ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
1755
2722
|
}
|
|
1756
|
-
function
|
|
2723
|
+
function fail3(message, code, details) {
|
|
1757
2724
|
throw new UlwLoopError(message, code, { details });
|
|
1758
2725
|
}
|
|
1759
2726
|
function kindMessage(prefix) {
|
|
@@ -1767,15 +2734,15 @@ function text2(value, field) {
|
|
|
1767
2734
|
const trimmed = value.trim();
|
|
1768
2735
|
if (trimmed.length > 0)
|
|
1769
2736
|
return trimmed;
|
|
1770
|
-
return
|
|
2737
|
+
return fail3(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field });
|
|
1771
2738
|
}
|
|
1772
2739
|
function required3(argv, flag) {
|
|
1773
2740
|
const value = text2(readValue(argv, flag), flag);
|
|
1774
|
-
return value ??
|
|
2741
|
+
return value ?? fail3(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag });
|
|
1775
2742
|
}
|
|
1776
2743
|
function requiredGoal(argv) {
|
|
1777
2744
|
const value = text2(parseGoalArg(argv), "--goal-id");
|
|
1778
|
-
return value ??
|
|
2745
|
+
return value ?? fail3("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" });
|
|
1779
2746
|
}
|
|
1780
2747
|
function readObject(value, key) {
|
|
1781
2748
|
return Object.entries(value).find(([name]) => name === key)?.[1];
|
|
@@ -1799,7 +2766,7 @@ function objectChildren(value, key) {
|
|
|
1799
2766
|
for (const item of candidate) {
|
|
1800
2767
|
const next = child(item);
|
|
1801
2768
|
if (next === null)
|
|
1802
|
-
return
|
|
2769
|
+
return fail3(`${key} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { key });
|
|
1803
2770
|
parsed.push(next);
|
|
1804
2771
|
}
|
|
1805
2772
|
return parsed;
|
|
@@ -1814,13 +2781,13 @@ function parseSteeringKind(argv) {
|
|
|
1814
2781
|
const value = readValue(argv, "--kind");
|
|
1815
2782
|
if (isKind(value))
|
|
1816
2783
|
return value;
|
|
1817
|
-
return value === undefined ?
|
|
2784
|
+
return value === undefined ? fail3(kindMessage("Missing --kind."), "ULW_LOOP_STEERING_KIND_REQUIRED", { flag: "--kind", expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP }) : fail3(kindMessage(`Invalid --kind: ${value}.`), "ULW_LOOP_STEERING_KIND_INVALID", { value, expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP });
|
|
1818
2785
|
}
|
|
1819
2786
|
function parseSteeringSource(argv) {
|
|
1820
2787
|
const value = readValue(argv, "--source");
|
|
1821
2788
|
if (value === undefined)
|
|
1822
2789
|
return "cli";
|
|
1823
|
-
return isSource(value) ? value :
|
|
2790
|
+
return isSource(value) ? value : fail3(`Invalid --source: ${value}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
|
|
1824
2791
|
}
|
|
1825
2792
|
function child(value) {
|
|
1826
2793
|
if (!isPlain(value))
|
|
@@ -1837,12 +2804,12 @@ async function children(argv, flag, needed) {
|
|
|
1837
2804
|
return [];
|
|
1838
2805
|
const raw = await readJsonInput(input);
|
|
1839
2806
|
if (!Array.isArray(raw))
|
|
1840
|
-
return
|
|
2807
|
+
return fail3(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
|
|
1841
2808
|
const parsed = [];
|
|
1842
2809
|
for (const item of raw) {
|
|
1843
2810
|
const next = child(item);
|
|
1844
2811
|
if (next === null)
|
|
1845
|
-
return
|
|
2812
|
+
return fail3(`${flag} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { flag });
|
|
1846
2813
|
parsed.push(next);
|
|
1847
2814
|
}
|
|
1848
2815
|
return parsed;
|
|
@@ -1850,11 +2817,11 @@ async function children(argv, flag, needed) {
|
|
|
1850
2817
|
async function stringArray2(argv, flag) {
|
|
1851
2818
|
const raw = await readJsonInput(required3(argv, flag));
|
|
1852
2819
|
if (!Array.isArray(raw))
|
|
1853
|
-
return
|
|
2820
|
+
return fail3(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
|
|
1854
2821
|
const values = [];
|
|
1855
2822
|
for (const item of raw) {
|
|
1856
2823
|
if (typeof item !== "string")
|
|
1857
|
-
return
|
|
2824
|
+
return fail3(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag });
|
|
1858
2825
|
values.push(text2(item, flag) ?? "");
|
|
1859
2826
|
}
|
|
1860
2827
|
return values;
|
|
@@ -1863,10 +2830,10 @@ function model(value) {
|
|
|
1863
2830
|
const trimmed = text2(value, "--user-model");
|
|
1864
2831
|
if (trimmed === undefined)
|
|
1865
2832
|
return;
|
|
1866
|
-
return isModel(trimmed) ? trimmed :
|
|
2833
|
+
return isModel(trimmed) ? trimmed : fail3(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS });
|
|
1867
2834
|
}
|
|
1868
2835
|
function neverKind(kind) {
|
|
1869
|
-
return
|
|
2836
|
+
return fail3(`Unsupported steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_UNSUPPORTED", { kind });
|
|
1870
2837
|
}
|
|
1871
2838
|
async function parseSteeringProposal(argv) {
|
|
1872
2839
|
const kind = parseSteeringKind(argv);
|
|
@@ -1887,7 +2854,7 @@ async function parseSteeringProposal(argv) {
|
|
|
1887
2854
|
const revisedTitle = readValue(argv, "--title");
|
|
1888
2855
|
const revisedObjective = readValue(argv, "--objective");
|
|
1889
2856
|
if (revisedTitle === undefined && revisedObjective === undefined)
|
|
1890
|
-
return
|
|
2857
|
+
return fail3("revise_pending_wording requires --title or --objective.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
|
|
1891
2858
|
return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...revisedTitle === undefined ? {} : { revisedTitle }, ...revisedObjective === undefined ? {} : { revisedObjective } });
|
|
1892
2859
|
}
|
|
1893
2860
|
case "revise_criterion": {
|
|
@@ -1897,7 +2864,7 @@ async function parseSteeringProposal(argv) {
|
|
|
1897
2864
|
const expectedEvidence = readValue(argv, "--expected-evidence");
|
|
1898
2865
|
const userModel = model(readValue(argv, "--user-model"));
|
|
1899
2866
|
if (scenario === undefined && expectedEvidence === undefined && userModel === undefined)
|
|
1900
|
-
return
|
|
2867
|
+
return fail3("revise_criterion requires scenario, expected-evidence, or user-model.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
|
|
1901
2868
|
return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, criterionId, ...scenario === undefined ? {} : { scenario }, ...expectedEvidence === undefined ? {} : { expectedEvidence }, ...userModel === undefined ? {} : { userModel } });
|
|
1902
2869
|
}
|
|
1903
2870
|
case "annotate_ledger":
|
|
@@ -1947,10 +2914,10 @@ async function parseSteeringProposals(argv) {
|
|
|
1947
2914
|
if (input === undefined)
|
|
1948
2915
|
return [await parseSteeringProposal(argv)];
|
|
1949
2916
|
if (readValue(argv, "--kind") !== undefined)
|
|
1950
|
-
return
|
|
2917
|
+
return fail3("--kind and --proposals-json are mutually exclusive.", "ULW_LOOP_STEERING_BATCH_CONFLICT", { flags: ["--kind", "--proposals-json"] });
|
|
1951
2918
|
const raw = await readJsonInput(input);
|
|
1952
2919
|
if (!Array.isArray(raw) || raw.length === 0)
|
|
1953
|
-
return
|
|
2920
|
+
return fail3("--proposals-json must be a non-empty JSON array.", "ULW_LOOP_STEERING_BATCH_ARRAY_REQUIRED", { flag: "--proposals-json" });
|
|
1954
2921
|
const proposals = [];
|
|
1955
2922
|
for (const item of raw)
|
|
1956
2923
|
proposals.push(normalizeSteeringProposal(proposalFromObject(item)));
|
|
@@ -1958,13 +2925,13 @@ async function parseSteeringProposals(argv) {
|
|
|
1958
2925
|
}
|
|
1959
2926
|
function proposalFromObject(value) {
|
|
1960
2927
|
if (!isPlain(value))
|
|
1961
|
-
return
|
|
2928
|
+
return fail3("--proposals-json entries must be objects.", "ULW_LOOP_STEERING_BATCH_ITEM_INVALID", { flag: "--proposals-json" });
|
|
1962
2929
|
const kind = readObject(value, "kind");
|
|
1963
2930
|
const source = readObject(value, "source") ?? "cli";
|
|
1964
2931
|
if (!isProposalKind(kind))
|
|
1965
|
-
return
|
|
2932
|
+
return fail3(`Invalid batch steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_INVALID", { value: kind });
|
|
1966
2933
|
if (!isProposalSource(source))
|
|
1967
|
-
return
|
|
2934
|
+
return fail3(`Invalid batch steering source: ${String(source)}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value: source });
|
|
1968
2935
|
let proposal = { kind, source, evidence: objectText(value, "evidence") ?? "", rationale: objectText(value, "rationale") ?? "" };
|
|
1969
2936
|
const goalId = objectText(value, "goalId");
|
|
1970
2937
|
const targetGoalId = objectText(value, "targetGoalId");
|
|
@@ -2036,7 +3003,7 @@ function printSteerBatchResult(result, json) {
|
|
|
2036
3003
|
printStatus(result.plan);
|
|
2037
3004
|
}
|
|
2038
3005
|
|
|
2039
|
-
//
|
|
3006
|
+
// components/ulw-loop/src/review-blockers.ts
|
|
2040
3007
|
var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
2041
3008
|
function ulwLoopError(message, code) {
|
|
2042
3009
|
throw new UlwLoopError(message, code);
|
|
@@ -2050,12 +3017,13 @@ function nextGoalId(plan) {
|
|
|
2050
3017
|
}
|
|
2051
3018
|
function appendBlockerGoal(plan, args, now) {
|
|
2052
3019
|
const index = plan.goals.length;
|
|
3020
|
+
const id = nextGoalId(plan);
|
|
2053
3021
|
const goal = {
|
|
2054
|
-
id
|
|
3022
|
+
id,
|
|
2055
3023
|
title: args.title,
|
|
2056
3024
|
objective: args.objective,
|
|
2057
3025
|
status: "pending",
|
|
2058
|
-
successCriteria: seedDefaultSuccessCriteria(index, args.objective),
|
|
3026
|
+
successCriteria: seedDefaultSuccessCriteria(index, args.objective, { goalId: id }),
|
|
2059
3027
|
attempt: 0,
|
|
2060
3028
|
createdAt: now,
|
|
2061
3029
|
updatedAt: now
|
|
@@ -2074,10 +3042,14 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2074
3042
|
if (!isFinalRunCompletionCandidate(plan, goal))
|
|
2075
3043
|
ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
|
|
2076
3044
|
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
|
|
2077
|
-
const
|
|
2078
|
-
|
|
3045
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
3046
|
+
expectedObjective: expectedCodexObjective(plan, goal),
|
|
3047
|
+
acceptedObjectives: compatibleCodexObjectives(plan),
|
|
3048
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(plan)
|
|
3049
|
+
});
|
|
2079
3050
|
if (!reconciliation.ok)
|
|
2080
|
-
|
|
3051
|
+
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
3052
|
+
acknowledgeDriverObjective(plan, reconciliation.unacknowledgedObjective);
|
|
2081
3053
|
const now = iso();
|
|
2082
3054
|
for (const field of BLOCKER_FIELDS)
|
|
2083
3055
|
Reflect.deleteProperty(goal, field);
|
|
@@ -2089,27 +3061,61 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2089
3061
|
delete plan.activeGoalId;
|
|
2090
3062
|
const newGoal = appendBlockerGoal(plan, args, now);
|
|
2091
3063
|
plan.updatedAt = now;
|
|
2092
|
-
const codexGoal =
|
|
3064
|
+
const codexGoal = snapshot?.raw;
|
|
2093
3065
|
const blockedEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
|
|
2094
3066
|
const addedEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
|
|
2095
3067
|
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}.` };
|
|
2096
3068
|
Reflect.set(summaryEntry, "kind", "blocker_recorded");
|
|
2097
3069
|
const ledgerEntries = [blockedEntry, addedEntry, summaryEntry];
|
|
2098
|
-
await
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
3070
|
+
await commit(repoRoot, scope, { plan, entries: ledgerEntries });
|
|
3071
|
+
return {
|
|
3072
|
+
plan,
|
|
3073
|
+
blockedGoal: goal,
|
|
3074
|
+
newGoal,
|
|
3075
|
+
ledgerEntries,
|
|
3076
|
+
nextActions: reconciliation.nextActions,
|
|
3077
|
+
warnings: reconciliation.warnings
|
|
3078
|
+
};
|
|
2102
3079
|
});
|
|
2103
3080
|
}
|
|
2104
3081
|
|
|
2105
|
-
//
|
|
3082
|
+
// components/ulw-loop/src/status-next-actions.ts
|
|
3083
|
+
function statusNextActions(plan, surface = "lazycodex") {
|
|
3084
|
+
const actions = [];
|
|
3085
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
3086
|
+
if (plan.goals.length === 0)
|
|
3087
|
+
actions.push(`No goals yet: bootstrap with \`${createGoalsAction(surface)}\`.`);
|
|
3088
|
+
else if (active !== undefined)
|
|
3089
|
+
actions.push(...activeGoalActions(plan, active, surface));
|
|
3090
|
+
for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
|
|
3091
|
+
actions.push(surface === "omo-senpi" ? `${goal.id} is review_blocked: capture the reviewer verdict with \`agentToolkit.recordReviewBlockers({ goalId: "${goal.id}", title: "<title>", objective: "<objective>", evidence: "<verdict>" })\`.` : `${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>'\`.`);
|
|
3092
|
+
if (plan.evidenceLayoutVersion !== 2 || active === undefined)
|
|
3093
|
+
actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
|
|
3094
|
+
return actions;
|
|
3095
|
+
}
|
|
3096
|
+
function activeGoalActions(plan, active, surface) {
|
|
3097
|
+
const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
|
|
3098
|
+
if (unresolved.length > 0)
|
|
3099
|
+
return [
|
|
3100
|
+
surface === "omo-senpi" ? `${active.id} has ${unresolved.length} unresolved criterion(s) (${unresolved.map((criterion) => criterion.id).join(", ")}): record proof with \`agentToolkit.recordEvidence({ goalId: "${active.id}", criterionId: "<id>", status: "pass", evidence: "<observable proof>" })\`.` : `${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>"\`.`
|
|
3101
|
+
];
|
|
3102
|
+
if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
|
|
3103
|
+
return [
|
|
3104
|
+
surface === "omo-senpi" ? `${active.id} passes every criterion and is the final story: complete the driver goal, then call \`agentToolkit.checkpoint({ goalId: "${active.id}", printTemplate: true })\` to build the final quality gate. Use status().result.currentAttemptDir for all quality-gate artifacts.` : `${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.`
|
|
3105
|
+
];
|
|
3106
|
+
return [
|
|
3107
|
+
surface === "omo-senpi" ? `${active.id} passes every criterion: close it with \`agentToolkit.checkpoint({ goalId: "${active.id}", status: "complete", evidence: "<proof>" })\`. Put quality-gate artifacts under status().result.currentAttemptDir.` : `${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.`
|
|
3108
|
+
];
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// components/ulw-loop/src/steering-mutations.ts
|
|
2106
3112
|
var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
2107
3113
|
var isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2108
3114
|
var text3 = (value, key) => {
|
|
2109
3115
|
const candidate = read2(value, key);
|
|
2110
3116
|
return isText(candidate) ? candidate.trim() : undefined;
|
|
2111
3117
|
};
|
|
2112
|
-
var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((
|
|
3118
|
+
var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2113
3119
|
var after = (proposal) => {
|
|
2114
3120
|
const candidate = read2(proposal, "after");
|
|
2115
3121
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
|
|
@@ -2139,11 +3145,11 @@ function nextId(plan, offset) {
|
|
|
2139
3145
|
}, 0);
|
|
2140
3146
|
return `G${String(max + offset).padStart(3, "0")}`;
|
|
2141
3147
|
}
|
|
2142
|
-
function makeGoal2(plan, childGoal, evidence, now, offset) {
|
|
3148
|
+
function makeGoal2(plan, childGoal, evidence, now, offset, surface = "lazycodex") {
|
|
2143
3149
|
const id = nextId(plan, offset);
|
|
2144
3150
|
const digits = /^G(\d+)/u.exec(id)?.[1];
|
|
2145
3151
|
const goalIndex = digits === undefined ? plan.goals.length + offset - 1 : Number(digits) - 1;
|
|
2146
|
-
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective), attempt: 0, createdAt: now, updatedAt: now, evidence };
|
|
3152
|
+
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective, { goalId: id, surface }), attempt: 0, createdAt: now, updatedAt: now, evidence };
|
|
2147
3153
|
}
|
|
2148
3154
|
function reviseWording(plan, proposal, now) {
|
|
2149
3155
|
const target = goal(plan, targets(proposal)[0]);
|
|
@@ -2155,11 +3161,11 @@ function reviseWording(plan, proposal, now) {
|
|
|
2155
3161
|
target.steeringRationale = proposal.rationale;
|
|
2156
3162
|
target.updatedAt = now;
|
|
2157
3163
|
}
|
|
2158
|
-
function splitOrBlock(plan, proposal, now) {
|
|
3164
|
+
function splitOrBlock(plan, proposal, now, surface = "lazycodex") {
|
|
2159
3165
|
const target = goal(plan, targets(proposal)[0]);
|
|
2160
3166
|
if (target === undefined)
|
|
2161
3167
|
return;
|
|
2162
|
-
const replacements = children2(proposal).map((item, index) => makeGoal2(plan, item, proposal.evidence, now, index + 1));
|
|
3168
|
+
const replacements = children2(proposal).map((item, index) => makeGoal2(plan, item, proposal.evidence, now, index + 1, surface));
|
|
2163
3169
|
target.steeringEvidence = proposal.evidence;
|
|
2164
3170
|
target.steeringRationale = proposal.rationale;
|
|
2165
3171
|
target.updatedAt = now;
|
|
@@ -2184,38 +3190,38 @@ function reviseCriterion(plan, proposal, now) {
|
|
|
2184
3190
|
const current = target?.successCriteria[index];
|
|
2185
3191
|
if (target === undefined || current === undefined)
|
|
2186
3192
|
return;
|
|
2187
|
-
const
|
|
2188
|
-
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(
|
|
3193
|
+
const model = read2(proposal, "userModel");
|
|
3194
|
+
target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model) ? model : current.userModel };
|
|
2189
3195
|
target.updatedAt = now;
|
|
2190
3196
|
}
|
|
2191
3197
|
|
|
2192
|
-
//
|
|
3198
|
+
// components/ulw-loop/src/steering-snapshot.ts
|
|
2193
3199
|
function buildSteeringPlanSnapshot(plan, changedGoalIds) {
|
|
2194
3200
|
const snapshot = {
|
|
2195
3201
|
updatedAt: plan.updatedAt,
|
|
2196
3202
|
goalCount: plan.goals.length,
|
|
2197
|
-
goalIds: plan.goals.map((
|
|
2198
|
-
goals: plan.goals.filter((
|
|
3203
|
+
goalIds: plan.goals.map((goal) => goal.id),
|
|
3204
|
+
goals: plan.goals.filter((goal) => changedGoalIds.has(goal.id))
|
|
2199
3205
|
};
|
|
2200
3206
|
return plan.activeGoalId === undefined ? snapshot : { ...snapshot, activeGoalId: plan.activeGoalId };
|
|
2201
3207
|
}
|
|
2202
|
-
function changedGoalIdsBetween(before,
|
|
2203
|
-
const beforeById = new Map(before.goals.map((
|
|
3208
|
+
function changedGoalIdsBetween(before, after) {
|
|
3209
|
+
const beforeById = new Map(before.goals.map((goal) => [goal.id, goal]));
|
|
2204
3210
|
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(
|
|
3211
|
+
for (const goal of after.goals) {
|
|
3212
|
+
const prior = beforeById.get(goal.id);
|
|
3213
|
+
if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal))
|
|
3214
|
+
changed.add(goal.id);
|
|
3215
|
+
beforeById.delete(goal.id);
|
|
2210
3216
|
}
|
|
2211
3217
|
for (const id of beforeById.keys())
|
|
2212
3218
|
changed.add(id);
|
|
2213
3219
|
return changed;
|
|
2214
3220
|
}
|
|
2215
3221
|
|
|
2216
|
-
//
|
|
3222
|
+
// components/ulw-loop/src/steering.ts
|
|
2217
3223
|
var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
|
|
2218
|
-
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
3224
|
+
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "acknowledgedDriverObjectives", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
2219
3225
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
2220
3226
|
var isPlain2 = (value) => isObject2(value) && !Array.isArray(value);
|
|
2221
3227
|
var read3 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
@@ -2226,7 +3232,7 @@ var text4 = (value, key) => {
|
|
|
2226
3232
|
};
|
|
2227
3233
|
var isKind2 = (value) => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
|
|
2228
3234
|
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((
|
|
3235
|
+
var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
2230
3236
|
var texts = (value, key) => {
|
|
2231
3237
|
const candidate = read3(value, key);
|
|
2232
3238
|
return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
|
|
@@ -2367,19 +3373,19 @@ function validateCriterion(plan, proposal, reasons) {
|
|
|
2367
3373
|
reasons.push("revise_criterion requires goalId");
|
|
2368
3374
|
else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
|
|
2369
3375
|
reasons.push("revise_criterion requires criterionId");
|
|
2370
|
-
const
|
|
2371
|
-
if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined &&
|
|
3376
|
+
const model = read3(proposal, "userModel");
|
|
3377
|
+
if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model === undefined)
|
|
2372
3378
|
reasons.push("revise_criterion requires update");
|
|
2373
|
-
if (
|
|
3379
|
+
if (model !== undefined && !isModel3(model))
|
|
2374
3380
|
reasons.push("invalid userModel");
|
|
2375
3381
|
}
|
|
2376
|
-
function applySteeringMutation(plan, proposal, audit) {
|
|
3382
|
+
function applySteeringMutation(plan, proposal, audit, surface = "lazycodex") {
|
|
2377
3383
|
const next = structuredClone(plan);
|
|
2378
3384
|
if (!audit.invariant.accepted)
|
|
2379
3385
|
return next;
|
|
2380
3386
|
const now = proposal.now?.toISOString() ?? iso();
|
|
2381
3387
|
if (proposal.kind === "add_subgoal")
|
|
2382
|
-
next.goals.push(makeGoal2(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1));
|
|
3388
|
+
next.goals.push(makeGoal2(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1, surface));
|
|
2383
3389
|
if (proposal.kind === "reorder_pending") {
|
|
2384
3390
|
const order = pendingOrder(proposal);
|
|
2385
3391
|
next.goals = [...order.map((id) => goal2(next, id)).filter((item) => item !== undefined), ...next.goals.filter((item) => !order.includes(item.id))];
|
|
@@ -2387,7 +3393,7 @@ function applySteeringMutation(plan, proposal, audit) {
|
|
|
2387
3393
|
if (proposal.kind === "revise_pending_wording")
|
|
2388
3394
|
reviseWording(next, proposal, now);
|
|
2389
3395
|
if (proposal.kind === "split_subgoal" || proposal.kind === "mark_blocked_superseded")
|
|
2390
|
-
splitOrBlock(next, proposal, now);
|
|
3396
|
+
splitOrBlock(next, proposal, now, surface);
|
|
2391
3397
|
if (proposal.kind === "revise_criterion")
|
|
2392
3398
|
reviseCriterion(next, proposal, now);
|
|
2393
3399
|
if (proposal.kind !== "annotate_ledger")
|
|
@@ -2397,8 +3403,8 @@ function applySteeringMutation(plan, proposal, audit) {
|
|
|
2397
3403
|
function isProposal(value) {
|
|
2398
3404
|
return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
|
|
2399
3405
|
}
|
|
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(
|
|
3406
|
+
function parseUlwLoopSteeringDirective(text) {
|
|
3407
|
+
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
3408
|
if (match?.[1] === undefined)
|
|
2403
3409
|
return null;
|
|
2404
3410
|
try {
|
|
@@ -2410,7 +3416,7 @@ function parseUlwLoopSteeringDirective(text5) {
|
|
|
2410
3416
|
throw error;
|
|
2411
3417
|
}
|
|
2412
3418
|
}
|
|
2413
|
-
async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
3419
|
+
async function steerUlwLoop(repoRoot, proposal, scope, surface = "lazycodex") {
|
|
2414
3420
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
2415
3421
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
2416
3422
|
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
@@ -2421,7 +3427,7 @@ async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
|
2421
3427
|
}
|
|
2422
3428
|
const audit = validateUlwLoopSteeringProposal(plan, proposal);
|
|
2423
3429
|
const accepted = audit.invariant.accepted;
|
|
2424
|
-
const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
|
|
3430
|
+
const next = accepted ? applySteeringMutation(plan, proposal, audit, surface) : plan;
|
|
2425
3431
|
const finalAudit = { ...audit };
|
|
2426
3432
|
if (accepted) {
|
|
2427
3433
|
const changed = changedGoalIdsBetween(plan, next);
|
|
@@ -2430,11 +3436,10 @@ async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
|
2430
3436
|
}
|
|
2431
3437
|
const at = proposal.now?.toISOString() ?? iso();
|
|
2432
3438
|
const batchEntry = accepted ? batchUpdateLedgerEntry(plan, next, at) : null;
|
|
2433
|
-
|
|
2434
|
-
await writePlan(repoRoot, next, scope);
|
|
2435
|
-
await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit, at), scope);
|
|
3439
|
+
const entries = [ledgerEntry(proposal, finalAudit, at)];
|
|
2436
3440
|
if (batchEntry !== null)
|
|
2437
|
-
|
|
3441
|
+
entries.push(batchEntry);
|
|
3442
|
+
await commit(repoRoot, scope, { plan: next, entries });
|
|
2438
3443
|
return { plan: next, accepted, audit: finalAudit, rejectedReasons: audit.invariant.rejectedReasons, deduped: false };
|
|
2439
3444
|
});
|
|
2440
3445
|
}
|
|
@@ -2450,7 +3455,257 @@ function ledgerEntry(proposal, audit, at) {
|
|
|
2450
3455
|
return entry;
|
|
2451
3456
|
}
|
|
2452
3457
|
|
|
2453
|
-
//
|
|
3458
|
+
// components/ulw-loop/src/sdk/manifest.ts
|
|
3459
|
+
var CRITERION_INPUT = '{ scenario: string, expectedEvidence: string, userModel?: "happy" | "edge" | "regression" | "adversarial", essential?: boolean }';
|
|
3460
|
+
var STEER_KINDS = '"add_subgoal" | "split_subgoal" | "reorder_pending" | "revise_pending_wording" | "revise_criterion" | "annotate_ledger" | "mark_blocked_superseded"';
|
|
3461
|
+
var ULW_LOOP_MANIFEST = {
|
|
3462
|
+
version: 1,
|
|
3463
|
+
name: "ulw-loop",
|
|
3464
|
+
operations: [
|
|
3465
|
+
{
|
|
3466
|
+
name: "help",
|
|
3467
|
+
method: "help",
|
|
3468
|
+
mutating: false,
|
|
3469
|
+
description: "Return this manifest: every operation with its method name, argument fields, and what it does.",
|
|
3470
|
+
args: {}
|
|
3471
|
+
},
|
|
3472
|
+
{
|
|
3473
|
+
name: "create-goals",
|
|
3474
|
+
method: "createGoals",
|
|
3475
|
+
mutating: true,
|
|
3476
|
+
description: "Create the session plan from a brief; one goal per bullet, each seeded with placeholder criteria until revised.",
|
|
3477
|
+
args: {
|
|
3478
|
+
brief: "string",
|
|
3479
|
+
codexGoalMode: '"aggregate" | "per_story"?',
|
|
3480
|
+
force: "boolean?",
|
|
3481
|
+
validationBatchesJson: "string?"
|
|
3482
|
+
}
|
|
3483
|
+
},
|
|
3484
|
+
{
|
|
3485
|
+
name: "status",
|
|
3486
|
+
method: "status",
|
|
3487
|
+
mutating: false,
|
|
3488
|
+
description: "Read the plan, its summary, structured nextActions, the stable evidenceRoot, and the active goal's currentAttemptDir.",
|
|
3489
|
+
args: {}
|
|
3490
|
+
},
|
|
3491
|
+
{
|
|
3492
|
+
name: "complete-goals",
|
|
3493
|
+
method: "completeGoals",
|
|
3494
|
+
mutating: true,
|
|
3495
|
+
description: "Acquire the next eligible goal or resume the in-progress one; returns { done: true } once the aggregate is complete.",
|
|
3496
|
+
args: { retryFailed: "boolean?" }
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
name: "checkpoint",
|
|
3500
|
+
method: "checkpoint",
|
|
3501
|
+
mutating: true,
|
|
3502
|
+
description: "Close the goal as complete, failed, or blocked with evidence; the final goal also needs the quality gate. printTemplate returns that gate's template instead.",
|
|
3503
|
+
args: {
|
|
3504
|
+
goalId: "string",
|
|
3505
|
+
status: '"complete" | "failed" | "blocked"',
|
|
3506
|
+
evidence: "string",
|
|
3507
|
+
codexGoalJson: "string?",
|
|
3508
|
+
qualityGateJson: "string?",
|
|
3509
|
+
printTemplate: "true? (with goalId? only)"
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
{
|
|
3513
|
+
name: "steer",
|
|
3514
|
+
method: "steer",
|
|
3515
|
+
mutating: true,
|
|
3516
|
+
description: "Propose an evidence-backed plan mutation; revise_criterion replaces a criterion's scenario, expectedEvidence, or userModel.",
|
|
3517
|
+
args: {
|
|
3518
|
+
kind: STEER_KINDS,
|
|
3519
|
+
source: '"finding" | "user_prompt_submit" | "cli"',
|
|
3520
|
+
evidence: "string",
|
|
3521
|
+
rationale: "string",
|
|
3522
|
+
goalId: "string?",
|
|
3523
|
+
criterionId: "string? (revise_criterion)",
|
|
3524
|
+
scenario: "string? (revise_criterion)",
|
|
3525
|
+
expectedEvidence: "string? (revise_criterion)",
|
|
3526
|
+
userModel: '"happy" | "edge" | "regression" | "adversarial"? (revise_criterion)',
|
|
3527
|
+
title: "string? (add_subgoal)",
|
|
3528
|
+
objective: "string? (add_subgoal)",
|
|
3529
|
+
childGoals: "{ title: string, objective: string }[]? (split_subgoal)",
|
|
3530
|
+
revisedTitle: "string? (revise_pending_wording)",
|
|
3531
|
+
revisedObjective: "string? (revise_pending_wording)",
|
|
3532
|
+
pendingOrder: "string[]? (reorder_pending)",
|
|
3533
|
+
blockedReason: "string? (mark_blocked_superseded)",
|
|
3534
|
+
idempotencyKey: "string?"
|
|
3535
|
+
}
|
|
3536
|
+
},
|
|
3537
|
+
{
|
|
3538
|
+
name: "add-goal",
|
|
3539
|
+
method: "addGoal",
|
|
3540
|
+
mutating: true,
|
|
3541
|
+
description: "Append a goal; pass successCriteria to define its criteria in the same call, otherwise placeholders name the revise call.",
|
|
3542
|
+
args: { title: "string", objective: "string", successCriteria: `${CRITERION_INPUT}[]?` }
|
|
3543
|
+
},
|
|
3544
|
+
{
|
|
3545
|
+
name: "criteria",
|
|
3546
|
+
method: "criteria",
|
|
3547
|
+
mutating: false,
|
|
3548
|
+
description: "List one goal's success criteria with their status and captured evidence.",
|
|
3549
|
+
args: { goalId: "string" }
|
|
3550
|
+
},
|
|
3551
|
+
{
|
|
3552
|
+
name: "record-evidence",
|
|
3553
|
+
method: "recordEvidence",
|
|
3554
|
+
mutating: true,
|
|
3555
|
+
description: "Record a criterion's pass, fail, or blocked evidence; artifacts must exist (resolved against the session cwd) and are stored with the criterion and the ledger entry.",
|
|
3556
|
+
args: {
|
|
3557
|
+
goalId: "string",
|
|
3558
|
+
criterionId: "string",
|
|
3559
|
+
status: '"pass" | "fail" | "blocked"',
|
|
3560
|
+
evidence: "string",
|
|
3561
|
+
notes: "string?",
|
|
3562
|
+
artifacts: "string[]?"
|
|
3563
|
+
}
|
|
3564
|
+
},
|
|
3565
|
+
{
|
|
3566
|
+
name: "record-review-blockers",
|
|
3567
|
+
method: "recordReviewBlockers",
|
|
3568
|
+
mutating: true,
|
|
3569
|
+
description: "Mark the final goal review_blocked and append the blocker goal the reviewer verdict names.",
|
|
3570
|
+
args: { goalId: "string", title: "string", objective: "string", evidence: "string", codexGoalJson: "string?" }
|
|
3571
|
+
}
|
|
3572
|
+
]
|
|
3573
|
+
};
|
|
3574
|
+
|
|
3575
|
+
// components/ulw-loop/src/sdk/factory.ts
|
|
3576
|
+
function validateCodexGoalJson(raw) {
|
|
3577
|
+
if (raw === undefined)
|
|
3578
|
+
return;
|
|
3579
|
+
try {
|
|
3580
|
+
JSON.parse(raw);
|
|
3581
|
+
} catch (error) {
|
|
3582
|
+
throw new UlwLoopError(`Invalid codexGoal: ${error instanceof Error ? error.message : "not valid JSON"}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
function validateContext(context) {
|
|
3586
|
+
if (!context.cwd.trim())
|
|
3587
|
+
throw new UlwLoopError("cwd is required.", "ULW_LOOP_CWD_REQUIRED");
|
|
3588
|
+
const sessionId = context.sessionId.trim();
|
|
3589
|
+
if (!sessionId)
|
|
3590
|
+
throw new UlwLoopError("ULW_LOOP_SESSION_ID_REQUIRED: sessionId is required.", "ULW_LOOP_SESSION_ID_REQUIRED");
|
|
3591
|
+
const normalizedSessionId = normalizeUlwLoopSessionId(sessionId);
|
|
3592
|
+
if (normalizedSessionId === null || /(?:^|[\\/])\.\.(?:[\\/]|$)/.test(sessionId))
|
|
3593
|
+
throw new UlwLoopError("ULW_LOOP_SESSION_ID_INVALID: sessionId normalizes to null.", "ULW_LOOP_SESSION_ID_INVALID");
|
|
3594
|
+
if (context.surface !== "omo-senpi" && context.surface !== "lazycodex")
|
|
3595
|
+
throw new UlwLoopError("surface must be omo-senpi or lazycodex.", "ULW_LOOP_SURFACE_INVALID");
|
|
3596
|
+
}
|
|
3597
|
+
function errorDetails(error) {
|
|
3598
|
+
const details = error.details === undefined ? undefined : Object.fromEntries(Object.entries(error.details).map(([key, value]) => [key, String(value)]));
|
|
3599
|
+
return details === undefined ? { code: error.code, message: error.message } : { code: error.code, message: error.message, details };
|
|
3600
|
+
}
|
|
3601
|
+
function failure(operation, error) {
|
|
3602
|
+
return { ok: false, operation, error: errorDetails(error) };
|
|
3603
|
+
}
|
|
3604
|
+
function caught(operation, error) {
|
|
3605
|
+
return failure(operation, error instanceof UlwLoopError ? error : new UlwLoopError(error.message, "ULW_LOOP_ERROR"));
|
|
3606
|
+
}
|
|
3607
|
+
function isKnownRequest(request) {
|
|
3608
|
+
return ULW_LOOP_MANIFEST.operations.some((operation) => operation.name === request.operation);
|
|
3609
|
+
}
|
|
3610
|
+
function unreachable(value) {
|
|
3611
|
+
throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
|
|
3612
|
+
}
|
|
3613
|
+
function stringsFrom(result, key) {
|
|
3614
|
+
if (!(key in result))
|
|
3615
|
+
return [];
|
|
3616
|
+
const value = Object.entries(result).find(([name]) => name === key)?.[1];
|
|
3617
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string").slice(0, 8) : [];
|
|
3618
|
+
}
|
|
3619
|
+
function checkpointWithValidatedSnapshot(context, scope, args) {
|
|
3620
|
+
validateCodexGoalJson(args.codexGoalJson);
|
|
3621
|
+
return checkpointUlwLoop(context.cwd, args, scope, { surface: context.surface });
|
|
3622
|
+
}
|
|
3623
|
+
function createAgentToolkit(context, deps = {}) {
|
|
3624
|
+
validateContext(context);
|
|
3625
|
+
const scope = { sessionId: context.sessionId };
|
|
3626
|
+
const notify = async (operation, response) => {
|
|
3627
|
+
if (deps.hooks?.onOperation !== undefined)
|
|
3628
|
+
await deps.hooks.onOperation({ operation, context, response });
|
|
3629
|
+
return response;
|
|
3630
|
+
};
|
|
3631
|
+
const invoke = async (operation, fn) => {
|
|
3632
|
+
try {
|
|
3633
|
+
const result = await fn();
|
|
3634
|
+
const nextActions = typeof result === "object" && result !== null ? stringsFrom(result, "nextActions") : [];
|
|
3635
|
+
const warnings = typeof result === "object" && result !== null ? stringsFrom(result, "warnings") : [];
|
|
3636
|
+
return await notify(operation, {
|
|
3637
|
+
ok: true,
|
|
3638
|
+
operation,
|
|
3639
|
+
result,
|
|
3640
|
+
nextActions,
|
|
3641
|
+
...warnings.length === 0 ? {} : { warnings }
|
|
3642
|
+
});
|
|
3643
|
+
} catch (error) {
|
|
3644
|
+
if (error instanceof UlwLoopError && error.code === "ULW_LOOP_PLAN_MISSING" && context.surface === "omo-senpi")
|
|
3645
|
+
return notify(operation, failure(operation, planMissingError(ulwLoopGoalsRelativePath(scope), listUlwLoopSessionIds(context.cwd), context.surface)));
|
|
3646
|
+
const response = caught(operation, error instanceof Error ? error : new Error("ULW_LOOP_ERROR"));
|
|
3647
|
+
return notify(operation, response);
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
3650
|
+
const toolkit = {
|
|
3651
|
+
dispatch: async (request) => {
|
|
3652
|
+
if (!isKnownRequest(request))
|
|
3653
|
+
return failure(request.operation, new UlwLoopError(`Unknown operation: ${request.operation}`, "ULW_LOOP_OPERATION_UNKNOWN"));
|
|
3654
|
+
switch (request.operation) {
|
|
3655
|
+
case "help":
|
|
3656
|
+
return toolkit.help();
|
|
3657
|
+
case "create-goals":
|
|
3658
|
+
return toolkit.createGoals(request.args);
|
|
3659
|
+
case "status":
|
|
3660
|
+
return toolkit.status();
|
|
3661
|
+
case "complete-goals":
|
|
3662
|
+
return toolkit.completeGoals(request.args);
|
|
3663
|
+
case "checkpoint":
|
|
3664
|
+
return toolkit.checkpoint(request.args);
|
|
3665
|
+
case "steer":
|
|
3666
|
+
return toolkit.steer(request.args);
|
|
3667
|
+
case "add-goal":
|
|
3668
|
+
return toolkit.addGoal(request.args);
|
|
3669
|
+
case "criteria":
|
|
3670
|
+
return toolkit.criteria(request.args);
|
|
3671
|
+
case "record-evidence":
|
|
3672
|
+
return toolkit.recordEvidence(request.args);
|
|
3673
|
+
case "record-review-blockers":
|
|
3674
|
+
return toolkit.recordReviewBlockers(request.args);
|
|
3675
|
+
default:
|
|
3676
|
+
return unreachable(request);
|
|
3677
|
+
}
|
|
3678
|
+
},
|
|
3679
|
+
help: () => invoke("help", async () => ULW_LOOP_MANIFEST),
|
|
3680
|
+
createGoals: (args) => invoke("create-goals", () => createUlwLoopPlan(context.cwd, args, scope, context.surface)),
|
|
3681
|
+
status: () => invoke("status", async () => {
|
|
3682
|
+
const plan = await readUlwLoopPlan(context.cwd, scope);
|
|
3683
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
3684
|
+
return {
|
|
3685
|
+
plan,
|
|
3686
|
+
summary: summarizeUlwLoopPlan(plan),
|
|
3687
|
+
nextActions: statusNextActions(plan, context.surface),
|
|
3688
|
+
evidenceRoot: ulwLoopEvidenceRoot(scope),
|
|
3689
|
+
...active === undefined || plan.evidenceLayoutVersion !== 2 ? {} : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }
|
|
3690
|
+
};
|
|
3691
|
+
}),
|
|
3692
|
+
completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
|
|
3693
|
+
checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface }) : checkpointWithValidatedSnapshot(context, scope, args)),
|
|
3694
|
+
steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope, context.surface)),
|
|
3695
|
+
addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope, context.surface)),
|
|
3696
|
+
criteria: (args) => invoke("criteria", async () => {
|
|
3697
|
+
const plan = await readUlwLoopPlan(context.cwd, scope);
|
|
3698
|
+
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
|
|
3699
|
+
if (goal === undefined)
|
|
3700
|
+
throw new UlwLoopError(`Unknown ulw-loop id: ${args.goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND");
|
|
3701
|
+
return { goalId: goal.id, criteria: goal.successCriteria };
|
|
3702
|
+
}),
|
|
3703
|
+
recordEvidence: (args) => invoke("record-evidence", () => recordEvidence(context.cwd, args, scope)),
|
|
3704
|
+
recordReviewBlockers: (args) => invoke("record-review-blockers", () => recordFinalReviewBlockers(context.cwd, args, scope))
|
|
3705
|
+
};
|
|
3706
|
+
return toolkit;
|
|
3707
|
+
}
|
|
3708
|
+
// components/ulw-loop/src/steering-batch.ts
|
|
2454
3709
|
async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
2455
3710
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
2456
3711
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
@@ -2458,7 +3713,7 @@ async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
|
2458
3713
|
const failed = prepared.results.find((item) => !item.accepted);
|
|
2459
3714
|
if (failed !== undefined) {
|
|
2460
3715
|
const entry = rejectedLedgerEntry(prepared.results);
|
|
2461
|
-
await
|
|
3716
|
+
await commit(repoRoot, scope, { plan, entries: [entry] });
|
|
2462
3717
|
return rejected(plan, prepared.results, failed.rejectedReasons);
|
|
2463
3718
|
}
|
|
2464
3719
|
let next = plan;
|
|
@@ -2467,10 +3722,9 @@ async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
|
2467
3722
|
next = item.prepared.next;
|
|
2468
3723
|
const fresh = prepared.items.filter((item) => item.kind === "fresh");
|
|
2469
3724
|
if (fresh.length > 0) {
|
|
2470
|
-
await writePlan(repoRoot, next, scope);
|
|
2471
3725
|
const entries = fresh.map((item) => ledgerEntry2(item.prepared.proposal, item.prepared.audit, item.prepared.proposal.now?.toISOString() ?? iso()));
|
|
2472
3726
|
const batchEntry = batchUpdateLedgerEntry(plan, next, iso());
|
|
2473
|
-
await
|
|
3727
|
+
await commit(repoRoot, scope, { plan: next, entries: batchEntry === null ? entries : [...entries, batchEntry] });
|
|
2474
3728
|
}
|
|
2475
3729
|
return { plan: next, accepted: true, results: prepared.results, rejectedReasons: [] };
|
|
2476
3730
|
});
|
|
@@ -2483,16 +3737,16 @@ async function prepareBatch(repoRoot, plan, proposals, scope) {
|
|
|
2483
3737
|
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
2484
3738
|
const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
|
|
2485
3739
|
if (prior?.steering !== undefined) {
|
|
2486
|
-
const
|
|
2487
|
-
items.push({ kind: "deduped", result
|
|
2488
|
-
results.push(
|
|
3740
|
+
const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
|
|
3741
|
+
items.push({ kind: "deduped", result });
|
|
3742
|
+
results.push(result);
|
|
2489
3743
|
continue;
|
|
2490
3744
|
}
|
|
2491
3745
|
const audit = validateUlwLoopSteeringProposal(current, proposal);
|
|
2492
3746
|
if (!audit.invariant.accepted) {
|
|
2493
|
-
const
|
|
2494
|
-
items.push({ kind: "deduped", result
|
|
2495
|
-
results.push(
|
|
3747
|
+
const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
|
|
3748
|
+
items.push({ kind: "deduped", result });
|
|
3749
|
+
results.push(result);
|
|
2496
3750
|
continue;
|
|
2497
3751
|
}
|
|
2498
3752
|
const next = applySteeringMutation(current, proposal, audit);
|
|
@@ -2531,20 +3785,20 @@ function ledgerEntry2(proposal, audit, at) {
|
|
|
2531
3785
|
return entry;
|
|
2532
3786
|
}
|
|
2533
3787
|
|
|
2534
|
-
//
|
|
3788
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
2535
3789
|
async function createGoals(repoRoot, argv, json, scope) {
|
|
2536
3790
|
const briefFile = readValue(argv, "--brief-file");
|
|
2537
|
-
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await
|
|
3791
|
+
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile4(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
|
|
2538
3792
|
if (!brief.trim()) {
|
|
2539
3793
|
throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
|
|
2540
3794
|
}
|
|
2541
3795
|
const validationBatchesJson = readValue(argv, "--validation-batch-json");
|
|
2542
|
-
const plan = await
|
|
3796
|
+
const plan = unwrap(await toolkitFor(repoRoot, scope).createGoals({
|
|
2543
3797
|
brief,
|
|
2544
3798
|
codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")),
|
|
2545
3799
|
force: hasFlag(argv, "--force"),
|
|
2546
3800
|
...validationBatchesJson === undefined ? {} : { validationBatchesJson }
|
|
2547
|
-
}
|
|
3801
|
+
}));
|
|
2548
3802
|
if (json)
|
|
2549
3803
|
printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
|
|
2550
3804
|
else {
|
|
@@ -2557,22 +3811,15 @@ ledger: ${plan.ledgerPath}
|
|
|
2557
3811
|
return 0;
|
|
2558
3812
|
}
|
|
2559
3813
|
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);
|
|
3814
|
+
const status = unwrap(await toolkitFor(repoRoot, scope).status());
|
|
3815
|
+
if (json)
|
|
3816
|
+
printJson({ ok: true, ...status });
|
|
3817
|
+
else
|
|
3818
|
+
printStatus(status.plan);
|
|
2572
3819
|
return 0;
|
|
2573
3820
|
}
|
|
2574
3821
|
async function completeGoals(repoRoot, argv, json, scope) {
|
|
2575
|
-
const result = await
|
|
3822
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).completeGoals({ retryFailed: hasFlag(argv, "--retry-failed") }));
|
|
2576
3823
|
if ("done" in result) {
|
|
2577
3824
|
const handoff = blockedDecisionHandoff(result.plan);
|
|
2578
3825
|
if (json) {
|
|
@@ -2601,16 +3848,19 @@ async function steer(repoRoot, argv, json, scope) {
|
|
|
2601
3848
|
const proposals = await parseSteeringProposals(argv);
|
|
2602
3849
|
const single = proposals[0];
|
|
2603
3850
|
if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
|
|
2604
|
-
const
|
|
2605
|
-
printSteerResult(
|
|
2606
|
-
return
|
|
3851
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).steer(single));
|
|
3852
|
+
printSteerResult(result, json);
|
|
3853
|
+
return result.accepted ? 0 : 1;
|
|
2607
3854
|
}
|
|
2608
3855
|
const result = await steerUlwLoopBatch(repoRoot, proposals, scope);
|
|
2609
3856
|
printSteerBatchResult(result, json);
|
|
2610
3857
|
return result.accepted ? 0 : 1;
|
|
2611
3858
|
}
|
|
2612
3859
|
async function addGoal(repoRoot, argv, json, scope) {
|
|
2613
|
-
const result = await
|
|
3860
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).addGoal({
|
|
3861
|
+
title: required4(argv, "--title"),
|
|
3862
|
+
objective: required4(argv, "--objective")
|
|
3863
|
+
}));
|
|
2614
3864
|
if (json)
|
|
2615
3865
|
printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
|
|
2616
3866
|
else {
|
|
@@ -2622,19 +3872,19 @@ async function addGoal(repoRoot, argv, json, scope) {
|
|
|
2622
3872
|
}
|
|
2623
3873
|
async function criteria(repoRoot, argv, json, scope) {
|
|
2624
3874
|
const goalId = required4(argv, "--goal-id");
|
|
2625
|
-
const
|
|
3875
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).criteria({ goalId }));
|
|
2626
3876
|
if (json)
|
|
2627
|
-
printJson({ ok: true, goalId:
|
|
3877
|
+
printJson({ ok: true, goalId: result.goalId, criteria: result.criteria });
|
|
2628
3878
|
else {
|
|
2629
|
-
process.stdout.write(`criteria for ${
|
|
2630
|
-
${
|
|
3879
|
+
process.stdout.write(`criteria for ${result.goalId}:
|
|
3880
|
+
${result.criteria.map(formatCriterionForCli).join(`
|
|
2631
3881
|
`)}
|
|
2632
3882
|
`);
|
|
2633
3883
|
}
|
|
2634
3884
|
return 0;
|
|
2635
3885
|
}
|
|
2636
3886
|
async function captureEvidence(repoRoot, argv, json, scope) {
|
|
2637
|
-
const result = await
|
|
3887
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).recordEvidence(parseRecordEvidenceArgs(argv)));
|
|
2638
3888
|
if (json)
|
|
2639
3889
|
printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
|
|
2640
3890
|
else {
|
|
@@ -2648,13 +3898,13 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
|
|
|
2648
3898
|
if (codexGoalJson === undefined) {
|
|
2649
3899
|
throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
|
|
2650
3900
|
}
|
|
2651
|
-
const result = await
|
|
3901
|
+
const result = unwrap(await toolkitFor(repoRoot, scope).recordReviewBlockers({
|
|
2652
3902
|
goalId: required4(argv, "--goal-id"),
|
|
2653
3903
|
title: required4(argv, "--title"),
|
|
2654
3904
|
objective: required4(argv, "--objective"),
|
|
2655
3905
|
evidence: required4(argv, "--evidence"),
|
|
2656
3906
|
codexGoalJson
|
|
2657
|
-
}
|
|
3907
|
+
}));
|
|
2658
3908
|
if (json) {
|
|
2659
3909
|
printJson({
|
|
2660
3910
|
ok: true,
|
|
@@ -2662,6 +3912,8 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
|
|
|
2662
3912
|
blockedGoal: result.blockedGoal,
|
|
2663
3913
|
goal: result.newGoal,
|
|
2664
3914
|
ledgerEntries: result.ledgerEntries,
|
|
3915
|
+
nextActions: result.nextActions,
|
|
3916
|
+
warnings: result.warnings,
|
|
2665
3917
|
summary: summarizeUlwLoopPlan(result.plan)
|
|
2666
3918
|
});
|
|
2667
3919
|
} else {
|
|
@@ -2670,6 +3922,20 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
|
|
|
2670
3922
|
}
|
|
2671
3923
|
return 0;
|
|
2672
3924
|
}
|
|
3925
|
+
function toolkitFor(repoRoot, scope) {
|
|
3926
|
+
const sessionId = scope?.sessionId?.trim();
|
|
3927
|
+
if (sessionId === undefined || sessionId.length === 0) {
|
|
3928
|
+
throw new UlwLoopError("Missing --session-id.", "ULW_LOOP_SESSION_ID_REQUIRED", {
|
|
3929
|
+
details: { flag: "--session-id" }
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
return createAgentToolkit({ cwd: repoRoot, sessionId, surface: resolveToolkitSurface() });
|
|
3933
|
+
}
|
|
3934
|
+
function unwrap(response) {
|
|
3935
|
+
if (response.ok)
|
|
3936
|
+
return response.result;
|
|
3937
|
+
throw new UlwLoopError(response.error.message, response.error.code);
|
|
3938
|
+
}
|
|
2673
3939
|
function formatCriterionForCli(criterion) {
|
|
2674
3940
|
const marker = isEssentialCriterion(criterion) ? "essential" : "non-essential";
|
|
2675
3941
|
return `- ${criterion.id} [${criterion.status}] [${marker}] (${criterion.userModel}) ${criterion.scenario} evidence: ${criterion.capturedEvidence ?? "pending"}`;
|
|
@@ -2680,14 +3946,8 @@ function required4(argv, flag) {
|
|
|
2680
3946
|
return value;
|
|
2681
3947
|
throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
|
|
2682
3948
|
}
|
|
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
3949
|
|
|
2690
|
-
//
|
|
3950
|
+
// components/ulw-loop/src/cli-commands.ts
|
|
2691
3951
|
var ULW_LOOP_SUBCOMMANDS = [
|
|
2692
3952
|
"help",
|
|
2693
3953
|
"create-goals",
|
|
@@ -2710,7 +3970,6 @@ async function ulwLoopCommand(argv) {
|
|
|
2710
3970
|
const repoRoot = process.cwd();
|
|
2711
3971
|
const json = hasFlag(rest, "--json");
|
|
2712
3972
|
try {
|
|
2713
|
-
const scope = commandScope(rest);
|
|
2714
3973
|
if (!isUlwLoopSubcommand(command)) {
|
|
2715
3974
|
if (json) {
|
|
2716
3975
|
printJsonError(new UlwLoopError(`Unknown ulw-loop subcommand: ${command}.`, "ULW_LOOP_SUBCOMMAND_UNKNOWN", {
|
|
@@ -2722,11 +3981,18 @@ async function ulwLoopCommand(argv) {
|
|
|
2722
3981
|
`);
|
|
2723
3982
|
return 1;
|
|
2724
3983
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3984
|
+
if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
|
|
3985
|
+
process.stdout.write(`${subcommandHelp(command)}
|
|
3986
|
+
`);
|
|
3987
|
+
return 0;
|
|
3988
|
+
}
|
|
3989
|
+
if (command === "help") {
|
|
3990
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
2728
3991
|
`);
|
|
2729
|
-
|
|
3992
|
+
return 0;
|
|
3993
|
+
}
|
|
3994
|
+
const scope = commandScope(repoRoot, rest);
|
|
3995
|
+
switch (command) {
|
|
2730
3996
|
case "create-goals":
|
|
2731
3997
|
return await createGoals(repoRoot, rest, json, scope);
|
|
2732
3998
|
case "status":
|
|
@@ -2772,26 +4038,29 @@ var SESSION_ID_FLAG = "--session-id";
|
|
|
2772
4038
|
function sessionIdFlagPresent(argv) {
|
|
2773
4039
|
return hasFlag(argv, SESSION_ID_FLAG) || argv.some((arg) => arg.startsWith(`${SESSION_ID_FLAG}=`));
|
|
2774
4040
|
}
|
|
2775
|
-
function commandScope(argv) {
|
|
4041
|
+
function commandScope(repoRoot, argv) {
|
|
2776
4042
|
if (sessionIdFlagPresent(argv)) {
|
|
2777
|
-
const
|
|
2778
|
-
if (!
|
|
2779
|
-
throw new UlwLoopError(
|
|
4043
|
+
const sessionId = readValue(argv, SESSION_ID_FLAG)?.trim();
|
|
4044
|
+
if (!sessionId) {
|
|
4045
|
+
throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
|
|
2780
4046
|
details: { flag: SESSION_ID_FLAG }
|
|
2781
4047
|
});
|
|
2782
4048
|
}
|
|
2783
|
-
return { sessionId
|
|
4049
|
+
return { sessionId };
|
|
2784
4050
|
}
|
|
2785
4051
|
const sessionId = resolveUlwLoopSessionIdFromEnv();
|
|
2786
|
-
|
|
4052
|
+
if (sessionId !== null)
|
|
4053
|
+
return { sessionId };
|
|
4054
|
+
const existingSessionIds = listUlwLoopSessionIds(repoRoot);
|
|
4055
|
+
throw new UlwLoopError(sessionScopeRequiredMessage(SESSION_ID_FLAG, existingSessionIds), "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { flag: SESSION_ID_FLAG, existingSessionIds } });
|
|
2787
4056
|
}
|
|
2788
4057
|
|
|
2789
|
-
//
|
|
2790
|
-
import { readFileSync as
|
|
4058
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
4059
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2791
4060
|
|
|
2792
|
-
//
|
|
2793
|
-
import { existsSync as
|
|
2794
|
-
import { fileURLToPath } from "node:url";
|
|
4061
|
+
// components/ulw-loop/src/ultrawork-skill-pointer.ts
|
|
4062
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
4063
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2795
4064
|
var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
|
|
2796
4065
|
ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
|
|
2797
4066
|
|
|
@@ -2822,22 +4091,22 @@ Do not start the requested work until all three steps are complete.
|
|
|
2822
4091
|
`;
|
|
2823
4092
|
var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
|
|
2824
4093
|
var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
|
|
2825
|
-
var ULTRAWORK_DIRECTIVE =
|
|
4094
|
+
var ULTRAWORK_DIRECTIVE = readFileSync4(new URL("../directive.md", import.meta.url), "utf8");
|
|
2826
4095
|
function resolveUltraworkSkillFilePath() {
|
|
2827
|
-
return
|
|
4096
|
+
return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
|
|
2828
4097
|
}
|
|
2829
4098
|
function buildUltraworkSkillPointer(skillFilePath) {
|
|
2830
4099
|
return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
|
|
2831
4100
|
}
|
|
2832
4101
|
function buildUltraworkAdditionalContext(options = {}) {
|
|
2833
4102
|
const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
|
|
2834
|
-
if (skillFilePath !== null &&
|
|
4103
|
+
if (skillFilePath !== null && existsSync6(skillFilePath)) {
|
|
2835
4104
|
return buildUltraworkSkillPointer(skillFilePath);
|
|
2836
4105
|
}
|
|
2837
4106
|
return ULTRAWORK_DIRECTIVE;
|
|
2838
4107
|
}
|
|
2839
4108
|
|
|
2840
|
-
//
|
|
4109
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
2841
4110
|
var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
|
|
2842
4111
|
var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
|
|
2843
4112
|
var TRANSCRIPT_SEARCH_BYTES = 512000;
|
|
@@ -2885,7 +4154,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
|
|
|
2885
4154
|
return false;
|
|
2886
4155
|
}
|
|
2887
4156
|
function readTranscriptTail(transcriptPath) {
|
|
2888
|
-
const rawTranscript =
|
|
4157
|
+
const rawTranscript = readFileSync5(transcriptPath);
|
|
2889
4158
|
return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
|
|
2890
4159
|
}
|
|
2891
4160
|
function isUltraworkPrompt(prompt) {
|
|
@@ -2940,7 +4209,7 @@ function isRecord3(value) {
|
|
|
2940
4209
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2941
4210
|
}
|
|
2942
4211
|
|
|
2943
|
-
//
|
|
4212
|
+
// components/ulw-loop/src/codex-hook.ts
|
|
2944
4213
|
var CREATE_GOAL_TOOL_NAME = "create_goal";
|
|
2945
4214
|
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
4215
|
function parseUserPromptSubmitPayload(raw) {
|
|
@@ -3066,97 +4335,304 @@ function optionalString(value) {
|
|
|
3066
4335
|
return value === undefined || typeof value === "string";
|
|
3067
4336
|
}
|
|
3068
4337
|
function readAll(stdin) {
|
|
3069
|
-
return new Promise((
|
|
4338
|
+
return new Promise((resolve, reject) => {
|
|
3070
4339
|
let data = "";
|
|
3071
4340
|
stdin.setEncoding("utf8");
|
|
3072
4341
|
stdin.on("data", (chunk) => {
|
|
3073
4342
|
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
|
3074
4343
|
});
|
|
3075
4344
|
stdin.once("error", reject);
|
|
3076
|
-
stdin.once("end", () =>
|
|
4345
|
+
stdin.once("end", () => resolve(data));
|
|
3077
4346
|
});
|
|
3078
4347
|
}
|
|
3079
4348
|
|
|
3080
|
-
//
|
|
3081
|
-
import {
|
|
3082
|
-
import { join as
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
4349
|
+
// components/ulw-loop/src/spawn-guard.ts
|
|
4350
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
4351
|
+
import { join as join7 } from "node:path";
|
|
4352
|
+
|
|
4353
|
+
// components/ulw-loop/src/spawn-budget-io.ts
|
|
4354
|
+
import { randomBytes } from "node:crypto";
|
|
4355
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4356
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
4357
|
+
function readAdmissionBreaker(sessionId) {
|
|
4358
|
+
const dataDir = process.env["PLUGIN_DATA"];
|
|
4359
|
+
if (typeof dataDir !== "string")
|
|
4360
|
+
return null;
|
|
4361
|
+
try {
|
|
4362
|
+
const value = JSON.parse(readFileSync6(join6(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
|
|
4363
|
+
return typeof value === "object" && value !== null && "reason" in value && typeof value.reason === "string" ? value.reason : "capacity limit";
|
|
4364
|
+
} catch {
|
|
4365
|
+
return null;
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
function atomicWriteJson(targetPath, data) {
|
|
4369
|
+
const tmp = join6(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
|
|
4370
|
+
writeFileSync2(tmp, JSON.stringify(data));
|
|
4371
|
+
renameSync2(tmp, targetPath);
|
|
4372
|
+
}
|
|
4373
|
+
function isNonEmptyFile(path) {
|
|
4374
|
+
try {
|
|
4375
|
+
return existsSync7(path) && statSync3(path).size > 0;
|
|
4376
|
+
} catch (error) {
|
|
4377
|
+
if (error instanceof Error)
|
|
4378
|
+
return false;
|
|
4379
|
+
throw error;
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
function readCount(counterPath) {
|
|
4383
|
+
try {
|
|
4384
|
+
const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
|
|
4385
|
+
return typeof parsed === "object" && parsed !== null && "count" in parsed && typeof parsed.count === "number" && parsed.count >= 0 ? parsed.count : 0;
|
|
4386
|
+
} catch (error) {
|
|
4387
|
+
if (error instanceof Error)
|
|
4388
|
+
return 0;
|
|
4389
|
+
throw error;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
function readCounts(counterPath) {
|
|
4393
|
+
try {
|
|
4394
|
+
const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
|
|
4395
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
4396
|
+
return {};
|
|
4397
|
+
const counts = {};
|
|
4398
|
+
for (const [key, value] of Object.entries(parsed))
|
|
4399
|
+
if (typeof value === "number" && value >= 0)
|
|
4400
|
+
counts[key] = value;
|
|
4401
|
+
return counts;
|
|
4402
|
+
} catch (error) {
|
|
4403
|
+
if (error instanceof Error)
|
|
4404
|
+
return {};
|
|
4405
|
+
throw error;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
// components/ulw-loop/src/spawn-role-guard.ts
|
|
4410
|
+
var LAZYCODEX_SPAWN_ROLES = new Set([
|
|
4411
|
+
"explorer",
|
|
4412
|
+
"lazycodex-clone-fidelity-reviewer",
|
|
4413
|
+
"lazycodex-code-reviewer",
|
|
4414
|
+
"lazycodex-gate-reviewer",
|
|
4415
|
+
"lazycodex-qa-executor",
|
|
4416
|
+
"lazycodex-worker-high",
|
|
4417
|
+
"lazycodex-worker-low",
|
|
4418
|
+
"lazycodex-worker-medium",
|
|
4419
|
+
"librarian",
|
|
4420
|
+
"metis",
|
|
4421
|
+
"momus",
|
|
4422
|
+
"plan"
|
|
4423
|
+
]);
|
|
4424
|
+
function spawnRoleDenial(input) {
|
|
4425
|
+
const role = typeof input === "object" && input !== null && "agent_type" in input ? input.agent_type : undefined;
|
|
4426
|
+
if (typeof role === "string" && LAZYCODEX_SPAWN_ROLES.has(role))
|
|
4427
|
+
return null;
|
|
4428
|
+
return `LazyCodex requires an explicit registered agent_type: ${[...LAZYCODEX_SPAWN_ROLES].join(", ")}. Received ${JSON.stringify(role) ?? "no agent_type"}. Use the matching role and fork_turns: "none" (V2) or fork_context: false (V1), unless full history is deliberately required. The hook cannot see the tool schema; if agent_type is unavailable, stop and report incompatible role routing rather than spawning a generic agent. Describing a role in message does not select its TOML.`;
|
|
4429
|
+
}
|
|
4430
|
+
|
|
4431
|
+
// components/ulw-loop/src/spawn-guard.ts
|
|
4432
|
+
var SPAWN_TOOL_TOKENS = new Set([
|
|
4433
|
+
"spawn_agent",
|
|
4434
|
+
"multi_agent_v1.spawn_agent",
|
|
4435
|
+
"collaborationspawn_agent",
|
|
4436
|
+
"collaboration.spawn_agent"
|
|
4437
|
+
]);
|
|
4438
|
+
var DEFAULT_FANOUT_LIMIT = 24;
|
|
4439
|
+
var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
|
|
4440
|
+
var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
|
|
4441
|
+
var REVIEW_AGENT_TYPES = [
|
|
4442
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
|
|
4443
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
|
|
4444
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
|
|
4445
|
+
];
|
|
4446
|
+
var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
|
|
4447
|
+
function applySpawnGuards(payload, options = {}) {
|
|
4448
|
+
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
4449
|
+
return "";
|
|
4450
|
+
if (resolveToolkitSurface() === "lazycodex") {
|
|
4451
|
+
const reason = spawnRoleDenial(payload.tool_input);
|
|
4452
|
+
if (reason !== null)
|
|
4453
|
+
return deny(reason);
|
|
4454
|
+
}
|
|
4455
|
+
return applySpawnBudgetGuards(payload, options);
|
|
4456
|
+
}
|
|
4457
|
+
function applySpawnBudgetGuards(payload, options = {}) {
|
|
3087
4458
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
3088
4459
|
return "";
|
|
3089
|
-
const
|
|
3090
|
-
|
|
4460
|
+
const breaker = readAdmissionBreaker(payload.session_id);
|
|
4461
|
+
if (breaker !== null)
|
|
4462
|
+
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.`);
|
|
4463
|
+
const scope = { sessionId: payload.session_id };
|
|
4464
|
+
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
4465
|
+
const plan = readPlan(payload.cwd, payload.session_id);
|
|
3091
4466
|
if (plan === null)
|
|
3092
4467
|
return "";
|
|
4468
|
+
const lockOptions = options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs };
|
|
4469
|
+
try {
|
|
4470
|
+
return withStateLockSync(ulwLoopStateLockPath(payload.cwd, scope), () => evaluateGuards(payload, plan, stateDir), lockOptions);
|
|
4471
|
+
} catch (error) {
|
|
4472
|
+
if (isStateLockTimeout(error))
|
|
4473
|
+
return deny(`ulw-loop spawn guard could not take the session state lock: ${error.message}`);
|
|
4474
|
+
throw error;
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
function evaluateGuards(payload, plan, stateDir) {
|
|
4478
|
+
const fanOutPeek = peekFanOutBudget(stateDir);
|
|
4479
|
+
if (fanOutPeek !== null)
|
|
4480
|
+
return deny(fanOutPeek);
|
|
4481
|
+
const missingArtifact = missingGateArtifact(payload, plan);
|
|
4482
|
+
if (missingArtifact !== null)
|
|
4483
|
+
return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
|
|
4484
|
+
const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
|
|
4485
|
+
if (reviewDenial !== null)
|
|
4486
|
+
return deny(reviewDenial);
|
|
3093
4487
|
const fanOutDenial = consumeFanOutBudget(stateDir);
|
|
3094
4488
|
if (fanOutDenial !== null)
|
|
3095
4489
|
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
4490
|
return "";
|
|
3100
4491
|
}
|
|
4492
|
+
async function runSpawnAdmissionRecorderCli(stdin, stdout) {
|
|
4493
|
+
const chunks = [];
|
|
4494
|
+
for await (const chunk of stdin)
|
|
4495
|
+
chunks.push(Buffer.from(chunk));
|
|
4496
|
+
try {
|
|
4497
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4498
|
+
const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
|
|
4499
|
+
if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
|
|
4500
|
+
return;
|
|
4501
|
+
const dataDir = process.env["PLUGIN_DATA"];
|
|
4502
|
+
if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
|
|
4503
|
+
return;
|
|
4504
|
+
const markerDir = join7(dataDir, "spawn-breaker");
|
|
4505
|
+
try {
|
|
4506
|
+
mkdirSync3(markerDir, { recursive: true });
|
|
4507
|
+
atomicWriteJson(join7(markerDir, `${payload["session_id"]}.json`), {
|
|
4508
|
+
reason: response,
|
|
4509
|
+
at: new Date().toISOString()
|
|
4510
|
+
});
|
|
4511
|
+
} catch (error) {
|
|
4512
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4513
|
+
process.stderr.write(`[ulw-loop] spawn-guard: could not persist admission failure: ${message}
|
|
4514
|
+
`);
|
|
4515
|
+
}
|
|
4516
|
+
} catch {}
|
|
4517
|
+
}
|
|
3101
4518
|
async function runSpawnGuardCli(stdin, stdout) {
|
|
3102
4519
|
try {
|
|
3103
4520
|
const chunks = [];
|
|
3104
4521
|
for await (const chunk of stdin)
|
|
3105
4522
|
chunks.push(Buffer.from(chunk));
|
|
3106
4523
|
const payload = parsePreToolUsePayload(Buffer.concat(chunks).toString("utf8"));
|
|
3107
|
-
if (payload === null)
|
|
4524
|
+
if (payload === null) {
|
|
4525
|
+
stdout.write(deny("LazyCodex spawn guard received an invalid hook payload; role routing was not verified."));
|
|
3108
4526
|
return;
|
|
4527
|
+
}
|
|
3109
4528
|
const output = applySpawnGuards(payload);
|
|
3110
4529
|
if (output.length > 0)
|
|
3111
4530
|
stdout.write(output);
|
|
3112
4531
|
} catch (error) {
|
|
3113
|
-
|
|
3114
|
-
return;
|
|
4532
|
+
stdout.write(deny(`LazyCodex spawn guard failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
3115
4533
|
}
|
|
3116
4534
|
}
|
|
4535
|
+
function peekFanOutBudget(stateDir) {
|
|
4536
|
+
const counterPath = join7(stateDir, "spawn-count.json");
|
|
4537
|
+
const count = readCount(counterPath) + 1;
|
|
4538
|
+
const limit = fanOutLimit();
|
|
4539
|
+
if (count <= limit)
|
|
4540
|
+
return null;
|
|
4541
|
+
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.`;
|
|
4542
|
+
}
|
|
3117
4543
|
function consumeFanOutBudget(stateDir) {
|
|
3118
|
-
const counterPath =
|
|
4544
|
+
const counterPath = join7(stateDir, "spawn-count.json");
|
|
3119
4545
|
const count = readCount(counterPath) + 1;
|
|
3120
|
-
|
|
4546
|
+
atomicWriteJson(counterPath, { count });
|
|
3121
4547
|
const limit = fanOutLimit();
|
|
3122
4548
|
if (count <= limit)
|
|
3123
4549
|
return null;
|
|
3124
4550
|
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
4551
|
}
|
|
4552
|
+
function consumeReviewSpawnBudget(payload, plan, stateDir) {
|
|
4553
|
+
const agentType = reviewAgentType(payload.tool_input);
|
|
4554
|
+
if (agentType === null)
|
|
4555
|
+
return null;
|
|
4556
|
+
const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
4557
|
+
if (goal === undefined)
|
|
4558
|
+
return null;
|
|
4559
|
+
const counterPath = join7(stateDir, "review-spawn-counts.json");
|
|
4560
|
+
const limit = reviewSpawnLimit();
|
|
4561
|
+
const counts = readCounts(counterPath);
|
|
4562
|
+
const key = `${agentType}:${goal.id}:a${goal.attempt}`;
|
|
4563
|
+
const count = (counts[key] ?? 0) + 1;
|
|
4564
|
+
if (count > limit)
|
|
4565
|
+
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.`;
|
|
4566
|
+
counts[key] = count;
|
|
4567
|
+
atomicWriteJson(counterPath, counts);
|
|
4568
|
+
return null;
|
|
4569
|
+
}
|
|
3126
4570
|
function missingGateArtifact(payload, plan) {
|
|
3127
4571
|
if (!isGateReviewerSpawn(payload.tool_input))
|
|
3128
4572
|
return null;
|
|
3129
|
-
const
|
|
3130
|
-
if (
|
|
4573
|
+
const goal = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
4574
|
+
if (goal === undefined || goal.status === "complete")
|
|
3131
4575
|
return null;
|
|
3132
|
-
if (!
|
|
4576
|
+
if (!goal.successCriteria.every((criterion) => criterion.status === "pass"))
|
|
3133
4577
|
return null;
|
|
3134
4578
|
const scope = { sessionId: payload.session_id };
|
|
4579
|
+
const requiredArtifacts = [`${goal.id}-manual-qa.md`];
|
|
3135
4580
|
if (plan.evidenceLayoutVersion === 2) {
|
|
3136
|
-
const attemptDir = ulwLoopAttemptEvidenceDir(
|
|
3137
|
-
for (const name of
|
|
3138
|
-
const
|
|
3139
|
-
if (!isNonEmptyFile(
|
|
3140
|
-
return
|
|
4581
|
+
const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
|
|
4582
|
+
for (const name of requiredArtifacts) {
|
|
4583
|
+
const relative = `${attemptDir}/${name}`;
|
|
4584
|
+
if (!isNonEmptyFile(join7(payload.cwd, relative)))
|
|
4585
|
+
return relative;
|
|
3141
4586
|
}
|
|
3142
4587
|
return null;
|
|
3143
4588
|
}
|
|
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;
|
|
4589
|
+
const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
|
|
4590
|
+
return isNonEmptyFile(join7(payload.cwd, manualQa)) ? null : manualQa;
|
|
3150
4591
|
}
|
|
3151
4592
|
function isGateReviewerSpawn(toolInput) {
|
|
4593
|
+
const agentType = reviewAgentType(toolInput);
|
|
4594
|
+
return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
|
|
4595
|
+
}
|
|
4596
|
+
function reviewAgentType(toolInput) {
|
|
3152
4597
|
if (typeof toolInput !== "object" || toolInput === null)
|
|
3153
|
-
return
|
|
4598
|
+
return null;
|
|
3154
4599
|
const record = toolInput;
|
|
3155
4600
|
const agentType = record["agent_type"];
|
|
3156
|
-
if (typeof agentType === "string")
|
|
3157
|
-
|
|
4601
|
+
if (typeof agentType === "string") {
|
|
4602
|
+
if (!REVIEW_AGENT_TYPE_SET.has(agentType))
|
|
4603
|
+
return null;
|
|
4604
|
+
return activeSurfaceReviewerAlias(agentType);
|
|
4605
|
+
}
|
|
3158
4606
|
const message = record["message"];
|
|
3159
|
-
|
|
4607
|
+
if (typeof message !== "string")
|
|
4608
|
+
return null;
|
|
4609
|
+
const normalizedMessage = message.toLowerCase();
|
|
4610
|
+
const allRoleNames = [...REVIEW_AGENT_TYPES];
|
|
4611
|
+
const explicitAssignment = allRoleNames.map((name) => ({
|
|
4612
|
+
name,
|
|
4613
|
+
index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
|
|
4614
|
+
})).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
|
|
4615
|
+
if (explicitAssignment !== undefined)
|
|
4616
|
+
return activeSurfaceReviewerAlias(explicitAssignment.name);
|
|
4617
|
+
const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
|
|
4618
|
+
if (nonReviewerActAs)
|
|
4619
|
+
return null;
|
|
4620
|
+
const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
|
|
4621
|
+
if (namedReviewer !== undefined)
|
|
4622
|
+
return activeSurfaceReviewerAlias(namedReviewer);
|
|
4623
|
+
return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
|
|
4624
|
+
}
|
|
4625
|
+
function activeSurfaceReviewerAlias(reviewer) {
|
|
4626
|
+
const activeRoles = reviewerRolesFor(resolveToolkitSurface());
|
|
4627
|
+
for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
|
|
4628
|
+
if (reviewer === roles.codeReview)
|
|
4629
|
+
return activeRoles.codeReview;
|
|
4630
|
+
if (reviewer === roles.manualQa)
|
|
4631
|
+
return activeRoles.manualQa;
|
|
4632
|
+
if (reviewer === roles.gateReview)
|
|
4633
|
+
return activeRoles.gateReview;
|
|
4634
|
+
}
|
|
4635
|
+
return reviewer;
|
|
3160
4636
|
}
|
|
3161
4637
|
function deny(reason) {
|
|
3162
4638
|
return `${JSON.stringify({
|
|
@@ -3176,37 +4652,16 @@ function fanOutLimit() {
|
|
|
3176
4652
|
const parsed = Number.parseInt(raw, 10);
|
|
3177
4653
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
|
|
3178
4654
|
}
|
|
3179
|
-
function
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
throw error;
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
function hasOtherEvidenceFile(evidenceDir, excludedName) {
|
|
3189
|
-
try {
|
|
3190
|
-
return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join2(evidenceDir, name)));
|
|
3191
|
-
} catch (error) {
|
|
3192
|
-
if (error instanceof Error)
|
|
3193
|
-
return false;
|
|
3194
|
-
throw error;
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
function readCount(counterPath) {
|
|
3198
|
-
try {
|
|
3199
|
-
const parsed = JSON.parse(readFileSync3(counterPath, "utf8"));
|
|
3200
|
-
return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
|
|
3201
|
-
} catch (error) {
|
|
3202
|
-
if (error instanceof Error)
|
|
3203
|
-
return 0;
|
|
3204
|
-
throw error;
|
|
3205
|
-
}
|
|
4655
|
+
function reviewSpawnLimit() {
|
|
4656
|
+
const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
4657
|
+
if (raw === undefined)
|
|
4658
|
+
return DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
4659
|
+
const parsed = Number.parseInt(raw, 10);
|
|
4660
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
3206
4661
|
}
|
|
3207
|
-
function readPlan(
|
|
4662
|
+
function readPlan(repoRoot, sessionId) {
|
|
3208
4663
|
try {
|
|
3209
|
-
return
|
|
4664
|
+
return readUlwLoopPlanSync(repoRoot, { sessionId });
|
|
3210
4665
|
} catch (error) {
|
|
3211
4666
|
if (error instanceof Error)
|
|
3212
4667
|
return null;
|
|
@@ -3214,9 +4669,9 @@ function readPlan(goalsPath) {
|
|
|
3214
4669
|
}
|
|
3215
4670
|
}
|
|
3216
4671
|
|
|
3217
|
-
//
|
|
3218
|
-
import { existsSync as
|
|
3219
|
-
import { isAbsolute as
|
|
4672
|
+
// components/ulw-loop/src/stop-resume-hook.ts
|
|
4673
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4674
|
+
import { isAbsolute as isAbsolute3, join as join8, resolve as resolve5, sep as sep3 } from "node:path";
|
|
3220
4675
|
var RESUME_CAP = 2;
|
|
3221
4676
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
3222
4677
|
"context compacted",
|
|
@@ -3235,18 +4690,19 @@ function runStopResumeHook(input) {
|
|
|
3235
4690
|
return "";
|
|
3236
4691
|
if (boulderContinuationWillFire(payload.cwd, payload.session_id))
|
|
3237
4692
|
return "";
|
|
3238
|
-
const
|
|
3239
|
-
const
|
|
4693
|
+
const scope = { sessionId: payload.session_id };
|
|
4694
|
+
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
4695
|
+
const plan = readPlan2(payload.cwd, payload.session_id);
|
|
3240
4696
|
if (plan === null || plan.aggregateCompletion?.status === "complete")
|
|
3241
4697
|
return "";
|
|
3242
|
-
const
|
|
3243
|
-
if (
|
|
4698
|
+
const goal = resumableGoal(plan);
|
|
4699
|
+
if (goal === undefined)
|
|
3244
4700
|
return "";
|
|
3245
|
-
if (!
|
|
4701
|
+
if (!consumeResumeBudgetLocked(ulwLoopStateLockPath(payload.cwd, scope), stateDir, goal.id))
|
|
3246
4702
|
return "";
|
|
3247
4703
|
const output = {
|
|
3248
4704
|
decision: "block",
|
|
3249
|
-
reason: renderResumeDirective(plan,
|
|
4705
|
+
reason: renderResumeDirective(plan, goal, payload.session_id)
|
|
3250
4706
|
};
|
|
3251
4707
|
return JSON.stringify(output);
|
|
3252
4708
|
}
|
|
@@ -3264,38 +4720,47 @@ async function runStopResumeHookCli(stdin, stdout) {
|
|
|
3264
4720
|
}
|
|
3265
4721
|
}
|
|
3266
4722
|
function resumableGoal(plan) {
|
|
3267
|
-
const active = plan.goals.find((
|
|
4723
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
3268
4724
|
if (active !== undefined && isResumableStatus(active.status))
|
|
3269
4725
|
return active;
|
|
3270
|
-
return plan.goals.find((
|
|
4726
|
+
return plan.goals.find((goal) => isResumableStatus(goal.status));
|
|
4727
|
+
}
|
|
4728
|
+
function isResumableStatus(status) {
|
|
4729
|
+
return status === "pending" || status === "in_progress";
|
|
3271
4730
|
}
|
|
3272
|
-
function
|
|
3273
|
-
|
|
4731
|
+
function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
|
|
4732
|
+
try {
|
|
4733
|
+
return withStateLockSync(lockPath, () => consumeResumeBudget(stateDir, goalId));
|
|
4734
|
+
} catch (error) {
|
|
4735
|
+
if (isStateLockTimeout(error))
|
|
4736
|
+
return false;
|
|
4737
|
+
throw error;
|
|
4738
|
+
}
|
|
3274
4739
|
}
|
|
3275
4740
|
function consumeResumeBudget(stateDir, goalId) {
|
|
3276
|
-
const ledgerLineCount =
|
|
3277
|
-
const counterPath =
|
|
3278
|
-
const stuckPath =
|
|
4741
|
+
const ledgerLineCount = readLedgerAt(stateDir).length;
|
|
4742
|
+
const counterPath = resolve5(stateDir, `auto-resume-${goalId}.json`);
|
|
4743
|
+
const stuckPath = resolve5(stateDir, `auto-resume-${goalId}.stuck`);
|
|
3279
4744
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
3280
4745
|
return false;
|
|
3281
4746
|
const previous = readCounter(counterPath);
|
|
3282
4747
|
const count = previous !== null && previous.ledgerLineCount === ledgerLineCount ? previous.count : 0;
|
|
3283
4748
|
if (count >= RESUME_CAP) {
|
|
3284
|
-
|
|
4749
|
+
writeFileSync3(stuckPath, `no ledger progress after ${count} resumes
|
|
3285
4750
|
`);
|
|
3286
4751
|
return false;
|
|
3287
4752
|
}
|
|
3288
|
-
|
|
4753
|
+
writeFileSync3(counterPath, JSON.stringify({ count: count + 1, ledgerLineCount }));
|
|
3289
4754
|
return true;
|
|
3290
4755
|
}
|
|
3291
4756
|
function isInsideDir(dir, candidate) {
|
|
3292
|
-
return candidate.startsWith(
|
|
4757
|
+
return candidate.startsWith(resolve5(dir) + sep3);
|
|
3293
4758
|
}
|
|
3294
|
-
function renderResumeDirective(plan,
|
|
4759
|
+
function renderResumeDirective(plan, goal, sessionId) {
|
|
3295
4760
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|
|
3296
4761
|
const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
|
|
3297
4762
|
return [
|
|
3298
|
-
`The ulw-loop run in this session still has unfinished goals (next: ${
|
|
4763
|
+
`The ulw-loop run in this session still has unfinished goals (next: ${goal.id} — ${goal.title}).`,
|
|
3299
4764
|
"The turn ended before the loop completed. Resume it now:",
|
|
3300
4765
|
`1. Run \`omo-agent-toolkit ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
|
|
3301
4766
|
"2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
|
|
@@ -3304,30 +4769,20 @@ function renderResumeDirective(plan, goal3, sessionId) {
|
|
|
3304
4769
|
].join(`
|
|
3305
4770
|
`);
|
|
3306
4771
|
}
|
|
3307
|
-
function readPlan2(
|
|
4772
|
+
function readPlan2(repoRoot, sessionId) {
|
|
3308
4773
|
try {
|
|
3309
|
-
return
|
|
4774
|
+
return readUlwLoopPlanSync(repoRoot, { sessionId });
|
|
3310
4775
|
} catch (error) {
|
|
3311
4776
|
if (error instanceof Error)
|
|
3312
4777
|
return null;
|
|
3313
4778
|
throw error;
|
|
3314
4779
|
}
|
|
3315
4780
|
}
|
|
3316
|
-
function countLedgerLines(ledgerPath) {
|
|
3317
|
-
try {
|
|
3318
|
-
return readFileSync4(ledgerPath, "utf8").split(`
|
|
3319
|
-
`).filter(Boolean).length;
|
|
3320
|
-
} catch (error) {
|
|
3321
|
-
if (error instanceof Error)
|
|
3322
|
-
return 0;
|
|
3323
|
-
throw error;
|
|
3324
|
-
}
|
|
3325
|
-
}
|
|
3326
4781
|
function readCounter(counterPath) {
|
|
3327
4782
|
try {
|
|
3328
|
-
if (!
|
|
4783
|
+
if (!existsSync8(counterPath))
|
|
3329
4784
|
return null;
|
|
3330
|
-
const parsed = JSON.parse(
|
|
4785
|
+
const parsed = JSON.parse(readFileSync7(counterPath, "utf8"));
|
|
3331
4786
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
3332
4787
|
return null;
|
|
3333
4788
|
return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
|
|
@@ -3339,7 +4794,7 @@ function readCounter(counterPath) {
|
|
|
3339
4794
|
}
|
|
3340
4795
|
function boulderContinuationWillFire(cwd, sessionId) {
|
|
3341
4796
|
try {
|
|
3342
|
-
const raw = JSON.parse(
|
|
4797
|
+
const raw = JSON.parse(readFileSync7(join8(cwd, ".omo", "boulder.json"), "utf8"));
|
|
3343
4798
|
const works = raw["works"];
|
|
3344
4799
|
const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
|
|
3345
4800
|
return entries.some((work) => {
|
|
@@ -3358,7 +4813,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
|
|
|
3358
4813
|
}
|
|
3359
4814
|
function transcriptShowsContextPressure(transcriptPath) {
|
|
3360
4815
|
try {
|
|
3361
|
-
const transcript =
|
|
4816
|
+
const transcript = readFileSync7(transcriptPath, "utf8").toLowerCase();
|
|
3362
4817
|
return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
|
|
3363
4818
|
} catch (error) {
|
|
3364
4819
|
if (error instanceof Error)
|
|
@@ -3370,12 +4825,12 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
3370
4825
|
const activePlan = entry["active_plan"];
|
|
3371
4826
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
3372
4827
|
return false;
|
|
3373
|
-
const planPath =
|
|
4828
|
+
const planPath = isAbsolute3(activePlan) ? activePlan : join8(cwd, activePlan);
|
|
3374
4829
|
const worktree = entry["worktree_path"];
|
|
3375
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !
|
|
4830
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute3(activePlan) ? [join8(isAbsolute3(worktree) ? worktree : join8(cwd, worktree), activePlan), planPath] : [planPath];
|
|
3376
4831
|
for (const candidate of candidates) {
|
|
3377
4832
|
try {
|
|
3378
|
-
return
|
|
4833
|
+
return readFileSync7(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|
|
3379
4834
|
} catch (error) {
|
|
3380
4835
|
if (!(error instanceof Error))
|
|
3381
4836
|
throw error;
|
|
@@ -3399,19 +4854,13 @@ function parseStopPayload(value) {
|
|
|
3399
4854
|
};
|
|
3400
4855
|
}
|
|
3401
4856
|
|
|
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
|
-
`;
|
|
4857
|
+
// components/ulw-loop/src/cli.ts
|
|
3410
4858
|
async function main() {
|
|
3411
4859
|
const argv = process.argv.slice(2);
|
|
3412
4860
|
const command = argv[0];
|
|
3413
4861
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
3414
|
-
process.stdout.write(
|
|
4862
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
4863
|
+
`);
|
|
3415
4864
|
return 0;
|
|
3416
4865
|
}
|
|
3417
4866
|
if (command === "ulw-loop")
|
|
@@ -3432,6 +4881,10 @@ async function main() {
|
|
|
3432
4881
|
await runStopResumeHookCli(process.stdin, process.stdout);
|
|
3433
4882
|
return 0;
|
|
3434
4883
|
}
|
|
4884
|
+
if (sub === "post-tool-use-spawn") {
|
|
4885
|
+
await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
|
|
4886
|
+
return 0;
|
|
4887
|
+
}
|
|
3435
4888
|
if (sub === "pre-tool-use-spawn") {
|
|
3436
4889
|
await runSpawnGuardCli(process.stdin, process.stdout);
|
|
3437
4890
|
return 0;
|
|
@@ -3443,7 +4896,8 @@ async function main() {
|
|
|
3443
4896
|
if (isUlwLoopSubcommand(command))
|
|
3444
4897
|
return ulwLoopCommand(argv);
|
|
3445
4898
|
process.stderr.write(`[omo] unknown command: ${command}
|
|
3446
|
-
${
|
|
4899
|
+
${ULW_LOOP_HELP}
|
|
4900
|
+
`);
|
|
3447
4901
|
return 1;
|
|
3448
4902
|
}
|
|
3449
4903
|
main().then((code) => {
|