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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/cli/index.js +136 -44
  2. package/dist/cli-node/index.js +136 -44
  3. package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
  4. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
  5. package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
  6. package/dist/features/monitor/manager-internals.d.ts +1 -1
  7. package/dist/features/monitor/output-injector-types.d.ts +1 -1
  8. package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +1 -0
  9. package/dist/features/team-mode/tools/messaging-runtime.d.ts +1 -0
  10. package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
  11. package/dist/index.js +286 -99
  12. package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  13. package/dist/tools/session-manager/directory-filter.d.ts +2 -0
  14. package/dist/tui.js +109 -23
  15. package/package.json +16 -15
  16. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +47 -19
  17. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  18. package/packages/lsp-core/src/lsp/client.ts +6 -3
  19. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
  20. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
  21. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  22. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  23. package/packages/lsp-core/src/lsp/transport.ts +7 -2
  24. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
  25. package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
  26. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  27. package/packages/lsp-daemon/dist/cli.js +41 -16
  28. package/packages/lsp-daemon/dist/client.js +41 -16
  29. package/packages/lsp-daemon/dist/index.js +41 -16
  30. package/packages/lsp-tools-mcp/dist/cli.js +41 -16
  31. package/packages/lsp-tools-mcp/dist/lsp/manager.js +41 -16
  32. package/packages/lsp-tools-mcp/dist/mcp.js +41 -16
  33. package/packages/lsp-tools-mcp/dist/tools.js +41 -16
  34. package/packages/lsp-tools-mcp/package.json +1 -1
  35. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  36. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  37. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +3 -17
  39. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +3 -17
  40. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  42. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  44. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  46. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  48. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +39 -16
  49. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  50. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  52. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  54. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  56. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +5 -4
  58. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +489 -4
  59. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  60. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -2
  61. package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
  62. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
  63. package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
  64. package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
  65. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
  66. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  67. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  68. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +4 -2
  69. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -0
  70. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
  71. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +14 -0
  72. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +10 -1
  73. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
  74. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
  75. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +7 -5
  76. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
  77. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
  78. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -0
  79. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
  80. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +775 -312
  81. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +18 -0
  82. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
  83. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +14 -0
  84. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
  85. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +17 -2
  86. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +13 -0
  87. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +19 -0
  88. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +2 -0
  89. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +53 -0
  90. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +9 -0
  91. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +85 -0
  92. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +1 -1
  93. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +41 -99
  94. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
  95. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +184 -14
  96. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
  97. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
  98. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +3 -0
  99. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +15 -0
  100. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  101. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  102. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +17 -0
  103. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
  104. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +25 -1
  105. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +127 -0
  106. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +8 -12
  107. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +2 -1
  108. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -0
  109. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
  110. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +18 -0
  111. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +23 -0
  112. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
  113. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +18 -3
  114. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +27 -0
  115. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +57 -0
  116. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +103 -0
  117. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +1 -1
  118. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +50 -116
  119. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
  120. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +186 -12
  121. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
  122. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +22 -0
  123. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +66 -3
  124. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +92 -0
  125. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +183 -0
  126. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +13 -3
  127. package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
  128. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +101 -0
  129. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +16 -15
  130. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
  131. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +5 -0
  132. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +300 -3
  133. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +9 -3
  134. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  135. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  136. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  137. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  138. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  139. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  140. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  141. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  142. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  143. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  144. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  145. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  146. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  147. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  148. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  149. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  150. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  151. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  152. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  153. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  154. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  155. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  156. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  157. package/packages/omo-codex/plugin/package-lock.json +13 -13
  158. package/packages/omo-codex/plugin/package.json +1 -1
  159. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +31 -2
  160. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  161. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +17 -0
  162. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +11 -1
  163. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +13 -0
  164. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -2
  165. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  166. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
  167. package/dist/features/builtin-skills/index.d.ts +0 -1
  168. package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
  169. package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
  170. package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
  171. package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
  172. package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
  173. package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
  174. package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
  175. package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
  176. package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
  177. package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
  178. package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
  179. package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
  180. package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
  181. package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
  182. package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
  183. package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
  184. package/dist/features/builtin-skills/skills/index.d.ts +0 -1
  185. package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
  186. package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
  187. package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
  188. package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
  189. package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
  190. package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
  191. package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
  192. package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
  193. package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
  194. package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
  195. package/dist/features/builtin-skills/skills.d.ts +0 -1
  196. package/dist/features/builtin-skills/types.d.ts +0 -1
  197. package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
  198. package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
  199. package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
  200. package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
  201. package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
  202. package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
  203. package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
  204. package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
  205. package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
  206. package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
  207. package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
  208. package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
  209. package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
  210. package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
  211. package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
  212. package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
  213. package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
  214. package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
  215. package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
  216. package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
  217. package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
  218. package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
  219. package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
  220. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
  221. package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
  222. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
  223. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
  224. package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
  225. package/dist/shared/prompt-async-gate/types.d.ts +0 -1
  226. package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -483
  227. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +0 -490
@@ -87,6 +87,8 @@ function finalSection(plan, goal, isFinal, aggregate, surface) {
87
87
  if (!isFinal)
88
88
  return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
89
89
  const option = sessionOption(plan);
90
+ if (surface === "omo-senpi")
91
+ return senpiFinalSection(plan, goal, aggregate);
90
92
  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>"`;
91
93
  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>"`;
92
94
  return joinLines([
@@ -106,6 +108,22 @@ function finalSection(plan, goal, isFinal, aggregate, surface) {
106
108
  ` ${checkpointCommand}`,
107
109
  ]);
108
110
  }
111
+ function senpiFinalSection(plan, goal, aggregate) {
112
+ const option = sessionOption(plan);
113
+ 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)"`;
114
+ return joinLines([
115
+ "Final story — run the single-reviewer quality gate before update_goal:",
116
+ "- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to \"main-session\".",
117
+ '- Spawn exactly one gate reviewer with task(category: "deep").',
118
+ "- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
119
+ '- Set gateReview.by to the exact category:<name> literal used for the successful task.',
120
+ '- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.',
121
+ '- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
122
+ aggregate
123
+ ? `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint the aggregate story: ${checkpointCommand}`
124
+ : `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint: ${checkpointCommand}`,
125
+ ]);
126
+ }
109
127
  function sessionOption(plan) {
110
128
  const prefix = ".omo/ulw-loop/";
111
129
  const suffix = "/goals.json";
@@ -24,3 +24,16 @@ export declare function parseCodexGoalSnapshot(value: unknown): CodexGoalSnapsho
24
24
  export declare function readCodexGoalSnapshotInput(raw: string | undefined, cwd?: string): Promise<CodexGoalSnapshot | null>;
25
25
  export declare function reconcileCodexGoalSnapshot(snapshot: CodexGoalSnapshot | null | undefined, options: ReconcileCodexGoalOptions): CodexGoalReconciliation;
26
26
  export declare function formatCodexGoalReconciliation(reconciliation: CodexGoalReconciliation): string;
27
+ export interface CodexGoalMismatchRecovery {
28
+ readonly message: string;
29
+ readonly details: {
30
+ readonly expectedObjective: string;
31
+ readonly receivedObjective: string;
32
+ };
33
+ }
34
+ /**
35
+ * The reconciliation errors normalize whitespace for comparison, which makes the
36
+ * quoted objective unusable as a copy source. Recovery therefore carries the
37
+ * plan's `codexObjective` verbatim so the agent can paste it into `update_goal`.
38
+ */
39
+ export declare function codexGoalMismatchRecovery(expectedObjective: string, snapshot: CodexGoalSnapshot | null | undefined): CodexGoalMismatchRecovery;
@@ -95,3 +95,17 @@ export function formatCodexGoalReconciliation(reconciliation) {
95
95
  const parts = [...reconciliation.errors, ...reconciliation.warnings];
96
96
  return parts.join(" ");
97
97
  }
98
+ /**
99
+ * The reconciliation errors normalize whitespace for comparison, which makes the
100
+ * quoted objective unusable as a copy source. Recovery therefore carries the
101
+ * plan's `codexObjective` verbatim so the agent can paste it into `update_goal`.
102
+ */
103
+ export function codexGoalMismatchRecovery(expectedObjective, snapshot) {
104
+ const receivedObjective = snapshot?.objective ?? "";
105
+ const message = [
106
+ "Recovery: the Codex goal objective must equal the plan's codexObjective exactly — copy the expected value below into update_goal and re-run get_goal.",
107
+ `expected codexObjective: ${expectedObjective}`,
108
+ `received objective: ${receivedObjective || "(none)"}`,
109
+ ].join("\n");
110
+ return { message, details: { expectedObjective, receivedObjective } };
111
+ }
@@ -89,15 +89,7 @@ export interface UlwLoopManualQaAdversarialCase {
89
89
  readonly reason?: string;
90
90
  readonly artifactRefs: readonly string[];
91
91
  }
92
- export interface UlwLoopQualityGate {
93
- readonly codeReview: {
94
- readonly by: string;
95
- readonly recommendation: "APPROVE";
96
- readonly codeQualityStatus: "CLEAR" | "WATCH";
97
- readonly reportPath: string;
98
- readonly evidence: string;
99
- readonly blockers: readonly [];
100
- };
92
+ interface UlwLoopQualityGateCommon {
101
93
  readonly manualQa: {
102
94
  readonly by: string;
103
95
  readonly status: "passed";
@@ -128,6 +120,21 @@ export interface UlwLoopQualityGate {
128
120
  readonly adversarialClassesCovered: readonly string[];
129
121
  };
130
122
  }
123
+ export interface UlwLoopQualityGateLazycodex extends UlwLoopQualityGateCommon {
124
+ readonly surface: "lazycodex";
125
+ readonly codeReview: {
126
+ readonly by: string;
127
+ readonly recommendation: "APPROVE";
128
+ readonly codeQualityStatus: "CLEAR" | "WATCH";
129
+ readonly reportPath: string;
130
+ readonly evidence: string;
131
+ readonly blockers: readonly [];
132
+ };
133
+ }
134
+ export interface UlwLoopQualityGateSenpi extends UlwLoopQualityGateCommon {
135
+ readonly surface: "omo-senpi";
136
+ }
137
+ export type UlwLoopQualityGate = UlwLoopQualityGateLazycodex | UlwLoopQualityGateSenpi;
131
138
  export interface UlwLoopLedgerEntry {
132
139
  at: string;
133
140
  kind: UlwLoopLedgerEventKind;
@@ -149,3 +156,4 @@ export interface UlwLoopLedgerEntry {
149
156
  blockerOccurrenceCount?: number;
150
157
  requiredExternalDecision?: string;
151
158
  }
159
+ export {};
@@ -1,8 +1,9 @@
1
- import { createReadStream } from "node:fs";
1
+ import { createReadStream, readdirSync } from "node:fs";
2
2
  import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { createInterface } from "node:readline";
4
4
  import { aggregateCodexObjectiveForScope } from "./goal-status.js";
5
5
  import { repoRelative, ulwLoopDir, ulwLoopGoalsPath, ulwLoopLedgerPath, ulwLoopRelativeDir, } from "./paths.js";
6
+ import { planMissingRecovery } from "./plan-missing-recovery.js";
6
7
  import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
7
8
  const LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
8
9
  const 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.`;
@@ -47,7 +48,8 @@ export async function readUlwLoopPlan(repoRoot, scope) {
47
48
  catch (error) {
48
49
  if (!hasCode(error, "ENOENT"))
49
50
  throw error;
50
- 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 });
51
+ const recovery = planMissingRecovery(readSessionDirs(repoRoot));
52
+ throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.\n${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...(recovery.details === undefined ? {} : { details: recovery.details }) });
51
53
  }
52
54
  const parsed = JSON.parse(raw);
53
55
  if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
@@ -71,6 +73,19 @@ export async function readUlwLoopPlan(repoRoot, scope) {
71
73
  }
72
74
  return parsed;
73
75
  }
76
+ // Session dirs are the only recovery hint that matters when a plan is missing: the
77
+ // caller is almost always scoped to a session whose sibling actually holds the plan.
78
+ function readSessionDirs(repoRoot) {
79
+ try {
80
+ return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true })
81
+ .filter((entry) => entry.isDirectory())
82
+ .map((entry) => entry.name)
83
+ .sort();
84
+ }
85
+ catch {
86
+ return [];
87
+ }
88
+ }
74
89
  export async function writePlan(repoRoot, plan, scope) {
75
90
  await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
76
91
  const path = ulwLoopGoalsPath(repoRoot, scope);
@@ -0,0 +1,13 @@
1
+ export declare const ULW_LOOP_CREATE_GOALS_COMMAND = "omo-agent-toolkit ulw-loop create-goals --brief \"<brief>\" --json";
2
+ export interface PlanMissingRecovery {
3
+ readonly message: string;
4
+ readonly details?: {
5
+ readonly existingSessionIds: readonly string[];
6
+ };
7
+ }
8
+ /**
9
+ * A missing plan is either "never bootstrapped" or "bootstrapped under a different
10
+ * session id"; the recovery text has to answer both without the caller guessing.
11
+ */
12
+ export declare function planMissingRecovery(existingSessionIds: readonly string[]): PlanMissingRecovery;
13
+ export declare function sessionIdRequiredMessage(flag: string): string;
@@ -0,0 +1,19 @@
1
+ export const ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
2
+ /**
3
+ * A missing plan is either "never bootstrapped" or "bootstrapped under a different
4
+ * session id"; the recovery text has to answer both without the caller guessing.
5
+ */
6
+ export function planMissingRecovery(existingSessionIds) {
7
+ const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
8
+ if (existingSessionIds.length === 0)
9
+ return { message: lines.join("\n") };
10
+ lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
11
+ return { message: lines.join("\n"), details: { existingSessionIds } };
12
+ }
13
+ export function sessionIdRequiredMessage(flag) {
14
+ return [
15
+ `${flag} requires a non-empty value.`,
16
+ "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),",
17
+ `so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`,
18
+ ].join("\n");
19
+ }
@@ -0,0 +1,2 @@
1
+ import type { ValidateQualityGateOptions } from "./quality-gate.js";
2
+ export declare function aggregateQualityGateDefects(input: unknown, opts: ValidateQualityGateOptions | undefined): void;
@@ -0,0 +1,53 @@
1
+ import { resolve } from "node:path";
2
+ import { isRecord } from "./quality-gate-fields.js";
3
+ import { UlwLoopError } from "./types.js";
4
+ const PLACEHOLDER = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
5
+ function add(defects, field, message) {
6
+ defects.push({ field, message });
7
+ }
8
+ function text(value, field, defects) {
9
+ if (typeof value !== "string" || value.trim() === "")
10
+ add(defects, field, `Final quality gate requires non-empty ${field}.`);
11
+ else if (PLACEHOLDER.test(value.trim()))
12
+ add(defects, field, `Final quality gate rejects placeholder ${field}.`);
13
+ }
14
+ function section(value, field, defects) {
15
+ if (!isRecord(value)) {
16
+ add(defects, field, `Final quality gate is missing ${field} evidence.`);
17
+ return {};
18
+ }
19
+ return value;
20
+ }
21
+ export function aggregateQualityGateDefects(input, opts) {
22
+ if (!isRecord(input))
23
+ return;
24
+ const defects = [];
25
+ const gate = input;
26
+ const manual = section(gate["manualQa"], "manualQa", defects);
27
+ const review = section(gate["gateReview"], "gateReview", defects);
28
+ text(manual["evidence"], "manualQa.evidence", defects);
29
+ text(review["evidence"], "gateReview.evidence", defects);
30
+ if (review["recommendation"] !== "APPROVE")
31
+ add(defects, "gateReview.recommendation", "gateReview.recommendation must be APPROVE.");
32
+ const artifacts = Array.isArray(manual["artifactRefs"]) ? manual["artifactRefs"] : [];
33
+ for (const [index, item] of artifacts.entries()) {
34
+ if (!isRecord(item))
35
+ continue;
36
+ const path = item["path"];
37
+ if (typeof path !== "string" || path.trim() === "")
38
+ continue;
39
+ if (opts?.repoRoot !== undefined && opts.fs !== undefined && !opts.fs.existsSync(resolve(opts.repoRoot, path)))
40
+ add(defects, `manualQa.artifactRefs[${index}].path`, `manualQa.artifactRefs[${index}].path must point to an existing artifact.`);
41
+ }
42
+ if (defects.length === 0)
43
+ return;
44
+ const truncated = defects.length > 25;
45
+ const fields = defects.slice(0, 25).map(({ field, message }) => ({ field, message }));
46
+ const message = [
47
+ `Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
48
+ ...fields.map((item) => `- ${item.field}: ${item.message}`),
49
+ ].join("\n");
50
+ throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
51
+ details: { field: fields[0]?.field, fields, ...(truncated ? { truncated: true } : {}) },
52
+ });
53
+ }
@@ -0,0 +1,9 @@
1
+ import type { ValidateQualityGateOptions } from "./quality-gate.js";
2
+ import type { UlwLoopManualQaArtifactKind, UlwLoopManualQaArtifactRef, UlwLoopManualQaSurface } from "./types.js";
3
+ export declare function surfaceField(value: unknown, field: string): UlwLoopManualQaSurface;
4
+ export declare function kindField(value: unknown, field: string): UlwLoopManualQaArtifactKind;
5
+ export declare function artifactCompatible(surface: UlwLoopManualQaSurface, kind: UlwLoopManualQaArtifactKind): boolean;
6
+ export declare function checkFile(path: string, field: string, opts?: ValidateQualityGateOptions): void;
7
+ export declare function artifactMap(refs: readonly UlwLoopManualQaArtifactRef[]): Map<string, UlwLoopManualQaArtifactRef>;
8
+ export declare function parseArtifactRefs(value: unknown, opts?: ValidateQualityGateOptions): readonly UlwLoopManualQaArtifactRef[];
9
+ export declare function referencedArtifacts(value: unknown, field: string, byId: ReadonlyMap<string, UlwLoopManualQaArtifactRef>): readonly UlwLoopManualQaArtifactRef[];
@@ -0,0 +1,85 @@
1
+ import { resolve } from "node:path";
2
+ import { isWithinAttemptDir } from "./paths.js";
3
+ import { invalid, section, stringArray, textField } from "./quality-gate-fields.js";
4
+ export function surfaceField(value, field) {
5
+ if (value === "cli" ||
6
+ value === "http" ||
7
+ value === "tmux" ||
8
+ value === "browser" ||
9
+ value === "gui" ||
10
+ value === "data")
11
+ return value;
12
+ invalid(`${field} must be a supported manual QA surface.`, field);
13
+ }
14
+ export function kindField(value, field) {
15
+ if (value === "cli-transcript" ||
16
+ value === "log" ||
17
+ value === "screenshot" ||
18
+ value === "image" ||
19
+ value === "http-dump" ||
20
+ value === "data-diff")
21
+ return value;
22
+ invalid(`${field} must be a supported artifact kind.`, field);
23
+ }
24
+ export function artifactCompatible(surface, kind) {
25
+ switch (surface) {
26
+ case "cli":
27
+ case "tmux":
28
+ return kind === "cli-transcript" || kind === "log";
29
+ case "http":
30
+ return kind === "http-dump";
31
+ case "browser":
32
+ case "gui":
33
+ return kind === "screenshot" || kind === "image";
34
+ case "data":
35
+ return kind === "data-diff";
36
+ default:
37
+ invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
38
+ }
39
+ }
40
+ export function checkFile(path, field, opts) {
41
+ if (opts?.repoRoot === undefined || opts.fs === undefined)
42
+ return;
43
+ const absolute = resolve(opts.repoRoot, path);
44
+ if (!opts.fs.existsSync(absolute))
45
+ invalid(`${field} must point to an existing artifact.`, field);
46
+ if (opts.fs.statSync(absolute).size <= 0)
47
+ invalid(`${field} must point to a non-empty artifact.`, field);
48
+ if (opts.currentAttemptDir !== undefined && opts.repoRoot !== undefined) {
49
+ const attemptRoot = resolve(opts.repoRoot, opts.currentAttemptDir);
50
+ if (!isWithinAttemptDir(absolute, attemptRoot))
51
+ invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
52
+ }
53
+ }
54
+ export function artifactMap(refs) {
55
+ const byId = new Map();
56
+ for (const ref of refs) {
57
+ if (byId.has(ref.id))
58
+ invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
59
+ byId.set(ref.id, ref);
60
+ }
61
+ return byId;
62
+ }
63
+ export function parseArtifactRefs(value, opts) {
64
+ if (!Array.isArray(value) || value.length === 0)
65
+ invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
66
+ return value.map((item, index) => {
67
+ const ref = section(item, `manualQa.artifactRefs[${index}]`);
68
+ const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
69
+ checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
70
+ return {
71
+ id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
72
+ kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
73
+ description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
74
+ path,
75
+ };
76
+ });
77
+ }
78
+ export function referencedArtifacts(value, field, byId) {
79
+ return stringArray(value, field).map((id) => {
80
+ const artifact = byId.get(id);
81
+ if (artifact === undefined)
82
+ invalid(`${field} references unknown artifact ${id}.`, field);
83
+ return artifact;
84
+ });
85
+ }
@@ -1,5 +1,5 @@
1
1
  import { UlwLoopError } from "./types.js";
2
- const PLACEHOLDER_PATTERN = /^(?:placeholder|todo|tbd|n\/a|stub)$/i;
2
+ const PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
3
3
  export function invalid(message, field) {
4
4
  throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
5
5
  }
@@ -1,8 +1,8 @@
1
- import { resolve } from "node:path";
2
- import { isWithinAttemptDir } from "./paths.js";
1
+ import { aggregateQualityGateDefects } from "./quality-gate-aggregate.js";
2
+ import { artifactCompatible, artifactMap, checkFile, parseArtifactRefs, referencedArtifacts, surfaceField, } from "./quality-gate-artifacts.js";
3
3
  import { emptyBlockers, invalid, literal, numberField, section, stringArray, textField, } from "./quality-gate-fields.js";
4
4
  import { adversarialVerdict, codeQualityStatusField, passedVerdict } from "./quality-gate-verdicts.js";
5
- import { reviewerRolesFor } from "./surface.js";
5
+ import { GATE_SECTION_BY_ACCEPTOR, REQUIRED_GATE_SECTIONS_BY_SURFACE } from "./surface.js";
6
6
  export { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, normalizeBlockerEvidence, sameBlockerOccurrences, } from "./quality-gate-blockers.js";
7
7
  function reviewerRoleField(value, expected, field) {
8
8
  const actual = textField(value, field);
@@ -10,96 +10,31 @@ function reviewerRoleField(value, expected, field) {
10
10
  invalid(`${field} must be ${expected}.`, field);
11
11
  return expected;
12
12
  }
13
- function surfaceField(value, field) {
14
- if (value === "cli" ||
15
- value === "http" ||
16
- value === "tmux" ||
17
- value === "browser" ||
18
- value === "gui" ||
19
- value === "data")
20
- return value;
21
- invalid(`${field} must be a supported manual QA surface.`, field);
22
- }
23
- function kindField(value, field) {
24
- if (value === "cli-transcript" ||
25
- value === "log" ||
26
- value === "screenshot" ||
27
- value === "image" ||
28
- value === "http-dump" ||
29
- value === "data-diff")
30
- return value;
31
- invalid(`${field} must be a supported artifact kind.`, field);
32
- }
33
- function artifactCompatible(surface, kind) {
34
- switch (surface) {
35
- case "cli":
36
- case "tmux":
37
- return kind === "cli-transcript" || kind === "log";
38
- case "http":
39
- return kind === "http-dump";
40
- case "browser":
41
- case "gui":
42
- return kind === "screenshot" || kind === "image";
43
- case "data":
44
- return kind === "data-diff";
45
- default:
46
- invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
47
- }
48
- }
49
- function checkFile(path, field, opts) {
50
- if (opts?.repoRoot === undefined || opts.fs === undefined)
51
- return;
52
- const absolute = resolve(opts.repoRoot, path);
53
- if (!opts.fs.existsSync(absolute))
54
- invalid(`${field} must point to an existing artifact.`, field);
55
- if (opts.fs.statSync(absolute).size <= 0)
56
- invalid(`${field} must point to a non-empty artifact.`, field);
57
- if (opts.currentAttemptDir !== undefined && opts.repoRoot !== undefined) {
58
- const attemptRoot = resolve(opts.repoRoot, opts.currentAttemptDir);
59
- if (!isWithinAttemptDir(absolute, attemptRoot))
60
- invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
61
- }
62
- }
63
- function artifactMap(refs) {
64
- const byId = new Map();
65
- for (const ref of refs) {
66
- if (byId.has(ref.id))
67
- invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
68
- byId.set(ref.id, ref);
69
- }
70
- return byId;
71
- }
72
- function parseArtifactRefs(value, opts) {
73
- if (!Array.isArray(value) || value.length === 0)
74
- invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
75
- return value.map((item, index) => {
76
- const ref = section(item, `manualQa.artifactRefs[${index}]`);
77
- const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
78
- checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
79
- return {
80
- id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
81
- kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
82
- description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
83
- path,
84
- };
85
- });
86
- }
87
- function referencedArtifacts(value, field, byId) {
88
- return stringArray(value, field).map((id) => {
89
- const artifact = byId.get(id);
90
- if (artifact === undefined)
91
- invalid(`${field} references unknown artifact ${id}.`, field);
92
- return artifact;
93
- });
13
+ function reviewerAcceptorField(value, surface, sectionName) {
14
+ const field = `${sectionName}.by`;
15
+ const actual = textField(value, field);
16
+ const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
17
+ if (accepted === undefined || !accepted.includes(actual))
18
+ invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
19
+ return actual;
94
20
  }
95
21
  export function validateQualityGate(input, opts) {
96
- const reviewerRoles = reviewerRolesFor(opts?.reviewerSurface ?? "lazycodex");
22
+ const surface = opts?.reviewerSurface ?? "lazycodex";
23
+ aggregateQualityGateDefects(input, opts);
97
24
  const gate = section(input, "qualityGate");
98
- const codeReview = section(gate["codeReview"], "codeReview");
25
+ const requiredSections = REQUIRED_GATE_SECTIONS_BY_SURFACE[surface];
26
+ for (const sectionName of requiredSections)
27
+ section(gate[sectionName], sectionName);
28
+ if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
29
+ invalid("omo-senpi gate has no codeReview lane.", "codeReview");
99
30
  const manualQa = section(gate["manualQa"], "manualQa");
100
31
  const gateReview = section(gate["gateReview"], "gateReview");
101
32
  const iteration = section(gate["iteration"], "iteration");
102
33
  const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
34
+ const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
35
+ const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
36
+ if (surface === "lazycodex")
37
+ reviewerRoleField(section(gate["codeReview"], "codeReview")["by"], "lazycodex-code-reviewer", "codeReview.by");
103
38
  const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
104
39
  const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
105
40
  if (passCount < totalCriteria)
@@ -108,21 +43,11 @@ export function validateQualityGate(input, opts) {
108
43
  const byId = artifactMap(artifactRefs);
109
44
  const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
110
45
  const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
111
- const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
112
46
  const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
113
- checkFile(codeReportPath, "codeReview.reportPath", opts);
114
47
  checkFile(gateReportPath, "gateReview.reportPath", opts);
115
- return {
116
- codeReview: {
117
- by: reviewerRoleField(codeReview["by"], reviewerRoles.codeReview, "codeReview.by"),
118
- recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
119
- codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
120
- reportPath: codeReportPath,
121
- evidence: textField(codeReview["evidence"], "codeReview.evidence"),
122
- blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers"),
123
- },
48
+ const common = {
124
49
  manualQa: {
125
- by: reviewerRoleField(manualQa["by"], reviewerRoles.manualQa, "manualQa.by"),
50
+ by: manualQaBy,
126
51
  status: literal(manualQa["status"], "passed", "manualQa.status"),
127
52
  evidence: textField(manualQa["evidence"], "manualQa.evidence"),
128
53
  surfaceEvidence,
@@ -130,7 +55,7 @@ export function validateQualityGate(input, opts) {
130
55
  artifactRefs,
131
56
  },
132
57
  gateReview: {
133
- by: reviewerRoleField(gateReview["by"], reviewerRoles.gateReview, "gateReview.by"),
58
+ by: gateReviewBy,
134
59
  recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
135
60
  reportPath: gateReportPath,
136
61
  evidence: textField(gateReview["evidence"], "gateReview.evidence"),
@@ -151,6 +76,23 @@ export function validateQualityGate(input, opts) {
151
76
  adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered"),
152
77
  },
153
78
  };
79
+ if (surface === "omo-senpi")
80
+ return { surface, ...common };
81
+ const codeReview = section(gate["codeReview"], "codeReview");
82
+ const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
83
+ checkFile(codeReportPath, "codeReview.reportPath", opts);
84
+ return {
85
+ surface,
86
+ ...common,
87
+ codeReview: {
88
+ by: "lazycodex-code-reviewer",
89
+ recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
90
+ codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
91
+ reportPath: codeReportPath,
92
+ evidence: textField(codeReview["evidence"], "codeReview.evidence"),
93
+ blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers"),
94
+ },
95
+ };
154
96
  }
155
97
  function parseSurfaceEvidence(value, byId) {
156
98
  if (!Array.isArray(value) || value.length === 0)
@@ -1,4 +1,5 @@
1
1
  // biome-ignore-all format: compact port must stay within the requested pure LOC budget.
2
+ import { codexSnapshotMismatchError } from "./checkpoint-reconciliation.js";
2
3
  import { readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot } from "./codex-goal-snapshot.js";
3
4
  import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
4
5
  import { seedDefaultSuccessCriteria } from "./plan-crud.js";
@@ -42,9 +43,10 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
42
43
  ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
43
44
  const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
44
45
  const aggregate = codexGoalMode(plan) === "aggregate";
45
- const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}), allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
46
+ const expected = expectedCodexObjective(plan, goal);
47
+ const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expected, ...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}), allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
46
48
  if (!reconciliation.ok)
47
- ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
49
+ throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expected });
48
50
  const now = iso();
49
51
  for (const field of BLOCKER_FIELDS)
50
52
  Reflect.deleteProperty(goal, field);