oh-my-codex 0.16.4 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/dist/catalog/__tests__/generator.test.js +2 -0
  4. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  5. package/dist/cli/__tests__/doctor-warning-copy.test.js +80 -7
  6. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  7. package/dist/cli/__tests__/index.test.js +17 -11
  8. package/dist/cli/__tests__/index.test.js.map +1 -1
  9. package/dist/cli/__tests__/mcp-serve.test.js +4 -0
  10. package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
  11. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +3 -0
  12. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  13. package/dist/cli/__tests__/ralph.test.js +0 -124
  14. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  15. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -3
  16. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  17. package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
  18. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  19. package/dist/cli/__tests__/setup-refresh.test.js +3 -3
  20. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  21. package/dist/cli/__tests__/team.test.js +166 -42
  22. package/dist/cli/__tests__/team.test.js.map +1 -1
  23. package/dist/cli/__tests__/ultragoal.test.js +22 -0
  24. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  25. package/dist/cli/doctor.d.ts.map +1 -1
  26. package/dist/cli/doctor.js +75 -14
  27. package/dist/cli/doctor.js.map +1 -1
  28. package/dist/cli/index.d.ts +8 -2
  29. package/dist/cli/index.d.ts.map +1 -1
  30. package/dist/cli/index.js +17 -7
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/cli/mcp-serve.d.ts.map +1 -1
  33. package/dist/cli/mcp-serve.js +4 -0
  34. package/dist/cli/mcp-serve.js.map +1 -1
  35. package/dist/cli/plugin-marketplace.d.ts +25 -1
  36. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  37. package/dist/cli/plugin-marketplace.js +146 -3
  38. package/dist/cli/plugin-marketplace.js.map +1 -1
  39. package/dist/cli/question.d.ts +1 -1
  40. package/dist/cli/question.d.ts.map +1 -1
  41. package/dist/cli/question.js +98 -4
  42. package/dist/cli/question.js.map +1 -1
  43. package/dist/cli/ralph.d.ts.map +1 -1
  44. package/dist/cli/ralph.js +1 -49
  45. package/dist/cli/ralph.js.map +1 -1
  46. package/dist/cli/setup.d.ts +1 -0
  47. package/dist/cli/setup.d.ts.map +1 -1
  48. package/dist/cli/setup.js +103 -18
  49. package/dist/cli/setup.js.map +1 -1
  50. package/dist/cli/team.d.ts.map +1 -1
  51. package/dist/cli/team.js +21 -29
  52. package/dist/cli/team.js.map +1 -1
  53. package/dist/cli/ultragoal.d.ts.map +1 -1
  54. package/dist/cli/ultragoal.js +7 -1
  55. package/dist/cli/ultragoal.js.map +1 -1
  56. package/dist/config/__tests__/codex-hooks.test.js +136 -9
  57. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  58. package/dist/config/__tests__/generator-idempotent.test.js +15 -0
  59. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  60. package/dist/config/codex-hooks.d.ts +13 -14
  61. package/dist/config/codex-hooks.d.ts.map +1 -1
  62. package/dist/config/codex-hooks.js +85 -7
  63. package/dist/config/codex-hooks.js.map +1 -1
  64. package/dist/config/generator.d.ts +8 -1
  65. package/dist/config/generator.d.ts.map +1 -1
  66. package/dist/config/generator.js +73 -9
  67. package/dist/config/generator.js.map +1 -1
  68. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  69. package/dist/config/omx-first-party-mcp.js +7 -0
  70. package/dist/config/omx-first-party-mcp.js.map +1 -1
  71. package/dist/hooks/__tests__/agents-overlay.test.js +29 -0
  72. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  73. package/dist/hooks/__tests__/design-skill.test.d.ts +2 -0
  74. package/dist/hooks/__tests__/design-skill.test.d.ts.map +1 -0
  75. package/dist/hooks/__tests__/design-skill.test.js +55 -0
  76. package/dist/hooks/__tests__/design-skill.test.js.map +1 -0
  77. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +265 -0
  78. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  79. package/dist/hooks/__tests__/session.test.js +126 -1
  80. package/dist/hooks/__tests__/session.test.js.map +1 -1
  81. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +1 -1
  82. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  83. package/dist/hooks/__tests__/skill-guidance-contract.test.js +41 -0
  84. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  85. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  86. package/dist/hooks/agents-overlay.js +6 -3
  87. package/dist/hooks/agents-overlay.js.map +1 -1
  88. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  89. package/dist/hooks/keyword-detector.js +5 -1
  90. package/dist/hooks/keyword-detector.js.map +1 -1
  91. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  92. package/dist/hooks/keyword-registry.js +2 -0
  93. package/dist/hooks/keyword-registry.js.map +1 -1
  94. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  95. package/dist/hooks/prompt-guidance-contract.js +47 -2
  96. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  97. package/dist/hooks/session.d.ts +11 -3
  98. package/dist/hooks/session.d.ts.map +1 -1
  99. package/dist/hooks/session.js +68 -6
  100. package/dist/hooks/session.js.map +1 -1
  101. package/dist/hud/__tests__/reconcile.test.js +63 -0
  102. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  103. package/dist/hud/__tests__/tmux.test.d.ts +2 -0
  104. package/dist/hud/__tests__/tmux.test.d.ts.map +1 -0
  105. package/dist/hud/__tests__/tmux.test.js +92 -0
  106. package/dist/hud/__tests__/tmux.test.js.map +1 -0
  107. package/dist/hud/reconcile.d.ts +2 -0
  108. package/dist/hud/reconcile.d.ts.map +1 -1
  109. package/dist/hud/reconcile.js +14 -1
  110. package/dist/hud/reconcile.js.map +1 -1
  111. package/dist/hud/tmux.d.ts +12 -0
  112. package/dist/hud/tmux.d.ts.map +1 -1
  113. package/dist/hud/tmux.js +88 -0
  114. package/dist/hud/tmux.js.map +1 -1
  115. package/dist/mcp/__tests__/bootstrap.test.js +3 -0
  116. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  117. package/dist/mcp/__tests__/hermes-bridge.test.d.ts +2 -0
  118. package/dist/mcp/__tests__/hermes-bridge.test.d.ts.map +1 -0
  119. package/dist/mcp/__tests__/hermes-bridge.test.js +441 -0
  120. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -0
  121. package/dist/mcp/__tests__/state-paths.test.js +96 -13
  122. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  123. package/dist/mcp/bootstrap.d.ts +1 -1
  124. package/dist/mcp/bootstrap.d.ts.map +1 -1
  125. package/dist/mcp/bootstrap.js +2 -0
  126. package/dist/mcp/bootstrap.js.map +1 -1
  127. package/dist/mcp/hermes-bridge.d.ts +111 -0
  128. package/dist/mcp/hermes-bridge.d.ts.map +1 -0
  129. package/dist/mcp/hermes-bridge.js +474 -0
  130. package/dist/mcp/hermes-bridge.js.map +1 -0
  131. package/dist/mcp/hermes-server.d.ts +374 -0
  132. package/dist/mcp/hermes-server.d.ts.map +1 -0
  133. package/dist/mcp/hermes-server.js +158 -0
  134. package/dist/mcp/hermes-server.js.map +1 -0
  135. package/dist/mcp/state-paths.d.ts.map +1 -1
  136. package/dist/mcp/state-paths.js +41 -9
  137. package/dist/mcp/state-paths.js.map +1 -1
  138. package/dist/modes/__tests__/base-tmux-pane.test.js +31 -1
  139. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  140. package/dist/pipeline/__tests__/stages.test.js +18 -9
  141. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  142. package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
  143. package/dist/pipeline/stages/team-exec.js +2 -7
  144. package/dist/pipeline/stages/team-exec.js.map +1 -1
  145. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +111 -269
  146. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  147. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +31 -72
  148. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -1
  149. package/dist/planning/__tests__/artifacts.test.js +27 -372
  150. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  151. package/dist/planning/artifacts.d.ts +1 -14
  152. package/dist/planning/artifacts.d.ts.map +1 -1
  153. package/dist/planning/artifacts.js +11 -31
  154. package/dist/planning/artifacts.js.map +1 -1
  155. package/dist/question/__tests__/state.test.js +287 -1
  156. package/dist/question/__tests__/state.test.js.map +1 -1
  157. package/dist/question/__tests__/ui.test.js +8 -8
  158. package/dist/question/__tests__/ui.test.js.map +1 -1
  159. package/dist/question/events.d.ts +53 -0
  160. package/dist/question/events.d.ts.map +1 -0
  161. package/dist/question/events.js +201 -0
  162. package/dist/question/events.js.map +1 -0
  163. package/dist/question/state.d.ts +25 -1
  164. package/dist/question/state.d.ts.map +1 -1
  165. package/dist/question/state.js +259 -3
  166. package/dist/question/state.js.map +1 -1
  167. package/dist/question/types.d.ts +1 -0
  168. package/dist/question/types.d.ts.map +1 -1
  169. package/dist/question/types.js.map +1 -1
  170. package/dist/question/ui.d.ts.map +1 -1
  171. package/dist/question/ui.js +1 -18
  172. package/dist/question/ui.js.map +1 -1
  173. package/dist/ralph/__tests__/completion-audit.test.js +39 -0
  174. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -1
  175. package/dist/scripts/__tests__/codex-native-hook.test.js +298 -3
  176. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  177. package/dist/scripts/__tests__/run-test-files.test.js +22 -0
  178. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  179. package/dist/scripts/codex-native-hook.d.ts +1 -0
  180. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  181. package/dist/scripts/codex-native-hook.js +137 -18
  182. package/dist/scripts/codex-native-hook.js.map +1 -1
  183. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  184. package/dist/scripts/codex-native-pre-post.js +12 -6
  185. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  186. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  187. package/dist/scripts/notify-hook/tmux-injection.js +91 -2
  188. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  189. package/dist/scripts/run-test-files.js +12 -1
  190. package/dist/scripts/run-test-files.js.map +1 -1
  191. package/dist/state/mode-state-context.d.ts +2 -0
  192. package/dist/state/mode-state-context.d.ts.map +1 -1
  193. package/dist/state/mode-state-context.js +21 -0
  194. package/dist/state/mode-state-context.js.map +1 -1
  195. package/dist/team/__tests__/approved-execution.test.js +25 -24
  196. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  197. package/dist/team/__tests__/runtime.test.js +173 -26
  198. package/dist/team/__tests__/runtime.test.js.map +1 -1
  199. package/dist/team/__tests__/scaling.test.js +66 -17
  200. package/dist/team/__tests__/scaling.test.js.map +1 -1
  201. package/dist/team/__tests__/tmux-session.test.js +42 -0
  202. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  203. package/dist/team/__tests__/worker-bootstrap.test.js +205 -0
  204. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  205. package/dist/team/approved-execution.d.ts +13 -0
  206. package/dist/team/approved-execution.d.ts.map +1 -1
  207. package/dist/team/approved-execution.js +65 -30
  208. package/dist/team/approved-execution.js.map +1 -1
  209. package/dist/team/runtime.d.ts.map +1 -1
  210. package/dist/team/runtime.js +28 -24
  211. package/dist/team/runtime.js.map +1 -1
  212. package/dist/team/scaling.d.ts.map +1 -1
  213. package/dist/team/scaling.js +7 -8
  214. package/dist/team/scaling.js.map +1 -1
  215. package/dist/team/tmux-session.d.ts.map +1 -1
  216. package/dist/team/tmux-session.js +48 -2
  217. package/dist/team/tmux-session.js.map +1 -1
  218. package/dist/team/ultragoal-context.d.ts +35 -0
  219. package/dist/team/ultragoal-context.d.ts.map +1 -0
  220. package/dist/team/ultragoal-context.js +191 -0
  221. package/dist/team/ultragoal-context.js.map +1 -0
  222. package/dist/ultragoal/__tests__/artifacts.test.js +121 -0
  223. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  224. package/dist/ultragoal/__tests__/docs-contract.test.js +19 -0
  225. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  226. package/dist/ultragoal/artifacts.d.ts +9 -1
  227. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  228. package/dist/ultragoal/artifacts.js +105 -3
  229. package/dist/ultragoal/artifacts.js.map +1 -1
  230. package/dist/utils/__tests__/paths.test.js +31 -1
  231. package/dist/utils/__tests__/paths.test.js.map +1 -1
  232. package/dist/utils/paths.d.ts +6 -0
  233. package/dist/utils/paths.d.ts.map +1 -1
  234. package/dist/utils/paths.js +18 -0
  235. package/dist/utils/paths.js.map +1 -1
  236. package/dist/wiki/lifecycle.js +3 -3
  237. package/dist/wiki/lifecycle.js.map +1 -1
  238. package/package.json +1 -1
  239. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  240. package/plugins/oh-my-codex/.mcp.json +8 -0
  241. package/plugins/oh-my-codex/skills/design/SKILL.md +180 -0
  242. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -3
  243. package/plugins/oh-my-codex/skills/ralph/SKILL.md +2 -2
  244. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  245. package/plugins/oh-my-codex/skills/skill/SKILL.md +2 -1
  246. package/plugins/oh-my-codex/skills/team/SKILL.md +6 -0
  247. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +11 -0
  248. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +161 -47
  249. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +2 -2
  250. package/skills/design/SKILL.md +180 -0
  251. package/skills/frontend-ui-ux/SKILL.md +6 -2
  252. package/skills/plan/SKILL.md +3 -3
  253. package/skills/ralph/SKILL.md +2 -2
  254. package/skills/ralplan/SKILL.md +1 -1
  255. package/skills/skill/SKILL.md +2 -1
  256. package/skills/team/SKILL.md +6 -0
  257. package/skills/ultragoal/SKILL.md +11 -0
  258. package/skills/ultraqa/SKILL.md +161 -47
  259. package/skills/visual-ralph/SKILL.md +2 -2
  260. package/src/scripts/__tests__/codex-native-hook.test.ts +339 -2
  261. package/src/scripts/__tests__/run-test-files.test.ts +32 -0
  262. package/src/scripts/codex-native-hook.ts +166 -20
  263. package/src/scripts/codex-native-pre-post.ts +12 -6
  264. package/src/scripts/notify-hook/tmux-injection.ts +110 -3
  265. package/src/scripts/run-test-files.ts +13 -2
  266. package/templates/catalog-manifest.json +9 -2
  267. package/dist/planning/__tests__/context-pack-status.test.d.ts +0 -2
  268. package/dist/planning/__tests__/context-pack-status.test.d.ts.map +0 -1
  269. package/dist/planning/__tests__/context-pack-status.test.js +0 -795
  270. package/dist/planning/__tests__/context-pack-status.test.js.map +0 -1
  271. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +0 -2
  272. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +0 -1
  273. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +0 -612
  274. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +0 -1
  275. package/dist/planning/context-pack-status.d.ts +0 -73
  276. package/dist/planning/context-pack-status.d.ts.map +0 -1
  277. package/dist/planning/context-pack-status.js +0 -745
  278. package/dist/planning/context-pack-status.js.map +0 -1
@@ -33,7 +33,7 @@ import {
33
33
  writeTeamLeaderAttention,
34
34
  writeTeamPhase,
35
35
  } from "../team/state.js";
36
- import { omxNotepadPath, omxProjectMemoryPath } from "../utils/paths.js";
36
+ import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
37
37
  import { findGitLayout } from "../utils/git-layout.js";
38
38
  import { getBaseStateDir, getStateFilePath, getStatePath } from "../mcp/state-paths.js";
39
39
  import {
@@ -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,
@@ -157,6 +160,12 @@ function safeObject(value: unknown): Record<string, unknown> {
157
160
  return value && typeof value === "object" ? value as Record<string, unknown> : {};
158
161
  }
159
162
 
163
+ function safeContextSnippet(value: unknown, maxLength = 300): string {
164
+ const text = safeString(value).replace(/\s+/g, " ").trim();
165
+ if (text.length <= maxLength) return text;
166
+ return `${text.slice(0, maxLength - 1).trimEnd()}…`;
167
+ }
168
+
160
169
  interface NativeSubagentSessionStartMetadata {
161
170
  parentThreadId: string;
162
171
  agentNickname?: string;
@@ -1122,28 +1131,31 @@ async function buildSessionStartContext(
1122
1131
  sections.push(["[Active OMX modes]", ...modeSummaries].join("\n"));
1123
1132
  }
1124
1133
 
1125
- const projectMemory = await readJsonIfExists(omxProjectMemoryPath(cwd));
1126
- if (projectMemory) {
1134
+ const projectMemoryPath = resolveProjectMemoryPath(cwd);
1135
+ const projectMemory = projectMemoryPath ? await readJsonIfExists(projectMemoryPath) : null;
1136
+ if (projectMemory && projectMemoryPath) {
1127
1137
  const directives = Array.isArray(projectMemory.directives) ? projectMemory.directives : [];
1128
1138
  const notes = Array.isArray(projectMemory.notes) ? projectMemory.notes : [];
1129
- const techStack = safeString(projectMemory.techStack).trim();
1130
- const conventions = safeString(projectMemory.conventions).trim();
1131
- const build = safeString(projectMemory.build).trim();
1139
+ const techStack = safeContextSnippet(projectMemory.techStack);
1140
+ const conventions = safeContextSnippet(projectMemory.conventions);
1141
+ const build = safeContextSnippet(projectMemory.build);
1132
1142
  const summary: string[] = [];
1143
+ const relativeMemoryPath = relative(cwd, projectMemoryPath).replace(/\\/g, "/");
1144
+ summary.push(`- source: ${relativeMemoryPath === "project-memory.json" ? "project-memory.json" : ".omx/project-memory.json"}`);
1133
1145
  if (techStack) summary.push(`- stack: ${techStack}`);
1134
1146
  if (conventions) summary.push(`- conventions: ${conventions}`);
1135
1147
  if (build) summary.push(`- build: ${build}`);
1136
1148
  if (directives.length > 0) {
1137
1149
  const firstDirective = directives[0] as Record<string, unknown>;
1138
- const directive = safeString(firstDirective.directive).trim();
1150
+ const directive = safeContextSnippet(firstDirective.directive);
1139
1151
  if (directive) summary.push(`- directive: ${directive}`);
1140
1152
  }
1141
1153
  if (notes.length > 0) {
1142
1154
  const firstNote = notes[0] as Record<string, unknown>;
1143
- const note = safeString(firstNote.content).trim();
1155
+ const note = safeContextSnippet(firstNote.content);
1144
1156
  if (note) summary.push(`- note: ${note}`);
1145
1157
  }
1146
- if (summary.length > 0) {
1158
+ if (summary.length > 1) {
1147
1159
  sections.push(["[Project memory]", ...summary].join("\n"));
1148
1160
  }
1149
1161
  }
@@ -1695,20 +1707,33 @@ async function buildModeBasedStopOutput(
1695
1707
  };
1696
1708
  }
1697
1709
 
1698
- function looksLikeGoalCompletionPrompt(text: string): boolean {
1699
- return /\b(?:complete|checkpoint|finish|close|mark)\b.{0,80}\b(?:goal|ultragoal|performance-goal|autoresearch-goal)\b/i.test(text)
1700
- || /\bupdate_goal\s*\(/i.test(text)
1701
- || /\bomx\s+(?:ultragoal|performance-goal|autoresearch-goal)\s+(?:checkpoint|complete)\b/i.test(text);
1710
+ export function looksLikeGoalCompletionPrompt(text: string): boolean {
1711
+ return /\bupdate_goal\s*\(/i.test(text)
1712
+ || /\bomx\s+(?:ultragoal|performance-goal|autoresearch-goal)\s+(?:checkpoint|complete)\b/i.test(text)
1713
+ || /\b(?:complete|checkpoint|finish|close|mark)\b.{0,80}\b(?:goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b/i.test(text)
1714
+ || /\b(?:ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b.{0,80}\b(?:complete|checkpoint|finish|close|mark)\b/i.test(text)
1715
+ || /(?:^|[.!?]\s+)(?:the\s+)?goal\s+(?:is\s+|now\s+|has\s+been\s+)?(?:complete|completed|finished|closed)(?:\s*(?:[.!?]|$)|\s*[:;]\s*\S|\s*[—–-]\s*\S)/i.test(text);
1702
1716
  }
1703
1717
 
1704
- async function findActiveGoalWorkflowReconciliationRequirement(cwd: string): Promise<{ workflow: string; command: string } | null> {
1718
+ async function findActiveGoalWorkflowReconciliationRequirement(cwd: string): Promise<{ workflow: string; command: string; remediation?: string } | null> {
1705
1719
  const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
1720
+ const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
1721
+ const aggregateProductComplete = safeString(aggregateCompletion.status) === "complete";
1706
1722
  const ultragoals = Array.isArray(ultragoal?.goals) ? ultragoal.goals.map(safeObject) : [];
1707
- const activeUltragoal = ultragoals.find((goal) => safeString(goal.status) === "in_progress" || safeString(goal.id) === safeString(ultragoal?.activeGoalId));
1723
+ const activeUltragoal = aggregateProductComplete
1724
+ ? undefined
1725
+ : ultragoals.find((goal) => safeString(goal.status) === "in_progress" || safeString(goal.id) === safeString(ultragoal?.activeGoalId));
1708
1726
  if (activeUltragoal) {
1727
+ const goalId = safeString(activeUltragoal.id) || "<goal-id>";
1709
1728
  return {
1710
1729
  workflow: "ultragoal",
1711
- command: `omx ultragoal checkpoint --goal-id ${safeString(activeUltragoal.id) || "<goal-id>"} --status complete --codex-goal-json '<get_goal JSON or path>' --evidence '<evidence>'`,
1730
+ command: `omx ultragoal checkpoint --goal-id ${goalId} --status complete --codex-goal-json '<get_goal JSON or path>' --evidence '<evidence>'`,
1731
+ remediation: [
1732
+ `If get_goal returns a completed task-scoped objective for the same aggregate ultragoal plan, checkpoint ${goalId} with evidence naming ${goalId} plus .omx/ultragoal/goals.json or ledger.jsonl and pass final quality-gate JSON; OMX will reconcile the completed planned scope without mutating Codex goal state.`,
1733
+ `If get_goal instead returns a different completed legacy objective and complete checkpointing fails, do not repeat --status complete in this thread.`,
1734
+ `Record the non-terminal blocker with: omx ultragoal checkpoint --goal-id ${goalId} --status blocked --codex-goal-json '<different completed get_goal JSON or path>' --evidence '<completed legacy Codex goal blocks create_goal in this thread>'.`,
1735
+ "Then continue this ultragoal from a fresh Codex thread in the same repo/worktree and create the intended goal there.",
1736
+ ].join(" "),
1712
1737
  };
1713
1738
  }
1714
1739
 
@@ -1752,7 +1777,8 @@ async function buildGoalWorkflowReconciliationPromptWarning(cwd: string, prompt:
1752
1777
  `OMX ${requirement.workflow} goal workflow requires Codex goal snapshot reconciliation before completion.`,
1753
1778
  "Call get_goal, pass the resulting JSON or a path with --codex-goal-json, and do not rely on hooks or shell commands to mutate Codex-owned goal state.",
1754
1779
  `Required command shape: ${requirement.command}.`,
1755
- ].join(" ");
1780
+ requirement.remediation,
1781
+ ].filter(Boolean).join(" ");
1756
1782
  }
1757
1783
 
1758
1784
  async function buildGoalWorkflowReconciliationStopOutput(
@@ -1764,7 +1790,11 @@ async function buildGoalWorkflowReconciliationStopOutput(
1764
1790
  const requirement = await findActiveGoalWorkflowReconciliationRequirement(cwd);
1765
1791
  if (!requirement) return null;
1766
1792
  const systemMessage =
1767
- `OMX ${requirement.workflow} requires get_goal snapshot reconciliation before completion; call get_goal and pass --codex-goal-json to ${requirement.command}. Hooks must not mutate Codex goal state.`;
1793
+ [
1794
+ `OMX ${requirement.workflow} requires get_goal snapshot reconciliation before completion; call get_goal and pass --codex-goal-json to ${requirement.command}.`,
1795
+ requirement.remediation,
1796
+ "Hooks must not mutate Codex goal state.",
1797
+ ].filter(Boolean).join(" ");
1768
1798
  return {
1769
1799
  decision: "block",
1770
1800
  reason: systemMessage,
@@ -2323,6 +2353,109 @@ function readPreviousNativeStopSignature(
2323
2353
  return safeString(sessionState.last_signature).trim();
2324
2354
  }
2325
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
+
2326
2459
  async function persistNativeStopSignature(
2327
2460
  stateDir: string,
2328
2461
  payload: CodexHookPayload,
@@ -2651,8 +2784,14 @@ async function buildStopHookOutput(
2651
2784
  if (ralphCompletionAuditBlock) {
2652
2785
  await reopenRalphCompletionAuditBlock(ralphCompletionAuditBlock);
2653
2786
  const blockingPath = formatStopStatePath(cwd, ralphCompletionAuditBlock.path);
2654
- const systemMessage =
2655
- `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(" ");
2656
2795
  return await returnPersistentStopBlock(
2657
2796
  payload,
2658
2797
  stateDir,
@@ -2843,6 +2982,13 @@ async function buildStopHookOutput(
2843
2982
  { allowRepeatDuringStopHook: true },
2844
2983
  );
2845
2984
  }
2985
+ const ordinaryNoProgressOutput = await maybeBuildOrdinaryStopNoProgressOutput(
2986
+ payload,
2987
+ stateDir,
2988
+ canonicalSessionId,
2989
+ );
2990
+ if (ordinaryNoProgressOutput) return ordinaryNoProgressOutput;
2991
+
2846
2992
  const autoNudgeConfig = await loadAutoNudgeConfig();
2847
2993
  const autoNudgePhase = await readStopAutoNudgePhase(cwd, stateDir, canonicalSessionId, threadId);
2848
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
 
@@ -99,7 +99,7 @@ async function resolveCanonicalPaneFromPaneTarget(paneTarget: any, expectedCwd:
99
99
  return finalizeResolvedPane(healedPaneId, 'healed_hud_pane_target', expectedCwd);
100
100
  }
101
101
 
102
- async function resolvePreferredModePane(stateDir: string, allowedModes: string[]): Promise<{ mode: string; state: any; pane: string } | null> {
102
+ async function resolvePreferredModePane(stateDir: string, allowedModes: string[]): Promise<{ mode: string; state: any; pane: string; stateDir: string } | null> {
103
103
  const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir).catch(() => [stateDir]);
104
104
  const dirs = [...scopedDirs];
105
105
  if (!dirs.map((dir) => resolvePath(dir)).includes(resolvePath(stateDir))) {
@@ -111,13 +111,84 @@ async function resolvePreferredModePane(stateDir: string, allowedModes: string[]
111
111
  const parsed = await readJsonIfExists(path, null);
112
112
  const pane = safeString(parsed?.tmux_pane_id || '').trim();
113
113
  if (parsed?.active && pane) {
114
- return { mode, state: parsed, pane };
114
+ return { mode, state: parsed, pane, stateDir: dir };
115
115
  }
116
116
  }
117
117
  }
118
118
  return null;
119
119
  }
120
120
 
121
+ function modeStateMatchesInvocationOwner(modeState: any, payload: any, managedContext: any): { ok: true } | { ok: false; reason: string } {
122
+ const invocationSessionId = resolveInvocationSessionId(payload);
123
+ const canonicalSessionId = safeString(managedContext?.canonicalSessionId || managedContext?.sessionState?.session_id).trim();
124
+ const nativeSessionId = safeString(managedContext?.nativeSessionId || managedContext?.sessionState?.native_session_id || managedContext?.sessionState?.codex_session_id).trim();
125
+ const allowedSessionIds = new Set([
126
+ invocationSessionId,
127
+ canonicalSessionId,
128
+ nativeSessionId,
129
+ ].filter(Boolean));
130
+
131
+ const ownerOmxSessionId = safeString(modeState?.owner_omx_session_id).trim();
132
+ if (ownerOmxSessionId && !allowedSessionIds.has(ownerOmxSessionId)) {
133
+ return { ok: false, reason: 'mode_owner_session_mismatch' };
134
+ }
135
+
136
+ const stateSessionId = safeString(modeState?.session_id).trim();
137
+ if (!ownerOmxSessionId && stateSessionId && !allowedSessionIds.has(stateSessionId)) {
138
+ return { ok: false, reason: 'mode_session_mismatch' };
139
+ }
140
+
141
+ const ownerCodexSessionId = safeString(modeState?.owner_codex_session_id || modeState?.codex_session_id).trim();
142
+ if (ownerCodexSessionId && !allowedSessionIds.has(ownerCodexSessionId)) {
143
+ return { ok: false, reason: 'mode_codex_session_mismatch' };
144
+ }
145
+
146
+ return { ok: true };
147
+ }
148
+
149
+ async function validateResolvedInjectionOwnership({
150
+ paneTarget,
151
+ cwd,
152
+ payload,
153
+ modeState,
154
+ modePane,
155
+ managedCurrentPane,
156
+ }: any): Promise<{ ok: true } | { ok: false; reason: string; managedContext?: any }> {
157
+ const ownership = await verifyManagedPaneTarget(paneTarget, cwd, payload, { allowTeamWorker: false });
158
+ if (!ownership.ok) {
159
+ return { ok: false, reason: ownership.reason || 'pane_not_managed_session', managedContext: ownership.managedContext };
160
+ }
161
+
162
+ const modeOwner = modeStateMatchesInvocationOwner(modeState, payload, ownership.managedContext);
163
+ if (!modeOwner.ok) return { ...modeOwner, managedContext: ownership.managedContext };
164
+
165
+ const statePane = safeString(modePane || modeState?.tmux_pane_id).trim();
166
+ const currentPane = safeString(managedCurrentPane).trim();
167
+ if (statePane && currentPane && statePane !== currentPane) {
168
+ return { ok: false, reason: 'mode_pane_current_pane_mismatch', managedContext: ownership.managedContext };
169
+ }
170
+
171
+ const expectedWindowId = safeString(modeState?.tmux_window_id || modeState?.tmuxWindowId).trim();
172
+ if (!expectedWindowId) {
173
+ return { ok: true };
174
+ }
175
+
176
+ try {
177
+ const windowResult = await runProcess('tmux', ['display-message', '-p', '-t', paneTarget, '#{window_id}'], 2000);
178
+ const paneWindowId = safeString(windowResult.stdout).trim();
179
+ if (!paneWindowId) {
180
+ return { ok: false, reason: 'pane_window_unverified', managedContext: ownership.managedContext };
181
+ }
182
+ if (paneWindowId !== expectedWindowId) {
183
+ return { ok: false, reason: 'pane_window_mismatch', managedContext: ownership.managedContext };
184
+ }
185
+ } catch {
186
+ return { ok: false, reason: 'pane_window_unverified', managedContext: ownership.managedContext };
187
+ }
188
+
189
+ return { ok: true };
190
+ }
191
+
121
192
  async function readVisibleAllowedModes(
122
193
  cwd: string,
123
194
  stateDir: string,
@@ -460,7 +531,22 @@ export async function handleTmuxInjection({
460
531
  turnId,
461
532
  timestamp: nowIso,
462
533
  }), sourceText);
463
- const preferredPaneTarget = modePane || await resolveManagedCurrentPane(cwd, payload, { allowTeamWorker: false });
534
+ const managedCurrentPane = await resolveManagedCurrentPane(cwd, payload, { allowTeamWorker: false });
535
+ if (modePane && managedCurrentPane && modePane !== managedCurrentPane) {
536
+ state.last_reason = 'mode_pane_current_pane_mismatch';
537
+ state.last_event_at = nowIso;
538
+ await writeFile(hookStatePath, JSON.stringify(state, null, 2)).catch(() => {});
539
+ await logTmuxHookEvent(logsDir, {
540
+ ...baseLog,
541
+ event: 'injection_skipped',
542
+ reason: 'mode_pane_current_pane_mismatch',
543
+ mode_pane: modePane,
544
+ current_pane: managedCurrentPane,
545
+ });
546
+ return;
547
+ }
548
+
549
+ const preferredPaneTarget = modePane || managedCurrentPane;
464
550
  let resolution = preferredModePane
465
551
  ? await resolvePaneTarget({ type: 'pane', value: preferredModePane.pane }, cwd, preferredModePane.pane, cwd, payload)
466
552
  : preferredPaneTarget
@@ -484,6 +570,27 @@ export async function handleTmuxInjection({
484
570
  }
485
571
  const paneTarget = resolution.paneTarget;
486
572
 
573
+ const ownership = await validateResolvedInjectionOwnership({
574
+ paneTarget,
575
+ cwd,
576
+ payload,
577
+ modeState,
578
+ modePane,
579
+ managedCurrentPane,
580
+ });
581
+ if (!ownership.ok) {
582
+ state.last_reason = ownership.reason;
583
+ state.last_event_at = nowIso;
584
+ await writeFile(hookStatePath, JSON.stringify(state, null, 2)).catch(() => {});
585
+ await logTmuxHookEvent(logsDir, {
586
+ ...baseLog,
587
+ event: 'injection_skipped',
588
+ reason: ownership.reason,
589
+ pane_target: paneTarget,
590
+ });
591
+ return;
592
+ }
593
+
487
594
  // Final guard phase: pane is canonical identity for quota/cooldown.
488
595
  const guard = evaluateInjectionGuards({
489
596
  config,
@@ -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);
@@ -181,13 +181,20 @@
181
181
  "internalRequired": false
182
182
  },
183
183
  {
184
- "name": "frontend-ui-ux",
184
+ "name": "design",
185
185
  "category": "shortcut",
186
- "status": "alias",
186
+ "status": "active",
187
187
  "canonical": "designer",
188
188
  "core": false,
189
189
  "internalRequired": false
190
190
  },
191
+ {
192
+ "name": "frontend-ui-ux",
193
+ "category": "shortcut",
194
+ "status": "deprecated",
195
+ "core": false,
196
+ "internalRequired": false
197
+ },
191
198
  {
192
199
  "name": "git-master",
193
200
  "category": "shortcut",
@@ -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":""}