oh-my-opencode 5.0.0-beta.33 → 5.0.0-beta.35

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 (221) hide show
  1. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +1 -3
  2. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +1 -1
  3. package/.agents/skills/tech-debt-audit/SKILL.md +6 -76
  4. package/README.ja.md +1 -1
  5. package/README.ko.md +1 -1
  6. package/README.md +1 -1
  7. package/README.ru.md +1 -1
  8. package/README.zh-cn.md +1 -1
  9. package/THIRD-PARTY-NOTICES.md +27 -99
  10. package/dist/cli/index.js +48350 -48559
  11. package/dist/cli-node/index.js +48480 -48689
  12. package/dist/config/index.d.ts +1 -1
  13. package/dist/config/schema/hooks.d.ts +0 -1
  14. package/dist/config/schema/oh-my-opencode-config.d.ts +0 -10
  15. package/dist/config/schema.d.ts +0 -1
  16. package/dist/create-hooks.d.ts +0 -1
  17. package/dist/hooks/index.d.ts +0 -1
  18. package/dist/index.js +11675 -13742
  19. package/dist/mcp/index.d.ts +0 -4
  20. package/dist/mcp/types.d.ts +0 -1
  21. package/dist/oh-my-opencode.schema.json +1 -45
  22. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
  23. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
  24. package/dist/shared/omo-process-sweep.d.ts +2 -4
  25. package/dist/skills/init-deep/SKILL.md +13 -13
  26. package/dist/skills/ulw-plan/SKILL.md +0 -1
  27. package/dist/tui.js +44809 -44910
  28. package/package.json +16 -18
  29. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +0 -17
  30. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -3
  31. package/packages/omo-codex/plugin/.mcp.json +0 -6
  32. package/packages/omo-codex/plugin/AGENTS.md +5 -5
  33. package/packages/omo-codex/plugin/README.md +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -15
  35. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  40. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  42. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +1 -1
  44. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  45. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  46. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  48. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  50. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +1 -1
  52. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  53. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  54. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +2 -2
  55. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  56. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +0 -1
  58. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  59. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  60. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/ulw-loop/README.md +11 -1
  62. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +2 -2
  63. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
  64. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
  65. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +1 -0
  66. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +5 -0
  67. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +10 -3
  68. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +35 -38
  69. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +278 -193
  70. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +5 -0
  71. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +7 -51
  72. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +39 -22
  73. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
  74. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +64 -8
  75. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
  76. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +37 -18
  77. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  78. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  79. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -1
  80. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
  81. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +6 -0
  82. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +10 -3
  83. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +32 -63
  84. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +7 -53
  85. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +50 -31
  86. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +67 -14
  87. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
  88. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +36 -17
  89. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +26 -0
  90. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +47 -2
  91. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
  92. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
  93. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
  94. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
  95. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  98. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  99. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  106. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  107. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  108. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  109. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  110. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  111. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  112. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  113. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  114. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  116. package/packages/omo-codex/plugin/package-lock.json +12 -146
  117. package/packages/omo-codex/plugin/package.json +1 -2
  118. package/packages/omo-codex/plugin/scripts/AGENTS.md +0 -1
  119. package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +0 -1
  120. package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
  121. package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
  122. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
  123. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -8
  124. package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
  125. package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
  126. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
  127. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +13 -13
  128. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +1 -1
  129. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +2 -2
  130. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -1
  131. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +0 -1
  132. package/packages/omo-codex/plugin/test/AGENTS.md +2 -2
  133. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +2 -21
  134. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -2
  135. package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
  136. package/packages/omo-codex/plugin/test/auto-update.test.mjs +0 -25
  137. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
  138. package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
  139. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +0 -50
  140. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +0 -1
  141. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
  142. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +27 -1
  143. package/packages/omo-codex/scripts/install-config-git-bash.test.mjs +0 -23
  144. package/packages/omo-codex/scripts/install-dist/install-local.mjs +238 -431
  145. package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +1 -25
  146. package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +0 -36
  147. package/packages/shared-skills/skills/init-deep/SKILL.md +13 -13
  148. package/packages/shared-skills/skills/ulw-plan/SKILL.md +0 -1
  149. package/dist/config/schema/codegraph.d.ts +0 -12
  150. package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +0 -17
  151. package/dist/hooks/codegraph-bootstrap/hook.d.ts +0 -35
  152. package/dist/hooks/codegraph-bootstrap/index.d.ts +0 -1
  153. package/dist/hooks/codegraph-bootstrap/project-root.d.ts +0 -1
  154. package/dist/hooks/codegraph-bootstrap/status.d.ts +0 -11
  155. package/dist/mcp/codegraph.d.ts +0 -16
  156. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
  157. package/packages/omo-codex/plugin/components/codegraph/LICENSE +0 -21
  158. package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
  159. package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
  160. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12326
  161. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9908
  162. package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
  163. package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
  164. package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
  165. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
  166. package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
  167. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
  168. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
  169. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -310
  170. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -92
  171. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
  172. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
  173. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
  174. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
  175. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
  176. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -22
  177. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
  178. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
  179. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
  180. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
  181. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
  182. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
  183. package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
  184. package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
  185. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
  186. package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
  187. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
  188. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
  189. package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
  190. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
  191. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
  192. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
  193. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
  194. package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
  195. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
  196. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
  197. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
  198. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
  199. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
  200. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +0 -47
  201. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
  202. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
  203. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
  204. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
  205. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
  206. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
  207. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
  208. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
  209. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
  210. package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
  211. package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
  212. package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
  213. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +0 -101
  214. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
  215. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
  216. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
  217. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
  218. package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
  219. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
  220. package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
  221. package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
@@ -1,263 +0,0 @@
1
- #!/usr/bin/env node
2
- import { existsSync, realpathSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { basename, join, resolve } from "node:path";
5
- import {
6
- cwd as processCwd,
7
- env as processEnv,
8
- stdin as processStdin,
9
- stderr as processStderr,
10
- stdout as processStdout,
11
- } from "node:process";
12
- import type { Readable, Writable } from "node:stream";
13
- import { fileURLToPath } from "node:url";
14
-
15
- import {
16
- buildCodegraphChildEnv,
17
- buildCodegraphEnv,
18
- } from "../../../../../utils/src/codegraph/env.ts";
19
- import { hasCodegraphManagedInstall, resolvePinnedCodegraphBin } from "../../../../../utils/src/codegraph/managed-runtime.ts";
20
- import type { ParentWatchdogConfig } from "../../../../../mcp-stdio-core/src/index.ts";
21
- import { CODEGRAPH_PINNED_VERSION } from "../../../../../utils/src/codegraph/manifest.ts";
22
- import {
23
- buildCodegraphNodeSkipHint,
24
- evaluateCodegraphNodeSupport,
25
- } from "../../../../../utils/src/codegraph/node-support.ts";
26
- import {
27
- ensureCodegraphProvisioned,
28
- type EnsureCodegraphProvisionedOptions,
29
- } from "../../../../../utils/src/codegraph/provision.ts";
30
- import {
31
- codegraphCommandRequiresSupportedLocalNode,
32
- resolveCodegraphCommand,
33
- type CodegraphCommandResolution,
34
- type ResolveCodegraphCommandOptions,
35
- } from "../../../../../utils/src/codegraph/resolve.ts";
36
- import { shouldExcludeCodegraphProject } from "../../../../../utils/src/codegraph/workspace.ts";
37
- import { getCodexOmoConfig, type CodexOmoConfig } from "../../../shared/src/config-loader.ts";
38
- import type { CodegraphConfig } from "./hook.js";
39
- import { runBridgedCodegraphProcess } from "./mcp-bridge.js";
40
- import { runUnavailableCodegraphMcpServer } from "./mcp-unavailable.js";
41
- import { SESSION_START_CWD_ENV } from "./session-start-worker.js";
42
- export { resolveServeProcessInvocation } from "./serve-invocation.js";
43
-
44
- export type ServeStdio = "pipe";
45
-
46
- export interface CodegraphServeProcessOptions {
47
- readonly cwd: string;
48
- readonly env: Record<string, string | undefined>;
49
- readonly input: Readable;
50
- readonly output: Writable;
51
- readonly stderr: CodegraphServeStderr;
52
- readonly stdio: ServeStdio;
53
- readonly parentWatchdog?: ParentWatchdogConfig;
54
- }
55
-
56
- export type CodegraphServeProcessRunner = (
57
- command: string,
58
- args: readonly string[],
59
- options: CodegraphServeProcessOptions,
60
- ) => Promise<number>;
61
- export type CodegraphProvisioner = (
62
- options: EnsureCodegraphProvisionedOptions,
63
- ) => ReturnType<typeof ensureCodegraphProvisioned>;
64
-
65
- export interface CodegraphServeStderr {
66
- readonly write: (chunk: string) => void;
67
- }
68
-
69
- export interface RunCodegraphServeOptions {
70
- readonly buildEnv?: (options: { readonly daemon: boolean; readonly homeDir: string }) => Record<string, string>;
71
- readonly commandExists?: (filePath: string) => boolean;
72
- readonly config?: CodexOmoConfig;
73
- readonly cwd?: string;
74
- readonly env?: Record<string, string | undefined>;
75
- readonly homeDir?: string;
76
- readonly managedInstallExists?: (installDir: string) => boolean;
77
- readonly resolveManagedBin?: (installDir: string) => string | null;
78
- readonly stdin?: Readable;
79
- readonly stdout?: Writable;
80
- readonly nodeVersion?: string;
81
- readonly resolve?: (options: ResolveCodegraphCommandOptions) => ReturnType<typeof resolveCodegraphCommand>;
82
- readonly runProcess?: CodegraphServeProcessRunner;
83
- readonly stderr?: CodegraphServeStderr;
84
- readonly ensureProvisioned?: CodegraphProvisioner;
85
- // Test seam: production callers leave this unset so the bridge and the
86
- // unavailable facade use the watchdog defaults (parentPid = process.ppid,
87
- // 30s poll).
88
- readonly parentWatchdog?: ParentWatchdogConfig;
89
- }
90
-
91
- const CODEGRAPH_SKIP_HINT =
92
- "CodeGraph MCP skipped: codegraph binary not found. Install CodeGraph or set OMO_CODEGRAPH_BIN.\n";
93
- const CODEGRAPH_DISABLED_HINT =
94
- "CodeGraph MCP skipped: disabled by OMO SOT config. Set [codex].codegraph.enabled=true to enable it.\n";
95
- const CODEGRAPH_EXCLUDED_HINT = "CodeGraph MCP skipped: project excluded by OMO CodeGraph policy.\n";
96
- const CODEGRAPH_VERSION = CODEGRAPH_PINNED_VERSION;
97
- const PROJECT_CWD_ENV_KEYS = ["OMO_CODEGRAPH_PROJECT_CWD", SESSION_START_CWD_ENV, "PWD"] as const;
98
-
99
- export async function runCodegraphServe(options: RunCodegraphServeOptions = {}): Promise<number> {
100
- const env = options.env ?? processEnv;
101
- const homeDir = options.homeDir ?? homedir();
102
- const wrapperCwd = options.cwd ?? processCwd();
103
- const projectCwd = resolveProjectCwd(env, wrapperCwd);
104
- const config = options.config ?? getCodexOmoConfig({ cwd: projectCwd, env, homeDir });
105
- const codegraphConfig = config.codegraph ?? {};
106
- if (codegraphConfig.enabled === false) {
107
- return runUnavailableMcp(CODEGRAPH_DISABLED_HINT, options);
108
- }
109
- const excludedRoots = codegraphConfig.excluded_roots;
110
- const exclusion = shouldExcludeCodegraphProject(projectCwd, {
111
- homeDir,
112
- ...(excludedRoots === undefined ? {} : { excludedRoots }),
113
- });
114
- if (exclusion.excluded) {
115
- return runUnavailableMcp(CODEGRAPH_EXCLUDED_HINT, options);
116
- }
117
-
118
- const trustedInstallDir = config.trustedCodegraphInstallDir;
119
- const installDir = trustedInstallDir ?? join(homeDir, ".omo", "codegraph");
120
- const resolutionOptions = {
121
- env,
122
- homeDir,
123
- provisioned: () => provisionedBinFromInstallDir(installDir),
124
- } satisfies ResolveCodegraphCommandOptions;
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
- }
141
- const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
142
- if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync)) {
143
- if (resolution.source === "path" && !nodeSupport.supported) {
144
- return runUnavailableMcp(buildCodegraphNodeSkipHint(nodeSupport), options);
145
- }
146
- const provisioned = await provisionMissingCodegraph({
147
- config: codegraphConfig,
148
- ensureProvisioned: options.ensureProvisioned ?? ensureCodegraphProvisioned,
149
- homeDir,
150
- resolution,
151
- ...(trustedInstallDir === undefined ? {} : { trustedInstallDir }),
152
- });
153
- if (provisioned === null) {
154
- return runUnavailableMcp(CODEGRAPH_SKIP_HINT, options);
155
- }
156
- resolution = provisioned;
157
- }
158
-
159
- if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
160
- return runUnavailableMcp(buildCodegraphNodeSkipHint(nodeSupport), options);
161
- }
162
-
163
- const runProcess = options.runProcess ?? runBridgedCodegraphProcess;
164
- const codegraphEnv = codegraphEnvForConfig(trustedInstallDir, homeDir, codegraphConfig.daemon !== false, options.buildEnv);
165
- const mergedEnv = buildCodegraphChildEnv({ ambientEnv: env, codegraphEnv, runtimeEnv: env });
166
- return runProcess(resolution.command, [...resolution.argsPrefix, "serve", "--mcp"], {
167
- cwd: projectCwd,
168
- env: mergedEnv,
169
- input: options.stdin ?? processStdin,
170
- output: options.stdout ?? processStdout,
171
- stderr: options.stderr ?? processStderr,
172
- stdio: "pipe",
173
- parentWatchdog: options.parentWatchdog ?? {},
174
- });
175
- }
176
-
177
- async function runUnavailableMcp(reason: string, options: RunCodegraphServeOptions): Promise<number> {
178
- (options.stderr ?? processStderr).write(reason);
179
- await runUnavailableCodegraphMcpServer({
180
- input: options.stdin ?? processStdin,
181
- output: options.stdout ?? processStdout,
182
- reason,
183
- serverVersion: CODEGRAPH_VERSION,
184
- parentWatchdog: options.parentWatchdog ?? {},
185
- });
186
- return 0;
187
- }
188
-
189
- async function provisionMissingCodegraph(options: {
190
- readonly config: CodegraphConfig;
191
- readonly ensureProvisioned: CodegraphProvisioner;
192
- readonly homeDir: string;
193
- readonly resolution: CodegraphCommandResolution;
194
- readonly trustedInstallDir?: string;
195
- }): Promise<CodegraphCommandResolution | null> {
196
- if (options.resolution.source === "env") return null;
197
- if (options.config.auto_provision === false) return null;
198
-
199
- const installDir = options.trustedInstallDir ?? join(options.homeDir, ".omo", "codegraph");
200
- const result = await options.ensureProvisioned({
201
- installDir,
202
- lockDir: join(installDir, ".locks"),
203
- version: CODEGRAPH_VERSION,
204
- });
205
- if (!result.provisioned || result.binPath === undefined) return null;
206
- return { argsPrefix: [], command: result.binPath, exists: true, source: "provisioned" };
207
- }
208
-
209
- function shouldSkipResolvedCommand(
210
- resolution: CodegraphCommandResolution,
211
- commandExists: (filePath: string) => boolean,
212
- ): boolean {
213
- if (resolution.source !== "env") return false;
214
- if (!looksLikePath(resolution.command)) return false;
215
- return !commandExists(resolution.command);
216
- }
217
-
218
- function looksLikePath(command: string): boolean {
219
- return command.includes("/") || command.includes("\\");
220
- }
221
-
222
- function codegraphEnvForConfig(
223
- trustedInstallDir: string | undefined,
224
- homeDir: string,
225
- daemon: boolean,
226
- buildEnv: ((options: { readonly daemon: boolean; readonly homeDir: string }) => Record<string, string>) | undefined,
227
- ): Record<string, string> {
228
- const env = buildEnv?.({ daemon, homeDir }) ?? buildCodegraphEnv({ daemon, homeDir });
229
- return trustedInstallDir === undefined ? env : { ...env, CODEGRAPH_INSTALL_DIR: trustedInstallDir };
230
- }
231
-
232
- function resolveProjectCwd(env: Record<string, string | undefined>, fallback: string): string {
233
- for (const key of PROJECT_CWD_ENV_KEYS) {
234
- const candidate = env[key]?.trim();
235
- if (candidate === undefined || candidate.length === 0) continue;
236
- const resolved = resolve(candidate);
237
- if (existsSync(resolved)) return resolved;
238
- }
239
- return resolve(fallback);
240
- }
241
-
242
- function provisionedBinFromInstallDir(installDir: string | undefined): string | null {
243
- return resolvePinnedCodegraphBin(installDir);
244
- }
245
-
246
- export async function runCodegraphServeCli(): Promise<void> {
247
- process.exitCode = await runCodegraphServe();
248
- }
249
-
250
- if (isDirectInvocation(process.argv[1])) {
251
- runCodegraphServeCli().catch((error: unknown) => {
252
- processStderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
253
- process.exitCode = 1;
254
- });
255
- }
256
-
257
- function isDirectInvocation(argvPath: string | undefined): boolean {
258
- if (argvPath === undefined) return false;
259
- const modulePath = fileURLToPath(import.meta.url);
260
- const moduleName = basename(modulePath);
261
- if (moduleName !== "serve.js" && moduleName !== "serve.ts") return false;
262
- return realpathSync(resolve(argvPath)) === realpathSync(modulePath);
263
- }
@@ -1,106 +0,0 @@
1
- import { join } from "node:path";
2
- import { env as processEnv, stderr as processStderr } from "node:process";
3
-
4
- import { buildCodegraphChildEnv, buildCodegraphEnv } from "../../../../../utils/src/codegraph/env.ts";
5
- import { CODEGRAPH_PINNED_VERSION } from "../../../../../utils/src/codegraph/manifest.ts";
6
- import type { CodegraphNodeSupport } from "../../../../../utils/src/codegraph/node-support.ts";
7
- import {
8
- codegraphCommandRequiresSupportedLocalNode,
9
- type CodegraphCommandResolution,
10
- } from "../../../../../utils/src/codegraph/resolve.ts";
11
- import { runProcessWithTreeTimeout } from "../../../../../utils/src/process-tree.ts";
12
- import type { CodegraphCommandResult, CodegraphConfig, CodegraphSessionStartDeps } from "./hook-types.js";
13
- import { resolveServeProcessInvocation } from "./serve-invocation.js";
14
-
15
- export const SESSION_START_COMMAND_TIMEOUT_MS = 60_000;
16
-
17
- export type CodegraphBootstrapConfig = CodegraphConfig & {
18
- readonly trustedCodegraphInstallDir?: string;
19
- };
20
-
21
- export type SessionStartCommandResolution =
22
- | { readonly kind: "resolved"; readonly resolution: CodegraphCommandResolution }
23
- | { readonly kind: "unsupported-node" }
24
- | { readonly error: string; readonly kind: "unavailable"; readonly source: CodegraphCommandResolution["source"] };
25
-
26
- export async function resolveSessionStartCommand(options: {
27
- readonly config: CodegraphBootstrapConfig;
28
- readonly deps: CodegraphSessionStartDeps;
29
- readonly env: Record<string, string | undefined>;
30
- readonly homeDir: string;
31
- readonly nodeSupport: CodegraphNodeSupport;
32
- }): Promise<SessionStartCommandResolution> {
33
- const trustedInstallDir = options.config.trustedCodegraphInstallDir;
34
- const installDir = trustedInstallDir ?? join(options.homeDir, ".omo", "codegraph");
35
- const resolved = options.deps.resolveCommand({
36
- env: options.env,
37
- homeDir: options.homeDir,
38
- provisioned: () => options.deps.resolveManagedBin(installDir),
39
- });
40
- const managedBin = options.deps.resolveManagedBin(installDir);
41
- if (resolved.source !== "env" && managedBin !== null) {
42
- return { kind: "resolved", resolution: { argsPrefix: [], command: managedBin, exists: true, source: "provisioned" } };
43
- }
44
- if (resolved.source !== "env" && options.config.auto_provision !== false && options.deps.managedInstallExists(installDir)) {
45
- const upgraded = await options.deps.ensureProvisioned({
46
- installDir,
47
- lockDir: join(installDir, ".locks"),
48
- version: CODEGRAPH_PINNED_VERSION,
49
- });
50
- if (upgraded.provisioned && upgraded.binPath !== undefined) {
51
- return { kind: "resolved", resolution: { argsPrefix: [], command: upgraded.binPath, exists: true, source: "provisioned" } };
52
- }
53
- }
54
- if (resolved.exists && canUseResolvedCommand(resolved, options.nodeSupport)) {
55
- return { kind: "resolved", resolution: resolved };
56
- }
57
- if (resolved.exists && options.config.auto_provision === false) return { kind: "unsupported-node" };
58
- if (options.config.auto_provision === false) {
59
- return { error: "codegraph binary unavailable and auto_provision is disabled", kind: "unavailable", source: resolved.source };
60
- }
61
-
62
- const provisioned = await options.deps.ensureProvisioned({
63
- installDir,
64
- lockDir: join(installDir, ".locks"),
65
- version: CODEGRAPH_PINNED_VERSION,
66
- });
67
- if (!provisioned.provisioned || provisioned.binPath === undefined) {
68
- return { error: provisioned.error ?? "provisioning did not produce a binary", kind: "unavailable", source: resolved.source };
69
- }
70
- return { kind: "resolved", resolution: { argsPrefix: [], command: provisioned.binPath, exists: true, source: "provisioned" } };
71
- }
72
-
73
- export function sessionStartCodegraphEnv(config: CodegraphBootstrapConfig, homeDir: string): Record<string, string> {
74
- const env = buildCodegraphEnv({ daemon: false, homeDir });
75
- return config.trustedCodegraphInstallDir === undefined ? env : { ...env, CODEGRAPH_INSTALL_DIR: config.trustedCodegraphInstallDir };
76
- }
77
-
78
- export async function runSessionStartCodegraphCommand(
79
- projectRoot: string,
80
- command: string,
81
- args: readonly string[],
82
- options: { readonly env: Record<string, string>; readonly timeoutMs: number },
83
- ): Promise<CodegraphCommandResult> {
84
- const invocation = resolveCodegraphCommandInvocation(command, args);
85
- return runProcessWithTreeTimeout({
86
- args: invocation.args,
87
- command: invocation.command,
88
- cwd: projectRoot,
89
- env: buildCodegraphChildEnv({ ambientEnv: processEnv, codegraphEnv: options.env }),
90
- maxBuffer: 1024 * 1024,
91
- onTerminationReport: (report) => processStderr.write(`[codegraph-session-start] process tree termination ${JSON.stringify(report)}\n`),
92
- timeoutMs: options.timeoutMs,
93
- }).then(({ exitCode, stderr, stdout, timedOut }) => ({ exitCode, stderr, stdout, timedOut }));
94
- }
95
-
96
- export function resolveCodegraphCommandInvocation(
97
- command: string,
98
- args: readonly string[],
99
- platform: NodeJS.Platform = process.platform,
100
- ): { readonly args: readonly string[]; readonly command: string } {
101
- return resolveServeProcessInvocation(command, args, platform);
102
- }
103
-
104
- function canUseResolvedCommand(resolved: CodegraphCommandResolution, nodeSupport: CodegraphNodeSupport): boolean {
105
- return !codegraphCommandRequiresSupportedLocalNode(resolved) || nodeSupport.supported;
106
- }
@@ -1,145 +0,0 @@
1
- import { mkdirSync, readFileSync, rmSync } from "node:fs";
2
-
3
- import { writeFileAtomically } from "../../../../../utils/src/atomic-write.ts";
4
- import { resolveSessionStartStatePaths, stateParent } from "./session-start-paths.js";
5
-
6
- export const DEFAULT_SESSION_START_COOLDOWN_MS = 15 * 60 * 1_000;
7
- export const MAX_SESSION_START_COOLDOWN_MS = 24 * 60 * 60 * 1_000;
8
-
9
- export type SessionStartFailureAction =
10
- | "failed"
11
- | "skipped-probe-error"
12
- | "skipped-status"
13
- | "skipped-unavailable"
14
- | "skipped-unsupported-node"
15
- | "spawn-failed"
16
- | "stale-lock"
17
- | "timeout";
18
-
19
- export interface SessionStartCooldownState {
20
- readonly action: SessionStartFailureAction;
21
- readonly cooldownUntil: number;
22
- readonly failureCount: number;
23
- readonly lastFailureAt: number;
24
- readonly projectRoot: string;
25
- readonly version: 1;
26
- }
27
-
28
- export type SessionStartCooldownDecision =
29
- | { readonly cooldownUntil: number; readonly failureCount: number; readonly kind: "active" }
30
- | { readonly failureCount: number; readonly kind: "inactive" }
31
- | { readonly kind: "inconclusive"; readonly reason: string };
32
-
33
- export interface SessionStartCooldownOptions {
34
- readonly baseCooldownMs: number;
35
- readonly homeDir: string;
36
- readonly maxCooldownMs?: number;
37
- readonly nowMs: number;
38
- readonly projectRoot: string;
39
- }
40
-
41
- export function readSessionStartCooldown(options: SessionStartCooldownOptions): SessionStartCooldownDecision {
42
- const paths = resolveSessionStartStatePaths(options.homeDir, options.projectRoot);
43
- const state = readCooldownState(paths.cooldownPath, paths.canonicalProjectRoot);
44
- if (state.kind === "inconclusive") return state;
45
- if (state.kind === "missing") return { failureCount: 0, kind: "inactive" };
46
- const cooldownUntil = state.value.lastFailureAt + cooldownDuration(
47
- options.baseCooldownMs,
48
- state.value.failureCount,
49
- options.maxCooldownMs ?? MAX_SESSION_START_COOLDOWN_MS,
50
- );
51
- return options.nowMs < cooldownUntil
52
- ? { cooldownUntil, failureCount: state.value.failureCount, kind: "active" }
53
- : { failureCount: state.value.failureCount, kind: "inactive" };
54
- }
55
-
56
- export function recordSessionStartFailure(
57
- options: SessionStartCooldownOptions & { readonly action: SessionStartFailureAction },
58
- ): SessionStartCooldownState {
59
- const paths = resolveSessionStartStatePaths(options.homeDir, options.projectRoot);
60
- const existing = readCooldownState(paths.cooldownPath, paths.canonicalProjectRoot);
61
- const failureCount = existing.kind === "present" ? existing.value.failureCount + 1 : 1;
62
- const cooldownUntil = options.nowMs + cooldownDuration(
63
- options.baseCooldownMs,
64
- failureCount,
65
- options.maxCooldownMs ?? MAX_SESSION_START_COOLDOWN_MS,
66
- );
67
- const state: SessionStartCooldownState = {
68
- action: options.action,
69
- cooldownUntil,
70
- failureCount,
71
- lastFailureAt: options.nowMs,
72
- projectRoot: paths.canonicalProjectRoot,
73
- version: 1,
74
- };
75
- mkdirSync(stateParent(paths.cooldownPath), { recursive: true });
76
- writeFileAtomically(paths.cooldownPath, `${JSON.stringify(state)}\n`);
77
- return state;
78
- }
79
-
80
- export function clearSessionStartCooldown(options: { readonly homeDir: string; readonly projectRoot: string }): void {
81
- const path = resolveSessionStartStatePaths(options.homeDir, options.projectRoot).cooldownPath;
82
- rmSync(path, { force: true });
83
- }
84
-
85
- function cooldownDuration(baseCooldownMs: number, failureCount: number, maxCooldownMs: number): number {
86
- let duration = Math.min(baseCooldownMs, maxCooldownMs);
87
- for (let attempt = 1; attempt < failureCount && duration < maxCooldownMs; attempt += 1) {
88
- duration = Math.min(duration * 2, maxCooldownMs);
89
- }
90
- return duration;
91
- }
92
-
93
- type CooldownReadResult =
94
- | { readonly kind: "inconclusive"; readonly reason: string }
95
- | { readonly kind: "missing" }
96
- | { readonly kind: "present"; readonly value: SessionStartCooldownState };
97
-
98
- function readCooldownState(path: string, projectRoot: string): CooldownReadResult {
99
- try {
100
- const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
101
- const state = parseCooldownState(parsed, projectRoot);
102
- return state === null
103
- ? { kind: "inconclusive", reason: "cooldown state is invalid" }
104
- : { kind: "present", value: state };
105
- } catch (error) {
106
- if (!(error instanceof Error)) throw error;
107
- if (errorCode(error) === "ENOENT") return { kind: "missing" };
108
- return { kind: "inconclusive", reason: error.message };
109
- }
110
- }
111
-
112
- function parseCooldownState(value: unknown, projectRoot: string): SessionStartCooldownState | null {
113
- if (typeof value !== "object" || value === null) return null;
114
- const action = Reflect.get(value, "action");
115
- const cooldownUntil = Reflect.get(value, "cooldownUntil");
116
- const failureCount = Reflect.get(value, "failureCount");
117
- const lastFailureAt = Reflect.get(value, "lastFailureAt");
118
- const recordedRoot = Reflect.get(value, "projectRoot");
119
- const version = Reflect.get(value, "version");
120
- if (!isFailureAction(action)) return null;
121
- if (typeof cooldownUntil !== "number" || !Number.isFinite(cooldownUntil)) return null;
122
- if (typeof failureCount !== "number" || !Number.isInteger(failureCount) || failureCount < 1) return null;
123
- if (typeof lastFailureAt !== "number" || !Number.isFinite(lastFailureAt)) return null;
124
- if (recordedRoot !== projectRoot || version !== 1) return null;
125
- return { action, cooldownUntil, failureCount, lastFailureAt, projectRoot: recordedRoot, version };
126
- }
127
-
128
- function isFailureAction(value: unknown): value is SessionStartFailureAction {
129
- return typeof value === "string" && [
130
- "failed",
131
- "skipped-probe-error",
132
- "skipped-status",
133
- "skipped-unavailable",
134
- "skipped-unsupported-node",
135
- "spawn-failed",
136
- "stale-lock",
137
- "timeout",
138
- ].includes(value);
139
- }
140
-
141
- function errorCode(error: unknown): string | undefined {
142
- if (typeof error !== "object" || error === null) return undefined;
143
- const code = Reflect.get(error, "code");
144
- return typeof code === "string" ? code : undefined;
145
- }
@@ -1,22 +0,0 @@
1
- import { spawn } from "node:child_process";
2
-
3
- import type { HookStdout, WorkerSpawnInvocation } from "./hook-types.js";
4
-
5
- export function spawnDetachedSessionStartWorker(invocation: WorkerSpawnInvocation): void {
6
- const child = spawn(invocation.command, [...invocation.args], {
7
- detached: true,
8
- env: invocation.env,
9
- stdio: "ignore",
10
- windowsHide: true,
11
- });
12
- child.unref();
13
- }
14
-
15
- export function writeSessionStartNotice(stdout: HookStdout, notice: string): void {
16
- stdout.write(`${JSON.stringify({
17
- hookSpecificOutput: {
18
- hookEventName: "SessionStart",
19
- additionalContext: notice,
20
- },
21
- })}\n`);
22
- }
@@ -1,139 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
3
-
4
- import { resolveSessionStartStatePaths, stateParent } from "./session-start-paths.js";
5
-
6
- export interface SessionStartLock {
7
- readonly path: string;
8
- readonly token: string;
9
- }
10
-
11
- export type SessionStartLockResult =
12
- | { readonly kind: "acquired"; readonly lock: SessionStartLock; readonly recoveredStale: boolean }
13
- | { readonly kind: "inconclusive"; readonly reason: string }
14
- | { readonly kind: "locked" };
15
-
16
- export interface AcquireSessionStartLockOptions {
17
- readonly homeDir: string;
18
- readonly nowMs: number;
19
- readonly projectRoot: string;
20
- readonly staleMs: number;
21
- }
22
-
23
- type LockSnapshot =
24
- | { readonly kind: "inconclusive"; readonly reason: string }
25
- | { readonly kind: "missing" }
26
- | { readonly kind: "present"; readonly mtimeMs: number; readonly raw: string; readonly token: string };
27
-
28
- export function acquireSessionStartLock(options: AcquireSessionStartLockOptions): SessionStartLockResult {
29
- const paths = resolveSessionStartStatePaths(options.homeDir, options.projectRoot);
30
- const lockPath = paths.lockPath;
31
- const canonicalOptions = { ...options, projectRoot: paths.canonicalProjectRoot };
32
- try {
33
- mkdirSync(stateParent(lockPath), { recursive: true });
34
- } catch (error) {
35
- if (error instanceof Error) return { kind: "inconclusive", reason: error.message };
36
- throw error;
37
- }
38
-
39
- const initial = tryCreateLock(lockPath, canonicalOptions.projectRoot);
40
- if (initial.kind !== "locked") return initial;
41
-
42
- const snapshot = readLockSnapshot(lockPath);
43
- if (snapshot.kind === "missing") return tryCreateLock(lockPath, canonicalOptions.projectRoot);
44
- if (snapshot.kind === "inconclusive") return snapshot;
45
- if (canonicalOptions.nowMs - snapshot.mtimeMs < canonicalOptions.staleMs) return { kind: "locked" };
46
- return reclaimStaleLock(lockPath, snapshot, canonicalOptions);
47
- }
48
-
49
- export function releaseSessionStartLock(lock: SessionStartLock): boolean {
50
- const snapshot = readLockSnapshot(lock.path);
51
- if (snapshot.kind !== "present" || snapshot.token !== lock.token) return false;
52
- try {
53
- rmSync(lock.path, { force: true });
54
- return true;
55
- } catch (error) {
56
- if (error instanceof Error) return false;
57
- throw error;
58
- }
59
- }
60
-
61
- export function sessionStartLockIsOwned(lock: SessionStartLock): boolean {
62
- const snapshot = readLockSnapshot(lock.path);
63
- return snapshot.kind === "present" && snapshot.token === lock.token;
64
- }
65
-
66
- function reclaimStaleLock(
67
- lockPath: string,
68
- staleSnapshot: Extract<LockSnapshot, { readonly kind: "present" }>,
69
- options: AcquireSessionStartLockOptions,
70
- ): SessionStartLockResult {
71
- const reclaimPath = `${lockPath}.reclaim`;
72
- const reclaim = tryCreateMarker(reclaimPath);
73
- if (reclaim.kind !== "acquired") return reclaim;
74
-
75
- try {
76
- const current = readLockSnapshot(lockPath);
77
- if (current.kind === "inconclusive") return current;
78
- if (current.kind === "present") {
79
- const changed = current.raw !== staleSnapshot.raw || current.mtimeMs !== staleSnapshot.mtimeMs;
80
- if (changed || options.nowMs - current.mtimeMs < options.staleMs) return { kind: "locked" };
81
- rmSync(lockPath, { force: true });
82
- }
83
- const acquired = tryCreateLock(lockPath, options.projectRoot);
84
- return acquired.kind === "acquired" ? { ...acquired, recoveredStale: true } : acquired;
85
- } catch (error) {
86
- if (error instanceof Error) return { kind: "inconclusive", reason: error.message };
87
- throw error;
88
- } finally {
89
- rmSync(reclaimPath, { force: true });
90
- }
91
- }
92
-
93
- function tryCreateLock(lockPath: string, projectRoot: string): SessionStartLockResult {
94
- const token = randomUUID();
95
- try {
96
- writeFileSync(lockPath, `${JSON.stringify({ projectRoot, token })}\n`, {
97
- encoding: "utf8",
98
- flag: "wx",
99
- mode: 0o600,
100
- });
101
- return { kind: "acquired", lock: { path: lockPath, token }, recoveredStale: false };
102
- } catch (error) {
103
- if (!(error instanceof Error)) throw error;
104
- if (errorCode(error) === "EEXIST") return { kind: "locked" };
105
- return { kind: "inconclusive", reason: error.message };
106
- }
107
- }
108
-
109
- function tryCreateMarker(path: string): { readonly kind: "acquired" } | { readonly kind: "inconclusive"; readonly reason: string } | { readonly kind: "locked" } {
110
- try {
111
- writeFileSync(path, `${randomUUID()}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
112
- return { kind: "acquired" };
113
- } catch (error) {
114
- if (!(error instanceof Error)) throw error;
115
- if (errorCode(error) === "EEXIST") return { kind: "locked" };
116
- return { kind: "inconclusive", reason: error.message };
117
- }
118
- }
119
-
120
- function readLockSnapshot(path: string): LockSnapshot {
121
- try {
122
- const raw = readFileSync(path, "utf8");
123
- const parsed: unknown = JSON.parse(raw);
124
- if (typeof parsed !== "object" || parsed === null) return { kind: "inconclusive", reason: "lock body is not an object" };
125
- const token = Reflect.get(parsed, "token");
126
- if (typeof token !== "string" || token.length === 0) return { kind: "inconclusive", reason: "lock token is missing" };
127
- return { kind: "present", mtimeMs: statSync(path).mtimeMs, raw, token };
128
- } catch (error) {
129
- if (!(error instanceof Error)) throw error;
130
- if (errorCode(error) === "ENOENT") return { kind: "missing" };
131
- return { kind: "inconclusive", reason: error.message };
132
- }
133
- }
134
-
135
- function errorCode(error: unknown): string | undefined {
136
- if (typeof error !== "object" || error === null) return undefined;
137
- const code = Reflect.get(error, "code");
138
- return typeof code === "string" ? code : undefined;
139
- }