oh-my-opencode 4.5.12 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.agents/skills/opencode-qa/SKILL.md +194 -0
  2. package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
  3. package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
  4. package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
  5. package/.agents/skills/opencode-qa/references/sdk.md +96 -0
  6. package/.agents/skills/opencode-qa/references/server-api.md +200 -0
  7. package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
  8. package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
  9. package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
  10. package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
  11. package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
  12. package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
  13. package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
  14. package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
  15. package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
  16. package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
  17. package/README.ja.md +13 -3
  18. package/README.ko.md +13 -3
  19. package/README.md +24 -14
  20. package/README.ru.md +13 -3
  21. package/README.zh-cn.md +13 -3
  22. package/bin/oh-my-opencode.js +4 -3
  23. package/bin/oh-my-opencode.test.ts +35 -7
  24. package/bin/platform.d.ts +1 -1
  25. package/bin/platform.js +4 -4
  26. package/bin/platform.test.ts +31 -9
  27. package/dist/cli/cleanup-command.d.ts +4 -0
  28. package/dist/cli/cleanup.d.ts +11 -0
  29. package/dist/cli/cli-program.d.ts +2 -1
  30. package/dist/cli/index.js +1837 -450
  31. package/dist/cli/install-codex/codex-cache.d.ts +1 -0
  32. package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
  33. package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
  34. package/dist/cli/install-codex/codex-config-mcp.d.ts +1 -0
  35. package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
  36. package/dist/cli/install-codex/codex-config-reasoning.d.ts +1 -0
  37. package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
  38. package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
  39. package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
  40. package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
  41. package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
  42. package/dist/cli/install-codex/git-bash.d.ts +35 -0
  43. package/dist/cli/install-codex/index.d.ts +4 -0
  44. package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
  45. package/dist/cli/install-codex/types.d.ts +20 -0
  46. package/dist/cli/run/event-state.d.ts +1 -0
  47. package/dist/cli/run/poll-for-completion.d.ts +1 -0
  48. package/dist/cli/run/prompt-start.d.ts +7 -0
  49. package/dist/cli/star-request.d.ts +9 -0
  50. package/dist/config/schema/hooks.d.ts +0 -1
  51. package/dist/create-hooks.d.ts +0 -1
  52. package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
  53. package/dist/features/builtin-skills/skills/index.d.ts +1 -0
  54. package/dist/hooks/index.d.ts +0 -1
  55. package/dist/index.js +267 -114
  56. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  57. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  58. package/dist/plugin/messages-transform.d.ts +8 -1
  59. package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
  60. package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
  61. package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
  62. package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
  63. package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
  64. package/dist/shared/prompt-async-gate/types.d.ts +2 -0
  65. package/dist/shared/prompt-async-gate.d.ts +1 -1
  66. package/package.json +22 -17
  67. package/packages/git-bash-mcp/dist/cli.js +367 -0
  68. package/packages/omo-codex/plugin/.mcp.json +11 -0
  69. package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
  70. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
  71. package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
  72. package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
  73. package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
  74. package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
  75. package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
  76. package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
  77. package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
  78. package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
  79. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
  80. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
  81. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
  82. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
  83. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
  84. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
  85. package/packages/omo-codex/plugin/components/rules/README.md +1 -1
  86. package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
  87. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
  88. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
  89. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +5 -4
  90. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
  91. package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -2
  92. package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +1 -0
  93. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
  94. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -1
  95. package/packages/omo-codex/plugin/components/ultrawork/directive.md +31 -5
  96. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
  97. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
  98. package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
  99. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +27 -205
  100. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +230 -0
  101. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
  102. package/packages/omo-codex/plugin/hooks/hooks.json +24 -2
  103. package/packages/omo-codex/plugin/package-lock.json +19 -0
  104. package/packages/omo-codex/plugin/package.json +3 -1
  105. package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
  106. package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
  107. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +87 -0
  108. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +27 -2
  109. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +20 -0
  110. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +27 -205
  111. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +230 -0
  112. package/packages/omo-codex/plugin/test/aggregate.test.mjs +23 -8
  113. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +56 -11
  114. package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
  115. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
  116. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
  117. package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
  118. package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +66 -0
  119. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +32 -2
  120. package/packages/omo-codex/scripts/install/cache.mjs +5 -3
  121. package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
  122. package/packages/omo-codex/scripts/install/config.mjs +36 -1
  123. package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
  124. package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
  125. package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
  126. package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
  127. package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
  128. package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
  129. package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
  130. package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
  131. package/packages/omo-codex/scripts/install/reasoning-config.mjs +14 -0
  132. package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
  133. package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
  134. package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
  135. package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
  136. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +62 -0
  137. package/packages/omo-codex/scripts/install-config.test.mjs +206 -0
  138. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +129 -0
  139. package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
  140. package/packages/omo-codex/scripts/install-local.mjs +91 -8
  141. package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
  142. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
  143. package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
  144. package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
  145. package/packages/shared-skills/skills/review-work/SKILL.md +27 -2
  146. package/packages/shared-skills/skills/start-work/SKILL.md +20 -0
  147. package/dist/hooks/context-window-monitor.d.ts +0 -19
@@ -1,5 +1,7 @@
1
1
  // biome-ignore-all format: smoke test pulls verbatim JSON for structural assertion.
2
- import { readFile, stat } from "node:fs/promises";
2
+ import { spawn } from "node:child_process";
3
+ import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
3
5
  import { dirname, join, resolve } from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
7
  import { describe, expect, it } from "vitest";
@@ -14,6 +16,41 @@ async function readJson(relative: string): Promise<unknown> {
14
16
  return JSON.parse(await readText(relative));
15
17
  }
16
18
 
19
+ type ShellResult = {
20
+ readonly code: number | null;
21
+ readonly stdout: string;
22
+ readonly stderr: string;
23
+ };
24
+
25
+ function bootstrapScriptFrom(text: string): string {
26
+ const heading = text.indexOf("### 1. Create goals from the brief");
27
+ expect(heading).toBeGreaterThanOrEqual(0);
28
+ const blockStart = text.indexOf("```sh\n", heading);
29
+ expect(blockStart).toBeGreaterThanOrEqual(0);
30
+ const codeStart = blockStart + "```sh\n".length;
31
+ const blockEnd = text.indexOf("\n```", codeStart);
32
+ expect(blockEnd).toBeGreaterThan(codeStart);
33
+ return text.slice(codeStart, blockEnd);
34
+ }
35
+
36
+ async function runShell(script: string, env: NodeJS.ProcessEnv): Promise<ShellResult> {
37
+ return new Promise((resolvePromise, reject) => {
38
+ const child = spawn("/bin/sh", ["-c", script], { env });
39
+ const stdout: Buffer[] = [];
40
+ const stderr: Buffer[] = [];
41
+ child.stdout.on("data", (chunk: Buffer) => stdout.push(chunk));
42
+ child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk));
43
+ child.on("error", reject);
44
+ child.on("close", (code) => {
45
+ resolvePromise({
46
+ code,
47
+ stdout: Buffer.concat(stdout).toString("utf8"),
48
+ stderr: Buffer.concat(stderr).toString("utf8"),
49
+ });
50
+ });
51
+ });
52
+ }
53
+
17
54
  describe("package.json", () => {
18
55
  it("declares ESM + npm + Node >=20", async () => {
19
56
  const pkg = await readJson("package.json") as Record<string, unknown>;
@@ -102,13 +139,13 @@ describe("skills/ulw-loop/SKILL.md", () => {
102
139
  });
103
140
 
104
141
  it("references the success criteria and record-evidence vocabulary", async () => {
105
- const text = await readText("skills/ulw-loop/SKILL.md");
142
+ const text = await readText("skills/ulw-loop/references/full-workflow.md");
106
143
  expect(text.toLowerCase()).toMatch(/success criteria|successcriteria/);
107
144
  expect(text.toLowerCase()).toContain("record-evidence");
108
145
  });
109
146
 
110
147
  it("#given omo is absent from PATH #when bootstrap instructions are read #then local cached CLI fallback is documented", async () => {
111
- const text = await readText("skills/ulw-loop/SKILL.md");
148
+ const text = await readText("skills/ulw-loop/references/full-workflow.md");
112
149
 
113
150
  expect(text).toContain("If `omo` is absent from PATH");
114
151
  expect(text).toContain("ULW_LOOP_CLI");
@@ -116,7 +153,7 @@ describe("skills/ulw-loop/SKILL.md", () => {
116
153
  });
117
154
 
118
155
  it("#given empty PATH #when bootstrap instructions are read #then handles empty PATH without losing notepad bootstrap", async () => {
119
- const text = await readText("skills/ulw-loop/SKILL.md");
156
+ const text = await readText("skills/ulw-loop/references/full-workflow.md");
120
157
 
121
158
  expect(text).toContain("If PATH is empty");
122
159
  expect(text).toContain("ULW_LOOP_NODE");
@@ -124,13 +161,57 @@ describe("skills/ulw-loop/SKILL.md", () => {
124
161
  expect(text).not.toContain("ls -1");
125
162
  });
126
163
 
164
+ it("#given PATH omo lacks ulw-loop #when bootstrap runs #then falls back to cached ulw-loop CLI", async () => {
165
+ const text = await readText("skills/ulw-loop/references/full-workflow.md");
166
+ const bootstrap = bootstrapScriptFrom(text);
167
+ const root = await mkdtemp(join(tmpdir(), "omo-ulw-loop-bootstrap-"));
168
+ try {
169
+ const badBin = join(root, "bad-bin");
170
+ const home = join(root, "home");
171
+ const codexHome = join(home, ".codex");
172
+ const cachedCli = join(codexHome, "plugins", "cache", "sisyphuslabs", "omo", "0.1.0", "components", "ulw-loop", "dist", "cli.js");
173
+ await mkdir(badBin, { recursive: true });
174
+ await mkdir(dirname(cachedCli), { recursive: true });
175
+ await writeFile(join(badBin, "omo"), "#!/bin/sh\nprintf '%s\\n' \"error: unknown command 'ulw-loop'\" >&2\nexit 1\n");
176
+ await chmod(join(badBin, "omo"), 0o755);
177
+ await writeFile(
178
+ cachedCli,
179
+ [
180
+ "#!/usr/bin/env node",
181
+ "const args = process.argv.slice(2);",
182
+ "if (args[0] === 'ulw-loop' && args[1] === 'help') process.exit(0);",
183
+ "if (args[0] === 'ulw-loop' && args[1] === 'status' && args.includes('--json')) {",
184
+ " console.log(JSON.stringify({ ok: true, source: 'cached-ulw-loop' }));",
185
+ " process.exit(0);",
186
+ "}",
187
+ "console.error('unexpected args: ' + args.join(' '));",
188
+ "process.exit(1);",
189
+ "",
190
+ ].join("\n"),
191
+ );
192
+
193
+ const result = await runShell(`${bootstrap}\nomo ulw-loop status --json`, {
194
+ ...process.env,
195
+ CODEX_HOME: codexHome,
196
+ HOME: home,
197
+ PATH: `${badBin}:${process.env["PATH"] ?? ""}`,
198
+ });
199
+
200
+ expect(result.code).toBe(0);
201
+ expect(result.stdout).toContain('"source":"cached-ulw-loop"');
202
+ expect(result.stderr).not.toContain("unknown command");
203
+ } finally {
204
+ await rm(root, { recursive: true, force: true });
205
+ }
206
+ });
207
+
127
208
  it("uses the .omo workspace path", async () => {
128
209
  const text = await readText("skills/ulw-loop/SKILL.md");
129
210
  expect(text).toContain(".omo/ulw-loop");
130
211
  });
131
212
 
132
213
  it("#given long Codex runs #when worker guidance is inspected #then avoids context-expensive agent polling", async () => {
133
- const text = await readText("skills/ulw-loop/SKILL.md");
214
+ const text = await readText("skills/ulw-loop/references/full-workflow.md");
134
215
 
135
216
  expect(text).toMatch(/list_agents/);
136
217
  expect(text).toMatch(/polling or status tool/);
@@ -139,9 +220,25 @@ describe("skills/ulw-loop/SKILL.md", () => {
139
220
  expect(text).toMatch(/wait_agent.*completion/);
140
221
  expect(text).toMatch(/targeted followups only when needed/);
141
222
  expect(text).toMatch(/close_agent.*after integrating each result/);
223
+ expect(text).toMatch(/Plan and reviewer agents may run for a long time/);
224
+ expect(text).toMatch(/short wait_agent cycles/);
225
+ expect(text).toMatch(/single long blocking wait/);
142
226
  expect(text).toContain("Every worker message MUST carry");
143
227
  expect(text).toContain("Each worker does strict TDD");
144
228
  });
229
+
230
+ it("#given Codex subagent delegation #when worker guidance is inspected #then assignment ambiguity is hardened", async () => {
231
+ const text = await readText("skills/ulw-loop/SKILL.md");
232
+
233
+ expect(text).toMatch(/TASK:/);
234
+ expect(text).toMatch(/fork_turns:\s*"none"/);
235
+ expect(text).toMatch(/wait_agent.*signal, not proof/);
236
+ expect(text).toMatch(/one targeted followup/);
237
+ expect(text).toMatch(/respawn.*smaller/);
238
+ expect(text).toMatch(/Plan and reviewer agents may run for a long time/);
239
+ expect(text).toMatch(/short wait_agent cycles/);
240
+ expect(text).toMatch(/single long blocking wait/);
241
+ });
145
242
  });
146
243
 
147
244
  describe("source LOC budget", () => {
@@ -55,6 +55,17 @@
55
55
  }
56
56
  ],
57
57
  "PreToolUse": [
58
+ {
59
+ "matcher": "^Bash$",
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook pre-tool-use",
64
+ "timeout": 5,
65
+ "statusMessage": "LazyCodex(0.1.0): Recommending Git Bash Mcp"
66
+ }
67
+ ]
68
+ },
58
69
  {
59
70
  "matcher": "^create_goal$",
60
71
  "hooks": [
@@ -75,13 +86,13 @@
75
86
  "type": "command",
76
87
  "command": "node \"${PLUGIN_ROOT}/components/comment-checker/dist/cli.js\" hook post-tool-use",
77
88
  "timeout": 30,
78
- "statusMessage": "LazyCodex(0.1.0): Checking Comments"
89
+ "statusMessage": "LazyCodex(0.1.1): Checking Comments"
79
90
  },
80
91
  {
81
92
  "type": "command",
82
93
  "command": "node \"${PLUGIN_ROOT}/components/lsp/dist/cli.js\" hook post-tool-use",
83
94
  "timeout": 60,
84
- "statusMessage": "LazyCodex(0.1.0): Checking LSP Diagnostics"
95
+ "statusMessage": "LazyCodex(0.2.0): Checking LSP Diagnostics"
85
96
  }
86
97
  ]
87
98
  },
@@ -98,6 +109,17 @@
98
109
  }
99
110
  ],
100
111
  "PostCompact": [
112
+ {
113
+ "matcher": "manual|auto",
114
+ "hooks": [
115
+ {
116
+ "type": "command",
117
+ "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook post-compact",
118
+ "timeout": 5,
119
+ "statusMessage": "LazyCodex(0.1.0): Resetting Git Bash Mcp Reminder"
120
+ }
121
+ ]
122
+ },
101
123
  {
102
124
  "matcher": "manual|auto",
103
125
  "hooks": [
@@ -9,6 +9,7 @@
9
9
  "version": "0.1.0",
10
10
  "workspaces": [
11
11
  "components/comment-checker",
12
+ "components/git-bash",
12
13
  "components/rules",
13
14
  "components/lsp",
14
15
  "components/telemetry",
@@ -61,6 +62,20 @@
61
62
  "@code-yeongyu/comment-checker": "^0.8.0"
62
63
  }
63
64
  },
65
+ "components/git-bash": {
66
+ "name": "@sisyphuslabs/codex-git-bash-hook",
67
+ "version": "0.1.0",
68
+ "bin": {
69
+ "omo-git-bash-hook": "dist/cli.js"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^25.7.0",
73
+ "typescript": "^6.0.3"
74
+ },
75
+ "engines": {
76
+ "node": ">=20.0.0"
77
+ }
78
+ },
64
79
  "components/lsp": {
65
80
  "name": "@code-yeongyu/codex-lsp",
66
81
  "version": "0.2.0",
@@ -765,6 +780,10 @@
765
780
  "dev": true,
766
781
  "license": "MIT"
767
782
  },
783
+ "node_modules/@sisyphuslabs/codex-git-bash-hook": {
784
+ "resolved": "components/git-bash",
785
+ "link": true
786
+ },
768
787
  "node_modules/@standard-schema/spec": {
769
788
  "version": "1.1.0",
770
789
  "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -7,6 +7,7 @@
7
7
  "private": true,
8
8
  "workspaces": [
9
9
  "components/comment-checker",
10
+ "components/git-bash",
10
11
  "components/rules",
11
12
  "components/lsp",
12
13
  "components/telemetry",
@@ -18,8 +19,9 @@
18
19
  "@oh-my-opencode/shared-skills": "file:../../shared-skills"
19
20
  },
20
21
  "scripts": {
21
- "build": "node scripts/build-bundled-mcp-runtimes.mjs && node scripts/sync-skills.mjs && node ../scripts/sync-telemetry-component.mjs && node scripts/build-components.mjs",
22
+ "build": "node scripts/sync-hook-status-messages.mjs && node scripts/build-bundled-mcp-runtimes.mjs && node scripts/sync-skills.mjs && node ../scripts/sync-telemetry-component.mjs && node scripts/build-components.mjs",
22
23
  "check": "npm run build && npm test",
24
+ "sync:hooks": "node scripts/sync-hook-status-messages.mjs",
23
25
  "sync:skills": "node scripts/sync-skills.mjs",
24
26
  "test": "node --test test/*.test.mjs"
25
27
  }
@@ -18,6 +18,11 @@ const runtimes = [
18
18
  packageRoot: join(repoPackagesRoot, "ast-grep-mcp"),
19
19
  requiredOutputs: ["dist/cli.js"],
20
20
  },
21
+ {
22
+ label: "git-bash-mcp",
23
+ packageRoot: join(repoPackagesRoot, "git-bash-mcp"),
24
+ requiredOutputs: ["dist/cli.js"],
25
+ },
21
26
  ];
22
27
 
23
28
  for (const runtime of runtimes) {
@@ -25,20 +30,30 @@ for (const runtime of runtimes) {
25
30
  }
26
31
 
27
32
  function buildRuntime(runtime) {
33
+ if (hasBundledDist(runtime)) {
34
+ console.log(`Using bundled ${runtime.label} dist`);
35
+ return;
36
+ }
37
+
28
38
  if (!existsSync(join(runtime.packageRoot, "package.json"))) {
29
39
  assertBundledDist(runtime);
30
40
  console.log(`Using bundled ${runtime.label} dist`);
31
41
  return;
32
42
  }
33
43
 
34
- const result = spawnSync("bun", ["run", "build"], {
44
+ const result = spawnSync("npm", ["run", "build"], {
35
45
  cwd: runtime.packageRoot,
46
+ shell: process.platform === "win32",
36
47
  stdio: "inherit",
37
48
  });
38
49
  if (result.error !== undefined) throw result.error;
39
50
  if (result.status !== 0) process.exit(result.status ?? 1);
40
51
  }
41
52
 
53
+ function hasBundledDist(runtime) {
54
+ return runtime.requiredOutputs.every((output) => existsSync(join(runtime.packageRoot, output)));
55
+ }
56
+
42
57
  function assertBundledDist(runtime) {
43
58
  const missingOutputs = runtime.requiredOutputs.filter((output) => !existsSync(join(runtime.packageRoot, output)));
44
59
  if (missingOutputs.length === 0) return;
@@ -14,8 +14,9 @@ for (const workspace of workspaces) {
14
14
  if (typeof workspacePackageJson.scripts?.build !== "string") continue;
15
15
 
16
16
  console.log(`Building ${workspace}`);
17
- const result = spawnSync("bun", ["run", "--cwd", workspace, "build"], {
17
+ const result = spawnSync("npm", ["run", "--workspace", workspace, "build"], {
18
18
  cwd: root,
19
+ shell: process.platform === "win32",
19
20
  stdio: "inherit",
20
21
  });
21
22
  if (result.error !== undefined) throw result.error;
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ import { access, readdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import { formatLazyCodexHookStatusMessage, normalizeLazyCodexHookStatusLabel } from "./hook-status-message.mjs";
8
+
9
+ const defaultRoot = dirname(dirname(fileURLToPath(import.meta.url)));
10
+
11
+ async function exists(path) {
12
+ try {
13
+ await access(path);
14
+ return true;
15
+ } catch (error) {
16
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
17
+ throw error;
18
+ }
19
+ }
20
+
21
+ async function readJson(path) {
22
+ return JSON.parse(await readFile(path, "utf8"));
23
+ }
24
+
25
+ async function writeJson(path, value) {
26
+ await writeFile(path, `${JSON.stringify(value, null, "\t")}\n`);
27
+ }
28
+
29
+ async function readPackageVersion(path) {
30
+ const packageJson = await readJson(path);
31
+ return packageJson.version;
32
+ }
33
+
34
+ async function readComponentVersions(root) {
35
+ const componentsRoot = join(root, "components");
36
+ const entries = await readdir(componentsRoot, { withFileTypes: true });
37
+ const versions = new Map();
38
+ for (const entry of entries) {
39
+ if (!entry.isDirectory()) continue;
40
+ versions.set(entry.name, await readPackageVersion(join(componentsRoot, entry.name, "package.json")));
41
+ }
42
+ return versions;
43
+ }
44
+
45
+ function componentVersionForCommand(command, componentVersions, fallbackVersion) {
46
+ for (const [componentName, version] of componentVersions.entries()) {
47
+ if (command.includes(`/components/${componentName}/dist/cli.js`)) return version;
48
+ }
49
+ return fallbackVersion;
50
+ }
51
+
52
+ function syncHooksJson(hooksJson, versionForCommand) {
53
+ for (const groups of Object.values(hooksJson.hooks)) {
54
+ for (const group of groups) {
55
+ for (const hook of group.hooks) {
56
+ if (hook.type !== "command") continue;
57
+ const label = normalizeLazyCodexHookStatusLabel(hook.statusMessage);
58
+ hook.statusMessage = formatLazyCodexHookStatusMessage(versionForCommand(hook.command), label);
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ async function syncComponentHooks(root, componentName, version) {
65
+ const hooksPath = join(root, "components", componentName, "hooks", "hooks.json");
66
+ if (!(await exists(hooksPath))) return;
67
+ const hooksJson = await readJson(hooksPath);
68
+ syncHooksJson(hooksJson, () => version);
69
+ await writeJson(hooksPath, hooksJson);
70
+ }
71
+
72
+ export async function syncHookStatusMessages(root = defaultRoot) {
73
+ const aggregateVersion = await readPackageVersion(join(root, ".codex-plugin", "plugin.json"));
74
+ const componentVersions = await readComponentVersions(root);
75
+ const aggregateHooksPath = join(root, "hooks", "hooks.json");
76
+ const aggregateHooks = await readJson(aggregateHooksPath);
77
+ syncHooksJson(aggregateHooks, (command) => componentVersionForCommand(command, componentVersions, aggregateVersion));
78
+ await writeJson(aggregateHooksPath, aggregateHooks);
79
+
80
+ for (const [componentName, version] of componentVersions.entries()) {
81
+ await syncComponentHooks(root, componentName, version);
82
+ }
83
+ }
84
+
85
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
86
+ await syncHookStatusMessages();
87
+ }
@@ -18,6 +18,26 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
18
18
 
19
19
  When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
20
20
 
21
+ ## Codex Subagent Reliability
22
+
23
+ Every `spawn_agent` message must be self-contained. Start with
24
+ `TASK: <imperative assignment>`, then name `DELIVERABLE`, `SCOPE`, and
25
+ `VERIFY`. State that it is an executable assignment, not a context
26
+ handoff. Role selection requires `agent_type`; `model` +
27
+ `reasoning_effort` alone creates a default agent, not a reviewer or
28
+ worker. Prefer `fork_turns: "none"` unless full history is truly
29
+ required; paste only the review context that worker needs.
30
+
31
+ Plan and reviewer agents may run for a long time; spawn them in the background, keep doing independent root work, and poll with short wait_agent cycles. Never use a single long blocking wait for them.
32
+
33
+ Use `wait_agent` for completion signals, but treat `wait_agent` as a
34
+ mailbox signal, not proof of completion, content, or errors. After two
35
+ waits with no substantive result, send one targeted followup:
36
+ `TASK STILL ACTIVE: return <deliverable> or BLOCKED: <reason>`. If the
37
+ child stays silent or ack-only, mark that review lane inconclusive, do
38
+ not count it as PASS or approval, close if safe, and respawn a smaller
39
+ `fork_turns: "none"` reviewer with the missing deliverable.
40
+
21
41
  # Review Work - 5-Agent Parallel Review Orchestrator
22
42
 
23
43
  Launch 5 specialized sub-agents in parallel to review completed implementation work from every angle. All 5 must pass for the review to pass. If even ONE fails, the review fails.
@@ -493,9 +513,12 @@ OUTPUT FORMAT:
493
513
 
494
514
  ## Phase 2: Wait & Collect
495
515
 
496
- After launching all 5 agents in one turn, **end your response**. Wait for system notifications as each agent completes.
516
+ After launching all 5 agents in one turn, wait for completions in bounded
517
+ cycles. Do not treat a timeout, ack-only reply, or empty child result as
518
+ a PASS.
497
519
 
498
- As each completes, collect via `background_output(task_id="bg_...")`. Store each verdict:
520
+ As each completes, collect via the Codex mapping above (`wait_agent`,
521
+ then the child's substantive final result). Store each verdict:
499
522
 
500
523
  | Agent | Verdict | Notes |
501
524
  |-------|---------|-------|
@@ -506,6 +529,8 @@ As each completes, collect via `background_output(task_id="bg_...")`. Store each
506
529
  | 5. Context Mining | pending | - |
507
530
 
508
531
  Do NOT deliver the final report until ALL 5 have completed.
532
+ If a lane remains silent after the reliability followup, record it as
533
+ inconclusive and respawn a smaller reviewer/worker for that exact lane.
509
534
 
510
535
  ---
511
536
 
@@ -20,6 +20,26 @@ This skill ports the OpenCode `/start-work` flow onto Codex. Any OpenCode-only t
20
20
 
21
21
  When translating `load_skills=[...]`, name the skills inside the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
22
22
 
23
+ ## Codex Subagent Reliability
24
+
25
+ Every `spawn_agent` message must be self-contained. Start with
26
+ `TASK: <imperative assignment>`, then name `DELIVERABLE`, `SCOPE`, and
27
+ `VERIFY`. State that it is an executable assignment, not a context
28
+ handoff. Role selection requires `agent_type`; `model` +
29
+ `reasoning_effort` alone creates a default agent, not a reviewer or
30
+ worker. Prefer `fork_turns: "none"` unless full history is truly
31
+ required; paste only the context the child needs.
32
+
33
+ Plan and reviewer agents may run for a long time; spawn them in the background, keep doing independent root work, and poll with short wait_agent cycles. Never use a single long blocking wait for them.
34
+
35
+ Use `wait_agent` for completion signals, but treat `wait_agent` as a
36
+ mailbox signal, not proof of completion, content, or errors. After two
37
+ waits with no substantive result, send one targeted followup:
38
+ `TASK STILL ACTIVE: return <deliverable> or BLOCKED: <reason>`. If the
39
+ child stays silent or ack-only, record the result as inconclusive, do
40
+ not count it as pass/review approval, close if safe, and respawn a
41
+ smaller `fork_turns: "none"` task with the missing deliverable.
42
+
23
43
  # start-work
24
44
 
25
45
  Execute a Prometheus work plan until every top-level checkbox is complete. This skill pairs with the Codex `Stop` / `SubagentStop` continuation hook in `components/start-work-continuation`, which re-injects the next turn while `.omo/boulder.json` says the current `codex:<session_id>` still has unchecked plan work.