oh-my-opencode 4.18.2 → 4.19.1

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 (307) hide show
  1. package/.agents/command/omomomo.md +1 -1
  2. package/.agents/skills/omomomo/SKILL.md +1 -1
  3. package/.opencode/command/omomomo.md +1 -1
  4. package/README.ja.md +6 -6
  5. package/README.ko.md +6 -6
  6. package/README.md +8 -8
  7. package/README.ru.md +6 -6
  8. package/README.zh-cn.md +6 -6
  9. package/dist/agents/atlas/agent.d.ts +7 -6
  10. package/dist/agents/gpt-prompt-identity.d.ts +3 -0
  11. package/dist/agents/sisyphus/gpt-5-5.d.ts +1 -1
  12. package/dist/agents/sisyphus/gpt-task-system-guide.d.ts +1 -0
  13. package/dist/agents/sisyphus/index.d.ts +5 -0
  14. package/dist/agents/sisyphus/kimi-k3.d.ts +19 -0
  15. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  16. package/dist/agents/sisyphus-junior/agent.d.ts +8 -4
  17. package/dist/agents/sisyphus-junior/gpt-5-5.d.ts +2 -2
  18. package/dist/agents/sisyphus-junior/index.d.ts +2 -0
  19. package/dist/agents/sisyphus-junior/kimi-k3.d.ts +12 -0
  20. package/dist/agents/types.d.ts +2 -2
  21. package/dist/cli/index.js +781 -215
  22. package/dist/cli-node/index.js +781 -215
  23. package/dist/config/index.d.ts +1 -1
  24. package/dist/config/schema/agent-overrides.d.ts +48 -48
  25. package/dist/config/schema/browser-automation.d.ts +1 -0
  26. package/dist/config/schema/categories.d.ts +6 -6
  27. package/dist/config/schema/codegraph.d.ts +2 -0
  28. package/dist/config/schema/commands.d.ts +1 -3
  29. package/dist/config/schema/default-mode.d.ts +1 -1
  30. package/dist/config/schema/fallback-models.d.ts +5 -5
  31. package/dist/config/schema/goal.d.ts +7 -0
  32. package/dist/config/schema/hooks.d.ts +1 -1
  33. package/dist/config/schema/oh-my-opencode-config.d.ts +56 -58
  34. package/dist/config/schema/ralph-loop.d.ts +1 -1
  35. package/dist/config/schema.d.ts +1 -1
  36. package/dist/create-hooks.d.ts +1 -1
  37. package/dist/features/builtin-commands/templates/goal.d.ts +1 -0
  38. package/dist/features/builtin-commands/templates/stop-continuation.d.ts +1 -1
  39. package/dist/features/builtin-commands/types.d.ts +1 -1
  40. package/dist/hooks/auto-slash-command/executor.d.ts +1 -0
  41. package/dist/hooks/codegraph-bootstrap/hook.d.ts +3 -2
  42. package/dist/hooks/goal/command-arguments.d.ts +13 -0
  43. package/dist/hooks/goal/controller.d.ts +26 -0
  44. package/dist/hooks/goal/index.d.ts +23 -0
  45. package/dist/hooks/goal/prompt.d.ts +3 -0
  46. package/dist/hooks/goal/store.d.ts +8 -0
  47. package/dist/hooks/goal/tools.d.ts +10 -0
  48. package/dist/hooks/goal/types.d.ts +93 -0
  49. package/dist/hooks/goal/validation.d.ts +4 -0
  50. package/dist/hooks/index.d.ts +1 -0
  51. package/dist/hooks/keyword-detector/hook.d.ts +1 -2
  52. package/dist/hooks/ralph-loop/types.d.ts +1 -1
  53. package/dist/hooks/sisyphus-junior-notepad/constants.d.ts +1 -1
  54. package/dist/hooks/start-work/notepad-scaffold.d.ts +10 -0
  55. package/dist/index.js +5398 -5109
  56. package/dist/oh-my-opencode.schema.json +34 -19
  57. package/dist/plugin/chat-message/loop-commands.d.ts +1 -1
  58. package/dist/plugin/chat-message/types.d.ts +23 -10
  59. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -1
  60. package/dist/plugin/hooks/create-session-hooks.d.ts +3 -2
  61. package/dist/plugin/hooks/create-transform-hooks.d.ts +0 -2
  62. package/dist/plugin/stop-continuation.d.ts +2 -2
  63. package/dist/shared/omo-process-sweep.d.ts +18 -0
  64. package/dist/shared/system-directive.d.ts +0 -1
  65. package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.test.ts +87 -0
  66. package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.ts +35 -5
  67. package/dist/skills/ulw-plan/SKILL.md +31 -8
  68. package/dist/skills/ulw-plan/references/full-workflow.md +117 -6
  69. package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  70. package/dist/skills/ulw-research/SKILL.md +3 -3
  71. package/dist/testing/create-plugin-module.d.ts +1 -0
  72. package/dist/tools/delegate-task/types.d.ts +0 -1
  73. package/dist/tui.js +138 -80
  74. package/package.json +14 -14
  75. package/packages/git-bash-mcp/dist/cli.js +54 -8
  76. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +60 -0
  77. package/packages/lsp-core/src/lsp/client.ts +14 -1
  78. package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +2 -2
  79. package/packages/lsp-core/src/mcp.ts +9 -0
  80. package/packages/lsp-daemon/dist/cli.js +319 -51
  81. package/packages/lsp-daemon/dist/client.js +136 -48
  82. package/packages/lsp-daemon/dist/daemon-client.d.ts +0 -10
  83. package/packages/lsp-daemon/dist/daemon-client.js +4 -34
  84. package/packages/lsp-daemon/dist/daemon-failure-result.d.ts +3 -0
  85. package/packages/lsp-daemon/dist/daemon-failure-result.js +38 -0
  86. package/packages/lsp-daemon/dist/daemon-request-error.d.ts +14 -0
  87. package/packages/lsp-daemon/dist/daemon-request-error.js +26 -0
  88. package/packages/lsp-daemon/dist/daemon-server.js +5 -1
  89. package/packages/lsp-daemon/dist/index.js +137 -48
  90. package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
  91. package/packages/lsp-daemon/dist/proxy.js +1 -0
  92. package/packages/lsp-daemon/dist/version-reap.d.ts +26 -0
  93. package/packages/lsp-daemon/dist/version-reap.js +182 -0
  94. package/packages/lsp-tools-mcp/dist/cli.js +61 -10
  95. package/packages/lsp-tools-mcp/dist/lsp/manager.js +6 -1
  96. package/packages/lsp-tools-mcp/dist/mcp.js +61 -10
  97. package/packages/lsp-tools-mcp/dist/tools.js +6 -1
  98. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  99. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +416 -25
  100. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  101. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  102. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +22 -2
  103. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +7 -6
  104. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +638 -267
  105. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +178 -89
  106. package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
  107. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +3 -1
  108. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +3 -1
  109. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +98 -32
  110. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +5 -0
  111. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +15 -6
  112. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +6 -2
  113. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +1 -0
  114. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +1 -1
  115. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +148 -0
  116. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +31 -2
  117. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +1 -1
  118. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +65 -1
  119. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -7
  120. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +1 -1
  121. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +57 -3
  122. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +41 -0
  123. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +109 -8
  124. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +2 -2
  125. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +43 -0
  126. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  127. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  128. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  129. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  130. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  131. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  132. package/packages/omo-codex/plugin/components/lsp/AGENTS.md +1 -0
  133. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  134. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +80 -40
  135. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  136. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  137. package/packages/omo-codex/plugin/components/rules/dist/cli.js +11 -0
  138. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  139. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  140. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +11 -0
  141. package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +60 -0
  142. package/packages/omo-codex/plugin/components/start-work-continuation/AGENTS.md +8 -0
  143. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +147 -60
  144. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  145. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  146. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +3 -71
  147. package/packages/omo-codex/plugin/components/start-work-continuation/src/plan-checklist.ts +171 -0
  148. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +198 -23
  149. package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +4 -3
  150. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +6 -5
  151. package/packages/omo-codex/plugin/components/start-work-continuation/test/fixtures/plan-scaffold.md +15 -0
  152. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  153. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  154. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  155. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  156. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -10
  157. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +2 -2
  158. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +2 -2
  159. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
  160. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +1 -1
  161. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +2 -2
  162. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +3 -3
  163. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +1 -1
  164. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +1 -1
  165. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  166. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  167. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +31 -8
  168. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +117 -6
  169. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  170. package/packages/omo-codex/plugin/components/ultrawork/src/codex-hook.ts +1 -1
  171. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +42 -0
  172. package/packages/omo-codex/plugin/components/ulw-loop/README.md +6 -4
  173. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.d.ts +21 -0
  174. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +64 -0
  175. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +15 -2
  176. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +1 -1
  177. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
  178. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
  179. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +3 -3
  180. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.d.ts +3 -0
  181. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.js +85 -0
  182. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +0 -1
  183. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +13 -33
  184. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +998 -593
  185. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +1 -0
  186. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -1
  187. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +2 -0
  188. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +6 -0
  189. package/packages/omo-codex/plugin/components/ulw-loop/dist/goal-status.d.ts +1 -0
  190. package/packages/omo-codex/plugin/components/ulw-loop/dist/goal-status.js +3 -0
  191. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +1 -0
  192. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +4 -0
  193. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +1 -0
  194. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +10 -2
  195. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +1 -1
  196. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.d.ts +15 -0
  197. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +84 -0
  198. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +5 -0
  199. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +81 -0
  200. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +7 -59
  201. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +12 -4
  202. package/packages/omo-codex/plugin/components/ulw-loop/dist/validation-batch.d.ts +9 -0
  203. package/packages/omo-codex/plugin/components/ulw-loop/dist/validation-batch.js +122 -0
  204. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  205. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  206. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +8 -6
  207. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +84 -0
  208. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +13 -2
  209. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +1 -1
  210. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +1 -1
  211. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +3 -3
  212. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts +49 -0
  213. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +13 -45
  214. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +1 -0
  215. package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +2 -0
  216. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +7 -0
  217. package/packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts +4 -0
  218. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +4 -1
  219. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +20 -2
  220. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +1 -1
  221. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +125 -0
  222. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +59 -0
  223. package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +6 -58
  224. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +12 -4
  225. package/packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts +122 -0
  226. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +76 -0
  227. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +111 -0
  228. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +67 -0
  229. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +57 -0
  230. package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +1 -0
  231. package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +152 -0
  232. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +29 -0
  233. package/packages/omo-codex/plugin/components/ulw-loop/test/validation-batch-checkpoint.test.ts +147 -0
  234. package/packages/omo-codex/plugin/components/ulw-loop/test/validation-batch.test.ts +73 -0
  235. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  236. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  237. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  238. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  239. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  240. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  241. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  242. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  243. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  244. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  245. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  246. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +2 -2
  247. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  248. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +2 -2
  249. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  250. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +2 -2
  251. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  252. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  253. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  254. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  255. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  256. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  257. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  258. package/packages/omo-codex/plugin/package-lock.json +41 -41
  259. package/packages/omo-codex/plugin/package.json +1 -1
  260. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +32 -28
  261. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +17 -45
  262. package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +455 -0
  263. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +3 -18
  264. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +43 -0
  265. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +35 -5
  266. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +8 -6
  267. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +31 -8
  268. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +117 -6
  269. package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  270. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +11 -11
  271. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +26 -0
  272. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +41 -0
  273. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +2 -2
  274. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +6 -6
  275. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +3 -3
  276. package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +62 -0
  277. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +271 -14
  278. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +3 -18
  279. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +0 -5
  280. package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +293 -0
  281. package/packages/omo-codex/scripts/install-config.test.mjs +10 -10
  282. package/packages/omo-codex/scripts/install-dist/install-local.mjs +401 -22
  283. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +39 -2
  284. package/packages/omo-codex/scripts/install-marketplace-cache.test.mjs +4 -1
  285. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.test.ts +87 -0
  286. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +35 -5
  287. package/packages/shared-skills/skills/ulw-plan/SKILL.md +31 -8
  288. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +117 -6
  289. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  290. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
  291. package/dist/features/builtin-commands/templates/ralph-loop.d.ts +0 -3
  292. package/dist/skills/ultraresearch/SKILL.md +0 -10
  293. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +0 -273
  294. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -12
  295. package/packages/shared-skills/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -107
  296. package/packages/shared-skills/skills/lcx-doctor/SKILL.md +0 -128
  297. package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +0 -11
  298. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +0 -268
  299. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +0 -11
  300. package/packages/shared-skills/skills/ultraresearch/SKILL.md +0 -10
  301. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/SKILL.md +0 -0
  302. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -0
  303. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -0
  304. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/SKILL.md +0 -0
  305. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/agents/openai.yaml +0 -0
  306. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/SKILL.md +0 -0
  307. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/agents/openai.yaml +0 -0
@@ -0,0 +1,67 @@
1
+ // biome-ignore-all format: compact CLI batch tests stay under the pure LOC budget.
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, expect, it, vi } from "vitest";
6
+
7
+ import { ulwLoopCommand } from "../src/cli-commands.js";
8
+ import { parseSteeringProposals } from "../src/cli-steering.js";
9
+ import { ulwLoopGoalsPath } from "../src/paths.js";
10
+ import { writePlan } from "../src/plan-io.js";
11
+ import type { UlwLoopPlan } from "../src/types.js";
12
+
13
+ const NOW = "2026-05-23T00:00:00.000Z";
14
+
15
+ function plan(): UlwLoopPlan {
16
+ return { version: 1, createdAt: NOW, updatedAt: NOW, briefPath: ".omo/ulw-loop/brief.md", goalsPath: ".omo/ulw-loop/goals.json", ledgerPath: ".omo/ulw-loop/ledger.jsonl", goals: [] };
17
+ }
18
+
19
+ describe("parseSteeringProposals", () => {
20
+ it("#given proposals-json #when parsing steer args #then returns every proposal", async () => {
21
+ const proposals = await parseSteeringProposals([
22
+ "--proposals-json",
23
+ JSON.stringify([
24
+ { kind: "annotate_ledger", evidence: "one", rationale: "because one" },
25
+ { kind: "annotate_ledger", source: "cli", evidence: "two", rationale: "because two" },
26
+ ]),
27
+ ]);
28
+
29
+ expect(proposals).toHaveLength(2);
30
+ expect(proposals[0]).toMatchObject({ evidence: "one", source: "cli" });
31
+ expect(proposals[1]).toMatchObject({ evidence: "two" });
32
+ });
33
+
34
+ it("#given proposals-json path #when parsing steer args #then reads proposals from the file", async () => {
35
+ const repo = await mkdtemp(join(tmpdir(), "ug-cli-steer-batch-"));
36
+ const file = join(repo, "batch.json");
37
+ await writeFile(file, JSON.stringify([{ kind: "annotate_ledger", source: "cli", evidence: "file", rationale: "because file" }]), "utf8");
38
+
39
+ const proposals = await parseSteeringProposals(["--proposals-json", file]);
40
+
41
+ expect(proposals).toHaveLength(1);
42
+ expect(proposals[0]).toMatchObject({ evidence: "file" });
43
+ });
44
+
45
+ it("#given kind and proposals-json #when steering through CLI #then rejects conflict without mutation", async () => {
46
+ const repo = await mkdtemp(join(tmpdir(), "ug-cli-steer-batch-conflict-"));
47
+ const out: string[] = [];
48
+ try {
49
+ await writePlan(repo, plan());
50
+ const before = await readFile(ulwLoopGoalsPath(repo), "utf8");
51
+ vi.spyOn(process, "cwd").mockReturnValue(repo);
52
+ vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
53
+ out.push(chunk.toString());
54
+ return true;
55
+ });
56
+ vi.spyOn(process.stderr, "write").mockImplementation((): boolean => true);
57
+
58
+ expect(await ulwLoopCommand(["steer", "--kind", "annotate_ledger", "--evidence", "flag", "--rationale", "flag", "--proposals-json", '[{"kind":"annotate_ledger","evidence":"batch","rationale":"batch"}]', "--json"])).toBe(1);
59
+
60
+ expect(JSON.parse(out.join(""))).toHaveProperty("error.code", "ULW_LOOP_STEERING_BATCH_CONFLICT");
61
+ expect(await readFile(ulwLoopGoalsPath(repo), "utf8")).toBe(before);
62
+ } finally {
63
+ vi.restoreAllMocks();
64
+ await rm(repo, { recursive: true, force: true });
65
+ }
66
+ });
67
+ });
@@ -0,0 +1,57 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+
6
+ import { ulwLoopCommand } from "../src/cli-commands.js";
7
+
8
+ let testDir: string;
9
+ let out: string[];
10
+
11
+ beforeEach(async () => {
12
+ testDir = await mkdtemp(join(tmpdir(), "ug-cli-validation-batch-"));
13
+ out = [];
14
+ vi.spyOn(process, "cwd").mockReturnValue(testDir);
15
+ vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
16
+ out.push(chunk.toString());
17
+ return true;
18
+ });
19
+ vi.spyOn(process.stderr, "write").mockImplementation((): boolean => true);
20
+ });
21
+
22
+ afterEach(async () => {
23
+ vi.restoreAllMocks();
24
+ await rm(testDir, { recursive: true, force: true });
25
+ });
26
+
27
+ describe("ulwLoopCommand create-goals validation batches", () => {
28
+ it("#given validation-batch-json #when creating goals #then JSON output includes validation batches", async () => {
29
+ expect(
30
+ await ulwLoopCommand([
31
+ "create-goals",
32
+ "--brief",
33
+ "- Goal alpha\n- Goal beta",
34
+ "--validation-batch-json",
35
+ '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G002-goal-beta"],"finalGoalId":"G002-goal-beta"}]',
36
+ "--json",
37
+ ]),
38
+ ).toBe(0);
39
+
40
+ expect(JSON.parse(out.join(""))).toHaveProperty("plan.validationBatches.0.batchId", "VB001");
41
+ });
42
+
43
+ it("#given finalGoalId outside members #when creating goals from CLI #then exits one with the distinct code", async () => {
44
+ expect(
45
+ await ulwLoopCommand([
46
+ "create-goals",
47
+ "--brief",
48
+ "- Goal alpha\n- Goal beta\n- Goal gamma",
49
+ "--validation-batch-json",
50
+ '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G002-goal-beta"],"finalGoalId":"G003-goal-gamma"}]',
51
+ "--json",
52
+ ]),
53
+ ).toBe(1);
54
+
55
+ expect(JSON.parse(out.join(""))).toHaveProperty("error.code", "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER");
56
+ });
57
+ });
@@ -1,3 +1,4 @@
1
+ // biome-ignore-all format: compact path tests predate this change.
1
2
  import { join, posix, win32 } from "node:path";
2
3
 
3
4
  import { describe, expect, it } from "vitest";
@@ -0,0 +1,152 @@
1
+ // biome-ignore-all format: compact steering batch tests stay under the pure LOC budget.
2
+ import { mkdtemp } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, expect, it, vi } from "vitest";
6
+
7
+ import type { CliSteeringProposal } from "../src/cli-steering.js";
8
+ import { readSteeringLedgerEntries, readUlwLoopPlan, writePlan } from "../src/plan-io.js";
9
+ import { steerUlwLoopBatch } from "../src/steering-batch.js";
10
+ import type { UlwLoopItem, UlwLoopPlan, UlwLoopSteeringProposal } from "../src/types.js";
11
+
12
+ const appendFileCalls = vi.hoisted(() => vi.fn());
13
+ vi.mock("node:fs/promises", async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import("node:fs/promises")>();
15
+ appendFileCalls.mockImplementation(actual.appendFile);
16
+ return { ...actual, appendFile: appendFileCalls };
17
+ });
18
+
19
+ const NOW = "2026-05-23T00:00:00.000Z";
20
+
21
+ function goal(id: string): UlwLoopItem {
22
+ return {
23
+ id,
24
+ title: id,
25
+ objective: `Do ${id}`,
26
+ status: "pending",
27
+ successCriteria: [],
28
+ attempt: 0,
29
+ createdAt: NOW,
30
+ updatedAt: NOW,
31
+ };
32
+ }
33
+
34
+ function plan(): UlwLoopPlan {
35
+ return {
36
+ version: 1,
37
+ createdAt: NOW,
38
+ updatedAt: NOW,
39
+ briefPath: ".omo/ulw-loop/brief.md",
40
+ goalsPath: ".omo/ulw-loop/goals.json",
41
+ ledgerPath: ".omo/ulw-loop/ledger.jsonl",
42
+ goals: [goal("G001"), goal("G002")],
43
+ };
44
+ }
45
+
46
+ function proposal(overrides: Partial<UlwLoopSteeringProposal> = {}): UlwLoopSteeringProposal {
47
+ return {
48
+ kind: "add_subgoal",
49
+ source: "cli",
50
+ evidence: "observable evidence",
51
+ rationale: "necessary plan refinement",
52
+ title: "New goal",
53
+ objective: "Do the new goal",
54
+ ...overrides,
55
+ };
56
+ }
57
+
58
+ async function repoWithPlan(): Promise<string> {
59
+ const repo = await mkdtemp(join(tmpdir(), "ug-steer-batch-"));
60
+ await writePlan(repo, plan());
61
+ return repo;
62
+ }
63
+
64
+ describe("steerUlwLoopBatch", () => {
65
+ it("#given two valid proposals #when applied as a batch #then both mutations commit", async () => {
66
+ const repo = await repoWithPlan();
67
+
68
+ const result = await steerUlwLoopBatch(repo, [
69
+ proposal({ idempotencyKey: "b1" }),
70
+ proposal({ kind: "revise_pending_wording", targetGoalId: "G001", revisedTitle: "Revised", idempotencyKey: "b2" }),
71
+ ]);
72
+
73
+ expect(result.accepted).toBe(true);
74
+ expect(result.results).toHaveLength(2);
75
+ expect((await readUlwLoopPlan(repo)).goals.map((item) => item.title)).toContain("Revised");
76
+ expect((await readSteeringLedgerEntries(repo)).filter((entry) => entry.kind === "steering_accepted")).toHaveLength(2);
77
+ });
78
+
79
+ it("#given rejected proposals #when applied as a batch #then one rejection audit lists every rejected index", async () => {
80
+ const repo = await repoWithPlan();
81
+ const before = JSON.stringify(await readUlwLoopPlan(repo));
82
+
83
+ const result = await steerUlwLoopBatch(repo, [proposal({ idempotencyKey: "ok" }), proposal({ kind: "reorder_pending", pendingOrder: ["missing"] }), proposal({ evidence: "" })]);
84
+
85
+ expect(result.accepted).toBe(false);
86
+ expect(JSON.stringify(await readUlwLoopPlan(repo))).toBe(before);
87
+ expect((await readUlwLoopPlan(repo)).goals).toHaveLength(2);
88
+ const entries = await readSteeringLedgerEntries(repo);
89
+ expect(entries.filter((entry) => entry.kind === "steering_accepted")).toHaveLength(0);
90
+ expect(entries.filter((entry) => entry.kind === "steering_rejected")).toHaveLength(1);
91
+ expect(entries.find((entry) => entry.kind === "steering_rejected")?.message).toContain("index 1:");
92
+ expect(entries.find((entry) => entry.kind === "steering_rejected")?.message).toContain("index 2:");
93
+ });
94
+
95
+ it("#given a batch member split in proposals-json #when applied #then updates validation batch membership", async () => {
96
+ const repo = await repoWithPlan();
97
+ const seed = await readUlwLoopPlan(repo);
98
+ await writePlan(repo, { ...seed, validationBatches: [{ batchId: "VB001", memberIds: ["G001", "G002"], finalGoalId: "G002" }] });
99
+
100
+ const result = await steerUlwLoopBatch(repo, [
101
+ proposal({
102
+ kind: "split_subgoal",
103
+ targetGoalId: "G001",
104
+ childGoals: [{ title: "Child", objective: "Do child" }],
105
+ }),
106
+ ]);
107
+
108
+ expect(result.plan.validationBatches).toEqual([{ batchId: "VB001", memberIds: ["G003", "G002"], finalGoalId: "G002" }]);
109
+ expect((await readSteeringLedgerEntries(repo)).at(-1)).toMatchObject({ kind: "batch_updated" });
110
+ });
111
+
112
+ it("#given a prior idempotency key #when batched with a fresh proposal #then dedupes one and applies the other", async () => {
113
+ const repo = await repoWithPlan();
114
+ await steerUlwLoopBatch(repo, [proposal({ idempotencyKey: "same" })]);
115
+
116
+ const result = await steerUlwLoopBatch(repo, [proposal({ idempotencyKey: "same" }), proposal({ idempotencyKey: "fresh" })]);
117
+
118
+ expect(result.accepted).toBe(true);
119
+ expect(result.results.map((item) => item.deduped)).toEqual([true, false]);
120
+ expect((await readUlwLoopPlan(repo)).goals).toHaveLength(4);
121
+ });
122
+
123
+ it("#given mixed fresh and deduped proposals #when accepted and replayed #then one append records all fresh audits and replay records none", async () => {
124
+ const repo = await repoWithPlan();
125
+ const seeded = await readUlwLoopPlan(repo);
126
+ seeded.goals[0]?.successCriteria.push({ id: "C001", scenario: "old", expectedEvidence: "proof", userModel: "edge", status: "pending", capturedEvidence: null });
127
+ await writePlan(repo, { ...seeded, validationBatches: [{ batchId: "VB001", memberIds: ["G001", "G002"], finalGoalId: "G002" }] });
128
+ await steerUlwLoopBatch(repo, [proposal({ idempotencyKey: "dedupe-me" })]);
129
+ appendFileCalls.mockClear();
130
+
131
+ const revise = { ...proposal({ kind: "revise_criterion", targetGoalId: "G001", criterionId: "C001", idempotencyKey: "revise-c1" }), scenario: "new" } satisfies CliSteeringProposal;
132
+ const batch = [
133
+ proposal({ idempotencyKey: "dedupe-me" }),
134
+ revise,
135
+ proposal({ kind: "split_subgoal", targetGoalId: "G002", childGoals: [{ title: "Child", objective: "Do child" }], idempotencyKey: "split-g2" }),
136
+ ];
137
+ const result = await steerUlwLoopBatch(repo, batch);
138
+
139
+ expect(result.accepted).toBe(true);
140
+ expect(result.results.map((item) => item.deduped)).toEqual([true, false, false]);
141
+ expect(appendFileCalls).toHaveBeenCalledTimes(1);
142
+ expect((await readSteeringLedgerEntries(repo)).map((entry) => entry.kind).slice(-3)).toEqual(["criteria_revised", "steering_accepted", "batch_updated"]);
143
+ const after = JSON.stringify(await readUlwLoopPlan(repo));
144
+ appendFileCalls.mockClear();
145
+
146
+ const replay = await steerUlwLoopBatch(repo, batch);
147
+
148
+ expect(replay.results.map((item) => item.deduped)).toEqual([true, true, true]);
149
+ expect(JSON.stringify(await readUlwLoopPlan(repo))).toBe(after);
150
+ expect(appendFileCalls).not.toHaveBeenCalled();
151
+ });
152
+ });
@@ -179,6 +179,35 @@ describe("runStopResumeHook", () => {
179
179
  expect(JSON.parse(output).reason).toContain("--session-id s1");
180
180
  });
181
181
 
182
+ it("#given a goal id with path traversal #when the hook runs #then nothing is written outside the state dir and the resume is denied", () => {
183
+ writeGoals([pendingGoal("../../../escaped-marker")]);
184
+ const outsideDir = join(workDir, ".omo", "ulw-loop");
185
+ const sentinel = join(outsideDir, "escaped-marker.json");
186
+ writeFileSync(sentinel, "sentinel\n");
187
+
188
+ const output = runStopResumeHook(stopPayload());
189
+
190
+ expect(output).toBe("");
191
+ expect(readFileSync(sentinel, "utf8")).toBe("sentinel\n");
192
+ expect(existsSync(join(outsideDir, "escaped-marker.stuck"))).toBe(false);
193
+ expect(existsSync(join(outsideDir, "auto-resume-escaped-marker.json"))).toBe(false);
194
+ });
195
+
196
+ it("#given a normal goal id #when the hook runs past the cap #then the counter and stuck marker are written inside the state dir", () => {
197
+ writeGoals([pendingGoal("goal-abc")]);
198
+
199
+ runStopResumeHook(stopPayload());
200
+ runStopResumeHook(stopPayload());
201
+ const third = runStopResumeHook(stopPayload());
202
+
203
+ expect(third).toBe("");
204
+ const counter = JSON.parse(readFileSync(join(sessionDir(), "auto-resume-goal-abc.json"), "utf8"));
205
+ expect(counter.count).toBe(2);
206
+ expect(readFileSync(join(sessionDir(), "auto-resume-goal-abc.stuck"), "utf8")).toBe(
207
+ "no ledger progress after 2 resumes\n",
208
+ );
209
+ });
210
+
182
211
  it("#given ledger movement between stops #when the hook fires again #then the cap resets", () => {
183
212
  writeGoals([pendingGoal()]);
184
213
 
@@ -0,0 +1,147 @@
1
+ // biome-ignore-all format: compact validation-batch checkpoint tests stay under the pure LOC budget.
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { checkpointUlwLoop } from "../src/checkpoint.js";
5
+ import { readUlwLoopPlan } from "../src/plan-io.js";
6
+ import { criterion, expectCode, goal, plan, repoWith, snapshot } from "./fixtures/checkpoint-builders.js";
7
+ import { qualityGateJson } from "./fixtures/quality-gate-builder.js";
8
+
9
+ const batch = { batchId: "VB001", memberIds: ["G001", "G002"], finalGoalId: "G002" };
10
+
11
+ describe("validation-batch checkpoint enforcement", () => {
12
+ it("#given an open batch member #when completing the batch final goal #then rejects with batch open", async () => {
13
+ const repo = await repoWith(
14
+ plan([
15
+ goal({ id: "G001", status: "pending" }),
16
+ goal({ id: "G002", status: "in_progress" }),
17
+ goal({ id: "G003", status: "pending" }),
18
+ ], { validationBatches: [batch] }),
19
+ );
20
+
21
+ await expectCode(
22
+ () =>
23
+ checkpointUlwLoop(repo, {
24
+ goalId: "G002",
25
+ status: "complete",
26
+ evidence: "batch final implementation done",
27
+ codexGoalJson: snapshot("active"),
28
+ qualityGateJson: "{}",
29
+ }),
30
+ "ULW_LOOP_VALIDATION_BATCH_OPEN",
31
+ );
32
+ });
33
+
34
+ it("#given complete members but no gate #when completing the batch final goal #then requires a batch gate", async () => {
35
+ const repo = await repoWith(
36
+ plan([
37
+ goal({ id: "G001", status: "complete" }),
38
+ goal({ id: "G002", status: "in_progress" }),
39
+ goal({ id: "G003", status: "pending" }),
40
+ ], { validationBatches: [batch] }),
41
+ );
42
+
43
+ await expectCode(
44
+ () =>
45
+ checkpointUlwLoop(repo, {
46
+ goalId: "G002",
47
+ status: "complete",
48
+ evidence: "batch final implementation done",
49
+ codexGoalJson: snapshot("active"),
50
+ }),
51
+ "ULW_LOOP_VALIDATION_BATCH_GATE_REQUIRED",
52
+ );
53
+ });
54
+
55
+ it("#given a member criterion pending #when the batch gate is present #then rejects pending criteria", async () => {
56
+ const repo = await repoWith(
57
+ plan([
58
+ goal({ id: "G001", status: "complete", successCriteria: [criterion("C001", "pass"), criterion("C002", "pending")] }),
59
+ goal({ id: "G002", status: "in_progress", successCriteria: [criterion("C001", "pass"), criterion("C002", "pass")] }),
60
+ goal({ id: "G003", status: "pending" }),
61
+ ], { validationBatches: [batch] }),
62
+ );
63
+
64
+ const gateJson = await qualityGateJson(repo, undefined, "G002");
65
+ await expectCode(
66
+ () =>
67
+ checkpointUlwLoop(repo, {
68
+ goalId: "G002",
69
+ status: "complete",
70
+ evidence: "batch final implementation done",
71
+ codexGoalJson: snapshot("active"),
72
+ qualityGateJson: gateJson,
73
+ }),
74
+ "ULW_LOOP_VALIDATION_BATCH_CRITERIA_PENDING",
75
+ );
76
+ });
77
+
78
+ it("#given all member criteria pass #when coverage counts mismatch #then rejects the gate", async () => {
79
+ const repo = await repoWith(
80
+ plan([
81
+ goal({ id: "G001", status: "complete", successCriteria: [criterion("C001", "pass"), criterion("C002", "pass")] }),
82
+ goal({ id: "G002", status: "in_progress", successCriteria: [criterion("C001", "pass"), criterion("C002", "pass")] }),
83
+ goal({ id: "G003", status: "pending" }),
84
+ ], { validationBatches: [batch] }),
85
+ );
86
+
87
+ const gateJson = await qualityGateJson(repo, undefined, "G002");
88
+ await expectCode(
89
+ () =>
90
+ checkpointUlwLoop(repo, {
91
+ goalId: "G002",
92
+ status: "complete",
93
+ evidence: "batch final implementation done",
94
+ codexGoalJson: snapshot("active"),
95
+ qualityGateJson: gateJson,
96
+ }),
97
+ "ULW_LOOP_VALIDATION_BATCH_GATE_MISMATCH",
98
+ );
99
+ });
100
+
101
+ it("#given batch-final goal is also run-final #when completing it #then closes batch and aggregate", async () => {
102
+ const repo = await repoWith(
103
+ plan([
104
+ goal({ id: "G001", status: "complete", successCriteria: [criterion("C001", "pass")] }),
105
+ goal({ id: "G002", status: "complete", successCriteria: [criterion("C001", "pass")] }),
106
+ goal({ id: "G003", status: "in_progress", successCriteria: [criterion("C001", "pass")] }),
107
+ ], { validationBatches: [{ batchId: "VB002", memberIds: ["G002", "G003"], finalGoalId: "G003" }] }),
108
+ );
109
+
110
+ const gate = JSON.parse(await qualityGateJson(repo, undefined, "G003"));
111
+ gate.criteriaCoverage.totalCriteria = 2;
112
+ gate.criteriaCoverage.passCount = 2;
113
+ const result = await checkpointUlwLoop(repo, {
114
+ goalId: "G003",
115
+ status: "complete",
116
+ evidence: "batch final is also aggregate final",
117
+ codexGoalJson: snapshot("complete"),
118
+ qualityGateJson: JSON.stringify(gate),
119
+ });
120
+
121
+ expect(result.aggregateCompletion?.status).toBe("complete");
122
+ expect((await readUlwLoopPlan(repo)).goals.find((item) => item.id === "G003")?.status).toBe("complete");
123
+ });
124
+
125
+ it("#given all batch checks pass #when completing final member #then appends batch closed", async () => {
126
+ const repo = await repoWith(
127
+ plan([
128
+ goal({ id: "G001", status: "complete", successCriteria: [criterion("C001", "pass")] }),
129
+ goal({ id: "G002", status: "in_progress", successCriteria: [criterion("C001", "pass")] }),
130
+ goal({ id: "G003", status: "pending" }),
131
+ ], { validationBatches: [batch] }),
132
+ );
133
+
134
+ const gate = JSON.parse(await qualityGateJson(repo, undefined, "G002"));
135
+ gate.criteriaCoverage.totalCriteria = 2;
136
+ gate.criteriaCoverage.passCount = 2;
137
+ await checkpointUlwLoop(repo, {
138
+ goalId: "G002",
139
+ status: "complete",
140
+ evidence: "batch final implementation done",
141
+ codexGoalJson: snapshot("active"),
142
+ qualityGateJson: JSON.stringify(gate),
143
+ });
144
+
145
+ expect((await readUlwLoopPlan(repo)).goals.find((item) => item.id === "G002")?.status).toBe("complete");
146
+ });
147
+ });
@@ -0,0 +1,73 @@
1
+ // biome-ignore-all format: compact validation-batch tests stay under the pure LOC budget.
2
+ import { mkdtemp, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ import { createUlwLoopPlan } from "../src/plan-crud.js";
8
+
9
+ async function repo(): Promise<string> {
10
+ return mkdtemp(join(tmpdir(), "ug-validation-batch-"));
11
+ }
12
+
13
+ describe("createUlwLoopPlan validation batches", () => {
14
+ it("#given a valid validation batch #when creating goals #then stores it on the plan", async () => {
15
+ const plan = await createUlwLoopPlan(await repo(), {
16
+ brief: "- Goal alpha\n- Goal beta\n- Goal gamma",
17
+ validationBatchesJson: '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G002-goal-beta"],"finalGoalId":"G002-goal-beta"}]',
18
+ });
19
+
20
+ expect(plan.validationBatches).toEqual([
21
+ { batchId: "VB001", memberIds: ["G001-goal-alpha", "G002-goal-beta"], finalGoalId: "G002-goal-beta" },
22
+ ]);
23
+ });
24
+
25
+ it("#given an unknown member id #when creating goals #then rejects with the member-unknown code", async () => {
26
+ await expect(
27
+ createUlwLoopPlan(await repo(), {
28
+ brief: "- Goal alpha\n- Goal beta",
29
+ validationBatchesJson: '[{"batchId":"VB001","memberIds":["G001-goal-alpha","missing"],"finalGoalId":"G001-goal-alpha"}]',
30
+ }),
31
+ ).rejects.toMatchObject({ code: "ULW_LOOP_VALIDATION_BATCH_MEMBER_UNKNOWN" });
32
+ });
33
+
34
+ it("#given a final id outside members #when creating goals #then rejects with the final-not-member code", async () => {
35
+ await expect(
36
+ createUlwLoopPlan(await repo(), {
37
+ brief: "- Goal alpha\n- Goal beta\n- Goal gamma",
38
+ validationBatchesJson: '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G002-goal-beta"],"finalGoalId":"G003-goal-gamma"}]',
39
+ }),
40
+ ).rejects.toMatchObject({ code: "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER" });
41
+ });
42
+
43
+ it("#given overlapping validation batches #when creating goals #then rejects with the overlap code", async () => {
44
+ await expect(
45
+ createUlwLoopPlan(await repo(), {
46
+ brief: "- Goal alpha\n- Goal beta\n- Goal gamma",
47
+ validationBatchesJson: '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G002-goal-beta"],"finalGoalId":"G002-goal-beta"},{"batchId":"VB002","memberIds":["G002-goal-beta","G003-goal-gamma"],"finalGoalId":"G003-goal-gamma"}]',
48
+ }),
49
+ ).rejects.toMatchObject({ code: "ULW_LOOP_VALIDATION_BATCH_OVERLAP" });
50
+ });
51
+
52
+ it("#given duplicate members #when creating goals #then retains the structural invalid code", async () => {
53
+ await expect(
54
+ createUlwLoopPlan(await repo(), {
55
+ brief: "- Goal alpha\n- Goal beta",
56
+ validationBatchesJson: '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G001-goal-alpha"],"finalGoalId":"G001-goal-alpha"}]',
57
+ }),
58
+ ).rejects.toMatchObject({ code: "ULW_LOOP_VALIDATION_BATCH_INVALID" });
59
+ });
60
+
61
+ it("#given a validation batch file #when creating goals #then reads the batch JSON from disk", async () => {
62
+ const root = await repo();
63
+ const file = join(root, "batches.json");
64
+ await import("node:fs/promises").then((fs) => fs.writeFile(file, '[{"batchId":"VB001","memberIds":["G001-goal-alpha","G002-goal-beta"],"finalGoalId":"G002-goal-beta"}]', "utf8"));
65
+
66
+ const plan = await createUlwLoopPlan(root, {
67
+ brief: "- Goal alpha\n- Goal beta",
68
+ validationBatchesJson: file,
69
+ });
70
+
71
+ expect(JSON.parse(await readFile(join(root, ".omo/ulw-loop/goals.json"), "utf8"))).toHaveProperty("validationBatches", plan.validationBatches);
72
+ });
73
+ });
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook post-compact",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.18.2) Resetting Git Bash MCP Reminder",
10
+ "statusMessage": "(OmO 4.19.1) Resetting Git Bash MCP Reminder",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\git-bash\\dist\\cli.js\" hook post-compact"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lsp/dist/cli.js\" hook post-compact",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.18.2) Resetting LSP Diagnostics Cache",
10
+ "statusMessage": "(OmO 4.19.1) Resetting LSP Diagnostics Cache",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lsp\\dist\\cli.js\" hook post-compact"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook post-compact",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.18.2) Resetting Project Rule Cache",
10
+ "statusMessage": "(OmO 4.19.1) Resetting Project Rule Cache",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook post-compact"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/codegraph/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.18.2) Checking CodeGraph Init Guidance",
10
+ "statusMessage": "(OmO 4.19.1) Checking CodeGraph Init Guidance",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\codegraph\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/comment-checker/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 30,
10
- "statusMessage": "(OmO 4.18.2) Checking Comments",
10
+ "statusMessage": "(OmO 4.19.1) Checking Comments",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\comment-checker\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lsp/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 60,
10
- "statusMessage": "(OmO 4.18.2) Checking LSP Diagnostics",
10
+ "statusMessage": "(OmO 4.19.1) Checking LSP Diagnostics",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lsp\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/teammode/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 4.18.2) Checking Thread Title Hygiene",
11
+ "statusMessage": "(OmO 4.19.1) Checking Thread Title Hygiene",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\teammode\\dist\\cli.js\" hook post-tool-use"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.18.2) Matching Project Rules",
10
+ "statusMessage": "(OmO 4.19.1) Matching Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.18.2) Enforcing Unlimited Goal Budget",
10
+ "statusMessage": "(OmO 4.19.1) Enforcing Unlimited Goal Budget",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use-spawn",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 4.18.2) Guarding Ulw-Loop Spawns",
11
+ "statusMessage": "(OmO 4.19.1) Guarding Ulw-Loop Spawns",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use-spawn"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.18.2) Recommending Git Bash MCP",
10
+ "statusMessage": "(OmO 4.19.1) Recommending Git Bash MCP",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\git-bash\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],