oh-my-opencode 4.19.2 → 4.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/.agents/command/publish.md +95 -28
  2. package/.agents/skills/publish/SKILL.md +20 -6
  3. package/.opencode/command/publish.md +95 -28
  4. package/THIRD-PARTY-NOTICES.md +7 -7
  5. package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +1 -1
  6. package/dist/cli/config-migrate.d.ts +8 -0
  7. package/dist/cli/doctor/checks/legacy-config-leftovers.d.ts +10 -0
  8. package/dist/cli/doctor/checks/model-resolution-config.d.ts +1 -1
  9. package/dist/cli/doctor/checks/tools-lsp.d.ts +1 -1
  10. package/dist/cli/doctor/framework/constants.d.ts +1 -1
  11. package/dist/cli/index.js +95557 -92944
  12. package/dist/cli-node/index.js +95539 -92926
  13. package/dist/config/validate.d.ts +2 -1
  14. package/dist/config-migration/deep-diff.d.ts +1 -0
  15. package/dist/config-migration/discovery-paths.d.ts +11 -0
  16. package/dist/config-migration/discovery-roots.d.ts +7 -0
  17. package/dist/config-migration/discovery.d.ts +4 -0
  18. package/dist/config-migration/index.d.ts +9 -0
  19. package/dist/config-migration/legacy-history.d.ts +3 -0
  20. package/dist/config-migration/migration-executor.d.ts +4 -0
  21. package/dist/config-migration/migration-plans.d.ts +14 -0
  22. package/dist/config-migration/record-values.d.ts +4 -0
  23. package/dist/config-migration/schema-url.d.ts +1 -0
  24. package/dist/config-migration/transform-config-jsonc.d.ts +2 -0
  25. package/dist/config-migration/transform-opencode.d.ts +2 -0
  26. package/dist/config-migration/transform-types.d.ts +24 -0
  27. package/dist/config-migration/types.d.ts +39 -0
  28. package/dist/features/builtin-commands/templates/hyperplan.d.ts +1 -1
  29. package/dist/hooks/auto-update-checker/constants.d.ts +3 -3
  30. package/dist/index.js +10424 -8161
  31. package/dist/plugin-config/omo-config-chain.d.ts +14 -0
  32. package/dist/plugin-config/unknown-key-diagnostics.d.ts +5 -0
  33. package/dist/plugin-config.d.ts +2 -2
  34. package/dist/shared/agent-display-names.d.ts +1 -1
  35. package/dist/shared/jsonc-parser.d.ts +1 -1
  36. package/dist/shared/migration.d.ts +5 -1
  37. package/dist/shared/opencode-config-dir-types.d.ts +0 -1
  38. package/dist/shared/plugin-identity.d.ts +6 -8
  39. package/dist/shared/project-discovery-dirs.d.ts +0 -1
  40. package/dist/skills/frontend/ATTRIBUTION.md +7 -0
  41. package/dist/skills/frontend/SKILL.md +4 -1
  42. package/dist/skills/frontend/references/design/README.md +8 -0
  43. package/dist/skills/frontend/references/design/_INDEX.md +14 -1
  44. package/dist/skills/frontend/references/design/interaction-skill.md +144 -0
  45. package/dist/startup-migration.d.ts +28 -0
  46. package/dist/testing/create-plugin-module.d.ts +4 -0
  47. package/dist/tui.js +5356 -4643
  48. package/package.json +13 -13
  49. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +19 -3
  50. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +11 -2
  51. package/packages/omo-codex/THIRD-PARTY-NOTICES.md +2 -2
  52. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  53. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  54. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +11 -5
  56. package/packages/omo-codex/plugin/components/codegraph/NOTICE +1 -1
  57. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +10153 -2571
  58. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +7311 -572
  59. package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
  60. package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +33 -0
  61. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +36 -10
  62. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +164 -159
  63. package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +34 -0
  64. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +20 -2
  65. package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +106 -0
  66. package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +145 -0
  67. package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +21 -0
  68. package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +139 -0
  69. package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +15 -0
  70. package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +32 -0
  71. package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +109 -0
  72. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +148 -0
  73. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +126 -175
  74. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +4 -4
  75. package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +160 -0
  76. package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +12 -138
  77. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +28 -24
  78. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +3 -3
  79. package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +2 -2
  80. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +5 -5
  81. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -3
  82. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +1 -1
  83. package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +45 -0
  84. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +2 -2
  85. package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +70 -13
  86. package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +58 -0
  87. package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +104 -0
  88. package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +4 -2
  89. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +116 -0
  90. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +58 -57
  91. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  92. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  93. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  94. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  95. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  96. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  97. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  98. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  99. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  100. package/packages/omo-codex/plugin/components/rules/dist/cli.js +31 -14
  101. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  102. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  103. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  104. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  105. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  106. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  107. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  108. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  109. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  110. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  111. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  112. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  113. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +17 -0
  114. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  116. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  117. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  118. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  119. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  120. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  121. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  122. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  123. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  124. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  125. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  126. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  127. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  128. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  129. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  130. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  131. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  132. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  133. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  134. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  135. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  136. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  137. package/packages/omo-codex/plugin/package-lock.json +42 -41
  138. package/packages/omo-codex/plugin/package.json +1 -1
  139. package/packages/omo-codex/plugin/shared/src/config-loader.ts +165 -16
  140. package/packages/omo-codex/plugin/shared/src/config-migration.ts +162 -0
  141. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +186 -9
  142. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -0
  143. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -1
  144. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +8 -0
  145. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +14 -1
  146. package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +144 -0
  147. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +17 -0
  148. package/packages/omo-codex/scripts/install-dist/install-local.mjs +1 -1
  149. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -0
  150. package/packages/shared-skills/skills/frontend/SKILL.md +4 -1
  151. package/packages/shared-skills/skills/frontend/references/design/README.md +8 -0
  152. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +14 -1
  153. package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +144 -0
  154. package/dist/plugin-config/layered-config-loader.d.ts +0 -2
  155. package/dist/plugin-config/single-config-loader.d.ts +0 -4
  156. package/dist/shared/migrate-legacy-config-file.d.ts +0 -1
  157. package/dist/shared/migration/config-migration.d.ts +0 -1
@@ -0,0 +1,160 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import { Readable } from "node:stream";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import {
9
+ executeCodegraphSessionStartHook,
10
+ type CodegraphSessionStartOutcome,
11
+ type WorkerSpawnInvocation,
12
+ } from "../src/hook.ts";
13
+ import { recordSessionStartFailure } from "../src/session-start-cooldown.ts";
14
+
15
+ const pluginRoot = resolve(fileURLToPath(new URL("../../..", import.meta.url)));
16
+
17
+ function createAllowedWorkspace(prefix: string): string {
18
+ return mkdtempSync(join(pluginRoot, `.tmp-${prefix}-`));
19
+ }
20
+
21
+ describe("CodeGraph SessionStart spawn guards", () => {
22
+ it("#given an inconclusive probe timeout #when SessionStart runs #then timeout is not treated as uninitialized", async () => {
23
+ // given
24
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-timeout-home-"));
25
+ const workspace = createAllowedWorkspace("codegraph-timeout-workspace");
26
+ const spawned: WorkerSpawnInvocation[] = [];
27
+ try {
28
+ // when
29
+ const result = await executeCodegraphSessionStartHook({
30
+ ancestorProbe: () => ({ kind: "inconclusive", reason: "status timed out" }),
31
+ config: { codegraph: { enabled: true }, sources: [], warnings: [] },
32
+ cwd: workspace,
33
+ env: { HOME: homeDir },
34
+ spawnWorker: (invocation) => spawned.push(invocation),
35
+ stdin: Readable.from(["{}"]),
36
+ sweepZombies: () => undefined,
37
+ });
38
+
39
+ // then
40
+ expect(result).toEqual({ action: "skipped-inconclusive", exitCode: 0 });
41
+ expect(spawned).toEqual([]);
42
+ } finally {
43
+ rmSync(homeDir, { recursive: true, force: true });
44
+ rmSync(workspace, { recursive: true, force: true });
45
+ }
46
+ });
47
+
48
+ it("#given one hook already owns the project lock #when a second SessionStart runs #then it records skipped-locked without spawning", async () => {
49
+ // given
50
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-dedup-home-"));
51
+ const workspace = createAllowedWorkspace("codegraph-dedup-workspace");
52
+ const outcomes: CodegraphSessionStartOutcome[] = [];
53
+ const spawned: WorkerSpawnInvocation[] = [];
54
+ const options = {
55
+ ancestorProbe: () => ({ kind: "uninitialized" } as const),
56
+ config: { codegraph: { enabled: true }, sources: [], warnings: [] },
57
+ cwd: workspace,
58
+ env: { HOME: homeDir },
59
+ logOutcome: (outcome: CodegraphSessionStartOutcome) => outcomes.push(outcome),
60
+ spawnWorker: (invocation: WorkerSpawnInvocation) => {
61
+ spawned.push(invocation);
62
+ },
63
+ stdin: Readable.from(["{}"]),
64
+ sweepZombies: () => undefined,
65
+ };
66
+ try {
67
+ await executeCodegraphSessionStartHook(options);
68
+
69
+ // when
70
+ const second = await executeCodegraphSessionStartHook({ ...options, stdin: Readable.from(["{}"]) });
71
+
72
+ // then
73
+ expect(second).toEqual({ action: "skipped-locked", exitCode: 0 });
74
+ expect(spawned).toHaveLength(1);
75
+ expect(outcomes).toContainEqual({ action: "skipped-locked", projectRoot: workspace });
76
+ } finally {
77
+ rmSync(homeDir, { recursive: true, force: true });
78
+ rmSync(workspace, { recursive: true, force: true });
79
+ }
80
+ });
81
+
82
+ it("#given a failed worker cooldown #when SessionStart retries before and after expiry #then only the expired retry spawns", async () => {
83
+ // given
84
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-cooldown-hook-home-"));
85
+ const workspace = createAllowedWorkspace("codegraph-cooldown-hook-workspace");
86
+ const outcomes: CodegraphSessionStartOutcome[] = [];
87
+ const spawned: WorkerSpawnInvocation[] = [];
88
+ try {
89
+ recordSessionStartFailure({
90
+ action: "failed",
91
+ baseCooldownMs: 60_000,
92
+ homeDir,
93
+ nowMs: 1_000,
94
+ projectRoot: workspace,
95
+ });
96
+ const baseOptions = {
97
+ ancestorProbe: () => ({ kind: "uninitialized" } as const),
98
+ config: { codegraph: { enabled: true, session_start_cooldown_ms: 60_000 }, sources: [], warnings: [] },
99
+ cwd: workspace,
100
+ env: { HOME: homeDir },
101
+ logOutcome: (outcome: CodegraphSessionStartOutcome) => outcomes.push(outcome),
102
+ spawnWorker: (invocation: WorkerSpawnInvocation) => {
103
+ spawned.push(invocation);
104
+ },
105
+ sweepZombies: () => undefined,
106
+ };
107
+
108
+ // when
109
+ const suppressed = await executeCodegraphSessionStartHook({ ...baseOptions, nowMs: 60_999, stdin: Readable.from(["{}"]) });
110
+ const expired = await executeCodegraphSessionStartHook({ ...baseOptions, nowMs: 61_000, stdin: Readable.from(["{}"]) });
111
+
112
+ // then
113
+ expect(suppressed).toEqual({ action: "skipped-cooldown", exitCode: 0 });
114
+ expect(expired).toEqual({ action: "spawned", exitCode: 0 });
115
+ expect(spawned).toHaveLength(1);
116
+ expect(outcomes).toContainEqual({
117
+ action: "skipped-cooldown",
118
+ cooldownUntil: 61_000,
119
+ failureCount: 1,
120
+ projectRoot: workspace,
121
+ });
122
+ } finally {
123
+ rmSync(homeDir, { recursive: true, force: true });
124
+ rmSync(workspace, { recursive: true, force: true });
125
+ }
126
+ });
127
+
128
+ it("#given an ancestor has a CodeGraph database #when SessionStart runs in a nested project #then it records skipped-nested-root", async () => {
129
+ // given
130
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-nested-home-"));
131
+ const ancestorRoot = createAllowedWorkspace("codegraph-nested-ancestor");
132
+ const workspace = join(ancestorRoot, "apps", "server");
133
+ const outcomes: CodegraphSessionStartOutcome[] = [];
134
+ const spawned: WorkerSpawnInvocation[] = [];
135
+ try {
136
+ mkdirSync(join(ancestorRoot, ".codegraph"), { recursive: true });
137
+ mkdirSync(workspace, { recursive: true });
138
+ writeFileSync(join(ancestorRoot, ".codegraph", "codegraph.db"), "fixture");
139
+
140
+ // when
141
+ const result = await executeCodegraphSessionStartHook({
142
+ config: { codegraph: { enabled: true }, sources: [], warnings: [] },
143
+ cwd: workspace,
144
+ env: { HOME: homeDir },
145
+ logOutcome: (outcome) => outcomes.push(outcome),
146
+ spawnWorker: (invocation) => spawned.push(invocation),
147
+ stdin: Readable.from(["{}"]),
148
+ sweepZombies: () => undefined,
149
+ });
150
+
151
+ // then
152
+ expect(result).toEqual({ action: "skipped-nested-root", exitCode: 0 });
153
+ expect(spawned).toEqual([]);
154
+ expect(outcomes).toContainEqual({ action: "skipped-nested-root", ancestorRoot, projectRoot: workspace });
155
+ } finally {
156
+ rmSync(homeDir, { recursive: true, force: true });
157
+ rmSync(ancestorRoot, { recursive: true, force: true });
158
+ }
159
+ });
160
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { Readable } from "node:stream";
@@ -9,108 +9,33 @@ import { executeCodegraphSessionStartHook, type WorkerSpawnInvocation } from "..
9
9
 
10
10
  const pluginRoot = resolve(fileURLToPath(new URL("../../..", import.meta.url)));
11
11
 
12
- // Observed verbatim from a REAL codegraph 1.4.1 binary running `status --json` (exit 0)
13
- // against a project store built by a REAL codegraph 1.0.1 binary
14
- // (see .omo/evidence/codegraph-daemon-process-hygiene/task-2-store-upgrade.txt).
15
- const MIGRATED_1_0_1_STORE_STATUS_JSON = JSON.stringify({
16
- initialized: true,
17
- version: "1.4.1",
18
- projectPath: "/private/tmp/cg-task2/fixture",
19
- indexPath: "/private/tmp/cg-task2/fixture/.codegraph",
20
- lastIndexed: "2026-07-21T04:28:09.593Z",
21
- fileCount: 2,
22
- nodeCount: 5,
23
- edgeCount: 4,
24
- dbSizeBytes: 159744,
25
- backend: "node-sqlite",
26
- journalMode: "wal",
27
- nodesByKind: { file: 2, function: 3 },
28
- languages: ["typescript"],
29
- pendingChanges: { added: 0, modified: 0, removed: 0 },
30
- worktreeMismatch: null,
31
- index: {
32
- builtWithVersion: "1.0.1",
33
- builtWithExtractionVersion: 24,
34
- currentExtractionVersion: 24,
35
- reindexRecommended: false,
36
- state: null,
37
- pendingRefs: 0,
38
- },
39
- });
40
-
41
12
  function createAllowedWorkspace(prefix: string): string {
42
13
  return mkdtempSync(join(pluginRoot, `.tmp-${prefix}-`));
43
14
  }
44
15
 
45
- // On win32 execFile cannot run a #!/bin/sh script, so emit a codegraph.cmd batch file instead;
46
- // resolveCodegraphCommandInvocation wraps .cmd in `cmd.exe /d /s /c`, exactly like the real codegraph.cmd.
47
- function createFakeCodegraphBin(scripts: {
48
- readonly posix: string;
49
- readonly win32: string;
50
- readonly win32Sidecar?: string;
51
- }): { readonly binPath: string; readonly dir: string } {
52
- const dir = mkdtempSync(join(tmpdir(), "omo-codegraph-fake-bin-"));
53
- if (process.platform === "win32") {
54
- const binPath = join(dir, "codegraph.cmd");
55
- writeFileSync(binPath, scripts.win32);
56
- if (scripts.win32Sidecar !== undefined) writeFileSync(join(dir, "hang.cjs"), scripts.win32Sidecar);
57
- return { binPath, dir };
58
- }
59
- const binPath = join(dir, "codegraph");
60
- writeFileSync(binPath, scripts.posix, { mode: 0o755 });
61
- chmodSync(binPath, 0o755);
62
- return { binPath, dir };
63
- }
64
-
65
- // Keep the probe hermetic on POSIX but viable on win32: resolveCodegraphCommandInvocation wraps
66
- // the fake .cmd in `cmd.exe /d /s /c`, and Windows can only resolve cmd.exe when the child env
67
- // carries the real system PATH (System32) plus SystemRoot (both whitelisted in SAFE_AMBIENT_ENV_KEYS).
68
- // A POSIX-only PATH makes the probe spawn fail, so the hook reports not-initialized and spawns.
69
- function probeEnv(homeDir: string, binPath: string): Record<string, string> {
70
- if (process.platform === "win32") {
71
- const env: Record<string, string> = {
72
- HOME: homeDir,
73
- OMO_CODEGRAPH_BIN: binPath,
74
- };
75
- if (process.env["PATH"] !== undefined) env["PATH"] = process.env["PATH"];
76
- if (process.env["SystemRoot"] !== undefined) env["SystemRoot"] = process.env["SystemRoot"];
77
- return env;
78
- }
79
- return { HOME: homeDir, OMO_CODEGRAPH_BIN: binPath, PATH: "/usr/bin:/bin" };
80
- }
81
-
82
- function isProcessAlive(pid: number): boolean {
83
- try {
84
- process.kill(pid, 0);
85
- return true;
86
- } catch {
87
- return false;
88
- }
89
- }
90
-
91
- describe("CodeGraph SessionStart hook with a 1.0.1-era project store under the 1.4.1 binary", () => {
92
- it("#given a real 1.4.1 status payload for a migrated 1.0.1 store #when SessionStart probes via the default probe #then it stays silent without spawning a re-init worker", async () => {
16
+ describe("CodeGraph SessionStart hook with a migrated 1.0.1 project store under CodeGraph 1.5.0", () => {
17
+ it("#given the migrated store has an exact database #when SessionStart runs #then it never invokes the CodeGraph CLI", async () => {
93
18
  // given
94
19
  const stdout: string[] = [];
95
20
  const spawned: WorkerSpawnInvocation[] = [];
96
21
  const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-upgrade-home-"));
97
- // The payload is a single JSON line with no cmd metacharacters (% ^ & < > |), so a plain echo is safe.
98
- const fake = createFakeCodegraphBin({
99
- posix: `#!/bin/sh\nprintf '%s\\n' '${MIGRATED_1_0_1_STORE_STATUS_JSON}'\n`,
100
- win32: `@echo off\r\n@echo ${MIGRATED_1_0_1_STORE_STATUS_JSON}\r\n`,
101
- });
102
22
  const workspace = createAllowedWorkspace("codegraph-upgrade-workspace");
103
- mkdirSync(join(workspace, ".codegraph"), { recursive: true });
104
-
105
23
  try {
24
+ mkdirSync(join(workspace, ".codegraph"), { recursive: true });
25
+ writeFileSync(join(workspace, ".codegraph", "codegraph.db"), "migrated-store");
26
+
106
27
  // when
107
28
  const result = await executeCodegraphSessionStartHook({
29
+ ancestorProbe: () => {
30
+ throw new Error("the exact database check must be the complete fast path");
31
+ },
108
32
  config: { codegraph: { enabled: true }, sources: [], warnings: [] },
109
33
  cwd: workspace,
110
- env: probeEnv(homeDir, fake.binPath),
34
+ env: { HOME: homeDir },
111
35
  stdin: Readable.from(["{}"]),
112
36
  stdout: { write: (chunk) => stdout.push(chunk) },
113
37
  spawnWorker: (invocation) => spawned.push(invocation),
38
+ sweepZombies: () => undefined,
114
39
  });
115
40
 
116
41
  // then
@@ -119,58 +44,7 @@ describe("CodeGraph SessionStart hook with a 1.0.1-era project store under the 1
119
44
  expect(stdout.join("")).toBe("");
120
45
  } finally {
121
46
  rmSync(homeDir, { recursive: true, force: true });
122
- rmSync(fake.dir, { recursive: true, force: true });
123
- rmSync(workspace, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 });
124
- }
125
- });
126
-
127
- it("#given the status probe spawns a hanging descendant #when SessionStart times out #then it exits promptly and reaps the descendant", async () => {
128
- // given
129
- const stdout: string[] = [];
130
- const spawned: WorkerSpawnInvocation[] = [];
131
- const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-slow-home-"));
132
- const survivorPidPath = join(homeDir, "survivor.pid");
133
- const fake = createFakeCodegraphBin({
134
- posix: "#!/bin/sh\nsleep 30 &\necho \"$!\" > \"$HOME/survivor.pid\"\nwait\n",
135
- win32: '@echo off\r\nnode "%~dp0hang.cjs"\r\n',
136
- win32Sidecar: [
137
- 'const { spawn } = require("node:child_process");',
138
- 'const { writeFileSync } = require("node:fs");',
139
- 'const { join } = require("node:path");',
140
- 'const child = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { stdio: "ignore" });',
141
- 'writeFileSync(join(process.env.HOME, "survivor.pid"), String(child.pid));',
142
- "setInterval(() => {}, 1000);",
143
- "",
144
- ].join("\n"),
145
- });
146
- const workspace = createAllowedWorkspace("codegraph-slow-probe-workspace");
147
- let descendantPid: number | undefined;
148
-
149
- try {
150
- // when
151
- const startedAt = Date.now();
152
- const result = await executeCodegraphSessionStartHook({
153
- config: { codegraph: { enabled: true }, sources: [], warnings: [] },
154
- cwd: workspace,
155
- env: probeEnv(homeDir, fake.binPath),
156
- stdin: Readable.from(["{}"]),
157
- stdout: { write: (chunk) => stdout.push(chunk) },
158
- spawnWorker: (invocation) => spawned.push(invocation),
159
- });
160
- const elapsedMs = Date.now() - startedAt;
161
- descendantPid = Number.parseInt(readFileSync(survivorPidPath, "utf8").trim(), 10);
162
-
163
- // then
164
- expect(result.exitCode).toBe(0);
165
- expect(result.action).toBe("spawned");
166
- expect(spawned).toHaveLength(1);
167
- expect(elapsedMs).toBeLessThan(15_000);
168
- expect(isProcessAlive(descendantPid)).toBeFalse();
169
- } finally {
170
- if (descendantPid !== undefined && isProcessAlive(descendantPid)) process.kill(descendantPid, "SIGKILL");
171
- rmSync(homeDir, { recursive: true, force: true });
172
- rmSync(fake.dir, { recursive: true, force: true });
173
- rmSync(workspace, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 });
47
+ rmSync(workspace, { recursive: true, force: true });
174
48
  }
175
49
  });
176
50
  });
@@ -39,8 +39,9 @@ describe("CodeGraph SessionStart hook", () => {
39
39
  env: { HOME: homeDir },
40
40
  stdin: Readable.from(["{}"]),
41
41
  stdout: { write: (chunk) => stdout.push(chunk) },
42
+ ancestorProbe: () => ({ kind: "uninitialized" }),
42
43
  spawnWorker: (invocation) => spawned.push(invocation),
43
- statusProbe: () => Promise.resolve(false),
44
+ sweepZombies: () => undefined,
44
45
  });
45
46
 
46
47
  // then
@@ -132,7 +133,7 @@ describe("CodeGraph SessionStart hook", () => {
132
133
  try {
133
134
  mkdirSync(join(homeDir, ".omo"), { recursive: true });
134
135
  writeFileSync(
135
- join(homeDir, ".omo", "config.jsonc"),
136
+ join(homeDir, ".omo", "omo.jsonc"),
136
137
  '{ "codegraph": { "enabled": true }, "[codex]": { "codegraph": { "enabled": false } } }\n',
137
138
  );
138
139
 
@@ -164,8 +165,8 @@ describe("CodeGraph SessionStart hook", () => {
164
165
  try {
165
166
  mkdirSync(join(homeDir, ".omo"), { recursive: true });
166
167
  mkdirSync(join(workspace, ".omo"), { recursive: true });
167
- writeFileSync(join(homeDir, ".omo", "config.jsonc"), '{ "codegraph": { "enabled": true } }\n');
168
- writeFileSync(join(workspace, ".omo", "config.jsonc"), '{ "[codex]": { "codegraph": { "enabled": false } } }\n');
168
+ writeFileSync(join(homeDir, ".omo", "omo.jsonc"), '{ "codegraph": { "enabled": true } }\n');
169
+ writeFileSync(join(workspace, ".omo", "omo.jsonc"), '{ "[codex]": { "codegraph": { "enabled": false } } }\n');
169
170
 
170
171
  // when
171
172
  const result = await executeCodegraphSessionStartHook({
@@ -193,7 +194,7 @@ describe("CodeGraph SessionStart hook", () => {
193
194
  const spawned: WorkerSpawnInvocation[] = [];
194
195
  try {
195
196
  mkdirSync(join(homeDir, ".omo"), { recursive: true });
196
- writeFileSync(join(homeDir, ".omo", "config.jsonc"), '{ "codegraph": { "enabled": true } }\n');
197
+ writeFileSync(join(homeDir, ".omo", "omo.jsonc"), '{ "codegraph": { "enabled": true } }\n');
197
198
 
198
199
  // when
199
200
  const result = await executeCodegraphSessionStartHook({
@@ -215,38 +216,33 @@ describe("CodeGraph SessionStart hook", () => {
215
216
  // given
216
217
  const stdout: string[] = [];
217
218
  const spawned: WorkerSpawnInvocation[] = [];
218
- const statusProbeDaemonValues: boolean[] = [];
219
219
  const workspace = createAllowedWorkspace("codegraph-workspace");
220
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-spawn-home-"));
220
221
 
221
222
  try {
222
223
  // when
223
224
  const result = await executeCodegraphSessionStartHook({
225
+ ancestorProbe: () => ({ kind: "uninitialized" }),
224
226
  config: { codegraph: { enabled: true }, sources: [], warnings: [] },
225
227
  cwd: workspace,
226
- env: { HOME: "/tmp/home", KEEP: "1", OPENAI_API_KEY: "sk-test-secret" },
228
+ env: { HOME: homeDir, KEEP: "1", OPENAI_API_KEY: "sk-test-secret" },
227
229
  stdin: Readable.from(["{}"]),
228
230
  stdout: { write: (chunk) => stdout.push(chunk) },
229
- spawnWorker: (invocation) => spawned.push(invocation),
230
- statusProbe: (options) => {
231
- statusProbeDaemonValues.push(options.daemon);
232
- return Promise.resolve(false);
231
+ spawnWorker: (invocation) => {
232
+ spawned.push(invocation);
233
233
  },
234
+ sweepZombies: () => undefined,
234
235
  workerCliPath: "/plugin/components/codegraph/dist/cli.js",
235
236
  });
236
237
 
237
238
  // then
238
239
  expect(result).toEqual({ action: "spawned", exitCode: 0 });
239
- expect(statusProbeDaemonValues).toEqual([true]);
240
- expect(spawned).toEqual([
241
- {
242
- args: ["/plugin/components/codegraph/dist/cli.js", "hook", "session-start-worker"],
243
- command: process.execPath,
244
- env: {
245
- HOME: "/tmp/home",
246
- OMO_CODEGRAPH_SESSION_START_CWD: workspace,
247
- },
248
- },
249
- ]);
240
+ expect(spawned).toHaveLength(1);
241
+ expect(spawned[0]?.args).toEqual(["/plugin/components/codegraph/dist/cli.js", "hook", "session-start-worker"]);
242
+ expect(spawned[0]?.command).toBe(process.execPath);
243
+ expect(spawned[0]?.env["HOME"]).toBe(homeDir);
244
+ expect(spawned[0]?.env["OMO_CODEGRAPH_SESSION_START_CWD"]).toBe(workspace);
245
+ expect(spawned[0]?.env["OMO_CODEGRAPH_SESSION_START_LOCK_TOKEN"]).toMatch(/^[0-9a-f-]{36}$/);
250
246
  expect(spawned[0]?.env["OPENAI_API_KEY"]).toBeUndefined();
251
247
  expect(spawned[0]?.env["KEEP"]).toBeUndefined();
252
248
  expect(JSON.parse(stdout.join(""))).toEqual({
@@ -256,6 +252,7 @@ describe("CodeGraph SessionStart hook", () => {
256
252
  },
257
253
  });
258
254
  } finally {
255
+ rmSync(homeDir, { recursive: true, force: true });
259
256
  rmSync(workspace, { recursive: true, force: true });
260
257
  }
261
258
  });
@@ -265,17 +262,23 @@ describe("CodeGraph SessionStart hook", () => {
265
262
  const stdout: string[] = [];
266
263
  const spawned: WorkerSpawnInvocation[] = [];
267
264
  const workspace = createAllowedWorkspace("codegraph-initialized-workspace");
265
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-initialized-home-"));
268
266
 
269
267
  try {
268
+ mkdirSync(join(workspace, ".codegraph"), { recursive: true });
269
+ writeFileSync(join(workspace, ".codegraph", "codegraph.db"), "fixture");
270
+
270
271
  // when
271
272
  const result = await executeCodegraphSessionStartHook({
273
+ ancestorProbe: () => {
274
+ throw new Error("an initialized exact root must not invoke any fallback probe");
275
+ },
272
276
  config: { codegraph: { enabled: true }, sources: [], warnings: [] },
273
277
  cwd: workspace,
274
- env: { HOME: "/tmp/home", KEEP: "1" },
278
+ env: { HOME: homeDir, KEEP: "1" },
275
279
  stdin: Readable.from(["{}"]),
276
280
  stdout: { write: (chunk) => stdout.push(chunk) },
277
281
  spawnWorker: (invocation) => spawned.push(invocation),
278
- statusProbe: () => Promise.resolve(true),
279
282
  });
280
283
 
281
284
  // then
@@ -283,6 +286,7 @@ describe("CodeGraph SessionStart hook", () => {
283
286
  expect(spawned).toEqual([]);
284
287
  expect(stdout.join("")).toBe("");
285
288
  } finally {
289
+ rmSync(homeDir, { recursive: true, force: true });
286
290
  rmSync(workspace, { recursive: true, force: true });
287
291
  }
288
292
  });
@@ -1,6 +1,6 @@
1
1
  import { chmodSync, writeFileSync } from "node:fs";
2
2
 
3
- export const CODEGRAPH_141_DEFAULT_TOOLS: readonly Record<string, unknown>[] = [
3
+ export const CODEGRAPH_150_DEFAULT_TOOLS: readonly Record<string, unknown>[] = [
4
4
  {
5
5
  name: "codegraph_explore",
6
6
  description:
@@ -41,10 +41,10 @@ export function writeFakeNewlineCodegraph(filePath: string): void {
41
41
  "rl.on('line', (line) => {",
42
42
  " const request = JSON.parse(line);",
43
43
  " if (request.method === 'initialize') {",
44
- " process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { capabilities: { tools: { listChanged: false } }, protocolVersion: request.params.protocolVersion, serverInfo: { name: 'codegraph', version: '1.4.1' } } }) + '\\n');",
44
+ " process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { capabilities: { tools: { listChanged: false } }, protocolVersion: request.params.protocolVersion, serverInfo: { name: 'codegraph', version: '1.5.0' } } }) + '\\n');",
45
45
  " }",
46
46
  " if (request.method === 'tools/list') {",
47
- ` process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { tools: ${JSON.stringify(CODEGRAPH_141_DEFAULT_TOOLS)} } }) + '\\n');`,
47
+ ` process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { tools: ${JSON.stringify(CODEGRAPH_150_DEFAULT_TOOLS)} } }) + '\\n');`,
48
48
  " }",
49
49
  "});",
50
50
  ].join("\n"),
@@ -43,7 +43,7 @@ describe("CodeGraph component runtime package metadata", () => {
43
43
  const files = packageJson.files ?? [];
44
44
 
45
45
  // then
46
- expect(optionalDependencies["@colbymchenry/codegraph"]).toBe("1.4.1");
46
+ expect(optionalDependencies["@colbymchenry/codegraph"]).toBe("1.5.0");
47
47
  expect(files).toContain("LICENSE");
48
48
  expect(files).toContain("NODE-RUNTIME-LICENSES.md");
49
49
  expect(files).toContain("NOTICE");
@@ -58,7 +58,7 @@ describe("CodeGraph component runtime package metadata", () => {
58
58
  // given
59
59
  const notice = readFileSync(resolve(componentRoot, "NOTICE"), "utf8");
60
60
 
61
- expect(notice).toContain("@colbymchenry/codegraph@1.0.1");
61
+ expect(notice).toContain("@colbymchenry/codegraph@1.5.0");
62
62
  expect(notice).toContain("MIT license");
63
63
  expect(notice).toContain("Node.js v24.16.0 runtime");
64
64
  expect(notice).toContain("NODE-RUNTIME-LICENSES.md");
@@ -77,19 +77,19 @@ describe("CodeGraph provisioned launcher Node guard", () => {
77
77
  source: provisioned === null ? "path" : "provisioned",
78
78
  };
79
79
  },
80
+ resolveManagedBin: () => binPath,
80
81
  runCommand: (_projectRoot, command, args) => {
81
82
  calls.push({ args, command });
82
- return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
83
+ mkdirSync(join(workspace, ".codegraph"), { recursive: true });
84
+ writeFileSync(join(workspace, ".codegraph", "codegraph.db"), "fixture");
85
+ return Promise.resolve({ exitCode: 0, stdout: "", timedOut: false });
83
86
  },
84
87
  },
85
88
  });
86
89
 
87
90
  // then
88
91
  expect(result).toEqual({ action: "initialized" });
89
- expect(calls).toEqual([
90
- { args: ["status", "--json"], command: binPath },
91
- { args: ["init"], command: binPath },
92
- ]);
92
+ expect(calls).toEqual([{ args: ["init"], command: binPath }]);
93
93
  expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "provisioned", timedOut: false }]);
94
94
  } finally {
95
95
  rmSync(workspace, { recursive: true, force: true });
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import { runCodegraphServe } from "../src/serve.ts";
8
8
  import {
9
9
  arrayProperty,
10
- CODEGRAPH_141_DEFAULT_TOOLS,
10
+ CODEGRAPH_150_DEFAULT_TOOLS,
11
11
  findTool,
12
12
  frameMcpRequest,
13
13
  parseMcpBodies,
@@ -87,14 +87,14 @@ describe("runCodegraphServe MCP protocol bridge", () => {
87
87
  result: {
88
88
  capabilities: { tools: { listChanged: false } },
89
89
  protocolVersion: "2025-06-18",
90
- serverInfo: { name: "codegraph", version: "1.4.1" },
90
+ serverInfo: { name: "codegraph", version: "1.5.0" },
91
91
  },
92
92
  },
93
93
  {
94
94
  id: 2,
95
95
  jsonrpc: "2.0",
96
96
  result: {
97
- tools: CODEGRAPH_141_DEFAULT_TOOLS,
97
+ tools: CODEGRAPH_150_DEFAULT_TOOLS,
98
98
  },
99
99
  },
100
100
  ]);
@@ -48,7 +48,7 @@ describe("runCodegraphServe MCP unavailable facade", () => {
48
48
  result: {
49
49
  capabilities: { tools: { listChanged: false } },
50
50
  protocolVersion: "2025-06-18",
51
- serverInfo: { name: "codegraph", version: "1.4.1" },
51
+ serverInfo: { name: "codegraph", version: "1.5.0" },
52
52
  },
53
53
  });
54
54
  });
@@ -1,4 +1,6 @@
1
1
  import { describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
2
4
  import { join } from "node:path";
3
5
 
4
6
  import { runCodegraphServe } from "../src/serve.ts";
@@ -51,6 +53,49 @@ describe("runCodegraphServe provisioning", () => {
51
53
  ]);
52
54
  });
53
55
 
56
+ it("#given a bundled runtime and a stale managed install #when serving MCP #then it upgrades and uses the managed runtime", async () => {
57
+ // given
58
+ const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-serve-managed-upgrade-"));
59
+ const installDir = join(homeDir, ".omo", "codegraph");
60
+ const binPath = join(installDir, "bin", "codegraph");
61
+ mkdirSync(join(installDir, "bin"), { recursive: true });
62
+ mkdirSync(join(installDir, ".provisioned"), { recursive: true });
63
+ writeFileSync(binPath, "stale codegraph\n");
64
+ writeFileSync(
65
+ join(installDir, ".provisioned", "codegraph-1.4.1.json"),
66
+ `${JSON.stringify({ binPath, version: "1.4.1" })}\n`,
67
+ );
68
+ const provisionCalls: unknown[] = [];
69
+ const processCalls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
70
+
71
+ try {
72
+ // when
73
+ const exitCode = await runCodegraphServe({
74
+ config: { codegraph: { enabled: true }, sources: [], warnings: [] },
75
+ homeDir,
76
+ managedInstallExists: () => true,
77
+ nodeVersion: "22.14.0",
78
+ resolve: () => ({ argsPrefix: ["codegraph.js"], command: "/opt/node22/bin/node", exists: true, source: "bundled" }),
79
+ resolveManagedBin: () => null,
80
+ ensureProvisioned: (options) => {
81
+ provisionCalls.push(options);
82
+ return Promise.resolve({ binPath, provisioned: true });
83
+ },
84
+ runProcess: (command, args) => {
85
+ processCalls.push({ args, command });
86
+ return Promise.resolve(0);
87
+ },
88
+ });
89
+
90
+ // then
91
+ expect(exitCode).toBe(0);
92
+ expect(provisionCalls).toEqual([{ installDir, lockDir: join(installDir, ".locks"), version: "1.5.0" }]);
93
+ expect(processCalls).toEqual([{ args: ["serve", "--mcp"], command: binPath }]);
94
+ } finally {
95
+ rmSync(homeDir, { force: true, recursive: true });
96
+ }
97
+ });
98
+
54
99
  it("#given CodeGraph is unresolved and codegraph.daemon=false #when serving MCP #then provisions CodeGraph with daemon-off", async () => {
55
100
  // given
56
101
  const binPath = join("/tmp/home/.omo/codegraph", "bin", "codegraph");
@@ -97,8 +97,8 @@ describe("runCodegraphServe unavailable CodeGraph paths", () => {
97
97
  try {
98
98
  mkdirSync(join(homeDir, ".omo"), { recursive: true });
99
99
  mkdirSync(join(workspace, ".omo"), { recursive: true });
100
- writeFileSync(join(homeDir, ".omo", "config.jsonc"), '{ "codegraph": { "enabled": true } }\n');
101
- writeFileSync(join(workspace, ".omo", "config.jsonc"), '{ "[codex]": { "codegraph": { "enabled": false } } }\n');
100
+ writeFileSync(join(homeDir, ".omo", "omo.jsonc"), '{ "codegraph": { "enabled": true } }\n');
101
+ writeFileSync(join(workspace, ".omo", "omo.jsonc"), '{ "[codex]": { "codegraph": { "enabled": false } } }\n');
102
102
 
103
103
  // when
104
104
  const exitCode = await runCodegraphServe({