oh-my-opencode 4.19.1 → 4.19.3

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 (268) hide show
  1. package/.agents/command/publish.md +95 -28
  2. package/.agents/skills/publish/SKILL.md +20 -6
  3. package/.opencode/command/publish.md +95 -28
  4. package/README.ja.md +2 -2
  5. package/README.ko.md +2 -2
  6. package/README.md +2 -2
  7. package/README.ru.md +2 -2
  8. package/README.zh-cn.md +2 -2
  9. package/THIRD-PARTY-NOTICES.md +7 -7
  10. package/dist/agents/atlas/agent.d.ts +4 -2
  11. package/dist/agents/explore.d.ts +4 -2
  12. package/dist/agents/hephaestus/agent.d.ts +4 -2
  13. package/dist/agents/librarian.d.ts +4 -2
  14. package/dist/agents/metis.d.ts +4 -2
  15. package/dist/agents/momus.d.ts +3 -2
  16. package/dist/agents/multimodal-looker.d.ts +4 -2
  17. package/dist/agents/oracle.d.ts +4 -2
  18. package/dist/agents/sisyphus/claude-opus-5.d.ts +33 -0
  19. package/dist/agents/sisyphus/index.d.ts +2 -0
  20. package/dist/agents/sisyphus-agent-factory.d.ts +5 -2
  21. package/dist/agents/sisyphus-junior/agent.d.ts +4 -1
  22. package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +1 -1
  23. package/dist/agents/types.d.ts +2 -2
  24. package/dist/cli/config-migrate.d.ts +8 -0
  25. package/dist/cli/doctor/checks/legacy-config-leftovers.d.ts +10 -0
  26. package/dist/cli/doctor/checks/model-resolution-config.d.ts +1 -1
  27. package/dist/cli/doctor/checks/tools-lsp.d.ts +1 -1
  28. package/dist/cli/doctor/framework/constants.d.ts +1 -1
  29. package/dist/cli/index.js +95391 -88524
  30. package/dist/cli-node/index.js +95504 -88637
  31. package/dist/config/schema/agent-names.d.ts +26 -26
  32. package/dist/config/schema/agent-overrides.d.ts +400 -400
  33. package/dist/config/schema/browser-automation.d.ts +2 -2
  34. package/dist/config/schema/categories.d.ts +36 -36
  35. package/dist/config/schema/commands.d.ts +2 -2
  36. package/dist/config/schema/dynamic-context-pruning.d.ts +1 -1
  37. package/dist/config/schema/experimental.d.ts +1 -1
  38. package/dist/config/schema/fallback-models.d.ts +25 -25
  39. package/dist/config/schema/hooks.d.ts +43 -43
  40. package/dist/config/schema/internal/permission.d.ts +9 -9
  41. package/dist/config/schema/keyword-detector.d.ts +6 -6
  42. package/dist/config/schema/oh-my-opencode-config.d.ts +402 -402
  43. package/dist/config/schema/tmux.d.ts +6 -6
  44. package/dist/config/validate.d.ts +2 -1
  45. package/dist/config-migration/deep-diff.d.ts +1 -0
  46. package/dist/config-migration/discovery-paths.d.ts +11 -0
  47. package/dist/config-migration/discovery-roots.d.ts +7 -0
  48. package/dist/config-migration/discovery.d.ts +4 -0
  49. package/dist/config-migration/index.d.ts +9 -0
  50. package/dist/config-migration/legacy-history.d.ts +3 -0
  51. package/dist/config-migration/migration-executor.d.ts +4 -0
  52. package/dist/config-migration/migration-plans.d.ts +14 -0
  53. package/dist/config-migration/record-values.d.ts +4 -0
  54. package/dist/config-migration/schema-url.d.ts +1 -0
  55. package/dist/config-migration/transform-config-jsonc.d.ts +2 -0
  56. package/dist/config-migration/transform-opencode.d.ts +2 -0
  57. package/dist/config-migration/transform-types.d.ts +24 -0
  58. package/dist/config-migration/types.d.ts +39 -0
  59. package/dist/create-hooks.d.ts +35 -35
  60. package/dist/features/background-agent/spawner/tmux-callback-invoker.d.ts +11 -0
  61. package/dist/features/builtin-commands/templates/hyperplan.d.ts +1 -1
  62. package/dist/features/builtin-commands/templates/start-work.d.ts +1 -1
  63. package/dist/features/claude-tasks/types.d.ts +4 -4
  64. package/dist/features/team-mode/team-runtime/shutdown-helpers.d.ts +1 -1
  65. package/dist/features/team-mode/team-runtime/shutdown-test-fixtures.d.ts +4 -4
  66. package/dist/features/team-mode/tools/lifecycle-test-fixture.d.ts +42 -42
  67. package/dist/features/tmux-subagent/environment-eligibility.d.ts +2 -0
  68. package/dist/features/tmux-subagent/index.d.ts +1 -0
  69. package/dist/features/tmux-subagent/tracked-session-state.d.ts +1 -0
  70. package/dist/features/tui-sidebar/snapshot-schema.d.ts +6 -6
  71. package/dist/help/schema/acp.d.ts +3 -3
  72. package/dist/help/schema/doctor.d.ts +4 -4
  73. package/dist/help/schema/status.d.ts +3 -3
  74. package/dist/hooks/auto-update-checker/constants.d.ts +3 -3
  75. package/dist/hooks/goal/types.d.ts +5 -5
  76. package/dist/hooks/non-interactive-env/constants.d.ts +4 -1
  77. package/dist/hooks/start-work/parse-user-request.d.ts +2 -0
  78. package/dist/hooks/start-work/worktree-block.d.ts +5 -0
  79. package/dist/index.js +35450 -28257
  80. package/dist/mcp/types.d.ts +3 -3
  81. package/dist/oh-my-opencode.schema.json +1 -1
  82. package/dist/plugin/chat-message/loop-commands.d.ts +1 -0
  83. package/dist/plugin/command-execute-before.d.ts +3 -3
  84. package/dist/plugin/hooks/create-core-hooks.d.ts +25 -25
  85. package/dist/plugin-config/omo-config-chain.d.ts +14 -0
  86. package/dist/plugin-config/unknown-key-diagnostics.d.ts +5 -0
  87. package/dist/plugin-config.d.ts +2 -2
  88. package/dist/shared/agent-display-names.d.ts +1 -1
  89. package/dist/shared/jsonc-parser.d.ts +1 -1
  90. package/dist/shared/migration.d.ts +5 -1
  91. package/dist/shared/opencode-config-dir-types.d.ts +0 -1
  92. package/dist/shared/plugin-identity.d.ts +6 -8
  93. package/dist/shared/project-discovery-dirs.d.ts +0 -1
  94. package/dist/shared/tmux/tmux-utils/environment.d.ts +1 -1
  95. package/dist/shared/tmux/tmux-utils.d.ts +1 -1
  96. package/dist/shared/typescript-native-source-parser.d.ts +7 -0
  97. package/dist/skills/coding-agent-sessions/SKILL.md +5 -2
  98. package/dist/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  99. package/dist/skills/coding-agent-sessions/references/senpi.md +15 -5
  100. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +46 -0
  101. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +19 -10
  102. package/dist/skills/debugging/SKILL.md +2 -1
  103. package/dist/skills/debugging/references/methodology/03-flaky-triage.md +47 -0
  104. package/dist/skills/frontend/ATTRIBUTION.md +7 -0
  105. package/dist/skills/frontend/SKILL.md +4 -1
  106. package/dist/skills/frontend/references/design/README.md +8 -0
  107. package/dist/skills/frontend/references/design/_INDEX.md +14 -1
  108. package/dist/skills/frontend/references/design/interaction-skill.md +144 -0
  109. package/dist/skills/programming/SKILL.md +11 -1
  110. package/dist/skills/programming/references/logging.md +6 -1
  111. package/dist/skills/programming/scripts/typescript/check-no-excuse-rules.ts +51 -17
  112. package/dist/skills/start-work/SKILL.md +12 -2
  113. package/dist/skills/ulw-plan/SKILL.md +1 -1
  114. package/dist/skills/ulw-plan/references/full-workflow.md +12 -1
  115. package/dist/skills/ulw-plan/references/intent-clear.md +1 -1
  116. package/dist/startup-migration.d.ts +28 -0
  117. package/dist/testing/create-plugin-module.d.ts +4 -0
  118. package/dist/tools/task/types.d.ts +10 -10
  119. package/dist/tui.js +31812 -26849
  120. package/package.json +14 -14
  121. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +21 -5
  122. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +11 -2
  123. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +2 -2
  124. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  125. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +8 -4
  126. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  127. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  128. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +11 -5
  129. package/packages/omo-codex/plugin/components/codegraph/NOTICE +1 -1
  130. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +10272 -2600
  131. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +7341 -562
  132. package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
  133. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +33 -0
  134. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +36 -10
  135. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +170 -186
  136. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +34 -0
  137. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +22 -5
  138. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +106 -0
  139. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +145 -0
  140. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +21 -0
  141. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +139 -0
  142. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +15 -0
  143. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +32 -0
  144. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +109 -0
  145. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +148 -0
  146. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +126 -186
  147. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +9 -4
  148. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +160 -0
  149. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +12 -110
  150. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +29 -20
  151. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +3 -3
  152. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +2 -2
  153. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +11 -5
  154. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -3
  155. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +2 -1
  156. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +50 -5
  157. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +3 -2
  158. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +24 -13
  159. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +70 -13
  160. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +58 -0
  161. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +104 -0
  162. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +4 -2
  163. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +116 -0
  164. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +114 -119
  165. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  166. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  167. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  168. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  169. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  170. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  171. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  172. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  173. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  174. package/packages/omo-codex/plugin/components/rules/dist/cli.js +31 -14
  175. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  176. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  177. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
  178. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  179. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  180. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  181. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  182. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  183. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  184. package/packages/omo-codex/plugin/components/ultrawork/directive.md +12 -7
  185. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  186. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  187. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +12 -7
  188. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +1 -1
  189. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +12 -1
  190. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +1 -1
  191. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +12 -7
  192. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  193. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  194. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +19 -1
  195. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  196. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  197. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  198. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  199. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  200. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  201. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  202. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  203. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  204. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  205. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  206. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  207. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  208. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  209. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  210. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  211. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  212. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  213. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  214. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  215. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  216. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  217. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  218. package/packages/omo-codex/plugin/package-lock.json +42 -41
  219. package/packages/omo-codex/plugin/package.json +1 -1
  220. package/packages/omo-codex/plugin/shared/src/config-loader.ts +165 -16
  221. package/packages/omo-codex/plugin/shared/src/config-migration.ts +162 -0
  222. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +196 -18
  223. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +5 -2
  224. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  225. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +15 -5
  226. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +46 -0
  227. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +19 -10
  228. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -1
  229. package/packages/omo-codex/plugin/skills/debugging/references/methodology/03-flaky-triage.md +47 -0
  230. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -0
  231. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -1
  232. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +8 -0
  233. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +14 -1
  234. package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +144 -0
  235. package/packages/omo-codex/plugin/skills/programming/SKILL.md +11 -1
  236. package/packages/omo-codex/plugin/skills/programming/references/logging.md +6 -1
  237. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +51 -17
  238. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +12 -2
  239. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +12 -7
  240. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +19 -1
  241. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +1 -1
  242. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +12 -1
  243. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +1 -1
  244. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +22 -0
  245. package/packages/omo-codex/scripts/install-dist/install-local.mjs +1 -1
  246. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +5 -2
  247. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  248. package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +15 -5
  249. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +46 -0
  250. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +19 -10
  251. package/packages/shared-skills/skills/debugging/SKILL.md +2 -1
  252. package/packages/shared-skills/skills/debugging/references/methodology/03-flaky-triage.md +47 -0
  253. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -0
  254. package/packages/shared-skills/skills/frontend/SKILL.md +4 -1
  255. package/packages/shared-skills/skills/frontend/references/design/README.md +8 -0
  256. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +14 -1
  257. package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +144 -0
  258. package/packages/shared-skills/skills/programming/SKILL.md +11 -1
  259. package/packages/shared-skills/skills/programming/references/logging.md +6 -1
  260. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +51 -17
  261. package/packages/shared-skills/skills/start-work/SKILL.md +12 -2
  262. package/packages/shared-skills/skills/ulw-plan/SKILL.md +1 -1
  263. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +12 -1
  264. package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +1 -1
  265. package/dist/plugin-config/layered-config-loader.d.ts +0 -2
  266. package/dist/plugin-config/single-config-loader.d.ts +0 -4
  267. package/dist/shared/migrate-legacy-config-file.d.ts +0 -1
  268. 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.1",
3
+ "version": "4.19.3",
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,6 +1,3 @@
1
- import { execFile, spawn } from "node:child_process";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
1
  import {
5
2
  cwd as processCwd,
6
3
  env as processEnv,
@@ -8,25 +5,33 @@ import {
8
5
  stdin as processStdin,
9
6
  stdout as processStdout,
10
7
  } from "node:process";
11
- import type { Readable } from "node:stream";
12
8
  import { fileURLToPath } from "node:url";
13
9
 
14
- import { buildCodegraphChildEnv, buildCodegraphEnv } from "../../../../../utils/src/codegraph/env.ts";
15
- import { buildCodegraphInitGuidanceForToolResult } from "../../../../../utils/src/codegraph/guidance.ts";
16
- import { resolveCodegraphCommand } from "../../../../../utils/src/codegraph/resolve.ts";
10
+ import { buildCodegraphChildEnv } from "../../../../../utils/src/codegraph/env.ts";
17
11
  import { shouldExcludeCodegraphProject } from "../../../../../utils/src/codegraph/workspace.ts";
18
12
  import { getCodexOmoConfig } from "../../../shared/src/config-loader.ts";
19
13
  import { pruneCodegraphProjectStoresBestEffort } from "./cache-gc.js";
14
+ import { readHookInput, resolveHomeDir, resolveHookProjectRoot } from "./hook-input.js";
20
15
  import { sweepCodegraphZombiesBestEffort } from "./hook-sweep.js";
21
- import { resolveCodegraphCommandInvocation, SESSION_START_CWD_ENV } from "./session-start-worker.js";
22
16
  import type {
23
- HookStdout,
24
- PostToolUseHookOptions,
25
- PostToolUseHookResult,
17
+ CodegraphSessionStartOutcome,
26
18
  SessionStartHookOptions,
27
19
  SessionStartHookResult,
28
- WorkerSpawnInvocation,
29
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";
30
35
 
31
36
  export type {
32
37
  CodegraphCommandResult,
@@ -47,25 +52,46 @@ export type {
47
52
  WorkerAction,
48
53
  WorkerSpawnInvocation,
49
54
  } from "./hook-types.js";
50
- 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";
51
62
 
52
63
  export const CODEGRAPH_SESSION_START_NOTICE = "LazyCodex CodeGraph bootstrap scheduled in background";
53
- 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
+ };
54
75
 
55
76
  export async function runCodegraphSessionStartHook(options: SessionStartHookOptions = {}): Promise<number> {
56
77
  return (await executeCodegraphSessionStartHook(options)).exitCode;
57
78
  }
58
79
 
59
- export async function runCodegraphPostToolUseHookCli(options: PostToolUseHookOptions = {}): Promise<number> {
60
- return (await executeCodegraphPostToolUseHook(options)).exitCode;
61
- }
62
-
63
80
  export async function executeCodegraphSessionStartHook(options: SessionStartHookOptions = {}): Promise<SessionStartHookResult> {
64
81
  const env = options.env ?? processEnv;
65
82
  const input = await readHookInput(options.stdin ?? processStdin);
66
- const projectRoot = resolveProjectRoot(input, options.cwd ?? processCwd());
83
+ const projectRoot = resolveHookProjectRoot(input, options.cwd ?? processCwd());
67
84
  const homeDir = resolveHomeDir(env);
68
85
  const config = options.config ?? getCodexOmoConfig({ cwd: projectRoot, env, homeDir });
86
+ if (config.codegraph?.enabled === false) return { action: "skipped-disabled", exitCode: 0 };
87
+
88
+ const excludedRoots = config.codegraph?.excluded_roots;
89
+ const exclusion = shouldExcludeCodegraphProject(projectRoot, {
90
+ homeDir,
91
+ ...(excludedRoots === undefined ? {} : { excludedRoots }),
92
+ });
93
+ if (exclusion.excluded) return { action: "skipped-excluded", exitCode: 0 };
94
+
69
95
  pruneCodegraphProjectStoresBestEffort(homeDir, { debugLog: writeDebugLog });
70
96
  await sweepCodegraphZombiesBestEffort({
71
97
  env,
@@ -74,199 +100,157 @@ export async function executeCodegraphSessionStartHook(options: SessionStartHook
74
100
  log: writeDebugLog,
75
101
  }, options.sweepZombies);
76
102
 
77
- if (config.codegraph?.enabled === false) {
78
- return { action: "skipped-disabled", exitCode: 0 };
79
- }
80
- const excludedRoots = config.codegraph?.excluded_roots;
81
- const exclusion = shouldExcludeCodegraphProject(projectRoot, {
103
+ const nowMs = options.nowMs ?? Date.now();
104
+ const context: HookContext = {
105
+ baseCooldownMs: config.codegraph?.session_start_cooldown_ms ?? DEFAULT_SESSION_START_COOLDOWN_MS,
106
+ env,
82
107
  homeDir,
83
- ...(excludedRoots === undefined ? {} : { excludedRoots }),
84
- });
85
- if (exclusion.excluded) {
86
- return { action: "skipped-excluded", exitCode: 0 };
87
- }
108
+ logOutcome: options.logOutcome ?? ((outcome) => appendSessionStartOutcome(homeDir, outcome, nowMs)),
109
+ nowMs,
110
+ options,
111
+ projectRoot,
112
+ };
113
+ const projectDecision = handleProjectState(resolveHookProjectState(context), context);
114
+ if (projectDecision !== null) return projectDecision;
88
115
 
89
- const isInitialized = await (options.statusProbe ?? isCodegraphProjectInitialized)({
90
- daemon: config.codegraph?.daemon === true,
91
- env,
116
+ const cooldown = readSessionStartCooldown({
117
+ baseCooldownMs: context.baseCooldownMs,
92
118
  homeDir,
119
+ nowMs,
93
120
  projectRoot,
94
- ...(config.trustedCodegraphInstallDir === undefined ? {} : { trustedCodegraphInstallDir: config.trustedCodegraphInstallDir }),
95
121
  });
96
- if (isInitialized) {
97
- return { action: "skipped-initialized", exitCode: 0 };
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 };
98
131
  }
99
132
 
100
- (options.spawnWorker ?? spawnDetachedWorker)({
101
- args: [options.workerCliPath ?? defaultWorkerCliPath(), "hook", "session-start-worker"],
102
- command: process.execPath,
103
- env: buildCodegraphChildEnv({
104
- ambientEnv: env,
105
- codegraphEnv: { [SESSION_START_CWD_ENV]: projectRoot },
106
- runtimeEnv: env,
107
- }),
108
- });
109
- writeHookJson(options.stdout ?? processStdout);
110
- return { action: "spawned", exitCode: 0 };
111
- }
112
-
113
- async function isCodegraphProjectInitialized(options: {
114
- readonly daemon: boolean;
115
- readonly env: Record<string, string | undefined>;
116
- readonly homeDir: string;
117
- readonly projectRoot: string;
118
- readonly trustedCodegraphInstallDir?: string;
119
- }): Promise<boolean> {
120
- const resolved = resolveCodegraphCommand({
121
- env: options.env,
122
- homeDir: options.homeDir,
123
- provisioned: () => provisionedBinFromInstallDir(options.trustedCodegraphInstallDir),
124
- });
125
- if (!resolved.exists) return false;
126
-
127
- const invocation = resolveCodegraphCommandInvocation(resolved.command, [...resolved.argsPrefix, "status", "--json"]);
128
- const codegraphEnv = {
129
- ...buildCodegraphEnv({ daemon: options.daemon, homeDir: options.homeDir }),
130
- ...(options.trustedCodegraphInstallDir === undefined ? {} : { CODEGRAPH_INSTALL_DIR: options.trustedCodegraphInstallDir }),
131
- };
132
- const status = await runStatusProbe(
133
- options.projectRoot,
134
- invocation.command,
135
- invocation.args,
136
- buildCodegraphChildEnv({ ambientEnv: options.env, codegraphEnv, runtimeEnv: options.env }),
137
- );
138
- if (status.exitCode !== 0 || status.timedOut) return false;
139
- return codegraphStatusSaysInitialized(status.stdout);
140
- }
141
-
142
- function runStatusProbe(
143
- projectRoot: string,
144
- command: string,
145
- args: readonly string[],
146
- env: Record<string, string>,
147
- ): Promise<{ readonly exitCode: number; readonly stdout: string; readonly timedOut: boolean }> {
148
- return new Promise((resolveProbe) => {
149
- execFile(
150
- command,
151
- [...args],
152
- {
153
- cwd: projectRoot,
154
- encoding: "utf8",
155
- env,
156
- maxBuffer: 1024 * 1024,
157
- timeout: STATUS_PROBE_TIMEOUT_MS,
158
- windowsHide: true,
159
- },
160
- (error, stdout) => {
161
- if (error === null) {
162
- resolveProbe({ exitCode: 0, stdout: toOutputText(stdout), timedOut: false });
163
- return;
164
- }
165
- resolveProbe({ exitCode: resolveExitCode(error), stdout: toOutputText(stdout), timedOut: error.killed === true });
166
- },
167
- );
133
+ const lockResult = acquireSessionStartLock({
134
+ homeDir,
135
+ nowMs,
136
+ projectRoot,
137
+ staleMs: options.lockStaleMs ?? DEFAULT_SESSION_START_LOCK_STALE_MS,
168
138
  });
169
- }
170
-
171
- function codegraphStatusSaysInitialized(stdout: string): boolean {
172
- const parsed = parseJson(stdout);
173
- if (!isRecord(parsed)) return false;
174
- const initialized = parsed["initialized"] ?? parsed["isInitialized"] ?? parsed["ready"];
175
- if (typeof initialized === "boolean") return initialized;
176
- const status = parsed["status"];
177
- if (typeof status !== "string") return false;
178
- const normalized = status.toLowerCase();
179
- return (normalized.includes("initialized") || normalized.includes("ready")) && !normalized.includes("not initialized") && !normalized.includes("uninitialized");
180
- }
181
-
182
- function provisionedBinFromInstallDir(installDir: string | undefined): string | null {
183
- if (installDir === undefined) return null;
184
- return join(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
185
- }
186
-
187
- export async function executeCodegraphPostToolUseHook(options: PostToolUseHookOptions = {}): Promise<PostToolUseHookResult> {
188
- const env = options.env ?? processEnv;
189
- const input = await readHookInput(options.stdin ?? processStdin);
190
- const output = runCodegraphPostToolUseHook(input, { homeDir: resolveHomeDir(env) });
191
- if (output.length === 0) return { action: "skipped", exitCode: 0 };
192
-
193
- (options.stdout ?? processStdout).write(output);
194
- return { action: "emitted-guidance", exitCode: 0 };
195
- }
196
-
197
- export function runCodegraphPostToolUseHook(input: unknown, options: { readonly homeDir?: string } = {}): string {
198
- const toolName = isRecord(input) ? input["tool_name"] : undefined;
199
- const cwd = isRecord(input) ? input["cwd"] : undefined;
200
- const toolOutput = isRecord(input) ? (input["tool_response"] ?? input["tool_output"] ?? input["response"]) : input;
201
- const guidance = buildCodegraphInitGuidanceForToolResult({ cwd, toolName, toolOutput }, options);
202
- if (guidance === null) return "";
203
-
204
- return `${JSON.stringify({
205
- hookSpecificOutput: {
206
- hookEventName: "PostToolUse",
207
- additionalContext: guidance,
208
- },
209
- })}\n`;
210
- }
211
-
212
- function writeHookJson(stdout: HookStdout): void {
213
- const output = {
214
- hookSpecificOutput: {
215
- hookEventName: "SessionStart",
216
- additionalContext: CODEGRAPH_SESSION_START_NOTICE,
217
- },
218
- };
219
- stdout.write(`${JSON.stringify(output)}\n`);
220
- }
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);
221
145
 
222
- function writeDebugLog(message: string): void {
223
- if (processEnv["OMO_CODEGRAPH_DEBUG"] !== "1") return;
224
- processStderr.write(`${message}\n`);
225
- }
146
+ const lockedProjectDecision = handleProjectState(resolveHookProjectState(context), context);
147
+ if (lockedProjectDecision !== null) {
148
+ releaseSessionStartLock(lockResult.lock);
149
+ return lockedProjectDecision;
150
+ }
226
151
 
227
- function spawnDetachedWorker(invocation: WorkerSpawnInvocation): void {
228
- const child = spawn(invocation.command, [...invocation.args], { detached: true, env: invocation.env, stdio: "ignore" });
229
- child.unref();
230
- }
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
+ }
231
169
 
232
- function resolveHomeDir(env: Record<string, string | undefined>): string {
233
- return env["HOME"] ?? env["USERPROFILE"] ?? homedir();
170
+ writeSessionStartNotice(options.stdout ?? processStdout, CODEGRAPH_SESSION_START_NOTICE);
171
+ return { action: "spawned", exitCode: 0 };
234
172
  }
235
173
 
236
- function resolveExitCode(error: Error): number {
237
- if ("code" in error && typeof error.code === "number") return error.code;
238
- return 1;
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);
239
178
  }
240
179
 
241
- function toOutputText(value: string | Buffer): string {
242
- return Buffer.isBuffer(value) ? value.toString("utf8") : value;
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
+ }
243
192
  }
244
193
 
245
- function resolveProjectRoot(input: unknown, fallback: string): string {
246
- if (!isRecord(input)) return fallback;
247
- const cwd = input["cwd"];
248
- 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
+ }
249
216
  }
250
217
 
251
- async function readHookInput(stdin: Readable & { readonly isTTY?: boolean }): Promise<unknown> {
252
- if (stdin.isTTY === true) return undefined;
253
- let text = "";
254
- for await (const chunk of stdin) text += typeof chunk === "string" ? chunk : String(chunk);
255
- if (text.trim().length === 0) return undefined;
256
- 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 };
257
240
  }
258
241
 
259
- function parseJson(text: string): unknown {
242
+ function safeLogOutcome(logOutcome: (outcome: CodegraphSessionStartOutcome) => void, outcome: CodegraphSessionStartOutcome): void {
260
243
  try {
261
- return JSON.parse(text);
244
+ logOutcome(outcome);
262
245
  } catch (error) {
263
- if (error instanceof SyntaxError) return undefined;
264
- throw error;
246
+ if (error instanceof Error) writeDebugLog(`failed to write SessionStart outcome: ${error.message}`);
247
+ else throw error;
265
248
  }
266
249
  }
267
250
 
268
- function isRecord(value: unknown): value is Record<string, unknown> {
269
- 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`);
270
254
  }
271
255
 
272
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,6 +16,7 @@ import {
16
16
  buildCodegraphChildEnv,
17
17
  buildCodegraphEnv,
18
18
  } from "../../../../../utils/src/codegraph/env.ts";
19
+ import { hasCodegraphManagedInstall, resolvePinnedCodegraphBin } from "../../../../../utils/src/codegraph/managed-runtime.ts";
19
20
  import type { ParentWatchdogConfig } from "../../../../../mcp-stdio-core/src/index.ts";
20
21
  import { CODEGRAPH_PINNED_VERSION } from "../../../../../utils/src/codegraph/manifest.ts";
21
22
  import {
@@ -72,6 +73,8 @@ export interface RunCodegraphServeOptions {
72
73
  readonly cwd?: string;
73
74
  readonly env?: Record<string, string | undefined>;
74
75
  readonly homeDir?: string;
76
+ readonly managedInstallExists?: (installDir: string) => boolean;
77
+ readonly resolveManagedBin?: (installDir: string) => string | null;
75
78
  readonly stdin?: Readable;
76
79
  readonly stdout?: Writable;
77
80
  readonly nodeVersion?: string;
@@ -113,12 +116,28 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
113
116
  }
114
117
 
115
118
  const trustedInstallDir = config.trustedCodegraphInstallDir;
119
+ const installDir = trustedInstallDir ?? join(homeDir, ".omo", "codegraph");
116
120
  const resolutionOptions = {
117
121
  env,
118
122
  homeDir,
119
- provisioned: () => provisionedBinFromInstallDir(trustedInstallDir),
123
+ provisioned: () => provisionedBinFromInstallDir(installDir),
120
124
  } satisfies ResolveCodegraphCommandOptions;
121
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
+ }
122
141
  const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
123
142
  if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync)) {
124
143
  if (resolution.source === "path" && !nodeSupport.supported) {
@@ -142,7 +161,7 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
142
161
  }
143
162
 
144
163
  const runProcess = options.runProcess ?? runBridgedCodegraphProcess;
145
- const codegraphEnv = codegraphEnvForConfig(trustedInstallDir, homeDir, codegraphConfig.daemon === true, options.buildEnv);
164
+ const codegraphEnv = codegraphEnvForConfig(trustedInstallDir, homeDir, codegraphConfig.daemon !== false, options.buildEnv);
146
165
  const mergedEnv = buildCodegraphChildEnv({ ambientEnv: env, codegraphEnv, runtimeEnv: env });
147
166
  return runProcess(resolution.command, [...resolution.argsPrefix, "serve", "--mcp"], {
148
167
  cwd: projectCwd,
@@ -221,9 +240,7 @@ function resolveProjectCwd(env: Record<string, string | undefined>, fallback: st
221
240
  }
222
241
 
223
242
  function provisionedBinFromInstallDir(installDir: string | undefined): string | null {
224
- if (installDir === undefined) return null;
225
- const candidate = join(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
226
- return existsSync(candidate) ? candidate : null;
243
+ return resolvePinnedCodegraphBin(installDir);
227
244
  }
228
245
 
229
246
  export async function runCodegraphServeCli(): Promise<void> {