oh-my-opencode 5.0.0-beta.30 → 5.0.0-beta.32
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/dist/cli/index.js +178 -37
- package/dist/cli-node/index.js +178 -37
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
- package/dist/features/background-agent/constants.d.ts +0 -7
- package/dist/features/background-agent/manager.d.ts +2 -1
- package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
- package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
- package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
- package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
- package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
- package/dist/features/monitor/manager-internals.d.ts +1 -1
- package/dist/features/monitor/output-injector-types.d.ts +1 -1
- package/dist/features/task-toast-manager/manager.d.ts +0 -24
- package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +1 -0
- package/dist/features/team-mode/tools/messaging-runtime.d.ts +1 -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/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/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/keyword-detector/hook.d.ts +7 -0
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
- package/dist/hooks/model-fallback/hook.d.ts +1 -1
- package/dist/index.js +681 -273
- package/dist/oh-my-opencode.schema.json +5 -0
- package/dist/plugin-dispose.d.ts +3 -0
- package/dist/shared/agent-tool-restrictions.d.ts +0 -1
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
- package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +1 -0
- package/dist/tools/session-manager/directory-filter.d.ts +2 -0
- package/dist/tui.js +148 -9
- package/package.json +16 -15
- package/packages/git-bash-mcp/dist/cli.js +1 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +47 -19
- package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
- package/packages/lsp-core/src/lsp/client.ts +6 -3
- package/packages/lsp-core/src/lsp/constants.ts +5 -0
- package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
- package/packages/lsp-core/src/lsp/errors.ts +15 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
- package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
- package/packages/lsp-core/src/lsp/manager.ts +106 -13
- 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 +7 -2
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
- package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
- package/packages/lsp-core/src/mcp.ts +3 -0
- package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
- package/packages/lsp-daemon/dist/cli.js +155 -34
- package/packages/lsp-daemon/dist/client.js +155 -34
- package/packages/lsp-daemon/dist/index.js +155 -34
- package/packages/lsp-daemon/dist/proxy.js +3 -0
- package/packages/lsp-tools-mcp/dist/cli.js +155 -34
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +134 -28
- package/packages/lsp-tools-mcp/dist/mcp.js +155 -34
- package/packages/lsp-tools-mcp/dist/tools.js +154 -34
- package/packages/lsp-tools-mcp/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- 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/codegraph/dist/cli.js +17 -2
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +17 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +152 -34
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +5 -4
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +489 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -2
- package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
- 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/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/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +5 -2
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +14 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +10 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +9 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +856 -347
- 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 +27 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +14 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +17 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +53 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +85 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +1 -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 +41 -102
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +185 -14
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +65 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +17 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +25 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +127 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +10 -12
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +34 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +23 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +18 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +27 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +57 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +103 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +54 -123
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +187 -12
- package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +95 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +107 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +92 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +183 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +35 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +101 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +46 -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.test.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +318 -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 +9 -3
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +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 +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +31 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +17 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +11 -1
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +13 -0
- package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- 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/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/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/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
- 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/ultrawork/directive.md +0 -483
- package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +0 -490
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { validateQualityGate } from "../src/quality-gate.js";
|
|
4
|
+
import { UlwLoopError } from "../src/types.js";
|
|
5
|
+
|
|
6
|
+
const opts = { repoRoot: process.cwd(), fs: { existsSync, statSync }, reviewerSurface: "omo-senpi" as const };
|
|
7
|
+
|
|
8
|
+
describe("aggregated quality gate validation", () => {
|
|
9
|
+
it("#given four independent defects #when validating #then reports all defects in one error", () => {
|
|
10
|
+
const gate = {
|
|
11
|
+
manualQa: {
|
|
12
|
+
by: "main-session",
|
|
13
|
+
status: "passed",
|
|
14
|
+
evidence: "placeholder",
|
|
15
|
+
surfaceEvidence: [],
|
|
16
|
+
adversarialCases: [],
|
|
17
|
+
artifactRefs: [
|
|
18
|
+
{ id: "missing", kind: "cli-transcript", description: "artifact", path: "does-not-exist.txt" },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
gateReview: {
|
|
22
|
+
by: "category:deep",
|
|
23
|
+
recommendation: "REJECT",
|
|
24
|
+
reportPath: "test/fixtures/artifacts/gate-review.md",
|
|
25
|
+
blockers: [],
|
|
26
|
+
},
|
|
27
|
+
iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
|
|
28
|
+
criteriaCoverage: {
|
|
29
|
+
totalCriteria: 1,
|
|
30
|
+
passCount: 1,
|
|
31
|
+
originalIntent: "intent",
|
|
32
|
+
desiredOutcome: "outcome",
|
|
33
|
+
userOutcomeReview: "review",
|
|
34
|
+
adversarialClassesCovered: ["none"],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
validateQualityGate(gate, opts);
|
|
39
|
+
throw new Error("expected validation failure");
|
|
40
|
+
} catch (error) {
|
|
41
|
+
expect(error).toBeInstanceOf(UlwLoopError);
|
|
42
|
+
if (!(error instanceof UlwLoopError)) throw error;
|
|
43
|
+
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
|
|
44
|
+
expect(error.details?.["field"]).toBe("manualQa.evidence");
|
|
45
|
+
expect(error.details?.["fields"]).toEqual(
|
|
46
|
+
expect.arrayContaining([
|
|
47
|
+
{ field: "manualQa.evidence", message: expect.stringContaining("placeholder") },
|
|
48
|
+
{ field: "gateReview.evidence", message: expect.stringContaining("non-empty") },
|
|
49
|
+
{ field: "gateReview.recommendation", message: expect.stringContaining("APPROVE") },
|
|
50
|
+
{ field: "manualQa.artifactRefs[0].path", message: expect.stringContaining("existing") },
|
|
51
|
+
]),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("#given more than twenty-five defects #when validating #then caps the list and flags truncation", () => {
|
|
57
|
+
const refs = Array.from({ length: 30 }, (_, index) => ({
|
|
58
|
+
id: `ref-${index}`,
|
|
59
|
+
kind: "cli-transcript",
|
|
60
|
+
description: "artifact",
|
|
61
|
+
path: `does-not-exist-${index}.txt`,
|
|
62
|
+
}));
|
|
63
|
+
const gate = {
|
|
64
|
+
manualQa: {
|
|
65
|
+
by: "main-session",
|
|
66
|
+
status: "passed",
|
|
67
|
+
evidence: "real evidence",
|
|
68
|
+
surfaceEvidence: [],
|
|
69
|
+
adversarialCases: [],
|
|
70
|
+
artifactRefs: refs,
|
|
71
|
+
},
|
|
72
|
+
gateReview: {
|
|
73
|
+
by: "category:deep",
|
|
74
|
+
recommendation: "APPROVE",
|
|
75
|
+
reportPath: "test/fixtures/artifacts/gate-review.md",
|
|
76
|
+
evidence: "gate evidence",
|
|
77
|
+
blockers: [],
|
|
78
|
+
},
|
|
79
|
+
iteration: { fullRerun: true, status: "passed", rerunCommands: ["test"], evidence: "rerun" },
|
|
80
|
+
criteriaCoverage: {
|
|
81
|
+
totalCriteria: 1,
|
|
82
|
+
passCount: 1,
|
|
83
|
+
originalIntent: "intent",
|
|
84
|
+
desiredOutcome: "outcome",
|
|
85
|
+
userOutcomeReview: "review",
|
|
86
|
+
adversarialClassesCovered: ["none"],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
validateQualityGate(gate, opts);
|
|
91
|
+
throw new Error("expected validation failure");
|
|
92
|
+
} catch (error) {
|
|
93
|
+
expect(error).toBeInstanceOf(UlwLoopError);
|
|
94
|
+
if (!(error instanceof UlwLoopError)) throw error;
|
|
95
|
+
const fields = error.details?.["fields"];
|
|
96
|
+
expect(Array.isArray(fields) && fields.length).toBe(25);
|
|
97
|
+
expect(error.details?.["truncated"]).toBe(true);
|
|
98
|
+
expect(error.message).toContain("25+");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -75,9 +75,9 @@ function gateWith(overrides: Record<string, unknown>): Record<string, unknown> {
|
|
|
75
75
|
return { ...BASE_GATE, ...overrides };
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function qualityGateError(input: unknown): UlwLoopError {
|
|
78
|
+
function qualityGateError(input: unknown, surface?: "lazycodex" | "omo-senpi"): UlwLoopError {
|
|
79
79
|
try {
|
|
80
|
-
validateQualityGate(input);
|
|
80
|
+
validateQualityGate(input, surface === undefined ? undefined : { reviewerSurface: surface });
|
|
81
81
|
} catch (error) {
|
|
82
82
|
if (error instanceof UlwLoopError) return error;
|
|
83
83
|
throw error;
|
|
@@ -85,6 +85,22 @@ function qualityGateError(input: unknown): UlwLoopError {
|
|
|
85
85
|
throw new Error("Expected UlwLoopError");
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function senpiGate(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
89
|
+
const { codeReview: _codeReview, ...withoutCodeReview } = BASE_GATE;
|
|
90
|
+
return {
|
|
91
|
+
...withoutCodeReview,
|
|
92
|
+
manualQa: { ...BASE_GATE.manualQa, by: "main-session" },
|
|
93
|
+
gateReview: { ...BASE_GATE.gateReview, by: "category:deep" },
|
|
94
|
+
criteriaCoverage: {
|
|
95
|
+
...BASE_GATE.criteriaCoverage,
|
|
96
|
+
originalIntent: "User wanted the senpi surface to accept honest reviewer attribution.",
|
|
97
|
+
desiredOutcome: "A gate naming the omo-senpi reviewers validates on the omo-senpi surface.",
|
|
98
|
+
userOutcomeReview: "The senpi-surface gate passed with truthful reviewer identities.",
|
|
99
|
+
},
|
|
100
|
+
...overrides,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
describe("validateQualityGate reviewer roles", () => {
|
|
89
105
|
it("#given arbitrary reviewer role names #when validated #then each role field is rejected", () => {
|
|
90
106
|
const cases = [
|
|
@@ -106,6 +122,34 @@ describe("validateQualityGate reviewer roles", () => {
|
|
|
106
122
|
}
|
|
107
123
|
});
|
|
108
124
|
|
|
125
|
+
it("#given omo-senpi reviewer identities #when validated on the omo-senpi surface #then the gate is accepted", () => {
|
|
126
|
+
expect(() => validateQualityGate(senpiGate(), { reviewerSurface: "omo-senpi" })).not.toThrow();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("#given a codeReview lane #when validated on the omo-senpi surface #then it is rejected", () => {
|
|
130
|
+
const error = qualityGateError(gateWith({}), "omo-senpi");
|
|
131
|
+
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
|
|
132
|
+
expect(error.message).toContain("no codeReview lane");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("#given a four-section senpi gate #when validated on the default lazycodex surface #then codeReview is required", () => {
|
|
136
|
+
const error = qualityGateError(senpiGate());
|
|
137
|
+
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
|
|
138
|
+
expect(error.message).toContain("codeReview");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("#given swapped omo-senpi reviewer roles #when validated on the omo-senpi surface #then section-specific roles are enforced", () => {
|
|
142
|
+
const swapped = senpiGate({
|
|
143
|
+
manualQa: { ...BASE_GATE.manualQa, by: "category:deep" },
|
|
144
|
+
gateReview: { ...BASE_GATE.gateReview, by: "main-session" },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const error = qualityGateError(swapped, "omo-senpi");
|
|
148
|
+
|
|
149
|
+
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
|
|
150
|
+
expect(error.message).toContain("manualQa.by");
|
|
151
|
+
});
|
|
152
|
+
|
|
109
153
|
it("#given swapped LazyCodex reviewer roles #when validated #then section-specific roles are enforced", () => {
|
|
110
154
|
const cases = [
|
|
111
155
|
{
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { validateQualityGate } from "../src/quality-gate.js";
|
|
6
|
+
import { UlwLoopError } from "../src/types.js";
|
|
7
|
+
|
|
8
|
+
const COMMON = {
|
|
9
|
+
manualQa: {
|
|
10
|
+
status: "passed",
|
|
11
|
+
evidence: "Ran senpi manual QA with artifact-backed evidence.",
|
|
12
|
+
surfaceEvidence: [
|
|
13
|
+
{
|
|
14
|
+
id: "surface-cli-pass",
|
|
15
|
+
criterionRef: "C1",
|
|
16
|
+
surface: "cli",
|
|
17
|
+
invocation: "omo-agent-toolkit ulw-loop checkpoint --status complete",
|
|
18
|
+
verdict: "passed",
|
|
19
|
+
artifactRefs: ["artifact-cli-pass"],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
adversarialCases: [
|
|
23
|
+
{
|
|
24
|
+
id: "adv-role",
|
|
25
|
+
criterionRef: "C2",
|
|
26
|
+
scenario: "a reviewer role is invalid",
|
|
27
|
+
expectedBehavior: "the gate rejects the invalid role",
|
|
28
|
+
verdict: "passed",
|
|
29
|
+
artifactRefs: ["artifact-cli-pass"],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
artifactRefs: [
|
|
33
|
+
{
|
|
34
|
+
id: "artifact-cli-pass",
|
|
35
|
+
kind: "cli-transcript",
|
|
36
|
+
description: "CLI transcript for valid checkpoint.",
|
|
37
|
+
path: "cli-pass.txt",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
iteration: {
|
|
42
|
+
fullRerun: true,
|
|
43
|
+
status: "passed",
|
|
44
|
+
rerunCommands: ["bunx vitest run"],
|
|
45
|
+
evidence: "Focused tests passed.",
|
|
46
|
+
},
|
|
47
|
+
criteriaCoverage: {
|
|
48
|
+
totalCriteria: 2,
|
|
49
|
+
passCount: 2,
|
|
50
|
+
originalIntent: "User wanted a valid senpi quality gate.",
|
|
51
|
+
desiredOutcome: "The senpi gate accepts its four required lanes.",
|
|
52
|
+
userOutcomeReview: "The requested senpi outcome is covered.",
|
|
53
|
+
adversarialClassesCovered: ["role_mismatch"],
|
|
54
|
+
},
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
function senpiGate(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
58
|
+
return {
|
|
59
|
+
...COMMON,
|
|
60
|
+
manualQa: { ...COMMON.manualQa, by: "main-session" },
|
|
61
|
+
gateReview: {
|
|
62
|
+
by: "category:deep",
|
|
63
|
+
recommendation: "APPROVE",
|
|
64
|
+
reportPath: "gate-review.md",
|
|
65
|
+
evidence: "Verified the senpi gate.",
|
|
66
|
+
blockers: [],
|
|
67
|
+
},
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function section(input: Record<string, unknown>, key: string): Record<string, unknown> {
|
|
73
|
+
const value = input[key];
|
|
74
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
75
|
+
throw new Error(`missing object section: ${key}`);
|
|
76
|
+
return value as Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function errorFor(input: unknown): UlwLoopError {
|
|
80
|
+
try {
|
|
81
|
+
validateQualityGate(input, { reviewerSurface: "omo-senpi" });
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error instanceof UlwLoopError) return error;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Expected quality gate validation to fail");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("validateQualityGate omo-senpi surface", () => {
|
|
90
|
+
it("#given a four-section senpi gate #when validated #then it accepts main-session manual QA and category:deep review", () => {
|
|
91
|
+
expect(() => validateQualityGate(senpiGate(), { reviewerSurface: "omo-senpi" })).not.toThrow();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("#given a senpi gate with codeReview #when validated #then it rejects the forbidden codeReview lane", () => {
|
|
95
|
+
const error = errorFor(senpiGate({ codeReview: { by: "anything" } }));
|
|
96
|
+
expect(error.details?.["field"]).toContain("codeReview");
|
|
97
|
+
expect(error.message).toContain("omo-senpi gate has no codeReview lane");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it.each(["category:deep", "category:unspecified-high", "category:unspecified-low"])(
|
|
101
|
+
"#given gateReview.by %s #when validated on senpi #then it accepts the category role",
|
|
102
|
+
(by) => {
|
|
103
|
+
expect(() =>
|
|
104
|
+
validateQualityGate(senpiGate({ gateReview: { ...section(senpiGate(), "gateReview"), by } }), {
|
|
105
|
+
reviewerSurface: "omo-senpi",
|
|
106
|
+
}),
|
|
107
|
+
).not.toThrow();
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it.each(["omo-senpi-gate-reviewer", "lazycodex-gate-reviewer", "arbitrary"])(
|
|
112
|
+
"#given gateReview.by %s #when validated on senpi #then it rejects the non-category role",
|
|
113
|
+
(by) => {
|
|
114
|
+
const error = errorFor(senpiGate({ gateReview: { ...section(senpiGate(), "gateReview"), by } }));
|
|
115
|
+
expect(error.message).toContain("gateReview.by");
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
it.each(["omo-senpi-qa-executor", "lazycodex-qa-executor", "arbitrary"])(
|
|
120
|
+
"#given manualQa.by %s #when validated on senpi #then it rejects the non-main-session role",
|
|
121
|
+
(by) => {
|
|
122
|
+
const error = errorFor(senpiGate({ manualQa: { ...COMMON.manualQa, by } }));
|
|
123
|
+
expect(error.message).toContain("manualQa.by");
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
it("#given the existing lazycodex fixture #when validated on the default surface #then it remains valid", async () => {
|
|
128
|
+
const raw = await readFile(new URL("./fixtures/sample-quality-gate.json", import.meta.url), "utf8");
|
|
129
|
+
expect(() => validateQualityGate(JSON.parse(raw))).not.toThrow();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -111,7 +111,10 @@ describe("validateQualityGate", () => {
|
|
|
111
111
|
"gateReview",
|
|
112
112
|
"iteration",
|
|
113
113
|
"manualQa",
|
|
114
|
+
"surface",
|
|
114
115
|
]);
|
|
116
|
+
expect("codeReview" in gate).toBe(true);
|
|
117
|
+
if (!("codeReview" in gate)) throw new Error("expected lazycodex codeReview section");
|
|
115
118
|
expect(gate.codeReview.codeQualityStatus).toBe("CLEAR");
|
|
116
119
|
expect(gate).toMatchObject({
|
|
117
120
|
criteriaCoverage: { totalCriteria: 9, passCount: 9, userOutcomeReview: expect.stringContaining("user") },
|
|
@@ -127,6 +130,8 @@ describe("validateQualityGate", () => {
|
|
|
127
130
|
const gate = validateQualityGate(parsed, FS_OPTS);
|
|
128
131
|
|
|
129
132
|
// then
|
|
133
|
+
expect("codeReview" in gate).toBe(true);
|
|
134
|
+
if (!("codeReview" in gate)) throw new Error("expected lazycodex codeReview section");
|
|
130
135
|
expect(gate.codeReview.recommendation).toBe("APPROVE");
|
|
131
136
|
expect(gate.manualQa.artifactRefs).toHaveLength(5);
|
|
132
137
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -9,16 +9,27 @@ import { applySpawnGuards } from "../src/spawn-guard.ts";
|
|
|
9
9
|
|
|
10
10
|
let workDir: string;
|
|
11
11
|
let originalLimit: string | undefined;
|
|
12
|
+
let originalReviewLimit: string | undefined;
|
|
13
|
+
let originalToolkitSurface: string | undefined;
|
|
12
14
|
|
|
13
15
|
beforeEach(async () => {
|
|
14
16
|
workDir = await mkdtemp(join(tmpdir(), "ulw-spawn-guard-"));
|
|
15
17
|
originalLimit = process.env["OMO_SPAWN_FANOUT_LIMIT"];
|
|
18
|
+
originalReviewLimit = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
19
|
+
originalToolkitSurface = process.env["OMO_AGENT_TOOLKIT_SURFACE"];
|
|
16
20
|
delete process.env["OMO_SPAWN_FANOUT_LIMIT"];
|
|
21
|
+
delete process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
22
|
+
delete process.env["OMO_AGENT_TOOLKIT_SURFACE"];
|
|
17
23
|
});
|
|
18
24
|
|
|
19
25
|
afterEach(async () => {
|
|
26
|
+
delete process.env["OMO_AGENT_TOOLKIT_SURFACE"];
|
|
20
27
|
if (originalLimit === undefined) delete process.env["OMO_SPAWN_FANOUT_LIMIT"];
|
|
21
28
|
else process.env["OMO_SPAWN_FANOUT_LIMIT"] = originalLimit;
|
|
29
|
+
if (originalReviewLimit === undefined) delete process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
30
|
+
else process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"] = originalReviewLimit;
|
|
31
|
+
if (originalToolkitSurface === undefined) delete process.env["OMO_AGENT_TOOLKIT_SURFACE"];
|
|
32
|
+
else process.env["OMO_AGENT_TOOLKIT_SURFACE"] = originalToolkitSurface;
|
|
22
33
|
await rm(workDir, { recursive: true, force: true });
|
|
23
34
|
});
|
|
24
35
|
|
|
@@ -132,7 +143,118 @@ describe("applySpawnGuards fan-out cap", () => {
|
|
|
132
143
|
});
|
|
133
144
|
});
|
|
134
145
|
|
|
146
|
+
describe("applySpawnGuards fan-out cap + reviewer quota interaction", () => {
|
|
147
|
+
it("#given fan-out cap exhausted #when a reviewer spawns #then denies without charging reviewer quota", () => {
|
|
148
|
+
writeGoals();
|
|
149
|
+
process.env["OMO_SPAWN_FANOUT_LIMIT"] = "3";
|
|
150
|
+
|
|
151
|
+
// exhaust the fan-out cap with non-reviewer spawns
|
|
152
|
+
expect(applySpawnGuards(payload("spawn_agent", { message: "scan" }))).toBe("");
|
|
153
|
+
expect(applySpawnGuards(payload("spawn_agent", { message: "scan" }))).toBe("");
|
|
154
|
+
expect(applySpawnGuards(payload("spawn_agent", { message: "scan" }))).toBe("");
|
|
155
|
+
|
|
156
|
+
// reviewer spawn while cap is exhausted — must deny without incrementing reviewer count
|
|
157
|
+
const reviewSpawn = payload("spawn_agent", {
|
|
158
|
+
agent_type: "lazycodex-code-reviewer",
|
|
159
|
+
message: "review the current diff",
|
|
160
|
+
});
|
|
161
|
+
const result = applySpawnGuards(reviewSpawn);
|
|
162
|
+
expect(result).not.toBe("");
|
|
163
|
+
expect(deny(result).permissionDecisionReason).toContain("4/3");
|
|
164
|
+
|
|
165
|
+
// reviewer quota must be untouched — a subsequent spawn after raising the limit must succeed
|
|
166
|
+
process.env["OMO_SPAWN_FANOUT_LIMIT"] = "10";
|
|
167
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
168
|
+
const counters = JSON.parse(readFileSync(join(sessionDir(), "review-spawn-counts.json"), "utf8"));
|
|
169
|
+
expect(counters["lazycodex-code-reviewer:g1:a1"]).toBe(1);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("applySpawnGuards review repetition cap", () => {
|
|
174
|
+
it("#given one goal attempt #when the same reviewer spawns four times #then denies until the attempt advances", () => {
|
|
175
|
+
writeGoals();
|
|
176
|
+
const reviewSpawn = payload("spawn_agent", {
|
|
177
|
+
agent_type: "lazycodex-code-reviewer",
|
|
178
|
+
message: "review the current diff",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
182
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
183
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
184
|
+
|
|
185
|
+
const fourth = applySpawnGuards(reviewSpawn);
|
|
186
|
+
expect(fourth).not.toBe("");
|
|
187
|
+
expect(deny(fourth).permissionDecisionReason).toContain("4/3");
|
|
188
|
+
const counter = JSON.parse(readFileSync(join(sessionDir(), "spawn-count.json"), "utf8"));
|
|
189
|
+
expect(counter.count).toBe(3);
|
|
190
|
+
|
|
191
|
+
const goalsPath = join(sessionDir(), "goals.json");
|
|
192
|
+
const plan = JSON.parse(readFileSync(goalsPath, "utf8")) as {
|
|
193
|
+
goals: Array<{ attempt: number }>;
|
|
194
|
+
};
|
|
195
|
+
const [goal] = plan.goals;
|
|
196
|
+
if (goal === undefined) throw new Error("fixture must include a goal");
|
|
197
|
+
goal.attempt = 2;
|
|
198
|
+
writeFileSync(goalsPath, JSON.stringify(plan));
|
|
199
|
+
|
|
200
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it.each(["lazycodex-code-reviewer", "lazycodex-qa-executor"])(
|
|
204
|
+
"#given a MultiAgentV2 %s message #when it spawns four times #then applies the reviewer cap",
|
|
205
|
+
(agentType) => {
|
|
206
|
+
writeGoals();
|
|
207
|
+
const reviewSpawn = payload("spawn_agent", {
|
|
208
|
+
message: `Delegate this final-quality lane to ${agentType}`,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
212
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
213
|
+
expect(applySpawnGuards(reviewSpawn)).toBe("");
|
|
214
|
+
|
|
215
|
+
const fourth = deny(applySpawnGuards(reviewSpawn));
|
|
216
|
+
expect(fourth.permissionDecisionReason).toContain(`${agentType} 4/3`);
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
it("#given exhausted code reviews and an explicit QA assignment #when a mixed V2 prompt is guarded #then charges the QA role", () => {
|
|
221
|
+
writeGoals();
|
|
222
|
+
const codeReview = payload("collaboration.spawn_agent", {
|
|
223
|
+
message: "Act as lazycodex-code-reviewer; review the current diff",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(applySpawnGuards(codeReview)).toBe("");
|
|
227
|
+
expect(applySpawnGuards(codeReview)).toBe("");
|
|
228
|
+
expect(applySpawnGuards(codeReview)).toBe("");
|
|
229
|
+
|
|
230
|
+
const output = applySpawnGuards(
|
|
231
|
+
payload("collaboration.spawn_agent", {
|
|
232
|
+
message: "Act as a lazycodex-qa-executor; verify the lazycodex-code-reviewer finding",
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
expect(output).toBe("");
|
|
236
|
+
|
|
237
|
+
const counters = JSON.parse(readFileSync(join(sessionDir(), "review-spawn-counts.json"), "utf8"));
|
|
238
|
+
expect(counters["lazycodex-code-reviewer:g1:a1"]).toBe(3);
|
|
239
|
+
expect(counters["lazycodex-qa-executor:g1:a1"]).toBe(1);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
135
243
|
describe("applySpawnGuards gate-artifact guard", () => {
|
|
244
|
+
it("#given a mixed-role V2 gate prompt #when guarded #then prioritizes the gate role and denies on missing artifact without charging quota", () => {
|
|
245
|
+
writeGoals();
|
|
246
|
+
|
|
247
|
+
const output = applySpawnGuards(
|
|
248
|
+
payload("spawn_agent", {
|
|
249
|
+
message: "Act as lazycodex-gate-reviewer; audit the lazycodex-code-reviewer report",
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(output).not.toBe("");
|
|
254
|
+
expect(deny(output).permissionDecisionReason).toContain("g1-code-review.md");
|
|
255
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
136
258
|
it("#given a gate spawn by agent_type without artifacts #when guarded #then denies naming the missing path", () => {
|
|
137
259
|
writeGoals();
|
|
138
260
|
|
|
@@ -146,6 +268,53 @@ describe("applySpawnGuards gate-artifact guard", () => {
|
|
|
146
268
|
expect(parsed.permissionDecisionReason).toContain("g1-code-review.md");
|
|
147
269
|
});
|
|
148
270
|
|
|
271
|
+
it("#given senpi main-session QA is present but code review is absent #when the gate reviewer spawns #then the self-check allows it", () => {
|
|
272
|
+
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
273
|
+
writeGoals();
|
|
274
|
+
mkdirSync(join(workDir, ".omo", "evidence"), { recursive: true });
|
|
275
|
+
writeFileSync(join(workDir, ".omo", "evidence", "g1-manual-qa.md"), "matrix\n");
|
|
276
|
+
|
|
277
|
+
const output = applySpawnGuards(
|
|
278
|
+
payload("collaborationspawn_agent", { agent_type: "omo-senpi-gate-reviewer", message: "audit the artifacts" }),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(output).toBe("");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("#given senpi QA is absent #when the gate reviewer spawns #then the self-check blocks naming manual QA", () => {
|
|
285
|
+
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
286
|
+
writeGoals();
|
|
287
|
+
|
|
288
|
+
const output = applySpawnGuards(
|
|
289
|
+
payload("collaborationspawn_agent", { agent_type: "omo-senpi-gate-reviewer", message: "audit the artifacts" }),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const parsed = deny(output);
|
|
293
|
+
expect(parsed.permissionDecision).toBe("deny");
|
|
294
|
+
expect(parsed.permissionDecisionReason).toContain("g1-manual-qa.md");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("#given an omo-senpi gate spawn with a code-review artifact only #when guarded #then blocks the missing manual QA", () => {
|
|
298
|
+
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
299
|
+
writeGoals();
|
|
300
|
+
mkdirSync(join(workDir, ".omo", "evidence"), { recursive: true });
|
|
301
|
+
writeFileSync(join(workDir, ".omo", "evidence", "g1-code-review.md"), "report\n");
|
|
302
|
+
|
|
303
|
+
const output = applySpawnGuards(
|
|
304
|
+
payload("collaborationspawn_agent", { agent_type: "omo-senpi-gate-reviewer", message: "audit the artifacts" }),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
expect(deny(output).permissionDecisionReason).toContain("g1-manual-qa.md");
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("#given an omo-senpi gate reviewer named in the message #when guarded #then still denies", () => {
|
|
311
|
+
writeGoals();
|
|
312
|
+
|
|
313
|
+
const output = applySpawnGuards(payload("spawn_agent", { message: "spawn omo-senpi-gate-reviewer now" }));
|
|
314
|
+
|
|
315
|
+
expect(deny(output).permissionDecision).toBe("deny");
|
|
316
|
+
});
|
|
317
|
+
|
|
149
318
|
it("#given a gate spawn identified by message only #when guarded #then still denies", () => {
|
|
150
319
|
writeGoals();
|
|
151
320
|
|
|
@@ -154,6 +323,49 @@ describe("applySpawnGuards gate-artifact guard", () => {
|
|
|
154
323
|
expect(deny(output).permissionDecision).toBe("deny");
|
|
155
324
|
});
|
|
156
325
|
|
|
326
|
+
it("#given the staged Senpi surface #when generic and explicit gate spawns alternate #then they share one cap", () => {
|
|
327
|
+
writeGoals();
|
|
328
|
+
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
329
|
+
mkdirSync(join(workDir, ".omo", "evidence"), { recursive: true });
|
|
330
|
+
writeFileSync(join(workDir, ".omo", "evidence", "g1-code-review.md"), "report\n");
|
|
331
|
+
writeFileSync(join(workDir, ".omo", "evidence", "g1-manual-qa.md"), "matrix\n");
|
|
332
|
+
const genericGate = payload("spawn_agent", { message: "run the final gate review now" });
|
|
333
|
+
const explicitGate = payload("spawn_agent", {
|
|
334
|
+
message: "Act as omo-senpi-gate-reviewer; audit the final evidence",
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
expect(applySpawnGuards(genericGate)).toBe("");
|
|
338
|
+
expect(applySpawnGuards(explicitGate)).toBe("");
|
|
339
|
+
expect(applySpawnGuards(genericGate)).toBe("");
|
|
340
|
+
|
|
341
|
+
const fourth = deny(applySpawnGuards(explicitGate));
|
|
342
|
+
expect(fourth.permissionDecisionReason).toContain("omo-senpi-gate-reviewer 4/3");
|
|
343
|
+
const counters = JSON.parse(readFileSync(join(sessionDir(), "review-spawn-counts.json"), "utf8"));
|
|
344
|
+
expect(counters["omo-senpi-gate-reviewer:g1:a1"]).toBe(3);
|
|
345
|
+
expect(counters["lazycodex-gate-reviewer:g1:a1"]).toBeUndefined();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("#given the staged Senpi surface #when LazyCodex and Senpi aliases alternate #then one reviewer lane owns the cap", () => {
|
|
349
|
+
writeGoals();
|
|
350
|
+
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
351
|
+
const senpiReviewer = payload("spawn_agent", {
|
|
352
|
+
message: "Act as omo-senpi-code-reviewer; inspect the current diff",
|
|
353
|
+
});
|
|
354
|
+
const lazycodexReviewer = payload("spawn_agent", {
|
|
355
|
+
message: "Act as lazycodex-code-reviewer; inspect the current diff",
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(applySpawnGuards(senpiReviewer)).toBe("");
|
|
359
|
+
expect(applySpawnGuards(lazycodexReviewer)).toBe("");
|
|
360
|
+
expect(applySpawnGuards(senpiReviewer)).toBe("");
|
|
361
|
+
|
|
362
|
+
const fourth = deny(applySpawnGuards(lazycodexReviewer));
|
|
363
|
+
expect(fourth.permissionDecisionReason).toContain("omo-senpi-code-reviewer 4/3");
|
|
364
|
+
const counters = JSON.parse(readFileSync(join(sessionDir(), "review-spawn-counts.json"), "utf8"));
|
|
365
|
+
expect(counters["omo-senpi-code-reviewer:g1:a1"]).toBe(3);
|
|
366
|
+
expect(counters["lazycodex-code-reviewer:g1:a1"]).toBeUndefined();
|
|
367
|
+
});
|
|
368
|
+
|
|
157
369
|
it("#given v1 artifacts on disk #when the gate spawns #then allows", () => {
|
|
158
370
|
writeGoals();
|
|
159
371
|
mkdirSync(join(workDir, ".omo", "evidence"), { recursive: true });
|
|
@@ -226,3 +438,108 @@ describe("applySpawnGuards gate-artifact guard", () => {
|
|
|
226
438
|
expect(output).toBe("");
|
|
227
439
|
});
|
|
228
440
|
});
|
|
441
|
+
|
|
442
|
+
describe("applySpawnGuards V1 agent_type non-reviewer early-exit", () => {
|
|
443
|
+
it("#given a V1 spawn with a non-reviewer agent_type #when guarded #then allows without charging any quota", () => {
|
|
444
|
+
writeGoals();
|
|
445
|
+
const explorerSpawn = payload("spawn_agent", {
|
|
446
|
+
agent_type: "explorer",
|
|
447
|
+
message: "scan the codebase for lazycodex-code-reviewer patterns",
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const result = applySpawnGuards(explorerSpawn);
|
|
451
|
+
|
|
452
|
+
expect(result).toBe("");
|
|
453
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("#given a V1 spawn with a non-reviewer agent_type mentioning a gate role #when guarded #then does not charge gate quota", () => {
|
|
457
|
+
writeGoals();
|
|
458
|
+
const explorerSpawn = payload("spawn_agent", {
|
|
459
|
+
agent_type: "deep",
|
|
460
|
+
message: "final gate review — summarize findings",
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const result = applySpawnGuards(explorerSpawn);
|
|
464
|
+
|
|
465
|
+
expect(result).toBe("");
|
|
466
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe("applySpawnGuards V2 explicit non-reviewer act-as guard", () => {
|
|
471
|
+
it("#given an explicit 'act as explorer' assignment mentioning a reviewer #when guarded #then allows without charging reviewer quota", () => {
|
|
472
|
+
writeGoals();
|
|
473
|
+
const explorerSpawn = payload("spawn_agent", {
|
|
474
|
+
message: "Act as explorer; scan for lazycodex-code-reviewer usage patterns",
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const result = applySpawnGuards(explorerSpawn);
|
|
478
|
+
|
|
479
|
+
expect(result).toBe("");
|
|
480
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("#given an explicit 'act as a deep' assignment with gate keyword #when guarded #then allows without charging gate quota", () => {
|
|
484
|
+
writeGoals();
|
|
485
|
+
const deepSpawn = payload("spawn_agent", {
|
|
486
|
+
message: "Act as a deep agent; run the final gate review summary",
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const result = applySpawnGuards(deepSpawn);
|
|
490
|
+
|
|
491
|
+
expect(result).toBe("");
|
|
492
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("#given an explicit reviewer act-as assignment #when guarded #then still charges reviewer quota", () => {
|
|
496
|
+
writeGoals();
|
|
497
|
+
const reviewerSpawn = payload("spawn_agent", {
|
|
498
|
+
message: "Act as lazycodex-code-reviewer; inspect the diff",
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const result = applySpawnGuards(reviewerSpawn);
|
|
502
|
+
|
|
503
|
+
expect(result).toBe("");
|
|
504
|
+
const counters = JSON.parse(readFileSync(join(sessionDir(), "review-spawn-counts.json"), "utf8"));
|
|
505
|
+
expect(counters["lazycodex-code-reviewer:g1:a1"]).toBe(1);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
describe("applySpawnGuards gate-artifact check before reviewer quota", () => {
|
|
510
|
+
it("#given a gate spawn without artifacts #when the fan-out budget is available #then denies on artifact rule without charging reviewer quota", () => {
|
|
511
|
+
writeGoals();
|
|
512
|
+
|
|
513
|
+
const gateSpawn = payload("spawn_agent", {
|
|
514
|
+
agent_type: "lazycodex-gate-reviewer",
|
|
515
|
+
message: "final gate review",
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const result = applySpawnGuards(gateSpawn);
|
|
519
|
+
|
|
520
|
+
expect(result).not.toBe("");
|
|
521
|
+
expect(deny(result).permissionDecisionReason).toContain("missing");
|
|
522
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("#given a gate spawn denied for missing artifacts #when retried after artifacts land #then charges quota only on the allowed spawn", () => {
|
|
526
|
+
writeGoals();
|
|
527
|
+
const gateSpawn = payload("spawn_agent", {
|
|
528
|
+
agent_type: "lazycodex-gate-reviewer",
|
|
529
|
+
message: "final gate review",
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
const firstResult = applySpawnGuards(gateSpawn);
|
|
533
|
+
expect(firstResult).not.toBe("");
|
|
534
|
+
expect(existsSync(join(sessionDir(), "review-spawn-counts.json"))).toBe(false);
|
|
535
|
+
|
|
536
|
+
mkdirSync(join(workDir, ".omo", "evidence"), { recursive: true });
|
|
537
|
+
writeFileSync(join(workDir, ".omo", "evidence", "g1-code-review.md"), "report\n");
|
|
538
|
+
writeFileSync(join(workDir, ".omo", "evidence", "g1-manual-qa.md"), "matrix\n");
|
|
539
|
+
|
|
540
|
+
const secondResult = applySpawnGuards(gateSpawn);
|
|
541
|
+
expect(secondResult).toBe("");
|
|
542
|
+
const counters = JSON.parse(readFileSync(join(sessionDir(), "review-spawn-counts.json"), "utf8"));
|
|
543
|
+
expect(counters["lazycodex-gate-reviewer:g1:a1"]).toBe(1);
|
|
544
|
+
});
|
|
545
|
+
});
|