oh-my-opencode 4.19.2 → 4.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/.agents/command/get-unpublished-changes.md +2 -0
  2. package/.agents/command/omomomo.md +1 -1
  3. package/.agents/command/publish.md +102 -28
  4. package/.agents/skills/get-unpublished-changes/SKILL.md +2 -0
  5. package/.agents/skills/hyperplan/SKILL.md +3 -3
  6. package/.agents/skills/omomomo/SKILL.md +1 -1
  7. package/.agents/skills/publish/SKILL.md +27 -6
  8. package/.opencode/command/get-unpublished-changes.md +2 -0
  9. package/.opencode/command/omomomo.md +1 -1
  10. package/.opencode/command/publish.md +102 -28
  11. package/.opencode/skills/hyperplan/SKILL.md +3 -3
  12. package/THIRD-PARTY-NOTICES.md +7 -7
  13. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  14. package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +1 -1
  15. package/dist/cli/config-migrate.d.ts +8 -0
  16. package/dist/cli/doctor/checks/deprecated-reasoning-keys.d.ts +2 -0
  17. package/dist/cli/doctor/checks/legacy-config-leftovers.d.ts +10 -0
  18. package/dist/cli/doctor/checks/model-resolution-config.d.ts +1 -1
  19. package/dist/cli/doctor/checks/tools-lsp.d.ts +1 -1
  20. package/dist/cli/doctor/framework/constants.d.ts +1 -1
  21. package/dist/cli/index.js +95684 -92814
  22. package/dist/cli-node/index.js +95650 -92780
  23. package/dist/config/schema/agent-overrides.d.ts +800 -0
  24. package/dist/config/schema/categories.d.ts +132 -0
  25. package/dist/config/schema/fallback-models.d.ts +50 -0
  26. package/dist/config/schema/oh-my-opencode-config.d.ts +818 -2
  27. package/dist/config/validate.d.ts +2 -1
  28. package/dist/config-migration/deep-diff.d.ts +1 -0
  29. package/dist/config-migration/discovery-paths.d.ts +11 -0
  30. package/dist/config-migration/discovery-roots.d.ts +7 -0
  31. package/dist/config-migration/discovery.d.ts +4 -0
  32. package/dist/config-migration/index.d.ts +10 -0
  33. package/dist/config-migration/legacy-history.d.ts +3 -0
  34. package/dist/config-migration/migration-executor.d.ts +4 -0
  35. package/dist/config-migration/migration-plans.d.ts +15 -0
  36. package/dist/config-migration/reasoning-unification.d.ts +3 -0
  37. package/dist/config-migration/record-values.d.ts +4 -0
  38. package/dist/config-migration/schema-url.d.ts +1 -0
  39. package/dist/config-migration/transform-config-jsonc.d.ts +2 -0
  40. package/dist/config-migration/transform-opencode.d.ts +2 -0
  41. package/dist/config-migration/transform-types.d.ts +24 -0
  42. package/dist/config-migration/types.d.ts +39 -0
  43. package/dist/features/builtin-commands/templates/hyperplan.d.ts +1 -1
  44. package/dist/features/team-mode/tools/lifecycle-test-fixture.d.ts +2 -0
  45. package/dist/hooks/auto-update-checker/constants.d.ts +3 -3
  46. package/dist/hooks/codegraph-bootstrap/command-runner.d.ts +1 -0
  47. package/dist/hooks/model-fallback/next-fallback.d.ts +1 -0
  48. package/dist/hooks/runtime-fallback/constants.d.ts +1 -1
  49. package/dist/index.js +12368 -9788
  50. package/dist/oh-my-opencode.schema.json +1672 -8
  51. package/dist/plugin-config/omo-config-chain.d.ts +14 -0
  52. package/dist/plugin-config/unknown-key-diagnostics.d.ts +5 -0
  53. package/dist/plugin-config.d.ts +2 -2
  54. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +1 -0
  55. package/dist/shared/agent-display-names.d.ts +1 -1
  56. package/dist/shared/agent-variant.d.ts +11 -0
  57. package/dist/shared/jsonc-parser.d.ts +1 -1
  58. package/dist/shared/migration.d.ts +5 -1
  59. package/dist/shared/opencode-config-dir-types.d.ts +0 -1
  60. package/dist/shared/plugin-identity.d.ts +6 -8
  61. package/dist/shared/project-discovery-dirs.d.ts +0 -1
  62. package/dist/shared/session-prompt-params-helpers.d.ts +6 -1
  63. package/dist/skills/coding-agent-sessions/SKILL.md +4 -3
  64. package/dist/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  65. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +140 -0
  66. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +3 -0
  67. package/dist/skills/data-scientist/SKILL.md +243 -0
  68. package/dist/skills/data-scientist/references/common-scenarios.md +176 -0
  69. package/dist/skills/data-scientist/references/execution-templates.md +197 -0
  70. package/dist/skills/data-scientist/references/integration-patterns.md +153 -0
  71. package/dist/skills/data-scientist/references/performance-benchmarks.md +37 -0
  72. package/dist/skills/data-scientist/references/uv-setup.md +78 -0
  73. package/dist/skills/data-scientist/scripts/quick-query.py +111 -0
  74. package/dist/skills/data-scientist/scripts/setup-uv.ps1 +53 -0
  75. package/dist/skills/data-scientist/scripts/setup-uv.sh +60 -0
  76. package/dist/skills/debugging/SKILL.md +1 -1
  77. package/dist/skills/frontend/ATTRIBUTION.md +7 -0
  78. package/dist/skills/frontend/SKILL.md +4 -1
  79. package/dist/skills/frontend/references/design/README.md +8 -0
  80. package/dist/skills/frontend/references/design/_INDEX.md +14 -1
  81. package/dist/skills/frontend/references/design/interaction-skill.md +144 -0
  82. package/dist/skills/programming/SKILL.md +1 -2
  83. package/dist/skills/ulw-plan/SKILL.md +1 -1
  84. package/dist/skills/ulw-research/SKILL.md +122 -11
  85. package/dist/startup-migration.d.ts +28 -0
  86. package/dist/testing/create-plugin-module.d.ts +4 -0
  87. package/dist/tools/delegate-task/builtin-categories.d.ts +1 -0
  88. package/dist/tools/delegate-task/builtin-category-definition.d.ts +1 -0
  89. package/dist/tools/delegate-task/constants.d.ts +1 -1
  90. package/dist/tui.js +6040 -5443
  91. package/package.json +13 -13
  92. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +21 -5
  93. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +11 -2
  94. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +2 -2
  95. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  96. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +21 -3
  97. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  98. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  99. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +11 -5
  100. package/packages/omo-codex/plugin/components/codegraph/NOTICE +1 -1
  101. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +10373 -2570
  102. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +7502 -574
  103. package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
  104. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +33 -0
  105. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +36 -10
  106. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +164 -159
  107. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +34 -0
  108. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +20 -2
  109. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +106 -0
  110. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +145 -0
  111. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +21 -0
  112. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +139 -0
  113. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +15 -0
  114. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +32 -0
  115. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +109 -0
  116. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +148 -0
  117. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +126 -175
  118. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +4 -4
  119. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +160 -0
  120. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +12 -138
  121. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +34 -24
  122. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +3 -3
  123. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +2 -2
  124. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +5 -5
  125. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -3
  126. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +1 -1
  127. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +45 -0
  128. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +2 -2
  129. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +70 -13
  130. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +58 -0
  131. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +104 -0
  132. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +4 -2
  133. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +116 -0
  134. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +58 -57
  135. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  136. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  137. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  138. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  139. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  140. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  141. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  142. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  143. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  144. package/packages/omo-codex/plugin/components/rules/dist/cli.js +32 -15
  145. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  146. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  147. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +1 -1
  148. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  149. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  150. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  151. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  152. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  153. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  154. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  155. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  156. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +1 -1
  157. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  158. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  159. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +17 -0
  160. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  161. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  162. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  163. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  164. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  165. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  166. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  167. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  168. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  169. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  170. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  171. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  183. package/packages/omo-codex/plugin/package-lock.json +42 -41
  184. package/packages/omo-codex/plugin/package.json +1 -1
  185. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -0
  186. package/packages/omo-codex/plugin/shared/src/config-loader.ts +165 -16
  187. package/packages/omo-codex/plugin/shared/src/config-migration.ts +162 -0
  188. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +186 -9
  189. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +4 -3
  190. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  191. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +140 -0
  192. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +3 -0
  193. package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +243 -0
  194. package/packages/omo-codex/plugin/skills/data-scientist/agents/openai.yaml +2 -0
  195. package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +176 -0
  196. package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +197 -0
  197. package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +153 -0
  198. package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +37 -0
  199. package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +78 -0
  200. package/packages/omo-codex/plugin/skills/data-scientist/scripts/quick-query.py +111 -0
  201. package/packages/omo-codex/plugin/skills/data-scientist/scripts/setup-uv.ps1 +53 -0
  202. package/packages/omo-codex/plugin/skills/data-scientist/scripts/setup-uv.sh +60 -0
  203. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +1 -1
  204. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -0
  205. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -1
  206. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +8 -0
  207. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +14 -1
  208. package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +144 -0
  209. package/packages/omo-codex/plugin/skills/programming/SKILL.md +1 -2
  210. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +17 -0
  211. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +1 -1
  212. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +121 -11
  213. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -0
  214. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +12 -0
  215. package/packages/omo-codex/scripts/install-dist/install-local.mjs +24 -5
  216. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +4 -3
  217. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +3 -1
  218. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +140 -0
  219. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +3 -0
  220. package/packages/shared-skills/skills/data-scientist/SKILL.md +243 -0
  221. package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +176 -0
  222. package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +197 -0
  223. package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +153 -0
  224. package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +37 -0
  225. package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +78 -0
  226. package/packages/shared-skills/skills/data-scientist/scripts/quick-query.py +111 -0
  227. package/packages/shared-skills/skills/data-scientist/scripts/setup-uv.ps1 +53 -0
  228. package/packages/shared-skills/skills/data-scientist/scripts/setup-uv.sh +60 -0
  229. package/packages/shared-skills/skills/debugging/SKILL.md +1 -1
  230. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -0
  231. package/packages/shared-skills/skills/frontend/SKILL.md +4 -1
  232. package/packages/shared-skills/skills/frontend/references/design/README.md +8 -0
  233. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +14 -1
  234. package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +144 -0
  235. package/packages/shared-skills/skills/programming/SKILL.md +1 -2
  236. package/packages/shared-skills/skills/ulw-plan/SKILL.md +1 -1
  237. package/packages/shared-skills/skills/ulw-research/SKILL.md +122 -11
  238. package/dist/plugin-config/layered-config-loader.d.ts +0 -2
  239. package/dist/plugin-config/single-config-loader.d.ts +0 -4
  240. package/dist/shared/migrate-legacy-config-file.d.ts +0 -1
  241. package/dist/shared/migration/config-migration.d.ts +0 -1
@@ -0,0 +1,106 @@
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
+ }
@@ -0,0 +1,145 @@
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
+ }
@@ -0,0 +1,21 @@
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
+ });
11
+ child.unref();
12
+ }
13
+
14
+ export function writeSessionStartNotice(stdout: HookStdout, notice: string): void {
15
+ stdout.write(`${JSON.stringify({
16
+ hookSpecificOutput: {
17
+ hookEventName: "SessionStart",
18
+ additionalContext: notice,
19
+ },
20
+ })}\n`);
21
+ }
@@ -0,0 +1,139 @@
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
+ }
@@ -0,0 +1,15 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+
4
+ import { codegraphDataRoot } from "../../../../../utils/src/codegraph/paths.ts";
5
+ import type { CodegraphSessionStartOutcome } from "./hook-types.js";
6
+
7
+ export function appendSessionStartOutcome(
8
+ homeDir: string,
9
+ outcome: CodegraphSessionStartOutcome,
10
+ nowMs: number = Date.now(),
11
+ ): void {
12
+ const path = join(codegraphDataRoot(homeDir), "session-start.jsonl");
13
+ mkdirSync(dirname(path), { recursive: true });
14
+ appendFileSync(path, `${JSON.stringify({ ...outcome, timestamp: new Date(nowMs).toISOString() })}\n`);
15
+ }
@@ -0,0 +1,32 @@
1
+ import { basename, dirname, join } from "node:path";
2
+
3
+ import {
4
+ canonicalizeCodegraphPath,
5
+ resolveCodegraphWorkspacePaths,
6
+ } from "../../../../../utils/src/codegraph/paths.ts";
7
+
8
+ export interface SessionStartStatePaths {
9
+ readonly canonicalProjectRoot: string;
10
+ readonly cooldownPath: string;
11
+ readonly lockPath: string;
12
+ readonly outcomePath: string;
13
+ readonly stateDirectory: string;
14
+ }
15
+
16
+ export function resolveSessionStartStatePaths(homeDir: string, projectRoot: string): SessionStartStatePaths {
17
+ const canonicalProjectRoot = canonicalizeCodegraphPath(projectRoot);
18
+ const workspacePaths = resolveCodegraphWorkspacePaths(canonicalProjectRoot, { homeDir });
19
+ const projectKey = basename(workspacePaths.dataDir);
20
+ const stateDirectory = join(workspacePaths.dataRoot, "session-start");
21
+ return {
22
+ canonicalProjectRoot,
23
+ cooldownPath: join(stateDirectory, "cooldowns", `${projectKey}.json`),
24
+ lockPath: join(stateDirectory, "locks", `${projectKey}.lock`),
25
+ outcomePath: join(workspacePaths.dataRoot, "session-start.jsonl"),
26
+ stateDirectory,
27
+ };
28
+ }
29
+
30
+ export function stateParent(path: string): string {
31
+ return dirname(path);
32
+ }
@@ -0,0 +1,109 @@
1
+ import { realpathSync, statSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+
4
+ export type CodegraphProjectState =
5
+ | { readonly kind: "inconclusive"; readonly reason: string }
6
+ | { readonly kind: "initialized" }
7
+ | { readonly ancestorRoot: string; readonly kind: "nested-root" }
8
+ | { readonly kind: "uninitialized" };
9
+
10
+ export type CodegraphAncestorState = Exclude<CodegraphProjectState, { readonly kind: "initialized" }>;
11
+ export type CodegraphExactProjectState = Extract<
12
+ CodegraphProjectState,
13
+ { readonly kind: "inconclusive" | "initialized" | "uninitialized" }
14
+ >;
15
+
16
+ type DatabaseState =
17
+ | { readonly kind: "inconclusive"; readonly reason: string }
18
+ | { readonly kind: "missing" }
19
+ | { readonly kind: "present" };
20
+
21
+ export interface CodegraphProjectProbeOptions {
22
+ readonly probeDatabase?: (directory: string) => DatabaseState;
23
+ }
24
+
25
+ export function probeCodegraphProject(
26
+ projectRoot: string,
27
+ options: CodegraphProjectProbeOptions = {},
28
+ ): CodegraphProjectState {
29
+ const resolved = resolveProjectRoot(projectRoot);
30
+ if (resolved.kind === "inconclusive") return resolved;
31
+
32
+ const probeDatabase = options.probeDatabase ?? probeDatabaseAt;
33
+ const exact = probeResolvedExactProject(resolved.projectRoot, probeDatabase);
34
+ if (exact.kind !== "uninitialized") return exact;
35
+ return probeResolvedAncestors(resolved.projectRoot, probeDatabase);
36
+ }
37
+
38
+ export function probeExactCodegraphProject(
39
+ projectRoot: string,
40
+ options: CodegraphProjectProbeOptions = {},
41
+ ): CodegraphExactProjectState {
42
+ const resolved = resolveProjectRoot(projectRoot);
43
+ if (resolved.kind === "inconclusive") return resolved;
44
+ return probeResolvedExactProject(resolved.projectRoot, options.probeDatabase ?? probeDatabaseAt);
45
+ }
46
+
47
+ export function probeCodegraphAncestors(
48
+ projectRoot: string,
49
+ options: CodegraphProjectProbeOptions = {},
50
+ ): CodegraphAncestorState {
51
+ const resolved = resolveProjectRoot(projectRoot);
52
+ if (resolved.kind === "inconclusive") return resolved;
53
+ return probeResolvedAncestors(resolved.projectRoot, options.probeDatabase ?? probeDatabaseAt);
54
+ }
55
+
56
+ function probeResolvedExactProject(
57
+ projectRoot: string,
58
+ probeDatabase: (directory: string) => DatabaseState,
59
+ ): CodegraphExactProjectState {
60
+ const exact = probeDatabase(projectRoot);
61
+ if (exact.kind === "present") return { kind: "initialized" };
62
+ if (exact.kind === "inconclusive") return exact;
63
+ return { kind: "uninitialized" };
64
+ }
65
+
66
+ function probeResolvedAncestors(
67
+ projectRoot: string,
68
+ probeDatabase: (directory: string) => DatabaseState,
69
+ ): CodegraphAncestorState {
70
+ let ancestorRoot = dirname(projectRoot);
71
+ while (ancestorRoot !== projectRoot) {
72
+ const database = probeDatabase(ancestorRoot);
73
+ if (database.kind === "present") return { ancestorRoot, kind: "nested-root" };
74
+ if (database.kind === "inconclusive") return database;
75
+ const parent = dirname(ancestorRoot);
76
+ if (parent === ancestorRoot) break;
77
+ ancestorRoot = parent;
78
+ }
79
+ return { kind: "uninitialized" };
80
+ }
81
+
82
+ function resolveProjectRoot(projectRoot: string):
83
+ | { readonly kind: "inconclusive"; readonly reason: string }
84
+ | { readonly kind: "resolved"; readonly projectRoot: string } {
85
+ try {
86
+ return { kind: "resolved", projectRoot: realpathSync(projectRoot) };
87
+ } catch (error) {
88
+ if (error instanceof Error) return { kind: "inconclusive", reason: error.message };
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ function probeDatabaseAt(directory: string): DatabaseState {
94
+ try {
95
+ statSync(join(directory, ".codegraph", "codegraph.db"));
96
+ return { kind: "present" };
97
+ } catch (error) {
98
+ if (!(error instanceof Error)) throw error;
99
+ const code = errorCode(error);
100
+ if (code === "ENOENT" || code === "ENOTDIR") return { kind: "missing" };
101
+ return { kind: "inconclusive", reason: error.message };
102
+ }
103
+ }
104
+
105
+ function errorCode(error: unknown): string | undefined {
106
+ if (typeof error !== "object" || error === null) return undefined;
107
+ const code = Reflect.get(error, "code");
108
+ return typeof code === "string" ? code : undefined;
109
+ }