oh-my-opencode 4.19.2 → 4.19.4

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 (241) hide show
  1. package/.agents/command/get-unpublished-changes.md +2 -0
  2. package/.agents/command/omomomo.md +1 -1
  3. package/.agents/command/publish.md +102 -28
  4. package/.agents/skills/get-unpublished-changes/SKILL.md +2 -0
  5. package/.agents/skills/hyperplan/SKILL.md +3 -3
  6. package/.agents/skills/omomomo/SKILL.md +1 -1
  7. package/.agents/skills/publish/SKILL.md +27 -6
  8. package/.opencode/command/get-unpublished-changes.md +2 -0
  9. package/.opencode/command/omomomo.md +1 -1
  10. package/.opencode/command/publish.md +102 -28
  11. package/.opencode/skills/hyperplan/SKILL.md +3 -3
  12. package/THIRD-PARTY-NOTICES.md +7 -7
  13. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  14. package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +1 -1
  15. package/dist/cli/config-migrate.d.ts +8 -0
  16. package/dist/cli/doctor/checks/deprecated-reasoning-keys.d.ts +2 -0
  17. package/dist/cli/doctor/checks/legacy-config-leftovers.d.ts +10 -0
  18. package/dist/cli/doctor/checks/model-resolution-config.d.ts +1 -1
  19. package/dist/cli/doctor/checks/tools-lsp.d.ts +1 -1
  20. package/dist/cli/doctor/framework/constants.d.ts +1 -1
  21. package/dist/cli/index.js +95684 -92814
  22. package/dist/cli-node/index.js +95650 -92780
  23. package/dist/config/schema/agent-overrides.d.ts +800 -0
  24. package/dist/config/schema/categories.d.ts +132 -0
  25. package/dist/config/schema/fallback-models.d.ts +50 -0
  26. package/dist/config/schema/oh-my-opencode-config.d.ts +818 -2
  27. package/dist/config/validate.d.ts +2 -1
  28. package/dist/config-migration/deep-diff.d.ts +1 -0
  29. package/dist/config-migration/discovery-paths.d.ts +11 -0
  30. package/dist/config-migration/discovery-roots.d.ts +7 -0
  31. package/dist/config-migration/discovery.d.ts +4 -0
  32. package/dist/config-migration/index.d.ts +10 -0
  33. package/dist/config-migration/legacy-history.d.ts +3 -0
  34. package/dist/config-migration/migration-executor.d.ts +4 -0
  35. package/dist/config-migration/migration-plans.d.ts +15 -0
  36. package/dist/config-migration/reasoning-unification.d.ts +3 -0
  37. package/dist/config-migration/record-values.d.ts +4 -0
  38. package/dist/config-migration/schema-url.d.ts +1 -0
  39. package/dist/config-migration/transform-config-jsonc.d.ts +2 -0
  40. package/dist/config-migration/transform-opencode.d.ts +2 -0
  41. package/dist/config-migration/transform-types.d.ts +24 -0
  42. package/dist/config-migration/types.d.ts +39 -0
  43. package/dist/features/builtin-commands/templates/hyperplan.d.ts +1 -1
  44. package/dist/features/team-mode/tools/lifecycle-test-fixture.d.ts +2 -0
  45. package/dist/hooks/auto-update-checker/constants.d.ts +3 -3
  46. package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +1 -0
  47. package/dist/hooks/model-fallback/next-fallback.d.ts +1 -0
  48. package/dist/hooks/runtime-fallback/constants.d.ts +1 -1
  49. package/dist/index.js +12368 -9788
  50. package/dist/oh-my-opencode.schema.json +1672 -8
  51. package/dist/plugin-config/omo-config-chain.d.ts +14 -0
  52. package/dist/plugin-config/unknown-key-diagnostics.d.ts +5 -0
  53. package/dist/plugin-config.d.ts +2 -2
  54. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +1 -0
  55. package/dist/shared/agent-display-names.d.ts +1 -1
  56. package/dist/shared/agent-variant.d.ts +11 -0
  57. package/dist/shared/jsonc-parser.d.ts +1 -1
  58. package/dist/shared/migration.d.ts +5 -1
  59. package/dist/shared/opencode-config-dir-types.d.ts +0 -1
  60. package/dist/shared/plugin-identity.d.ts +6 -8
  61. package/dist/shared/project-discovery-dirs.d.ts +0 -1
  62. package/dist/shared/session-prompt-params-helpers.d.ts +6 -1
  63. package/dist/skills/coding-agent-sessions/SKILL.md +4 -3
  64. package/dist/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  65. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +140 -0
  66. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +3 -0
  67. package/dist/skills/data-scientist/SKILL.md +243 -0
  68. package/dist/skills/data-scientist/references/common-scenarios.md +176 -0
  69. package/dist/skills/data-scientist/references/execution-templates.md +197 -0
  70. package/dist/skills/data-scientist/references/integration-patterns.md +153 -0
  71. package/dist/skills/data-scientist/references/performance-benchmarks.md +37 -0
  72. package/dist/skills/data-scientist/references/uv-setup.md +78 -0
  73. package/dist/skills/data-scientist/scripts/quick-query.py +111 -0
  74. package/dist/skills/data-scientist/scripts/setup-uv.ps1 +53 -0
  75. package/dist/skills/data-scientist/scripts/setup-uv.sh +60 -0
  76. package/dist/skills/debugging/SKILL.md +1 -1
  77. package/dist/skills/frontend/ATTRIBUTION.md +7 -0
  78. package/dist/skills/frontend/SKILL.md +4 -1
  79. package/dist/skills/frontend/references/design/README.md +8 -0
  80. package/dist/skills/frontend/references/design/_INDEX.md +14 -1
  81. package/dist/skills/frontend/references/design/interaction-skill.md +144 -0
  82. package/dist/skills/programming/SKILL.md +1 -2
  83. package/dist/skills/ulw-plan/SKILL.md +1 -1
  84. package/dist/skills/ulw-research/SKILL.md +122 -11
  85. package/dist/startup-migration.d.ts +28 -0
  86. package/dist/testing/create-plugin-module.d.ts +4 -0
  87. package/dist/tools/delegate-task/builtin-categories.d.ts +1 -0
  88. package/dist/tools/delegate-task/builtin-category-definition.d.ts +1 -0
  89. package/dist/tools/delegate-task/constants.d.ts +1 -1
  90. package/dist/tui.js +6040 -5443
  91. package/package.json +13 -13
  92. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +21 -5
  93. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +11 -2
  94. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +2 -2
  95. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  96. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +21 -3
  97. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  98. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  99. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +11 -5
  100. package/packages/omo-codex/plugin/components/codegraph/NOTICE +1 -1
  101. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +10373 -2570
  102. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +7502 -574
  103. package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
  104. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +33 -0
  105. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +36 -10
  106. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +164 -159
  107. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +34 -0
  108. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +20 -2
  109. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +106 -0
  110. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +145 -0
  111. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +21 -0
  112. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +139 -0
  113. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +15 -0
  114. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +32 -0
  115. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +109 -0
  116. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +148 -0
  117. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +126 -175
  118. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +4 -4
  119. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +160 -0
  120. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +12 -138
  121. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +34 -24
  122. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +3 -3
  123. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +2 -2
  124. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +5 -5
  125. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -3
  126. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +1 -1
  127. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +45 -0
  128. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +2 -2
  129. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +70 -13
  130. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +58 -0
  131. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +104 -0
  132. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +4 -2
  133. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +116 -0
  134. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +58 -57
  135. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  136. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  137. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  138. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  139. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  140. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  141. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  142. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  143. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  144. package/packages/omo-codex/plugin/components/rules/dist/cli.js +32 -15
  145. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  146. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  147. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +1 -1
  148. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  149. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  150. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  151. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  152. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  153. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  154. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  155. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  156. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +1 -1
  157. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  158. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  159. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +17 -0
  160. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  161. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  162. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  163. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  164. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  165. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  166. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  167. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  168. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  169. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  170. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  171. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  183. package/packages/omo-codex/plugin/package-lock.json +42 -41
  184. package/packages/omo-codex/plugin/package.json +1 -1
  185. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -0
  186. package/packages/omo-codex/plugin/shared/src/config-loader.ts +165 -16
  187. package/packages/omo-codex/plugin/shared/src/config-migration.ts +162 -0
  188. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +186 -9
  189. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +4 -3
  190. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  191. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +140 -0
  192. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +3 -0
  193. package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +243 -0
  194. package/packages/omo-codex/plugin/skills/data-scientist/agents/openai.yaml +2 -0
  195. package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +176 -0
  196. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +197 -0
  197. package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +153 -0
  198. package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +37 -0
  199. package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +78 -0
  200. package/packages/omo-codex/plugin/skills/data-scientist/scripts/quick-query.py +111 -0
  201. package/packages/omo-codex/plugin/skills/data-scientist/scripts/setup-uv.ps1 +53 -0
  202. package/packages/omo-codex/plugin/skills/data-scientist/scripts/setup-uv.sh +60 -0
  203. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +1 -1
  204. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -0
  205. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -1
  206. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +8 -0
  207. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +14 -1
  208. package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +144 -0
  209. package/packages/omo-codex/plugin/skills/programming/SKILL.md +1 -2
  210. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +17 -0
  211. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +1 -1
  212. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +121 -11
  213. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -0
  214. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +12 -0
  215. package/packages/omo-codex/scripts/install-dist/install-local.mjs +24 -5
  216. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +4 -3
  217. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  218. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +140 -0
  219. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +3 -0
  220. package/packages/shared-skills/skills/data-scientist/SKILL.md +243 -0
  221. package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +176 -0
  222. package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +197 -0
  223. package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +153 -0
  224. package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +37 -0
  225. package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +78 -0
  226. package/packages/shared-skills/skills/data-scientist/scripts/quick-query.py +111 -0
  227. package/packages/shared-skills/skills/data-scientist/scripts/setup-uv.ps1 +53 -0
  228. package/packages/shared-skills/skills/data-scientist/scripts/setup-uv.sh +60 -0
  229. package/packages/shared-skills/skills/debugging/SKILL.md +1 -1
  230. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -0
  231. package/packages/shared-skills/skills/frontend/SKILL.md +4 -1
  232. package/packages/shared-skills/skills/frontend/references/design/README.md +8 -0
  233. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +14 -1
  234. package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +144 -0
  235. package/packages/shared-skills/skills/programming/SKILL.md +1 -2
  236. package/packages/shared-skills/skills/ulw-plan/SKILL.md +1 -1
  237. package/packages/shared-skills/skills/ulw-research/SKILL.md +122 -11
  238. package/dist/plugin-config/layered-config-loader.d.ts +0 -2
  239. package/dist/plugin-config/single-config-loader.d.ts +0 -4
  240. package/dist/shared/migrate-legacy-config-file.d.ts +0 -1
  241. package/dist/shared/migration/config-migration.d.ts +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-codegraph",
3
- "version": "4.19.2",
3
+ "version": "4.19.4",
4
4
  "description": "Codex plugin MCP wrapper for CodeGraph.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -25,7 +25,7 @@
25
25
  "typescript": "^6.0.3"
26
26
  },
27
27
  "optionalDependencies": {
28
- "@colbymchenry/codegraph": "1.4.1"
28
+ "@colbymchenry/codegraph": "1.5.0"
29
29
  },
30
30
  "engines": {
31
31
  "node": ">=20.0.0"
@@ -0,0 +1,33 @@
1
+ import { homedir } from "node:os";
2
+ import type { Readable } from "node:stream";
3
+
4
+ export function resolveHomeDir(env: Record<string, string | undefined>): string {
5
+ return env["HOME"] ?? env["USERPROFILE"] ?? homedir();
6
+ }
7
+
8
+ export function resolveHookProjectRoot(input: unknown, fallback: string): string {
9
+ if (!isRecord(input)) return fallback;
10
+ const cwd = input["cwd"];
11
+ return typeof cwd === "string" && cwd.trim().length > 0 ? cwd : fallback;
12
+ }
13
+
14
+ export async function readHookInput(stdin: Readable & { readonly isTTY?: boolean }): Promise<unknown> {
15
+ if (stdin.isTTY === true) return undefined;
16
+ let text = "";
17
+ for await (const chunk of stdin) text += typeof chunk === "string" ? chunk : String(chunk);
18
+ if (text.trim().length === 0) return undefined;
19
+ return parseJson(text);
20
+ }
21
+
22
+ export function isRecord(value: unknown): value is Record<string, unknown> {
23
+ return typeof value === "object" && value !== null;
24
+ }
25
+
26
+ function parseJson(text: string): unknown {
27
+ try {
28
+ return JSON.parse(text);
29
+ } catch (error) {
30
+ if (error instanceof SyntaxError) return undefined;
31
+ throw error;
32
+ }
33
+ }
@@ -10,10 +10,32 @@ import type {
10
10
  } from "../../../../../utils/src/codegraph/resolve.ts";
11
11
  import type { CodegraphWorkspacePreparation as SharedCodegraphWorkspacePreparation } from "../../../../../utils/src/codegraph/workspace.ts";
12
12
  import type { CodegraphConfig as SharedCodegraphConfig } from "../../../../../utils/src/omo-config.ts";
13
+ import type { CodegraphAncestorState, CodegraphProjectState } from "./session-start-project.js";
13
14
 
14
- export type SessionStartAction = "skipped-disabled" | "skipped-excluded" | "skipped-initialized" | "spawned";
15
+ export type SessionStartAction =
16
+ | "skipped-cooldown"
17
+ | "skipped-disabled"
18
+ | "skipped-excluded"
19
+ | "skipped-inconclusive"
20
+ | "skipped-initialized"
21
+ | "skipped-locked"
22
+ | "skipped-nested-root"
23
+ | "skipped-spawn-error"
24
+ | "spawned";
15
25
  export type PostToolUseAction = "emitted-guidance" | "skipped";
16
- export type WorkerAction = "failed" | "initialized" | "skipped-disabled" | "skipped-status" | "skipped-unavailable" | "skipped-unsupported-node" | "synced";
26
+ export type WorkerAction =
27
+ | "failed"
28
+ | "initialized"
29
+ | "skipped-disabled"
30
+ | "skipped-initialized"
31
+ | "skipped-locked"
32
+ | "skipped-nested-root"
33
+ | "skipped-probe-error"
34
+ | "skipped-status"
35
+ | "skipped-unavailable"
36
+ | "skipped-unsupported-node"
37
+ | "synced";
38
+ export type SessionStartOutcomeAction = SessionStartAction | WorkerAction;
17
39
 
18
40
  export interface WorkerSpawnInvocation {
19
41
  readonly args: readonly string[];
@@ -49,9 +71,12 @@ export type CodegraphProvisionResult = SharedCodegraphProvisionResult;
49
71
  export type CodegraphWorkspacePreparation = SharedCodegraphWorkspacePreparation;
50
72
 
51
73
  export interface CodegraphSessionStartOutcome {
52
- readonly action: WorkerAction;
74
+ readonly action: SessionStartOutcomeAction;
75
+ readonly ancestorRoot?: string;
76
+ readonly cooldownUntil?: number;
53
77
  readonly error?: string;
54
78
  readonly exitCode?: number;
79
+ readonly failureCount?: number;
55
80
  readonly projectRoot?: string;
56
81
  readonly source?: CodegraphCommandResolution["source"];
57
82
  readonly timedOut?: boolean;
@@ -60,8 +85,10 @@ export interface CodegraphSessionStartOutcome {
60
85
  export interface CodegraphSessionStartDeps {
61
86
  readonly ensureGitignored: (projectRoot: string) => boolean;
62
87
  readonly ensureProvisioned: (options: { readonly installDir?: string; readonly lockDir: string; readonly version: typeof CODEGRAPH_PINNED_VERSION }) => Promise<CodegraphProvisionResult>;
88
+ readonly managedInstallExists: (installDir: string) => boolean;
63
89
  readonly prepareWorkspace: (projectRoot: string, options: { readonly homeDir: string }) => CodegraphWorkspacePreparation;
64
90
  readonly resolveCommand: (options?: ResolveCodegraphCommandOptions) => CodegraphCommandResolution;
91
+ readonly resolveManagedBin: (installDir: string) => string | null;
65
92
  readonly runCommand: (
66
93
  projectRoot: string,
67
94
  command: string,
@@ -71,18 +98,15 @@ export interface CodegraphSessionStartDeps {
71
98
  }
72
99
 
73
100
  export interface SessionStartHookOptions {
101
+ readonly ancestorProbe?: (projectRoot: string) => CodegraphAncestorState;
74
102
  readonly argv?: readonly string[];
75
103
  readonly config?: CodexOmoConfig;
76
104
  readonly cwd?: string;
77
105
  readonly env?: Record<string, string | undefined>;
106
+ readonly lockStaleMs?: number;
107
+ readonly logOutcome?: (outcome: CodegraphSessionStartOutcome) => void;
108
+ readonly nowMs?: number;
78
109
  readonly spawnWorker?: (invocation: WorkerSpawnInvocation) => void;
79
- readonly statusProbe?: (options: {
80
- readonly daemon: boolean;
81
- readonly env: Record<string, string | undefined>;
82
- readonly homeDir: string;
83
- readonly projectRoot: string;
84
- readonly trustedCodegraphInstallDir?: string;
85
- }) => Promise<boolean>;
86
110
  readonly stdin?: Readable & { readonly isTTY?: boolean };
87
111
  readonly stdout?: HookStdout;
88
112
  readonly sweepZombies?: (options: SweepCodegraphZombiesOptions) => Promise<unknown> | unknown;
@@ -102,4 +126,6 @@ export interface SessionStartWorkerOptions {
102
126
  readonly env?: Record<string, string | undefined>;
103
127
  readonly logOutcome?: (outcome: CodegraphSessionStartOutcome) => void;
104
128
  readonly nodeVersion?: string;
129
+ readonly nowMs?: number;
130
+ readonly projectProbe?: (projectRoot: string) => CodegraphProjectState;
105
131
  }
@@ -1,5 +1,3 @@
1
- import { spawn } from "node:child_process";
2
- import { homedir } from "node:os";
3
1
  import {
4
2
  cwd as processCwd,
5
3
  env as processEnv,
@@ -7,27 +5,33 @@ import {
7
5
  stdin as processStdin,
8
6
  stdout as processStdout,
9
7
  } from "node:process";
10
- import type { Readable } from "node:stream";
11
8
  import { fileURLToPath } from "node:url";
12
9
 
13
- import { buildCodegraphChildEnv, buildCodegraphEnv } from "../../../../../utils/src/codegraph/env.ts";
14
- import { buildCodegraphInitGuidanceForToolResult } from "../../../../../utils/src/codegraph/guidance.ts";
15
- import { resolvePinnedCodegraphBin } from "../../../../../utils/src/codegraph/managed-runtime.ts";
16
- import { resolveCodegraphCommand } from "../../../../../utils/src/codegraph/resolve.ts";
17
- import { runProcessWithTreeTimeout } from "../../../../../utils/src/process-tree.ts";
10
+ import { buildCodegraphChildEnv } from "../../../../../utils/src/codegraph/env.ts";
18
11
  import { shouldExcludeCodegraphProject } from "../../../../../utils/src/codegraph/workspace.ts";
19
12
  import { getCodexOmoConfig } from "../../../shared/src/config-loader.ts";
20
13
  import { pruneCodegraphProjectStoresBestEffort } from "./cache-gc.js";
14
+ import { readHookInput, resolveHomeDir, resolveHookProjectRoot } from "./hook-input.js";
21
15
  import { sweepCodegraphZombiesBestEffort } from "./hook-sweep.js";
22
- import { resolveCodegraphCommandInvocation, SESSION_START_CWD_ENV } from "./session-start-worker.js";
23
16
  import type {
24
- HookStdout,
25
- PostToolUseHookOptions,
26
- PostToolUseHookResult,
17
+ CodegraphSessionStartOutcome,
27
18
  SessionStartHookOptions,
28
19
  SessionStartHookResult,
29
- WorkerSpawnInvocation,
30
20
  } from "./hook-types.js";
21
+ import {
22
+ DEFAULT_SESSION_START_COOLDOWN_MS,
23
+ readSessionStartCooldown,
24
+ recordSessionStartFailure,
25
+ } from "./session-start-cooldown.js";
26
+ import { spawnDetachedSessionStartWorker, writeSessionStartNotice } from "./session-start-hook-runtime.js";
27
+ import { acquireSessionStartLock, releaseSessionStartLock, type SessionStartLock } from "./session-start-lock.js";
28
+ import { appendSessionStartOutcome } from "./session-start-outcome.js";
29
+ import {
30
+ probeCodegraphAncestors,
31
+ probeExactCodegraphProject,
32
+ type CodegraphProjectState,
33
+ } from "./session-start-project.js";
34
+ import { SESSION_START_CWD_ENV, SESSION_START_LOCK_TOKEN_ENV } from "./session-start-worker.js";
31
35
 
32
36
  export type {
33
37
  CodegraphCommandResult,
@@ -48,37 +52,45 @@ export type {
48
52
  WorkerAction,
49
53
  WorkerSpawnInvocation,
50
54
  } from "./hook-types.js";
51
- export { resolveCodegraphCommandInvocation, runCodegraphSessionStartWorker } from "./session-start-worker.js";
55
+ export {
56
+ executeCodegraphPostToolUseHook,
57
+ runCodegraphPostToolUseHook,
58
+ runCodegraphPostToolUseHookCli,
59
+ } from "./post-tool-use-hook.js";
60
+ export { resolveCodegraphCommandInvocation } from "./session-start-command.js";
61
+ export { runCodegraphSessionStartWorker } from "./session-start-worker.js";
52
62
 
53
63
  export const CODEGRAPH_SESSION_START_NOTICE = "LazyCodex CodeGraph bootstrap scheduled in background";
54
- const STATUS_PROBE_TIMEOUT_MS = 2_000;
64
+ export const DEFAULT_SESSION_START_LOCK_STALE_MS = 10 * 60 * 1_000;
65
+
66
+ type HookContext = {
67
+ readonly baseCooldownMs: number;
68
+ readonly env: Record<string, string | undefined>;
69
+ readonly homeDir: string;
70
+ readonly logOutcome: (outcome: CodegraphSessionStartOutcome) => void;
71
+ readonly nowMs: number;
72
+ readonly options: SessionStartHookOptions;
73
+ readonly projectRoot: string;
74
+ };
55
75
 
56
76
  export async function runCodegraphSessionStartHook(options: SessionStartHookOptions = {}): Promise<number> {
57
77
  return (await executeCodegraphSessionStartHook(options)).exitCode;
58
78
  }
59
79
 
60
- export async function runCodegraphPostToolUseHookCli(options: PostToolUseHookOptions = {}): Promise<number> {
61
- return (await executeCodegraphPostToolUseHook(options)).exitCode;
62
- }
63
-
64
80
  export async function executeCodegraphSessionStartHook(options: SessionStartHookOptions = {}): Promise<SessionStartHookResult> {
65
81
  const env = options.env ?? processEnv;
66
82
  const input = await readHookInput(options.stdin ?? processStdin);
67
- const projectRoot = resolveProjectRoot(input, options.cwd ?? processCwd());
83
+ const projectRoot = resolveHookProjectRoot(input, options.cwd ?? processCwd());
68
84
  const homeDir = resolveHomeDir(env);
69
85
  const config = options.config ?? getCodexOmoConfig({ cwd: projectRoot, env, homeDir });
86
+ if (config.codegraph?.enabled === false) return { action: "skipped-disabled", exitCode: 0 };
70
87
 
71
- if (config.codegraph?.enabled === false) {
72
- return { action: "skipped-disabled", exitCode: 0 };
73
- }
74
88
  const excludedRoots = config.codegraph?.excluded_roots;
75
89
  const exclusion = shouldExcludeCodegraphProject(projectRoot, {
76
90
  homeDir,
77
91
  ...(excludedRoots === undefined ? {} : { excludedRoots }),
78
92
  });
79
- if (exclusion.excluded) {
80
- return { action: "skipped-excluded", exitCode: 0 };
81
- }
93
+ if (exclusion.excluded) return { action: "skipped-excluded", exitCode: 0 };
82
94
 
83
95
  pruneCodegraphProjectStoresBestEffort(homeDir, { debugLog: writeDebugLog });
84
96
  await sweepCodegraphZombiesBestEffort({
@@ -88,164 +100,157 @@ export async function executeCodegraphSessionStartHook(options: SessionStartHook
88
100
  log: writeDebugLog,
89
101
  }, options.sweepZombies);
90
102
 
91
- const isInitialized = await (options.statusProbe ?? isCodegraphProjectInitialized)({
92
- daemon: config.codegraph?.daemon !== false,
103
+ const nowMs = options.nowMs ?? Date.now();
104
+ const context: HookContext = {
105
+ baseCooldownMs: config.codegraph?.session_start_cooldown_ms ?? DEFAULT_SESSION_START_COOLDOWN_MS,
93
106
  env,
94
107
  homeDir,
108
+ logOutcome: options.logOutcome ?? ((outcome) => appendSessionStartOutcome(homeDir, outcome, nowMs)),
109
+ nowMs,
110
+ options,
95
111
  projectRoot,
96
- ...(config.trustedCodegraphInstallDir === undefined ? {} : { trustedCodegraphInstallDir: config.trustedCodegraphInstallDir }),
97
- });
98
- if (isInitialized) {
99
- return { action: "skipped-initialized", exitCode: 0 };
100
- }
112
+ };
113
+ const projectDecision = handleProjectState(resolveHookProjectState(context), context);
114
+ if (projectDecision !== null) return projectDecision;
101
115
 
102
- (options.spawnWorker ?? spawnDetachedWorker)({
103
- args: [options.workerCliPath ?? defaultWorkerCliPath(), "hook", "session-start-worker"],
104
- command: process.execPath,
105
- env: buildCodegraphChildEnv({
106
- ambientEnv: env,
107
- codegraphEnv: { [SESSION_START_CWD_ENV]: projectRoot },
108
- runtimeEnv: env,
109
- }),
116
+ const cooldown = readSessionStartCooldown({
117
+ baseCooldownMs: context.baseCooldownMs,
118
+ homeDir,
119
+ nowMs,
120
+ projectRoot,
110
121
  });
111
- writeHookJson(options.stdout ?? processStdout);
112
- return { action: "spawned", exitCode: 0 };
113
- }
122
+ if (cooldown.kind === "inconclusive") return { action: "skipped-inconclusive", exitCode: 0 };
123
+ if (cooldown.kind === "active") {
124
+ safeLogOutcome(context.logOutcome, {
125
+ action: "skipped-cooldown",
126
+ cooldownUntil: cooldown.cooldownUntil,
127
+ failureCount: cooldown.failureCount,
128
+ projectRoot,
129
+ });
130
+ return { action: "skipped-cooldown", exitCode: 0 };
131
+ }
114
132
 
115
- async function isCodegraphProjectInitialized(options: {
116
- readonly daemon: boolean;
117
- readonly env: Record<string, string | undefined>;
118
- readonly homeDir: string;
119
- readonly projectRoot: string;
120
- readonly trustedCodegraphInstallDir?: string;
121
- }): Promise<boolean> {
122
- const resolved = resolveCodegraphCommand({
123
- env: options.env,
124
- homeDir: options.homeDir,
125
- provisioned: () => provisionedBinFromInstallDir(options.trustedCodegraphInstallDir),
133
+ const lockResult = acquireSessionStartLock({
134
+ homeDir,
135
+ nowMs,
136
+ projectRoot,
137
+ staleMs: options.lockStaleMs ?? DEFAULT_SESSION_START_LOCK_STALE_MS,
126
138
  });
127
- if (!resolved.exists) return false;
128
-
129
- const invocation = resolveCodegraphCommandInvocation(resolved.command, [...resolved.argsPrefix, "status", "--json"]);
130
- const codegraphEnv = {
131
- ...buildCodegraphEnv({ daemon: options.daemon, homeDir: options.homeDir }),
132
- ...(options.trustedCodegraphInstallDir === undefined ? {} : { CODEGRAPH_INSTALL_DIR: options.trustedCodegraphInstallDir }),
133
- };
134
- const status = await runStatusProbe(
135
- options.projectRoot,
136
- invocation.command,
137
- invocation.args,
138
- buildCodegraphChildEnv({ ambientEnv: options.env, codegraphEnv, runtimeEnv: options.env }),
139
- );
140
- if (status.exitCode !== 0 || status.timedOut) return false;
141
- return codegraphStatusSaysInitialized(status.stdout);
142
- }
143
-
144
- function runStatusProbe(
145
- projectRoot: string,
146
- command: string,
147
- args: readonly string[],
148
- env: Record<string, string>,
149
- ): Promise<{ readonly exitCode: number; readonly stdout: string; readonly timedOut: boolean }> {
150
- return runProcessWithTreeTimeout({
151
- args,
152
- command,
153
- cwd: projectRoot,
154
- env,
155
- maxBuffer: 1024 * 1024,
156
- timeoutMs: STATUS_PROBE_TIMEOUT_MS,
157
- }).then(({ exitCode, stdout, timedOut }) => ({ exitCode, stdout, timedOut }));
158
- }
159
-
160
- function codegraphStatusSaysInitialized(stdout: string): boolean {
161
- const parsed = parseJson(stdout);
162
- if (!isRecord(parsed)) return false;
163
- const initialized = parsed["initialized"] ?? parsed["isInitialized"] ?? parsed["ready"];
164
- if (typeof initialized === "boolean") return initialized;
165
- const status = parsed["status"];
166
- if (typeof status !== "string") return false;
167
- const normalized = status.toLowerCase();
168
- return (normalized.includes("initialized") || normalized.includes("ready")) && !normalized.includes("not initialized") && !normalized.includes("uninitialized");
169
- }
170
-
171
- function provisionedBinFromInstallDir(installDir: string | undefined): string | null {
172
- return resolvePinnedCodegraphBin(installDir);
173
- }
174
-
175
- export async function executeCodegraphPostToolUseHook(options: PostToolUseHookOptions = {}): Promise<PostToolUseHookResult> {
176
- const env = options.env ?? processEnv;
177
- const input = await readHookInput(options.stdin ?? processStdin);
178
- const output = runCodegraphPostToolUseHook(input, { homeDir: resolveHomeDir(env) });
179
- if (output.length === 0) return { action: "skipped", exitCode: 0 };
180
-
181
- (options.stdout ?? processStdout).write(output);
182
- return { action: "emitted-guidance", exitCode: 0 };
183
- }
139
+ if (lockResult.kind === "inconclusive") return { action: "skipped-inconclusive", exitCode: 0 };
140
+ if (lockResult.kind === "locked") {
141
+ safeLogOutcome(context.logOutcome, { action: "skipped-locked", projectRoot });
142
+ return { action: "skipped-locked", exitCode: 0 };
143
+ }
144
+ if (lockResult.recoveredStale) return handleRecoveredStaleLock(lockResult.lock, context);
184
145
 
185
- export function runCodegraphPostToolUseHook(input: unknown, options: { readonly homeDir?: string } = {}): string {
186
- const toolName = isRecord(input) ? input["tool_name"] : undefined;
187
- const cwd = isRecord(input) ? input["cwd"] : undefined;
188
- const toolOutput = isRecord(input) ? (input["tool_response"] ?? input["tool_output"] ?? input["response"]) : input;
189
- const guidance = buildCodegraphInitGuidanceForToolResult({ cwd, toolName, toolOutput }, options);
190
- if (guidance === null) return "";
191
-
192
- return `${JSON.stringify({
193
- hookSpecificOutput: {
194
- hookEventName: "PostToolUse",
195
- additionalContext: guidance,
196
- },
197
- })}\n`;
198
- }
146
+ const lockedProjectDecision = handleProjectState(resolveHookProjectState(context), context);
147
+ if (lockedProjectDecision !== null) {
148
+ releaseSessionStartLock(lockResult.lock);
149
+ return lockedProjectDecision;
150
+ }
199
151
 
200
- function writeHookJson(stdout: HookStdout): void {
201
- const output = {
202
- hookSpecificOutput: {
203
- hookEventName: "SessionStart",
204
- additionalContext: CODEGRAPH_SESSION_START_NOTICE,
205
- },
206
- };
207
- stdout.write(`${JSON.stringify(output)}\n`);
208
- }
152
+ try {
153
+ (options.spawnWorker ?? spawnDetachedSessionStartWorker)({
154
+ args: [options.workerCliPath ?? defaultWorkerCliPath(), "hook", "session-start-worker"],
155
+ command: process.execPath,
156
+ env: buildCodegraphChildEnv({
157
+ ambientEnv: env,
158
+ codegraphEnv: {
159
+ [SESSION_START_CWD_ENV]: projectRoot,
160
+ [SESSION_START_LOCK_TOKEN_ENV]: lockResult.lock.token,
161
+ },
162
+ runtimeEnv: env,
163
+ }),
164
+ });
165
+ } catch (error) {
166
+ if (error instanceof Error) return handleSpawnFailure(error, lockResult.lock, context);
167
+ throw error;
168
+ }
209
169
 
210
- function writeDebugLog(message: string): void {
211
- if (processEnv["OMO_CODEGRAPH_DEBUG"] !== "1") return;
212
- processStderr.write(`${message}\n`);
170
+ writeSessionStartNotice(options.stdout ?? processStdout, CODEGRAPH_SESSION_START_NOTICE);
171
+ return { action: "spawned", exitCode: 0 };
213
172
  }
214
173
 
215
- function spawnDetachedWorker(invocation: WorkerSpawnInvocation): void {
216
- const child = spawn(invocation.command, [...invocation.args], { detached: true, env: invocation.env, stdio: "ignore" });
217
- child.unref();
174
+ function resolveHookProjectState(context: HookContext): CodegraphProjectState {
175
+ const exact = probeExactCodegraphProject(context.projectRoot);
176
+ if (exact.kind !== "uninitialized") return exact;
177
+ return (context.options.ancestorProbe ?? probeCodegraphAncestors)(context.projectRoot);
218
178
  }
219
179
 
220
- function resolveHomeDir(env: Record<string, string | undefined>): string {
221
- return env["HOME"] ?? env["USERPROFILE"] ?? homedir();
180
+ function handleProjectState(state: CodegraphProjectState, context: HookContext): SessionStartHookResult | null {
181
+ switch (state.kind) {
182
+ case "inconclusive":
183
+ return { action: "skipped-inconclusive", exitCode: 0 };
184
+ case "initialized":
185
+ return { action: "skipped-initialized", exitCode: 0 };
186
+ case "nested-root":
187
+ safeLogOutcome(context.logOutcome, { action: "skipped-nested-root", ancestorRoot: state.ancestorRoot, projectRoot: context.projectRoot });
188
+ return { action: "skipped-nested-root", exitCode: 0 };
189
+ case "uninitialized":
190
+ return null;
191
+ }
222
192
  }
223
193
 
224
- function resolveProjectRoot(input: unknown, fallback: string): string {
225
- if (!isRecord(input)) return fallback;
226
- const cwd = input["cwd"];
227
- return typeof cwd === "string" && cwd.trim().length > 0 ? cwd : fallback;
194
+ function handleRecoveredStaleLock(lock: SessionStartLock, context: HookContext): SessionStartHookResult {
195
+ try {
196
+ const cooldown = recordSessionStartFailure({
197
+ action: "stale-lock",
198
+ baseCooldownMs: context.baseCooldownMs,
199
+ homeDir: context.homeDir,
200
+ nowMs: context.nowMs,
201
+ projectRoot: context.projectRoot,
202
+ });
203
+ releaseSessionStartLock(lock);
204
+ safeLogOutcome(context.logOutcome, {
205
+ action: "skipped-cooldown",
206
+ cooldownUntil: cooldown.cooldownUntil,
207
+ error: "recovered stale SessionStart worker lock",
208
+ failureCount: cooldown.failureCount,
209
+ projectRoot: context.projectRoot,
210
+ });
211
+ return { action: "skipped-cooldown", exitCode: 0 };
212
+ } catch (error) {
213
+ if (error instanceof Error) return { action: "skipped-inconclusive", exitCode: 0 };
214
+ throw error;
215
+ }
228
216
  }
229
217
 
230
- async function readHookInput(stdin: Readable & { readonly isTTY?: boolean }): Promise<unknown> {
231
- if (stdin.isTTY === true) return undefined;
232
- let text = "";
233
- for await (const chunk of stdin) text += typeof chunk === "string" ? chunk : String(chunk);
234
- if (text.trim().length === 0) return undefined;
235
- return parseJson(text);
218
+ function handleSpawnFailure(error: unknown, lock: SessionStartLock, context: HookContext): SessionStartHookResult {
219
+ try {
220
+ const cooldown = recordSessionStartFailure({
221
+ action: "spawn-failed",
222
+ baseCooldownMs: context.baseCooldownMs,
223
+ homeDir: context.homeDir,
224
+ nowMs: context.nowMs,
225
+ projectRoot: context.projectRoot,
226
+ });
227
+ releaseSessionStartLock(lock);
228
+ safeLogOutcome(context.logOutcome, {
229
+ action: "skipped-spawn-error",
230
+ cooldownUntil: cooldown.cooldownUntil,
231
+ error: error instanceof Error ? error.message : String(error),
232
+ failureCount: cooldown.failureCount,
233
+ projectRoot: context.projectRoot,
234
+ });
235
+ } catch (cooldownError) {
236
+ if (cooldownError instanceof Error) return { action: "skipped-inconclusive", exitCode: 0 };
237
+ throw cooldownError;
238
+ }
239
+ return { action: "skipped-spawn-error", exitCode: 0 };
236
240
  }
237
241
 
238
- function parseJson(text: string): unknown {
242
+ function safeLogOutcome(logOutcome: (outcome: CodegraphSessionStartOutcome) => void, outcome: CodegraphSessionStartOutcome): void {
239
243
  try {
240
- return JSON.parse(text);
244
+ logOutcome(outcome);
241
245
  } catch (error) {
242
- if (error instanceof SyntaxError) return undefined;
243
- throw error;
246
+ if (error instanceof Error) writeDebugLog(`failed to write SessionStart outcome: ${error.message}`);
247
+ else throw error;
244
248
  }
245
249
  }
246
250
 
247
- function isRecord(value: unknown): value is Record<string, unknown> {
248
- return typeof value === "object" && value !== null;
251
+ function writeDebugLog(message: string): void {
252
+ if (processEnv["OMO_CODEGRAPH_DEBUG"] !== "1") return;
253
+ processStderr.write(`${message}\n`);
249
254
  }
250
255
 
251
256
  function defaultWorkerCliPath(): string {
@@ -0,0 +1,34 @@
1
+ import { env as processEnv, stdin as processStdin, stdout as processStdout } from "node:process";
2
+
3
+ import { buildCodegraphInitGuidanceForToolResult } from "../../../../../utils/src/codegraph/guidance.ts";
4
+ import { isRecord, readHookInput, resolveHomeDir } from "./hook-input.js";
5
+ import type { PostToolUseHookOptions, PostToolUseHookResult } from "./hook-types.js";
6
+
7
+ export async function runCodegraphPostToolUseHookCli(options: PostToolUseHookOptions = {}): Promise<number> {
8
+ return (await executeCodegraphPostToolUseHook(options)).exitCode;
9
+ }
10
+
11
+ export async function executeCodegraphPostToolUseHook(options: PostToolUseHookOptions = {}): Promise<PostToolUseHookResult> {
12
+ const env = options.env ?? processEnv;
13
+ const input = await readHookInput(options.stdin ?? processStdin);
14
+ const output = runCodegraphPostToolUseHook(input, { homeDir: resolveHomeDir(env) });
15
+ if (output.length === 0) return { action: "skipped", exitCode: 0 };
16
+
17
+ (options.stdout ?? processStdout).write(output);
18
+ return { action: "emitted-guidance", exitCode: 0 };
19
+ }
20
+
21
+ export function runCodegraphPostToolUseHook(input: unknown, options: { readonly homeDir?: string } = {}): string {
22
+ const toolName = isRecord(input) ? input["tool_name"] : undefined;
23
+ const cwd = isRecord(input) ? input["cwd"] : undefined;
24
+ const toolOutput = isRecord(input) ? (input["tool_response"] ?? input["tool_output"] ?? input["response"]) : input;
25
+ const guidance = buildCodegraphInitGuidanceForToolResult({ cwd, toolName, toolOutput }, options);
26
+ if (guidance === null) return "";
27
+
28
+ return `${JSON.stringify({
29
+ hookSpecificOutput: {
30
+ hookEventName: "PostToolUse",
31
+ additionalContext: guidance,
32
+ },
33
+ })}\n`;
34
+ }
@@ -16,7 +16,7 @@ import {
16
16
  buildCodegraphChildEnv,
17
17
  buildCodegraphEnv,
18
18
  } from "../../../../../utils/src/codegraph/env.ts";
19
- import { resolvePinnedCodegraphBin } from "../../../../../utils/src/codegraph/managed-runtime.ts";
19
+ import { hasCodegraphManagedInstall, resolvePinnedCodegraphBin } from "../../../../../utils/src/codegraph/managed-runtime.ts";
20
20
  import type { ParentWatchdogConfig } from "../../../../../mcp-stdio-core/src/index.ts";
21
21
  import { CODEGRAPH_PINNED_VERSION } from "../../../../../utils/src/codegraph/manifest.ts";
22
22
  import {
@@ -73,6 +73,8 @@ export interface RunCodegraphServeOptions {
73
73
  readonly cwd?: string;
74
74
  readonly env?: Record<string, string | undefined>;
75
75
  readonly homeDir?: string;
76
+ readonly managedInstallExists?: (installDir: string) => boolean;
77
+ readonly resolveManagedBin?: (installDir: string) => string | null;
76
78
  readonly stdin?: Readable;
77
79
  readonly stdout?: Writable;
78
80
  readonly nodeVersion?: string;
@@ -114,12 +116,28 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
114
116
  }
115
117
 
116
118
  const trustedInstallDir = config.trustedCodegraphInstallDir;
119
+ const installDir = trustedInstallDir ?? join(homeDir, ".omo", "codegraph");
117
120
  const resolutionOptions = {
118
121
  env,
119
122
  homeDir,
120
- provisioned: () => provisionedBinFromInstallDir(trustedInstallDir),
123
+ provisioned: () => provisionedBinFromInstallDir(installDir),
121
124
  } satisfies ResolveCodegraphCommandOptions;
122
125
  let resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
126
+ const resolveManagedBin = options.resolveManagedBin ?? (options.resolve === undefined ? provisionedBinFromInstallDir : () => null);
127
+ const managedInstallExists = options.managedInstallExists ?? (options.resolve === undefined ? hasCodegraphManagedInstall : () => false);
128
+ const managedBin = resolveManagedBin(installDir);
129
+ if (resolution.source !== "env" && managedBin !== null) {
130
+ resolution = { argsPrefix: [], command: managedBin, exists: true, source: "provisioned" };
131
+ } else if (resolution.source !== "env" && codegraphConfig.auto_provision !== false && managedInstallExists(installDir)) {
132
+ const upgraded = await provisionMissingCodegraph({
133
+ config: codegraphConfig,
134
+ ensureProvisioned: options.ensureProvisioned ?? ensureCodegraphProvisioned,
135
+ homeDir,
136
+ resolution,
137
+ ...(trustedInstallDir === undefined ? {} : { trustedInstallDir }),
138
+ });
139
+ if (upgraded !== null) resolution = upgraded;
140
+ }
123
141
  const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
124
142
  if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync)) {
125
143
  if (resolution.source === "path" && !nodeSupport.supported) {