oh-my-codex 0.18.8 → 0.18.9

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 (88) hide show
  1. package/Cargo.lock +12 -12
  2. package/Cargo.toml +1 -1
  3. package/dist/autopilot/__tests__/fsm.test.js +3 -0
  4. package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
  5. package/dist/autopilot/fsm.js +2 -2
  6. package/dist/autopilot/fsm.js.map +1 -1
  7. package/dist/cli/__tests__/auth.test.js +4 -2
  8. package/dist/cli/__tests__/auth.test.js.map +1 -1
  9. package/dist/cli/__tests__/codex-plugin-layout.test.js +1 -1
  10. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  11. package/dist/cli/__tests__/index.test.js +38 -2
  12. package/dist/cli/__tests__/index.test.js.map +1 -1
  13. package/dist/cli/__tests__/package-bin-contract.test.js +20 -4
  14. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  15. package/dist/cli/__tests__/question.test.js +26 -9
  16. package/dist/cli/__tests__/question.test.js.map +1 -1
  17. package/dist/cli/__tests__/resume.test.js +50 -1
  18. package/dist/cli/__tests__/resume.test.js.map +1 -1
  19. package/dist/cli/__tests__/update.test.js +214 -17
  20. package/dist/cli/__tests__/update.test.js.map +1 -1
  21. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
  22. package/dist/cli/index.d.ts +11 -3
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli/index.js +35 -13
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/update.d.ts +20 -3
  27. package/dist/cli/update.d.ts.map +1 -1
  28. package/dist/cli/update.js +265 -23
  29. package/dist/cli/update.js.map +1 -1
  30. package/dist/cli/version.d.ts.map +1 -1
  31. package/dist/cli/version.js +5 -9
  32. package/dist/cli/version.js.map +1 -1
  33. package/dist/compat/__tests__/doctor-contract.test.js +12 -1
  34. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  35. package/dist/hooks/__tests__/code-review-skill-contract.test.js +7 -3
  36. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  37. package/dist/hooks/__tests__/deep-interview-contract.test.js +30 -1
  38. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  39. package/dist/hud/__tests__/reconcile.test.js +121 -0
  40. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  41. package/dist/hud/__tests__/state.test.js +28 -0
  42. package/dist/hud/__tests__/state.test.js.map +1 -1
  43. package/dist/hud/state.d.ts.map +1 -1
  44. package/dist/hud/state.js +4 -18
  45. package/dist/hud/state.js.map +1 -1
  46. package/dist/question/__tests__/renderer.test.js +566 -1
  47. package/dist/question/__tests__/renderer.test.js.map +1 -1
  48. package/dist/question/renderer.d.ts +9 -1
  49. package/dist/question/renderer.d.ts.map +1 -1
  50. package/dist/question/renderer.js +246 -70
  51. package/dist/question/renderer.js.map +1 -1
  52. package/dist/scripts/__tests__/codex-native-hook.test.js +154 -0
  53. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  54. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  55. package/dist/scripts/codex-native-hook.js +20 -9
  56. package/dist/scripts/codex-native-hook.js.map +1 -1
  57. package/dist/utils/__tests__/platform-command.test.js +16 -1
  58. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  59. package/dist/utils/__tests__/version.test.d.ts +2 -0
  60. package/dist/utils/__tests__/version.test.d.ts.map +1 -0
  61. package/dist/utils/__tests__/version.test.js +51 -0
  62. package/dist/utils/__tests__/version.test.js.map +1 -0
  63. package/dist/utils/paths.d.ts +8 -1
  64. package/dist/utils/paths.d.ts.map +1 -1
  65. package/dist/utils/paths.js +16 -4
  66. package/dist/utils/paths.js.map +1 -1
  67. package/dist/utils/platform-command.d.ts +9 -0
  68. package/dist/utils/platform-command.d.ts.map +1 -1
  69. package/dist/utils/platform-command.js +15 -0
  70. package/dist/utils/platform-command.js.map +1 -1
  71. package/dist/utils/version.d.ts +7 -0
  72. package/dist/utils/version.d.ts.map +1 -0
  73. package/dist/utils/version.js +67 -0
  74. package/dist/utils/version.js.map +1 -0
  75. package/dist/verification/__tests__/ci-rust-gates.test.js +8 -0
  76. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  77. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +16 -2
  78. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  79. package/package.json +4 -3
  80. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  81. package/plugins/oh-my-codex/skills/code-review/SKILL.md +2 -2
  82. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +51 -11
  83. package/skills/code-review/SKILL.md +2 -2
  84. package/skills/deep-interview/SKILL.md +51 -11
  85. package/src/scripts/__tests__/codex-native-hook.test.ts +175 -0
  86. package/src/scripts/codex-native-hook.ts +19 -7
  87. package/src/scripts/prepare-build.js +83 -0
  88. package/src/scripts/postinstall-bootstrap.js +0 -23
@@ -1867,6 +1867,51 @@ describe("codex native hook dispatch", () => {
1867
1867
  }
1868
1868
  });
1869
1869
 
1870
+ it("includes repo-local .omx project-memory during SessionStart when OMX_ROOT is boxed", async () => {
1871
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-boxed-memory-"));
1872
+ const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-root-"));
1873
+ const previousOmxRoot = process.env.OMX_ROOT;
1874
+ try {
1875
+ process.env.OMX_ROOT = boxedRoot;
1876
+ await writeJson(join(cwd, ".omx", "project-memory.json"), {
1877
+ techStack: "Repo-local CLI memory",
1878
+ conventions: "SessionStart should load CLI-written project memory",
1879
+ directives: [
1880
+ { directive: "Prefer repo-local .omx project memory over boxed runtime fallback.", priority: "high" },
1881
+ ],
1882
+ });
1883
+ await writeJson(join(boxedRoot, ".omx", "project-memory.json"), {
1884
+ techStack: "Boxed runtime memory should not win",
1885
+ notes: [{ category: "runtime", content: "stale boxed runtime note", timestamp: new Date().toISOString() }],
1886
+ });
1887
+
1888
+ const result = await dispatchCodexNativeHook(
1889
+ {
1890
+ hook_event_name: "SessionStart",
1891
+ cwd,
1892
+ session_id: "sess-boxed-memory-1",
1893
+ },
1894
+ { cwd, sessionOwnerPid: 43210 },
1895
+ );
1896
+
1897
+ const additionalContext = String(
1898
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
1899
+ );
1900
+ assert.match(additionalContext, /\[Project memory\]/);
1901
+ assert.match(additionalContext, /source: \.omx\/project-memory\.json/);
1902
+ assert.match(additionalContext, /Repo-local CLI memory/);
1903
+ assert.match(additionalContext, /SessionStart should load CLI-written project memory/);
1904
+ assert.match(additionalContext, /Prefer repo-local \.omx project memory over boxed runtime fallback\./);
1905
+ assert.doesNotMatch(additionalContext, /Boxed runtime memory should not win/);
1906
+ assert.doesNotMatch(additionalContext, /stale boxed runtime note/);
1907
+ } finally {
1908
+ if (previousOmxRoot === undefined) delete process.env.OMX_ROOT;
1909
+ else process.env.OMX_ROOT = previousOmxRoot;
1910
+ await rm(cwd, { recursive: true, force: true });
1911
+ await rm(boxedRoot, { recursive: true, force: true });
1912
+ }
1913
+ });
1914
+
1870
1915
  it("prefers repository project-memory.json during SessionStart while preserving legacy wiki guidance", async () => {
1871
1916
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-root-memory-legacy-wiki-"));
1872
1917
  try {
@@ -13932,6 +13977,136 @@ exit 0
13932
13977
  }
13933
13978
  });
13934
13979
 
13980
+ it("blocks implementation writes when Autopilot ralplan is visible only in skill-active phase", async () => {
13981
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-skill-ralplan-pretool-block-"));
13982
+ try {
13983
+ const stateDir = join(cwd, ".omx", "state");
13984
+ const sessionId = "sess-autopilot-skill-ralplan-pretool-block";
13985
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13986
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13987
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13988
+ active: true,
13989
+ skill: "autopilot",
13990
+ phase: "autopilot:ralplan",
13991
+ session_id: sessionId,
13992
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
13993
+ });
13994
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
13995
+ active: true,
13996
+ mode: "autopilot",
13997
+ current_phase: "planning",
13998
+ session_id: sessionId,
13999
+ state: {
14000
+ handoff_artifacts: {
14001
+ ralplan_consensus_gate: { required: true, complete: false },
14002
+ },
14003
+ },
14004
+ });
14005
+
14006
+ const result = await dispatchCodexNativeHook(
14007
+ {
14008
+ hook_event_name: "PreToolUse",
14009
+ cwd,
14010
+ session_id: sessionId,
14011
+ thread_id: "thread-autopilot-skill-ralplan-pretool-block",
14012
+ tool_name: "Edit",
14013
+ tool_input: { file_path: "src/runtime.ts" },
14014
+ },
14015
+ { cwd },
14016
+ );
14017
+
14018
+ assert.equal(result.omxEventName, "pre-tool-use");
14019
+ assert.equal(result.outputJson?.decision, "block");
14020
+ assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
14021
+ } finally {
14022
+ await rm(cwd, { recursive: true, force: true });
14023
+ }
14024
+ });
14025
+
14026
+ it("ignores stale Autopilot ralplan skill mirrors after detail state leaves planning", async () => {
14027
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-stale-ralplan-mirror-"));
14028
+ try {
14029
+ const stateDir = join(cwd, ".omx", "state");
14030
+ const sessionId = "sess-autopilot-stale-ralplan-mirror";
14031
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14032
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14033
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14034
+ active: true,
14035
+ skill: "autopilot",
14036
+ phase: "autopilot:ralplan",
14037
+ session_id: sessionId,
14038
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
14039
+ });
14040
+
14041
+ for (const phase of ["ultragoal", "code-review", "completing", "complete"]) {
14042
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14043
+ active: true,
14044
+ mode: "autopilot",
14045
+ current_phase: phase,
14046
+ session_id: sessionId,
14047
+ });
14048
+
14049
+ const result = await dispatchCodexNativeHook(
14050
+ {
14051
+ hook_event_name: "PreToolUse",
14052
+ cwd,
14053
+ session_id: sessionId,
14054
+ thread_id: "thread-autopilot-stale-ralplan-mirror",
14055
+ tool_name: "Edit",
14056
+ tool_input: { file_path: "src/runtime.ts" },
14057
+ },
14058
+ { cwd },
14059
+ );
14060
+
14061
+ assert.equal(result.omxEventName, "pre-tool-use");
14062
+ assert.equal(result.outputJson, null, `stale skill-active ralplan mirror must not block when Autopilot detail phase is ${phase}`);
14063
+ }
14064
+ } finally {
14065
+ await rm(cwd, { recursive: true, force: true });
14066
+ }
14067
+ });
14068
+
14069
+ it("allows explicit blank Autopilot detail phase to use a ralplan skill mirror", async () => {
14070
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-blank-phase-mirror-"));
14071
+ try {
14072
+ const stateDir = join(cwd, ".omx", "state");
14073
+ const sessionId = "sess-autopilot-blank-phase-mirror";
14074
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14075
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14076
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14077
+ active: true,
14078
+ skill: "autopilot",
14079
+ phase: "autopilot:ralplan",
14080
+ session_id: sessionId,
14081
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
14082
+ });
14083
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14084
+ active: true,
14085
+ mode: "autopilot",
14086
+ current_phase: "",
14087
+ session_id: sessionId,
14088
+ });
14089
+
14090
+ const result = await dispatchCodexNativeHook(
14091
+ {
14092
+ hook_event_name: "PreToolUse",
14093
+ cwd,
14094
+ session_id: sessionId,
14095
+ thread_id: "thread-autopilot-blank-phase-mirror",
14096
+ tool_name: "Edit",
14097
+ tool_input: { file_path: "src/runtime.ts" },
14098
+ },
14099
+ { cwd },
14100
+ );
14101
+
14102
+ assert.equal(result.omxEventName, "pre-tool-use");
14103
+ assert.equal(result.outputJson?.decision, "block");
14104
+ assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
14105
+ } finally {
14106
+ await rm(cwd, { recursive: true, force: true });
14107
+ }
14108
+ });
14109
+
13935
14110
  it("does not block implementation writes from Autopilot ralplan detail state without canonical skill state", async () => {
13936
14111
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-no-canonical-"));
13937
14112
  try {
@@ -83,6 +83,7 @@ import {
83
83
  onSessionStart as buildWikiSessionStartContext,
84
84
  } from "../wiki/lifecycle.js";
85
85
  import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
86
+ import { normalizeAutopilotPhase } from "../autopilot/fsm.js";
86
87
  import { readRunState } from "../runtime/run-state.js";
87
88
  import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
88
89
  import { getRunContinuationSnapshot, shouldContinueRun } from "../runtime/run-loop.js";
@@ -2592,12 +2593,12 @@ function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
2592
2593
  return true;
2593
2594
  }
2594
2595
 
2595
- function isActiveAutopilotRalplanPhase(state: Record<string, unknown> | null): boolean {
2596
- if (!state || state.active !== true) return false;
2597
- const mode = safeString(state.mode).trim();
2598
- if (mode && mode !== "autopilot") return false;
2599
- const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
2600
- return phase === "ralplan" || phase === "replan" || phase === "autopilot:replan";
2596
+ function isAutopilotRalplanLikePhase(phase: string): boolean {
2597
+ return normalizeAutopilotPhase(phase) === "ralplan";
2598
+ }
2599
+
2600
+ function canAutopilotSkillMirrorSupplyRalplanPhase(phase: string): boolean {
2601
+ return phase === "" || normalizeAutopilotPhase(phase) === "ralplan";
2601
2602
  }
2602
2603
 
2603
2604
  function hasExplicitExecutionHandoffSkill(
@@ -2733,7 +2734,9 @@ async function readActiveRalplanStateForPreToolUse(
2733
2734
  const autopilotState = sessionId
2734
2735
  ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
2735
2736
  : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
2736
- if (!isActiveAutopilotRalplanPhase(autopilotState) || !autopilotState) return null;
2737
+ if (!autopilotState || autopilotState.active !== true) return null;
2738
+ const autopilotMode = safeString(autopilotState.mode).trim();
2739
+ if (autopilotMode && autopilotMode !== "autopilot") return null;
2737
2740
  if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId)) return null;
2738
2741
  const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
2739
2742
  if (terminalAutopilotRunState) return null;
@@ -2742,6 +2745,15 @@ async function readActiveRalplanStateForPreToolUse(
2742
2745
  entry.skill === "autopilot"
2743
2746
  && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
2744
2747
  ));
2748
+ if (!hasActiveAutopilotSkill) return null;
2749
+ const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
2750
+ if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase)) return null;
2751
+ const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
2752
+ entry.skill === "autopilot"
2753
+ && isAutopilotRalplanLikePhase(safeString(entry.phase).trim().toLowerCase())
2754
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
2755
+ ));
2756
+ if (!isAutopilotRalplanLikePhase(autopilotStatePhase) && !hasRalplanScopedAutopilotSkill) return null;
2745
2757
  return hasActiveAutopilotSkill ? autopilotState : null;
2746
2758
  }
2747
2759
 
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, rmSync } from 'node:fs';
3
+ import { delimiter, join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const requiredDistFiles = [
7
+ join(process.cwd(), 'dist', 'cli', 'omx.js'),
8
+ join(process.cwd(), 'dist', 'scripts', 'postinstall.js'),
9
+ ];
10
+
11
+ if (requiredDistFiles.every((file) => existsSync(file))) {
12
+ process.exit(0);
13
+ }
14
+
15
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
16
+ const tscBin = process.platform === 'win32'
17
+ ? join(process.cwd(), 'node_modules', '.bin', 'tsc.cmd')
18
+ : join(process.cwd(), 'node_modules', '.bin', 'tsc');
19
+ const nodeModulesDir = join(process.cwd(), 'node_modules');
20
+
21
+ function runNpm(args, env = process.env) {
22
+ return spawnSync(npmBin, args, {
23
+ cwd: process.cwd(),
24
+ stdio: process.env.npm_config_json === 'true' ? ['inherit', 'ignore', 'inherit'] : 'inherit',
25
+ env,
26
+ });
27
+ }
28
+
29
+ function exitOnFailure(result, label) {
30
+ if (result.error) {
31
+ console.error(`omx prepare: failed to launch ${label}: ${result.error.message}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ if (result.status !== 0) {
36
+ process.exit(typeof result.status === 'number' ? result.status : 1);
37
+ }
38
+ }
39
+
40
+ let shouldCleanupBootstrappedDependencies = false;
41
+
42
+ if (!existsSync(tscBin)) {
43
+ const hadNodeModules = existsSync(nodeModulesDir);
44
+ const installResult = runNpm(
45
+ [
46
+ 'install',
47
+ '--global=false',
48
+ '--location=project',
49
+ '--include=dev',
50
+ '--ignore-scripts',
51
+ '--no-audit',
52
+ '--no-progress',
53
+ ],
54
+ {
55
+ ...process.env,
56
+ npm_config_global: 'false',
57
+ npm_config_location: 'project',
58
+ },
59
+ );
60
+ exitOnFailure(installResult, 'npm dependency bootstrap');
61
+ shouldCleanupBootstrappedDependencies = !hadNodeModules;
62
+ }
63
+
64
+ const pathWithLocalBins = [
65
+ join(process.cwd(), 'node_modules', '.bin'),
66
+ process.env.PATH ?? '',
67
+ ].filter(Boolean).join(delimiter);
68
+
69
+ const buildResult = spawnSync(npmBin, ['run', 'build'], {
70
+ cwd: process.cwd(),
71
+ stdio: process.env.npm_config_json === 'true' ? ['inherit', 'ignore', 'inherit'] : 'inherit',
72
+ env: { ...process.env, PATH: pathWithLocalBins },
73
+ });
74
+ exitOnFailure(buildResult, 'npm build');
75
+
76
+ if (shouldCleanupBootstrappedDependencies) {
77
+ try {
78
+ rmSync(nodeModulesDir, { recursive: true, force: true });
79
+ } catch (error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ console.warn(`[omx:prepare] Warning: could not remove bootstrapped node_modules: ${message}`);
82
+ }
83
+ }
@@ -1,23 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath, pathToFileURL } from "node:url";
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
- const distScriptPath = join(__dirname, "..", "..", "dist", "scripts", "postinstall.js");
8
-
9
- if (!existsSync(distScriptPath)) {
10
- process.exit(0);
11
- }
12
-
13
- const moduleUrl = pathToFileURL(distScriptPath).href;
14
- try {
15
- const postinstallModule = await import(moduleUrl);
16
- if (typeof postinstallModule.main === "function") {
17
- await postinstallModule.main();
18
- }
19
- } catch (error) {
20
- console.warn(
21
- `[omx] Postinstall bootstrap skipped after a non-fatal error: ${error instanceof Error ? error.message : String(error)}`,
22
- );
23
- }