oh-my-opencode 4.12.0 → 4.12.1
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/dist/cli/index.js +36 -17
- package/dist/cli-node/index.js +36 -17
- package/dist/index.js +361 -254
- package/dist/skills/ultraresearch/SKILL.md +11 -2
- package/dist/tools/background-task/constants.d.ts +1 -1
- package/dist/tui.js +9 -3
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +3 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +327 -171
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +275 -31
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +11 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +12 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +34 -0
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +40 -3
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +6 -5
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +49 -87
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +48 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +1 -0
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +142 -0
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/dist/cli.js +94 -0
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +17 -0
- package/packages/omo-codex/plugin/components/teammode/package.json +26 -0
- package/packages/omo-codex/plugin/components/teammode/src/cli.ts +12 -0
- package/packages/omo-codex/plugin/components/teammode/src/codex-hook.ts +125 -0
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +116 -0
- package/packages/omo-codex/plugin/components/teammode/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +30 -12
- package/packages/omo-codex/plugin/package.json +2 -1
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +1 -0
- package/packages/omo-codex/plugin/skills/ultraresearch/SKILL.md +11 -2
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +36 -0
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -1
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +24 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/ultraresearch-skill-contract.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +14 -1
- package/packages/shared-skills/skills/ultraresearch/SKILL.md +11 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/serve.ts
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { homedir as
|
|
7
|
-
import { basename as
|
|
5
|
+
import { existsSync as existsSync5, realpathSync } from "node:fs";
|
|
6
|
+
import { homedir as homedir5 } from "node:os";
|
|
7
|
+
import { basename as basename3, extname, join as join6, resolve as resolve2 } from "node:path";
|
|
8
8
|
import {
|
|
9
9
|
cwd as processCwd,
|
|
10
10
|
env as processEnv,
|
|
@@ -59,16 +59,235 @@ function parseNodeMajor(version) {
|
|
|
59
59
|
return Number.isNaN(major) ? 0 : major;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// ../../../../utils/src/codegraph/
|
|
62
|
+
// ../../../../utils/src/codegraph/provision.ts
|
|
63
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
64
|
+
import { execFile } from "node:child_process";
|
|
65
|
+
import { chmod, mkdir, readdir, readFile, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
|
|
63
66
|
import { existsSync } from "node:fs";
|
|
64
|
-
import { homedir as homedir2 } from "node:os";
|
|
67
|
+
import { homedir as homedir2, hostname } from "node:os";
|
|
68
|
+
import { basename, join as join2 } from "node:path";
|
|
69
|
+
import { promisify } from "node:util";
|
|
70
|
+
|
|
71
|
+
// ../../../../utils/src/codegraph/manifest.ts
|
|
72
|
+
var CODEGRAPH_PROVISION_MANIFEST = {
|
|
73
|
+
assets: {
|
|
74
|
+
"darwin-arm64": {
|
|
75
|
+
executableName: "codegraph",
|
|
76
|
+
sha256: "95bb27bf6382b69659e158e0c04d71cc394778951e1317d582be7807e7866908",
|
|
77
|
+
url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-darwin-arm64.tar.gz"
|
|
78
|
+
},
|
|
79
|
+
"darwin-x64": {
|
|
80
|
+
executableName: "codegraph",
|
|
81
|
+
sha256: "3311cc1d1f0f0ad742709b6a43d8a9187b1ef0af0dd30e0b58008dc673e29478",
|
|
82
|
+
url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-darwin-x64.tar.gz"
|
|
83
|
+
},
|
|
84
|
+
"linux-arm64": {
|
|
85
|
+
executableName: "codegraph",
|
|
86
|
+
sha256: "e16f612bc96c2ebccd04574cbed500c9939147c80666ad6bb024398dff7992ae",
|
|
87
|
+
url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-linux-arm64.tar.gz"
|
|
88
|
+
},
|
|
89
|
+
"linux-x64": {
|
|
90
|
+
executableName: "codegraph",
|
|
91
|
+
sha256: "d45a068f44596a85c7ba7d0ef924eaf7103fbbf3cafbeb668127daff60a52228",
|
|
92
|
+
url: "https://github.com/colbymchenry/codegraph/releases/download/v1.0.1/codegraph-linux-x64.tar.gz"
|
|
93
|
+
},
|
|
94
|
+
"win32-arm64": {
|
|
95
|
+
executableName: "codegraph.cmd",
|
|
96
|
+
sha256: "8d57ced73b24d35f758f2ede2318e80e1d7241987f37a999e3d80edb6fddf961",
|
|
97
|
+
url: "https://registry.npmjs.org/@colbymchenry/codegraph-win32-arm64/-/codegraph-win32-arm64-1.0.1.tgz"
|
|
98
|
+
},
|
|
99
|
+
"win32-x64": {
|
|
100
|
+
executableName: "codegraph.cmd",
|
|
101
|
+
sha256: "52607fe73b05e741fd1087da2ceca9d3c8f565e36bf1a7070600bdbdf3931e32",
|
|
102
|
+
url: "https://registry.npmjs.org/@colbymchenry/codegraph-win32-x64/-/codegraph-win32-x64-1.0.1.tgz"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
version: "1.0.1"
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// ../../../../utils/src/codegraph/provision.ts
|
|
109
|
+
var DEFAULT_LOCK_WAIT_MS = 5000;
|
|
110
|
+
var DEFAULT_LOCK_STALE_MS = 120000;
|
|
111
|
+
var DEFAULT_DOWNLOAD_TIMEOUT_MS = 60000;
|
|
112
|
+
var execFileAsync = promisify(execFile);
|
|
113
|
+
function platformKey() {
|
|
114
|
+
return `${process.platform}-${process.arch}`;
|
|
115
|
+
}
|
|
116
|
+
function markerPath(installDir, version) {
|
|
117
|
+
return join2(installDir, ".provisioned", `codegraph-${version}.json`);
|
|
118
|
+
}
|
|
119
|
+
function defaultInstallDir() {
|
|
120
|
+
return join2(homedir2(), ".omo", "codegraph");
|
|
121
|
+
}
|
|
122
|
+
function sha256(bytes) {
|
|
123
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
124
|
+
}
|
|
125
|
+
function isErrnoException(error) {
|
|
126
|
+
return error instanceof Error && "code" in error;
|
|
127
|
+
}
|
|
128
|
+
async function removeEmptyDirectory(path) {
|
|
129
|
+
try {
|
|
130
|
+
await rmdir(path);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (isErrnoException(error) && error.code === "ENOENT")
|
|
133
|
+
return;
|
|
134
|
+
if (isErrnoException(error) && error.code === "ENOTEMPTY")
|
|
135
|
+
return;
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function sleep(ms) {
|
|
140
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
141
|
+
}
|
|
142
|
+
async function defaultDownloader(asset, timeoutMs = DEFAULT_DOWNLOAD_TIMEOUT_MS) {
|
|
143
|
+
const response = await fetch(asset.url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
144
|
+
if (!response.ok)
|
|
145
|
+
throw new Error(`download failed with HTTP ${response.status}`);
|
|
146
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
147
|
+
}
|
|
148
|
+
function forcedBadChecksumOptions(options) {
|
|
149
|
+
if (options.forceBadChecksum !== true)
|
|
150
|
+
return null;
|
|
151
|
+
const key = options.platformKey ?? platformKey();
|
|
152
|
+
return {
|
|
153
|
+
downloader: async () => new TextEncoder().encode("checksum mismatch"),
|
|
154
|
+
installDir: options.installDir ?? join2(options.lockDir, "codegraph-force-bad-checksum"),
|
|
155
|
+
manifest: {
|
|
156
|
+
assets: {
|
|
157
|
+
[key]: { executableName: process.platform === "win32" ? "codegraph.cmd" : "codegraph", sha256: "0000", url: "memory://bad" }
|
|
158
|
+
},
|
|
159
|
+
version: options.version
|
|
160
|
+
},
|
|
161
|
+
platformKey: key
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function readMarker(path) {
|
|
165
|
+
if (!existsSync(path))
|
|
166
|
+
return null;
|
|
167
|
+
try {
|
|
168
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
169
|
+
if (typeof raw === "object" && raw !== null && "binPath" in raw) {
|
|
170
|
+
const value = raw.binPath;
|
|
171
|
+
return typeof value === "string" && existsSync(value) ? value : null;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof Error)
|
|
176
|
+
return null;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function acquireLock(lockPath, waitMs, staleMs) {
|
|
181
|
+
const startedAt = Date.now();
|
|
182
|
+
await mkdir(join2(lockPath, ".."), { recursive: true });
|
|
183
|
+
while (Date.now() - startedAt <= waitMs) {
|
|
184
|
+
try {
|
|
185
|
+
await mkdir(lockPath);
|
|
186
|
+
return () => rm(lockPath, { force: true, recursive: true });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (!isErrnoException(error) || error.code !== "EEXIST")
|
|
189
|
+
throw error;
|
|
190
|
+
const lockStat = await stat(lockPath).catch(() => null);
|
|
191
|
+
if (lockStat !== null && Date.now() - lockStat.mtimeMs > staleMs) {
|
|
192
|
+
await rm(lockPath, { force: true, recursive: true });
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
await sleep(25);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
async function extractTarGz(archivePath, destinationDir) {
|
|
201
|
+
await execFileAsync("tar", ["-xzf", archivePath, "-C", destinationDir]);
|
|
202
|
+
}
|
|
203
|
+
async function installExtractedBundle(extractDir, installDir, executableName) {
|
|
204
|
+
const roots = await readdir(extractDir);
|
|
205
|
+
if (roots.length !== 1)
|
|
206
|
+
throw new Error(`CodeGraph archive should contain one root directory, found ${roots.length}`);
|
|
207
|
+
const bundleDir = join2(extractDir, roots[0] ?? "");
|
|
208
|
+
const bundleEntries = await readdir(bundleDir);
|
|
209
|
+
await mkdir(installDir, { recursive: true });
|
|
210
|
+
for (const entry of bundleEntries) {
|
|
211
|
+
await rm(join2(installDir, entry), { force: true, recursive: true });
|
|
212
|
+
await rename(join2(bundleDir, entry), join2(installDir, entry));
|
|
213
|
+
}
|
|
214
|
+
const destination = join2(installDir, "bin", executableName);
|
|
215
|
+
if (!existsSync(destination))
|
|
216
|
+
throw new Error(`CodeGraph archive did not contain bin/${executableName}`);
|
|
217
|
+
await chmod(destination, 493);
|
|
218
|
+
return destination;
|
|
219
|
+
}
|
|
220
|
+
async function installAsset(layout) {
|
|
221
|
+
const { asset, downloader, installDir, version } = layout;
|
|
222
|
+
const stagingDir = join2(installDir, ".staging", randomUUID());
|
|
223
|
+
const archivePath = join2(stagingDir, basename(asset.url));
|
|
224
|
+
const extractDir = join2(stagingDir, "extract");
|
|
225
|
+
try {
|
|
226
|
+
await mkdir(extractDir, { recursive: true });
|
|
227
|
+
const bytes = await downloader(asset);
|
|
228
|
+
const actualChecksum = sha256(bytes);
|
|
229
|
+
if (actualChecksum !== asset.sha256) {
|
|
230
|
+
throw new Error(`checksum mismatch for ${basename(asset.url)}: expected ${asset.sha256}, got ${actualChecksum}`);
|
|
231
|
+
}
|
|
232
|
+
if (!asset.url.endsWith(".tar.gz") && !asset.url.endsWith(".tgz")) {
|
|
233
|
+
throw new Error(`unsupported CodeGraph archive type for ${basename(asset.url)}`);
|
|
234
|
+
}
|
|
235
|
+
await writeFile(archivePath, bytes);
|
|
236
|
+
await extractTarGz(archivePath, extractDir);
|
|
237
|
+
const destination = await installExtractedBundle(extractDir, installDir, asset.executableName);
|
|
238
|
+
await mkdir(join2(installDir, ".provisioned"), { recursive: true });
|
|
239
|
+
await writeFile(markerPath(installDir, version), `${JSON.stringify({ binPath: destination, version })}
|
|
240
|
+
`);
|
|
241
|
+
return destination;
|
|
242
|
+
} finally {
|
|
243
|
+
await rm(stagingDir, { force: true, recursive: true });
|
|
244
|
+
await removeEmptyDirectory(join2(installDir, ".staging"));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function ensureCodegraphProvisioned(options) {
|
|
248
|
+
const forced = forcedBadChecksumOptions(options);
|
|
249
|
+
const installDir = forced?.installDir ?? options.installDir ?? defaultInstallDir();
|
|
250
|
+
const manifest = forced?.manifest ?? options.manifest ?? CODEGRAPH_PROVISION_MANIFEST;
|
|
251
|
+
const activePlatformKey = forced?.platformKey ?? options.platformKey ?? platformKey();
|
|
252
|
+
const downloader = forced?.downloader ?? options.downloader ?? ((asset) => defaultDownloader(asset, options.downloadTimeoutMs));
|
|
253
|
+
const marker = markerPath(installDir, options.version);
|
|
254
|
+
const existing = await readMarker(marker);
|
|
255
|
+
if (existing !== null)
|
|
256
|
+
return { binPath: existing, provisioned: true };
|
|
257
|
+
const lockPath = join2(options.lockDir, `codegraph-${hostname()}.lock`);
|
|
258
|
+
const release = await acquireLock(lockPath, options.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS, options.lockStaleMs ?? DEFAULT_LOCK_STALE_MS);
|
|
259
|
+
if (release === null)
|
|
260
|
+
return { error: "timed out waiting for codegraph provisioning lock", provisioned: false };
|
|
261
|
+
try {
|
|
262
|
+
const lockedExisting = await readMarker(marker);
|
|
263
|
+
if (lockedExisting !== null)
|
|
264
|
+
return { binPath: lockedExisting, provisioned: true };
|
|
265
|
+
if (manifest.version !== options.version) {
|
|
266
|
+
return { error: `manifest version ${manifest.version} does not match requested ${options.version}`, provisioned: false };
|
|
267
|
+
}
|
|
268
|
+
const asset = manifest.assets[activePlatformKey];
|
|
269
|
+
if (asset === undefined) {
|
|
270
|
+
return { error: `no CodeGraph ${options.version} asset for ${activePlatformKey}`, provisioned: false };
|
|
271
|
+
}
|
|
272
|
+
const binPath = await installAsset({ asset, downloader, installDir, version: options.version });
|
|
273
|
+
return { binPath, provisioned: true };
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return { error: error instanceof Error ? error.message : String(error), provisioned: false };
|
|
276
|
+
} finally {
|
|
277
|
+
await release();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ../../../../utils/src/codegraph/resolve.ts
|
|
282
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
283
|
+
import { homedir as homedir3 } from "node:os";
|
|
65
284
|
import { spawnSync } from "node:child_process";
|
|
66
|
-
import { basename, dirname, join as
|
|
285
|
+
import { basename as basename2, dirname, join as join4 } from "node:path";
|
|
67
286
|
import { createRequire } from "node:module";
|
|
68
287
|
|
|
69
288
|
// ../../../../utils/src/runtime/which.ts
|
|
70
289
|
import { accessSync, constants } from "node:fs";
|
|
71
|
-
import { delimiter, join as
|
|
290
|
+
import { delimiter, join as join3 } from "node:path";
|
|
72
291
|
var runtime = globalThis;
|
|
73
292
|
function isUnsafeCommandName(commandName) {
|
|
74
293
|
if (commandName.includes("/") || commandName.includes("\\"))
|
|
@@ -123,7 +342,7 @@ function bunWhich(commandName) {
|
|
|
123
342
|
return null;
|
|
124
343
|
for (const pathEntry of pathEntries) {
|
|
125
344
|
for (const candidateName of candidateNames) {
|
|
126
|
-
const candidatePath =
|
|
345
|
+
const candidatePath = join3(pathEntry, candidateName);
|
|
127
346
|
if (isExecutable(candidatePath))
|
|
128
347
|
return candidatePath;
|
|
129
348
|
}
|
|
@@ -164,7 +383,7 @@ ${result.stderr}`.trim().split(/\s+/)[0];
|
|
|
164
383
|
}
|
|
165
384
|
}
|
|
166
385
|
function isNodeExecutableName(filePath) {
|
|
167
|
-
const executable =
|
|
386
|
+
const executable = basename2(filePath).toLowerCase();
|
|
168
387
|
return executable === "node" || executable === "node.exe" || /^node\d+(\.exe)?$/.test(executable);
|
|
169
388
|
}
|
|
170
389
|
function looksLikePath(command) {
|
|
@@ -204,8 +423,8 @@ function defaultNodeRuntime(env, fileExists, which, nodeVersion) {
|
|
|
204
423
|
function defaultProvisionedBin(homeDir, fileExists) {
|
|
205
424
|
const binaryName = process.platform === "win32" ? "codegraph.cmd" : "codegraph";
|
|
206
425
|
const candidates = [
|
|
207
|
-
|
|
208
|
-
|
|
426
|
+
join4(homeDir, ".omo", "codegraph", "bin", binaryName),
|
|
427
|
+
join4(homeDir, ".omo", "codegraph", "node-servers", "node_modules", ".bin", binaryName)
|
|
209
428
|
];
|
|
210
429
|
return candidates.find((candidate) => fileExists(candidate)) ?? null;
|
|
211
430
|
}
|
|
@@ -213,7 +432,7 @@ function resolveBundledShim(requireResolve, fileExists) {
|
|
|
213
432
|
try {
|
|
214
433
|
const packageJson = requireResolve(`${CODEGRAPH_PACKAGE}/package.json`);
|
|
215
434
|
const packageRoot = dirname(packageJson);
|
|
216
|
-
const candidates = [
|
|
435
|
+
const candidates = [join4(packageRoot, "bin", "codegraph.js"), join4(packageRoot, "npm-shim.js")];
|
|
217
436
|
return candidates.find((candidate) => fileExists(candidate)) ?? null;
|
|
218
437
|
} catch (error) {
|
|
219
438
|
if (error instanceof Error)
|
|
@@ -229,7 +448,7 @@ function resolveBundledShim(requireResolve, fileExists) {
|
|
|
229
448
|
}
|
|
230
449
|
function resolveCodegraphCommand(options = {}) {
|
|
231
450
|
const env = options.env ?? process.env;
|
|
232
|
-
const fileExists = options.fileExists ??
|
|
451
|
+
const fileExists = options.fileExists ?? existsSync2;
|
|
233
452
|
const configuredBin = env[CODEGRAPH_ENV_BIN]?.trim() || env[CODEGRAPH_LEGACY_ENV_BIN]?.trim();
|
|
234
453
|
if (configuredBin !== undefined && configuredBin.length > 0) {
|
|
235
454
|
return { argsPrefix: [], command: configuredBin, exists: fileExists(configuredBin), source: "env" };
|
|
@@ -241,7 +460,7 @@ function resolveCodegraphCommand(options = {}) {
|
|
|
241
460
|
if (bundled !== null && runtime2 !== null) {
|
|
242
461
|
return { argsPrefix: [bundled], command: runtime2, exists: true, source: "bundled" };
|
|
243
462
|
}
|
|
244
|
-
const provisioned = options.provisioned?.() ?? defaultProvisionedBin(options.homeDir ??
|
|
463
|
+
const provisioned = options.provisioned?.() ?? defaultProvisionedBin(options.homeDir ?? homedir3(), fileExists);
|
|
245
464
|
if (provisioned !== null && fileExists(provisioned)) {
|
|
246
465
|
return { argsPrefix: [], command: provisioned, exists: true, source: "provisioned" };
|
|
247
466
|
}
|
|
@@ -255,8 +474,8 @@ function resolveCodegraphCommand(options = {}) {
|
|
|
255
474
|
}
|
|
256
475
|
|
|
257
476
|
// ../../../../utils/src/omo-config/loader.ts
|
|
258
|
-
import { existsSync as
|
|
259
|
-
import { homedir as
|
|
477
|
+
import { existsSync as existsSync4, readFileSync } from "node:fs";
|
|
478
|
+
import { homedir as homedir4 } from "node:os";
|
|
260
479
|
|
|
261
480
|
// ../../../../utils/src/deep-merge.ts
|
|
262
481
|
var DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -1212,8 +1431,8 @@ function buildEnvOverrides(harness, env, warnings, merge) {
|
|
|
1212
1431
|
}
|
|
1213
1432
|
|
|
1214
1433
|
// ../../../../utils/src/omo-config/resolve.ts
|
|
1215
|
-
import { existsSync as
|
|
1216
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
1434
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1435
|
+
import { dirname as dirname2, isAbsolute, join as join5, relative, resolve } from "node:path";
|
|
1217
1436
|
function containsPath(parent, child) {
|
|
1218
1437
|
const pathToChild = relative(parent, child);
|
|
1219
1438
|
return pathToChild === "" || !pathToChild.startsWith("..") && !isAbsolute(pathToChild);
|
|
@@ -1226,8 +1445,8 @@ function findProjectConfigPathsNearestFirst(cwd, homeDir) {
|
|
|
1226
1445
|
while (true) {
|
|
1227
1446
|
if (stopBeforeDir !== null && currentDir === stopBeforeDir)
|
|
1228
1447
|
break;
|
|
1229
|
-
const configPath =
|
|
1230
|
-
if (
|
|
1448
|
+
const configPath = join5(currentDir, ".omo", "config.jsonc");
|
|
1449
|
+
if (existsSync3(configPath)) {
|
|
1231
1450
|
paths.push(configPath);
|
|
1232
1451
|
}
|
|
1233
1452
|
const parentDir = dirname2(currentDir);
|
|
@@ -1238,7 +1457,7 @@ function findProjectConfigPathsNearestFirst(cwd, homeDir) {
|
|
|
1238
1457
|
return paths;
|
|
1239
1458
|
}
|
|
1240
1459
|
function resolveOmoConfigPaths(options) {
|
|
1241
|
-
const globalPath =
|
|
1460
|
+
const globalPath = join5(resolve(options.homeDir), ".omo", "config.jsonc");
|
|
1242
1461
|
const projectPathsFarthestFirst = findProjectConfigPathsNearestFirst(options.cwd, options.homeDir).reverse();
|
|
1243
1462
|
return [
|
|
1244
1463
|
{ path: globalPath, scope: "global" },
|
|
@@ -1449,13 +1668,13 @@ function validateHarnessApplicability(config, harness) {
|
|
|
1449
1668
|
}
|
|
1450
1669
|
function loadOmoConfig(options) {
|
|
1451
1670
|
const cwd = options.cwd ?? process.cwd();
|
|
1452
|
-
const homeDir = options.homeDir ?? process.env["HOME"] ?? process.env["USERPROFILE"] ??
|
|
1671
|
+
const homeDir = options.homeDir ?? process.env["HOME"] ?? process.env["USERPROFILE"] ?? homedir4();
|
|
1453
1672
|
const env = options.env ?? process.env;
|
|
1454
1673
|
let config = BUILT_IN_DEFAULTS;
|
|
1455
1674
|
const sources = [];
|
|
1456
1675
|
const warnings = [];
|
|
1457
1676
|
for (const candidate of resolveOmoConfigPaths({ cwd, homeDir })) {
|
|
1458
|
-
if (!
|
|
1677
|
+
if (!existsSync4(candidate.path)) {
|
|
1459
1678
|
if (candidate.scope === "global") {
|
|
1460
1679
|
sources.push(toMissingSource(candidate));
|
|
1461
1680
|
}
|
|
@@ -1497,11 +1716,12 @@ var CODEGRAPH_SKIP_HINT = `CodeGraph MCP skipped: codegraph binary not found. In
|
|
|
1497
1716
|
`;
|
|
1498
1717
|
var CODEGRAPH_DISABLED_HINT = `CodeGraph MCP skipped: disabled by OMO SOT config. Set [codex].codegraph.enabled=true to enable it.
|
|
1499
1718
|
`;
|
|
1719
|
+
var CODEGRAPH_VERSION = "1.0.1";
|
|
1500
1720
|
var WINDOWS_CMD_EXTENSIONS = new Set([".bat", ".cmd"]);
|
|
1501
1721
|
var WINDOWS_NODE_SCRIPT_EXTENSIONS = new Set([".cjs", ".js", ".mjs"]);
|
|
1502
1722
|
async function runCodegraphServe(options = {}) {
|
|
1503
1723
|
const env = options.env ?? processEnv;
|
|
1504
|
-
const homeDir = options.homeDir ??
|
|
1724
|
+
const homeDir = options.homeDir ?? homedir5();
|
|
1505
1725
|
const config = options.config ?? getCodexOmoConfig({ cwd: options.cwd ?? processCwd(), env, homeDir });
|
|
1506
1726
|
const codegraphConfig = config.codegraph ?? {};
|
|
1507
1727
|
if (codegraphConfig.enabled === false) {
|
|
@@ -1513,15 +1733,24 @@ async function runCodegraphServe(options = {}) {
|
|
|
1513
1733
|
homeDir,
|
|
1514
1734
|
provisioned: () => provisionedBinFromInstallDir(codegraphConfig.install_dir)
|
|
1515
1735
|
};
|
|
1516
|
-
|
|
1736
|
+
let resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
|
|
1517
1737
|
const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
|
|
1518
|
-
if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ??
|
|
1738
|
+
if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync5)) {
|
|
1519
1739
|
if (resolution.source === "path" && !nodeSupport.supported) {
|
|
1520
1740
|
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
1521
1741
|
return 1;
|
|
1522
1742
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1743
|
+
const provisioned = await provisionMissingCodegraph({
|
|
1744
|
+
config: codegraphConfig,
|
|
1745
|
+
ensureProvisioned: options.ensureProvisioned ?? ensureCodegraphProvisioned,
|
|
1746
|
+
homeDir,
|
|
1747
|
+
resolution
|
|
1748
|
+
});
|
|
1749
|
+
if (provisioned === null) {
|
|
1750
|
+
(options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
|
|
1751
|
+
return 1;
|
|
1752
|
+
}
|
|
1753
|
+
resolution = provisioned;
|
|
1525
1754
|
}
|
|
1526
1755
|
if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
|
|
1527
1756
|
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
@@ -1538,6 +1767,21 @@ async function runCodegraphServe(options = {}) {
|
|
|
1538
1767
|
stdio: "inherit"
|
|
1539
1768
|
});
|
|
1540
1769
|
}
|
|
1770
|
+
async function provisionMissingCodegraph(options) {
|
|
1771
|
+
if (options.resolution.source === "env")
|
|
1772
|
+
return null;
|
|
1773
|
+
if (options.config.auto_provision === false)
|
|
1774
|
+
return null;
|
|
1775
|
+
const installDir = options.config.install_dir ?? join6(options.homeDir, ".omo", "codegraph");
|
|
1776
|
+
const result = await options.ensureProvisioned({
|
|
1777
|
+
installDir,
|
|
1778
|
+
lockDir: join6(installDir, ".locks"),
|
|
1779
|
+
version: CODEGRAPH_VERSION
|
|
1780
|
+
});
|
|
1781
|
+
if (!result.provisioned || result.binPath === undefined)
|
|
1782
|
+
return null;
|
|
1783
|
+
return { argsPrefix: [], command: result.binPath, exists: true, source: "provisioned" };
|
|
1784
|
+
}
|
|
1541
1785
|
function shouldSkipResolvedCommand(resolution, commandExists) {
|
|
1542
1786
|
if (resolution.source !== "env")
|
|
1543
1787
|
return false;
|
|
@@ -1555,8 +1799,8 @@ function codegraphEnvForConfig(config, homeDir, buildEnv) {
|
|
|
1555
1799
|
function provisionedBinFromInstallDir(installDir) {
|
|
1556
1800
|
if (installDir === undefined)
|
|
1557
1801
|
return null;
|
|
1558
|
-
const candidate =
|
|
1559
|
-
return
|
|
1802
|
+
const candidate = join6(installDir, "bin", process.platform === "win32" ? "codegraph.cmd" : "codegraph");
|
|
1803
|
+
return existsSync5(candidate) ? candidate : null;
|
|
1560
1804
|
}
|
|
1561
1805
|
async function runCodegraphServeCli() {
|
|
1562
1806
|
process.exitCode = await runCodegraphServe();
|
|
@@ -1598,7 +1842,7 @@ function isDirectInvocation(argvPath) {
|
|
|
1598
1842
|
if (argvPath === undefined)
|
|
1599
1843
|
return false;
|
|
1600
1844
|
const modulePath = fileURLToPath(import.meta.url);
|
|
1601
|
-
const moduleName =
|
|
1845
|
+
const moduleName = basename3(modulePath);
|
|
1602
1846
|
if (moduleName !== "serve.js" && moduleName !== "serve.ts")
|
|
1603
1847
|
return false;
|
|
1604
1848
|
return realpathSync(resolve2(argvPath)) === realpathSync(modulePath);
|
|
@@ -5,8 +5,10 @@ import { stderr as processStderr } from "node:process";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
runCodegraphPostToolUseHookCli,
|
|
8
9
|
runCodegraphSessionStartHook,
|
|
9
10
|
runCodegraphSessionStartWorker,
|
|
11
|
+
type PostToolUseHookOptions,
|
|
10
12
|
type SessionStartHookOptions,
|
|
11
13
|
type SessionStartWorkerOptions,
|
|
12
14
|
} from "./hook.js";
|
|
@@ -25,6 +27,15 @@ export async function runCodegraphCli(options: RunCodegraphCliOptions = {}): Pro
|
|
|
25
27
|
return runCodegraphSessionStartHook(options);
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
if (command === "hook" && subcommand === "post-tool-use") {
|
|
31
|
+
const hookOptions: PostToolUseHookOptions = {
|
|
32
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
33
|
+
...(options.stdin === undefined ? {} : { stdin: options.stdin }),
|
|
34
|
+
...(options.stdout === undefined ? {} : { stdout: options.stdout }),
|
|
35
|
+
};
|
|
36
|
+
return runCodegraphPostToolUseHookCli(hookOptions);
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
if (command === "hook" && subcommand === "session-start-worker") {
|
|
29
40
|
const workerOptions: SessionStartWorkerOptions = {
|
|
30
41
|
...options.workerOptions,
|
|
@@ -10,6 +10,7 @@ import type { CodegraphWorkspacePreparation as SharedCodegraphWorkspacePreparati
|
|
|
10
10
|
import type { CodegraphConfig as SharedCodegraphConfig } from "../../../../../utils/src/omo-config.ts";
|
|
11
11
|
|
|
12
12
|
export type SessionStartAction = "skipped-disabled" | "spawned";
|
|
13
|
+
export type PostToolUseAction = "emitted-guidance" | "skipped";
|
|
13
14
|
export type WorkerAction = "failed" | "initialized" | "skipped-disabled" | "skipped-status" | "skipped-unavailable" | "skipped-unsupported-node" | "synced";
|
|
14
15
|
|
|
15
16
|
export interface WorkerSpawnInvocation {
|
|
@@ -27,6 +28,11 @@ export interface SessionStartHookResult {
|
|
|
27
28
|
readonly exitCode: 0;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export interface PostToolUseHookResult {
|
|
32
|
+
readonly action: PostToolUseAction;
|
|
33
|
+
readonly exitCode: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
export interface CodegraphCommandResult {
|
|
31
37
|
readonly exitCode: number;
|
|
32
38
|
readonly stderr?: string;
|
|
@@ -73,6 +79,12 @@ export interface SessionStartHookOptions {
|
|
|
73
79
|
readonly workerCliPath?: string;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
export interface PostToolUseHookOptions {
|
|
83
|
+
readonly env?: Record<string, string | undefined>;
|
|
84
|
+
readonly stdin?: Readable & { readonly isTTY?: boolean };
|
|
85
|
+
readonly stdout?: HookStdout;
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
export interface SessionStartWorkerOptions {
|
|
77
89
|
readonly config?: CodexOmoConfig;
|
|
78
90
|
readonly cwd?: string;
|
|
@@ -4,10 +4,13 @@ import { cwd as processCwd, env as processEnv, stdin as processStdin, stdout as
|
|
|
4
4
|
import type { Readable } from "node:stream";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
+
import { buildCodegraphInitGuidanceForToolResult } from "../../../../../utils/src/codegraph/guidance.ts";
|
|
7
8
|
import { getCodexOmoConfig } from "../../../shared/src/config-loader.ts";
|
|
8
9
|
import { SESSION_START_CWD_ENV } from "./session-start-worker.js";
|
|
9
10
|
import type {
|
|
10
11
|
HookStdout,
|
|
12
|
+
PostToolUseHookOptions,
|
|
13
|
+
PostToolUseHookResult,
|
|
11
14
|
SessionStartHookOptions,
|
|
12
15
|
SessionStartHookResult,
|
|
13
16
|
WorkerSpawnInvocation,
|
|
@@ -23,6 +26,8 @@ export type {
|
|
|
23
26
|
CodexOmoConfig,
|
|
24
27
|
HookStdout,
|
|
25
28
|
OmoConfigSource,
|
|
29
|
+
PostToolUseHookOptions,
|
|
30
|
+
PostToolUseHookResult,
|
|
26
31
|
SessionStartAction,
|
|
27
32
|
SessionStartHookOptions,
|
|
28
33
|
SessionStartHookResult,
|
|
@@ -38,6 +43,10 @@ export async function runCodegraphSessionStartHook(options: SessionStartHookOpti
|
|
|
38
43
|
return (await executeCodegraphSessionStartHook(options)).exitCode;
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
export async function runCodegraphPostToolUseHookCli(options: PostToolUseHookOptions = {}): Promise<number> {
|
|
47
|
+
return (await executeCodegraphPostToolUseHook(options)).exitCode;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
export async function executeCodegraphSessionStartHook(options: SessionStartHookOptions = {}): Promise<SessionStartHookResult> {
|
|
42
51
|
const env = options.env ?? processEnv;
|
|
43
52
|
const input = await readHookInput(options.stdin ?? processStdin);
|
|
@@ -58,6 +67,31 @@ export async function executeCodegraphSessionStartHook(options: SessionStartHook
|
|
|
58
67
|
return { action: "spawned", exitCode: 0 };
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
export async function executeCodegraphPostToolUseHook(options: PostToolUseHookOptions = {}): Promise<PostToolUseHookResult> {
|
|
71
|
+
const env = options.env ?? processEnv;
|
|
72
|
+
const input = await readHookInput(options.stdin ?? processStdin);
|
|
73
|
+
const output = runCodegraphPostToolUseHook(input, { homeDir: resolveHomeDir(env) });
|
|
74
|
+
if (output.length === 0) return { action: "skipped", exitCode: 0 };
|
|
75
|
+
|
|
76
|
+
(options.stdout ?? processStdout).write(output);
|
|
77
|
+
return { action: "emitted-guidance", exitCode: 0 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function runCodegraphPostToolUseHook(input: unknown, options: { readonly homeDir?: string } = {}): string {
|
|
81
|
+
const toolName = isRecord(input) ? input["tool_name"] : undefined;
|
|
82
|
+
const cwd = isRecord(input) ? input["cwd"] : undefined;
|
|
83
|
+
const toolOutput = isRecord(input) ? (input["tool_response"] ?? input["tool_output"] ?? input["response"]) : input;
|
|
84
|
+
const guidance = buildCodegraphInitGuidanceForToolResult({ cwd, toolName, toolOutput }, options);
|
|
85
|
+
if (guidance === null) return "";
|
|
86
|
+
|
|
87
|
+
return `${JSON.stringify({
|
|
88
|
+
hookSpecificOutput: {
|
|
89
|
+
hookEventName: "PostToolUse",
|
|
90
|
+
additionalContext: guidance,
|
|
91
|
+
},
|
|
92
|
+
})}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
61
95
|
function writeHookJson(stdout: HookStdout): void {
|
|
62
96
|
const output = {
|
|
63
97
|
hookSpecificOutput: {
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
buildCodegraphNodeSkipHint,
|
|
17
17
|
evaluateCodegraphNodeSupport,
|
|
18
18
|
} from "../../../../../utils/src/codegraph/node-support.ts";
|
|
19
|
+
import {
|
|
20
|
+
ensureCodegraphProvisioned,
|
|
21
|
+
type EnsureCodegraphProvisionedOptions,
|
|
22
|
+
} from "../../../../../utils/src/codegraph/provision.ts";
|
|
19
23
|
import {
|
|
20
24
|
codegraphCommandRequiresSupportedLocalNode,
|
|
21
25
|
resolveCodegraphCommand,
|
|
@@ -37,6 +41,9 @@ export type CodegraphServeProcessRunner = (
|
|
|
37
41
|
args: readonly string[],
|
|
38
42
|
options: CodegraphServeProcessOptions,
|
|
39
43
|
) => Promise<number>;
|
|
44
|
+
export type CodegraphProvisioner = (
|
|
45
|
+
options: EnsureCodegraphProvisionedOptions,
|
|
46
|
+
) => ReturnType<typeof ensureCodegraphProvisioned>;
|
|
40
47
|
|
|
41
48
|
export interface ServeProcessInvocation {
|
|
42
49
|
readonly args: readonly string[];
|
|
@@ -58,12 +65,14 @@ export interface RunCodegraphServeOptions {
|
|
|
58
65
|
readonly resolve?: (options: ResolveCodegraphCommandOptions) => ReturnType<typeof resolveCodegraphCommand>;
|
|
59
66
|
readonly runProcess?: CodegraphServeProcessRunner;
|
|
60
67
|
readonly stderr?: CodegraphServeStderr;
|
|
68
|
+
readonly ensureProvisioned?: CodegraphProvisioner;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
const CODEGRAPH_SKIP_HINT =
|
|
64
72
|
"CodeGraph MCP skipped: codegraph binary not found. Install CodeGraph or set OMO_CODEGRAPH_BIN.\n";
|
|
65
73
|
const CODEGRAPH_DISABLED_HINT =
|
|
66
74
|
"CodeGraph MCP skipped: disabled by OMO SOT config. Set [codex].codegraph.enabled=true to enable it.\n";
|
|
75
|
+
const CODEGRAPH_VERSION = "1.0.1";
|
|
67
76
|
const WINDOWS_CMD_EXTENSIONS = new Set([".bat", ".cmd"]);
|
|
68
77
|
const WINDOWS_NODE_SCRIPT_EXTENSIONS = new Set([".cjs", ".js", ".mjs"]);
|
|
69
78
|
|
|
@@ -82,15 +91,24 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
|
|
|
82
91
|
homeDir,
|
|
83
92
|
provisioned: () => provisionedBinFromInstallDir(codegraphConfig.install_dir),
|
|
84
93
|
} satisfies ResolveCodegraphCommandOptions;
|
|
85
|
-
|
|
94
|
+
let resolution = options.resolve?.(resolutionOptions) ?? resolveCodegraphCommand(resolutionOptions);
|
|
86
95
|
const nodeSupport = evaluateCodegraphNodeSupport({ env, nodeVersion: options.nodeVersion });
|
|
87
96
|
if (!resolution.exists || shouldSkipResolvedCommand(resolution, options.commandExists ?? existsSync)) {
|
|
88
97
|
if (resolution.source === "path" && !nodeSupport.supported) {
|
|
89
98
|
(options.stderr ?? processStderr).write(buildCodegraphNodeSkipHint(nodeSupport));
|
|
90
99
|
return 1;
|
|
91
100
|
}
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
const provisioned = await provisionMissingCodegraph({
|
|
102
|
+
config: codegraphConfig,
|
|
103
|
+
ensureProvisioned: options.ensureProvisioned ?? ensureCodegraphProvisioned,
|
|
104
|
+
homeDir,
|
|
105
|
+
resolution,
|
|
106
|
+
});
|
|
107
|
+
if (provisioned === null) {
|
|
108
|
+
(options.stderr ?? processStderr).write(CODEGRAPH_SKIP_HINT);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
resolution = provisioned;
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
if (codegraphCommandRequiresSupportedLocalNode(resolution) && !nodeSupport.supported) {
|
|
@@ -110,6 +128,25 @@ export async function runCodegraphServe(options: RunCodegraphServeOptions = {}):
|
|
|
110
128
|
});
|
|
111
129
|
}
|
|
112
130
|
|
|
131
|
+
async function provisionMissingCodegraph(options: {
|
|
132
|
+
readonly config: CodegraphConfig;
|
|
133
|
+
readonly ensureProvisioned: CodegraphProvisioner;
|
|
134
|
+
readonly homeDir: string;
|
|
135
|
+
readonly resolution: CodegraphCommandResolution;
|
|
136
|
+
}): Promise<CodegraphCommandResolution | null> {
|
|
137
|
+
if (options.resolution.source === "env") return null;
|
|
138
|
+
if (options.config.auto_provision === false) return null;
|
|
139
|
+
|
|
140
|
+
const installDir = options.config.install_dir ?? join(options.homeDir, ".omo", "codegraph");
|
|
141
|
+
const result = await options.ensureProvisioned({
|
|
142
|
+
installDir,
|
|
143
|
+
lockDir: join(installDir, ".locks"),
|
|
144
|
+
version: CODEGRAPH_VERSION,
|
|
145
|
+
});
|
|
146
|
+
if (!result.provisioned || result.binPath === undefined) return null;
|
|
147
|
+
return { argsPrefix: [], command: result.binPath, exists: true, source: "provisioned" };
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
function shouldSkipResolvedCommand(
|
|
114
151
|
resolution: CodegraphCommandResolution,
|
|
115
152
|
commandExists: (filePath: string) => boolean,
|