oh-my-opencode 5.0.0-beta.5 → 5.0.0-beta.50

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 (952) hide show
  1. package/.agents/command/publish.md +44 -16
  2. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
  3. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +1 -1
  4. package/.agents/skills/pre-publish-review/SKILL.md +9 -12
  5. package/.agents/skills/publish/SKILL.md +44 -16
  6. package/.agents/skills/senpi-qa/SKILL.md +80 -0
  7. package/.agents/skills/senpi-qa/scripts/resolve-evidence-dir.mjs +65 -0
  8. package/.agents/skills/senpi-qa/scripts/resolve-evidence-dir.test.mjs +171 -0
  9. package/.agents/skills/tech-debt-audit/SKILL.md +6 -76
  10. package/.agents/skills/work-with-pr/SKILL.md +37 -23
  11. package/.opencode/command/publish.md +44 -16
  12. package/.opencode/skills/pre-publish-review/SKILL.md +9 -12
  13. package/.opencode/skills/work-with-pr/SKILL.md +37 -23
  14. package/README.ja.md +58 -46
  15. package/README.ko.md +60 -48
  16. package/README.md +58 -58
  17. package/README.ru.md +55 -46
  18. package/README.zh-cn.md +54 -46
  19. package/THIRD-PARTY-NOTICES.md +27 -99
  20. package/dist/agents/atlas/agent.d.ts +0 -1
  21. package/dist/agents/gpt-prompt-identity.d.ts +1 -1
  22. package/dist/agents/sisyphus/grok-4.d.ts +20 -0
  23. package/dist/agents/sisyphus/index.d.ts +2 -0
  24. package/dist/agents/sisyphus-agent-config.d.ts +6 -0
  25. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  26. package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +15 -4
  27. package/dist/agents/types.d.ts +4 -2
  28. package/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
  29. package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
  30. package/dist/cli/fallback-lane-policy.d.ts +1 -0
  31. package/dist/cli/index.js +51536 -59608
  32. package/dist/cli/install-codex/install-codex-test-fixtures.d.ts +1 -1
  33. package/dist/cli/run/on-complete-hook.d.ts +2 -0
  34. package/dist/cli/runtime-commands.d.ts +1 -1
  35. package/dist/cli/worktree-sweep/classify.d.ts +16 -0
  36. package/dist/cli/worktree-sweep/format.d.ts +8 -0
  37. package/dist/cli/worktree-sweep/git.d.ts +19 -0
  38. package/dist/cli/worktree-sweep/index.d.ts +7 -0
  39. package/dist/cli/worktree-sweep/options.d.ts +2 -0
  40. package/dist/cli/worktree-sweep/parse-worktree-list.d.ts +8 -0
  41. package/dist/cli/worktree-sweep/sweep.d.ts +2 -0
  42. package/dist/cli/worktree-sweep/types.d.ts +65 -0
  43. package/dist/cli/worktree-sweep/worktree-sweep.d.ts +2 -0
  44. package/dist/cli-node/index.js +51554 -59603
  45. package/dist/config/index.d.ts +1 -1
  46. package/dist/config/schema/background-task.d.ts +1 -0
  47. package/dist/config/schema/commands.d.ts +1 -1
  48. package/dist/config/schema/hooks.d.ts +1 -2
  49. package/dist/config/schema/oh-my-opencode-config.d.ts +5 -11
  50. package/dist/config/schema/ulw-execute.d.ts +5 -0
  51. package/dist/config/schema.d.ts +0 -1
  52. package/dist/config/validate.d.ts +2 -0
  53. package/dist/create-hooks.d.ts +3 -2
  54. package/dist/features/background-agent/compaction-aware-message-resolver.d.ts +1 -0
  55. package/dist/features/background-agent/constants.d.ts +0 -7
  56. package/dist/features/background-agent/error-classifier.d.ts +1 -0
  57. package/dist/features/background-agent/manager.d.ts +2 -1
  58. package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
  59. package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
  60. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
  61. package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
  62. package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
  63. package/dist/features/btw-side/btw-command-draft.d.ts +7 -0
  64. package/dist/features/btw-side/context-injector.d.ts +15 -0
  65. package/dist/features/btw-side/index.d.ts +8 -0
  66. package/dist/features/btw-side/metadata.d.ts +15 -0
  67. package/dist/features/btw-side/parent-context-budget.d.ts +9 -0
  68. package/dist/features/btw-side/server-session-registry.d.ts +8 -0
  69. package/dist/features/btw-side/tui-adoption-cache.d.ts +13 -0
  70. package/dist/features/btw-side/tui-adoption-guard.d.ts +5 -0
  71. package/dist/features/btw-side/tui-controller-types.d.ts +68 -0
  72. package/dist/features/btw-side/tui-controller.d.ts +19 -0
  73. package/dist/features/btw-side/tui-escape-return.d.ts +12 -0
  74. package/dist/features/btw-side/tui-keymap.d.ts +17 -0
  75. package/dist/features/btw-side/tui-parent-validator.d.ts +6 -0
  76. package/dist/features/btw-side/tui-picker-options.d.ts +20 -0
  77. package/dist/features/btw-side/tui-picker.d.ts +9 -0
  78. package/dist/features/btw-side/tui-prompt-queue.d.ts +8 -0
  79. package/dist/features/btw-side/tui-session-bridge.d.ts +11 -0
  80. package/dist/features/btw-side/tui-session-catalog.d.ts +35 -0
  81. package/dist/features/btw-side/tui-side-removal.d.ts +11 -0
  82. package/dist/features/btw-side/tui-side-start.d.ts +9 -0
  83. package/dist/features/btw-side/tui-wiring.d.ts +8 -0
  84. package/dist/features/builtin-commands/templates/ulw-execute.d.ts +1 -0
  85. package/dist/features/builtin-commands/types.d.ts +1 -1
  86. package/dist/features/monitor/manager-internals.d.ts +1 -1
  87. package/dist/features/monitor/output-injector-types.d.ts +1 -1
  88. package/dist/features/opengateway-provider/index.d.ts +13 -0
  89. package/dist/features/task-toast-manager/manager.d.ts +0 -24
  90. package/dist/features/team-mode/tools/messaging-fallback-wake.d.ts +17 -0
  91. package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +3 -0
  92. package/dist/features/team-mode/tools/messaging-runtime.d.ts +7 -0
  93. package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
  94. package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
  95. package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
  96. package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
  97. package/dist/hooks/atlas/atlas-hook.d.ts +1 -0
  98. package/dist/hooks/atlas/atlas-lifecycle-store.d.ts +50 -0
  99. package/dist/hooks/atlas/event-handler.d.ts +1 -0
  100. package/dist/hooks/atlas/final-wave-approval-gate.test-support.d.ts +50 -0
  101. package/dist/hooks/atlas/system-reminder-templates.d.ts +0 -1
  102. package/dist/hooks/atlas/tool-execute-before.d.ts +3 -0
  103. package/dist/hooks/atlas/types.d.ts +1 -0
  104. package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
  105. package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
  106. package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
  107. package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
  108. package/dist/hooks/compaction-context-injector/validated-model.d.ts +2 -0
  109. package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
  110. package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
  111. package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
  112. package/dist/hooks/index.d.ts +1 -2
  113. package/dist/hooks/keyword-detector/hook.d.ts +7 -0
  114. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
  115. package/dist/hooks/model-fallback/hook.d.ts +1 -1
  116. package/dist/hooks/prometheus-md-only/agent-resolution.d.ts +2 -2
  117. package/dist/hooks/runtime-fallback/chat-message-handler.d.ts +2 -0
  118. package/dist/hooks/runtime-fallback/retry-model-payload.d.ts +1 -0
  119. package/dist/hooks/runtime-fallback/runtime-model-settings.d.ts +6 -0
  120. package/dist/hooks/runtime-fallback/session-status-handler.d.ts +1 -1
  121. package/dist/hooks/runtime-fallback/types.d.ts +3 -1
  122. package/dist/hooks/sisyphus-junior-notepad/constants.d.ts +1 -1
  123. package/dist/hooks/todo-continuation-enforcer/types.d.ts +1 -0
  124. package/dist/hooks/{start-work → ulw-execute}/context-info-builder.d.ts +1 -1
  125. package/dist/hooks/{start-work → ulw-execute}/index.d.ts +1 -1
  126. package/dist/hooks/ulw-execute/ulw-execute-hook.d.ts +23 -0
  127. package/dist/index.js +28955 -25346
  128. package/dist/mcp/index.d.ts +0 -4
  129. package/dist/mcp/types.d.ts +0 -1
  130. package/dist/oh-my-opencode.schema.json +21 -47
  131. package/dist/plugin/chat-message/types.d.ts +3 -3
  132. package/dist/plugin/chat-message/ulw-execute-message.d.ts +5 -0
  133. package/dist/plugin/event-session-lifecycle.d.ts +1 -0
  134. package/dist/plugin/hooks/create-core-hooks.d.ts +2 -2
  135. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -3
  136. package/dist/plugin/hooks/create-transform-hooks.d.ts +2 -0
  137. package/dist/plugin/messages-transform.d.ts +1 -0
  138. package/dist/plugin-dispose.d.ts +3 -0
  139. package/dist/shared/agent-tool-restrictions.d.ts +0 -1
  140. package/dist/shared/compaction-agent-config-checkpoint.d.ts +1 -0
  141. package/dist/shared/index.d.ts +2 -0
  142. package/dist/shared/legacy-plugin-warning.d.ts +2 -1
  143. package/dist/shared/normalize-sdk-response.d.ts +1 -0
  144. package/dist/shared/omo-process-sweep.d.ts +2 -4
  145. package/dist/shared/plugin-entry-migrator.d.ts +4 -3
  146. package/dist/shared/plugin-entry-shape.d.ts +6 -0
  147. package/dist/shared/runtime-fallback-retry-marker.d.ts +16 -0
  148. package/dist/shared/shell-env.d.ts +1 -1
  149. package/dist/skills/ast-grep/AGENTS.md +51 -0
  150. package/dist/skills/ast-grep/SKILL.md +1 -1
  151. package/dist/skills/coding-agent-sessions/AGENTS.md +62 -0
  152. package/dist/skills/coding-agent-sessions/SKILL.md +3 -2
  153. package/dist/skills/coding-agent-sessions/references/all-platforms.md +1 -1
  154. package/dist/skills/coding-agent-sessions/references/senpi.md +4 -4
  155. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
  156. package/dist/skills/data-scientist/SKILL.md +99 -239
  157. package/dist/skills/data-scientist/references/execution-surfaces.md +91 -0
  158. package/dist/skills/data-scientist/references/placement.md +74 -0
  159. package/dist/skills/data-scientist/references/polars-lane.md +95 -0
  160. package/dist/skills/data-scientist/references/uv-setup.md +1 -1
  161. package/dist/skills/data-scientist/references/visualization.md +64 -0
  162. package/dist/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  163. package/dist/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  164. package/dist/skills/debugging/SKILL.md +3 -1
  165. package/dist/skills/debugging/references/methodology/00-setup.md +12 -0
  166. package/dist/skills/debugging/references/methodology/02-investigate.md +21 -0
  167. package/dist/skills/debugging/references/runtimes/go.md +15 -1
  168. package/dist/skills/debugging/references/runtimes/native-binary.md +38 -0
  169. package/dist/skills/debugging/references/runtimes/node.md +25 -0
  170. package/dist/skills/debugging/references/runtimes/python.md +13 -0
  171. package/dist/skills/debugging/references/runtimes/rust.md +41 -0
  172. package/dist/skills/debugging/references/scripts/dap.mjs +267 -0
  173. package/dist/skills/debugging/references/scripts/dap.test.ts +86 -0
  174. package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  175. package/dist/skills/debugging/references/tools/dap.md +103 -0
  176. package/dist/skills/debugging/references/tools/frida.md +193 -0
  177. package/dist/skills/frontend/SKILL.md +12 -8
  178. package/dist/skills/frontend/references/design/_INDEX.md +1 -0
  179. package/dist/skills/frontend/references/design/print-paged-media.md +76 -0
  180. package/dist/skills/frontend/references/design/stylegallery.md +80 -0
  181. package/dist/skills/frontend/references/designpowers/README.md +1 -1
  182. package/dist/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  183. package/dist/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  184. package/dist/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  185. package/dist/skills/frontend/references/designpowers/orchestration.md +1 -1
  186. package/dist/skills/frontend/references/designpowers/routing.md +5 -5
  187. package/dist/skills/git-master/SKILL.md +1 -1
  188. package/dist/skills/init-deep/SKILL.md +14 -14
  189. package/dist/skills/lsp-setup/SKILL.md +1 -1
  190. package/dist/skills/programming/SKILL.md +1 -1
  191. package/dist/skills/refactor/SKILL.md +1 -1
  192. package/dist/skills/remove-ai-slops/SKILL.md +1 -1
  193. package/dist/skills/review-work/SKILL.md +100 -418
  194. package/dist/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
  195. package/dist/skills/ultimate-browsing/SKILL.md +26 -9
  196. package/dist/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  197. package/dist/skills/ultimate-browsing/engine/templates/package.json +1 -1
  198. package/dist/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
  199. package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  200. package/dist/skills/{start-work → ulw-execute}/SKILL.md +17 -16
  201. package/dist/skills/ulw-plan/SKILL.md +7 -8
  202. package/dist/skills/ulw-plan/references/full-workflow.md +31 -7
  203. package/dist/skills/ulw-plan/references/intent-clear.md +2 -1
  204. package/dist/skills/ulw-plan/references/intent-unclear.md +5 -5
  205. package/dist/skills/ulw-research/SKILL.md +8 -3
  206. package/dist/skills/visual-qa/AGENTS.md +58 -0
  207. package/dist/skills/visual-qa/SKILL.md +4 -3
  208. package/dist/tools/delegate-task/builtin-categories.d.ts +3 -1
  209. package/dist/tools/delegate-task/builtin-category-definition.d.ts +2 -1
  210. package/dist/tools/delegate-task/constants.d.ts +2 -2
  211. package/dist/tools/delegate-task/openai-categories.d.ts +7 -2
  212. package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
  213. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
  214. package/dist/tools/delegate-task/tool-description.d.ts +3 -1
  215. package/dist/tools/delegate-task/types.d.ts +4 -0
  216. package/dist/tools/session-manager/directory-filter.d.ts +2 -0
  217. package/dist/tui.d.ts +12 -0
  218. package/dist/tui.js +37066 -46704
  219. package/package.json +47 -41
  220. package/packages/git-bash-mcp/dist/cli.js +6 -1
  221. package/packages/git-bash-mcp/package.json +1 -1
  222. package/packages/lsp-core/src/index.ts +1 -0
  223. package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +44 -0
  224. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +57 -48
  225. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  226. package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
  227. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +163 -13
  228. package/packages/lsp-core/src/lsp/client-wrapper.ts +110 -23
  229. package/packages/lsp-core/src/lsp/client.ts +32 -3
  230. package/packages/lsp-core/src/lsp/connection.ts +16 -0
  231. package/packages/lsp-core/src/lsp/constants.ts +6 -0
  232. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
  233. package/packages/lsp-core/src/lsp/errors.ts +15 -0
  234. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
  235. package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
  236. package/packages/lsp-core/src/lsp/format-document.ts +90 -0
  237. package/packages/lsp-core/src/lsp/json-rpc-connection.ts +11 -6
  238. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  239. package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
  240. package/packages/lsp-core/src/lsp/manager.ts +138 -12
  241. package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
  242. package/packages/lsp-core/src/lsp/server-definitions.ts +29 -0
  243. package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
  244. package/packages/lsp-core/src/lsp/server-installation.ts +200 -27
  245. package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
  246. package/packages/lsp-core/src/lsp/server-resolution.ts +18 -5
  247. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  248. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  249. package/packages/lsp-core/src/lsp/transport.ts +16 -2
  250. package/packages/lsp-core/src/lsp/types.ts +8 -0
  251. package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +29 -1
  252. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
  253. package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
  254. package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +20 -1
  255. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
  256. package/packages/lsp-core/src/mcp.ts +3 -0
  257. package/packages/lsp-core/src/request-context.test.ts +29 -0
  258. package/packages/lsp-core/src/request-context.ts +1 -1
  259. package/packages/lsp-core/src/tool-surface.test.ts +13 -1
  260. package/packages/lsp-core/src/tools/definitions.ts +14 -0
  261. package/packages/lsp-core/src/tools/diagnostics.ts +3 -3
  262. package/packages/lsp-core/src/tools/format.test.ts +56 -0
  263. package/packages/lsp-core/src/tools/format.ts +56 -0
  264. package/packages/lsp-core/src/tools/index.ts +1 -0
  265. package/packages/lsp-core/src/tools/navigation.ts +4 -2
  266. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  267. package/packages/lsp-core/src/tools/rename.ts +4 -2
  268. package/packages/lsp-core/src/tools/symbols.ts +1 -1
  269. package/packages/lsp-core/src/tools/types.ts +10 -0
  270. package/packages/lsp-daemon/dist/cli.js +961 -483
  271. package/packages/lsp-daemon/dist/client.d.ts +10 -0
  272. package/packages/lsp-daemon/dist/client.js +976 -495
  273. package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
  274. package/packages/lsp-daemon/dist/daemon-client.js +3 -0
  275. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +6 -1
  276. package/packages/lsp-daemon/dist/ensure-daemon.js +10 -2
  277. package/packages/lsp-daemon/dist/index.js +881 -405
  278. package/packages/lsp-daemon/dist/proxy.js +3 -0
  279. package/packages/lsp-daemon/dist/version-reap.js +1 -1
  280. package/packages/lsp-daemon/package.json +1 -1
  281. package/packages/lsp-tools-mcp/dist/cli.js +820 -345
  282. package/packages/lsp-tools-mcp/dist/lsp/manager.js +272 -138
  283. package/packages/lsp-tools-mcp/dist/mcp.js +845 -372
  284. package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
  285. package/packages/lsp-tools-mcp/dist/tools.js +775 -306
  286. package/packages/lsp-tools-mcp/package.json +1 -1
  287. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +4 -21
  288. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +4 -6
  289. package/packages/omo-codex/plugin/.mcp.json +0 -6
  290. package/packages/omo-codex/plugin/AGENTS.md +47 -0
  291. package/packages/omo-codex/plugin/README.md +6 -2
  292. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +193 -148
  293. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  294. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  295. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -0
  296. package/packages/omo-codex/plugin/components/comment-checker/AGENTS.md +9 -0
  297. package/packages/omo-codex/plugin/components/comment-checker/biome.json +2 -2
  298. package/packages/omo-codex/plugin/components/comment-checker/dist/cli.js +2 -1
  299. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  300. package/packages/omo-codex/plugin/components/comment-checker/package.json +5 -5
  301. package/packages/omo-codex/plugin/components/comment-checker/src/apply-patch.ts +2 -8
  302. package/packages/omo-codex/plugin/components/comment-checker/src/core.ts +1 -2
  303. package/packages/omo-codex/plugin/components/comment-checker/src/request-extractor.ts +1 -1
  304. package/packages/omo-codex/plugin/components/comment-checker/src/runner.ts +1 -0
  305. package/packages/omo-codex/plugin/components/git-bash/AGENTS.md +1 -1
  306. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  307. package/packages/omo-codex/plugin/components/git-bash/package.json +3 -4
  308. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/AGENTS.md +2 -2
  309. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +2 -2
  310. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/directive.md +2 -0
  311. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/cli.js +36 -29
  312. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/codex-hook.js +32 -26
  313. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/types.d.ts +3 -0
  314. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  315. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +5 -5
  316. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/codex-hook.ts +32 -28
  317. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/types.ts +3 -0
  318. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/cli.test.ts +1 -1
  319. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +137 -28
  320. package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +4 -2
  321. package/packages/omo-codex/plugin/components/lsp/AGENTS.md +30 -15
  322. package/packages/omo-codex/plugin/components/lsp/biome.json +2 -2
  323. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
  324. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +999 -544
  325. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +1 -1
  326. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  327. package/packages/omo-codex/plugin/components/lsp/package.json +5 -5
  328. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +1 -1
  329. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +1 -1
  330. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +1 -5
  331. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +6 -7
  332. package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +1 -2
  333. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +11 -1
  334. package/packages/omo-codex/plugin/components/rules/AGENTS.md +12 -0
  335. package/packages/omo-codex/plugin/components/rules/README.md +2 -0
  336. package/packages/omo-codex/plugin/components/rules/biome.json +2 -2
  337. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +123 -0
  338. package/packages/omo-codex/plugin/components/rules/dist/cli.js +91 -68
  339. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  340. package/packages/omo-codex/plugin/components/rules/package.json +7 -7
  341. package/packages/omo-codex/plugin/components/rules/src/config.ts +1 -2
  342. package/packages/omo-codex/plugin/components/rules/src/persistent-cache.ts +1 -2
  343. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +15 -5
  344. package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +1 -4
  345. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -9
  346. package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
  347. package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +17 -25
  348. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
  349. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
  350. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
  351. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
  352. package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
  353. package/packages/omo-codex/plugin/components/rules/test/dynamic-target-fingerprints.test.ts +1 -2
  354. package/packages/omo-codex/plugin/components/rules/test/engine.test.ts +7 -4
  355. package/packages/omo-codex/plugin/components/rules/test/finder.test.ts +2 -3
  356. package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +39 -72
  357. package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +38 -3
  358. package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
  359. package/packages/omo-codex/plugin/components/rules/test/matcher.test.ts +2 -3
  360. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +1 -1
  361. package/packages/omo-codex/plugin/components/rules/test/parser.test.ts +1 -2
  362. package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +29 -6
  363. package/packages/omo-codex/plugin/components/rules/test/rules-engine-consumption.test.ts +3 -6
  364. package/packages/omo-codex/plugin/components/rules/test/scanner.test.ts +1 -2
  365. package/packages/omo-codex/plugin/components/rules/test/sources.test.ts +2 -5
  366. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +2 -17
  367. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  368. package/packages/omo-codex/plugin/components/teammode/package.json +5 -5
  369. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +4 -10
  370. package/packages/omo-codex/plugin/components/telemetry/AGENTS.md +8 -0
  371. package/packages/omo-codex/plugin/components/telemetry/biome.json +2 -2
  372. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +2435 -700
  373. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2439 -705
  374. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  375. package/packages/omo-codex/plugin/components/telemetry/package.json +5 -5
  376. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +1 -4
  377. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -14
  378. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +1 -1
  379. package/packages/omo-codex/plugin/components/telemetry/test/diagnostics.test.ts +2 -5
  380. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -4
  381. package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -0
  382. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  383. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
  384. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
  385. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
  386. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
  387. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +1 -1
  388. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-low.toml +1 -1
  389. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +1 -1
  390. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +1 -1
  391. package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +1 -1
  392. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +1 -1
  393. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +1 -1
  394. package/packages/omo-codex/plugin/components/ultrawork/biome.json +2 -2
  395. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +490 -4
  396. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  397. package/packages/omo-codex/plugin/components/ultrawork/package.json +5 -6
  398. package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
  399. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +6 -7
  400. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +31 -7
  401. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -1
  402. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +5 -5
  403. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
  404. package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
  405. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +9 -1
  406. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
  407. package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
  408. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
  409. package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
  410. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/AGENTS.md +4 -4
  411. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/NOTICE +1 -1
  412. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/README.md +6 -6
  413. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/biome.json +2 -2
  414. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/directive.md +10 -10
  415. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/dist/cli.js +16 -16
  416. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +16 -0
  417. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/package.json +12 -12
  418. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/boulder-reader.ts +1 -1
  419. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/cli.ts +1 -1
  420. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/codex-hook.ts +4 -4
  421. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/directive.ts +1 -1
  422. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/index.ts +1 -1
  423. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/types.ts +1 -1
  424. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/boulder-reader.test.ts +3 -3
  425. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/cli.test.ts +4 -11
  426. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/codex-hook.test.ts +24 -22
  427. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +19 -0
  428. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +1 -1
  429. package/packages/omo-codex/plugin/components/ulw-loop/README.md +12 -1
  430. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +2 -2
  431. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +26 -13
  432. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
  433. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
  434. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
  435. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +15 -0
  436. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +15 -1
  437. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
  438. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
  439. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +40 -39
  440. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +21 -8
  441. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
  442. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +12 -1
  443. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
  444. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +1264 -429
  445. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  446. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +28 -8
  447. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
  448. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +15 -1
  449. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -0
  450. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +1 -0
  451. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
  452. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
  453. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +13 -4
  454. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +1 -0
  455. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +34 -4
  456. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +14 -0
  457. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +29 -0
  458. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +7 -0
  459. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +9 -0
  460. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +12 -0
  461. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +104 -0
  462. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
  463. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +65 -9
  464. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
  465. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  466. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +72 -117
  467. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
  468. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +6 -1
  469. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +189 -29
  470. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +9 -0
  471. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +227 -0
  472. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
  473. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
  474. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +20 -6
  475. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +19 -0
  476. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +75 -0
  477. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +17 -4
  478. package/packages/omo-codex/plugin/components/ulw-loop/package.json +6 -6
  479. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +6 -4
  480. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +108 -0
  481. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +32 -8
  482. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
  483. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
  484. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +31 -1
  485. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +128 -0
  486. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +38 -71
  487. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +24 -8
  488. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +14 -1
  489. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
  490. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +8 -6
  491. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +35 -7
  492. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +26 -1
  493. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts +3 -1
  494. package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +1 -0
  495. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
  496. package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +25 -4
  497. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +39 -4
  498. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +38 -0
  499. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +11 -0
  500. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +123 -0
  501. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +71 -15
  502. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
  503. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +92 -136
  504. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
  505. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +200 -28
  506. package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +239 -0
  507. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
  508. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +19 -6
  509. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +107 -0
  510. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
  511. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +132 -1
  512. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +147 -0
  513. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +4 -2
  514. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint.test.ts +4 -2
  515. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-commands.test.ts +2 -0
  516. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-complete-goals.test.ts +3 -1
  517. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +35 -8
  518. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +38 -4
  519. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +2 -0
  520. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-scope-required.test.ts +144 -0
  521. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +185 -0
  522. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +8 -3
  523. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +6 -0
  524. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +37 -2
  525. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +18 -0
  526. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
  527. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/cli-session.ts +4 -0
  528. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +9 -4
  529. package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
  530. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +5 -37
  531. package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +9 -0
  532. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io-cross-process.test.ts +106 -0
  533. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +36 -2
  534. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
  535. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
  536. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-fields-messages.test.ts +98 -0
  537. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-lazycodex-surface.test.ts +93 -0
  538. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
  539. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +51 -2
  540. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
  541. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
  542. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +58 -3
  543. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +423 -6
  544. package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +218 -0
  545. package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +8 -5
  546. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +1 -1
  547. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
  548. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +13 -8
  549. package/packages/omo-codex/plugin/components/ulw-loop/vitest.config.ts +1 -0
  550. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  551. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  552. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  553. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  554. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  555. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  556. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  557. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +18 -0
  558. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  559. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  560. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  561. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  562. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  563. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  564. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  565. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +17 -0
  566. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  567. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  568. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  569. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  570. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  571. package/packages/omo-codex/plugin/model-catalog.json +16 -7
  572. package/packages/omo-codex/plugin/package-lock.json +676 -524
  573. package/packages/omo-codex/plugin/package.json +2 -3
  574. package/packages/omo-codex/plugin/scripts/AGENTS.md +39 -0
  575. package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +1 -2
  576. package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
  577. package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
  578. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
  579. package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +16 -7
  580. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +8 -4
  581. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +18 -90
  582. package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +1 -1
  583. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +94 -55
  584. package/packages/omo-codex/plugin/shared/package.json +3 -4
  585. package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
  586. package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
  587. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
  588. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +51 -0
  589. package/packages/omo-codex/plugin/skills/ast-grep/SKILL.md +1 -1
  590. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +62 -0
  591. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +3 -2
  592. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +1 -1
  593. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +4 -4
  594. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
  595. package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
  596. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
  597. package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
  598. package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
  599. package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
  600. package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
  601. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  602. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  603. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +3 -1
  604. package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
  605. package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
  606. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
  607. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
  608. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
  609. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
  610. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
  611. package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
  612. package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  613. package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
  614. package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
  615. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +12 -8
  616. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +1 -0
  617. package/packages/omo-codex/plugin/skills/frontend/references/design/print-paged-media.md +76 -0
  618. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +80 -0
  619. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/README.md +1 -1
  620. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  621. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  622. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  623. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/orchestration.md +1 -1
  624. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/routing.md +5 -5
  625. package/packages/omo-codex/plugin/skills/git-master/SKILL.md +1 -1
  626. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +18 -16
  627. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +4 -2
  628. package/packages/omo-codex/plugin/skills/lsp-setup/SKILL.md +1 -1
  629. package/packages/omo-codex/plugin/skills/programming/SKILL.md +1 -1
  630. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +5 -3
  631. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +5 -3
  632. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +114 -422
  633. package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
  634. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +26 -9
  635. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  636. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +1 -1
  637. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
  638. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  639. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +26 -13
  640. package/packages/omo-codex/plugin/skills/{start-work → ulw-execute}/SKILL.md +26 -28
  641. package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
  642. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +30 -4
  643. package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +108 -0
  644. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +32 -8
  645. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +6 -7
  646. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +31 -7
  647. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -1
  648. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +5 -5
  649. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +12 -5
  650. package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +58 -0
  651. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +8 -5
  652. package/packages/omo-codex/plugin/test/AGENTS.md +47 -0
  653. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
  654. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +16 -51
  655. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -3
  656. package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
  657. package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +14 -4
  658. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
  659. package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
  660. package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
  661. package/packages/omo-codex/plugin/test/auto-update.test.mjs +4 -29
  662. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
  663. package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
  664. package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +2 -2
  665. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -51
  666. package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +88 -2
  667. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +5 -6
  668. package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
  669. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
  670. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +35 -35
  671. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +1 -1
  672. package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
  673. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +107 -15
  674. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +168 -0
  675. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +54 -24
  676. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +18 -119
  677. package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
  678. package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
  679. package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
  680. package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
  681. package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
  682. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -5
  683. package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
  684. package/packages/omo-codex/scripts/AGENTS.md +36 -0
  685. package/packages/omo-codex/scripts/check-model-catalog-parity.mjs +42 -0
  686. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
  687. package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +1 -1
  688. package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +2 -1
  689. package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
  690. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +5 -5
  691. package/packages/omo-codex/scripts/install-config.test.mjs +15 -17
  692. package/packages/omo-codex/scripts/install-dist/install-local.mjs +3028 -1392
  693. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +3 -4
  694. package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
  695. package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +7 -2
  696. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
  697. package/packages/omo-codex/scripts/install-packaged-local.test.mjs +1 -6
  698. package/packages/{omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md → prompts-core/prompts/ultrawork/codex.md} +26 -20
  699. package/packages/shared-skills/package.json +7 -1
  700. package/packages/shared-skills/skill-source-filter.d.ts +16 -0
  701. package/packages/shared-skills/skill-source-filter.mjs +44 -0
  702. package/packages/shared-skills/skills/ast-grep/AGENTS.md +51 -0
  703. package/packages/shared-skills/skills/ast-grep/SKILL.md +1 -1
  704. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +62 -0
  705. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +3 -2
  706. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +1 -1
  707. package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +4 -4
  708. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
  709. package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
  710. package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
  711. package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
  712. package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
  713. package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
  714. package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
  715. package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  716. package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  717. package/packages/shared-skills/skills/debugging/SKILL.md +3 -1
  718. package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
  719. package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
  720. package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
  721. package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
  722. package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
  723. package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
  724. package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
  725. package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
  726. package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
  727. package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  728. package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
  729. package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
  730. package/packages/shared-skills/skills/frontend/SKILL.md +12 -8
  731. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +1 -0
  732. package/packages/shared-skills/skills/frontend/references/design/print-paged-media.md +76 -0
  733. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +80 -0
  734. package/packages/shared-skills/skills/frontend/references/designpowers/README.md +1 -1
  735. package/packages/shared-skills/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  736. package/packages/shared-skills/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  737. package/packages/shared-skills/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  738. package/packages/shared-skills/skills/frontend/references/designpowers/orchestration.md +1 -1
  739. package/packages/shared-skills/skills/frontend/references/designpowers/routing.md +5 -5
  740. package/packages/shared-skills/skills/git-master/SKILL.md +1 -1
  741. package/packages/shared-skills/skills/init-deep/SKILL.md +14 -14
  742. package/packages/shared-skills/skills/lsp-setup/SKILL.md +1 -1
  743. package/packages/shared-skills/skills/programming/SKILL.md +1 -1
  744. package/packages/shared-skills/skills/refactor/SKILL.md +1 -1
  745. package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +1 -1
  746. package/packages/shared-skills/skills/review-work/SKILL.md +100 -418
  747. package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
  748. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +26 -9
  749. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  750. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +1 -1
  751. package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
  752. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  753. package/packages/shared-skills/skills/{start-work → ulw-execute}/SKILL.md +17 -16
  754. package/packages/shared-skills/skills/ulw-plan/SKILL.md +7 -8
  755. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +31 -7
  756. package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -1
  757. package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +5 -5
  758. package/packages/shared-skills/skills/ulw-research/SKILL.md +8 -3
  759. package/packages/shared-skills/skills/visual-qa/AGENTS.md +58 -0
  760. package/packages/shared-skills/skills/visual-qa/SKILL.md +4 -3
  761. package/postinstall.mjs +2 -0
  762. package/script/qa/web-terminal-visual-qa.mjs +2 -0
  763. package/script/qa/xterm-live-terminal.mjs +18 -2
  764. package/dist/config/schema/codegraph.d.ts +0 -12
  765. package/dist/config/schema/start-work.d.ts +0 -5
  766. package/dist/features/builtin-commands/templates/start-work.d.ts +0 -1
  767. package/dist/features/builtin-skills/index.d.ts +0 -1
  768. package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
  769. package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
  770. package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
  771. package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
  772. package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
  773. package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
  774. package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
  775. package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
  776. package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
  777. package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
  778. package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
  779. package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
  780. package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
  781. package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
  782. package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
  783. package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
  784. package/dist/features/builtin-skills/skills/index.d.ts +0 -1
  785. package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
  786. package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
  787. package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
  788. package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
  789. package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
  790. package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
  791. package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
  792. package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
  793. package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
  794. package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
  795. package/dist/features/builtin-skills/skills.d.ts +0 -1
  796. package/dist/features/builtin-skills/types.d.ts +0 -1
  797. package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
  798. package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
  799. package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
  800. package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
  801. package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
  802. package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
  803. package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
  804. package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
  805. package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
  806. package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
  807. package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
  808. package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
  809. package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
  810. package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
  811. package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
  812. package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
  813. package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
  814. package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
  815. package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
  816. package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
  817. package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
  818. package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
  819. package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
  820. package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
  821. package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
  822. package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
  823. package/dist/hooks/start-work/start-work-hook.d.ts +0 -23
  824. package/dist/mcp/codegraph.d.ts +0 -16
  825. package/dist/plugin/chat-message/start-work-message.d.ts +0 -5
  826. package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
  827. package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
  828. package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
  829. package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
  830. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
  831. package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
  832. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
  833. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
  834. package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
  835. package/dist/shared/prompt-async-gate/types.d.ts +0 -1
  836. package/dist/skills/data-scientist/references/common-scenarios.md +0 -176
  837. package/dist/skills/data-scientist/references/execution-templates.md +0 -197
  838. package/dist/skills/data-scientist/references/integration-patterns.md +0 -153
  839. package/dist/skills/data-scientist/references/performance-benchmarks.md +0 -37
  840. package/dist/tools/call-omo-agent/background-agent-executor.d.ts +0 -5
  841. package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
  842. package/dist/tools/delegate-task/anthropic-categories.d.ts +0 -2
  843. package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
  844. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  845. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  846. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  847. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  848. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
  849. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
  850. package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
  851. package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
  852. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12136
  853. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9734
  854. package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
  855. package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
  856. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
  857. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
  858. package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
  859. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
  860. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
  861. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -309
  862. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -80
  863. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
  864. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
  865. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
  866. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
  867. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
  868. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -21
  869. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
  870. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
  871. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
  872. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
  873. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
  874. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
  875. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
  876. package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
  877. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
  878. package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
  879. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
  880. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
  881. package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
  882. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
  883. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
  884. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
  885. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
  886. package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
  887. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
  888. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
  889. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
  890. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
  891. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
  892. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
  893. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
  894. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
  895. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
  896. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
  897. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
  898. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
  899. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
  900. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
  901. package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
  902. package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
  903. package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
  904. package/packages/omo-codex/plugin/components/start-work-continuation/LICENSE +0 -21
  905. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +0 -28
  906. package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -471
  907. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
  908. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
  909. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +0 -17
  910. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +0 -17
  911. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
  912. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
  913. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
  914. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
  915. package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
  916. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
  917. package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
  918. package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
  919. package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +0 -2
  920. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
  921. package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
  922. package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
  923. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
  924. package/packages/omo-codex/plugin/test/ulw-plan-scope-contract.test.mjs +0 -24
  925. package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
  926. package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
  927. package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
  928. package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
  929. /package/dist/hooks/{start-work → ulw-execute}/context-info-formatters.d.ts +0 -0
  930. /package/dist/hooks/{start-work → ulw-execute}/explicit-plan-context.d.ts +0 -0
  931. /package/dist/hooks/{start-work → ulw-execute}/notepad-scaffold.d.ts +0 -0
  932. /package/dist/hooks/{start-work → ulw-execute}/parse-user-request.d.ts +0 -0
  933. /package/dist/hooks/{start-work → ulw-execute}/plan-discovery-context.d.ts +0 -0
  934. /package/dist/hooks/{start-work → ulw-execute}/plan-selection.d.ts +0 -0
  935. /package/dist/hooks/{start-work → ulw-execute}/session-plan-affinity.d.ts +0 -0
  936. /package/dist/hooks/{start-work → ulw-execute}/work-initializer.d.ts +0 -0
  937. /package/dist/hooks/{start-work → ulw-execute}/worktree-block.d.ts +0 -0
  938. /package/dist/hooks/{start-work → ulw-execute}/worktree-detector.d.ts +0 -0
  939. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/.gitattributes +0 -0
  940. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/CHANGELOG.md +0 -0
  941. /package/packages/omo-codex/plugin/components/{codegraph → ulw-execute-continuation}/LICENSE +0 -0
  942. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/plan-checklist.ts +0 -0
  943. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-completed.json +0 -0
  944. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-mixed-platforms.json +0 -0
  945. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-single-codex-work.json +0 -0
  946. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-all-done.md +0 -0
  947. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-scaffold.md +0 -0
  948. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-nested-checkboxes.md +0 -0
  949. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-unchecked.md +0 -0
  950. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.build.json +0 -0
  951. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.json +0 -0
  952. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/vitest.config.ts +0 -0
@@ -1,21 +1,129 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
4
- import { existsSync as existsSync3, statSync } from "node:fs";
3
+ // components/ulw-loop/src/checkpoint.ts
4
+ import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
5
5
  import { readFile as readFile5 } from "node:fs/promises";
6
6
  import { resolve as resolve3 } from "node:path";
7
7
 
8
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
8
+ // components/ulw-loop/src/checkpoint-reconciliation.ts
9
+ import { existsSync as existsSync2 } from "node:fs";
10
+ import { readFile as readFile2 } from "node:fs/promises";
11
+
12
+ // components/ulw-loop/src/codex-goal-snapshot.ts
9
13
  import { existsSync } from "node:fs";
10
14
  import { readFile } from "node:fs/promises";
15
+ import { resolve } from "node:path";
16
+
17
+ class CodexGoalSnapshotError extends Error {
18
+ }
19
+ function safeObject(value) {
20
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
21
+ }
22
+ function safeString(value) {
23
+ return typeof value === "string" ? value.trim() : "";
24
+ }
25
+ function normalizeStatus(value) {
26
+ const status = safeString(value).toLowerCase();
27
+ if (status === "complete" || status === "completed" || status === "done")
28
+ return "complete";
29
+ if (status === "cancelled" || status === "canceled")
30
+ return "cancelled";
31
+ if (status === "failed" || status === "failure")
32
+ return "failed";
33
+ if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
34
+ return "active";
35
+ return "unknown";
36
+ }
37
+ function normalizeObjective(value) {
38
+ return value.replace(/\s+/g, " ").trim();
39
+ }
40
+ function parseCodexGoalSnapshot(value) {
41
+ const root = safeObject(value);
42
+ const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
43
+ if (goalValue === null || goalValue === undefined || goalValue === false) {
44
+ return { available: false, raw: value };
45
+ }
46
+ const goal = safeObject(goalValue);
47
+ const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
48
+ const status = normalizeStatus(goal["status"] ?? root["status"]);
49
+ return {
50
+ available: Boolean(objective || status !== "unknown"),
51
+ ...objective ? { objective } : {},
52
+ status,
53
+ raw: value
54
+ };
55
+ }
56
+ async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
57
+ if (!raw?.trim())
58
+ return null;
59
+ const trimmed = raw.trim();
60
+ try {
61
+ return parseCodexGoalSnapshot(JSON.parse(trimmed));
62
+ } catch {
63
+ const path = resolve(cwd, trimmed);
64
+ if (!existsSync(path)) {
65
+ throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
66
+ }
67
+ try {
68
+ return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
69
+ } catch (error) {
70
+ throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
71
+ }
72
+ }
73
+ }
74
+ function reconcileCodexGoalSnapshot(snapshot, options) {
75
+ const effectiveSnapshot = snapshot ?? { available: false, raw: null };
76
+ const errors = [];
77
+ const warnings = [];
78
+ if (!effectiveSnapshot.available) {
79
+ const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
80
+ if (options.requireSnapshot)
81
+ errors.push(message);
82
+ else
83
+ warnings.push(message);
84
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
85
+ }
86
+ const expected = normalizeObjective(options.expectedObjective);
87
+ const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))].filter(Boolean));
88
+ const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
89
+ if (!actual) {
90
+ errors.push("Codex goal snapshot is missing objective text.");
91
+ } else if (!accepted.has(actual)) {
92
+ errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
93
+ }
94
+ const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
95
+ const actualStatus = effectiveSnapshot.status ?? "unknown";
96
+ if (!allowed.includes(actualStatus)) {
97
+ errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
98
+ }
99
+ if (options.requireComplete && actualStatus !== "complete") {
100
+ errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
101
+ }
102
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
103
+ }
104
+ function formatCodexGoalReconciliation(reconciliation) {
105
+ const parts = [...reconciliation.errors, ...reconciliation.warnings];
106
+ return parts.join(" ");
107
+ }
108
+ function codexGoalMismatchRecovery(expectedObjective, snapshot) {
109
+ const receivedObjective = snapshot?.objective ?? "";
110
+ const message = [
111
+ "Recovery: the Codex goal objective must equal the plan's codexObjective exactly — copy the expected value below into update_goal and re-run get_goal.",
112
+ `expected codexObjective: ${expectedObjective}`,
113
+ `received objective: ${receivedObjective || "(none)"}`
114
+ ].join(`
115
+ `);
116
+ return { message, details: { expectedObjective, receivedObjective } };
117
+ }
11
118
 
12
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
119
+ // components/ulw-loop/src/paths.ts
13
120
  import { isAbsolute, join, relative, sep } from "node:path";
14
- // packages/omo-codex/plugin/components/ulw-loop/src/constants.ts
121
+ // components/ulw-loop/src/constants.ts
15
122
  var ULW_LOOP_DIR = ".omo/ulw-loop";
16
123
  var ULW_LOOP_BRIEF = "brief.md";
17
124
  var ULW_LOOP_GOALS = "goals.json";
18
125
  var ULW_LOOP_LEDGER = "ledger.jsonl";
126
+ var ULW_LOOP_STATE_LOCK = ".state.lock";
19
127
  var ULW_LOOP_STEERING_MUTATION_KINDS = [
20
128
  "add_subgoal",
21
129
  "split_subgoal",
@@ -31,10 +139,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
31
139
  "regression",
32
140
  "adversarial"
33
141
  ];
34
- // packages/omo-codex/plugin/components/ulw-loop/src/runtime.ts
142
+ // components/ulw-loop/src/runtime.ts
35
143
  class UlwLoopError extends Error {
36
- code;
37
- details;
38
144
  constructor(message, code, opts) {
39
145
  super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
40
146
  this.name = "UlwLoopError";
@@ -47,8 +153,8 @@ class UlwLoopError extends Error {
47
153
  function iso() {
48
154
  return new Date().toISOString();
49
155
  }
50
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
51
- var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"];
156
+ // components/ulw-loop/src/paths.ts
157
+ var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
52
158
  function normalizeUlwLoopSessionId(sessionId) {
53
159
  const trimmed = sessionId?.trim();
54
160
  if (!trimmed)
@@ -90,6 +196,9 @@ function ulwLoopGoalsPath(repoRoot, scope) {
90
196
  function ulwLoopLedgerPath(repoRoot, scope) {
91
197
  return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_LEDGER);
92
198
  }
199
+ function ulwLoopStateLockPath(repoRoot, scope) {
200
+ return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_STATE_LOCK);
201
+ }
93
202
  function repoRelative(absolutePath, repoRoot) {
94
203
  const slashPrefix = `${repoRoot}/`;
95
204
  const backslashPrefix = `${repoRoot}\\`;
@@ -100,7 +209,10 @@ function repoRelative(absolutePath, repoRoot) {
100
209
  return absolutePath.split("\\").join("/");
101
210
  }
102
211
  function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
103
- const sessionId = normalizeUlwLoopSessionId(scope?.sessionId) ?? resolveUlwLoopSessionIdFromEnv() ?? "session";
212
+ const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
213
+ if (sessionId === null) {
214
+ throw new UlwLoopError(`Evidence for ${goalId} attempt ${attempt} needs a session scope; pass --session-id <id> so the attempt directory lives under .omo/evidence/ulw/<id>/.`, "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { goalId, attempt } });
215
+ }
104
216
  return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
105
217
  }
106
218
  var PLATFORM_PATH_API = { relative, isAbsolute, sep };
@@ -113,7 +225,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
113
225
  return !pathApi.isAbsolute(relativePath);
114
226
  }
115
227
 
116
- // packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts
228
+ // components/ulw-loop/src/goal-status.ts
117
229
  var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
118
230
  function aggregateCodexObjectiveForScope(scope) {
119
231
  return `Complete the durable ulw-loop plan in ${ulwLoopGoalsRelativePath(scope)}, including later accepted/appended stories, under the original brief constraints; use ${ulwLoopLedgerRelativePath(scope)} as the audit trail.`;
@@ -196,8 +308,15 @@ function hasEssentialCriteriaPass(goal) {
196
308
  return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
197
309
  }
198
310
 
199
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
200
- function normalizeObjective(value) {
311
+ // components/ulw-loop/src/checkpoint-reconciliation.ts
312
+ function codexSnapshotMismatchError(input) {
313
+ const { reconciliation, snapshot, taskScopedHint } = input;
314
+ const hint = taskScopedHint?.aggregate === true && snapshot?.status === "complete" && snapshot.objective !== undefined ? buildTaskScopedAggregateReconciliationHint(taskScopedHint.goal, taskScopedHint.final) : "";
315
+ const recovery = codexGoalMismatchRecovery(input.expectedObjective, snapshot);
316
+ return new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${hint}
317
+ ${recovery.message}`, "ulw_loop_codex_snapshot_mismatch", { details: recovery.details });
318
+ }
319
+ function normalizeObjective2(value) {
201
320
  return value.replace(/\s+/g, " ").trim();
202
321
  }
203
322
  function textMentionsUlwLoopPlanArtifact(value) {
@@ -214,13 +333,13 @@ function textHasCompletionValidationEvidence(value) {
214
333
  return done && verified;
215
334
  }
216
335
  async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope) {
217
- const actual = normalizeObjective(snapshotObjective).toLowerCase();
336
+ const actual = normalizeObjective2(snapshotObjective).toLowerCase();
218
337
  if (textMentionsUlwLoopPlanArtifact(actual))
219
338
  return true;
220
- if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot, scope)))
339
+ if (actual.length < 24 || !existsSync2(ulwLoopBriefPath(repoRoot, scope)))
221
340
  return false;
222
341
  try {
223
- const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
342
+ const brief = normalizeObjective2(await readFile2(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
224
343
  return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
225
344
  } catch (error) {
226
345
  if (error instanceof Error)
@@ -267,111 +386,280 @@ function buildTaskScopedAggregateReconciliationHint(goal, final) {
267
386
  return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
268
387
  }
269
388
 
270
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts
271
- import { existsSync as existsSync2 } from "node:fs";
272
- import { readFile as readFile2 } from "node:fs/promises";
273
- import { resolve } from "node:path";
389
+ // components/ulw-loop/src/checkpoint-codex-validation.ts
390
+ function normalizeObjective3(value) {
391
+ return value.replace(/\s+/g, " ").trim();
392
+ }
393
+ async function validateCheckpointCodexGoal(input) {
394
+ const aggregate = codexGoalMode(input.plan) === "aggregate";
395
+ const final = isFinalRunCompletionCandidate(input.plan, input.goal);
396
+ const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
397
+ const expectedObjective = expectedCodexObjective(input.plan, input.goal);
398
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
399
+ expectedObjective,
400
+ ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {},
401
+ allowedStatuses: aggregate ? final ? ["complete"] : ["active"] : ["complete"],
402
+ requireSnapshot: true,
403
+ requireComplete: !aggregate || final
404
+ });
405
+ if (reconciliation.ok)
406
+ return reconciliation.snapshot.raw;
407
+ const objective = snapshot?.objective;
408
+ const mismatchedTaskObjective = snapshot?.available === true && objective !== undefined && normalizeObjective3(objective) !== normalizeObjective3(expectedObjective);
409
+ const completedTaskScoped = mismatchedTaskObjective && snapshot.status === "complete" && await canReconcileCompletedTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
410
+ const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
411
+ if (completedTaskScoped || activeFinalTaskScoped)
412
+ return reconciliation.snapshot.raw;
413
+ throw codexSnapshotMismatchError({
414
+ reconciliation,
415
+ snapshot,
416
+ expectedObjective,
417
+ taskScopedHint: { goal: input.goal, aggregate, final }
418
+ });
419
+ }
420
+ function combineCheckpointValidationErrors(codexError, gateError) {
421
+ return new UlwLoopError(`${codexError.message}
422
+ ${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
423
+ details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
424
+ });
425
+ }
274
426
 
275
- class CodexGoalSnapshotError extends Error {
427
+ // components/ulw-loop/src/plan-io.ts
428
+ import { AsyncLocalStorage } from "node:async_hooks";
429
+ import { createReadStream, readdirSync } from "node:fs";
430
+ import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
431
+ import { createInterface } from "node:readline";
432
+
433
+ // components/ulw-loop/src/plan-missing-recovery.ts
434
+ var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
435
+ function planMissingRecovery(existingSessionIds) {
436
+ const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
437
+ if (existingSessionIds.length === 0)
438
+ return { message: lines.join(`
439
+ `) };
440
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
441
+ return { message: lines.join(`
442
+ `), details: { existingSessionIds } };
443
+ }
444
+ function sessionScopeRequiredMessage(flag, existingSessionIds) {
445
+ const lines = [
446
+ "No ulw-loop session scope: neither the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID) nor the flag names this run, and the shared .omo/ulw-loop root is never used implicitly because every session in this directory would read and overwrite it.",
447
+ `Recovery: pass the scope explicitly: \`${flag} <id>\` (subprocess, eval, and hook contexts do not inherit the session env).`
448
+ ];
449
+ if (existingSessionIds.length > 0) {
450
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}.`);
451
+ }
452
+ return lines.join(`
453
+ `);
276
454
  }
277
- function safeObject(value) {
278
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
455
+ function sessionIdRequiredMessage(flag) {
456
+ return [
457
+ `${flag} requires a non-empty value.`,
458
+ "Recovery: subprocess, eval, and hook contexts do not inherit the ulw-loop session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID),",
459
+ `so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
460
+ ].join(`
461
+ `);
279
462
  }
280
- function safeString(value) {
281
- return typeof value === "string" ? value.trim() : "";
463
+
464
+ // components/ulw-loop/src/state-lock.ts
465
+ import { randomUUID } from "node:crypto";
466
+ import { closeSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeSync } from "node:fs";
467
+ import { dirname } from "node:path";
468
+ import { setTimeout as sleep } from "node:timers/promises";
469
+ var ULW_LOOP_LOCK_TIMEOUT_CODE = "ULW_LOOP_LOCK_TIMEOUT";
470
+ var DEFAULT_TIMEOUT_MS = 1e4;
471
+ var DEFAULT_STALE_MS = 60000;
472
+ var MIN_DELAY_MS = 5;
473
+ var MAX_DELAY_MS = 100;
474
+ var SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
475
+ async function withStateLock(lockPath, fn, options = {}) {
476
+ const token = await acquireAsync(lockPath, options);
477
+ try {
478
+ return await fn();
479
+ } finally {
480
+ release(lockPath, token);
481
+ }
282
482
  }
283
- function normalizeStatus(value) {
284
- const status = safeString(value).toLowerCase();
285
- if (status === "complete" || status === "completed" || status === "done")
286
- return "complete";
287
- if (status === "cancelled" || status === "canceled")
288
- return "cancelled";
289
- if (status === "failed" || status === "failure")
290
- return "failed";
291
- if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
292
- return "active";
293
- return "unknown";
483
+ function withStateLockSync(lockPath, fn, options = {}) {
484
+ const token = acquireSync(lockPath, options);
485
+ try {
486
+ return fn();
487
+ } finally {
488
+ release(lockPath, token);
489
+ }
294
490
  }
295
- function normalizeObjective2(value) {
296
- return value.replace(/\s+/g, " ").trim();
491
+ function isStateLockTimeout(error) {
492
+ return error instanceof UlwLoopError && error.code === ULW_LOOP_LOCK_TIMEOUT_CODE;
493
+ }
494
+ async function acquireAsync(lockPath, options) {
495
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
496
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
497
+ mkdirSync(dirname(lockPath), { recursive: true });
498
+ for (let attempt = 0;; ) {
499
+ const outcome = attemptOnce(lockPath, staleMs);
500
+ if (outcome.kind === "acquired")
501
+ return outcome.token;
502
+ if (outcome.kind === "retry")
503
+ continue;
504
+ if (Date.now() >= deadline)
505
+ throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
506
+ await sleep(backoffMs(attempt));
507
+ attempt += 1;
508
+ }
297
509
  }
298
- function parseCodexGoalSnapshot(value) {
299
- const root = safeObject(value);
300
- const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
301
- if (goalValue === null || goalValue === undefined || goalValue === false) {
302
- return { available: false, raw: value };
510
+ function acquireSync(lockPath, options) {
511
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
512
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
513
+ mkdirSync(dirname(lockPath), { recursive: true });
514
+ for (let attempt = 0;; ) {
515
+ const outcome = attemptOnce(lockPath, staleMs);
516
+ if (outcome.kind === "acquired")
517
+ return outcome.token;
518
+ if (outcome.kind === "retry")
519
+ continue;
520
+ if (Date.now() >= deadline)
521
+ throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
522
+ Atomics.wait(SLEEP_CELL, 0, 0, backoffMs(attempt));
523
+ attempt += 1;
303
524
  }
304
- const goal = safeObject(goalValue);
305
- const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? root["objective"]);
306
- const status = normalizeStatus(goal["status"] ?? root["status"]);
307
- return {
308
- available: Boolean(objective || status !== "unknown"),
309
- ...objective ? { objective } : {},
310
- status,
311
- raw: value
312
- };
313
525
  }
314
- async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
315
- if (!raw?.trim())
316
- return null;
317
- const trimmed = raw.trim();
526
+ function attemptOnce(lockPath, staleMs) {
318
527
  try {
319
- return parseCodexGoalSnapshot(JSON.parse(trimmed));
320
- } catch {
321
- const path = resolve(cwd, trimmed);
322
- if (!existsSync2(path)) {
323
- throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
324
- }
528
+ const token = tryCreate(lockPath);
529
+ if (token !== null)
530
+ return { kind: "acquired", token };
531
+ const snapshot = readSnapshot(lockPath);
532
+ if (snapshot === null)
533
+ return { kind: "retry" };
534
+ if (isStale(snapshot, staleMs) && reclaim(lockPath, snapshot.raw))
535
+ return { kind: "retry" };
536
+ return { kind: "wait" };
537
+ } catch (error) {
538
+ if (hasCode(error, "EINTR"))
539
+ return { kind: "wait" };
540
+ throw error;
541
+ }
542
+ }
543
+ function tryCreate(lockPath) {
544
+ let fd;
545
+ try {
546
+ fd = openSync(lockPath, "wx");
547
+ } catch (error) {
548
+ if (hasCode(error, "EEXIST"))
549
+ return null;
550
+ throw error;
551
+ }
552
+ const record = { pid: process.pid, createdAt: new Date().toISOString(), token: randomUUID() };
553
+ try {
554
+ writeSync(fd, JSON.stringify(record));
555
+ } catch (error) {
556
+ closeSync(fd);
325
557
  try {
326
- return parseCodexGoalSnapshot(JSON.parse(await readFile2(path, "utf-8")));
327
- } catch (error) {
328
- throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
329
- }
558
+ unlinkSync(lockPath);
559
+ } catch {}
560
+ throw error;
330
561
  }
562
+ closeSync(fd);
563
+ return record.token;
331
564
  }
332
- function reconcileCodexGoalSnapshot(snapshot, options) {
333
- const effectiveSnapshot = snapshot ?? { available: false, raw: null };
334
- const errors = [];
335
- const warnings = [];
336
- if (!effectiveSnapshot.available) {
337
- const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
338
- if (options.requireSnapshot)
339
- errors.push(message);
340
- else
341
- warnings.push(message);
342
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
565
+ function readSnapshot(lockPath) {
566
+ try {
567
+ const raw = readFileSync(lockPath, "utf8");
568
+ const ageMs = Date.now() - statSync(lockPath).mtimeMs;
569
+ return { raw, record: parseRecord(raw), ageMs };
570
+ } catch (error) {
571
+ if (hasCode(error, "ENOENT"))
572
+ return null;
573
+ throw error;
343
574
  }
344
- const expected = normalizeObjective2(options.expectedObjective);
345
- const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective2(objective))].filter(Boolean));
346
- const actual = normalizeObjective2(effectiveSnapshot.objective ?? "");
347
- if (!actual) {
348
- errors.push("Codex goal snapshot is missing objective text.");
349
- } else if (!accepted.has(actual)) {
350
- errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
575
+ }
576
+ function parseRecord(raw) {
577
+ try {
578
+ const parsed = JSON.parse(raw);
579
+ if (typeof parsed !== "object" || parsed === null)
580
+ return null;
581
+ const record = parsed;
582
+ const pid = record["pid"];
583
+ const createdAt = record["createdAt"];
584
+ const token = record["token"];
585
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof createdAt !== "string")
586
+ return null;
587
+ if (typeof token !== "string" || token.length === 0)
588
+ return null;
589
+ return { pid, createdAt, token };
590
+ } catch (error) {
591
+ if (error instanceof SyntaxError)
592
+ return null;
593
+ throw error;
351
594
  }
352
- const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
353
- const actualStatus = effectiveSnapshot.status ?? "unknown";
354
- if (!allowed.includes(actualStatus)) {
355
- errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
595
+ }
596
+ function isStale(snapshot, staleMs) {
597
+ if (snapshot.record === null)
598
+ return snapshot.ageMs > staleMs;
599
+ return !isProcessAlive(snapshot.record.pid);
600
+ }
601
+ function isProcessAlive(pid) {
602
+ try {
603
+ process.kill(pid, 0);
604
+ return true;
605
+ } catch (error) {
606
+ if (hasCode(error, "ESRCH"))
607
+ return false;
608
+ if (hasCode(error, "EPERM"))
609
+ return true;
610
+ throw error;
356
611
  }
357
- if (options.requireComplete && actualStatus !== "complete") {
358
- errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
612
+ }
613
+ function reclaim(lockPath, expectedRaw) {
614
+ const current = readSnapshot(lockPath);
615
+ if (current === null)
616
+ return true;
617
+ if (current.raw !== expectedRaw)
618
+ return false;
619
+ try {
620
+ unlinkSync(lockPath);
621
+ } catch (error) {
622
+ if (!hasCode(error, "ENOENT"))
623
+ throw error;
359
624
  }
360
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
625
+ return true;
361
626
  }
362
- function formatCodexGoalReconciliation(reconciliation) {
363
- const parts = [...reconciliation.errors, ...reconciliation.warnings];
364
- return parts.join(" ");
627
+ function release(lockPath, token) {
628
+ const current = readSnapshot(lockPath);
629
+ if (current === null || current.record?.token !== token)
630
+ return;
631
+ try {
632
+ unlinkSync(lockPath);
633
+ } catch (error) {
634
+ if (!hasCode(error, "ENOENT"))
635
+ throw error;
636
+ }
637
+ }
638
+ function backoffMs(attempt) {
639
+ const exponential = Math.min(MAX_DELAY_MS, MIN_DELAY_MS * 2 ** attempt);
640
+ return exponential + Math.random() * MIN_DELAY_MS;
641
+ }
642
+ function lockTimeout(lockPath, timeoutMs) {
643
+ const holder = readSnapshot(lockPath)?.record;
644
+ const owner = holder === undefined || holder === null ? "another process" : `pid ${holder.pid}`;
645
+ return new UlwLoopError(`ulw-loop state lock ${lockPath} is held by ${owner} for more than ${timeoutMs}ms; retry once that process finishes, or delete the lock file if that process is gone.`, ULW_LOOP_LOCK_TIMEOUT_CODE, {
646
+ details: {
647
+ lockPath,
648
+ timeoutMs,
649
+ ...holder === undefined || holder === null ? {} : { holderPid: holder.pid }
650
+ }
651
+ });
652
+ }
653
+ function hasCode(error, code) {
654
+ return error instanceof Error && "code" in error && error.code === code;
365
655
  }
366
656
 
367
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts
368
- import { createReadStream } from "node:fs";
369
- import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
370
- import { createInterface } from "node:readline";
657
+ // components/ulw-loop/src/plan-io.ts
371
658
  var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
372
659
  var LEGACY_OBJECTIVE = `Complete all ulw-loop stories listed in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}. Use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the durable audit trail.`;
373
660
  var locks = new Map;
374
- function hasCode(error, code) {
661
+ var heldLocks = new AsyncLocalStorage;
662
+ function hasCode2(error, code) {
375
663
  return error instanceof Error && "code" in error && error.code === code;
376
664
  }
377
665
  function isLegacyEnumeratedAggregateObjective(objective) {
@@ -386,8 +674,10 @@ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
386
674
  if (fn === undefined)
387
675
  throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
388
676
  const lockKey = `${repoRoot}\x00${ulwLoopRelativeDir(scope)}`;
677
+ const lockPath = ulwLoopStateLockPath(repoRoot, scope);
678
+ const locked = () => withStateLock(lockPath, () => heldLocks.run(new Set([...heldLocks.getStore() ?? [], lockKey]), fn));
389
679
  const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
390
- const run = prior.then(fn, fn);
680
+ const run = prior.then(locked, locked);
391
681
  const gate = run.then(() => {
392
682
  return;
393
683
  }, () => {
@@ -406,9 +696,11 @@ async function readUlwLoopPlan(repoRoot, scope) {
406
696
  try {
407
697
  raw = await readFile3(path, "utf8");
408
698
  } catch (error) {
409
- if (!hasCode(error, "ENOENT"))
699
+ if (!hasCode2(error, "ENOENT"))
410
700
  throw error;
411
- throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}. Run \`omo-agent-toolkit ulw-loop create-goals ...\` first.`, "ULW_LOOP_PLAN_MISSING", { cause: error });
701
+ const recovery = planMissingRecovery(listUlwLoopSessionIds(repoRoot));
702
+ throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.
703
+ ${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...recovery.details === undefined ? {} : { details: recovery.details } });
412
704
  }
413
705
  const parsed = JSON.parse(raw);
414
706
  if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
@@ -416,6 +708,9 @@ async function readUlwLoopPlan(repoRoot, scope) {
416
708
  }
417
709
  const previousObjective = parsed.codexObjective;
418
710
  if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && isLegacyEnumeratedAggregateObjective(previousObjective)) {
711
+ if (!(heldLocks.getStore()?.has(`${repoRoot}\x00${ulwLoopRelativeDir(scope)}`) ?? false)) {
712
+ throw new UlwLoopError(`The ulw-loop plan at ${repoRelative(path, repoRoot)} carries a legacy enumerated aggregate objective that must be migrated before reads continue. Run any state-mutating ulw-loop command once (e.g. \`record-evidence\`, \`steer\`, \`checkpoint\`) to migrate it under the state lock, then retry.`, "ULW_LOOP_MIGRATION_REQUIRED");
713
+ }
419
714
  const now = iso();
420
715
  parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
421
716
  parsed.codexObjectiveAliases = [...new Set([...parsed.codexObjectiveAliases ?? [], previousObjective])];
@@ -431,6 +726,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
431
726
  }
432
727
  return parsed;
433
728
  }
729
+ function listUlwLoopSessionIds(repoRoot) {
730
+ try {
731
+ return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
732
+ } catch {
733
+ return [];
734
+ }
735
+ }
434
736
  async function writePlan(repoRoot, plan, scope) {
435
737
  await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
436
738
  const path = ulwLoopGoalsPath(repoRoot, scope);
@@ -459,7 +761,7 @@ async function* ledgerLines(repoRoot, scope) {
459
761
  yield line;
460
762
  }
461
763
  } catch (error) {
462
- if (!hasCode(error, "ENOENT"))
764
+ if (!hasCode2(error, "ENOENT"))
463
765
  throw error;
464
766
  } finally {
465
767
  lines.close();
@@ -482,7 +784,7 @@ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
482
784
  return;
483
785
  }
484
786
 
485
- // packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts
787
+ // components/ulw-loop/src/evidence.ts
486
788
  function ulwLoopFail(message, code, details) {
487
789
  throw new UlwLoopError(message, code, { details });
488
790
  }
@@ -588,57 +890,216 @@ function requireEssentialCriteriaPass(goal) {
588
890
  });
589
891
  }
590
892
 
591
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
893
+ // components/ulw-loop/src/quality-gate-artifacts.ts
592
894
  import { resolve as resolve2 } from "node:path";
593
895
 
594
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts
595
- var PLACEHOLDER_PATTERN = /^(?:placeholder|todo|tbd|n\/a|stub)$/i;
896
+ // components/ulw-loop/src/quality-gate-fields.ts
897
+ var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
898
+ var activeCollector;
899
+ function withQualityGateCollector(operation) {
900
+ const previous = activeCollector;
901
+ const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
902
+ activeCollector = collector;
903
+ try {
904
+ const result = operation();
905
+ if (collector.defects.length === 0)
906
+ return result;
907
+ throwQualityGateDefects(collector.defects);
908
+ } finally {
909
+ activeCollector = previous;
910
+ }
911
+ }
912
+ function throwQualityGateDefects(defects) {
913
+ const uniqueDefects = [
914
+ ...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
915
+ ];
916
+ const fields = uniqueDefects.slice(0, 25);
917
+ const truncated = uniqueDefects.length > fields.length;
918
+ const message = [
919
+ `Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
920
+ ...fields.map((item) => `- ${item.field}: ${item.message}`)
921
+ ].join(`
922
+ `);
923
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
924
+ details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
925
+ });
926
+ }
596
927
  function invalid(message, field) {
597
- throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
928
+ if (activeCollector === undefined)
929
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
930
+ activeCollector.defects.push({ field, message });
931
+ activeCollector.poisonedFields.add(field);
932
+ return;
933
+ }
934
+ function isPoisoned(field) {
935
+ return activeCollector?.poisonedFields.has(field) ?? false;
936
+ }
937
+ function markPoisonedArtifactKind(id) {
938
+ activeCollector?.poisonedArtifactKinds.add(id);
939
+ }
940
+ function isPoisonedArtifactKind(id) {
941
+ return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
598
942
  }
599
943
  function isRecord(value) {
600
944
  return typeof value === "object" && value !== null && !Array.isArray(value);
601
945
  }
602
946
  function section(value, field) {
603
- return isRecord(value) ? value : invalid(`Final quality gate is missing ${field} evidence.`, field);
947
+ if (isRecord(value))
948
+ return value;
949
+ invalid(`Final quality gate is missing ${field} evidence (accepted input: a bare object with manualQa/gateReview/iteration/criteriaCoverage and optional codeReview, or the same object under a top-level "qualityGate" key).`, field);
950
+ return {};
604
951
  }
605
952
  function textField(value, field) {
606
- if (typeof value !== "string" || value.trim() === "")
953
+ if (typeof value !== "string" || value.trim() === "") {
607
954
  invalid(`Final quality gate requires non-empty ${field}.`, field);
955
+ return "";
956
+ }
608
957
  const trimmed = value.trim();
609
958
  if (PLACEHOLDER_PATTERN.test(trimmed))
610
959
  invalid(`Final quality gate rejects placeholder ${field}.`, field);
611
960
  return trimmed;
612
961
  }
613
- function numberField(value, field) {
614
- return typeof value === "number" && Number.isFinite(value) ? value : invalid(`Final quality gate requires numeric ${field}.`, field);
962
+ function numberField(value, field) {
963
+ if (typeof value === "number" && Number.isFinite(value))
964
+ return value;
965
+ invalid(`Final quality gate requires numeric ${field}.`, field);
966
+ return 0;
967
+ }
968
+ function stringArray(value, field) {
969
+ if (!Array.isArray(value) || value.length === 0) {
970
+ invalid(`Final quality gate requires ${field}.`, field);
971
+ return [];
972
+ }
973
+ return value.map((item) => textField(item, field));
974
+ }
975
+ function emptyBlockers(value, field) {
976
+ if (Array.isArray(value) && value.length === 0)
977
+ return [];
978
+ invalid(`${field} must be empty.`, field);
979
+ return [];
980
+ }
981
+ function literal(value, expected, field) {
982
+ if (value === expected)
983
+ return expected;
984
+ invalid(`${field} must be ${String(expected)}.`, field);
985
+ return expected;
986
+ }
987
+
988
+ // components/ulw-loop/src/quality-gate-artifacts.ts
989
+ var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
990
+ var SUPPORTED_KINDS = [
991
+ "cli-transcript",
992
+ "log",
993
+ "screenshot",
994
+ "image",
995
+ "http-dump",
996
+ "data-diff"
997
+ ];
998
+ var COMPATIBLE_KINDS = {
999
+ cli: ["cli-transcript", "log"],
1000
+ tmux: ["cli-transcript", "log"],
1001
+ http: ["http-dump"],
1002
+ browser: ["screenshot", "image"],
1003
+ gui: ["screenshot", "image"],
1004
+ data: ["data-diff"]
1005
+ };
1006
+ function isSupportedSurface(value) {
1007
+ return SUPPORTED_SURFACES.some((surface) => surface === value);
1008
+ }
1009
+ function isSupportedKind(value) {
1010
+ return SUPPORTED_KINDS.some((kind) => kind === value);
1011
+ }
1012
+ function surfaceField(value, field) {
1013
+ if (isSupportedSurface(value))
1014
+ return value;
1015
+ invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
1016
+ return "cli";
1017
+ }
1018
+ function kindField(value, field) {
1019
+ if (isSupportedKind(value))
1020
+ return value;
1021
+ invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
1022
+ return "log";
1023
+ }
1024
+ function compatibleKindsFor(surface) {
1025
+ return COMPATIBLE_KINDS[surface];
615
1026
  }
616
- function stringArray(value, field) {
617
- if (!Array.isArray(value) || value.length === 0)
618
- return invalid(`Final quality gate requires ${field}.`, field);
619
- return value.map((item) => textField(item, field));
1027
+ function artifactCompatible(surface, kind) {
1028
+ return compatibleKindsFor(surface).includes(kind);
620
1029
  }
621
- function emptyBlockers(value, field) {
622
- if (Array.isArray(value) && value.length === 0)
1030
+ function checkFile(path, field, opts) {
1031
+ if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
1032
+ return;
1033
+ const absolute = resolve2(opts.repoRoot, path);
1034
+ if (!opts.fs.existsSync(absolute)) {
1035
+ invalid(`${field} must point to an existing artifact.`, field);
1036
+ return;
1037
+ }
1038
+ if (opts.fs.statSync(absolute).size <= 0)
1039
+ invalid(`${field} must point to a non-empty artifact.`, field);
1040
+ if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve2(opts.repoRoot, opts.currentAttemptDir)))
1041
+ invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
1042
+ }
1043
+ function artifactMap(refs) {
1044
+ const byId = new Map;
1045
+ for (const ref of refs) {
1046
+ if (byId.has(ref.id))
1047
+ invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
1048
+ byId.set(ref.id, ref);
1049
+ }
1050
+ return byId;
1051
+ }
1052
+ function parseArtifactRefs(value, opts) {
1053
+ if (!Array.isArray(value) || value.length === 0) {
1054
+ invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
623
1055
  return [];
624
- invalid(`${field} must be empty.`, field);
1056
+ }
1057
+ return value.flatMap((item, index) => {
1058
+ const prefix = `manualQa.artifactRefs[${index}]`;
1059
+ const ref = section(item, prefix);
1060
+ if (isPoisoned(prefix))
1061
+ return [];
1062
+ const pathField = `${prefix}.path`;
1063
+ const idField = `${prefix}.id`;
1064
+ const kindFieldName = `${prefix}.kind`;
1065
+ const descriptionField = `${prefix}.description`;
1066
+ const path = textField(ref["path"], pathField);
1067
+ const id = textField(ref["id"], idField);
1068
+ const kind = kindField(ref["kind"], kindFieldName);
1069
+ if (isPoisoned(kindFieldName))
1070
+ markPoisonedArtifactKind(id);
1071
+ const description = textField(ref["description"], descriptionField);
1072
+ checkFile(path, pathField, opts);
1073
+ return [{ id, kind, description, path }];
1074
+ });
625
1075
  }
626
- function literal(value, expected, field) {
627
- if (value === expected)
628
- return expected;
629
- invalid(`${field} must be ${String(expected)}.`, field);
1076
+ function referencedArtifacts(value, field, byId) {
1077
+ const ids = stringArray(value, field);
1078
+ if (isPoisoned(field))
1079
+ return [];
1080
+ return ids.flatMap((id) => {
1081
+ const artifact = byId.get(id);
1082
+ if (artifact === undefined) {
1083
+ invalid(`${field} references unknown artifact ${id}.`, field);
1084
+ return [];
1085
+ }
1086
+ return [artifact];
1087
+ });
630
1088
  }
631
1089
 
632
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts
1090
+ // components/ulw-loop/src/quality-gate-verdicts.ts
633
1091
  function passedVerdict(value, field) {
634
- if (value === "not_applicable")
1092
+ if (value === "not_applicable") {
635
1093
  invalid(`${field} must not be not_applicable.`, field);
1094
+ return "passed";
1095
+ }
636
1096
  return literal(value, "passed", field);
637
1097
  }
638
1098
  function codeQualityStatusField(value, field) {
639
1099
  if (value === "CLEAR" || value === "WATCH")
640
1100
  return value;
641
1101
  invalid(`${field} must be CLEAR or WATCH.`, field);
1102
+ return "CLEAR";
642
1103
  }
643
1104
  function adversarialVerdict(row, field) {
644
1105
  const value = row["verdict"];
@@ -648,9 +1109,82 @@ function adversarialVerdict(row, field) {
648
1109
  return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
649
1110
  }
650
1111
  invalid(`${field} must be passed or not_applicable with a reason.`, field);
1112
+ return { verdict: "passed" };
1113
+ }
1114
+
1115
+ // components/ulw-loop/src/surface.ts
1116
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
1117
+ import { dirname as dirname2, join as join2 } from "node:path";
1118
+ import { fileURLToPath } from "node:url";
1119
+ var REVIEWER_ROLES_BY_SURFACE = {
1120
+ lazycodex: {
1121
+ codeReview: "lazycodex-code-reviewer",
1122
+ manualQa: "lazycodex-qa-executor",
1123
+ gateReview: "lazycodex-gate-reviewer"
1124
+ },
1125
+ "omo-senpi": {
1126
+ codeReview: "omo-senpi-code-reviewer",
1127
+ manualQa: "omo-senpi-qa-executor",
1128
+ gateReview: "omo-senpi-gate-reviewer"
1129
+ }
1130
+ };
1131
+ var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
1132
+ var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
1133
+ lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
1134
+ "omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
1135
+ };
1136
+ var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
1137
+ lazycodex: ["codeReview"],
1138
+ "omo-senpi": []
1139
+ };
1140
+ var GATE_SECTION_BY_ACCEPTOR = {
1141
+ lazycodex: {
1142
+ codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
1143
+ manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
1144
+ gateReview: [
1145
+ REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
1146
+ "category:deep",
1147
+ "category:unspecified-high",
1148
+ "category:unspecified-low",
1149
+ "main-session"
1150
+ ]
1151
+ },
1152
+ "omo-senpi": {
1153
+ manualQa: ["main-session"],
1154
+ gateReview: ["category:deep", "category:unspecified-high", "category:unspecified-low"]
1155
+ }
1156
+ };
1157
+ function reviewerRolesFor(surface) {
1158
+ return REVIEWER_ROLES_BY_SURFACE[surface];
1159
+ }
1160
+ var SURFACE_MARKER_FILENAME = "surface.json";
1161
+ var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
1162
+ function parseSurface(value) {
1163
+ return value === "lazycodex" || value === "omo-senpi" ? value : null;
1164
+ }
1165
+ function resolveToolkitSurface(options) {
1166
+ const env = options?.env ?? process.env;
1167
+ const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
1168
+ if (fromEnv !== null)
1169
+ return fromEnv;
1170
+ const entryDir = options?.entryDir ?? dirname2(fileURLToPath(import.meta.url));
1171
+ const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
1172
+ return readMarkerSurface(markerPath) ?? "lazycodex";
1173
+ }
1174
+ function readMarkerSurface(markerPath) {
1175
+ try {
1176
+ if (!existsSync3(markerPath))
1177
+ return null;
1178
+ const parsed = JSON.parse(readFileSync2(markerPath, "utf8"));
1179
+ return parseSurface(parsed["surface"]);
1180
+ } catch (error) {
1181
+ if (error instanceof Error)
1182
+ return null;
1183
+ throw error;
1184
+ }
651
1185
  }
652
1186
 
653
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-blockers.ts
1187
+ // components/ulw-loop/src/quality-gate-blockers.ts
654
1188
  var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
655
1189
  var URL_PATTERN = /https?:\/\/\S+/g;
656
1190
  var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
@@ -692,131 +1226,68 @@ function clearGoalBlockerFields(goal) {
692
1226
  Reflect.deleteProperty(goal, key);
693
1227
  }
694
1228
 
695
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
696
- var REVIEWER_ROLES = {
697
- codeReview: "lazycodex-code-reviewer",
698
- manualQa: "lazycodex-qa-executor",
699
- gateReview: "lazycodex-gate-reviewer"
700
- };
701
- function reviewerRoleField(value, expected, field) {
702
- const actual = textField(value, field);
703
- if (actual !== expected)
704
- invalid(`${field} must be ${expected}.`, field);
705
- return expected;
706
- }
707
- function surfaceField(value, field) {
708
- if (value === "cli" || value === "http" || value === "tmux" || value === "browser" || value === "gui" || value === "data")
709
- return value;
710
- invalid(`${field} must be a supported manual QA surface.`, field);
711
- }
712
- function kindField(value, field) {
713
- if (value === "cli-transcript" || value === "log" || value === "screenshot" || value === "image" || value === "http-dump" || value === "data-diff")
714
- return value;
715
- invalid(`${field} must be a supported artifact kind.`, field);
716
- }
717
- function artifactCompatible(surface, kind) {
718
- switch (surface) {
719
- case "cli":
720
- case "tmux":
721
- return kind === "cli-transcript" || kind === "log";
722
- case "http":
723
- return kind === "http-dump";
724
- case "browser":
725
- case "gui":
726
- return kind === "screenshot" || kind === "image";
727
- case "data":
728
- return kind === "data-diff";
729
- default:
730
- invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
731
- }
732
- }
733
- function checkFile(path, field, opts) {
734
- if (opts === undefined)
735
- return;
736
- const absolute = resolve2(opts.repoRoot, path);
737
- if (!opts.fs.existsSync(absolute))
738
- invalid(`${field} must point to an existing artifact.`, field);
739
- if (opts.fs.statSync(absolute).size <= 0)
740
- invalid(`${field} must point to a non-empty artifact.`, field);
741
- if (opts.currentAttemptDir !== undefined) {
742
- const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
743
- if (!isWithinAttemptDir(absolute, attemptRoot))
744
- invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
745
- }
746
- }
747
- function artifactMap(refs) {
748
- const byId = new Map;
749
- for (const ref of refs) {
750
- if (byId.has(ref.id))
751
- invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
752
- byId.set(ref.id, ref);
1229
+ // components/ulw-loop/src/quality-gate.ts
1230
+ function reviewerAcceptorField(value, surface, sectionName) {
1231
+ const field = `${sectionName}.by`;
1232
+ const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
1233
+ if (typeof value !== "string" || value.trim() === "") {
1234
+ textField(value, field);
1235
+ return accepted?.[0] ?? "";
753
1236
  }
754
- return byId;
755
- }
756
- function parseArtifactRefs(value, opts) {
757
- if (!Array.isArray(value) || value.length === 0)
758
- invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
759
- return value.map((item, index) => {
760
- const ref = section(item, `manualQa.artifactRefs[${index}]`);
761
- const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
762
- checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
763
- return {
764
- id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
765
- kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
766
- description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
767
- path
768
- };
769
- });
770
- }
771
- function referencedArtifacts(value, field, byId) {
772
- return stringArray(value, field).map((id) => {
773
- const artifact = byId.get(id);
774
- if (artifact === undefined)
775
- invalid(`${field} references unknown artifact ${id}.`, field);
776
- return artifact;
777
- });
1237
+ const actual = textField(value, field);
1238
+ if (accepted === undefined || !accepted.includes(actual))
1239
+ invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
1240
+ return actual;
778
1241
  }
779
1242
  function validateQualityGate(input, opts) {
780
- const gate = section(input, "qualityGate");
781
- const codeReview = section(gate["codeReview"], "codeReview");
1243
+ return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
1244
+ }
1245
+ function validateQualityGateUncollected(input, opts) {
1246
+ const surface = opts?.reviewerSurface ?? "lazycodex";
1247
+ const raw = input;
1248
+ const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
1249
+ for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
1250
+ section(gate[name], name);
1251
+ for (const name of Object.keys(gate)) {
1252
+ if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
1253
+ invalid("omo-senpi gate has no codeReview lane.", name);
1254
+ }
1255
+ if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
1256
+ invalid("omo-senpi gate has no codeReview lane.", "codeReview");
782
1257
  const manualQa = section(gate["manualQa"], "manualQa");
783
1258
  const gateReview = section(gate["gateReview"], "gateReview");
784
1259
  const iteration = section(gate["iteration"], "iteration");
785
1260
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
1261
+ const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
1262
+ const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
1263
+ const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
1264
+ const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
1265
+ const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
786
1266
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
787
1267
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
788
- if (passCount < totalCriteria)
1268
+ if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
789
1269
  invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
790
1270
  const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
791
1271
  const byId = artifactMap(artifactRefs);
792
1272
  const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
793
1273
  const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
794
- const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
795
1274
  const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
796
- checkFile(codeReportPath, "codeReview.reportPath", opts);
797
- checkFile(gateReportPath, "gateReview.reportPath", opts);
798
- return {
799
- codeReview: {
800
- by: reviewerRoleField(codeReview["by"], REVIEWER_ROLES.codeReview, "codeReview.by"),
801
- recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
802
- codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
803
- reportPath: codeReportPath,
804
- evidence: textField(codeReview["evidence"], "codeReview.evidence"),
805
- blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
806
- },
1275
+ if (!isPoisoned("gateReview.reportPath"))
1276
+ checkFile(gateReportPath, "gateReview.reportPath", opts);
1277
+ const common = {
807
1278
  manualQa: {
808
- by: reviewerRoleField(manualQa["by"], REVIEWER_ROLES.manualQa, "manualQa.by"),
1279
+ by: manualQaBy,
809
1280
  status: literal(manualQa["status"], "passed", "manualQa.status"),
810
- evidence: textField(manualQa["evidence"], "manualQa.evidence"),
1281
+ evidence: manualQaEvidence,
811
1282
  surfaceEvidence,
812
1283
  adversarialCases,
813
1284
  artifactRefs
814
1285
  },
815
1286
  gateReview: {
816
- by: reviewerRoleField(gateReview["by"], REVIEWER_ROLES.gateReview, "gateReview.by"),
1287
+ by: gateReviewBy,
817
1288
  recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
818
1289
  reportPath: gateReportPath,
819
- evidence: textField(gateReview["evidence"], "gateReview.evidence"),
1290
+ evidence: gateReviewEvidence,
820
1291
  blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
821
1292
  },
822
1293
  iteration: {
@@ -834,17 +1305,41 @@ function validateQualityGate(input, opts) {
834
1305
  adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
835
1306
  }
836
1307
  };
1308
+ if (surface === "omo-senpi")
1309
+ return { surface, ...common };
1310
+ if (codeReview === undefined)
1311
+ return { surface, ...common };
1312
+ const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
1313
+ checkFile(codeReportPath, "codeReview.reportPath", opts);
1314
+ return {
1315
+ surface,
1316
+ ...common,
1317
+ codeReview: {
1318
+ by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
1319
+ recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
1320
+ codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
1321
+ reportPath: codeReportPath,
1322
+ evidence: textField(codeReview["evidence"], "codeReview.evidence"),
1323
+ blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
1324
+ }
1325
+ };
837
1326
  }
838
1327
  function parseSurfaceEvidence(value, byId) {
839
- if (!Array.isArray(value) || value.length === 0)
1328
+ if (!Array.isArray(value) || value.length === 0) {
840
1329
  invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
841
- return value.map((item, index) => {
1330
+ return [];
1331
+ }
1332
+ return value.flatMap((item, index) => {
842
1333
  const row = section(item, `manualQa.surfaceEvidence[${index}]`);
1334
+ if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
1335
+ return [];
843
1336
  const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
844
1337
  const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
845
1338
  for (const artifact of artifacts) {
1339
+ if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
1340
+ continue;
846
1341
  if (!artifactCompatible(surface, artifact.kind)) {
847
- invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
1342
+ invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
848
1343
  }
849
1344
  }
850
1345
  return {
@@ -858,10 +1353,14 @@ function parseSurfaceEvidence(value, byId) {
858
1353
  });
859
1354
  }
860
1355
  function parseAdversarialCases(value, byId) {
861
- if (!Array.isArray(value) || value.length === 0)
1356
+ if (!Array.isArray(value) || value.length === 0) {
862
1357
  invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
863
- return value.map((item, index) => {
1358
+ return [];
1359
+ }
1360
+ return value.flatMap((item, index) => {
864
1361
  const row = section(item, `manualQa.adversarialCases[${index}]`);
1362
+ if (isPoisoned(`manualQa.adversarialCases[${index}]`))
1363
+ return [];
865
1364
  const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
866
1365
  const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
867
1366
  return {
@@ -876,7 +1375,7 @@ function parseAdversarialCases(value, byId) {
876
1375
  });
877
1376
  }
878
1377
 
879
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts
1378
+ // components/ulw-loop/src/cli-arg-parser.ts
880
1379
  import { readFile as readFile4 } from "node:fs/promises";
881
1380
  var VALUE_FLAGS = new Set("--brief --brief-file --session-id --codex-goal-mode --validation-batch-json --goal --goal-id --criterion-id --status --evidence --notes --codex-goal-json --quality-gate-json --kind --rationale --title --objective --target-goal-id --source --after-json --directive-json --directive-file --idempotency-key --proposals-json".split(" "));
882
1381
  var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
@@ -967,7 +1466,7 @@ function parseRecordEvidenceArgs(argv) {
967
1466
  return notes ? { ...result, notes } : result;
968
1467
  }
969
1468
 
970
- // packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts
1469
+ // components/ulw-loop/src/validation-batch.ts
971
1470
  function isObject(value) {
972
1471
  return typeof value === "object" && value !== null && !Array.isArray(value);
973
1472
  }
@@ -1085,14 +1584,11 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
1085
1584
  throw new UlwLoopError(message, code);
1086
1585
  }
1087
1586
 
1088
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
1089
- var QUALITY_GATE_FS = { existsSync: existsSync3, statSync };
1587
+ // components/ulw-loop/src/checkpoint.ts
1588
+ var QUALITY_GATE_FS = { existsSync: existsSync4, statSync: statSync2 };
1090
1589
  function ulwLoopFail2(message, code) {
1091
1590
  throw new UlwLoopError(message, code);
1092
1591
  }
1093
- function normalizeObjective3(value) {
1094
- return value.replace(/\s+/g, " ").trim();
1095
- }
1096
1592
  function nonEmptyEvidence2(value) {
1097
1593
  const trimmed = value.trim();
1098
1594
  return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
@@ -1112,7 +1608,7 @@ async function readJsonInput2(raw, repoRoot) {
1112
1608
  throw error;
1113
1609
  }
1114
1610
  const path = resolve3(repoRoot, trimmed);
1115
- if (!existsSync3(path))
1611
+ if (!existsSync4(path))
1116
1612
  return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
1117
1613
  try {
1118
1614
  return JSON.parse(await readFile5(path, "utf8"));
@@ -1155,13 +1651,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
1155
1651
  return status === "blocked" ? "goal_blocked" : "goal_failed";
1156
1652
  }
1157
1653
  function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
1158
- const watch = qualityGate?.codeReview.codeQualityStatus === "WATCH";
1654
+ const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
1159
1655
  const entry = {
1160
1656
  at: now,
1161
1657
  kind: ledgerKind2(args.status, goal, aggregateCompletion),
1162
1658
  goalId: goal.id,
1163
1659
  status: goal.status,
1164
- evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
1660
+ evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
1165
1661
  };
1166
1662
  if (codexGoal !== undefined)
1167
1663
  entry.codexGoal = codexGoal;
@@ -1196,23 +1692,20 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1196
1692
  requireEssentialCriteriaPass(goal);
1197
1693
  else
1198
1694
  requireAllCriteriaPass(goal);
1199
- const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
1200
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
1201
- expectedObjective: expectedCodexObjective(plan, goal),
1202
- ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {},
1203
- allowedStatuses: aggregate ? final ? ["complete"] : ["active"] : ["complete"],
1204
- requireSnapshot: true,
1205
- requireComplete: !aggregate || final
1206
- });
1207
- codexGoal = reconciliation.snapshot.raw;
1208
- if (!reconciliation.ok) {
1209
- const objective = snapshot?.objective;
1210
- const mismatchedTaskObjective = snapshot?.available === true && objective !== undefined && normalizeObjective3(objective) !== normalizeObjective3(expectedCodexObjective(plan, goal));
1211
- const completedTaskScoped = mismatchedTaskObjective && snapshot.status === "complete" && await canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence, scope);
1212
- const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence, scope);
1213
- const taskScoped = completedTaskScoped || activeFinalTaskScoped;
1214
- if (!taskScoped)
1215
- throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
1695
+ let codexValidationError;
1696
+ try {
1697
+ codexGoal = await validateCheckpointCodexGoal({
1698
+ repoRoot,
1699
+ plan,
1700
+ goal,
1701
+ raw: args.codexGoalJson,
1702
+ evidence,
1703
+ ...scope === undefined ? {} : { scope }
1704
+ });
1705
+ } catch (error) {
1706
+ if (!(error instanceof UlwLoopError))
1707
+ throw error;
1708
+ codexValidationError = error;
1216
1709
  }
1217
1710
  if (closesBatch)
1218
1711
  requireBatchFinalReady(plan, goal);
@@ -1221,13 +1714,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1221
1714
  if (final)
1222
1715
  aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
1223
1716
  if (final || aggregateCompletion !== undefined || closesBatch) {
1224
- qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1225
- repoRoot,
1226
- fs: QUALITY_GATE_FS,
1227
- ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1228
- });
1229
- requireBatchGate(plan, goal, qualityGate);
1717
+ try {
1718
+ qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1719
+ repoRoot,
1720
+ fs: QUALITY_GATE_FS,
1721
+ reviewerSurface: resolveToolkitSurface(),
1722
+ ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1723
+ });
1724
+ requireBatchGate(plan, goal, qualityGate);
1725
+ } catch (error) {
1726
+ if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
1727
+ throw error;
1728
+ throw combineCheckpointValidationErrors(codexValidationError, error);
1729
+ }
1230
1730
  }
1731
+ if (codexValidationError !== undefined)
1732
+ throw codexValidationError;
1231
1733
  goal.status = "complete";
1232
1734
  goal.completedAt = now;
1233
1735
  goal.evidence = evidence;
@@ -1252,19 +1754,141 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1252
1754
  });
1253
1755
  }
1254
1756
 
1255
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts
1757
+ // components/ulw-loop/src/checkpoint-template.ts
1758
+ function artifactPath(base, name) {
1759
+ return `${base}/${name}`;
1760
+ }
1761
+ function gateTemplate(surface, base) {
1762
+ const artifacts = [
1763
+ {
1764
+ id: "artifact-cli",
1765
+ kind: "cli-transcript",
1766
+ description: "<replace:artifact description>",
1767
+ path: artifactPath(base, "cli-transcript.txt")
1768
+ },
1769
+ {
1770
+ id: "artifact-data",
1771
+ kind: "data-diff",
1772
+ description: "<replace:artifact description>",
1773
+ path: artifactPath(base, "data-diff.txt")
1774
+ }
1775
+ ];
1776
+ const manualQa = {
1777
+ by: "main-session",
1778
+ status: "passed",
1779
+ evidence: "<replace:manual QA evidence>",
1780
+ surfaceEvidence: [
1781
+ {
1782
+ id: "surface-cli",
1783
+ criterionRef: "<replace:criterion id>",
1784
+ surface: "cli",
1785
+ invocation: "<replace:command>",
1786
+ verdict: "passed",
1787
+ artifactRefs: ["artifact-cli"]
1788
+ },
1789
+ {
1790
+ id: "surface-data",
1791
+ criterionRef: "<replace:criterion id>",
1792
+ surface: "data",
1793
+ invocation: "<replace:command>",
1794
+ verdict: "passed",
1795
+ artifactRefs: ["artifact-data"]
1796
+ }
1797
+ ],
1798
+ adversarialCases: [
1799
+ {
1800
+ id: "<replace:adversarial case id>",
1801
+ criterionRef: "<replace:criterion id>",
1802
+ scenario: "<replace:scenario>",
1803
+ expectedBehavior: "<replace:expected behavior>",
1804
+ verdict: "not_applicable",
1805
+ reason: "<replace:reason>",
1806
+ artifactRefs: ["artifact-cli"]
1807
+ }
1808
+ ],
1809
+ artifactRefs: artifacts
1810
+ };
1811
+ const common = {
1812
+ manualQa,
1813
+ gateReview: {
1814
+ by: surface === "omo-senpi" ? "category:deep" : "main-session",
1815
+ recommendation: "APPROVE",
1816
+ reportPath: artifactPath(base, "gate-review.md"),
1817
+ evidence: "<replace:gate review evidence>",
1818
+ blockers: [],
1819
+ notes: []
1820
+ },
1821
+ iteration: {
1822
+ fullRerun: true,
1823
+ status: "passed",
1824
+ rerunCommands: ["<replace:verification command>"],
1825
+ evidence: "<replace:iteration evidence>"
1826
+ },
1827
+ criteriaCoverage: {
1828
+ totalCriteria: 0,
1829
+ passCount: 0,
1830
+ originalIntent: "<replace:original intent>",
1831
+ desiredOutcome: "<replace:desired outcome>",
1832
+ userOutcomeReview: "<replace:user outcome review>",
1833
+ adversarialClassesCovered: ["<replace:adversarial class>"]
1834
+ }
1835
+ };
1836
+ return common;
1837
+ }
1838
+ async function checkpointTemplate(repoRoot, scope, goalId) {
1839
+ const plan = await readUlwLoopPlan(repoRoot, scope);
1840
+ const targetId = goalId ?? plan.activeGoalId;
1841
+ const active = plan.goals.find((goal) => goal.id === targetId);
1842
+ if (goalId !== undefined && active === undefined)
1843
+ throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
1844
+ const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
1845
+ const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
1846
+ const guidance = [
1847
+ "codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
1848
+ "Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
1849
+ 'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
1850
+ 'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
1851
+ ...resolveToolkitSurface() === "lazycodex" ? [
1852
+ "Self-review defaults: manualQa.by and gateReview.by are main-session. Alternatives: manualQa.by accepts lazycodex-qa-executor; gateReview.by accepts lazycodex-gate-reviewer, category:deep, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session."
1853
+ ] : [],
1854
+ ...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
1855
+ ].join(" ");
1856
+ return {
1857
+ qualityGateTemplate: gateTemplate(resolveToolkitSurface(), attemptDir),
1858
+ codexGoalTemplate: {
1859
+ goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
1860
+ },
1861
+ guidance,
1862
+ ...hasAttempt ? { attemptDir } : {}
1863
+ };
1864
+ }
1865
+
1866
+ // components/ulw-loop/src/cli-output.ts
1256
1867
  var ULW_LOOP_HELP = `Usage:
1868
+ omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
1869
+ omo-agent-toolkit help | --help | -h (this message)
1870
+ omo-agent-toolkit ulw-loop help
1257
1871
  omo-agent-toolkit ulw-loop create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json <json-or-path>] [--force] [--json]
1258
1872
  omo-agent-toolkit ulw-loop status [--json]
1259
1873
  omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
1260
1874
  omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
1261
1875
  omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
1876
+ omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
1262
1877
  omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
1263
1878
  omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
1264
1879
  omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
1265
1880
  omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
1266
1881
 
1267
- All subcommands accept [--session-id <id>] to isolate state under .omo/ulw-loop/<id>/; without it, Codex session env is used when present.`;
1882
+ Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it.
1883
+ Every subcommand accepts --help | -h to print its own usage line.`;
1884
+ function subcommandHelp(subcommand) {
1885
+ const lines = ULW_LOOP_HELP.split(`
1886
+ `).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
1887
+ if (lines.length === 0)
1888
+ return ULW_LOOP_HELP;
1889
+ return ["Usage:", ...lines].join(`
1890
+ `);
1891
+ }
1268
1892
  function printJson(value) {
1269
1893
  process.stdout.write(`${JSON.stringify(value, null, 2)}
1270
1894
  `);
@@ -1330,17 +1954,18 @@ function normalizeCodexGoalMode(value) {
1330
1954
  throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
1331
1955
  }
1332
1956
 
1333
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts
1957
+ // components/ulw-loop/src/codex-goal-instruction.ts
1334
1958
  function buildCodexGoalInstruction(args) {
1335
1959
  const mode = codexGoalMode(args.plan);
1336
1960
  const createGoal = buildCreateGoalPayload(args.plan, args.goal);
1337
1961
  const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
1338
- return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal), json: createGoal };
1962
+ const surface = args.surface ?? resolveToolkitSurface();
1963
+ return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
1339
1964
  }
1340
1965
  function buildCreateGoalPayload(plan, goal) {
1341
1966
  return { objective: expectedCodexObjective(plan, goal) };
1342
1967
  }
1343
- function buildText(mode, plan, goal, createGoal, isFinal) {
1968
+ function buildText(mode, plan, goal, createGoal, isFinal, surface) {
1344
1969
  return joinLines([
1345
1970
  mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
1346
1971
  `Mode: ${mode}`,
@@ -1357,7 +1982,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
1357
1982
  "- Goals are unlimited. Do not add numeric limits.",
1358
1983
  ...modeConstraintLines(mode, isFinal),
1359
1984
  ...evidenceLayoutLines(plan),
1360
- finalSection(plan, goal, isFinal, mode === "aggregate"),
1985
+ finalSection(plan, goal, isFinal, mode === "aggregate", surface),
1361
1986
  ...checkpointLines(plan, mode),
1362
1987
  "",
1363
1988
  "create_goal payload:",
@@ -1409,19 +2034,21 @@ function evidenceLayoutLines(plan) {
1409
2034
  "- Evidence layout v2: write every artifact for the active goal (QA matrix, review reports, receipts) under the current attempt directory — read currentAttemptDir from `omo-agent-toolkit ulw-loop status --json` (.omo/evidence/ulw/<session>/<goalId>/a<attempt>). The final checkpoint rejects quality-gate artifacts outside that directory."
1410
2035
  ];
1411
2036
  }
1412
- function finalSection(plan, goal, isFinal, aggregate) {
2037
+ function finalSection(plan, goal, isFinal, aggregate, surface) {
2038
+ const roles = reviewerRolesFor(surface);
1413
2039
  if (!isFinal)
1414
2040
  return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
1415
2041
  const option = sessionOption(plan);
2042
+ if (surface === "omo-senpi")
2043
+ return senpiFinalSection(plan, goal, aggregate);
1416
2044
  const blockerCommand = `omo-agent-toolkit ulw-loop record-review-blockers${option} --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`;
1417
2045
  const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<targeted verification/manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
1418
2046
  return joinLines([
1419
- "Final story — run mandatory quality gate before update_goal:",
2047
+ "Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
1420
2048
  "- Run targeted verification for changed behavior.",
1421
2049
  "- Confirm every manualQa artifact path exists and has non-zero size.",
1422
- '- First spawn lazycodex-code-reviewer and lazycodex-qa-executor in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).',
1423
- "- Only then spawn lazycodex-gate-reviewer (same fork settings), passing those artifact paths.",
1424
- "- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
2050
+ `- Run manual QA yourself and write its artifact under currentAttemptDir. Only if the user explicitly demands strict, rigorous, or high-accuracy review, spawn ${roles.gateReview}, optionally also ${roles.codeReview} and ${roles.manualQa}; otherwise set manualQa.by and gateReview.by to "main-session".`,
2051
+ "- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage; include codeReview only when strict review was requested. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
1425
2052
  "- On a reviewer REJECT, fix only the cited blockers, rerun the affected verification/Manual-QA, and re-review the delta at most TWICE; if blockers remain, record them and surface to the user.",
1426
2053
  "- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
1427
2054
  "- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
@@ -1430,6 +2057,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
1430
2057
  ` ${checkpointCommand}`
1431
2058
  ]);
1432
2059
  }
2060
+ function senpiFinalSection(plan, goal, aggregate) {
2061
+ const option = sessionOption(plan);
2062
+ const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "$(omo-agent-toolkit ulw-loop checkpoint${option} --print-template)"`;
2063
+ return joinLines([
2064
+ "Final story — run the single-reviewer quality gate before update_goal:",
2065
+ '- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
2066
+ '- Spawn exactly one gate reviewer with task(category: "deep").',
2067
+ "- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
2068
+ "- Set gateReview.by to the exact category:<name> literal used for the successful task.",
2069
+ "- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
2070
+ '- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
2071
+ aggregate ? `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint the aggregate story: ${checkpointCommand}` : `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint: ${checkpointCommand}`
2072
+ ]);
2073
+ }
1433
2074
  function sessionOption(plan) {
1434
2075
  const prefix = ".omo/ulw-loop/";
1435
2076
  const suffix = "/goals.json";
@@ -1443,11 +2084,11 @@ function joinLines(lines) {
1443
2084
  `);
1444
2085
  }
1445
2086
 
1446
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
1447
- import { existsSync as existsSync4 } from "node:fs";
2087
+ // components/ulw-loop/src/plan-crud.ts
2088
+ import { existsSync as existsSync5 } from "node:fs";
1448
2089
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
1449
2090
 
1450
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts
2091
+ // components/ulw-loop/src/plan-goal-factory.ts
1451
2092
  function cleanLine(line) {
1452
2093
  return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
1453
2094
  }
@@ -1536,7 +2177,7 @@ function appendGoalToPlan(plan, title, objective, now) {
1536
2177
  return goal;
1537
2178
  }
1538
2179
 
1539
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
2180
+ // components/ulw-loop/src/plan-crud.ts
1540
2181
  function isScheduleEligible(goal) {
1541
2182
  return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
1542
2183
  }
@@ -1554,7 +2195,7 @@ function clearGoalBlockerFields2(goal) {
1554
2195
  }
1555
2196
  async function createUlwLoopPlan(repoRoot, args, scope) {
1556
2197
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1557
- if (!args.force && existsSync4(ulwLoopGoalsPath(repoRoot, scope))) {
2198
+ if (!args.force && existsSync5(ulwLoopGoalsPath(repoRoot, scope))) {
1558
2199
  const existing = await readUlwLoopPlan(repoRoot, scope);
1559
2200
  if (isUlwLoopDone(existing))
1560
2201
  throw completedPlanExistsError(scope);
@@ -1663,7 +2304,7 @@ function summarizeUlwLoopPlan(plan) {
1663
2304
  };
1664
2305
  }
1665
2306
 
1666
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts
2307
+ // components/ulw-loop/src/checkpoint-continuation.ts
1667
2308
  async function checkpointAndContinue(repoRoot, args, scope) {
1668
2309
  const result = await checkpointUlwLoop(repoRoot, args, scope);
1669
2310
  if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
@@ -1675,6 +2316,14 @@ async function checkpointAndContinue(repoRoot, args, scope) {
1675
2316
  return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
1676
2317
  }
1677
2318
  async function checkpoint(repoRoot, argv, json, scope) {
2319
+ if (hasFlag(argv, "--print-template")) {
2320
+ const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
2321
+ if (json)
2322
+ printJson({ ok: true, ...template });
2323
+ else
2324
+ printJson(template);
2325
+ return 0;
2326
+ }
1678
2327
  const goalId = required2(argv, "--goal-id");
1679
2328
  const statusValue = checkpointStatus(required2(argv, "--status"));
1680
2329
  const evidence = required2(argv, "--evidence");
@@ -1726,10 +2375,10 @@ function checkpointStatus(value) {
1726
2375
  throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
1727
2376
  }
1728
2377
 
1729
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
2378
+ // components/ulw-loop/src/cli-subcommands.ts
1730
2379
  import { readFile as readFile6 } from "node:fs/promises";
1731
2380
 
1732
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts
2381
+ // components/ulw-loop/src/cli-steering.ts
1733
2382
  var SOURCES = ["user_prompt_submit", "finding", "cli"];
1734
2383
  var STEERING_KIND_HELP = [
1735
2384
  `Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
@@ -2036,7 +2685,7 @@ function printSteerBatchResult(result, json) {
2036
2685
  printStatus(result.plan);
2037
2686
  }
2038
2687
 
2039
- // packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts
2688
+ // components/ulw-loop/src/review-blockers.ts
2040
2689
  var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
2041
2690
  function ulwLoopError(message, code) {
2042
2691
  throw new UlwLoopError(message, code);
@@ -2075,9 +2724,10 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2075
2724
  ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
2076
2725
  const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
2077
2726
  const aggregate = codexGoalMode(plan) === "aggregate";
2078
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
2727
+ const expected = expectedCodexObjective(plan, goal);
2728
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expected, ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
2079
2729
  if (!reconciliation.ok)
2080
- ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
2730
+ throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expected });
2081
2731
  const now = iso();
2082
2732
  for (const field of BLOCKER_FIELDS)
2083
2733
  Reflect.deleteProperty(goal, field);
@@ -2102,14 +2752,43 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2102
2752
  });
2103
2753
  }
2104
2754
 
2105
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts
2755
+ // components/ulw-loop/src/status-next-actions.ts
2756
+ function statusNextActions(plan) {
2757
+ const actions = [];
2758
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
2759
+ if (plan.goals.length === 0)
2760
+ actions.push(`No goals yet: bootstrap with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`);
2761
+ else if (active !== undefined)
2762
+ actions.push(...activeGoalActions(plan, active));
2763
+ for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
2764
+ actions.push(`${goal.id} is review_blocked: capture the reviewer verdict with \`omo-agent-toolkit ulw-loop record-review-blockers --goal-id ${goal.id} --title "<title>" --objective "<objective>" --evidence "<verdict>" --codex-goal-json '<get_goal json>'\`.`);
2765
+ if (plan.evidenceLayoutVersion !== 2 || active === undefined)
2766
+ actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
2767
+ return actions;
2768
+ }
2769
+ function activeGoalActions(plan, active) {
2770
+ const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
2771
+ if (unresolved.length > 0)
2772
+ return [
2773
+ `${active.id} has ${unresolved.length} unresolved criterion(s) (${unresolved.map((criterion) => criterion.id).join(", ")}): record proof with \`omo-agent-toolkit ulw-loop record-evidence --goal-id ${active.id} --criterion-id <id> --status pass --evidence "<observable proof>"\`.`
2774
+ ];
2775
+ if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
2776
+ return [
2777
+ `${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate. Use status --json's currentAttemptDir for all quality-gate artifacts.`
2778
+ ];
2779
+ return [
2780
+ `${active.id} passes every criterion: close it with \`omo-agent-toolkit ulw-loop checkpoint --goal-id ${active.id} --status complete --evidence "<proof>" --codex-goal-json '<get_goal json>'\`. Put quality-gate artifacts under the currentAttemptDir shown by status --json.`
2781
+ ];
2782
+ }
2783
+
2784
+ // components/ulw-loop/src/steering-mutations.ts
2106
2785
  var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
2107
2786
  var isText = (value) => typeof value === "string" && value.trim().length > 0;
2108
2787
  var text3 = (value, key) => {
2109
2788
  const candidate = read2(value, key);
2110
2789
  return isText(candidate) ? candidate.trim() : undefined;
2111
2790
  };
2112
- var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model2) => model2 === value);
2791
+ var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2113
2792
  var after = (proposal) => {
2114
2793
  const candidate = read2(proposal, "after");
2115
2794
  return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
@@ -2184,36 +2863,36 @@ function reviseCriterion(plan, proposal, now) {
2184
2863
  const current = target?.successCriteria[index];
2185
2864
  if (target === undefined || current === undefined)
2186
2865
  return;
2187
- const model2 = read2(proposal, "userModel");
2188
- target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model2) ? model2 : current.userModel };
2866
+ const model = read2(proposal, "userModel");
2867
+ target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model) ? model : current.userModel };
2189
2868
  target.updatedAt = now;
2190
2869
  }
2191
2870
 
2192
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-snapshot.ts
2871
+ // components/ulw-loop/src/steering-snapshot.ts
2193
2872
  function buildSteeringPlanSnapshot(plan, changedGoalIds) {
2194
2873
  const snapshot = {
2195
2874
  updatedAt: plan.updatedAt,
2196
2875
  goalCount: plan.goals.length,
2197
- goalIds: plan.goals.map((goal2) => goal2.id),
2198
- goals: plan.goals.filter((goal2) => changedGoalIds.has(goal2.id))
2876
+ goalIds: plan.goals.map((goal) => goal.id),
2877
+ goals: plan.goals.filter((goal) => changedGoalIds.has(goal.id))
2199
2878
  };
2200
2879
  return plan.activeGoalId === undefined ? snapshot : { ...snapshot, activeGoalId: plan.activeGoalId };
2201
2880
  }
2202
- function changedGoalIdsBetween(before, after2) {
2203
- const beforeById = new Map(before.goals.map((goal2) => [goal2.id, goal2]));
2881
+ function changedGoalIdsBetween(before, after) {
2882
+ const beforeById = new Map(before.goals.map((goal) => [goal.id, goal]));
2204
2883
  const changed = new Set;
2205
- for (const goal2 of after2.goals) {
2206
- const prior = beforeById.get(goal2.id);
2207
- if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal2))
2208
- changed.add(goal2.id);
2209
- beforeById.delete(goal2.id);
2884
+ for (const goal of after.goals) {
2885
+ const prior = beforeById.get(goal.id);
2886
+ if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal))
2887
+ changed.add(goal.id);
2888
+ beforeById.delete(goal.id);
2210
2889
  }
2211
2890
  for (const id of beforeById.keys())
2212
2891
  changed.add(id);
2213
2892
  return changed;
2214
2893
  }
2215
2894
 
2216
- // packages/omo-codex/plugin/components/ulw-loop/src/steering.ts
2895
+ // components/ulw-loop/src/steering.ts
2217
2896
  var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
2218
2897
  var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
2219
2898
  var isObject2 = (value) => typeof value === "object" && value !== null;
@@ -2226,7 +2905,7 @@ var text4 = (value, key) => {
2226
2905
  };
2227
2906
  var isKind2 = (value) => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
2228
2907
  var isSource2 = (value) => typeof value === "string" && SOURCES2.some((source) => source === value);
2229
- var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model2) => model2 === value);
2908
+ var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2230
2909
  var texts = (value, key) => {
2231
2910
  const candidate = read3(value, key);
2232
2911
  return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
@@ -2367,10 +3046,10 @@ function validateCriterion(plan, proposal, reasons) {
2367
3046
  reasons.push("revise_criterion requires goalId");
2368
3047
  else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
2369
3048
  reasons.push("revise_criterion requires criterionId");
2370
- const model2 = read3(proposal, "userModel");
2371
- if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model2 === undefined)
3049
+ const model = read3(proposal, "userModel");
3050
+ if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model === undefined)
2372
3051
  reasons.push("revise_criterion requires update");
2373
- if (model2 !== undefined && !isModel3(model2))
3052
+ if (model !== undefined && !isModel3(model))
2374
3053
  reasons.push("invalid userModel");
2375
3054
  }
2376
3055
  function applySteeringMutation(plan, proposal, audit) {
@@ -2397,8 +3076,8 @@ function applySteeringMutation(plan, proposal, audit) {
2397
3076
  function isProposal(value) {
2398
3077
  return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
2399
3078
  }
2400
- function parseUlwLoopSteeringDirective(text5) {
2401
- const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(text5);
3079
+ function parseUlwLoopSteeringDirective(text) {
3080
+ const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(text);
2402
3081
  if (match?.[1] === undefined)
2403
3082
  return null;
2404
3083
  try {
@@ -2450,7 +3129,7 @@ function ledgerEntry(proposal, audit, at) {
2450
3129
  return entry;
2451
3130
  }
2452
3131
 
2453
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts
3132
+ // components/ulw-loop/src/steering-batch.ts
2454
3133
  async function steerUlwLoopBatch(repoRoot, proposals, scope) {
2455
3134
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
2456
3135
  const plan = await readUlwLoopPlan(repoRoot, scope);
@@ -2483,16 +3162,16 @@ async function prepareBatch(repoRoot, plan, proposals, scope) {
2483
3162
  const key = proposal.idempotencyKey ?? proposal.promptSignature;
2484
3163
  const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
2485
3164
  if (prior?.steering !== undefined) {
2486
- const result2 = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
2487
- items.push({ kind: "deduped", result: result2 });
2488
- results.push(result2);
3165
+ const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
3166
+ items.push({ kind: "deduped", result });
3167
+ results.push(result);
2489
3168
  continue;
2490
3169
  }
2491
3170
  const audit = validateUlwLoopSteeringProposal(current, proposal);
2492
3171
  if (!audit.invariant.accepted) {
2493
- const result2 = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
2494
- items.push({ kind: "deduped", result: result2 });
2495
- results.push(result2);
3172
+ const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
3173
+ items.push({ kind: "deduped", result });
3174
+ results.push(result);
2496
3175
  continue;
2497
3176
  }
2498
3177
  const next = applySteeringMutation(current, proposal, audit);
@@ -2531,7 +3210,7 @@ function ledgerEntry2(proposal, audit, at) {
2531
3210
  return entry;
2532
3211
  }
2533
3212
 
2534
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
3213
+ // components/ulw-loop/src/cli-subcommands.ts
2535
3214
  async function createGoals(repoRoot, argv, json, scope) {
2536
3215
  const briefFile = readValue(argv, "--brief-file");
2537
3216
  const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile6(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
@@ -2559,13 +3238,14 @@ ledger: ${plan.ledgerPath}
2559
3238
  async function status(repoRoot, json, scope) {
2560
3239
  const plan = await readUlwLoopPlan(repoRoot, scope);
2561
3240
  if (json) {
2562
- const active = plan.goals.find((goal3) => goal3.id === plan.activeGoalId);
3241
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
2563
3242
  const currentAttemptDir = plan.evidenceLayoutVersion === 2 && active ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : undefined;
2564
3243
  printJson({
2565
3244
  ok: true,
2566
3245
  plan,
2567
3246
  summary: summarizeUlwLoopPlan(plan),
2568
- ...currentAttemptDir === undefined ? {} : { currentAttemptDir }
3247
+ ...currentAttemptDir === undefined ? {} : { currentAttemptDir },
3248
+ nextActions: statusNextActions(plan)
2569
3249
  });
2570
3250
  } else
2571
3251
  printStatus(plan);
@@ -2601,9 +3281,9 @@ async function steer(repoRoot, argv, json, scope) {
2601
3281
  const proposals = await parseSteeringProposals(argv);
2602
3282
  const single = proposals[0];
2603
3283
  if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
2604
- const result2 = await steerUlwLoop(repoRoot, single, scope);
2605
- printSteerResult(result2, json);
2606
- return result2.accepted ? 0 : 1;
3284
+ const result = await steerUlwLoop(repoRoot, single, scope);
3285
+ printSteerResult(result, json);
3286
+ return result.accepted ? 0 : 1;
2607
3287
  }
2608
3288
  const result = await steerUlwLoopBatch(repoRoot, proposals, scope);
2609
3289
  printSteerBatchResult(result, json);
@@ -2622,12 +3302,12 @@ async function addGoal(repoRoot, argv, json, scope) {
2622
3302
  }
2623
3303
  async function criteria(repoRoot, argv, json, scope) {
2624
3304
  const goalId = required4(argv, "--goal-id");
2625
- const goal3 = findGoal3(await readUlwLoopPlan(repoRoot, scope), goalId);
3305
+ const goal = findGoal3(await readUlwLoopPlan(repoRoot, scope), goalId);
2626
3306
  if (json)
2627
- printJson({ ok: true, goalId: goal3.id, criteria: goal3.successCriteria });
3307
+ printJson({ ok: true, goalId: goal.id, criteria: goal.successCriteria });
2628
3308
  else {
2629
- process.stdout.write(`criteria for ${goal3.id}:
2630
- ${goal3.successCriteria.map(formatCriterionForCli).join(`
3309
+ process.stdout.write(`criteria for ${goal.id}:
3310
+ ${goal.successCriteria.map(formatCriterionForCli).join(`
2631
3311
  `)}
2632
3312
  `);
2633
3313
  }
@@ -2681,13 +3361,13 @@ function required4(argv, flag) {
2681
3361
  throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
2682
3362
  }
2683
3363
  function findGoal3(plan, goalId) {
2684
- const goal3 = plan.goals.find((candidate) => candidate.id === goalId);
2685
- if (goal3 !== undefined)
2686
- return goal3;
3364
+ const goal = plan.goals.find((candidate) => candidate.id === goalId);
3365
+ if (goal !== undefined)
3366
+ return goal;
2687
3367
  throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
2688
3368
  }
2689
3369
 
2690
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts
3370
+ // components/ulw-loop/src/cli-commands.ts
2691
3371
  var ULW_LOOP_SUBCOMMANDS = [
2692
3372
  "help",
2693
3373
  "create-goals",
@@ -2710,7 +3390,6 @@ async function ulwLoopCommand(argv) {
2710
3390
  const repoRoot = process.cwd();
2711
3391
  const json = hasFlag(rest, "--json");
2712
3392
  try {
2713
- const scope = commandScope(rest);
2714
3393
  if (!isUlwLoopSubcommand(command)) {
2715
3394
  if (json) {
2716
3395
  printJsonError(new UlwLoopError(`Unknown ulw-loop subcommand: ${command}.`, "ULW_LOOP_SUBCOMMAND_UNKNOWN", {
@@ -2722,11 +3401,18 @@ async function ulwLoopCommand(argv) {
2722
3401
  `);
2723
3402
  return 1;
2724
3403
  }
2725
- switch (command) {
2726
- case "help":
2727
- process.stdout.write(`${ULW_LOOP_HELP}
3404
+ if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
3405
+ process.stdout.write(`${subcommandHelp(command)}
3406
+ `);
3407
+ return 0;
3408
+ }
3409
+ if (command === "help") {
3410
+ process.stdout.write(`${ULW_LOOP_HELP}
2728
3411
  `);
2729
- return 0;
3412
+ return 0;
3413
+ }
3414
+ const scope = commandScope(repoRoot, rest);
3415
+ switch (command) {
2730
3416
  case "create-goals":
2731
3417
  return await createGoals(repoRoot, rest, json, scope);
2732
3418
  case "status":
@@ -2772,26 +3458,29 @@ var SESSION_ID_FLAG = "--session-id";
2772
3458
  function sessionIdFlagPresent(argv) {
2773
3459
  return hasFlag(argv, SESSION_ID_FLAG) || argv.some((arg) => arg.startsWith(`${SESSION_ID_FLAG}=`));
2774
3460
  }
2775
- function commandScope(argv) {
3461
+ function commandScope(repoRoot, argv) {
2776
3462
  if (sessionIdFlagPresent(argv)) {
2777
- const sessionId2 = readValue(argv, SESSION_ID_FLAG)?.trim();
2778
- if (!sessionId2) {
2779
- throw new UlwLoopError(`${SESSION_ID_FLAG} requires a non-empty value.`, "ULW_LOOP_SESSION_ID_REQUIRED", {
3463
+ const sessionId = readValue(argv, SESSION_ID_FLAG)?.trim();
3464
+ if (!sessionId) {
3465
+ throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
2780
3466
  details: { flag: SESSION_ID_FLAG }
2781
3467
  });
2782
3468
  }
2783
- return { sessionId: sessionId2 };
3469
+ return { sessionId };
2784
3470
  }
2785
3471
  const sessionId = resolveUlwLoopSessionIdFromEnv();
2786
- return sessionId === null ? undefined : { sessionId };
3472
+ if (sessionId !== null)
3473
+ return { sessionId };
3474
+ const existingSessionIds = listUlwLoopSessionIds(repoRoot);
3475
+ throw new UlwLoopError(sessionScopeRequiredMessage(SESSION_ID_FLAG, existingSessionIds), "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { flag: SESSION_ID_FLAG, existingSessionIds } });
2787
3476
  }
2788
3477
 
2789
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
2790
- import { readFileSync as readFileSync2 } from "node:fs";
3478
+ // components/ulw-loop/src/ultrawork-directive.ts
3479
+ import { readFileSync as readFileSync4 } from "node:fs";
2791
3480
 
2792
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-skill-pointer.ts
2793
- import { existsSync as existsSync5, readFileSync } from "node:fs";
2794
- import { fileURLToPath } from "node:url";
3481
+ // components/ulw-loop/src/ultrawork-skill-pointer.ts
3482
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
3483
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2795
3484
  var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
2796
3485
  ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
2797
3486
 
@@ -2822,22 +3511,22 @@ Do not start the requested work until all three steps are complete.
2822
3511
  `;
2823
3512
  var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
2824
3513
  var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
2825
- var ULTRAWORK_DIRECTIVE = readFileSync(new URL("../directive.md", import.meta.url), "utf8");
3514
+ var ULTRAWORK_DIRECTIVE = readFileSync3(new URL("../directive.md", import.meta.url), "utf8");
2826
3515
  function resolveUltraworkSkillFilePath() {
2827
- return fileURLToPath(ULTRAWORK_SKILL_FILE_URL);
3516
+ return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
2828
3517
  }
2829
3518
  function buildUltraworkSkillPointer(skillFilePath) {
2830
3519
  return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
2831
3520
  }
2832
3521
  function buildUltraworkAdditionalContext(options = {}) {
2833
3522
  const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
2834
- if (skillFilePath !== null && existsSync5(skillFilePath)) {
3523
+ if (skillFilePath !== null && existsSync6(skillFilePath)) {
2835
3524
  return buildUltraworkSkillPointer(skillFilePath);
2836
3525
  }
2837
3526
  return ULTRAWORK_DIRECTIVE;
2838
3527
  }
2839
3528
 
2840
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
3529
+ // components/ulw-loop/src/ultrawork-directive.ts
2841
3530
  var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
2842
3531
  var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
2843
3532
  var TRANSCRIPT_SEARCH_BYTES = 512000;
@@ -2885,7 +3574,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
2885
3574
  return false;
2886
3575
  }
2887
3576
  function readTranscriptTail(transcriptPath) {
2888
- const rawTranscript = readFileSync2(transcriptPath);
3577
+ const rawTranscript = readFileSync4(transcriptPath);
2889
3578
  return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
2890
3579
  }
2891
3580
  function isUltraworkPrompt(prompt) {
@@ -2940,7 +3629,7 @@ function isRecord3(value) {
2940
3629
  return typeof value === "object" && value !== null && !Array.isArray(value);
2941
3630
  }
2942
3631
 
2943
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts
3632
+ // components/ulw-loop/src/codex-hook.ts
2944
3633
  var CREATE_GOAL_TOOL_NAME = "create_goal";
2945
3634
  var CREATE_GOAL_PAYLOAD_WARNING = "Use create_goal with objective only. Omit token_budget so the goal stays unlimited, and put lifecycle status changes on update_goal.";
2946
3635
  function parseUserPromptSubmitPayload(raw) {
@@ -3066,38 +3755,86 @@ function optionalString(value) {
3066
3755
  return value === undefined || typeof value === "string";
3067
3756
  }
3068
3757
  function readAll(stdin) {
3069
- return new Promise((resolve4, reject) => {
3758
+ return new Promise((resolve, reject) => {
3070
3759
  let data = "";
3071
3760
  stdin.setEncoding("utf8");
3072
3761
  stdin.on("data", (chunk) => {
3073
3762
  data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
3074
3763
  });
3075
3764
  stdin.once("error", reject);
3076
- stdin.once("end", () => resolve4(data));
3765
+ stdin.once("end", () => resolve(data));
3077
3766
  });
3078
3767
  }
3079
3768
 
3080
- // packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts
3081
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync as statSync2, writeFileSync } from "node:fs";
3082
- import { join as join2 } from "node:path";
3769
+ // components/ulw-loop/src/spawn-guard.ts
3770
+ import { randomBytes } from "node:crypto";
3771
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, statSync as statSync3, writeFileSync } from "node:fs";
3772
+ import { dirname as dirname3, join as join3 } from "node:path";
3083
3773
  var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
3084
- var DEFAULT_FANOUT_LIMIT = 60;
3085
- var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|final gate review/i;
3086
- function applySpawnGuards(payload) {
3774
+ var DEFAULT_FANOUT_LIMIT = 24;
3775
+ var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
3776
+ var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
3777
+ var REVIEW_AGENT_TYPES = [
3778
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
3779
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
3780
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
3781
+ ];
3782
+ var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
3783
+ function applySpawnGuards(payload, options = {}) {
3087
3784
  if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
3088
3785
  return "";
3089
- const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3090
- const plan = readPlan(join2(stateDir, "goals.json"));
3786
+ const breaker = readAdmissionBreaker(payload.session_id);
3787
+ if (breaker !== null)
3788
+ return deny(`Subagent admission failed earlier in this session (${breaker}). Do not spawn more workers or reviewers; report the capacity block and wait for the user.`);
3789
+ const scope = { sessionId: payload.session_id };
3790
+ const stateDir = ulwLoopDir(payload.cwd, scope);
3791
+ const plan = readPlan(join3(stateDir, "goals.json"));
3091
3792
  if (plan === null)
3092
3793
  return "";
3794
+ const lockOptions = options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs };
3795
+ try {
3796
+ return withStateLockSync(ulwLoopStateLockPath(payload.cwd, scope), () => evaluateGuards(payload, plan, stateDir), lockOptions);
3797
+ } catch (error) {
3798
+ if (isStateLockTimeout(error))
3799
+ return deny(`ulw-loop spawn guard could not take the session state lock: ${error.message}`);
3800
+ throw error;
3801
+ }
3802
+ }
3803
+ function evaluateGuards(payload, plan, stateDir) {
3804
+ const fanOutPeek = peekFanOutBudget(stateDir);
3805
+ if (fanOutPeek !== null)
3806
+ return deny(fanOutPeek);
3807
+ const missingArtifact = missingGateArtifact(payload, plan);
3808
+ if (missingArtifact !== null)
3809
+ return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
3810
+ const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
3811
+ if (reviewDenial !== null)
3812
+ return deny(reviewDenial);
3093
3813
  const fanOutDenial = consumeFanOutBudget(stateDir);
3094
3814
  if (fanOutDenial !== null)
3095
3815
  return deny(fanOutDenial);
3096
- const missingArtifact = missingGateArtifact(payload, plan);
3097
- if (missingArtifact !== null)
3098
- return deny(`spawn code-review + QA first; gate audits their artifacts: missing ${missingArtifact}`);
3099
3816
  return "";
3100
3817
  }
3818
+ async function runSpawnAdmissionRecorderCli(stdin, stdout) {
3819
+ const chunks = [];
3820
+ for await (const chunk of stdin)
3821
+ chunks.push(Buffer.from(chunk));
3822
+ try {
3823
+ const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3824
+ const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
3825
+ if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
3826
+ return;
3827
+ const dataDir = process.env["PLUGIN_DATA"];
3828
+ if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
3829
+ return;
3830
+ const markerDir = join3(dataDir, "spawn-breaker");
3831
+ mkdirSync2(markerDir, { recursive: true });
3832
+ atomicWriteJson(join3(markerDir, `${payload["session_id"]}.json`), {
3833
+ reason: response,
3834
+ at: new Date().toISOString()
3835
+ });
3836
+ } catch {}
3837
+ }
3101
3838
  async function runSpawnGuardCli(stdin, stdout) {
3102
3839
  try {
3103
3840
  const chunks = [];
@@ -3114,49 +3851,112 @@ async function runSpawnGuardCli(stdin, stdout) {
3114
3851
  return;
3115
3852
  }
3116
3853
  }
3854
+ function peekFanOutBudget(stateDir) {
3855
+ const counterPath = join3(stateDir, "spawn-count.json");
3856
+ const count = readCount(counterPath) + 1;
3857
+ const limit = fanOutLimit();
3858
+ if (count <= limit)
3859
+ return null;
3860
+ return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
3861
+ }
3117
3862
  function consumeFanOutBudget(stateDir) {
3118
- const counterPath = join2(stateDir, "spawn-count.json");
3863
+ const counterPath = join3(stateDir, "spawn-count.json");
3119
3864
  const count = readCount(counterPath) + 1;
3120
- writeFileSync(counterPath, JSON.stringify({ count }));
3865
+ atomicWriteJson(counterPath, { count });
3121
3866
  const limit = fanOutLimit();
3122
3867
  if (count <= limit)
3123
3868
  return null;
3124
3869
  return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
3125
3870
  }
3871
+ function consumeReviewSpawnBudget(payload, plan, stateDir) {
3872
+ const agentType = reviewAgentType(payload.tool_input);
3873
+ if (agentType === null)
3874
+ return null;
3875
+ const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3876
+ if (goal === undefined)
3877
+ return null;
3878
+ const counterPath = join3(stateDir, "review-spawn-counts.json");
3879
+ const limit = reviewSpawnLimit();
3880
+ const counts = readCounts(counterPath);
3881
+ const key = `${agentType}:${goal.id}:a${goal.attempt}`;
3882
+ const count = (counts[key] ?? 0) + 1;
3883
+ if (count > limit)
3884
+ return `ulw-loop reviewer no-progress cap reached (${agentType} ${count}/${limit}) for ${goal.id} attempt ${goal.attempt}. Consolidate existing review findings, or checkpoint and start a new attempt after concrete progress.`;
3885
+ counts[key] = count;
3886
+ atomicWriteJson(counterPath, counts);
3887
+ return null;
3888
+ }
3126
3889
  function missingGateArtifact(payload, plan) {
3127
3890
  if (!isGateReviewerSpawn(payload.tool_input))
3128
3891
  return null;
3129
- const goal3 = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3130
- if (goal3 === undefined || goal3.status === "complete")
3892
+ const goal = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3893
+ if (goal === undefined || goal.status === "complete")
3131
3894
  return null;
3132
- if (!goal3.successCriteria.every((criterion) => criterion.status === "pass"))
3895
+ if (!goal.successCriteria.every((criterion) => criterion.status === "pass"))
3133
3896
  return null;
3134
3897
  const scope = { sessionId: payload.session_id };
3898
+ const requiredArtifacts = [`${goal.id}-manual-qa.md`];
3135
3899
  if (plan.evidenceLayoutVersion === 2) {
3136
- const attemptDir = ulwLoopAttemptEvidenceDir(goal3.id, goal3.attempt, scope);
3137
- for (const name of [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`]) {
3138
- const relative2 = `${attemptDir}/${name}`;
3139
- if (!isNonEmptyFile(join2(payload.cwd, relative2)))
3140
- return relative2;
3900
+ const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
3901
+ for (const name of requiredArtifacts) {
3902
+ const relative = `${attemptDir}/${name}`;
3903
+ if (!isNonEmptyFile(join3(payload.cwd, relative)))
3904
+ return relative;
3141
3905
  }
3142
3906
  return null;
3143
3907
  }
3144
- const flatReport = `.omo/evidence/${goal3.id}-code-review.md`;
3145
- if (!isNonEmptyFile(join2(payload.cwd, flatReport)))
3146
- return flatReport;
3147
- if (!hasOtherEvidenceFile(join2(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
3148
- return `.omo/evidence/<any manual-QA artifact besides ${goal3.id}-code-review.md>`;
3149
- return null;
3908
+ const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
3909
+ return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
3150
3910
  }
3151
3911
  function isGateReviewerSpawn(toolInput) {
3912
+ const agentType = reviewAgentType(toolInput);
3913
+ return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
3914
+ }
3915
+ function reviewAgentType(toolInput) {
3152
3916
  if (typeof toolInput !== "object" || toolInput === null)
3153
- return false;
3917
+ return null;
3154
3918
  const record = toolInput;
3155
3919
  const agentType = record["agent_type"];
3156
- if (typeof agentType === "string")
3157
- return agentType === "lazycodex-gate-reviewer";
3920
+ if (typeof agentType === "string") {
3921
+ if (!REVIEW_AGENT_TYPE_SET.has(agentType))
3922
+ return null;
3923
+ return activeSurfaceReviewerAlias(agentType);
3924
+ }
3158
3925
  const message = record["message"];
3159
- return typeof message === "string" && GATE_MESSAGE_PATTERN.test(message);
3926
+ if (typeof message !== "string")
3927
+ return null;
3928
+ const normalizedMessage = message.toLowerCase();
3929
+ const allRoleNames = [...REVIEW_AGENT_TYPES];
3930
+ const explicitAssignment = allRoleNames.map((name) => ({
3931
+ name,
3932
+ index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
3933
+ })).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
3934
+ if (explicitAssignment !== undefined)
3935
+ return activeSurfaceReviewerAlias(explicitAssignment.name);
3936
+ const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
3937
+ if (nonReviewerActAs)
3938
+ return null;
3939
+ const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
3940
+ if (namedReviewer !== undefined)
3941
+ return activeSurfaceReviewerAlias(namedReviewer);
3942
+ return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
3943
+ }
3944
+ function activeSurfaceReviewerAlias(reviewer) {
3945
+ const activeRoles = reviewerRolesFor(resolveToolkitSurface());
3946
+ for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
3947
+ if (reviewer === roles.codeReview)
3948
+ return activeRoles.codeReview;
3949
+ if (reviewer === roles.manualQa)
3950
+ return activeRoles.manualQa;
3951
+ if (reviewer === roles.gateReview)
3952
+ return activeRoles.gateReview;
3953
+ }
3954
+ return reviewer;
3955
+ }
3956
+ function atomicWriteJson(targetPath, data) {
3957
+ const tmp = join3(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
3958
+ writeFileSync(tmp, JSON.stringify(data));
3959
+ renameSync(tmp, targetPath);
3160
3960
  }
3161
3961
  function deny(reason) {
3162
3962
  return `${JSON.stringify({
@@ -3169,6 +3969,17 @@ function deny(reason) {
3169
3969
  })}
3170
3970
  `;
3171
3971
  }
3972
+ function readAdmissionBreaker(sessionId) {
3973
+ const dataDir = process.env["PLUGIN_DATA"];
3974
+ if (typeof dataDir !== "string")
3975
+ return null;
3976
+ try {
3977
+ const value = JSON.parse(readFileSync5(join3(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
3978
+ return typeof value.reason === "string" ? value.reason : "capacity limit";
3979
+ } catch {
3980
+ return null;
3981
+ }
3982
+ }
3172
3983
  function fanOutLimit() {
3173
3984
  const raw = process.env["OMO_SPAWN_FANOUT_LIMIT"];
3174
3985
  if (raw === undefined)
@@ -3176,37 +3987,52 @@ function fanOutLimit() {
3176
3987
  const parsed = Number.parseInt(raw, 10);
3177
3988
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
3178
3989
  }
3990
+ function reviewSpawnLimit() {
3991
+ const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
3992
+ if (raw === undefined)
3993
+ return DEFAULT_REVIEW_SPAWN_LIMIT;
3994
+ const parsed = Number.parseInt(raw, 10);
3995
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
3996
+ }
3179
3997
  function isNonEmptyFile(path) {
3180
3998
  try {
3181
- return existsSync6(path) && statSync2(path).size > 0;
3999
+ return existsSync7(path) && statSync3(path).size > 0;
3182
4000
  } catch (error) {
3183
4001
  if (error instanceof Error)
3184
4002
  return false;
3185
4003
  throw error;
3186
4004
  }
3187
4005
  }
3188
- function hasOtherEvidenceFile(evidenceDir, excludedName) {
4006
+ function readCount(counterPath) {
3189
4007
  try {
3190
- return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join2(evidenceDir, name)));
4008
+ const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
4009
+ return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
3191
4010
  } catch (error) {
3192
4011
  if (error instanceof Error)
3193
- return false;
4012
+ return 0;
3194
4013
  throw error;
3195
4014
  }
3196
4015
  }
3197
- function readCount(counterPath) {
4016
+ function readCounts(counterPath) {
3198
4017
  try {
3199
- const parsed = JSON.parse(readFileSync3(counterPath, "utf8"));
3200
- return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
4018
+ const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
4019
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
4020
+ return {};
4021
+ const counts = {};
4022
+ for (const [key, value] of Object.entries(parsed)) {
4023
+ if (typeof value === "number" && value >= 0)
4024
+ counts[key] = value;
4025
+ }
4026
+ return counts;
3201
4027
  } catch (error) {
3202
4028
  if (error instanceof Error)
3203
- return 0;
4029
+ return {};
3204
4030
  throw error;
3205
4031
  }
3206
4032
  }
3207
4033
  function readPlan(goalsPath) {
3208
4034
  try {
3209
- return JSON.parse(readFileSync3(goalsPath, "utf8"));
4035
+ return JSON.parse(readFileSync5(goalsPath, "utf8"));
3210
4036
  } catch (error) {
3211
4037
  if (error instanceof Error)
3212
4038
  return null;
@@ -3214,9 +4040,9 @@ function readPlan(goalsPath) {
3214
4040
  }
3215
4041
  }
3216
4042
 
3217
- // packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts
3218
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
3219
- import { isAbsolute as isAbsolute2, join as join3, resolve as resolve4, sep as sep2 } from "node:path";
4043
+ // components/ulw-loop/src/stop-resume-hook.ts
4044
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
4045
+ import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
3220
4046
  var RESUME_CAP = 2;
3221
4047
  var CONTEXT_PRESSURE_MARKERS2 = [
3222
4048
  "context compacted",
@@ -3235,18 +4061,19 @@ function runStopResumeHook(input) {
3235
4061
  return "";
3236
4062
  if (boulderContinuationWillFire(payload.cwd, payload.session_id))
3237
4063
  return "";
3238
- const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3239
- const plan = readPlan2(join3(stateDir, "goals.json"));
4064
+ const scope = { sessionId: payload.session_id };
4065
+ const stateDir = ulwLoopDir(payload.cwd, scope);
4066
+ const plan = readPlan2(join4(stateDir, "goals.json"));
3240
4067
  if (plan === null || plan.aggregateCompletion?.status === "complete")
3241
4068
  return "";
3242
- const goal3 = resumableGoal(plan);
3243
- if (goal3 === undefined)
4069
+ const goal = resumableGoal(plan);
4070
+ if (goal === undefined)
3244
4071
  return "";
3245
- if (!consumeResumeBudget(stateDir, goal3.id))
4072
+ if (!consumeResumeBudgetLocked(ulwLoopStateLockPath(payload.cwd, scope), stateDir, goal.id))
3246
4073
  return "";
3247
4074
  const output = {
3248
4075
  decision: "block",
3249
- reason: renderResumeDirective(plan, goal3, payload.session_id)
4076
+ reason: renderResumeDirective(plan, goal, payload.session_id)
3250
4077
  };
3251
4078
  return JSON.stringify(output);
3252
4079
  }
@@ -3264,16 +4091,25 @@ async function runStopResumeHookCli(stdin, stdout) {
3264
4091
  }
3265
4092
  }
3266
4093
  function resumableGoal(plan) {
3267
- const active = plan.goals.find((goal3) => goal3.id === plan.activeGoalId);
4094
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3268
4095
  if (active !== undefined && isResumableStatus(active.status))
3269
4096
  return active;
3270
- return plan.goals.find((goal3) => isResumableStatus(goal3.status));
4097
+ return plan.goals.find((goal) => isResumableStatus(goal.status));
3271
4098
  }
3272
- function isResumableStatus(status2) {
3273
- return status2 === "pending" || status2 === "in_progress";
4099
+ function isResumableStatus(status) {
4100
+ return status === "pending" || status === "in_progress";
4101
+ }
4102
+ function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
4103
+ try {
4104
+ return withStateLockSync(lockPath, () => consumeResumeBudget(stateDir, goalId));
4105
+ } catch (error) {
4106
+ if (isStateLockTimeout(error))
4107
+ return false;
4108
+ throw error;
4109
+ }
3274
4110
  }
3275
4111
  function consumeResumeBudget(stateDir, goalId) {
3276
- const ledgerLineCount = countLedgerLines(join3(stateDir, "ledger.jsonl"));
4112
+ const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
3277
4113
  const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
3278
4114
  const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
3279
4115
  if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
@@ -3291,11 +4127,11 @@ function consumeResumeBudget(stateDir, goalId) {
3291
4127
  function isInsideDir(dir, candidate) {
3292
4128
  return candidate.startsWith(resolve4(dir) + sep2);
3293
4129
  }
3294
- function renderResumeDirective(plan, goal3, sessionId) {
4130
+ function renderResumeDirective(plan, goal, sessionId) {
3295
4131
  const normalized = normalizeUlwLoopSessionId(sessionId);
3296
4132
  const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
3297
4133
  return [
3298
- `The ulw-loop run in this session still has unfinished goals (next: ${goal3.id} — ${goal3.title}).`,
4134
+ `The ulw-loop run in this session still has unfinished goals (next: ${goal.id} — ${goal.title}).`,
3299
4135
  "The turn ended before the loop completed. Resume it now:",
3300
4136
  `1. Run \`omo-agent-toolkit ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
3301
4137
  "2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
@@ -3306,7 +4142,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
3306
4142
  }
3307
4143
  function readPlan2(goalsPath) {
3308
4144
  try {
3309
- return JSON.parse(readFileSync4(goalsPath, "utf8"));
4145
+ return JSON.parse(readFileSync6(goalsPath, "utf8"));
3310
4146
  } catch (error) {
3311
4147
  if (error instanceof Error)
3312
4148
  return null;
@@ -3315,7 +4151,7 @@ function readPlan2(goalsPath) {
3315
4151
  }
3316
4152
  function countLedgerLines(ledgerPath) {
3317
4153
  try {
3318
- return readFileSync4(ledgerPath, "utf8").split(`
4154
+ return readFileSync6(ledgerPath, "utf8").split(`
3319
4155
  `).filter(Boolean).length;
3320
4156
  } catch (error) {
3321
4157
  if (error instanceof Error)
@@ -3325,9 +4161,9 @@ function countLedgerLines(ledgerPath) {
3325
4161
  }
3326
4162
  function readCounter(counterPath) {
3327
4163
  try {
3328
- if (!existsSync7(counterPath))
4164
+ if (!existsSync8(counterPath))
3329
4165
  return null;
3330
- const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
4166
+ const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
3331
4167
  if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
3332
4168
  return null;
3333
4169
  return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
@@ -3339,7 +4175,7 @@ function readCounter(counterPath) {
3339
4175
  }
3340
4176
  function boulderContinuationWillFire(cwd, sessionId) {
3341
4177
  try {
3342
- const raw = JSON.parse(readFileSync4(join3(cwd, ".omo", "boulder.json"), "utf8"));
4178
+ const raw = JSON.parse(readFileSync6(join4(cwd, ".omo", "boulder.json"), "utf8"));
3343
4179
  const works = raw["works"];
3344
4180
  const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
3345
4181
  return entries.some((work) => {
@@ -3358,7 +4194,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
3358
4194
  }
3359
4195
  function transcriptShowsContextPressure(transcriptPath) {
3360
4196
  try {
3361
- const transcript = readFileSync4(transcriptPath, "utf8").toLowerCase();
4197
+ const transcript = readFileSync6(transcriptPath, "utf8").toLowerCase();
3362
4198
  return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
3363
4199
  } catch (error) {
3364
4200
  if (error instanceof Error)
@@ -3370,12 +4206,12 @@ function boulderPlanHasChecklist(cwd, entry) {
3370
4206
  const activePlan = entry["active_plan"];
3371
4207
  if (typeof activePlan !== "string" || activePlan.trim().length === 0)
3372
4208
  return false;
3373
- const planPath = isAbsolute2(activePlan) ? activePlan : join3(cwd, activePlan);
4209
+ const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
3374
4210
  const worktree = entry["worktree_path"];
3375
- const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join3(isAbsolute2(worktree) ? worktree : join3(cwd, worktree), activePlan), planPath] : [planPath];
4211
+ const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
3376
4212
  for (const candidate of candidates) {
3377
4213
  try {
3378
- return readFileSync4(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
4214
+ return readFileSync6(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
3379
4215
  } catch (error) {
3380
4216
  if (!(error instanceof Error))
3381
4217
  throw error;
@@ -3399,19 +4235,13 @@ function parseStopPayload(value) {
3399
4235
  };
3400
4236
  }
3401
4237
 
3402
- // packages/omo-codex/plugin/components/ulw-loop/src/cli.ts
3403
- var TOP_LEVEL_HELP = `Usage:
3404
- omo-agent-toolkit ulw-loop <subcommand> [args]
3405
- omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
3406
- omo-agent-toolkit help | --help | -h (this message)
3407
-
3408
- Run \`omo-agent-toolkit ulw-loop help\` for ulw-loop subcommands.
3409
- `;
4238
+ // components/ulw-loop/src/cli.ts
3410
4239
  async function main() {
3411
4240
  const argv = process.argv.slice(2);
3412
4241
  const command = argv[0];
3413
4242
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
3414
- process.stdout.write(TOP_LEVEL_HELP);
4243
+ process.stdout.write(`${ULW_LOOP_HELP}
4244
+ `);
3415
4245
  return 0;
3416
4246
  }
3417
4247
  if (command === "ulw-loop")
@@ -3432,6 +4262,10 @@ async function main() {
3432
4262
  await runStopResumeHookCli(process.stdin, process.stdout);
3433
4263
  return 0;
3434
4264
  }
4265
+ if (sub === "post-tool-use-spawn") {
4266
+ await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
4267
+ return 0;
4268
+ }
3435
4269
  if (sub === "pre-tool-use-spawn") {
3436
4270
  await runSpawnGuardCli(process.stdin, process.stdout);
3437
4271
  return 0;
@@ -3443,7 +4277,8 @@ async function main() {
3443
4277
  if (isUlwLoopSubcommand(command))
3444
4278
  return ulwLoopCommand(argv);
3445
4279
  process.stderr.write(`[omo] unknown command: ${command}
3446
- ${TOP_LEVEL_HELP}`);
4280
+ ${ULW_LOOP_HELP}
4281
+ `);
3447
4282
  return 1;
3448
4283
  }
3449
4284
  main().then((code) => {