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.
Files changed (278) hide show
  1. package/dist/cli/index.js +178 -37
  2. package/dist/cli-node/index.js +178 -37
  3. package/dist/config/schema/background-task.d.ts +1 -0
  4. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
  5. package/dist/features/background-agent/constants.d.ts +0 -7
  6. package/dist/features/background-agent/manager.d.ts +2 -1
  7. package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
  8. package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
  9. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
  10. package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
  11. package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
  12. package/dist/features/monitor/manager-internals.d.ts +1 -1
  13. package/dist/features/monitor/output-injector-types.d.ts +1 -1
  14. package/dist/features/task-toast-manager/manager.d.ts +0 -24
  15. package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +1 -0
  16. package/dist/features/team-mode/tools/messaging-runtime.d.ts +1 -0
  17. package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
  18. package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
  19. package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
  20. package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
  21. package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
  22. package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
  23. package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
  24. package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
  25. package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
  26. package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
  27. package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
  28. package/dist/hooks/keyword-detector/hook.d.ts +7 -0
  29. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
  30. package/dist/hooks/model-fallback/hook.d.ts +1 -1
  31. package/dist/index.js +681 -273
  32. package/dist/oh-my-opencode.schema.json +5 -0
  33. package/dist/plugin-dispose.d.ts +3 -0
  34. package/dist/shared/agent-tool-restrictions.d.ts +0 -1
  35. package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  36. package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
  37. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
  38. package/dist/tools/delegate-task/types.d.ts +1 -0
  39. package/dist/tools/session-manager/directory-filter.d.ts +2 -0
  40. package/dist/tui.js +148 -9
  41. package/package.json +16 -15
  42. package/packages/git-bash-mcp/dist/cli.js +1 -0
  43. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +47 -19
  44. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  45. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
  46. package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
  47. package/packages/lsp-core/src/lsp/client.ts +6 -3
  48. package/packages/lsp-core/src/lsp/constants.ts +5 -0
  49. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
  50. package/packages/lsp-core/src/lsp/errors.ts +15 -0
  51. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
  52. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  53. package/packages/lsp-core/src/lsp/manager.ts +106 -13
  54. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  55. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  56. package/packages/lsp-core/src/lsp/transport.ts +7 -2
  57. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
  58. package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
  59. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
  60. package/packages/lsp-core/src/mcp.ts +3 -0
  61. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  62. package/packages/lsp-daemon/dist/cli.js +155 -34
  63. package/packages/lsp-daemon/dist/client.js +155 -34
  64. package/packages/lsp-daemon/dist/index.js +155 -34
  65. package/packages/lsp-daemon/dist/proxy.js +3 -0
  66. package/packages/lsp-tools-mcp/dist/cli.js +155 -34
  67. package/packages/lsp-tools-mcp/dist/lsp/manager.js +134 -28
  68. package/packages/lsp-tools-mcp/dist/mcp.js +155 -34
  69. package/packages/lsp-tools-mcp/dist/tools.js +154 -34
  70. package/packages/lsp-tools-mcp/package.json +1 -1
  71. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  72. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  73. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  74. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +17 -2
  75. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +17 -2
  76. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  77. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
  78. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
  79. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  80. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  81. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  82. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  83. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  84. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  85. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  86. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +152 -34
  87. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  88. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  89. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  90. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  91. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  92. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  93. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  94. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  95. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +5 -4
  96. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +489 -4
  97. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  98. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -2
  99. package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
  100. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
  101. package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
  102. package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
  103. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
  104. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  105. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  106. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +5 -2
  107. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -0
  108. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
  109. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +14 -0
  110. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +10 -1
  111. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
  112. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
  113. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +9 -5
  114. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
  115. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
  116. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -0
  117. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
  118. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +856 -347
  119. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  120. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +27 -6
  121. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
  122. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +14 -0
  123. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
  124. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +17 -2
  125. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +13 -0
  126. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +19 -0
  127. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +2 -0
  128. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +53 -0
  129. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +9 -0
  130. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +85 -0
  131. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +1 -1
  132. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  133. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +41 -102
  134. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
  135. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +185 -14
  136. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
  137. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
  138. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +18 -0
  139. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +65 -0
  140. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  141. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  142. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +17 -0
  143. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
  144. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +25 -1
  145. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +127 -0
  146. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +10 -12
  147. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +2 -1
  148. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -0
  149. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
  150. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +34 -5
  151. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +23 -0
  152. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
  153. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +18 -3
  154. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +27 -0
  155. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +57 -0
  156. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +103 -0
  157. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +1 -1
  158. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +54 -123
  159. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
  160. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +187 -12
  161. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
  162. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +95 -0
  163. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +107 -1
  164. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +92 -0
  165. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +183 -0
  166. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +35 -0
  167. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
  168. package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
  169. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +101 -0
  170. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +46 -2
  171. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
  172. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +5 -0
  173. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +318 -1
  174. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
  175. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +9 -3
  176. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  190. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  191. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  192. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  193. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  194. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  195. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  196. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  197. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  198. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  199. package/packages/omo-codex/plugin/package-lock.json +13 -13
  200. package/packages/omo-codex/plugin/package.json +1 -1
  201. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +31 -2
  202. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  203. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +17 -0
  204. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +11 -1
  205. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +13 -0
  206. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -2
  207. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  208. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  209. package/dist/features/builtin-skills/index.d.ts +0 -1
  210. package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
  211. package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
  212. package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
  213. package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
  214. package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
  215. package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
  216. package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
  217. package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
  218. package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
  219. package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
  220. package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
  221. package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
  222. package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
  223. package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
  224. package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
  225. package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
  226. package/dist/features/builtin-skills/skills/index.d.ts +0 -1
  227. package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
  228. package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
  229. package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
  230. package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
  231. package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
  232. package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
  233. package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
  234. package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
  235. package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
  236. package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
  237. package/dist/features/builtin-skills/skills.d.ts +0 -1
  238. package/dist/features/builtin-skills/types.d.ts +0 -1
  239. package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
  240. package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
  241. package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
  242. package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
  243. package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
  244. package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
  245. package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
  246. package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
  247. package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
  248. package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
  249. package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
  250. package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
  251. package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
  252. package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
  253. package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
  254. package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
  255. package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
  256. package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
  257. package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
  258. package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
  259. package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
  260. package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
  261. package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
  262. package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
  263. package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
  264. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
  265. package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
  266. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
  267. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
  268. package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
  269. package/dist/shared/prompt-async-gate/types.d.ts +0 -1
  270. package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
  271. package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
  272. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  273. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  274. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  275. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  276. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
  277. package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -483
  278. 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
+ });