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.
- package/.agents/command/publish.md +95 -28
- package/.agents/skills/publish/SKILL.md +20 -6
- package/.opencode/command/publish.md +95 -28
- package/THIRD-PARTY-NOTICES.md +7 -7
- package/dist/agents/sisyphus-runtime-prompt-reconciler.d.ts +1 -1
- package/dist/cli/config-migrate.d.ts +8 -0
- package/dist/cli/doctor/checks/legacy-config-leftovers.d.ts +10 -0
- package/dist/cli/doctor/checks/model-resolution-config.d.ts +1 -1
- package/dist/cli/doctor/checks/tools-lsp.d.ts +1 -1
- package/dist/cli/doctor/framework/constants.d.ts +1 -1
- package/dist/cli/index.js +95557 -92944
- package/dist/cli-node/index.js +95539 -92926
- package/dist/config/validate.d.ts +2 -1
- package/dist/config-migration/deep-diff.d.ts +1 -0
- package/dist/config-migration/discovery-paths.d.ts +11 -0
- package/dist/config-migration/discovery-roots.d.ts +7 -0
- package/dist/config-migration/discovery.d.ts +4 -0
- package/dist/config-migration/index.d.ts +9 -0
- package/dist/config-migration/legacy-history.d.ts +3 -0
- package/dist/config-migration/migration-executor.d.ts +4 -0
- package/dist/config-migration/migration-plans.d.ts +14 -0
- package/dist/config-migration/record-values.d.ts +4 -0
- package/dist/config-migration/schema-url.d.ts +1 -0
- package/dist/config-migration/transform-config-jsonc.d.ts +2 -0
- package/dist/config-migration/transform-opencode.d.ts +2 -0
- package/dist/config-migration/transform-types.d.ts +24 -0
- package/dist/config-migration/types.d.ts +39 -0
- package/dist/features/builtin-commands/templates/hyperplan.d.ts +1 -1
- package/dist/hooks/auto-update-checker/constants.d.ts +3 -3
- package/dist/index.js +10424 -8161
- package/dist/plugin-config/omo-config-chain.d.ts +14 -0
- package/dist/plugin-config/unknown-key-diagnostics.d.ts +5 -0
- package/dist/plugin-config.d.ts +2 -2
- package/dist/shared/agent-display-names.d.ts +1 -1
- package/dist/shared/jsonc-parser.d.ts +1 -1
- package/dist/shared/migration.d.ts +5 -1
- package/dist/shared/opencode-config-dir-types.d.ts +0 -1
- package/dist/shared/plugin-identity.d.ts +6 -8
- package/dist/shared/project-discovery-dirs.d.ts +0 -1
- package/dist/skills/frontend/ATTRIBUTION.md +7 -0
- package/dist/skills/frontend/SKILL.md +4 -1
- package/dist/skills/frontend/references/design/README.md +8 -0
- package/dist/skills/frontend/references/design/_INDEX.md +14 -1
- package/dist/skills/frontend/references/design/interaction-skill.md +144 -0
- package/dist/startup-migration.d.ts +28 -0
- package/dist/testing/create-plugin-module.d.ts +4 -0
- package/dist/tui.js +5356 -4643
- package/package.json +13 -13
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +19 -3
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +11 -2
- package/packages/omo-codex/THIRD-PARTY-NOTICES.md +2 -2
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +11 -5
- package/packages/omo-codex/plugin/components/codegraph/NOTICE +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +10153 -2571
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +7311 -572
- package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
- package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +33 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +36 -10
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +164 -159
- package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +34 -0
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +20 -2
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +106 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +145 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +21 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +139 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +15 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +32 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +109 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +148 -0
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +126 -175
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +4 -4
- package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +160 -0
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +12 -138
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +28 -24
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +3 -3
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +2 -2
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +5 -5
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +3 -3
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +45 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +2 -2
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +70 -13
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +58 -0
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +104 -0
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +4 -2
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +116 -0
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +58 -57
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +31 -14
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +17 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +42 -41
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/shared/src/config-loader.ts +165 -16
- package/packages/omo-codex/plugin/shared/src/config-migration.ts +162 -0
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +186 -9
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +8 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +14 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/interaction-skill.md +144 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +17 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +1 -1
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +4 -1
- package/packages/shared-skills/skills/frontend/references/design/README.md +8 -0
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +14 -1
- package/packages/shared-skills/skills/frontend/references/design/interaction-skill.md +144 -0
- package/dist/plugin-config/layered-config-loader.d.ts +0 -2
- package/dist/plugin-config/single-config-loader.d.ts +0 -4
- package/dist/shared/migrate-legacy-config-file.d.ts +0 -1
- 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 {
|
|
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
|
-
|
|
46
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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", "
|
|
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", "
|
|
168
|
-
writeFileSync(join(workspace, ".omo", "
|
|
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", "
|
|
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:
|
|
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) =>
|
|
230
|
-
|
|
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(
|
|
240
|
-
expect(spawned).toEqual([
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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", "
|
|
101
|
-
writeFileSync(join(workspace, ".omo", "
|
|
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({
|