oh-my-opencode 5.0.0-beta.6 → 5.0.0-beta.62

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 (997) hide show
  1. package/.agents/command/publish.md +59 -23
  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 +59 -23
  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 +59 -23
  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 +65 -60
  15. package/README.ko.md +66 -61
  16. package/README.md +65 -72
  17. package/README.ru.md +62 -60
  18. package/README.zh-cn.md +61 -60
  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/index.d.ts +2 -0
  30. package/dist/cli/doctor/checks/latest-version.d.ts +11 -0
  31. package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
  32. package/dist/cli/doctor/framework/constants.d.ts +5 -0
  33. package/dist/cli/doctor/framework/types.d.ts +2 -0
  34. package/dist/cli/fallback-lane-policy.d.ts +1 -0
  35. package/dist/cli/index.js +52548 -60412
  36. package/dist/cli/install-codex/install-codex-test-fixtures.d.ts +1 -1
  37. package/dist/cli/run/on-complete-hook.d.ts +2 -0
  38. package/dist/cli/runtime-commands.d.ts +1 -1
  39. package/dist/cli/worktree-sweep/classify.d.ts +16 -0
  40. package/dist/cli/worktree-sweep/format.d.ts +8 -0
  41. package/dist/cli/worktree-sweep/git.d.ts +19 -0
  42. package/dist/cli/worktree-sweep/index.d.ts +7 -0
  43. package/dist/cli/worktree-sweep/options.d.ts +2 -0
  44. package/dist/cli/worktree-sweep/parse-worktree-list.d.ts +8 -0
  45. package/dist/cli/worktree-sweep/sweep.d.ts +2 -0
  46. package/dist/cli/worktree-sweep/types.d.ts +65 -0
  47. package/dist/cli/worktree-sweep/worktree-sweep.d.ts +2 -0
  48. package/dist/cli-node/index.js +51202 -59043
  49. package/dist/config/index.d.ts +1 -1
  50. package/dist/config/schema/background-task.d.ts +1 -0
  51. package/dist/config/schema/commands.d.ts +1 -1
  52. package/dist/config/schema/hooks.d.ts +1 -2
  53. package/dist/config/schema/oh-my-opencode-config.d.ts +5 -11
  54. package/dist/config/schema/ulw-execute.d.ts +5 -0
  55. package/dist/config/schema.d.ts +0 -1
  56. package/dist/config/validate.d.ts +2 -0
  57. package/dist/create-hooks.d.ts +3 -2
  58. package/dist/features/background-agent/compaction-aware-message-resolver.d.ts +1 -0
  59. package/dist/features/background-agent/constants.d.ts +0 -7
  60. package/dist/features/background-agent/error-classifier.d.ts +1 -0
  61. package/dist/features/background-agent/manager.d.ts +9 -1
  62. package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
  63. package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
  64. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
  65. package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
  66. package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
  67. package/dist/features/background-agent/types.d.ts +4 -0
  68. package/dist/features/btw-side/btw-command-draft.d.ts +7 -0
  69. package/dist/features/btw-side/context-injector.d.ts +15 -0
  70. package/dist/features/btw-side/index.d.ts +8 -0
  71. package/dist/features/btw-side/metadata.d.ts +15 -0
  72. package/dist/features/btw-side/parent-context-budget.d.ts +9 -0
  73. package/dist/features/btw-side/server-session-registry.d.ts +8 -0
  74. package/dist/features/btw-side/tui-adoption-cache.d.ts +13 -0
  75. package/dist/features/btw-side/tui-adoption-guard.d.ts +5 -0
  76. package/dist/features/btw-side/tui-controller-types.d.ts +68 -0
  77. package/dist/features/btw-side/tui-controller.d.ts +19 -0
  78. package/dist/features/btw-side/tui-escape-return.d.ts +12 -0
  79. package/dist/features/btw-side/tui-keymap.d.ts +17 -0
  80. package/dist/features/btw-side/tui-parent-validator.d.ts +6 -0
  81. package/dist/features/btw-side/tui-picker-options.d.ts +20 -0
  82. package/dist/features/btw-side/tui-picker.d.ts +9 -0
  83. package/dist/features/btw-side/tui-prompt-queue.d.ts +8 -0
  84. package/dist/features/btw-side/tui-session-bridge.d.ts +11 -0
  85. package/dist/features/btw-side/tui-session-catalog.d.ts +35 -0
  86. package/dist/features/btw-side/tui-side-removal.d.ts +11 -0
  87. package/dist/features/btw-side/tui-side-start.d.ts +9 -0
  88. package/dist/features/btw-side/tui-wiring.d.ts +8 -0
  89. package/dist/features/builtin-commands/templates/ulw-execute.d.ts +1 -0
  90. package/dist/features/builtin-commands/types.d.ts +1 -1
  91. package/dist/features/claude-tasks/storage.d.ts +7 -2
  92. package/dist/features/hook-message-injector/index.d.ts +1 -0
  93. package/dist/features/hook-message-injector/sdk-message-lookup.d.ts +1 -0
  94. package/dist/features/monitor/manager-internals.d.ts +1 -1
  95. package/dist/features/monitor/output-injector-types.d.ts +1 -1
  96. package/dist/features/opengateway-provider/index.d.ts +13 -0
  97. package/dist/features/task-toast-manager/manager.d.ts +0 -24
  98. package/dist/features/team-mode/tools/messaging-fallback-wake.d.ts +17 -0
  99. package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +3 -0
  100. package/dist/features/team-mode/tools/messaging-runtime.d.ts +7 -0
  101. package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
  102. package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
  103. package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
  104. package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
  105. package/dist/hooks/atlas/atlas-hook.d.ts +1 -0
  106. package/dist/hooks/atlas/atlas-lifecycle-store.d.ts +50 -0
  107. package/dist/hooks/atlas/event-handler.d.ts +1 -0
  108. package/dist/hooks/atlas/final-wave-approval-gate.test-support.d.ts +50 -0
  109. package/dist/hooks/atlas/system-reminder-templates.d.ts +0 -1
  110. package/dist/hooks/atlas/tool-execute-before.d.ts +3 -0
  111. package/dist/hooks/atlas/types.d.ts +1 -0
  112. package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
  113. package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
  114. package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
  115. package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
  116. package/dist/hooks/compaction-context-injector/validated-model.d.ts +2 -0
  117. package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
  118. package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
  119. package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
  120. package/dist/hooks/index.d.ts +1 -2
  121. package/dist/hooks/keyword-detector/hook.d.ts +9 -0
  122. package/dist/hooks/keyword-detector/ultrawork/index.d.ts +2 -0
  123. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
  124. package/dist/hooks/model-fallback/hook.d.ts +1 -1
  125. package/dist/hooks/prometheus-md-only/agent-resolution.d.ts +2 -2
  126. package/dist/hooks/runtime-fallback/chat-message-handler.d.ts +2 -0
  127. package/dist/hooks/runtime-fallback/retry-model-payload.d.ts +1 -0
  128. package/dist/hooks/runtime-fallback/runtime-model-settings.d.ts +6 -0
  129. package/dist/hooks/runtime-fallback/session-status-handler.d.ts +1 -1
  130. package/dist/hooks/runtime-fallback/types.d.ts +3 -1
  131. package/dist/hooks/sisyphus-junior-notepad/constants.d.ts +1 -1
  132. package/dist/hooks/todo-continuation-enforcer/types.d.ts +1 -0
  133. package/dist/hooks/{start-work → ulw-execute}/context-info-builder.d.ts +1 -1
  134. package/dist/hooks/{start-work → ulw-execute}/index.d.ts +1 -1
  135. package/dist/hooks/ulw-execute/ulw-execute-hook.d.ts +23 -0
  136. package/dist/hooks/unstable-agent-babysitter/task-message-analyzer.d.ts +1 -0
  137. package/dist/index.js +29256 -25391
  138. package/dist/mcp/index.d.ts +0 -4
  139. package/dist/mcp/types.d.ts +0 -1
  140. package/dist/oh-my-opencode.schema.json +21 -47
  141. package/dist/plugin/chat-message/types.d.ts +6 -4
  142. package/dist/plugin/chat-message/ulw-execute-message.d.ts +5 -0
  143. package/dist/plugin/event-session-lifecycle.d.ts +1 -0
  144. package/dist/plugin/hooks/create-core-hooks.d.ts +2 -2
  145. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -3
  146. package/dist/plugin/hooks/create-transform-hooks.d.ts +2 -0
  147. package/dist/plugin/messages-transform.d.ts +1 -0
  148. package/dist/plugin/stop-continuation.d.ts +3 -0
  149. package/dist/plugin/system-transform.d.ts +5 -1
  150. package/dist/plugin-dispose.d.ts +3 -0
  151. package/dist/shared/agent-tool-restrictions.d.ts +0 -1
  152. package/dist/shared/compaction-agent-config-checkpoint.d.ts +1 -0
  153. package/dist/shared/index.d.ts +2 -0
  154. package/dist/shared/legacy-plugin-warning.d.ts +2 -1
  155. package/dist/shared/normalize-sdk-response.d.ts +1 -0
  156. package/dist/shared/omo-process-sweep.d.ts +2 -4
  157. package/dist/shared/plugin-entry-migrator.d.ts +4 -3
  158. package/dist/shared/plugin-entry-shape.d.ts +6 -0
  159. package/dist/shared/runtime-fallback-retry-marker.d.ts +16 -0
  160. package/dist/shared/shell-env.d.ts +1 -1
  161. package/dist/skills/ast-grep/AGENTS.md +51 -0
  162. package/dist/skills/ast-grep/SKILL.md +1 -1
  163. package/dist/skills/coding-agent-sessions/AGENTS.md +62 -0
  164. package/dist/skills/coding-agent-sessions/SKILL.md +1 -1
  165. package/dist/skills/data-scientist/SKILL.md +99 -239
  166. package/dist/skills/data-scientist/references/execution-surfaces.md +91 -0
  167. package/dist/skills/data-scientist/references/placement.md +74 -0
  168. package/dist/skills/data-scientist/references/polars-lane.md +95 -0
  169. package/dist/skills/data-scientist/references/uv-setup.md +1 -1
  170. package/dist/skills/data-scientist/references/visualization.md +64 -0
  171. package/dist/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  172. package/dist/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  173. package/dist/skills/debugging/SKILL.md +3 -1
  174. package/dist/skills/debugging/references/methodology/00-setup.md +12 -0
  175. package/dist/skills/debugging/references/methodology/02-investigate.md +21 -0
  176. package/dist/skills/debugging/references/runtimes/go.md +15 -1
  177. package/dist/skills/debugging/references/runtimes/native-binary.md +38 -0
  178. package/dist/skills/debugging/references/runtimes/node.md +25 -0
  179. package/dist/skills/debugging/references/runtimes/python.md +13 -0
  180. package/dist/skills/debugging/references/runtimes/rust.md +41 -0
  181. package/dist/skills/debugging/references/scripts/dap.mjs +267 -0
  182. package/dist/skills/debugging/references/scripts/dap.test.ts +86 -0
  183. package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  184. package/dist/skills/debugging/references/tools/dap.md +103 -0
  185. package/dist/skills/debugging/references/tools/frida.md +193 -0
  186. package/dist/skills/frontend/ATTRIBUTION.md +10 -0
  187. package/dist/skills/frontend/SKILL.md +15 -8
  188. package/dist/skills/frontend/references/design/README.md +10 -2
  189. package/dist/skills/frontend/references/design/_INDEX.md +2 -0
  190. package/dist/skills/frontend/references/design/ambience-skill.md +145 -0
  191. package/dist/skills/frontend/references/design/interaction-skill.md +2 -0
  192. package/dist/skills/frontend/references/design/print-paged-media.md +76 -0
  193. package/dist/skills/frontend/references/design/stylegallery.md +80 -0
  194. package/dist/skills/frontend/references/designpowers/README.md +1 -1
  195. package/dist/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  196. package/dist/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  197. package/dist/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  198. package/dist/skills/frontend/references/designpowers/orchestration.md +1 -1
  199. package/dist/skills/frontend/references/designpowers/routing.md +5 -5
  200. package/dist/skills/git-master/SKILL.md +1 -1
  201. package/dist/skills/init-deep/SKILL.md +14 -14
  202. package/dist/skills/lsp-setup/SKILL.md +1 -1
  203. package/dist/skills/programming/SKILL.md +1 -1
  204. package/dist/skills/refactor/SKILL.md +4 -4
  205. package/dist/skills/remove-ai-slops/SKILL.md +1 -1
  206. package/dist/skills/review-work/SKILL.md +101 -419
  207. package/dist/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
  208. package/dist/skills/ultimate-browsing/SKILL.md +26 -9
  209. package/dist/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  210. package/dist/skills/ultimate-browsing/engine/templates/package.json +1 -1
  211. package/dist/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
  212. package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  213. package/dist/skills/{start-work → ulw-execute}/SKILL.md +20 -19
  214. package/dist/skills/ulw-plan/SKILL.md +7 -8
  215. package/dist/skills/ulw-plan/references/full-workflow.md +31 -7
  216. package/dist/skills/ulw-plan/references/intent-clear.md +2 -1
  217. package/dist/skills/ulw-plan/references/intent-unclear.md +5 -5
  218. package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -1
  219. package/dist/skills/ulw-research/SKILL.md +8 -3
  220. package/dist/skills/visual-qa/AGENTS.md +58 -0
  221. package/dist/skills/visual-qa/SKILL.md +4 -3
  222. package/dist/tools/delegate-task/builtin-categories.d.ts +3 -1
  223. package/dist/tools/delegate-task/builtin-category-definition.d.ts +2 -1
  224. package/dist/tools/delegate-task/constants.d.ts +2 -2
  225. package/dist/tools/delegate-task/openai-categories.d.ts +7 -2
  226. package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
  227. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
  228. package/dist/tools/delegate-task/tool-description.d.ts +3 -1
  229. package/dist/tools/delegate-task/types.d.ts +4 -0
  230. package/dist/tools/session-manager/directory-filter.d.ts +2 -0
  231. package/dist/tui.d.ts +12 -0
  232. package/dist/tui.js +37048 -46599
  233. package/package.json +47 -41
  234. package/packages/git-bash-mcp/dist/cli.js +6 -1
  235. package/packages/git-bash-mcp/package.json +1 -1
  236. package/packages/lsp-core/src/index.ts +1 -0
  237. package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +44 -0
  238. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +57 -48
  239. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  240. package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
  241. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +103 -6
  242. package/packages/lsp-core/src/lsp/client-wrapper.ts +50 -16
  243. package/packages/lsp-core/src/lsp/client.ts +32 -3
  244. package/packages/lsp-core/src/lsp/connection.ts +16 -0
  245. package/packages/lsp-core/src/lsp/constants.ts +6 -0
  246. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
  247. package/packages/lsp-core/src/lsp/errors.ts +15 -0
  248. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
  249. package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
  250. package/packages/lsp-core/src/lsp/format-document.ts +90 -0
  251. package/packages/lsp-core/src/lsp/json-rpc-connection.ts +11 -6
  252. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  253. package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
  254. package/packages/lsp-core/src/lsp/manager.ts +138 -12
  255. package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
  256. package/packages/lsp-core/src/lsp/server-definitions.ts +29 -0
  257. package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
  258. package/packages/lsp-core/src/lsp/server-installation.ts +200 -27
  259. package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
  260. package/packages/lsp-core/src/lsp/server-resolution.ts +18 -5
  261. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  262. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  263. package/packages/lsp-core/src/lsp/transport.ts +16 -2
  264. package/packages/lsp-core/src/lsp/types.ts +8 -0
  265. package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +29 -1
  266. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
  267. package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
  268. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
  269. package/packages/lsp-core/src/mcp.ts +3 -0
  270. package/packages/lsp-core/src/request-context.test.ts +29 -0
  271. package/packages/lsp-core/src/request-context.ts +1 -1
  272. package/packages/lsp-core/src/tool-surface.test.ts +13 -1
  273. package/packages/lsp-core/src/tools/definitions.ts +14 -0
  274. package/packages/lsp-core/src/tools/format.test.ts +56 -0
  275. package/packages/lsp-core/src/tools/format.ts +56 -0
  276. package/packages/lsp-core/src/tools/index.ts +1 -0
  277. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  278. package/packages/lsp-core/src/tools/types.ts +10 -0
  279. package/packages/lsp-daemon/dist/cli.js +902 -479
  280. package/packages/lsp-daemon/dist/client.d.ts +10 -0
  281. package/packages/lsp-daemon/dist/client.js +920 -493
  282. package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
  283. package/packages/lsp-daemon/dist/daemon-client.js +3 -0
  284. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +6 -1
  285. package/packages/lsp-daemon/dist/ensure-daemon.js +10 -2
  286. package/packages/lsp-daemon/dist/index.js +824 -402
  287. package/packages/lsp-daemon/dist/ownership.js +2 -2
  288. package/packages/lsp-daemon/dist/proxy.js +3 -0
  289. package/packages/lsp-daemon/dist/version-reap.js +1 -1
  290. package/packages/lsp-daemon/package.json +1 -1
  291. package/packages/lsp-tools-mcp/dist/cli.js +762 -341
  292. package/packages/lsp-tools-mcp/dist/lsp/manager.js +272 -138
  293. package/packages/lsp-tools-mcp/dist/mcp.js +787 -368
  294. package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
  295. package/packages/lsp-tools-mcp/dist/tools.js +717 -302
  296. package/packages/lsp-tools-mcp/package.json +1 -1
  297. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +4 -21
  298. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +4 -6
  299. package/packages/omo-codex/plugin/.mcp.json +0 -6
  300. package/packages/omo-codex/plugin/AGENTS.md +47 -0
  301. package/packages/omo-codex/plugin/README.md +6 -2
  302. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +7093 -408
  303. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  304. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  305. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -0
  306. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +7 -1
  307. package/packages/omo-codex/plugin/components/comment-checker/AGENTS.md +9 -0
  308. package/packages/omo-codex/plugin/components/comment-checker/biome.json +2 -2
  309. package/packages/omo-codex/plugin/components/comment-checker/dist/cli.js +2 -1
  310. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  311. package/packages/omo-codex/plugin/components/comment-checker/package.json +5 -5
  312. package/packages/omo-codex/plugin/components/comment-checker/src/apply-patch.ts +2 -8
  313. package/packages/omo-codex/plugin/components/comment-checker/src/core.ts +1 -2
  314. package/packages/omo-codex/plugin/components/comment-checker/src/request-extractor.ts +1 -1
  315. package/packages/omo-codex/plugin/components/comment-checker/src/runner.ts +1 -0
  316. package/packages/omo-codex/plugin/components/git-bash/AGENTS.md +1 -1
  317. package/packages/omo-codex/plugin/components/git-bash/dist/cli.js +1 -1
  318. package/packages/omo-codex/plugin/components/git-bash/dist/codex-hook.js +2 -1
  319. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  320. package/packages/omo-codex/plugin/components/git-bash/package.json +3 -4
  321. package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +2 -1
  322. package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +6 -7
  323. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/AGENTS.md +2 -2
  324. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +2 -2
  325. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/directive.md +2 -0
  326. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/cli.js +36 -29
  327. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/codex-hook.js +32 -26
  328. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/types.d.ts +3 -0
  329. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  330. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +5 -5
  331. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/codex-hook.ts +32 -28
  332. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/types.ts +3 -0
  333. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/cli.test.ts +1 -1
  334. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +137 -28
  335. package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +4 -2
  336. package/packages/omo-codex/plugin/components/lsp/AGENTS.md +30 -15
  337. package/packages/omo-codex/plugin/components/lsp/biome.json +2 -2
  338. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
  339. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +941 -540
  340. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +1 -1
  341. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  342. package/packages/omo-codex/plugin/components/lsp/package.json +5 -5
  343. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +1 -1
  344. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +1 -1
  345. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +1 -5
  346. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +6 -7
  347. package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +1 -2
  348. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +11 -1
  349. package/packages/omo-codex/plugin/components/rules/AGENTS.md +12 -0
  350. package/packages/omo-codex/plugin/components/rules/README.md +2 -0
  351. package/packages/omo-codex/plugin/components/rules/biome.json +2 -2
  352. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +123 -0
  353. package/packages/omo-codex/plugin/components/rules/dist/cli.js +91 -68
  354. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  355. package/packages/omo-codex/plugin/components/rules/package.json +7 -7
  356. package/packages/omo-codex/plugin/components/rules/src/config.ts +1 -2
  357. package/packages/omo-codex/plugin/components/rules/src/persistent-cache.ts +1 -2
  358. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +15 -5
  359. package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +1 -4
  360. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -9
  361. package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
  362. package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +17 -25
  363. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
  364. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
  365. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
  366. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
  367. package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
  368. package/packages/omo-codex/plugin/components/rules/test/dynamic-target-fingerprints.test.ts +1 -2
  369. package/packages/omo-codex/plugin/components/rules/test/engine.test.ts +7 -4
  370. package/packages/omo-codex/plugin/components/rules/test/finder.test.ts +2 -3
  371. package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +39 -72
  372. package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +38 -3
  373. package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
  374. package/packages/omo-codex/plugin/components/rules/test/matcher.test.ts +2 -3
  375. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +1 -1
  376. package/packages/omo-codex/plugin/components/rules/test/parser.test.ts +1 -2
  377. package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +29 -6
  378. package/packages/omo-codex/plugin/components/rules/test/rules-engine-consumption.test.ts +3 -6
  379. package/packages/omo-codex/plugin/components/rules/test/scanner.test.ts +1 -2
  380. package/packages/omo-codex/plugin/components/rules/test/sources.test.ts +2 -5
  381. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +2 -17
  382. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  383. package/packages/omo-codex/plugin/components/teammode/package.json +5 -5
  384. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +4 -10
  385. package/packages/omo-codex/plugin/components/telemetry/AGENTS.md +8 -0
  386. package/packages/omo-codex/plugin/components/telemetry/biome.json +2 -2
  387. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +2407 -684
  388. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2411 -689
  389. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  390. package/packages/omo-codex/plugin/components/telemetry/package.json +5 -5
  391. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +1 -4
  392. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -14
  393. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +1 -1
  394. package/packages/omo-codex/plugin/components/telemetry/test/diagnostics.test.ts +2 -5
  395. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -4
  396. package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -0
  397. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  398. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
  399. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
  400. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
  401. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
  402. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +1 -1
  403. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-low.toml +1 -1
  404. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +1 -1
  405. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +1 -1
  406. package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +1 -1
  407. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +1 -1
  408. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +1 -1
  409. package/packages/omo-codex/plugin/components/ultrawork/biome.json +2 -2
  410. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +499 -4
  411. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  412. package/packages/omo-codex/plugin/components/ultrawork/package.json +5 -6
  413. package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
  414. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +6 -7
  415. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +31 -7
  416. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -1
  417. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +5 -5
  418. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
  419. package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
  420. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +9 -1
  421. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
  422. package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
  423. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
  424. package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
  425. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/AGENTS.md +4 -4
  426. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/NOTICE +1 -1
  427. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/README.md +6 -6
  428. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/biome.json +2 -2
  429. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/directive.md +10 -10
  430. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/dist/cli.js +16 -16
  431. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +16 -0
  432. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/package.json +12 -12
  433. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/boulder-reader.ts +1 -1
  434. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/cli.ts +1 -1
  435. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/codex-hook.ts +4 -4
  436. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/directive.ts +1 -1
  437. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/index.ts +1 -1
  438. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/types.ts +1 -1
  439. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/boulder-reader.test.ts +3 -3
  440. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/cli.test.ts +4 -11
  441. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/codex-hook.test.ts +24 -22
  442. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +19 -0
  443. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +3 -1
  444. package/packages/omo-codex/plugin/components/ulw-loop/README.md +12 -1
  445. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +2 -2
  446. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +46 -24
  447. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +16 -0
  448. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +25 -0
  449. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +10 -4
  450. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +2 -5
  451. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -78
  452. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +12 -0
  453. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +114 -0
  454. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.d.ts +7 -1
  455. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +50 -42
  456. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +2 -2
  457. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +21 -8
  458. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
  459. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +12 -1
  460. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +1 -1
  461. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +40 -37
  462. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +1468 -535
  463. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  464. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +28 -8
  465. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +2 -5
  466. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +24 -20
  467. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -0
  468. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +1 -0
  469. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
  470. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
  471. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +13 -4
  472. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +1 -0
  473. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +34 -4
  474. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +14 -0
  475. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +29 -0
  476. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +7 -0
  477. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +9 -0
  478. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +12 -0
  479. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +104 -0
  480. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
  481. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +65 -9
  482. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
  483. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  484. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +72 -117
  485. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.d.ts +2 -0
  486. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +16 -7
  487. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.d.ts +2 -0
  488. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +141 -0
  489. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +46 -0
  490. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +28 -0
  491. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +127 -0
  492. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.js +1 -0
  493. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.d.ts +3 -0
  494. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.js +3 -0
  495. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +7 -1
  496. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +216 -33
  497. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.d.ts +2 -0
  498. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.js +22 -0
  499. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +9 -0
  500. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +227 -0
  501. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
  502. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
  503. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +20 -6
  504. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +19 -0
  505. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +75 -0
  506. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +18 -5
  507. package/packages/omo-codex/plugin/components/ulw-loop/package.json +6 -6
  508. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +10 -4
  509. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +108 -0
  510. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +32 -8
  511. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +40 -0
  512. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +8 -6
  513. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +5 -98
  514. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +134 -0
  515. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +52 -73
  516. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +10 -4
  517. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +24 -8
  518. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +14 -1
  519. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +52 -46
  520. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +8 -6
  521. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +35 -7
  522. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +34 -28
  523. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts +3 -1
  524. package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +1 -0
  525. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
  526. package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +25 -4
  527. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +39 -4
  528. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +38 -0
  529. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +11 -0
  530. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +123 -0
  531. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +71 -15
  532. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
  533. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +92 -136
  534. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +29 -8
  535. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +183 -0
  536. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +42 -0
  537. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +140 -0
  538. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk.ts +3 -0
  539. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +226 -31
  540. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-role-guard.ts +22 -0
  541. package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +239 -0
  542. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
  543. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +19 -6
  544. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +107 -0
  545. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
  546. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +131 -82
  547. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +147 -0
  548. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +16 -22
  549. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +4 -2
  550. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint.test.ts +31 -2
  551. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-commands.test.ts +2 -0
  552. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-complete-goals.test.ts +3 -1
  553. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +35 -8
  554. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +38 -4
  555. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +27 -0
  556. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-scope-required.test.ts +144 -0
  557. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +185 -0
  558. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +8 -3
  559. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +6 -0
  560. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +37 -2
  561. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +52 -11
  562. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
  563. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/cli-session.ts +4 -0
  564. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +9 -4
  565. package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +67 -0
  566. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +5 -37
  567. package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +9 -0
  568. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io-cross-process.test.ts +106 -0
  569. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +36 -2
  570. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
  571. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
  572. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-fields-messages.test.ts +98 -0
  573. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-lazycodex-surface.test.ts +93 -0
  574. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
  575. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +51 -2
  576. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
  577. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
  578. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +58 -3
  579. package/packages/omo-codex/plugin/components/ulw-loop/test/review-blockers.test.ts +9 -7
  580. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-contract.test.ts +137 -0
  581. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-driver-states.test.ts +145 -0
  582. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +148 -0
  583. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +450 -7
  584. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-matrix.test.ts +113 -0
  585. package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +218 -0
  586. package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +8 -5
  587. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +1 -1
  588. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
  589. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +13 -8
  590. package/packages/omo-codex/plugin/components/ulw-loop/vitest.config.ts +1 -0
  591. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  592. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  593. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  594. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  595. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  596. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  597. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  598. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +18 -0
  599. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  600. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  601. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  602. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  603. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  604. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  605. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  606. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +17 -0
  607. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  608. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  609. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  610. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  611. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  612. package/packages/omo-codex/plugin/model-catalog.json +16 -7
  613. package/packages/omo-codex/plugin/package-lock.json +676 -524
  614. package/packages/omo-codex/plugin/package.json +2 -3
  615. package/packages/omo-codex/plugin/scripts/AGENTS.md +39 -0
  616. package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +1 -2
  617. package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
  618. package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
  619. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
  620. package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +16 -7
  621. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +8 -4
  622. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +19 -90
  623. package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +1 -1
  624. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +96 -57
  625. package/packages/omo-codex/plugin/shared/package.json +3 -4
  626. package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
  627. package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
  628. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
  629. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +51 -0
  630. package/packages/omo-codex/plugin/skills/ast-grep/SKILL.md +1 -1
  631. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +62 -0
  632. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +1 -1
  633. package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
  634. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
  635. package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
  636. package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
  637. package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
  638. package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
  639. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  640. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  641. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +3 -1
  642. package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
  643. package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
  644. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
  645. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
  646. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
  647. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
  648. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
  649. package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
  650. package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  651. package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
  652. package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
  653. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +10 -0
  654. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +15 -8
  655. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +10 -2
  656. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -0
  657. package/packages/omo-codex/plugin/skills/frontend/references/design/ambience-skill.md +145 -0
  658. package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +2 -0
  659. package/packages/omo-codex/plugin/skills/frontend/references/design/print-paged-media.md +76 -0
  660. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +80 -0
  661. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/README.md +1 -1
  662. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  663. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  664. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  665. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/orchestration.md +1 -1
  666. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/routing.md +5 -5
  667. package/packages/omo-codex/plugin/skills/git-master/SKILL.md +1 -1
  668. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +20 -18
  669. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +4 -2
  670. package/packages/omo-codex/plugin/skills/lsp-setup/SKILL.md +1 -1
  671. package/packages/omo-codex/plugin/skills/programming/SKILL.md +1 -1
  672. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +10 -8
  673. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +7 -5
  674. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +116 -424
  675. package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
  676. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +26 -9
  677. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  678. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +1 -1
  679. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
  680. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  681. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +46 -24
  682. package/packages/omo-codex/plugin/skills/{start-work → ulw-execute}/SKILL.md +31 -33
  683. package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
  684. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +34 -4
  685. package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +108 -0
  686. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +32 -8
  687. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +6 -7
  688. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +31 -7
  689. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -1
  690. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +5 -5
  691. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +14 -7
  692. package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +58 -0
  693. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +10 -7
  694. package/packages/omo-codex/plugin/test/AGENTS.md +47 -0
  695. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
  696. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +16 -51
  697. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -3
  698. package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
  699. package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +14 -4
  700. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
  701. package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
  702. package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
  703. package/packages/omo-codex/plugin/test/auto-update.test.mjs +4 -29
  704. package/packages/omo-codex/plugin/test/bootstrap-default-role.test.mjs +66 -0
  705. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
  706. package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
  707. package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +2 -2
  708. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -51
  709. package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +100 -2
  710. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +5 -6
  711. package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
  712. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
  713. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +35 -35
  714. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +1 -1
  715. package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
  716. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +107 -15
  717. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +168 -0
  718. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +54 -24
  719. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +18 -119
  720. package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
  721. package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
  722. package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
  723. package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
  724. package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
  725. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -5
  726. package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
  727. package/packages/omo-codex/scripts/AGENTS.md +36 -0
  728. package/packages/omo-codex/scripts/check-model-catalog-parity.mjs +42 -0
  729. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
  730. package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +1 -1
  731. package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +2 -1
  732. package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
  733. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +5 -5
  734. package/packages/omo-codex/scripts/install-config.test.mjs +15 -17
  735. package/packages/omo-codex/scripts/install-dist/install-local.mjs +9652 -1394
  736. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +3 -4
  737. package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
  738. package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +7 -2
  739. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
  740. package/packages/omo-codex/scripts/install-packaged-local.test.mjs +1 -6
  741. package/packages/{omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md → prompts-core/prompts/ultrawork/codex.md} +46 -31
  742. package/packages/shared-skills/package.json +7 -1
  743. package/packages/shared-skills/skill-source-filter.d.ts +16 -0
  744. package/packages/shared-skills/skill-source-filter.mjs +44 -0
  745. package/packages/shared-skills/skills/ast-grep/AGENTS.md +51 -0
  746. package/packages/shared-skills/skills/ast-grep/SKILL.md +1 -1
  747. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +62 -0
  748. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +1 -1
  749. package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
  750. package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
  751. package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
  752. package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
  753. package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
  754. package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
  755. package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  756. package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  757. package/packages/shared-skills/skills/debugging/SKILL.md +3 -1
  758. package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
  759. package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
  760. package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
  761. package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
  762. package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
  763. package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
  764. package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
  765. package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
  766. package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
  767. package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  768. package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
  769. package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
  770. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +10 -0
  771. package/packages/shared-skills/skills/frontend/SKILL.md +15 -8
  772. package/packages/shared-skills/skills/frontend/references/design/README.md +10 -2
  773. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -0
  774. package/packages/shared-skills/skills/frontend/references/design/ambience-skill.md +145 -0
  775. package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +2 -0
  776. package/packages/shared-skills/skills/frontend/references/design/print-paged-media.md +76 -0
  777. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +80 -0
  778. package/packages/shared-skills/skills/frontend/references/designpowers/README.md +1 -1
  779. package/packages/shared-skills/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  780. package/packages/shared-skills/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  781. package/packages/shared-skills/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  782. package/packages/shared-skills/skills/frontend/references/designpowers/orchestration.md +1 -1
  783. package/packages/shared-skills/skills/frontend/references/designpowers/routing.md +5 -5
  784. package/packages/shared-skills/skills/git-master/SKILL.md +1 -1
  785. package/packages/shared-skills/skills/init-deep/SKILL.md +14 -14
  786. package/packages/shared-skills/skills/lsp-setup/SKILL.md +1 -1
  787. package/packages/shared-skills/skills/programming/SKILL.md +1 -1
  788. package/packages/shared-skills/skills/refactor/SKILL.md +4 -4
  789. package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +1 -1
  790. package/packages/shared-skills/skills/review-work/SKILL.md +101 -419
  791. package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +38 -11
  792. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +26 -9
  793. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  794. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +1 -1
  795. package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +23 -18
  796. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  797. package/packages/shared-skills/skills/{start-work → ulw-execute}/SKILL.md +20 -19
  798. package/packages/shared-skills/skills/ulw-plan/SKILL.md +7 -8
  799. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +31 -7
  800. package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -1
  801. package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +5 -5
  802. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -1
  803. package/packages/shared-skills/skills/ulw-research/SKILL.md +8 -3
  804. package/packages/shared-skills/skills/visual-qa/AGENTS.md +58 -0
  805. package/packages/shared-skills/skills/visual-qa/SKILL.md +4 -3
  806. package/postinstall.mjs +2 -0
  807. package/script/qa/web-terminal-visual-qa.mjs +2 -0
  808. package/script/qa/xterm-live-terminal.mjs +18 -2
  809. package/dist/config/schema/codegraph.d.ts +0 -12
  810. package/dist/config/schema/start-work.d.ts +0 -5
  811. package/dist/features/builtin-commands/templates/start-work.d.ts +0 -1
  812. package/dist/features/builtin-skills/index.d.ts +0 -1
  813. package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
  814. package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
  815. package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
  816. package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
  817. package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
  818. package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
  819. package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
  820. package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
  821. package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
  822. package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
  823. package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
  824. package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
  825. package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
  826. package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
  827. package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
  828. package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
  829. package/dist/features/builtin-skills/skills/index.d.ts +0 -1
  830. package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
  831. package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
  832. package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
  833. package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
  834. package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
  835. package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
  836. package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
  837. package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
  838. package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
  839. package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
  840. package/dist/features/builtin-skills/skills.d.ts +0 -1
  841. package/dist/features/builtin-skills/types.d.ts +0 -1
  842. package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
  843. package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
  844. package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
  845. package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
  846. package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
  847. package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
  848. package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
  849. package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
  850. package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
  851. package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
  852. package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
  853. package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
  854. package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
  855. package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
  856. package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
  857. package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
  858. package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
  859. package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
  860. package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
  861. package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
  862. package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
  863. package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
  864. package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
  865. package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
  866. package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
  867. package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
  868. package/dist/hooks/start-work/start-work-hook.d.ts +0 -23
  869. package/dist/mcp/codegraph.d.ts +0 -16
  870. package/dist/plugin/chat-message/start-work-message.d.ts +0 -5
  871. package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
  872. package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
  873. package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
  874. package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
  875. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
  876. package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
  877. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
  878. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
  879. package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
  880. package/dist/shared/prompt-async-gate/types.d.ts +0 -1
  881. package/dist/skills/data-scientist/references/common-scenarios.md +0 -176
  882. package/dist/skills/data-scientist/references/execution-templates.md +0 -197
  883. package/dist/skills/data-scientist/references/integration-patterns.md +0 -153
  884. package/dist/skills/data-scientist/references/performance-benchmarks.md +0 -37
  885. package/dist/tools/call-omo-agent/background-agent-executor.d.ts +0 -5
  886. package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
  887. package/dist/tools/delegate-task/anthropic-categories.d.ts +0 -2
  888. package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
  889. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  890. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  891. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  892. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  893. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
  894. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
  895. package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
  896. package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
  897. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12149
  898. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9747
  899. package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
  900. package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
  901. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
  902. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
  903. package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
  904. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
  905. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
  906. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -309
  907. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -80
  908. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
  909. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
  910. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
  911. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
  912. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
  913. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -21
  914. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
  915. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
  916. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
  917. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
  918. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
  919. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
  920. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
  921. package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
  922. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
  923. package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
  924. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
  925. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
  926. package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
  927. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
  928. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
  929. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
  930. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
  931. package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
  932. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
  933. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
  934. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
  935. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
  936. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
  937. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
  938. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
  939. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
  940. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
  941. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
  942. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
  943. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
  944. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
  945. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
  946. package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
  947. package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
  948. package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
  949. package/packages/omo-codex/plugin/components/start-work-continuation/LICENSE +0 -21
  950. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +0 -28
  951. package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -471
  952. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
  953. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
  954. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +0 -17
  955. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +0 -17
  956. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
  957. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
  958. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
  959. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
  960. package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
  961. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
  962. package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
  963. package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
  964. package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +0 -2
  965. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
  966. package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
  967. package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
  968. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
  969. package/packages/omo-codex/plugin/test/ulw-plan-scope-contract.test.mjs +0 -24
  970. package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
  971. package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
  972. package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
  973. package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
  974. /package/dist/hooks/{start-work → ulw-execute}/context-info-formatters.d.ts +0 -0
  975. /package/dist/hooks/{start-work → ulw-execute}/explicit-plan-context.d.ts +0 -0
  976. /package/dist/hooks/{start-work → ulw-execute}/notepad-scaffold.d.ts +0 -0
  977. /package/dist/hooks/{start-work → ulw-execute}/parse-user-request.d.ts +0 -0
  978. /package/dist/hooks/{start-work → ulw-execute}/plan-discovery-context.d.ts +0 -0
  979. /package/dist/hooks/{start-work → ulw-execute}/plan-selection.d.ts +0 -0
  980. /package/dist/hooks/{start-work → ulw-execute}/session-plan-affinity.d.ts +0 -0
  981. /package/dist/hooks/{start-work → ulw-execute}/work-initializer.d.ts +0 -0
  982. /package/dist/hooks/{start-work → ulw-execute}/worktree-block.d.ts +0 -0
  983. /package/dist/hooks/{start-work → ulw-execute}/worktree-detector.d.ts +0 -0
  984. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/.gitattributes +0 -0
  985. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/CHANGELOG.md +0 -0
  986. /package/packages/omo-codex/plugin/components/{codegraph → ulw-execute-continuation}/LICENSE +0 -0
  987. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/plan-checklist.ts +0 -0
  988. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-completed.json +0 -0
  989. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-mixed-platforms.json +0 -0
  990. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-single-codex-work.json +0 -0
  991. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-all-done.md +0 -0
  992. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-scaffold.md +0 -0
  993. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-nested-checkboxes.md +0 -0
  994. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-unchecked.md +0 -0
  995. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.build.json +0 -0
  996. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.json +0 -0
  997. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/vitest.config.ts +0 -0
@@ -1,21 +1,120 @@
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";
5
- import { readFile as readFile5 } from "node:fs/promises";
3
+ // components/ulw-loop/src/checkpoint.ts
4
+ import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
5
+ import { readFile as readFile4 } 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/codex-goal-snapshot.ts
9
9
  import { existsSync } from "node:fs";
10
10
  import { readFile } from "node:fs/promises";
11
+ import { resolve } from "node:path";
12
+
13
+ class CodexGoalSnapshotError extends Error {
14
+ }
15
+ function safeObject(value) {
16
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
17
+ }
18
+ function safeString(value) {
19
+ return typeof value === "string" ? value : "";
20
+ }
21
+ function safeStatusString(value) {
22
+ return typeof value === "string" ? value.trim() : "";
23
+ }
24
+ function normalizeStatus(value) {
25
+ const status = safeStatusString(value).toLowerCase();
26
+ if (status === "complete" || status === "completed" || status === "done")
27
+ return "complete";
28
+ if (status === "cancelled" || status === "canceled")
29
+ return "cancelled";
30
+ if (status === "failed" || status === "failure")
31
+ return "failed";
32
+ if (status === "paused")
33
+ return "paused";
34
+ if (status === "usage_limited")
35
+ return "usage_limited";
36
+ if (status === "budget_limited")
37
+ return "budget_limited";
38
+ if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
39
+ return "active";
40
+ return "unknown";
41
+ }
42
+ function normalizeObjective(value) {
43
+ return value.replace(/\s+/g, " ").trim();
44
+ }
45
+ function parseCodexGoalSnapshot(value) {
46
+ const root = safeObject(value);
47
+ const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
48
+ if (goalValue === null || goalValue === undefined || goalValue === false) {
49
+ return { available: false, raw: value };
50
+ }
51
+ const goal = safeObject(goalValue);
52
+ const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
53
+ const status = normalizeStatus(goal["status"] ?? root["status"]);
54
+ return {
55
+ available: Boolean(objective || status !== "unknown"),
56
+ ...objective ? { objective } : {},
57
+ status,
58
+ raw: value
59
+ };
60
+ }
61
+ async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
62
+ if (!raw?.trim())
63
+ return null;
64
+ const trimmed = raw.trim();
65
+ try {
66
+ return parseCodexGoalSnapshot(JSON.parse(trimmed));
67
+ } catch {
68
+ const path = resolve(cwd, trimmed);
69
+ if (!existsSync(path)) {
70
+ throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
71
+ }
72
+ try {
73
+ return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
74
+ } catch (error) {
75
+ throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
76
+ }
77
+ }
78
+ }
79
+ function reconcileCodexGoalSnapshot(snapshot, options) {
80
+ const effectiveSnapshot = snapshot ?? { available: false, raw: null };
81
+ const errors = [];
82
+ const warnings = [];
83
+ const expected = options.expectedObjective;
84
+ const normalizedExpected = normalizeObjective(expected);
85
+ if (!effectiveSnapshot.available) {
86
+ warnings.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
87
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
88
+ }
89
+ const accepted = new Set([
90
+ normalizedExpected,
91
+ ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))
92
+ ].filter(Boolean));
93
+ const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
94
+ if (actual && !accepted.has(normalizeObjective(actual))) {
95
+ warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
96
+ }
97
+ const actualStatus = effectiveSnapshot.status ?? "unknown";
98
+ if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
99
+ warnings.push("/goal resume or raise the budget");
100
+ }
101
+ if (actualStatus === "complete")
102
+ warnings.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
103
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
104
+ }
105
+ function formatCodexGoalReconciliation(reconciliation) {
106
+ const parts = [...reconciliation.errors, ...reconciliation.warnings];
107
+ return parts.join(" ");
108
+ }
11
109
 
12
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
110
+ // components/ulw-loop/src/paths.ts
13
111
  import { isAbsolute, join, relative, sep } from "node:path";
14
- // packages/omo-codex/plugin/components/ulw-loop/src/constants.ts
112
+ // components/ulw-loop/src/constants.ts
15
113
  var ULW_LOOP_DIR = ".omo/ulw-loop";
16
114
  var ULW_LOOP_BRIEF = "brief.md";
17
115
  var ULW_LOOP_GOALS = "goals.json";
18
116
  var ULW_LOOP_LEDGER = "ledger.jsonl";
117
+ var ULW_LOOP_STATE_LOCK = ".state.lock";
19
118
  var ULW_LOOP_STEERING_MUTATION_KINDS = [
20
119
  "add_subgoal",
21
120
  "split_subgoal",
@@ -31,10 +130,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
31
130
  "regression",
32
131
  "adversarial"
33
132
  ];
34
- // packages/omo-codex/plugin/components/ulw-loop/src/runtime.ts
133
+ // components/ulw-loop/src/runtime.ts
35
134
  class UlwLoopError extends Error {
36
- code;
37
- details;
38
135
  constructor(message, code, opts) {
39
136
  super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
40
137
  this.name = "UlwLoopError";
@@ -47,8 +144,8 @@ class UlwLoopError extends Error {
47
144
  function iso() {
48
145
  return new Date().toISOString();
49
146
  }
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"];
147
+ // components/ulw-loop/src/paths.ts
148
+ var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
52
149
  function normalizeUlwLoopSessionId(sessionId) {
53
150
  const trimmed = sessionId?.trim();
54
151
  if (!trimmed)
@@ -90,6 +187,9 @@ function ulwLoopGoalsPath(repoRoot, scope) {
90
187
  function ulwLoopLedgerPath(repoRoot, scope) {
91
188
  return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_LEDGER);
92
189
  }
190
+ function ulwLoopStateLockPath(repoRoot, scope) {
191
+ return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_STATE_LOCK);
192
+ }
93
193
  function repoRelative(absolutePath, repoRoot) {
94
194
  const slashPrefix = `${repoRoot}/`;
95
195
  const backslashPrefix = `${repoRoot}\\`;
@@ -100,7 +200,10 @@ function repoRelative(absolutePath, repoRoot) {
100
200
  return absolutePath.split("\\").join("/");
101
201
  }
102
202
  function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
103
- const sessionId = normalizeUlwLoopSessionId(scope?.sessionId) ?? resolveUlwLoopSessionIdFromEnv() ?? "session";
203
+ const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
204
+ if (sessionId === null) {
205
+ 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 } });
206
+ }
104
207
  return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
105
208
  }
106
209
  var PLATFORM_PATH_API = { relative, isAbsolute, sep };
@@ -113,7 +216,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
113
216
  return !pathApi.isAbsolute(relativePath);
114
217
  }
115
218
 
116
- // packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts
219
+ // components/ulw-loop/src/goal-status.ts
117
220
  var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
118
221
  function aggregateCodexObjectiveForScope(scope) {
119
222
  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,182 +299,265 @@ function hasEssentialCriteriaPass(goal) {
196
299
  return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
197
300
  }
198
301
 
199
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
200
- function normalizeObjective(value) {
201
- return value.replace(/\s+/g, " ").trim();
302
+ // components/ulw-loop/src/checkpoint-codex-validation.ts
303
+ async function validateCheckpointCodexGoal(input) {
304
+ const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
305
+ const expected = expectedCodexObjective(input.plan, input.goal);
306
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
307
+ expectedObjective: expected,
308
+ ...codexGoalMode(input.plan) === "aggregate" ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {}
309
+ });
310
+ if (!reconciliation.ok)
311
+ throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
312
+ return {
313
+ raw: snapshot?.raw,
314
+ nextActions: reconciliation.warnings,
315
+ warnings: reconciliation.warnings.filter((warning) => warning.startsWith("driver_objective_differs"))
316
+ };
202
317
  }
203
- function textMentionsUlwLoopPlanArtifact(value) {
204
- const normalized = (value ?? "").toLowerCase();
205
- return normalized.includes(ULW_LOOP_DIR.toLowerCase()) || normalized.includes(ULW_LOOP_GOALS.toLowerCase()) || normalized.includes(ULW_LOOP_LEDGER.toLowerCase());
318
+ function combineCheckpointValidationErrors(codexError, gateError) {
319
+ return new UlwLoopError(`${codexError.message}
320
+ ${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
321
+ details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
322
+ });
206
323
  }
207
- function textMentionsGoalId(value, goalId) {
208
- return (value ?? "").toLowerCase().includes(goalId.toLowerCase());
324
+
325
+ // components/ulw-loop/src/plan-io.ts
326
+ import { AsyncLocalStorage } from "node:async_hooks";
327
+ import { createReadStream, readdirSync } from "node:fs";
328
+ import { appendFile, mkdir, readFile as readFile2, rename, writeFile } from "node:fs/promises";
329
+ import { createInterface } from "node:readline";
330
+
331
+ // components/ulw-loop/src/plan-missing-recovery.ts
332
+ var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
333
+ function planMissingRecovery(existingSessionIds) {
334
+ const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
335
+ if (existingSessionIds.length === 0)
336
+ return { message: lines.join(`
337
+ `) };
338
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
339
+ return { message: lines.join(`
340
+ `), details: { existingSessionIds } };
341
+ }
342
+ function sessionScopeRequiredMessage(flag, existingSessionIds) {
343
+ const lines = [
344
+ "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.",
345
+ `Recovery: pass the scope explicitly: \`${flag} <id>\` (subprocess, eval, and hook contexts do not inherit the session env).`
346
+ ];
347
+ if (existingSessionIds.length > 0) {
348
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}.`);
349
+ }
350
+ return lines.join(`
351
+ `);
209
352
  }
210
- function textHasCompletionValidationEvidence(value) {
211
- const normalized = (value ?? "").toLowerCase();
212
- const done = /\b(?:planned work|implementation|deliverables?|scope|task|work)\b/.test(normalized) && /\b(?:done|complete|completed|finished|shipped)\b/.test(normalized);
213
- const verified = /\b(?:validation|verification|tests?|build|lint|review|quality gate|code-review)\b/.test(normalized) && /\b(?:passed|complete|completed|clean|green|approve|approved|clear)\b/.test(normalized);
214
- return done && verified;
353
+ function sessionIdRequiredMessage(flag) {
354
+ return [
355
+ `${flag} requires a non-empty value.`,
356
+ "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),",
357
+ `so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
358
+ ].join(`
359
+ `);
215
360
  }
216
- async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope) {
217
- const actual = normalizeObjective(snapshotObjective).toLowerCase();
218
- if (textMentionsUlwLoopPlanArtifact(actual))
219
- return true;
220
- if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot, scope)))
221
- return false;
361
+
362
+ // components/ulw-loop/src/state-lock.ts
363
+ import { randomUUID } from "node:crypto";
364
+ import { closeSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeSync } from "node:fs";
365
+ import { dirname } from "node:path";
366
+ import { setTimeout as sleep } from "node:timers/promises";
367
+ var ULW_LOOP_LOCK_TIMEOUT_CODE = "ULW_LOOP_LOCK_TIMEOUT";
368
+ var DEFAULT_TIMEOUT_MS = 1e4;
369
+ var DEFAULT_STALE_MS = 60000;
370
+ var MIN_DELAY_MS = 5;
371
+ var MAX_DELAY_MS = 100;
372
+ var SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
373
+ async function withStateLock(lockPath, fn, options = {}) {
374
+ const token = await acquireAsync(lockPath, options);
222
375
  try {
223
- const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
224
- return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
225
- } catch (error) {
226
- if (error instanceof Error)
227
- return false;
228
- throw error;
376
+ return await fn();
377
+ } finally {
378
+ release(lockPath, token);
229
379
  }
230
380
  }
231
- async function canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot, plan, goal, snapshotObjective, evidence, scope) {
232
- if (codexGoalMode(plan) !== "aggregate")
233
- return false;
234
- if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id)
235
- return false;
236
- if (isFinalRunCompletionCandidate(plan, goal)) {
237
- return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
381
+ function withStateLockSync(lockPath, fn, options = {}) {
382
+ const token = acquireSync(lockPath, options);
383
+ try {
384
+ return fn();
385
+ } finally {
386
+ release(lockPath, token);
238
387
  }
239
- if (!textMentionsUlwLoopPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id))
240
- return false;
241
- if (!textHasCompletionValidationEvidence(evidence))
242
- return false;
243
- return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
244
388
  }
245
- async function canReconcileActiveFinalTaskScopedAggregateSnapshot(repoRoot, plan, goal, snapshotObjective, evidence, scope) {
246
- if (codexGoalMode(plan) !== "aggregate")
247
- return false;
248
- if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id)
249
- return false;
250
- if (!isFinalRunCompletionCandidate(plan, goal))
251
- return false;
252
- if (!textHasCompletionValidationEvidence(evidence))
253
- return false;
254
- return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
255
- }
256
- function buildCompletedLegacyGoalRemediation(goal) {
257
- return [
258
- "If get_goal returns a different completed legacy/thread objective, do not repeat --status complete in this thread.",
259
- `Record a non-terminal blocker with: omo-agent-toolkit ulw-loop checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<different completed get_goal JSON or path>".`,
260
- "Then continue only from a Codex goal context with no active/completed conflicting goal, in the same repo/worktree, and create the intended goal there."
261
- ].join(" ");
389
+ function isStateLockTimeout(error) {
390
+ return error instanceof UlwLoopError && error.code === ULW_LOOP_LOCK_TIMEOUT_CODE;
391
+ }
392
+ async function acquireAsync(lockPath, options) {
393
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
394
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
395
+ mkdirSync(dirname(lockPath), { recursive: true });
396
+ for (let attempt = 0;; ) {
397
+ const outcome = attemptOnce(lockPath, staleMs);
398
+ if (outcome.kind === "acquired")
399
+ return outcome.token;
400
+ if (outcome.kind === "retry")
401
+ continue;
402
+ if (Date.now() >= deadline)
403
+ throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
404
+ await sleep(backoffMs(attempt));
405
+ attempt += 1;
406
+ }
262
407
  }
263
- function buildTaskScopedAggregateReconciliationHint(goal, final) {
264
- if (final) {
265
- return ` Final task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress final OMO goal and the completed get_goal objective to map to the ulw-loop brief or artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
408
+ function acquireSync(lockPath, options) {
409
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
410
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
411
+ mkdirSync(dirname(lockPath), { recursive: true });
412
+ for (let attempt = 0;; ) {
413
+ const outcome = attemptOnce(lockPath, staleMs);
414
+ if (outcome.kind === "acquired")
415
+ return outcome.token;
416
+ if (outcome.kind === "retry")
417
+ continue;
418
+ if (Date.now() >= deadline)
419
+ throw lockTimeout(lockPath, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
420
+ Atomics.wait(SLEEP_CELL, 0, 0, backoffMs(attempt));
421
+ attempt += 1;
266
422
  }
267
- 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
423
  }
269
-
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";
274
-
275
- class CodexGoalSnapshotError extends Error {
424
+ function attemptOnce(lockPath, staleMs) {
425
+ try {
426
+ const token = tryCreate(lockPath);
427
+ if (token !== null)
428
+ return { kind: "acquired", token };
429
+ const snapshot = readSnapshot(lockPath);
430
+ if (snapshot === null)
431
+ return { kind: "retry" };
432
+ if (isStale(snapshot, staleMs) && reclaim(lockPath, snapshot.raw))
433
+ return { kind: "retry" };
434
+ return { kind: "wait" };
435
+ } catch (error) {
436
+ if (hasCode(error, "EINTR"))
437
+ return { kind: "wait" };
438
+ throw error;
439
+ }
276
440
  }
277
- function safeObject(value) {
278
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
441
+ function tryCreate(lockPath) {
442
+ let fd;
443
+ try {
444
+ fd = openSync(lockPath, "wx");
445
+ } catch (error) {
446
+ if (hasCode(error, "EEXIST"))
447
+ return null;
448
+ throw error;
449
+ }
450
+ const record = { pid: process.pid, createdAt: new Date().toISOString(), token: randomUUID() };
451
+ try {
452
+ writeSync(fd, JSON.stringify(record));
453
+ } catch (error) {
454
+ closeSync(fd);
455
+ try {
456
+ unlinkSync(lockPath);
457
+ } catch {}
458
+ throw error;
459
+ }
460
+ closeSync(fd);
461
+ return record.token;
279
462
  }
280
- function safeString(value) {
281
- return typeof value === "string" ? value.trim() : "";
463
+ function readSnapshot(lockPath) {
464
+ try {
465
+ const raw = readFileSync(lockPath, "utf8");
466
+ const ageMs = Date.now() - statSync(lockPath).mtimeMs;
467
+ return { raw, record: parseRecord(raw), ageMs };
468
+ } catch (error) {
469
+ if (hasCode(error, "ENOENT"))
470
+ return null;
471
+ throw error;
472
+ }
282
473
  }
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";
474
+ function parseRecord(raw) {
475
+ try {
476
+ const parsed = JSON.parse(raw);
477
+ if (typeof parsed !== "object" || parsed === null)
478
+ return null;
479
+ const record = parsed;
480
+ const pid = record["pid"];
481
+ const createdAt = record["createdAt"];
482
+ const token = record["token"];
483
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof createdAt !== "string")
484
+ return null;
485
+ if (typeof token !== "string" || token.length === 0)
486
+ return null;
487
+ return { pid, createdAt, token };
488
+ } catch (error) {
489
+ if (error instanceof SyntaxError)
490
+ return null;
491
+ throw error;
492
+ }
294
493
  }
295
- function normalizeObjective2(value) {
296
- return value.replace(/\s+/g, " ").trim();
494
+ function isStale(snapshot, staleMs) {
495
+ if (snapshot.record === null)
496
+ return snapshot.ageMs > staleMs;
497
+ return !isProcessAlive(snapshot.record.pid);
297
498
  }
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 };
499
+ function isProcessAlive(pid) {
500
+ try {
501
+ process.kill(pid, 0);
502
+ return true;
503
+ } catch (error) {
504
+ if (hasCode(error, "ESRCH"))
505
+ return false;
506
+ if (hasCode(error, "EPERM"))
507
+ return true;
508
+ throw error;
303
509
  }
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
510
  }
314
- async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
315
- if (!raw?.trim())
316
- return null;
317
- const trimmed = raw.trim();
511
+ function reclaim(lockPath, expectedRaw) {
512
+ const current = readSnapshot(lockPath);
513
+ if (current === null)
514
+ return true;
515
+ if (current.raw !== expectedRaw)
516
+ return false;
318
517
  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
- }
325
- 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
- }
518
+ unlinkSync(lockPath);
519
+ } catch (error) {
520
+ if (!hasCode(error, "ENOENT"))
521
+ throw error;
330
522
  }
523
+ return true;
331
524
  }
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 };
343
- }
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}".`);
351
- }
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}.`);
356
- }
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.');
525
+ function release(lockPath, token) {
526
+ const current = readSnapshot(lockPath);
527
+ if (current === null || current.record?.token !== token)
528
+ return;
529
+ try {
530
+ unlinkSync(lockPath);
531
+ } catch (error) {
532
+ if (!hasCode(error, "ENOENT"))
533
+ throw error;
359
534
  }
360
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
361
535
  }
362
- function formatCodexGoalReconciliation(reconciliation) {
363
- const parts = [...reconciliation.errors, ...reconciliation.warnings];
364
- return parts.join(" ");
536
+ function backoffMs(attempt) {
537
+ const exponential = Math.min(MAX_DELAY_MS, MIN_DELAY_MS * 2 ** attempt);
538
+ return exponential + Math.random() * MIN_DELAY_MS;
539
+ }
540
+ function lockTimeout(lockPath, timeoutMs) {
541
+ const holder = readSnapshot(lockPath)?.record;
542
+ const owner = holder === undefined || holder === null ? "another process" : `pid ${holder.pid}`;
543
+ 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, {
544
+ details: {
545
+ lockPath,
546
+ timeoutMs,
547
+ ...holder === undefined || holder === null ? {} : { holderPid: holder.pid }
548
+ }
549
+ });
550
+ }
551
+ function hasCode(error, code) {
552
+ return error instanceof Error && "code" in error && error.code === code;
365
553
  }
366
554
 
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";
555
+ // components/ulw-loop/src/plan-io.ts
371
556
  var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
372
557
  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
558
  var locks = new Map;
374
- function hasCode(error, code) {
559
+ var heldLocks = new AsyncLocalStorage;
560
+ function hasCode2(error, code) {
375
561
  return error instanceof Error && "code" in error && error.code === code;
376
562
  }
377
563
  function isLegacyEnumeratedAggregateObjective(objective) {
@@ -386,8 +572,10 @@ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
386
572
  if (fn === undefined)
387
573
  throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
388
574
  const lockKey = `${repoRoot}\x00${ulwLoopRelativeDir(scope)}`;
575
+ const lockPath = ulwLoopStateLockPath(repoRoot, scope);
576
+ const locked = () => withStateLock(lockPath, () => heldLocks.run(new Set([...heldLocks.getStore() ?? [], lockKey]), fn));
389
577
  const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
390
- const run = prior.then(fn, fn);
578
+ const run = prior.then(locked, locked);
391
579
  const gate = run.then(() => {
392
580
  return;
393
581
  }, () => {
@@ -404,11 +592,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
404
592
  const path = ulwLoopGoalsPath(repoRoot, scope);
405
593
  let raw;
406
594
  try {
407
- raw = await readFile3(path, "utf8");
595
+ raw = await readFile2(path, "utf8");
408
596
  } catch (error) {
409
- if (!hasCode(error, "ENOENT"))
597
+ if (!hasCode2(error, "ENOENT"))
410
598
  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 });
599
+ const recovery = planMissingRecovery(listUlwLoopSessionIds(repoRoot));
600
+ throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.
601
+ ${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...recovery.details === undefined ? {} : { details: recovery.details } });
412
602
  }
413
603
  const parsed = JSON.parse(raw);
414
604
  if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
@@ -416,6 +606,9 @@ async function readUlwLoopPlan(repoRoot, scope) {
416
606
  }
417
607
  const previousObjective = parsed.codexObjective;
418
608
  if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && isLegacyEnumeratedAggregateObjective(previousObjective)) {
609
+ if (!(heldLocks.getStore()?.has(`${repoRoot}\x00${ulwLoopRelativeDir(scope)}`) ?? false)) {
610
+ 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");
611
+ }
419
612
  const now = iso();
420
613
  parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
421
614
  parsed.codexObjectiveAliases = [...new Set([...parsed.codexObjectiveAliases ?? [], previousObjective])];
@@ -431,6 +624,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
431
624
  }
432
625
  return parsed;
433
626
  }
627
+ function listUlwLoopSessionIds(repoRoot) {
628
+ try {
629
+ return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
630
+ } catch {
631
+ return [];
632
+ }
633
+ }
434
634
  async function writePlan(repoRoot, plan, scope) {
435
635
  await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
436
636
  const path = ulwLoopGoalsPath(repoRoot, scope);
@@ -459,7 +659,7 @@ async function* ledgerLines(repoRoot, scope) {
459
659
  yield line;
460
660
  }
461
661
  } catch (error) {
462
- if (!hasCode(error, "ENOENT"))
662
+ if (!hasCode2(error, "ENOENT"))
463
663
  throw error;
464
664
  } finally {
465
665
  lines.close();
@@ -482,7 +682,7 @@ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
482
682
  return;
483
683
  }
484
684
 
485
- // packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts
685
+ // components/ulw-loop/src/evidence.ts
486
686
  function ulwLoopFail(message, code, details) {
487
687
  throw new UlwLoopError(message, code, { details });
488
688
  }
@@ -588,57 +788,216 @@ function requireEssentialCriteriaPass(goal) {
588
788
  });
589
789
  }
590
790
 
591
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
791
+ // components/ulw-loop/src/quality-gate-artifacts.ts
592
792
  import { resolve as resolve2 } from "node:path";
593
793
 
594
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts
595
- var PLACEHOLDER_PATTERN = /^(?:placeholder|todo|tbd|n\/a|stub)$/i;
794
+ // components/ulw-loop/src/quality-gate-fields.ts
795
+ var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
796
+ var activeCollector;
797
+ function withQualityGateCollector(operation) {
798
+ const previous = activeCollector;
799
+ const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
800
+ activeCollector = collector;
801
+ try {
802
+ const result = operation();
803
+ if (collector.defects.length === 0)
804
+ return result;
805
+ throwQualityGateDefects(collector.defects);
806
+ } finally {
807
+ activeCollector = previous;
808
+ }
809
+ }
810
+ function throwQualityGateDefects(defects) {
811
+ const uniqueDefects = [
812
+ ...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
813
+ ];
814
+ const fields = uniqueDefects.slice(0, 25);
815
+ const truncated = uniqueDefects.length > fields.length;
816
+ const message = [
817
+ `Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
818
+ ...fields.map((item) => `- ${item.field}: ${item.message}`)
819
+ ].join(`
820
+ `);
821
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
822
+ details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
823
+ });
824
+ }
596
825
  function invalid(message, field) {
597
- throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
826
+ if (activeCollector === undefined)
827
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
828
+ activeCollector.defects.push({ field, message });
829
+ activeCollector.poisonedFields.add(field);
830
+ return;
831
+ }
832
+ function isPoisoned(field) {
833
+ return activeCollector?.poisonedFields.has(field) ?? false;
834
+ }
835
+ function markPoisonedArtifactKind(id) {
836
+ activeCollector?.poisonedArtifactKinds.add(id);
837
+ }
838
+ function isPoisonedArtifactKind(id) {
839
+ return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
598
840
  }
599
841
  function isRecord(value) {
600
842
  return typeof value === "object" && value !== null && !Array.isArray(value);
601
843
  }
602
844
  function section(value, field) {
603
- return isRecord(value) ? value : invalid(`Final quality gate is missing ${field} evidence.`, field);
845
+ if (isRecord(value))
846
+ return value;
847
+ 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);
848
+ return {};
604
849
  }
605
850
  function textField(value, field) {
606
- if (typeof value !== "string" || value.trim() === "")
851
+ if (typeof value !== "string" || value.trim() === "") {
607
852
  invalid(`Final quality gate requires non-empty ${field}.`, field);
853
+ return "";
854
+ }
608
855
  const trimmed = value.trim();
609
856
  if (PLACEHOLDER_PATTERN.test(trimmed))
610
857
  invalid(`Final quality gate rejects placeholder ${field}.`, field);
611
858
  return trimmed;
612
859
  }
613
860
  function numberField(value, field) {
614
- return typeof value === "number" && Number.isFinite(value) ? value : invalid(`Final quality gate requires numeric ${field}.`, field);
861
+ if (typeof value === "number" && Number.isFinite(value))
862
+ return value;
863
+ invalid(`Final quality gate requires numeric ${field}.`, field);
864
+ return 0;
615
865
  }
616
866
  function stringArray(value, field) {
617
- if (!Array.isArray(value) || value.length === 0)
618
- return invalid(`Final quality gate requires ${field}.`, field);
867
+ if (!Array.isArray(value) || value.length === 0) {
868
+ invalid(`Final quality gate requires ${field}.`, field);
869
+ return [];
870
+ }
619
871
  return value.map((item) => textField(item, field));
620
872
  }
621
873
  function emptyBlockers(value, field) {
622
874
  if (Array.isArray(value) && value.length === 0)
623
875
  return [];
624
876
  invalid(`${field} must be empty.`, field);
877
+ return [];
625
878
  }
626
879
  function literal(value, expected, field) {
627
880
  if (value === expected)
628
881
  return expected;
629
882
  invalid(`${field} must be ${String(expected)}.`, field);
883
+ return expected;
884
+ }
885
+
886
+ // components/ulw-loop/src/quality-gate-artifacts.ts
887
+ var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
888
+ var SUPPORTED_KINDS = [
889
+ "cli-transcript",
890
+ "log",
891
+ "screenshot",
892
+ "image",
893
+ "http-dump",
894
+ "data-diff"
895
+ ];
896
+ var COMPATIBLE_KINDS = {
897
+ cli: ["cli-transcript", "log"],
898
+ tmux: ["cli-transcript", "log"],
899
+ http: ["http-dump"],
900
+ browser: ["screenshot", "image"],
901
+ gui: ["screenshot", "image"],
902
+ data: ["data-diff"]
903
+ };
904
+ function isSupportedSurface(value) {
905
+ return SUPPORTED_SURFACES.some((surface) => surface === value);
906
+ }
907
+ function isSupportedKind(value) {
908
+ return SUPPORTED_KINDS.some((kind) => kind === value);
909
+ }
910
+ function surfaceField(value, field) {
911
+ if (isSupportedSurface(value))
912
+ return value;
913
+ invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
914
+ return "cli";
915
+ }
916
+ function kindField(value, field) {
917
+ if (isSupportedKind(value))
918
+ return value;
919
+ invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
920
+ return "log";
921
+ }
922
+ function compatibleKindsFor(surface) {
923
+ return COMPATIBLE_KINDS[surface];
924
+ }
925
+ function artifactCompatible(surface, kind) {
926
+ return compatibleKindsFor(surface).includes(kind);
927
+ }
928
+ function checkFile(path, field, opts) {
929
+ if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
930
+ return;
931
+ const absolute = resolve2(opts.repoRoot, path);
932
+ if (!opts.fs.existsSync(absolute)) {
933
+ invalid(`${field} must point to an existing artifact.`, field);
934
+ return;
935
+ }
936
+ if (opts.fs.statSync(absolute).size <= 0)
937
+ invalid(`${field} must point to a non-empty artifact.`, field);
938
+ if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve2(opts.repoRoot, opts.currentAttemptDir)))
939
+ invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
940
+ }
941
+ function artifactMap(refs) {
942
+ const byId = new Map;
943
+ for (const ref of refs) {
944
+ if (byId.has(ref.id))
945
+ invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
946
+ byId.set(ref.id, ref);
947
+ }
948
+ return byId;
949
+ }
950
+ function parseArtifactRefs(value, opts) {
951
+ if (!Array.isArray(value) || value.length === 0) {
952
+ invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
953
+ return [];
954
+ }
955
+ return value.flatMap((item, index) => {
956
+ const prefix = `manualQa.artifactRefs[${index}]`;
957
+ const ref = section(item, prefix);
958
+ if (isPoisoned(prefix))
959
+ return [];
960
+ const pathField = `${prefix}.path`;
961
+ const idField = `${prefix}.id`;
962
+ const kindFieldName = `${prefix}.kind`;
963
+ const descriptionField = `${prefix}.description`;
964
+ const path = textField(ref["path"], pathField);
965
+ const id = textField(ref["id"], idField);
966
+ const kind = kindField(ref["kind"], kindFieldName);
967
+ if (isPoisoned(kindFieldName))
968
+ markPoisonedArtifactKind(id);
969
+ const description = textField(ref["description"], descriptionField);
970
+ checkFile(path, pathField, opts);
971
+ return [{ id, kind, description, path }];
972
+ });
973
+ }
974
+ function referencedArtifacts(value, field, byId) {
975
+ const ids = stringArray(value, field);
976
+ if (isPoisoned(field))
977
+ return [];
978
+ return ids.flatMap((id) => {
979
+ const artifact = byId.get(id);
980
+ if (artifact === undefined) {
981
+ invalid(`${field} references unknown artifact ${id}.`, field);
982
+ return [];
983
+ }
984
+ return [artifact];
985
+ });
630
986
  }
631
987
 
632
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts
988
+ // components/ulw-loop/src/quality-gate-verdicts.ts
633
989
  function passedVerdict(value, field) {
634
- if (value === "not_applicable")
990
+ if (value === "not_applicable") {
635
991
  invalid(`${field} must not be not_applicable.`, field);
992
+ return "passed";
993
+ }
636
994
  return literal(value, "passed", field);
637
995
  }
638
996
  function codeQualityStatusField(value, field) {
639
997
  if (value === "CLEAR" || value === "WATCH")
640
998
  return value;
641
999
  invalid(`${field} must be CLEAR or WATCH.`, field);
1000
+ return "CLEAR";
642
1001
  }
643
1002
  function adversarialVerdict(row, field) {
644
1003
  const value = row["verdict"];
@@ -648,9 +1007,82 @@ function adversarialVerdict(row, field) {
648
1007
  return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
649
1008
  }
650
1009
  invalid(`${field} must be passed or not_applicable with a reason.`, field);
1010
+ return { verdict: "passed" };
651
1011
  }
652
1012
 
653
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-blockers.ts
1013
+ // components/ulw-loop/src/surface.ts
1014
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1015
+ import { dirname as dirname2, join as join2 } from "node:path";
1016
+ import { fileURLToPath } from "node:url";
1017
+ var REVIEWER_ROLES_BY_SURFACE = {
1018
+ lazycodex: {
1019
+ codeReview: "lazycodex-code-reviewer",
1020
+ manualQa: "lazycodex-qa-executor",
1021
+ gateReview: "lazycodex-gate-reviewer"
1022
+ },
1023
+ "omo-senpi": {
1024
+ codeReview: "omo-senpi-code-reviewer",
1025
+ manualQa: "omo-senpi-qa-executor",
1026
+ gateReview: "omo-senpi-gate-reviewer"
1027
+ }
1028
+ };
1029
+ var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
1030
+ var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
1031
+ lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
1032
+ "omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
1033
+ };
1034
+ var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
1035
+ lazycodex: ["codeReview"],
1036
+ "omo-senpi": []
1037
+ };
1038
+ var GATE_SECTION_BY_ACCEPTOR = {
1039
+ lazycodex: {
1040
+ codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
1041
+ manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
1042
+ gateReview: [
1043
+ REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
1044
+ "category:deep",
1045
+ "category:unspecified-high",
1046
+ "category:unspecified-low",
1047
+ "main-session"
1048
+ ]
1049
+ },
1050
+ "omo-senpi": {
1051
+ manualQa: ["main-session"],
1052
+ gateReview: ["category:deep", "category:unspecified-high", "category:unspecified-low"]
1053
+ }
1054
+ };
1055
+ function reviewerRolesFor(surface) {
1056
+ return REVIEWER_ROLES_BY_SURFACE[surface];
1057
+ }
1058
+ var SURFACE_MARKER_FILENAME = "surface.json";
1059
+ var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
1060
+ function parseSurface(value) {
1061
+ return value === "lazycodex" || value === "omo-senpi" ? value : null;
1062
+ }
1063
+ function resolveToolkitSurface(options) {
1064
+ const env = options?.env ?? process.env;
1065
+ const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
1066
+ if (fromEnv !== null)
1067
+ return fromEnv;
1068
+ const entryDir = options?.entryDir ?? dirname2(fileURLToPath(import.meta.url));
1069
+ const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
1070
+ return readMarkerSurface(markerPath) ?? "lazycodex";
1071
+ }
1072
+ function readMarkerSurface(markerPath) {
1073
+ try {
1074
+ if (!existsSync2(markerPath))
1075
+ return null;
1076
+ const parsed = JSON.parse(readFileSync2(markerPath, "utf8"));
1077
+ return parseSurface(parsed["surface"]);
1078
+ } catch (error) {
1079
+ if (error instanceof Error)
1080
+ return null;
1081
+ throw error;
1082
+ }
1083
+ }
1084
+
1085
+ // components/ulw-loop/src/quality-gate-blockers.ts
654
1086
  var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
655
1087
  var URL_PATTERN = /https?:\/\/\S+/g;
656
1088
  var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
@@ -674,149 +1106,86 @@ function classifyExternalAuthorizationBlocker(evidence) {
674
1106
  return null;
675
1107
  if (!GHCR_PATTERN.test(normalized))
676
1108
  return "EXTERNAL_AUTHORIZATION_REQUIRED";
677
- const status401 = GHCR_401_PATTERN.test(normalized) ? "HTTP_401_ANONYMOUS" : null;
678
- const status403 = GHCR_403_PATTERN.test(normalized) ? "HTTP_403_NO_READ_PACKAGES" : null;
679
- const status = [status401, status403].filter((part) => part !== null).join("+");
680
- return `GHCR_PULL_ACCESS:${status || "AUTHORIZATION_REQUIRED"}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`;
681
- }
682
- function nestedBlockerSignature(goal) {
683
- const blocker = Reflect.get(goal, "blocker");
684
- const signature = isRecord2(blocker) ? blocker["signature"] : null;
685
- return typeof signature === "string" ? signature : null;
686
- }
687
- function sameBlockerOccurrences(plan, signature) {
688
- return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature).length;
689
- }
690
- function clearGoalBlockerFields(goal) {
691
- for (const key of BLOCKER_FIELD_KEYS)
692
- Reflect.deleteProperty(goal, key);
693
- }
694
-
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
- }
1109
+ const status401 = GHCR_401_PATTERN.test(normalized) ? "HTTP_401_ANONYMOUS" : null;
1110
+ const status403 = GHCR_403_PATTERN.test(normalized) ? "HTTP_403_NO_READ_PACKAGES" : null;
1111
+ const status = [status401, status403].filter((part) => part !== null).join("+");
1112
+ return `GHCR_PULL_ACCESS:${status || "AUTHORIZATION_REQUIRED"}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`;
746
1113
  }
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);
753
- }
754
- return byId;
1114
+ function nestedBlockerSignature(goal) {
1115
+ const blocker = Reflect.get(goal, "blocker");
1116
+ const signature = isRecord2(blocker) ? blocker["signature"] : null;
1117
+ return typeof signature === "string" ? signature : null;
755
1118
  }
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
- });
1119
+ function sameBlockerOccurrences(plan, signature) {
1120
+ return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature).length;
770
1121
  }
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
- });
1122
+ function clearGoalBlockerFields(goal) {
1123
+ for (const key of BLOCKER_FIELD_KEYS)
1124
+ Reflect.deleteProperty(goal, key);
1125
+ }
1126
+
1127
+ // components/ulw-loop/src/quality-gate.ts
1128
+ function reviewerAcceptorField(value, surface, sectionName) {
1129
+ const field = `${sectionName}.by`;
1130
+ const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
1131
+ if (typeof value !== "string" || value.trim() === "") {
1132
+ textField(value, field);
1133
+ return accepted?.[0] ?? "";
1134
+ }
1135
+ const actual = textField(value, field);
1136
+ if (accepted === undefined || !accepted.includes(actual))
1137
+ invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
1138
+ return actual;
778
1139
  }
779
1140
  function validateQualityGate(input, opts) {
780
- const gate = section(input, "qualityGate");
781
- const codeReview = section(gate["codeReview"], "codeReview");
1141
+ return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
1142
+ }
1143
+ function validateQualityGateUncollected(input, opts) {
1144
+ const surface = opts?.reviewerSurface ?? "lazycodex";
1145
+ const raw = input;
1146
+ const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
1147
+ for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
1148
+ section(gate[name], name);
1149
+ for (const name of Object.keys(gate)) {
1150
+ if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
1151
+ invalid("omo-senpi gate has no codeReview lane.", name);
1152
+ }
1153
+ if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
1154
+ invalid("omo-senpi gate has no codeReview lane.", "codeReview");
782
1155
  const manualQa = section(gate["manualQa"], "manualQa");
783
1156
  const gateReview = section(gate["gateReview"], "gateReview");
784
1157
  const iteration = section(gate["iteration"], "iteration");
785
1158
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
1159
+ const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
1160
+ const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
1161
+ const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
1162
+ const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
1163
+ const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
786
1164
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
787
1165
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
788
- if (passCount < totalCriteria)
1166
+ if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
789
1167
  invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
790
1168
  const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
791
1169
  const byId = artifactMap(artifactRefs);
792
1170
  const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
793
1171
  const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
794
- const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
795
1172
  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
- },
1173
+ if (!isPoisoned("gateReview.reportPath"))
1174
+ checkFile(gateReportPath, "gateReview.reportPath", opts);
1175
+ const common = {
807
1176
  manualQa: {
808
- by: reviewerRoleField(manualQa["by"], REVIEWER_ROLES.manualQa, "manualQa.by"),
1177
+ by: manualQaBy,
809
1178
  status: literal(manualQa["status"], "passed", "manualQa.status"),
810
- evidence: textField(manualQa["evidence"], "manualQa.evidence"),
1179
+ evidence: manualQaEvidence,
811
1180
  surfaceEvidence,
812
1181
  adversarialCases,
813
1182
  artifactRefs
814
1183
  },
815
1184
  gateReview: {
816
- by: reviewerRoleField(gateReview["by"], REVIEWER_ROLES.gateReview, "gateReview.by"),
1185
+ by: gateReviewBy,
817
1186
  recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
818
1187
  reportPath: gateReportPath,
819
- evidence: textField(gateReview["evidence"], "gateReview.evidence"),
1188
+ evidence: gateReviewEvidence,
820
1189
  blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
821
1190
  },
822
1191
  iteration: {
@@ -834,17 +1203,41 @@ function validateQualityGate(input, opts) {
834
1203
  adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
835
1204
  }
836
1205
  };
1206
+ if (surface === "omo-senpi")
1207
+ return { surface, ...common };
1208
+ if (codeReview === undefined)
1209
+ return { surface, ...common };
1210
+ const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
1211
+ checkFile(codeReportPath, "codeReview.reportPath", opts);
1212
+ return {
1213
+ surface,
1214
+ ...common,
1215
+ codeReview: {
1216
+ by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
1217
+ recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
1218
+ codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
1219
+ reportPath: codeReportPath,
1220
+ evidence: textField(codeReview["evidence"], "codeReview.evidence"),
1221
+ blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
1222
+ }
1223
+ };
837
1224
  }
838
1225
  function parseSurfaceEvidence(value, byId) {
839
- if (!Array.isArray(value) || value.length === 0)
1226
+ if (!Array.isArray(value) || value.length === 0) {
840
1227
  invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
841
- return value.map((item, index) => {
1228
+ return [];
1229
+ }
1230
+ return value.flatMap((item, index) => {
842
1231
  const row = section(item, `manualQa.surfaceEvidence[${index}]`);
1232
+ if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
1233
+ return [];
843
1234
  const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
844
1235
  const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
845
1236
  for (const artifact of artifacts) {
1237
+ if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
1238
+ continue;
846
1239
  if (!artifactCompatible(surface, artifact.kind)) {
847
- invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
1240
+ invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
848
1241
  }
849
1242
  }
850
1243
  return {
@@ -858,10 +1251,14 @@ function parseSurfaceEvidence(value, byId) {
858
1251
  });
859
1252
  }
860
1253
  function parseAdversarialCases(value, byId) {
861
- if (!Array.isArray(value) || value.length === 0)
1254
+ if (!Array.isArray(value) || value.length === 0) {
862
1255
  invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
863
- return value.map((item, index) => {
1256
+ return [];
1257
+ }
1258
+ return value.flatMap((item, index) => {
864
1259
  const row = section(item, `manualQa.adversarialCases[${index}]`);
1260
+ if (isPoisoned(`manualQa.adversarialCases[${index}]`))
1261
+ return [];
865
1262
  const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
866
1263
  const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
867
1264
  return {
@@ -876,8 +1273,8 @@ function parseAdversarialCases(value, byId) {
876
1273
  });
877
1274
  }
878
1275
 
879
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts
880
- import { readFile as readFile4 } from "node:fs/promises";
1276
+ // components/ulw-loop/src/cli-arg-parser.ts
1277
+ import { readFile as readFile3 } from "node:fs/promises";
881
1278
  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
1279
  var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
883
1280
  function hasFlag(argv, flag) {
@@ -925,7 +1322,7 @@ async function readJsonInput(value) {
925
1322
  if (value === undefined)
926
1323
  return;
927
1324
  try {
928
- return JSON.parse(looksLikeJson(value) ? value : await readFile4(value, "utf8"));
1325
+ return JSON.parse(looksLikeJson(value) ? value : await readFile3(value, "utf8"));
929
1326
  } catch (error) {
930
1327
  const message = error instanceof Error ? error.message : "unknown error";
931
1328
  throw new UlwLoopError(`Invalid JSON input: ${message}`, "ULW_LOOP_JSON_INPUT_INVALID", { cause: error });
@@ -934,13 +1331,13 @@ async function readJsonInput(value) {
934
1331
  async function parseCodexGoalJson(value) {
935
1332
  if (value === undefined)
936
1333
  return;
937
- const raw = looksLikeJson(value) ? value : await readFile4(value, "utf8");
938
1334
  try {
1335
+ const raw = looksLikeJson(value) ? value : await readFile3(value, "utf8");
939
1336
  JSON.parse(raw);
940
1337
  return raw;
941
1338
  } catch (error) {
942
1339
  const message = error instanceof Error ? error.message : "unknown error";
943
- throw new UlwLoopError(`Invalid --codex-goal-json: ${message}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
1340
+ throw new UlwLoopError(`Invalid --codex-goal-json: ${looksLikeJson(value) ? message : "neither valid JSON nor a readable path"}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
944
1341
  }
945
1342
  }
946
1343
  function required(argv, flag, code) {
@@ -967,7 +1364,7 @@ function parseRecordEvidenceArgs(argv) {
967
1364
  return notes ? { ...result, notes } : result;
968
1365
  }
969
1366
 
970
- // packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts
1367
+ // components/ulw-loop/src/validation-batch.ts
971
1368
  function isObject(value) {
972
1369
  return typeof value === "object" && value !== null && !Array.isArray(value);
973
1370
  }
@@ -1085,14 +1482,11 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
1085
1482
  throw new UlwLoopError(message, code);
1086
1483
  }
1087
1484
 
1088
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
1089
- var QUALITY_GATE_FS = { existsSync: existsSync3, statSync };
1485
+ // components/ulw-loop/src/checkpoint.ts
1486
+ var QUALITY_GATE_FS = { existsSync: existsSync3, statSync: statSync2 };
1090
1487
  function ulwLoopFail2(message, code) {
1091
1488
  throw new UlwLoopError(message, code);
1092
1489
  }
1093
- function normalizeObjective3(value) {
1094
- return value.replace(/\s+/g, " ").trim();
1095
- }
1096
1490
  function nonEmptyEvidence2(value) {
1097
1491
  const trimmed = value.trim();
1098
1492
  return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
@@ -1115,7 +1509,7 @@ async function readJsonInput2(raw, repoRoot) {
1115
1509
  if (!existsSync3(path))
1116
1510
  return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
1117
1511
  try {
1118
- return JSON.parse(await readFile5(path, "utf8"));
1512
+ return JSON.parse(await readFile4(path, "utf8"));
1119
1513
  } catch (error) {
1120
1514
  return ulwLoopFail2(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ulw_loop_json_input_invalid");
1121
1515
  }
@@ -1155,13 +1549,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
1155
1549
  return status === "blocked" ? "goal_blocked" : "goal_failed";
1156
1550
  }
1157
1551
  function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
1158
- const watch = qualityGate?.codeReview.codeQualityStatus === "WATCH";
1552
+ const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
1159
1553
  const entry = {
1160
1554
  at: now,
1161
1555
  kind: ledgerKind2(args.status, goal, aggregateCompletion),
1162
1556
  goalId: goal.id,
1163
1557
  status: goal.status,
1164
- evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
1558
+ evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
1165
1559
  };
1166
1560
  if (codexGoal !== undefined)
1167
1561
  entry.codexGoal = codexGoal;
@@ -1175,7 +1569,7 @@ function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletio
1175
1569
  entry.requiredExternalDecision = goal.requiredExternalDecision;
1176
1570
  return entry;
1177
1571
  }
1178
- async function checkpointUlwLoop(repoRoot, args, scope) {
1572
+ async function checkpointUlwLoop(repoRoot, args, scope, dependencies) {
1179
1573
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1180
1574
  const plan = await readUlwLoopPlan(repoRoot, scope);
1181
1575
  const goal = findGoal2(plan, args.goalId);
@@ -1184,6 +1578,8 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1184
1578
  let aggregateCompletion;
1185
1579
  let qualityGate;
1186
1580
  let codexGoal;
1581
+ let nextActions = [];
1582
+ let warnings = [];
1187
1583
  if (args.status === "complete") {
1188
1584
  const aggregate = codexGoalMode(plan) === "aggregate";
1189
1585
  const final = isFinalRunCompletionCandidate(plan, goal);
@@ -1196,23 +1592,23 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1196
1592
  requireEssentialCriteriaPass(goal);
1197
1593
  else
1198
1594
  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");
1595
+ let codexValidationError;
1596
+ try {
1597
+ const validation = await validateCheckpointCodexGoal({
1598
+ repoRoot,
1599
+ plan,
1600
+ goal,
1601
+ raw: args.codexGoalJson,
1602
+ evidence,
1603
+ ...scope === undefined ? {} : { scope }
1604
+ });
1605
+ codexGoal = validation.raw;
1606
+ nextActions = validation.nextActions;
1607
+ warnings = validation.warnings;
1608
+ } catch (error) {
1609
+ if (!(error instanceof UlwLoopError))
1610
+ throw error;
1611
+ codexValidationError = error;
1216
1612
  }
1217
1613
  if (closesBatch)
1218
1614
  requireBatchFinalReady(plan, goal);
@@ -1221,13 +1617,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1221
1617
  if (final)
1222
1618
  aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
1223
1619
  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);
1620
+ try {
1621
+ qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1622
+ repoRoot,
1623
+ fs: QUALITY_GATE_FS,
1624
+ reviewerSurface: dependencies?.surface ?? resolveToolkitSurface(),
1625
+ ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1626
+ });
1627
+ requireBatchGate(plan, goal, qualityGate);
1628
+ } catch (error) {
1629
+ if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
1630
+ throw error;
1631
+ throw combineCheckpointValidationErrors(codexValidationError, error);
1632
+ }
1230
1633
  }
1634
+ if (codexValidationError !== undefined)
1635
+ throw codexValidationError;
1231
1636
  goal.status = "complete";
1232
1637
  goal.completedAt = now;
1233
1638
  goal.evidence = evidence;
@@ -1241,6 +1646,8 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1241
1646
  goal.updatedAt = now;
1242
1647
  if (aggregateCompletion !== undefined)
1243
1648
  plan.aggregateCompletion = aggregateCompletion;
1649
+ if (aggregateCompletion !== undefined)
1650
+ nextActions = [...nextActions, 'aggregate complete — now update_goal({status:"complete"})'];
1244
1651
  plan.updatedAt = now;
1245
1652
  await writePlan(repoRoot, plan, scope);
1246
1653
  const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
@@ -1248,23 +1655,146 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1248
1655
  const closedBatch = args.status === "complete" ? batchClosedBy(plan, goal.id) : undefined;
1249
1656
  if (closedBatch !== undefined)
1250
1657
  await appendLedger(repoRoot, { at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId }, scope);
1251
- return aggregateCompletion === undefined ? { plan, goal, ledgerEntry } : { plan, goal, ledgerEntry, aggregateCompletion };
1658
+ return aggregateCompletion === undefined ? { plan, goal, ledgerEntry, nextActions, warnings } : { plan, goal, ledgerEntry, aggregateCompletion, nextActions, warnings };
1252
1659
  });
1253
1660
  }
1254
1661
 
1255
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts
1662
+ // components/ulw-loop/src/checkpoint-template.ts
1663
+ function artifactPath(base, name) {
1664
+ return `${base}/${name}`;
1665
+ }
1666
+ function gateTemplate(surface, base) {
1667
+ const artifacts = [
1668
+ {
1669
+ id: "artifact-cli",
1670
+ kind: "cli-transcript",
1671
+ description: "<replace:artifact description>",
1672
+ path: artifactPath(base, "cli-transcript.txt")
1673
+ },
1674
+ {
1675
+ id: "artifact-data",
1676
+ kind: "data-diff",
1677
+ description: "<replace:artifact description>",
1678
+ path: artifactPath(base, "data-diff.txt")
1679
+ }
1680
+ ];
1681
+ const manualQa = {
1682
+ by: "main-session",
1683
+ status: "passed",
1684
+ evidence: "<replace:manual QA evidence>",
1685
+ surfaceEvidence: [
1686
+ {
1687
+ id: "surface-cli",
1688
+ criterionRef: "<replace:criterion id>",
1689
+ surface: "cli",
1690
+ invocation: "<replace:command>",
1691
+ verdict: "passed",
1692
+ artifactRefs: ["artifact-cli"]
1693
+ },
1694
+ {
1695
+ id: "surface-data",
1696
+ criterionRef: "<replace:criterion id>",
1697
+ surface: "data",
1698
+ invocation: "<replace:command>",
1699
+ verdict: "passed",
1700
+ artifactRefs: ["artifact-data"]
1701
+ }
1702
+ ],
1703
+ adversarialCases: [
1704
+ {
1705
+ id: "<replace:adversarial case id>",
1706
+ criterionRef: "<replace:criterion id>",
1707
+ scenario: "<replace:scenario>",
1708
+ expectedBehavior: "<replace:expected behavior>",
1709
+ verdict: "not_applicable",
1710
+ reason: "<replace:reason>",
1711
+ artifactRefs: ["artifact-cli"]
1712
+ }
1713
+ ],
1714
+ artifactRefs: artifacts
1715
+ };
1716
+ const common = {
1717
+ manualQa,
1718
+ gateReview: {
1719
+ by: surface === "omo-senpi" ? "category:deep" : "main-session",
1720
+ recommendation: "APPROVE",
1721
+ reportPath: artifactPath(base, "gate-review.md"),
1722
+ evidence: "<replace:gate review evidence>",
1723
+ blockers: [],
1724
+ notes: []
1725
+ },
1726
+ iteration: {
1727
+ fullRerun: true,
1728
+ status: "passed",
1729
+ rerunCommands: ["<replace:verification command>"],
1730
+ evidence: "<replace:iteration evidence>"
1731
+ },
1732
+ criteriaCoverage: {
1733
+ totalCriteria: 0,
1734
+ passCount: 0,
1735
+ originalIntent: "<replace:original intent>",
1736
+ desiredOutcome: "<replace:desired outcome>",
1737
+ userOutcomeReview: "<replace:user outcome review>",
1738
+ adversarialClassesCovered: ["<replace:adversarial class>"]
1739
+ }
1740
+ };
1741
+ return common;
1742
+ }
1743
+ async function checkpointTemplate(repoRoot, scope, goalId, dependencies) {
1744
+ const plan = await readUlwLoopPlan(repoRoot, scope);
1745
+ const surface = dependencies?.surface ?? resolveToolkitSurface();
1746
+ const targetId = goalId ?? plan.activeGoalId;
1747
+ const active = plan.goals.find((goal) => goal.id === targetId);
1748
+ if (goalId !== undefined && active === undefined)
1749
+ throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
1750
+ const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
1751
+ const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
1752
+ const guidance = [
1753
+ "codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
1754
+ "Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
1755
+ 'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
1756
+ '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.',
1757
+ ...surface === "lazycodex" ? [
1758
+ "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."
1759
+ ] : [],
1760
+ ...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
1761
+ ].join(" ");
1762
+ return {
1763
+ qualityGateTemplate: gateTemplate(surface, attemptDir),
1764
+ codexGoalTemplate: {
1765
+ goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
1766
+ },
1767
+ guidance,
1768
+ ...hasAttempt ? { attemptDir } : {}
1769
+ };
1770
+ }
1771
+
1772
+ // components/ulw-loop/src/cli-output.ts
1256
1773
  var ULW_LOOP_HELP = `Usage:
1774
+ omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
1775
+ omo-agent-toolkit help | --help | -h (this message)
1776
+ omo-agent-toolkit ulw-loop help
1257
1777
  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
1778
  omo-agent-toolkit ulw-loop status [--json]
1259
1779
  omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
1260
1780
  omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
1261
1781
  omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
1782
+ omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
1262
1783
  omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
1263
1784
  omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
1264
1785
  omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
1265
1786
  omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
1266
1787
 
1267
- All subcommands accept [--session-id <id>] to isolate state under .omo/ulw-loop/<id>/; without it, Codex session env is used when present.`;
1788
+ 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.
1789
+ Every subcommand accepts --help | -h to print its own usage line.`;
1790
+ function subcommandHelp(subcommand) {
1791
+ const lines = ULW_LOOP_HELP.split(`
1792
+ `).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
1793
+ if (lines.length === 0)
1794
+ return ULW_LOOP_HELP;
1795
+ return ["Usage:", ...lines].join(`
1796
+ `);
1797
+ }
1268
1798
  function printJson(value) {
1269
1799
  process.stdout.write(`${JSON.stringify(value, null, 2)}
1270
1800
  `);
@@ -1330,17 +1860,18 @@ function normalizeCodexGoalMode(value) {
1330
1860
  throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
1331
1861
  }
1332
1862
 
1333
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts
1863
+ // components/ulw-loop/src/codex-goal-instruction.ts
1334
1864
  function buildCodexGoalInstruction(args) {
1335
1865
  const mode = codexGoalMode(args.plan);
1336
1866
  const createGoal = buildCreateGoalPayload(args.plan, args.goal);
1337
1867
  const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
1338
- return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal), json: createGoal };
1868
+ const surface = args.surface ?? resolveToolkitSurface();
1869
+ return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
1339
1870
  }
1340
1871
  function buildCreateGoalPayload(plan, goal) {
1341
1872
  return { objective: expectedCodexObjective(plan, goal) };
1342
1873
  }
1343
- function buildText(mode, plan, goal, createGoal, isFinal) {
1874
+ function buildText(mode, plan, goal, createGoal, isFinal, surface) {
1344
1875
  return joinLines([
1345
1876
  mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
1346
1877
  `Mode: ${mode}`,
@@ -1357,7 +1888,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
1357
1888
  "- Goals are unlimited. Do not add numeric limits.",
1358
1889
  ...modeConstraintLines(mode, isFinal),
1359
1890
  ...evidenceLayoutLines(plan),
1360
- finalSection(plan, goal, isFinal, mode === "aggregate"),
1891
+ finalSection(plan, goal, isFinal, mode === "aggregate", surface),
1361
1892
  ...checkpointLines(plan, mode),
1362
1893
  "",
1363
1894
  "create_goal payload:",
@@ -1409,19 +1940,21 @@ function evidenceLayoutLines(plan) {
1409
1940
  "- 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
1941
  ];
1411
1942
  }
1412
- function finalSection(plan, goal, isFinal, aggregate) {
1943
+ function finalSection(plan, goal, isFinal, aggregate, surface) {
1944
+ const roles = reviewerRolesFor(surface);
1413
1945
  if (!isFinal)
1414
1946
  return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
1415
1947
  const option = sessionOption(plan);
1948
+ if (surface === "omo-senpi")
1949
+ return senpiFinalSection(plan, goal, aggregate);
1416
1950
  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
1951
  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
1952
  return joinLines([
1419
- "Final story — run mandatory quality gate before update_goal:",
1953
+ "Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
1420
1954
  "- Run targeted verification for changed behavior.",
1421
1955
  "- 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.",
1956
+ `- 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".`,
1957
+ "- 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
1958
  "- 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
1959
  "- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
1427
1960
  "- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
@@ -1430,6 +1963,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
1430
1963
  ` ${checkpointCommand}`
1431
1964
  ]);
1432
1965
  }
1966
+ function senpiFinalSection(plan, goal, aggregate) {
1967
+ const option = sessionOption(plan);
1968
+ 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)"`;
1969
+ return joinLines([
1970
+ "Final story — run the single-reviewer quality gate before update_goal:",
1971
+ '- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
1972
+ '- Spawn exactly one gate reviewer with task(category: "deep").',
1973
+ "- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
1974
+ "- Set gateReview.by to the exact category:<name> literal used for the successful task.",
1975
+ "- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
1976
+ '- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
1977
+ 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}`
1978
+ ]);
1979
+ }
1433
1980
  function sessionOption(plan) {
1434
1981
  const prefix = ".omo/ulw-loop/";
1435
1982
  const suffix = "/goals.json";
@@ -1443,15 +1990,15 @@ function joinLines(lines) {
1443
1990
  `);
1444
1991
  }
1445
1992
 
1446
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
1993
+ // components/ulw-loop/src/plan-crud.ts
1447
1994
  import { existsSync as existsSync4 } from "node:fs";
1448
1995
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
1449
1996
 
1450
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts
1997
+ // components/ulw-loop/src/plan-goal-factory.ts
1451
1998
  function cleanLine(line) {
1452
1999
  return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
1453
2000
  }
1454
- function normalizeObjective4(value) {
2001
+ function normalizeObjective2(value) {
1455
2002
  return value.replace(/\s+/g, " ").trim();
1456
2003
  }
1457
2004
  function titleFromObjective(objective, fallback) {
@@ -1472,7 +2019,7 @@ function truncateObjective(objective) {
1472
2019
  return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
1473
2020
  }
1474
2021
  function seedDefaultSuccessCriteria(goalIndex, objective) {
1475
- const subject = truncateObjective(normalizeObjective4(objective) || `Goal ${goalIndex + 1}`);
2022
+ const subject = truncateObjective(normalizeObjective2(objective) || `Goal ${goalIndex + 1}`);
1476
2023
  const rows = [
1477
2024
  [
1478
2025
  "C001",
@@ -1507,8 +2054,8 @@ function seedDefaultSuccessCriteria(goalIndex, objective) {
1507
2054
  }));
1508
2055
  }
1509
2056
  function deriveGoalCandidates(brief) {
1510
- const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective4(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
1511
- const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective4).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
2057
+ const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective2(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
2058
+ const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective2).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
1512
2059
  const selected = (bulletGoals.length > 0 ? bulletGoals : paragraphs).length > 0 ? bulletGoals.length > 0 ? bulletGoals : paragraphs : ["Complete the requested project objective."];
1513
2060
  return selected.map((objective, index) => ({
1514
2061
  title: titleFromObjective(objective, `Goal ${index + 1}`),
@@ -1536,7 +2083,7 @@ function appendGoalToPlan(plan, title, objective, now) {
1536
2083
  return goal;
1537
2084
  }
1538
2085
 
1539
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
2086
+ // components/ulw-loop/src/plan-crud.ts
1540
2087
  function isScheduleEligible(goal) {
1541
2088
  return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
1542
2089
  }
@@ -1663,7 +2210,7 @@ function summarizeUlwLoopPlan(plan) {
1663
2210
  };
1664
2211
  }
1665
2212
 
1666
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts
2213
+ // components/ulw-loop/src/checkpoint-continuation.ts
1667
2214
  async function checkpointAndContinue(repoRoot, args, scope) {
1668
2215
  const result = await checkpointUlwLoop(repoRoot, args, scope);
1669
2216
  if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
@@ -1675,13 +2222,18 @@ async function checkpointAndContinue(repoRoot, args, scope) {
1675
2222
  return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
1676
2223
  }
1677
2224
  async function checkpoint(repoRoot, argv, json, scope) {
2225
+ if (hasFlag(argv, "--print-template")) {
2226
+ const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
2227
+ if (json)
2228
+ printJson({ ok: true, ...template });
2229
+ else
2230
+ printJson(template);
2231
+ return 0;
2232
+ }
1678
2233
  const goalId = required2(argv, "--goal-id");
1679
2234
  const statusValue = checkpointStatus(required2(argv, "--status"));
1680
2235
  const evidence = required2(argv, "--evidence");
1681
- const codexGoalJson = await parseCodexGoalJson(statusValue === "complete" ? required2(argv, "--codex-goal-json") : readValue(argv, "--codex-goal-json"));
1682
- if (statusValue === "complete" && codexGoalJson === undefined) {
1683
- throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
1684
- }
2236
+ const codexGoalJson = await parseCodexGoalJson(readValue(argv, "--codex-goal-json"));
1685
2237
  const qualityGateJson = readValue(argv, "--quality-gate-json");
1686
2238
  const args = {
1687
2239
  goalId,
@@ -1726,10 +2278,10 @@ function checkpointStatus(value) {
1726
2278
  throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
1727
2279
  }
1728
2280
 
1729
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
1730
- import { readFile as readFile6 } from "node:fs/promises";
2281
+ // components/ulw-loop/src/cli-subcommands.ts
2282
+ import { readFile as readFile5 } from "node:fs/promises";
1731
2283
 
1732
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts
2284
+ // components/ulw-loop/src/cli-steering.ts
1733
2285
  var SOURCES = ["user_prompt_submit", "finding", "cli"];
1734
2286
  var STEERING_KIND_HELP = [
1735
2287
  `Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
@@ -2036,7 +2588,7 @@ function printSteerBatchResult(result, json) {
2036
2588
  printStatus(result.plan);
2037
2589
  }
2038
2590
 
2039
- // packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts
2591
+ // components/ulw-loop/src/review-blockers.ts
2040
2592
  var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
2041
2593
  function ulwLoopError(message, code) {
2042
2594
  throw new UlwLoopError(message, code);
@@ -2074,10 +2626,12 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2074
2626
  if (!isFinalRunCompletionCandidate(plan, goal))
2075
2627
  ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
2076
2628
  const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
2077
- const aggregate = codexGoalMode(plan) === "aggregate";
2078
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
2629
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
2630
+ expectedObjective: expectedCodexObjective(plan, goal),
2631
+ acceptedObjectives: compatibleCodexObjectives(plan)
2632
+ });
2079
2633
  if (!reconciliation.ok)
2080
- ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
2634
+ throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
2081
2635
  const now = iso();
2082
2636
  for (const field of BLOCKER_FIELDS)
2083
2637
  Reflect.deleteProperty(goal, field);
@@ -2089,7 +2643,7 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2089
2643
  delete plan.activeGoalId;
2090
2644
  const newGoal = appendBlockerGoal(plan, args, now);
2091
2645
  plan.updatedAt = now;
2092
- const codexGoal = reconciliation.snapshot.raw;
2646
+ const codexGoal = snapshot?.raw;
2093
2647
  const blockedEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
2094
2648
  const addedEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
2095
2649
  const summaryEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
@@ -2098,18 +2652,54 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2098
2652
  await writePlan(repoRoot, plan, scope);
2099
2653
  for (const entry of ledgerEntries)
2100
2654
  await appendLedger(repoRoot, entry, scope);
2101
- return { plan, blockedGoal: goal, newGoal, ledgerEntries };
2655
+ return {
2656
+ plan,
2657
+ blockedGoal: goal,
2658
+ newGoal,
2659
+ ledgerEntries,
2660
+ nextActions: reconciliation.warnings,
2661
+ warnings: reconciliation.warnings.filter((warning) => warning.startsWith("driver_objective_differs"))
2662
+ };
2102
2663
  });
2103
2664
  }
2104
2665
 
2105
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts
2666
+ // components/ulw-loop/src/status-next-actions.ts
2667
+ function statusNextActions(plan) {
2668
+ const actions = [];
2669
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
2670
+ if (plan.goals.length === 0)
2671
+ actions.push(`No goals yet: bootstrap with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`);
2672
+ else if (active !== undefined)
2673
+ actions.push(...activeGoalActions(plan, active));
2674
+ for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
2675
+ 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>'\`.`);
2676
+ if (plan.evidenceLayoutVersion !== 2 || active === undefined)
2677
+ actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
2678
+ return actions;
2679
+ }
2680
+ function activeGoalActions(plan, active) {
2681
+ const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
2682
+ if (unresolved.length > 0)
2683
+ return [
2684
+ `${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>"\`.`
2685
+ ];
2686
+ if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
2687
+ return [
2688
+ `${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.`
2689
+ ];
2690
+ return [
2691
+ `${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.`
2692
+ ];
2693
+ }
2694
+
2695
+ // components/ulw-loop/src/steering-mutations.ts
2106
2696
  var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
2107
2697
  var isText = (value) => typeof value === "string" && value.trim().length > 0;
2108
2698
  var text3 = (value, key) => {
2109
2699
  const candidate = read2(value, key);
2110
2700
  return isText(candidate) ? candidate.trim() : undefined;
2111
2701
  };
2112
- var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model2) => model2 === value);
2702
+ var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2113
2703
  var after = (proposal) => {
2114
2704
  const candidate = read2(proposal, "after");
2115
2705
  return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
@@ -2184,36 +2774,36 @@ function reviseCriterion(plan, proposal, now) {
2184
2774
  const current = target?.successCriteria[index];
2185
2775
  if (target === undefined || current === undefined)
2186
2776
  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 };
2777
+ const model = read2(proposal, "userModel");
2778
+ target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model) ? model : current.userModel };
2189
2779
  target.updatedAt = now;
2190
2780
  }
2191
2781
 
2192
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-snapshot.ts
2782
+ // components/ulw-loop/src/steering-snapshot.ts
2193
2783
  function buildSteeringPlanSnapshot(plan, changedGoalIds) {
2194
2784
  const snapshot = {
2195
2785
  updatedAt: plan.updatedAt,
2196
2786
  goalCount: plan.goals.length,
2197
- goalIds: plan.goals.map((goal2) => goal2.id),
2198
- goals: plan.goals.filter((goal2) => changedGoalIds.has(goal2.id))
2787
+ goalIds: plan.goals.map((goal) => goal.id),
2788
+ goals: plan.goals.filter((goal) => changedGoalIds.has(goal.id))
2199
2789
  };
2200
2790
  return plan.activeGoalId === undefined ? snapshot : { ...snapshot, activeGoalId: plan.activeGoalId };
2201
2791
  }
2202
- function changedGoalIdsBetween(before, after2) {
2203
- const beforeById = new Map(before.goals.map((goal2) => [goal2.id, goal2]));
2792
+ function changedGoalIdsBetween(before, after) {
2793
+ const beforeById = new Map(before.goals.map((goal) => [goal.id, goal]));
2204
2794
  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);
2795
+ for (const goal of after.goals) {
2796
+ const prior = beforeById.get(goal.id);
2797
+ if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal))
2798
+ changed.add(goal.id);
2799
+ beforeById.delete(goal.id);
2210
2800
  }
2211
2801
  for (const id of beforeById.keys())
2212
2802
  changed.add(id);
2213
2803
  return changed;
2214
2804
  }
2215
2805
 
2216
- // packages/omo-codex/plugin/components/ulw-loop/src/steering.ts
2806
+ // components/ulw-loop/src/steering.ts
2217
2807
  var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
2218
2808
  var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
2219
2809
  var isObject2 = (value) => typeof value === "object" && value !== null;
@@ -2226,7 +2816,7 @@ var text4 = (value, key) => {
2226
2816
  };
2227
2817
  var isKind2 = (value) => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
2228
2818
  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);
2819
+ var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2230
2820
  var texts = (value, key) => {
2231
2821
  const candidate = read3(value, key);
2232
2822
  return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
@@ -2367,10 +2957,10 @@ function validateCriterion(plan, proposal, reasons) {
2367
2957
  reasons.push("revise_criterion requires goalId");
2368
2958
  else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
2369
2959
  reasons.push("revise_criterion requires criterionId");
2370
- const model2 = read3(proposal, "userModel");
2371
- if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model2 === undefined)
2960
+ const model = read3(proposal, "userModel");
2961
+ if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model === undefined)
2372
2962
  reasons.push("revise_criterion requires update");
2373
- if (model2 !== undefined && !isModel3(model2))
2963
+ if (model !== undefined && !isModel3(model))
2374
2964
  reasons.push("invalid userModel");
2375
2965
  }
2376
2966
  function applySteeringMutation(plan, proposal, audit) {
@@ -2397,8 +2987,8 @@ function applySteeringMutation(plan, proposal, audit) {
2397
2987
  function isProposal(value) {
2398
2988
  return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
2399
2989
  }
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);
2990
+ function parseUlwLoopSteeringDirective(text) {
2991
+ 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
2992
  if (match?.[1] === undefined)
2403
2993
  return null;
2404
2994
  try {
@@ -2450,7 +3040,143 @@ function ledgerEntry(proposal, audit, at) {
2450
3040
  return entry;
2451
3041
  }
2452
3042
 
2453
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts
3043
+ // components/ulw-loop/src/sdk/manifest.ts
3044
+ var ULW_LOOP_MANIFEST = {
3045
+ version: 1,
3046
+ name: "ulw-loop",
3047
+ operations: [
3048
+ { name: "help", mutating: false },
3049
+ { name: "create-goals", mutating: true },
3050
+ { name: "status", mutating: false },
3051
+ { name: "complete-goals", mutating: true },
3052
+ { name: "checkpoint", mutating: true },
3053
+ { name: "steer", mutating: true },
3054
+ { name: "add-goal", mutating: true },
3055
+ { name: "criteria", mutating: false },
3056
+ { name: "record-evidence", mutating: true },
3057
+ { name: "record-review-blockers", mutating: true }
3058
+ ]
3059
+ };
3060
+
3061
+ // components/ulw-loop/src/sdk/factory.ts
3062
+ function validateCodexGoalJson(raw) {
3063
+ if (raw === undefined)
3064
+ return;
3065
+ try {
3066
+ JSON.parse(raw);
3067
+ } catch (error) {
3068
+ throw new UlwLoopError(`Invalid codexGoal: ${error instanceof Error ? error.message : "not valid JSON"}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
3069
+ }
3070
+ }
3071
+ function validateContext(context) {
3072
+ if (!context.cwd.trim())
3073
+ throw new UlwLoopError("cwd is required.", "ULW_LOOP_CWD_REQUIRED");
3074
+ if (!context.sessionId.trim())
3075
+ throw new UlwLoopError("ULW_LOOP_SESSION_ID_REQUIRED: sessionId is required.", "ULW_LOOP_SESSION_ID_REQUIRED");
3076
+ if (context.surface !== "omo-senpi" && context.surface !== "lazycodex")
3077
+ throw new UlwLoopError("surface must be omo-senpi or lazycodex.", "ULW_LOOP_SURFACE_INVALID");
3078
+ }
3079
+ function errorDetails(error) {
3080
+ const details = error.details === undefined ? undefined : Object.fromEntries(Object.entries(error.details).map(([key, value]) => [key, String(value)]));
3081
+ return details === undefined ? { code: error.code, message: error.message } : { code: error.code, message: error.message, details };
3082
+ }
3083
+ function failure(operation, error) {
3084
+ return { ok: false, operation, error: errorDetails(error) };
3085
+ }
3086
+ function caught(operation, error) {
3087
+ return failure(operation, error instanceof UlwLoopError ? error : new UlwLoopError(error.message, "ULW_LOOP_ERROR"));
3088
+ }
3089
+ function isKnownRequest(request) {
3090
+ return ULW_LOOP_MANIFEST.operations.some((operation) => operation.name === request.operation);
3091
+ }
3092
+ function unreachable(value) {
3093
+ throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
3094
+ }
3095
+ function nextActionsFrom(result) {
3096
+ if (!("nextActions" in result) || !Array.isArray(result.nextActions))
3097
+ return [];
3098
+ return result.nextActions.filter((action) => typeof action === "string").slice(0, 8);
3099
+ }
3100
+ function checkpointWithValidatedSnapshot(context, scope, args) {
3101
+ validateCodexGoalJson(args.codexGoalJson);
3102
+ return checkpointUlwLoop(context.cwd, args, scope, { surface: context.surface });
3103
+ }
3104
+ function createAgentToolkit(context, deps = {}) {
3105
+ validateContext(context);
3106
+ const scope = { sessionId: context.sessionId };
3107
+ const notify = async (operation, response) => {
3108
+ if (deps.hooks?.onOperation !== undefined)
3109
+ await deps.hooks.onOperation({ operation, context, response });
3110
+ return response;
3111
+ };
3112
+ const invoke = async (operation, fn) => {
3113
+ try {
3114
+ const result = await fn();
3115
+ const nextActions = typeof result === "object" && result !== null ? nextActionsFrom(result) : [];
3116
+ return await notify(operation, { ok: true, operation, result, nextActions });
3117
+ } catch (error) {
3118
+ const response = caught(operation, error instanceof Error ? error : new Error("ULW_LOOP_ERROR"));
3119
+ return notify(operation, response);
3120
+ }
3121
+ };
3122
+ const toolkit = {
3123
+ dispatch: async (request) => {
3124
+ if (!isKnownRequest(request))
3125
+ return failure(request.operation, new UlwLoopError(`Unknown operation: ${request.operation}`, "ULW_LOOP_OPERATION_UNKNOWN"));
3126
+ switch (request.operation) {
3127
+ case "help":
3128
+ return toolkit.help();
3129
+ case "create-goals":
3130
+ return toolkit.createGoals(request.args);
3131
+ case "status":
3132
+ return toolkit.status();
3133
+ case "complete-goals":
3134
+ return toolkit.completeGoals(request.args);
3135
+ case "checkpoint":
3136
+ return toolkit.checkpoint(request.args);
3137
+ case "steer":
3138
+ return toolkit.steer(request.args);
3139
+ case "add-goal":
3140
+ return toolkit.addGoal(request.args);
3141
+ case "criteria":
3142
+ return toolkit.criteria(request.args);
3143
+ case "record-evidence":
3144
+ return toolkit.recordEvidence(request.args);
3145
+ case "record-review-blockers":
3146
+ return toolkit.recordReviewBlockers(request.args);
3147
+ default:
3148
+ return unreachable(request);
3149
+ }
3150
+ },
3151
+ help: () => invoke("help", async () => ULW_LOOP_MANIFEST),
3152
+ createGoals: (args) => invoke("create-goals", () => createUlwLoopPlan(context.cwd, args, scope)),
3153
+ status: () => invoke("status", async () => {
3154
+ const plan = await readUlwLoopPlan(context.cwd, scope);
3155
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3156
+ return {
3157
+ plan,
3158
+ summary: summarizeUlwLoopPlan(plan),
3159
+ nextActions: statusNextActions(plan),
3160
+ ...active === undefined || plan.evidenceLayoutVersion !== 2 ? {} : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }
3161
+ };
3162
+ }),
3163
+ completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
3164
+ checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface }) : checkpointWithValidatedSnapshot(context, scope, args)),
3165
+ steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope)),
3166
+ addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope)),
3167
+ criteria: (args) => invoke("criteria", async () => {
3168
+ const plan = await readUlwLoopPlan(context.cwd, scope);
3169
+ const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
3170
+ if (goal === undefined)
3171
+ throw new UlwLoopError(`Unknown ulw-loop id: ${args.goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND");
3172
+ return { goalId: goal.id, criteria: goal.successCriteria };
3173
+ }),
3174
+ recordEvidence: (args) => invoke("record-evidence", () => recordEvidence(context.cwd, args, scope)),
3175
+ recordReviewBlockers: (args) => invoke("record-review-blockers", () => recordFinalReviewBlockers(context.cwd, args, scope))
3176
+ };
3177
+ return toolkit;
3178
+ }
3179
+ // components/ulw-loop/src/steering-batch.ts
2454
3180
  async function steerUlwLoopBatch(repoRoot, proposals, scope) {
2455
3181
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
2456
3182
  const plan = await readUlwLoopPlan(repoRoot, scope);
@@ -2483,16 +3209,16 @@ async function prepareBatch(repoRoot, plan, proposals, scope) {
2483
3209
  const key = proposal.idempotencyKey ?? proposal.promptSignature;
2484
3210
  const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
2485
3211
  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);
3212
+ const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
3213
+ items.push({ kind: "deduped", result });
3214
+ results.push(result);
2489
3215
  continue;
2490
3216
  }
2491
3217
  const audit = validateUlwLoopSteeringProposal(current, proposal);
2492
3218
  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);
3219
+ const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
3220
+ items.push({ kind: "deduped", result });
3221
+ results.push(result);
2496
3222
  continue;
2497
3223
  }
2498
3224
  const next = applySteeringMutation(current, proposal, audit);
@@ -2531,20 +3257,20 @@ function ledgerEntry2(proposal, audit, at) {
2531
3257
  return entry;
2532
3258
  }
2533
3259
 
2534
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
3260
+ // components/ulw-loop/src/cli-subcommands.ts
2535
3261
  async function createGoals(repoRoot, argv, json, scope) {
2536
3262
  const briefFile = readValue(argv, "--brief-file");
2537
- const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile6(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
3263
+ const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile5(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
2538
3264
  if (!brief.trim()) {
2539
3265
  throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
2540
3266
  }
2541
3267
  const validationBatchesJson = readValue(argv, "--validation-batch-json");
2542
- const plan = await createUlwLoopPlan(repoRoot, {
3268
+ const plan = unwrap(await toolkitFor(repoRoot, scope).createGoals({
2543
3269
  brief,
2544
3270
  codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")),
2545
3271
  force: hasFlag(argv, "--force"),
2546
3272
  ...validationBatchesJson === undefined ? {} : { validationBatchesJson }
2547
- }, scope);
3273
+ }));
2548
3274
  if (json)
2549
3275
  printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
2550
3276
  else {
@@ -2557,22 +3283,15 @@ ledger: ${plan.ledgerPath}
2557
3283
  return 0;
2558
3284
  }
2559
3285
  async function status(repoRoot, json, scope) {
2560
- const plan = await readUlwLoopPlan(repoRoot, scope);
2561
- if (json) {
2562
- const active = plan.goals.find((goal3) => goal3.id === plan.activeGoalId);
2563
- const currentAttemptDir = plan.evidenceLayoutVersion === 2 && active ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : undefined;
2564
- printJson({
2565
- ok: true,
2566
- plan,
2567
- summary: summarizeUlwLoopPlan(plan),
2568
- ...currentAttemptDir === undefined ? {} : { currentAttemptDir }
2569
- });
2570
- } else
2571
- printStatus(plan);
3286
+ const status = unwrap(await toolkitFor(repoRoot, scope).status());
3287
+ if (json)
3288
+ printJson({ ok: true, ...status });
3289
+ else
3290
+ printStatus(status.plan);
2572
3291
  return 0;
2573
3292
  }
2574
3293
  async function completeGoals(repoRoot, argv, json, scope) {
2575
- const result = await startNextUlwLoop(repoRoot, { retryFailed: hasFlag(argv, "--retry-failed") }, scope);
3294
+ const result = unwrap(await toolkitFor(repoRoot, scope).completeGoals({ retryFailed: hasFlag(argv, "--retry-failed") }));
2576
3295
  if ("done" in result) {
2577
3296
  const handoff = blockedDecisionHandoff(result.plan);
2578
3297
  if (json) {
@@ -2601,16 +3320,19 @@ async function steer(repoRoot, argv, json, scope) {
2601
3320
  const proposals = await parseSteeringProposals(argv);
2602
3321
  const single = proposals[0];
2603
3322
  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;
3323
+ const result = unwrap(await toolkitFor(repoRoot, scope).steer(single));
3324
+ printSteerResult(result, json);
3325
+ return result.accepted ? 0 : 1;
2607
3326
  }
2608
3327
  const result = await steerUlwLoopBatch(repoRoot, proposals, scope);
2609
3328
  printSteerBatchResult(result, json);
2610
3329
  return result.accepted ? 0 : 1;
2611
3330
  }
2612
3331
  async function addGoal(repoRoot, argv, json, scope) {
2613
- const result = await addUlwLoopGoal(repoRoot, { title: required4(argv, "--title"), objective: required4(argv, "--objective") }, scope);
3332
+ const result = unwrap(await toolkitFor(repoRoot, scope).addGoal({
3333
+ title: required4(argv, "--title"),
3334
+ objective: required4(argv, "--objective")
3335
+ }));
2614
3336
  if (json)
2615
3337
  printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
2616
3338
  else {
@@ -2622,19 +3344,19 @@ async function addGoal(repoRoot, argv, json, scope) {
2622
3344
  }
2623
3345
  async function criteria(repoRoot, argv, json, scope) {
2624
3346
  const goalId = required4(argv, "--goal-id");
2625
- const goal3 = findGoal3(await readUlwLoopPlan(repoRoot, scope), goalId);
3347
+ const result = unwrap(await toolkitFor(repoRoot, scope).criteria({ goalId }));
2626
3348
  if (json)
2627
- printJson({ ok: true, goalId: goal3.id, criteria: goal3.successCriteria });
3349
+ printJson({ ok: true, goalId: result.goalId, criteria: result.criteria });
2628
3350
  else {
2629
- process.stdout.write(`criteria for ${goal3.id}:
2630
- ${goal3.successCriteria.map(formatCriterionForCli).join(`
3351
+ process.stdout.write(`criteria for ${result.goalId}:
3352
+ ${result.criteria.map(formatCriterionForCli).join(`
2631
3353
  `)}
2632
3354
  `);
2633
3355
  }
2634
3356
  return 0;
2635
3357
  }
2636
3358
  async function captureEvidence(repoRoot, argv, json, scope) {
2637
- const result = await recordEvidence(repoRoot, parseRecordEvidenceArgs(argv), scope);
3359
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordEvidence(parseRecordEvidenceArgs(argv)));
2638
3360
  if (json)
2639
3361
  printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
2640
3362
  else {
@@ -2648,13 +3370,13 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
2648
3370
  if (codexGoalJson === undefined) {
2649
3371
  throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
2650
3372
  }
2651
- const result = await recordFinalReviewBlockers(repoRoot, {
3373
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordReviewBlockers({
2652
3374
  goalId: required4(argv, "--goal-id"),
2653
3375
  title: required4(argv, "--title"),
2654
3376
  objective: required4(argv, "--objective"),
2655
3377
  evidence: required4(argv, "--evidence"),
2656
3378
  codexGoalJson
2657
- }, scope);
3379
+ }));
2658
3380
  if (json) {
2659
3381
  printJson({
2660
3382
  ok: true,
@@ -2662,6 +3384,8 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
2662
3384
  blockedGoal: result.blockedGoal,
2663
3385
  goal: result.newGoal,
2664
3386
  ledgerEntries: result.ledgerEntries,
3387
+ nextActions: result.nextActions,
3388
+ warnings: result.warnings,
2665
3389
  summary: summarizeUlwLoopPlan(result.plan)
2666
3390
  });
2667
3391
  } else {
@@ -2670,6 +3394,20 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
2670
3394
  }
2671
3395
  return 0;
2672
3396
  }
3397
+ function toolkitFor(repoRoot, scope) {
3398
+ const sessionId = scope?.sessionId?.trim();
3399
+ if (sessionId === undefined || sessionId.length === 0) {
3400
+ throw new UlwLoopError("Missing --session-id.", "ULW_LOOP_SESSION_ID_REQUIRED", {
3401
+ details: { flag: "--session-id" }
3402
+ });
3403
+ }
3404
+ return createAgentToolkit({ cwd: repoRoot, sessionId, surface: resolveToolkitSurface() });
3405
+ }
3406
+ function unwrap(response) {
3407
+ if (response.ok)
3408
+ return response.result;
3409
+ throw new UlwLoopError(response.error.message, response.error.code);
3410
+ }
2673
3411
  function formatCriterionForCli(criterion) {
2674
3412
  const marker = isEssentialCriterion(criterion) ? "essential" : "non-essential";
2675
3413
  return `- ${criterion.id} [${criterion.status}] [${marker}] (${criterion.userModel}) ${criterion.scenario} evidence: ${criterion.capturedEvidence ?? "pending"}`;
@@ -2680,14 +3418,8 @@ function required4(argv, flag) {
2680
3418
  return value;
2681
3419
  throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
2682
3420
  }
2683
- function findGoal3(plan, goalId) {
2684
- const goal3 = plan.goals.find((candidate) => candidate.id === goalId);
2685
- if (goal3 !== undefined)
2686
- return goal3;
2687
- throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
2688
- }
2689
3421
 
2690
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts
3422
+ // components/ulw-loop/src/cli-commands.ts
2691
3423
  var ULW_LOOP_SUBCOMMANDS = [
2692
3424
  "help",
2693
3425
  "create-goals",
@@ -2710,7 +3442,6 @@ async function ulwLoopCommand(argv) {
2710
3442
  const repoRoot = process.cwd();
2711
3443
  const json = hasFlag(rest, "--json");
2712
3444
  try {
2713
- const scope = commandScope(rest);
2714
3445
  if (!isUlwLoopSubcommand(command)) {
2715
3446
  if (json) {
2716
3447
  printJsonError(new UlwLoopError(`Unknown ulw-loop subcommand: ${command}.`, "ULW_LOOP_SUBCOMMAND_UNKNOWN", {
@@ -2722,11 +3453,18 @@ async function ulwLoopCommand(argv) {
2722
3453
  `);
2723
3454
  return 1;
2724
3455
  }
2725
- switch (command) {
2726
- case "help":
2727
- process.stdout.write(`${ULW_LOOP_HELP}
3456
+ if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
3457
+ process.stdout.write(`${subcommandHelp(command)}
3458
+ `);
3459
+ return 0;
3460
+ }
3461
+ if (command === "help") {
3462
+ process.stdout.write(`${ULW_LOOP_HELP}
2728
3463
  `);
2729
- return 0;
3464
+ return 0;
3465
+ }
3466
+ const scope = commandScope(repoRoot, rest);
3467
+ switch (command) {
2730
3468
  case "create-goals":
2731
3469
  return await createGoals(repoRoot, rest, json, scope);
2732
3470
  case "status":
@@ -2772,26 +3510,29 @@ var SESSION_ID_FLAG = "--session-id";
2772
3510
  function sessionIdFlagPresent(argv) {
2773
3511
  return hasFlag(argv, SESSION_ID_FLAG) || argv.some((arg) => arg.startsWith(`${SESSION_ID_FLAG}=`));
2774
3512
  }
2775
- function commandScope(argv) {
3513
+ function commandScope(repoRoot, argv) {
2776
3514
  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", {
3515
+ const sessionId = readValue(argv, SESSION_ID_FLAG)?.trim();
3516
+ if (!sessionId) {
3517
+ throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
2780
3518
  details: { flag: SESSION_ID_FLAG }
2781
3519
  });
2782
3520
  }
2783
- return { sessionId: sessionId2 };
3521
+ return { sessionId };
2784
3522
  }
2785
3523
  const sessionId = resolveUlwLoopSessionIdFromEnv();
2786
- return sessionId === null ? undefined : { sessionId };
3524
+ if (sessionId !== null)
3525
+ return { sessionId };
3526
+ const existingSessionIds = listUlwLoopSessionIds(repoRoot);
3527
+ throw new UlwLoopError(sessionScopeRequiredMessage(SESSION_ID_FLAG, existingSessionIds), "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { flag: SESSION_ID_FLAG, existingSessionIds } });
2787
3528
  }
2788
3529
 
2789
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
2790
- import { readFileSync as readFileSync2 } from "node:fs";
3530
+ // components/ulw-loop/src/ultrawork-directive.ts
3531
+ import { readFileSync as readFileSync4 } from "node:fs";
2791
3532
 
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";
3533
+ // components/ulw-loop/src/ultrawork-skill-pointer.ts
3534
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
3535
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2795
3536
  var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
2796
3537
  ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
2797
3538
 
@@ -2822,9 +3563,9 @@ Do not start the requested work until all three steps are complete.
2822
3563
  `;
2823
3564
  var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
2824
3565
  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");
3566
+ var ULTRAWORK_DIRECTIVE = readFileSync3(new URL("../directive.md", import.meta.url), "utf8");
2826
3567
  function resolveUltraworkSkillFilePath() {
2827
- return fileURLToPath(ULTRAWORK_SKILL_FILE_URL);
3568
+ return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
2828
3569
  }
2829
3570
  function buildUltraworkSkillPointer(skillFilePath) {
2830
3571
  return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
@@ -2837,7 +3578,7 @@ function buildUltraworkAdditionalContext(options = {}) {
2837
3578
  return ULTRAWORK_DIRECTIVE;
2838
3579
  }
2839
3580
 
2840
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
3581
+ // components/ulw-loop/src/ultrawork-directive.ts
2841
3582
  var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
2842
3583
  var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
2843
3584
  var TRANSCRIPT_SEARCH_BYTES = 512000;
@@ -2885,7 +3626,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
2885
3626
  return false;
2886
3627
  }
2887
3628
  function readTranscriptTail(transcriptPath) {
2888
- const rawTranscript = readFileSync2(transcriptPath);
3629
+ const rawTranscript = readFileSync4(transcriptPath);
2889
3630
  return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
2890
3631
  }
2891
3632
  function isUltraworkPrompt(prompt) {
@@ -2940,7 +3681,7 @@ function isRecord3(value) {
2940
3681
  return typeof value === "object" && value !== null && !Array.isArray(value);
2941
3682
  }
2942
3683
 
2943
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts
3684
+ // components/ulw-loop/src/codex-hook.ts
2944
3685
  var CREATE_GOAL_TOOL_NAME = "create_goal";
2945
3686
  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
3687
  function parseUserPromptSubmitPayload(raw) {
@@ -3066,97 +3807,254 @@ function optionalString(value) {
3066
3807
  return value === undefined || typeof value === "string";
3067
3808
  }
3068
3809
  function readAll(stdin) {
3069
- return new Promise((resolve4, reject) => {
3810
+ return new Promise((resolve, reject) => {
3070
3811
  let data = "";
3071
3812
  stdin.setEncoding("utf8");
3072
3813
  stdin.on("data", (chunk) => {
3073
3814
  data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
3074
3815
  });
3075
3816
  stdin.once("error", reject);
3076
- stdin.once("end", () => resolve4(data));
3817
+ stdin.once("end", () => resolve(data));
3077
3818
  });
3078
3819
  }
3079
3820
 
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";
3083
- 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) {
3821
+ // components/ulw-loop/src/spawn-guard.ts
3822
+ import { randomBytes } from "node:crypto";
3823
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, statSync as statSync3, writeFileSync } from "node:fs";
3824
+ import { dirname as dirname3, join as join3 } from "node:path";
3825
+
3826
+ // components/ulw-loop/src/spawn-role-guard.ts
3827
+ var LAZYCODEX_SPAWN_ROLES = new Set([
3828
+ "explorer",
3829
+ "lazycodex-clone-fidelity-reviewer",
3830
+ "lazycodex-code-reviewer",
3831
+ "lazycodex-gate-reviewer",
3832
+ "lazycodex-qa-executor",
3833
+ "lazycodex-worker-high",
3834
+ "lazycodex-worker-low",
3835
+ "lazycodex-worker-medium",
3836
+ "librarian",
3837
+ "metis",
3838
+ "momus",
3839
+ "plan"
3840
+ ]);
3841
+ function spawnRoleDenial(input) {
3842
+ const role = typeof input === "object" && input !== null && "agent_type" in input ? input.agent_type : undefined;
3843
+ if (typeof role === "string" && LAZYCODEX_SPAWN_ROLES.has(role))
3844
+ return null;
3845
+ return `LazyCodex requires an explicit registered agent_type: ${[...LAZYCODEX_SPAWN_ROLES].join(", ")}. Received ${JSON.stringify(role) ?? "no agent_type"}. Use the matching role and fork_turns: "none" (V2) or fork_context: false (V1), unless full history is deliberately required. The hook cannot see the tool schema; if agent_type is unavailable, stop and report incompatible role routing rather than spawning a generic agent. Describing a role in message does not select its TOML.`;
3846
+ }
3847
+
3848
+ // components/ulw-loop/src/spawn-guard.ts
3849
+ var SPAWN_TOOL_TOKENS = new Set([
3850
+ "spawn_agent",
3851
+ "multi_agent_v1.spawn_agent",
3852
+ "collaborationspawn_agent",
3853
+ "collaboration.spawn_agent"
3854
+ ]);
3855
+ var DEFAULT_FANOUT_LIMIT = 24;
3856
+ var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
3857
+ var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
3858
+ var REVIEW_AGENT_TYPES = [
3859
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
3860
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
3861
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
3862
+ ];
3863
+ var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
3864
+ function applySpawnGuards(payload, options = {}) {
3087
3865
  if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
3088
3866
  return "";
3089
- const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3090
- const plan = readPlan(join2(stateDir, "goals.json"));
3867
+ if (resolveToolkitSurface() === "lazycodex") {
3868
+ const reason = spawnRoleDenial(payload.tool_input);
3869
+ if (reason !== null)
3870
+ return deny(reason);
3871
+ }
3872
+ return applySpawnBudgetGuards(payload, options);
3873
+ }
3874
+ function applySpawnBudgetGuards(payload, options = {}) {
3875
+ if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
3876
+ return "";
3877
+ const breaker = readAdmissionBreaker(payload.session_id);
3878
+ if (breaker !== null)
3879
+ 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.`);
3880
+ const scope = { sessionId: payload.session_id };
3881
+ const stateDir = ulwLoopDir(payload.cwd, scope);
3882
+ const plan = readPlan(join3(stateDir, "goals.json"));
3091
3883
  if (plan === null)
3092
3884
  return "";
3885
+ const lockOptions = options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs };
3886
+ try {
3887
+ return withStateLockSync(ulwLoopStateLockPath(payload.cwd, scope), () => evaluateGuards(payload, plan, stateDir), lockOptions);
3888
+ } catch (error) {
3889
+ if (isStateLockTimeout(error))
3890
+ return deny(`ulw-loop spawn guard could not take the session state lock: ${error.message}`);
3891
+ throw error;
3892
+ }
3893
+ }
3894
+ function evaluateGuards(payload, plan, stateDir) {
3895
+ const fanOutPeek = peekFanOutBudget(stateDir);
3896
+ if (fanOutPeek !== null)
3897
+ return deny(fanOutPeek);
3898
+ const missingArtifact = missingGateArtifact(payload, plan);
3899
+ if (missingArtifact !== null)
3900
+ return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
3901
+ const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
3902
+ if (reviewDenial !== null)
3903
+ return deny(reviewDenial);
3093
3904
  const fanOutDenial = consumeFanOutBudget(stateDir);
3094
3905
  if (fanOutDenial !== null)
3095
3906
  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
3907
  return "";
3100
3908
  }
3909
+ async function runSpawnAdmissionRecorderCli(stdin, stdout) {
3910
+ const chunks = [];
3911
+ for await (const chunk of stdin)
3912
+ chunks.push(Buffer.from(chunk));
3913
+ try {
3914
+ const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
3915
+ const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
3916
+ if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
3917
+ return;
3918
+ const dataDir = process.env["PLUGIN_DATA"];
3919
+ if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
3920
+ return;
3921
+ const markerDir = join3(dataDir, "spawn-breaker");
3922
+ try {
3923
+ mkdirSync2(markerDir, { recursive: true });
3924
+ atomicWriteJson(join3(markerDir, `${payload["session_id"]}.json`), {
3925
+ reason: response,
3926
+ at: new Date().toISOString()
3927
+ });
3928
+ } catch (error) {
3929
+ const message = error instanceof Error ? error.message : String(error);
3930
+ process.stderr.write(`[ulw-loop] spawn-guard: could not persist admission failure: ${message}
3931
+ `);
3932
+ }
3933
+ } catch {}
3934
+ }
3101
3935
  async function runSpawnGuardCli(stdin, stdout) {
3102
3936
  try {
3103
3937
  const chunks = [];
3104
3938
  for await (const chunk of stdin)
3105
3939
  chunks.push(Buffer.from(chunk));
3106
3940
  const payload = parsePreToolUsePayload(Buffer.concat(chunks).toString("utf8"));
3107
- if (payload === null)
3941
+ if (payload === null) {
3942
+ stdout.write(deny("LazyCodex spawn guard received an invalid hook payload; role routing was not verified."));
3108
3943
  return;
3944
+ }
3109
3945
  const output = applySpawnGuards(payload);
3110
3946
  if (output.length > 0)
3111
3947
  stdout.write(output);
3112
3948
  } catch (error) {
3113
- if (error instanceof Error)
3114
- return;
3949
+ stdout.write(deny(`LazyCodex spawn guard failed: ${error instanceof Error ? error.message : String(error)}`));
3115
3950
  }
3116
3951
  }
3952
+ function peekFanOutBudget(stateDir) {
3953
+ const counterPath = join3(stateDir, "spawn-count.json");
3954
+ const count = readCount(counterPath) + 1;
3955
+ const limit = fanOutLimit();
3956
+ if (count <= limit)
3957
+ return null;
3958
+ 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.`;
3959
+ }
3117
3960
  function consumeFanOutBudget(stateDir) {
3118
- const counterPath = join2(stateDir, "spawn-count.json");
3961
+ const counterPath = join3(stateDir, "spawn-count.json");
3119
3962
  const count = readCount(counterPath) + 1;
3120
- writeFileSync(counterPath, JSON.stringify({ count }));
3963
+ atomicWriteJson(counterPath, { count });
3121
3964
  const limit = fanOutLimit();
3122
3965
  if (count <= limit)
3123
3966
  return null;
3124
3967
  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
3968
  }
3969
+ function consumeReviewSpawnBudget(payload, plan, stateDir) {
3970
+ const agentType = reviewAgentType(payload.tool_input);
3971
+ if (agentType === null)
3972
+ return null;
3973
+ const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3974
+ if (goal === undefined)
3975
+ return null;
3976
+ const counterPath = join3(stateDir, "review-spawn-counts.json");
3977
+ const limit = reviewSpawnLimit();
3978
+ const counts = readCounts(counterPath);
3979
+ const key = `${agentType}:${goal.id}:a${goal.attempt}`;
3980
+ const count = (counts[key] ?? 0) + 1;
3981
+ if (count > limit)
3982
+ 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.`;
3983
+ counts[key] = count;
3984
+ atomicWriteJson(counterPath, counts);
3985
+ return null;
3986
+ }
3126
3987
  function missingGateArtifact(payload, plan) {
3127
3988
  if (!isGateReviewerSpawn(payload.tool_input))
3128
3989
  return null;
3129
- const goal3 = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3130
- if (goal3 === undefined || goal3.status === "complete")
3990
+ const goal = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3991
+ if (goal === undefined || goal.status === "complete")
3131
3992
  return null;
3132
- if (!goal3.successCriteria.every((criterion) => criterion.status === "pass"))
3993
+ if (!goal.successCriteria.every((criterion) => criterion.status === "pass"))
3133
3994
  return null;
3134
3995
  const scope = { sessionId: payload.session_id };
3996
+ const requiredArtifacts = [`${goal.id}-manual-qa.md`];
3135
3997
  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;
3998
+ const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
3999
+ for (const name of requiredArtifacts) {
4000
+ const relative = `${attemptDir}/${name}`;
4001
+ if (!isNonEmptyFile(join3(payload.cwd, relative)))
4002
+ return relative;
3141
4003
  }
3142
4004
  return null;
3143
4005
  }
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;
4006
+ const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
4007
+ return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
3150
4008
  }
3151
4009
  function isGateReviewerSpawn(toolInput) {
4010
+ const agentType = reviewAgentType(toolInput);
4011
+ return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
4012
+ }
4013
+ function reviewAgentType(toolInput) {
3152
4014
  if (typeof toolInput !== "object" || toolInput === null)
3153
- return false;
4015
+ return null;
3154
4016
  const record = toolInput;
3155
4017
  const agentType = record["agent_type"];
3156
- if (typeof agentType === "string")
3157
- return agentType === "lazycodex-gate-reviewer";
4018
+ if (typeof agentType === "string") {
4019
+ if (!REVIEW_AGENT_TYPE_SET.has(agentType))
4020
+ return null;
4021
+ return activeSurfaceReviewerAlias(agentType);
4022
+ }
3158
4023
  const message = record["message"];
3159
- return typeof message === "string" && GATE_MESSAGE_PATTERN.test(message);
4024
+ if (typeof message !== "string")
4025
+ return null;
4026
+ const normalizedMessage = message.toLowerCase();
4027
+ const allRoleNames = [...REVIEW_AGENT_TYPES];
4028
+ const explicitAssignment = allRoleNames.map((name) => ({
4029
+ name,
4030
+ index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
4031
+ })).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
4032
+ if (explicitAssignment !== undefined)
4033
+ return activeSurfaceReviewerAlias(explicitAssignment.name);
4034
+ const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
4035
+ if (nonReviewerActAs)
4036
+ return null;
4037
+ const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
4038
+ if (namedReviewer !== undefined)
4039
+ return activeSurfaceReviewerAlias(namedReviewer);
4040
+ return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
4041
+ }
4042
+ function activeSurfaceReviewerAlias(reviewer) {
4043
+ const activeRoles = reviewerRolesFor(resolveToolkitSurface());
4044
+ for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
4045
+ if (reviewer === roles.codeReview)
4046
+ return activeRoles.codeReview;
4047
+ if (reviewer === roles.manualQa)
4048
+ return activeRoles.manualQa;
4049
+ if (reviewer === roles.gateReview)
4050
+ return activeRoles.gateReview;
4051
+ }
4052
+ return reviewer;
4053
+ }
4054
+ function atomicWriteJson(targetPath, data) {
4055
+ const tmp = join3(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
4056
+ writeFileSync(tmp, JSON.stringify(data));
4057
+ renameSync(tmp, targetPath);
3160
4058
  }
3161
4059
  function deny(reason) {
3162
4060
  return `${JSON.stringify({
@@ -3169,6 +4067,17 @@ function deny(reason) {
3169
4067
  })}
3170
4068
  `;
3171
4069
  }
4070
+ function readAdmissionBreaker(sessionId) {
4071
+ const dataDir = process.env["PLUGIN_DATA"];
4072
+ if (typeof dataDir !== "string")
4073
+ return null;
4074
+ try {
4075
+ const value = JSON.parse(readFileSync5(join3(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
4076
+ return typeof value.reason === "string" ? value.reason : "capacity limit";
4077
+ } catch {
4078
+ return null;
4079
+ }
4080
+ }
3172
4081
  function fanOutLimit() {
3173
4082
  const raw = process.env["OMO_SPAWN_FANOUT_LIMIT"];
3174
4083
  if (raw === undefined)
@@ -3176,37 +4085,52 @@ function fanOutLimit() {
3176
4085
  const parsed = Number.parseInt(raw, 10);
3177
4086
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
3178
4087
  }
4088
+ function reviewSpawnLimit() {
4089
+ const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
4090
+ if (raw === undefined)
4091
+ return DEFAULT_REVIEW_SPAWN_LIMIT;
4092
+ const parsed = Number.parseInt(raw, 10);
4093
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
4094
+ }
3179
4095
  function isNonEmptyFile(path) {
3180
4096
  try {
3181
- return existsSync6(path) && statSync2(path).size > 0;
4097
+ return existsSync6(path) && statSync3(path).size > 0;
3182
4098
  } catch (error) {
3183
4099
  if (error instanceof Error)
3184
4100
  return false;
3185
4101
  throw error;
3186
4102
  }
3187
4103
  }
3188
- function hasOtherEvidenceFile(evidenceDir, excludedName) {
4104
+ function readCount(counterPath) {
3189
4105
  try {
3190
- return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join2(evidenceDir, name)));
4106
+ const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
4107
+ return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
3191
4108
  } catch (error) {
3192
4109
  if (error instanceof Error)
3193
- return false;
4110
+ return 0;
3194
4111
  throw error;
3195
4112
  }
3196
4113
  }
3197
- function readCount(counterPath) {
4114
+ function readCounts(counterPath) {
3198
4115
  try {
3199
- const parsed = JSON.parse(readFileSync3(counterPath, "utf8"));
3200
- return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
4116
+ const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
4117
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
4118
+ return {};
4119
+ const counts = {};
4120
+ for (const [key, value] of Object.entries(parsed)) {
4121
+ if (typeof value === "number" && value >= 0)
4122
+ counts[key] = value;
4123
+ }
4124
+ return counts;
3201
4125
  } catch (error) {
3202
4126
  if (error instanceof Error)
3203
- return 0;
4127
+ return {};
3204
4128
  throw error;
3205
4129
  }
3206
4130
  }
3207
4131
  function readPlan(goalsPath) {
3208
4132
  try {
3209
- return JSON.parse(readFileSync3(goalsPath, "utf8"));
4133
+ return JSON.parse(readFileSync5(goalsPath, "utf8"));
3210
4134
  } catch (error) {
3211
4135
  if (error instanceof Error)
3212
4136
  return null;
@@ -3214,9 +4138,9 @@ function readPlan(goalsPath) {
3214
4138
  }
3215
4139
  }
3216
4140
 
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";
4141
+ // components/ulw-loop/src/stop-resume-hook.ts
4142
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
4143
+ import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
3220
4144
  var RESUME_CAP = 2;
3221
4145
  var CONTEXT_PRESSURE_MARKERS2 = [
3222
4146
  "context compacted",
@@ -3235,18 +4159,19 @@ function runStopResumeHook(input) {
3235
4159
  return "";
3236
4160
  if (boulderContinuationWillFire(payload.cwd, payload.session_id))
3237
4161
  return "";
3238
- const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3239
- const plan = readPlan2(join3(stateDir, "goals.json"));
4162
+ const scope = { sessionId: payload.session_id };
4163
+ const stateDir = ulwLoopDir(payload.cwd, scope);
4164
+ const plan = readPlan2(join4(stateDir, "goals.json"));
3240
4165
  if (plan === null || plan.aggregateCompletion?.status === "complete")
3241
4166
  return "";
3242
- const goal3 = resumableGoal(plan);
3243
- if (goal3 === undefined)
4167
+ const goal = resumableGoal(plan);
4168
+ if (goal === undefined)
3244
4169
  return "";
3245
- if (!consumeResumeBudget(stateDir, goal3.id))
4170
+ if (!consumeResumeBudgetLocked(ulwLoopStateLockPath(payload.cwd, scope), stateDir, goal.id))
3246
4171
  return "";
3247
4172
  const output = {
3248
4173
  decision: "block",
3249
- reason: renderResumeDirective(plan, goal3, payload.session_id)
4174
+ reason: renderResumeDirective(plan, goal, payload.session_id)
3250
4175
  };
3251
4176
  return JSON.stringify(output);
3252
4177
  }
@@ -3264,16 +4189,25 @@ async function runStopResumeHookCli(stdin, stdout) {
3264
4189
  }
3265
4190
  }
3266
4191
  function resumableGoal(plan) {
3267
- const active = plan.goals.find((goal3) => goal3.id === plan.activeGoalId);
4192
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3268
4193
  if (active !== undefined && isResumableStatus(active.status))
3269
4194
  return active;
3270
- return plan.goals.find((goal3) => isResumableStatus(goal3.status));
4195
+ return plan.goals.find((goal) => isResumableStatus(goal.status));
3271
4196
  }
3272
- function isResumableStatus(status2) {
3273
- return status2 === "pending" || status2 === "in_progress";
4197
+ function isResumableStatus(status) {
4198
+ return status === "pending" || status === "in_progress";
4199
+ }
4200
+ function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
4201
+ try {
4202
+ return withStateLockSync(lockPath, () => consumeResumeBudget(stateDir, goalId));
4203
+ } catch (error) {
4204
+ if (isStateLockTimeout(error))
4205
+ return false;
4206
+ throw error;
4207
+ }
3274
4208
  }
3275
4209
  function consumeResumeBudget(stateDir, goalId) {
3276
- const ledgerLineCount = countLedgerLines(join3(stateDir, "ledger.jsonl"));
4210
+ const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
3277
4211
  const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
3278
4212
  const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
3279
4213
  if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
@@ -3291,11 +4225,11 @@ function consumeResumeBudget(stateDir, goalId) {
3291
4225
  function isInsideDir(dir, candidate) {
3292
4226
  return candidate.startsWith(resolve4(dir) + sep2);
3293
4227
  }
3294
- function renderResumeDirective(plan, goal3, sessionId) {
4228
+ function renderResumeDirective(plan, goal, sessionId) {
3295
4229
  const normalized = normalizeUlwLoopSessionId(sessionId);
3296
4230
  const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
3297
4231
  return [
3298
- `The ulw-loop run in this session still has unfinished goals (next: ${goal3.id} — ${goal3.title}).`,
4232
+ `The ulw-loop run in this session still has unfinished goals (next: ${goal.id} — ${goal.title}).`,
3299
4233
  "The turn ended before the loop completed. Resume it now:",
3300
4234
  `1. Run \`omo-agent-toolkit ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
3301
4235
  "2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
@@ -3306,7 +4240,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
3306
4240
  }
3307
4241
  function readPlan2(goalsPath) {
3308
4242
  try {
3309
- return JSON.parse(readFileSync4(goalsPath, "utf8"));
4243
+ return JSON.parse(readFileSync6(goalsPath, "utf8"));
3310
4244
  } catch (error) {
3311
4245
  if (error instanceof Error)
3312
4246
  return null;
@@ -3315,7 +4249,7 @@ function readPlan2(goalsPath) {
3315
4249
  }
3316
4250
  function countLedgerLines(ledgerPath) {
3317
4251
  try {
3318
- return readFileSync4(ledgerPath, "utf8").split(`
4252
+ return readFileSync6(ledgerPath, "utf8").split(`
3319
4253
  `).filter(Boolean).length;
3320
4254
  } catch (error) {
3321
4255
  if (error instanceof Error)
@@ -3327,7 +4261,7 @@ function readCounter(counterPath) {
3327
4261
  try {
3328
4262
  if (!existsSync7(counterPath))
3329
4263
  return null;
3330
- const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
4264
+ const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
3331
4265
  if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
3332
4266
  return null;
3333
4267
  return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
@@ -3339,7 +4273,7 @@ function readCounter(counterPath) {
3339
4273
  }
3340
4274
  function boulderContinuationWillFire(cwd, sessionId) {
3341
4275
  try {
3342
- const raw = JSON.parse(readFileSync4(join3(cwd, ".omo", "boulder.json"), "utf8"));
4276
+ const raw = JSON.parse(readFileSync6(join4(cwd, ".omo", "boulder.json"), "utf8"));
3343
4277
  const works = raw["works"];
3344
4278
  const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
3345
4279
  return entries.some((work) => {
@@ -3358,7 +4292,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
3358
4292
  }
3359
4293
  function transcriptShowsContextPressure(transcriptPath) {
3360
4294
  try {
3361
- const transcript = readFileSync4(transcriptPath, "utf8").toLowerCase();
4295
+ const transcript = readFileSync6(transcriptPath, "utf8").toLowerCase();
3362
4296
  return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
3363
4297
  } catch (error) {
3364
4298
  if (error instanceof Error)
@@ -3370,12 +4304,12 @@ function boulderPlanHasChecklist(cwd, entry) {
3370
4304
  const activePlan = entry["active_plan"];
3371
4305
  if (typeof activePlan !== "string" || activePlan.trim().length === 0)
3372
4306
  return false;
3373
- const planPath = isAbsolute2(activePlan) ? activePlan : join3(cwd, activePlan);
4307
+ const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
3374
4308
  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];
4309
+ const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
3376
4310
  for (const candidate of candidates) {
3377
4311
  try {
3378
- return readFileSync4(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
4312
+ return readFileSync6(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
3379
4313
  } catch (error) {
3380
4314
  if (!(error instanceof Error))
3381
4315
  throw error;
@@ -3399,19 +4333,13 @@ function parseStopPayload(value) {
3399
4333
  };
3400
4334
  }
3401
4335
 
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
- `;
4336
+ // components/ulw-loop/src/cli.ts
3410
4337
  async function main() {
3411
4338
  const argv = process.argv.slice(2);
3412
4339
  const command = argv[0];
3413
4340
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
3414
- process.stdout.write(TOP_LEVEL_HELP);
4341
+ process.stdout.write(`${ULW_LOOP_HELP}
4342
+ `);
3415
4343
  return 0;
3416
4344
  }
3417
4345
  if (command === "ulw-loop")
@@ -3432,6 +4360,10 @@ async function main() {
3432
4360
  await runStopResumeHookCli(process.stdin, process.stdout);
3433
4361
  return 0;
3434
4362
  }
4363
+ if (sub === "post-tool-use-spawn") {
4364
+ await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
4365
+ return 0;
4366
+ }
3435
4367
  if (sub === "pre-tool-use-spawn") {
3436
4368
  await runSpawnGuardCli(process.stdin, process.stdout);
3437
4369
  return 0;
@@ -3443,7 +4375,8 @@ async function main() {
3443
4375
  if (isUlwLoopSubcommand(command))
3444
4376
  return ulwLoopCommand(argv);
3445
4377
  process.stderr.write(`[omo] unknown command: ${command}
3446
- ${TOP_LEVEL_HELP}`);
4378
+ ${ULW_LOOP_HELP}
4379
+ `);
3447
4380
  return 1;
3448
4381
  }
3449
4382
  main().then((code) => {