oh-my-opencode 4.12.0 → 4.12.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 (54) hide show
  1. package/dist/cli/index.js +36 -17
  2. package/dist/cli-node/index.js +36 -17
  3. package/dist/index.js +361 -254
  4. package/dist/skills/ultraresearch/SKILL.md +11 -2
  5. package/dist/tools/background-task/constants.d.ts +1 -1
  6. package/dist/tui.js +9 -3
  7. package/package.json +13 -13
  8. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +3 -1
  9. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  10. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +327 -171
  11. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +275 -31
  12. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  13. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +11 -0
  14. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +12 -0
  15. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +34 -0
  16. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +40 -3
  17. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +6 -5
  18. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +49 -87
  19. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +1 -1
  20. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +48 -0
  21. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +1 -0
  22. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +142 -0
  23. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  27. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/teammode/dist/cli.js +94 -0
  30. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +17 -0
  31. package/packages/omo-codex/plugin/components/teammode/package.json +26 -0
  32. package/packages/omo-codex/plugin/components/teammode/src/cli.ts +12 -0
  33. package/packages/omo-codex/plugin/components/teammode/src/codex-hook.ts +125 -0
  34. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +116 -0
  35. package/packages/omo-codex/plugin/components/teammode/tsconfig.json +25 -0
  36. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  39. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +17 -0
  40. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +17 -0
  41. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  42. package/packages/omo-codex/plugin/package-lock.json +30 -12
  43. package/packages/omo-codex/plugin/package.json +2 -1
  44. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +1 -0
  45. package/packages/omo-codex/plugin/skills/ultraresearch/SKILL.md +11 -2
  46. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +36 -0
  47. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -1
  48. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
  49. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -0
  50. package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +24 -0
  51. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +1 -0
  52. package/packages/omo-codex/plugin/test/ultraresearch-skill-contract.test.mjs +48 -0
  53. package/packages/omo-codex/scripts/install-dist/install-local.mjs +14 -1
  54. package/packages/shared-skills/skills/ultraresearch/SKILL.md +11 -2
@@ -103,13 +103,10 @@ async function resolveOrProvisionCommand(
103
103
  nodeSupport: CodegraphNodeSupport,
104
104
  ): Promise<ResolutionResult> {
105
105
  const resolved = deps.resolveCommand({ env, homeDir, provisioned: () => provisionedBinFromInstallDir(config.install_dir) });
106
- if (resolved.exists) {
107
- if (codegraphCommandRequiresSupportedLocalNode(resolved) && !nodeSupport.supported) {
108
- return { kind: "unsupported-node" };
109
- }
106
+ if (resolved.exists && canUseResolvedCommand(resolved, nodeSupport)) {
110
107
  return { kind: "resolved", resolution: resolved };
111
108
  }
112
- if (!nodeSupport.supported) return { kind: "unsupported-node" };
109
+ if (resolved.exists && config.auto_provision === false) return { kind: "unsupported-node" };
113
110
  if (config.auto_provision === false) return { error: "codegraph binary unavailable and auto_provision is disabled", kind: "unavailable", source: resolved.source };
114
111
 
115
112
  const installDir = config.install_dir ?? join(homeDir, ".omo", "codegraph");
@@ -120,6 +117,10 @@ async function resolveOrProvisionCommand(
120
117
  return { kind: "resolved", resolution: { argsPrefix: [], command: provisioned.binPath, exists: true, source: "provisioned" } };
121
118
  }
122
119
 
120
+ function canUseResolvedCommand(resolved: CodegraphCommandResolution, nodeSupport: CodegraphNodeSupport): boolean {
121
+ return !codegraphCommandRequiresSupportedLocalNode(resolved) || nodeSupport.supported;
122
+ }
123
+
123
124
  function decideStartupAction(status: CodegraphCommandResult): { readonly kind: "init" } | { readonly kind: "skip"; readonly reason: string } | { readonly kind: "sync" } {
124
125
  if (status.timedOut) return { kind: "skip", reason: "status timed out" };
125
126
  const text = `${status.stdout}\n${status.stderr ?? ""}`.toLowerCase();
@@ -9,6 +9,7 @@ import { runCodegraphCli } from "../src/cli.ts";
9
9
  import {
10
10
  executeCodegraphSessionStartHook,
11
11
  resolveCodegraphCommandInvocation,
12
+ runCodegraphPostToolUseHook,
12
13
  runCodegraphSessionStartWorker,
13
14
  type WorkerSpawnInvocation,
14
15
  } from "../src/hook.ts";
@@ -48,6 +49,54 @@ describe("CodeGraph SessionStart hook", () => {
48
49
  }
49
50
  });
50
51
 
52
+ it("#given CodeGraph MCP reports an uninitialized project #when PostToolUse fires #then it emits OMO global-store init guidance", async () => {
53
+ // given
54
+ const output = runCodegraphPostToolUseHook(
55
+ {
56
+ cwd: "/Users/me/project",
57
+ tool_name: "codegraph.codegraph_status",
58
+ tool_response: {
59
+ error: [
60
+ "Tool execution failed: CodeGraph not initialized in /Users/me/project.",
61
+ "Run 'codegraph init' in that project first.",
62
+ ].join(" "),
63
+ },
64
+ },
65
+ { homeDir: "/Users/me" },
66
+ );
67
+
68
+ // when
69
+ const parsed = JSON.parse(output);
70
+
71
+ // then
72
+ expect(parsed).toEqual({
73
+ hookSpecificOutput: {
74
+ hookEventName: "PostToolUse",
75
+ additionalContext: expect.stringContaining('"/Users/me/.omo/codegraph/projects/project-'),
76
+ },
77
+ });
78
+ expect(parsed.hookSpecificOutput.additionalContext).toContain('run `codegraph init` from "/Users/me/project"');
79
+ });
80
+
81
+ it("#given real CodeGraph status output has no MCP path phrase #when PostToolUse fires #then it emits OMO global-store init guidance", async () => {
82
+ // given
83
+ const output = runCodegraphPostToolUseHook(
84
+ {
85
+ cwd: "/Users/me/project",
86
+ tool_name: "mcp__codegraph__codegraph_status",
87
+ tool_response: ['Project: /Users/me/project', "Not initialized", 'Run "codegraph init" to initialize'].join("\n"),
88
+ },
89
+ { homeDir: "/Users/me" },
90
+ );
91
+
92
+ // when
93
+ const parsed = JSON.parse(output);
94
+
95
+ // then
96
+ expect(parsed.hookSpecificOutput.additionalContext).toContain('CodeGraph is not initialized for "/Users/me/project"');
97
+ expect(parsed.hookSpecificOutput.additionalContext).toContain('"/Users/me/.omo/codegraph/projects/project-');
98
+ });
99
+
51
100
  it("#given CodeGraph is disabled by Codex SOT config #when SessionStart fires #then it skips without spawning", async () => {
52
101
  // given
53
102
  const stdout: string[] = [];
@@ -198,93 +247,6 @@ describe("CodeGraph SessionStart hook", () => {
198
247
  }
199
248
  });
200
249
 
201
- it("#given an unsupported local Node and a PATH CodeGraph command #when worker runs #then it skips without touching the workspace", async () => {
202
- // given
203
- const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-"));
204
- const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-home-"));
205
- const outcomes: unknown[] = [];
206
-
207
- try {
208
- // when
209
- const result = await runCodegraphSessionStartWorker({
210
- cwd: workspace,
211
- env: { HOME: homeDir },
212
- nodeVersion: "26.3.0",
213
- logOutcome: (outcome) => outcomes.push(outcome),
214
- deps: {
215
- resolveCommand: () => {
216
- return { argsPrefix: [], command: "/usr/local/bin/codegraph", exists: true, source: "path" };
217
- },
218
- ensureProvisioned: () => {
219
- throw new Error("ensureProvisioned should not run on unsupported Node");
220
- },
221
- prepareWorkspace: () => {
222
- throw new Error("prepareWorkspace should not run on unsupported Node");
223
- },
224
- runCommand: () => {
225
- throw new Error("runCommand should not run on unsupported Node");
226
- },
227
- },
228
- });
229
-
230
- // then
231
- expect(result).toEqual({ action: "skipped-unsupported-node" });
232
- expect(existsSync(join(workspace, ".codegraph"))).toBe(false);
233
- expect(outcomes).toEqual([{ action: "skipped-unsupported-node", projectRoot: workspace }]);
234
- } finally {
235
- rmSync(workspace, { recursive: true, force: true });
236
- rmSync(homeDir, { recursive: true, force: true });
237
- }
238
- });
239
-
240
- it("#given an unsupported local Node but bundled CodeGraph resolves through CODEGRAPH_NODE_BIN #when worker runs #then it bootstraps with the compatible runtime", async () => {
241
- // given
242
- const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-compatible-node-"));
243
- const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-compatible-node-home-"));
244
- const nodeBin = "/opt/node22/bin/node";
245
- const calls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
246
- const outcomes: unknown[] = [];
247
-
248
- try {
249
- // when
250
- const result = await runCodegraphSessionStartWorker({
251
- cwd: workspace,
252
- env: { CODEGRAPH_NODE_BIN: nodeBin, HOME: homeDir },
253
- nodeVersion: "26.3.0",
254
- logOutcome: (outcome) => outcomes.push(outcome),
255
- deps: {
256
- ensureGitignored: () => true,
257
- ensureProvisioned: () => {
258
- throw new Error("provisioning should not run when bundled CodeGraph resolved");
259
- },
260
- prepareWorkspace: () => ({
261
- dataDir: join(homeDir, ".omo/codegraph/projects/test"),
262
- dataRoot: join(homeDir, ".omo/codegraph"),
263
- linked: true,
264
- mode: "global-linked",
265
- projectLink: join(workspace, ".codegraph"),
266
- }),
267
- resolveCommand: () => ({ argsPrefix: ["codegraph.js"], command: nodeBin, exists: true, source: "bundled" }),
268
- runCommand: (_projectRoot, command, args) => {
269
- calls.push({ args, command });
270
- return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
271
- },
272
- },
273
- });
274
-
275
- // then
276
- expect(result).toEqual({ action: "initialized" });
277
- expect(calls).toEqual([
278
- { args: ["codegraph.js", "status", "--json"], command: nodeBin },
279
- { args: ["codegraph.js", "init"], command: nodeBin },
280
- ]);
281
- expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "bundled", timedOut: false }]);
282
- } finally {
283
- rmSync(workspace, { recursive: true, force: true });
284
- rmSync(homeDir, { recursive: true, force: true });
285
- }
286
- });
287
-
288
250
  it("#given CodeGraph cannot be resolved or provisioned #when worker runs #then it logs a graceful skip", async () => {
289
251
  // given
290
252
  const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-"));
@@ -35,7 +35,7 @@ describe("CodeGraph provisioned launcher Node guard", () => {
35
35
  const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node25-"));
36
36
  const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node25-home-"));
37
37
  const installDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node25-install-"));
38
- const binPath = join(installDir, "bin", "codegraph");
38
+ const binPath = join(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
39
39
  const calls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
40
40
  const outcomes: unknown[] = [];
41
41
 
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { join } from "node:path";
3
+
4
+ import { runCodegraphServe } from "../src/serve.ts";
5
+
6
+ describe("runCodegraphServe provisioning", () => {
7
+ it("#given CodeGraph is unresolved #when serving MCP #then provisions CodeGraph before spawning", async () => {
8
+ // given
9
+ const binPath = join("/tmp/home/.omo/codegraph", "bin", "codegraph");
10
+ const calls: Array<{
11
+ readonly args: readonly string[];
12
+ readonly command: string;
13
+ readonly env: Record<string, string | undefined>;
14
+ }> = [];
15
+ const stderr: string[] = [];
16
+
17
+ // when
18
+ const exitCode = await runCodegraphServe({
19
+ config: { codegraph: { enabled: true }, sources: [], warnings: [] },
20
+ env: { PATH: "/bin" },
21
+ homeDir: "/tmp/home",
22
+ nodeVersion: "22.14.0",
23
+ buildEnv: () => ({}),
24
+ resolve: () => ({ argsPrefix: [], command: "codegraph", exists: false, source: "path" }),
25
+ ensureProvisioned: (options) =>
26
+ Promise.resolve({
27
+ binPath: join(options.installDir ?? "/tmp/home/.omo/codegraph", "bin", "codegraph"),
28
+ provisioned: true,
29
+ }),
30
+ runProcess: (command, args, options) => {
31
+ calls.push({ args, command, env: options.env });
32
+ return Promise.resolve(0);
33
+ },
34
+ stderr: { write: (chunk: string) => stderr.push(chunk) },
35
+ });
36
+
37
+ // then
38
+ expect(exitCode).toBe(0);
39
+ expect(stderr).toEqual([]);
40
+ expect(calls).toEqual([
41
+ {
42
+ args: ["serve", "--mcp"],
43
+ command: binPath,
44
+ env: { PATH: "/bin" },
45
+ },
46
+ ]);
47
+ });
48
+ });
@@ -17,6 +17,7 @@ describe("runCodegraphServe", () => {
17
17
 
18
18
  // when
19
19
  const exitCode = await runCodegraphServe({
20
+ config: { codegraph: { auto_provision: false, enabled: true }, sources: [], warnings: [] },
20
21
  env: { PATH: "/bin" },
21
22
  buildEnv: () => ({}),
22
23
  resolve: () => ({ argsPrefix: [], command: "codegraph", exists: false, source: "path" }),
@@ -0,0 +1,142 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { existsSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { runCodegraphSessionStartWorker } from "../src/hook.ts";
7
+
8
+ describe("CodeGraph SessionStart worker Node support", () => {
9
+ it("#given an unsupported local Node and a PATH CodeGraph command with auto provisioning disabled #when worker runs #then it skips without touching the workspace", async () => {
10
+ // given
11
+ const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-"));
12
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-home-"));
13
+ const outcomes: unknown[] = [];
14
+
15
+ try {
16
+ // when
17
+ const result = await runCodegraphSessionStartWorker({
18
+ config: { codegraph: { auto_provision: false, enabled: true }, sources: [], warnings: [] },
19
+ cwd: workspace,
20
+ env: { HOME: homeDir },
21
+ nodeVersion: "26.3.0",
22
+ logOutcome: (outcome) => outcomes.push(outcome),
23
+ deps: {
24
+ resolveCommand: () => {
25
+ return { argsPrefix: [], command: "/usr/local/bin/codegraph", exists: true, source: "path" };
26
+ },
27
+ ensureProvisioned: () => {
28
+ throw new Error("ensureProvisioned should not run on unsupported Node");
29
+ },
30
+ prepareWorkspace: () => {
31
+ throw new Error("prepareWorkspace should not run on unsupported Node");
32
+ },
33
+ runCommand: () => {
34
+ throw new Error("runCommand should not run on unsupported Node");
35
+ },
36
+ },
37
+ });
38
+
39
+ // then
40
+ expect(result).toEqual({ action: "skipped-unsupported-node" });
41
+ expect(existsSync(join(workspace, ".codegraph"))).toBe(false);
42
+ expect(outcomes).toEqual([{ action: "skipped-unsupported-node", projectRoot: workspace }]);
43
+ } finally {
44
+ rmSync(workspace, { recursive: true, force: true });
45
+ rmSync(homeDir, { recursive: true, force: true });
46
+ }
47
+ });
48
+
49
+ it("#given an unsupported local Node and a PATH CodeGraph command #when auto provisioning succeeds #then it bootstraps with the provisioned binary", async () => {
50
+ // given
51
+ const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-provision-"));
52
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-provision-home-"));
53
+ const binPath = join(homeDir, ".omo", "codegraph", "bin", "codegraph");
54
+ const calls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
55
+ const outcomes: unknown[] = [];
56
+
57
+ try {
58
+ // when
59
+ const result = await runCodegraphSessionStartWorker({
60
+ cwd: workspace,
61
+ env: { HOME: homeDir },
62
+ nodeVersion: "26.3.0",
63
+ logOutcome: (outcome) => outcomes.push(outcome),
64
+ deps: {
65
+ ensureGitignored: () => true,
66
+ ensureProvisioned: () => Promise.resolve({ binPath, provisioned: true }),
67
+ prepareWorkspace: () => ({
68
+ dataDir: join(homeDir, ".omo/codegraph/projects/test"),
69
+ dataRoot: join(homeDir, ".omo/codegraph"),
70
+ linked: true,
71
+ mode: "global-linked",
72
+ projectLink: join(workspace, ".codegraph"),
73
+ }),
74
+ resolveCommand: () => ({ argsPrefix: [], command: "/usr/local/bin/codegraph", exists: true, source: "path" }),
75
+ runCommand: (_projectRoot, command, args) => {
76
+ calls.push({ args, command });
77
+ return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
78
+ },
79
+ },
80
+ });
81
+
82
+ // then
83
+ expect(result).toEqual({ action: "initialized" });
84
+ expect(calls).toEqual([
85
+ { args: ["status", "--json"], command: binPath },
86
+ { args: ["init"], command: binPath },
87
+ ]);
88
+ expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "provisioned", timedOut: false }]);
89
+ } finally {
90
+ rmSync(workspace, { recursive: true, force: true });
91
+ rmSync(homeDir, { recursive: true, force: true });
92
+ }
93
+ });
94
+
95
+ it("#given an unsupported local Node but bundled CodeGraph resolves through CODEGRAPH_NODE_BIN #when worker runs #then it bootstraps with the compatible runtime", async () => {
96
+ // given
97
+ const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-compatible-node-"));
98
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-compatible-node-home-"));
99
+ const nodeBin = "/opt/node22/bin/node";
100
+ const calls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
101
+ const outcomes: unknown[] = [];
102
+
103
+ try {
104
+ // when
105
+ const result = await runCodegraphSessionStartWorker({
106
+ cwd: workspace,
107
+ env: { CODEGRAPH_NODE_BIN: nodeBin, HOME: homeDir },
108
+ nodeVersion: "26.3.0",
109
+ logOutcome: (outcome) => outcomes.push(outcome),
110
+ deps: {
111
+ ensureGitignored: () => true,
112
+ ensureProvisioned: () => {
113
+ throw new Error("provisioning should not run when bundled CodeGraph resolved");
114
+ },
115
+ prepareWorkspace: () => ({
116
+ dataDir: join(homeDir, ".omo/codegraph/projects/test"),
117
+ dataRoot: join(homeDir, ".omo/codegraph"),
118
+ linked: true,
119
+ mode: "global-linked",
120
+ projectLink: join(workspace, ".codegraph"),
121
+ }),
122
+ resolveCommand: () => ({ argsPrefix: ["codegraph.js"], command: nodeBin, exists: true, source: "bundled" }),
123
+ runCommand: (_projectRoot, command, args) => {
124
+ calls.push({ args, command });
125
+ return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
126
+ },
127
+ },
128
+ });
129
+
130
+ // then
131
+ expect(result).toEqual({ action: "initialized" });
132
+ expect(calls).toEqual([
133
+ { args: ["codegraph.js", "status", "--json"], command: nodeBin },
134
+ { args: ["codegraph.js", "init"], command: nodeBin },
135
+ ]);
136
+ expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "bundled", timedOut: false }]);
137
+ } finally {
138
+ rmSync(workspace, { recursive: true, force: true });
139
+ rmSync(homeDir, { recursive: true, force: true });
140
+ }
141
+ });
142
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-comment-checker",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex plugin that runs comment-checker after edit-like PostToolUse hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-git-bash-hook",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex hook component that reminds Windows sessions to prefer the OMO git_bash MCP.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lazycodex-executor-verify",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex SubagentStop evidence verifier for LazyCodex executor completions.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lsp",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex plugin that exposes Language Server Protocol tools and post-edit diagnostics.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-rules",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex plugin that injects project rule files into model context through lifecycle hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-start-work-continuation",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex Stop hook continuation injector for omo-codex start-work plans.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ // components/teammode/src/codex-hook.ts
4
+ var CREATE_THREAD_TOOL_NAMES = new Set(["create_thread", "codex_app.create_thread"]);
5
+ function parsePostToolUsePayload(raw) {
6
+ if (raw.trim().length === 0)
7
+ return null;
8
+ try {
9
+ const parsed = JSON.parse(raw);
10
+ return isPostToolUsePayload(parsed) ? parsed : null;
11
+ } catch (error) {
12
+ if (error instanceof SyntaxError)
13
+ return null;
14
+ return null;
15
+ }
16
+ }
17
+ function runPostToolUseHook(payload) {
18
+ if (payload.hook_event_name !== "PostToolUse")
19
+ return "";
20
+ if (!CREATE_THREAD_TOOL_NAMES.has(payload.tool_name))
21
+ return "";
22
+ const threadReference = extractThreadCreationReference(payload.tool_response);
23
+ if (threadReference === null)
24
+ return "";
25
+ const output = {
26
+ hookSpecificOutput: {
27
+ hookEventName: "PostToolUse",
28
+ additionalContext: threadTitleReminder(threadReference)
29
+ }
30
+ };
31
+ return `${JSON.stringify(output)}
32
+ `;
33
+ }
34
+ async function runTeammodeHookCli(stdin, stdout) {
35
+ const payload = parsePostToolUsePayload(await readAll(stdin));
36
+ if (payload === null)
37
+ return;
38
+ const output = runPostToolUseHook(payload);
39
+ if (output.length > 0)
40
+ stdout.write(output);
41
+ }
42
+ function threadTitleReminder(threadReference) {
43
+ const id = formatIdentifier(threadReference.id);
44
+ return threadReference.kind === "thread" ? `THREAD ID ${id}: CALL codex_app.set_thread_title NOW. USE THE REAL TASK/ROLE.` : `PENDING WORKTREE ID ${id}: CALL codex_app.set_thread_title AS SOON AS THREAD ID EXISTS. USE THE REAL TASK/ROLE.`;
45
+ }
46
+ function formatIdentifier(value) {
47
+ const normalized = value.replace(/\s+/g, " ").trim();
48
+ return normalized.length <= 200 ? normalized : `${normalized.slice(0, 197)}...`;
49
+ }
50
+ function extractThreadCreationReference(toolResponse) {
51
+ if (!isRecord(toolResponse))
52
+ return null;
53
+ const threadId = toolResponse["threadId"];
54
+ if (typeof threadId === "string" && threadId.trim().length > 0) {
55
+ return { kind: "thread", id: threadId };
56
+ }
57
+ const pendingWorktreeId = toolResponse["pendingWorktreeId"];
58
+ if (typeof pendingWorktreeId === "string" && pendingWorktreeId.trim().length > 0) {
59
+ return { kind: "pendingWorktree", id: pendingWorktreeId };
60
+ }
61
+ return null;
62
+ }
63
+ function isPostToolUsePayload(value) {
64
+ if (!isRecord(value))
65
+ return false;
66
+ return value["hook_event_name"] === "PostToolUse" && typeof value["session_id"] === "string" && typeof value["tool_name"] === "string" && Object.hasOwn(value, "tool_input") && Object.hasOwn(value, "tool_response") && optionalString(value["turn_id"]) && optionalString(value["cwd"]) && optionalString(value["model"]) && optionalString(value["permission_mode"]) && optionalString(value["tool_use_id"]) && (value["transcript_path"] === undefined || value["transcript_path"] === null || typeof value["transcript_path"] === "string");
67
+ }
68
+ function optionalString(value) {
69
+ return value === undefined || typeof value === "string";
70
+ }
71
+ function isRecord(value) {
72
+ return typeof value === "object" && value !== null && !Array.isArray(value);
73
+ }
74
+ function readAll(stdin) {
75
+ return new Promise((resolve, reject) => {
76
+ let data = "";
77
+ stdin.setEncoding("utf8");
78
+ stdin.on("data", (chunk) => {
79
+ data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
80
+ });
81
+ stdin.once("error", reject);
82
+ stdin.once("end", () => resolve(data));
83
+ });
84
+ }
85
+
86
+ // components/teammode/src/cli.ts
87
+ var [command, subcommand] = process.argv.slice(2);
88
+ if (command === "hook" && subcommand === "post-tool-use") {
89
+ await runTeammodeHookCli(process.stdin, process.stdout);
90
+ } else {
91
+ process.stderr.write(`Usage: omo-teammode hook post-tool-use
92
+ `);
93
+ process.exitCode = 2;
94
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "^(create_thread|codex_app\\.create_thread)$",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
+ "timeout": 10,
11
+ "statusMessage": "(OmO) Checking Thread Title Hygiene"
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@sisyphuslabs/codex-teammode",
3
+ "version": "4.12.1",
4
+ "description": "Codex team-mode hook component that keeps background thread titles descriptive after create_thread.",
5
+ "type": "module",
6
+ "private": true,
7
+ "files": [
8
+ "dist",
9
+ "hooks",
10
+ "skills"
11
+ ],
12
+ "scripts": {
13
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && bun build src/cli.ts --target node --format esm --outfile dist/cli.js",
14
+ "test": "bun test test/*.test.ts",
15
+ "typecheck": "tsc --noEmit"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^25.9.3",
19
+ "bun-types": "^1.3.1",
20
+ "typescript": "^6.0.3",
21
+ "vitest": "^4.1.8"
22
+ },
23
+ "engines": {
24
+ "node": ">=20.0.0"
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runTeammodeHookCli } from "./codex-hook.js";
4
+
5
+ const [command, subcommand] = process.argv.slice(2);
6
+
7
+ if (command === "hook" && subcommand === "post-tool-use") {
8
+ await runTeammodeHookCli(process.stdin, process.stdout);
9
+ } else {
10
+ process.stderr.write("Usage: omo-teammode hook post-tool-use\n");
11
+ process.exitCode = 2;
12
+ }