greprag 5.82.0 → 5.83.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/dist/claude-lifecycle-cutover.js +5 -16
- package/dist/commands/codex.js +4 -0
- package/dist/commands/corpus/client.js +16 -0
- package/dist/commands/corpus/index.js +5 -2
- package/dist/commands/corpus/section.js +3 -3
- package/dist/commands/corpus/walk.js +3 -3
- package/dist/commands/delivery.js +64 -0
- package/dist/commands/docptr.js +10 -24
- package/dist/commands/init.js +4 -5
- package/dist/commands/load-primer-reminder.js +2 -2
- package/dist/commands/load.js +45 -12
- package/dist/commands/merge-guard.js +94 -9
- package/dist/commands/parity.js +0 -2
- package/dist/commands/pipe-wrap.js +4 -2
- package/dist/commands/skill-mirror-reminder.js +24 -6
- package/dist/commands/skill.js +24 -8
- package/dist/commands/skillgain.js +5 -1
- package/dist/commands/state-trigger.js +67 -31
- package/dist/delivery-config.js +69 -0
- package/dist/deploy-locks.js +18 -3
- package/dist/doc-mirror-client.js +39 -11
- package/dist/hook.js +35 -39
- package/dist/index.js +62 -27
- package/dist/native-adapter-refresh.js +114 -0
- package/dist/native-skill-adoption.js +20 -53
- package/dist/native-skill-mirror.js +130 -52
- package/dist/native-skill-pending.js +5 -2
- package/dist/opencode-plugin.bundle.js +1079 -159
- package/dist/opencode-plugin.js +6 -0
- package/dist/skill-activation-manifest.js +98 -22
- package/dist/skill-landing.js +6 -0
- package/dist/skill-mirror-files.js +59 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +27 -13
- package/skill/greprag/docs/corpus.md +2 -2
- package/skill/greprag/docs/setup.md +1 -1
- package/skill/mechanic/SKILL.md +14 -39
- package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
- package/skill/templates/chip-spawn.md +36 -20
|
@@ -23,10 +23,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/opencode-plugin.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
26
|
+
var crypto6 = __toESM(require("crypto"));
|
|
27
|
+
var fs12 = __toESM(require("fs"));
|
|
28
|
+
var os5 = __toESM(require("os"));
|
|
29
|
+
var path13 = __toESM(require("path"));
|
|
30
30
|
|
|
31
31
|
// src/opencode-plugin-helpers.ts
|
|
32
32
|
var crypto = __toESM(require("crypto"));
|
|
@@ -47,9 +47,9 @@ function safeSpawn(command, argsOrOptions, maybeOptions) {
|
|
|
47
47
|
|
|
48
48
|
// src/opencode-plugin-helpers.ts
|
|
49
49
|
var HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
50
|
-
function readAnchorFile(
|
|
50
|
+
function readAnchorFile(filePath2) {
|
|
51
51
|
try {
|
|
52
|
-
const raw = JSON.parse(fs.readFileSync(
|
|
52
|
+
const raw = JSON.parse(fs.readFileSync(filePath2, "utf-8"));
|
|
53
53
|
if (!raw || typeof raw !== "object")
|
|
54
54
|
return null;
|
|
55
55
|
const notify = raw.inbox_notify;
|
|
@@ -127,8 +127,8 @@ function isEphemeralCwd(cwd) {
|
|
|
127
127
|
return false;
|
|
128
128
|
}
|
|
129
129
|
function readAnchor(worktree) {
|
|
130
|
-
const
|
|
131
|
-
const file =
|
|
130
|
+
const filePath2 = findExistingAnchorFile(worktree);
|
|
131
|
+
const file = filePath2 ? readAnchorFile(filePath2) : null;
|
|
132
132
|
const root = path.resolve(worktree);
|
|
133
133
|
if (file && file.projectId && file.projectName) {
|
|
134
134
|
return {
|
|
@@ -1851,7 +1851,7 @@ var LOAD_PRIMER = [
|
|
|
1851
1851
|
].join("\n");
|
|
1852
1852
|
var CHIP_SPAWN_POINTER = [
|
|
1853
1853
|
"[greprag chips \u2014 agent coordination is built in (messaging plumbing in the inbox primer).]",
|
|
1854
|
-
"\u2022 About to spawn a chip (`spawn_task`)? \u2192 run `greprag load chip-spawn` FIRST for the method (Block 1 worktree +
|
|
1854
|
+
"\u2022 About to spawn a chip (`spawn_task`)? \u2192 run `greprag load chip-spawn` FIRST for the method (Block 1 worktree + report-back); it carries the multi-chip upgrade path inline. Non-negotiable \u2014 the spawn validator rejects an unstructured chip."
|
|
1855
1855
|
].join("\n");
|
|
1856
1856
|
var OPENCODE_CHIP_SPAWN_POINTER = [
|
|
1857
1857
|
"[greprag chips \u2014 agent coordination is built in (messaging plumbing in the inbox primer).]",
|
|
@@ -2175,29 +2175,95 @@ var skillGainAnnounceModule = {
|
|
|
2175
2175
|
};
|
|
2176
2176
|
|
|
2177
2177
|
// src/skill-activation-manifest.ts
|
|
2178
|
-
var
|
|
2179
|
-
var
|
|
2178
|
+
var fs3 = __toESM(require("fs"));
|
|
2179
|
+
var path4 = __toESM(require("path"));
|
|
2180
|
+
|
|
2181
|
+
// src/skill-landing.ts
|
|
2182
|
+
var path3 = __toESM(require("path"));
|
|
2183
|
+
|
|
2184
|
+
// src/skill-mirror-files.ts
|
|
2185
|
+
var crypto2 = __toESM(require("node:crypto"));
|
|
2186
|
+
var fs2 = __toESM(require("node:fs"));
|
|
2187
|
+
var path2 = __toESM(require("node:path"));
|
|
2188
|
+
function computeSkillFileHash(files) {
|
|
2189
|
+
const hash = crypto2.createHash("sha256");
|
|
2190
|
+
for (const file of [...files].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
2191
|
+
hash.update(file.path).update("\0").update(file.content).update("\0");
|
|
2192
|
+
}
|
|
2193
|
+
return hash.digest("hex");
|
|
2194
|
+
}
|
|
2195
|
+
var GENERATED_ADAPTER_MARKER = "## GENERATED ADAPTER \u2014 NEVER EDIT";
|
|
2196
|
+
function isGeneratedSkillAdapter(skillMdBody) {
|
|
2197
|
+
const body = skillMdBody.replace(/^?---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
|
|
2198
|
+
return body.slice(0, 400).includes(GENERATED_ADAPTER_MARKER);
|
|
2199
|
+
}
|
|
2200
|
+
var INSTALLED_SKILL_HARNESS_DIRS = [".claude", ".agents", ".codex", ".opencode", "opencode"];
|
|
2201
|
+
function isLinkedSkillFolder(dir) {
|
|
2202
|
+
try {
|
|
2203
|
+
return fs2.lstatSync(dir).isSymbolicLink();
|
|
2204
|
+
} catch {
|
|
2205
|
+
return false;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
function isSkillFolderOwnedElsewhere(dir) {
|
|
2209
|
+
if (isLinkedSkillFolder(dir))
|
|
2210
|
+
return true;
|
|
2211
|
+
const root = path2.dirname(path2.resolve(dir));
|
|
2212
|
+
const harnessDir = path2.basename(path2.dirname(root));
|
|
2213
|
+
if (path2.basename(root) !== "skills" || !INSTALLED_SKILL_HARNESS_DIRS.includes(harnessDir))
|
|
2214
|
+
return false;
|
|
2215
|
+
let skillMd = "";
|
|
2216
|
+
try {
|
|
2217
|
+
skillMd = fs2.readFileSync(path2.join(dir, "SKILL.md"), "utf8");
|
|
2218
|
+
} catch {
|
|
2219
|
+
}
|
|
2220
|
+
if (isGeneratedSkillAdapter(skillMd))
|
|
2221
|
+
return false;
|
|
2222
|
+
const frontmatter = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(skillMd)?.[1] || "";
|
|
2223
|
+
if (/^\s*repository:\s*\S/m.test(frontmatter))
|
|
2224
|
+
return true;
|
|
2225
|
+
if (harnessDir !== ".claude" && harnessDir !== ".agents")
|
|
2226
|
+
return false;
|
|
2227
|
+
try {
|
|
2228
|
+
const lock = JSON.parse(fs2.readFileSync(path2.join(path2.dirname(path2.dirname(root)), ".agents", ".skill-lock.json"), "utf8"));
|
|
2229
|
+
return Boolean(lock?.skills && Object.prototype.hasOwnProperty.call(lock.skills, path2.basename(dir)));
|
|
2230
|
+
} catch {
|
|
2231
|
+
return false;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
function isSafeMirroredSkillFile(file) {
|
|
2235
|
+
if (file.path === "SKILL.md")
|
|
2236
|
+
return true;
|
|
2237
|
+
return /^docs\/(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._\/-]+\.md$/.test(file.path);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// src/skill-landing.ts
|
|
2241
|
+
var BUNDLED_SKILLS = ["greprag", "commander", "content-advisor", "mechanic"];
|
|
2242
|
+
var LEARNED_DIR_RELPATH = path3.join("docs", "learned");
|
|
2243
|
+
var LEARNINGS_RELPATH = path3.join("docs", "learnings.md");
|
|
2244
|
+
|
|
2245
|
+
// src/skill-activation-manifest.ts
|
|
2180
2246
|
var MAX_NATIVE_SKILL_FILES = 1500;
|
|
2181
2247
|
var MAX_SCAN_DEPTH = 7;
|
|
2182
2248
|
function homeRoots(homeDir, platform) {
|
|
2183
2249
|
if (platform === "claude-code") {
|
|
2184
|
-
return [
|
|
2250
|
+
return [path4.join(homeDir, ".claude", "skills")];
|
|
2185
2251
|
}
|
|
2186
2252
|
if (platform === "codex") {
|
|
2187
2253
|
return [
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2254
|
+
path4.join(homeDir, ".codex", "skills"),
|
|
2255
|
+
path4.join(homeDir, ".agents", "skills"),
|
|
2256
|
+
path4.join(homeDir, ".codex", "plugins", "cache")
|
|
2191
2257
|
];
|
|
2192
2258
|
}
|
|
2193
|
-
return [
|
|
2259
|
+
return [path4.join(homeDir, ".config", "opencode", "skills")];
|
|
2194
2260
|
}
|
|
2195
2261
|
function ancestorDirs(cwd) {
|
|
2196
2262
|
const dirs = [];
|
|
2197
|
-
let current =
|
|
2263
|
+
let current = path4.resolve(cwd);
|
|
2198
2264
|
for (let depth = 0; depth < 16; depth++) {
|
|
2199
2265
|
dirs.push(current);
|
|
2200
|
-
const parent =
|
|
2266
|
+
const parent = path4.dirname(current);
|
|
2201
2267
|
if (parent === current)
|
|
2202
2268
|
break;
|
|
2203
2269
|
current = parent;
|
|
@@ -2207,11 +2273,11 @@ function ancestorDirs(cwd) {
|
|
|
2207
2273
|
function repoRoots(cwd, platform) {
|
|
2208
2274
|
return ancestorDirs(cwd).flatMap((dir) => {
|
|
2209
2275
|
if (platform === "claude-code")
|
|
2210
|
-
return [
|
|
2276
|
+
return [path4.join(dir, ".claude", "skills")];
|
|
2211
2277
|
if (platform === "codex") {
|
|
2212
|
-
return [
|
|
2278
|
+
return [path4.join(dir, ".codex", "skills"), path4.join(dir, ".agents", "skills")];
|
|
2213
2279
|
}
|
|
2214
|
-
return [
|
|
2280
|
+
return [path4.join(dir, ".opencode", "skills")];
|
|
2215
2281
|
});
|
|
2216
2282
|
}
|
|
2217
2283
|
function frontmatterName(content) {
|
|
@@ -2220,34 +2286,50 @@ function frontmatterName(content) {
|
|
|
2220
2286
|
return name?.[1]?.trim() || "";
|
|
2221
2287
|
}
|
|
2222
2288
|
function readNativeSkillNames(params) {
|
|
2223
|
-
const names = /* @__PURE__ */ new
|
|
2289
|
+
const names = /* @__PURE__ */ new Map();
|
|
2290
|
+
let rootNames = /* @__PURE__ */ new Map();
|
|
2291
|
+
const rank = { adapter: 0, external: 1, local: 2 };
|
|
2292
|
+
const note = (name, kind) => {
|
|
2293
|
+
const held = rootNames.get(name);
|
|
2294
|
+
if (!held || rank[kind] > rank[held])
|
|
2295
|
+
rootNames.set(name, kind);
|
|
2296
|
+
};
|
|
2224
2297
|
let visited = 0;
|
|
2298
|
+
const readPackage = (dir, fileName, linked) => {
|
|
2299
|
+
visited++;
|
|
2300
|
+
let content = "";
|
|
2301
|
+
try {
|
|
2302
|
+
content = fs3.readFileSync(path4.join(dir, fileName), "utf8");
|
|
2303
|
+
} catch {
|
|
2304
|
+
}
|
|
2305
|
+
const kind = linked || isSkillFolderOwnedElsewhere(dir) ? "external" : isGeneratedSkillAdapter(content) ? "adapter" : "local";
|
|
2306
|
+
note(path4.basename(dir).toLowerCase(), kind);
|
|
2307
|
+
const declared = frontmatterName(content);
|
|
2308
|
+
if (declared)
|
|
2309
|
+
note(declared.toLowerCase(), kind);
|
|
2310
|
+
};
|
|
2225
2311
|
const walk = (dir, depth) => {
|
|
2226
2312
|
if (depth > MAX_SCAN_DEPTH || visited >= MAX_NATIVE_SKILL_FILES)
|
|
2227
2313
|
return;
|
|
2228
2314
|
let entries;
|
|
2229
2315
|
try {
|
|
2230
|
-
entries =
|
|
2316
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
2231
2317
|
} catch {
|
|
2232
2318
|
return;
|
|
2233
2319
|
}
|
|
2320
|
+
const skillMd = depth > 0 ? entries.find((entry) => entry.isFile() && entry.name.toLowerCase() === "skill.md") : void 0;
|
|
2321
|
+
if (skillMd) {
|
|
2322
|
+
readPackage(dir, skillMd.name, false);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2234
2325
|
for (const entry of entries) {
|
|
2235
2326
|
if (visited >= MAX_NATIVE_SKILL_FILES)
|
|
2236
2327
|
return;
|
|
2237
|
-
const
|
|
2238
|
-
if (entry.isDirectory())
|
|
2239
|
-
walk(
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
if (!entry.isFile() || entry.name.toLowerCase() !== "skill.md")
|
|
2243
|
-
continue;
|
|
2244
|
-
visited++;
|
|
2245
|
-
names.add(path2.basename(path2.dirname(full)).toLowerCase());
|
|
2246
|
-
try {
|
|
2247
|
-
const declared = frontmatterName(fs2.readFileSync(full, "utf8"));
|
|
2248
|
-
if (declared)
|
|
2249
|
-
names.add(declared.toLowerCase());
|
|
2250
|
-
} catch {
|
|
2328
|
+
const child = path4.join(dir, entry.name);
|
|
2329
|
+
if (entry.isDirectory())
|
|
2330
|
+
walk(child, depth + 1);
|
|
2331
|
+
else if (isLinkedSkillFolder(child) && fs3.existsSync(path4.join(child, "SKILL.md"))) {
|
|
2332
|
+
readPackage(child, "SKILL.md", true);
|
|
2251
2333
|
}
|
|
2252
2334
|
}
|
|
2253
2335
|
};
|
|
@@ -2255,16 +2337,42 @@ function readNativeSkillNames(params) {
|
|
|
2255
2337
|
...homeRoots(params.homeDir, params.platform),
|
|
2256
2338
|
...repoRoots(params.cwd, params.platform)
|
|
2257
2339
|
];
|
|
2258
|
-
for (const root of roots)
|
|
2340
|
+
for (const root of roots) {
|
|
2341
|
+
rootNames = /* @__PURE__ */ new Map();
|
|
2259
2342
|
walk(root, 0);
|
|
2343
|
+
for (const [name, kind] of rootNames)
|
|
2344
|
+
if (!names.has(name))
|
|
2345
|
+
names.set(name, kind);
|
|
2346
|
+
}
|
|
2260
2347
|
return names;
|
|
2261
2348
|
}
|
|
2262
|
-
function
|
|
2349
|
+
function nativeAdapterGap(platform, mirrored, native) {
|
|
2350
|
+
const missing = [];
|
|
2351
|
+
const localCopies = [];
|
|
2352
|
+
if (!platform)
|
|
2353
|
+
return { missing, localCopies };
|
|
2354
|
+
for (const { skillName, scope } of mirrored) {
|
|
2355
|
+
if (!skillName || BUNDLED_SKILLS.includes(skillName))
|
|
2356
|
+
continue;
|
|
2357
|
+
const kind = native.get(skillName.toLowerCase());
|
|
2358
|
+
if (!kind)
|
|
2359
|
+
missing.push(skillName);
|
|
2360
|
+
else if (kind === "local" && platform === "claude-code" && scope !== "project")
|
|
2361
|
+
localCopies.push(skillName);
|
|
2362
|
+
}
|
|
2363
|
+
return { missing, localCopies };
|
|
2364
|
+
}
|
|
2365
|
+
function buildMirroredSkillActivation(entries, native, platform) {
|
|
2263
2366
|
const valid = entries.filter((entry) => entry.skillName && entry.triggerDescription);
|
|
2264
2367
|
if (valid.length === 0)
|
|
2265
2368
|
return void 0;
|
|
2266
|
-
const skills = valid.filter((entry) => !
|
|
2267
|
-
return {
|
|
2369
|
+
const skills = valid.filter((entry) => !native.has(entry.skillName.toLowerCase()));
|
|
2370
|
+
return {
|
|
2371
|
+
count: valid.length,
|
|
2372
|
+
skills,
|
|
2373
|
+
nativeCount: valid.length - skills.length,
|
|
2374
|
+
...nativeAdapterGap(platform, valid, native)
|
|
2375
|
+
};
|
|
2268
2376
|
}
|
|
2269
2377
|
function nativeAdapterInstallCommand(platform, missingNames) {
|
|
2270
2378
|
if (missingNames.length === 0)
|
|
@@ -2277,13 +2385,17 @@ function nativeAdapterInstallCommand(platform, missingNames) {
|
|
|
2277
2385
|
}
|
|
2278
2386
|
return null;
|
|
2279
2387
|
}
|
|
2388
|
+
function localCopyCutoverCommand(platform, localCopies) {
|
|
2389
|
+
return localCopies.length > 0 ? nativeAdapterInstallCommand(platform, ["<name>"]) : null;
|
|
2390
|
+
}
|
|
2280
2391
|
function activationFromApiRows(params) {
|
|
2281
2392
|
const entries = params.rows.filter((row) => typeof row.skillName === "string" && !!row.skillName && typeof row.triggerDescription === "string" && !!row.triggerDescription).map((row) => ({
|
|
2282
2393
|
skillName: row.skillName,
|
|
2283
2394
|
description: typeof row.description === "string" ? row.description : "",
|
|
2284
|
-
triggerDescription: row.triggerDescription
|
|
2395
|
+
triggerDescription: row.triggerDescription,
|
|
2396
|
+
scope: typeof row.scope === "string" ? row.scope : void 0
|
|
2285
2397
|
}));
|
|
2286
|
-
return buildMirroredSkillActivation(entries, readNativeSkillNames(params));
|
|
2398
|
+
return buildMirroredSkillActivation(entries, readNativeSkillNames(params), params.platform);
|
|
2287
2399
|
}
|
|
2288
2400
|
|
|
2289
2401
|
// src/commands/skill-mirror-reminder.ts
|
|
@@ -2297,16 +2409,25 @@ function renderEntry(skill) {
|
|
|
2297
2409
|
function buildSkillMirrorAnnounce(m, platform) {
|
|
2298
2410
|
if (!m || m.count <= 0)
|
|
2299
2411
|
return null;
|
|
2412
|
+
const cutoverCommand = localCopyCutoverCommand(platform || "", m.localCopies);
|
|
2413
|
+
const copies = m.localCopies.length === 1 ? "one mirrored skill" : `${m.localCopies.length} mirrored skills`;
|
|
2414
|
+
const copySection = cutoverCommand ? [
|
|
2415
|
+
"### Hand-Kept Copies",
|
|
2416
|
+
`This harness lists ${copies} from a hand-kept copy instead of an adapter, so it keeps running that copy when the mirrored skill changes: ${m.localCopies.join(", ")}. Replacing a copy with its adapter is the user's call, one skill at a time (the old SKILL.md is backed up first):`,
|
|
2417
|
+
"",
|
|
2418
|
+
` ${cutoverCommand}`,
|
|
2419
|
+
"",
|
|
2420
|
+
"Offer that command when the user asks why a skill behaves out of date, or when you needed one of them this session.",
|
|
2421
|
+
""
|
|
2422
|
+
] : [];
|
|
2300
2423
|
if (m.skills.length === 0) {
|
|
2301
|
-
|
|
2424
|
+
const line = "[greprag skills: slash skills are available. When discussing skills with the user, say use/create/update/refresh `/skill`; keep storage mechanics internal unless diagnosing a failure or the user asks how it works. Use `greprag load <skill>` to read skill instructions.]";
|
|
2425
|
+
return [line, "", ...copySection].join("\n").trimEnd();
|
|
2302
2426
|
}
|
|
2303
|
-
const installCommand = nativeAdapterInstallCommand(
|
|
2304
|
-
platform || "",
|
|
2305
|
-
m.skills.map((s) => s.skillName)
|
|
2306
|
-
);
|
|
2427
|
+
const installCommand = nativeAdapterInstallCommand(platform || "", m.missing);
|
|
2307
2428
|
const gapSection = installCommand ? [
|
|
2308
2429
|
"### Missing Native Adapters",
|
|
2309
|
-
`${m.
|
|
2430
|
+
`${m.missing.length} of these skills are in your GrepRAG mirror but have NO adapter installed in this harness, so they never appear in the native skill list. Install the adapters once with:`,
|
|
2310
2431
|
"",
|
|
2311
2432
|
` ${installCommand}`,
|
|
2312
2433
|
"",
|
|
@@ -2319,6 +2440,7 @@ function buildSkillMirrorAnnounce(m, platform) {
|
|
|
2319
2440
|
"When discussing skills with the user, say use/create/update/refresh `/skill`; keep storage mechanics internal unless diagnosing a failure or the user asks how it works.",
|
|
2320
2441
|
"",
|
|
2321
2442
|
...gapSection,
|
|
2443
|
+
...copySection,
|
|
2322
2444
|
"### Trigger Rules",
|
|
2323
2445
|
"If the user names a skill (with or without `/`) OR the request clearly matches a skill's description below, you MUST activate that skill for the current turn. Match meaning, not exact keywords.",
|
|
2324
2446
|
"",
|
|
@@ -2585,23 +2707,23 @@ function collectAnnounces(env, registry = REGISTRY) {
|
|
|
2585
2707
|
}
|
|
2586
2708
|
|
|
2587
2709
|
// src/app-settings.ts
|
|
2588
|
-
var
|
|
2710
|
+
var fs5 = __toESM(require("node:fs"));
|
|
2589
2711
|
var os2 = __toESM(require("node:os"));
|
|
2590
|
-
var
|
|
2712
|
+
var path6 = __toESM(require("node:path"));
|
|
2591
2713
|
|
|
2592
2714
|
// src/project-anchor.ts
|
|
2593
|
-
var
|
|
2594
|
-
var
|
|
2595
|
-
var
|
|
2715
|
+
var path5 = __toESM(require("path"));
|
|
2716
|
+
var fs4 = __toESM(require("fs"));
|
|
2717
|
+
var crypto3 = __toESM(require("crypto"));
|
|
2596
2718
|
var os = __toESM(require("os"));
|
|
2597
2719
|
var ANCHOR_DIR = ".greprag";
|
|
2598
2720
|
var ANCHOR_FILE = "project.json";
|
|
2599
2721
|
var LEGACY_ANCHOR_DIR = ".claude";
|
|
2600
2722
|
function anchorPathIn(dir) {
|
|
2601
|
-
return
|
|
2723
|
+
return path5.join(dir, ANCHOR_DIR, ANCHOR_FILE);
|
|
2602
2724
|
}
|
|
2603
2725
|
function legacyAnchorPathIn(dir) {
|
|
2604
|
-
return
|
|
2726
|
+
return path5.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
|
|
2605
2727
|
}
|
|
2606
2728
|
function globalAnchorPath() {
|
|
2607
2729
|
return anchorPathIn(os.homedir());
|
|
@@ -2612,22 +2734,22 @@ function legacyGlobalAnchorPath() {
|
|
|
2612
2734
|
function findExistingAnchor(startDir) {
|
|
2613
2735
|
const homeAnchor = globalAnchorPath();
|
|
2614
2736
|
const legacyHomeAnchor = legacyGlobalAnchorPath();
|
|
2615
|
-
let dir =
|
|
2737
|
+
let dir = path5.resolve(startDir);
|
|
2616
2738
|
while (true) {
|
|
2617
2739
|
const candidate = anchorPathIn(dir);
|
|
2618
|
-
if (candidate !== homeAnchor &&
|
|
2740
|
+
if (candidate !== homeAnchor && fs4.existsSync(candidate))
|
|
2619
2741
|
return candidate;
|
|
2620
2742
|
const legacyCandidate = legacyAnchorPathIn(dir);
|
|
2621
|
-
if (legacyCandidate !== legacyHomeAnchor &&
|
|
2743
|
+
if (legacyCandidate !== legacyHomeAnchor && fs4.existsSync(legacyCandidate))
|
|
2622
2744
|
return legacyCandidate;
|
|
2623
|
-
const parent =
|
|
2745
|
+
const parent = path5.dirname(dir);
|
|
2624
2746
|
if (parent === dir)
|
|
2625
2747
|
return null;
|
|
2626
2748
|
dir = parent;
|
|
2627
2749
|
}
|
|
2628
2750
|
}
|
|
2629
2751
|
function isEphemeralCwd2(cwd) {
|
|
2630
|
-
const norm =
|
|
2752
|
+
const norm = path5.resolve(cwd).replace(/\\/g, "/").toLowerCase();
|
|
2631
2753
|
if (norm.includes("/appdata/roaming/claude/local-agent-mode-sessions/"))
|
|
2632
2754
|
return true;
|
|
2633
2755
|
if (norm.includes("/appdata/local/claude/local-agent-mode-sessions/"))
|
|
@@ -2650,7 +2772,7 @@ function computeGitDerivedProjectId2(workingDir) {
|
|
|
2650
2772
|
const roots = out.trim().split(/\s+/).filter(Boolean).sort();
|
|
2651
2773
|
if (roots.length === 0)
|
|
2652
2774
|
return null;
|
|
2653
|
-
const hash =
|
|
2775
|
+
const hash = crypto3.createHash("sha256").update(roots.join("\n")).digest("hex");
|
|
2654
2776
|
return [
|
|
2655
2777
|
hash.slice(0, 8),
|
|
2656
2778
|
hash.slice(8, 12),
|
|
@@ -2665,8 +2787,8 @@ function computeGitDerivedProjectId2(workingDir) {
|
|
|
2665
2787
|
}
|
|
2666
2788
|
}
|
|
2667
2789
|
function deterministicProjectId2(workingDir) {
|
|
2668
|
-
const normalized =
|
|
2669
|
-
const hash =
|
|
2790
|
+
const normalized = path5.resolve(workingDir).toLowerCase();
|
|
2791
|
+
const hash = crypto3.createHash("sha256").update(normalized).digest("hex");
|
|
2670
2792
|
return [
|
|
2671
2793
|
hash.slice(0, 8),
|
|
2672
2794
|
hash.slice(8, 12),
|
|
@@ -2677,9 +2799,9 @@ function deterministicProjectId2(workingDir) {
|
|
|
2677
2799
|
hash.slice(20, 32)
|
|
2678
2800
|
].join("-");
|
|
2679
2801
|
}
|
|
2680
|
-
function tryReadAnchorFileContents(
|
|
2802
|
+
function tryReadAnchorFileContents(filePath2) {
|
|
2681
2803
|
try {
|
|
2682
|
-
const raw = JSON.parse(
|
|
2804
|
+
const raw = JSON.parse(fs4.readFileSync(filePath2, "utf-8"));
|
|
2683
2805
|
const notifyRaw = raw.inbox_notify;
|
|
2684
2806
|
const inboxNotify = notifyRaw === "off" || notifyRaw === "session_start_only" ? notifyRaw : "every_turn";
|
|
2685
2807
|
return {
|
|
@@ -2718,10 +2840,10 @@ function readAnchor2(cwd) {
|
|
|
2718
2840
|
}
|
|
2719
2841
|
const gitId = computeGitDerivedProjectId2(cwd);
|
|
2720
2842
|
if (gitId) {
|
|
2721
|
-
const root2 =
|
|
2843
|
+
const root2 = path5.resolve(cwd);
|
|
2722
2844
|
return {
|
|
2723
2845
|
projectId: gitId,
|
|
2724
|
-
projectName: fileContents?.projectName ||
|
|
2846
|
+
projectName: fileContents?.projectName || path5.basename(root2).toLowerCase(),
|
|
2725
2847
|
initialized: true,
|
|
2726
2848
|
source: "git",
|
|
2727
2849
|
anchorPath: existingPath || anchorPathIn(root2),
|
|
@@ -2735,7 +2857,7 @@ function readAnchor2(cwd) {
|
|
|
2735
2857
|
};
|
|
2736
2858
|
}
|
|
2737
2859
|
if (isEphemeralCwd2(cwd)) {
|
|
2738
|
-
const globalPath =
|
|
2860
|
+
const globalPath = fs4.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
|
|
2739
2861
|
const globalContents = tryReadAnchorFileContents(globalPath);
|
|
2740
2862
|
if (globalContents && globalContents.projectId && globalContents.projectName) {
|
|
2741
2863
|
return {
|
|
@@ -2754,10 +2876,10 @@ function readAnchor2(cwd) {
|
|
|
2754
2876
|
};
|
|
2755
2877
|
}
|
|
2756
2878
|
}
|
|
2757
|
-
const root =
|
|
2879
|
+
const root = path5.resolve(cwd);
|
|
2758
2880
|
return {
|
|
2759
2881
|
projectId: deterministicProjectId2(root),
|
|
2760
|
-
projectName: fileContents?.projectName ||
|
|
2882
|
+
projectName: fileContents?.projectName || path5.basename(root).toLowerCase(),
|
|
2761
2883
|
initialized: false,
|
|
2762
2884
|
source: "hash",
|
|
2763
2885
|
anchorPath: existingPath || anchorPathIn(root),
|
|
@@ -2774,14 +2896,14 @@ function readAnchor2(cwd) {
|
|
|
2774
2896
|
// src/app-settings.ts
|
|
2775
2897
|
function readJson(file) {
|
|
2776
2898
|
try {
|
|
2777
|
-
const parsed = JSON.parse(
|
|
2899
|
+
const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
2778
2900
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
2779
2901
|
} catch {
|
|
2780
2902
|
return {};
|
|
2781
2903
|
}
|
|
2782
2904
|
}
|
|
2783
2905
|
function localAppSettingsPath(homeDir = os2.homedir()) {
|
|
2784
|
-
return
|
|
2906
|
+
return path6.join(homeDir, ".greprag", "settings.json");
|
|
2785
2907
|
}
|
|
2786
2908
|
function readLocalAppSettings(homeDir = os2.homedir()) {
|
|
2787
2909
|
const raw = readJson(localAppSettingsPath(homeDir));
|
|
@@ -2838,8 +2960,8 @@ function getOpenCodeReminders(env) {
|
|
|
2838
2960
|
}
|
|
2839
2961
|
|
|
2840
2962
|
// src/procedure.ts
|
|
2841
|
-
var
|
|
2842
|
-
var
|
|
2963
|
+
var path8 = __toESM(require("path"));
|
|
2964
|
+
var fs7 = __toESM(require("fs"));
|
|
2843
2965
|
|
|
2844
2966
|
// src/delivery-lifecycle.ts
|
|
2845
2967
|
var DELIVERY_LIFECYCLE_VERBS = [
|
|
@@ -2863,8 +2985,8 @@ function isDeliveryOwnedVerb(verb) {
|
|
|
2863
2985
|
}
|
|
2864
2986
|
|
|
2865
2987
|
// src/procedure-scope.ts
|
|
2866
|
-
var
|
|
2867
|
-
var
|
|
2988
|
+
var path7 = __toESM(require("path"));
|
|
2989
|
+
var fs6 = __toESM(require("fs"));
|
|
2868
2990
|
var CD_TARGET_RE = /(?:^|[\s;&|(])(?:cd|pushd|chdir|set-location)\s+(?:\/d\s+|-path\s+)?("[^"]*"|'[^']*'|[^\s;&|)]+)/gi;
|
|
2869
2991
|
var GIT_C_TARGET_RE = /(?:^|[\s;&|(])git\s+-C\s+("[^"]*"|'[^']*'|[^\s;&|)]+)/gi;
|
|
2870
2992
|
function unquote(arg) {
|
|
@@ -2924,32 +3046,32 @@ function foreignCheckoutTarget(proc, roots) {
|
|
|
2924
3046
|
return null;
|
|
2925
3047
|
}
|
|
2926
3048
|
function findCheckoutRoot(startDir) {
|
|
2927
|
-
let dir =
|
|
3049
|
+
let dir = path7.resolve(startDir);
|
|
2928
3050
|
for (; ; ) {
|
|
2929
3051
|
try {
|
|
2930
|
-
if (
|
|
3052
|
+
if (fs6.existsSync(path7.join(dir, ".git")))
|
|
2931
3053
|
return dir;
|
|
2932
3054
|
} catch {
|
|
2933
3055
|
}
|
|
2934
|
-
const parent =
|
|
3056
|
+
const parent = path7.dirname(dir);
|
|
2935
3057
|
if (parent === dir)
|
|
2936
3058
|
return null;
|
|
2937
3059
|
dir = parent;
|
|
2938
3060
|
}
|
|
2939
3061
|
}
|
|
2940
3062
|
function mainCheckoutRoot(checkoutRoot) {
|
|
2941
|
-
const dotGit =
|
|
3063
|
+
const dotGit = path7.join(checkoutRoot, ".git");
|
|
2942
3064
|
try {
|
|
2943
|
-
if (!
|
|
3065
|
+
if (!fs6.statSync(dotGit).isFile())
|
|
2944
3066
|
return null;
|
|
2945
|
-
const m = /^\s*gitdir:\s*(.+?)\s*$/m.exec(
|
|
3067
|
+
const m = /^\s*gitdir:\s*(.+?)\s*$/m.exec(fs6.readFileSync(dotGit, "utf-8"));
|
|
2946
3068
|
if (!m)
|
|
2947
3069
|
return null;
|
|
2948
|
-
const gitDir =
|
|
3070
|
+
const gitDir = path7.resolve(checkoutRoot, m[1]);
|
|
2949
3071
|
const marker = normalizeCheckoutPath(gitDir).indexOf("/worktrees/");
|
|
2950
3072
|
if (marker < 0)
|
|
2951
3073
|
return null;
|
|
2952
|
-
return
|
|
3074
|
+
return path7.dirname(gitDir.slice(0, marker));
|
|
2953
3075
|
} catch {
|
|
2954
3076
|
return null;
|
|
2955
3077
|
}
|
|
@@ -2957,7 +3079,7 @@ function mainCheckoutRoot(checkoutRoot) {
|
|
|
2957
3079
|
function projectCheckoutRoots(startDir) {
|
|
2958
3080
|
const root = findCheckoutRoot(startDir);
|
|
2959
3081
|
if (!root)
|
|
2960
|
-
return startDir ? [
|
|
3082
|
+
return startDir ? [path7.resolve(startDir)] : [];
|
|
2961
3083
|
const main = mainCheckoutRoot(root);
|
|
2962
3084
|
return main && normalizeCheckoutPath(main) !== normalizeCheckoutPath(root) ? [root, main] : [root];
|
|
2963
3085
|
}
|
|
@@ -2966,14 +3088,14 @@ function projectCheckoutRoots(startDir) {
|
|
|
2966
3088
|
var PROCEDURE_STORE_VERSION = "2";
|
|
2967
3089
|
function stateDir() {
|
|
2968
3090
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
2969
|
-
return
|
|
3091
|
+
return path8.join(home, ".greprag", "state");
|
|
2970
3092
|
}
|
|
2971
3093
|
function procedureStorePath(projectId) {
|
|
2972
|
-
return
|
|
3094
|
+
return path8.join(stateDir(), `procedures-${projectId}.json`);
|
|
2973
3095
|
}
|
|
2974
3096
|
function readProcedureStore(projectId) {
|
|
2975
3097
|
try {
|
|
2976
|
-
const raw =
|
|
3098
|
+
const raw = fs7.readFileSync(procedureStorePath(projectId), "utf-8");
|
|
2977
3099
|
const parsed = JSON.parse(raw);
|
|
2978
3100
|
if (parsed && Array.isArray(parsed.procedures)) {
|
|
2979
3101
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
@@ -3256,11 +3378,11 @@ function activeProcedureAnnounces(store) {
|
|
|
3256
3378
|
}
|
|
3257
3379
|
|
|
3258
3380
|
// src/procedure-runtime.ts
|
|
3259
|
-
var
|
|
3381
|
+
var crypto5 = __toESM(require("crypto"));
|
|
3260
3382
|
|
|
3261
3383
|
// src/procedure-watch.ts
|
|
3262
|
-
var
|
|
3263
|
-
var
|
|
3384
|
+
var path9 = __toESM(require("path"));
|
|
3385
|
+
var fs8 = __toESM(require("fs"));
|
|
3264
3386
|
var TIER1_LEARN_TRIGGERS = LEARN_TRIGGER_VOCABULARY;
|
|
3265
3387
|
function matchLearnTrigger(prompt) {
|
|
3266
3388
|
return matchProcedure(prompt, {
|
|
@@ -3287,24 +3409,24 @@ function openWatch(verb, phase, openedAt, shadowRunId) {
|
|
|
3287
3409
|
}
|
|
3288
3410
|
function stateDir2() {
|
|
3289
3411
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
3290
|
-
return
|
|
3412
|
+
return path9.join(home, ".greprag", "state");
|
|
3291
3413
|
}
|
|
3292
3414
|
function procedureWatchPath(projectId) {
|
|
3293
|
-
return
|
|
3415
|
+
return path9.join(stateDir2(), `procedure-watch-${projectId}.json`);
|
|
3294
3416
|
}
|
|
3295
3417
|
function hasProcedureWatch(projectId) {
|
|
3296
3418
|
try {
|
|
3297
|
-
return
|
|
3419
|
+
return fs8.existsSync(procedureWatchPath(projectId));
|
|
3298
3420
|
} catch {
|
|
3299
3421
|
return false;
|
|
3300
3422
|
}
|
|
3301
3423
|
}
|
|
3302
3424
|
function writeProcedureWatch(projectId, watch) {
|
|
3303
3425
|
const file = procedureWatchPath(projectId);
|
|
3304
|
-
const dir =
|
|
3305
|
-
if (!
|
|
3306
|
-
|
|
3307
|
-
|
|
3426
|
+
const dir = path9.dirname(file);
|
|
3427
|
+
if (!fs8.existsSync(dir))
|
|
3428
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3429
|
+
fs8.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
|
|
3308
3430
|
}
|
|
3309
3431
|
function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
|
|
3310
3432
|
if (hasProcedureWatch(projectId))
|
|
@@ -3317,26 +3439,26 @@ function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
|
|
|
3317
3439
|
var PROCEDURE_SYSTEM_ENABLED = false;
|
|
3318
3440
|
|
|
3319
3441
|
// src/procedure-shadow.ts
|
|
3320
|
-
var
|
|
3321
|
-
var
|
|
3322
|
-
var
|
|
3442
|
+
var crypto4 = __toESM(require("crypto"));
|
|
3443
|
+
var fs9 = __toESM(require("fs"));
|
|
3444
|
+
var path10 = __toESM(require("path"));
|
|
3323
3445
|
function stateDir3() {
|
|
3324
3446
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
3325
|
-
return
|
|
3447
|
+
return path10.join(home, ".greprag", "state");
|
|
3326
3448
|
}
|
|
3327
3449
|
function procedureShadowPath(projectId) {
|
|
3328
|
-
return
|
|
3450
|
+
return path10.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
|
|
3329
3451
|
}
|
|
3330
3452
|
function appendShadowEvent(projectId, event) {
|
|
3331
3453
|
try {
|
|
3332
3454
|
const file = procedureShadowPath(projectId);
|
|
3333
|
-
|
|
3334
|
-
|
|
3455
|
+
fs9.mkdirSync(path10.dirname(file), { recursive: true });
|
|
3456
|
+
fs9.appendFileSync(file, JSON.stringify(event) + "\n");
|
|
3335
3457
|
} catch {
|
|
3336
3458
|
}
|
|
3337
3459
|
}
|
|
3338
3460
|
function createProcedureShadowRunId() {
|
|
3339
|
-
return
|
|
3461
|
+
return crypto4.randomUUID();
|
|
3340
3462
|
}
|
|
3341
3463
|
function procedureFingerprint(proc) {
|
|
3342
3464
|
const payload = JSON.stringify({
|
|
@@ -3348,7 +3470,7 @@ function procedureFingerprint(proc) {
|
|
|
3348
3470
|
endpoint: proc.endpoint,
|
|
3349
3471
|
definitionVersion: proc.definitionVersion ?? 1
|
|
3350
3472
|
});
|
|
3351
|
-
return
|
|
3473
|
+
return crypto4.createHash("sha256").update(payload).digest("hex");
|
|
3352
3474
|
}
|
|
3353
3475
|
function recordProcedureShadowTrigger(params) {
|
|
3354
3476
|
appendShadowEvent(params.projectId, {
|
|
@@ -3400,7 +3522,7 @@ function extractLatestOpenCodeProcedurePrompt(messages) {
|
|
|
3400
3522
|
}
|
|
3401
3523
|
if (!latest)
|
|
3402
3524
|
return null;
|
|
3403
|
-
const digest =
|
|
3525
|
+
const digest = crypto5.createHash("sha256").update(latest.prompt).digest("hex");
|
|
3404
3526
|
return { ...latest, key: `${userCount}:${digest}` };
|
|
3405
3527
|
}
|
|
3406
3528
|
function procedureRuntimeState(configuredState, proc) {
|
|
@@ -4052,15 +4174,805 @@ function recodeMessagesToPng(messages, opts) {
|
|
|
4052
4174
|
return stats;
|
|
4053
4175
|
}
|
|
4054
4176
|
|
|
4177
|
+
// src/claude-lifecycle-cutover.ts
|
|
4178
|
+
var fs11 = __toESM(require("fs"));
|
|
4179
|
+
var os4 = __toESM(require("os"));
|
|
4180
|
+
var path12 = __toESM(require("path"));
|
|
4181
|
+
|
|
4182
|
+
// src/native-skill-mirror.ts
|
|
4183
|
+
var fs10 = __toESM(require("node:fs"));
|
|
4184
|
+
var os3 = __toESM(require("node:os"));
|
|
4185
|
+
var path11 = __toESM(require("node:path"));
|
|
4186
|
+
var SKILL_NAME_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
4187
|
+
var NATIVE_SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4188
|
+
var PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4189
|
+
var NATIVE_DESCRIPTION_MAX_CHARS = 2500;
|
|
4190
|
+
var STATE_FILE = ".greprag-mirror.json";
|
|
4191
|
+
function nativeSkillMirrorEnabled(platform) {
|
|
4192
|
+
return platform !== "claude-code";
|
|
4193
|
+
}
|
|
4194
|
+
function nativeSkillRoot(platform, homeDir = os3.homedir()) {
|
|
4195
|
+
if (platform === "claude-code")
|
|
4196
|
+
return path11.join(homeDir, ".claude", "skills");
|
|
4197
|
+
if (platform === "codex")
|
|
4198
|
+
return path11.join(homeDir, ".codex", "skills");
|
|
4199
|
+
return path11.join(homeDir, ".config", "opencode", "skills");
|
|
4200
|
+
}
|
|
4201
|
+
function nativeProjectSkillRoot(platform, projectDir) {
|
|
4202
|
+
if (platform === "claude-code")
|
|
4203
|
+
return path11.join(projectDir, ".claude", "skills");
|
|
4204
|
+
if (platform === "codex")
|
|
4205
|
+
return path11.join(projectDir, ".agents", "skills");
|
|
4206
|
+
return path11.join(projectDir, ".opencode", "skills");
|
|
4207
|
+
}
|
|
4208
|
+
function writerOwnedProjectSkillRoot(platform, projectDir, homeDir = os3.homedir()) {
|
|
4209
|
+
if (path11.resolve(projectDir).toLowerCase() === path11.resolve(homeDir).toLowerCase())
|
|
4210
|
+
return null;
|
|
4211
|
+
return nativeProjectSkillRoot(platform, projectDir);
|
|
4212
|
+
}
|
|
4213
|
+
function skillScope(skill) {
|
|
4214
|
+
return skill.scope === "project" ? "project" : "global";
|
|
4215
|
+
}
|
|
4216
|
+
function canonicalSkillId(skill) {
|
|
4217
|
+
if (skill.skillId)
|
|
4218
|
+
return skill.skillId;
|
|
4219
|
+
const scope = skillScope(skill);
|
|
4220
|
+
if (scope === "global")
|
|
4221
|
+
return `global:${skill.skillName}`;
|
|
4222
|
+
if (!skill.projectId || !PROJECT_ID_RE.test(skill.projectId)) {
|
|
4223
|
+
throw new Error(`projectId required for project skill "${skill.skillName}"`);
|
|
4224
|
+
}
|
|
4225
|
+
return `project:${skill.projectId}:${skill.skillName}`;
|
|
4226
|
+
}
|
|
4227
|
+
function replacesGlobal(project, global) {
|
|
4228
|
+
const replaces = project.replaces || [];
|
|
4229
|
+
const globalId = canonicalSkillId(global);
|
|
4230
|
+
return replaces.some((value) => value === globalId || value === `global:${global.skillName}` || value === global.skillName || value === "global" && project.skillName === global.skillName);
|
|
4231
|
+
}
|
|
4232
|
+
function assertNoNativeSkillCollision(skills) {
|
|
4233
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4234
|
+
for (const skill of skills) {
|
|
4235
|
+
const key = skill.skillName.toLowerCase();
|
|
4236
|
+
byName.set(key, [...byName.get(key) || [], skill]);
|
|
4237
|
+
}
|
|
4238
|
+
for (const group of byName.values()) {
|
|
4239
|
+
const global = group.find((skill) => skillScope(skill) === "global");
|
|
4240
|
+
if (!global)
|
|
4241
|
+
continue;
|
|
4242
|
+
for (const project of group.filter((skill) => skillScope(skill) === "project")) {
|
|
4243
|
+
if (!replacesGlobal(project, global)) {
|
|
4244
|
+
throw new Error(
|
|
4245
|
+
`project skill "${project.skillName}" collides with global "${canonicalSkillId(global)}"; add replaces metadata`
|
|
4246
|
+
);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
function rootForSkill(platform, skill, homeDir, projectDir) {
|
|
4252
|
+
if (skillScope(skill) === "global")
|
|
4253
|
+
return nativeSkillRoot(platform, homeDir);
|
|
4254
|
+
if (!projectDir)
|
|
4255
|
+
return null;
|
|
4256
|
+
return writerOwnedProjectSkillRoot(platform, projectDir, homeDir);
|
|
4257
|
+
}
|
|
4258
|
+
function emptyState() {
|
|
4259
|
+
return { version: 1, skills: {} };
|
|
4260
|
+
}
|
|
4261
|
+
function readState(root) {
|
|
4262
|
+
try {
|
|
4263
|
+
const parsed = JSON.parse(fs10.readFileSync(path11.join(root, STATE_FILE), "utf8"));
|
|
4264
|
+
if (parsed?.version === 1 && parsed.skills && typeof parsed.skills === "object")
|
|
4265
|
+
return parsed;
|
|
4266
|
+
} catch {
|
|
4267
|
+
}
|
|
4268
|
+
return emptyState();
|
|
4269
|
+
}
|
|
4270
|
+
function writeState(root, state) {
|
|
4271
|
+
fs10.mkdirSync(root, { recursive: true });
|
|
4272
|
+
fs10.writeFileSync(path11.join(root, STATE_FILE), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
4273
|
+
}
|
|
4274
|
+
function targetPath(root, skillName) {
|
|
4275
|
+
const target = path11.resolve(root, skillName);
|
|
4276
|
+
const prefix = path11.resolve(root) + path11.sep;
|
|
4277
|
+
if (!target.startsWith(prefix))
|
|
4278
|
+
throw new Error("skill target escapes native root");
|
|
4279
|
+
return target;
|
|
4280
|
+
}
|
|
4281
|
+
function filePath(target, relative) {
|
|
4282
|
+
const resolved = path11.resolve(target, ...relative.split("/"));
|
|
4283
|
+
const prefix = path11.resolve(target) + path11.sep;
|
|
4284
|
+
if (!resolved.startsWith(prefix))
|
|
4285
|
+
throw new Error("skill file escapes target directory");
|
|
4286
|
+
return resolved;
|
|
4287
|
+
}
|
|
4288
|
+
function readManagedFiles(target, managed) {
|
|
4289
|
+
try {
|
|
4290
|
+
return managed.files.map((relative) => ({
|
|
4291
|
+
path: relative,
|
|
4292
|
+
content: fs10.readFileSync(filePath(target, relative), "utf8")
|
|
4293
|
+
}));
|
|
4294
|
+
} catch {
|
|
4295
|
+
return null;
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
function managedTargetExists(target) {
|
|
4299
|
+
try {
|
|
4300
|
+
fs10.statSync(target);
|
|
4301
|
+
return true;
|
|
4302
|
+
} catch {
|
|
4303
|
+
return false;
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
function unquote2(value) {
|
|
4307
|
+
const v = value.trim();
|
|
4308
|
+
if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")))
|
|
4309
|
+
return v.slice(1, -1);
|
|
4310
|
+
return v;
|
|
4311
|
+
}
|
|
4312
|
+
function parseDescriptionFromSkillMd(skillMd) {
|
|
4313
|
+
const text = (skillMd || "").replace(/\r\n?/g, "\n");
|
|
4314
|
+
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(text);
|
|
4315
|
+
const body = match ? text.slice(match[0].length) : text;
|
|
4316
|
+
if (match) {
|
|
4317
|
+
const lines = match[1].split("\n");
|
|
4318
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4319
|
+
const key = /^(\s*)description:\s*(.*)$/.exec(lines[i]);
|
|
4320
|
+
if (!key)
|
|
4321
|
+
continue;
|
|
4322
|
+
const scalar = key[2].trim();
|
|
4323
|
+
if (!/^[>|][+-]?$/.test(scalar))
|
|
4324
|
+
return unquote2(scalar).trim();
|
|
4325
|
+
const keyIndent = key[1].length;
|
|
4326
|
+
const block = [];
|
|
4327
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
4328
|
+
const line = lines[j];
|
|
4329
|
+
const indent = /^\s*/.exec(line)?.[0].length ?? 0;
|
|
4330
|
+
if (line.trim() && indent <= keyIndent)
|
|
4331
|
+
break;
|
|
4332
|
+
block.push(line);
|
|
4333
|
+
}
|
|
4334
|
+
const nonEmpty = block.filter((line) => line.trim());
|
|
4335
|
+
const blockIndent = nonEmpty.length ? Math.min(...nonEmpty.map((line) => /^\s*/.exec(line)?.[0].length ?? 0)) : 0;
|
|
4336
|
+
const dedented = block.map((line) => line.trim() ? line.slice(blockIndent).trimEnd() : "");
|
|
4337
|
+
const description = scalar.startsWith(">") ? dedented.join("\n").split(/\n{2,}/).map((p) => p.replace(/\n/g, " ").trim()).join("\n\n") : dedented.join("\n").trim();
|
|
4338
|
+
return description.trim();
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
const lead = body.split("\n").map((line) => line.trim()).find((line) => line && !line.startsWith("#") && !line.startsWith("```") && !line.startsWith("<!--")) || "";
|
|
4342
|
+
return lead.trim();
|
|
4343
|
+
}
|
|
4344
|
+
function adapterDescription(skill) {
|
|
4345
|
+
const direct = (skill.triggerDescription || skill.description || "").replace(/\r\n?/g, "\n").trim();
|
|
4346
|
+
if (direct)
|
|
4347
|
+
return boundedNativeDescription(skill.skillName, direct);
|
|
4348
|
+
const skillMd = skill.files.find((file) => file.path === "SKILL.md")?.content || "";
|
|
4349
|
+
return boundedNativeDescription(skill.skillName, parseDescriptionFromSkillMd(skillMd));
|
|
4350
|
+
}
|
|
4351
|
+
function collapseWhitespace(value) {
|
|
4352
|
+
return value.replace(/\s+/g, " ").trim();
|
|
4353
|
+
}
|
|
4354
|
+
function trimWithMarker(value, max) {
|
|
4355
|
+
const text = collapseWhitespace(value);
|
|
4356
|
+
if (text.length <= max)
|
|
4357
|
+
return text;
|
|
4358
|
+
if (max <= 15)
|
|
4359
|
+
return text.slice(0, Math.max(0, max)).trimEnd();
|
|
4360
|
+
const head = text.slice(0, max - 3);
|
|
4361
|
+
const boundary = Math.max(head.lastIndexOf(". "), head.lastIndexOf("; "), head.lastIndexOf(", "), head.lastIndexOf(" "));
|
|
4362
|
+
return `${head.slice(0, boundary > 80 ? boundary : head.length).trimEnd()}...`;
|
|
4363
|
+
}
|
|
4364
|
+
function sectionFrom(value, label) {
|
|
4365
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4366
|
+
const match = new RegExp(`${escaped}:?\\s*([\\s\\S]*)`, "i").exec(value);
|
|
4367
|
+
if (!match)
|
|
4368
|
+
return "";
|
|
4369
|
+
return match[1].split(/\n\s*\n(?:NOT for|Requires|Built on|Does NOT|REQUIRES)\b/i)[0].trim();
|
|
4370
|
+
}
|
|
4371
|
+
function beforeSection(value, label) {
|
|
4372
|
+
const index = value.toLowerCase().indexOf(label.toLowerCase());
|
|
4373
|
+
return index >= 0 ? value.slice(0, index).trim() : value.trim();
|
|
4374
|
+
}
|
|
4375
|
+
function boundedNativeDescription(skillName, raw) {
|
|
4376
|
+
const normalized = raw.replace(/\r\n?/g, "\n").trim();
|
|
4377
|
+
if (!normalized || normalized.length <= NATIVE_DESCRIPTION_MAX_CHARS)
|
|
4378
|
+
return normalized;
|
|
4379
|
+
const prefix = `Condensed native trigger for ${skillName}; full description: greprag load ${skillName}.`;
|
|
4380
|
+
const useWhen = sectionFrom(normalized, "Use when");
|
|
4381
|
+
const notFor = sectionFrom(normalized, "NOT for");
|
|
4382
|
+
const purposeSource = beforeSection(normalized, "Use when");
|
|
4383
|
+
const fixedParts = [prefix];
|
|
4384
|
+
const tailParts = [];
|
|
4385
|
+
if (useWhen)
|
|
4386
|
+
tailParts.push(["Use when", useWhen]);
|
|
4387
|
+
if (notFor)
|
|
4388
|
+
tailParts.push(["NOT for", notFor]);
|
|
4389
|
+
const fixedLength = fixedParts.join("\n\n").length + tailParts.reduce((sum, [label, body]) => sum + `
|
|
4390
|
+
|
|
4391
|
+
${label}: ${collapseWhitespace(body)}`.length, 0);
|
|
4392
|
+
const purposeBudget = Math.max(160, NATIVE_DESCRIPTION_MAX_CHARS - fixedLength - 12);
|
|
4393
|
+
const parts = [
|
|
4394
|
+
...fixedParts,
|
|
4395
|
+
`Purpose: ${trimWithMarker(purposeSource, purposeBudget)}`,
|
|
4396
|
+
...tailParts.map(([label, body]) => `${label}: ${collapseWhitespace(body)}`)
|
|
4397
|
+
];
|
|
4398
|
+
let rendered = parts.filter(Boolean).join("\n\n");
|
|
4399
|
+
if (rendered.length <= NATIVE_DESCRIPTION_MAX_CHARS)
|
|
4400
|
+
return rendered;
|
|
4401
|
+
const prefixAndUse = [prefix];
|
|
4402
|
+
const useBudget = NATIVE_DESCRIPTION_MAX_CHARS - prefix.length - 14;
|
|
4403
|
+
if (useWhen)
|
|
4404
|
+
prefixAndUse.push(`Use when: ${trimWithMarker(useWhen, useBudget)}`);
|
|
4405
|
+
else
|
|
4406
|
+
prefixAndUse.push(`Purpose: ${trimWithMarker(normalized, useBudget)}`);
|
|
4407
|
+
rendered = prefixAndUse.join("\n\n");
|
|
4408
|
+
return rendered.length <= NATIVE_DESCRIPTION_MAX_CHARS ? rendered : trimWithMarker(rendered, NATIVE_DESCRIPTION_MAX_CHARS);
|
|
4409
|
+
}
|
|
4410
|
+
function yamlLiteral(value) {
|
|
4411
|
+
return value.replace(/\r\n?/g, "\n").split("\n").map((line) => ` ${line}`).join("\n");
|
|
4412
|
+
}
|
|
4413
|
+
function sourceBindingState(skill) {
|
|
4414
|
+
const skillMd = skill.files.find((file) => file.path === "SKILL.md")?.content || "";
|
|
4415
|
+
return /<!--\s*greprag-source-bindings\b/.test(skillMd) ? "bound" : "none";
|
|
4416
|
+
}
|
|
4417
|
+
function canonicalContentHash(skill) {
|
|
4418
|
+
return skill.contentHash || computeSkillFileHash(skill.files);
|
|
4419
|
+
}
|
|
4420
|
+
function canonicalLookupArgs(skill) {
|
|
4421
|
+
return `--skill-id ${canonicalSkillId(skill)}`;
|
|
4422
|
+
}
|
|
4423
|
+
function canonicalWriteArgs(skill) {
|
|
4424
|
+
if (skill.scope !== "project" || !skill.projectId)
|
|
4425
|
+
return "";
|
|
4426
|
+
const replaces = (skill.replaces || []).map((id) => ` --replaces ${id}`).join("");
|
|
4427
|
+
return `--scope project --project-id ${skill.projectId}${replaces}`;
|
|
4428
|
+
}
|
|
4429
|
+
function canonicalEditArgs(skill) {
|
|
4430
|
+
return [canonicalLookupArgs(skill), canonicalWriteArgs(skill)].filter(Boolean).join(" ");
|
|
4431
|
+
}
|
|
4432
|
+
function nativeCreationLocations(platform) {
|
|
4433
|
+
if (platform === "codex") {
|
|
4434
|
+
return "`~/.codex/skills/<new-name>/SKILL.md` (global) or `<repo>/.agents/skills/<new-name>/SKILL.md` (project)";
|
|
4435
|
+
}
|
|
4436
|
+
if (platform === "opencode") {
|
|
4437
|
+
return "`~/.config/opencode/skills/<new-name>/SKILL.md` (global) or `<repo>/.opencode/skills/<new-name>/SKILL.md` (project)";
|
|
4438
|
+
}
|
|
4439
|
+
return "`~/.claude/skills/<new-name>/SKILL.md` (global) or `<repo>/.claude/skills/<new-name>/SKILL.md` (project)";
|
|
4440
|
+
}
|
|
4441
|
+
function renderChangeSkillLine(params) {
|
|
4442
|
+
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
4443
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash>`;
|
|
4444
|
+
if (params.platform === "codex") {
|
|
4445
|
+
return `- **Change this skill:** run \`greprag load skill-change\`, then \`${exportCommand}\`; patch the exported Markdown files non-interactively; run \`${applyCommand}\`; then \`${params.syncCommand}\`; then verify with \`${params.loadCommand}\`.`;
|
|
4446
|
+
}
|
|
4447
|
+
return `- **Change this skill:** run \`greprag load skill-change\`, then \`greprag skill edit ${params.skillName} ${params.editArgs}\`, then \`${params.syncCommand}\`, then verify with \`${params.loadCommand}\`.`;
|
|
4448
|
+
}
|
|
4449
|
+
function renderEditingSection(platform, params) {
|
|
4450
|
+
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
4451
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash>`;
|
|
4452
|
+
if (platform === "codex") {
|
|
4453
|
+
return `## Editing
|
|
4454
|
+
|
|
4455
|
+
Codex agents must use the non-interactive mirror path:
|
|
4456
|
+
|
|
4457
|
+
\`\`\`bash
|
|
4458
|
+
${exportCommand}
|
|
4459
|
+
# patch the exported Markdown files with normal file edits
|
|
4460
|
+
${applyCommand}
|
|
4461
|
+
\`\`\`
|
|
4462
|
+
|
|
4463
|
+
Do not launch an interactive editor from Codex. If using \`greprag skill edit\` as an export helper, include \`--no-open\` and run the printed apply command after patching.
|
|
4464
|
+
`;
|
|
4465
|
+
}
|
|
4466
|
+
return `## Editing
|
|
4467
|
+
|
|
4468
|
+
To edit this skill, run the guarded editor flow:
|
|
4469
|
+
|
|
4470
|
+
\`\`\`bash
|
|
4471
|
+
greprag skill edit ${params.skillName} ${params.editArgs}
|
|
4472
|
+
\`\`\`
|
|
4473
|
+
|
|
4474
|
+
Manual path:
|
|
4475
|
+
|
|
4476
|
+
\`\`\`bash
|
|
4477
|
+
${exportCommand}
|
|
4478
|
+
${applyCommand}
|
|
4479
|
+
\`\`\`
|
|
4480
|
+
`;
|
|
4481
|
+
}
|
|
4482
|
+
function renderAdapterBody(platform, skill) {
|
|
4483
|
+
const skillName = skill.skillName;
|
|
4484
|
+
const skillId = canonicalSkillId(skill);
|
|
4485
|
+
const identityArg = canonicalLookupArgs(skill);
|
|
4486
|
+
const editArgs = canonicalEditArgs(skill);
|
|
4487
|
+
const creationLocations = nativeCreationLocations(platform);
|
|
4488
|
+
const syncCommand = nativeAdapterInstallCommand(platform, [skillName]);
|
|
4489
|
+
const creationLine = platform === "claude-code" ? "- **Create another skill:** use the normal native creation path and leave it unmanaged until a separate Claude cutover is approved." : `- **Create another skill:** create a normal native skill at ${creationLocations}; run \`${syncCommand} --dry-run\` and confirm it says \`adopt\`; then run \`${syncCommand}\`. GrepRAG adopts it as canon and replaces the native package with an adapter.`;
|
|
4490
|
+
const companions = skill.files.map((file) => file.path).filter((relative) => relative !== "SKILL.md").sort();
|
|
4491
|
+
const harnessArg = `--harness ${platform}`;
|
|
4492
|
+
const loadCommand = `greprag load ${skillName} ${identityArg} ${harnessArg}`;
|
|
4493
|
+
const changeSkillLine = renderChangeSkillLine({
|
|
4494
|
+
platform,
|
|
4495
|
+
skillName,
|
|
4496
|
+
editArgs,
|
|
4497
|
+
identityArg,
|
|
4498
|
+
syncCommand,
|
|
4499
|
+
loadCommand
|
|
4500
|
+
});
|
|
4501
|
+
const editingSection = renderEditingSection(platform, { skillName, editArgs, identityArg });
|
|
4502
|
+
const companionLines = companions.length ? companions.map((relative) => `- \`greprag load ${skillName} ${relative} ${identityArg} ${harnessArg}\``).join("\n") : "- No companion docs are mirrored into this native adapter.";
|
|
4503
|
+
return `# ${skillName}
|
|
4504
|
+
|
|
4505
|
+
## GENERATED ADAPTER \u2014 NEVER EDIT
|
|
4506
|
+
|
|
4507
|
+
This is a disposable GrepRAG launcher for ${platform}, not canonical skill content. Never edit or copy this adapter, and never use \`greprag skill install\` to create or change a mirrored skill.
|
|
4508
|
+
|
|
4509
|
+
Do not add or maintain project-state/doc pointers here. Current project state belongs in repo docs and the GrepRAG doc mirror; \`greprag load ${skillName} ${identityArg} ${harnessArg}\` discovers relevant state docs dynamically.
|
|
4510
|
+
|
|
4511
|
+
Required action chains:
|
|
4512
|
+
|
|
4513
|
+
- **Use this skill:** run \`${loadCommand}\` before acting.
|
|
4514
|
+
${changeSkillLine}
|
|
4515
|
+
${creationLine}
|
|
4516
|
+
|
|
4517
|
+
Authority: ${skillId}
|
|
4518
|
+
Source binding: ${sourceBindingState(skill)}
|
|
4519
|
+
Canonical content hash: ${canonicalContentHash(skill)}
|
|
4520
|
+
${typeof skill.revision === "number" ? `Mirror revision: ${skill.revision}` : "Mirror revision: unknown"}
|
|
4521
|
+
|
|
4522
|
+
${editingSection}
|
|
4523
|
+
|
|
4524
|
+
Snapshot rollback:
|
|
4525
|
+
|
|
4526
|
+
\`\`\`bash
|
|
4527
|
+
greprag skill mirror snapshot list ${skillName} ${identityArg}
|
|
4528
|
+
greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash>
|
|
4529
|
+
\`\`\`
|
|
4530
|
+
|
|
4531
|
+
## Mandatory Invocation
|
|
4532
|
+
|
|
4533
|
+
When this skill activates, run:
|
|
4534
|
+
|
|
4535
|
+
\`\`\`bash
|
|
4536
|
+
greprag load ${skillName} ${identityArg} ${harnessArg}
|
|
4537
|
+
\`\`\`
|
|
4538
|
+
|
|
4539
|
+
Read and follow that canonical payload before responding or taking action.
|
|
4540
|
+
|
|
4541
|
+
## Companion Docs
|
|
4542
|
+
|
|
4543
|
+
${companionLines}
|
|
4544
|
+
`;
|
|
4545
|
+
}
|
|
4546
|
+
function renderNativeSkillAdapter(platform, skill, stamps) {
|
|
4547
|
+
if (!NATIVE_SKILL_NAME_RE.test(skill.skillName))
|
|
4548
|
+
return null;
|
|
4549
|
+
if (!skill.files.some((file) => file.path === "SKILL.md") || !skill.files.every(isSafeMirroredSkillFile))
|
|
4550
|
+
return null;
|
|
4551
|
+
const description = adapterDescription(skill);
|
|
4552
|
+
if (!description || description.length > NATIVE_DESCRIPTION_MAX_CHARS)
|
|
4553
|
+
return null;
|
|
4554
|
+
const skillId = canonicalSkillId(skill);
|
|
4555
|
+
const scope = skillScope(skill);
|
|
4556
|
+
const extraFrontmatter = [
|
|
4557
|
+
`greprag_skill_id: ${skillId}`,
|
|
4558
|
+
`greprag_scope: ${scope}`,
|
|
4559
|
+
`greprag_source_binding: ${sourceBindingState(skill)}`,
|
|
4560
|
+
`greprag_content_hash: ${canonicalContentHash(skill)}`,
|
|
4561
|
+
...typeof skill.revision === "number" ? [`greprag_revision: ${skill.revision}`] : [],
|
|
4562
|
+
...scope === "project" && skill.projectId ? [`greprag_project_id: ${skill.projectId}`] : [],
|
|
4563
|
+
...skill.replaces?.length ? [`replaces: ${skill.replaces.join(", ")}`] : [],
|
|
4564
|
+
...stamps
|
|
4565
|
+
].join("\n");
|
|
4566
|
+
return `---
|
|
4567
|
+
name: ${skill.skillName}
|
|
4568
|
+
description: |-
|
|
4569
|
+
${yamlLiteral(description)}
|
|
4570
|
+
${extraFrontmatter}
|
|
4571
|
+
---
|
|
4572
|
+
|
|
4573
|
+
${renderAdapterBody(platform, skill)}`;
|
|
4574
|
+
}
|
|
4575
|
+
var PROBE_PROJECT_ID = "00000000-0000-4000-8000-000000000000";
|
|
4576
|
+
var ADAPTER_FORMAT_PROBES = [
|
|
4577
|
+
{
|
|
4578
|
+
skillName: "format-probe",
|
|
4579
|
+
skillId: "global:format-probe",
|
|
4580
|
+
contentHash: "0".repeat(64),
|
|
4581
|
+
revision: 1,
|
|
4582
|
+
triggerDescription: "Format probe.",
|
|
4583
|
+
files: [{ path: "SKILL.md", content: "# Format probe\n" }, { path: "docs/probe.md", content: "" }]
|
|
4584
|
+
},
|
|
4585
|
+
{
|
|
4586
|
+
skillName: "format-probe",
|
|
4587
|
+
skillId: `project:${PROBE_PROJECT_ID}:format-probe`,
|
|
4588
|
+
scope: "project",
|
|
4589
|
+
projectId: PROBE_PROJECT_ID,
|
|
4590
|
+
replaces: ["global:format-probe"],
|
|
4591
|
+
triggerDescription: "Format probe.",
|
|
4592
|
+
files: [{ path: "SKILL.md", content: "<!-- greprag-source-bindings -->\n" }]
|
|
4593
|
+
}
|
|
4594
|
+
];
|
|
4595
|
+
var adapterFormats = /* @__PURE__ */ new Map();
|
|
4596
|
+
function nativeAdapterFormat(platform) {
|
|
4597
|
+
let format = adapterFormats.get(platform);
|
|
4598
|
+
if (!format) {
|
|
4599
|
+
format = computeSkillFileHash(ADAPTER_FORMAT_PROBES.map((probe, index) => ({
|
|
4600
|
+
path: String(index),
|
|
4601
|
+
content: renderNativeSkillAdapter(platform, probe, []) || ""
|
|
4602
|
+
}))).slice(0, 12);
|
|
4603
|
+
adapterFormats.set(platform, format);
|
|
4604
|
+
}
|
|
4605
|
+
return format;
|
|
4606
|
+
}
|
|
4607
|
+
function buildNativeSkillAdapter(platform, skill, options = {}) {
|
|
4608
|
+
if (!nativeSkillMirrorEnabled(platform) && !(platform === "claude-code" && options.allowClaudeCode))
|
|
4609
|
+
return null;
|
|
4610
|
+
const content = renderNativeSkillAdapter(platform, skill, [
|
|
4611
|
+
`greprag_adapter_format: ${nativeAdapterFormat(platform)}`
|
|
4612
|
+
]);
|
|
4613
|
+
return content === null ? null : [{ path: "SKILL.md", content }];
|
|
4614
|
+
}
|
|
4615
|
+
function removeTrackedFiles(target, relativeFiles) {
|
|
4616
|
+
const directories = /* @__PURE__ */ new Set();
|
|
4617
|
+
for (const relative of relativeFiles) {
|
|
4618
|
+
const file = filePath(target, relative);
|
|
4619
|
+
try {
|
|
4620
|
+
fs10.unlinkSync(file);
|
|
4621
|
+
} catch {
|
|
4622
|
+
}
|
|
4623
|
+
let dir = path11.dirname(file);
|
|
4624
|
+
while (dir.startsWith(path11.resolve(target) + path11.sep)) {
|
|
4625
|
+
directories.add(dir);
|
|
4626
|
+
dir = path11.dirname(dir);
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
for (const dir of [...directories].sort((a, b) => b.length - a.length)) {
|
|
4630
|
+
try {
|
|
4631
|
+
fs10.rmdirSync(dir);
|
|
4632
|
+
} catch {
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
try {
|
|
4636
|
+
fs10.rmdirSync(target);
|
|
4637
|
+
} catch {
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
function applyNativeSkillMirror(params) {
|
|
4641
|
+
if (!nativeSkillMirrorEnabled(params.platform)) {
|
|
4642
|
+
return {
|
|
4643
|
+
installed: 0,
|
|
4644
|
+
updated: 0,
|
|
4645
|
+
current: 0,
|
|
4646
|
+
removed: 0,
|
|
4647
|
+
skipped: params.skills.length,
|
|
4648
|
+
blocked: 0
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
assertNoNativeSkillCollision(params.skills);
|
|
4652
|
+
const homeDir = params.homeDir || os3.homedir();
|
|
4653
|
+
const projectDir = params.projectDir ? path11.resolve(params.projectDir) : void 0;
|
|
4654
|
+
const result = {
|
|
4655
|
+
installed: 0,
|
|
4656
|
+
updated: 0,
|
|
4657
|
+
current: 0,
|
|
4658
|
+
removed: 0,
|
|
4659
|
+
skipped: 0,
|
|
4660
|
+
blocked: 0
|
|
4661
|
+
};
|
|
4662
|
+
const roots = /* @__PURE__ */ new Map();
|
|
4663
|
+
const rootState = (root) => {
|
|
4664
|
+
let state = roots.get(root);
|
|
4665
|
+
if (!state) {
|
|
4666
|
+
state = { state: readState(root), present: /* @__PURE__ */ new Set(), touched: false };
|
|
4667
|
+
roots.set(root, state);
|
|
4668
|
+
}
|
|
4669
|
+
return state;
|
|
4670
|
+
};
|
|
4671
|
+
for (const skill of params.skills) {
|
|
4672
|
+
if (!SKILL_NAME_RE.test(skill.skillName)) {
|
|
4673
|
+
result.skipped++;
|
|
4674
|
+
continue;
|
|
4675
|
+
}
|
|
4676
|
+
const root = rootForSkill(params.platform, skill, homeDir, projectDir);
|
|
4677
|
+
if (!root) {
|
|
4678
|
+
result.skipped++;
|
|
4679
|
+
continue;
|
|
4680
|
+
}
|
|
4681
|
+
const rootEntry = rootState(root);
|
|
4682
|
+
if (params.platform === "codex" && skillScope(skill) === "global" && skill.skillName === "greprag") {
|
|
4683
|
+
if (rootEntry.state.skills[skill.skillName]) {
|
|
4684
|
+
delete rootEntry.state.skills[skill.skillName];
|
|
4685
|
+
rootEntry.touched = true;
|
|
4686
|
+
}
|
|
4687
|
+
continue;
|
|
4688
|
+
}
|
|
4689
|
+
rootEntry.present.add(skill.skillName);
|
|
4690
|
+
const adapterFiles = buildNativeSkillAdapter(params.platform, skill);
|
|
4691
|
+
if (!adapterFiles) {
|
|
4692
|
+
result.skipped++;
|
|
4693
|
+
continue;
|
|
4694
|
+
}
|
|
4695
|
+
const target = targetPath(root, skill.skillName);
|
|
4696
|
+
const managed = rootEntry.state.skills[skill.skillName];
|
|
4697
|
+
if (isSkillFolderOwnedElsewhere(target)) {
|
|
4698
|
+
if (managed)
|
|
4699
|
+
result.blocked++;
|
|
4700
|
+
else
|
|
4701
|
+
result.skipped++;
|
|
4702
|
+
continue;
|
|
4703
|
+
}
|
|
4704
|
+
if (fs10.existsSync(target) && !managed) {
|
|
4705
|
+
result.skipped++;
|
|
4706
|
+
continue;
|
|
4707
|
+
}
|
|
4708
|
+
const nextHash = computeSkillFileHash(adapterFiles);
|
|
4709
|
+
if (managed) {
|
|
4710
|
+
const targetExists = managedTargetExists(target);
|
|
4711
|
+
const currentFiles = targetExists ? readManagedFiles(target, managed) : null;
|
|
4712
|
+
if (targetExists && (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash)) {
|
|
4713
|
+
result.blocked++;
|
|
4714
|
+
continue;
|
|
4715
|
+
}
|
|
4716
|
+
if (targetExists && managed.hash === nextHash) {
|
|
4717
|
+
result.current++;
|
|
4718
|
+
continue;
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
fs10.mkdirSync(target, { recursive: true });
|
|
4722
|
+
for (const file of adapterFiles) {
|
|
4723
|
+
const destination = filePath(target, file.path);
|
|
4724
|
+
fs10.mkdirSync(path11.dirname(destination), { recursive: true });
|
|
4725
|
+
fs10.writeFileSync(destination, file.content, "utf8");
|
|
4726
|
+
}
|
|
4727
|
+
if (managed) {
|
|
4728
|
+
const nextPaths = new Set(adapterFiles.map((file) => file.path));
|
|
4729
|
+
removeTrackedFiles(target, managed.files.filter((relative) => !nextPaths.has(relative)));
|
|
4730
|
+
result.updated++;
|
|
4731
|
+
} else {
|
|
4732
|
+
result.installed++;
|
|
4733
|
+
}
|
|
4734
|
+
rootEntry.state.skills[skill.skillName] = {
|
|
4735
|
+
hash: nextHash,
|
|
4736
|
+
files: adapterFiles.map((file) => file.path).sort()
|
|
4737
|
+
};
|
|
4738
|
+
rootEntry.touched = true;
|
|
4739
|
+
}
|
|
4740
|
+
if (params.prune) {
|
|
4741
|
+
const globalRoot = nativeSkillRoot(params.platform, homeDir);
|
|
4742
|
+
if (params.pruneGlobalRoot !== false)
|
|
4743
|
+
rootState(globalRoot);
|
|
4744
|
+
const projectRoot = projectDir ? writerOwnedProjectSkillRoot(params.platform, projectDir, homeDir) : null;
|
|
4745
|
+
if (projectRoot)
|
|
4746
|
+
rootState(projectRoot);
|
|
4747
|
+
for (const [root, entry] of roots) {
|
|
4748
|
+
if (params.pruneGlobalRoot === false && root === globalRoot)
|
|
4749
|
+
continue;
|
|
4750
|
+
for (const [skillName, managed] of Object.entries(entry.state.skills)) {
|
|
4751
|
+
if (entry.present.has(skillName))
|
|
4752
|
+
continue;
|
|
4753
|
+
const target = targetPath(root, skillName);
|
|
4754
|
+
const currentFiles = isSkillFolderOwnedElsewhere(target) ? null : readManagedFiles(target, managed);
|
|
4755
|
+
if (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash) {
|
|
4756
|
+
result.blocked++;
|
|
4757
|
+
continue;
|
|
4758
|
+
}
|
|
4759
|
+
removeTrackedFiles(target, managed.files);
|
|
4760
|
+
delete entry.state.skills[skillName];
|
|
4761
|
+
entry.touched = true;
|
|
4762
|
+
result.removed++;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
}
|
|
4766
|
+
for (const [root, entry] of roots) {
|
|
4767
|
+
if (entry.touched || fs10.existsSync(path11.join(root, STATE_FILE)))
|
|
4768
|
+
writeState(root, entry.state);
|
|
4769
|
+
}
|
|
4770
|
+
return result;
|
|
4771
|
+
}
|
|
4772
|
+
async function fetchNativeMirroredSkill(apiUrl, apiKey, row) {
|
|
4773
|
+
const skillQs = new URLSearchParams();
|
|
4774
|
+
if (row.skillId)
|
|
4775
|
+
skillQs.set("skillId", row.skillId);
|
|
4776
|
+
else if (row.scope) {
|
|
4777
|
+
skillQs.set("scope", row.scope);
|
|
4778
|
+
if (row.projectId)
|
|
4779
|
+
skillQs.set("projectId", row.projectId);
|
|
4780
|
+
}
|
|
4781
|
+
const response = await fetch(
|
|
4782
|
+
`${apiUrl}/v1/skillmirror/${encodeURIComponent(row.skillName)}${skillQs.size ? `?${skillQs.toString()}` : ""}`,
|
|
4783
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
4784
|
+
);
|
|
4785
|
+
if (!response.ok)
|
|
4786
|
+
return null;
|
|
4787
|
+
const body = await response.json();
|
|
4788
|
+
return Array.isArray(body.files) ? {
|
|
4789
|
+
skillName: row.skillName,
|
|
4790
|
+
skillId: typeof row.skillId === "string" ? row.skillId : void 0,
|
|
4791
|
+
scope: row.scope === "project" ? "project" : "global",
|
|
4792
|
+
projectId: typeof row.projectId === "string" ? row.projectId : void 0,
|
|
4793
|
+
projectName: typeof row.projectName === "string" ? row.projectName : void 0,
|
|
4794
|
+
replaces: Array.isArray(row.replaces) ? row.replaces : [],
|
|
4795
|
+
contentHash: typeof body.contentHash === "string" ? body.contentHash : typeof row.contentHash === "string" ? row.contentHash : void 0,
|
|
4796
|
+
revision: typeof body.revision === "number" ? body.revision : typeof row.revision === "number" ? row.revision : void 0,
|
|
4797
|
+
files: body.files,
|
|
4798
|
+
description: typeof row.description === "string" ? row.description : void 0,
|
|
4799
|
+
triggerDescription: typeof row.triggerDescription === "string" ? row.triggerDescription : void 0
|
|
4800
|
+
} : null;
|
|
4801
|
+
}
|
|
4802
|
+
|
|
4803
|
+
// src/claude-lifecycle-cutover.ts
|
|
4804
|
+
var CLAUDE_LIFECYCLE_SKILLS = ["commit", "deploy", "release"];
|
|
4805
|
+
function adapterFor(skill) {
|
|
4806
|
+
const files = buildNativeSkillAdapter("claude-code", skill, { allowClaudeCode: true });
|
|
4807
|
+
if (!files || files.length !== 1 || files[0].path !== "SKILL.md") {
|
|
4808
|
+
throw new Error(`Claude lifecycle adapter is invalid for ${skill.skillName}.`);
|
|
4809
|
+
}
|
|
4810
|
+
return files;
|
|
4811
|
+
}
|
|
4812
|
+
function safeTimestamp(now) {
|
|
4813
|
+
return now.toISOString().replace(/[:.]/g, "-");
|
|
4814
|
+
}
|
|
4815
|
+
function requiredSkills(skills, names) {
|
|
4816
|
+
const byName = new Map(skills.filter((skill) => skill.scope !== "project").map((skill) => [skill.skillName, skill]));
|
|
4817
|
+
return names.map((name) => {
|
|
4818
|
+
const skill = byName.get(name);
|
|
4819
|
+
if (!skill)
|
|
4820
|
+
throw new Error(`Canonical Claude lifecycle skill missing: ${name}.`);
|
|
4821
|
+
return skill;
|
|
4822
|
+
});
|
|
4823
|
+
}
|
|
4824
|
+
function applyClaudeLifecycleCutover(params) {
|
|
4825
|
+
const homeDir = params.homeDir || os4.homedir();
|
|
4826
|
+
const root = nativeSkillRoot("claude-code", homeDir);
|
|
4827
|
+
const state = readState(root);
|
|
4828
|
+
const names = params.names?.length ? params.names : CLAUDE_LIFECYCLE_SKILLS;
|
|
4829
|
+
const prepared = requiredSkills(params.skills, names).map((skill) => {
|
|
4830
|
+
const target = path12.join(root, skill.skillName);
|
|
4831
|
+
if (isSkillFolderOwnedElsewhere(target)) {
|
|
4832
|
+
throw new Error(`Claude skill folder is owned elsewhere (a link or another installer's package): ${skill.skillName}; not written.`);
|
|
4833
|
+
}
|
|
4834
|
+
const skillMd = path12.join(target, "SKILL.md");
|
|
4835
|
+
const adapterFiles = adapterFor(skill);
|
|
4836
|
+
const nextHash = computeSkillFileHash(adapterFiles);
|
|
4837
|
+
const managed = state.skills[skill.skillName];
|
|
4838
|
+
let currentContent;
|
|
4839
|
+
if (fs11.existsSync(skillMd))
|
|
4840
|
+
currentContent = fs11.readFileSync(skillMd, "utf8");
|
|
4841
|
+
if (managed) {
|
|
4842
|
+
const currentFiles = currentContent === void 0 ? null : [{ path: "SKILL.md", content: currentContent }];
|
|
4843
|
+
if (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash) {
|
|
4844
|
+
throw new Error(`Claude lifecycle adapter diverged: ${skill.skillName}.`);
|
|
4845
|
+
}
|
|
4846
|
+
} else if (fs11.existsSync(target) && currentContent === void 0) {
|
|
4847
|
+
throw new Error(`Claude lifecycle package has no SKILL.md: ${skill.skillName}.`);
|
|
4848
|
+
}
|
|
4849
|
+
return { skill, target, skillMd, adapterFiles, nextHash, managed, currentContent };
|
|
4850
|
+
});
|
|
4851
|
+
const replacements = prepared.filter((item) => !item.managed && item.currentContent !== void 0);
|
|
4852
|
+
let backupDir;
|
|
4853
|
+
if (replacements.length > 0) {
|
|
4854
|
+
backupDir = path12.join(
|
|
4855
|
+
homeDir,
|
|
4856
|
+
".greprag",
|
|
4857
|
+
"backups",
|
|
4858
|
+
"claude-lifecycle-cutover",
|
|
4859
|
+
safeTimestamp(params.now || /* @__PURE__ */ new Date())
|
|
4860
|
+
);
|
|
4861
|
+
for (const item of replacements) {
|
|
4862
|
+
const destination = path12.join(backupDir, item.skill.skillName, "SKILL.md");
|
|
4863
|
+
fs11.mkdirSync(path12.dirname(destination), { recursive: true });
|
|
4864
|
+
fs11.writeFileSync(destination, item.currentContent, "utf8");
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
const result = {
|
|
4868
|
+
installed: 0,
|
|
4869
|
+
updated: 0,
|
|
4870
|
+
current: 0,
|
|
4871
|
+
blocked: 0,
|
|
4872
|
+
backupDir
|
|
4873
|
+
};
|
|
4874
|
+
for (const item of prepared) {
|
|
4875
|
+
if (item.managed && item.managed.hash === item.nextHash) {
|
|
4876
|
+
result.current++;
|
|
4877
|
+
continue;
|
|
4878
|
+
}
|
|
4879
|
+
fs11.mkdirSync(item.target, { recursive: true });
|
|
4880
|
+
fs11.writeFileSync(item.skillMd, item.adapterFiles[0].content, "utf8");
|
|
4881
|
+
state.skills[item.skill.skillName] = { hash: item.nextHash, files: ["SKILL.md"] };
|
|
4882
|
+
if (item.managed)
|
|
4883
|
+
result.updated++;
|
|
4884
|
+
else
|
|
4885
|
+
result.installed++;
|
|
4886
|
+
}
|
|
4887
|
+
writeState(root, state);
|
|
4888
|
+
return result;
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
// src/native-adapter-refresh.ts
|
|
4892
|
+
function readAdapterStamps(content) {
|
|
4893
|
+
const frontmatter = /^---\n([\s\S]*?)\n---/.exec(content.replace(/\r\n?/g, "\n"))?.[1] || "";
|
|
4894
|
+
const field = (key) => new RegExp(`^${key}:[ \\t]*(\\S+)`, "m").exec(frontmatter)?.[1];
|
|
4895
|
+
const revision = field("greprag_revision");
|
|
4896
|
+
return {
|
|
4897
|
+
skillId: field("greprag_skill_id"),
|
|
4898
|
+
contentHash: field("greprag_content_hash"),
|
|
4899
|
+
revision: revision === void 0 ? void 0 : Number(revision),
|
|
4900
|
+
format: field("greprag_adapter_format")
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4903
|
+
function staleNativeAdapters(params) {
|
|
4904
|
+
const globalRoot = nativeSkillRoot(params.platform, params.homeDir);
|
|
4905
|
+
const roots = [globalRoot];
|
|
4906
|
+
const projectRoot = params.projectDir && params.platform !== "claude-code" ? writerOwnedProjectSkillRoot(params.platform, params.projectDir, params.homeDir) : null;
|
|
4907
|
+
if (projectRoot)
|
|
4908
|
+
roots.push(projectRoot);
|
|
4909
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4910
|
+
for (const row of params.rows) {
|
|
4911
|
+
if (typeof row.skillName === "string" && typeof row.skillId === "string") {
|
|
4912
|
+
byId.set(row.skillId, row);
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
const format = nativeAdapterFormat(params.platform);
|
|
4916
|
+
const stale = [];
|
|
4917
|
+
for (const root of roots) {
|
|
4918
|
+
for (const [skillName, managed] of Object.entries(readState(root).skills)) {
|
|
4919
|
+
let files;
|
|
4920
|
+
try {
|
|
4921
|
+
files = readManagedFiles(targetPath(root, skillName), managed);
|
|
4922
|
+
} catch {
|
|
4923
|
+
continue;
|
|
4924
|
+
}
|
|
4925
|
+
if (!files?.length || computeSkillFileHash(files) !== managed.hash)
|
|
4926
|
+
continue;
|
|
4927
|
+
const stamps = readAdapterStamps(files.find((file) => file.path === "SKILL.md")?.content || "");
|
|
4928
|
+
const row = byId.get(stamps.skillId || (root === globalRoot ? `global:${skillName}` : ""));
|
|
4929
|
+
if (!row || row.skillName !== skillName)
|
|
4930
|
+
continue;
|
|
4931
|
+
const canonMoved = row.contentHash ? row.contentHash !== stamps.contentHash : typeof row.revision === "number" && row.revision !== stamps.revision;
|
|
4932
|
+
if (canonMoved || stamps.format !== format)
|
|
4933
|
+
stale.push({ root, row });
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
return stale;
|
|
4937
|
+
}
|
|
4938
|
+
async function refreshNativeAdapters(params) {
|
|
4939
|
+
const platform = params.platform;
|
|
4940
|
+
if (platform !== "claude-code" && platform !== "codex" && platform !== "opencode") {
|
|
4941
|
+
return { refreshed: 0, blocked: 0 };
|
|
4942
|
+
}
|
|
4943
|
+
const stale = staleNativeAdapters({ ...params, platform });
|
|
4944
|
+
const skills = await Promise.all(stale.map(({ row }) => fetchNativeMirroredSkill(params.apiUrl, params.apiKey, row).catch(() => null)));
|
|
4945
|
+
let refreshed = 0;
|
|
4946
|
+
let blocked = 0;
|
|
4947
|
+
for (const skill of skills) {
|
|
4948
|
+
if (!skill)
|
|
4949
|
+
continue;
|
|
4950
|
+
try {
|
|
4951
|
+
const result = platform === "claude-code" ? applyClaudeLifecycleCutover({ skills: [skill], names: [skill.skillName], homeDir: params.homeDir }) : applyNativeSkillMirror({
|
|
4952
|
+
platform,
|
|
4953
|
+
skills: [skill],
|
|
4954
|
+
homeDir: params.homeDir,
|
|
4955
|
+
projectDir: params.projectDir,
|
|
4956
|
+
prune: false
|
|
4957
|
+
});
|
|
4958
|
+
refreshed += result.updated;
|
|
4959
|
+
blocked += result.blocked;
|
|
4960
|
+
} catch {
|
|
4961
|
+
blocked++;
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
return { refreshed, blocked };
|
|
4965
|
+
}
|
|
4966
|
+
|
|
4055
4967
|
// src/opencode-plugin.ts
|
|
4056
|
-
var DEBUG_LOG_PATH =
|
|
4968
|
+
var DEBUG_LOG_PATH = path13.join(os5.homedir(), ".greprag", "opencode-plugin-debug.log");
|
|
4057
4969
|
var _debugLogReady = false;
|
|
4058
4970
|
function dlogInit() {
|
|
4059
4971
|
if (_debugLogReady)
|
|
4060
4972
|
return;
|
|
4061
4973
|
_debugLogReady = true;
|
|
4062
4974
|
try {
|
|
4063
|
-
|
|
4975
|
+
fs12.writeFileSync(DEBUG_LOG_PATH, "");
|
|
4064
4976
|
} catch {
|
|
4065
4977
|
}
|
|
4066
4978
|
}
|
|
@@ -4069,17 +4981,17 @@ function dlog(msg) {
|
|
|
4069
4981
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [greprag-memory] ${msg}
|
|
4070
4982
|
`;
|
|
4071
4983
|
try {
|
|
4072
|
-
|
|
4984
|
+
fs12.appendFileSync(DEBUG_LOG_PATH, line);
|
|
4073
4985
|
} catch {
|
|
4074
4986
|
}
|
|
4075
4987
|
}
|
|
4076
4988
|
dlog(`module top reached: pid=${process.pid} argv0=${process.argv[0]} distPath=${__filename}`);
|
|
4077
4989
|
var API_URL = "https://api.greprag.com";
|
|
4078
|
-
function loadEnvFile(
|
|
4990
|
+
function loadEnvFile(filePath2) {
|
|
4079
4991
|
try {
|
|
4080
|
-
if (!
|
|
4992
|
+
if (!fs12.existsSync(filePath2))
|
|
4081
4993
|
return;
|
|
4082
|
-
const raw =
|
|
4994
|
+
const raw = fs12.readFileSync(filePath2, "utf-8");
|
|
4083
4995
|
for (const line of raw.split(/\r?\n/)) {
|
|
4084
4996
|
const trimmed = line.trim();
|
|
4085
4997
|
if (!trimmed || trimmed.startsWith("#"))
|
|
@@ -4099,12 +5011,12 @@ function loadEnvFile(filePath) {
|
|
|
4099
5011
|
}
|
|
4100
5012
|
}
|
|
4101
5013
|
function loadGrepragEnv() {
|
|
4102
|
-
loadEnvFile(
|
|
5014
|
+
loadEnvFile(path13.join(HOME, ".greprag", ".env"));
|
|
4103
5015
|
try {
|
|
4104
|
-
const p =
|
|
4105
|
-
if (!
|
|
5016
|
+
const p = path13.join(HOME, ".claude", "settings.json");
|
|
5017
|
+
if (!fs12.existsSync(p))
|
|
4106
5018
|
return;
|
|
4107
|
-
const data = JSON.parse(
|
|
5019
|
+
const data = JSON.parse(fs12.readFileSync(p, "utf-8"));
|
|
4108
5020
|
if (data && data.env && typeof data.env === "object") {
|
|
4109
5021
|
for (const [key, val] of Object.entries(data.env)) {
|
|
4110
5022
|
if (!process.env[key] && typeof val === "string") {
|
|
@@ -4117,11 +5029,11 @@ function loadGrepragEnv() {
|
|
|
4117
5029
|
}
|
|
4118
5030
|
loadGrepragEnv();
|
|
4119
5031
|
dlog(`env loaded: MEMORY_HOOK_ENABLED=${process.env.MEMORY_HOOK_ENABLED || "<unset>"} GREPRAG_API_KEY=${process.env.GREPRAG_API_KEY ? "set" : "<unset>"} GREPRAG_OPENCODE_CAPTURE=${process.env.GREPRAG_OPENCODE_CAPTURE || "<unset>"}`);
|
|
4120
|
-
var WATCHER_LOCK =
|
|
5032
|
+
var WATCHER_LOCK = path13.join(HOME || os5.homedir(), ".greprag", "opencode-watch.lock");
|
|
4121
5033
|
var WATCHER_STALE_MS = 3e4;
|
|
4122
5034
|
function findGrepragBinary() {
|
|
4123
5035
|
const override = process.env.GREPRAG_BIN;
|
|
4124
|
-
if (override &&
|
|
5036
|
+
if (override && fs12.existsSync(override))
|
|
4125
5037
|
return override;
|
|
4126
5038
|
const binaryName = process.platform === "win32" ? "greprag.cmd" : "greprag";
|
|
4127
5039
|
try {
|
|
@@ -4130,14 +5042,14 @@ function findGrepragBinary() {
|
|
|
4130
5042
|
const candidate = line.trim();
|
|
4131
5043
|
if (!candidate)
|
|
4132
5044
|
continue;
|
|
4133
|
-
if (!
|
|
5045
|
+
if (!fs12.existsSync(candidate))
|
|
4134
5046
|
continue;
|
|
4135
5047
|
if (process.platform === "win32") {
|
|
4136
|
-
const ext =
|
|
5048
|
+
const ext = path13.extname(candidate).toLowerCase();
|
|
4137
5049
|
if (ext === ".cmd" || ext === ".exe" || ext === ".bat")
|
|
4138
5050
|
return candidate;
|
|
4139
5051
|
const cmdSibling = candidate + ".cmd";
|
|
4140
|
-
if (
|
|
5052
|
+
if (fs12.existsSync(cmdSibling))
|
|
4141
5053
|
return cmdSibling;
|
|
4142
5054
|
continue;
|
|
4143
5055
|
}
|
|
@@ -4146,19 +5058,19 @@ function findGrepragBinary() {
|
|
|
4146
5058
|
} catch {
|
|
4147
5059
|
}
|
|
4148
5060
|
const candidates = process.platform === "win32" ? [
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
5061
|
+
path13.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
|
|
5062
|
+
path13.join(HOME, "AppData", "Roaming", "npm", "greprag"),
|
|
5063
|
+
path13.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
|
|
5064
|
+
path13.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
|
|
4153
5065
|
] : [
|
|
4154
5066
|
"/usr/local/bin/greprag",
|
|
4155
5067
|
"/opt/homebrew/bin/greprag",
|
|
4156
|
-
|
|
4157
|
-
|
|
5068
|
+
path13.join(HOME || "", ".local", "bin", "greprag"),
|
|
5069
|
+
path13.join(HOME || "", ".yarn", "bin", "greprag")
|
|
4158
5070
|
];
|
|
4159
5071
|
for (const c of candidates) {
|
|
4160
5072
|
try {
|
|
4161
|
-
if (c &&
|
|
5073
|
+
if (c && fs12.existsSync(c))
|
|
4162
5074
|
return c;
|
|
4163
5075
|
} catch {
|
|
4164
5076
|
}
|
|
@@ -4167,18 +5079,18 @@ function findGrepragBinary() {
|
|
|
4167
5079
|
}
|
|
4168
5080
|
function tryClaimWatcherLock() {
|
|
4169
5081
|
try {
|
|
4170
|
-
const fd =
|
|
5082
|
+
const fd = fs12.openSync(WATCHER_LOCK, "wx");
|
|
4171
5083
|
return { ok: true, fd };
|
|
4172
5084
|
} catch (err) {
|
|
4173
5085
|
if (err.code !== "EEXIST") {
|
|
4174
5086
|
return { ok: false, reason: "lock-create-failed" };
|
|
4175
5087
|
}
|
|
4176
5088
|
try {
|
|
4177
|
-
const stat =
|
|
5089
|
+
const stat = fs12.statSync(WATCHER_LOCK);
|
|
4178
5090
|
const ageMs = Date.now() - stat.mtimeMs;
|
|
4179
5091
|
let pidAlive = false;
|
|
4180
5092
|
try {
|
|
4181
|
-
const pid = parseInt(
|
|
5093
|
+
const pid = parseInt(fs12.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
|
|
4182
5094
|
pidAlive = pid > 0 && isPidAlive(pid);
|
|
4183
5095
|
} catch {
|
|
4184
5096
|
}
|
|
@@ -4186,10 +5098,10 @@ function tryClaimWatcherLock() {
|
|
|
4186
5098
|
return { ok: false, reason: "busy" };
|
|
4187
5099
|
}
|
|
4188
5100
|
try {
|
|
4189
|
-
|
|
5101
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4190
5102
|
} catch {
|
|
4191
5103
|
}
|
|
4192
|
-
const fd =
|
|
5104
|
+
const fd = fs12.openSync(WATCHER_LOCK, "wx");
|
|
4193
5105
|
return { ok: true, fd };
|
|
4194
5106
|
} catch (err2) {
|
|
4195
5107
|
return { ok: false, reason: "lock-stale-replace-failed" };
|
|
@@ -4230,11 +5142,11 @@ function startCaptureWatcher() {
|
|
|
4230
5142
|
return;
|
|
4231
5143
|
}
|
|
4232
5144
|
try {
|
|
4233
|
-
|
|
4234
|
-
|
|
5145
|
+
fs12.writeSync(claim.fd, String(process.pid));
|
|
5146
|
+
fs12.closeSync(claim.fd);
|
|
4235
5147
|
} catch {
|
|
4236
5148
|
try {
|
|
4237
|
-
|
|
5149
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4238
5150
|
} catch {
|
|
4239
5151
|
}
|
|
4240
5152
|
return;
|
|
@@ -4250,11 +5162,11 @@ function startCaptureWatcher() {
|
|
|
4250
5162
|
const claim2 = tryClaimWatcherLock();
|
|
4251
5163
|
if (claim2.ok) {
|
|
4252
5164
|
try {
|
|
4253
|
-
|
|
4254
|
-
|
|
5165
|
+
fs12.writeSync(claim2.fd, String(process.pid));
|
|
5166
|
+
fs12.closeSync(claim2.fd);
|
|
4255
5167
|
} catch {
|
|
4256
5168
|
try {
|
|
4257
|
-
|
|
5169
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4258
5170
|
} catch {
|
|
4259
5171
|
}
|
|
4260
5172
|
return false;
|
|
@@ -4297,7 +5209,7 @@ function startCaptureWatcher() {
|
|
|
4297
5209
|
childStartTime = Date.now();
|
|
4298
5210
|
dlog(`startCaptureWatcher: spawned 'greprag opencode watch' pid=${child.pid ?? "<none>"}`);
|
|
4299
5211
|
try {
|
|
4300
|
-
|
|
5212
|
+
fs12.writeFileSync(WATCHER_LOCK, String(child.pid));
|
|
4301
5213
|
} catch {
|
|
4302
5214
|
}
|
|
4303
5215
|
child.stderr?.on("data", (chunk2) => {
|
|
@@ -4309,9 +5221,9 @@ function startCaptureWatcher() {
|
|
|
4309
5221
|
child.on("exit", (code, signal) => {
|
|
4310
5222
|
dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
|
|
4311
5223
|
try {
|
|
4312
|
-
const current =
|
|
5224
|
+
const current = fs12.readFileSync(WATCHER_LOCK, "utf-8").trim();
|
|
4313
5225
|
if (current === String(child.pid))
|
|
4314
|
-
|
|
5226
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4315
5227
|
} catch {
|
|
4316
5228
|
}
|
|
4317
5229
|
currentChild = null;
|
|
@@ -4340,7 +5252,7 @@ function startCaptureWatcher() {
|
|
|
4340
5252
|
`
|
|
4341
5253
|
);
|
|
4342
5254
|
try {
|
|
4343
|
-
|
|
5255
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4344
5256
|
} catch {
|
|
4345
5257
|
}
|
|
4346
5258
|
return;
|
|
@@ -4372,7 +5284,7 @@ function startCaptureWatcher() {
|
|
|
4372
5284
|
}, 2e3).unref();
|
|
4373
5285
|
}
|
|
4374
5286
|
try {
|
|
4375
|
-
|
|
5287
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4376
5288
|
} catch {
|
|
4377
5289
|
}
|
|
4378
5290
|
};
|
|
@@ -4437,6 +5349,14 @@ async function fetchSkillActivation(apiKey, cwd) {
|
|
|
4437
5349
|
const data = await response.json();
|
|
4438
5350
|
const rows = data.skills || [];
|
|
4439
5351
|
cachedSkillRows = { projectId: anchor.projectId, rows, fetchedAt: now };
|
|
5352
|
+
await refreshNativeAdapters({
|
|
5353
|
+
platform: "opencode",
|
|
5354
|
+
rows,
|
|
5355
|
+
apiUrl: API_URL,
|
|
5356
|
+
apiKey,
|
|
5357
|
+
homeDir: os5.homedir(),
|
|
5358
|
+
projectDir: cwd
|
|
5359
|
+
}).catch(() => void 0);
|
|
4440
5360
|
return activationFromApiRows({
|
|
4441
5361
|
rows,
|
|
4442
5362
|
platform: "opencode",
|
|
@@ -4509,11 +5429,11 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4509
5429
|
return;
|
|
4510
5430
|
}
|
|
4511
5431
|
try {
|
|
4512
|
-
|
|
4513
|
-
|
|
5432
|
+
fs12.writeSync(claim.fd, String(process.pid));
|
|
5433
|
+
fs12.closeSync(claim.fd);
|
|
4514
5434
|
} catch {
|
|
4515
5435
|
try {
|
|
4516
|
-
|
|
5436
|
+
fs12.unlinkSync(lockPath);
|
|
4517
5437
|
} catch {
|
|
4518
5438
|
}
|
|
4519
5439
|
return;
|
|
@@ -4528,11 +5448,11 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4528
5448
|
const claim2 = tryClaimRelayLock(lockPath);
|
|
4529
5449
|
if (claim2.ok) {
|
|
4530
5450
|
try {
|
|
4531
|
-
|
|
4532
|
-
|
|
5451
|
+
fs12.writeSync(claim2.fd, String(process.pid));
|
|
5452
|
+
fs12.closeSync(claim2.fd);
|
|
4533
5453
|
} catch {
|
|
4534
5454
|
try {
|
|
4535
|
-
|
|
5455
|
+
fs12.unlinkSync(lockPath);
|
|
4536
5456
|
} catch {
|
|
4537
5457
|
}
|
|
4538
5458
|
return false;
|
|
@@ -4568,7 +5488,7 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4568
5488
|
childStartTime = Date.now();
|
|
4569
5489
|
dlog(`startSessionRelay: spawned 'greprag opencode relay' pid=${child.pid ?? "<none>"}`);
|
|
4570
5490
|
try {
|
|
4571
|
-
|
|
5491
|
+
fs12.writeFileSync(lockPath, String(child.pid));
|
|
4572
5492
|
} catch {
|
|
4573
5493
|
}
|
|
4574
5494
|
child.stderr?.on("data", (chunk2) => {
|
|
@@ -4579,9 +5499,9 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4579
5499
|
});
|
|
4580
5500
|
child.on("exit", (code, signal) => {
|
|
4581
5501
|
try {
|
|
4582
|
-
const current =
|
|
5502
|
+
const current = fs12.readFileSync(lockPath, "utf-8").trim();
|
|
4583
5503
|
if (current === String(child.pid))
|
|
4584
|
-
|
|
5504
|
+
fs12.unlinkSync(lockPath);
|
|
4585
5505
|
} catch {
|
|
4586
5506
|
}
|
|
4587
5507
|
currentChild = null;
|
|
@@ -4608,7 +5528,7 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4608
5528
|
`
|
|
4609
5529
|
);
|
|
4610
5530
|
try {
|
|
4611
|
-
|
|
5531
|
+
fs12.unlinkSync(lockPath);
|
|
4612
5532
|
} catch {
|
|
4613
5533
|
}
|
|
4614
5534
|
return;
|
|
@@ -4640,7 +5560,7 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4640
5560
|
}, 2e3).unref();
|
|
4641
5561
|
}
|
|
4642
5562
|
try {
|
|
4643
|
-
|
|
5563
|
+
fs12.unlinkSync(lockPath);
|
|
4644
5564
|
} catch {
|
|
4645
5565
|
}
|
|
4646
5566
|
};
|
|
@@ -4701,7 +5621,7 @@ function runPngRecode(input, msgs) {
|
|
|
4701
5621
|
}
|
|
4702
5622
|
function makeCcrStash() {
|
|
4703
5623
|
return (original, sourceType) => {
|
|
4704
|
-
const hash =
|
|
5624
|
+
const hash = crypto6.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
|
|
4705
5625
|
const bin = grepragBinCached();
|
|
4706
5626
|
if (bin) {
|
|
4707
5627
|
try {
|