oh-my-codex 0.17.0 → 0.17.2

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 (178) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/dist/cli/__tests__/question.test.js +2 -0
  4. package/dist/cli/__tests__/question.test.js.map +1 -1
  5. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +3 -0
  6. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  7. package/dist/cli/__tests__/ralph.test.js +0 -124
  8. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  9. package/dist/cli/__tests__/setup-install-mode.test.js +156 -3
  10. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  11. package/dist/cli/__tests__/setup-refresh.test.js +3 -3
  12. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  13. package/dist/cli/__tests__/team.test.js +166 -42
  14. package/dist/cli/__tests__/team.test.js.map +1 -1
  15. package/dist/cli/doctor.js +9 -4
  16. package/dist/cli/doctor.js.map +1 -1
  17. package/dist/cli/plugin-marketplace.d.ts +5 -1
  18. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  19. package/dist/cli/plugin-marketplace.js +31 -2
  20. package/dist/cli/plugin-marketplace.js.map +1 -1
  21. package/dist/cli/question.d.ts +1 -1
  22. package/dist/cli/question.d.ts.map +1 -1
  23. package/dist/cli/question.js +98 -4
  24. package/dist/cli/question.js.map +1 -1
  25. package/dist/cli/ralph.d.ts.map +1 -1
  26. package/dist/cli/ralph.js +1 -49
  27. package/dist/cli/ralph.js.map +1 -1
  28. package/dist/cli/setup.d.ts +1 -0
  29. package/dist/cli/setup.d.ts.map +1 -1
  30. package/dist/cli/setup.js +75 -9
  31. package/dist/cli/setup.js.map +1 -1
  32. package/dist/cli/team.d.ts.map +1 -1
  33. package/dist/cli/team.js +21 -29
  34. package/dist/cli/team.js.map +1 -1
  35. package/dist/config/generator.d.ts +4 -0
  36. package/dist/config/generator.d.ts.map +1 -1
  37. package/dist/config/generator.js +58 -0
  38. package/dist/config/generator.js.map +1 -1
  39. package/dist/hooks/__tests__/agents-overlay.test.js +29 -0
  40. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  41. package/dist/hooks/__tests__/session.test.js +126 -1
  42. package/dist/hooks/__tests__/session.test.js.map +1 -1
  43. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  44. package/dist/hooks/agents-overlay.js +6 -3
  45. package/dist/hooks/agents-overlay.js.map +1 -1
  46. package/dist/hooks/session.d.ts +11 -3
  47. package/dist/hooks/session.d.ts.map +1 -1
  48. package/dist/hooks/session.js +68 -6
  49. package/dist/hooks/session.js.map +1 -1
  50. package/dist/hud/__tests__/reconcile.test.js +63 -0
  51. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  52. package/dist/hud/__tests__/tmux.test.d.ts +2 -0
  53. package/dist/hud/__tests__/tmux.test.d.ts.map +1 -0
  54. package/dist/hud/__tests__/tmux.test.js +92 -0
  55. package/dist/hud/__tests__/tmux.test.js.map +1 -0
  56. package/dist/hud/reconcile.d.ts +2 -0
  57. package/dist/hud/reconcile.d.ts.map +1 -1
  58. package/dist/hud/reconcile.js +14 -1
  59. package/dist/hud/reconcile.js.map +1 -1
  60. package/dist/hud/tmux.d.ts +12 -0
  61. package/dist/hud/tmux.d.ts.map +1 -1
  62. package/dist/hud/tmux.js +88 -0
  63. package/dist/hud/tmux.js.map +1 -1
  64. package/dist/mcp/__tests__/hermes-bridge.test.js +82 -1
  65. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  66. package/dist/mcp/hermes-bridge.d.ts +34 -1
  67. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  68. package/dist/mcp/hermes-bridge.js +75 -0
  69. package/dist/mcp/hermes-bridge.js.map +1 -1
  70. package/dist/mcp/hermes-server.d.ts +111 -6
  71. package/dist/mcp/hermes-server.d.ts.map +1 -1
  72. package/dist/mcp/hermes-server.js +38 -1
  73. package/dist/mcp/hermes-server.js.map +1 -1
  74. package/dist/pipeline/__tests__/stages.test.js +18 -9
  75. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  76. package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
  77. package/dist/pipeline/stages/team-exec.js +2 -7
  78. package/dist/pipeline/stages/team-exec.js.map +1 -1
  79. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +111 -269
  80. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  81. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +31 -72
  82. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -1
  83. package/dist/planning/__tests__/artifacts.test.js +27 -372
  84. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  85. package/dist/planning/artifacts.d.ts +1 -14
  86. package/dist/planning/artifacts.d.ts.map +1 -1
  87. package/dist/planning/artifacts.js +11 -31
  88. package/dist/planning/artifacts.js.map +1 -1
  89. package/dist/question/__tests__/state.test.js +349 -1
  90. package/dist/question/__tests__/state.test.js.map +1 -1
  91. package/dist/question/__tests__/ui.test.js +6 -6
  92. package/dist/question/__tests__/ui.test.js.map +1 -1
  93. package/dist/question/events.d.ts +53 -0
  94. package/dist/question/events.d.ts.map +1 -0
  95. package/dist/question/events.js +201 -0
  96. package/dist/question/events.js.map +1 -0
  97. package/dist/question/state.d.ts +30 -2
  98. package/dist/question/state.d.ts.map +1 -1
  99. package/dist/question/state.js +276 -5
  100. package/dist/question/state.js.map +1 -1
  101. package/dist/question/types.d.ts +1 -0
  102. package/dist/question/types.d.ts.map +1 -1
  103. package/dist/question/types.js.map +1 -1
  104. package/dist/question/ui.d.ts.map +1 -1
  105. package/dist/question/ui.js +3 -18
  106. package/dist/question/ui.js.map +1 -1
  107. package/dist/ralph/__tests__/completion-audit.test.js +39 -0
  108. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -1
  109. package/dist/scripts/__tests__/codex-native-hook.test.js +111 -1
  110. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  111. package/dist/scripts/__tests__/run-test-files.test.js +22 -0
  112. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  113. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  114. package/dist/scripts/codex-native-hook.js +93 -1
  115. package/dist/scripts/codex-native-hook.js.map +1 -1
  116. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  117. package/dist/scripts/codex-native-pre-post.js +12 -6
  118. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  119. package/dist/scripts/run-test-files.js +12 -1
  120. package/dist/scripts/run-test-files.js.map +1 -1
  121. package/dist/team/__tests__/approved-execution.test.js +25 -24
  122. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  123. package/dist/team/__tests__/runtime.test.js +173 -26
  124. package/dist/team/__tests__/runtime.test.js.map +1 -1
  125. package/dist/team/__tests__/scaling.test.js +66 -17
  126. package/dist/team/__tests__/scaling.test.js.map +1 -1
  127. package/dist/team/__tests__/tmux-session.test.js +42 -0
  128. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  129. package/dist/team/__tests__/worker-bootstrap.test.js +205 -0
  130. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  131. package/dist/team/approved-execution.d.ts +13 -0
  132. package/dist/team/approved-execution.d.ts.map +1 -1
  133. package/dist/team/approved-execution.js +65 -30
  134. package/dist/team/approved-execution.js.map +1 -1
  135. package/dist/team/runtime.d.ts.map +1 -1
  136. package/dist/team/runtime.js +28 -24
  137. package/dist/team/runtime.js.map +1 -1
  138. package/dist/team/scaling.d.ts.map +1 -1
  139. package/dist/team/scaling.js +7 -8
  140. package/dist/team/scaling.js.map +1 -1
  141. package/dist/team/tmux-session.d.ts.map +1 -1
  142. package/dist/team/tmux-session.js +48 -2
  143. package/dist/team/tmux-session.js.map +1 -1
  144. package/dist/team/ultragoal-context.d.ts +35 -0
  145. package/dist/team/ultragoal-context.d.ts.map +1 -0
  146. package/dist/team/ultragoal-context.js +191 -0
  147. package/dist/team/ultragoal-context.js.map +1 -0
  148. package/dist/ultragoal/__tests__/docs-contract.test.js +19 -0
  149. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  150. package/package.json +1 -1
  151. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  152. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -3
  153. package/plugins/oh-my-codex/skills/ralph/SKILL.md +2 -2
  154. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  155. package/plugins/oh-my-codex/skills/team/SKILL.md +6 -0
  156. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +11 -0
  157. package/skills/plan/SKILL.md +3 -3
  158. package/skills/ralph/SKILL.md +2 -2
  159. package/skills/ralplan/SKILL.md +1 -1
  160. package/skills/team/SKILL.md +6 -0
  161. package/skills/ultragoal/SKILL.md +11 -0
  162. package/src/scripts/__tests__/codex-native-hook.test.ts +133 -1
  163. package/src/scripts/__tests__/run-test-files.test.ts +32 -0
  164. package/src/scripts/codex-native-hook.ts +121 -2
  165. package/src/scripts/codex-native-pre-post.ts +12 -6
  166. package/src/scripts/run-test-files.ts +13 -2
  167. package/dist/planning/__tests__/context-pack-status.test.d.ts +0 -2
  168. package/dist/planning/__tests__/context-pack-status.test.d.ts.map +0 -1
  169. package/dist/planning/__tests__/context-pack-status.test.js +0 -795
  170. package/dist/planning/__tests__/context-pack-status.test.js.map +0 -1
  171. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +0 -2
  172. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +0 -1
  173. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +0 -612
  174. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +0 -1
  175. package/dist/planning/context-pack-status.d.ts +0 -73
  176. package/dist/planning/context-pack-status.d.ts.map +0 -1
  177. package/dist/planning/context-pack-status.js +0 -745
  178. package/dist/planning/context-pack-status.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-codex",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Multi-agent orchestration layer for OpenAI Codex CLI",
5
5
  "author": {
6
6
  "name": "Yeachan Heo",
@@ -95,8 +95,8 @@ Jumping into code without understanding requirements leads to rework, scope cree
95
95
  c. Update the plan file in `.omx/plans/` with the accepted improvements (add missing details, refine steps, strengthen acceptance criteria, ADR updates, etc.)
96
96
  d. Note which improvements were applied in a brief changelog section at the end of the plan
97
97
  e. Before any execution handoff, derive an explicit **available-agent-types roster** from the known prompt catalog and add concrete **follow-up staffing guidance** for both `$ralph` and `$team` (recommended roles, counts, suggested reasoning levels by lane, and why each lane exists)
98
- f. Add a product-facing **Goal-Mode Follow-up Suggestions** section: recommend `$ultragoal` by default for general goal-oriented follow-up, `$autoresearch-goal` when the context is a research project, and `$performance-goal` when the context is an optimization or performance project. Keep these suggestions alongside the Ralph/team paths rather than replacing them when implementation delivery is still the main need.
99
- g. For the `$team` path, add an explicit launch-hint block with concrete `omx team` / `$team` commands and a **team verification path** (what team proves before shutdown, what Ralph verifies after handoff)
98
+ f. Add a product-facing **Goal-Mode Follow-up Suggestions** section: recommend `$ultragoal` by default for general goal-oriented follow-up, `$autoresearch-goal` when the context is a research project, and `$performance-goal` when the context is an optimization or performance project. Keep these suggestions alongside the Ralph/team paths rather than replacing them when implementation delivery is still the main need. For durable-goal work that is also parallelizable, explicitly recommend **Team + Ultragoal**: Ultragoal remains leader-owned goal/ledger state and Team returns checkpoint-ready execution evidence.
99
+ g. For the `$team` path, add an explicit launch-hint block with concrete `omx team` / `$team` commands and a **team verification path** (what team proves before shutdown, what Ralph verifies after handoff). Distinguish Team + Ultragoal from a later Ralph follow-up: Team handles coordinated parallel lanes; Ralph is only for persistent sequential single-owner verification/fix pressure when needed.
100
100
  7. On Critic approval (with improvements applied): *(--interactive only)* If running with `--interactive`, use `AskUserQuestion` / the structured question UI to present the plan with these options:
101
101
  - **Approve and execute** — proceed to implementation via ralph+ultrawork
102
102
  - **Approve and implement via team** — proceed to implementation via coordinated parallel team agents
@@ -148,7 +148,7 @@ Plans are saved to `.omx/plans/`. Drafts go to `.omx/drafts/`.
148
148
  - In consensus mode, default to RALPLAN-DR short mode; enable deliberate mode on `--deliberate` or explicit high-risk signals (auth/security, migrations, destructive changes, production incidents, compliance/PII, public API breakage)
149
149
  - In consensus mode with `--interactive`: use `AskUserQuestion` / the structured question UI for the user feedback step (step 2) and the final approval step (step 7) -- never ask for approval in plain text when a structured surface is available. Without `--interactive`, auto-proceed through planning steps without pausing. Output the final plan without execution.
150
150
  - In consensus mode with `--interactive`, on user approval **MUST** invoke the selected follow-up lane from step 9 (`$ralph`, `$team`, `$ultragoal`, `$autoresearch-goal`, or `$performance-goal`) -- never implement directly in the planning agent
151
- - In consensus mode, execution follow-up handoff **MUST** include an explicit available-agent-types roster plus concrete staffing / role-allocation guidance grounded in that roster, suggested reasoning levels by lane, product-facing goal-mode follow-up suggestions (`$ultragoal` by default, `$autoresearch-goal` for research projects, `$performance-goal` for optimization/performance projects), explicit `omx team` / `$team` launch hints, and a team verification path
151
+ - In consensus mode, execution follow-up handoff **MUST** include an explicit available-agent-types roster plus concrete staffing / role-allocation guidance grounded in that roster, suggested reasoning levels by lane, product-facing goal-mode follow-up suggestions (`$ultragoal` by default, `$autoresearch-goal` for research projects, `$performance-goal` for optimization/performance projects), explicit `omx team` / `$team` launch hints, and a team verification path. For parallelizable durable-goal plans, recommend Team + Ultragoal with leader-owned checkpointing from Team evidence; reserve Ralph for persistent sequential single-owner verification/fix follow-up.
152
152
  </Tool_Usage>
153
153
 
154
154
  ## Scenario Examples
@@ -125,8 +125,8 @@ Use the CLI-first state surface for Ralph lifecycle state (`omx state write/read
125
125
  `omx state write --input '{"mode":"ralph","iteration":<current>,"current_phase":"executing"}' --json`
126
126
  - **On verification/fix transition**:
127
127
  `omx state write --input '{"mode":"ralph","current_phase":"verifying"}' --json` or `omx state write --input '{"mode":"ralph","current_phase":"fixing"}' --json`
128
- - **On completion**:
129
- `omx state write --input '{"mode":"ralph","active":false,"current_phase":"complete","completed_at":"<now>"}' --json`
128
+ - **On completion** (only after the completion audit passes with real evidence):
129
+ `omx state write --input '{"mode":"ralph","active":false,"current_phase":"complete","completed_at":"<now>","completion_audit":{"passed":true,"prompt_to_artifact_checklist":["<requirement mapped to artifact/evidence>"],"verification_evidence":["<fresh test/build/lint command and result>"]}}' --json`
130
130
  - **On cancellation/cleanup**:
131
131
  run `$cancel` (which should call `omx state clear --input '{"mode":"ralph"}' --json`)
132
132
 
@@ -70,7 +70,7 @@ When ralplan outputs a final handoff or asks the user to choose a next lane, inc
70
70
  - `$autoresearch-goal` — research-project follow-up when the plan centers on a question, literature/reference gathering, evaluator-backed research, or a professor/critic-style research deliverable.
71
71
  - `$performance-goal` — optimization/performance follow-up when the plan centers on speed, latency, throughput, memory, benchmark, or other measurable performance work.
72
72
 
73
- Keep `$ralph` and `$team` as first-class execution options where appropriate: use Ralph for persistent single-owner completion/verification pressure and team for coordinated parallel implementation. Do not present the goal-mode options as replacements for Ralph/team when the task is mainly implementation delivery; present them as better fits when durable goal tracking, research validation, or performance evaluators are the primary need.
73
+ Keep `$ralph` and `$team` as first-class execution options where appropriate: use Ralph for persistent single-owner completion/verification pressure and team for coordinated parallel implementation. For parallelizable durable-goal delivery, recommend `$ultragoal` + `$team` together: Ultragoal remains the leader-owned `.omx/ultragoal` ledger/Codex-goal wrapper while Team runs parallel lanes and returns checkpoint-ready evidence. Do not present the goal-mode options as replacements for Ralph/team when the task is mainly implementation delivery; present them as better fits when durable goal tracking, research validation, or performance evaluators are the primary need.
74
74
 
75
75
  ## Pre-context Intake
76
76
 
@@ -57,6 +57,12 @@ requiring a separate linked Ralph launch up front.
57
57
  - **Escalation:** start a separate `omx ralph ...` / `$ralph ...` only when a later manual follow-up still needs a persistent single-owner fix/verification loop.
58
58
  - **Deprecation:** `omx team ralph ...` has been removed. Use plain `omx team ...` for team execution or run `omx ralph ...` separately when you explicitly want a later Ralph loop.
59
59
 
60
+ ### Team + Ultragoal bridge
61
+
62
+ Use `$ultragoal` for durable leader-owned goal/ledger tracking and `$team` for parallel execution lanes. When Team is launched with an active `.omx/ultragoal/goals.json`, worker inboxes/status may include leader-owned Ultragoal context: `.omx/ultragoal/goals.json`, `.omx/ultragoal/ledger.jsonl`, the active goal id, Codex goal mode, and the `fresh_leader_get_goal_required` checkpoint policy.
63
+
64
+ Workers provide task status and verification evidence only. They do not own Ultragoal goal state, create worker ledgers, mutate `.omx/ultragoal`, auto-launch Team from Ultragoal, or perform hidden Codex goal mutation. The leader uses terminal Team evidence plus a fresh `get_goal` snapshot to run `omx ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evidence mentioning .omx/ultragoal and <id>>" --codex-goal-json <fresh-get_goal-json-or-path>`.
65
+
60
66
  ### Claude teammates (v0.6.0+)
61
67
 
62
68
  Important: `N:agent-type` (for example `2:executor`) selects the **worker role prompt**, not the worker CLI (`codex` vs `claude`).
@@ -43,6 +43,17 @@ Loop until `omx ultragoal status` reports all goals complete:
43
43
  `omx ultragoal checkpoint --goal-id <id> --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json <get_goal-json-or-path>`
44
44
  11. Resume failed goals with `omx ultragoal complete-goals --retry-failed`.
45
45
 
46
+ ## Use Ultragoal and Team together
47
+
48
+ Use ultragoal and team together for a durable Ultragoal story that benefits from parallel execution. Ultragoal remains leader-owned: `.omx/ultragoal/goals.json` stores the story plan and `.omx/ultragoal/ledger.jsonl` stores checkpoints. Team is the parallel execution engine and returns task/evidence status to the leader.
49
+
50
+ The leader checkpoints Ultragoal from Team evidence with a fresh `get_goal` snapshot:
51
+
52
+ ```sh
53
+ omx ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evidence mentioning .omx/ultragoal and <id>>" --codex-goal-json <fresh-get_goal-json-or-path>
54
+ ```
55
+
56
+ Workers do not own ultragoal goal state, do not create worker ultragoal ledgers, and do not checkpoint Ultragoal. Team launch remains explicit; Ultragoal does not auto-launch Team and performs no hidden Codex goal mutation.
46
57
 
47
58
  ## Mandatory final cleanup and review gate
48
59
 
@@ -95,8 +95,8 @@ Jumping into code without understanding requirements leads to rework, scope cree
95
95
  c. Update the plan file in `.omx/plans/` with the accepted improvements (add missing details, refine steps, strengthen acceptance criteria, ADR updates, etc.)
96
96
  d. Note which improvements were applied in a brief changelog section at the end of the plan
97
97
  e. Before any execution handoff, derive an explicit **available-agent-types roster** from the known prompt catalog and add concrete **follow-up staffing guidance** for both `$ralph` and `$team` (recommended roles, counts, suggested reasoning levels by lane, and why each lane exists)
98
- f. Add a product-facing **Goal-Mode Follow-up Suggestions** section: recommend `$ultragoal` by default for general goal-oriented follow-up, `$autoresearch-goal` when the context is a research project, and `$performance-goal` when the context is an optimization or performance project. Keep these suggestions alongside the Ralph/team paths rather than replacing them when implementation delivery is still the main need.
99
- g. For the `$team` path, add an explicit launch-hint block with concrete `omx team` / `$team` commands and a **team verification path** (what team proves before shutdown, what Ralph verifies after handoff)
98
+ f. Add a product-facing **Goal-Mode Follow-up Suggestions** section: recommend `$ultragoal` by default for general goal-oriented follow-up, `$autoresearch-goal` when the context is a research project, and `$performance-goal` when the context is an optimization or performance project. Keep these suggestions alongside the Ralph/team paths rather than replacing them when implementation delivery is still the main need. For durable-goal work that is also parallelizable, explicitly recommend **Team + Ultragoal**: Ultragoal remains leader-owned goal/ledger state and Team returns checkpoint-ready execution evidence.
99
+ g. For the `$team` path, add an explicit launch-hint block with concrete `omx team` / `$team` commands and a **team verification path** (what team proves before shutdown, what Ralph verifies after handoff). Distinguish Team + Ultragoal from a later Ralph follow-up: Team handles coordinated parallel lanes; Ralph is only for persistent sequential single-owner verification/fix pressure when needed.
100
100
  7. On Critic approval (with improvements applied): *(--interactive only)* If running with `--interactive`, use `AskUserQuestion` / the structured question UI to present the plan with these options:
101
101
  - **Approve and execute** — proceed to implementation via ralph+ultrawork
102
102
  - **Approve and implement via team** — proceed to implementation via coordinated parallel team agents
@@ -148,7 +148,7 @@ Plans are saved to `.omx/plans/`. Drafts go to `.omx/drafts/`.
148
148
  - In consensus mode, default to RALPLAN-DR short mode; enable deliberate mode on `--deliberate` or explicit high-risk signals (auth/security, migrations, destructive changes, production incidents, compliance/PII, public API breakage)
149
149
  - In consensus mode with `--interactive`: use `AskUserQuestion` / the structured question UI for the user feedback step (step 2) and the final approval step (step 7) -- never ask for approval in plain text when a structured surface is available. Without `--interactive`, auto-proceed through planning steps without pausing. Output the final plan without execution.
150
150
  - In consensus mode with `--interactive`, on user approval **MUST** invoke the selected follow-up lane from step 9 (`$ralph`, `$team`, `$ultragoal`, `$autoresearch-goal`, or `$performance-goal`) -- never implement directly in the planning agent
151
- - In consensus mode, execution follow-up handoff **MUST** include an explicit available-agent-types roster plus concrete staffing / role-allocation guidance grounded in that roster, suggested reasoning levels by lane, product-facing goal-mode follow-up suggestions (`$ultragoal` by default, `$autoresearch-goal` for research projects, `$performance-goal` for optimization/performance projects), explicit `omx team` / `$team` launch hints, and a team verification path
151
+ - In consensus mode, execution follow-up handoff **MUST** include an explicit available-agent-types roster plus concrete staffing / role-allocation guidance grounded in that roster, suggested reasoning levels by lane, product-facing goal-mode follow-up suggestions (`$ultragoal` by default, `$autoresearch-goal` for research projects, `$performance-goal` for optimization/performance projects), explicit `omx team` / `$team` launch hints, and a team verification path. For parallelizable durable-goal plans, recommend Team + Ultragoal with leader-owned checkpointing from Team evidence; reserve Ralph for persistent sequential single-owner verification/fix follow-up.
152
152
  </Tool_Usage>
153
153
 
154
154
  ## Scenario Examples
@@ -125,8 +125,8 @@ Use the CLI-first state surface for Ralph lifecycle state (`omx state write/read
125
125
  `omx state write --input '{"mode":"ralph","iteration":<current>,"current_phase":"executing"}' --json`
126
126
  - **On verification/fix transition**:
127
127
  `omx state write --input '{"mode":"ralph","current_phase":"verifying"}' --json` or `omx state write --input '{"mode":"ralph","current_phase":"fixing"}' --json`
128
- - **On completion**:
129
- `omx state write --input '{"mode":"ralph","active":false,"current_phase":"complete","completed_at":"<now>"}' --json`
128
+ - **On completion** (only after the completion audit passes with real evidence):
129
+ `omx state write --input '{"mode":"ralph","active":false,"current_phase":"complete","completed_at":"<now>","completion_audit":{"passed":true,"prompt_to_artifact_checklist":["<requirement mapped to artifact/evidence>"],"verification_evidence":["<fresh test/build/lint command and result>"]}}' --json`
130
130
  - **On cancellation/cleanup**:
131
131
  run `$cancel` (which should call `omx state clear --input '{"mode":"ralph"}' --json`)
132
132
 
@@ -70,7 +70,7 @@ When ralplan outputs a final handoff or asks the user to choose a next lane, inc
70
70
  - `$autoresearch-goal` — research-project follow-up when the plan centers on a question, literature/reference gathering, evaluator-backed research, or a professor/critic-style research deliverable.
71
71
  - `$performance-goal` — optimization/performance follow-up when the plan centers on speed, latency, throughput, memory, benchmark, or other measurable performance work.
72
72
 
73
- Keep `$ralph` and `$team` as first-class execution options where appropriate: use Ralph for persistent single-owner completion/verification pressure and team for coordinated parallel implementation. Do not present the goal-mode options as replacements for Ralph/team when the task is mainly implementation delivery; present them as better fits when durable goal tracking, research validation, or performance evaluators are the primary need.
73
+ Keep `$ralph` and `$team` as first-class execution options where appropriate: use Ralph for persistent single-owner completion/verification pressure and team for coordinated parallel implementation. For parallelizable durable-goal delivery, recommend `$ultragoal` + `$team` together: Ultragoal remains the leader-owned `.omx/ultragoal` ledger/Codex-goal wrapper while Team runs parallel lanes and returns checkpoint-ready evidence. Do not present the goal-mode options as replacements for Ralph/team when the task is mainly implementation delivery; present them as better fits when durable goal tracking, research validation, or performance evaluators are the primary need.
74
74
 
75
75
  ## Pre-context Intake
76
76
 
@@ -57,6 +57,12 @@ requiring a separate linked Ralph launch up front.
57
57
  - **Escalation:** start a separate `omx ralph ...` / `$ralph ...` only when a later manual follow-up still needs a persistent single-owner fix/verification loop.
58
58
  - **Deprecation:** `omx team ralph ...` has been removed. Use plain `omx team ...` for team execution or run `omx ralph ...` separately when you explicitly want a later Ralph loop.
59
59
 
60
+ ### Team + Ultragoal bridge
61
+
62
+ Use `$ultragoal` for durable leader-owned goal/ledger tracking and `$team` for parallel execution lanes. When Team is launched with an active `.omx/ultragoal/goals.json`, worker inboxes/status may include leader-owned Ultragoal context: `.omx/ultragoal/goals.json`, `.omx/ultragoal/ledger.jsonl`, the active goal id, Codex goal mode, and the `fresh_leader_get_goal_required` checkpoint policy.
63
+
64
+ Workers provide task status and verification evidence only. They do not own Ultragoal goal state, create worker ledgers, mutate `.omx/ultragoal`, auto-launch Team from Ultragoal, or perform hidden Codex goal mutation. The leader uses terminal Team evidence plus a fresh `get_goal` snapshot to run `omx ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evidence mentioning .omx/ultragoal and <id>>" --codex-goal-json <fresh-get_goal-json-or-path>`.
65
+
60
66
  ### Claude teammates (v0.6.0+)
61
67
 
62
68
  Important: `N:agent-type` (for example `2:executor`) selects the **worker role prompt**, not the worker CLI (`codex` vs `claude`).
@@ -43,6 +43,17 @@ Loop until `omx ultragoal status` reports all goals complete:
43
43
  `omx ultragoal checkpoint --goal-id <id> --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json <get_goal-json-or-path>`
44
44
  11. Resume failed goals with `omx ultragoal complete-goals --retry-failed`.
45
45
 
46
+ ## Use Ultragoal and Team together
47
+
48
+ Use ultragoal and team together for a durable Ultragoal story that benefits from parallel execution. Ultragoal remains leader-owned: `.omx/ultragoal/goals.json` stores the story plan and `.omx/ultragoal/ledger.jsonl` stores checkpoints. Team is the parallel execution engine and returns task/evidence status to the leader.
49
+
50
+ The leader checkpoints Ultragoal from Team evidence with a fresh `get_goal` snapshot:
51
+
52
+ ```sh
53
+ omx ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evidence mentioning .omx/ultragoal and <id>>" --codex-goal-json <fresh-get_goal-json-or-path>
54
+ ```
55
+
56
+ Workers do not own ultragoal goal state, do not create worker ultragoal ledgers, and do not checkpoint Ultragoal. Team launch remains explicit; Ultragoal does not auto-launch Team and performs no hidden Codex goal mutation.
46
57
 
47
58
  ## Mandatory final cleanup and review gate
48
59
 
@@ -200,6 +200,8 @@ const TEAM_ENV_KEYS = [
200
200
  "TMUX",
201
201
  "TMUX_PANE",
202
202
  "OMX_TMUX_HUD_OWNER",
203
+ "OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS",
204
+ "OMX_NATIVE_STOP_NO_PROGRESS_IDLE_MS",
203
205
  ] as const;
204
206
 
205
207
  const priorTeamEnv = new Map<(typeof TEAM_ENV_KEYS)[number], string | undefined>();
@@ -4403,6 +4405,87 @@ exit 0
4403
4405
  }
4404
4406
  });
4405
4407
 
4408
+ it("stays silent on PreToolUse for compact inline Lore commit with only OmX co-author trailer", async () => {
4409
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-compact-coauthor-"));
4410
+ try {
4411
+ const result = await dispatchCodexNativeHook(
4412
+ {
4413
+ hook_event_name: "PreToolUse",
4414
+ cwd,
4415
+ tool_name: "Bash",
4416
+ tool_use_id: "tool-git-commit-compact-coauthor",
4417
+ tool_input: {
4418
+ command: [
4419
+ 'git commit',
4420
+ '-m "Launch lvisai.xyz intro site"',
4421
+ '-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
4422
+ ].join(" "),
4423
+ },
4424
+ },
4425
+ { cwd },
4426
+ );
4427
+
4428
+ assert.equal(result.omxEventName, "pre-tool-use");
4429
+ assert.equal(result.outputJson, null);
4430
+ } finally {
4431
+ await rm(cwd, { recursive: true, force: true });
4432
+ }
4433
+ });
4434
+
4435
+ it("stays silent on PreToolUse for body-omitted inline Lore commit with decision trailers", async () => {
4436
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-compact-trailers-"));
4437
+ try {
4438
+ const result = await dispatchCodexNativeHook(
4439
+ {
4440
+ hook_event_name: "PreToolUse",
4441
+ cwd,
4442
+ tool_name: "Bash",
4443
+ tool_use_id: "tool-git-commit-compact-trailers",
4444
+ tool_input: {
4445
+ command: [
4446
+ 'git commit',
4447
+ '-m "Launch lvisai.xyz intro site"',
4448
+ '-m "Constraint: Native PreToolUse can only inspect inline Bash command text\nTested: node --test dist/scripts/__tests__/codex-native-hook.test.js\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
4449
+ ].join(" "),
4450
+ },
4451
+ },
4452
+ { cwd },
4453
+ );
4454
+
4455
+ assert.equal(result.omxEventName, "pre-tool-use");
4456
+ assert.equal(result.outputJson, null);
4457
+ } finally {
4458
+ await rm(cwd, { recursive: true, force: true });
4459
+ }
4460
+ });
4461
+
4462
+ it("blocks PreToolUse compact inline Lore commit when the blank separator is missing", async () => {
4463
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-compact-no-separator-"));
4464
+ try {
4465
+ const result = await dispatchCodexNativeHook(
4466
+ {
4467
+ hook_event_name: "PreToolUse",
4468
+ cwd,
4469
+ tool_name: "Bash",
4470
+ tool_use_id: "tool-git-commit-compact-no-separator",
4471
+ tool_input: {
4472
+ command: [
4473
+ 'git commit',
4474
+ '--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
4475
+ ].join(" "),
4476
+ },
4477
+ },
4478
+ { cwd },
4479
+ );
4480
+
4481
+ assert.equal(result.omxEventName, "pre-tool-use");
4482
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4483
+ assert.match(JSON.stringify(result.outputJson), /Add a blank line after the subject/);
4484
+ } finally {
4485
+ await rm(cwd, { recursive: true, force: true });
4486
+ }
4487
+ });
4488
+
4406
4489
  it("warns on PreToolUse git commit when mapped source changes lack staged docs refresh", async () => {
4407
4490
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-document-refresh-warn-"));
4408
4491
  try {
@@ -7663,7 +7746,11 @@ exit 0
7663
7746
  );
7664
7747
 
7665
7748
  assert.equal(result.omxEventName, "stop");
7666
- assert.match(String(result.outputJson?.reason), /Ralph completion audit is missing required evidence/);
7749
+ const reason = String(result.outputJson?.reason);
7750
+ assert.match(reason, /Ralph completion audit is missing required evidence/);
7751
+ assert.match(reason, /state\.completion_audit = \{ passed: true, prompt_to_artifact_checklist: \[\.\.\.\], verification_evidence: \[\.\.\.\] \}/);
7752
+ assert.match(reason, /repo-relative JSON file/);
7753
+ assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
7667
7754
  assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
7668
7755
  const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record<string, unknown>;
7669
7756
  assert.equal(reopened.active, true);
@@ -8519,6 +8606,51 @@ exit 0
8519
8606
  }
8520
8607
  });
8521
8608
 
8609
+ it("bounds repeated ordinary working Stop loops with a diagnostic summary", async () => {
8610
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-working-loop-"));
8611
+ try {
8612
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
8613
+ process.env.OMX_SESSION_ID = "sess-working-loop";
8614
+ process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS = "2";
8615
+ process.env.OMX_NATIVE_STOP_NO_PROGRESS_IDLE_MS = "0";
8616
+
8617
+ const payload = {
8618
+ hook_event_name: "Stop",
8619
+ cwd,
8620
+ session_id: "sess-working-loop",
8621
+ thread_id: "thread-working-loop",
8622
+ turn_id: "turn-working-loop-1",
8623
+ last_assistant_message: "Keep going and finish the cleanup.",
8624
+ };
8625
+
8626
+ const first = await dispatchCodexNativeHook(payload, { cwd });
8627
+ assert.equal(first.outputJson?.stopReason, "auto_nudge");
8628
+
8629
+ const repeated = await dispatchCodexNativeHook(
8630
+ {
8631
+ ...payload,
8632
+ turn_id: "turn-working-loop-2",
8633
+ stop_hook_active: true,
8634
+ },
8635
+ { cwd },
8636
+ );
8637
+
8638
+ assert.equal(repeated.omxEventName, "stop");
8639
+ assert.equal(repeated.outputJson?.decision, "block");
8640
+ assert.equal(repeated.outputJson?.stopReason, "ordinary_task_no_progress_guard");
8641
+ assert.match(String(repeated.outputJson?.systemMessage), /no-progress guard triggered/);
8642
+ assert.match(String(repeated.outputJson?.systemMessage), /diagnostic summary/);
8643
+ assert.match(String(repeated.outputJson?.systemMessage), /complete, blocked, failed, or needs missing information/);
8644
+
8645
+ const persisted = JSON.parse(
8646
+ await readFile(join(cwd, ".omx", "state", "native-stop-state.json"), "utf-8"),
8647
+ ) as { sessions: Record<string, { ordinary_no_progress_guard?: { repeat_count?: number } }> };
8648
+ assert.equal(persisted.sessions["sess-working-loop"]?.ordinary_no_progress_guard?.repeat_count, 2);
8649
+ } finally {
8650
+ await rm(cwd, { recursive: true, force: true });
8651
+ }
8652
+ });
8653
+
8522
8654
  it("re-blocks duplicate native auto-nudge replays for the same Stop reply", async () => {
8523
8655
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-once-"));
8524
8656
  try {
@@ -47,6 +47,38 @@ describe('run-test-files diagnostics', () => {
47
47
  }
48
48
  });
49
49
 
50
+
51
+ it('can force-exit Node test runner after successful CI tests to avoid leaked-handle hangs', () => {
52
+ const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
53
+ try {
54
+ const testsDir = join(wd, '__tests__');
55
+ mkdirSync(testsDir, { recursive: true });
56
+ writeFileSync(
57
+ join(testsDir, 'leaky-pass.test.js'),
58
+ [
59
+ "import { test } from 'node:test';",
60
+ "test('passes but leaves an interval', () => { setInterval(() => {}, 1_000); });",
61
+ '',
62
+ ].join('\n'),
63
+ );
64
+
65
+ const withoutForceExit = runCompiledRunner(wd, { OMX_NODE_TEST_RUNNER_TIMEOUT_MS: '750' }, 2_000);
66
+ assert.notEqual(withoutForceExit.status, 0);
67
+ assert.match(withoutForceExit.stderr, /force exit disabled/);
68
+ assert.match(withoutForceExit.stderr, /did not exit normally|runner timeout 750ms/);
69
+
70
+ const withForceExit = runCompiledRunner(
71
+ wd,
72
+ { OMX_NODE_TEST_RUNNER_TIMEOUT_MS: '750', OMX_NODE_TEST_FORCE_EXIT: '1' },
73
+ 2_000,
74
+ );
75
+ assert.equal(withForceExit.status, 0, withForceExit.stderr || withForceExit.stdout);
76
+ assert.match(withForceExit.stderr, /force exit enabled/);
77
+ } finally {
78
+ rmSync(wd, { recursive: true, force: true });
79
+ }
80
+ });
81
+
50
82
  it('logs that per-test timeout is disabled by default', () => {
51
83
  const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
52
84
  try {
@@ -127,6 +127,9 @@ const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
127
127
  const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
128
128
  const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
129
129
  const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
130
+ const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
131
+ const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
132
+ const ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH = 240;
130
133
  const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
131
134
  /^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
132
135
  /^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
@@ -2350,6 +2353,109 @@ function readPreviousNativeStopSignature(
2350
2353
  return safeString(sessionState.last_signature).trim();
2351
2354
  }
2352
2355
 
2356
+ function parseBoundedPositiveInteger(value: unknown, fallback: number): number {
2357
+ const parsed = Math.trunc(Number(value));
2358
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
2359
+ }
2360
+
2361
+ function parseBoundedNonNegativeInteger(value: unknown, fallback: number): number {
2362
+ const parsed = Math.trunc(Number(value));
2363
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
2364
+ }
2365
+
2366
+ function normalizeOrdinaryStopProgressText(value: unknown): string {
2367
+ return safeString(value)
2368
+ .replace(/\s+/g, " ")
2369
+ .trim()
2370
+ .toLowerCase();
2371
+ }
2372
+
2373
+ function shortenOrdinaryStopProgressText(value: string): string {
2374
+ const trimmed = value.replace(/\s+/g, " ").trim();
2375
+ if (trimmed.length <= ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH) return trimmed;
2376
+ return `${trimmed.slice(0, ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH - 1).trimEnd()}…`;
2377
+ }
2378
+
2379
+ function ordinaryStopProgressFingerprint(payload: CodexHookPayload): string {
2380
+ const message = normalizeOrdinaryStopProgressText(
2381
+ payload.last_assistant_message ?? payload.lastAssistantMessage,
2382
+ ) || "<no assistant message>";
2383
+ const mode = normalizeOrdinaryStopProgressText(payload.mode) || "ordinary";
2384
+ return `${mode}|${message}`;
2385
+ }
2386
+
2387
+ function readIsoTimeMs(value: unknown): number | null {
2388
+ const parsed = Date.parse(safeString(value));
2389
+ return Number.isFinite(parsed) ? parsed : null;
2390
+ }
2391
+
2392
+ async function maybeBuildOrdinaryStopNoProgressOutput(
2393
+ payload: CodexHookPayload,
2394
+ stateDir: string,
2395
+ canonicalSessionId?: string,
2396
+ ): Promise<Record<string, unknown> | null> {
2397
+ const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
2398
+ const state = await readJsonIfExists(statePath) ?? {};
2399
+ const sessions = safeObject(state.sessions);
2400
+ const sessionKey = readNativeStopSessionKey(payload, canonicalSessionId);
2401
+ const sessionState = safeObject(sessions[sessionKey]);
2402
+ const previousGuard = safeObject(sessionState.ordinary_no_progress_guard);
2403
+ const fingerprint = ordinaryStopProgressFingerprint(payload);
2404
+ const nowIso = new Date().toISOString();
2405
+ const previousFingerprint = safeString(previousGuard.fingerprint).trim();
2406
+ const sameFingerprint = previousFingerprint === fingerprint;
2407
+ const firstSeenAt = sameFingerprint
2408
+ ? safeString(previousGuard.first_seen_at).trim() || nowIso
2409
+ : nowIso;
2410
+ const repeatCount = sameFingerprint
2411
+ ? parseBoundedPositiveInteger(previousGuard.repeat_count, 1) + 1
2412
+ : 1;
2413
+
2414
+ sessions[sessionKey] = {
2415
+ ...sessionState,
2416
+ ordinary_no_progress_guard: {
2417
+ fingerprint,
2418
+ first_seen_at: firstSeenAt,
2419
+ last_seen_at: nowIso,
2420
+ repeat_count: repeatCount,
2421
+ last_turn_id: readPayloadTurnId(payload) || null,
2422
+ last_thread_id: readPayloadThreadId(payload) || null,
2423
+ },
2424
+ };
2425
+ await mkdir(stateDir, { recursive: true });
2426
+ await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
2427
+
2428
+ const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
2429
+ if (!stopHookActive) return null;
2430
+
2431
+ const maxRepeats = parseBoundedPositiveInteger(
2432
+ process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS,
2433
+ ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS,
2434
+ );
2435
+ const idleMs = parseBoundedNonNegativeInteger(
2436
+ process.env.OMX_NATIVE_STOP_NO_PROGRESS_IDLE_MS,
2437
+ ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS,
2438
+ );
2439
+ const firstSeenMs = readIsoTimeMs(firstSeenAt) ?? Date.now();
2440
+ const elapsedMs = Math.max(0, Date.now() - firstSeenMs);
2441
+ if (repeatCount < maxRepeats || elapsedMs < idleMs) return null;
2442
+
2443
+ const message = shortenOrdinaryStopProgressText(
2444
+ safeString(payload.last_assistant_message ?? payload.lastAssistantMessage) || "no assistant message recorded",
2445
+ );
2446
+ const elapsedSeconds = Math.round(elapsedMs / 1000);
2447
+ const diagnostic =
2448
+ `OMX ordinary task no-progress guard triggered after ${repeatCount} repeated Stop-hook pass(es) over ~${elapsedSeconds}s with unchanged status: "${message}". ` +
2449
+ "Emit a concise diagnostic summary now: state the last concrete progress/evidence, whether the task is complete, blocked, failed, or needs missing information, and stop instead of continuing a vague working loop.";
2450
+
2451
+ return {
2452
+ decision: "block",
2453
+ reason: diagnostic,
2454
+ stopReason: "ordinary_task_no_progress_guard",
2455
+ systemMessage: diagnostic,
2456
+ };
2457
+ }
2458
+
2353
2459
  async function persistNativeStopSignature(
2354
2460
  stateDir: string,
2355
2461
  payload: CodexHookPayload,
@@ -2678,8 +2784,14 @@ async function buildStopHookOutput(
2678
2784
  if (ralphCompletionAuditBlock) {
2679
2785
  await reopenRalphCompletionAuditBlock(ralphCompletionAuditBlock);
2680
2786
  const blockingPath = formatStopStatePath(cwd, ralphCompletionAuditBlock.path);
2681
- const systemMessage =
2682
- `OMX Ralph completion audit is missing required evidence (${ralphCompletionAuditBlock.reason}; state: ${blockingPath}); continue verification, record a prompt-to-artifact checklist plus verification evidence, and do not report complete yet.`;
2787
+ const systemMessage = [
2788
+ `OMX Ralph completion audit is missing required evidence (${ralphCompletionAuditBlock.reason}; state: ${blockingPath}).`,
2789
+ "Continue verification and do not report complete yet.",
2790
+ "Record machine-readable completion evidence before stopping:",
2791
+ "- either set state.completion_audit = { passed: true, prompt_to_artifact_checklist: [...], verification_evidence: [...] }",
2792
+ "- or set completion_audit_path / completion_audit_evidence_path to a repo-relative JSON file with those same fields.",
2793
+ "Markdown artifacts and flat top-level checklist/evidence fields are not accepted by the Ralph Stop gate.",
2794
+ ].join(" ");
2683
2795
  return await returnPersistentStopBlock(
2684
2796
  payload,
2685
2797
  stateDir,
@@ -2870,6 +2982,13 @@ async function buildStopHookOutput(
2870
2982
  { allowRepeatDuringStopHook: true },
2871
2983
  );
2872
2984
  }
2985
+ const ordinaryNoProgressOutput = await maybeBuildOrdinaryStopNoProgressOutput(
2986
+ payload,
2987
+ stateDir,
2988
+ canonicalSessionId,
2989
+ );
2990
+ if (ordinaryNoProgressOutput) return ordinaryNoProgressOutput;
2991
+
2873
2992
  const autoNudgeConfig = await loadAutoNudgeConfig();
2874
2993
  const autoNudgePhase = await readStopAutoNudgePhase(cwd, stateDir, canonicalSessionId, threadId);
2875
2994
 
@@ -793,14 +793,20 @@ function buildGitCommitComplianceErrors(message: string | null): string[] {
793
793
  errors.push("Add a blank line after the subject before the narrative body.");
794
794
  }
795
795
 
796
+ const hasSubject = (lines[0]?.trim() ?? "") !== "";
797
+ const hasBlankSeparator = lines.length >= 2 && lines[1]?.trim() === "";
796
798
  const { bodyText, trailerLines } = splitBodyAndTrailerLines(lines.slice(2).join("\n"));
797
- if (!bodyText) {
798
- errors.push("Add a narrative body paragraph explaining the decision context.");
799
- }
800
- if (!trailerLines.some((line) => LORE_TRAILER_PREFIXES.some((prefix) => line.startsWith(prefix)))) {
801
- errors.push("Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.");
799
+ const hasOmxCoauthorTrailer = trailerLines.includes(OMX_COAUTHOR_TRAILER);
800
+ const usesCompactLorePath = hasSubject && hasBlankSeparator && !bodyText && hasOmxCoauthorTrailer;
801
+ if (!usesCompactLorePath) {
802
+ if (!bodyText) {
803
+ errors.push("Add a narrative body paragraph explaining the decision context.");
804
+ }
805
+ if (!trailerLines.some((line) => LORE_TRAILER_PREFIXES.some((prefix) => line.startsWith(prefix)))) {
806
+ errors.push("Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.");
807
+ }
802
808
  }
803
- if (!trailerLines.includes(OMX_COAUTHOR_TRAILER)) {
809
+ if (!hasOmxCoauthorTrailer) {
804
810
  errors.push(`Add the required co-author trailer: \`${OMX_COAUTHOR_TRAILER}\`.`);
805
811
  }
806
812
 
@@ -6,6 +6,12 @@ const DEFAULT_TEST_TIMEOUT_MS = 0;
6
6
  const DEFAULT_RUNNER_TIMEOUT_MS = 30 * 60 * 1_000;
7
7
  const DEFAULT_CI_TEST_CONCURRENCY = 1;
8
8
 
9
+ function parseBooleanEnv(value: string | undefined): boolean {
10
+ if (!value) return false;
11
+ const normalized = value.trim().toLowerCase();
12
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
13
+ }
14
+
9
15
  function collectTests(path: string, out: string[]): void {
10
16
  let stats;
11
17
  try {
@@ -61,6 +67,7 @@ if (files.length === 0) {
61
67
  const testTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_TIMEOUT_MS, DEFAULT_TEST_TIMEOUT_MS);
62
68
  const runnerTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_RUNNER_TIMEOUT_MS, DEFAULT_RUNNER_TIMEOUT_MS);
63
69
  const testConcurrency = parseTestConcurrency(process.env);
70
+ const forceExit = parseBooleanEnv(process.env.OMX_NODE_TEST_FORCE_EXIT);
64
71
  const testArgs = ['--test'];
65
72
  if (testTimeoutMs > 0) {
66
73
  testArgs.push(`--test-timeout=${testTimeoutMs}`);
@@ -68,14 +75,17 @@ if (testTimeoutMs > 0) {
68
75
  if (testConcurrency) {
69
76
  testArgs.push(`--test-concurrency=${testConcurrency}`);
70
77
  }
78
+ if (forceExit) {
79
+ testArgs.push('--test-force-exit');
80
+ }
71
81
  testArgs.push(...files);
72
82
 
73
83
  console.error(
74
84
  `[run-test-files] running ${files.length} test file(s) from ${targets.join(', ')}${
75
85
  testTimeoutMs > 0 ? ` with per-test timeout ${testTimeoutMs}ms` : ' with per-test timeout disabled'
76
86
  }${testConcurrency ? `, test concurrency ${testConcurrency}` : ', default test concurrency'}${
77
- runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'
78
- }`,
87
+ forceExit ? ', force exit enabled' : ', force exit disabled'
88
+ }${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}`,
79
89
  );
80
90
 
81
91
  const childEnv = { ...process.env };
@@ -101,6 +111,7 @@ console.error(
101
111
  + `Roots: ${targets.join(', ')}. Test files: ${files.length}. `
102
112
  + `Per-test timeout: ${testTimeoutMs > 0 ? `${testTimeoutMs}ms` : 'disabled'}. `
103
113
  + `Test concurrency: ${testConcurrency ?? 'default'}. `
114
+ + `Force exit: ${forceExit ? 'enabled' : 'disabled'}. `
104
115
  + `Runner timeout: ${runnerTimeoutMs > 0 ? `${runnerTimeoutMs}ms` : 'disabled'}.`,
105
116
  );
106
117
  process.exit(1);
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=context-pack-status.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context-pack-status.test.d.ts","sourceRoot":"","sources":["../../../src/planning/__tests__/context-pack-status.test.ts"],"names":[],"mappings":""}