oh-my-opencode 4.11.0 → 4.12.0
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/skills/codex-qa/scripts/lib/app-server-client.mjs +132 -82
- package/.agents/skills/codex-qa/scripts/lib/app-server-client.test.js +48 -0
- package/.agents/skills/opencode-qa/scripts/serve-wake-split-probe.sh +361 -84
- package/.agents/skills/work-with-pr/SKILL.md +19 -5
- package/.opencode/skills/work-with-pr/SKILL.md +19 -5
- package/dist/agents/atlas/agent.d.ts +3 -2
- package/dist/agents/sisyphus/glm-5-2.d.ts +4 -0
- package/dist/agents/sisyphus/index.d.ts +1 -0
- package/dist/agents/sisyphus-agent-config.d.ts +1 -0
- package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
- package/dist/agents/sisyphus-junior/glm-5-2.d.ts +1 -0
- package/dist/agents/sisyphus-junior/index.d.ts +1 -0
- package/dist/agents/types.d.ts +7 -6
- package/dist/cli/index.js +449 -318
- package/dist/cli-node/index.js +449 -318
- package/dist/features/background-agent/parent-wake-flush-runner.d.ts +1 -0
- package/dist/features/background-agent/parent-wake-history-state.d.ts +1 -0
- package/dist/features/background-agent/parent-wake-session-history.d.ts +1 -47
- package/dist/features/background-agent/parent-wake-session-message.d.ts +47 -0
- package/dist/hooks/keyword-detector/ultrawork/glm.d.ts +2 -0
- package/dist/hooks/keyword-detector/ultrawork/index.d.ts +4 -2
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +5 -4
- package/dist/index.js +1185 -87
- package/dist/plugin/tool-execute-before.d.ts +2 -0
- package/dist/plugin-handlers/plugin-components-loader.d.ts +1 -0
- package/dist/skills/init-deep/SKILL.md +21 -26
- package/dist/skills/programming/SKILL.md +25 -121
- package/dist/skills/programming/references/code-smells.md +390 -0
- package/dist/skills/ulw-plan/SKILL.md +2 -1
- package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
- package/dist/skills/ulw-plan/references/intent-clear.md +2 -2
- package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -2
- package/dist/tools/delegate-task/sync-session-turns.d.ts +3 -0
- package/dist/tui.js +16 -7
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +22 -2
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +51 -21
- 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/dist/cli.js +18 -7
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +9 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +8 -7
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +7 -2
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +2 -1
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +20 -19
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +94 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +33 -0
- 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/comment-checker/test/package-smoke.test.ts +2 -71
- 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/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +26 -104
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +2 -2
- 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/rules/src/sparkshell-awareness.ts +2 -2
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +12 -92
- package/packages/omo-codex/plugin/components/rules/test/sparkshell-awareness.test.ts +3 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +157 -234
- 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 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +227 -15
- package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +62 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +34 -3
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +139 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +111 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +250 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +220 -0
- 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/test-support/package-smoke-fixture.ts +158 -0
- 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/ultrawork/skills/ulw-plan/SKILL.md +2 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +76 -49
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +17 -0
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +17 -0
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +16 -0
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +16 -0
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +16 -0
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +16 -0
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +16 -0
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +17 -0
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +16 -0
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +16 -0
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +16 -0
- package/packages/omo-codex/plugin/package-lock.json +12 -22
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +1 -1
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +14 -11
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +15 -4
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +37 -0
- package/packages/omo-codex/plugin/skills/ast-grep/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/comment-checker/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/debugging/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/frontend/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/git-master/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +21 -26
- package/packages/omo-codex/plugin/skills/init-deep/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lsp/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/lsp-setup/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +25 -121
- package/packages/omo-codex/plugin/skills/programming/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/programming/references/code-smells.md +390 -0
- package/packages/omo-codex/plugin/skills/refactor/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/remove-ai-slops/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/review-work/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/rules/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +139 -0
- package/packages/omo-codex/plugin/skills/teammode/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +111 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +250 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +220 -0
- package/packages/omo-codex/plugin/skills/ultraresearch/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -2
- package/packages/omo-codex/plugin/skills/visual-qa/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +34 -33
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +5 -2
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +13 -0
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/bootstrap-hooks.test.mjs +14 -6
- package/packages/omo-codex/plugin/test/bootstrap-ps-guard.test.mjs +7 -3
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +31 -0
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +3 -3
- package/packages/omo-codex/plugin/test/display-metadata.test.mjs +83 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +30 -19
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +3 -3
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +6 -6
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +9 -4
- package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +88 -0
- package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +240 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +412 -216
- package/packages/omo-codex/scripts/install-git-bash-mcp-env.test.mjs +24 -0
- package/packages/omo-codex/scripts/install-hook-targets.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +2 -2
- package/packages/omo-codex/scripts/install-local-bun-global-update.test.mjs +63 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +109 -1
- package/packages/omo-codex/scripts/install-local.mjs +1 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +36 -0
- package/packages/shared-skills/skills/init-deep/SKILL.md +21 -26
- package/packages/shared-skills/skills/programming/SKILL.md +25 -121
- package/packages/shared-skills/skills/programming/references/code-smells.md +390 -0
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +2 -1
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -2
- package/packages/omo-codex/plugin/hooks/hooks.json +0 -214
|
@@ -2144,30 +2144,44 @@ async function exists3(path) {
|
|
|
2144
2144
|
import { readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
|
|
2145
2145
|
import { join as join15 } from "node:path";
|
|
2146
2146
|
var GIT_BASH_ENV_KEY = "OMO_CODEX_GIT_BASH_PATH";
|
|
2147
|
+
var CODEGRAPH_RELATIVE_ARGS = new Set(["components/codegraph/dist/serve.js", "./components/codegraph/dist/serve.js"]);
|
|
2147
2148
|
async function stampGitBashMcpEnv(input) {
|
|
2148
|
-
if (input.platform !== "win32")
|
|
2149
|
-
return false;
|
|
2150
|
-
const rawOverride = input.env?.[GIT_BASH_ENV_KEY];
|
|
2151
|
-
const override = typeof rawOverride === "string" ? rawOverride.trim() : "";
|
|
2152
|
-
if (override === "")
|
|
2153
|
-
return false;
|
|
2154
2149
|
const manifestPath = join15(input.pluginRoot, ".mcp.json");
|
|
2155
2150
|
if (!await fileExistsStrict(manifestPath))
|
|
2156
2151
|
return false;
|
|
2157
2152
|
const parsed = JSON.parse(await readFile10(manifestPath, "utf8"));
|
|
2158
2153
|
if (!isPlainRecord(parsed) || !isPlainRecord(parsed["mcpServers"]))
|
|
2159
2154
|
return false;
|
|
2160
|
-
|
|
2161
|
-
if (
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2155
|
+
let changed = stampCodegraphMcpPath(parsed["mcpServers"], input.pluginRoot);
|
|
2156
|
+
if (input.platform === "win32") {
|
|
2157
|
+
const rawOverride = input.env?.[GIT_BASH_ENV_KEY];
|
|
2158
|
+
const override = typeof rawOverride === "string" ? rawOverride.trim() : "";
|
|
2159
|
+
const gitBashServer = parsed["mcpServers"]["git_bash"];
|
|
2160
|
+
if (override !== "" && isPlainRecord(gitBashServer)) {
|
|
2161
|
+
const serverEnv = isPlainRecord(gitBashServer["env"]) ? gitBashServer["env"] : {};
|
|
2162
|
+
if (serverEnv[GIT_BASH_ENV_KEY] !== override) {
|
|
2163
|
+
gitBashServer["env"] = { ...serverEnv, [GIT_BASH_ENV_KEY]: override };
|
|
2164
|
+
changed = true;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (!changed)
|
|
2165
2169
|
return false;
|
|
2166
|
-
gitBashServer["env"] = { ...serverEnv, [GIT_BASH_ENV_KEY]: override };
|
|
2167
2170
|
await writeFile6(manifestPath, `${JSON.stringify(parsed, null, "\t")}
|
|
2168
2171
|
`);
|
|
2169
2172
|
return true;
|
|
2170
2173
|
}
|
|
2174
|
+
function stampCodegraphMcpPath(mcpServers, pluginRoot) {
|
|
2175
|
+
const codegraphServer = mcpServers["codegraph"];
|
|
2176
|
+
if (!isPlainRecord(codegraphServer) || !Array.isArray(codegraphServer["args"]))
|
|
2177
|
+
return false;
|
|
2178
|
+
const args = codegraphServer["args"];
|
|
2179
|
+
const entrypoint = args[0];
|
|
2180
|
+
if (typeof entrypoint !== "string" || !CODEGRAPH_RELATIVE_ARGS.has(entrypoint))
|
|
2181
|
+
return false;
|
|
2182
|
+
codegraphServer["args"] = [join15(pluginRoot, "components", "codegraph", "dist", "serve.js"), ...args.slice(1)];
|
|
2183
|
+
return true;
|
|
2184
|
+
}
|
|
2171
2185
|
|
|
2172
2186
|
// ../src/install/codex-hook-trust.ts
|
|
2173
2187
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2190,17 +2204,33 @@ async function trustedHookStatesForPlugin(input) {
|
|
|
2190
2204
|
if (!await exists4(manifestPath))
|
|
2191
2205
|
return [];
|
|
2192
2206
|
const manifest = JSON.parse(await readFile11(manifestPath, "utf8"));
|
|
2193
|
-
if (!isPlainRecord(manifest)
|
|
2194
|
-
return [];
|
|
2195
|
-
const hooksPath = join16(input.pluginRoot, manifest.hooks);
|
|
2196
|
-
if (!await exists4(hooksPath))
|
|
2207
|
+
if (!isPlainRecord(manifest))
|
|
2197
2208
|
return [];
|
|
2198
|
-
const
|
|
2199
|
-
|
|
2209
|
+
const states = [];
|
|
2210
|
+
for (const hookPath of hookManifestPaths(manifest.hooks)) {
|
|
2211
|
+
const hooksPath = join16(input.pluginRoot, hookPath);
|
|
2212
|
+
if (!await exists4(hooksPath))
|
|
2213
|
+
continue;
|
|
2214
|
+
const parsed = JSON.parse(await readFile11(hooksPath, "utf8"));
|
|
2215
|
+
if (!isPlainRecord(parsed) || !isPlainRecord(parsed.hooks))
|
|
2216
|
+
continue;
|
|
2217
|
+
states.push(...trustedHookStatesForHooksFile({
|
|
2218
|
+
keySource: `${input.pluginName}@${input.marketplaceName}:${hookPath}`,
|
|
2219
|
+
hooks: parsed.hooks
|
|
2220
|
+
}));
|
|
2221
|
+
}
|
|
2222
|
+
return states;
|
|
2223
|
+
}
|
|
2224
|
+
function hookManifestPaths(value) {
|
|
2225
|
+
if (typeof value === "string" && value.trim() !== "")
|
|
2226
|
+
return [stripDotSlash(value)];
|
|
2227
|
+
if (!Array.isArray(value))
|
|
2200
2228
|
return [];
|
|
2201
|
-
|
|
2229
|
+
return value.filter((item) => typeof item === "string" && item.trim() !== "").map(stripDotSlash);
|
|
2230
|
+
}
|
|
2231
|
+
function trustedHookStatesForHooksFile(input) {
|
|
2202
2232
|
const states = [];
|
|
2203
|
-
for (const [eventName, groups] of Object.entries(
|
|
2233
|
+
for (const [eventName, groups] of Object.entries(input.hooks)) {
|
|
2204
2234
|
if (!Array.isArray(groups))
|
|
2205
2235
|
continue;
|
|
2206
2236
|
const eventLabel = EVENT_LABELS.get(eventName);
|
|
@@ -2216,7 +2246,7 @@ async function trustedHookStatesForPlugin(input) {
|
|
|
2216
2246
|
continue;
|
|
2217
2247
|
if (typeof handler.command !== "string" || handler.command.trim() === "")
|
|
2218
2248
|
continue;
|
|
2219
|
-
const key = `${keySource}:${eventLabel}:${groupIndex}:${handlerIndex}`;
|
|
2249
|
+
const key = `${input.keySource}:${eventLabel}:${groupIndex}:${handlerIndex}`;
|
|
2220
2250
|
states.push({ key, trustedHash: commandHookHash(eventLabel, group.matcher, handler) });
|
|
2221
2251
|
}
|
|
2222
2252
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
|
|
9
9
|
"commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
|
|
10
10
|
"timeout": 30,
|
|
11
|
-
"statusMessage": "
|
|
11
|
+
"statusMessage": "(OmO) Checking Bootstrap Provisioning"
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
14
|
}
|
|
@@ -1595,6 +1595,9 @@ function bunWhich(commandName) {
|
|
|
1595
1595
|
}
|
|
1596
1596
|
|
|
1597
1597
|
// ../../utils/src/codegraph/resolve.ts
|
|
1598
|
+
function codegraphCommandRequiresSupportedLocalNode(resolution) {
|
|
1599
|
+
return resolution.source !== "bundled" && resolution.source !== "env" && resolution.source !== "provisioned";
|
|
1600
|
+
}
|
|
1598
1601
|
var CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
|
|
1599
1602
|
var CODEGRAPH_ENV_BIN = "OMO_CODEGRAPH_BIN";
|
|
1600
1603
|
var CODEGRAPH_LEGACY_ENV_BIN = "CODEGRAPH_BIN";
|
|
@@ -1864,7 +1867,7 @@ function finish(action, detail, logOutcome) {
|
|
|
1864
1867
|
async function resolveOrProvisionCommand(deps, config, env, homeDir, nodeSupport) {
|
|
1865
1868
|
const resolved = deps.resolveCommand({ env, homeDir, provisioned: () => provisionedBinFromInstallDir(config.install_dir) });
|
|
1866
1869
|
if (resolved.exists) {
|
|
1867
|
-
if (resolved
|
|
1870
|
+
if (codegraphCommandRequiresSupportedLocalNode(resolved) && !nodeSupport.supported) {
|
|
1868
1871
|
return { kind: "unsupported-node" };
|
|
1869
1872
|
}
|
|
1870
1873
|
return { kind: "resolved", resolution: resolved };
|
|
@@ -1990,7 +1993,6 @@ async function executeCodegraphSessionStartHook(options = {}) {
|
|
|
1990
1993
|
const homeDir = resolveHomeDir2(env);
|
|
1991
1994
|
const config = options.config ?? getCodexOmoConfig({ cwd: projectRoot, env, homeDir });
|
|
1992
1995
|
if (config.codegraph?.enabled === false) {
|
|
1993
|
-
writeHookJson(options.stdout ?? processStdout, "skipped-disabled");
|
|
1994
1996
|
return { action: "skipped-disabled", exitCode: 0 };
|
|
1995
1997
|
}
|
|
1996
1998
|
(options.spawnWorker ?? spawnDetachedWorker)({
|
|
@@ -1998,11 +2000,16 @@ async function executeCodegraphSessionStartHook(options = {}) {
|
|
|
1998
2000
|
command: process.execPath,
|
|
1999
2001
|
env: { ...env, [SESSION_START_CWD_ENV]: projectRoot }
|
|
2000
2002
|
});
|
|
2001
|
-
writeHookJson(options.stdout ?? processStdout
|
|
2003
|
+
writeHookJson(options.stdout ?? processStdout);
|
|
2002
2004
|
return { action: "spawned", exitCode: 0 };
|
|
2003
2005
|
}
|
|
2004
|
-
function writeHookJson(stdout
|
|
2005
|
-
const output =
|
|
2006
|
+
function writeHookJson(stdout) {
|
|
2007
|
+
const output = {
|
|
2008
|
+
hookSpecificOutput: {
|
|
2009
|
+
hookEventName: "SessionStart",
|
|
2010
|
+
additionalContext: CODEGRAPH_SESSION_START_NOTICE
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
2006
2013
|
stdout.write(`${JSON.stringify(output)}
|
|
2007
2014
|
`);
|
|
2008
2015
|
}
|
|
@@ -2078,12 +2085,16 @@ async function runCodegraphServe(options = {}) {
|
|
|
2078
2085
|
provisioned: () => provisionedBinFromInstallDir2(codegraphConfig.install_dir)
|
|
2079
2086
|
};
|
|
2080
2087
|
const resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
|
|
2088
|
+
const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
|
|
2081
2089
|
if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync7)) {
|
|
2090
|
+
if (resolution.source === "path" && !nodeSupport.supported) {
|
|
2091
|
+
(options.stderr ?? processStderr2).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
2092
|
+
return 1;
|
|
2093
|
+
}
|
|
2082
2094
|
(options.stderr ?? processStderr2).write(CODEGRAPH_SKIP_HINT);
|
|
2083
2095
|
return 1;
|
|
2084
2096
|
}
|
|
2085
|
-
|
|
2086
|
-
if (resolution.source !== "bundled" && resolution.source !== "env" && !nodeSupport.supported) {
|
|
2097
|
+
if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
|
|
2087
2098
|
(options.stderr ?? processStderr2).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
2088
2099
|
return 1;
|
|
2089
2100
|
}
|
|
@@ -132,6 +132,9 @@ function bunWhich(commandName) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// ../../../../utils/src/codegraph/resolve.ts
|
|
135
|
+
function codegraphCommandRequiresSupportedLocalNode(resolution) {
|
|
136
|
+
return resolution.source !== "bundled" && resolution.source !== "env" && resolution.source !== "provisioned";
|
|
137
|
+
}
|
|
135
138
|
var CODEGRAPH_PACKAGE = "@colbymchenry/codegraph";
|
|
136
139
|
var CODEGRAPH_ENV_BIN = "OMO_CODEGRAPH_BIN";
|
|
137
140
|
var CODEGRAPH_LEGACY_ENV_BIN = "CODEGRAPH_BIN";
|
|
@@ -1511,12 +1514,16 @@ async function runCodegraphServe(options = {}) {
|
|
|
1511
1514
|
provisioned: () => provisionedBinFromInstallDir(codegraphConfig.install_dir)
|
|
1512
1515
|
};
|
|
1513
1516
|
const resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
|
|
1517
|
+
const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
|
|
1514
1518
|
if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync4)) {
|
|
1519
|
+
if (resolution.source === "path" && !nodeSupport.supported) {
|
|
1520
|
+
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
1521
|
+
return 1;
|
|
1522
|
+
}
|
|
1515
1523
|
(options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
|
|
1516
1524
|
return 1;
|
|
1517
1525
|
}
|
|
1518
|
-
|
|
1519
|
-
if (resolution.source !== "bundled" && resolution.source !== "env" && !nodeSupport.supported) {
|
|
1526
|
+
if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
|
|
1520
1527
|
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
1521
1528
|
return 1;
|
|
1522
1529
|
}
|
|
@@ -8,7 +8,6 @@ import { getCodexOmoConfig } from "../../../shared/src/config-loader.ts";
|
|
|
8
8
|
import { SESSION_START_CWD_ENV } from "./session-start-worker.js";
|
|
9
9
|
import type {
|
|
10
10
|
HookStdout,
|
|
11
|
-
SessionStartAction,
|
|
12
11
|
SessionStartHookOptions,
|
|
13
12
|
SessionStartHookResult,
|
|
14
13
|
WorkerSpawnInvocation,
|
|
@@ -47,7 +46,6 @@ export async function executeCodegraphSessionStartHook(options: SessionStartHook
|
|
|
47
46
|
const config = options.config ?? getCodexOmoConfig({ cwd: projectRoot, env, homeDir });
|
|
48
47
|
|
|
49
48
|
if (config.codegraph?.enabled === false) {
|
|
50
|
-
writeHookJson(options.stdout ?? processStdout, "skipped-disabled");
|
|
51
49
|
return { action: "skipped-disabled", exitCode: 0 };
|
|
52
50
|
}
|
|
53
51
|
|
|
@@ -56,14 +54,17 @@ export async function executeCodegraphSessionStartHook(options: SessionStartHook
|
|
|
56
54
|
command: process.execPath,
|
|
57
55
|
env: { ...env, [SESSION_START_CWD_ENV]: projectRoot },
|
|
58
56
|
});
|
|
59
|
-
writeHookJson(options.stdout ?? processStdout
|
|
57
|
+
writeHookJson(options.stdout ?? processStdout);
|
|
60
58
|
return { action: "spawned", exitCode: 0 };
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
function writeHookJson(stdout: HookStdout
|
|
64
|
-
const output =
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
function writeHookJson(stdout: HookStdout): void {
|
|
62
|
+
const output = {
|
|
63
|
+
hookSpecificOutput: {
|
|
64
|
+
hookEventName: "SessionStart",
|
|
65
|
+
additionalContext: CODEGRAPH_SESSION_START_NOTICE,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
67
68
|
stdout.write(`${JSON.stringify(output)}\n`);
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
evaluateCodegraphNodeSupport,
|
|
18
18
|
} from "../../../../../utils/src/codegraph/node-support.ts";
|
|
19
19
|
import {
|
|
20
|
+
codegraphCommandRequiresSupportedLocalNode,
|
|
20
21
|
resolveCodegraphCommand,
|
|
21
22
|
type CodegraphCommandResolution,
|
|
22
23
|
type ResolveCodegraphCommandOptions,
|
|
@@ -82,13 +83,17 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
|
|
|
82
83
|
provisioned: () => provisionedBinFromInstallDir(codegraphConfig.install_dir),
|
|
83
84
|
} satisfies ResolveCodegraphCommandOptions;
|
|
84
85
|
const resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
|
|
86
|
+
const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
|
|
85
87
|
if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync)) {
|
|
88
|
+
if (resolution.source === "path" && !nodeSupport.supported) {
|
|
89
|
+
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
86
92
|
(options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
|
|
87
93
|
return 1;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
if (resolution.source !== "bundled" && resolution.source !== "env" && !nodeSupport.supported) {
|
|
96
|
+
if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
|
|
92
97
|
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
93
98
|
return 1;
|
|
94
99
|
}
|
|
@@ -9,6 +9,7 @@ import { buildCodegraphEnv } from "../../../../../utils/src/codegraph/env.ts";
|
|
|
9
9
|
import { evaluateCodegraphNodeSupport, type CodegraphNodeSupport } from "../../../../../utils/src/codegraph/node-support.ts";
|
|
10
10
|
import { ensureCodegraphProvisioned } from "../../../../../utils/src/codegraph/provision.ts";
|
|
11
11
|
import {
|
|
12
|
+
codegraphCommandRequiresSupportedLocalNode,
|
|
12
13
|
resolveCodegraphCommand,
|
|
13
14
|
type CodegraphCommandResolution,
|
|
14
15
|
} from "../../../../../utils/src/codegraph/resolve.ts";
|
|
@@ -103,7 +104,7 @@ async function resolveOrProvisionCommand(
|
|
|
103
104
|
): Promise<ResolutionResult> {
|
|
104
105
|
const resolved = deps.resolveCommand({ env, homeDir, provisioned: () => provisionedBinFromInstallDir(config.install_dir) });
|
|
105
106
|
if (resolved.exists) {
|
|
106
|
-
if (resolved
|
|
107
|
+
if (codegraphCommandRequiresSupportedLocalNode(resolved) && !nodeSupport.supported) {
|
|
107
108
|
return { kind: "unsupported-node" };
|
|
108
109
|
}
|
|
109
110
|
return { kind: "resolved", resolution: resolved };
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "../src/hook.ts";
|
|
15
15
|
|
|
16
16
|
const pluginRoot = resolve(fileURLToPath(new URL("../../..", import.meta.url)));
|
|
17
|
-
const
|
|
17
|
+
const pluginConfigPath = resolve(pluginRoot, ".codex-plugin/plugin.json");
|
|
18
18
|
|
|
19
19
|
describe("CodeGraph SessionStart hook", () => {
|
|
20
20
|
it("#given hook session-start cli args #when invoked with empty JSON input #then it emits valid JSON and exits zero", async () => {
|
|
@@ -42,7 +42,6 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
42
42
|
hookEventName: "SessionStart",
|
|
43
43
|
additionalContext: "LazyCodex CodeGraph bootstrap scheduled in background",
|
|
44
44
|
},
|
|
45
|
-
codegraph: { action: "spawned" },
|
|
46
45
|
});
|
|
47
46
|
} finally {
|
|
48
47
|
rmSync(homeDir, { recursive: true, force: true });
|
|
@@ -66,10 +65,7 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
66
65
|
// then
|
|
67
66
|
expect(result).toEqual({ action: "skipped-disabled", exitCode: 0 });
|
|
68
67
|
expect(spawned).toEqual([]);
|
|
69
|
-
expect(
|
|
70
|
-
hookSpecificOutput: { hookEventName: "SessionStart" },
|
|
71
|
-
codegraph: { action: "skipped-disabled" },
|
|
72
|
-
});
|
|
68
|
+
expect(stdout.join("")).toBe("");
|
|
73
69
|
});
|
|
74
70
|
|
|
75
71
|
it("#given HOME OMO config disables Codex CodeGraph #when SessionStart fires #then it skips without spawning", async () => {
|
|
@@ -97,7 +93,7 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
97
93
|
// then
|
|
98
94
|
expect(result).toEqual({ action: "skipped-disabled", exitCode: 0 });
|
|
99
95
|
expect(spawned).toEqual([]);
|
|
100
|
-
expect(
|
|
96
|
+
expect(stdout.join("")).toBe("");
|
|
101
97
|
} finally {
|
|
102
98
|
rmSync(homeDir, { recursive: true, force: true });
|
|
103
99
|
rmSync(workspace, { recursive: true, force: true });
|
|
@@ -128,7 +124,7 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
128
124
|
// then
|
|
129
125
|
expect(result).toEqual({ action: "skipped-disabled", exitCode: 0 });
|
|
130
126
|
expect(spawned).toEqual([]);
|
|
131
|
-
expect(
|
|
127
|
+
expect(stdout.join("")).toBe("");
|
|
132
128
|
} finally {
|
|
133
129
|
rmSync(homeDir, { recursive: true, force: true });
|
|
134
130
|
rmSync(workspace, { recursive: true, force: true });
|
|
@@ -191,7 +187,12 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
191
187
|
},
|
|
192
188
|
},
|
|
193
189
|
]);
|
|
194
|
-
expect(JSON.parse(stdout.join(""))
|
|
190
|
+
expect(JSON.parse(stdout.join(""))).toEqual({
|
|
191
|
+
hookSpecificOutput: {
|
|
192
|
+
additionalContext: "LazyCodex CodeGraph bootstrap scheduled in background",
|
|
193
|
+
hookEventName: "SessionStart",
|
|
194
|
+
},
|
|
195
|
+
});
|
|
195
196
|
} finally {
|
|
196
197
|
rmSync(workspace, { recursive: true, force: true });
|
|
197
198
|
}
|
|
@@ -514,7 +515,7 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
514
515
|
}
|
|
515
516
|
});
|
|
516
517
|
|
|
517
|
-
it("#given malformed hook input #when SessionStart fires #then it
|
|
518
|
+
it("#given malformed hook input with CodeGraph disabled #when SessionStart fires #then it stays silent and exits zero", async () => {
|
|
518
519
|
// given
|
|
519
520
|
const stdout: string[] = [];
|
|
520
521
|
const spawned: WorkerSpawnInvocation[] = [];
|
|
@@ -531,23 +532,23 @@ describe("CodeGraph SessionStart hook", () => {
|
|
|
531
532
|
// then
|
|
532
533
|
expect(result.exitCode).toBe(0);
|
|
533
534
|
expect(spawned).toEqual([]);
|
|
534
|
-
expect(
|
|
535
|
+
expect(stdout.join("")).toBe("");
|
|
535
536
|
});
|
|
536
537
|
|
|
537
538
|
it("#given plugin hook config #when inspected #then CodeGraph is registered after bootstrap SessionStart", () => {
|
|
538
539
|
// given
|
|
539
|
-
const
|
|
540
|
+
const pluginConfig: unknown = JSON.parse(readFileSync(pluginConfigPath, "utf8"));
|
|
540
541
|
|
|
541
542
|
// when
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
543
|
+
const hookPaths =
|
|
544
|
+
typeof pluginConfig === "object" && pluginConfig !== null && "hooks" in pluginConfig && Array.isArray(pluginConfig.hooks)
|
|
545
|
+
? pluginConfig.hooks.filter((hookPath): hookPath is string => typeof hookPath === "string")
|
|
546
|
+
: [];
|
|
546
547
|
|
|
547
548
|
// then
|
|
548
|
-
expect(
|
|
549
|
-
expect(
|
|
550
|
-
|
|
549
|
+
expect(hookPaths).toContain("./hooks/session-start-checking-codegraph-bootstrap.json");
|
|
550
|
+
expect(hookPaths.indexOf("./hooks/session-start-checking-bootstrap-provisioning.json")).toBeLessThan(
|
|
551
|
+
hookPaths.indexOf("./hooks/session-start-checking-codegraph-bootstrap.json"),
|
|
551
552
|
);
|
|
552
553
|
});
|
|
553
554
|
});
|
|
@@ -0,0 +1,94 @@
|
|
|
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 } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { runCodegraphSessionStartWorker } from "../src/hook.ts";
|
|
7
|
+
import { runCodegraphServe } from "../src/serve.ts";
|
|
8
|
+
|
|
9
|
+
describe("CodeGraph provisioned launcher Node guard", () => {
|
|
10
|
+
it("#given provisioned CodeGraph binary #when serve runs under unsupported local Node #then it trusts the launcher", async () => {
|
|
11
|
+
// given
|
|
12
|
+
const commandPath = "/home/test/.omo/codegraph/bin/codegraph";
|
|
13
|
+
const spawned: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
|
|
14
|
+
|
|
15
|
+
// when
|
|
16
|
+
const exitCode = await runCodegraphServe({
|
|
17
|
+
env: {},
|
|
18
|
+
nodeVersion: "26.3.0",
|
|
19
|
+
buildEnv: () => ({}),
|
|
20
|
+
resolve: () => ({ argsPrefix: [], command: commandPath, exists: true, source: "provisioned" }),
|
|
21
|
+
runProcess: (command, args) => {
|
|
22
|
+
spawned.push({ args, command });
|
|
23
|
+
return Promise.resolve(0);
|
|
24
|
+
},
|
|
25
|
+
stderr: { write: () => undefined },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// then
|
|
29
|
+
expect(exitCode).toBe(0);
|
|
30
|
+
expect(spawned).toEqual([{ args: ["serve", "--mcp"], command: commandPath }]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("#given provisioned CodeGraph exists #when SessionStart worker runs under unsupported local Node #then it bootstraps through the launcher", async () => {
|
|
34
|
+
// given
|
|
35
|
+
const workspace = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node25-"));
|
|
36
|
+
const homeDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node25-home-"));
|
|
37
|
+
const installDir = mkdtempSync(join(tmpdir(), "omo-codegraph-worker-node25-install-"));
|
|
38
|
+
const binPath = join(installDir, "bin", "codegraph");
|
|
39
|
+
const calls: Array<{ readonly args: readonly string[]; readonly command: string }> = [];
|
|
40
|
+
const outcomes: unknown[] = [];
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(join(installDir, "bin"), { recursive: true });
|
|
44
|
+
writeFileSync(binPath, "");
|
|
45
|
+
|
|
46
|
+
// when
|
|
47
|
+
const result = await runCodegraphSessionStartWorker({
|
|
48
|
+
config: { codegraph: { enabled: true, install_dir: installDir }, sources: [], warnings: [] },
|
|
49
|
+
nodeVersion: "26.3.0",
|
|
50
|
+
cwd: workspace,
|
|
51
|
+
env: { HOME: homeDir },
|
|
52
|
+
logOutcome: (outcome) => outcomes.push(outcome),
|
|
53
|
+
deps: {
|
|
54
|
+
ensureGitignored: () => true,
|
|
55
|
+
ensureProvisioned: () => {
|
|
56
|
+
throw new Error("provisioning should not run when install_dir binary exists");
|
|
57
|
+
},
|
|
58
|
+
prepareWorkspace: () => ({
|
|
59
|
+
dataDir: join(homeDir, ".omo/codegraph/projects/test"),
|
|
60
|
+
dataRoot: join(homeDir, ".omo/codegraph"),
|
|
61
|
+
linked: true,
|
|
62
|
+
mode: "global-linked",
|
|
63
|
+
projectLink: join(workspace, ".codegraph"),
|
|
64
|
+
}),
|
|
65
|
+
resolveCommand: (options) => {
|
|
66
|
+
const provisioned = options?.provisioned?.() ?? null;
|
|
67
|
+
return {
|
|
68
|
+
argsPrefix: [],
|
|
69
|
+
command: provisioned ?? "missing-codegraph",
|
|
70
|
+
exists: provisioned !== null,
|
|
71
|
+
source: provisioned === null ? "path" : "provisioned",
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
runCommand: (_projectRoot, command, args) => {
|
|
75
|
+
calls.push({ args, command });
|
|
76
|
+
return Promise.resolve({ exitCode: 0, stdout: calls.length === 1 ? '{"initialized":false}' : "", timedOut: false });
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// then
|
|
82
|
+
expect(result).toEqual({ action: "initialized" });
|
|
83
|
+
expect(calls).toEqual([
|
|
84
|
+
{ args: ["status", "--json"], command: binPath },
|
|
85
|
+
{ args: ["init"], command: binPath },
|
|
86
|
+
]);
|
|
87
|
+
expect(outcomes).toEqual([{ action: "initialized", exitCode: 0, projectRoot: workspace, source: "provisioned", timedOut: false }]);
|
|
88
|
+
} finally {
|
|
89
|
+
rmSync(workspace, { recursive: true, force: true });
|
|
90
|
+
rmSync(homeDir, { recursive: true, force: true });
|
|
91
|
+
rmSync(installDir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { CODEGRAPH_UNSAFE_NODE_ENV } from "../../../../../utils/src/codegraph/node-support.ts";
|
|
4
|
+
import { runCodegraphServe } from "../src/serve.ts";
|
|
5
|
+
|
|
6
|
+
describe("runCodegraphServe node support", () => {
|
|
7
|
+
it("#given Node is too new and no command resolves #when serving MCP #then the unsupported-node hint wins", async () => {
|
|
8
|
+
// given
|
|
9
|
+
const stderr: string[] = [];
|
|
10
|
+
const spawned: string[] = [];
|
|
11
|
+
|
|
12
|
+
// when
|
|
13
|
+
const exitCode = await runCodegraphServe({
|
|
14
|
+
env: { PATH: "/bin" },
|
|
15
|
+
nodeVersion: "26.3.0",
|
|
16
|
+
buildEnv: () => ({}),
|
|
17
|
+
resolve: () => ({ argsPrefix: [], command: "codegraph", exists: false, source: "path" }),
|
|
18
|
+
runProcess: (command: string) => {
|
|
19
|
+
spawned.push(command);
|
|
20
|
+
return Promise.resolve(0);
|
|
21
|
+
},
|
|
22
|
+
stderr: { write: (chunk: string) => stderr.push(chunk) },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// then
|
|
26
|
+
expect(exitCode).toBe(1);
|
|
27
|
+
expect(spawned).toEqual([]);
|
|
28
|
+
expect(stderr).toHaveLength(1);
|
|
29
|
+
expect(stderr[0]).toContain("CodeGraph MCP skipped");
|
|
30
|
+
expect(stderr[0]).toContain("Node 26 is unsupported");
|
|
31
|
+
expect(stderr[0]).toContain(CODEGRAPH_UNSAFE_NODE_ENV);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -1,44 +1,12 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { describe, expect, it } from "vitest";
|
|
3
|
-
|
|
4
|
-
type PackageJson = {
|
|
5
|
-
readonly type: string;
|
|
6
|
-
readonly packageManager: string;
|
|
7
|
-
readonly bin: Record<string, string>;
|
|
8
|
-
readonly dependencies?: Record<string, unknown>;
|
|
9
|
-
readonly optionalDependencies: Record<string, string>;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type HookCommand = {
|
|
13
|
-
readonly command: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type HookEntry = {
|
|
17
|
-
readonly hooks: readonly HookCommand[];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type HooksJson = {
|
|
21
|
-
readonly hooks: Record<string, readonly HookEntry[]>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function readPackageJson(path: string): PackageJson {
|
|
25
|
-
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
26
|
-
if (!isPackageJson(parsed)) throw new TypeError(`Invalid package metadata: ${path}`);
|
|
27
|
-
return parsed;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function readHooksJson(path: string): HooksJson {
|
|
31
|
-
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
32
|
-
if (!isHooksJson(parsed)) throw new TypeError(`Invalid hooks metadata: ${path}`);
|
|
33
|
-
return parsed;
|
|
34
|
-
}
|
|
2
|
+
import { readHooksJson, readPackageJson, readTextFile } from "../../test-support/package-smoke-fixture.js";
|
|
35
3
|
|
|
36
4
|
describe("plugin package metadata", () => {
|
|
37
5
|
it("#given packaged plugin files #when validating entrypoints #then hook command uses portable plugin root interpolation", () => {
|
|
38
6
|
// given
|
|
39
7
|
const packageJson = readPackageJson("package.json");
|
|
40
8
|
const hooksJson = readHooksJson("hooks/hooks.json");
|
|
41
|
-
const cliSource =
|
|
9
|
+
const cliSource = readTextFile("src/cli.ts");
|
|
42
10
|
|
|
43
11
|
// when
|
|
44
12
|
const command = hooksJson.hooks["PostToolUse"]?.[0]?.hooks[0]?.command;
|
|
@@ -54,40 +22,3 @@ describe("plugin package metadata", () => {
|
|
|
54
22
|
expect(command).toBe(`node "${pluginRoot}/dist/cli.js" hook post-tool-use`);
|
|
55
23
|
});
|
|
56
24
|
});
|
|
57
|
-
|
|
58
|
-
function isPackageJson(value: unknown): value is PackageJson {
|
|
59
|
-
if (!isRecord(value)) return false;
|
|
60
|
-
const dependencies = value["dependencies"];
|
|
61
|
-
return (
|
|
62
|
-
value["type"] === "module" &&
|
|
63
|
-
value["packageManager"] === "npm@11.12.1" &&
|
|
64
|
-
isStringRecord(value["bin"]) &&
|
|
65
|
-
isStringRecord(value["optionalDependencies"]) &&
|
|
66
|
-
(dependencies === undefined || isRecord(dependencies))
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function isHooksJson(value: unknown): value is HooksJson {
|
|
71
|
-
if (!isRecord(value) || !isRecord(value["hooks"])) return false;
|
|
72
|
-
return Object.values(value["hooks"]).every(isHookEntries);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function isHookEntries(value: unknown): value is readonly HookEntry[] {
|
|
76
|
-
return Array.isArray(value) && value.every(isHookEntry);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function isHookEntry(value: unknown): value is HookEntry {
|
|
80
|
-
return isRecord(value) && Array.isArray(value["hooks"]) && value["hooks"].every(isHookCommand);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isHookCommand(value: unknown): value is HookCommand {
|
|
84
|
-
return isRecord(value) && typeof value["command"] === "string";
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
88
|
-
return isRecord(value) && Object.values(value).every((item) => typeof item === "string");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
92
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
93
|
-
}
|