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
@@ -2,9 +2,9 @@
2
2
 
3
3
  // src/serve.ts
4
4
  import { spawn } from "node:child_process";
5
- import { existsSync as existsSync4, realpathSync } from "node:fs";
6
- import { homedir as homedir4 } from "node:os";
7
- import { basename as basename2, extname, join as join5, resolve as resolve2 } from "node:path";
5
+ import { existsSync as existsSync5, realpathSync } from "node:fs";
6
+ import { homedir as homedir5 } from "node:os";
7
+ import { basename as basename3, extname, join as join6, resolve as resolve2 } from "node:path";
8
8
  import {
9
9
  cwd as processCwd,
10
10
  env as processEnv,
@@ -59,16 +59,235 @@ function parseNodeMajor(version) {
59
59
  return Number.isNaN(major) ? 0 : major;
60
60
  }
61
61
 
62
- // ../../../../utils/src/codegraph/resolve.ts
62
+ // ../../../../utils/src/codegraph/provision.ts
63
+ import { createHash, randomUUID } from "node:crypto";
64
+ import { execFile } from "node:child_process";
65
+ import { chmod, mkdir, readdir, readFile, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
63
66
  import { existsSync } from "node:fs";
64
- import { homedir as homedir2 } from "node:os";
67
+ import { homedir as homedir2, hostname } from "node:os";
68
+ import { basename, join as join2 } from "node:path";
69
+ import { promisify } from "node:util";
70
+
71
+ // ../../../../utils/src/codegraph/manifest.ts
72
+ var CODEGRAPH_PROVISION_MANIFEST = {
73
+ assets: {
74
+ "darwin-arm64": {
75
+ executableName: "codegraph",
76
+ sha256: "95bb27bf6382b69659e158e0c04d71cc394778951e1317d582be7807e7866908",
77
+ url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-darwin-arm64.tar.gz"
78
+ },
79
+ "darwin-x64": {
80
+ executableName: "codegraph",
81
+ sha256: "3311cc1d1f0f0ad742709b6a43d8a9187b1ef0af0dd30e0b58008dc673e29478",
82
+ url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-darwin-x64.tar.gz"
83
+ },
84
+ "linux-arm64": {
85
+ executableName: "codegraph",
86
+ sha256: "e16f612bc96c2ebccd04574cbed500c9939147c80666ad6bb024398dff7992ae",
87
+ url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-linux-arm64.tar.gz"
88
+ },
89
+ "linux-x64": {
90
+ executableName: "codegraph",
91
+ sha256: "d45a068f44596a85c7ba7d0ef924eaf7103fbbf3cafbeb668127daff60a52228",
92
+ url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-linux-x64.tar.gz"
93
+ },
94
+ "win32-arm64": {
95
+ executableName: "codegraph.cmd",
96
+ sha256: "8d57ced73b24d35f758f2ede2318e80e1d7241987f37a999e3d80edb6fddf961",
97
+ url: "https://registry.npmjs.org/@colbymchenry/codegraph-win32-arm64/-/codegraph-win32-arm64-1.0.1.tgz"
98
+ },
99
+ "win32-x64": {
100
+ executableName: "codegraph.cmd",
101
+ sha256: "52607fe73b05e741fd1087da2ceca9d3c8f565e36bf1a7070600bdbdf3931e32",
102
+ url: "https://registry.npmjs.org/@colbymchenry/codegraph-win32-x64/-/codegraph-win32-x64-1.0.1.tgz"
103
+ }
104
+ },
105
+ version: "1.0.1"
106
+ };
107
+
108
+ // ../../../../utils/src/codegraph/provision.ts
109
+ var DEFAULT_LOCK_WAIT_MS = 5000;
110
+ var DEFAULT_LOCK_STALE_MS = 120000;
111
+ var DEFAULT_DOWNLOAD_TIMEOUT_MS = 60000;
112
+ var execFileAsync = promisify(execFile);
113
+ function platformKey() {
114
+ return `${process.platform}-${process.arch}`;
115
+ }
116
+ function markerPath(installDir, version) {
117
+ return join2(installDir, ".provisioned", `codegraph-${version}.json`);
118
+ }
119
+ function defaultInstallDir() {
120
+ return join2(homedir2(), ".omo", "codegraph");
121
+ }
122
+ function sha256(bytes) {
123
+ return createHash("sha256").update(bytes).digest("hex");
124
+ }
125
+ function isErrnoException(error) {
126
+ return error instanceof Error && "code" in error;
127
+ }
128
+ async function removeEmptyDirectory(path) {
129
+ try {
130
+ await rmdir(path);
131
+ } catch (error) {
132
+ if (isErrnoException(error) && error.code === "ENOENT")
133
+ return;
134
+ if (isErrnoException(error) && error.code === "ENOTEMPTY")
135
+ return;
136
+ throw error;
137
+ }
138
+ }
139
+ function sleep(ms) {
140
+ return new Promise((resolve) => setTimeout(resolve, ms));
141
+ }
142
+ async function defaultDownloader(asset, timeoutMs = DEFAULT_DOWNLOAD_TIMEOUT_MS) {
143
+ const response = await fetch(asset.url, { signal: AbortSignal.timeout(timeoutMs) });
144
+ if (!response.ok)
145
+ throw new Error(`download failed with HTTP ${response.status}`);
146
+ return new Uint8Array(await response.arrayBuffer());
147
+ }
148
+ function forcedBadChecksumOptions(options) {
149
+ if (options.forceBadChecksum !== true)
150
+ return null;
151
+ const key = options.platformKey ?? platformKey();
152
+ return {
153
+ downloader: async () => new TextEncoder().encode("checksum mismatch"),
154
+ installDir: options.installDir ?? join2(options.lockDir, "codegraph-force-bad-checksum"),
155
+ manifest: {
156
+ assets: {
157
+ [key]: { executableName: process.platform === "win32" ? "codegraph.cmd" : "codegraph", sha256: "0000", url: "memory://bad" }
158
+ },
159
+ version: options.version
160
+ },
161
+ platformKey: key
162
+ };
163
+ }
164
+ async function readMarker(path) {
165
+ if (!existsSync(path))
166
+ return null;
167
+ try {
168
+ const raw = JSON.parse(await readFile(path, "utf8"));
169
+ if (typeof raw === "object" && raw !== null && "binPath" in raw) {
170
+ const value = raw.binPath;
171
+ return typeof value === "string" && existsSync(value) ? value : null;
172
+ }
173
+ return null;
174
+ } catch (error) {
175
+ if (error instanceof Error)
176
+ return null;
177
+ throw error;
178
+ }
179
+ }
180
+ async function acquireLock(lockPath, waitMs, staleMs) {
181
+ const startedAt = Date.now();
182
+ await mkdir(join2(lockPath, ".."), { recursive: true });
183
+ while (Date.now() - startedAt <= waitMs) {
184
+ try {
185
+ await mkdir(lockPath);
186
+ return () => rm(lockPath, { force: true, recursive: true });
187
+ } catch (error) {
188
+ if (!isErrnoException(error) || error.code !== "EEXIST")
189
+ throw error;
190
+ const lockStat = await stat(lockPath).catch(() => null);
191
+ if (lockStat !== null && Date.now() - lockStat.mtimeMs > staleMs) {
192
+ await rm(lockPath, { force: true, recursive: true });
193
+ continue;
194
+ }
195
+ await sleep(25);
196
+ }
197
+ }
198
+ return null;
199
+ }
200
+ async function extractTarGz(archivePath, destinationDir) {
201
+ await execFileAsync("tar", ["-xzf", archivePath, "-C", destinationDir]);
202
+ }
203
+ async function installExtractedBundle(extractDir, installDir, executableName) {
204
+ const roots = await readdir(extractDir);
205
+ if (roots.length !== 1)
206
+ throw new Error(`CodeGraph archive should contain one root directory, found ${roots.length}`);
207
+ const bundleDir = join2(extractDir, roots[0] ?? "");
208
+ const bundleEntries = await readdir(bundleDir);
209
+ await mkdir(installDir, { recursive: true });
210
+ for (const entry of bundleEntries) {
211
+ await rm(join2(installDir, entry), { force: true, recursive: true });
212
+ await rename(join2(bundleDir, entry), join2(installDir, entry));
213
+ }
214
+ const destination = join2(installDir, "bin", executableName);
215
+ if (!existsSync(destination))
216
+ throw new Error(`CodeGraph archive did not contain bin/${executableName}`);
217
+ await chmod(destination, 493);
218
+ return destination;
219
+ }
220
+ async function installAsset(layout) {
221
+ const { asset, downloader, installDir, version } = layout;
222
+ const stagingDir = join2(installDir, ".staging", randomUUID());
223
+ const archivePath = join2(stagingDir, basename(asset.url));
224
+ const extractDir = join2(stagingDir, "extract");
225
+ try {
226
+ await mkdir(extractDir, { recursive: true });
227
+ const bytes = await downloader(asset);
228
+ const actualChecksum = sha256(bytes);
229
+ if (actualChecksum !== asset.sha256) {
230
+ throw new Error(`checksum mismatch for ${basename(asset.url)}: expected ${asset.sha256}, got ${actualChecksum}`);
231
+ }
232
+ if (!asset.url.endsWith(".tar.gz") && !asset.url.endsWith(".tgz")) {
233
+ throw new Error(`unsupported CodeGraph archive type for ${basename(asset.url)}`);
234
+ }
235
+ await writeFile(archivePath, bytes);
236
+ await extractTarGz(archivePath, extractDir);
237
+ const destination = await installExtractedBundle(extractDir, installDir, asset.executableName);
238
+ await mkdir(join2(installDir, ".provisioned"), { recursive: true });
239
+ await writeFile(markerPath(installDir, version), `${JSON.stringify({ binPath: destination, version })}
240
+ `);
241
+ return destination;
242
+ } finally {
243
+ await rm(stagingDir, { force: true, recursive: true });
244
+ await removeEmptyDirectory(join2(installDir, ".staging"));
245
+ }
246
+ }
247
+ async function ensureCodegraphProvisioned(options) {
248
+ const forced = forcedBadChecksumOptions(options);
249
+ const installDir = forced?.installDir ?? options.installDir ?? defaultInstallDir();
250
+ const manifest = forced?.manifest ?? options.manifest ?? CODEGRAPH_PROVISION_MANIFEST;
251
+ const activePlatformKey = forced?.platformKey ?? options.platformKey ?? platformKey();
252
+ const downloader = forced?.downloader ?? options.downloader ?? ((asset) => defaultDownloader(asset, options.downloadTimeoutMs));
253
+ const marker = markerPath(installDir, options.version);
254
+ const existing = await readMarker(marker);
255
+ if (existing !== null)
256
+ return { binPath: existing, provisioned: true };
257
+ const lockPath = join2(options.lockDir, `codegraph-${hostname()}.lock`);
258
+ const release = await acquireLock(lockPath, options.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS, options.lockStaleMs ?? DEFAULT_LOCK_STALE_MS);
259
+ if (release === null)
260
+ return { error: "timed out waiting for codegraph provisioning lock", provisioned: false };
261
+ try {
262
+ const lockedExisting = await readMarker(marker);
263
+ if (lockedExisting !== null)
264
+ return { binPath: lockedExisting, provisioned: true };
265
+ if (manifest.version !== options.version) {
266
+ return { error: `manifest version ${manifest.version} does not match requested ${options.version}`, provisioned: false };
267
+ }
268
+ const asset = manifest.assets[activePlatformKey];
269
+ if (asset === undefined) {
270
+ return { error: `no CodeGraph ${options.version} asset for ${activePlatformKey}`, provisioned: false };
271
+ }
272
+ const binPath = await installAsset({ asset, downloader, installDir, version: options.version });
273
+ return { binPath, provisioned: true };
274
+ } catch (error) {
275
+ return { error: error instanceof Error ? error.message : String(error), provisioned: false };
276
+ } finally {
277
+ await release();
278
+ }
279
+ }
280
+
281
+ // ../../../../utils/src/codegraph/resolve.ts
282
+ import { existsSync as existsSync2 } from "node:fs";
283
+ import { homedir as homedir3 } from "node:os";
65
284
  import { spawnSync } from "node:child_process";
66
- import { basename, dirname, join as join3 } from "node:path";
285
+ import { basename as basename2, dirname, join as join4 } from "node:path";
67
286
  import { createRequire } from "node:module";
68
287
 
69
288
  // ../../../../utils/src/runtime/which.ts
70
289
  import { accessSync, constants } from "node:fs";
71
- import { delimiter, join as join2 } from "node:path";
290
+ import { delimiter, join as join3 } from "node:path";
72
291
  var runtime = globalThis;
73
292
  function isUnsafeCommandName(commandName) {
74
293
  if (commandName.includes("/") || commandName.includes("\\"))
@@ -123,7 +342,7 @@ function bunWhich(commandName) {
123
342
  return null;
124
343
  for (const pathEntry of pathEntries) {
125
344
  for (const candidateName of candidateNames) {
126
- const candidatePath = join2(pathEntry, candidateName);
345
+ const candidatePath = join3(pathEntry, candidateName);
127
346
  if (isExecutable(candidatePath))
128
347
  return candidatePath;
129
348
  }
@@ -164,7 +383,7 @@ ${result.stderr}`.trim().split(/\s+/)[0];
164
383
  }
165
384
  }
166
385
  function isNodeExecutableName(filePath) {
167
- const executable = basename(filePath).toLowerCase();
386
+ const executable = basename2(filePath).toLowerCase();
168
387
  return executable === "node" || executable === "node.exe" || /^node\d+(\.exe)?$/.test(executable);
169
388
  }
170
389
  function looksLikePath(command) {
@@ -204,8 +423,8 @@ function defaultNodeRuntime(env, fileExists, which, nodeVersion) {
204
423
  function defaultProvisionedBin(homeDir, fileExists) {
205
424
  const binaryName = process.platform === "win32" ? "codegraph.cmd" : "codegraph";
206
425
  const candidates = [
207
- join3(homeDir, ".omo", "codegraph", "bin", binaryName),
208
- join3(homeDir, ".omo", "codegraph", "node-servers", "node_modules", ".bin", binaryName)
426
+ join4(homeDir, ".omo", "codegraph", "bin", binaryName),
427
+ join4(homeDir, ".omo", "codegraph", "node-servers", "node_modules", ".bin", binaryName)
209
428
  ];
210
429
  return candidates.find((candidate) => fileExists(candidate)) ?? null;
211
430
  }
@@ -213,7 +432,7 @@ function resolveBundledShim(requireResolve, fileExists) {
213
432
  try {
214
433
  const packageJson = requireResolve(`${CODEGRAPH_PACKAGE}/package.json`);
215
434
  const packageRoot = dirname(packageJson);
216
- const candidates = [join3(packageRoot, "bin", "codegraph.js"), join3(packageRoot, "npm-shim.js")];
435
+ const candidates = [join4(packageRoot, "bin", "codegraph.js"), join4(packageRoot, "npm-shim.js")];
217
436
  return candidates.find((candidate) => fileExists(candidate)) ?? null;
218
437
  } catch (error) {
219
438
  if (error instanceof Error)
@@ -229,7 +448,7 @@ function resolveBundledShim(requireResolve, fileExists) {
229
448
  }
230
449
  function resolveCodegraphCommand(options = {}) {
231
450
  const env = options.env ?? process.env;
232
- const fileExists = options.fileExists ?? existsSync;
451
+ const fileExists = options.fileExists ?? existsSync2;
233
452
  const configuredBin = env[CODEGRAPH_ENV_BIN]?.trim() || env[CODEGRAPH_LEGACY_ENV_BIN]?.trim();
234
453
  if (configuredBin !== undefined && configuredBin.length > 0) {
235
454
  return { argsPrefix: [], command: configuredBin, exists: fileExists(configuredBin), source: "env" };
@@ -241,7 +460,7 @@ function resolveCodegraphCommand(options = {}) {
241
460
  if (bundled !== null && runtime2 !== null) {
242
461
  return { argsPrefix: [bundled], command: runtime2, exists: true, source: "bundled" };
243
462
  }
244
- const provisioned = options.provisioned?.() ?? defaultProvisionedBin(options.homeDir ?? homedir2(), fileExists);
463
+ const provisioned = options.provisioned?.() ?? defaultProvisionedBin(options.homeDir ?? homedir3(), fileExists);
245
464
  if (provisioned !== null && fileExists(provisioned)) {
246
465
  return { argsPrefix: [], command: provisioned, exists: true, source: "provisioned" };
247
466
  }
@@ -255,8 +474,8 @@ function resolveCodegraphCommand(options = {}) {
255
474
  }
256
475
 
257
476
  // ../../../../utils/src/omo-config/loader.ts
258
- import { existsSync as existsSync3, readFileSync } from "node:fs";
259
- import { homedir as homedir3 } from "node:os";
477
+ import { existsSync as existsSync4, readFileSync } from "node:fs";
478
+ import { homedir as homedir4 } from "node:os";
260
479
 
261
480
  // ../../../../utils/src/deep-merge.ts
262
481
  var DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
@@ -1212,8 +1431,8 @@ function buildEnvOverrides(harness, env, warnings, merge) {
1212
1431
  }
1213
1432
 
1214
1433
  // ../../../../utils/src/omo-config/resolve.ts
1215
- import { existsSync as existsSync2 } from "node:fs";
1216
- import { dirname as dirname2, isAbsolute, join as join4, relative, resolve } from "node:path";
1434
+ import { existsSync as existsSync3 } from "node:fs";
1435
+ import { dirname as dirname2, isAbsolute, join as join5, relative, resolve } from "node:path";
1217
1436
  function containsPath(parent, child) {
1218
1437
  const pathToChild = relative(parent, child);
1219
1438
  return pathToChild === "" || !pathToChild.startsWith("..") && !isAbsolute(pathToChild);
@@ -1226,8 +1445,8 @@ function findProjectConfigPathsNearestFirst(cwd, homeDir) {
1226
1445
  while (true) {
1227
1446
  if (stopBeforeDir !== null && currentDir === stopBeforeDir)
1228
1447
  break;
1229
- const configPath = join4(currentDir, ".omo", "config.jsonc");
1230
- if (existsSync2(configPath)) {
1448
+ const configPath = join5(currentDir, ".omo", "config.jsonc");
1449
+ if (existsSync3(configPath)) {
1231
1450
  paths.push(configPath);
1232
1451
  }
1233
1452
  const parentDir = dirname2(currentDir);
@@ -1238,7 +1457,7 @@ function findProjectConfigPathsNearestFirst(cwd, homeDir) {
1238
1457
  return paths;
1239
1458
  }
1240
1459
  function resolveOmoConfigPaths(options) {
1241
- const globalPath = join4(resolve(options.homeDir), ".omo", "config.jsonc");
1460
+ const globalPath = join5(resolve(options.homeDir), ".omo", "config.jsonc");
1242
1461
  const projectPathsFarthestFirst = findProjectConfigPathsNearestFirst(options.cwd, options.homeDir).reverse();
1243
1462
  return [
1244
1463
  { path: globalPath, scope: "global" },
@@ -1449,13 +1668,13 @@ function validateHarnessApplicability(config, harness) {
1449
1668
  }
1450
1669
  function loadOmoConfig(options) {
1451
1670
  const cwd = options.cwd ?? process.cwd();
1452
- const homeDir = options.homeDir ?? process.env["HOME"] ?? process.env["USERPROFILE"] ?? homedir3();
1671
+ const homeDir = options.homeDir ?? process.env["HOME"] ?? process.env["USERPROFILE"] ?? homedir4();
1453
1672
  const env = options.env ?? process.env;
1454
1673
  let config = BUILT_IN_DEFAULTS;
1455
1674
  const sources = [];
1456
1675
  const warnings = [];
1457
1676
  for (const candidate of resolveOmoConfigPaths({ cwd, homeDir })) {
1458
- if (!existsSync3(candidate.path)) {
1677
+ if (!existsSync4(candidate.path)) {
1459
1678
  if (candidate.scope === "global") {
1460
1679
  sources.push(toMissingSource(candidate));
1461
1680
  }
@@ -1497,11 +1716,12 @@ var CODEGRAPH_SKIP_HINT = `CodeGraph MCP skipped: codegraph binary not found. In
1497
1716
  `;
1498
1717
  var CODEGRAPH_DISABLED_HINT = `CodeGraph MCP skipped: disabled by OMO SOT config. Set [codex].codegraph.enabled=true to enable it.
1499
1718
  `;
1719
+ var CODEGRAPH_VERSION = "1.0.1";
1500
1720
  var WINDOWS_CMD_EXTENSIONS = new Set([".bat", ".cmd"]);
1501
1721
  var WINDOWS_NODE_SCRIPT_EXTENSIONS = new Set([".cjs", ".js", ".mjs"]);
1502
1722
  async function runCodegraphServe(options = {}) {
1503
1723
  const env = options.env ?? processEnv;
1504
- const homeDir = options.homeDir ?? homedir4();
1724
+ const homeDir = options.homeDir ?? homedir5();
1505
1725
  const config = options.config ?? getCodexOmoConfig({ cwd: options.cwd ?? processCwd(), env, homeDir });
1506
1726
  const codegraphConfig = config.codegraph ?? {};
1507
1727
  if (codegraphConfig.enabled === false) {
@@ -1513,15 +1733,24 @@ async function runCodegraphServe(options = {}) {
1513
1733
  homeDir,
1514
1734
  provisioned: () => provisionedBinFromInstallDir(codegraphConfig.install_dir)
1515
1735
  };
1516
- const resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
1736
+ let resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
1517
1737
  const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
1518
- if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync4)) {
1738
+ if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync5)) {
1519
1739
  if (resolution.source === "path" && !nodeSupport.supported) {
1520
1740
  (options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
1521
1741
  return 1;
1522
1742
  }
1523
- (options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
1524
- return 1;
1743
+ const provisioned = await provisionMissingCodegraph({
1744
+ config: codegraphConfig,
1745
+ ensureProvisioned: options.ensureProvisioned ?? ensureCodegraphProvisioned,
1746
+ homeDir,
1747
+ resolution
1748
+ });
1749
+ if (provisioned === null) {
1750
+ (options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
1751
+ return 1;
1752
+ }
1753
+ resolution = provisioned;
1525
1754
  }
1526
1755
  if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
1527
1756
  (options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
@@ -1538,6 +1767,21 @@ async function runCodegraphServe(options = {}) {
1538
1767
  stdio: "inherit"
1539
1768
  });
1540
1769
  }
1770
+ async function provisionMissingCodegraph(options) {
1771
+ if (options.resolution.source === "env")
1772
+ return null;
1773
+ if (options.config.auto_provision === false)
1774
+ return null;
1775
+ const installDir = options.config.install_dir ?? join6(options.homeDir, ".omo", "codegraph");
1776
+ const result = await options.ensureProvisioned({
1777
+ installDir,
1778
+ lockDir: join6(installDir, ".locks"),
1779
+ version: CODEGRAPH_VERSION
1780
+ });
1781
+ if (!result.provisioned || result.binPath === undefined)
1782
+ return null;
1783
+ return { argsPrefix: [], command: result.binPath, exists: true, source: "provisioned" };
1784
+ }
1541
1785
  function shouldSkipResolvedCommand(resolution, commandExists) {
1542
1786
  if (resolution.source !== "env")
1543
1787
  return false;
@@ -1555,8 +1799,8 @@ function codegraphEnvForConfig(config, homeDir, buildEnv) {
1555
1799
  function provisionedBinFromInstallDir(installDir) {
1556
1800
  if (installDir === undefined)
1557
1801
  return null;
1558
- const candidate = join5(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
1559
- return existsSync4(candidate) ? candidate : null;
1802
+ const candidate = join6(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
1803
+ return existsSync5(candidate) ? candidate : null;
1560
1804
  }
1561
1805
  async function runCodegraphServeCli() {
1562
1806
  process.exitCode = await runCodegraphServe();
@@ -1598,7 +1842,7 @@ function isDirectInvocation(argvPath) {
1598
1842
  if (argvPath === undefined)
1599
1843
  return false;
1600
1844
  const modulePath = fileURLToPath(import.meta.url);
1601
- const moduleName = basename2(modulePath);
1845
+ const moduleName = basename3(modulePath);
1602
1846
  if (moduleName !== "serve.js" && moduleName !== "serve.ts")
1603
1847
  return false;
1604
1848
  return realpathSync(resolve2(argvPath)) === realpathSync(modulePath);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-codegraph",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Codex plugin MCP wrapper for CodeGraph.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -5,8 +5,10 @@ import { stderr as processStderr } from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  import {
8
+ runCodegraphPostToolUseHookCli,
8
9
  runCodegraphSessionStartHook,
9
10
  runCodegraphSessionStartWorker,
11
+ type PostToolUseHookOptions,
10
12
  type SessionStartHookOptions,
11
13
  type SessionStartWorkerOptions,
12
14
  } from "./hook.js";
@@ -25,6 +27,15 @@ export async function runCodegraphCli(options: RunCodegraphCliOptions = {}): Pro
25
27
  return runCodegraphSessionStartHook(options);
26
28
  }
27
29
 
30
+ if (command === "hook" && subcommand === "post-tool-use") {
31
+ const hookOptions: PostToolUseHookOptions = {
32
+ ...(options.env === undefined ? {} : { env: options.env }),
33
+ ...(options.stdin === undefined ? {} : { stdin: options.stdin }),
34
+ ...(options.stdout === undefined ? {} : { stdout: options.stdout }),
35
+ };
36
+ return runCodegraphPostToolUseHookCli(hookOptions);
37
+ }
38
+
28
39
  if (command === "hook" && subcommand === "session-start-worker") {
29
40
  const workerOptions: SessionStartWorkerOptions = {
30
41
  ...options.workerOptions,
@@ -10,6 +10,7 @@ import type { CodegraphWorkspacePreparation as SharedCodegraphWorkspacePreparati
10
10
  import type { CodegraphConfig as SharedCodegraphConfig } from "../../../../../utils/src/omo-config.ts";
11
11
 
12
12
  export type SessionStartAction = "skipped-disabled" | "spawned";
13
+ export type PostToolUseAction = "emitted-guidance" | "skipped";
13
14
  export type WorkerAction = "failed" | "initialized" | "skipped-disabled" | "skipped-status" | "skipped-unavailable" | "skipped-unsupported-node" | "synced";
14
15
 
15
16
  export interface WorkerSpawnInvocation {
@@ -27,6 +28,11 @@ export interface SessionStartHookResult {
27
28
  readonly exitCode: 0;
28
29
  }
29
30
 
31
+ export interface PostToolUseHookResult {
32
+ readonly action: PostToolUseAction;
33
+ readonly exitCode: 0;
34
+ }
35
+
30
36
  export interface CodegraphCommandResult {
31
37
  readonly exitCode: number;
32
38
  readonly stderr?: string;
@@ -73,6 +79,12 @@ export interface SessionStartHookOptions {
73
79
  readonly workerCliPath?: string;
74
80
  }
75
81
 
82
+ export interface PostToolUseHookOptions {
83
+ readonly env?: Record<string, string | undefined>;
84
+ readonly stdin?: Readable & { readonly isTTY?: boolean };
85
+ readonly stdout?: HookStdout;
86
+ }
87
+
76
88
  export interface SessionStartWorkerOptions {
77
89
  readonly config?: CodexOmoConfig;
78
90
  readonly cwd?: string;
@@ -4,10 +4,13 @@ import { cwd as processCwd, env as processEnv, stdin as processStdin, stdout as
4
4
  import type { Readable } from "node:stream";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
+ import { buildCodegraphInitGuidanceForToolResult } from "../../../../../utils/src/codegraph/guidance.ts";
7
8
  import { getCodexOmoConfig } from "../../../shared/src/config-loader.ts";
8
9
  import { SESSION_START_CWD_ENV } from "./session-start-worker.js";
9
10
  import type {
10
11
  HookStdout,
12
+ PostToolUseHookOptions,
13
+ PostToolUseHookResult,
11
14
  SessionStartHookOptions,
12
15
  SessionStartHookResult,
13
16
  WorkerSpawnInvocation,
@@ -23,6 +26,8 @@ export type {
23
26
  CodexOmoConfig,
24
27
  HookStdout,
25
28
  OmoConfigSource,
29
+ PostToolUseHookOptions,
30
+ PostToolUseHookResult,
26
31
  SessionStartAction,
27
32
  SessionStartHookOptions,
28
33
  SessionStartHookResult,
@@ -38,6 +43,10 @@ export async function runCodegraphSessionStartHook(options: SessionStartHookOpti
38
43
  return (await executeCodegraphSessionStartHook(options)).exitCode;
39
44
  }
40
45
 
46
+ export async function runCodegraphPostToolUseHookCli(options: PostToolUseHookOptions = {}): Promise<number> {
47
+ return (await executeCodegraphPostToolUseHook(options)).exitCode;
48
+ }
49
+
41
50
  export async function executeCodegraphSessionStartHook(options: SessionStartHookOptions = {}): Promise<SessionStartHookResult> {
42
51
  const env = options.env ?? processEnv;
43
52
  const input = await readHookInput(options.stdin ?? processStdin);
@@ -58,6 +67,31 @@ export async function executeCodegraphSessionStartHook(options: SessionStartHook
58
67
  return { action: "spawned", exitCode: 0 };
59
68
  }
60
69
 
70
+ export async function executeCodegraphPostToolUseHook(options: PostToolUseHookOptions = {}): Promise<PostToolUseHookResult> {
71
+ const env = options.env ?? processEnv;
72
+ const input = await readHookInput(options.stdin ?? processStdin);
73
+ const output = runCodegraphPostToolUseHook(input, { homeDir: resolveHomeDir(env) });
74
+ if (output.length === 0) return { action: "skipped", exitCode: 0 };
75
+
76
+ (options.stdout ?? processStdout).write(output);
77
+ return { action: "emitted-guidance", exitCode: 0 };
78
+ }
79
+
80
+ export function runCodegraphPostToolUseHook(input: unknown, options: { readonly homeDir?: string } = {}): string {
81
+ const toolName = isRecord(input) ? input["tool_name"] : undefined;
82
+ const cwd = isRecord(input) ? input["cwd"] : undefined;
83
+ const toolOutput = isRecord(input) ? (input["tool_response"] ?? input["tool_output"] ?? input["response"]) : input;
84
+ const guidance = buildCodegraphInitGuidanceForToolResult({ cwd, toolName, toolOutput }, options);
85
+ if (guidance === null) return "";
86
+
87
+ return `${JSON.stringify({
88
+ hookSpecificOutput: {
89
+ hookEventName: "PostToolUse",
90
+ additionalContext: guidance,
91
+ },
92
+ })}\n`;
93
+ }
94
+
61
95
  function writeHookJson(stdout: HookStdout): void {
62
96
  const output = {
63
97
  hookSpecificOutput: {
@@ -16,6 +16,10 @@ import {
16
16
  buildCodegraphNodeSkipHint,
17
17
  evaluateCodegraphNodeSupport,
18
18
  } from "../../../../../utils/src/codegraph/node-support.ts";
19
+ import {
20
+ ensureCodegraphProvisioned,
21
+ type EnsureCodegraphProvisionedOptions,
22
+ } from "../../../../../utils/src/codegraph/provision.ts";
19
23
  import {
20
24
  codegraphCommandRequiresSupportedLocalNode,
21
25
  resolveCodegraphCommand,
@@ -37,6 +41,9 @@ export type CodegraphServeProcessRunner = (
37
41
  args: readonly string[],
38
42
  options: CodegraphServeProcessOptions,
39
43
  ) => Promise<number>;
44
+ export type CodegraphProvisioner = (
45
+ options: EnsureCodegraphProvisionedOptions,
46
+ ) => ReturnType<typeof ensureCodegraphProvisioned>;
40
47
 
41
48
  export interface ServeProcessInvocation {
42
49
  readonly args: readonly string[];
@@ -58,12 +65,14 @@ export interface RunCodegraphServeOptions {
58
65
  readonly resolve?: (options: ResolveCodegraphCommandOptions) => ReturnType<typeof resolveCodegraphCommand>;
59
66
  readonly runProcess?: CodegraphServeProcessRunner;
60
67
  readonly stderr?: CodegraphServeStderr;
68
+ readonly ensureProvisioned?: CodegraphProvisioner;
61
69
  }
62
70
 
63
71
  const CODEGRAPH_SKIP_HINT =
64
72
  "CodeGraph MCP skipped: codegraph binary not found. Install CodeGraph or set OMO_CODEGRAPH_BIN.\n";
65
73
  const CODEGRAPH_DISABLED_HINT =
66
74
  "CodeGraph MCP skipped: disabled by OMO SOT config. Set [codex].codegraph.enabled=true to enable it.\n";
75
+ const CODEGRAPH_VERSION = "1.0.1";
67
76
  const WINDOWS_CMD_EXTENSIONS = new Set([".bat", ".cmd"]);
68
77
  const WINDOWS_NODE_SCRIPT_EXTENSIONS = new Set([".cjs", ".js", ".mjs"]);
69
78
 
@@ -82,15 +91,24 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
82
91
  homeDir,
83
92
  provisioned: () => provisionedBinFromInstallDir(codegraphConfig.install_dir),
84
93
  } satisfies ResolveCodegraphCommandOptions;
85
- const resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
94
+ let resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
86
95
  const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
87
96
  if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync)) {
88
97
  if (resolution.source === "path" && !nodeSupport.supported) {
89
98
  (options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
90
99
  return 1;
91
100
  }
92
- (options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
93
- return 1;
101
+ const provisioned = await provisionMissingCodegraph({
102
+ config: codegraphConfig,
103
+ ensureProvisioned: options.ensureProvisioned ?? ensureCodegraphProvisioned,
104
+ homeDir,
105
+ resolution,
106
+ });
107
+ if (provisioned === null) {
108
+ (options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
109
+ return 1;
110
+ }
111
+ resolution = provisioned;
94
112
  }
95
113
 
96
114
  if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
@@ -110,6 +128,25 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
110
128
  });
111
129
  }
112
130
 
131
+ async function provisionMissingCodegraph(options: {
132
+ readonly config: CodegraphConfig;
133
+ readonly ensureProvisioned: CodegraphProvisioner;
134
+ readonly homeDir: string;
135
+ readonly resolution: CodegraphCommandResolution;
136
+ }): Promise<CodegraphCommandResolution | null> {
137
+ if (options.resolution.source === "env") return null;
138
+ if (options.config.auto_provision === false) return null;
139
+
140
+ const installDir = options.config.install_dir ?? join(options.homeDir, ".omo", "codegraph");
141
+ const result = await options.ensureProvisioned({
142
+ installDir,
143
+ lockDir: join(installDir, ".locks"),
144
+ version: CODEGRAPH_VERSION,
145
+ });
146
+ if (!result.provisioned || result.binPath === undefined) return null;
147
+ return { argsPrefix: [], command: result.binPath, exists: true, source: "provisioned" };
148
+ }
149
+
113
150
  function shouldSkipResolvedCommand(
114
151
  resolution: CodegraphCommandResolution,
115
152
  commandExists: (filePath: string) => boolean,