lazycodex-ai 5.0.0-beta.9 → 5.0.0

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 (869) hide show
  1. package/README.ja.md +64 -69
  2. package/README.ko.md +65 -70
  3. package/README.md +108 -182
  4. package/README.ru.md +60 -68
  5. package/README.zh-cn.md +60 -69
  6. package/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
  7. package/dist/cli/config-manager/refresh-opencode-plugin-sandbox.d.ts +31 -0
  8. package/dist/cli/doctor/checks/browser-provider.d.ts +2 -0
  9. package/dist/cli/doctor/checks/index.d.ts +2 -0
  10. package/dist/cli/doctor/checks/latest-version.d.ts +11 -0
  11. package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
  12. package/dist/cli/doctor/checks/tui-plugin-config.d.ts +11 -0
  13. package/dist/cli/doctor/framework/constants.d.ts +5 -0
  14. package/dist/cli/doctor/framework/types.d.ts +2 -0
  15. package/dist/cli/fallback-lane-policy.d.ts +1 -0
  16. package/dist/cli/index.js +60340 -63161
  17. package/dist/cli/install-codex/install-codex-test-fixtures.d.ts +1 -1
  18. package/dist/cli/install-native/index.d.ts +12 -0
  19. package/dist/cli/install-native/legacy-omo-bin.d.ts +27 -0
  20. package/dist/cli/install-native/offer-native-setup.d.ts +31 -0
  21. package/dist/cli/install-native/omo-bin-test-fixtures.d.ts +35 -0
  22. package/dist/cli/install-native/plan.d.ts +21 -0
  23. package/dist/cli/install-native/repair-legacy-omo-bin.d.ts +26 -0
  24. package/dist/cli/install-native/run-native-install.d.ts +33 -0
  25. package/dist/cli/install-native/verify-omo-command.d.ts +22 -0
  26. package/dist/cli/install-native-dev/index.d.ts +4 -0
  27. package/dist/cli/native-dev-platform-flag.d.ts +5 -0
  28. package/dist/cli/native-edition-hint.d.ts +10 -0
  29. package/dist/cli/runtime-commands.d.ts +1 -1
  30. package/dist/cli/types.d.ts +3 -2
  31. package/dist/cli/worktree-sweep/classify.d.ts +16 -0
  32. package/dist/cli/worktree-sweep/format.d.ts +8 -0
  33. package/dist/cli/worktree-sweep/git.d.ts +19 -0
  34. package/dist/cli/worktree-sweep/index.d.ts +7 -0
  35. package/dist/cli/worktree-sweep/options.d.ts +2 -0
  36. package/dist/cli/worktree-sweep/parse-worktree-list.d.ts +8 -0
  37. package/dist/cli/worktree-sweep/sweep.d.ts +2 -0
  38. package/dist/cli/worktree-sweep/types.d.ts +65 -0
  39. package/dist/cli/worktree-sweep/worktree-sweep.d.ts +2 -0
  40. package/dist/cli-node/index.js +61667 -64465
  41. package/package.json +15 -14
  42. package/packages/git-bash-mcp/dist/cli.js +6 -1
  43. package/packages/lsp-daemon/dist/cli.js +1421 -960
  44. package/packages/lsp-daemon/dist/client.d.ts +10 -0
  45. package/packages/lsp-daemon/dist/client.js +956 -498
  46. package/packages/lsp-daemon/dist/daemon-client.d.ts +3 -0
  47. package/packages/lsp-daemon/dist/daemon-client.js +23 -3
  48. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +6 -1
  49. package/packages/lsp-daemon/dist/ensure-daemon.js +27 -5
  50. package/packages/lsp-daemon/dist/index.js +860 -407
  51. package/packages/lsp-daemon/dist/ownership.js +2 -2
  52. package/packages/lsp-daemon/dist/proxy.js +3 -0
  53. package/packages/lsp-daemon/dist/version-reap.js +1 -1
  54. package/packages/lsp-daemon/package.json +5 -4
  55. package/packages/lsp-tools-mcp/dist/cli.js +764 -341
  56. package/packages/lsp-tools-mcp/dist/lsp/manager.js +274 -138
  57. package/packages/lsp-tools-mcp/dist/mcp.js +789 -368
  58. package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
  59. package/packages/lsp-tools-mcp/dist/tools.js +719 -302
  60. package/packages/lsp-tools-mcp/package.json +5 -4
  61. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +4 -6
  62. package/packages/omo-codex/plugin/.mcp.json +0 -6
  63. package/packages/omo-codex/plugin/AGENTS.md +47 -0
  64. package/packages/omo-codex/plugin/README.md +6 -2
  65. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +8504 -408
  66. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  67. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  68. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -0
  69. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +7 -1
  70. package/packages/omo-codex/plugin/components/comment-checker/AGENTS.md +9 -0
  71. package/packages/omo-codex/plugin/components/comment-checker/biome.json +2 -2
  72. package/packages/omo-codex/plugin/components/comment-checker/dist/cli.js +2 -1
  73. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  74. package/packages/omo-codex/plugin/components/comment-checker/package.json +6 -5
  75. package/packages/omo-codex/plugin/components/comment-checker/src/apply-patch.ts +2 -8
  76. package/packages/omo-codex/plugin/components/comment-checker/src/core.ts +1 -2
  77. package/packages/omo-codex/plugin/components/comment-checker/src/request-extractor.ts +1 -1
  78. package/packages/omo-codex/plugin/components/comment-checker/src/runner.ts +1 -0
  79. package/packages/omo-codex/plugin/components/git-bash/AGENTS.md +1 -1
  80. package/packages/omo-codex/plugin/components/git-bash/dist/cli.js +1 -1
  81. package/packages/omo-codex/plugin/components/git-bash/dist/codex-hook.js +2 -1
  82. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  83. package/packages/omo-codex/plugin/components/git-bash/package.json +3 -4
  84. package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +2 -1
  85. package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +6 -7
  86. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/AGENTS.md +2 -2
  87. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +2 -2
  88. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/directive.md +2 -0
  89. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/cli.js +36 -29
  90. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/codex-hook.js +32 -26
  91. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/types.d.ts +3 -0
  92. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  93. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +6 -5
  94. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/codex-hook.ts +32 -28
  95. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/types.ts +3 -0
  96. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/cli.test.ts +1 -1
  97. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +134 -11
  98. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +1 -1
  99. package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +7 -5
  100. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +5 -5
  101. package/packages/omo-codex/plugin/components/lsp/AGENTS.md +30 -15
  102. package/packages/omo-codex/plugin/components/lsp/biome.json +2 -2
  103. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
  104. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +977 -545
  105. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +1 -1
  106. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  107. package/packages/omo-codex/plugin/components/lsp/package.json +6 -5
  108. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +1 -1
  109. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +1 -1
  110. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +1 -5
  111. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +6 -7
  112. package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +1 -2
  113. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +11 -1
  114. package/packages/omo-codex/plugin/components/rules/AGENTS.md +12 -0
  115. package/packages/omo-codex/plugin/components/rules/README.md +2 -0
  116. package/packages/omo-codex/plugin/components/rules/biome.json +2 -2
  117. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -1
  118. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +127 -0
  119. package/packages/omo-codex/plugin/components/rules/dist/cli.js +103 -68
  120. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  121. package/packages/omo-codex/plugin/components/rules/package.json +8 -7
  122. package/packages/omo-codex/plugin/components/rules/src/config.ts +1 -2
  123. package/packages/omo-codex/plugin/components/rules/src/persistent-cache.ts +1 -2
  124. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +27 -5
  125. package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +1 -4
  126. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -9
  127. package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +1 -2
  128. package/packages/omo-codex/plugin/components/rules/test/dynamic-target-fingerprints.test.ts +1 -2
  129. package/packages/omo-codex/plugin/components/rules/test/engine.test.ts +7 -4
  130. package/packages/omo-codex/plugin/components/rules/test/finder.test.ts +2 -3
  131. package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +2 -3
  132. package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +38 -3
  133. package/packages/omo-codex/plugin/components/rules/test/matcher.test.ts +2 -3
  134. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +1 -1
  135. package/packages/omo-codex/plugin/components/rules/test/parser.test.ts +1 -2
  136. package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +29 -6
  137. package/packages/omo-codex/plugin/components/rules/test/rules-engine-consumption.test.ts +3 -6
  138. package/packages/omo-codex/plugin/components/rules/test/scanner.test.ts +1 -2
  139. package/packages/omo-codex/plugin/components/rules/test/sources.test.ts +2 -5
  140. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +1 -2
  141. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  142. package/packages/omo-codex/plugin/components/teammode/package.json +6 -5
  143. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +1 -1
  144. package/packages/omo-codex/plugin/components/telemetry/AGENTS.md +8 -0
  145. package/packages/omo-codex/plugin/components/telemetry/biome.json +2 -2
  146. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +5328 -1382
  147. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +5524 -1579
  148. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  149. package/packages/omo-codex/plugin/components/telemetry/package.json +6 -5
  150. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +1 -4
  151. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -14
  152. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +1 -1
  153. package/packages/omo-codex/plugin/components/telemetry/test/diagnostics.test.ts +2 -5
  154. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -4
  155. package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -1
  156. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  157. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
  158. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
  159. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
  160. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
  161. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +1 -1
  162. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-low.toml +1 -1
  163. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +1 -1
  164. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +1 -1
  165. package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +1 -1
  166. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +1 -1
  167. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +20 -7
  168. package/packages/omo-codex/plugin/components/ultrawork/biome.json +2 -2
  169. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +478 -4
  170. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  171. package/packages/omo-codex/plugin/components/ultrawork/package.json +6 -6
  172. package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
  173. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +9 -10
  174. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +17 -13
  175. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +5 -6
  176. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +4 -4
  177. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/plan-templates.mjs +180 -0
  178. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +3 -153
  179. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
  180. package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
  181. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +9 -1
  182. package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
  183. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
  184. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/AGENTS.md +4 -4
  185. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/NOTICE +1 -1
  186. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/README.md +6 -6
  187. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/biome.json +2 -2
  188. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/directive.md +13 -13
  189. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/dist/cli.js +16 -16
  190. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +16 -0
  191. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/package.json +13 -12
  192. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/boulder-reader.ts +1 -1
  193. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/cli.ts +1 -1
  194. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/codex-hook.ts +4 -4
  195. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/directive.ts +1 -1
  196. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/index.ts +1 -1
  197. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/types.ts +1 -1
  198. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/boulder-reader.test.ts +3 -3
  199. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/cli.test.ts +4 -8
  200. package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/codex-hook.test.ts +22 -6
  201. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +19 -0
  202. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +17 -1
  203. package/packages/omo-codex/plugin/components/ulw-loop/README.md +12 -1
  204. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +2 -2
  205. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +102 -107
  206. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +18 -0
  207. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +30 -0
  208. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +10 -4
  209. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +2 -5
  210. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -78
  211. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +12 -0
  212. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +114 -0
  213. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.d.ts +7 -1
  214. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +57 -46
  215. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-arg-parser.js +2 -2
  216. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +21 -8
  217. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
  218. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +12 -1
  219. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +1 -1
  220. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +40 -37
  221. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +2205 -737
  222. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  223. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +28 -8
  224. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +8 -5
  225. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +35 -23
  226. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -0
  227. package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +1 -0
  228. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +25 -9
  229. package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.d.ts +4 -0
  230. package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.js +18 -0
  231. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.d.ts +1 -0
  232. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.js +37 -0
  233. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.d.ts +1 -0
  234. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.js +24 -12
  235. package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.d.ts +4 -0
  236. package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.js +39 -0
  237. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +2 -0
  238. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +19 -4
  239. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.d.ts +19 -0
  240. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.js +108 -0
  241. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +6 -2
  242. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +54 -25
  243. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.d.ts +10 -3
  244. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.js +38 -31
  245. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +10 -7
  246. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +121 -100
  247. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.d.ts +16 -0
  248. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.js +115 -0
  249. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +18 -0
  250. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +41 -0
  251. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +7 -0
  252. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +9 -0
  253. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +12 -0
  254. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +104 -0
  255. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
  256. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +65 -9
  257. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
  258. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  259. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +72 -117
  260. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.d.ts +2 -0
  261. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +25 -13
  262. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.d.ts +2 -0
  263. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +157 -0
  264. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +381 -0
  265. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +127 -0
  266. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +130 -0
  267. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.js +1 -0
  268. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.d.ts +3 -0
  269. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk.js +3 -0
  270. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.d.ts +5 -0
  271. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.js +67 -0
  272. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +7 -1
  273. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +190 -59
  274. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.d.ts +2 -0
  275. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.js +22 -0
  276. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +19 -0
  277. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +259 -0
  278. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +7 -0
  279. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +41 -0
  280. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +4 -4
  281. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +3 -2
  282. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +4 -4
  283. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-types.d.ts +9 -1
  284. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.d.ts +3 -2
  285. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +11 -11
  286. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +28 -23
  287. package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.d.ts +9 -0
  288. package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.js +41 -0
  289. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +21 -0
  290. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +100 -0
  291. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +18 -5
  292. package/packages/omo-codex/plugin/components/ulw-loop/package.json +7 -6
  293. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +7 -2
  294. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +2 -3
  295. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +34 -11
  296. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +52 -0
  297. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +8 -6
  298. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +5 -98
  299. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +134 -0
  300. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +59 -77
  301. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts +10 -4
  302. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +24 -8
  303. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +14 -1
  304. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +52 -46
  305. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +8 -6
  306. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +35 -7
  307. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +51 -33
  308. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts +3 -1
  309. package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +1 -0
  310. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +27 -9
  311. package/packages/omo-codex/plugin/components/ulw-loop/src/driver-objective-ack.ts +20 -0
  312. package/packages/omo-codex/plugin/components/ulw-loop/src/evidence-artifacts.ts +45 -0
  313. package/packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts +23 -16
  314. package/packages/omo-codex/plugin/components/ulw-loop/src/ledger.ts +38 -0
  315. package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +32 -4
  316. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-commit.ts +132 -0
  317. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +67 -54
  318. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts +63 -31
  319. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +140 -119
  320. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-log.ts +112 -0
  321. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +61 -0
  322. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +11 -0
  323. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +123 -0
  324. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +71 -15
  325. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
  326. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +92 -136
  327. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +38 -13
  328. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +218 -0
  329. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +156 -0
  330. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +143 -0
  331. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk.ts +3 -0
  332. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-budget-io.ts +59 -0
  333. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +201 -52
  334. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-role-guard.ts +22 -0
  335. package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +257 -0
  336. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +45 -0
  337. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +4 -4
  338. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +5 -4
  339. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-types.ts +13 -1
  340. package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +12 -10
  341. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +27 -22
  342. package/packages/omo-codex/plugin/components/ulw-loop/src/success-criteria-input.ts +56 -0
  343. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +133 -0
  344. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +131 -82
  345. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +147 -0
  346. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +16 -22
  347. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +4 -2
  348. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint.test.ts +31 -2
  349. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-commands.test.ts +2 -0
  350. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-complete-goals.test.ts +3 -1
  351. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +35 -8
  352. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +38 -4
  353. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +27 -0
  354. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-scope-required.test.ts +144 -0
  355. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +185 -0
  356. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +8 -3
  357. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +6 -0
  358. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +35 -0
  359. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +88 -11
  360. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/barrier.ts +13 -0
  361. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/cli-session.ts +4 -0
  362. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-child.ts +65 -0
  363. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-cli-qa.mjs +76 -0
  364. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-crash.ts +23 -0
  365. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +58 -0
  366. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-writer.ts +65 -0
  367. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/lease-clock.ts +23 -0
  368. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +9 -4
  369. package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +67 -0
  370. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -2
  371. package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +9 -0
  372. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-log.test.ts +252 -0
  373. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-recovery.test.ts +190 -0
  374. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io-cross-process.test.ts +106 -0
  375. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +74 -25
  376. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-legacy-projection.test.ts +228 -0
  377. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-newest.test.ts +72 -0
  378. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
  379. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
  380. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-fields-messages.test.ts +98 -0
  381. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-lazycodex-surface.test.ts +93 -0
  382. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
  383. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +51 -2
  384. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
  385. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
  386. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +58 -3
  387. package/packages/omo-codex/plugin/components/ulw-loop/test/review-blockers.test.ts +9 -7
  388. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-completed-plan.test.ts +43 -0
  389. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-contract.test.ts +137 -0
  390. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-driver-states.test.ts +174 -0
  391. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-dx.test.ts +219 -0
  392. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +169 -0
  393. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard-native-reviewer.test.ts +207 -0
  394. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +452 -7
  395. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-matrix.test.ts +113 -0
  396. package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +310 -0
  397. package/packages/omo-codex/plugin/components/ulw-loop/test/status-next-actions-surface.test.ts +133 -0
  398. package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +12 -12
  399. package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +12 -6
  400. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +1 -1
  401. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +113 -0
  402. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +9 -3
  403. package/packages/omo-codex/plugin/components/ulw-loop/vitest.config.ts +1 -0
  404. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  405. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  406. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  407. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  408. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  409. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  410. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  411. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +18 -0
  412. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  413. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  414. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  415. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  416. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  417. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  418. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  419. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +17 -0
  420. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  421. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  422. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  423. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  424. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  425. package/packages/omo-codex/plugin/model-catalog.json +16 -7
  426. package/packages/omo-codex/plugin/package-lock.json +992 -681
  427. package/packages/omo-codex/plugin/package.json +6 -3
  428. package/packages/omo-codex/plugin/scripts/AGENTS.md +39 -0
  429. package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +1 -2
  430. package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
  431. package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
  432. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
  433. package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +8 -2
  434. package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +16 -7
  435. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +8 -4
  436. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +19 -90
  437. package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +1 -1
  438. package/packages/omo-codex/plugin/scripts/sync-skills.d.mts +4 -0
  439. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +114 -67
  440. package/packages/omo-codex/plugin/shared/package.json +3 -4
  441. package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
  442. package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
  443. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
  444. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +51 -0
  445. package/packages/omo-codex/plugin/skills/ast-grep/SKILL.md +13 -1
  446. package/packages/omo-codex/plugin/skills/browser/ATTRIBUTION.md +37 -0
  447. package/packages/omo-codex/plugin/skills/browser/SKILL.md +109 -0
  448. package/packages/omo-codex/plugin/skills/browser/agents/openai.yaml +2 -0
  449. package/packages/omo-codex/plugin/skills/browser/references/commands.md +105 -0
  450. package/packages/omo-codex/plugin/skills/browser/references/install.md +97 -0
  451. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/README.md +62 -0
  452. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/frames-and-humans.md +52 -0
  453. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/ladder.md +61 -0
  454. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/network.md +62 -0
  455. package/packages/omo-codex/plugin/skills/browser/references/recipes/1password.md +61 -0
  456. package/packages/omo-codex/plugin/skills/browser/references/remote.md +25 -0
  457. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +1534 -0
  458. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +9 -0
  459. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/page-bundle.js +1395 -0
  460. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +57 -0
  461. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +50 -0
  462. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +25 -0
  463. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +62 -0
  464. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +1 -1
  465. package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
  466. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
  467. package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
  468. package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
  469. package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
  470. package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
  471. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  472. package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  473. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +5 -3
  474. package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
  475. package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +26 -5
  476. package/packages/omo-codex/plugin/skills/debugging/references/methodology/06-fix.md +3 -3
  477. package/packages/omo-codex/plugin/skills/debugging/references/methodology/08-qa.md +1 -1
  478. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
  479. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
  480. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
  481. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
  482. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
  483. package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
  484. package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  485. package/packages/omo-codex/plugin/skills/debugging/references/tools/browser-qa.md +104 -0
  486. package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
  487. package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
  488. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +10 -0
  489. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +8 -3
  490. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +13 -4
  491. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +1 -0
  492. package/packages/omo-codex/plugin/skills/frontend/references/design/ambience-skill.md +146 -0
  493. package/packages/omo-codex/plugin/skills/frontend/references/design/aside.md +1 -1
  494. package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
  495. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +30 -6
  496. package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +19 -5
  497. package/packages/omo-codex/plugin/skills/frontend/references/design/print-paged-media.md +76 -0
  498. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/README.md +1 -1
  499. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  500. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  501. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  502. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/orchestration.md +1 -1
  503. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/routing.md +5 -5
  504. package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +1 -0
  505. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
  506. package/packages/omo-codex/plugin/skills/git-master/SKILL.md +1 -1
  507. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +20 -18
  508. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +1 -1
  509. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +7 -5
  510. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +5 -5
  511. package/packages/omo-codex/plugin/skills/lsp-setup/SKILL.md +1 -1
  512. package/packages/omo-codex/plugin/skills/programming/SKILL.md +12 -18
  513. package/packages/omo-codex/plugin/skills/programming/references/rust/README.md +43 -15
  514. package/packages/omo-codex/plugin/skills/programming/references/rust/api-design.md +81 -0
  515. package/packages/omo-codex/plugin/skills/programming/references/rust/async-tokio.md +60 -28
  516. package/packages/omo-codex/plugin/skills/programming/references/rust/axum-stack.md +1 -13
  517. package/packages/omo-codex/plugin/skills/programming/references/rust/cargo-strict.md +44 -8
  518. package/packages/omo-codex/plugin/skills/programming/references/rust/clap-stack.md +8 -3
  519. package/packages/omo-codex/plugin/skills/programming/references/rust/concurrency.md +66 -52
  520. package/packages/omo-codex/plugin/skills/programming/references/rust/libraries.md +35 -25
  521. package/packages/omo-codex/plugin/skills/programming/references/rust/macros.md +63 -0
  522. package/packages/omo-codex/plugin/skills/programming/references/rust/one-liners.md +5 -3
  523. package/packages/omo-codex/plugin/skills/programming/references/rust/proptest-insta.md +8 -0
  524. package/packages/omo-codex/plugin/skills/programming/references/rust/type-state.md +50 -12
  525. package/packages/omo-codex/plugin/skills/programming/references/rust/unsafe-discipline.md +34 -6
  526. package/packages/omo-codex/plugin/skills/programming/references/rust/zero-cost-safety.md +62 -52
  527. package/packages/omo-codex/plugin/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
  528. package/packages/omo-codex/plugin/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
  529. package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
  530. package/packages/omo-codex/plugin/skills/programming/scripts/rust/new-project.py +31 -28
  531. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +11 -9
  532. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +11 -9
  533. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +116 -424
  534. package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
  535. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +40 -28
  536. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -1
  537. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +2 -2
  538. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
  539. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
  540. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
  541. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
  542. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +34 -111
  543. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +14 -7
  544. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +22 -36
  545. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
  546. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +102 -107
  547. package/packages/omo-codex/plugin/skills/{start-work → ulw-execute}/SKILL.md +38 -39
  548. package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
  549. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +31 -2
  550. package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +2 -3
  551. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +34 -11
  552. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +9 -10
  553. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +17 -13
  554. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +5 -6
  555. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +4 -4
  556. package/packages/omo-codex/plugin/skills/ulw-plan/scripts/plan-templates.mjs +180 -0
  557. package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +3 -153
  558. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +63 -0
  559. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +52 -33
  560. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +187 -0
  561. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +66 -0
  562. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +102 -0
  563. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +162 -0
  564. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +204 -0
  565. package/packages/omo-codex/plugin/skills/ulw-research/scripts/design-spec.mjs +161 -0
  566. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +39 -0
  567. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entry-guard.mjs +21 -0
  568. package/packages/omo-codex/plugin/skills/ulw-research/scripts/format-extract-css.mjs +122 -0
  569. package/packages/omo-codex/plugin/skills/ulw-research/scripts/format-extract.mjs +229 -0
  570. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-figures.mjs +173 -0
  571. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-layout.mjs +146 -0
  572. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +167 -0
  573. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +118 -0
  574. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +190 -0
  575. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +239 -0
  576. package/packages/omo-codex/plugin/skills/ulw-research/scripts/layout-probe.mjs +83 -0
  577. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +250 -0
  578. package/packages/omo-codex/plugin/skills/ulw-research/scripts/repair-tracker.mjs +171 -0
  579. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +214 -0
  580. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +61 -0
  581. package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +58 -0
  582. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +10 -7
  583. package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +77 -0
  584. package/packages/omo-codex/plugin/test/AGENTS.md +47 -0
  585. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +12 -12
  586. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +13 -28
  587. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -3
  588. package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
  589. package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +14 -4
  590. package/packages/omo-codex/plugin/test/auto-update.test.mjs +4 -29
  591. package/packages/omo-codex/plugin/test/bootstrap-default-role.test.mjs +66 -0
  592. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
  593. package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
  594. package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +2 -2
  595. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -51
  596. package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +100 -2
  597. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +5 -6
  598. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
  599. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +35 -35
  600. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +1 -1
  601. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +107 -15
  602. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +81 -3
  603. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +57 -26
  604. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +37 -0
  605. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -2
  606. package/packages/omo-codex/scripts/install-dist/install-local.mjs +16238 -4291
  607. package/packages/{omo-codex/plugin/components/ultrawork/directive.md → prompts-core/prompts/ultrawork/codex.md} +102 -107
  608. package/packages/shared-skills/package.json +7 -1
  609. package/packages/shared-skills/skill-source-filter.d.ts +16 -0
  610. package/packages/shared-skills/skill-source-filter.mjs +44 -0
  611. package/packages/shared-skills/skills/ast-grep/AGENTS.md +51 -0
  612. package/packages/shared-skills/skills/ast-grep/SKILL.md +13 -1
  613. package/packages/shared-skills/skills/browser/ATTRIBUTION.md +37 -0
  614. package/packages/shared-skills/skills/browser/SKILL.md +109 -0
  615. package/packages/shared-skills/skills/browser/references/commands.md +105 -0
  616. package/packages/shared-skills/skills/browser/references/install.md +97 -0
  617. package/packages/shared-skills/skills/browser/references/owned-engine/README.md +62 -0
  618. package/packages/shared-skills/skills/browser/references/owned-engine/frames-and-humans.md +52 -0
  619. package/packages/shared-skills/skills/browser/references/owned-engine/ladder.md +61 -0
  620. package/packages/shared-skills/skills/browser/references/owned-engine/network.md +62 -0
  621. package/packages/shared-skills/skills/browser/references/recipes/1password.md +61 -0
  622. package/packages/shared-skills/skills/browser/references/remote.md +25 -0
  623. package/packages/shared-skills/skills/browser/runtime/omowright/index.js +1534 -0
  624. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +9 -0
  625. package/packages/shared-skills/skills/browser/runtime/omowright/page-bundle.js +1395 -0
  626. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +57 -0
  627. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +50 -0
  628. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +25 -0
  629. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +62 -0
  630. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +1 -1
  631. package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
  632. package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
  633. package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
  634. package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
  635. package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
  636. package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
  637. package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
  638. package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
  639. package/packages/shared-skills/skills/debugging/SKILL.md +5 -3
  640. package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
  641. package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +26 -5
  642. package/packages/shared-skills/skills/debugging/references/methodology/06-fix.md +3 -3
  643. package/packages/shared-skills/skills/debugging/references/methodology/08-qa.md +1 -1
  644. package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
  645. package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
  646. package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
  647. package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
  648. package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
  649. package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
  650. package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
  651. package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  652. package/packages/shared-skills/skills/debugging/references/tools/browser-qa.md +104 -0
  653. package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
  654. package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
  655. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +10 -0
  656. package/packages/shared-skills/skills/frontend/SKILL.md +8 -3
  657. package/packages/shared-skills/skills/frontend/references/design/README.md +13 -4
  658. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +1 -0
  659. package/packages/shared-skills/skills/frontend/references/design/ambience-skill.md +146 -0
  660. package/packages/shared-skills/skills/frontend/references/design/aside.md +1 -1
  661. package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
  662. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +30 -6
  663. package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +19 -5
  664. package/packages/shared-skills/skills/frontend/references/design/print-paged-media.md +76 -0
  665. package/packages/shared-skills/skills/frontend/references/designpowers/README.md +1 -1
  666. package/packages/shared-skills/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
  667. package/packages/shared-skills/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
  668. package/packages/shared-skills/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
  669. package/packages/shared-skills/skills/frontend/references/designpowers/orchestration.md +1 -1
  670. package/packages/shared-skills/skills/frontend/references/designpowers/routing.md +5 -5
  671. package/packages/shared-skills/skills/frontend/references/perfection/README.md +1 -0
  672. package/packages/shared-skills/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
  673. package/packages/shared-skills/skills/git-master/SKILL.md +1 -1
  674. package/packages/shared-skills/skills/init-deep/SKILL.md +14 -14
  675. package/packages/shared-skills/skills/lsp-setup/SKILL.md +1 -1
  676. package/packages/shared-skills/skills/programming/SKILL.md +12 -18
  677. package/packages/shared-skills/skills/programming/references/rust/README.md +43 -15
  678. package/packages/shared-skills/skills/programming/references/rust/api-design.md +81 -0
  679. package/packages/shared-skills/skills/programming/references/rust/async-tokio.md +60 -28
  680. package/packages/shared-skills/skills/programming/references/rust/axum-stack.md +1 -13
  681. package/packages/shared-skills/skills/programming/references/rust/cargo-strict.md +44 -8
  682. package/packages/shared-skills/skills/programming/references/rust/clap-stack.md +8 -3
  683. package/packages/shared-skills/skills/programming/references/rust/concurrency.md +66 -52
  684. package/packages/shared-skills/skills/programming/references/rust/libraries.md +35 -25
  685. package/packages/shared-skills/skills/programming/references/rust/macros.md +63 -0
  686. package/packages/shared-skills/skills/programming/references/rust/one-liners.md +5 -3
  687. package/packages/shared-skills/skills/programming/references/rust/proptest-insta.md +8 -0
  688. package/packages/shared-skills/skills/programming/references/rust/type-state.md +50 -12
  689. package/packages/shared-skills/skills/programming/references/rust/unsafe-discipline.md +34 -6
  690. package/packages/shared-skills/skills/programming/references/rust/zero-cost-safety.md +62 -52
  691. package/packages/shared-skills/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
  692. package/packages/shared-skills/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
  693. package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
  694. package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.test.ts +83 -0
  695. package/packages/shared-skills/skills/programming/scripts/rust/new-project.py +31 -28
  696. package/packages/shared-skills/skills/refactor/SKILL.md +5 -5
  697. package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +5 -5
  698. package/packages/shared-skills/skills/review-work/SKILL.md +101 -419
  699. package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
  700. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +40 -28
  701. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -1
  702. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +2 -2
  703. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
  704. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
  705. package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
  706. package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
  707. package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +34 -111
  708. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +14 -7
  709. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +22 -36
  710. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
  711. package/packages/shared-skills/skills/{start-work → ulw-execute}/SKILL.md +27 -25
  712. package/packages/shared-skills/skills/ulw-plan/SKILL.md +10 -11
  713. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +18 -13
  714. package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +5 -6
  715. package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +4 -4
  716. package/packages/shared-skills/skills/ulw-plan/scripts/plan-templates.mjs +180 -0
  717. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +3 -153
  718. package/packages/shared-skills/skills/ulw-research/AGENTS.md +63 -0
  719. package/packages/shared-skills/skills/ulw-research/SKILL.md +47 -30
  720. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +187 -0
  721. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +66 -0
  722. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +102 -0
  723. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +88 -0
  724. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +162 -0
  725. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +177 -0
  726. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +204 -0
  727. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.test.ts +159 -0
  728. package/packages/shared-skills/skills/ulw-research/scripts/design-spec.mjs +161 -0
  729. package/packages/shared-skills/skills/ulw-research/scripts/design-spec.test.ts +162 -0
  730. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +39 -0
  731. package/packages/shared-skills/skills/ulw-research/scripts/entry-guard.mjs +21 -0
  732. package/packages/shared-skills/skills/ulw-research/scripts/format-extract-css.mjs +122 -0
  733. package/packages/shared-skills/skills/ulw-research/scripts/format-extract.mjs +229 -0
  734. package/packages/shared-skills/skills/ulw-research/scripts/format-extract.test.ts +243 -0
  735. package/packages/shared-skills/skills/ulw-research/scripts/gates-figures.mjs +173 -0
  736. package/packages/shared-skills/skills/ulw-research/scripts/gates-layout.mjs +146 -0
  737. package/packages/shared-skills/skills/ulw-research/scripts/gates-layout.test.ts +236 -0
  738. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +167 -0
  739. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.test.ts +265 -0
  740. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +118 -0
  741. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +190 -0
  742. package/packages/shared-skills/skills/ulw-research/scripts/glossary-vocabulary.test.ts +43 -0
  743. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +239 -0
  744. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.test.ts +191 -0
  745. package/packages/shared-skills/skills/ulw-research/scripts/layout-probe.mjs +83 -0
  746. package/packages/shared-skills/skills/ulw-research/scripts/layout-probe.test.ts +89 -0
  747. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +250 -0
  748. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +281 -0
  749. package/packages/shared-skills/skills/ulw-research/scripts/repair-tracker.mjs +171 -0
  750. package/packages/shared-skills/skills/ulw-research/scripts/repair-tracker.test.ts +246 -0
  751. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +214 -0
  752. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +61 -0
  753. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +213 -0
  754. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/assets/figure-1.svg +1 -0
  755. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/bad-report.html +44 -0
  756. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/boxes.json +90 -0
  757. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/design-spec.md +29 -0
  758. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/good-report.html +57 -0
  759. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/reference-report.html +86 -0
  760. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/reference.md +23 -0
  761. package/packages/shared-skills/skills/visual-qa/AGENTS.md +58 -0
  762. package/packages/shared-skills/skills/visual-qa/SKILL.md +4 -3
  763. package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +77 -0
  764. package/script/qa/web-terminal-visual-qa.mjs +2 -0
  765. package/script/qa/xterm-live-terminal.mjs +18 -2
  766. package/dist/cli/install-senpi/index.d.ts +0 -1
  767. package/dist/cli/senpi-platform-flag.d.ts +0 -4
  768. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  769. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  770. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  771. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  772. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
  773. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
  774. package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
  775. package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
  776. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12244
  777. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9842
  778. package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
  779. package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
  780. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
  781. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
  782. package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
  783. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
  784. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
  785. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -309
  786. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -80
  787. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
  788. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
  789. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
  790. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
  791. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
  792. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -21
  793. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
  794. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
  795. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
  796. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
  797. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
  798. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
  799. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
  800. package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
  801. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
  802. package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
  803. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
  804. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
  805. package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
  806. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
  807. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
  808. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
  809. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
  810. package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
  811. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
  812. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
  813. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
  814. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
  815. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
  816. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
  817. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
  818. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
  819. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
  820. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
  821. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
  822. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
  823. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
  824. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
  825. package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
  826. package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
  827. package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
  828. package/packages/omo-codex/plugin/components/start-work-continuation/LICENSE +0 -21
  829. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +0 -28
  830. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +0 -484
  831. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
  832. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
  833. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +0 -17
  834. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +0 -17
  835. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
  836. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
  837. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
  838. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
  839. package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
  840. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
  841. package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
  842. package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
  843. package/packages/omo-codex/plugin/skills/debugging/references/tools/playwright-cli.md +0 -194
  844. package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
  845. package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +0 -2
  846. package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +0 -45
  847. package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
  848. package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
  849. package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
  850. package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
  851. package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
  852. package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
  853. package/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md +0 -194
  854. package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
  855. package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +0 -45
  856. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/.gitattributes +0 -0
  857. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/CHANGELOG.md +0 -0
  858. /package/packages/omo-codex/plugin/components/{codegraph → ulw-execute-continuation}/LICENSE +0 -0
  859. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/plan-checklist.ts +0 -0
  860. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-completed.json +0 -0
  861. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-mixed-platforms.json +0 -0
  862. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-single-codex-work.json +0 -0
  863. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-all-done.md +0 -0
  864. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-scaffold.md +0 -0
  865. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-nested-checkboxes.md +0 -0
  866. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-unchecked.md +0 -0
  867. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.build.json +0 -0
  868. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.json +0 -0
  869. /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/vitest.config.ts +0 -0
@@ -1,21 +1,145 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
4
- import { existsSync as existsSync3, statSync } from "node:fs";
5
- import { readFile as readFile5 } from "node:fs/promises";
6
- import { resolve as resolve3 } from "node:path";
3
+ // components/ulw-loop/src/checkpoint.ts
4
+ import { existsSync as existsSync5, statSync as statSync2 } from "node:fs";
5
+ import { readFile as readFile3 } from "node:fs/promises";
6
+ import { resolve as resolve4 } from "node:path";
7
7
 
8
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
8
+ // components/ulw-loop/src/codex-goal-snapshot.ts
9
9
  import { existsSync } from "node:fs";
10
10
  import { readFile } from "node:fs/promises";
11
+ import { resolve } from "node:path";
12
+
13
+ class CodexGoalSnapshotError extends Error {
14
+ }
15
+ function safeObject(value) {
16
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
17
+ }
18
+ function safeString(value) {
19
+ return typeof value === "string" ? value : "";
20
+ }
21
+ function safeStatusString(value) {
22
+ return typeof value === "string" ? value.trim() : "";
23
+ }
24
+ function normalizeStatus(value) {
25
+ const status = safeStatusString(value).toLowerCase();
26
+ if (status === "complete" || status === "completed" || status === "done")
27
+ return "complete";
28
+ if (status === "cancelled" || status === "canceled")
29
+ return "cancelled";
30
+ if (status === "failed" || status === "failure")
31
+ return "failed";
32
+ if (status === "paused")
33
+ return "paused";
34
+ if (status === "usage_limited")
35
+ return "usage_limited";
36
+ if (status === "budget_limited")
37
+ return "budget_limited";
38
+ if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
39
+ return "active";
40
+ return "unknown";
41
+ }
42
+ function normalizeObjective(value) {
43
+ return value.replace(/\s+/g, " ").trim();
44
+ }
45
+ function parseCodexGoalSnapshot(value) {
46
+ const root = safeObject(value);
47
+ const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
48
+ if (goalValue === null || goalValue === undefined || goalValue === false) {
49
+ return { available: false, raw: value };
50
+ }
51
+ const goal = safeObject(goalValue);
52
+ const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
53
+ const status = normalizeStatus(goal["status"] ?? root["status"]);
54
+ return {
55
+ available: Boolean(objective || status !== "unknown"),
56
+ ...objective ? { objective } : {},
57
+ status,
58
+ raw: value
59
+ };
60
+ }
61
+ async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
62
+ if (!raw?.trim())
63
+ return null;
64
+ const trimmed = raw.trim();
65
+ try {
66
+ return parseCodexGoalSnapshot(JSON.parse(trimmed));
67
+ } catch {
68
+ const path = resolve(cwd, trimmed);
69
+ if (!existsSync(path)) {
70
+ throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
71
+ }
72
+ try {
73
+ return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
74
+ } catch (error) {
75
+ throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
76
+ }
77
+ }
78
+ }
79
+ function reconcileCodexGoalSnapshot(snapshot, options) {
80
+ const effectiveSnapshot = snapshot ?? { available: false, raw: null };
81
+ const errors = [];
82
+ const warnings = [];
83
+ const nextActions = [];
84
+ const expected = options.expectedObjective;
85
+ if (!effectiveSnapshot.available) {
86
+ nextActions.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
87
+ return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, nextActions, errors };
88
+ }
89
+ const normalized = (objectives) => new Set(objectives.map(normalizeObjective).filter(Boolean));
90
+ const accepted = normalized([expected, ...options.acceptedObjectives ?? []]);
91
+ const acknowledged = normalized(options.acknowledgedObjectives ?? []);
92
+ const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
93
+ let unacknowledgedObjective;
94
+ if (actual && !accepted.has(actual) && !acknowledged.has(actual)) {
95
+ warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
96
+ unacknowledgedObjective = actual;
97
+ }
98
+ const actualStatus = effectiveSnapshot.status ?? "unknown";
99
+ if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
100
+ nextActions.push("/goal resume or raise the budget");
101
+ }
102
+ if (actualStatus === "complete")
103
+ nextActions.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
104
+ return {
105
+ ok: errors.length === 0,
106
+ snapshot: effectiveSnapshot,
107
+ warnings,
108
+ nextActions,
109
+ errors,
110
+ ...unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective }
111
+ };
112
+ }
113
+ function formatCodexGoalReconciliation(reconciliation) {
114
+ const parts = [...reconciliation.errors, ...reconciliation.nextActions, ...reconciliation.warnings];
115
+ return parts.join(" ");
116
+ }
11
117
 
12
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
118
+ // components/ulw-loop/src/driver-objective-ack.ts
119
+ function normalizeDriverObjective(value) {
120
+ return value.replace(/\s+/g, " ").trim();
121
+ }
122
+ function acknowledgedDriverObjectives(plan) {
123
+ return plan.acknowledgedDriverObjectives ?? [];
124
+ }
125
+ function acknowledgeDriverObjective(plan, objective) {
126
+ if (objective === undefined)
127
+ return false;
128
+ const normalized = normalizeDriverObjective(objective);
129
+ if (!normalized || acknowledgedDriverObjectives(plan).includes(normalized))
130
+ return false;
131
+ plan.acknowledgedDriverObjectives = [...acknowledgedDriverObjectives(plan), normalized];
132
+ return true;
133
+ }
134
+
135
+ // components/ulw-loop/src/paths.ts
13
136
  import { isAbsolute, join, relative, sep } from "node:path";
14
- // packages/omo-codex/plugin/components/ulw-loop/src/constants.ts
137
+ // components/ulw-loop/src/constants.ts
15
138
  var ULW_LOOP_DIR = ".omo/ulw-loop";
16
139
  var ULW_LOOP_BRIEF = "brief.md";
17
140
  var ULW_LOOP_GOALS = "goals.json";
18
141
  var ULW_LOOP_LEDGER = "ledger.jsonl";
142
+ var ULW_LOOP_STATE_LOCK = ".state.lock";
19
143
  var ULW_LOOP_STEERING_MUTATION_KINDS = [
20
144
  "add_subgoal",
21
145
  "split_subgoal",
@@ -31,10 +155,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
31
155
  "regression",
32
156
  "adversarial"
33
157
  ];
34
- // packages/omo-codex/plugin/components/ulw-loop/src/runtime.ts
158
+ // components/ulw-loop/src/runtime.ts
35
159
  class UlwLoopError extends Error {
36
- code;
37
- details;
38
160
  constructor(message, code, opts) {
39
161
  super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
40
162
  this.name = "UlwLoopError";
@@ -47,8 +169,8 @@ class UlwLoopError extends Error {
47
169
  function iso() {
48
170
  return new Date().toISOString();
49
171
  }
50
- // packages/omo-codex/plugin/components/ulw-loop/src/paths.ts
51
- var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"];
172
+ // components/ulw-loop/src/paths.ts
173
+ var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
52
174
  function normalizeUlwLoopSessionId(sessionId) {
53
175
  const trimmed = sessionId?.trim();
54
176
  if (!trimmed)
@@ -81,14 +203,11 @@ function ulwLoopGoalsRelativePath(scope) {
81
203
  function ulwLoopLedgerRelativePath(scope) {
82
204
  return `${ulwLoopRelativeDir(scope)}/${ULW_LOOP_LEDGER}`;
83
205
  }
84
- function ulwLoopBriefPath(repoRoot, scope) {
85
- return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_BRIEF);
86
- }
87
206
  function ulwLoopGoalsPath(repoRoot, scope) {
88
207
  return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_GOALS);
89
208
  }
90
- function ulwLoopLedgerPath(repoRoot, scope) {
91
- return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_LEDGER);
209
+ function ulwLoopStateLockPath(repoRoot, scope) {
210
+ return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_STATE_LOCK);
92
211
  }
93
212
  function repoRelative(absolutePath, repoRoot) {
94
213
  const slashPrefix = `${repoRoot}/`;
@@ -99,8 +218,15 @@ function repoRelative(absolutePath, repoRoot) {
99
218
  return absolutePath.slice(backslashPrefix.length).split("\\").join("/");
100
219
  return absolutePath.split("\\").join("/");
101
220
  }
221
+ function ulwLoopEvidenceRoot(scope) {
222
+ const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
223
+ return sessionId === null ? ".omo/evidence" : `.omo/evidence/ulw/${sessionId}`;
224
+ }
102
225
  function ulwLoopAttemptEvidenceDir(goalId, attempt, scope) {
103
- const sessionId = normalizeUlwLoopSessionId(scope?.sessionId) ?? resolveUlwLoopSessionIdFromEnv() ?? "session";
226
+ const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
227
+ if (sessionId === null) {
228
+ throw new UlwLoopError(`Evidence for ${goalId} attempt ${attempt} needs a session scope; pass --session-id <id> so the attempt directory lives under .omo/evidence/ulw/<id>/.`, "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { goalId, attempt } });
229
+ }
104
230
  return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
105
231
  }
106
232
  var PLATFORM_PATH_API = { relative, isAbsolute, sep };
@@ -113,7 +239,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
113
239
  return !pathApi.isAbsolute(relativePath);
114
240
  }
115
241
 
116
- // packages/omo-codex/plugin/components/ulw-loop/src/goal-status.ts
242
+ // components/ulw-loop/src/goal-status.ts
117
243
  var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
118
244
  function aggregateCodexObjectiveForScope(scope) {
119
245
  return `Complete the durable ulw-loop plan in ${ulwLoopGoalsRelativePath(scope)}, including later accepted/appended stories, under the original brief constraints; use ${ulwLoopLedgerRelativePath(scope)} as the audit trail.`;
@@ -196,198 +322,553 @@ function hasEssentialCriteriaPass(goal) {
196
322
  return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
197
323
  }
198
324
 
199
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts
200
- function normalizeObjective(value) {
201
- return value.replace(/\s+/g, " ").trim();
325
+ // components/ulw-loop/src/checkpoint-codex-validation.ts
326
+ async function validateCheckpointCodexGoal(input) {
327
+ const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
328
+ const expected = expectedCodexObjective(input.plan, input.goal);
329
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
330
+ expectedObjective: expected,
331
+ acknowledgedObjectives: acknowledgedDriverObjectives(input.plan),
332
+ ...codexGoalMode(input.plan) === "aggregate" ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {}
333
+ });
334
+ if (!reconciliation.ok)
335
+ throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
336
+ return {
337
+ raw: snapshot?.raw,
338
+ nextActions: reconciliation.nextActions,
339
+ warnings: reconciliation.warnings,
340
+ ...reconciliation.unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective: reconciliation.unacknowledgedObjective }
341
+ };
202
342
  }
203
- function textMentionsUlwLoopPlanArtifact(value) {
204
- const normalized = (value ?? "").toLowerCase();
205
- return normalized.includes(ULW_LOOP_DIR.toLowerCase()) || normalized.includes(ULW_LOOP_GOALS.toLowerCase()) || normalized.includes(ULW_LOOP_LEDGER.toLowerCase());
343
+ function combineCheckpointValidationErrors(codexError, gateError) {
344
+ return new UlwLoopError(`${codexError.message}
345
+ ${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
346
+ details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
347
+ });
206
348
  }
207
- function textMentionsGoalId(value, goalId) {
208
- return (value ?? "").toLowerCase().includes(goalId.toLowerCase());
349
+
350
+ // components/ulw-loop/src/evidence-artifacts.ts
351
+ import { existsSync as existsSync2 } from "node:fs";
352
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
353
+ function fail(message, code, details) {
354
+ throw new UlwLoopError(message, code, { details });
209
355
  }
210
- function textHasCompletionValidationEvidence(value) {
211
- const normalized = (value ?? "").toLowerCase();
212
- const done = /\b(?:planned work|implementation|deliverables?|scope|task|work)\b/.test(normalized) && /\b(?:done|complete|completed|finished|shipped)\b/.test(normalized);
213
- const verified = /\b(?:validation|verification|tests?|build|lint|review|quality gate|code-review)\b/.test(normalized) && /\b(?:passed|complete|completed|clean|green|approve|approved|clear)\b/.test(normalized);
214
- return done && verified;
356
+ function stored(repoRoot, absolute) {
357
+ const rel = relative2(repoRoot, absolute);
358
+ const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
359
+ return inside ? rel.split(sep2).join("/") : absolute;
215
360
  }
216
- async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope) {
217
- const actual = normalizeObjective(snapshotObjective).toLowerCase();
218
- if (textMentionsUlwLoopPlanArtifact(actual))
219
- return true;
220
- if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot, scope)))
221
- return false;
361
+ function resolveEvidenceArtifacts(repoRoot, artifacts) {
362
+ if (artifacts === undefined)
363
+ return;
364
+ const seen = new Set;
365
+ const resolved = [];
366
+ for (const [index, candidate] of artifacts.entries()) {
367
+ const path = typeof candidate === "string" ? candidate.trim() : "";
368
+ if (!path)
369
+ fail(`Artifact ${index + 1} must be a non-empty path.`, "ULW_LOOP_ARGUMENT_INVALID", { index });
370
+ const absolute = resolve2(repoRoot, path);
371
+ if (!existsSync2(absolute))
372
+ fail(`Evidence artifact does not exist: ${path} (resolved to ${absolute}).`, "ULW_LOOP_EVIDENCE_ARTIFACT_MISSING", {
373
+ path,
374
+ resolved: absolute
375
+ });
376
+ const value = stored(repoRoot, absolute);
377
+ if (!seen.has(value)) {
378
+ seen.add(value);
379
+ resolved.push(value);
380
+ }
381
+ }
382
+ return resolved;
383
+ }
384
+
385
+ // components/ulw-loop/src/plan-commit.ts
386
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
387
+ import { randomUUID as randomUUID2 } from "node:crypto";
388
+ import { linkSync, mkdirSync as mkdirSync2, renameSync, rmSync, writeFileSync } from "node:fs";
389
+ import { rename, writeFile } from "node:fs/promises";
390
+ import { join as join4 } from "node:path";
391
+
392
+ // components/ulw-loop/src/ledger.ts
393
+ import { join as join3 } from "node:path";
394
+
395
+ // components/ulw-loop/src/plan-log.ts
396
+ import { existsSync as existsSync3, readdirSync, readFileSync } from "node:fs";
397
+ import { join as join2 } from "node:path";
398
+ function hasCode(error, code) {
399
+ return error instanceof Error && "code" in error && error.code === code;
400
+ }
401
+ function readOptional(path) {
222
402
  try {
223
- const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
224
- return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
403
+ return readFileSync(path, "utf8");
225
404
  } catch (error) {
226
- if (error instanceof Error)
227
- return false;
405
+ if (hasCode(error, "ENOENT"))
406
+ return;
228
407
  throw error;
229
408
  }
230
409
  }
231
- async function canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot, plan, goal, snapshotObjective, evidence, scope) {
232
- if (codexGoalMode(plan) !== "aggregate")
233
- return false;
234
- if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id)
235
- return false;
236
- if (isFinalRunCompletionCandidate(plan, goal)) {
237
- return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
410
+ function logNames(dir) {
411
+ try {
412
+ return readdirSync(join2(dir, "revisions")).filter((name) => /^\d{8,}\.json$/.test(name)).sort();
413
+ } catch (error) {
414
+ if (hasCode(error, "ENOENT"))
415
+ return [];
416
+ throw error;
238
417
  }
239
- if (!textMentionsUlwLoopPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id))
240
- return false;
241
- if (!textHasCompletionValidationEvidence(evidence))
242
- return false;
243
- return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
244
418
  }
245
- async function canReconcileActiveFinalTaskScopedAggregateSnapshot(repoRoot, plan, goal, snapshotObjective, evidence, scope) {
246
- if (codexGoalMode(plan) !== "aggregate")
247
- return false;
248
- if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id)
249
- return false;
250
- if (!isFinalRunCompletionCandidate(plan, goal))
251
- return false;
252
- if (!textHasCompletionValidationEvidence(evidence))
253
- return false;
254
- return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
419
+ function readRecord(dir, name) {
420
+ try {
421
+ const record = JSON.parse(readFileSync(join2(dir, "revisions", name), "utf8"));
422
+ return record.version === 1 && Number.isInteger(record.revision) && record.plan?.version === 1 && Array.isArray(record.plan.goals) && Array.isArray(record.ledger) ? record : undefined;
423
+ } catch (error) {
424
+ if (!(error instanceof SyntaxError))
425
+ throw error;
426
+ return;
427
+ }
255
428
  }
256
- function buildCompletedLegacyGoalRemediation(goal) {
257
- return [
258
- "If get_goal returns a different completed legacy/thread objective, do not repeat --status complete in this thread.",
259
- `Record a non-terminal blocker with: omo-agent-toolkit ulw-loop checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<different completed get_goal JSON or path>".`,
260
- "Then continue only from a Codex goal context with no active/completed conflicting goal, in the same repo/worktree, and create the intended goal there."
261
- ].join(" ");
429
+ function readRecords(dir) {
430
+ const records = [];
431
+ for (const name of logNames(dir)) {
432
+ const record = readRecord(dir, name);
433
+ if (record !== undefined)
434
+ records.push(record);
435
+ }
436
+ return records.sort((a, b) => a.revision - b.revision);
437
+ }
438
+ function readNewestRecord(dir) {
439
+ const names = logNames(dir).sort((a, b) => Number.parseInt(b, 10) - Number.parseInt(a, 10));
440
+ for (const name of names) {
441
+ const record = readRecord(dir, name);
442
+ if (record !== undefined)
443
+ return record;
444
+ }
445
+ return;
262
446
  }
263
- function buildTaskScopedAggregateReconciliationHint(goal, final) {
264
- if (final) {
265
- return ` Final task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress final OMO goal and the completed get_goal objective to map to the ulw-loop brief or artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
447
+ function reconcilePlan(dir) {
448
+ const raw = readOptional(join2(dir, "goals.json"));
449
+ let cached;
450
+ if (raw !== undefined) {
451
+ try {
452
+ cached = JSON.parse(raw);
453
+ } catch (error) {
454
+ if (!(error instanceof SyntaxError))
455
+ throw error;
456
+ }
266
457
  }
267
- return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
458
+ const latest = readNewestRecord(dir);
459
+ if (latest === undefined)
460
+ return cached;
461
+ if (cached === undefined)
462
+ return latest.plan;
463
+ if ((cached.revision ?? 0) > latest.revision)
464
+ return cached;
465
+ if (cached.revision === undefined || cached.revision === latest.revision) {
466
+ const published = new Set(latest.plan.goals.map((goal) => goal.id));
467
+ if (Array.isArray(cached.goals) && cached.goals.some((goal) => !published.has(goal.id)))
468
+ return { ...cached, revision: latest.revision };
469
+ }
470
+ return latest.plan;
471
+ }
472
+ function assertProjectionComplete(plan, ledger) {
473
+ const present = new Set(plan.goals.map((goal) => goal.id));
474
+ const missing = new Set;
475
+ for (const entry of ledger)
476
+ if (entry.kind === "goal_added" && entry.goalId !== undefined && !present.has(entry.goalId))
477
+ missing.add(entry.goalId);
478
+ if (missing.size === 0)
479
+ return;
480
+ throw new UlwLoopError(`Refusing to rewrite goals.json: the ledger records goals the plan projection lacks (${[...missing].join(", ")}). Restore those goals into the newest revisions/ record (keeping plan.revision equal to its file number) before mutating this run.`, "ULW_LOOP_PROJECTION_TRUNCATED", { details: { missingGoalIds: [...missing] } });
481
+ }
482
+ function planExists(repoRoot, scope) {
483
+ const dir = ulwLoopDir(repoRoot, scope);
484
+ return existsSync3(join2(dir, "goals.json")) || logNames(dir).length > 0;
268
485
  }
269
486
 
270
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts
271
- import { existsSync as existsSync2 } from "node:fs";
272
- import { readFile as readFile2 } from "node:fs/promises";
273
- import { resolve } from "node:path";
274
-
275
- class CodexGoalSnapshotError extends Error {
487
+ // components/ulw-loop/src/ledger.ts
488
+ function readLedgerAt(dir) {
489
+ const entries = new Map;
490
+ const lines = (readOptional(join3(dir, "ledger.jsonl")) ?? "").split(/\r?\n/);
491
+ let reached = 0;
492
+ for (const [index, line] of lines.entries()) {
493
+ if (line.trim().length === 0)
494
+ continue;
495
+ try {
496
+ const entry = JSON.parse(line);
497
+ entry.revision = entry.revision || reached;
498
+ reached = Math.max(reached, entry.revision);
499
+ entry.id ??= `legacy-${index + 1}`;
500
+ entries.set(entry.id, entry);
501
+ } catch (error) {
502
+ if (!(error instanceof SyntaxError))
503
+ throw error;
504
+ }
505
+ }
506
+ for (const record of readRecords(dir))
507
+ for (const [seq, entry] of record.ledger.entries()) {
508
+ const id = entry.id ?? `${record.revision}-${seq}`;
509
+ entries.set(id, { ...entry, revision: record.revision, id });
510
+ }
511
+ const reset = reconcilePlan(dir)?.ledgerResetRevision ?? 0;
512
+ const sequence = (entry) => Number(entry.id?.split("-").at(-1) ?? 0);
513
+ return [...entries.values()].filter((entry) => (entry.revision ?? 0) >= reset).sort((a, b) => (a.revision ?? 0) - (b.revision ?? 0) || sequence(a) - sequence(b));
276
514
  }
277
- function safeObject(value) {
278
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
515
+ function readLedger(repoRoot, scope) {
516
+ return readLedgerAt(ulwLoopDir(repoRoot, scope));
279
517
  }
280
- function safeString(value) {
281
- return typeof value === "string" ? value.trim() : "";
518
+
519
+ // components/ulw-loop/src/plan-io.ts
520
+ import { AsyncLocalStorage } from "node:async_hooks";
521
+ import { readdirSync as readdirSync2 } from "node:fs";
522
+
523
+ // components/ulw-loop/src/plan-missing-recovery.ts
524
+ var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
525
+ function createGoalsAction(surface) {
526
+ return surface === "omo-senpi" ? 'agentToolkit.createGoals({ brief: "<brief>" })' : ULW_LOOP_CREATE_GOALS_COMMAND;
527
+ }
528
+ function planMissingRecovery(existingSessionIds, surface = "lazycodex") {
529
+ const lines = [`Recovery: bootstrap the plan with \`${createGoalsAction(surface)}\`.`];
530
+ if (existingSessionIds.length === 0)
531
+ return { message: lines.join(`
532
+ `) };
533
+ lines.push(surface === "omo-senpi" ? `Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. The SDK is bound to the current session; resume the owning session to target its plan.` : `Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
534
+ return { message: lines.join(`
535
+ `), details: { existingSessionIds } };
536
+ }
537
+ function planMissingError(planPath, existingSessionIds, surface = "lazycodex") {
538
+ const recovery = planMissingRecovery(existingSessionIds, surface);
539
+ return new UlwLoopError(`No ulw-loop plan found at ${planPath}.
540
+ ${recovery.message}`, "ULW_LOOP_PLAN_MISSING", {
541
+ ...recovery.details === undefined ? {} : { details: recovery.details }
542
+ });
282
543
  }
283
- function normalizeStatus(value) {
284
- const status = safeString(value).toLowerCase();
285
- if (status === "complete" || status === "completed" || status === "done")
286
- return "complete";
287
- if (status === "cancelled" || status === "canceled")
288
- return "cancelled";
289
- if (status === "failed" || status === "failure")
290
- return "failed";
291
- if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
292
- return "active";
293
- return "unknown";
544
+ function sessionScopeRequiredMessage(flag, existingSessionIds) {
545
+ const lines = [
546
+ "No ulw-loop session scope: neither the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID) nor the flag names this run, and the shared .omo/ulw-loop root is never used implicitly because every session in this directory would read and overwrite it.",
547
+ `Recovery: pass the scope explicitly: \`${flag} <id>\` (subprocess, eval, and hook contexts do not inherit the session env).`
548
+ ];
549
+ if (existingSessionIds.length > 0) {
550
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}.`);
551
+ }
552
+ return lines.join(`
553
+ `);
294
554
  }
295
- function normalizeObjective2(value) {
296
- return value.replace(/\s+/g, " ").trim();
555
+ function sessionIdRequiredMessage(flag) {
556
+ return [
557
+ `${flag} requires a non-empty value.`,
558
+ "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),",
559
+ `so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
560
+ ].join(`
561
+ `);
297
562
  }
298
- function parseCodexGoalSnapshot(value) {
299
- const root = safeObject(value);
300
- const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
301
- if (goalValue === null || goalValue === undefined || goalValue === false) {
302
- return { available: false, raw: value };
563
+
564
+ // components/ulw-loop/src/state-lock.ts
565
+ import { randomUUID } from "node:crypto";
566
+ import { closeSync, ftruncateSync, mkdirSync, openSync, readFileSync as readFileSync2, statSync, unlinkSync, writeSync } from "node:fs";
567
+ import { dirname } from "node:path";
568
+ var ULW_LOOP_LOCK_TIMEOUT_CODE = "ULW_LOOP_LOCK_TIMEOUT";
569
+ var DEFAULT_TIMEOUT_MS = 1e4;
570
+ var DEFAULT_STALE_MS = 60000;
571
+ var DEFAULT_LEASE_MS = 30000;
572
+ var SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
573
+ var systemClock = {
574
+ now: Date.now,
575
+ schedule(fn, ms) {
576
+ const timer = setTimeout(fn, ms);
577
+ return {
578
+ unref: () => {
579
+ timer.unref();
580
+ },
581
+ cancel: () => clearTimeout(timer)
582
+ };
303
583
  }
304
- const goal = safeObject(goalValue);
305
- const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? root["objective"]);
306
- const status = normalizeStatus(goal["status"] ?? root["status"]);
307
- return {
308
- available: Boolean(objective || status !== "unknown"),
309
- ...objective ? { objective } : {},
310
- status,
311
- raw: value
584
+ };
585
+ async function withStateLock(lockPath, fn, options = {}) {
586
+ const holder = await acquireAsync(lockPath, options);
587
+ const clock = options.clock ?? systemClock;
588
+ let timer;
589
+ let heartbeatError;
590
+ const beat = () => {
591
+ try {
592
+ holder.record.leaseUntil = clock.now() + (options.leaseMs ?? DEFAULT_LEASE_MS);
593
+ writeRecord(holder);
594
+ schedule();
595
+ } catch (error) {
596
+ heartbeatError = error;
597
+ }
312
598
  };
599
+ const schedule = () => {
600
+ if (options.heartbeatMs === 0)
601
+ return;
602
+ timer = clock.schedule(beat, options.heartbeatMs ?? (options.leaseMs ?? DEFAULT_LEASE_MS) / 3);
603
+ timer.unref();
604
+ };
605
+ schedule();
606
+ try {
607
+ const result = await fn(holder.record.token);
608
+ if (heartbeatError !== undefined)
609
+ throw heartbeatError;
610
+ return result;
611
+ } finally {
612
+ timer?.cancel();
613
+ closeSync(holder.fd);
614
+ release(lockPath, holder.record.token);
615
+ }
313
616
  }
314
- async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
315
- if (!raw?.trim())
316
- return null;
317
- const trimmed = raw.trim();
617
+ function withStateLockSync(lockPath, fn, options = {}) {
618
+ const holder = acquireSync(lockPath, options);
318
619
  try {
319
- return parseCodexGoalSnapshot(JSON.parse(trimmed));
320
- } catch {
321
- const path = resolve(cwd, trimmed);
322
- if (!existsSync2(path)) {
323
- throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
324
- }
620
+ return fn();
621
+ } finally {
622
+ closeSync(holder.fd);
623
+ release(lockPath, holder.record.token);
624
+ }
625
+ }
626
+ function isStateLockTimeout(error) {
627
+ return error instanceof UlwLoopError && error.code === ULW_LOOP_LOCK_TIMEOUT_CODE;
628
+ }
629
+ async function acquireAsync(lockPath, options) {
630
+ const clock = options.clock ?? systemClock;
631
+ const deadline = clock.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
632
+ mkdirSync(dirname(lockPath), { recursive: true });
633
+ for (let attempt = 0;; ) {
634
+ const outcome = attemptOnce(lockPath, options, clock.now(), options.leaseMs ?? DEFAULT_LEASE_MS);
635
+ if (outcome.kind === "acquired")
636
+ return outcome.holder;
637
+ if (outcome.kind === "retry")
638
+ continue;
639
+ if (clock.now() >= deadline)
640
+ throw lockTimeout(lockPath, options);
641
+ await new Promise((resolve) => clock.schedule(resolve, backoffMs(attempt)));
642
+ attempt += 1;
643
+ }
644
+ }
645
+ function acquireSync(lockPath, options) {
646
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
647
+ mkdirSync(dirname(lockPath), { recursive: true });
648
+ for (let attempt = 0;; ) {
649
+ const outcome = attemptOnce(lockPath, options, Date.now());
650
+ if (outcome.kind === "acquired")
651
+ return outcome.holder;
652
+ if (outcome.kind === "retry")
653
+ continue;
654
+ if (Date.now() >= deadline)
655
+ throw lockTimeout(lockPath, options);
656
+ Atomics.wait(SLEEP_CELL, 0, 0, backoffMs(attempt));
657
+ attempt += 1;
658
+ }
659
+ }
660
+ function attemptOnce(lockPath, options, now, leaseMs) {
661
+ try {
662
+ const holder = tryCreate(lockPath, now, leaseMs);
663
+ if (holder !== null)
664
+ return { kind: "acquired", holder };
665
+ const snapshot = readSnapshot(lockPath, now);
666
+ if (snapshot === null)
667
+ return { kind: "retry" };
668
+ const record = snapshot.record;
669
+ const stale = record === null ? snapshot.ageMs > (options.staleMs ?? DEFAULT_STALE_MS) : !isProcessAlive(record.pid) || record.leaseUntil !== undefined && now > record.leaseUntil;
670
+ if (stale && reclaim(lockPath, snapshot.raw))
671
+ return { kind: "retry" };
672
+ return { kind: "wait" };
673
+ } catch (error) {
674
+ if (hasCode2(error, "EINTR"))
675
+ return { kind: "wait" };
676
+ throw error;
677
+ }
678
+ }
679
+ function writeRecord(holder) {
680
+ const buf = Buffer.from(JSON.stringify(holder.record));
681
+ ftruncateSync(holder.fd, 0);
682
+ let offset = 0;
683
+ while (offset < buf.length) {
684
+ const written = writeSync(holder.fd, buf, offset, buf.length - offset, offset);
685
+ if (written === 0)
686
+ throw new Error("State lock write made no progress.");
687
+ offset += written;
688
+ }
689
+ }
690
+ function tryCreate(lockPath, now, leaseMs) {
691
+ let fd;
692
+ try {
693
+ fd = openSync(lockPath, "wx");
694
+ } catch (error) {
695
+ if (hasCode2(error, "EEXIST"))
696
+ return null;
697
+ throw error;
698
+ }
699
+ const record = {
700
+ pid: process.pid,
701
+ createdAt: new Date(now).toISOString(),
702
+ token: randomUUID(),
703
+ ...leaseMs === undefined ? {} : { leaseUntil: now + leaseMs }
704
+ };
705
+ const holder = { fd, record };
706
+ try {
707
+ writeRecord(holder);
708
+ } catch (error) {
709
+ closeSync(fd);
325
710
  try {
326
- return parseCodexGoalSnapshot(JSON.parse(await readFile2(path, "utf-8")));
327
- } catch (error) {
328
- throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
711
+ unlinkSync(lockPath);
712
+ } catch (cleanup) {
713
+ if (!hasCode2(cleanup, "ENOENT"))
714
+ throw cleanup;
329
715
  }
716
+ throw error;
330
717
  }
718
+ return holder;
331
719
  }
332
- function reconcileCodexGoalSnapshot(snapshot, options) {
333
- const effectiveSnapshot = snapshot ?? { available: false, raw: null };
334
- const errors = [];
335
- const warnings = [];
336
- if (!effectiveSnapshot.available) {
337
- const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
338
- if (options.requireSnapshot)
339
- errors.push(message);
340
- else
341
- warnings.push(message);
342
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
343
- }
344
- const expected = normalizeObjective2(options.expectedObjective);
345
- const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective2(objective))].filter(Boolean));
346
- const actual = normalizeObjective2(effectiveSnapshot.objective ?? "");
347
- if (!actual) {
348
- errors.push("Codex goal snapshot is missing objective text.");
349
- } else if (!accepted.has(actual)) {
350
- errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
351
- }
352
- const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
353
- const actualStatus = effectiveSnapshot.status ?? "unknown";
354
- if (!allowed.includes(actualStatus)) {
355
- errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
720
+ function readSnapshot(lockPath, now) {
721
+ try {
722
+ const raw = readFileSync2(lockPath, "utf8");
723
+ return { raw, record: parseRecord(raw), ageMs: now - statSync(lockPath).mtimeMs };
724
+ } catch (error) {
725
+ if (hasCode2(error, "ENOENT"))
726
+ return null;
727
+ throw error;
356
728
  }
357
- if (options.requireComplete && actualStatus !== "complete") {
358
- errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
729
+ }
730
+ function parseRecord(raw) {
731
+ try {
732
+ const record = JSON.parse(raw);
733
+ if (typeof record !== "object" || record === null)
734
+ return null;
735
+ if (!("pid" in record) || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0)
736
+ return null;
737
+ if (!("createdAt" in record) || typeof record.createdAt !== "string")
738
+ return null;
739
+ if (!("token" in record) || typeof record.token !== "string" || record.token.length === 0)
740
+ return null;
741
+ return {
742
+ pid: record.pid,
743
+ createdAt: record.createdAt,
744
+ token: record.token,
745
+ ..."leaseUntil" in record && typeof record.leaseUntil === "number" ? { leaseUntil: record.leaseUntil } : {}
746
+ };
747
+ } catch (error) {
748
+ if (error instanceof SyntaxError)
749
+ return null;
750
+ throw error;
751
+ }
752
+ }
753
+ function isProcessAlive(pid) {
754
+ try {
755
+ process.kill(pid, 0);
756
+ return true;
757
+ } catch (error) {
758
+ if (hasCode2(error, "ESRCH"))
759
+ return false;
760
+ if (hasCode2(error, "EPERM"))
761
+ return true;
762
+ throw error;
763
+ }
764
+ }
765
+ function reclaim(lockPath, expectedRaw) {
766
+ const current = readSnapshot(lockPath, Date.now());
767
+ if (current === null)
768
+ return true;
769
+ if (current.raw !== expectedRaw)
770
+ return false;
771
+ try {
772
+ unlinkSync(lockPath);
773
+ } catch (error) {
774
+ if (hasCode2(error, "EPERM") || hasCode2(error, "EACCES"))
775
+ return false;
776
+ if (!hasCode2(error, "ENOENT"))
777
+ throw error;
778
+ }
779
+ return true;
780
+ }
781
+ function release(lockPath, token) {
782
+ if (readSnapshot(lockPath, Date.now())?.record?.token !== token)
783
+ return;
784
+ try {
785
+ unlinkSync(lockPath);
786
+ } catch (error) {
787
+ if (!hasCode2(error, "ENOENT"))
788
+ throw error;
359
789
  }
360
- return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
361
790
  }
362
- function formatCodexGoalReconciliation(reconciliation) {
363
- const parts = [...reconciliation.errors, ...reconciliation.warnings];
364
- return parts.join(" ");
791
+ function backoffMs(attempt) {
792
+ return Math.min(100, 5 * 2 ** attempt) + Math.random() * 5;
793
+ }
794
+ function lockTimeout(lockPath, options) {
795
+ const holder = readSnapshot(lockPath, Date.now())?.record;
796
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
797
+ const owner = holder == null ? "another process" : `pid ${holder.pid}`;
798
+ return new UlwLoopError(`ulw-loop state lock ${lockPath} is held by ${owner} for more than ${timeoutMs}ms. The lock owner is still alive. If a JS eval kernel was interrupted while writing, wait for its lease to expire (${options.leaseMs ?? DEFAULT_LEASE_MS} ms) or restart the owning senpi process; never delete a lock owned by a live process.`, ULW_LOOP_LOCK_TIMEOUT_CODE, { details: { lockPath, timeoutMs, ...holder == null ? {} : { holderPid: holder.pid } } });
799
+ }
800
+ function hasCode2(error, code) {
801
+ return error instanceof Error && "code" in error && error.code === code;
365
802
  }
366
803
 
367
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts
368
- import { createReadStream } from "node:fs";
369
- import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
370
- import { createInterface } from "node:readline";
804
+ // components/ulw-loop/src/plan-io.ts
805
+ function readLedger2(repoRoot, scope) {
806
+ const lockPath = ulwLoopStateLockPath(repoRoot, scope);
807
+ if (heldLocks.getStore()?.has(lockPath)) {
808
+ assertStateLockOwned(lockPath);
809
+ materializeSync(ulwLoopDir(repoRoot, scope));
810
+ }
811
+ return readLedger(repoRoot, scope);
812
+ }
371
813
  var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
372
814
  var LEGACY_OBJECTIVE = `Complete all ulw-loop stories listed in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}. Use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the durable audit trail.`;
373
815
  var locks = new Map;
374
- function hasCode(error, code) {
375
- return error instanceof Error && "code" in error && error.code === code;
376
- }
377
- function isLegacyEnumeratedAggregateObjective(objective) {
378
- return objective === LEGACY_OBJECTIVE || Boolean(objective?.startsWith(LEGACY_OBJECTIVE_PREFIX));
816
+ var heldLocks = new AsyncLocalStorage;
817
+ var lockOptions = new AsyncLocalStorage;
818
+ function tokenAt(lockPath) {
819
+ const raw = readOptional(lockPath);
820
+ if (raw === undefined)
821
+ return;
822
+ try {
823
+ const record = JSON.parse(raw);
824
+ return typeof record === "object" && record !== null && "token" in record && typeof record.token === "string" ? record.token : undefined;
825
+ } catch (error) {
826
+ if (error instanceof SyntaxError)
827
+ return;
828
+ throw error;
829
+ }
379
830
  }
380
- function isSteeringKind(value) {
381
- return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised" || value === "batch_updated";
831
+ function assertStateLockOwned(lockPath) {
832
+ const context = heldLocks.getStore()?.get(lockPath);
833
+ if (context === undefined)
834
+ return;
835
+ if (tokenAt(lockPath) !== context.token)
836
+ throw new UlwLoopError("The ulw-loop mutation lock changed owners.", "ULW_LOOP_LOCK_LOST");
837
+ }
838
+ function migrationEntries(plan) {
839
+ for (const context of heldLocks.getStore()?.values() ?? []) {
840
+ const entries = context.migrations.get(plan.goalsPath);
841
+ if (entries !== undefined) {
842
+ context.migrations.delete(plan.goalsPath);
843
+ return entries;
844
+ }
845
+ }
846
+ return [];
382
847
  }
383
- async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
848
+ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn, options = {}) {
384
849
  const scope = typeof scopeOrFn === "function" ? undefined : scopeOrFn;
385
850
  const fn = typeof scopeOrFn === "function" ? scopeOrFn : maybeFn;
386
851
  if (fn === undefined)
387
852
  throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
388
853
  const lockKey = `${repoRoot}\x00${ulwLoopRelativeDir(scope)}`;
854
+ const lockPath = ulwLoopStateLockPath(repoRoot, scope);
855
+ const locked = () => withStateLock(lockPath, async (token) => {
856
+ return heldLocks.run(new Map([...heldLocks.getStore() ?? [], [lockPath, { token, migrations: new Map }]]), async () => {
857
+ for (let attempt = 0;; attempt += 1) {
858
+ try {
859
+ return await fn();
860
+ } catch (error) {
861
+ if (!(error instanceof UlwLoopError) || error.code !== "ULW_LOOP_PUBLISH_CONFLICT")
862
+ throw error;
863
+ assertStateLockOwned(lockPath);
864
+ if (attempt !== 0)
865
+ throw error;
866
+ }
867
+ }
868
+ });
869
+ }, { ...lockOptions.getStore(), ...options });
389
870
  const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
390
- const run = prior.then(fn, fn);
871
+ const run = prior.then(locked, locked);
391
872
  const gate = run.then(() => {
392
873
  return;
393
874
  }, () => {
@@ -400,89 +881,144 @@ async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
400
881
  });
401
882
  return run;
402
883
  }
403
- async function readUlwLoopPlan(repoRoot, scope) {
884
+ function readUlwLoopPlanSync(repoRoot, scope) {
404
885
  const path = ulwLoopGoalsPath(repoRoot, scope);
405
- let raw;
406
- try {
407
- raw = await readFile3(path, "utf8");
408
- } catch (error) {
409
- if (!hasCode(error, "ENOENT"))
410
- throw error;
411
- throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}. Run \`omo-agent-toolkit ulw-loop create-goals ...\` first.`, "ULW_LOOP_PLAN_MISSING", { cause: error });
412
- }
413
- const parsed = JSON.parse(raw);
414
- if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
886
+ const parsed = reconcilePlan(ulwLoopDir(repoRoot, scope));
887
+ if (parsed === undefined)
888
+ throw planMissingError(repoRelative(path, repoRoot), listUlwLoopSessionIds(repoRoot));
889
+ if (parsed.version !== 1 || !Array.isArray(parsed.goals))
415
890
  throw new UlwLoopError(`Invalid ulw-loop plan at ${repoRelative(path, repoRoot)}.`, "ULW_LOOP_PLAN_INVALID");
416
- }
417
891
  const previousObjective = parsed.codexObjective;
418
- if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && isLegacyEnumeratedAggregateObjective(previousObjective)) {
419
- const now = iso();
892
+ if ((parsed.codexGoalMode ?? "per_story") === "aggregate" && previousObjective !== undefined && (previousObjective === LEGACY_OBJECTIVE || previousObjective.startsWith(LEGACY_OBJECTIVE_PREFIX))) {
420
893
  parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
421
894
  parsed.codexObjectiveAliases = [...new Set([...parsed.codexObjectiveAliases ?? [], previousObjective])];
422
- parsed.updatedAt = now;
423
- await writePlan(repoRoot, parsed, scope);
424
- await appendLedger(repoRoot, {
425
- at: now,
426
- kind: "aggregate_objective_migrated",
427
- message: "Migrated legacy enumerated aggregate Codex objective to the stable pointer objective.",
428
- before: { codexObjective: previousObjective },
429
- after: { codexObjective: parsed.codexObjective }
430
- }, scope);
895
+ heldLocks.getStore()?.get(ulwLoopStateLockPath(repoRoot, scope))?.migrations.set(parsed.goalsPath, [
896
+ {
897
+ at: iso(),
898
+ kind: "aggregate_objective_migrated",
899
+ before: { codexObjective: previousObjective },
900
+ after: { codexObjective: parsed.codexObjective }
901
+ }
902
+ ]);
431
903
  }
432
904
  return parsed;
433
905
  }
434
- async function writePlan(repoRoot, plan, scope) {
435
- await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
436
- const path = ulwLoopGoalsPath(repoRoot, scope);
437
- const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
438
- await writeFile(tmpPath, `${JSON.stringify(plan, null, 2)}
439
- `, "utf8");
440
- await rename(tmpPath, path);
906
+ async function readUlwLoopPlan(repoRoot, scope) {
907
+ if (heldLocks.getStore()?.has(ulwLoopStateLockPath(repoRoot, scope))) {
908
+ assertStateLockOwned(ulwLoopStateLockPath(repoRoot, scope));
909
+ await materialize(ulwLoopDir(repoRoot, scope));
910
+ }
911
+ return readUlwLoopPlanSync(repoRoot, scope);
912
+ }
913
+ function listUlwLoopSessionIds(repoRoot) {
914
+ try {
915
+ return readdirSync2(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
916
+ } catch {
917
+ return [];
918
+ }
441
919
  }
442
- async function appendLedger(repoRoot, entry, scope) {
443
- await appendLedgerEntries(repoRoot, [entry], scope);
920
+ function isSteeringKind(value) {
921
+ return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised" || value === "batch_updated";
444
922
  }
445
- async function appendLedgerEntries(repoRoot, entries, scope) {
446
- if (entries.length === 0)
447
- return;
448
- await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
449
- await appendFile(ulwLoopLedgerPath(repoRoot, scope), `${entries.map((entry) => JSON.stringify(entry)).join(`
450
- `)}
451
- `, "utf8");
923
+ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
924
+ return readLedger2(repoRoot, scope).find((entry) => isSteeringKind(entry.kind) && entry.steering?.invariant.accepted === true && (entry.idempotencyKey === key || entry.steering.idempotencyKey === key || entry.steering.promptSignature === key));
452
925
  }
453
- async function* ledgerLines(repoRoot, scope) {
454
- const stream = createReadStream(ulwLoopLedgerPath(repoRoot, scope), { encoding: "utf8" });
455
- const lines = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
926
+
927
+ // components/ulw-loop/src/plan-commit.ts
928
+ var hooks = new AsyncLocalStorage2;
929
+ async function beforePlanMutation() {
930
+ await hooks.getStore()?.beforeMutation?.();
931
+ }
932
+ async function writeViewFile(path, content) {
933
+ await (hooks.getStore()?.writeView ?? writeFile)(path, content);
934
+ }
935
+ async function commit(repoRoot, scope, mutation) {
936
+ const { plan } = mutation;
937
+ const dir = ulwLoopDir(repoRoot, scope);
938
+ assertStateLockOwned(ulwLoopStateLockPath(repoRoot, scope));
939
+ await hooks.getStore()?.beforeCommit?.();
940
+ const next = (plan.revision ?? 0) + 1;
941
+ const brief = plan.brief ?? readOptional(join4(dir, "brief.md")) ?? "";
942
+ const entries = [...migrationEntries(plan), ...mutation.entries].map((entry, seq) => ({
943
+ ...entry,
944
+ revision: next,
945
+ id: `${next}-${seq}`
946
+ }));
947
+ const record = {
948
+ version: 1,
949
+ revision: next,
950
+ plan: { ...plan, revision: next, brief },
951
+ ledger: entries
952
+ };
953
+ mkdirSync2(join4(dir, "revisions"), { recursive: true });
954
+ mkdirSync2(join4(dir, "tmp"), { recursive: true });
955
+ const temp = join4(dir, "tmp", `${process.pid}-${randomUUID2()}.json`);
456
956
  try {
457
- for await (const line of lines) {
458
- if (line.trim().length > 0)
459
- yield line;
460
- }
461
- } catch (error) {
462
- if (!hasCode(error, "ENOENT"))
957
+ writeFileSync(temp, `${JSON.stringify(record)}
958
+ `, { flag: "wx" });
959
+ try {
960
+ (hooks.getStore()?.link ?? linkSync)(temp, join4(dir, "revisions", `${String(next).padStart(8, "0")}.json`));
961
+ } catch (error) {
962
+ if (hasCode(error, "EEXIST"))
963
+ throw new UlwLoopError("Another writer published the read revision.", "ULW_LOOP_PUBLISH_CONFLICT", {
964
+ cause: error
965
+ });
966
+ if (["EPERM", "ENOTSUP", "EXDEV"].some((code) => hasCode(error, code)))
967
+ throw new UlwLoopError("Immutable ulw-loop publication requires atomic hard links.", "ULW_LOOP_PUBLISH_UNSUPPORTED_FS", { cause: error });
463
968
  throw error;
969
+ }
464
970
  } finally {
465
- lines.close();
466
- stream.destroy();
971
+ rmSync(temp, { force: true });
467
972
  }
973
+ plan.revision = next;
974
+ plan.brief = brief;
975
+ for (const [index, entry] of mutation.entries.entries())
976
+ Object.assign(entry, entries[index + entries.length - mutation.entries.length]);
977
+ await hooks.getStore()?.afterCommit?.();
978
+ await materialize(dir);
979
+ }
980
+ function viewContents(dir) {
981
+ const plan = reconcilePlan(dir);
982
+ if (plan === undefined || readRecords(dir).length === 0)
983
+ return [];
984
+ const ledger = readLedgerAt(dir);
985
+ assertProjectionComplete(plan, ledger);
986
+ const views = [
987
+ ["goals.json", `${JSON.stringify(plan, null, 2)}
988
+ `],
989
+ ["ledger.jsonl", ledger.length === 0 ? "" : `${ledger.map((entry) => JSON.stringify(entry)).join(`
990
+ `)}
991
+ `],
992
+ ["brief.md", plan.brief ?? readOptional(join4(dir, "brief.md")) ?? ""]
993
+ ];
994
+ return views.filter(([name, content]) => readOptional(join4(dir, name)) !== content);
468
995
  }
469
- async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
470
- const probe = JSON.stringify(key);
471
- for await (const line of ledgerLines(repoRoot, scope)) {
472
- if (!line.includes(probe))
473
- continue;
474
- const entry = JSON.parse(line);
475
- if (!isSteeringKind(entry.kind))
476
- continue;
477
- if (entry.steering?.invariant.accepted !== true)
478
- continue;
479
- if (entry.idempotencyKey === key || entry.steering.idempotencyKey === key || entry.steering.promptSignature === key)
480
- return entry;
996
+ function materializeSync(dir) {
997
+ for (const [name, content] of viewContents(dir)) {
998
+ mkdirSync2(join4(dir, "tmp"), { recursive: true });
999
+ const temp = join4(dir, "tmp", `${process.pid}-${randomUUID2()}-${name}`);
1000
+ try {
1001
+ writeFileSync(temp, content, { flag: "wx" });
1002
+ renameSync(temp, join4(dir, name));
1003
+ } finally {
1004
+ rmSync(temp, { force: true });
1005
+ }
1006
+ }
1007
+ }
1008
+ async function materialize(dir) {
1009
+ for (const [name, content] of viewContents(dir)) {
1010
+ mkdirSync2(join4(dir, "tmp"), { recursive: true });
1011
+ const temp = join4(dir, "tmp", `${process.pid}-${randomUUID2()}-${name}`);
1012
+ try {
1013
+ await writeViewFile(temp, content);
1014
+ await rename(temp, join4(dir, name));
1015
+ } finally {
1016
+ rmSync(temp, { force: true });
1017
+ }
481
1018
  }
482
- return;
483
1019
  }
484
1020
 
485
- // packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts
1021
+ // components/ulw-loop/src/evidence.ts
486
1022
  function ulwLoopFail(message, code, details) {
487
1023
  throw new UlwLoopError(message, code, { details });
488
1024
  }
@@ -519,6 +1055,7 @@ async function recordEvidence(repoRoot, args, scope) {
519
1055
  const goal = findGoal(plan, args.goalId);
520
1056
  const criterion = findCriterion(goal, args.criterionId);
521
1057
  const evidence = nonEmptyEvidence(args.evidence);
1058
+ const artifacts = resolveEvidenceArtifacts(repoRoot, args.artifacts);
522
1059
  const kind = ledgerKind(args.status);
523
1060
  const prevStatus = criterion.status;
524
1061
  const capturedAt = iso();
@@ -527,9 +1064,12 @@ async function recordEvidence(repoRoot, args, scope) {
527
1064
  criterion.capturedAt = capturedAt;
528
1065
  if (args.notes !== undefined)
529
1066
  criterion.notes = args.notes;
1067
+ if (artifacts !== undefined)
1068
+ criterion.artifacts = artifacts;
1069
+ else
1070
+ delete criterion.artifacts;
530
1071
  goal.updatedAt = capturedAt;
531
1072
  plan.updatedAt = capturedAt;
532
- await writePlan(repoRoot, plan, scope);
533
1073
  const ledgerEntry = {
534
1074
  at: capturedAt,
535
1075
  kind,
@@ -538,10 +1078,11 @@ async function recordEvidence(repoRoot, args, scope) {
538
1078
  criterionStatus: args.status,
539
1079
  evidence,
540
1080
  capturedEvidence: evidence,
1081
+ ...artifacts === undefined ? {} : { artifacts },
541
1082
  before: { status: prevStatus },
542
1083
  after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus }
543
1084
  };
544
- await appendLedger(repoRoot, ledgerEntry, scope);
1085
+ await commit(repoRoot, scope, { plan, entries: [ledgerEntry] });
545
1086
  return { plan, goal, criterion, ledgerEntry };
546
1087
  });
547
1088
  }
@@ -588,57 +1129,216 @@ function requireEssentialCriteriaPass(goal) {
588
1129
  });
589
1130
  }
590
1131
 
591
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
592
- import { resolve as resolve2 } from "node:path";
1132
+ // components/ulw-loop/src/quality-gate-artifacts.ts
1133
+ import { resolve as resolve3 } from "node:path";
593
1134
 
594
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts
595
- var PLACEHOLDER_PATTERN = /^(?:placeholder|todo|tbd|n\/a|stub)$/i;
1135
+ // components/ulw-loop/src/quality-gate-fields.ts
1136
+ var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
1137
+ var activeCollector;
1138
+ function withQualityGateCollector(operation) {
1139
+ const previous = activeCollector;
1140
+ const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
1141
+ activeCollector = collector;
1142
+ try {
1143
+ const result = operation();
1144
+ if (collector.defects.length === 0)
1145
+ return result;
1146
+ throwQualityGateDefects(collector.defects);
1147
+ } finally {
1148
+ activeCollector = previous;
1149
+ }
1150
+ }
1151
+ function throwQualityGateDefects(defects) {
1152
+ const uniqueDefects = [
1153
+ ...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
1154
+ ];
1155
+ const fields = uniqueDefects.slice(0, 25);
1156
+ const truncated = uniqueDefects.length > fields.length;
1157
+ const message = [
1158
+ `Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
1159
+ ...fields.map((item) => `- ${item.field}: ${item.message}`)
1160
+ ].join(`
1161
+ `);
1162
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
1163
+ details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
1164
+ });
1165
+ }
596
1166
  function invalid(message, field) {
597
- throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
1167
+ if (activeCollector === undefined)
1168
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
1169
+ activeCollector.defects.push({ field, message });
1170
+ activeCollector.poisonedFields.add(field);
1171
+ return;
1172
+ }
1173
+ function isPoisoned(field) {
1174
+ return activeCollector?.poisonedFields.has(field) ?? false;
1175
+ }
1176
+ function markPoisonedArtifactKind(id) {
1177
+ activeCollector?.poisonedArtifactKinds.add(id);
1178
+ }
1179
+ function isPoisonedArtifactKind(id) {
1180
+ return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
598
1181
  }
599
1182
  function isRecord(value) {
600
1183
  return typeof value === "object" && value !== null && !Array.isArray(value);
601
1184
  }
602
1185
  function section(value, field) {
603
- return isRecord(value) ? value : invalid(`Final quality gate is missing ${field} evidence.`, field);
1186
+ if (isRecord(value))
1187
+ return value;
1188
+ invalid(`Final quality gate is missing ${field} evidence (accepted input: a bare object with manualQa/gateReview/iteration/criteriaCoverage and optional codeReview, or the same object under a top-level "qualityGate" key).`, field);
1189
+ return {};
604
1190
  }
605
1191
  function textField(value, field) {
606
- if (typeof value !== "string" || value.trim() === "")
1192
+ if (typeof value !== "string" || value.trim() === "") {
607
1193
  invalid(`Final quality gate requires non-empty ${field}.`, field);
1194
+ return "";
1195
+ }
608
1196
  const trimmed = value.trim();
609
1197
  if (PLACEHOLDER_PATTERN.test(trimmed))
610
1198
  invalid(`Final quality gate rejects placeholder ${field}.`, field);
611
1199
  return trimmed;
612
1200
  }
613
1201
  function numberField(value, field) {
614
- return typeof value === "number" && Number.isFinite(value) ? value : invalid(`Final quality gate requires numeric ${field}.`, field);
1202
+ if (typeof value === "number" && Number.isFinite(value))
1203
+ return value;
1204
+ invalid(`Final quality gate requires numeric ${field}.`, field);
1205
+ return 0;
615
1206
  }
616
1207
  function stringArray(value, field) {
617
- if (!Array.isArray(value) || value.length === 0)
618
- return invalid(`Final quality gate requires ${field}.`, field);
1208
+ if (!Array.isArray(value) || value.length === 0) {
1209
+ invalid(`Final quality gate requires ${field}.`, field);
1210
+ return [];
1211
+ }
619
1212
  return value.map((item) => textField(item, field));
620
1213
  }
621
1214
  function emptyBlockers(value, field) {
622
1215
  if (Array.isArray(value) && value.length === 0)
623
1216
  return [];
624
1217
  invalid(`${field} must be empty.`, field);
1218
+ return [];
625
1219
  }
626
1220
  function literal(value, expected, field) {
627
1221
  if (value === expected)
628
1222
  return expected;
629
1223
  invalid(`${field} must be ${String(expected)}.`, field);
1224
+ return expected;
1225
+ }
1226
+
1227
+ // components/ulw-loop/src/quality-gate-artifacts.ts
1228
+ var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
1229
+ var SUPPORTED_KINDS = [
1230
+ "cli-transcript",
1231
+ "log",
1232
+ "screenshot",
1233
+ "image",
1234
+ "http-dump",
1235
+ "data-diff"
1236
+ ];
1237
+ var COMPATIBLE_KINDS = {
1238
+ cli: ["cli-transcript", "log"],
1239
+ tmux: ["cli-transcript", "log"],
1240
+ http: ["http-dump"],
1241
+ browser: ["screenshot", "image"],
1242
+ gui: ["screenshot", "image"],
1243
+ data: ["data-diff"]
1244
+ };
1245
+ function isSupportedSurface(value) {
1246
+ return SUPPORTED_SURFACES.some((surface) => surface === value);
1247
+ }
1248
+ function isSupportedKind(value) {
1249
+ return SUPPORTED_KINDS.some((kind) => kind === value);
1250
+ }
1251
+ function surfaceField(value, field) {
1252
+ if (isSupportedSurface(value))
1253
+ return value;
1254
+ invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
1255
+ return "cli";
1256
+ }
1257
+ function kindField(value, field) {
1258
+ if (isSupportedKind(value))
1259
+ return value;
1260
+ invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
1261
+ return "log";
1262
+ }
1263
+ function compatibleKindsFor(surface) {
1264
+ return COMPATIBLE_KINDS[surface];
1265
+ }
1266
+ function artifactCompatible(surface, kind) {
1267
+ return compatibleKindsFor(surface).includes(kind);
1268
+ }
1269
+ function checkFile(path, field, opts) {
1270
+ if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
1271
+ return;
1272
+ const absolute = resolve3(opts.repoRoot, path);
1273
+ if (!opts.fs.existsSync(absolute)) {
1274
+ invalid(`${field} must point to an existing artifact.`, field);
1275
+ return;
1276
+ }
1277
+ if (opts.fs.statSync(absolute).size <= 0)
1278
+ invalid(`${field} must point to a non-empty artifact.`, field);
1279
+ if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve3(opts.repoRoot, opts.currentAttemptDir)))
1280
+ invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
1281
+ }
1282
+ function artifactMap(refs) {
1283
+ const byId = new Map;
1284
+ for (const ref of refs) {
1285
+ if (byId.has(ref.id))
1286
+ invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
1287
+ byId.set(ref.id, ref);
1288
+ }
1289
+ return byId;
1290
+ }
1291
+ function parseArtifactRefs(value, opts) {
1292
+ if (!Array.isArray(value) || value.length === 0) {
1293
+ invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
1294
+ return [];
1295
+ }
1296
+ return value.flatMap((item, index) => {
1297
+ const prefix = `manualQa.artifactRefs[${index}]`;
1298
+ const ref = section(item, prefix);
1299
+ if (isPoisoned(prefix))
1300
+ return [];
1301
+ const pathField = `${prefix}.path`;
1302
+ const idField = `${prefix}.id`;
1303
+ const kindFieldName = `${prefix}.kind`;
1304
+ const descriptionField = `${prefix}.description`;
1305
+ const path = textField(ref["path"], pathField);
1306
+ const id = textField(ref["id"], idField);
1307
+ const kind = kindField(ref["kind"], kindFieldName);
1308
+ if (isPoisoned(kindFieldName))
1309
+ markPoisonedArtifactKind(id);
1310
+ const description = textField(ref["description"], descriptionField);
1311
+ checkFile(path, pathField, opts);
1312
+ return [{ id, kind, description, path }];
1313
+ });
1314
+ }
1315
+ function referencedArtifacts(value, field, byId) {
1316
+ const ids = stringArray(value, field);
1317
+ if (isPoisoned(field))
1318
+ return [];
1319
+ return ids.flatMap((id) => {
1320
+ const artifact = byId.get(id);
1321
+ if (artifact === undefined) {
1322
+ invalid(`${field} references unknown artifact ${id}.`, field);
1323
+ return [];
1324
+ }
1325
+ return [artifact];
1326
+ });
630
1327
  }
631
1328
 
632
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts
1329
+ // components/ulw-loop/src/quality-gate-verdicts.ts
633
1330
  function passedVerdict(value, field) {
634
- if (value === "not_applicable")
1331
+ if (value === "not_applicable") {
635
1332
  invalid(`${field} must not be not_applicable.`, field);
1333
+ return "passed";
1334
+ }
636
1335
  return literal(value, "passed", field);
637
1336
  }
638
1337
  function codeQualityStatusField(value, field) {
639
1338
  if (value === "CLEAR" || value === "WATCH")
640
1339
  return value;
641
1340
  invalid(`${field} must be CLEAR or WATCH.`, field);
1341
+ return "CLEAR";
642
1342
  }
643
1343
  function adversarialVerdict(row, field) {
644
1344
  const value = row["verdict"];
@@ -648,9 +1348,102 @@ function adversarialVerdict(row, field) {
648
1348
  return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
649
1349
  }
650
1350
  invalid(`${field} must be passed or not_applicable with a reason.`, field);
1351
+ return { verdict: "passed" };
1352
+ }
1353
+
1354
+ // components/ulw-loop/src/surface.ts
1355
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
1356
+ import { dirname as dirname2, join as join5 } from "node:path";
1357
+ import { fileURLToPath } from "node:url";
1358
+ var REVIEWER_ROLES_BY_SURFACE = {
1359
+ lazycodex: {
1360
+ codeReview: "lazycodex-code-reviewer",
1361
+ manualQa: "lazycodex-qa-executor",
1362
+ gateReview: "lazycodex-gate-reviewer"
1363
+ },
1364
+ "omo-senpi": {
1365
+ codeReview: "omo-native-code-reviewer",
1366
+ manualQa: "omo-native-qa-executor",
1367
+ gateReview: "omo-native-gate-reviewer"
1368
+ }
1369
+ };
1370
+ var LEGACY_REVIEWER_AGENT_ALIASES = {
1371
+ "omo-senpi-code-reviewer": REVIEWER_ROLES_BY_SURFACE["omo-senpi"].codeReview,
1372
+ "omo-senpi-qa-executor": REVIEWER_ROLES_BY_SURFACE["omo-senpi"].manualQa,
1373
+ "omo-senpi-gate-reviewer": REVIEWER_ROLES_BY_SURFACE["omo-senpi"].gateReview
1374
+ };
1375
+ function canonicalReviewerAgentName(reviewer) {
1376
+ return LEGACY_REVIEWER_AGENT_ALIASES[reviewer] ?? reviewer;
1377
+ }
1378
+ var CANONICAL_GATE_REVIEW_NAMES = Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview);
1379
+ var GATE_REVIEWER_AGENT_NAMES = new Set([
1380
+ ...CANONICAL_GATE_REVIEW_NAMES,
1381
+ ...Object.entries(LEGACY_REVIEWER_AGENT_ALIASES).filter(([, canonical]) => CANONICAL_GATE_REVIEW_NAMES.includes(canonical)).map(([legacy]) => legacy)
1382
+ ]);
1383
+ var REQUIRED_GATE_SECTIONS_BY_SURFACE = {
1384
+ lazycodex: ["manualQa", "gateReview", "iteration", "criteriaCoverage"],
1385
+ "omo-senpi": ["manualQa", "gateReview", "iteration", "criteriaCoverage"]
1386
+ };
1387
+ var OPTIONAL_GATE_SECTIONS_BY_SURFACE = {
1388
+ lazycodex: ["codeReview"],
1389
+ "omo-senpi": []
1390
+ };
1391
+ var GATE_SECTION_BY_ACCEPTOR = {
1392
+ lazycodex: {
1393
+ codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview, "main-session"],
1394
+ manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa, "main-session"],
1395
+ gateReview: [
1396
+ REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview,
1397
+ "category:deep-high",
1398
+ "category:deep-low",
1399
+ "category:deep",
1400
+ "category:unspecified-high",
1401
+ "category:unspecified-low",
1402
+ "main-session"
1403
+ ]
1404
+ },
1405
+ "omo-senpi": {
1406
+ manualQa: ["main-session"],
1407
+ gateReview: [
1408
+ "category:deep-high",
1409
+ "category:deep-low",
1410
+ "category:deep",
1411
+ "category:unspecified-high",
1412
+ "category:unspecified-low"
1413
+ ]
1414
+ }
1415
+ };
1416
+ function reviewerRolesFor(surface) {
1417
+ return REVIEWER_ROLES_BY_SURFACE[surface];
1418
+ }
1419
+ var SURFACE_MARKER_FILENAME = "surface.json";
1420
+ var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
1421
+ function parseSurface(value) {
1422
+ return value === "lazycodex" || value === "omo-senpi" ? value : null;
1423
+ }
1424
+ function resolveToolkitSurface(options) {
1425
+ const env = options?.env ?? process.env;
1426
+ const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
1427
+ if (fromEnv !== null)
1428
+ return fromEnv;
1429
+ const entryDir = options?.entryDir ?? dirname2(fileURLToPath(import.meta.url));
1430
+ const markerPath = join5(entryDir, SURFACE_MARKER_FILENAME);
1431
+ return readMarkerSurface(markerPath) ?? "lazycodex";
1432
+ }
1433
+ function readMarkerSurface(markerPath) {
1434
+ try {
1435
+ if (!existsSync4(markerPath))
1436
+ return null;
1437
+ const parsed = JSON.parse(readFileSync3(markerPath, "utf8"));
1438
+ return parseSurface(parsed["surface"]);
1439
+ } catch (error) {
1440
+ if (error instanceof Error)
1441
+ return null;
1442
+ throw error;
1443
+ }
651
1444
  }
652
1445
 
653
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-blockers.ts
1446
+ // components/ulw-loop/src/quality-gate-blockers.ts
654
1447
  var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
655
1448
  var URL_PATTERN = /https?:\/\/\S+/g;
656
1449
  var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
@@ -692,131 +1485,68 @@ function clearGoalBlockerFields(goal) {
692
1485
  Reflect.deleteProperty(goal, key);
693
1486
  }
694
1487
 
695
- // packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts
696
- var REVIEWER_ROLES = {
697
- codeReview: "lazycodex-code-reviewer",
698
- manualQa: "lazycodex-qa-executor",
699
- gateReview: "lazycodex-gate-reviewer"
700
- };
701
- function reviewerRoleField(value, expected, field) {
702
- const actual = textField(value, field);
703
- if (actual !== expected)
704
- invalid(`${field} must be ${expected}.`, field);
705
- return expected;
706
- }
707
- function surfaceField(value, field) {
708
- if (value === "cli" || value === "http" || value === "tmux" || value === "browser" || value === "gui" || value === "data")
709
- return value;
710
- invalid(`${field} must be a supported manual QA surface.`, field);
711
- }
712
- function kindField(value, field) {
713
- if (value === "cli-transcript" || value === "log" || value === "screenshot" || value === "image" || value === "http-dump" || value === "data-diff")
714
- return value;
715
- invalid(`${field} must be a supported artifact kind.`, field);
716
- }
717
- function artifactCompatible(surface, kind) {
718
- switch (surface) {
719
- case "cli":
720
- case "tmux":
721
- return kind === "cli-transcript" || kind === "log";
722
- case "http":
723
- return kind === "http-dump";
724
- case "browser":
725
- case "gui":
726
- return kind === "screenshot" || kind === "image";
727
- case "data":
728
- return kind === "data-diff";
729
- default:
730
- invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
731
- }
732
- }
733
- function checkFile(path, field, opts) {
734
- if (opts === undefined)
735
- return;
736
- const absolute = resolve2(opts.repoRoot, path);
737
- if (!opts.fs.existsSync(absolute))
738
- invalid(`${field} must point to an existing artifact.`, field);
739
- if (opts.fs.statSync(absolute).size <= 0)
740
- invalid(`${field} must point to a non-empty artifact.`, field);
741
- if (opts.currentAttemptDir !== undefined) {
742
- const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
743
- if (!isWithinAttemptDir(absolute, attemptRoot))
744
- invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
745
- }
746
- }
747
- function artifactMap(refs) {
748
- const byId = new Map;
749
- for (const ref of refs) {
750
- if (byId.has(ref.id))
751
- invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
752
- byId.set(ref.id, ref);
1488
+ // components/ulw-loop/src/quality-gate.ts
1489
+ function reviewerAcceptorField(value, surface, sectionName) {
1490
+ const field = `${sectionName}.by`;
1491
+ const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
1492
+ if (typeof value !== "string" || value.trim() === "") {
1493
+ textField(value, field);
1494
+ return accepted?.[0] ?? "";
753
1495
  }
754
- return byId;
755
- }
756
- function parseArtifactRefs(value, opts) {
757
- if (!Array.isArray(value) || value.length === 0)
758
- invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
759
- return value.map((item, index) => {
760
- const ref = section(item, `manualQa.artifactRefs[${index}]`);
761
- const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
762
- checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
763
- return {
764
- id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
765
- kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
766
- description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
767
- path
768
- };
769
- });
770
- }
771
- function referencedArtifacts(value, field, byId) {
772
- return stringArray(value, field).map((id) => {
773
- const artifact = byId.get(id);
774
- if (artifact === undefined)
775
- invalid(`${field} references unknown artifact ${id}.`, field);
776
- return artifact;
777
- });
1496
+ const actual = textField(value, field);
1497
+ if (accepted === undefined || !accepted.includes(actual))
1498
+ invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
1499
+ return actual;
778
1500
  }
779
1501
  function validateQualityGate(input, opts) {
780
- const gate = section(input, "qualityGate");
781
- const codeReview = section(gate["codeReview"], "codeReview");
1502
+ return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
1503
+ }
1504
+ function validateQualityGateUncollected(input, opts) {
1505
+ const surface = opts?.reviewerSurface ?? "lazycodex";
1506
+ const raw = input;
1507
+ const gate = section(raw && typeof raw === "object" && "qualityGate" in raw ? raw["qualityGate"] : input, "qualityGate");
1508
+ for (const name of REQUIRED_GATE_SECTIONS_BY_SURFACE[surface])
1509
+ section(gate[name], name);
1510
+ for (const name of Object.keys(gate)) {
1511
+ if (name === "codeReview" && !OPTIONAL_GATE_SECTIONS_BY_SURFACE[surface].includes(name))
1512
+ invalid("omo-senpi gate has no codeReview lane.", name);
1513
+ }
1514
+ if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
1515
+ invalid("omo-senpi gate has no codeReview lane.", "codeReview");
782
1516
  const manualQa = section(gate["manualQa"], "manualQa");
783
1517
  const gateReview = section(gate["gateReview"], "gateReview");
784
1518
  const iteration = section(gate["iteration"], "iteration");
785
1519
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
1520
+ const codeReview = gate["codeReview"] !== undefined ? section(gate["codeReview"], "codeReview") : undefined;
1521
+ const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
1522
+ const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
1523
+ const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
1524
+ const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
786
1525
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
787
1526
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
788
- if (passCount < totalCriteria)
1527
+ if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
789
1528
  invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
790
1529
  const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
791
1530
  const byId = artifactMap(artifactRefs);
792
1531
  const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
793
1532
  const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
794
- const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
795
1533
  const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
796
- checkFile(codeReportPath, "codeReview.reportPath", opts);
797
- checkFile(gateReportPath, "gateReview.reportPath", opts);
798
- return {
799
- codeReview: {
800
- by: reviewerRoleField(codeReview["by"], REVIEWER_ROLES.codeReview, "codeReview.by"),
801
- recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
802
- codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
803
- reportPath: codeReportPath,
804
- evidence: textField(codeReview["evidence"], "codeReview.evidence"),
805
- blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
806
- },
1534
+ if (!isPoisoned("gateReview.reportPath"))
1535
+ checkFile(gateReportPath, "gateReview.reportPath", opts);
1536
+ const common = {
807
1537
  manualQa: {
808
- by: reviewerRoleField(manualQa["by"], REVIEWER_ROLES.manualQa, "manualQa.by"),
1538
+ by: manualQaBy,
809
1539
  status: literal(manualQa["status"], "passed", "manualQa.status"),
810
- evidence: textField(manualQa["evidence"], "manualQa.evidence"),
1540
+ evidence: manualQaEvidence,
811
1541
  surfaceEvidence,
812
1542
  adversarialCases,
813
1543
  artifactRefs
814
1544
  },
815
1545
  gateReview: {
816
- by: reviewerRoleField(gateReview["by"], REVIEWER_ROLES.gateReview, "gateReview.by"),
1546
+ by: gateReviewBy,
817
1547
  recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
818
1548
  reportPath: gateReportPath,
819
- evidence: textField(gateReview["evidence"], "gateReview.evidence"),
1549
+ evidence: gateReviewEvidence,
820
1550
  blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
821
1551
  },
822
1552
  iteration: {
@@ -834,17 +1564,41 @@ function validateQualityGate(input, opts) {
834
1564
  adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
835
1565
  }
836
1566
  };
1567
+ if (surface === "omo-senpi")
1568
+ return { surface, ...common };
1569
+ if (codeReview === undefined)
1570
+ return { surface, ...common };
1571
+ const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
1572
+ checkFile(codeReportPath, "codeReview.reportPath", opts);
1573
+ return {
1574
+ surface,
1575
+ ...common,
1576
+ codeReview: {
1577
+ by: reviewerAcceptorField(codeReview["by"], surface, "codeReview"),
1578
+ recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
1579
+ codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
1580
+ reportPath: codeReportPath,
1581
+ evidence: textField(codeReview["evidence"], "codeReview.evidence"),
1582
+ blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
1583
+ }
1584
+ };
837
1585
  }
838
1586
  function parseSurfaceEvidence(value, byId) {
839
- if (!Array.isArray(value) || value.length === 0)
1587
+ if (!Array.isArray(value) || value.length === 0) {
840
1588
  invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
841
- return value.map((item, index) => {
1589
+ return [];
1590
+ }
1591
+ return value.flatMap((item, index) => {
842
1592
  const row = section(item, `manualQa.surfaceEvidence[${index}]`);
1593
+ if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
1594
+ return [];
843
1595
  const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
844
1596
  const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
845
1597
  for (const artifact of artifacts) {
1598
+ if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
1599
+ continue;
846
1600
  if (!artifactCompatible(surface, artifact.kind)) {
847
- invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
1601
+ invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
848
1602
  }
849
1603
  }
850
1604
  return {
@@ -858,10 +1612,14 @@ function parseSurfaceEvidence(value, byId) {
858
1612
  });
859
1613
  }
860
1614
  function parseAdversarialCases(value, byId) {
861
- if (!Array.isArray(value) || value.length === 0)
1615
+ if (!Array.isArray(value) || value.length === 0) {
862
1616
  invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
863
- return value.map((item, index) => {
1617
+ return [];
1618
+ }
1619
+ return value.flatMap((item, index) => {
864
1620
  const row = section(item, `manualQa.adversarialCases[${index}]`);
1621
+ if (isPoisoned(`manualQa.adversarialCases[${index}]`))
1622
+ return [];
865
1623
  const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
866
1624
  const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
867
1625
  return {
@@ -876,8 +1634,8 @@ function parseAdversarialCases(value, byId) {
876
1634
  });
877
1635
  }
878
1636
 
879
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-arg-parser.ts
880
- import { readFile as readFile4 } from "node:fs/promises";
1637
+ // components/ulw-loop/src/cli-arg-parser.ts
1638
+ import { readFile as readFile2 } from "node:fs/promises";
881
1639
  var VALUE_FLAGS = new Set("--brief --brief-file --session-id --codex-goal-mode --validation-batch-json --goal --goal-id --criterion-id --status --evidence --notes --codex-goal-json --quality-gate-json --kind --rationale --title --objective --target-goal-id --source --after-json --directive-json --directive-file --idempotency-key --proposals-json".split(" "));
882
1640
  var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
883
1641
  function hasFlag(argv, flag) {
@@ -925,7 +1683,7 @@ async function readJsonInput(value) {
925
1683
  if (value === undefined)
926
1684
  return;
927
1685
  try {
928
- return JSON.parse(looksLikeJson(value) ? value : await readFile4(value, "utf8"));
1686
+ return JSON.parse(looksLikeJson(value) ? value : await readFile2(value, "utf8"));
929
1687
  } catch (error) {
930
1688
  const message = error instanceof Error ? error.message : "unknown error";
931
1689
  throw new UlwLoopError(`Invalid JSON input: ${message}`, "ULW_LOOP_JSON_INPUT_INVALID", { cause: error });
@@ -934,13 +1692,13 @@ async function readJsonInput(value) {
934
1692
  async function parseCodexGoalJson(value) {
935
1693
  if (value === undefined)
936
1694
  return;
937
- const raw = looksLikeJson(value) ? value : await readFile4(value, "utf8");
938
1695
  try {
1696
+ const raw = looksLikeJson(value) ? value : await readFile2(value, "utf8");
939
1697
  JSON.parse(raw);
940
1698
  return raw;
941
1699
  } catch (error) {
942
1700
  const message = error instanceof Error ? error.message : "unknown error";
943
- throw new UlwLoopError(`Invalid --codex-goal-json: ${message}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
1701
+ throw new UlwLoopError(`Invalid --codex-goal-json: ${looksLikeJson(value) ? message : "neither valid JSON nor a readable path"}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
944
1702
  }
945
1703
  }
946
1704
  function required(argv, flag, code) {
@@ -967,7 +1725,7 @@ function parseRecordEvidenceArgs(argv) {
967
1725
  return notes ? { ...result, notes } : result;
968
1726
  }
969
1727
 
970
- // packages/omo-codex/plugin/components/ulw-loop/src/validation-batch.ts
1728
+ // components/ulw-loop/src/validation-batch.ts
971
1729
  function isObject(value) {
972
1730
  return typeof value === "object" && value !== null && !Array.isArray(value);
973
1731
  }
@@ -987,23 +1745,23 @@ async function parseValidationBatches(input, goals) {
987
1745
  if (raw === undefined)
988
1746
  return;
989
1747
  if (!Array.isArray(raw))
990
- fail("--validation-batch-json must be a JSON array.");
1748
+ fail2("--validation-batch-json must be a JSON array.");
991
1749
  const batches = raw.map(batchFromObject);
992
1750
  validateBatches(batches, goals);
993
1751
  return batches;
994
1752
  }
995
1753
  function batchFromObject(value) {
996
1754
  if (!isObject(value))
997
- fail("validation batch entries must be objects.");
1755
+ fail2("validation batch entries must be objects.");
998
1756
  const batchId = text(value, "batchId");
999
1757
  const memberIds = strings(value, "memberIds");
1000
1758
  const finalGoalId = text(value, "finalGoalId");
1001
1759
  if (batchId === undefined)
1002
- fail("validation batch requires batchId.");
1760
+ fail2("validation batch requires batchId.");
1003
1761
  if (memberIds === undefined || memberIds.length < 2)
1004
- fail("validation batch requires at least two memberIds.");
1762
+ fail2("validation batch requires at least two memberIds.");
1005
1763
  if (finalGoalId === undefined)
1006
- fail("validation batch requires finalGoalId.");
1764
+ fail2("validation batch requires finalGoalId.");
1007
1765
  return { batchId, memberIds, finalGoalId };
1008
1766
  }
1009
1767
  function validateBatches(batches, goals) {
@@ -1012,17 +1770,17 @@ function validateBatches(batches, goals) {
1012
1770
  const members = new Set;
1013
1771
  for (const batch of batches) {
1014
1772
  if (batchIds.has(batch.batchId))
1015
- fail(`duplicate validation batch id: ${batch.batchId}.`);
1773
+ fail2(`duplicate validation batch id: ${batch.batchId}.`);
1016
1774
  batchIds.add(batch.batchId);
1017
1775
  if (new Set(batch.memberIds).size !== batch.memberIds.length)
1018
- fail(`validation batch ${batch.batchId} has duplicate memberIds.`);
1776
+ fail2(`validation batch ${batch.batchId} has duplicate memberIds.`);
1019
1777
  if (!batch.memberIds.includes(batch.finalGoalId))
1020
- fail(`validation batch ${batch.batchId} finalGoalId must be a member.`, "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER");
1778
+ fail2(`validation batch ${batch.batchId} finalGoalId must be a member.`, "ULW_LOOP_VALIDATION_BATCH_FINAL_NOT_MEMBER");
1021
1779
  for (const memberId of batch.memberIds) {
1022
1780
  if (!goalIds.has(memberId))
1023
- fail(`validation batch ${batch.batchId} references unknown goal: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_MEMBER_UNKNOWN");
1781
+ fail2(`validation batch ${batch.batchId} references unknown goal: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_MEMBER_UNKNOWN");
1024
1782
  if (members.has(memberId))
1025
- fail(`goal appears in multiple validation batches: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_OVERLAP");
1783
+ fail2(`goal appears in multiple validation batches: ${memberId}.`, "ULW_LOOP_VALIDATION_BATCH_OVERLAP");
1026
1784
  members.add(memberId);
1027
1785
  }
1028
1786
  }
@@ -1081,18 +1839,15 @@ function memberResolved(plan, goalId) {
1081
1839
  const goal = plan.goals.find((candidate) => candidate.id === goalId);
1082
1840
  return goal !== undefined && isMemberResolved(goal, plan);
1083
1841
  }
1084
- function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
1842
+ function fail2(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
1085
1843
  throw new UlwLoopError(message, code);
1086
1844
  }
1087
1845
 
1088
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts
1089
- var QUALITY_GATE_FS = { existsSync: existsSync3, statSync };
1846
+ // components/ulw-loop/src/checkpoint.ts
1847
+ var QUALITY_GATE_FS = { existsSync: existsSync5, statSync: statSync2 };
1090
1848
  function ulwLoopFail2(message, code) {
1091
1849
  throw new UlwLoopError(message, code);
1092
1850
  }
1093
- function normalizeObjective3(value) {
1094
- return value.replace(/\s+/g, " ").trim();
1095
- }
1096
1851
  function nonEmptyEvidence2(value) {
1097
1852
  const trimmed = value.trim();
1098
1853
  return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
@@ -1111,11 +1866,11 @@ async function readJsonInput2(raw, repoRoot) {
1111
1866
  if (!(error instanceof SyntaxError))
1112
1867
  throw error;
1113
1868
  }
1114
- const path = resolve3(repoRoot, trimmed);
1115
- if (!existsSync3(path))
1869
+ const path = resolve4(repoRoot, trimmed);
1870
+ if (!existsSync5(path))
1116
1871
  return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
1117
1872
  try {
1118
- return JSON.parse(await readFile5(path, "utf8"));
1873
+ return JSON.parse(await readFile3(path, "utf8"));
1119
1874
  } catch (error) {
1120
1875
  return ulwLoopFail2(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ulw_loop_json_input_invalid");
1121
1876
  }
@@ -1155,13 +1910,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
1155
1910
  return status === "blocked" ? "goal_blocked" : "goal_failed";
1156
1911
  }
1157
1912
  function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
1158
- const watch = qualityGate?.codeReview.codeQualityStatus === "WATCH";
1913
+ const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview?.codeQualityStatus === "WATCH";
1159
1914
  const entry = {
1160
1915
  at: now,
1161
1916
  kind: ledgerKind2(args.status, goal, aggregateCompletion),
1162
1917
  goalId: goal.id,
1163
1918
  status: goal.status,
1164
- evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
1919
+ evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview?.evidence ?? ""}` : args.evidence
1165
1920
  };
1166
1921
  if (codexGoal !== undefined)
1167
1922
  entry.codexGoal = codexGoal;
@@ -1175,7 +1930,7 @@ function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletio
1175
1930
  entry.requiredExternalDecision = goal.requiredExternalDecision;
1176
1931
  return entry;
1177
1932
  }
1178
- async function checkpointUlwLoop(repoRoot, args, scope) {
1933
+ async function checkpointUlwLoop(repoRoot, args, scope, dependencies) {
1179
1934
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1180
1935
  const plan = await readUlwLoopPlan(repoRoot, scope);
1181
1936
  const goal = findGoal2(plan, args.goalId);
@@ -1184,6 +1939,8 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1184
1939
  let aggregateCompletion;
1185
1940
  let qualityGate;
1186
1941
  let codexGoal;
1942
+ let nextActions = [];
1943
+ let warnings = [];
1187
1944
  if (args.status === "complete") {
1188
1945
  const aggregate = codexGoalMode(plan) === "aggregate";
1189
1946
  const final = isFinalRunCompletionCandidate(plan, goal);
@@ -1196,23 +1953,24 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1196
1953
  requireEssentialCriteriaPass(goal);
1197
1954
  else
1198
1955
  requireAllCriteriaPass(goal);
1199
- const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
1200
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
1201
- expectedObjective: expectedCodexObjective(plan, goal),
1202
- ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {},
1203
- allowedStatuses: aggregate ? final ? ["complete"] : ["active"] : ["complete"],
1204
- requireSnapshot: true,
1205
- requireComplete: !aggregate || final
1206
- });
1207
- codexGoal = reconciliation.snapshot.raw;
1208
- if (!reconciliation.ok) {
1209
- const objective = snapshot?.objective;
1210
- const mismatchedTaskObjective = snapshot?.available === true && objective !== undefined && normalizeObjective3(objective) !== normalizeObjective3(expectedCodexObjective(plan, goal));
1211
- const completedTaskScoped = mismatchedTaskObjective && snapshot.status === "complete" && await canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence, scope);
1212
- const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence, scope);
1213
- const taskScoped = completedTaskScoped || activeFinalTaskScoped;
1214
- if (!taskScoped)
1215
- throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
1956
+ let codexValidationError;
1957
+ try {
1958
+ const validation = await validateCheckpointCodexGoal({
1959
+ repoRoot,
1960
+ plan,
1961
+ goal,
1962
+ raw: args.codexGoalJson,
1963
+ evidence,
1964
+ ...scope === undefined ? {} : { scope }
1965
+ });
1966
+ codexGoal = validation.raw;
1967
+ nextActions = validation.nextActions;
1968
+ warnings = validation.warnings;
1969
+ acknowledgeDriverObjective(plan, validation.unacknowledgedObjective);
1970
+ } catch (error) {
1971
+ if (!(error instanceof UlwLoopError))
1972
+ throw error;
1973
+ codexValidationError = error;
1216
1974
  }
1217
1975
  if (closesBatch)
1218
1976
  requireBatchFinalReady(plan, goal);
@@ -1221,13 +1979,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1221
1979
  if (final)
1222
1980
  aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
1223
1981
  if (final || aggregateCompletion !== undefined || closesBatch) {
1224
- qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1225
- repoRoot,
1226
- fs: QUALITY_GATE_FS,
1227
- ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1228
- });
1229
- requireBatchGate(plan, goal, qualityGate);
1982
+ try {
1983
+ qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
1984
+ repoRoot,
1985
+ fs: QUALITY_GATE_FS,
1986
+ reviewerSurface: dependencies?.surface ?? resolveToolkitSurface(),
1987
+ ...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
1988
+ });
1989
+ requireBatchGate(plan, goal, qualityGate);
1990
+ } catch (error) {
1991
+ if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
1992
+ throw error;
1993
+ throw combineCheckpointValidationErrors(codexValidationError, error);
1994
+ }
1230
1995
  }
1996
+ if (codexValidationError !== undefined)
1997
+ throw codexValidationError;
1231
1998
  goal.status = "complete";
1232
1999
  goal.completedAt = now;
1233
2000
  goal.evidence = evidence;
@@ -1241,30 +2008,155 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
1241
2008
  goal.updatedAt = now;
1242
2009
  if (aggregateCompletion !== undefined)
1243
2010
  plan.aggregateCompletion = aggregateCompletion;
2011
+ if (aggregateCompletion !== undefined)
2012
+ nextActions = [...nextActions, 'aggregate complete — now update_goal({status:"complete"})'];
1244
2013
  plan.updatedAt = now;
1245
- await writePlan(repoRoot, plan, scope);
1246
2014
  const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
1247
- await appendLedger(repoRoot, ledgerEntry, scope);
2015
+ const entries = [ledgerEntry];
1248
2016
  const closedBatch = args.status === "complete" ? batchClosedBy(plan, goal.id) : undefined;
1249
2017
  if (closedBatch !== undefined)
1250
- await appendLedger(repoRoot, { at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId }, scope);
1251
- return aggregateCompletion === undefined ? { plan, goal, ledgerEntry } : { plan, goal, ledgerEntry, aggregateCompletion };
2018
+ entries.push({ at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId });
2019
+ await commit(repoRoot, scope, { plan, entries });
2020
+ return aggregateCompletion === undefined ? { plan, goal, ledgerEntry, nextActions, warnings } : { plan, goal, ledgerEntry, aggregateCompletion, nextActions, warnings };
1252
2021
  });
1253
2022
  }
1254
2023
 
1255
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts
2024
+ // components/ulw-loop/src/checkpoint-template.ts
2025
+ function artifactPath(base, name) {
2026
+ return `${base}/${name}`;
2027
+ }
2028
+ function gateTemplate(surface, base) {
2029
+ const artifacts = [
2030
+ {
2031
+ id: "artifact-cli",
2032
+ kind: "cli-transcript",
2033
+ description: "<replace:artifact description>",
2034
+ path: artifactPath(base, "cli-transcript.txt")
2035
+ },
2036
+ {
2037
+ id: "artifact-data",
2038
+ kind: "data-diff",
2039
+ description: "<replace:artifact description>",
2040
+ path: artifactPath(base, "data-diff.txt")
2041
+ }
2042
+ ];
2043
+ const manualQa = {
2044
+ by: "main-session",
2045
+ status: "passed",
2046
+ evidence: "<replace:manual QA evidence>",
2047
+ surfaceEvidence: [
2048
+ {
2049
+ id: "surface-cli",
2050
+ criterionRef: "<replace:criterion id>",
2051
+ surface: "cli",
2052
+ invocation: "<replace:command>",
2053
+ verdict: "passed",
2054
+ artifactRefs: ["artifact-cli"]
2055
+ },
2056
+ {
2057
+ id: "surface-data",
2058
+ criterionRef: "<replace:criterion id>",
2059
+ surface: "data",
2060
+ invocation: "<replace:command>",
2061
+ verdict: "passed",
2062
+ artifactRefs: ["artifact-data"]
2063
+ }
2064
+ ],
2065
+ adversarialCases: [
2066
+ {
2067
+ id: "<replace:adversarial case id>",
2068
+ criterionRef: "<replace:criterion id>",
2069
+ scenario: "<replace:scenario>",
2070
+ expectedBehavior: "<replace:expected behavior>",
2071
+ verdict: "not_applicable",
2072
+ reason: "<replace:reason>",
2073
+ artifactRefs: ["artifact-cli"]
2074
+ }
2075
+ ],
2076
+ artifactRefs: artifacts
2077
+ };
2078
+ const common = {
2079
+ manualQa,
2080
+ gateReview: {
2081
+ by: surface === "omo-senpi" ? "category:deep-high" : "main-session",
2082
+ recommendation: "APPROVE",
2083
+ reportPath: artifactPath(base, "gate-review.md"),
2084
+ evidence: "<replace:gate review evidence>",
2085
+ blockers: [],
2086
+ notes: []
2087
+ },
2088
+ iteration: {
2089
+ fullRerun: true,
2090
+ status: "passed",
2091
+ rerunCommands: ["<replace:verification command>"],
2092
+ evidence: "<replace:iteration evidence>"
2093
+ },
2094
+ criteriaCoverage: {
2095
+ totalCriteria: 0,
2096
+ passCount: 0,
2097
+ originalIntent: "<replace:original intent>",
2098
+ desiredOutcome: "<replace:desired outcome>",
2099
+ userOutcomeReview: "<replace:user outcome review>",
2100
+ adversarialClassesCovered: ["<replace:adversarial class>"]
2101
+ }
2102
+ };
2103
+ return common;
2104
+ }
2105
+ async function checkpointTemplate(repoRoot, scope, goalId, dependencies) {
2106
+ const plan = await readUlwLoopPlan(repoRoot, scope);
2107
+ const surface = dependencies?.surface ?? resolveToolkitSurface();
2108
+ const targetId = goalId ?? plan.activeGoalId;
2109
+ const active = plan.goals.find((goal) => goal.id === targetId);
2110
+ if (goalId !== undefined && active === undefined)
2111
+ throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
2112
+ const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
2113
+ const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
2114
+ const guidance = [
2115
+ "codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
2116
+ "Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
2117
+ 'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
2118
+ 'Passing quality-gate-json example requires gateReview {"by":"category:deep-high","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
2119
+ ...surface === "lazycodex" ? [
2120
+ "Self-review defaults: manualQa.by and gateReview.by are main-session. Alternatives: manualQa.by accepts lazycodex-qa-executor; gateReview.by accepts lazycodex-gate-reviewer, category:deep-high, category:deep-low, category:unspecified-high, or category:unspecified-low. Optional codeReview.by accepts lazycodex-code-reviewer or main-session."
2121
+ ] : [],
2122
+ ...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
2123
+ ].join(" ");
2124
+ return {
2125
+ qualityGateTemplate: gateTemplate(surface, attemptDir),
2126
+ codexGoalTemplate: {
2127
+ goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
2128
+ },
2129
+ guidance,
2130
+ ...hasAttempt ? { attemptDir } : {}
2131
+ };
2132
+ }
2133
+
2134
+ // components/ulw-loop/src/cli-output.ts
1256
2135
  var ULW_LOOP_HELP = `Usage:
2136
+ omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
2137
+ omo-agent-toolkit help | --help | -h (this message)
2138
+ omo-agent-toolkit ulw-loop help
1257
2139
  omo-agent-toolkit ulw-loop create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json <json-or-path>] [--force] [--json]
1258
2140
  omo-agent-toolkit ulw-loop status [--json]
1259
2141
  omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
1260
2142
  omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
1261
2143
  omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
2144
+ omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
1262
2145
  omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
1263
2146
  omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
1264
2147
  omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
1265
2148
  omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
1266
2149
 
1267
- All subcommands accept [--session-id <id>] to isolate state under .omo/ulw-loop/<id>/; without it, Codex session env is used when present.`;
2150
+ Every state subcommand needs a session scope: [--session-id <id>] or the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID); state lives under .omo/ulw-loop/<id>/ and the unscoped root is never used implicitly. status --json exposes the currentAttemptDir; put all quality-gate artifacts under it.
2151
+ Every subcommand accepts --help | -h to print its own usage line.`;
2152
+ function subcommandHelp(subcommand) {
2153
+ const lines = ULW_LOOP_HELP.split(`
2154
+ `).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
2155
+ if (lines.length === 0)
2156
+ return ULW_LOOP_HELP;
2157
+ return ["Usage:", ...lines].join(`
2158
+ `);
2159
+ }
1268
2160
  function printJson(value) {
1269
2161
  process.stdout.write(`${JSON.stringify(value, null, 2)}
1270
2162
  `);
@@ -1330,17 +2222,18 @@ function normalizeCodexGoalMode(value) {
1330
2222
  throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
1331
2223
  }
1332
2224
 
1333
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts
2225
+ // components/ulw-loop/src/codex-goal-instruction.ts
1334
2226
  function buildCodexGoalInstruction(args) {
1335
2227
  const mode = codexGoalMode(args.plan);
1336
2228
  const createGoal = buildCreateGoalPayload(args.plan, args.goal);
1337
2229
  const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
1338
- return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal), json: createGoal };
2230
+ const surface = args.surface ?? resolveToolkitSurface();
2231
+ return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
1339
2232
  }
1340
2233
  function buildCreateGoalPayload(plan, goal) {
1341
2234
  return { objective: expectedCodexObjective(plan, goal) };
1342
2235
  }
1343
- function buildText(mode, plan, goal, createGoal, isFinal) {
2236
+ function buildText(mode, plan, goal, createGoal, isFinal, surface) {
1344
2237
  return joinLines([
1345
2238
  mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
1346
2239
  `Mode: ${mode}`,
@@ -1357,7 +2250,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
1357
2250
  "- Goals are unlimited. Do not add numeric limits.",
1358
2251
  ...modeConstraintLines(mode, isFinal),
1359
2252
  ...evidenceLayoutLines(plan),
1360
- finalSection(plan, goal, isFinal, mode === "aggregate"),
2253
+ finalSection(plan, goal, isFinal, mode === "aggregate", surface),
1361
2254
  ...checkpointLines(plan, mode),
1362
2255
  "",
1363
2256
  "create_goal payload:",
@@ -1409,19 +2302,21 @@ function evidenceLayoutLines(plan) {
1409
2302
  "- Evidence layout v2: write every artifact for the active goal (QA matrix, review reports, receipts) under the current attempt directory — read currentAttemptDir from `omo-agent-toolkit ulw-loop status --json` (.omo/evidence/ulw/<session>/<goalId>/a<attempt>). The final checkpoint rejects quality-gate artifacts outside that directory."
1410
2303
  ];
1411
2304
  }
1412
- function finalSection(plan, goal, isFinal, aggregate) {
2305
+ function finalSection(plan, goal, isFinal, aggregate, surface) {
2306
+ const roles = reviewerRolesFor(surface);
1413
2307
  if (!isFinal)
1414
2308
  return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
1415
2309
  const option = sessionOption(plan);
2310
+ if (surface === "omo-senpi")
2311
+ return senpiFinalSection(plan, goal, aggregate);
1416
2312
  const blockerCommand = `omo-agent-toolkit ulw-loop record-review-blockers${option} --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`;
1417
2313
  const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<targeted verification/manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
1418
2314
  return joinLines([
1419
- "Final story — run mandatory quality gate before update_goal:",
2315
+ "Final story — self-review and manual QA are the default; use the quality gate before update_goal:",
1420
2316
  "- Run targeted verification for changed behavior.",
1421
2317
  "- Confirm every manualQa artifact path exists and has non-zero size.",
1422
- '- First spawn lazycodex-code-reviewer and lazycodex-qa-executor in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).',
1423
- "- Only then spawn lazycodex-gate-reviewer (same fork settings), passing those artifact paths.",
1424
- "- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
2318
+ `- Run manual QA yourself and write its artifact under currentAttemptDir. Only if the user explicitly demands strict, rigorous, or high-accuracy review, spawn ${roles.gateReview}, optionally also ${roles.codeReview} and ${roles.manualQa}; otherwise set manualQa.by and gateReview.by to "main-session".`,
2319
+ "- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage; include codeReview only when strict review was requested. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
1425
2320
  "- On a reviewer REJECT, fix only the cited blockers, rerun the affected verification/Manual-QA, and re-review the delta at most TWICE; if blockers remain, record them and surface to the user.",
1426
2321
  "- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
1427
2322
  "- If any reviewer is blocked/inconclusive or the quality gate is not clean, do not call update_goal. Record blocker work first:",
@@ -1430,6 +2325,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
1430
2325
  ` ${checkpointCommand}`
1431
2326
  ]);
1432
2327
  }
2328
+ function senpiFinalSection(plan, goal, aggregate) {
2329
+ const option = sessionOption(plan);
2330
+ 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)"`;
2331
+ return joinLines([
2332
+ "Final story — run the single-reviewer quality gate before update_goal:",
2333
+ '- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
2334
+ '- Spawn exactly one gate reviewer with task(category: "deep-high").',
2335
+ "- If that task fails with any model_unavailable failure, retry with category:deep-low, then category:unspecified-high, then category:unspecified-low.",
2336
+ "- Set gateReview.by to the exact category:<name> literal used for the successful task.",
2337
+ "- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
2338
+ '- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
2339
+ 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}`
2340
+ ]);
2341
+ }
1433
2342
  function sessionOption(plan) {
1434
2343
  const prefix = ".omo/ulw-loop/";
1435
2344
  const suffix = "/goals.json";
@@ -1443,15 +2352,53 @@ function joinLines(lines) {
1443
2352
  `);
1444
2353
  }
1445
2354
 
1446
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
1447
- import { existsSync as existsSync4 } from "node:fs";
1448
- import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
2355
+ // components/ulw-loop/src/success-criteria-input.ts
2356
+ function invalid2(message, details) {
2357
+ throw new UlwLoopError(`Invalid successCriteria: ${message}`, "ULW_LOOP_ARGUMENT_INVALID", { details });
2358
+ }
2359
+ function isUserModel(value) {
2360
+ return typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2361
+ }
2362
+ function requireText(value, field, index) {
2363
+ const trimmed = typeof value === "string" ? value.trim() : "";
2364
+ return trimmed || invalid2(`entry ${index + 1} needs a non-empty ${field}.`, { index, field });
2365
+ }
2366
+ function criterionId(index) {
2367
+ return `C${String(index + 1).padStart(3, "0")}`;
2368
+ }
2369
+ function criteriaFromInput(input) {
2370
+ if (input.length === 0)
2371
+ invalid2("provide at least one criterion or omit the field for placeholders.", { count: 0 });
2372
+ return input.map((entry, index) => {
2373
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
2374
+ return invalid2(`entry ${index + 1} must be an object.`, { index });
2375
+ const record = { ...entry };
2376
+ const userModel = record["userModel"] ?? "happy";
2377
+ if (!isUserModel(userModel))
2378
+ return invalid2(`entry ${index + 1} userModel must be one of ${ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.join(", ")}.`, {
2379
+ index,
2380
+ userModel: String(userModel)
2381
+ });
2382
+ const essential = record["essential"];
2383
+ if (essential !== undefined && typeof essential !== "boolean")
2384
+ return invalid2(`entry ${index + 1} essential must be a boolean.`, { index });
2385
+ return {
2386
+ id: criterionId(index),
2387
+ scenario: requireText(record["scenario"], "scenario", index),
2388
+ userModel,
2389
+ expectedEvidence: requireText(record["expectedEvidence"], "expectedEvidence", index),
2390
+ essential: essential ?? true,
2391
+ capturedEvidence: null,
2392
+ status: "pending"
2393
+ };
2394
+ });
2395
+ }
1449
2396
 
1450
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts
2397
+ // components/ulw-loop/src/plan-goal-factory.ts
1451
2398
  function cleanLine(line) {
1452
2399
  return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
1453
2400
  }
1454
- function normalizeObjective4(value) {
2401
+ function normalizeObjective2(value) {
1455
2402
  return value.replace(/\s+/g, " ").trim();
1456
2403
  }
1457
2404
  function titleFromObjective(objective, fallback) {
@@ -1471,72 +2418,72 @@ function assertNonEmpty(value, label) {
1471
2418
  function truncateObjective(objective) {
1472
2419
  return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
1473
2420
  }
1474
- function seedDefaultSuccessCriteria(goalIndex, objective) {
1475
- const subject = truncateObjective(normalizeObjective4(objective) || `Goal ${goalIndex + 1}`);
2421
+ function replaceVia(goalId, id, surface) {
2422
+ return surface === "omo-senpi" ? `Replace via agentToolkit.steer({ kind: "revise_criterion", source: "finding", goalId: "${goalId}", criterionId: "${id}", scenario, expectedEvidence, evidence, rationale }) (or pass successCriteria to addGoal)` : `Replace via omo-agent-toolkit ulw-loop steer --kind revise_criterion --goal-id ${goalId} --criterion-id ${id} --scenario "<scenario>" --expected-evidence "<proof>" --evidence "<why>" --rationale "<why>"`;
2423
+ }
2424
+ function seedDefaultSuccessCriteria(goalIndex, objective, options = {}) {
2425
+ const subject = truncateObjective(normalizeObjective2(objective) || `Goal ${goalIndex + 1}`);
2426
+ const goalId = options.goalId ?? `G${String(goalIndex + 1).padStart(3, "0")}`;
2427
+ const surface = options.surface ?? "lazycodex";
1476
2428
  const rows = [
2429
+ ["happy", `happy path for: ${subject}`, `observable happy-path proof for goal ${goalIndex + 1}`, true],
2430
+ ["edge", "edge case (boundary/empty/malformed)", `boundary or malformed-input proof for: ${subject}`, true],
1477
2431
  [
1478
- "C001",
1479
- "happy",
1480
- `happy path for: ${subject}`,
1481
- `Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`,
1482
- true
1483
- ],
1484
- [
1485
- "C002",
1486
- "edge",
1487
- "edge case (boundary/empty/malformed)",
1488
- `Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`,
1489
- true
1490
- ],
1491
- [
1492
- "C003",
1493
2432
  "regression",
1494
2433
  "regression: adjacent surface still works",
1495
- `Replace via revise_criterion with regression proof for neighboring behavior after: ${subject}.`,
2434
+ `regression proof for neighboring behavior after: ${subject}`,
1496
2435
  false
1497
2436
  ]
1498
2437
  ];
1499
- return rows.map(([id, userModel, scenario, expectedEvidence, essential]) => ({
1500
- id,
1501
- scenario,
1502
- userModel,
1503
- expectedEvidence,
1504
- essential,
1505
- capturedEvidence: null,
1506
- status: "pending"
1507
- }));
2438
+ return rows.map(([userModel, scenario, proof, essential], index) => {
2439
+ const id = criterionId(index);
2440
+ return {
2441
+ id,
2442
+ scenario,
2443
+ userModel,
2444
+ expectedEvidence: `${replaceVia(goalId, id, surface)} with ${proof}.`,
2445
+ essential,
2446
+ capturedEvidence: null,
2447
+ status: "pending"
2448
+ };
2449
+ });
1508
2450
  }
1509
2451
  function deriveGoalCandidates(brief) {
1510
- const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective4(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
1511
- const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective4).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
2452
+ const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective2(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
2453
+ const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective2).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
1512
2454
  const selected = (bulletGoals.length > 0 ? bulletGoals : paragraphs).length > 0 ? bulletGoals.length > 0 ? bulletGoals : paragraphs : ["Complete the requested project objective."];
1513
2455
  return selected.map((objective, index) => ({
1514
2456
  title: titleFromObjective(objective, `Goal ${index + 1}`),
1515
2457
  objective
1516
2458
  }));
1517
2459
  }
1518
- function makeGoal(title, objective, index, now) {
2460
+ function makeGoal(title, objective, index, now, options = {}) {
1519
2461
  const cleanTitle = assertNonEmpty(title, "title");
1520
2462
  const cleanObjective = assertNonEmpty(objective, "objective");
2463
+ const id = normalizeGoalId(cleanTitle, index);
2464
+ const successCriteria = options.successCriteria === undefined ? seedDefaultSuccessCriteria(index, cleanObjective, {
2465
+ goalId: id,
2466
+ ...options.surface === undefined ? {} : { surface: options.surface }
2467
+ }) : criteriaFromInput(options.successCriteria);
1521
2468
  return {
1522
- id: normalizeGoalId(cleanTitle, index),
2469
+ id,
1523
2470
  title: cleanTitle,
1524
2471
  objective: cleanObjective,
1525
2472
  status: "pending",
1526
- successCriteria: seedDefaultSuccessCriteria(index, cleanObjective),
2473
+ successCriteria,
1527
2474
  attempt: 0,
1528
2475
  createdAt: now,
1529
2476
  updatedAt: now
1530
2477
  };
1531
2478
  }
1532
- function appendGoalToPlan(plan, title, objective, now) {
1533
- const goal = makeGoal(title, objective, plan.goals.length, now);
2479
+ function appendGoalToPlan(plan, title, objective, now, options = {}) {
2480
+ const goal = makeGoal(title, objective, plan.goals.length, now, options);
1534
2481
  plan.goals.push(goal);
1535
2482
  plan.updatedAt = now;
1536
2483
  return goal;
1537
2484
  }
1538
2485
 
1539
- // packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts
2486
+ // components/ulw-loop/src/plan-crud.ts
1540
2487
  function isScheduleEligible(goal) {
1541
2488
  return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
1542
2489
  }
@@ -1552,18 +2499,31 @@ function clearGoalBlockerFields2(goal) {
1552
2499
  ])
1553
2500
  delete goal[key];
1554
2501
  }
1555
- async function createUlwLoopPlan(repoRoot, args, scope) {
2502
+ async function createUlwLoopPlan(repoRoot, args, scope, surface = "lazycodex") {
1556
2503
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1557
- if (!args.force && existsSync4(ulwLoopGoalsPath(repoRoot, scope))) {
1558
- const existing = await readUlwLoopPlan(repoRoot, scope);
2504
+ let existing;
2505
+ if (planExists(repoRoot, scope)) {
2506
+ try {
2507
+ existing = await readUlwLoopPlan(repoRoot, scope);
2508
+ } catch (error) {
2509
+ if (!args.force)
2510
+ throw error;
2511
+ }
2512
+ }
2513
+ if (!args.force && existing !== undefined) {
1559
2514
  if (isUlwLoopDone(existing))
1560
- throw completedPlanExistsError(scope);
2515
+ throw completedPlanExistsError(scope, surface);
1561
2516
  throw new UlwLoopError(`Refusing to overwrite existing ${ulwLoopGoalsRelativePath(scope)}; pass --force to recreate it.`, "ULW_LOOP_PLAN_EXISTS");
1562
2517
  }
1563
2518
  const now = iso();
1564
- const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
2519
+ const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now, { surface }));
1565
2520
  const plan = {
1566
2521
  version: 1,
2522
+ revision: existing?.revision ?? 0,
2523
+ ledgerResetRevision: (existing?.revision ?? 0) + 1,
2524
+ brief: args.brief.endsWith(`
2525
+ `) ? args.brief : `${args.brief}
2526
+ `,
1567
2527
  evidenceLayoutVersion: 2,
1568
2528
  createdAt: now,
1569
2529
  updatedAt: now,
@@ -1578,30 +2538,37 @@ async function createUlwLoopPlan(repoRoot, args, scope) {
1578
2538
  plan.validationBatches = validationBatches;
1579
2539
  if (plan.codexGoalMode === "aggregate")
1580
2540
  plan.codexObjective = aggregateCodexObjectiveForScope(scope);
1581
- await mkdir2(ulwLoopDir(repoRoot, scope), { recursive: true });
1582
- await writeFile2(ulwLoopBriefPath(repoRoot, scope), args.brief.endsWith(`
1583
- `) ? args.brief : `${args.brief}
1584
- `, "utf8");
1585
- await writePlan(repoRoot, plan, scope);
1586
- await writeFile2(ulwLoopLedgerPath(repoRoot, scope), "", "utf8");
1587
- await appendLedger(repoRoot, { at: now, kind: "plan_created", message: `${goals.length} goal(s) created` }, scope);
2541
+ await beforePlanMutation();
2542
+ await commit(repoRoot, scope, {
2543
+ plan,
2544
+ entries: [{ at: now, kind: "plan_created", message: `${goals.length} goal(s) created` }]
2545
+ });
1588
2546
  return plan;
1589
2547
  });
1590
2548
  }
1591
- function completedPlanExistsError(scope) {
2549
+ function completedPlanExistsError(scope, surface) {
1592
2550
  return new UlwLoopError([
1593
2551
  `Existing ulw-loop aggregate is already complete at ${ulwLoopGoalsRelativePath(scope)}.`,
1594
- "Start a new run with `omo-agent-toolkit ulw-loop create-goals --session-id <new-id> ...` to isolate fresh state.",
1595
- "Use --force only when you intentionally want to overwrite the completed evidence."
2552
+ ...surface === "omo-senpi" ? [
2553
+ "Start a new run under a fresh session id (a new senpi session) or call agentToolkit.createGoals({ brief, force: true }) to recreate."
2554
+ ] : [
2555
+ "Start a new run with `omo-agent-toolkit ulw-loop create-goals --session-id <new-id> ...` to isolate fresh state.",
2556
+ "Use --force only when you intentionally want to overwrite the completed evidence."
2557
+ ]
1596
2558
  ].join(" "), "ULW_LOOP_PLAN_EXISTS_COMPLETE");
1597
2559
  }
1598
- async function addUlwLoopGoal(repoRoot, args, scope) {
2560
+ async function addUlwLoopGoal(repoRoot, args, scope, surface = "lazycodex") {
1599
2561
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
1600
2562
  const plan = await readUlwLoopPlan(repoRoot, scope);
1601
2563
  const now = iso();
1602
- const goal = appendGoalToPlan(plan, args.title, args.objective, now);
1603
- await writePlan(repoRoot, plan, scope);
1604
- await appendLedger(repoRoot, { at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }, scope);
2564
+ const goal = appendGoalToPlan(plan, args.title, args.objective, now, {
2565
+ surface,
2566
+ ...args.successCriteria === undefined ? {} : { successCriteria: args.successCriteria }
2567
+ });
2568
+ await commit(repoRoot, scope, {
2569
+ plan,
2570
+ entries: [{ at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }]
2571
+ });
1605
2572
  return { plan, goal };
1606
2573
  });
1607
2574
  }
@@ -1614,17 +2581,18 @@ async function startNextUlwLoop(repoRoot, args = {}, scope) {
1614
2581
  const existing = plan.goals.find((goal) => goal.status === "in_progress" && isScheduleEligible(goal));
1615
2582
  if (existing)
1616
2583
  return { plan, goal: existing, resumed: true };
2584
+ const entries = [];
1617
2585
  let next = plan.goals.find((goal) => goal.status === "pending" && isScheduleEligible(goal));
1618
2586
  if (!next && args.retryFailed) {
1619
2587
  next = plan.goals.find((goal) => goal.status === "failed" && !goal.nonRetriable && isScheduleEligible(goal));
1620
2588
  if (next)
1621
- await appendLedger(repoRoot, {
2589
+ entries.push({
1622
2590
  at: now,
1623
2591
  kind: "goal_retried",
1624
2592
  goalId: next.id,
1625
2593
  status: "pending",
1626
2594
  ...next.failureReason ? { message: next.failureReason } : {}
1627
- }, scope);
2595
+ });
1628
2596
  }
1629
2597
  if (!next)
1630
2598
  return { done: true, plan };
@@ -1635,8 +2603,14 @@ async function startNextUlwLoop(repoRoot, args = {}, scope) {
1635
2603
  next.updatedAt = now;
1636
2604
  plan.activeGoalId = next.id;
1637
2605
  plan.updatedAt = now;
1638
- await writePlan(repoRoot, plan, scope);
1639
- await appendLedger(repoRoot, { at: now, kind: "goal_started", goalId: next.id, status: next.status, message: `Attempt ${next.attempt}` }, scope);
2606
+ entries.push({
2607
+ at: now,
2608
+ kind: "goal_started",
2609
+ goalId: next.id,
2610
+ status: next.status,
2611
+ message: `Attempt ${next.attempt}`
2612
+ });
2613
+ await commit(repoRoot, scope, { plan, entries });
1640
2614
  return { plan, goal: next, resumed: false };
1641
2615
  });
1642
2616
  }
@@ -1663,7 +2637,7 @@ function summarizeUlwLoopPlan(plan) {
1663
2637
  };
1664
2638
  }
1665
2639
 
1666
- // packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts
2640
+ // components/ulw-loop/src/checkpoint-continuation.ts
1667
2641
  async function checkpointAndContinue(repoRoot, args, scope) {
1668
2642
  const result = await checkpointUlwLoop(repoRoot, args, scope);
1669
2643
  if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
@@ -1675,13 +2649,18 @@ async function checkpointAndContinue(repoRoot, args, scope) {
1675
2649
  return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
1676
2650
  }
1677
2651
  async function checkpoint(repoRoot, argv, json, scope) {
2652
+ if (hasFlag(argv, "--print-template")) {
2653
+ const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
2654
+ if (json)
2655
+ printJson({ ok: true, ...template });
2656
+ else
2657
+ printJson(template);
2658
+ return 0;
2659
+ }
1678
2660
  const goalId = required2(argv, "--goal-id");
1679
2661
  const statusValue = checkpointStatus(required2(argv, "--status"));
1680
2662
  const evidence = required2(argv, "--evidence");
1681
- const codexGoalJson = await parseCodexGoalJson(statusValue === "complete" ? required2(argv, "--codex-goal-json") : readValue(argv, "--codex-goal-json"));
1682
- if (statusValue === "complete" && codexGoalJson === undefined) {
1683
- throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
1684
- }
2663
+ const codexGoalJson = await parseCodexGoalJson(readValue(argv, "--codex-goal-json"));
1685
2664
  const qualityGateJson = readValue(argv, "--quality-gate-json");
1686
2665
  const args = {
1687
2666
  goalId,
@@ -1726,10 +2705,10 @@ function checkpointStatus(value) {
1726
2705
  throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
1727
2706
  }
1728
2707
 
1729
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
1730
- import { readFile as readFile6 } from "node:fs/promises";
2708
+ // components/ulw-loop/src/cli-subcommands.ts
2709
+ import { readFile as readFile4 } from "node:fs/promises";
1731
2710
 
1732
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts
2711
+ // components/ulw-loop/src/cli-steering.ts
1733
2712
  var SOURCES = ["user_prompt_submit", "finding", "cli"];
1734
2713
  var STEERING_KIND_HELP = [
1735
2714
  `Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
@@ -1753,7 +2732,7 @@ function isSource(value) {
1753
2732
  function isModel(value) {
1754
2733
  return ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
1755
2734
  }
1756
- function fail2(message, code, details) {
2735
+ function fail3(message, code, details) {
1757
2736
  throw new UlwLoopError(message, code, { details });
1758
2737
  }
1759
2738
  function kindMessage(prefix) {
@@ -1767,15 +2746,15 @@ function text2(value, field) {
1767
2746
  const trimmed = value.trim();
1768
2747
  if (trimmed.length > 0)
1769
2748
  return trimmed;
1770
- return fail2(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field });
2749
+ return fail3(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field });
1771
2750
  }
1772
2751
  function required3(argv, flag) {
1773
2752
  const value = text2(readValue(argv, flag), flag);
1774
- return value ?? fail2(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag });
2753
+ return value ?? fail3(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag });
1775
2754
  }
1776
2755
  function requiredGoal(argv) {
1777
2756
  const value = text2(parseGoalArg(argv), "--goal-id");
1778
- return value ?? fail2("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" });
2757
+ return value ?? fail3("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" });
1779
2758
  }
1780
2759
  function readObject(value, key) {
1781
2760
  return Object.entries(value).find(([name]) => name === key)?.[1];
@@ -1799,7 +2778,7 @@ function objectChildren(value, key) {
1799
2778
  for (const item of candidate) {
1800
2779
  const next = child(item);
1801
2780
  if (next === null)
1802
- return fail2(`${key} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { key });
2781
+ return fail3(`${key} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { key });
1803
2782
  parsed.push(next);
1804
2783
  }
1805
2784
  return parsed;
@@ -1814,13 +2793,13 @@ function parseSteeringKind(argv) {
1814
2793
  const value = readValue(argv, "--kind");
1815
2794
  if (isKind(value))
1816
2795
  return value;
1817
- return value === undefined ? fail2(kindMessage("Missing --kind."), "ULW_LOOP_STEERING_KIND_REQUIRED", { flag: "--kind", expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP }) : fail2(kindMessage(`Invalid --kind: ${value}.`), "ULW_LOOP_STEERING_KIND_INVALID", { value, expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP });
2796
+ return value === undefined ? fail3(kindMessage("Missing --kind."), "ULW_LOOP_STEERING_KIND_REQUIRED", { flag: "--kind", expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP }) : fail3(kindMessage(`Invalid --kind: ${value}.`), "ULW_LOOP_STEERING_KIND_INVALID", { value, expected: ULW_LOOP_STEERING_MUTATION_KINDS, usage: STEERING_KIND_HELP });
1818
2797
  }
1819
2798
  function parseSteeringSource(argv) {
1820
2799
  const value = readValue(argv, "--source");
1821
2800
  if (value === undefined)
1822
2801
  return "cli";
1823
- return isSource(value) ? value : fail2(`Invalid --source: ${value}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
2802
+ return isSource(value) ? value : fail3(`Invalid --source: ${value}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
1824
2803
  }
1825
2804
  function child(value) {
1826
2805
  if (!isPlain(value))
@@ -1837,12 +2816,12 @@ async function children(argv, flag, needed) {
1837
2816
  return [];
1838
2817
  const raw = await readJsonInput(input);
1839
2818
  if (!Array.isArray(raw))
1840
- return fail2(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
2819
+ return fail3(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
1841
2820
  const parsed = [];
1842
2821
  for (const item of raw) {
1843
2822
  const next = child(item);
1844
2823
  if (next === null)
1845
- return fail2(`${flag} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { flag });
2824
+ return fail3(`${flag} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { flag });
1846
2825
  parsed.push(next);
1847
2826
  }
1848
2827
  return parsed;
@@ -1850,11 +2829,11 @@ async function children(argv, flag, needed) {
1850
2829
  async function stringArray2(argv, flag) {
1851
2830
  const raw = await readJsonInput(required3(argv, flag));
1852
2831
  if (!Array.isArray(raw))
1853
- return fail2(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
2832
+ return fail3(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
1854
2833
  const values = [];
1855
2834
  for (const item of raw) {
1856
2835
  if (typeof item !== "string")
1857
- return fail2(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag });
2836
+ return fail3(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag });
1858
2837
  values.push(text2(item, flag) ?? "");
1859
2838
  }
1860
2839
  return values;
@@ -1863,10 +2842,10 @@ function model(value) {
1863
2842
  const trimmed = text2(value, "--user-model");
1864
2843
  if (trimmed === undefined)
1865
2844
  return;
1866
- return isModel(trimmed) ? trimmed : fail2(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS });
2845
+ return isModel(trimmed) ? trimmed : fail3(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS });
1867
2846
  }
1868
2847
  function neverKind(kind) {
1869
- return fail2(`Unsupported steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_UNSUPPORTED", { kind });
2848
+ return fail3(`Unsupported steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_UNSUPPORTED", { kind });
1870
2849
  }
1871
2850
  async function parseSteeringProposal(argv) {
1872
2851
  const kind = parseSteeringKind(argv);
@@ -1887,7 +2866,7 @@ async function parseSteeringProposal(argv) {
1887
2866
  const revisedTitle = readValue(argv, "--title");
1888
2867
  const revisedObjective = readValue(argv, "--objective");
1889
2868
  if (revisedTitle === undefined && revisedObjective === undefined)
1890
- return fail2("revise_pending_wording requires --title or --objective.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
2869
+ return fail3("revise_pending_wording requires --title or --objective.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
1891
2870
  return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...revisedTitle === undefined ? {} : { revisedTitle }, ...revisedObjective === undefined ? {} : { revisedObjective } });
1892
2871
  }
1893
2872
  case "revise_criterion": {
@@ -1897,7 +2876,7 @@ async function parseSteeringProposal(argv) {
1897
2876
  const expectedEvidence = readValue(argv, "--expected-evidence");
1898
2877
  const userModel = model(readValue(argv, "--user-model"));
1899
2878
  if (scenario === undefined && expectedEvidence === undefined && userModel === undefined)
1900
- return fail2("revise_criterion requires scenario, expected-evidence, or user-model.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
2879
+ return fail3("revise_criterion requires scenario, expected-evidence, or user-model.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind });
1901
2880
  return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, criterionId, ...scenario === undefined ? {} : { scenario }, ...expectedEvidence === undefined ? {} : { expectedEvidence }, ...userModel === undefined ? {} : { userModel } });
1902
2881
  }
1903
2882
  case "annotate_ledger":
@@ -1947,10 +2926,10 @@ async function parseSteeringProposals(argv) {
1947
2926
  if (input === undefined)
1948
2927
  return [await parseSteeringProposal(argv)];
1949
2928
  if (readValue(argv, "--kind") !== undefined)
1950
- return fail2("--kind and --proposals-json are mutually exclusive.", "ULW_LOOP_STEERING_BATCH_CONFLICT", { flags: ["--kind", "--proposals-json"] });
2929
+ return fail3("--kind and --proposals-json are mutually exclusive.", "ULW_LOOP_STEERING_BATCH_CONFLICT", { flags: ["--kind", "--proposals-json"] });
1951
2930
  const raw = await readJsonInput(input);
1952
2931
  if (!Array.isArray(raw) || raw.length === 0)
1953
- return fail2("--proposals-json must be a non-empty JSON array.", "ULW_LOOP_STEERING_BATCH_ARRAY_REQUIRED", { flag: "--proposals-json" });
2932
+ return fail3("--proposals-json must be a non-empty JSON array.", "ULW_LOOP_STEERING_BATCH_ARRAY_REQUIRED", { flag: "--proposals-json" });
1954
2933
  const proposals = [];
1955
2934
  for (const item of raw)
1956
2935
  proposals.push(normalizeSteeringProposal(proposalFromObject(item)));
@@ -1958,13 +2937,13 @@ async function parseSteeringProposals(argv) {
1958
2937
  }
1959
2938
  function proposalFromObject(value) {
1960
2939
  if (!isPlain(value))
1961
- return fail2("--proposals-json entries must be objects.", "ULW_LOOP_STEERING_BATCH_ITEM_INVALID", { flag: "--proposals-json" });
2940
+ return fail3("--proposals-json entries must be objects.", "ULW_LOOP_STEERING_BATCH_ITEM_INVALID", { flag: "--proposals-json" });
1962
2941
  const kind = readObject(value, "kind");
1963
2942
  const source = readObject(value, "source") ?? "cli";
1964
2943
  if (!isProposalKind(kind))
1965
- return fail2(`Invalid batch steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_INVALID", { value: kind });
2944
+ return fail3(`Invalid batch steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_INVALID", { value: kind });
1966
2945
  if (!isProposalSource(source))
1967
- return fail2(`Invalid batch steering source: ${String(source)}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value: source });
2946
+ return fail3(`Invalid batch steering source: ${String(source)}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value: source });
1968
2947
  let proposal = { kind, source, evidence: objectText(value, "evidence") ?? "", rationale: objectText(value, "rationale") ?? "" };
1969
2948
  const goalId = objectText(value, "goalId");
1970
2949
  const targetGoalId = objectText(value, "targetGoalId");
@@ -2036,7 +3015,7 @@ function printSteerBatchResult(result, json) {
2036
3015
  printStatus(result.plan);
2037
3016
  }
2038
3017
 
2039
- // packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts
3018
+ // components/ulw-loop/src/review-blockers.ts
2040
3019
  var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
2041
3020
  function ulwLoopError(message, code) {
2042
3021
  throw new UlwLoopError(message, code);
@@ -2050,12 +3029,13 @@ function nextGoalId(plan) {
2050
3029
  }
2051
3030
  function appendBlockerGoal(plan, args, now) {
2052
3031
  const index = plan.goals.length;
3032
+ const id = nextGoalId(plan);
2053
3033
  const goal = {
2054
- id: nextGoalId(plan),
3034
+ id,
2055
3035
  title: args.title,
2056
3036
  objective: args.objective,
2057
3037
  status: "pending",
2058
- successCriteria: seedDefaultSuccessCriteria(index, args.objective),
3038
+ successCriteria: seedDefaultSuccessCriteria(index, args.objective, { goalId: id }),
2059
3039
  attempt: 0,
2060
3040
  createdAt: now,
2061
3041
  updatedAt: now
@@ -2074,10 +3054,14 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2074
3054
  if (!isFinalRunCompletionCandidate(plan, goal))
2075
3055
  ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
2076
3056
  const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
2077
- const aggregate = codexGoalMode(plan) === "aggregate";
2078
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
3057
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
3058
+ expectedObjective: expectedCodexObjective(plan, goal),
3059
+ acceptedObjectives: compatibleCodexObjectives(plan),
3060
+ acknowledgedObjectives: acknowledgedDriverObjectives(plan)
3061
+ });
2079
3062
  if (!reconciliation.ok)
2080
- ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
3063
+ throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
3064
+ acknowledgeDriverObjective(plan, reconciliation.unacknowledgedObjective);
2081
3065
  const now = iso();
2082
3066
  for (const field of BLOCKER_FIELDS)
2083
3067
  Reflect.deleteProperty(goal, field);
@@ -2089,27 +3073,61 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
2089
3073
  delete plan.activeGoalId;
2090
3074
  const newGoal = appendBlockerGoal(plan, args, now);
2091
3075
  plan.updatedAt = now;
2092
- const codexGoal = reconciliation.snapshot.raw;
3076
+ const codexGoal = snapshot?.raw;
2093
3077
  const blockedEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
2094
3078
  const addedEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
2095
3079
  const summaryEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
2096
3080
  Reflect.set(summaryEntry, "kind", "blocker_recorded");
2097
3081
  const ledgerEntries = [blockedEntry, addedEntry, summaryEntry];
2098
- await writePlan(repoRoot, plan, scope);
2099
- for (const entry of ledgerEntries)
2100
- await appendLedger(repoRoot, entry, scope);
2101
- return { plan, blockedGoal: goal, newGoal, ledgerEntries };
3082
+ await commit(repoRoot, scope, { plan, entries: ledgerEntries });
3083
+ return {
3084
+ plan,
3085
+ blockedGoal: goal,
3086
+ newGoal,
3087
+ ledgerEntries,
3088
+ nextActions: reconciliation.nextActions,
3089
+ warnings: reconciliation.warnings
3090
+ };
2102
3091
  });
2103
3092
  }
2104
3093
 
2105
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts
3094
+ // components/ulw-loop/src/status-next-actions.ts
3095
+ function statusNextActions(plan, surface = "lazycodex") {
3096
+ const actions = [];
3097
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3098
+ if (plan.goals.length === 0)
3099
+ actions.push(`No goals yet: bootstrap with \`${createGoalsAction(surface)}\`.`);
3100
+ else if (active !== undefined)
3101
+ actions.push(...activeGoalActions(plan, active, surface));
3102
+ for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
3103
+ actions.push(surface === "omo-senpi" ? `${goal.id} is review_blocked: capture the reviewer verdict with \`agentToolkit.recordReviewBlockers({ goalId: "${goal.id}", title: "<title>", objective: "<objective>", evidence: "<verdict>" })\`.` : `${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>'\`.`);
3104
+ if (plan.evidenceLayoutVersion !== 2 || active === undefined)
3105
+ actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
3106
+ return actions;
3107
+ }
3108
+ function activeGoalActions(plan, active, surface) {
3109
+ const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
3110
+ if (unresolved.length > 0)
3111
+ return [
3112
+ surface === "omo-senpi" ? `${active.id} has ${unresolved.length} unresolved criterion(s) (${unresolved.map((criterion) => criterion.id).join(", ")}): record proof with \`agentToolkit.recordEvidence({ goalId: "${active.id}", criterionId: "<id>", status: "pass", evidence: "<observable proof>" })\`.` : `${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>"\`.`
3113
+ ];
3114
+ if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
3115
+ return [
3116
+ surface === "omo-senpi" ? `${active.id} passes every criterion and is the final story: complete the driver goal, then call \`agentToolkit.checkpoint({ goalId: "${active.id}", printTemplate: true })\` to build the final quality gate. Use status().result.currentAttemptDir for all quality-gate artifacts.` : `${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate. Use status --json's currentAttemptDir for all quality-gate artifacts.`
3117
+ ];
3118
+ return [
3119
+ surface === "omo-senpi" ? `${active.id} passes every criterion: close it with \`agentToolkit.checkpoint({ goalId: "${active.id}", status: "complete", evidence: "<proof>" })\`. Put quality-gate artifacts under status().result.currentAttemptDir.` : `${active.id} passes every criterion: close it with \`omo-agent-toolkit ulw-loop checkpoint --goal-id ${active.id} --status complete --evidence "<proof>" --codex-goal-json '<get_goal json>'\`. Put quality-gate artifacts under the currentAttemptDir shown by status --json.`
3120
+ ];
3121
+ }
3122
+
3123
+ // components/ulw-loop/src/steering-mutations.ts
2106
3124
  var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
2107
3125
  var isText = (value) => typeof value === "string" && value.trim().length > 0;
2108
3126
  var text3 = (value, key) => {
2109
3127
  const candidate = read2(value, key);
2110
3128
  return isText(candidate) ? candidate.trim() : undefined;
2111
3129
  };
2112
- var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model2) => model2 === value);
3130
+ var isModel2 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2113
3131
  var after = (proposal) => {
2114
3132
  const candidate = read2(proposal, "after");
2115
3133
  return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : undefined;
@@ -2139,11 +3157,11 @@ function nextId(plan, offset) {
2139
3157
  }, 0);
2140
3158
  return `G${String(max + offset).padStart(3, "0")}`;
2141
3159
  }
2142
- function makeGoal2(plan, childGoal, evidence, now, offset) {
3160
+ function makeGoal2(plan, childGoal, evidence, now, offset, surface = "lazycodex") {
2143
3161
  const id = nextId(plan, offset);
2144
3162
  const digits = /^G(\d+)/u.exec(id)?.[1];
2145
3163
  const goalIndex = digits === undefined ? plan.goals.length + offset - 1 : Number(digits) - 1;
2146
- return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective), attempt: 0, createdAt: now, updatedAt: now, evidence };
3164
+ return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective, { goalId: id, surface }), attempt: 0, createdAt: now, updatedAt: now, evidence };
2147
3165
  }
2148
3166
  function reviseWording(plan, proposal, now) {
2149
3167
  const target = goal(plan, targets(proposal)[0]);
@@ -2155,11 +3173,11 @@ function reviseWording(plan, proposal, now) {
2155
3173
  target.steeringRationale = proposal.rationale;
2156
3174
  target.updatedAt = now;
2157
3175
  }
2158
- function splitOrBlock(plan, proposal, now) {
3176
+ function splitOrBlock(plan, proposal, now, surface = "lazycodex") {
2159
3177
  const target = goal(plan, targets(proposal)[0]);
2160
3178
  if (target === undefined)
2161
3179
  return;
2162
- const replacements = children2(proposal).map((item, index) => makeGoal2(plan, item, proposal.evidence, now, index + 1));
3180
+ const replacements = children2(proposal).map((item, index) => makeGoal2(plan, item, proposal.evidence, now, index + 1, surface));
2163
3181
  target.steeringEvidence = proposal.evidence;
2164
3182
  target.steeringRationale = proposal.rationale;
2165
3183
  target.updatedAt = now;
@@ -2184,38 +3202,38 @@ function reviseCriterion(plan, proposal, now) {
2184
3202
  const current = target?.successCriteria[index];
2185
3203
  if (target === undefined || current === undefined)
2186
3204
  return;
2187
- const model2 = read2(proposal, "userModel");
2188
- target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model2) ? model2 : current.userModel };
3205
+ const model = read2(proposal, "userModel");
3206
+ target.successCriteria[index] = { ...current, scenario: text3(proposal, "scenario") ?? current.scenario, expectedEvidence: text3(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel2(model) ? model : current.userModel };
2189
3207
  target.updatedAt = now;
2190
3208
  }
2191
3209
 
2192
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-snapshot.ts
3210
+ // components/ulw-loop/src/steering-snapshot.ts
2193
3211
  function buildSteeringPlanSnapshot(plan, changedGoalIds) {
2194
3212
  const snapshot = {
2195
3213
  updatedAt: plan.updatedAt,
2196
3214
  goalCount: plan.goals.length,
2197
- goalIds: plan.goals.map((goal2) => goal2.id),
2198
- goals: plan.goals.filter((goal2) => changedGoalIds.has(goal2.id))
3215
+ goalIds: plan.goals.map((goal) => goal.id),
3216
+ goals: plan.goals.filter((goal) => changedGoalIds.has(goal.id))
2199
3217
  };
2200
3218
  return plan.activeGoalId === undefined ? snapshot : { ...snapshot, activeGoalId: plan.activeGoalId };
2201
3219
  }
2202
- function changedGoalIdsBetween(before, after2) {
2203
- const beforeById = new Map(before.goals.map((goal2) => [goal2.id, goal2]));
3220
+ function changedGoalIdsBetween(before, after) {
3221
+ const beforeById = new Map(before.goals.map((goal) => [goal.id, goal]));
2204
3222
  const changed = new Set;
2205
- for (const goal2 of after2.goals) {
2206
- const prior = beforeById.get(goal2.id);
2207
- if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal2))
2208
- changed.add(goal2.id);
2209
- beforeById.delete(goal2.id);
3223
+ for (const goal of after.goals) {
3224
+ const prior = beforeById.get(goal.id);
3225
+ if (prior === undefined || JSON.stringify(prior) !== JSON.stringify(goal))
3226
+ changed.add(goal.id);
3227
+ beforeById.delete(goal.id);
2210
3228
  }
2211
3229
  for (const id of beforeById.keys())
2212
3230
  changed.add(id);
2213
3231
  return changed;
2214
3232
  }
2215
3233
 
2216
- // packages/omo-codex/plugin/components/ulw-loop/src/steering.ts
3234
+ // components/ulw-loop/src/steering.ts
2217
3235
  var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
2218
- var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
3236
+ var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "acknowledgedDriverObjectives", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
2219
3237
  var isObject2 = (value) => typeof value === "object" && value !== null;
2220
3238
  var isPlain2 = (value) => isObject2(value) && !Array.isArray(value);
2221
3239
  var read3 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
@@ -2226,7 +3244,7 @@ var text4 = (value, key) => {
2226
3244
  };
2227
3245
  var isKind2 = (value) => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
2228
3246
  var isSource2 = (value) => typeof value === "string" && SOURCES2.some((source) => source === value);
2229
- var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model2) => model2 === value);
3247
+ var isModel3 = (value) => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
2230
3248
  var texts = (value, key) => {
2231
3249
  const candidate = read3(value, key);
2232
3250
  return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
@@ -2367,19 +3385,19 @@ function validateCriterion(plan, proposal, reasons) {
2367
3385
  reasons.push("revise_criterion requires goalId");
2368
3386
  else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId))
2369
3387
  reasons.push("revise_criterion requires criterionId");
2370
- const model2 = read3(proposal, "userModel");
2371
- if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model2 === undefined)
3388
+ const model = read3(proposal, "userModel");
3389
+ if (read3(proposal, "scenario") === undefined && read3(proposal, "expectedEvidence") === undefined && model === undefined)
2372
3390
  reasons.push("revise_criterion requires update");
2373
- if (model2 !== undefined && !isModel3(model2))
3391
+ if (model !== undefined && !isModel3(model))
2374
3392
  reasons.push("invalid userModel");
2375
3393
  }
2376
- function applySteeringMutation(plan, proposal, audit) {
3394
+ function applySteeringMutation(plan, proposal, audit, surface = "lazycodex") {
2377
3395
  const next = structuredClone(plan);
2378
3396
  if (!audit.invariant.accepted)
2379
3397
  return next;
2380
3398
  const now = proposal.now?.toISOString() ?? iso();
2381
3399
  if (proposal.kind === "add_subgoal")
2382
- next.goals.push(makeGoal2(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1));
3400
+ next.goals.push(makeGoal2(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1, surface));
2383
3401
  if (proposal.kind === "reorder_pending") {
2384
3402
  const order = pendingOrder(proposal);
2385
3403
  next.goals = [...order.map((id) => goal2(next, id)).filter((item) => item !== undefined), ...next.goals.filter((item) => !order.includes(item.id))];
@@ -2387,7 +3405,7 @@ function applySteeringMutation(plan, proposal, audit) {
2387
3405
  if (proposal.kind === "revise_pending_wording")
2388
3406
  reviseWording(next, proposal, now);
2389
3407
  if (proposal.kind === "split_subgoal" || proposal.kind === "mark_blocked_superseded")
2390
- splitOrBlock(next, proposal, now);
3408
+ splitOrBlock(next, proposal, now, surface);
2391
3409
  if (proposal.kind === "revise_criterion")
2392
3410
  reviseCriterion(next, proposal, now);
2393
3411
  if (proposal.kind !== "annotate_ledger")
@@ -2397,8 +3415,8 @@ function applySteeringMutation(plan, proposal, audit) {
2397
3415
  function isProposal(value) {
2398
3416
  return isPlain2(value) && isKind2(read3(value, "kind")) && isSource2(read3(value, "source")) && isText2(read3(value, "evidence")) && isText2(read3(value, "rationale"));
2399
3417
  }
2400
- function parseUlwLoopSteeringDirective(text5) {
2401
- const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(text5);
3418
+ function parseUlwLoopSteeringDirective(text) {
3419
+ const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer|omo-agent-toolkit ulw-loop steer):\s*([\s\S]+)$/u.exec(text);
2402
3420
  if (match?.[1] === undefined)
2403
3421
  return null;
2404
3422
  try {
@@ -2410,7 +3428,7 @@ function parseUlwLoopSteeringDirective(text5) {
2410
3428
  throw error;
2411
3429
  }
2412
3430
  }
2413
- async function steerUlwLoop(repoRoot, proposal, scope) {
3431
+ async function steerUlwLoop(repoRoot, proposal, scope, surface = "lazycodex") {
2414
3432
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
2415
3433
  const plan = await readUlwLoopPlan(repoRoot, scope);
2416
3434
  const key = proposal.idempotencyKey ?? proposal.promptSignature;
@@ -2421,7 +3439,7 @@ async function steerUlwLoop(repoRoot, proposal, scope) {
2421
3439
  }
2422
3440
  const audit = validateUlwLoopSteeringProposal(plan, proposal);
2423
3441
  const accepted = audit.invariant.accepted;
2424
- const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
3442
+ const next = accepted ? applySteeringMutation(plan, proposal, audit, surface) : plan;
2425
3443
  const finalAudit = { ...audit };
2426
3444
  if (accepted) {
2427
3445
  const changed = changedGoalIdsBetween(plan, next);
@@ -2430,11 +3448,10 @@ async function steerUlwLoop(repoRoot, proposal, scope) {
2430
3448
  }
2431
3449
  const at = proposal.now?.toISOString() ?? iso();
2432
3450
  const batchEntry = accepted ? batchUpdateLedgerEntry(plan, next, at) : null;
2433
- if (accepted)
2434
- await writePlan(repoRoot, next, scope);
2435
- await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit, at), scope);
3451
+ const entries = [ledgerEntry(proposal, finalAudit, at)];
2436
3452
  if (batchEntry !== null)
2437
- await appendLedger(repoRoot, batchEntry, scope);
3453
+ entries.push(batchEntry);
3454
+ await commit(repoRoot, scope, { plan: next, entries });
2438
3455
  return { plan: next, accepted, audit: finalAudit, rejectedReasons: audit.invariant.rejectedReasons, deduped: false };
2439
3456
  });
2440
3457
  }
@@ -2450,7 +3467,257 @@ function ledgerEntry(proposal, audit, at) {
2450
3467
  return entry;
2451
3468
  }
2452
3469
 
2453
- // packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts
3470
+ // components/ulw-loop/src/sdk/manifest.ts
3471
+ var CRITERION_INPUT = '{ scenario: string, expectedEvidence: string, userModel?: "happy" | "edge" | "regression" | "adversarial", essential?: boolean }';
3472
+ var STEER_KINDS = '"add_subgoal" | "split_subgoal" | "reorder_pending" | "revise_pending_wording" | "revise_criterion" | "annotate_ledger" | "mark_blocked_superseded"';
3473
+ var ULW_LOOP_MANIFEST = {
3474
+ version: 1,
3475
+ name: "ulw-loop",
3476
+ operations: [
3477
+ {
3478
+ name: "help",
3479
+ method: "help",
3480
+ mutating: false,
3481
+ description: "Return this manifest: every operation with its method name, argument fields, and what it does.",
3482
+ args: {}
3483
+ },
3484
+ {
3485
+ name: "create-goals",
3486
+ method: "createGoals",
3487
+ mutating: true,
3488
+ description: "Create the session plan from a brief; one goal per bullet, each seeded with placeholder criteria until revised.",
3489
+ args: {
3490
+ brief: "string",
3491
+ codexGoalMode: '"aggregate" | "per_story"?',
3492
+ force: "boolean?",
3493
+ validationBatchesJson: "string?"
3494
+ }
3495
+ },
3496
+ {
3497
+ name: "status",
3498
+ method: "status",
3499
+ mutating: false,
3500
+ description: "Read the plan, its summary, structured nextActions, the stable evidenceRoot, and the active goal's currentAttemptDir.",
3501
+ args: {}
3502
+ },
3503
+ {
3504
+ name: "complete-goals",
3505
+ method: "completeGoals",
3506
+ mutating: true,
3507
+ description: "Acquire the next eligible goal or resume the in-progress one; returns { done: true } once the aggregate is complete.",
3508
+ args: { retryFailed: "boolean?" }
3509
+ },
3510
+ {
3511
+ name: "checkpoint",
3512
+ method: "checkpoint",
3513
+ mutating: true,
3514
+ description: "Close the goal as complete, failed, or blocked with evidence; the final goal also needs the quality gate. printTemplate returns that gate's template instead.",
3515
+ args: {
3516
+ goalId: "string",
3517
+ status: '"complete" | "failed" | "blocked"',
3518
+ evidence: "string",
3519
+ codexGoalJson: "string?",
3520
+ qualityGateJson: "string?",
3521
+ printTemplate: "true? (with goalId? only)"
3522
+ }
3523
+ },
3524
+ {
3525
+ name: "steer",
3526
+ method: "steer",
3527
+ mutating: true,
3528
+ description: "Propose an evidence-backed plan mutation; revise_criterion replaces a criterion's scenario, expectedEvidence, or userModel.",
3529
+ args: {
3530
+ kind: STEER_KINDS,
3531
+ source: '"finding" | "user_prompt_submit" | "cli"',
3532
+ evidence: "string",
3533
+ rationale: "string",
3534
+ goalId: "string?",
3535
+ criterionId: "string? (revise_criterion)",
3536
+ scenario: "string? (revise_criterion)",
3537
+ expectedEvidence: "string? (revise_criterion)",
3538
+ userModel: '"happy" | "edge" | "regression" | "adversarial"? (revise_criterion)',
3539
+ title: "string? (add_subgoal)",
3540
+ objective: "string? (add_subgoal)",
3541
+ childGoals: "{ title: string, objective: string }[]? (split_subgoal)",
3542
+ revisedTitle: "string? (revise_pending_wording)",
3543
+ revisedObjective: "string? (revise_pending_wording)",
3544
+ pendingOrder: "string[]? (reorder_pending)",
3545
+ blockedReason: "string? (mark_blocked_superseded)",
3546
+ idempotencyKey: "string?"
3547
+ }
3548
+ },
3549
+ {
3550
+ name: "add-goal",
3551
+ method: "addGoal",
3552
+ mutating: true,
3553
+ description: "Append a goal; pass successCriteria to define its criteria in the same call, otherwise placeholders name the revise call.",
3554
+ args: { title: "string", objective: "string", successCriteria: `${CRITERION_INPUT}[]?` }
3555
+ },
3556
+ {
3557
+ name: "criteria",
3558
+ method: "criteria",
3559
+ mutating: false,
3560
+ description: "List one goal's success criteria with their status and captured evidence.",
3561
+ args: { goalId: "string" }
3562
+ },
3563
+ {
3564
+ name: "record-evidence",
3565
+ method: "recordEvidence",
3566
+ mutating: true,
3567
+ description: "Record a criterion's pass, fail, or blocked evidence; artifacts must exist (resolved against the session cwd) and are stored with the criterion and the ledger entry.",
3568
+ args: {
3569
+ goalId: "string",
3570
+ criterionId: "string",
3571
+ status: '"pass" | "fail" | "blocked"',
3572
+ evidence: "string",
3573
+ notes: "string?",
3574
+ artifacts: "string[]?"
3575
+ }
3576
+ },
3577
+ {
3578
+ name: "record-review-blockers",
3579
+ method: "recordReviewBlockers",
3580
+ mutating: true,
3581
+ description: "Mark the final goal review_blocked and append the blocker goal the reviewer verdict names.",
3582
+ args: { goalId: "string", title: "string", objective: "string", evidence: "string", codexGoalJson: "string?" }
3583
+ }
3584
+ ]
3585
+ };
3586
+
3587
+ // components/ulw-loop/src/sdk/factory.ts
3588
+ function validateCodexGoalJson(raw) {
3589
+ if (raw === undefined)
3590
+ return;
3591
+ try {
3592
+ JSON.parse(raw);
3593
+ } catch (error) {
3594
+ throw new UlwLoopError(`Invalid codexGoal: ${error instanceof Error ? error.message : "not valid JSON"}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
3595
+ }
3596
+ }
3597
+ function validateContext(context) {
3598
+ if (!context.cwd.trim())
3599
+ throw new UlwLoopError("cwd is required.", "ULW_LOOP_CWD_REQUIRED");
3600
+ const sessionId = context.sessionId.trim();
3601
+ if (!sessionId)
3602
+ throw new UlwLoopError("ULW_LOOP_SESSION_ID_REQUIRED: sessionId is required.", "ULW_LOOP_SESSION_ID_REQUIRED");
3603
+ const normalizedSessionId = normalizeUlwLoopSessionId(sessionId);
3604
+ if (normalizedSessionId === null || /(?:^|[\\/])\.\.(?:[\\/]|$)/.test(sessionId))
3605
+ throw new UlwLoopError("ULW_LOOP_SESSION_ID_INVALID: sessionId normalizes to null.", "ULW_LOOP_SESSION_ID_INVALID");
3606
+ if (context.surface !== "omo-senpi" && context.surface !== "lazycodex")
3607
+ throw new UlwLoopError("surface must be omo-senpi or lazycodex.", "ULW_LOOP_SURFACE_INVALID");
3608
+ }
3609
+ function errorDetails(error) {
3610
+ const details = error.details === undefined ? undefined : Object.fromEntries(Object.entries(error.details).map(([key, value]) => [key, String(value)]));
3611
+ return details === undefined ? { code: error.code, message: error.message } : { code: error.code, message: error.message, details };
3612
+ }
3613
+ function failure(operation, error) {
3614
+ return { ok: false, operation, error: errorDetails(error) };
3615
+ }
3616
+ function caught(operation, error) {
3617
+ return failure(operation, error instanceof UlwLoopError ? error : new UlwLoopError(error.message, "ULW_LOOP_ERROR"));
3618
+ }
3619
+ function isKnownRequest(request) {
3620
+ return ULW_LOOP_MANIFEST.operations.some((operation) => operation.name === request.operation);
3621
+ }
3622
+ function unreachable(value) {
3623
+ throw new UlwLoopError(`Unhandled operation: ${String(value)}`, "ULW_LOOP_OPERATION_UNHANDLED");
3624
+ }
3625
+ function stringsFrom(result, key) {
3626
+ if (!(key in result))
3627
+ return [];
3628
+ const value = Object.entries(result).find(([name]) => name === key)?.[1];
3629
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string").slice(0, 8) : [];
3630
+ }
3631
+ function checkpointWithValidatedSnapshot(context, scope, args) {
3632
+ validateCodexGoalJson(args.codexGoalJson);
3633
+ return checkpointUlwLoop(context.cwd, args, scope, { surface: context.surface });
3634
+ }
3635
+ function createAgentToolkit(context, deps = {}) {
3636
+ validateContext(context);
3637
+ const scope = { sessionId: context.sessionId };
3638
+ const notify = async (operation, response) => {
3639
+ if (deps.hooks?.onOperation !== undefined)
3640
+ await deps.hooks.onOperation({ operation, context, response });
3641
+ return response;
3642
+ };
3643
+ const invoke = async (operation, fn) => {
3644
+ try {
3645
+ const result = await fn();
3646
+ const nextActions = typeof result === "object" && result !== null ? stringsFrom(result, "nextActions") : [];
3647
+ const warnings = typeof result === "object" && result !== null ? stringsFrom(result, "warnings") : [];
3648
+ return await notify(operation, {
3649
+ ok: true,
3650
+ operation,
3651
+ result,
3652
+ nextActions,
3653
+ ...warnings.length === 0 ? {} : { warnings }
3654
+ });
3655
+ } catch (error) {
3656
+ if (error instanceof UlwLoopError && error.code === "ULW_LOOP_PLAN_MISSING" && context.surface === "omo-senpi")
3657
+ return notify(operation, failure(operation, planMissingError(ulwLoopGoalsRelativePath(scope), listUlwLoopSessionIds(context.cwd), context.surface)));
3658
+ const response = caught(operation, error instanceof Error ? error : new Error("ULW_LOOP_ERROR"));
3659
+ return notify(operation, response);
3660
+ }
3661
+ };
3662
+ const toolkit = {
3663
+ dispatch: async (request) => {
3664
+ if (!isKnownRequest(request))
3665
+ return failure(request.operation, new UlwLoopError(`Unknown operation: ${request.operation}`, "ULW_LOOP_OPERATION_UNKNOWN"));
3666
+ switch (request.operation) {
3667
+ case "help":
3668
+ return toolkit.help();
3669
+ case "create-goals":
3670
+ return toolkit.createGoals(request.args);
3671
+ case "status":
3672
+ return toolkit.status();
3673
+ case "complete-goals":
3674
+ return toolkit.completeGoals(request.args);
3675
+ case "checkpoint":
3676
+ return toolkit.checkpoint(request.args);
3677
+ case "steer":
3678
+ return toolkit.steer(request.args);
3679
+ case "add-goal":
3680
+ return toolkit.addGoal(request.args);
3681
+ case "criteria":
3682
+ return toolkit.criteria(request.args);
3683
+ case "record-evidence":
3684
+ return toolkit.recordEvidence(request.args);
3685
+ case "record-review-blockers":
3686
+ return toolkit.recordReviewBlockers(request.args);
3687
+ default:
3688
+ return unreachable(request);
3689
+ }
3690
+ },
3691
+ help: () => invoke("help", async () => ULW_LOOP_MANIFEST),
3692
+ createGoals: (args) => invoke("create-goals", () => createUlwLoopPlan(context.cwd, args, scope, context.surface)),
3693
+ status: () => invoke("status", async () => {
3694
+ const plan = await readUlwLoopPlan(context.cwd, scope);
3695
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3696
+ return {
3697
+ plan,
3698
+ summary: summarizeUlwLoopPlan(plan),
3699
+ nextActions: statusNextActions(plan, context.surface),
3700
+ evidenceRoot: ulwLoopEvidenceRoot(scope),
3701
+ ...active === undefined || plan.evidenceLayoutVersion !== 2 ? {} : { currentAttemptDir: ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) }
3702
+ };
3703
+ }),
3704
+ completeGoals: (args = {}) => invoke("complete-goals", () => startNextUlwLoop(context.cwd, args, scope)),
3705
+ checkpoint: (args) => invoke("checkpoint", () => args.printTemplate === true ? checkpointTemplate(context.cwd, scope, args.goalId, { surface: context.surface }) : checkpointWithValidatedSnapshot(context, scope, args)),
3706
+ steer: (args) => invoke("steer", () => steerUlwLoop(context.cwd, args, scope, context.surface)),
3707
+ addGoal: (args) => invoke("add-goal", () => addUlwLoopGoal(context.cwd, args, scope, context.surface)),
3708
+ criteria: (args) => invoke("criteria", async () => {
3709
+ const plan = await readUlwLoopPlan(context.cwd, scope);
3710
+ const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
3711
+ if (goal === undefined)
3712
+ throw new UlwLoopError(`Unknown ulw-loop id: ${args.goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND");
3713
+ return { goalId: goal.id, criteria: goal.successCriteria };
3714
+ }),
3715
+ recordEvidence: (args) => invoke("record-evidence", () => recordEvidence(context.cwd, args, scope)),
3716
+ recordReviewBlockers: (args) => invoke("record-review-blockers", () => recordFinalReviewBlockers(context.cwd, args, scope))
3717
+ };
3718
+ return toolkit;
3719
+ }
3720
+ // components/ulw-loop/src/steering-batch.ts
2454
3721
  async function steerUlwLoopBatch(repoRoot, proposals, scope) {
2455
3722
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
2456
3723
  const plan = await readUlwLoopPlan(repoRoot, scope);
@@ -2458,7 +3725,7 @@ async function steerUlwLoopBatch(repoRoot, proposals, scope) {
2458
3725
  const failed = prepared.results.find((item) => !item.accepted);
2459
3726
  if (failed !== undefined) {
2460
3727
  const entry = rejectedLedgerEntry(prepared.results);
2461
- await appendLedger(repoRoot, entry, scope);
3728
+ await commit(repoRoot, scope, { plan, entries: [entry] });
2462
3729
  return rejected(plan, prepared.results, failed.rejectedReasons);
2463
3730
  }
2464
3731
  let next = plan;
@@ -2467,10 +3734,9 @@ async function steerUlwLoopBatch(repoRoot, proposals, scope) {
2467
3734
  next = item.prepared.next;
2468
3735
  const fresh = prepared.items.filter((item) => item.kind === "fresh");
2469
3736
  if (fresh.length > 0) {
2470
- await writePlan(repoRoot, next, scope);
2471
3737
  const entries = fresh.map((item) => ledgerEntry2(item.prepared.proposal, item.prepared.audit, item.prepared.proposal.now?.toISOString() ?? iso()));
2472
3738
  const batchEntry = batchUpdateLedgerEntry(plan, next, iso());
2473
- await appendLedgerEntries(repoRoot, batchEntry === null ? entries : [...entries, batchEntry], scope);
3739
+ await commit(repoRoot, scope, { plan: next, entries: batchEntry === null ? entries : [...entries, batchEntry] });
2474
3740
  }
2475
3741
  return { plan: next, accepted: true, results: prepared.results, rejectedReasons: [] };
2476
3742
  });
@@ -2483,16 +3749,16 @@ async function prepareBatch(repoRoot, plan, proposals, scope) {
2483
3749
  const key = proposal.idempotencyKey ?? proposal.promptSignature;
2484
3750
  const prior = key === undefined ? undefined : await findAcceptedSteeringLedgerEntry(repoRoot, key, scope);
2485
3751
  if (prior?.steering !== undefined) {
2486
- const result2 = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
2487
- items.push({ kind: "deduped", result: result2 });
2488
- results.push(result2);
3752
+ const result = { accepted: true, deduped: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [] };
3753
+ items.push({ kind: "deduped", result });
3754
+ results.push(result);
2489
3755
  continue;
2490
3756
  }
2491
3757
  const audit = validateUlwLoopSteeringProposal(current, proposal);
2492
3758
  if (!audit.invariant.accepted) {
2493
- const result2 = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
2494
- items.push({ kind: "deduped", result: result2 });
2495
- results.push(result2);
3759
+ const result = { accepted: false, deduped: false, audit, rejectedReasons: audit.invariant.rejectedReasons };
3760
+ items.push({ kind: "deduped", result });
3761
+ results.push(result);
2496
3762
  continue;
2497
3763
  }
2498
3764
  const next = applySteeringMutation(current, proposal, audit);
@@ -2531,20 +3797,20 @@ function ledgerEntry2(proposal, audit, at) {
2531
3797
  return entry;
2532
3798
  }
2533
3799
 
2534
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts
3800
+ // components/ulw-loop/src/cli-subcommands.ts
2535
3801
  async function createGoals(repoRoot, argv, json, scope) {
2536
3802
  const briefFile = readValue(argv, "--brief-file");
2537
- const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile6(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
3803
+ const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile4(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
2538
3804
  if (!brief.trim()) {
2539
3805
  throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
2540
3806
  }
2541
3807
  const validationBatchesJson = readValue(argv, "--validation-batch-json");
2542
- const plan = await createUlwLoopPlan(repoRoot, {
3808
+ const plan = unwrap(await toolkitFor(repoRoot, scope).createGoals({
2543
3809
  brief,
2544
3810
  codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")),
2545
3811
  force: hasFlag(argv, "--force"),
2546
3812
  ...validationBatchesJson === undefined ? {} : { validationBatchesJson }
2547
- }, scope);
3813
+ }));
2548
3814
  if (json)
2549
3815
  printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
2550
3816
  else {
@@ -2557,22 +3823,15 @@ ledger: ${plan.ledgerPath}
2557
3823
  return 0;
2558
3824
  }
2559
3825
  async function status(repoRoot, json, scope) {
2560
- const plan = await readUlwLoopPlan(repoRoot, scope);
2561
- if (json) {
2562
- const active = plan.goals.find((goal3) => goal3.id === plan.activeGoalId);
2563
- const currentAttemptDir = plan.evidenceLayoutVersion === 2 && active ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : undefined;
2564
- printJson({
2565
- ok: true,
2566
- plan,
2567
- summary: summarizeUlwLoopPlan(plan),
2568
- ...currentAttemptDir === undefined ? {} : { currentAttemptDir }
2569
- });
2570
- } else
2571
- printStatus(plan);
3826
+ const status = unwrap(await toolkitFor(repoRoot, scope).status());
3827
+ if (json)
3828
+ printJson({ ok: true, ...status });
3829
+ else
3830
+ printStatus(status.plan);
2572
3831
  return 0;
2573
3832
  }
2574
3833
  async function completeGoals(repoRoot, argv, json, scope) {
2575
- const result = await startNextUlwLoop(repoRoot, { retryFailed: hasFlag(argv, "--retry-failed") }, scope);
3834
+ const result = unwrap(await toolkitFor(repoRoot, scope).completeGoals({ retryFailed: hasFlag(argv, "--retry-failed") }));
2576
3835
  if ("done" in result) {
2577
3836
  const handoff = blockedDecisionHandoff(result.plan);
2578
3837
  if (json) {
@@ -2601,16 +3860,19 @@ async function steer(repoRoot, argv, json, scope) {
2601
3860
  const proposals = await parseSteeringProposals(argv);
2602
3861
  const single = proposals[0];
2603
3862
  if (single !== undefined && proposals.length === 1 && readValue(argv, "--proposals-json") === undefined) {
2604
- const result2 = await steerUlwLoop(repoRoot, single, scope);
2605
- printSteerResult(result2, json);
2606
- return result2.accepted ? 0 : 1;
3863
+ const result = unwrap(await toolkitFor(repoRoot, scope).steer(single));
3864
+ printSteerResult(result, json);
3865
+ return result.accepted ? 0 : 1;
2607
3866
  }
2608
3867
  const result = await steerUlwLoopBatch(repoRoot, proposals, scope);
2609
3868
  printSteerBatchResult(result, json);
2610
3869
  return result.accepted ? 0 : 1;
2611
3870
  }
2612
3871
  async function addGoal(repoRoot, argv, json, scope) {
2613
- const result = await addUlwLoopGoal(repoRoot, { title: required4(argv, "--title"), objective: required4(argv, "--objective") }, scope);
3872
+ const result = unwrap(await toolkitFor(repoRoot, scope).addGoal({
3873
+ title: required4(argv, "--title"),
3874
+ objective: required4(argv, "--objective")
3875
+ }));
2614
3876
  if (json)
2615
3877
  printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
2616
3878
  else {
@@ -2622,19 +3884,19 @@ async function addGoal(repoRoot, argv, json, scope) {
2622
3884
  }
2623
3885
  async function criteria(repoRoot, argv, json, scope) {
2624
3886
  const goalId = required4(argv, "--goal-id");
2625
- const goal3 = findGoal3(await readUlwLoopPlan(repoRoot, scope), goalId);
3887
+ const result = unwrap(await toolkitFor(repoRoot, scope).criteria({ goalId }));
2626
3888
  if (json)
2627
- printJson({ ok: true, goalId: goal3.id, criteria: goal3.successCriteria });
3889
+ printJson({ ok: true, goalId: result.goalId, criteria: result.criteria });
2628
3890
  else {
2629
- process.stdout.write(`criteria for ${goal3.id}:
2630
- ${goal3.successCriteria.map(formatCriterionForCli).join(`
3891
+ process.stdout.write(`criteria for ${result.goalId}:
3892
+ ${result.criteria.map(formatCriterionForCli).join(`
2631
3893
  `)}
2632
3894
  `);
2633
3895
  }
2634
3896
  return 0;
2635
3897
  }
2636
3898
  async function captureEvidence(repoRoot, argv, json, scope) {
2637
- const result = await recordEvidence(repoRoot, parseRecordEvidenceArgs(argv), scope);
3899
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordEvidence(parseRecordEvidenceArgs(argv)));
2638
3900
  if (json)
2639
3901
  printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
2640
3902
  else {
@@ -2648,13 +3910,13 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
2648
3910
  if (codexGoalJson === undefined) {
2649
3911
  throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
2650
3912
  }
2651
- const result = await recordFinalReviewBlockers(repoRoot, {
3913
+ const result = unwrap(await toolkitFor(repoRoot, scope).recordReviewBlockers({
2652
3914
  goalId: required4(argv, "--goal-id"),
2653
3915
  title: required4(argv, "--title"),
2654
3916
  objective: required4(argv, "--objective"),
2655
3917
  evidence: required4(argv, "--evidence"),
2656
3918
  codexGoalJson
2657
- }, scope);
3919
+ }));
2658
3920
  if (json) {
2659
3921
  printJson({
2660
3922
  ok: true,
@@ -2662,6 +3924,8 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
2662
3924
  blockedGoal: result.blockedGoal,
2663
3925
  goal: result.newGoal,
2664
3926
  ledgerEntries: result.ledgerEntries,
3927
+ nextActions: result.nextActions,
3928
+ warnings: result.warnings,
2665
3929
  summary: summarizeUlwLoopPlan(result.plan)
2666
3930
  });
2667
3931
  } else {
@@ -2670,6 +3934,20 @@ async function reviewBlockers(repoRoot, argv, json, scope) {
2670
3934
  }
2671
3935
  return 0;
2672
3936
  }
3937
+ function toolkitFor(repoRoot, scope) {
3938
+ const sessionId = scope?.sessionId?.trim();
3939
+ if (sessionId === undefined || sessionId.length === 0) {
3940
+ throw new UlwLoopError("Missing --session-id.", "ULW_LOOP_SESSION_ID_REQUIRED", {
3941
+ details: { flag: "--session-id" }
3942
+ });
3943
+ }
3944
+ return createAgentToolkit({ cwd: repoRoot, sessionId, surface: resolveToolkitSurface() });
3945
+ }
3946
+ function unwrap(response) {
3947
+ if (response.ok)
3948
+ return response.result;
3949
+ throw new UlwLoopError(response.error.message, response.error.code);
3950
+ }
2673
3951
  function formatCriterionForCli(criterion) {
2674
3952
  const marker = isEssentialCriterion(criterion) ? "essential" : "non-essential";
2675
3953
  return `- ${criterion.id} [${criterion.status}] [${marker}] (${criterion.userModel}) ${criterion.scenario} evidence: ${criterion.capturedEvidence ?? "pending"}`;
@@ -2680,14 +3958,8 @@ function required4(argv, flag) {
2680
3958
  return value;
2681
3959
  throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
2682
3960
  }
2683
- function findGoal3(plan, goalId) {
2684
- const goal3 = plan.goals.find((candidate) => candidate.id === goalId);
2685
- if (goal3 !== undefined)
2686
- return goal3;
2687
- throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
2688
- }
2689
3961
 
2690
- // packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts
3962
+ // components/ulw-loop/src/cli-commands.ts
2691
3963
  var ULW_LOOP_SUBCOMMANDS = [
2692
3964
  "help",
2693
3965
  "create-goals",
@@ -2710,7 +3982,6 @@ async function ulwLoopCommand(argv) {
2710
3982
  const repoRoot = process.cwd();
2711
3983
  const json = hasFlag(rest, "--json");
2712
3984
  try {
2713
- const scope = commandScope(rest);
2714
3985
  if (!isUlwLoopSubcommand(command)) {
2715
3986
  if (json) {
2716
3987
  printJsonError(new UlwLoopError(`Unknown ulw-loop subcommand: ${command}.`, "ULW_LOOP_SUBCOMMAND_UNKNOWN", {
@@ -2722,11 +3993,18 @@ async function ulwLoopCommand(argv) {
2722
3993
  `);
2723
3994
  return 1;
2724
3995
  }
2725
- switch (command) {
2726
- case "help":
2727
- process.stdout.write(`${ULW_LOOP_HELP}
3996
+ if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
3997
+ process.stdout.write(`${subcommandHelp(command)}
3998
+ `);
3999
+ return 0;
4000
+ }
4001
+ if (command === "help") {
4002
+ process.stdout.write(`${ULW_LOOP_HELP}
2728
4003
  `);
2729
- return 0;
4004
+ return 0;
4005
+ }
4006
+ const scope = commandScope(repoRoot, rest);
4007
+ switch (command) {
2730
4008
  case "create-goals":
2731
4009
  return await createGoals(repoRoot, rest, json, scope);
2732
4010
  case "status":
@@ -2772,26 +4050,29 @@ var SESSION_ID_FLAG = "--session-id";
2772
4050
  function sessionIdFlagPresent(argv) {
2773
4051
  return hasFlag(argv, SESSION_ID_FLAG) || argv.some((arg) => arg.startsWith(`${SESSION_ID_FLAG}=`));
2774
4052
  }
2775
- function commandScope(argv) {
4053
+ function commandScope(repoRoot, argv) {
2776
4054
  if (sessionIdFlagPresent(argv)) {
2777
- const sessionId2 = readValue(argv, SESSION_ID_FLAG)?.trim();
2778
- if (!sessionId2) {
2779
- throw new UlwLoopError(`${SESSION_ID_FLAG} requires a non-empty value.`, "ULW_LOOP_SESSION_ID_REQUIRED", {
4055
+ const sessionId = readValue(argv, SESSION_ID_FLAG)?.trim();
4056
+ if (!sessionId) {
4057
+ throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
2780
4058
  details: { flag: SESSION_ID_FLAG }
2781
4059
  });
2782
4060
  }
2783
- return { sessionId: sessionId2 };
4061
+ return { sessionId };
2784
4062
  }
2785
4063
  const sessionId = resolveUlwLoopSessionIdFromEnv();
2786
- return sessionId === null ? undefined : { sessionId };
4064
+ if (sessionId !== null)
4065
+ return { sessionId };
4066
+ const existingSessionIds = listUlwLoopSessionIds(repoRoot);
4067
+ throw new UlwLoopError(sessionScopeRequiredMessage(SESSION_ID_FLAG, existingSessionIds), "ULW_LOOP_SESSION_SCOPE_REQUIRED", { details: { flag: SESSION_ID_FLAG, existingSessionIds } });
2787
4068
  }
2788
4069
 
2789
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
2790
- import { readFileSync as readFileSync2 } from "node:fs";
4070
+ // components/ulw-loop/src/ultrawork-directive.ts
4071
+ import { readFileSync as readFileSync5 } from "node:fs";
2791
4072
 
2792
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-skill-pointer.ts
2793
- import { existsSync as existsSync5, readFileSync } from "node:fs";
2794
- import { fileURLToPath } from "node:url";
4073
+ // components/ulw-loop/src/ultrawork-skill-pointer.ts
4074
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
4075
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2795
4076
  var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
2796
4077
  ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
2797
4078
 
@@ -2822,22 +4103,22 @@ Do not start the requested work until all three steps are complete.
2822
4103
  `;
2823
4104
  var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
2824
4105
  var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
2825
- var ULTRAWORK_DIRECTIVE = readFileSync(new URL("../directive.md", import.meta.url), "utf8");
4106
+ var ULTRAWORK_DIRECTIVE = readFileSync4(new URL("../directive.md", import.meta.url), "utf8");
2826
4107
  function resolveUltraworkSkillFilePath() {
2827
- return fileURLToPath(ULTRAWORK_SKILL_FILE_URL);
4108
+ return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
2828
4109
  }
2829
4110
  function buildUltraworkSkillPointer(skillFilePath) {
2830
4111
  return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
2831
4112
  }
2832
4113
  function buildUltraworkAdditionalContext(options = {}) {
2833
4114
  const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
2834
- if (skillFilePath !== null && existsSync5(skillFilePath)) {
4115
+ if (skillFilePath !== null && existsSync6(skillFilePath)) {
2835
4116
  return buildUltraworkSkillPointer(skillFilePath);
2836
4117
  }
2837
4118
  return ULTRAWORK_DIRECTIVE;
2838
4119
  }
2839
4120
 
2840
- // packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts
4121
+ // components/ulw-loop/src/ultrawork-directive.ts
2841
4122
  var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
2842
4123
  var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
2843
4124
  var TRANSCRIPT_SEARCH_BYTES = 512000;
@@ -2885,7 +4166,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
2885
4166
  return false;
2886
4167
  }
2887
4168
  function readTranscriptTail(transcriptPath) {
2888
- const rawTranscript = readFileSync2(transcriptPath);
4169
+ const rawTranscript = readFileSync5(transcriptPath);
2889
4170
  return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
2890
4171
  }
2891
4172
  function isUltraworkPrompt(prompt) {
@@ -2940,7 +4221,7 @@ function isRecord3(value) {
2940
4221
  return typeof value === "object" && value !== null && !Array.isArray(value);
2941
4222
  }
2942
4223
 
2943
- // packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts
4224
+ // components/ulw-loop/src/codex-hook.ts
2944
4225
  var CREATE_GOAL_TOOL_NAME = "create_goal";
2945
4226
  var CREATE_GOAL_PAYLOAD_WARNING = "Use create_goal with objective only. Omit token_budget so the goal stays unlimited, and put lifecycle status changes on update_goal.";
2946
4227
  function parseUserPromptSubmitPayload(raw) {
@@ -3066,97 +4347,306 @@ function optionalString(value) {
3066
4347
  return value === undefined || typeof value === "string";
3067
4348
  }
3068
4349
  function readAll(stdin) {
3069
- return new Promise((resolve4, reject) => {
4350
+ return new Promise((resolve, reject) => {
3070
4351
  let data = "";
3071
4352
  stdin.setEncoding("utf8");
3072
4353
  stdin.on("data", (chunk) => {
3073
4354
  data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
3074
4355
  });
3075
4356
  stdin.once("error", reject);
3076
- stdin.once("end", () => resolve4(data));
4357
+ stdin.once("end", () => resolve(data));
3077
4358
  });
3078
4359
  }
3079
4360
 
3080
- // packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts
3081
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync as statSync2, writeFileSync } from "node:fs";
3082
- import { join as join2 } from "node:path";
3083
- var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
3084
- var DEFAULT_FANOUT_LIMIT = 60;
3085
- var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|final gate review/i;
3086
- function applySpawnGuards(payload) {
4361
+ // components/ulw-loop/src/spawn-guard.ts
4362
+ import { mkdirSync as mkdirSync3 } from "node:fs";
4363
+ import { join as join7 } from "node:path";
4364
+
4365
+ // components/ulw-loop/src/spawn-budget-io.ts
4366
+ import { randomBytes } from "node:crypto";
4367
+ import { existsSync as existsSync7, readFileSync as readFileSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
4368
+ import { dirname as dirname3, join as join6 } from "node:path";
4369
+ function readAdmissionBreaker(sessionId) {
4370
+ const dataDir = process.env["PLUGIN_DATA"];
4371
+ if (typeof dataDir !== "string")
4372
+ return null;
4373
+ try {
4374
+ const value = JSON.parse(readFileSync6(join6(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
4375
+ return typeof value === "object" && value !== null && "reason" in value && typeof value.reason === "string" ? value.reason : "capacity limit";
4376
+ } catch {
4377
+ return null;
4378
+ }
4379
+ }
4380
+ function atomicWriteJson(targetPath, data) {
4381
+ const tmp = join6(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
4382
+ writeFileSync2(tmp, JSON.stringify(data));
4383
+ renameSync2(tmp, targetPath);
4384
+ }
4385
+ function isNonEmptyFile(path) {
4386
+ try {
4387
+ return existsSync7(path) && statSync3(path).size > 0;
4388
+ } catch (error) {
4389
+ if (error instanceof Error)
4390
+ return false;
4391
+ throw error;
4392
+ }
4393
+ }
4394
+ function readCount(counterPath) {
4395
+ try {
4396
+ const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
4397
+ return typeof parsed === "object" && parsed !== null && "count" in parsed && typeof parsed.count === "number" && parsed.count >= 0 ? parsed.count : 0;
4398
+ } catch (error) {
4399
+ if (error instanceof Error)
4400
+ return 0;
4401
+ throw error;
4402
+ }
4403
+ }
4404
+ function readCounts(counterPath) {
4405
+ try {
4406
+ const parsed = JSON.parse(readFileSync6(counterPath, "utf8"));
4407
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
4408
+ return {};
4409
+ const counts = {};
4410
+ for (const [key, value] of Object.entries(parsed))
4411
+ if (typeof value === "number" && value >= 0)
4412
+ counts[key] = value;
4413
+ return counts;
4414
+ } catch (error) {
4415
+ if (error instanceof Error)
4416
+ return {};
4417
+ throw error;
4418
+ }
4419
+ }
4420
+
4421
+ // components/ulw-loop/src/spawn-role-guard.ts
4422
+ var LAZYCODEX_SPAWN_ROLES = new Set([
4423
+ "explorer",
4424
+ "lazycodex-clone-fidelity-reviewer",
4425
+ "lazycodex-code-reviewer",
4426
+ "lazycodex-gate-reviewer",
4427
+ "lazycodex-qa-executor",
4428
+ "lazycodex-worker-high",
4429
+ "lazycodex-worker-low",
4430
+ "lazycodex-worker-medium",
4431
+ "librarian",
4432
+ "metis",
4433
+ "momus",
4434
+ "plan"
4435
+ ]);
4436
+ function spawnRoleDenial(input) {
4437
+ const role = typeof input === "object" && input !== null && "agent_type" in input ? input.agent_type : undefined;
4438
+ if (typeof role === "string" && LAZYCODEX_SPAWN_ROLES.has(role))
4439
+ return null;
4440
+ return `LazyCodex requires an explicit registered agent_type: ${[...LAZYCODEX_SPAWN_ROLES].join(", ")}. Received ${JSON.stringify(role) ?? "no agent_type"}. Use the matching role and fork_turns: "none" (V2) or fork_context: false (V1), unless full history is deliberately required. The hook cannot see the tool schema; if agent_type is unavailable, stop and report incompatible role routing rather than spawning a generic agent. Describing a role in message does not select its TOML.`;
4441
+ }
4442
+
4443
+ // components/ulw-loop/src/spawn-guard.ts
4444
+ var SPAWN_TOOL_TOKENS = new Set([
4445
+ "spawn_agent",
4446
+ "multi_agent_v1.spawn_agent",
4447
+ "collaborationspawn_agent",
4448
+ "collaboration.spawn_agent"
4449
+ ]);
4450
+ var DEFAULT_FANOUT_LIMIT = 24;
4451
+ var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
4452
+ var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-native-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
4453
+ var REVIEW_AGENT_TYPES = [
4454
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
4455
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
4456
+ ...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa),
4457
+ ...Object.keys(LEGACY_REVIEWER_AGENT_ALIASES)
4458
+ ];
4459
+ var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
4460
+ function applySpawnGuards(payload, options = {}) {
4461
+ if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
4462
+ return "";
4463
+ if (resolveToolkitSurface() === "lazycodex") {
4464
+ const reason = spawnRoleDenial(payload.tool_input);
4465
+ if (reason !== null)
4466
+ return deny(reason);
4467
+ }
4468
+ return applySpawnBudgetGuards(payload, options);
4469
+ }
4470
+ function applySpawnBudgetGuards(payload, options = {}) {
3087
4471
  if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
3088
4472
  return "";
3089
- const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3090
- const plan = readPlan(join2(stateDir, "goals.json"));
4473
+ const breaker = readAdmissionBreaker(payload.session_id);
4474
+ if (breaker !== null)
4475
+ return deny(`Subagent admission failed earlier in this session (${breaker}). Do not spawn more workers or reviewers; report the capacity block and wait for the user.`);
4476
+ const scope = { sessionId: payload.session_id };
4477
+ const stateDir = ulwLoopDir(payload.cwd, scope);
4478
+ const plan = readPlan(payload.cwd, payload.session_id);
3091
4479
  if (plan === null)
3092
4480
  return "";
4481
+ const lockOptions = options.lockTimeoutMs === undefined ? {} : { timeoutMs: options.lockTimeoutMs };
4482
+ try {
4483
+ return withStateLockSync(ulwLoopStateLockPath(payload.cwd, scope), () => evaluateGuards(payload, plan, stateDir), lockOptions);
4484
+ } catch (error) {
4485
+ if (isStateLockTimeout(error))
4486
+ return deny(`ulw-loop spawn guard could not take the session state lock: ${error.message}`);
4487
+ throw error;
4488
+ }
4489
+ }
4490
+ function evaluateGuards(payload, plan, stateDir) {
4491
+ const fanOutPeek = peekFanOutBudget(stateDir);
4492
+ if (fanOutPeek !== null)
4493
+ return deny(fanOutPeek);
4494
+ const missingArtifact = missingGateArtifact(payload, plan);
4495
+ if (missingArtifact !== null)
4496
+ return deny(`record manual QA first; gate audits its artifacts: missing ${missingArtifact}`);
4497
+ const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
4498
+ if (reviewDenial !== null)
4499
+ return deny(reviewDenial);
3093
4500
  const fanOutDenial = consumeFanOutBudget(stateDir);
3094
4501
  if (fanOutDenial !== null)
3095
4502
  return deny(fanOutDenial);
3096
- const missingArtifact = missingGateArtifact(payload, plan);
3097
- if (missingArtifact !== null)
3098
- return deny(`spawn code-review + QA first; gate audits their artifacts: missing ${missingArtifact}`);
3099
4503
  return "";
3100
4504
  }
4505
+ async function runSpawnAdmissionRecorderCli(stdin, stdout) {
4506
+ const chunks = [];
4507
+ for await (const chunk of stdin)
4508
+ chunks.push(Buffer.from(chunk));
4509
+ try {
4510
+ const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
4511
+ const response = typeof payload["tool_response"] === "string" ? payload["tool_response"] : JSON.stringify(payload["tool_response"] ?? "");
4512
+ if (!/too many active cells|AgentLimitReached|max_threads|max_concurrent_threads_per_session/i.test(response))
4513
+ return;
4514
+ const dataDir = process.env["PLUGIN_DATA"];
4515
+ if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
4516
+ return;
4517
+ const markerDir = join7(dataDir, "spawn-breaker");
4518
+ try {
4519
+ mkdirSync3(markerDir, { recursive: true });
4520
+ atomicWriteJson(join7(markerDir, `${payload["session_id"]}.json`), {
4521
+ reason: response,
4522
+ at: new Date().toISOString()
4523
+ });
4524
+ } catch (error) {
4525
+ const message = error instanceof Error ? error.message : String(error);
4526
+ process.stderr.write(`[ulw-loop] spawn-guard: could not persist admission failure: ${message}
4527
+ `);
4528
+ }
4529
+ } catch {}
4530
+ }
3101
4531
  async function runSpawnGuardCli(stdin, stdout) {
3102
4532
  try {
3103
4533
  const chunks = [];
3104
4534
  for await (const chunk of stdin)
3105
4535
  chunks.push(Buffer.from(chunk));
3106
4536
  const payload = parsePreToolUsePayload(Buffer.concat(chunks).toString("utf8"));
3107
- if (payload === null)
4537
+ if (payload === null) {
4538
+ stdout.write(deny("LazyCodex spawn guard received an invalid hook payload; role routing was not verified."));
3108
4539
  return;
4540
+ }
3109
4541
  const output = applySpawnGuards(payload);
3110
4542
  if (output.length > 0)
3111
4543
  stdout.write(output);
3112
4544
  } catch (error) {
3113
- if (error instanceof Error)
3114
- return;
4545
+ stdout.write(deny(`LazyCodex spawn guard failed: ${error instanceof Error ? error.message : String(error)}`));
3115
4546
  }
3116
4547
  }
4548
+ function peekFanOutBudget(stateDir) {
4549
+ const counterPath = join7(stateDir, "spawn-count.json");
4550
+ const count = readCount(counterPath) + 1;
4551
+ const limit = fanOutLimit();
4552
+ if (count <= limit)
4553
+ return null;
4554
+ 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.`;
4555
+ }
3117
4556
  function consumeFanOutBudget(stateDir) {
3118
- const counterPath = join2(stateDir, "spawn-count.json");
4557
+ const counterPath = join7(stateDir, "spawn-count.json");
3119
4558
  const count = readCount(counterPath) + 1;
3120
- writeFileSync(counterPath, JSON.stringify({ count }));
4559
+ atomicWriteJson(counterPath, { count });
3121
4560
  const limit = fanOutLimit();
3122
4561
  if (count <= limit)
3123
4562
  return null;
3124
4563
  return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
3125
4564
  }
4565
+ function consumeReviewSpawnBudget(payload, plan, stateDir) {
4566
+ const agentType = reviewAgentType(payload.tool_input);
4567
+ if (agentType === null)
4568
+ return null;
4569
+ const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
4570
+ if (goal === undefined)
4571
+ return null;
4572
+ const counterPath = join7(stateDir, "review-spawn-counts.json");
4573
+ const limit = reviewSpawnLimit();
4574
+ const counts = readCounts(counterPath);
4575
+ const key = `${agentType}:${goal.id}:a${goal.attempt}`;
4576
+ const count = (counts[key] ?? 0) + 1;
4577
+ if (count > limit)
4578
+ return `ulw-loop reviewer no-progress cap reached (${agentType} ${count}/${limit}) for ${goal.id} attempt ${goal.attempt}. Consolidate existing review findings, or checkpoint and start a new attempt after concrete progress.`;
4579
+ counts[key] = count;
4580
+ atomicWriteJson(counterPath, counts);
4581
+ return null;
4582
+ }
3126
4583
  function missingGateArtifact(payload, plan) {
3127
4584
  if (!isGateReviewerSpawn(payload.tool_input))
3128
4585
  return null;
3129
- const goal3 = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
3130
- if (goal3 === undefined || goal3.status === "complete")
4586
+ const goal = plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
4587
+ if (goal === undefined || goal.status === "complete")
3131
4588
  return null;
3132
- if (!goal3.successCriteria.every((criterion) => criterion.status === "pass"))
4589
+ if (!goal.successCriteria.every((criterion) => criterion.status === "pass"))
3133
4590
  return null;
3134
4591
  const scope = { sessionId: payload.session_id };
4592
+ const requiredArtifacts = [`${goal.id}-manual-qa.md`];
3135
4593
  if (plan.evidenceLayoutVersion === 2) {
3136
- const attemptDir = ulwLoopAttemptEvidenceDir(goal3.id, goal3.attempt, scope);
3137
- for (const name of [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`]) {
3138
- const relative2 = `${attemptDir}/${name}`;
3139
- if (!isNonEmptyFile(join2(payload.cwd, relative2)))
3140
- return relative2;
4594
+ const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
4595
+ for (const name of requiredArtifacts) {
4596
+ const relative = `${attemptDir}/${name}`;
4597
+ if (!isNonEmptyFile(join7(payload.cwd, relative)))
4598
+ return relative;
3141
4599
  }
3142
4600
  return null;
3143
4601
  }
3144
- const flatReport = `.omo/evidence/${goal3.id}-code-review.md`;
3145
- if (!isNonEmptyFile(join2(payload.cwd, flatReport)))
3146
- return flatReport;
3147
- if (!hasOtherEvidenceFile(join2(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
3148
- return `.omo/evidence/<any manual-QA artifact besides ${goal3.id}-code-review.md>`;
3149
- return null;
4602
+ const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
4603
+ return isNonEmptyFile(join7(payload.cwd, manualQa)) ? null : manualQa;
3150
4604
  }
3151
4605
  function isGateReviewerSpawn(toolInput) {
4606
+ const agentType = reviewAgentType(toolInput);
4607
+ return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
4608
+ }
4609
+ function reviewAgentType(toolInput) {
3152
4610
  if (typeof toolInput !== "object" || toolInput === null)
3153
- return false;
4611
+ return null;
3154
4612
  const record = toolInput;
3155
4613
  const agentType = record["agent_type"];
3156
- if (typeof agentType === "string")
3157
- return agentType === "lazycodex-gate-reviewer";
4614
+ if (typeof agentType === "string") {
4615
+ if (!REVIEW_AGENT_TYPE_SET.has(agentType))
4616
+ return null;
4617
+ return activeSurfaceReviewerAlias(agentType);
4618
+ }
3158
4619
  const message = record["message"];
3159
- return typeof message === "string" && GATE_MESSAGE_PATTERN.test(message);
4620
+ if (typeof message !== "string")
4621
+ return null;
4622
+ const normalizedMessage = message.toLowerCase();
4623
+ const allRoleNames = [...REVIEW_AGENT_TYPES];
4624
+ const explicitAssignment = allRoleNames.map((name) => ({
4625
+ name,
4626
+ index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
4627
+ })).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
4628
+ if (explicitAssignment !== undefined)
4629
+ return activeSurfaceReviewerAlias(explicitAssignment.name);
4630
+ const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
4631
+ if (nonReviewerActAs)
4632
+ return null;
4633
+ const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
4634
+ if (namedReviewer !== undefined)
4635
+ return activeSurfaceReviewerAlias(namedReviewer);
4636
+ return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
4637
+ }
4638
+ function activeSurfaceReviewerAlias(reviewer) {
4639
+ const canonical = canonicalReviewerAgentName(reviewer);
4640
+ const activeRoles = reviewerRolesFor(resolveToolkitSurface());
4641
+ for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
4642
+ if (canonical === roles.codeReview)
4643
+ return activeRoles.codeReview;
4644
+ if (canonical === roles.manualQa)
4645
+ return activeRoles.manualQa;
4646
+ if (canonical === roles.gateReview)
4647
+ return activeRoles.gateReview;
4648
+ }
4649
+ return canonical;
3160
4650
  }
3161
4651
  function deny(reason) {
3162
4652
  return `${JSON.stringify({
@@ -3176,37 +4666,16 @@ function fanOutLimit() {
3176
4666
  const parsed = Number.parseInt(raw, 10);
3177
4667
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
3178
4668
  }
3179
- function isNonEmptyFile(path) {
3180
- try {
3181
- return existsSync6(path) && statSync2(path).size > 0;
3182
- } catch (error) {
3183
- if (error instanceof Error)
3184
- return false;
3185
- throw error;
3186
- }
3187
- }
3188
- function hasOtherEvidenceFile(evidenceDir, excludedName) {
3189
- try {
3190
- return readdirSync(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join2(evidenceDir, name)));
3191
- } catch (error) {
3192
- if (error instanceof Error)
3193
- return false;
3194
- throw error;
3195
- }
3196
- }
3197
- function readCount(counterPath) {
3198
- try {
3199
- const parsed = JSON.parse(readFileSync3(counterPath, "utf8"));
3200
- return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
3201
- } catch (error) {
3202
- if (error instanceof Error)
3203
- return 0;
3204
- throw error;
3205
- }
4669
+ function reviewSpawnLimit() {
4670
+ const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
4671
+ if (raw === undefined)
4672
+ return DEFAULT_REVIEW_SPAWN_LIMIT;
4673
+ const parsed = Number.parseInt(raw, 10);
4674
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
3206
4675
  }
3207
- function readPlan(goalsPath) {
4676
+ function readPlan(repoRoot, sessionId) {
3208
4677
  try {
3209
- return JSON.parse(readFileSync3(goalsPath, "utf8"));
4678
+ return readUlwLoopPlanSync(repoRoot, { sessionId });
3210
4679
  } catch (error) {
3211
4680
  if (error instanceof Error)
3212
4681
  return null;
@@ -3214,9 +4683,9 @@ function readPlan(goalsPath) {
3214
4683
  }
3215
4684
  }
3216
4685
 
3217
- // packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts
3218
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
3219
- import { isAbsolute as isAbsolute2, join as join3, resolve as resolve4, sep as sep2 } from "node:path";
4686
+ // components/ulw-loop/src/stop-resume-hook.ts
4687
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
4688
+ import { isAbsolute as isAbsolute3, join as join8, resolve as resolve5, sep as sep3 } from "node:path";
3220
4689
  var RESUME_CAP = 2;
3221
4690
  var CONTEXT_PRESSURE_MARKERS2 = [
3222
4691
  "context compacted",
@@ -3235,18 +4704,19 @@ function runStopResumeHook(input) {
3235
4704
  return "";
3236
4705
  if (boulderContinuationWillFire(payload.cwd, payload.session_id))
3237
4706
  return "";
3238
- const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
3239
- const plan = readPlan2(join3(stateDir, "goals.json"));
4707
+ const scope = { sessionId: payload.session_id };
4708
+ const stateDir = ulwLoopDir(payload.cwd, scope);
4709
+ const plan = readPlan2(payload.cwd, payload.session_id);
3240
4710
  if (plan === null || plan.aggregateCompletion?.status === "complete")
3241
4711
  return "";
3242
- const goal3 = resumableGoal(plan);
3243
- if (goal3 === undefined)
4712
+ const goal = resumableGoal(plan);
4713
+ if (goal === undefined)
3244
4714
  return "";
3245
- if (!consumeResumeBudget(stateDir, goal3.id))
4715
+ if (!consumeResumeBudgetLocked(ulwLoopStateLockPath(payload.cwd, scope), stateDir, goal.id))
3246
4716
  return "";
3247
4717
  const output = {
3248
4718
  decision: "block",
3249
- reason: renderResumeDirective(plan, goal3, payload.session_id)
4719
+ reason: renderResumeDirective(plan, goal, payload.session_id)
3250
4720
  };
3251
4721
  return JSON.stringify(output);
3252
4722
  }
@@ -3264,38 +4734,47 @@ async function runStopResumeHookCli(stdin, stdout) {
3264
4734
  }
3265
4735
  }
3266
4736
  function resumableGoal(plan) {
3267
- const active = plan.goals.find((goal3) => goal3.id === plan.activeGoalId);
4737
+ const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
3268
4738
  if (active !== undefined && isResumableStatus(active.status))
3269
4739
  return active;
3270
- return plan.goals.find((goal3) => isResumableStatus(goal3.status));
4740
+ return plan.goals.find((goal) => isResumableStatus(goal.status));
4741
+ }
4742
+ function isResumableStatus(status) {
4743
+ return status === "pending" || status === "in_progress";
3271
4744
  }
3272
- function isResumableStatus(status2) {
3273
- return status2 === "pending" || status2 === "in_progress";
4745
+ function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
4746
+ try {
4747
+ return withStateLockSync(lockPath, () => consumeResumeBudget(stateDir, goalId));
4748
+ } catch (error) {
4749
+ if (isStateLockTimeout(error))
4750
+ return false;
4751
+ throw error;
4752
+ }
3274
4753
  }
3275
4754
  function consumeResumeBudget(stateDir, goalId) {
3276
- const ledgerLineCount = countLedgerLines(join3(stateDir, "ledger.jsonl"));
3277
- const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
3278
- const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
4755
+ const ledgerLineCount = readLedgerAt(stateDir).length;
4756
+ const counterPath = resolve5(stateDir, `auto-resume-${goalId}.json`);
4757
+ const stuckPath = resolve5(stateDir, `auto-resume-${goalId}.stuck`);
3279
4758
  if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
3280
4759
  return false;
3281
4760
  const previous = readCounter(counterPath);
3282
4761
  const count = previous !== null && previous.ledgerLineCount === ledgerLineCount ? previous.count : 0;
3283
4762
  if (count >= RESUME_CAP) {
3284
- writeFileSync2(stuckPath, `no ledger progress after ${count} resumes
4763
+ writeFileSync3(stuckPath, `no ledger progress after ${count} resumes
3285
4764
  `);
3286
4765
  return false;
3287
4766
  }
3288
- writeFileSync2(counterPath, JSON.stringify({ count: count + 1, ledgerLineCount }));
4767
+ writeFileSync3(counterPath, JSON.stringify({ count: count + 1, ledgerLineCount }));
3289
4768
  return true;
3290
4769
  }
3291
4770
  function isInsideDir(dir, candidate) {
3292
- return candidate.startsWith(resolve4(dir) + sep2);
4771
+ return candidate.startsWith(resolve5(dir) + sep3);
3293
4772
  }
3294
- function renderResumeDirective(plan, goal3, sessionId) {
4773
+ function renderResumeDirective(plan, goal, sessionId) {
3295
4774
  const normalized = normalizeUlwLoopSessionId(sessionId);
3296
4775
  const option = normalized !== null && plan.goalsPath.includes(`/${normalized}/`) ? ` --session-id ${normalized}` : "";
3297
4776
  return [
3298
- `The ulw-loop run in this session still has unfinished goals (next: ${goal3.id} — ${goal3.title}).`,
4777
+ `The ulw-loop run in this session still has unfinished goals (next: ${goal.id} — ${goal.title}).`,
3299
4778
  "The turn ended before the loop completed. Resume it now:",
3300
4779
  `1. Run \`omo-agent-toolkit ulw-loop status${option} --json\` to reload the plan, the active goal, and currentAttemptDir.`,
3301
4780
  "2. Continue the active goal's remaining success criteria, recording evidence with record-evidence.",
@@ -3304,30 +4783,20 @@ function renderResumeDirective(plan, goal3, sessionId) {
3304
4783
  ].join(`
3305
4784
  `);
3306
4785
  }
3307
- function readPlan2(goalsPath) {
4786
+ function readPlan2(repoRoot, sessionId) {
3308
4787
  try {
3309
- return JSON.parse(readFileSync4(goalsPath, "utf8"));
4788
+ return readUlwLoopPlanSync(repoRoot, { sessionId });
3310
4789
  } catch (error) {
3311
4790
  if (error instanceof Error)
3312
4791
  return null;
3313
4792
  throw error;
3314
4793
  }
3315
4794
  }
3316
- function countLedgerLines(ledgerPath) {
3317
- try {
3318
- return readFileSync4(ledgerPath, "utf8").split(`
3319
- `).filter(Boolean).length;
3320
- } catch (error) {
3321
- if (error instanceof Error)
3322
- return 0;
3323
- throw error;
3324
- }
3325
- }
3326
4795
  function readCounter(counterPath) {
3327
4796
  try {
3328
- if (!existsSync7(counterPath))
4797
+ if (!existsSync8(counterPath))
3329
4798
  return null;
3330
- const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
4799
+ const parsed = JSON.parse(readFileSync7(counterPath, "utf8"));
3331
4800
  if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
3332
4801
  return null;
3333
4802
  return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
@@ -3339,7 +4808,7 @@ function readCounter(counterPath) {
3339
4808
  }
3340
4809
  function boulderContinuationWillFire(cwd, sessionId) {
3341
4810
  try {
3342
- const raw = JSON.parse(readFileSync4(join3(cwd, ".omo", "boulder.json"), "utf8"));
4811
+ const raw = JSON.parse(readFileSync7(join8(cwd, ".omo", "boulder.json"), "utf8"));
3343
4812
  const works = raw["works"];
3344
4813
  const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
3345
4814
  return entries.some((work) => {
@@ -3358,7 +4827,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
3358
4827
  }
3359
4828
  function transcriptShowsContextPressure(transcriptPath) {
3360
4829
  try {
3361
- const transcript = readFileSync4(transcriptPath, "utf8").toLowerCase();
4830
+ const transcript = readFileSync7(transcriptPath, "utf8").toLowerCase();
3362
4831
  return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
3363
4832
  } catch (error) {
3364
4833
  if (error instanceof Error)
@@ -3370,12 +4839,12 @@ function boulderPlanHasChecklist(cwd, entry) {
3370
4839
  const activePlan = entry["active_plan"];
3371
4840
  if (typeof activePlan !== "string" || activePlan.trim().length === 0)
3372
4841
  return false;
3373
- const planPath = isAbsolute2(activePlan) ? activePlan : join3(cwd, activePlan);
4842
+ const planPath = isAbsolute3(activePlan) ? activePlan : join8(cwd, activePlan);
3374
4843
  const worktree = entry["worktree_path"];
3375
- const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join3(isAbsolute2(worktree) ? worktree : join3(cwd, worktree), activePlan), planPath] : [planPath];
4844
+ const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute3(activePlan) ? [join8(isAbsolute3(worktree) ? worktree : join8(cwd, worktree), activePlan), planPath] : [planPath];
3376
4845
  for (const candidate of candidates) {
3377
4846
  try {
3378
- return readFileSync4(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
4847
+ return readFileSync7(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
3379
4848
  } catch (error) {
3380
4849
  if (!(error instanceof Error))
3381
4850
  throw error;
@@ -3399,19 +4868,13 @@ function parseStopPayload(value) {
3399
4868
  };
3400
4869
  }
3401
4870
 
3402
- // packages/omo-codex/plugin/components/ulw-loop/src/cli.ts
3403
- var TOP_LEVEL_HELP = `Usage:
3404
- omo-agent-toolkit ulw-loop <subcommand> [args]
3405
- omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
3406
- omo-agent-toolkit help | --help | -h (this message)
3407
-
3408
- Run \`omo-agent-toolkit ulw-loop help\` for ulw-loop subcommands.
3409
- `;
4871
+ // components/ulw-loop/src/cli.ts
3410
4872
  async function main() {
3411
4873
  const argv = process.argv.slice(2);
3412
4874
  const command = argv[0];
3413
4875
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
3414
- process.stdout.write(TOP_LEVEL_HELP);
4876
+ process.stdout.write(`${ULW_LOOP_HELP}
4877
+ `);
3415
4878
  return 0;
3416
4879
  }
3417
4880
  if (command === "ulw-loop")
@@ -3432,6 +4895,10 @@ async function main() {
3432
4895
  await runStopResumeHookCli(process.stdin, process.stdout);
3433
4896
  return 0;
3434
4897
  }
4898
+ if (sub === "post-tool-use-spawn") {
4899
+ await runSpawnAdmissionRecorderCli(process.stdin, process.stdout);
4900
+ return 0;
4901
+ }
3435
4902
  if (sub === "pre-tool-use-spawn") {
3436
4903
  await runSpawnGuardCli(process.stdin, process.stdout);
3437
4904
  return 0;
@@ -3443,7 +4910,8 @@ async function main() {
3443
4910
  if (isUlwLoopSubcommand(command))
3444
4911
  return ulwLoopCommand(argv);
3445
4912
  process.stderr.write(`[omo] unknown command: ${command}
3446
- ${TOP_LEVEL_HELP}`);
4913
+ ${ULW_LOOP_HELP}
4914
+ `);
3447
4915
  return 1;
3448
4916
  }
3449
4917
  main().then((code) => {