oh-my-opencode 5.0.0-beta.30 → 5.0.0-beta.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/dist/cli/index.js +178 -37
  2. package/dist/cli-node/index.js +178 -37
  3. package/dist/config/schema/background-task.d.ts +1 -0
  4. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
  5. package/dist/features/background-agent/constants.d.ts +0 -7
  6. package/dist/features/background-agent/manager.d.ts +2 -1
  7. package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
  8. package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
  9. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
  10. package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
  11. package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
  12. package/dist/features/monitor/manager-internals.d.ts +1 -1
  13. package/dist/features/monitor/output-injector-types.d.ts +1 -1
  14. package/dist/features/task-toast-manager/manager.d.ts +0 -24
  15. package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +1 -0
  16. package/dist/features/team-mode/tools/messaging-runtime.d.ts +1 -0
  17. package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
  18. package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
  19. package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
  20. package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
  21. package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
  22. package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
  23. package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
  24. package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
  25. package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
  26. package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
  27. package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
  28. package/dist/hooks/keyword-detector/hook.d.ts +7 -0
  29. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
  30. package/dist/hooks/model-fallback/hook.d.ts +1 -1
  31. package/dist/index.js +681 -273
  32. package/dist/oh-my-opencode.schema.json +5 -0
  33. package/dist/plugin-dispose.d.ts +3 -0
  34. package/dist/shared/agent-tool-restrictions.d.ts +0 -1
  35. package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  36. package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
  37. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
  38. package/dist/tools/delegate-task/types.d.ts +1 -0
  39. package/dist/tools/session-manager/directory-filter.d.ts +2 -0
  40. package/dist/tui.js +148 -9
  41. package/package.json +16 -15
  42. package/packages/git-bash-mcp/dist/cli.js +1 -0
  43. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +47 -19
  44. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  45. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
  46. package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
  47. package/packages/lsp-core/src/lsp/client.ts +6 -3
  48. package/packages/lsp-core/src/lsp/constants.ts +5 -0
  49. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
  50. package/packages/lsp-core/src/lsp/errors.ts +15 -0
  51. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
  52. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  53. package/packages/lsp-core/src/lsp/manager.ts +106 -13
  54. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  55. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  56. package/packages/lsp-core/src/lsp/transport.ts +7 -2
  57. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
  58. package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
  59. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
  60. package/packages/lsp-core/src/mcp.ts +3 -0
  61. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  62. package/packages/lsp-daemon/dist/cli.js +155 -34
  63. package/packages/lsp-daemon/dist/client.js +155 -34
  64. package/packages/lsp-daemon/dist/index.js +155 -34
  65. package/packages/lsp-daemon/dist/proxy.js +3 -0
  66. package/packages/lsp-tools-mcp/dist/cli.js +155 -34
  67. package/packages/lsp-tools-mcp/dist/lsp/manager.js +134 -28
  68. package/packages/lsp-tools-mcp/dist/mcp.js +155 -34
  69. package/packages/lsp-tools-mcp/dist/tools.js +154 -34
  70. package/packages/lsp-tools-mcp/package.json +1 -1
  71. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  72. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  73. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  74. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +17 -2
  75. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +17 -2
  76. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  77. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
  78. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
  79. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  80. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  81. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  82. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  83. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  84. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  85. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  86. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +152 -34
  87. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  88. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  89. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  90. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  91. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  92. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  93. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  94. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  95. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +5 -4
  96. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +489 -4
  97. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  98. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -2
  99. package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
  100. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
  101. package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
  102. package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
  103. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
  104. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  105. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  106. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +5 -2
  107. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -0
  108. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
  109. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +14 -0
  110. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +10 -1
  111. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
  112. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
  113. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +9 -5
  114. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
  115. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
  116. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -0
  117. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
  118. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +856 -347
  119. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  120. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +27 -6
  121. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
  122. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +14 -0
  123. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
  124. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +17 -2
  125. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +13 -0
  126. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +19 -0
  127. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +2 -0
  128. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +53 -0
  129. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +9 -0
  130. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +85 -0
  131. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +1 -1
  132. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  133. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +41 -102
  134. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
  135. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +185 -14
  136. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
  137. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
  138. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +18 -0
  139. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +65 -0
  140. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  141. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  142. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +17 -0
  143. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
  144. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +25 -1
  145. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +127 -0
  146. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +10 -12
  147. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +2 -1
  148. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -0
  149. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
  150. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +34 -5
  151. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +23 -0
  152. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
  153. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +18 -3
  154. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +27 -0
  155. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +57 -0
  156. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +103 -0
  157. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +1 -1
  158. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +54 -123
  159. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
  160. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +187 -12
  161. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
  162. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +95 -0
  163. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +107 -1
  164. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +92 -0
  165. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +183 -0
  166. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +35 -0
  167. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
  168. package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
  169. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +101 -0
  170. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +46 -2
  171. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
  172. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +5 -0
  173. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +318 -1
  174. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
  175. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +9 -3
  176. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  190. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  191. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  192. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  193. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  194. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  195. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  196. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  197. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  198. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  199. package/packages/omo-codex/plugin/package-lock.json +13 -13
  200. package/packages/omo-codex/plugin/package.json +1 -1
  201. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +31 -2
  202. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  203. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +17 -0
  204. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +11 -1
  205. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +13 -0
  206. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -2
  207. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  208. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  209. package/dist/features/builtin-skills/index.d.ts +0 -1
  210. package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
  211. package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
  212. package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
  213. package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
  214. package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
  215. package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
  216. package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
  217. package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
  218. package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
  219. package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
  220. package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
  221. package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
  222. package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
  223. package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
  224. package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
  225. package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
  226. package/dist/features/builtin-skills/skills/index.d.ts +0 -1
  227. package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
  228. package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
  229. package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
  230. package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
  231. package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
  232. package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
  233. package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
  234. package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
  235. package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
  236. package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
  237. package/dist/features/builtin-skills/skills.d.ts +0 -1
  238. package/dist/features/builtin-skills/types.d.ts +0 -1
  239. package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
  240. package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
  241. package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
  242. package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
  243. package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
  244. package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
  245. package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
  246. package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
  247. package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
  248. package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
  249. package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
  250. package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
  251. package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
  252. package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
  253. package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
  254. package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
  255. package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
  256. package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
  257. package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
  258. package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
  259. package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
  260. package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
  261. package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
  262. package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
  263. package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
  264. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
  265. package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
  266. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
  267. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
  268. package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
  269. package/dist/shared/prompt-async-gate/types.d.ts +0 -1
  270. package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
  271. package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
  272. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  273. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  274. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  275. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  276. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
  277. package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -483
  278. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +0 -490
@@ -1,17 +1,124 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
4
- import { existsSync as existsSync3, statSync } from "node:fs";
3
+ // components/ulw-loop/src/checkpoint.ts
4
+ import { existsSync as existsSync4, statSync } from "node:fs";
5
5
  import { readFile as readFile5 } from "node:fs/promises";
6
- import { resolve as resolve3 } from "node:path";
6
+ import { resolve as resolve4 } from "node:path";
7
+
8
+ // components/ulw-loop/src/checkpoint-reconciliation.ts
9
+ import { existsSync as existsSync2 } from "node:fs";
10
+ import { readFile as readFile2 } from "node:fs/promises";
7
11
 
8
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
12
+ // components/ulw-loop/src/codex-goal-snapshot.ts
9
13
  import { existsSync } from "node:fs";
10
14
  import { readFile } from "node:fs/promises";
15
+ import { resolve } from "node:path";
11
16
 
12
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
17
+ class CodexGoalSnapshotError extends Error {
18
+ }
19
+ function safeObject(value) {
20
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
21
+ }
22
+ function safeString(value) {
23
+ return typeof value === "string" ? value.trim() : "";
24
+ }
25
+ function normalizeStatus(value) {
26
+ const status = safeString(value).toLowerCase();
27
+ if (status === "complete" || status === "completed" || status === "done")
28
+ return "complete";
29
+ if (status === "cancelled" || status === "canceled")
30
+ return "cancelled";
31
+ if (status === "failed" || status === "failure")
32
+ return "failed";
33
+ if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
34
+ return "active";
35
+ return "unknown";
36
+ }
37
+ function normalizeObjective(value) {
38
+ return value.replace(/\s+/g, " ").trim();
39
+ }
40
+ function parseCodexGoalSnapshot(value) {
41
+ const root = safeObject(value);
42
+ const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
43
+ if (goalValue === null || goalValue === undefined || goalValue === false) {
44
+ return { available: false, raw: value };
45
+ }
46
+ const goal = safeObject(goalValue);
47
+ const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
48
+ const status = normalizeStatus(goal["status"] ?? root["status"]);
49
+ return {
50
+ available: Boolean(objective || status !== "unknown"),
51
+ ...objective ? { objective } : {},
52
+ status,
53
+ raw: value
54
+ };
55
+ }
56
+ async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
57
+ if (!raw?.trim())
58
+ return null;
59
+ const trimmed = raw.trim();
60
+ try {
61
+ return parseCodexGoalSnapshot(JSON.parse(trimmed));
62
+ } catch {
63
+ const path = resolve(cwd, trimmed);
64
+ if (!existsSync(path)) {
65
+ throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
66
+ }
67
+ try {
68
+ return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
69
+ } catch (error) {
70
+ throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
71
+ }
72
+ }
73
+ }
74
+ function reconcileCodexGoalSnapshot(snapshot, options) {
75
+ const effectiveSnapshot = snapshot ?? { available: false, raw: null };
76
+ const errors = [];
77
+ const warnings = [];
78
+ if (!effectiveSnapshot.available) {
79
+ const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
80
+ if (options.requireSnapshot)
81
+ errors.push(message);
82
+ else
83
+ warnings.push(message);
84
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
85
+ }
86
+ const expected = normalizeObjective(options.expectedObjective);
87
+ const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))].filter(Boolean));
88
+ const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
89
+ if (!actual) {
90
+ errors.push("Codex goal snapshot is missing objective text.");
91
+ } else if (!accepted.has(actual)) {
92
+ errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
93
+ }
94
+ const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
95
+ const actualStatus = effectiveSnapshot.status ?? "unknown";
96
+ if (!allowed.includes(actualStatus)) {
97
+ errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
98
+ }
99
+ if (options.requireComplete && actualStatus !== "complete") {
100
+ errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
101
+ }
102
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
103
+ }
104
+ function formatCodexGoalReconciliation(reconciliation) {
105
+ const parts = [...reconciliation.errors, ...reconciliation.warnings];
106
+ return parts.join(" ");
107
+ }
108
+ function codexGoalMismatchRecovery(expectedObjective, snapshot) {
109
+ const receivedObjective = snapshot?.objective ?? "";
110
+ const message = [
111
+ "Recovery: the Codex goal objective must equal the plan's codexObjective exactly — copy the expected value below into update_goal and re-run get_goal.",
112
+ `expected codexObjective: ${expectedObjective}`,
113
+ `received objective: ${receivedObjective || "(none)"}`
114
+ ].join(`
115
+ `);
116
+ return { message, details: { expectedObjective, receivedObjective } };
117
+ }
118
+
119
+ // components/ulw-loop/src/paths.ts
13
120
  import { isAbsolute, join, relative, sep } from "node:path";
14
- // packages/omo-codex/plugin/components/ulw-loop/src/constants.ts
121
+ // components/ulw-loop/src/constants.ts
15
122
  var ULW_LOOP_DIR = ".omo/ulw-loop";
16
123
  var ULW_LOOP_BRIEF = "brief.md";
17
124
  var ULW_LOOP_GOALS = "goals.json";
@@ -31,7 +138,7 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
31
138
  "regression",
32
139
  "adversarial"
33
140
  ];
34
- // packages/omo-codex/plugin/components/ulw-loop/src/runtime.ts
141
+ // components/ulw-loop/src/runtime.ts
35
142
  class UlwLoopError extends Error {
36
143
  constructor(message, code, opts) {
37
144
  super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
@@ -45,7 +152,7 @@ class UlwLoopError extends Error {
45
152
  function iso() {
46
153
  return new Date().toISOString();
47
154
  }
48
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
155
+ // components/ulw-loop/src/paths.ts
49
156
  var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
50
157
  function normalizeUlwLoopSessionId(sessionId) {
51
158
  const trimmed = sessionId?.trim();
@@ -111,7 +218,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
111
218
  return !pathApi.isAbsolute(relativePath);
112
219
  }
113
220
 
114
- // packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts
221
+ // components/ulw-loop/src/goal-status.ts
115
222
  var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
116
223
  function aggregateCodexObjectiveForScope(scope) {
117
224
  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.`;
@@ -194,8 +301,15 @@ function hasEssentialCriteriaPass(goal) {
194
301
  return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
195
302
  }
196
303
 
197
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
198
- function normalizeObjective(value) {
304
+ // components/ulw-loop/src/checkpoint-reconciliation.ts
305
+ function codexSnapshotMismatchError(input) {
306
+ const { reconciliation, snapshot, taskScopedHint } = input;
307
+ const hint = taskScopedHint?.aggregate === true && snapshot?.status === "complete" && snapshot.objective !== undefined ? buildTaskScopedAggregateReconciliationHint(taskScopedHint.goal, taskScopedHint.final) : "";
308
+ const recovery = codexGoalMismatchRecovery(input.expectedObjective, snapshot);
309
+ return new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${hint}
310
+ ${recovery.message}`, "ulw_loop_codex_snapshot_mismatch", { details: recovery.details });
311
+ }
312
+ function normalizeObjective2(value) {
199
313
  return value.replace(/\s+/g, " ").trim();
200
314
  }
201
315
  function textMentionsUlwLoopPlanArtifact(value) {
@@ -212,13 +326,13 @@ function textHasCompletionValidationEvidence(value) {
212
326
  return done && verified;
213
327
  }
214
328
  async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope) {
215
- const actual = normalizeObjective(snapshotObjective).toLowerCase();
329
+ const actual = normalizeObjective2(snapshotObjective).toLowerCase();
216
330
  if (textMentionsUlwLoopPlanArtifact(actual))
217
331
  return true;
218
- if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot, scope)))
332
+ if (actual.length < 24 || !existsSync2(ulwLoopBriefPath(repoRoot, scope)))
219
333
  return false;
220
334
  try {
221
- const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
335
+ const brief = normalizeObjective2(await readFile2(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
222
336
  return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
223
337
  } catch (error) {
224
338
  if (error instanceof Error)
@@ -265,107 +379,32 @@ function buildTaskScopedAggregateReconciliationHint(goal, final) {
265
379
  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)}`;
266
380
  }
267
381
 
268
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts
269
- import { existsSync as existsSync2 } from "node:fs";
270
- import { readFile as readFile2 } from "node:fs/promises";
271
- import { resolve } from "node:path";
382
+ // components/ulw-loop/src/plan-io.ts
383
+ import { createReadStream, readdirSync } from "node:fs";
384
+ import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
385
+ import { createInterface } from "node:readline";
272
386
 
273
- class CodexGoalSnapshotError extends Error {
274
- }
275
- function safeObject(value) {
276
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
277
- }
278
- function safeString(value) {
279
- return typeof value === "string" ? value.trim() : "";
280
- }
281
- function normalizeStatus(value) {
282
- const status = safeString(value).toLowerCase();
283
- if (status === "complete" || status === "completed" || status === "done")
284
- return "complete";
285
- if (status === "cancelled" || status === "canceled")
286
- return "cancelled";
287
- if (status === "failed" || status === "failure")
288
- return "failed";
289
- if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
290
- return "active";
291
- return "unknown";
292
- }
293
- function normalizeObjective2(value) {
294
- return value.replace(/\s+/g, " ").trim();
295
- }
296
- function parseCodexGoalSnapshot(value) {
297
- const root = safeObject(value);
298
- const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
299
- if (goalValue === null || goalValue === undefined || goalValue === false) {
300
- return { available: false, raw: value };
301
- }
302
- const goal = safeObject(goalValue);
303
- const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
304
- const status = normalizeStatus(goal["status"] ?? root["status"]);
305
- return {
306
- available: Boolean(objective || status !== "unknown"),
307
- ...objective ? { objective } : {},
308
- status,
309
- raw: value
310
- };
311
- }
312
- async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
313
- if (!raw?.trim())
314
- return null;
315
- const trimmed = raw.trim();
316
- try {
317
- return parseCodexGoalSnapshot(JSON.parse(trimmed));
318
- } catch {
319
- const path = resolve(cwd, trimmed);
320
- if (!existsSync2(path)) {
321
- throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
322
- }
323
- try {
324
- return parseCodexGoalSnapshot(JSON.parse(await readFile2(path, "utf-8")));
325
- } catch (error) {
326
- throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
327
- }
328
- }
329
- }
330
- function reconcileCodexGoalSnapshot(snapshot, options) {
331
- const effectiveSnapshot = snapshot ?? { available: false, raw: null };
332
- const errors = [];
333
- const warnings = [];
334
- if (!effectiveSnapshot.available) {
335
- const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
336
- if (options.requireSnapshot)
337
- errors.push(message);
338
- else
339
- warnings.push(message);
340
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
341
- }
342
- const expected = normalizeObjective2(options.expectedObjective);
343
- const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective2(objective))].filter(Boolean));
344
- const actual = normalizeObjective2(effectiveSnapshot.objective ?? "");
345
- if (!actual) {
346
- errors.push("Codex goal snapshot is missing objective text.");
347
- } else if (!accepted.has(actual)) {
348
- errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
349
- }
350
- const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
351
- const actualStatus = effectiveSnapshot.status ?? "unknown";
352
- if (!allowed.includes(actualStatus)) {
353
- errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
354
- }
355
- if (options.requireComplete && actualStatus !== "complete") {
356
- 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.');
357
- }
358
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
359
- }
360
- function formatCodexGoalReconciliation(reconciliation) {
361
- const parts = [...reconciliation.errors, ...reconciliation.warnings];
362
- return parts.join(" ");
387
+ // components/ulw-loop/src/plan-missing-recovery.ts
388
+ var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
389
+ function planMissingRecovery(existingSessionIds) {
390
+ const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
391
+ if (existingSessionIds.length === 0)
392
+ return { message: lines.join(`
393
+ `) };
394
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
395
+ return { message: lines.join(`
396
+ `), details: { existingSessionIds } };
397
+ }
398
+ function sessionIdRequiredMessage(flag) {
399
+ return [
400
+ `${flag} requires a non-empty value.`,
401
+ "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),",
402
+ `so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
403
+ ].join(`
404
+ `);
363
405
  }
364
406
 
365
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts
366
- import { createReadStream } from "node:fs";
367
- import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
368
- import { createInterface } from "node:readline";
407
+ // components/ulw-loop/src/plan-io.ts
369
408
  var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
370
409
  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.`;
371
410
  var locks = new Map;
@@ -406,7 +445,9 @@ async function readUlwLoopPlan(repoRoot, scope) {
406
445
  } catch (error) {
407
446
  if (!hasCode(error, "ENOENT"))
408
447
  throw error;
409
- 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 });
448
+ const recovery = planMissingRecovery(readSessionDirs(repoRoot));
449
+ throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.
450
+ ${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...recovery.details === undefined ? {} : { details: recovery.details } });
410
451
  }
411
452
  const parsed = JSON.parse(raw);
412
453
  if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
@@ -429,6 +470,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
429
470
  }
430
471
  return parsed;
431
472
  }
473
+ function readSessionDirs(repoRoot) {
474
+ try {
475
+ return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
476
+ } catch {
477
+ return [];
478
+ }
479
+ }
432
480
  async function writePlan(repoRoot, plan, scope) {
433
481
  await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
434
482
  const path = ulwLoopGoalsPath(repoRoot, scope);
@@ -480,7 +528,7 @@ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
480
528
  return;
481
529
  }
482
530
 
483
- // packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts
531
+ // components/ulw-loop/src/evidence.ts
484
532
  function ulwLoopFail(message, code, details) {
485
533
  throw new UlwLoopError(message, code, { details });
486
534
  }
@@ -586,11 +634,11 @@ function requireEssentialCriteriaPass(goal) {
586
634
  });
587
635
  }
588
636
 
589
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
637
+ // components/ulw-loop/src/quality-gate-aggregate.ts
590
638
  import { resolve as resolve2 } from "node:path";
591
639
 
592
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts
593
- var PLACEHOLDER_PATTERN = /^(?:placeholder|todo|tbd|n\/a|stub)$/i;
640
+ // components/ulw-loop/src/quality-gate-fields.ts
641
+ var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
594
642
  function invalid(message, field) {
595
643
  throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
596
644
  }
@@ -627,81 +675,61 @@ function literal(value, expected, field) {
627
675
  invalid(`${field} must be ${String(expected)}.`, field);
628
676
  }
629
677
 
630
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts
631
- function passedVerdict(value, field) {
632
- if (value === "not_applicable")
633
- invalid(`${field} must not be not_applicable.`, field);
634
- return literal(value, "passed", field);
678
+ // components/ulw-loop/src/quality-gate-aggregate.ts
679
+ var PLACEHOLDER = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
680
+ function add(defects, field, message) {
681
+ defects.push({ field, message });
635
682
  }
636
- function codeQualityStatusField(value, field) {
637
- if (value === "CLEAR" || value === "WATCH")
638
- return value;
639
- invalid(`${field} must be CLEAR or WATCH.`, field);
683
+ function text(value, field, defects) {
684
+ if (typeof value !== "string" || value.trim() === "")
685
+ add(defects, field, `Final quality gate requires non-empty ${field}.`);
686
+ else if (PLACEHOLDER.test(value.trim()))
687
+ add(defects, field, `Final quality gate rejects placeholder ${field}.`);
640
688
  }
641
- function adversarialVerdict(row, field) {
642
- const value = row["verdict"];
643
- if (value === "passed")
644
- return { verdict: "passed" };
645
- if (value === "not_applicable") {
646
- return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
689
+ function section2(value, field, defects) {
690
+ if (!isRecord(value)) {
691
+ add(defects, field, `Final quality gate is missing ${field} evidence.`);
692
+ return {};
647
693
  }
648
- invalid(`${field} must be passed or not_applicable with a reason.`, field);
649
- }
650
-
651
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-blockers.ts
652
- var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
653
- var URL_PATTERN = /https?:\/\/\S+/g;
654
- var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
655
- var WHITESPACE_PATTERN = /\s+/g;
656
- var AUTH_PATTERN = /\b(auth\w*|credential\w*|token|permission\w*|scope\w*|access|unauthorized|forbidden|401|403)\b/;
657
- var MISSING_PATTERN = /\b(unset|missing|required|requires|without|omit\w*|not set|not available|no read packages|read packages)\b/;
658
- var GHCR_PATTERN = /\b(ghcr|github container registry|read packages|imagepullsecret|package api|anonymous|container image)\b/;
659
- var GHCR_401_PATTERN = /\b(401|unauthorized|anonymous pull|authentication required)\b/;
660
- var GHCR_403_PATTERN = /\b(403|forbidden|read packages|package api)\b/;
661
- function isRecord2(value) {
662
- return typeof value === "object" && value !== null && !Array.isArray(value);
663
- }
664
- function normalizeBlockerEvidence(evidence) {
665
- const withoutUrls = evidence.toLowerCase().replace(URL_PATTERN, " ");
666
- const withoutPunctuation = withoutUrls.replace(PUNCTUATION_PATTERN, " ");
667
- return withoutPunctuation.replace(WHITESPACE_PATTERN, " ").trim();
668
- }
669
- function classifyExternalAuthorizationBlocker(evidence) {
670
- const normalized = normalizeBlockerEvidence(evidence);
671
- if (!normalized || !AUTH_PATTERN.test(normalized) || !MISSING_PATTERN.test(normalized))
672
- return null;
673
- if (!GHCR_PATTERN.test(normalized))
674
- return "EXTERNAL_AUTHORIZATION_REQUIRED";
675
- const status401 = GHCR_401_PATTERN.test(normalized) ? "HTTP_401_ANONYMOUS" : null;
676
- const status403 = GHCR_403_PATTERN.test(normalized) ? "HTTP_403_NO_READ_PACKAGES" : null;
677
- const status = [status401, status403].filter((part) => part !== null).join("+");
678
- return `GHCR_PULL_ACCESS:${status || "AUTHORIZATION_REQUIRED"}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`;
694
+ return value;
679
695
  }
680
- function nestedBlockerSignature(goal) {
681
- const blocker = Reflect.get(goal, "blocker");
682
- const signature = isRecord2(blocker) ? blocker["signature"] : null;
683
- return typeof signature === "string" ? signature : null;
684
- }
685
- function sameBlockerOccurrences(plan, signature) {
686
- return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature).length;
687
- }
688
- function clearGoalBlockerFields(goal) {
689
- for (const key of BLOCKER_FIELD_KEYS)
690
- Reflect.deleteProperty(goal, key);
696
+ function aggregateQualityGateDefects(input, opts) {
697
+ if (!isRecord(input))
698
+ return;
699
+ const defects = [];
700
+ const gate = input;
701
+ const manual = section2(gate["manualQa"], "manualQa", defects);
702
+ const review = section2(gate["gateReview"], "gateReview", defects);
703
+ text(manual["evidence"], "manualQa.evidence", defects);
704
+ text(review["evidence"], "gateReview.evidence", defects);
705
+ if (review["recommendation"] !== "APPROVE")
706
+ add(defects, "gateReview.recommendation", "gateReview.recommendation must be APPROVE.");
707
+ const artifacts = Array.isArray(manual["artifactRefs"]) ? manual["artifactRefs"] : [];
708
+ for (const [index, item] of artifacts.entries()) {
709
+ if (!isRecord(item))
710
+ continue;
711
+ const path = item["path"];
712
+ if (typeof path !== "string" || path.trim() === "")
713
+ continue;
714
+ if (opts?.repoRoot !== undefined && opts.fs !== undefined && !opts.fs.existsSync(resolve2(opts.repoRoot, path)))
715
+ add(defects, `manualQa.artifactRefs[${index}].path`, `manualQa.artifactRefs[${index}].path must point to an existing artifact.`);
716
+ }
717
+ if (defects.length === 0)
718
+ return;
719
+ const truncated = defects.length > 25;
720
+ const fields = defects.slice(0, 25).map(({ field, message: message2 }) => ({ field, message: message2 }));
721
+ const message = [
722
+ `Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
723
+ ...fields.map((item) => `- ${item.field}: ${item.message}`)
724
+ ].join(`
725
+ `);
726
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
727
+ details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
728
+ });
691
729
  }
692
730
 
693
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
694
- var REVIEWER_ROLES = {
695
- codeReview: "lazycodex-code-reviewer",
696
- manualQa: "lazycodex-qa-executor",
697
- gateReview: "lazycodex-gate-reviewer"
698
- };
699
- function reviewerRoleField(value, expected, field) {
700
- const actual = textField(value, field);
701
- if (actual !== expected)
702
- invalid(`${field} must be ${expected}.`, field);
703
- return expected;
704
- }
731
+ // components/ulw-loop/src/quality-gate-artifacts.ts
732
+ import { resolve as resolve3 } from "node:path";
705
733
  function surfaceField(value, field) {
706
734
  if (value === "cli" || value === "http" || value === "tmux" || value === "browser" || value === "gui" || value === "data")
707
735
  return value;
@@ -729,15 +757,15 @@ function artifactCompatible(surface, kind) {
729
757
  }
730
758
  }
731
759
  function checkFile(path, field, opts) {
732
- if (opts === undefined)
760
+ if (opts?.repoRoot === undefined || opts.fs === undefined)
733
761
  return;
734
- const absolute = resolve2(opts.repoRoot, path);
762
+ const absolute = resolve3(opts.repoRoot, path);
735
763
  if (!opts.fs.existsSync(absolute))
736
764
  invalid(`${field} must point to an existing artifact.`, field);
737
765
  if (opts.fs.statSync(absolute).size <= 0)
738
766
  invalid(`${field} must point to a non-empty artifact.`, field);
739
- if (opts.currentAttemptDir !== undefined) {
740
- const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
767
+ if (opts.currentAttemptDir !== undefined && opts.repoRoot !== undefined) {
768
+ const attemptRoot = resolve3(opts.repoRoot, opts.currentAttemptDir);
741
769
  if (!isWithinAttemptDir(absolute, attemptRoot))
742
770
  invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
743
771
  }
@@ -774,13 +802,164 @@ function referencedArtifacts(value, field, byId) {
774
802
  return artifact;
775
803
  });
776
804
  }
805
+
806
+ // components/ulw-loop/src/quality-gate-verdicts.ts
807
+ function passedVerdict(value, field) {
808
+ if (value === "not_applicable")
809
+ invalid(`${field} must not be not_applicable.`, field);
810
+ return literal(value, "passed", field);
811
+ }
812
+ function codeQualityStatusField(value, field) {
813
+ if (value === "CLEAR" || value === "WATCH")
814
+ return value;
815
+ invalid(`${field} must be CLEAR or WATCH.`, field);
816
+ }
817
+ function adversarialVerdict(row, field) {
818
+ const value = row["verdict"];
819
+ if (value === "passed")
820
+ return { verdict: "passed" };
821
+ if (value === "not_applicable") {
822
+ return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
823
+ }
824
+ invalid(`${field} must be passed or not_applicable with a reason.`, field);
825
+ }
826
+
827
+ // components/ulw-loop/src/surface.ts
828
+ import { existsSync as existsSync3, readFileSync } from "node:fs";
829
+ import { dirname, join as join2 } from "node:path";
830
+ import { fileURLToPath } from "node:url";
831
+ var REVIEWER_ROLES_BY_SURFACE = {
832
+ lazycodex: {
833
+ codeReview: "lazycodex-code-reviewer",
834
+ manualQa: "lazycodex-qa-executor",
835
+ gateReview: "lazycodex-gate-reviewer"
836
+ },
837
+ "omo-senpi": {
838
+ codeReview: "omo-senpi-code-reviewer",
839
+ manualQa: "omo-senpi-qa-executor",
840
+ gateReview: "omo-senpi-gate-reviewer"
841
+ }
842
+ };
843
+ var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
844
+ var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
845
+ lazycodex: ["codeReview", "manualQa", "gateReview", "iteration", "criteriaCoverage"],
846
+ "omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
847
+ };
848
+ var GATE_SECTION_BY_ACCEPTOR = {
849
+ lazycodex: {
850
+ codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview],
851
+ manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa],
852
+ gateReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview]
853
+ },
854
+ "omo-senpi": {
855
+ manualQa: ["main-session"],
856
+ gateReview: ["category:deep", "category:unspecified-high", "category:unspecified-low"]
857
+ }
858
+ };
859
+ function reviewerRolesFor(surface) {
860
+ return REVIEWER_ROLES_BY_SURFACE[surface];
861
+ }
862
+ var SURFACE_MARKER_FILENAME = "surface.json";
863
+ var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
864
+ function parseSurface(value) {
865
+ return value === "lazycodex" || value === "omo-senpi" ? value : null;
866
+ }
867
+ function resolveToolkitSurface(options) {
868
+ const env = options?.env ?? process.env;
869
+ const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
870
+ if (fromEnv !== null)
871
+ return fromEnv;
872
+ const entryDir = options?.entryDir ?? dirname(fileURLToPath(import.meta.url));
873
+ const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
874
+ return readMarkerSurface(markerPath) ?? "lazycodex";
875
+ }
876
+ function readMarkerSurface(markerPath) {
877
+ try {
878
+ if (!existsSync3(markerPath))
879
+ return null;
880
+ const parsed = JSON.parse(readFileSync(markerPath, "utf8"));
881
+ return parseSurface(parsed["surface"]);
882
+ } catch (error) {
883
+ if (error instanceof Error)
884
+ return null;
885
+ throw error;
886
+ }
887
+ }
888
+
889
+ // components/ulw-loop/src/quality-gate-blockers.ts
890
+ var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
891
+ var URL_PATTERN = /https?:\/\/\S+/g;
892
+ var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
893
+ var WHITESPACE_PATTERN = /\s+/g;
894
+ var AUTH_PATTERN = /\b(auth\w*|credential\w*|token|permission\w*|scope\w*|access|unauthorized|forbidden|401|403)\b/;
895
+ var MISSING_PATTERN = /\b(unset|missing|required|requires|without|omit\w*|not set|not available|no read packages|read packages)\b/;
896
+ var GHCR_PATTERN = /\b(ghcr|github container registry|read packages|imagepullsecret|package api|anonymous|container image)\b/;
897
+ var GHCR_401_PATTERN = /\b(401|unauthorized|anonymous pull|authentication required)\b/;
898
+ var GHCR_403_PATTERN = /\b(403|forbidden|read packages|package api)\b/;
899
+ function isRecord2(value) {
900
+ return typeof value === "object" && value !== null && !Array.isArray(value);
901
+ }
902
+ function normalizeBlockerEvidence(evidence) {
903
+ const withoutUrls = evidence.toLowerCase().replace(URL_PATTERN, " ");
904
+ const withoutPunctuation = withoutUrls.replace(PUNCTUATION_PATTERN, " ");
905
+ return withoutPunctuation.replace(WHITESPACE_PATTERN, " ").trim();
906
+ }
907
+ function classifyExternalAuthorizationBlocker(evidence) {
908
+ const normalized = normalizeBlockerEvidence(evidence);
909
+ if (!normalized || !AUTH_PATTERN.test(normalized) || !MISSING_PATTERN.test(normalized))
910
+ return null;
911
+ if (!GHCR_PATTERN.test(normalized))
912
+ return "EXTERNAL_AUTHORIZATION_REQUIRED";
913
+ const status401 = GHCR_401_PATTERN.test(normalized) ? "HTTP_401_ANONYMOUS" : null;
914
+ const status403 = GHCR_403_PATTERN.test(normalized) ? "HTTP_403_NO_READ_PACKAGES" : null;
915
+ const status = [status401, status403].filter((part) => part !== null).join("+");
916
+ return `GHCR_PULL_ACCESS:${status || "AUTHORIZATION_REQUIRED"}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`;
917
+ }
918
+ function nestedBlockerSignature(goal) {
919
+ const blocker = Reflect.get(goal, "blocker");
920
+ const signature = isRecord2(blocker) ? blocker["signature"] : null;
921
+ return typeof signature === "string" ? signature : null;
922
+ }
923
+ function sameBlockerOccurrences(plan, signature) {
924
+ return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature).length;
925
+ }
926
+ function clearGoalBlockerFields(goal) {
927
+ for (const key of BLOCKER_FIELD_KEYS)
928
+ Reflect.deleteProperty(goal, key);
929
+ }
930
+
931
+ // components/ulw-loop/src/quality-gate.ts
932
+ function reviewerRoleField(value, expected, field) {
933
+ const actual = textField(value, field);
934
+ if (actual !== expected)
935
+ invalid(`${field} must be ${expected}.`, field);
936
+ return expected;
937
+ }
938
+ function reviewerAcceptorField(value, surface, sectionName) {
939
+ const field = `${sectionName}.by`;
940
+ const actual = textField(value, field);
941
+ const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
942
+ if (accepted === undefined || !accepted.includes(actual))
943
+ invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
944
+ return actual;
945
+ }
777
946
  function validateQualityGate(input, opts) {
947
+ const surface = opts?.reviewerSurface ?? "lazycodex";
948
+ aggregateQualityGateDefects(input, opts);
778
949
  const gate = section(input, "qualityGate");
779
- const codeReview = section(gate["codeReview"], "codeReview");
950
+ const requiredSections = REQUIRED_GATE_SECTIONS_BY_SURFACE[surface];
951
+ for (const sectionName of requiredSections)
952
+ section(gate[sectionName], sectionName);
953
+ if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
954
+ invalid("omo-senpi gate has no codeReview lane.", "codeReview");
780
955
  const manualQa = section(gate["manualQa"], "manualQa");
781
956
  const gateReview = section(gate["gateReview"], "gateReview");
782
957
  const iteration = section(gate["iteration"], "iteration");
783
958
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
959
+ const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
960
+ const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
961
+ if (surface === "lazycodex")
962
+ reviewerRoleField(section(gate["codeReview"], "codeReview")["by"], "lazycodex-code-reviewer", "codeReview.by");
784
963
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
785
964
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
786
965
  if (passCount < totalCriteria)
@@ -789,21 +968,11 @@ function validateQualityGate(input, opts) {
789
968
  const byId = artifactMap(artifactRefs);
790
969
  const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
791
970
  const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
792
- const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
793
971
  const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
794
- checkFile(codeReportPath, "codeReview.reportPath", opts);
795
972
  checkFile(gateReportPath, "gateReview.reportPath", opts);
796
- return {
797
- codeReview: {
798
- by: reviewerRoleField(codeReview["by"], REVIEWER_ROLES.codeReview, "codeReview.by"),
799
- recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
800
- codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
801
- reportPath: codeReportPath,
802
- evidence: textField(codeReview["evidence"], "codeReview.evidence"),
803
- blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
804
- },
973
+ const common = {
805
974
  manualQa: {
806
- by: reviewerRoleField(manualQa["by"], REVIEWER_ROLES.manualQa, "manualQa.by"),
975
+ by: manualQaBy,
807
976
  status: literal(manualQa["status"], "passed", "manualQa.status"),
808
977
  evidence: textField(manualQa["evidence"], "manualQa.evidence"),
809
978
  surfaceEvidence,
@@ -811,7 +980,7 @@ function validateQualityGate(input, opts) {
811
980
  artifactRefs
812
981
  },
813
982
  gateReview: {
814
- by: reviewerRoleField(gateReview["by"], REVIEWER_ROLES.gateReview, "gateReview.by"),
983
+ by: gateReviewBy,
815
984
  recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
816
985
  reportPath: gateReportPath,
817
986
  evidence: textField(gateReview["evidence"], "gateReview.evidence"),
@@ -832,6 +1001,23 @@ function validateQualityGate(input, opts) {
832
1001
  adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
833
1002
  }
834
1003
  };
1004
+ if (surface === "omo-senpi")
1005
+ return { surface, ...common };
1006
+ const codeReview = section(gate["codeReview"], "codeReview");
1007
+ const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
1008
+ checkFile(codeReportPath, "codeReview.reportPath", opts);
1009
+ return {
1010
+ surface,
1011
+ ...common,
1012
+ codeReview: {
1013
+ by: "lazycodex-code-reviewer",
1014
+ recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
1015
+ codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
1016
+ reportPath: codeReportPath,
1017
+ evidence: textField(codeReview["evidence"], "codeReview.evidence"),
1018
+ blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
1019
+ }
1020
+ };
835
1021
  }
836
1022
  function parseSurfaceEvidence(value, byId) {
837
1023
  if (!Array.isArray(value) || value.length === 0)
@@ -874,7 +1060,7 @@ function parseAdversarialCases(value, byId) {
874
1060
  });
875
1061
  }
876
1062
 
877
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts
1063
+ // components/ulw-loop/src/cli-arg-parser.ts
878
1064
  import { readFile as readFile4 } from "node:fs/promises";
879
1065
  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(" "));
880
1066
  var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
@@ -965,14 +1151,14 @@ function parseRecordEvidenceArgs(argv) {
965
1151
  return notes ? { ...result, notes } : result;
966
1152
  }
967
1153
 
968
- // packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts
1154
+ // components/ulw-loop/src/validation-batch.ts
969
1155
  function isObject(value) {
970
1156
  return typeof value === "object" && value !== null && !Array.isArray(value);
971
1157
  }
972
1158
  function read(value, key) {
973
1159
  return Object.entries(value).find(([name]) => name === key)?.[1];
974
1160
  }
975
- function text(value, key) {
1161
+ function text2(value, key) {
976
1162
  const candidate = read(value, key);
977
1163
  return typeof candidate === "string" && candidate.trim().length > 0 ? candidate.trim() : undefined;
978
1164
  }
@@ -993,9 +1179,9 @@ async function parseValidationBatches(input, goals) {
993
1179
  function batchFromObject(value) {
994
1180
  if (!isObject(value))
995
1181
  fail("validation batch entries must be objects.");
996
- const batchId = text(value, "batchId");
1182
+ const batchId = text2(value, "batchId");
997
1183
  const memberIds = strings(value, "memberIds");
998
- const finalGoalId = text(value, "finalGoalId");
1184
+ const finalGoalId = text2(value, "finalGoalId");
999
1185
  if (batchId === undefined)
1000
1186
  fail("validation batch requires batchId.");
1001
1187
  if (memberIds === undefined || memberIds.length < 2)
@@ -1083,8 +1269,8 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
1083
1269
  throw new UlwLoopError(message, code);
1084
1270
  }
1085
1271
 
1086
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
1087
- var QUALITY_GATE_FS = { existsSync: existsSync3, statSync };
1272
+ // components/ulw-loop/src/checkpoint.ts
1273
+ var QUALITY_GATE_FS = { existsSync: existsSync4, statSync };
1088
1274
  function ulwLoopFail2(message, code) {
1089
1275
  throw new UlwLoopError(message, code);
1090
1276
  }
@@ -1109,8 +1295,8 @@ async function readJsonInput2(raw, repoRoot) {
1109
1295
  if (!(error instanceof SyntaxError))
1110
1296
  throw error;
1111
1297
  }
1112
- const path = resolve3(repoRoot, trimmed);
1113
- if (!existsSync3(path))
1298
+ const path = resolve4(repoRoot, trimmed);
1299
+ if (!existsSync4(path))
1114
1300
  return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
1115
1301
  try {
1116
1302
  return JSON.parse(await readFile5(path, "utf8"));
@@ -1153,13 +1339,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
1153
1339
  return status === "blocked" ? "goal_blocked" : "goal_failed";
1154
1340
  }
1155
1341
  function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
1156
- const watch = qualityGate?.codeReview.codeQualityStatus === "WATCH";
1342
+ const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview.codeQualityStatus === "WATCH";
1157
1343
  const entry = {
1158
1344
  at: now,
1159
1345
  kind: ledgerKind2(args.status, goal, aggregateCompletion),
1160
1346
  goalId: goal.id,
1161
1347
  status: goal.status,
1162
- evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
1348
+ evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
1163
1349
  };
1164
1350
  if (codexGoal !== undefined)
1165
1351
  entry.codexGoal = codexGoal;
@@ -1210,7 +1396,7 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1210
1396
  const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence, scope);
1211
1397
  const taskScoped = completedTaskScoped || activeFinalTaskScoped;
1212
1398
  if (!taskScoped)
1213
- throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
1399
+ throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expectedCodexObjective(plan, goal), taskScopedHint: { goal, aggregate, final } });
1214
1400
  }
1215
1401
  if (closesBatch)
1216
1402
  requireBatchFinalReady(plan, goal);
@@ -1222,6 +1408,7 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1222
1408
  qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1223
1409
  repoRoot,
1224
1410
  fs: QUALITY_GATE_FS,
1411
+ reviewerSurface: resolveToolkitSurface(),
1225
1412
  ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1226
1413
  });
1227
1414
  requireBatchGate(plan, goal, qualityGate);
@@ -1250,7 +1437,116 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1250
1437
  });
1251
1438
  }
1252
1439
 
1253
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts
1440
+ // components/ulw-loop/src/checkpoint-template.ts
1441
+ function artifactPath(base, name) {
1442
+ return `${base}/${name}`;
1443
+ }
1444
+ function gateTemplate(surface, base) {
1445
+ const artifacts = [
1446
+ {
1447
+ id: "artifact-cli",
1448
+ kind: "cli-transcript",
1449
+ description: "<replace:artifact description>",
1450
+ path: artifactPath(base, "cli-transcript.txt")
1451
+ },
1452
+ {
1453
+ id: "artifact-data",
1454
+ kind: "data-diff",
1455
+ description: "<replace:artifact description>",
1456
+ path: artifactPath(base, "data-diff.txt")
1457
+ }
1458
+ ];
1459
+ const manualQa = {
1460
+ by: surface === "omo-senpi" ? "main-session" : "lazycodex-qa-executor",
1461
+ status: "passed",
1462
+ evidence: "<replace:manual QA evidence>",
1463
+ surfaceEvidence: [
1464
+ {
1465
+ id: "surface-cli",
1466
+ criterionRef: "<replace:criterion id>",
1467
+ surface: "cli",
1468
+ invocation: "<replace:command>",
1469
+ verdict: "passed",
1470
+ artifactRefs: ["artifact-cli"]
1471
+ },
1472
+ {
1473
+ id: "surface-data",
1474
+ criterionRef: "<replace:criterion id>",
1475
+ surface: "data",
1476
+ invocation: "<replace:command>",
1477
+ verdict: "passed",
1478
+ artifactRefs: ["artifact-data"]
1479
+ }
1480
+ ],
1481
+ adversarialCases: [
1482
+ {
1483
+ id: "<replace:adversarial case id>",
1484
+ criterionRef: "<replace:criterion id>",
1485
+ scenario: "<replace:scenario>",
1486
+ expectedBehavior: "<replace:expected behavior>",
1487
+ verdict: "not_applicable",
1488
+ reason: "<replace:reason>",
1489
+ artifactRefs: ["artifact-cli"]
1490
+ }
1491
+ ],
1492
+ artifactRefs: artifacts
1493
+ };
1494
+ const common = {
1495
+ manualQa,
1496
+ gateReview: {
1497
+ by: surface === "omo-senpi" ? "category:deep" : "lazycodex-gate-reviewer",
1498
+ recommendation: "APPROVE",
1499
+ reportPath: artifactPath(base, "gate-review.md"),
1500
+ evidence: "<replace:gate review evidence>",
1501
+ blockers: []
1502
+ },
1503
+ iteration: {
1504
+ fullRerun: true,
1505
+ status: "passed",
1506
+ rerunCommands: ["<replace:verification command>"],
1507
+ evidence: "<replace:iteration evidence>"
1508
+ },
1509
+ criteriaCoverage: {
1510
+ totalCriteria: 0,
1511
+ passCount: 0,
1512
+ originalIntent: "<replace:original intent>",
1513
+ desiredOutcome: "<replace:desired outcome>",
1514
+ userOutcomeReview: "<replace:user outcome review>",
1515
+ adversarialClassesCovered: ["<replace:adversarial class>"]
1516
+ }
1517
+ };
1518
+ if (surface === "omo-senpi")
1519
+ return common;
1520
+ return {
1521
+ codeReview: {
1522
+ by: "lazycodex-code-reviewer",
1523
+ recommendation: "APPROVE",
1524
+ codeQualityStatus: "CLEAR",
1525
+ reportPath: artifactPath(base, "code-review.md"),
1526
+ evidence: "<replace:code review evidence>",
1527
+ blockers: []
1528
+ },
1529
+ ...common
1530
+ };
1531
+ }
1532
+ async function checkpointTemplate(repoRoot, scope, goalId) {
1533
+ const plan = await readUlwLoopPlan(repoRoot, scope);
1534
+ const targetId = goalId ?? plan.activeGoalId;
1535
+ const active = plan.goals.find((goal) => goal.id === targetId);
1536
+ if (goalId !== undefined && active === undefined)
1537
+ throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
1538
+ const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
1539
+ const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
1540
+ return {
1541
+ qualityGateTemplate: gateTemplate(resolveToolkitSurface(), attemptDir),
1542
+ codexGoalTemplate: {
1543
+ goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
1544
+ },
1545
+ ...hasAttempt ? { attemptDir } : { guidance: "This plan is evidence-layout v1; artifacts go under .omo/evidence/." }
1546
+ };
1547
+ }
1548
+
1549
+ // components/ulw-loop/src/cli-output.ts
1254
1550
  var ULW_LOOP_HELP = `Usage:
1255
1551
  omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
1256
1552
  omo-agent-toolkit help | --help | -h (this message)
@@ -1260,6 +1556,7 @@ var ULW_LOOP_HELP = `Usage:
1260
1556
  omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
1261
1557
  omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
1262
1558
  omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
1559
+ omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
1263
1560
  omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
1264
1561
  omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
1265
1562
  omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
@@ -1340,17 +1637,18 @@ function normalizeCodexGoalMode(value) {
1340
1637
  throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
1341
1638
  }
1342
1639
 
1343
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts
1640
+ // components/ulw-loop/src/codex-goal-instruction.ts
1344
1641
  function buildCodexGoalInstruction(args) {
1345
1642
  const mode = codexGoalMode(args.plan);
1346
1643
  const createGoal = buildCreateGoalPayload(args.plan, args.goal);
1347
1644
  const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
1348
- return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal), json: createGoal };
1645
+ const surface = args.surface ?? resolveToolkitSurface();
1646
+ return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
1349
1647
  }
1350
1648
  function buildCreateGoalPayload(plan, goal) {
1351
1649
  return { objective: expectedCodexObjective(plan, goal) };
1352
1650
  }
1353
- function buildText(mode, plan, goal, createGoal, isFinal) {
1651
+ function buildText(mode, plan, goal, createGoal, isFinal, surface) {
1354
1652
  return joinLines([
1355
1653
  mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
1356
1654
  `Mode: ${mode}`,
@@ -1367,7 +1665,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
1367
1665
  "- Goals are unlimited. Do not add numeric limits.",
1368
1666
  ...modeConstraintLines(mode, isFinal),
1369
1667
  ...evidenceLayoutLines(plan),
1370
- finalSection(plan, goal, isFinal, mode === "aggregate"),
1668
+ finalSection(plan, goal, isFinal, mode === "aggregate", surface),
1371
1669
  ...checkpointLines(plan, mode),
1372
1670
  "",
1373
1671
  "create_goal payload:",
@@ -1419,18 +1717,21 @@ function evidenceLayoutLines(plan) {
1419
1717
  "- 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."
1420
1718
  ];
1421
1719
  }
1422
- function finalSection(plan, goal, isFinal, aggregate) {
1720
+ function finalSection(plan, goal, isFinal, aggregate, surface) {
1721
+ const roles = reviewerRolesFor(surface);
1423
1722
  if (!isFinal)
1424
1723
  return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
1425
1724
  const option = sessionOption(plan);
1725
+ if (surface === "omo-senpi")
1726
+ return senpiFinalSection(plan, goal, aggregate);
1426
1727
  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>"`;
1427
1728
  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>"`;
1428
1729
  return joinLines([
1429
1730
  "Final story — run mandatory quality gate before update_goal:",
1430
1731
  "- Run targeted verification for changed behavior.",
1431
1732
  "- Confirm every manualQa artifact path exists and has non-zero size.",
1432
- '- 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).',
1433
- "- Only then spawn lazycodex-gate-reviewer (same fork settings), passing those artifact paths.",
1733
+ `- First spawn ${roles.codeReview} and ${roles.manualQa} 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).`,
1734
+ `- Only then spawn ${roles.gateReview} (same fork settings), passing those artifact paths.`,
1434
1735
  "- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
1435
1736
  "- 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.",
1436
1737
  "- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
@@ -1440,6 +1741,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
1440
1741
  ` ${checkpointCommand}`
1441
1742
  ]);
1442
1743
  }
1744
+ function senpiFinalSection(plan, goal, aggregate) {
1745
+ const option = sessionOption(plan);
1746
+ 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)"`;
1747
+ return joinLines([
1748
+ "Final story — run the single-reviewer quality gate before update_goal:",
1749
+ '- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
1750
+ '- Spawn exactly one gate reviewer with task(category: "deep").',
1751
+ "- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
1752
+ "- Set gateReview.by to the exact category:<name> literal used for the successful task.",
1753
+ "- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
1754
+ '- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
1755
+ 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}`
1756
+ ]);
1757
+ }
1443
1758
  function sessionOption(plan) {
1444
1759
  const prefix = ".omo/ulw-loop/";
1445
1760
  const suffix = "/goals.json";
@@ -1453,11 +1768,11 @@ function joinLines(lines) {
1453
1768
  `);
1454
1769
  }
1455
1770
 
1456
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
1457
- import { existsSync as existsSync4 } from "node:fs";
1771
+ // components/ulw-loop/src/plan-crud.ts
1772
+ import { existsSync as existsSync5 } from "node:fs";
1458
1773
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
1459
1774
 
1460
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts
1775
+ // components/ulw-loop/src/plan-goal-factory.ts
1461
1776
  function cleanLine(line) {
1462
1777
  return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
1463
1778
  }
@@ -1546,7 +1861,7 @@ function appendGoalToPlan(plan, title, objective, now) {
1546
1861
  return goal;
1547
1862
  }
1548
1863
 
1549
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
1864
+ // components/ulw-loop/src/plan-crud.ts
1550
1865
  function isScheduleEligible(goal) {
1551
1866
  return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
1552
1867
  }
@@ -1564,7 +1879,7 @@ function clearGoalBlockerFields2(goal) {
1564
1879
  }
1565
1880
  async function createUlwLoopPlan(repoRoot, args, scope) {
1566
1881
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1567
- if (!args.force && existsSync4(ulwLoopGoalsPath(repoRoot, scope))) {
1882
+ if (!args.force && existsSync5(ulwLoopGoalsPath(repoRoot, scope))) {
1568
1883
  const existing = await readUlwLoopPlan(repoRoot, scope);
1569
1884
  if (isUlwLoopDone(existing))
1570
1885
  throw completedPlanExistsError(scope);
@@ -1673,7 +1988,7 @@ function summarizeUlwLoopPlan(plan) {
1673
1988
  };
1674
1989
  }
1675
1990
 
1676
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts
1991
+ // components/ulw-loop/src/checkpoint-continuation.ts
1677
1992
  async function checkpointAndContinue(repoRoot, args, scope) {
1678
1993
  const result = await checkpointUlwLoop(repoRoot, args, scope);
1679
1994
  if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
@@ -1685,6 +2000,14 @@ async function checkpointAndContinue(repoRoot, args, scope) {
1685
2000
  return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
1686
2001
  }
1687
2002
  async function checkpoint(repoRoot, argv, json, scope) {
2003
+ if (hasFlag(argv, "--print-template")) {
2004
+ const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
2005
+ if (json)
2006
+ printJson({ ok: true, ...template });
2007
+ else
2008
+ printJson(template);
2009
+ return 0;
2010
+ }
1688
2011
  const goalId = required2(argv, "--goal-id");
1689
2012
  const statusValue = checkpointStatus(required2(argv, "--status"));
1690
2013
  const evidence = required2(argv, "--evidence");
@@ -1736,10 +2059,10 @@ function checkpointStatus(value) {
1736
2059
  throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
1737
2060
  }
1738
2061
 
1739
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
2062
+ // components/ulw-loop/src/cli-subcommands.ts
1740
2063
  import { readFile as readFile6 } from "node:fs/promises";
1741
2064
 
1742
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts
2065
+ // components/ulw-loop/src/cli-steering.ts
1743
2066
  var SOURCES = ["user_prompt_submit", "finding", "cli"];
1744
2067
  var STEERING_KIND_HELP = [
1745
2068
  `Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
@@ -1771,7 +2094,7 @@ function kindMessage(prefix) {
1771
2094
 
1772
2095
  ${STEERING_KIND_HELP}`;
1773
2096
  }
1774
- function text2(value, field) {
2097
+ function text3(value, field) {
1775
2098
  if (value === undefined)
1776
2099
  return;
1777
2100
  const trimmed = value.trim();
@@ -1780,11 +2103,11 @@ function text2(value, field) {
1780
2103
  return fail2(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field });
1781
2104
  }
1782
2105
  function required3(argv, flag) {
1783
- const value = text2(readValue(argv, flag), flag);
2106
+ const value = text3(readValue(argv, flag), flag);
1784
2107
  return value ?? fail2(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag });
1785
2108
  }
1786
2109
  function requiredGoal(argv) {
1787
- const value = text2(parseGoalArg(argv), "--goal-id");
2110
+ const value = text3(parseGoalArg(argv), "--goal-id");
1788
2111
  return value ?? fail2("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" });
1789
2112
  }
1790
2113
  function readObject(value, key) {
@@ -1835,14 +2158,14 @@ function parseSteeringSource(argv) {
1835
2158
  function child(value) {
1836
2159
  if (!isPlain(value))
1837
2160
  return null;
1838
- const title = text2(objectText(value, "title"), "title");
1839
- const objective = text2(objectText(value, "objective"), "objective");
2161
+ const title = text3(objectText(value, "title"), "title");
2162
+ const objective = text3(objectText(value, "objective"), "objective");
1840
2163
  if (title === undefined || objective === undefined)
1841
2164
  return null;
1842
2165
  return { title, objective };
1843
2166
  }
1844
2167
  async function children(argv, flag, needed) {
1845
- const input = needed ? required3(argv, flag) : text2(readValue(argv, flag), flag);
2168
+ const input = needed ? required3(argv, flag) : text3(readValue(argv, flag), flag);
1846
2169
  if (input === undefined)
1847
2170
  return [];
1848
2171
  const raw = await readJsonInput(input);
@@ -1865,12 +2188,12 @@ async function stringArray2(argv, flag) {
1865
2188
  for (const item of raw) {
1866
2189
  if (typeof item !== "string")
1867
2190
  return fail2(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag });
1868
- values.push(text2(item, flag) ?? "");
2191
+ values.push(text3(item, flag) ?? "");
1869
2192
  }
1870
2193
  return values;
1871
2194
  }
1872
2195
  function model(value) {
1873
- const trimmed = text2(value, "--user-model");
2196
+ const trimmed = text3(value, "--user-model");
1874
2197
  if (trimmed === undefined)
1875
2198
  return;
1876
2199
  return isModel(trimmed) ? trimmed : fail2(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS });
@@ -1881,7 +2204,7 @@ function neverKind(kind) {
1881
2204
  async function parseSteeringProposal(argv) {
1882
2205
  const kind = parseSteeringKind(argv);
1883
2206
  const source = parseSteeringSource(argv);
1884
- const idempotencyKey = text2(readValue(argv, "--idempotency-key"), "--idempotency-key");
2207
+ const idempotencyKey = text3(readValue(argv, "--idempotency-key"), "--idempotency-key");
1885
2208
  const base = { kind, source, evidence: required3(argv, "--evidence"), rationale: required3(argv, "--rationale"), ...idempotencyKey === undefined ? {} : { idempotencyKey } };
1886
2209
  switch (kind) {
1887
2210
  case "add_subgoal":
@@ -1924,36 +2247,36 @@ async function parseSteeringProposal(argv) {
1924
2247
  function normalizedChildren(values) {
1925
2248
  if (values === undefined)
1926
2249
  return;
1927
- return values.map((item) => ({ title: text2(item.title, "child.title") ?? "", objective: text2(item.objective, "child.objective") ?? "" }));
2250
+ return values.map((item) => ({ title: text3(item.title, "child.title") ?? "", objective: text3(item.objective, "child.objective") ?? "" }));
1928
2251
  }
1929
2252
  function normalizedStrings(values, field) {
1930
2253
  if (values === undefined)
1931
2254
  return;
1932
- return values.map((value) => text2(value, field) ?? "");
2255
+ return values.map((value) => text3(value, field) ?? "");
1933
2256
  }
1934
2257
  function normalizeSteeringProposal(proposal) {
1935
- const evidence = text2(proposal.evidence, "evidence") ?? "";
1936
- const rationale = text2(proposal.rationale, "rationale") ?? "";
1937
- const goalId = text2(proposal.goalId, "goalId");
1938
- const targetGoalId = text2(proposal.targetGoalId, "targetGoalId");
2258
+ const evidence = text3(proposal.evidence, "evidence") ?? "";
2259
+ const rationale = text3(proposal.rationale, "rationale") ?? "";
2260
+ const goalId = text3(proposal.goalId, "goalId");
2261
+ const targetGoalId = text3(proposal.targetGoalId, "targetGoalId");
1939
2262
  const targetGoalIds = normalizedStrings(proposal.targetGoalIds, "targetGoalIds");
1940
- const criterionId = text2(proposal.criterionId, "criterionId");
1941
- const title = text2(proposal.title, "title");
1942
- const objective = text2(proposal.objective, "objective");
1943
- const revisedTitle = text2(proposal.revisedTitle, "revisedTitle");
1944
- const revisedObjective = text2(proposal.revisedObjective, "revisedObjective");
1945
- const blockedReason = text2(proposal.blockedReason, "blockedReason");
1946
- const directiveText = text2(proposal.directiveText, "directiveText");
1947
- const promptSignature = text2(proposal.promptSignature, "promptSignature");
1948
- const idempotencyKey = text2(proposal.idempotencyKey, "idempotencyKey");
1949
- const scenario = text2(proposal.scenario, "scenario");
1950
- const expectedEvidence = text2(proposal.expectedEvidence, "expectedEvidence");
2263
+ const criterionId = text3(proposal.criterionId, "criterionId");
2264
+ const title = text3(proposal.title, "title");
2265
+ const objective = text3(proposal.objective, "objective");
2266
+ const revisedTitle = text3(proposal.revisedTitle, "revisedTitle");
2267
+ const revisedObjective = text3(proposal.revisedObjective, "revisedObjective");
2268
+ const blockedReason = text3(proposal.blockedReason, "blockedReason");
2269
+ const directiveText = text3(proposal.directiveText, "directiveText");
2270
+ const promptSignature = text3(proposal.promptSignature, "promptSignature");
2271
+ const idempotencyKey = text3(proposal.idempotencyKey, "idempotencyKey");
2272
+ const scenario = text3(proposal.scenario, "scenario");
2273
+ const expectedEvidence = text3(proposal.expectedEvidence, "expectedEvidence");
1951
2274
  const childGoals = normalizedChildren(proposal.childGoals);
1952
2275
  const pendingOrder = normalizedStrings(proposal.pendingOrder, "pendingOrder");
1953
2276
  return { kind: proposal.kind, source: proposal.source, evidence, rationale, ...goalId === undefined ? {} : { goalId }, ...targetGoalId === undefined ? {} : { targetGoalId }, ...targetGoalIds === undefined ? {} : { targetGoalIds }, ...criterionId === undefined ? {} : { criterionId }, ...title === undefined ? {} : { title }, ...objective === undefined ? {} : { objective }, ...childGoals === undefined ? {} : { childGoals }, ...revisedTitle === undefined ? {} : { revisedTitle }, ...revisedObjective === undefined ? {} : { revisedObjective }, ...pendingOrder === undefined ? {} : { pendingOrder }, ...blockedReason === undefined ? {} : { blockedReason }, ...proposal.after === undefined ? {} : { after: proposal.after }, ...directiveText === undefined ? {} : { directiveText }, ...promptSignature === undefined ? {} : { promptSignature }, ...idempotencyKey === undefined ? {} : { idempotencyKey }, ...proposal.now === undefined ? {} : { now: proposal.now }, ...scenario === undefined ? {} : { scenario }, ...expectedEvidence === undefined ? {} : { expectedEvidence }, ...proposal.userModel === undefined ? {} : { userModel: proposal.userModel } };
1954
2277
  }
1955
2278
  async function parseSteeringProposals(argv) {
1956
- const input = text2(readValue(argv, "--proposals-json"), "--proposals-json");
2279
+ const input = text3(readValue(argv, "--proposals-json"), "--proposals-json");
1957
2280
  if (input === undefined)
1958
2281
  return [await parseSteeringProposal(argv)];
1959
2282
  if (readValue(argv, "--kind") !== undefined)
@@ -2046,7 +2369,7 @@ function printSteerBatchResult(result, json) {
2046
2369
  printStatus(result.plan);
2047
2370
  }
2048
2371
 
2049
- // packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts
2372
+ // components/ulw-loop/src/review-blockers.ts
2050
2373
  var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
2051
2374
  function ulwLoopError(message, code) {
2052
2375
  throw new UlwLoopError(message, code);
@@ -2085,9 +2408,10 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2085
2408
  ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
2086
2409
  const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
2087
2410
  const aggregate = codexGoalMode(plan) === "aggregate";
2088
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
2411
+ const expected = expectedCodexObjective(plan, goal);
2412
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expected, ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
2089
2413
  if (!reconciliation.ok)
2090
- ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
2414
+ throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expected });
2091
2415
  const now = iso();
2092
2416
  for (const field of BLOCKER_FIELDS)
2093
2417
  Reflect.deleteProperty(goal, field);
@@ -2112,10 +2436,39 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2112
2436
  });
2113
2437
  }
2114
2438
 
2115
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts
2439
+ // components/ulw-loop/src/status-next-actions.ts
2440
+ function statusNextActions(plan) {
2441
+ const actions = [];
2442
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
2443
+ if (plan.goals.length === 0)
2444
+ actions.push(`No goals yet: bootstrap with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`);
2445
+ else if (active !== undefined)
2446
+ actions.push(...activeGoalActions(plan, active));
2447
+ for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
2448
+ 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>'\`.`);
2449
+ if (plan.evidenceLayoutVersion !== 2 || active === undefined)
2450
+ actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
2451
+ return actions;
2452
+ }
2453
+ function activeGoalActions(plan, active) {
2454
+ const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
2455
+ if (unresolved.length > 0)
2456
+ return [
2457
+ `${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>"\`.`
2458
+ ];
2459
+ if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
2460
+ return [
2461
+ `${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate.`
2462
+ ];
2463
+ return [
2464
+ `${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>'\`.`
2465
+ ];
2466
+ }
2467
+
2468
+ // components/ulw-loop/src/steering-mutations.ts
2116
2469
  var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
2117
2470
  var isText = (value) => typeof value === "string" && value.trim().length > 0;
2118
- var text3 = (value, key) => {
2471
+ var text4 = (value, key) => {
2119
2472
  const candidate = read2(value, key);
2120
2473
  return isText(candidate) ? candidate.trim() : undefined;
2121
2474
  };
@@ -2124,13 +2477,13 @@ var after = (proposal) => {
2124
2477
  const candidate = read2(proposal, "after");
2125
2478
  return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
2126
2479
  };
2127
- var revised = (proposal, direct, nested) => text3(proposal, direct) ?? text3(after(proposal) ?? proposal, nested);
2128
- var targets = (proposal) => proposal.targetGoalIds ?? [proposal.targetGoalId ?? text3(proposal, "goalId") ?? ""].filter(Boolean);
2480
+ var revised = (proposal, direct, nested) => text4(proposal, direct) ?? text4(after(proposal) ?? proposal, nested);
2481
+ var targets = (proposal) => proposal.targetGoalIds ?? [proposal.targetGoalId ?? text4(proposal, "goalId") ?? ""].filter(Boolean);
2129
2482
  var child2 = (value) => {
2130
2483
  if (typeof value !== "object" || value === null || Array.isArray(value))
2131
2484
  return null;
2132
- const title = text3(value, "title");
2133
- const objective = text3(value, "objective");
2485
+ const title = text4(value, "title");
2486
+ const objective = text4(value, "objective");
2134
2487
  return title === undefined || objective === undefined ? null : { title, objective };
2135
2488
  };
2136
2489
  var children2 = (proposal) => {
@@ -2195,11 +2548,11 @@ function reviseCriterion(plan, proposal, now) {
2195
2548
  if (target === undefined || current === undefined)
2196
2549
  return;
2197
2550
  const model2 = read2(proposal, "userModel");
2198
- target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model2) ? model2 : current.userModel };
2551
+ target.successCriteria[index] = { ...current, scenario: text4(proposal, "scenario") ?? current.scenario, expectedEvidence: text4(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model2) ? model2 : current.userModel };
2199
2552
  target.updatedAt = now;
2200
2553
  }
2201
2554
 
2202
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-snapshot.ts
2555
+ // components/ulw-loop/src/steering-snapshot.ts
2203
2556
  function buildSteeringPlanSnapshot(plan, changedGoalIds) {
2204
2557
  const snapshot = {
2205
2558
  updatedAt: plan.updatedAt,
@@ -2223,14 +2576,14 @@ function changedGoalIdsBetween(before, after2) {
2223
2576
  return changed;
2224
2577
  }
2225
2578
 
2226
- // packages/omo-codex/plugin/components/ulw-loop/src/steering.ts
2579
+ // components/ulw-loop/src/steering.ts
2227
2580
  var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
2228
2581
  var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
2229
2582
  var isObject2 = (value) => typeof value === "object" && value !== null;
2230
2583
  var isPlain2 = (value) => isObject2(value) && !Array.isArray(value);
2231
2584
  var read3 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
2232
2585
  var isText2 = (value) => typeof value === "string" && value.trim().length > 0;
2233
- var text4 = (value, key) => {
2586
+ var text5 = (value, key) => {
2234
2587
  const candidate = read3(value, key);
2235
2588
  return isText2(candidate) ? candidate.trim() : undefined;
2236
2589
  };
@@ -2243,19 +2596,19 @@ var texts = (value, key) => {
2243
2596
  };
2244
2597
  function targets2(proposal) {
2245
2598
  const many = texts(proposal, "targetGoalIds");
2246
- const one = text4(proposal, "targetGoalId") ?? text4(proposal, "goalId");
2599
+ const one = text5(proposal, "targetGoalId") ?? text5(proposal, "goalId");
2247
2600
  return many.length > 0 ? many : one === undefined ? [] : [one];
2248
2601
  }
2249
2602
  var after2 = (proposal) => {
2250
2603
  const candidate = read3(proposal, "after");
2251
2604
  return isPlain2(candidate) ? candidate : undefined;
2252
2605
  };
2253
- var revised2 = (proposal, direct, nested) => text4(proposal, direct) ?? text4(after2(proposal) ?? proposal, nested);
2606
+ var revised2 = (proposal, direct, nested) => text5(proposal, direct) ?? text5(after2(proposal) ?? proposal, nested);
2254
2607
  function child3(value) {
2255
2608
  if (!isPlain2(value))
2256
2609
  return null;
2257
- const title = text4(value, "title");
2258
- const objective = text4(value, "objective");
2610
+ const title = text5(value, "title");
2611
+ const objective = text5(value, "objective");
2259
2612
  if (title === undefined || objective === undefined)
2260
2613
  return null;
2261
2614
  return { title, objective };
@@ -2294,15 +2647,15 @@ function auditFor(proposal, reasons) {
2294
2647
  const object = isPlain2(proposal) ? proposal : undefined;
2295
2648
  const kindRaw = object === undefined ? undefined : read3(object, "kind");
2296
2649
  const sourceRaw = object === undefined ? undefined : read3(object, "source");
2297
- const evidence = object === undefined ? "" : text4(object, "evidence") ?? "";
2298
- const rationale = object === undefined ? "" : text4(object, "rationale") ?? "";
2650
+ const evidence = object === undefined ? "" : text5(object, "evidence") ?? "";
2651
+ const rationale = object === undefined ? "" : text5(object, "rationale") ?? "";
2299
2652
  const audit = { kind: isKind2(kindRaw) ? kindRaw : "annotate_ledger", source: isSource2(sourceRaw) ? sourceRaw : "cli", targetGoalIds: object === undefined ? [] : targets2(object), evidence, rationale, invariant: { accepted: reasons.length === 0, structuralInvariantAccepted: reasons.length === 0, evidenceBackedNecessity: evidence.length > 0 && rationale.length > 0, noEasierCompletion: !weakens(proposal), rejectedReasons: reasons, reasons } };
2300
2653
  if (object === undefined)
2301
2654
  return audit;
2302
- const criterionId = text4(object, "criterionId");
2303
- const directiveText = text4(object, "directiveText");
2304
- const promptSignature = text4(object, "promptSignature");
2305
- const idempotencyKey = text4(object, "idempotencyKey");
2655
+ const criterionId = text5(object, "criterionId");
2656
+ const directiveText = text5(object, "directiveText");
2657
+ const promptSignature = text5(object, "promptSignature");
2658
+ const idempotencyKey = text5(object, "idempotencyKey");
2306
2659
  if (criterionId !== undefined)
2307
2660
  audit.criterionId = criterionId;
2308
2661
  if (directiveText !== undefined)
@@ -2323,9 +2676,9 @@ function validateUlwLoopSteeringProposal(plan, proposal) {
2323
2676
  reasons.push(`invalid kind: ${String(kind)}`);
2324
2677
  if (!isSource2(read3(object, "source")))
2325
2678
  reasons.push(`invalid source: ${String(read3(object, "source"))}`);
2326
- if (text4(object, "evidence") === undefined)
2679
+ if (text5(object, "evidence") === undefined)
2327
2680
  reasons.push("missing evidence");
2328
- if (text4(object, "rationale") === undefined)
2681
+ if (text5(object, "rationale") === undefined)
2329
2682
  reasons.push("missing rationale");
2330
2683
  if (hasProtected(proposal))
2331
2684
  reasons.push("protected payload");
@@ -2342,7 +2695,7 @@ function goal2(plan, id) {
2342
2695
  }
2343
2696
  function validateKind(plan, proposal, kind, reasons) {
2344
2697
  const target = goal2(plan, targets2(proposal)[0]);
2345
- if (kind === "add_subgoal" && (text4(proposal, "title") === undefined || text4(proposal, "objective") === undefined))
2698
+ if (kind === "add_subgoal" && (text5(proposal, "title") === undefined || text5(proposal, "objective") === undefined))
2346
2699
  reasons.push("add_subgoal requires title/objective");
2347
2700
  if ((kind === "split_subgoal" || kind === "revise_pending_wording" || kind === "mark_blocked_superseded") && target === undefined)
2348
2701
  reasons.push(`${kind} requires target`);
@@ -2372,7 +2725,7 @@ function validateOrder(plan, proposal, reasons) {
2372
2725
  }
2373
2726
  function validateCriterion(plan, proposal, reasons) {
2374
2727
  const target = goal2(plan, targets2(proposal)[0]);
2375
- const criterionId = text4(proposal, "criterionId");
2728
+ const criterionId = text5(proposal, "criterionId");
2376
2729
  if (target === undefined)
2377
2730
  reasons.push("revise_criterion requires goalId");
2378
2731
  else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
@@ -2407,8 +2760,8 @@ function applySteeringMutation(plan, proposal, audit) {
2407
2760
  function isProposal(value) {
2408
2761
  return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
2409
2762
  }
2410
- function parseUlwLoopSteeringDirective(text5) {
2411
- 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);
2763
+ function parseUlwLoopSteeringDirective(text6) {
2764
+ 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(text6);
2412
2765
  if (match?.[1] === undefined)
2413
2766
  return null;
2414
2767
  try {
@@ -2460,7 +2813,7 @@ function ledgerEntry(proposal, audit, at) {
2460
2813
  return entry;
2461
2814
  }
2462
2815
 
2463
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts
2816
+ // components/ulw-loop/src/steering-batch.ts
2464
2817
  async function steerUlwLoopBatch(repoRoot, proposals, scope) {
2465
2818
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
2466
2819
  const plan = await readUlwLoopPlan(repoRoot, scope);
@@ -2541,7 +2894,7 @@ function ledgerEntry2(proposal, audit, at) {
2541
2894
  return entry;
2542
2895
  }
2543
2896
 
2544
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
2897
+ // components/ulw-loop/src/cli-subcommands.ts
2545
2898
  async function createGoals(repoRoot, argv, json, scope) {
2546
2899
  const briefFile = readValue(argv, "--brief-file");
2547
2900
  const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile6(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
@@ -2575,7 +2928,8 @@ async function status(repoRoot, json, scope) {
2575
2928
  ok: true,
2576
2929
  plan,
2577
2930
  summary: summarizeUlwLoopPlan(plan),
2578
- ...currentAttemptDir === undefined ? {} : { currentAttemptDir }
2931
+ ...currentAttemptDir === undefined ? {} : { currentAttemptDir },
2932
+ nextActions: statusNextActions(plan)
2579
2933
  });
2580
2934
  } else
2581
2935
  printStatus(plan);
@@ -2697,7 +3051,7 @@ function findGoal3(plan, goalId) {
2697
3051
  throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
2698
3052
  }
2699
3053
 
2700
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts
3054
+ // components/ulw-loop/src/cli-commands.ts
2701
3055
  var ULW_LOOP_SUBCOMMANDS = [
2702
3056
  "help",
2703
3057
  "create-goals",
@@ -2791,7 +3145,7 @@ function commandScope(argv) {
2791
3145
  if (sessionIdFlagPresent(argv)) {
2792
3146
  const sessionId2 = readValue(argv, SESSION_ID_FLAG)?.trim();
2793
3147
  if (!sessionId2) {
2794
- throw new UlwLoopError(`${SESSION_ID_FLAG} requires a non-empty value.`, "ULW_LOOP_SESSION_ID_REQUIRED", {
3148
+ throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
2795
3149
  details: { flag: SESSION_ID_FLAG }
2796
3150
  });
2797
3151
  }
@@ -2801,12 +3155,12 @@ function commandScope(argv) {
2801
3155
  return sessionId === null ? undefined : { sessionId };
2802
3156
  }
2803
3157
 
2804
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
2805
- import { readFileSync as readFileSync2 } from "node:fs";
3158
+ // components/ulw-loop/src/ultrawork-directive.ts
3159
+ import { readFileSync as readFileSync3 } from "node:fs";
2806
3160
 
2807
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-skill-pointer.ts
2808
- import { existsSync as existsSync5, readFileSync } from "node:fs";
2809
- import { fileURLToPath } from "node:url";
3161
+ // components/ulw-loop/src/ultrawork-skill-pointer.ts
3162
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
3163
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2810
3164
  var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
2811
3165
  ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
2812
3166
 
@@ -2837,22 +3191,22 @@ Do not start the requested work until all three steps are complete.
2837
3191
  `;
2838
3192
  var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
2839
3193
  var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
2840
- var ULTRAWORK_DIRECTIVE = readFileSync(new URL("../directive.md", import.meta.url), "utf8");
3194
+ var ULTRAWORK_DIRECTIVE = readFileSync2(new URL("../directive.md", import.meta.url), "utf8");
2841
3195
  function resolveUltraworkSkillFilePath() {
2842
- return fileURLToPath(ULTRAWORK_SKILL_FILE_URL);
3196
+ return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
2843
3197
  }
2844
3198
  function buildUltraworkSkillPointer(skillFilePath) {
2845
3199
  return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
2846
3200
  }
2847
3201
  function buildUltraworkAdditionalContext(options = {}) {
2848
3202
  const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
2849
- if (skillFilePath !== null && existsSync5(skillFilePath)) {
3203
+ if (skillFilePath !== null && existsSync6(skillFilePath)) {
2850
3204
  return buildUltraworkSkillPointer(skillFilePath);
2851
3205
  }
2852
3206
  return ULTRAWORK_DIRECTIVE;
2853
3207
  }
2854
3208
 
2855
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
3209
+ // components/ulw-loop/src/ultrawork-directive.ts
2856
3210
  var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
2857
3211
  var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
2858
3212
  var TRANSCRIPT_SEARCH_BYTES = 512000;
@@ -2900,7 +3254,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
2900
3254
  return false;
2901
3255
  }
2902
3256
  function readTranscriptTail(transcriptPath) {
2903
- const rawTranscript = readFileSync2(transcriptPath);
3257
+ const rawTranscript = readFileSync3(transcriptPath);
2904
3258
  return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
2905
3259
  }
2906
3260
  function isUltraworkPrompt(prompt) {
@@ -2955,7 +3309,7 @@ function isRecord3(value) {
2955
3309
  return typeof value === "object" && value !== null && !Array.isArray(value);
2956
3310
  }
2957
3311
 
2958
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts
3312
+ // components/ulw-loop/src/codex-hook.ts
2959
3313
  var CREATE_GOAL_TOOL_NAME = "create_goal";
2960
3314
  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.";
2961
3315
  function parseUserPromptSubmitPayload(raw) {
@@ -3081,36 +3435,60 @@ function optionalString(value) {
3081
3435
  return value === undefined || typeof value === "string";
3082
3436
  }
3083
3437
  function readAll(stdin) {
3084
- return new Promise((resolve4, reject) => {
3438
+ return new Promise((resolve5, reject) => {
3085
3439
  let data = "";
3086
3440
  stdin.setEncoding("utf8");
3087
3441
  stdin.on("data", (chunk) => {
3088
3442
  data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
3089
3443
  });
3090
3444
  stdin.once("error", reject);
3091
- stdin.once("end", () => resolve4(data));
3445
+ stdin.once("end", () => resolve5(data));
3092
3446
  });
3093
3447
  }
3094
3448
 
3095
- // packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts
3096
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync as statSync2, writeFileSync } from "node:fs";
3097
- import { join as join2 } from "node:path";
3449
+ // components/ulw-loop/src/spawn-guard.ts
3450
+ import { randomBytes } from "node:crypto";
3451
+ import {
3452
+ existsSync as existsSync7,
3453
+ mkdirSync,
3454
+ openSync,
3455
+ readdirSync as readdirSync2,
3456
+ readFileSync as readFileSync4,
3457
+ renameSync,
3458
+ statSync as statSync2,
3459
+ unlinkSync,
3460
+ writeFileSync
3461
+ } from "node:fs";
3462
+ import { dirname as dirname2, join as join3 } from "node:path";
3098
3463
  var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
3099
3464
  var DEFAULT_FANOUT_LIMIT = 60;
3100
- var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|final gate review/i;
3465
+ var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
3466
+ var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
3467
+ var REVIEW_AGENT_TYPES = [
3468
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
3469
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
3470
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
3471
+ ];
3472
+ var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
3101
3473
  function applySpawnGuards(payload) {
3102
3474
  if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
3103
3475
  return "";
3104
3476
  const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3105
- const plan = readPlan(join2(stateDir, "goals.json"));
3477
+ const plan = readPlan(join3(stateDir, "goals.json"));
3106
3478
  if (plan === null)
3107
3479
  return "";
3108
- const fanOutDenial = consumeFanOutBudget(stateDir);
3109
- if (fanOutDenial !== null)
3110
- return deny(fanOutDenial);
3480
+ const fanOutPeek = peekFanOutBudget(stateDir);
3481
+ if (fanOutPeek !== null)
3482
+ return deny(fanOutPeek);
3111
3483
  const missingArtifact = missingGateArtifact(payload, plan);
3112
3484
  if (missingArtifact !== null)
3113
3485
  return deny(`spawn code-review + QA first; gate audits their artifacts: missing ${missingArtifact}`);
3486
+ const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
3487
+ if (reviewDenial !== null)
3488
+ return deny(reviewDenial);
3489
+ const fanOutDenial = consumeFanOutBudget(stateDir);
3490
+ if (fanOutDenial !== null)
3491
+ return deny(fanOutDenial);
3114
3492
  return "";
3115
3493
  }
3116
3494
  async function runSpawnGuardCli(stdin, stdout) {
@@ -3129,15 +3507,44 @@ async function runSpawnGuardCli(stdin, stdout) {
3129
3507
  return;
3130
3508
  }
3131
3509
  }
3510
+ function peekFanOutBudget(stateDir) {
3511
+ const counterPath = join3(stateDir, "spawn-count.json");
3512
+ const count = readCount(counterPath) + 1;
3513
+ const limit = fanOutLimit();
3514
+ if (count <= limit)
3515
+ return null;
3516
+ 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.`;
3517
+ }
3132
3518
  function consumeFanOutBudget(stateDir) {
3133
- const counterPath = join2(stateDir, "spawn-count.json");
3519
+ const counterPath = join3(stateDir, "spawn-count.json");
3134
3520
  const count = readCount(counterPath) + 1;
3135
- writeFileSync(counterPath, JSON.stringify({ count }));
3521
+ atomicWriteJson(counterPath, { count });
3136
3522
  const limit = fanOutLimit();
3137
3523
  if (count <= limit)
3138
3524
  return null;
3139
3525
  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.`;
3140
3526
  }
3527
+ function consumeReviewSpawnBudget(payload, plan, stateDir) {
3528
+ const agentType = reviewAgentType(payload.tool_input);
3529
+ if (agentType === null)
3530
+ return null;
3531
+ const goal3 = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3532
+ if (goal3 === undefined)
3533
+ return null;
3534
+ const counterPath = join3(stateDir, "review-spawn-counts.json");
3535
+ const lockPath = `${counterPath}.lock`;
3536
+ const limit = reviewSpawnLimit();
3537
+ return withExclusiveLock(lockPath, () => {
3538
+ const counts = readCounts(counterPath);
3539
+ const key = `${agentType}:${goal3.id}:a${goal3.attempt}`;
3540
+ const count = (counts[key] ?? 0) + 1;
3541
+ if (count > limit)
3542
+ return `ulw-loop reviewer no-progress cap reached (${agentType} ${count}/${limit}) for ${goal3.id} attempt ${goal3.attempt}. Consolidate existing review findings, or checkpoint and start a new attempt after concrete progress.`;
3543
+ counts[key] = count;
3544
+ atomicWriteJson(counterPath, counts);
3545
+ return null;
3546
+ });
3547
+ }
3141
3548
  function missingGateArtifact(payload, plan) {
3142
3549
  if (!isGateReviewerSpawn(payload.tool_input))
3143
3550
  return null;
@@ -3147,31 +3554,109 @@ function missingGateArtifact(payload, plan) {
3147
3554
  if (!goal3.successCriteria.every((criterion) => criterion.status === "pass"))
3148
3555
  return null;
3149
3556
  const scope = { sessionId: payload.session_id };
3557
+ const surface = resolveToolkitSurface();
3558
+ const requiredArtifacts = surface === "omo-senpi" ? [`${goal3.id}-manual-qa.md`] : [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`];
3150
3559
  if (plan.evidenceLayoutVersion === 2) {
3151
3560
  const attemptDir = ulwLoopAttemptEvidenceDir(goal3.id, goal3.attempt, scope);
3152
- for (const name of [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`]) {
3561
+ for (const name of requiredArtifacts) {
3153
3562
  const relative2 = `${attemptDir}/${name}`;
3154
- if (!isNonEmptyFile(join2(payload.cwd, relative2)))
3563
+ if (!isNonEmptyFile(join3(payload.cwd, relative2)))
3155
3564
  return relative2;
3156
3565
  }
3157
3566
  return null;
3158
3567
  }
3159
3568
  const flatReport = `.omo/evidence/${goal3.id}-code-review.md`;
3160
- if (!isNonEmptyFile(join2(payload.cwd, flatReport)))
3569
+ if (surface !== "omo-senpi" && !isNonEmptyFile(join3(payload.cwd, flatReport)))
3161
3570
  return flatReport;
3162
- if (!hasOtherEvidenceFile(join2(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
3163
- return `.omo/evidence/<any manual-QA artifact besides ${goal3.id}-code-review.md>`;
3571
+ if (surface === "omo-senpi") {
3572
+ const manualQa = `.omo/evidence/${goal3.id}-manual-qa.md`;
3573
+ return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
3574
+ }
3575
+ if (!hasOtherEvidenceFile(join3(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
3576
+ return `.omo/evidence/${goal3.id}-manual-qa.md`;
3164
3577
  return null;
3165
3578
  }
3166
3579
  function isGateReviewerSpawn(toolInput) {
3580
+ const agentType = reviewAgentType(toolInput);
3581
+ return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
3582
+ }
3583
+ function reviewAgentType(toolInput) {
3167
3584
  if (typeof toolInput !== "object" || toolInput === null)
3168
- return false;
3585
+ return null;
3169
3586
  const record = toolInput;
3170
3587
  const agentType = record["agent_type"];
3171
- if (typeof agentType === "string")
3172
- return agentType === "lazycodex-gate-reviewer";
3588
+ if (typeof agentType === "string") {
3589
+ if (!REVIEW_AGENT_TYPE_SET.has(agentType))
3590
+ return null;
3591
+ return activeSurfaceReviewerAlias(agentType);
3592
+ }
3173
3593
  const message = record["message"];
3174
- return typeof message === "string" && GATE_MESSAGE_PATTERN.test(message);
3594
+ if (typeof message !== "string")
3595
+ return null;
3596
+ const normalizedMessage = message.toLowerCase();
3597
+ const allRoleNames = [...REVIEW_AGENT_TYPES];
3598
+ const explicitAssignment = allRoleNames.map((name) => ({
3599
+ name,
3600
+ index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
3601
+ })).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
3602
+ if (explicitAssignment !== undefined)
3603
+ return activeSurfaceReviewerAlias(explicitAssignment.name);
3604
+ const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
3605
+ if (nonReviewerActAs)
3606
+ return null;
3607
+ const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
3608
+ if (namedReviewer !== undefined)
3609
+ return activeSurfaceReviewerAlias(namedReviewer);
3610
+ return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
3611
+ }
3612
+ function activeSurfaceReviewerAlias(reviewer) {
3613
+ const activeRoles = reviewerRolesFor(resolveToolkitSurface());
3614
+ for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
3615
+ if (reviewer === roles.codeReview)
3616
+ return activeRoles.codeReview;
3617
+ if (reviewer === roles.manualQa)
3618
+ return activeRoles.manualQa;
3619
+ if (reviewer === roles.gateReview)
3620
+ return activeRoles.gateReview;
3621
+ }
3622
+ return reviewer;
3623
+ }
3624
+ function withExclusiveLock(lockPath, fn) {
3625
+ mkdirSync(dirname2(lockPath), { recursive: true });
3626
+ const maxAttempts = 10;
3627
+ const baseDelayMs = 10;
3628
+ for (let attempt = 0;attempt < maxAttempts; attempt++) {
3629
+ let fd = null;
3630
+ try {
3631
+ fd = openSync(lockPath, "wx");
3632
+ writeFileSync(fd, process.pid.toString());
3633
+ try {
3634
+ return fn();
3635
+ } finally {
3636
+ try {
3637
+ unlinkSync(lockPath);
3638
+ } catch {}
3639
+ }
3640
+ } catch (error) {
3641
+ if (fd !== null) {
3642
+ try {
3643
+ unlinkSync(lockPath);
3644
+ } catch {}
3645
+ }
3646
+ if (error.code !== "EEXIST") {
3647
+ return fn();
3648
+ }
3649
+ const delayMs = baseDelayMs * 2 ** attempt + Math.random() * baseDelayMs;
3650
+ const deadline = Date.now() + delayMs;
3651
+ while (Date.now() < deadline) {}
3652
+ }
3653
+ }
3654
+ return fn();
3655
+ }
3656
+ function atomicWriteJson(targetPath, data) {
3657
+ const tmp = join3(dirname2(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
3658
+ writeFileSync(tmp, JSON.stringify(data));
3659
+ renameSync(tmp, targetPath);
3175
3660
  }
3176
3661
  function deny(reason) {
3177
3662
  return `${JSON.stringify({
@@ -3191,9 +3676,16 @@ function fanOutLimit() {
3191
3676
  const parsed = Number.parseInt(raw, 10);
3192
3677
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
3193
3678
  }
3679
+ function reviewSpawnLimit() {
3680
+ const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
3681
+ if (raw === undefined)
3682
+ return DEFAULT_REVIEW_SPAWN_LIMIT;
3683
+ const parsed = Number.parseInt(raw, 10);
3684
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
3685
+ }
3194
3686
  function isNonEmptyFile(path) {
3195
3687
  try {
3196
- return existsSync6(path) && statSync2(path).size > 0;
3688
+ return existsSync7(path) && statSync2(path).size > 0;
3197
3689
  } catch (error) {
3198
3690
  if (error instanceof Error)
3199
3691
  return false;
@@ -3202,7 +3694,7 @@ function isNonEmptyFile(path) {
3202
3694
  }
3203
3695
  function hasOtherEvidenceFile(evidenceDir, excludedName) {
3204
3696
  try {
3205
- return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join2(evidenceDir, name)));
3697
+ return readdirSync2(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join3(evidenceDir, name)));
3206
3698
  } catch (error) {
3207
3699
  if (error instanceof Error)
3208
3700
  return false;
@@ -3211,7 +3703,7 @@ function hasOtherEvidenceFile(evidenceDir, excludedName) {
3211
3703
  }
3212
3704
  function readCount(counterPath) {
3213
3705
  try {
3214
- const parsed = JSON.parse(readFileSync3(counterPath, "utf8"));
3706
+ const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
3215
3707
  return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
3216
3708
  } catch (error) {
3217
3709
  if (error instanceof Error)
@@ -3219,9 +3711,26 @@ function readCount(counterPath) {
3219
3711
  throw error;
3220
3712
  }
3221
3713
  }
3714
+ function readCounts(counterPath) {
3715
+ try {
3716
+ const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
3717
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
3718
+ return {};
3719
+ const counts = {};
3720
+ for (const [key, value] of Object.entries(parsed)) {
3721
+ if (typeof value === "number" && value >= 0)
3722
+ counts[key] = value;
3723
+ }
3724
+ return counts;
3725
+ } catch (error) {
3726
+ if (error instanceof Error)
3727
+ return {};
3728
+ throw error;
3729
+ }
3730
+ }
3222
3731
  function readPlan(goalsPath) {
3223
3732
  try {
3224
- return JSON.parse(readFileSync3(goalsPath, "utf8"));
3733
+ return JSON.parse(readFileSync4(goalsPath, "utf8"));
3225
3734
  } catch (error) {
3226
3735
  if (error instanceof Error)
3227
3736
  return null;
@@ -3229,9 +3738,9 @@ function readPlan(goalsPath) {
3229
3738
  }
3230
3739
  }
3231
3740
 
3232
- // packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts
3233
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
3234
- import { isAbsolute as isAbsolute2, join as join3, resolve as resolve4, sep as sep2 } from "node:path";
3741
+ // components/ulw-loop/src/stop-resume-hook.ts
3742
+ import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
3743
+ import { isAbsolute as isAbsolute2, join as join4, resolve as resolve5, sep as sep2 } from "node:path";
3235
3744
  var RESUME_CAP = 2;
3236
3745
  var CONTEXT_PRESSURE_MARKERS2 = [
3237
3746
  "context compacted",
@@ -3251,7 +3760,7 @@ function runStopResumeHook(input) {
3251
3760
  if (boulderContinuationWillFire(payload.cwd, payload.session_id))
3252
3761
  return "";
3253
3762
  const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3254
- const plan = readPlan2(join3(stateDir, "goals.json"));
3763
+ const plan = readPlan2(join4(stateDir, "goals.json"));
3255
3764
  if (plan === null || plan.aggregateCompletion?.status === "complete")
3256
3765
  return "";
3257
3766
  const goal3 = resumableGoal(plan);
@@ -3288,9 +3797,9 @@ function isResumableStatus(status2) {
3288
3797
  return status2 === "pending" || status2 === "in_progress";
3289
3798
  }
3290
3799
  function consumeResumeBudget(stateDir, goalId) {
3291
- const ledgerLineCount = countLedgerLines(join3(stateDir, "ledger.jsonl"));
3292
- const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
3293
- const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
3800
+ const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
3801
+ const counterPath = resolve5(stateDir, `auto-resume-${goalId}.json`);
3802
+ const stuckPath = resolve5(stateDir, `auto-resume-${goalId}.stuck`);
3294
3803
  if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
3295
3804
  return false;
3296
3805
  const previous = readCounter(counterPath);
@@ -3304,7 +3813,7 @@ function consumeResumeBudget(stateDir, goalId) {
3304
3813
  return true;
3305
3814
  }
3306
3815
  function isInsideDir(dir, candidate) {
3307
- return candidate.startsWith(resolve4(dir) + sep2);
3816
+ return candidate.startsWith(resolve5(dir) + sep2);
3308
3817
  }
3309
3818
  function renderResumeDirective(plan, goal3, sessionId) {
3310
3819
  const normalized = normalizeUlwLoopSessionId(sessionId);
@@ -3321,7 +3830,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
3321
3830
  }
3322
3831
  function readPlan2(goalsPath) {
3323
3832
  try {
3324
- return JSON.parse(readFileSync4(goalsPath, "utf8"));
3833
+ return JSON.parse(readFileSync5(goalsPath, "utf8"));
3325
3834
  } catch (error) {
3326
3835
  if (error instanceof Error)
3327
3836
  return null;
@@ -3330,7 +3839,7 @@ function readPlan2(goalsPath) {
3330
3839
  }
3331
3840
  function countLedgerLines(ledgerPath) {
3332
3841
  try {
3333
- return readFileSync4(ledgerPath, "utf8").split(`
3842
+ return readFileSync5(ledgerPath, "utf8").split(`
3334
3843
  `).filter(Boolean).length;
3335
3844
  } catch (error) {
3336
3845
  if (error instanceof Error)
@@ -3340,9 +3849,9 @@ function countLedgerLines(ledgerPath) {
3340
3849
  }
3341
3850
  function readCounter(counterPath) {
3342
3851
  try {
3343
- if (!existsSync7(counterPath))
3852
+ if (!existsSync8(counterPath))
3344
3853
  return null;
3345
- const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
3854
+ const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
3346
3855
  if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
3347
3856
  return null;
3348
3857
  return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
@@ -3354,7 +3863,7 @@ function readCounter(counterPath) {
3354
3863
  }
3355
3864
  function boulderContinuationWillFire(cwd, sessionId) {
3356
3865
  try {
3357
- const raw = JSON.parse(readFileSync4(join3(cwd, ".omo", "boulder.json"), "utf8"));
3866
+ const raw = JSON.parse(readFileSync5(join4(cwd, ".omo", "boulder.json"), "utf8"));
3358
3867
  const works = raw["works"];
3359
3868
  const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
3360
3869
  return entries.some((work) => {
@@ -3373,7 +3882,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
3373
3882
  }
3374
3883
  function transcriptShowsContextPressure(transcriptPath) {
3375
3884
  try {
3376
- const transcript = readFileSync4(transcriptPath, "utf8").toLowerCase();
3885
+ const transcript = readFileSync5(transcriptPath, "utf8").toLowerCase();
3377
3886
  return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
3378
3887
  } catch (error) {
3379
3888
  if (error instanceof Error)
@@ -3385,12 +3894,12 @@ function boulderPlanHasChecklist(cwd, entry) {
3385
3894
  const activePlan = entry["active_plan"];
3386
3895
  if (typeof activePlan !== "string" || activePlan.trim().length === 0)
3387
3896
  return false;
3388
- const planPath = isAbsolute2(activePlan) ? activePlan : join3(cwd, activePlan);
3897
+ const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
3389
3898
  const worktree = entry["worktree_path"];
3390
- const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join3(isAbsolute2(worktree) ? worktree : join3(cwd, worktree), activePlan), planPath] : [planPath];
3899
+ const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
3391
3900
  for (const candidate of candidates) {
3392
3901
  try {
3393
- return readFileSync4(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
3902
+ return readFileSync5(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
3394
3903
  } catch (error) {
3395
3904
  if (!(error instanceof Error))
3396
3905
  throw error;
@@ -3414,7 +3923,7 @@ function parseStopPayload(value) {
3414
3923
  };
3415
3924
  }
3416
3925
 
3417
- // packages/omo-codex/plugin/components/ulw-loop/src/cli.ts
3926
+ // components/ulw-loop/src/cli.ts
3418
3927
  async function main() {
3419
3928
  const argv = process.argv.slice(2);
3420
3929
  const command = argv[0];