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,10 +1,12 @@
1
1
  import { describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
2
4
  import { join } from "node:path";
3
5
 
4
6
  import { runCodegraphServe } from "../src/serve.ts";
5
7
 
6
8
  describe("runCodegraphServe provisioning", () => {
7
- it("#given CodeGraph is unresolved and daemon is off by default #when serving MCP #then provisions CodeGraph before spawning with CODEGRAPH_NO_DAEMON=1", async () => {
9
+ it("#given CodeGraph is unresolved and daemon is omitted #when serving MCP #then provisions CodeGraph with daemon-on", async () => {
8
10
  // given
9
11
  const binPath = join("/tmp/home/.omo/codegraph", "bin", "codegraph");
10
12
  const calls: Array<{
@@ -42,7 +44,6 @@ describe("runCodegraphServe provisioning", () => {
42
44
  command: binPath,
43
45
  env: {
44
46
  CODEGRAPH_INSTALL_DIR: join("/tmp/home", ".omo", "codegraph"),
45
- CODEGRAPH_NO_DAEMON: "1",
46
47
  CODEGRAPH_NO_DOWNLOAD: "1",
47
48
  CODEGRAPH_TELEMETRY: "0",
48
49
  DO_NOT_TRACK: "1",
@@ -52,7 +53,50 @@ describe("runCodegraphServe provisioning", () => {
52
53
  ]);
53
54
  });
54
55
 
55
- it("#given CodeGraph is unresolved and codegraph.daemon=true #when serving MCP #then provisions CodeGraph and omits CODEGRAPH_NO_DAEMON so the daemon may run", async () => {
56
+ it("#given a bundled runtime and a stale managed install #when serving MCP #then it upgrades and uses the managed runtime", async () => {
57
+ // given
58
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-serve-managed-upgrade-"));
59
+ const installDir = join(homeDir, ".omo", "codegraph");
60
+ const binPath = join(installDir, "bin", "codegraph");
61
+ mkdirSync(join(installDir, "bin"), { recursive: true });
62
+ mkdirSync(join(installDir, ".provisioned"), { recursive: true });
63
+ writeFileSync(binPath, "stale codegraph\n");
64
+ writeFileSync(
65
+ join(installDir, ".provisioned", "codegraph-1.4.1.json"),
66
+ `${JSON.stringify({ binPath, version: "1.4.1" })}\n`,
67
+ );
68
+ const provisionCalls: unknown[] = [];
69
+ const processCalls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
70
+
71
+ try {
72
+ // when
73
+ const exitCode = await runCodegraphServe({
74
+ config: { codegraph: { enabled: true }, sources: [], warnings: [] },
75
+ homeDir,
76
+ managedInstallExists: () => true,
77
+ nodeVersion: "22.14.0",
78
+ resolve: () => ({ argsPrefix: ["codegraph.js"], command: "/opt/node22/bin/node", exists: true, source: "bundled" }),
79
+ resolveManagedBin: () => null,
80
+ ensureProvisioned: (options) => {
81
+ provisionCalls.push(options);
82
+ return Promise.resolve({ binPath, provisioned: true });
83
+ },
84
+ runProcess: (command, args) => {
85
+ processCalls.push({ args, command });
86
+ return Promise.resolve(0);
87
+ },
88
+ });
89
+
90
+ // then
91
+ expect(exitCode).toBe(0);
92
+ expect(provisionCalls).toEqual([{ installDir, lockDir: join(installDir, ".locks"), version: "1.5.0" }]);
93
+ expect(processCalls).toEqual([{ args: ["serve", "--mcp"], command: binPath }]);
94
+ } finally {
95
+ rmSync(homeDir, { force: true, recursive: true });
96
+ }
97
+ });
98
+
99
+ it("#given CodeGraph is unresolved and codegraph.daemon=false #when serving MCP #then provisions CodeGraph with daemon-off", async () => {
56
100
  // given
57
101
  const binPath = join("/tmp/home/.omo/codegraph", "bin", "codegraph");
58
102
  const calls: Array<{
@@ -64,7 +108,7 @@ describe("runCodegraphServe provisioning", () => {
64
108
 
65
109
  // when
66
110
  const exitCode = await runCodegraphServe({
67
- config: { codegraph: { daemon: true, enabled: true }, sources: [], warnings: [] },
111
+ config: { codegraph: { daemon: false, enabled: true }, sources: [], warnings: [] },
68
112
  env: { PATH: "/bin" },
69
113
  homeDir: "/tmp/home",
70
114
  nodeVersion: "22.14.0",
@@ -90,6 +134,7 @@ describe("runCodegraphServe provisioning", () => {
90
134
  command: binPath,
91
135
  env: {
92
136
  CODEGRAPH_INSTALL_DIR: join("/tmp/home", ".omo", "codegraph"),
137
+ CODEGRAPH_NO_DAEMON: "1",
93
138
  CODEGRAPH_NO_DOWNLOAD: "1",
94
139
  CODEGRAPH_TELEMETRY: "0",
95
140
  DO_NOT_TRACK: "1",
@@ -97,6 +142,6 @@ describe("runCodegraphServe provisioning", () => {
97
142
  },
98
143
  },
99
144
  ]);
100
- expect(calls[0]?.env["CODEGRAPH_NO_DAEMON"]).toBeUndefined();
145
+ expect(calls[0]?.env["CODEGRAPH_NO_DAEMON"]).toBe("1");
101
146
  });
102
147
  });
@@ -17,6 +17,7 @@ describe("runCodegraphServe unavailable CodeGraph paths", () => {
17
17
  ...closedMcpStdio(),
18
18
  config: { codegraph: { auto_provision: false, enabled: true }, sources: [], warnings: [] },
19
19
  env: { PATH: "/bin" },
20
+ nodeVersion: "22.14.0",
20
21
  buildEnv: () => ({}),
21
22
  resolve: () => ({ argsPrefix: [], command: "codegraph", exists: false, source: "path" }),
22
23
  runProcess: (command: string) => {
@@ -96,8 +97,8 @@ describe("runCodegraphServe unavailable CodeGraph paths", () => {
96
97
  try {
97
98
  mkdirSync(join(homeDir, ".omo"), { recursive: true });
98
99
  mkdirSync(join(workspace, ".omo"), { recursive: true });
99
- writeFileSync(join(homeDir, ".omo", "config.jsonc"), '{ "codegraph": { "enabled": true } }\n');
100
- writeFileSync(join(workspace, ".omo", "config.jsonc"), '{ "[codex]": { "codegraph": { "enabled": false } } }\n');
100
+ writeFileSync(join(homeDir, ".omo", "omo.jsonc"), '{ "codegraph": { "enabled": true } }\n');
101
+ writeFileSync(join(workspace, ".omo", "omo.jsonc"), '{ "[codex]": { "codegraph": { "enabled": false } } }\n');
101
102
 
102
103
  // when
103
104
  const exitCode = await runCodegraphServe({
@@ -5,6 +5,7 @@ import { dirname, join, resolve } from "node:path";
5
5
  import { Readable, Writable } from "node:stream";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
+ import { CODEGRAPH_PINNED_VERSION } from "../../../../../utils/src/codegraph/manifest.ts";
8
9
  import { resolveServeProcessInvocation, runCodegraphServe } from "../src/serve.ts";
9
10
 
10
11
  const componentRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -48,7 +49,6 @@ describe("runCodegraphServe", () => {
48
49
  cwd: resolve(runCwd),
49
50
  env: {
50
51
  CODEGRAPH_INSTALL_DIR: join("/tmp/home", ".omo", "codegraph"),
51
- CODEGRAPH_NO_DAEMON: "1",
52
52
  CODEGRAPH_NO_DOWNLOAD: "1",
53
53
  CODEGRAPH_TELEMETRY: "0",
54
54
  DO_NOT_TRACK: "1",
@@ -61,7 +61,7 @@ describe("runCodegraphServe", () => {
61
61
  expect(calls[0]?.env["OPENAI_API_KEY"]).toBeUndefined();
62
62
  });
63
63
 
64
- it("#given codegraph.daemon=true #when serving MCP #then CODEGRAPH_NO_DAEMON is omitted so the daemon may run", async () => {
64
+ it("#given codegraph.daemon=false #when serving MCP #then CODEGRAPH_NO_DAEMON pins daemon-off", async () => {
65
65
  // given
66
66
  const runCwd = componentRoot;
67
67
  const calls: Array<{
@@ -74,7 +74,7 @@ describe("runCodegraphServe", () => {
74
74
 
75
75
  // when
76
76
  const exitCode = await runCodegraphServe({
77
- config: { codegraph: { daemon: true, enabled: true }, sources: [], warnings: [] },
77
+ config: { codegraph: { daemon: false, enabled: true }, sources: [], warnings: [] },
78
78
  cwd: runCwd,
79
79
  env: { CUSTOM: "drop", HOME: "/tmp/home", OPENAI_API_KEY: "sk-test-secret" },
80
80
  nodeVersion: "22.14.0",
@@ -100,6 +100,7 @@ describe("runCodegraphServe", () => {
100
100
  cwd: resolve(runCwd),
101
101
  env: {
102
102
  CODEGRAPH_INSTALL_DIR: join("/tmp/home", ".omo", "codegraph"),
103
+ CODEGRAPH_NO_DAEMON: "1",
103
104
  CODEGRAPH_NO_DOWNLOAD: "1",
104
105
  CODEGRAPH_TELEMETRY: "0",
105
106
  DO_NOT_TRACK: "1",
@@ -108,7 +109,7 @@ describe("runCodegraphServe", () => {
108
109
  stdio: "pipe",
109
110
  },
110
111
  ]);
111
- expect(calls[0]?.env["CODEGRAPH_NO_DAEMON"]).toBeUndefined();
112
+ expect(calls[0]?.env["CODEGRAPH_NO_DAEMON"]).toBe("1");
112
113
  expect(calls[0]?.env["CUSTOM"]).toBeUndefined();
113
114
  expect(calls[0]?.env["OPENAI_API_KEY"]).toBeUndefined();
114
115
  });
@@ -182,12 +183,12 @@ describe("runCodegraphServe", () => {
182
183
  expect(spawned).toEqual([{ args: ["serve", "--mcp"], command: commandPath }]);
183
184
  });
184
185
 
185
- it("#given Windows Codex SOT install_dir has codegraph.cmd and daemon is off by default #when serving MCP #then it resolves there, exports CODEGRAPH_INSTALL_DIR, and pins CODEGRAPH_NO_DAEMON=1", async () => {
186
- await withProcessPlatform("win32", async () => {
186
+ it("#given Codex SOT install_dir has the pinned launcher and daemon is omitted #when serving MCP #then it defaults daemon-on", async () => {
187
+ await withProcessPlatform(process.platform, async () => {
187
188
  // given
188
189
  const tempRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-serve-install-dir-"));
189
190
  const installDir = join(tempRoot, "custom-codegraph");
190
- const binPath = join(installDir, "bin", "codegraph.cmd");
191
+ const binPath = join(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
191
192
  const calls: Array<{
192
193
  readonly args: readonly string[];
193
194
  readonly command: string;
@@ -196,7 +197,12 @@ describe("runCodegraphServe", () => {
196
197
 
197
198
  try {
198
199
  mkdirSync(join(installDir, "bin"), { recursive: true });
200
+ mkdirSync(join(installDir, ".provisioned"), { recursive: true });
199
201
  writeFileSync(binPath, "");
202
+ writeFileSync(
203
+ join(installDir, ".provisioned", `codegraph-${CODEGRAPH_PINNED_VERSION}.json`),
204
+ `${JSON.stringify({ binPath, version: CODEGRAPH_PINNED_VERSION })}\n`,
205
+ );
200
206
 
201
207
  // when
202
208
  const exitCode = await runCodegraphServe({
@@ -223,7 +229,6 @@ describe("runCodegraphServe", () => {
223
229
  command: binPath,
224
230
  env: {
225
231
  CODEGRAPH_INSTALL_DIR: installDir,
226
- CODEGRAPH_NO_DAEMON: "1",
227
232
  CODEGRAPH_NO_DOWNLOAD: "1",
228
233
  CODEGRAPH_TELEMETRY: "0",
229
234
  DO_NOT_TRACK: "1",
@@ -237,12 +242,12 @@ describe("runCodegraphServe", () => {
237
242
  });
238
243
  });
239
244
 
240
- it("#given Windows Codex SOT install_dir with codegraph.daemon=true #when serving MCP #then CODEGRAPH_NO_DAEMON is omitted so the daemon may run", async () => {
241
- await withProcessPlatform("win32", async () => {
245
+ it("#given Codex SOT install_dir with codegraph.daemon=false #when serving MCP #then CODEGRAPH_NO_DAEMON pins daemon-off", async () => {
246
+ await withProcessPlatform(process.platform, async () => {
242
247
  // given
243
248
  const tempRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-serve-install-dir-daemon-"));
244
249
  const installDir = join(tempRoot, "custom-codegraph");
245
- const binPath = join(installDir, "bin", "codegraph.cmd");
250
+ const binPath = join(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
246
251
  const calls: Array<{
247
252
  readonly args: readonly string[];
248
253
  readonly command: string;
@@ -251,11 +256,16 @@ describe("runCodegraphServe", () => {
251
256
 
252
257
  try {
253
258
  mkdirSync(join(installDir, "bin"), { recursive: true });
259
+ mkdirSync(join(installDir, ".provisioned"), { recursive: true });
254
260
  writeFileSync(binPath, "");
261
+ writeFileSync(
262
+ join(installDir, ".provisioned", `codegraph-${CODEGRAPH_PINNED_VERSION}.json`),
263
+ `${JSON.stringify({ binPath, version: CODEGRAPH_PINNED_VERSION })}\n`,
264
+ );
255
265
 
256
266
  // when
257
267
  const exitCode = await runCodegraphServe({
258
- config: { codegraph: { daemon: true, enabled: true, install_dir: installDir }, sources: [], trustedCodegraphInstallDir: installDir, warnings: [] },
268
+ config: { codegraph: { daemon: false, enabled: true, install_dir: installDir }, sources: [], trustedCodegraphInstallDir: installDir, warnings: [] },
259
269
  env: { HOME: "/tmp/home" },
260
270
  nodeVersion: "22.14.0",
261
271
  homeDir: "/tmp/home",
@@ -278,6 +288,7 @@ describe("runCodegraphServe", () => {
278
288
  command: binPath,
279
289
  env: {
280
290
  CODEGRAPH_INSTALL_DIR: installDir,
291
+ CODEGRAPH_NO_DAEMON: "1",
281
292
  CODEGRAPH_NO_DOWNLOAD: "1",
282
293
  CODEGRAPH_TELEMETRY: "0",
283
294
  DO_NOT_TRACK: "1",
@@ -285,7 +296,7 @@ describe("runCodegraphServe", () => {
285
296
  },
286
297
  },
287
298
  ]);
288
- expect(calls[0]?.env["CODEGRAPH_NO_DAEMON"]).toBeUndefined();
299
+ expect(calls[0]?.env["CODEGRAPH_NO_DAEMON"]).toBe("1");
289
300
  } finally {
290
301
  rmSync(tempRoot, { recursive: true, force: true });
291
302
  }
@@ -1,10 +1,15 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import { existsSync, mkdtempSync, rmSync } from "node:fs";
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
 
6
6
  import { runCodegraphSessionStartWorker } from "../src/hook.ts";
7
7
 
8
+ function writeProjectDatabase(workspace: string): void {
9
+ mkdirSync(join(workspace, ".codegraph"), { recursive: true });
10
+ writeFileSync(join(workspace, ".codegraph", "codegraph.db"), "fixture");
11
+ }
12
+
8
13
  describe("CodeGraph SessionStart worker Node support", () => {
9
14
  it("#given an unsupported local Node and a PATH CodeGraph command with auto provisioning disabled #when worker runs #then it skips without touching the workspace", async () => {
10
15
  // given
@@ -52,7 +57,7 @@ describe("CodeGraph SessionStart worker Node support", () => {
52
57
  const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node-provision-home-"));
53
58
  const binPath = join(homeDir, ".omo", "codegraph", "bin", "codegraph");
54
59
  const calls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
55
- const provisionCalls: Array<{ readonly installDir?: string; readonly lockDir: string; readonly version: "1.4.1" }> = [];
60
+ const provisionCalls: Array<{ readonly installDir?: string; readonly lockDir: string; readonly version: "1.5.0" }> = [];
56
61
  const outcomes: unknown[] = [];
57
62
 
58
63
  try {
@@ -78,19 +83,17 @@ describe("CodeGraph SessionStart worker Node support", () => {
78
83
  resolveCommand: () => ({ argsPrefix: [], command: "/usr/local/bin/codegraph", exists: true, source: "path" }),
79
84
  runCommand: (_projectRoot, command, args) => {
80
85
  calls.push({ args, command });
81
- return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
86
+ writeProjectDatabase(workspace);
87
+ return Promise.resolve({ exitCode: 0, stdout: "", timedOut: false });
82
88
  },
83
89
  },
84
90
  });
85
91
 
86
92
  // then
87
93
  expect(result).toEqual({ action: "initialized" });
88
- expect(calls).toEqual([
89
- { args: ["status", "--json"], command: binPath },
90
- { args: ["init"], command: binPath },
91
- ]);
94
+ expect(calls).toEqual([{ args: ["init"], command: binPath }]);
92
95
  expect(provisionCalls).toEqual([
93
- { installDir: join(homeDir, ".omo", "codegraph"), lockDir: join(homeDir, ".omo", "codegraph", ".locks"), version: "1.4.1" },
96
+ { installDir: join(homeDir, ".omo", "codegraph"), lockDir: join(homeDir, ".omo", "codegraph", ".locks"), version: "1.5.0" },
94
97
  ]);
95
98
  expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "provisioned", timedOut: false }]);
96
99
  } finally {
@@ -99,6 +102,62 @@ describe("CodeGraph SessionStart worker Node support", () => {
99
102
  }
100
103
  });
101
104
 
105
+ it("#given bundled CodeGraph and a stale managed install #when the worker runs #then it upgrades and uses the managed runtime", async () => {
106
+ // given
107
+ const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-managed-upgrade-"));
108
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-managed-upgrade-home-"));
109
+ const installDir = join(homeDir, ".omo", "codegraph");
110
+ const binPath = join(installDir, "bin", "codegraph");
111
+ mkdirSync(join(installDir, "bin"), { recursive: true });
112
+ mkdirSync(join(installDir, ".provisioned"), { recursive: true });
113
+ writeFileSync(binPath, "stale codegraph\n");
114
+ writeFileSync(
115
+ join(installDir, ".provisioned", "codegraph-1.4.1.json"),
116
+ `${JSON.stringify({ binPath, version: "1.4.1" })}\n`,
117
+ );
118
+ const provisionCalls: unknown[] = [];
119
+ const commandCalls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
120
+
121
+ try {
122
+ // when
123
+ const result = await runCodegraphSessionStartWorker({
124
+ cwd: workspace,
125
+ env: { HOME: homeDir },
126
+ nodeVersion: "22.14.0",
127
+ deps: {
128
+ ensureGitignored: () => true,
129
+ ensureProvisioned: (options) => {
130
+ provisionCalls.push(options);
131
+ return Promise.resolve({ binPath, provisioned: true });
132
+ },
133
+ managedInstallExists: () => true,
134
+ prepareWorkspace: () => ({
135
+ dataDir: join(homeDir, ".omo/codegraph/projects/test"),
136
+ dataRoot: join(homeDir, ".omo/codegraph"),
137
+ linked: true,
138
+ mode: "global-linked",
139
+ projectLink: join(workspace, ".codegraph"),
140
+ }),
141
+ resolveCommand: () => ({ argsPrefix: ["codegraph.js"], command: "/opt/node22/bin/node", exists: true, source: "bundled" }),
142
+ resolveManagedBin: () => null,
143
+ runCommand: (_projectRoot, command, args) => {
144
+ commandCalls.push({ args, command });
145
+ writeProjectDatabase(workspace);
146
+ return Promise.resolve({ exitCode: 0, stdout: "", timedOut: false });
147
+ },
148
+ },
149
+ });
150
+
151
+ // then
152
+ expect(result).toEqual({ action: "initialized" });
153
+ expect(provisionCalls).toEqual([{ installDir, lockDir: join(installDir, ".locks"), version: "1.5.0" }]);
154
+ expect(commandCalls).toEqual([{ args: ["init"], command: binPath }]);
155
+ } finally {
156
+ rmSync(workspace, { recursive: true, force: true });
157
+ rmSync(homeDir, { recursive: true, force: true });
158
+ }
159
+ });
160
+
102
161
  it("#given an unsupported local Node but bundled CodeGraph resolves through CODEGRAPH_NODE_BIN #when worker runs #then it bootstraps with the compatible runtime", async () => {
103
162
  // given
104
163
  const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-compatible-node-"));
@@ -129,17 +188,15 @@ describe("CodeGraph SessionStart worker Node support", () => {
129
188
  resolveCommand: () => ({ argsPrefix: ["codegraph.js"], command: nodeBin, exists: true, source: "bundled" }),
130
189
  runCommand: (_projectRoot, command, args) => {
131
190
  calls.push({ args, command });
132
- return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
191
+ writeProjectDatabase(workspace);
192
+ return Promise.resolve({ exitCode: 0, stdout: "", timedOut: false });
133
193
  },
134
194
  },
135
195
  });
136
196
 
137
197
  // then
138
198
  expect(result).toEqual({ action: "initialized" });
139
- expect(calls).toEqual([
140
- { args: ["codegraph.js", "status", "--json"], command: nodeBin },
141
- { args: ["codegraph.js", "init"], command: nodeBin },
142
- ]);
199
+ expect(calls).toEqual([{ args: ["codegraph.js", "init"], command: nodeBin }]);
143
200
  expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "bundled", timedOut: false }]);
144
201
  } finally {
145
202
  rmSync(workspace, { recursive: true, force: true });
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { probeCodegraphProject } from "../src/session-start-project.ts";
7
+
8
+ describe("CodeGraph SessionStart project probe", () => {
9
+ it("#given the exact project directory has a CodeGraph database #when probing #then it is initialized", () => {
10
+ // given
11
+ const projectRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-probe-exact-"));
12
+ try {
13
+ mkdirSync(join(projectRoot, ".codegraph"), { recursive: true });
14
+ writeFileSync(join(projectRoot, ".codegraph", "codegraph.db"), "fixture");
15
+
16
+ // when
17
+ const result = probeCodegraphProject(projectRoot);
18
+
19
+ // then
20
+ expect(result).toEqual({ kind: "initialized" });
21
+ } finally {
22
+ rmSync(projectRoot, { recursive: true, force: true });
23
+ }
24
+ });
25
+
26
+ it("#given an ancestor directory has a CodeGraph database #when probing a nested project #then the ancestor covers it", () => {
27
+ // given
28
+ const ancestorRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-probe-ancestor-"));
29
+ const projectRoot = join(ancestorRoot, "apps", "server");
30
+ try {
31
+ mkdirSync(join(ancestorRoot, ".codegraph"), { recursive: true });
32
+ mkdirSync(projectRoot, { recursive: true });
33
+ writeFileSync(join(ancestorRoot, ".codegraph", "codegraph.db"), "fixture");
34
+
35
+ // when
36
+ const result = probeCodegraphProject(projectRoot);
37
+
38
+ // then
39
+ expect(result).toEqual({ ancestorRoot: realpathSync(ancestorRoot), kind: "nested-root" });
40
+ } finally {
41
+ rmSync(ancestorRoot, { recursive: true, force: true });
42
+ }
43
+ });
44
+
45
+ it("#given neither the project nor its ancestors have a CodeGraph database #when probing #then it is definitively uninitialized", () => {
46
+ // given
47
+ const projectRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-probe-missing-"));
48
+ try {
49
+ // when
50
+ const result = probeCodegraphProject(projectRoot);
51
+
52
+ // then
53
+ expect(result).toEqual({ kind: "uninitialized" });
54
+ } finally {
55
+ rmSync(projectRoot, { recursive: true, force: true });
56
+ }
57
+ });
58
+ });
@@ -0,0 +1,104 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { mkdtempSync, rmSync, utimesSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import {
7
+ clearSessionStartCooldown,
8
+ readSessionStartCooldown,
9
+ recordSessionStartFailure,
10
+ } from "../src/session-start-cooldown.ts";
11
+ import {
12
+ acquireSessionStartLock,
13
+ releaseSessionStartLock,
14
+ } from "../src/session-start-lock.ts";
15
+
16
+ describe("CodeGraph SessionStart lock", () => {
17
+ it("#given a live project lock #when a second hook acquires it #then the second hook is deduplicated", () => {
18
+ // given
19
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-lock-home-"));
20
+ const projectRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-lock-project-"));
21
+ try {
22
+ const first = acquireSessionStartLock({ homeDir, nowMs: 10_000, projectRoot, staleMs: 60_000 });
23
+ expect(first.kind).toBe("acquired");
24
+
25
+ // when
26
+ const second = acquireSessionStartLock({ homeDir, nowMs: 10_001, projectRoot, staleMs: 60_000 });
27
+
28
+ // then
29
+ expect(second).toEqual({ kind: "locked" });
30
+ if (first.kind === "acquired") releaseSessionStartLock(first.lock);
31
+ } finally {
32
+ rmSync(homeDir, { recursive: true, force: true });
33
+ rmSync(projectRoot, { recursive: true, force: true });
34
+ }
35
+ });
36
+
37
+ it("#given a project lock older than the stale threshold #when another hook acquires it #then the stale lock is recovered", () => {
38
+ // given
39
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-stale-home-"));
40
+ const projectRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-stale-project-"));
41
+ try {
42
+ const first = acquireSessionStartLock({ homeDir, nowMs: 1_000, projectRoot, staleMs: 500 });
43
+ expect(first.kind).toBe("acquired");
44
+ if (first.kind !== "acquired") return;
45
+ const staleDate = new Date(1_000);
46
+ utimesSync(first.lock.path, staleDate, staleDate);
47
+
48
+ // when
49
+ const second = acquireSessionStartLock({ homeDir, nowMs: 2_000, projectRoot, staleMs: 500 });
50
+
51
+ // then
52
+ expect(second.kind).toBe("acquired");
53
+ if (second.kind === "acquired") {
54
+ expect(second.recoveredStale).toBe(true);
55
+ releaseSessionStartLock(second.lock);
56
+ }
57
+ } finally {
58
+ rmSync(homeDir, { recursive: true, force: true });
59
+ rmSync(projectRoot, { recursive: true, force: true });
60
+ }
61
+ });
62
+ });
63
+
64
+ describe("CodeGraph SessionStart cooldown", () => {
65
+ it("#given a failed worker outcome #when the cooldown is checked #then retries are suppressed until expiry and the next failure backs off", () => {
66
+ // given
67
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-cooldown-home-"));
68
+ const projectRoot = mkdtempSync(join(tmpdir(), "omo-codegraph-cooldown-project-"));
69
+ try {
70
+ const first = recordSessionStartFailure({
71
+ action: "failed",
72
+ baseCooldownMs: 1_000,
73
+ homeDir,
74
+ nowMs: 1_000,
75
+ projectRoot,
76
+ });
77
+
78
+ // when
79
+ const active = readSessionStartCooldown({ baseCooldownMs: 1_000, homeDir, nowMs: 1_999, projectRoot });
80
+ const expired = readSessionStartCooldown({ baseCooldownMs: 1_000, homeDir, nowMs: 2_000, projectRoot });
81
+ const second = recordSessionStartFailure({
82
+ action: "skipped-status",
83
+ baseCooldownMs: 1_000,
84
+ homeDir,
85
+ nowMs: 2_000,
86
+ projectRoot,
87
+ });
88
+
89
+ // then
90
+ expect(first).toMatchObject({ cooldownUntil: 2_000, failureCount: 1 });
91
+ expect(active).toMatchObject({ cooldownUntil: 2_000, failureCount: 1, kind: "active" });
92
+ expect(expired).toEqual({ failureCount: 1, kind: "inactive" });
93
+ expect(second).toMatchObject({ cooldownUntil: 4_000, failureCount: 2 });
94
+ clearSessionStartCooldown({ homeDir, projectRoot });
95
+ expect(readSessionStartCooldown({ baseCooldownMs: 1_000, homeDir, nowMs: 2_001, projectRoot })).toEqual({
96
+ failureCount: 0,
97
+ kind: "inactive",
98
+ });
99
+ } finally {
100
+ rmSync(homeDir, { recursive: true, force: true });
101
+ rmSync(projectRoot, { recursive: true, force: true });
102
+ }
103
+ });
104
+ });
@@ -15,7 +15,7 @@ describe("CodeGraph SessionStart trust boundary", () => {
15
15
  const calls: Array<{ readonly env: Record<string, string>; readonly installDir?: string; readonly lockDir?: string }> = [];
16
16
  try {
17
17
  mkdirSync(join(workspace, ".omo"), { recursive: true });
18
- writeFileSync(join(workspace, ".omo", "config.jsonc"), JSON.stringify({ codegraph: { enabled: true, install_dir: attackerInstallDir } }));
18
+ writeFileSync(join(workspace, ".omo", "omo.jsonc"), JSON.stringify({ codegraph: { enabled: true, install_dir: attackerInstallDir } }));
19
19
 
20
20
  // when
21
21
  const result = await runCodegraphSessionStartWorker({
@@ -45,7 +45,9 @@ describe("CodeGraph SessionStart trust boundary", () => {
45
45
  },
46
46
  runCommand: (_projectRoot, _command, _args, options) => {
47
47
  calls.push({ env: options.env });
48
- return Promise.resolve({ exitCode: 0, stdout: calls.length === 2 ? '{"initialized":false}' : "", timedOut: false });
48
+ mkdirSync(join(workspace, ".codegraph"), { recursive: true });
49
+ writeFileSync(join(workspace, ".codegraph", "codegraph.db"), "fixture");
50
+ return Promise.resolve({ exitCode: 0, stdout: "", timedOut: false });
49
51
  },
50
52
  },
51
53
  });