greprag 5.82.1 → 5.83.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/claude-lifecycle-cutover.js +5 -16
- package/dist/commands/codex.js +4 -0
- package/dist/commands/collision-check.js +9 -6
- 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-reminder.js +13 -9
- 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 +1083 -160
- 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 +4 -19
- package/skill/greprag/SKILL.md +1 -1
- package/skill/greprag/docs/corpus.md +2 -2
- package/skill/greprag/docs/setup.md +1 -1
- package/skill/mechanic/SKILL.md +15 -40
- package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
- package/skill/templates/chip-spawn.md +36 -20
- package/skill/templates/delivery.md +19 -5
|
@@ -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
|
"",
|
|
@@ -2412,7 +2534,10 @@ var loadoutRegistrarModule = {
|
|
|
2412
2534
|
|
|
2413
2535
|
// src/commands/delivery-reminder.ts
|
|
2414
2536
|
function coordinationLine(platform) {
|
|
2415
|
-
|
|
2537
|
+
if (platform === "claude-code") {
|
|
2538
|
+
return "Send same-repo peers no delivery notice: Git is the coordination. Never poll, wait on, or ask peer sessions for status.";
|
|
2539
|
+
}
|
|
2540
|
+
const how = platform === "codex" ? "codex_app.list_threads + codex_app.send_message_to_thread" : platform === "opencode" ? "the GrepRAG registry" : "your harness's own peer tools";
|
|
2416
2541
|
const never = platform === "codex" ? "never poll, call wait_threads, ask for status, or follow up" : "never poll, wait, ask for status, or follow up";
|
|
2417
2542
|
return `Notify same-repo peer sessions ONCE via ${how}, then continue. A \`Chip: \` session is not a peer. Silence is approval: ${never}.`;
|
|
2418
2543
|
}
|
|
@@ -2585,23 +2710,23 @@ function collectAnnounces(env, registry = REGISTRY) {
|
|
|
2585
2710
|
}
|
|
2586
2711
|
|
|
2587
2712
|
// src/app-settings.ts
|
|
2588
|
-
var
|
|
2713
|
+
var fs5 = __toESM(require("node:fs"));
|
|
2589
2714
|
var os2 = __toESM(require("node:os"));
|
|
2590
|
-
var
|
|
2715
|
+
var path6 = __toESM(require("node:path"));
|
|
2591
2716
|
|
|
2592
2717
|
// src/project-anchor.ts
|
|
2593
|
-
var
|
|
2594
|
-
var
|
|
2595
|
-
var
|
|
2718
|
+
var path5 = __toESM(require("path"));
|
|
2719
|
+
var fs4 = __toESM(require("fs"));
|
|
2720
|
+
var crypto3 = __toESM(require("crypto"));
|
|
2596
2721
|
var os = __toESM(require("os"));
|
|
2597
2722
|
var ANCHOR_DIR = ".greprag";
|
|
2598
2723
|
var ANCHOR_FILE = "project.json";
|
|
2599
2724
|
var LEGACY_ANCHOR_DIR = ".claude";
|
|
2600
2725
|
function anchorPathIn(dir) {
|
|
2601
|
-
return
|
|
2726
|
+
return path5.join(dir, ANCHOR_DIR, ANCHOR_FILE);
|
|
2602
2727
|
}
|
|
2603
2728
|
function legacyAnchorPathIn(dir) {
|
|
2604
|
-
return
|
|
2729
|
+
return path5.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
|
|
2605
2730
|
}
|
|
2606
2731
|
function globalAnchorPath() {
|
|
2607
2732
|
return anchorPathIn(os.homedir());
|
|
@@ -2612,22 +2737,22 @@ function legacyGlobalAnchorPath() {
|
|
|
2612
2737
|
function findExistingAnchor(startDir) {
|
|
2613
2738
|
const homeAnchor = globalAnchorPath();
|
|
2614
2739
|
const legacyHomeAnchor = legacyGlobalAnchorPath();
|
|
2615
|
-
let dir =
|
|
2740
|
+
let dir = path5.resolve(startDir);
|
|
2616
2741
|
while (true) {
|
|
2617
2742
|
const candidate = anchorPathIn(dir);
|
|
2618
|
-
if (candidate !== homeAnchor &&
|
|
2743
|
+
if (candidate !== homeAnchor && fs4.existsSync(candidate))
|
|
2619
2744
|
return candidate;
|
|
2620
2745
|
const legacyCandidate = legacyAnchorPathIn(dir);
|
|
2621
|
-
if (legacyCandidate !== legacyHomeAnchor &&
|
|
2746
|
+
if (legacyCandidate !== legacyHomeAnchor && fs4.existsSync(legacyCandidate))
|
|
2622
2747
|
return legacyCandidate;
|
|
2623
|
-
const parent =
|
|
2748
|
+
const parent = path5.dirname(dir);
|
|
2624
2749
|
if (parent === dir)
|
|
2625
2750
|
return null;
|
|
2626
2751
|
dir = parent;
|
|
2627
2752
|
}
|
|
2628
2753
|
}
|
|
2629
2754
|
function isEphemeralCwd2(cwd) {
|
|
2630
|
-
const norm =
|
|
2755
|
+
const norm = path5.resolve(cwd).replace(/\\/g, "/").toLowerCase();
|
|
2631
2756
|
if (norm.includes("/appdata/roaming/claude/local-agent-mode-sessions/"))
|
|
2632
2757
|
return true;
|
|
2633
2758
|
if (norm.includes("/appdata/local/claude/local-agent-mode-sessions/"))
|
|
@@ -2650,7 +2775,7 @@ function computeGitDerivedProjectId2(workingDir) {
|
|
|
2650
2775
|
const roots = out.trim().split(/\s+/).filter(Boolean).sort();
|
|
2651
2776
|
if (roots.length === 0)
|
|
2652
2777
|
return null;
|
|
2653
|
-
const hash =
|
|
2778
|
+
const hash = crypto3.createHash("sha256").update(roots.join("\n")).digest("hex");
|
|
2654
2779
|
return [
|
|
2655
2780
|
hash.slice(0, 8),
|
|
2656
2781
|
hash.slice(8, 12),
|
|
@@ -2665,8 +2790,8 @@ function computeGitDerivedProjectId2(workingDir) {
|
|
|
2665
2790
|
}
|
|
2666
2791
|
}
|
|
2667
2792
|
function deterministicProjectId2(workingDir) {
|
|
2668
|
-
const normalized =
|
|
2669
|
-
const hash =
|
|
2793
|
+
const normalized = path5.resolve(workingDir).toLowerCase();
|
|
2794
|
+
const hash = crypto3.createHash("sha256").update(normalized).digest("hex");
|
|
2670
2795
|
return [
|
|
2671
2796
|
hash.slice(0, 8),
|
|
2672
2797
|
hash.slice(8, 12),
|
|
@@ -2677,9 +2802,9 @@ function deterministicProjectId2(workingDir) {
|
|
|
2677
2802
|
hash.slice(20, 32)
|
|
2678
2803
|
].join("-");
|
|
2679
2804
|
}
|
|
2680
|
-
function tryReadAnchorFileContents(
|
|
2805
|
+
function tryReadAnchorFileContents(filePath2) {
|
|
2681
2806
|
try {
|
|
2682
|
-
const raw = JSON.parse(
|
|
2807
|
+
const raw = JSON.parse(fs4.readFileSync(filePath2, "utf-8"));
|
|
2683
2808
|
const notifyRaw = raw.inbox_notify;
|
|
2684
2809
|
const inboxNotify = notifyRaw === "off" || notifyRaw === "session_start_only" ? notifyRaw : "every_turn";
|
|
2685
2810
|
return {
|
|
@@ -2718,10 +2843,10 @@ function readAnchor2(cwd) {
|
|
|
2718
2843
|
}
|
|
2719
2844
|
const gitId = computeGitDerivedProjectId2(cwd);
|
|
2720
2845
|
if (gitId) {
|
|
2721
|
-
const root2 =
|
|
2846
|
+
const root2 = path5.resolve(cwd);
|
|
2722
2847
|
return {
|
|
2723
2848
|
projectId: gitId,
|
|
2724
|
-
projectName: fileContents?.projectName ||
|
|
2849
|
+
projectName: fileContents?.projectName || path5.basename(root2).toLowerCase(),
|
|
2725
2850
|
initialized: true,
|
|
2726
2851
|
source: "git",
|
|
2727
2852
|
anchorPath: existingPath || anchorPathIn(root2),
|
|
@@ -2735,7 +2860,7 @@ function readAnchor2(cwd) {
|
|
|
2735
2860
|
};
|
|
2736
2861
|
}
|
|
2737
2862
|
if (isEphemeralCwd2(cwd)) {
|
|
2738
|
-
const globalPath =
|
|
2863
|
+
const globalPath = fs4.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
|
|
2739
2864
|
const globalContents = tryReadAnchorFileContents(globalPath);
|
|
2740
2865
|
if (globalContents && globalContents.projectId && globalContents.projectName) {
|
|
2741
2866
|
return {
|
|
@@ -2754,10 +2879,10 @@ function readAnchor2(cwd) {
|
|
|
2754
2879
|
};
|
|
2755
2880
|
}
|
|
2756
2881
|
}
|
|
2757
|
-
const root =
|
|
2882
|
+
const root = path5.resolve(cwd);
|
|
2758
2883
|
return {
|
|
2759
2884
|
projectId: deterministicProjectId2(root),
|
|
2760
|
-
projectName: fileContents?.projectName ||
|
|
2885
|
+
projectName: fileContents?.projectName || path5.basename(root).toLowerCase(),
|
|
2761
2886
|
initialized: false,
|
|
2762
2887
|
source: "hash",
|
|
2763
2888
|
anchorPath: existingPath || anchorPathIn(root),
|
|
@@ -2774,14 +2899,14 @@ function readAnchor2(cwd) {
|
|
|
2774
2899
|
// src/app-settings.ts
|
|
2775
2900
|
function readJson(file) {
|
|
2776
2901
|
try {
|
|
2777
|
-
const parsed = JSON.parse(
|
|
2902
|
+
const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
2778
2903
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
2779
2904
|
} catch {
|
|
2780
2905
|
return {};
|
|
2781
2906
|
}
|
|
2782
2907
|
}
|
|
2783
2908
|
function localAppSettingsPath(homeDir = os2.homedir()) {
|
|
2784
|
-
return
|
|
2909
|
+
return path6.join(homeDir, ".greprag", "settings.json");
|
|
2785
2910
|
}
|
|
2786
2911
|
function readLocalAppSettings(homeDir = os2.homedir()) {
|
|
2787
2912
|
const raw = readJson(localAppSettingsPath(homeDir));
|
|
@@ -2838,8 +2963,8 @@ function getOpenCodeReminders(env) {
|
|
|
2838
2963
|
}
|
|
2839
2964
|
|
|
2840
2965
|
// src/procedure.ts
|
|
2841
|
-
var
|
|
2842
|
-
var
|
|
2966
|
+
var path8 = __toESM(require("path"));
|
|
2967
|
+
var fs7 = __toESM(require("fs"));
|
|
2843
2968
|
|
|
2844
2969
|
// src/delivery-lifecycle.ts
|
|
2845
2970
|
var DELIVERY_LIFECYCLE_VERBS = [
|
|
@@ -2863,8 +2988,8 @@ function isDeliveryOwnedVerb(verb) {
|
|
|
2863
2988
|
}
|
|
2864
2989
|
|
|
2865
2990
|
// src/procedure-scope.ts
|
|
2866
|
-
var
|
|
2867
|
-
var
|
|
2991
|
+
var path7 = __toESM(require("path"));
|
|
2992
|
+
var fs6 = __toESM(require("fs"));
|
|
2868
2993
|
var CD_TARGET_RE = /(?:^|[\s;&|(])(?:cd|pushd|chdir|set-location)\s+(?:\/d\s+|-path\s+)?("[^"]*"|'[^']*'|[^\s;&|)]+)/gi;
|
|
2869
2994
|
var GIT_C_TARGET_RE = /(?:^|[\s;&|(])git\s+-C\s+("[^"]*"|'[^']*'|[^\s;&|)]+)/gi;
|
|
2870
2995
|
function unquote(arg) {
|
|
@@ -2924,32 +3049,32 @@ function foreignCheckoutTarget(proc, roots) {
|
|
|
2924
3049
|
return null;
|
|
2925
3050
|
}
|
|
2926
3051
|
function findCheckoutRoot(startDir) {
|
|
2927
|
-
let dir =
|
|
3052
|
+
let dir = path7.resolve(startDir);
|
|
2928
3053
|
for (; ; ) {
|
|
2929
3054
|
try {
|
|
2930
|
-
if (
|
|
3055
|
+
if (fs6.existsSync(path7.join(dir, ".git")))
|
|
2931
3056
|
return dir;
|
|
2932
3057
|
} catch {
|
|
2933
3058
|
}
|
|
2934
|
-
const parent =
|
|
3059
|
+
const parent = path7.dirname(dir);
|
|
2935
3060
|
if (parent === dir)
|
|
2936
3061
|
return null;
|
|
2937
3062
|
dir = parent;
|
|
2938
3063
|
}
|
|
2939
3064
|
}
|
|
2940
3065
|
function mainCheckoutRoot(checkoutRoot) {
|
|
2941
|
-
const dotGit =
|
|
3066
|
+
const dotGit = path7.join(checkoutRoot, ".git");
|
|
2942
3067
|
try {
|
|
2943
|
-
if (!
|
|
3068
|
+
if (!fs6.statSync(dotGit).isFile())
|
|
2944
3069
|
return null;
|
|
2945
|
-
const m = /^\s*gitdir:\s*(.+?)\s*$/m.exec(
|
|
3070
|
+
const m = /^\s*gitdir:\s*(.+?)\s*$/m.exec(fs6.readFileSync(dotGit, "utf-8"));
|
|
2946
3071
|
if (!m)
|
|
2947
3072
|
return null;
|
|
2948
|
-
const gitDir =
|
|
3073
|
+
const gitDir = path7.resolve(checkoutRoot, m[1]);
|
|
2949
3074
|
const marker = normalizeCheckoutPath(gitDir).indexOf("/worktrees/");
|
|
2950
3075
|
if (marker < 0)
|
|
2951
3076
|
return null;
|
|
2952
|
-
return
|
|
3077
|
+
return path7.dirname(gitDir.slice(0, marker));
|
|
2953
3078
|
} catch {
|
|
2954
3079
|
return null;
|
|
2955
3080
|
}
|
|
@@ -2957,7 +3082,7 @@ function mainCheckoutRoot(checkoutRoot) {
|
|
|
2957
3082
|
function projectCheckoutRoots(startDir) {
|
|
2958
3083
|
const root = findCheckoutRoot(startDir);
|
|
2959
3084
|
if (!root)
|
|
2960
|
-
return startDir ? [
|
|
3085
|
+
return startDir ? [path7.resolve(startDir)] : [];
|
|
2961
3086
|
const main = mainCheckoutRoot(root);
|
|
2962
3087
|
return main && normalizeCheckoutPath(main) !== normalizeCheckoutPath(root) ? [root, main] : [root];
|
|
2963
3088
|
}
|
|
@@ -2966,14 +3091,14 @@ function projectCheckoutRoots(startDir) {
|
|
|
2966
3091
|
var PROCEDURE_STORE_VERSION = "2";
|
|
2967
3092
|
function stateDir() {
|
|
2968
3093
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
2969
|
-
return
|
|
3094
|
+
return path8.join(home, ".greprag", "state");
|
|
2970
3095
|
}
|
|
2971
3096
|
function procedureStorePath(projectId) {
|
|
2972
|
-
return
|
|
3097
|
+
return path8.join(stateDir(), `procedures-${projectId}.json`);
|
|
2973
3098
|
}
|
|
2974
3099
|
function readProcedureStore(projectId) {
|
|
2975
3100
|
try {
|
|
2976
|
-
const raw =
|
|
3101
|
+
const raw = fs7.readFileSync(procedureStorePath(projectId), "utf-8");
|
|
2977
3102
|
const parsed = JSON.parse(raw);
|
|
2978
3103
|
if (parsed && Array.isArray(parsed.procedures)) {
|
|
2979
3104
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
@@ -3256,11 +3381,11 @@ function activeProcedureAnnounces(store) {
|
|
|
3256
3381
|
}
|
|
3257
3382
|
|
|
3258
3383
|
// src/procedure-runtime.ts
|
|
3259
|
-
var
|
|
3384
|
+
var crypto5 = __toESM(require("crypto"));
|
|
3260
3385
|
|
|
3261
3386
|
// src/procedure-watch.ts
|
|
3262
|
-
var
|
|
3263
|
-
var
|
|
3387
|
+
var path9 = __toESM(require("path"));
|
|
3388
|
+
var fs8 = __toESM(require("fs"));
|
|
3264
3389
|
var TIER1_LEARN_TRIGGERS = LEARN_TRIGGER_VOCABULARY;
|
|
3265
3390
|
function matchLearnTrigger(prompt) {
|
|
3266
3391
|
return matchProcedure(prompt, {
|
|
@@ -3287,24 +3412,24 @@ function openWatch(verb, phase, openedAt, shadowRunId) {
|
|
|
3287
3412
|
}
|
|
3288
3413
|
function stateDir2() {
|
|
3289
3414
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
3290
|
-
return
|
|
3415
|
+
return path9.join(home, ".greprag", "state");
|
|
3291
3416
|
}
|
|
3292
3417
|
function procedureWatchPath(projectId) {
|
|
3293
|
-
return
|
|
3418
|
+
return path9.join(stateDir2(), `procedure-watch-${projectId}.json`);
|
|
3294
3419
|
}
|
|
3295
3420
|
function hasProcedureWatch(projectId) {
|
|
3296
3421
|
try {
|
|
3297
|
-
return
|
|
3422
|
+
return fs8.existsSync(procedureWatchPath(projectId));
|
|
3298
3423
|
} catch {
|
|
3299
3424
|
return false;
|
|
3300
3425
|
}
|
|
3301
3426
|
}
|
|
3302
3427
|
function writeProcedureWatch(projectId, watch) {
|
|
3303
3428
|
const file = procedureWatchPath(projectId);
|
|
3304
|
-
const dir =
|
|
3305
|
-
if (!
|
|
3306
|
-
|
|
3307
|
-
|
|
3429
|
+
const dir = path9.dirname(file);
|
|
3430
|
+
if (!fs8.existsSync(dir))
|
|
3431
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3432
|
+
fs8.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
|
|
3308
3433
|
}
|
|
3309
3434
|
function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
|
|
3310
3435
|
if (hasProcedureWatch(projectId))
|
|
@@ -3317,26 +3442,26 @@ function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
|
|
|
3317
3442
|
var PROCEDURE_SYSTEM_ENABLED = false;
|
|
3318
3443
|
|
|
3319
3444
|
// src/procedure-shadow.ts
|
|
3320
|
-
var
|
|
3321
|
-
var
|
|
3322
|
-
var
|
|
3445
|
+
var crypto4 = __toESM(require("crypto"));
|
|
3446
|
+
var fs9 = __toESM(require("fs"));
|
|
3447
|
+
var path10 = __toESM(require("path"));
|
|
3323
3448
|
function stateDir3() {
|
|
3324
3449
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
3325
|
-
return
|
|
3450
|
+
return path10.join(home, ".greprag", "state");
|
|
3326
3451
|
}
|
|
3327
3452
|
function procedureShadowPath(projectId) {
|
|
3328
|
-
return
|
|
3453
|
+
return path10.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
|
|
3329
3454
|
}
|
|
3330
3455
|
function appendShadowEvent(projectId, event) {
|
|
3331
3456
|
try {
|
|
3332
3457
|
const file = procedureShadowPath(projectId);
|
|
3333
|
-
|
|
3334
|
-
|
|
3458
|
+
fs9.mkdirSync(path10.dirname(file), { recursive: true });
|
|
3459
|
+
fs9.appendFileSync(file, JSON.stringify(event) + "\n");
|
|
3335
3460
|
} catch {
|
|
3336
3461
|
}
|
|
3337
3462
|
}
|
|
3338
3463
|
function createProcedureShadowRunId() {
|
|
3339
|
-
return
|
|
3464
|
+
return crypto4.randomUUID();
|
|
3340
3465
|
}
|
|
3341
3466
|
function procedureFingerprint(proc) {
|
|
3342
3467
|
const payload = JSON.stringify({
|
|
@@ -3348,7 +3473,7 @@ function procedureFingerprint(proc) {
|
|
|
3348
3473
|
endpoint: proc.endpoint,
|
|
3349
3474
|
definitionVersion: proc.definitionVersion ?? 1
|
|
3350
3475
|
});
|
|
3351
|
-
return
|
|
3476
|
+
return crypto4.createHash("sha256").update(payload).digest("hex");
|
|
3352
3477
|
}
|
|
3353
3478
|
function recordProcedureShadowTrigger(params) {
|
|
3354
3479
|
appendShadowEvent(params.projectId, {
|
|
@@ -3400,7 +3525,7 @@ function extractLatestOpenCodeProcedurePrompt(messages) {
|
|
|
3400
3525
|
}
|
|
3401
3526
|
if (!latest)
|
|
3402
3527
|
return null;
|
|
3403
|
-
const digest =
|
|
3528
|
+
const digest = crypto5.createHash("sha256").update(latest.prompt).digest("hex");
|
|
3404
3529
|
return { ...latest, key: `${userCount}:${digest}` };
|
|
3405
3530
|
}
|
|
3406
3531
|
function procedureRuntimeState(configuredState, proc) {
|
|
@@ -4052,15 +4177,805 @@ function recodeMessagesToPng(messages, opts) {
|
|
|
4052
4177
|
return stats;
|
|
4053
4178
|
}
|
|
4054
4179
|
|
|
4180
|
+
// src/claude-lifecycle-cutover.ts
|
|
4181
|
+
var fs11 = __toESM(require("fs"));
|
|
4182
|
+
var os4 = __toESM(require("os"));
|
|
4183
|
+
var path12 = __toESM(require("path"));
|
|
4184
|
+
|
|
4185
|
+
// src/native-skill-mirror.ts
|
|
4186
|
+
var fs10 = __toESM(require("node:fs"));
|
|
4187
|
+
var os3 = __toESM(require("node:os"));
|
|
4188
|
+
var path11 = __toESM(require("node:path"));
|
|
4189
|
+
var SKILL_NAME_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
4190
|
+
var NATIVE_SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4191
|
+
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;
|
|
4192
|
+
var NATIVE_DESCRIPTION_MAX_CHARS = 2500;
|
|
4193
|
+
var STATE_FILE = ".greprag-mirror.json";
|
|
4194
|
+
function nativeSkillMirrorEnabled(platform) {
|
|
4195
|
+
return platform !== "claude-code";
|
|
4196
|
+
}
|
|
4197
|
+
function nativeSkillRoot(platform, homeDir = os3.homedir()) {
|
|
4198
|
+
if (platform === "claude-code")
|
|
4199
|
+
return path11.join(homeDir, ".claude", "skills");
|
|
4200
|
+
if (platform === "codex")
|
|
4201
|
+
return path11.join(homeDir, ".codex", "skills");
|
|
4202
|
+
return path11.join(homeDir, ".config", "opencode", "skills");
|
|
4203
|
+
}
|
|
4204
|
+
function nativeProjectSkillRoot(platform, projectDir) {
|
|
4205
|
+
if (platform === "claude-code")
|
|
4206
|
+
return path11.join(projectDir, ".claude", "skills");
|
|
4207
|
+
if (platform === "codex")
|
|
4208
|
+
return path11.join(projectDir, ".agents", "skills");
|
|
4209
|
+
return path11.join(projectDir, ".opencode", "skills");
|
|
4210
|
+
}
|
|
4211
|
+
function writerOwnedProjectSkillRoot(platform, projectDir, homeDir = os3.homedir()) {
|
|
4212
|
+
if (path11.resolve(projectDir).toLowerCase() === path11.resolve(homeDir).toLowerCase())
|
|
4213
|
+
return null;
|
|
4214
|
+
return nativeProjectSkillRoot(platform, projectDir);
|
|
4215
|
+
}
|
|
4216
|
+
function skillScope(skill) {
|
|
4217
|
+
return skill.scope === "project" ? "project" : "global";
|
|
4218
|
+
}
|
|
4219
|
+
function canonicalSkillId(skill) {
|
|
4220
|
+
if (skill.skillId)
|
|
4221
|
+
return skill.skillId;
|
|
4222
|
+
const scope = skillScope(skill);
|
|
4223
|
+
if (scope === "global")
|
|
4224
|
+
return `global:${skill.skillName}`;
|
|
4225
|
+
if (!skill.projectId || !PROJECT_ID_RE.test(skill.projectId)) {
|
|
4226
|
+
throw new Error(`projectId required for project skill "${skill.skillName}"`);
|
|
4227
|
+
}
|
|
4228
|
+
return `project:${skill.projectId}:${skill.skillName}`;
|
|
4229
|
+
}
|
|
4230
|
+
function replacesGlobal(project, global) {
|
|
4231
|
+
const replaces = project.replaces || [];
|
|
4232
|
+
const globalId = canonicalSkillId(global);
|
|
4233
|
+
return replaces.some((value) => value === globalId || value === `global:${global.skillName}` || value === global.skillName || value === "global" && project.skillName === global.skillName);
|
|
4234
|
+
}
|
|
4235
|
+
function assertNoNativeSkillCollision(skills) {
|
|
4236
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4237
|
+
for (const skill of skills) {
|
|
4238
|
+
const key = skill.skillName.toLowerCase();
|
|
4239
|
+
byName.set(key, [...byName.get(key) || [], skill]);
|
|
4240
|
+
}
|
|
4241
|
+
for (const group of byName.values()) {
|
|
4242
|
+
const global = group.find((skill) => skillScope(skill) === "global");
|
|
4243
|
+
if (!global)
|
|
4244
|
+
continue;
|
|
4245
|
+
for (const project of group.filter((skill) => skillScope(skill) === "project")) {
|
|
4246
|
+
if (!replacesGlobal(project, global)) {
|
|
4247
|
+
throw new Error(
|
|
4248
|
+
`project skill "${project.skillName}" collides with global "${canonicalSkillId(global)}"; add replaces metadata`
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
function rootForSkill(platform, skill, homeDir, projectDir) {
|
|
4255
|
+
if (skillScope(skill) === "global")
|
|
4256
|
+
return nativeSkillRoot(platform, homeDir);
|
|
4257
|
+
if (!projectDir)
|
|
4258
|
+
return null;
|
|
4259
|
+
return writerOwnedProjectSkillRoot(platform, projectDir, homeDir);
|
|
4260
|
+
}
|
|
4261
|
+
function emptyState() {
|
|
4262
|
+
return { version: 1, skills: {} };
|
|
4263
|
+
}
|
|
4264
|
+
function readState(root) {
|
|
4265
|
+
try {
|
|
4266
|
+
const parsed = JSON.parse(fs10.readFileSync(path11.join(root, STATE_FILE), "utf8"));
|
|
4267
|
+
if (parsed?.version === 1 && parsed.skills && typeof parsed.skills === "object")
|
|
4268
|
+
return parsed;
|
|
4269
|
+
} catch {
|
|
4270
|
+
}
|
|
4271
|
+
return emptyState();
|
|
4272
|
+
}
|
|
4273
|
+
function writeState(root, state) {
|
|
4274
|
+
fs10.mkdirSync(root, { recursive: true });
|
|
4275
|
+
fs10.writeFileSync(path11.join(root, STATE_FILE), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
4276
|
+
}
|
|
4277
|
+
function targetPath(root, skillName) {
|
|
4278
|
+
const target = path11.resolve(root, skillName);
|
|
4279
|
+
const prefix = path11.resolve(root) + path11.sep;
|
|
4280
|
+
if (!target.startsWith(prefix))
|
|
4281
|
+
throw new Error("skill target escapes native root");
|
|
4282
|
+
return target;
|
|
4283
|
+
}
|
|
4284
|
+
function filePath(target, relative) {
|
|
4285
|
+
const resolved = path11.resolve(target, ...relative.split("/"));
|
|
4286
|
+
const prefix = path11.resolve(target) + path11.sep;
|
|
4287
|
+
if (!resolved.startsWith(prefix))
|
|
4288
|
+
throw new Error("skill file escapes target directory");
|
|
4289
|
+
return resolved;
|
|
4290
|
+
}
|
|
4291
|
+
function readManagedFiles(target, managed) {
|
|
4292
|
+
try {
|
|
4293
|
+
return managed.files.map((relative) => ({
|
|
4294
|
+
path: relative,
|
|
4295
|
+
content: fs10.readFileSync(filePath(target, relative), "utf8")
|
|
4296
|
+
}));
|
|
4297
|
+
} catch {
|
|
4298
|
+
return null;
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
function managedTargetExists(target) {
|
|
4302
|
+
try {
|
|
4303
|
+
fs10.statSync(target);
|
|
4304
|
+
return true;
|
|
4305
|
+
} catch {
|
|
4306
|
+
return false;
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function unquote2(value) {
|
|
4310
|
+
const v = value.trim();
|
|
4311
|
+
if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")))
|
|
4312
|
+
return v.slice(1, -1);
|
|
4313
|
+
return v;
|
|
4314
|
+
}
|
|
4315
|
+
function parseDescriptionFromSkillMd(skillMd) {
|
|
4316
|
+
const text = (skillMd || "").replace(/\r\n?/g, "\n");
|
|
4317
|
+
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(text);
|
|
4318
|
+
const body = match ? text.slice(match[0].length) : text;
|
|
4319
|
+
if (match) {
|
|
4320
|
+
const lines = match[1].split("\n");
|
|
4321
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4322
|
+
const key = /^(\s*)description:\s*(.*)$/.exec(lines[i]);
|
|
4323
|
+
if (!key)
|
|
4324
|
+
continue;
|
|
4325
|
+
const scalar = key[2].trim();
|
|
4326
|
+
if (!/^[>|][+-]?$/.test(scalar))
|
|
4327
|
+
return unquote2(scalar).trim();
|
|
4328
|
+
const keyIndent = key[1].length;
|
|
4329
|
+
const block = [];
|
|
4330
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
4331
|
+
const line = lines[j];
|
|
4332
|
+
const indent = /^\s*/.exec(line)?.[0].length ?? 0;
|
|
4333
|
+
if (line.trim() && indent <= keyIndent)
|
|
4334
|
+
break;
|
|
4335
|
+
block.push(line);
|
|
4336
|
+
}
|
|
4337
|
+
const nonEmpty = block.filter((line) => line.trim());
|
|
4338
|
+
const blockIndent = nonEmpty.length ? Math.min(...nonEmpty.map((line) => /^\s*/.exec(line)?.[0].length ?? 0)) : 0;
|
|
4339
|
+
const dedented = block.map((line) => line.trim() ? line.slice(blockIndent).trimEnd() : "");
|
|
4340
|
+
const description = scalar.startsWith(">") ? dedented.join("\n").split(/\n{2,}/).map((p) => p.replace(/\n/g, " ").trim()).join("\n\n") : dedented.join("\n").trim();
|
|
4341
|
+
return description.trim();
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
const lead = body.split("\n").map((line) => line.trim()).find((line) => line && !line.startsWith("#") && !line.startsWith("```") && !line.startsWith("<!--")) || "";
|
|
4345
|
+
return lead.trim();
|
|
4346
|
+
}
|
|
4347
|
+
function adapterDescription(skill) {
|
|
4348
|
+
const direct = (skill.triggerDescription || skill.description || "").replace(/\r\n?/g, "\n").trim();
|
|
4349
|
+
if (direct)
|
|
4350
|
+
return boundedNativeDescription(skill.skillName, direct);
|
|
4351
|
+
const skillMd = skill.files.find((file) => file.path === "SKILL.md")?.content || "";
|
|
4352
|
+
return boundedNativeDescription(skill.skillName, parseDescriptionFromSkillMd(skillMd));
|
|
4353
|
+
}
|
|
4354
|
+
function collapseWhitespace(value) {
|
|
4355
|
+
return value.replace(/\s+/g, " ").trim();
|
|
4356
|
+
}
|
|
4357
|
+
function trimWithMarker(value, max) {
|
|
4358
|
+
const text = collapseWhitespace(value);
|
|
4359
|
+
if (text.length <= max)
|
|
4360
|
+
return text;
|
|
4361
|
+
if (max <= 15)
|
|
4362
|
+
return text.slice(0, Math.max(0, max)).trimEnd();
|
|
4363
|
+
const head = text.slice(0, max - 3);
|
|
4364
|
+
const boundary = Math.max(head.lastIndexOf(". "), head.lastIndexOf("; "), head.lastIndexOf(", "), head.lastIndexOf(" "));
|
|
4365
|
+
return `${head.slice(0, boundary > 80 ? boundary : head.length).trimEnd()}...`;
|
|
4366
|
+
}
|
|
4367
|
+
function sectionFrom(value, label) {
|
|
4368
|
+
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4369
|
+
const match = new RegExp(`${escaped}:?\\s*([\\s\\S]*)`, "i").exec(value);
|
|
4370
|
+
if (!match)
|
|
4371
|
+
return "";
|
|
4372
|
+
return match[1].split(/\n\s*\n(?:NOT for|Requires|Built on|Does NOT|REQUIRES)\b/i)[0].trim();
|
|
4373
|
+
}
|
|
4374
|
+
function beforeSection(value, label) {
|
|
4375
|
+
const index = value.toLowerCase().indexOf(label.toLowerCase());
|
|
4376
|
+
return index >= 0 ? value.slice(0, index).trim() : value.trim();
|
|
4377
|
+
}
|
|
4378
|
+
function boundedNativeDescription(skillName, raw) {
|
|
4379
|
+
const normalized = raw.replace(/\r\n?/g, "\n").trim();
|
|
4380
|
+
if (!normalized || normalized.length <= NATIVE_DESCRIPTION_MAX_CHARS)
|
|
4381
|
+
return normalized;
|
|
4382
|
+
const prefix = `Condensed native trigger for ${skillName}; full description: greprag load ${skillName}.`;
|
|
4383
|
+
const useWhen = sectionFrom(normalized, "Use when");
|
|
4384
|
+
const notFor = sectionFrom(normalized, "NOT for");
|
|
4385
|
+
const purposeSource = beforeSection(normalized, "Use when");
|
|
4386
|
+
const fixedParts = [prefix];
|
|
4387
|
+
const tailParts = [];
|
|
4388
|
+
if (useWhen)
|
|
4389
|
+
tailParts.push(["Use when", useWhen]);
|
|
4390
|
+
if (notFor)
|
|
4391
|
+
tailParts.push(["NOT for", notFor]);
|
|
4392
|
+
const fixedLength = fixedParts.join("\n\n").length + tailParts.reduce((sum, [label, body]) => sum + `
|
|
4393
|
+
|
|
4394
|
+
${label}: ${collapseWhitespace(body)}`.length, 0);
|
|
4395
|
+
const purposeBudget = Math.max(160, NATIVE_DESCRIPTION_MAX_CHARS - fixedLength - 12);
|
|
4396
|
+
const parts = [
|
|
4397
|
+
...fixedParts,
|
|
4398
|
+
`Purpose: ${trimWithMarker(purposeSource, purposeBudget)}`,
|
|
4399
|
+
...tailParts.map(([label, body]) => `${label}: ${collapseWhitespace(body)}`)
|
|
4400
|
+
];
|
|
4401
|
+
let rendered = parts.filter(Boolean).join("\n\n");
|
|
4402
|
+
if (rendered.length <= NATIVE_DESCRIPTION_MAX_CHARS)
|
|
4403
|
+
return rendered;
|
|
4404
|
+
const prefixAndUse = [prefix];
|
|
4405
|
+
const useBudget = NATIVE_DESCRIPTION_MAX_CHARS - prefix.length - 14;
|
|
4406
|
+
if (useWhen)
|
|
4407
|
+
prefixAndUse.push(`Use when: ${trimWithMarker(useWhen, useBudget)}`);
|
|
4408
|
+
else
|
|
4409
|
+
prefixAndUse.push(`Purpose: ${trimWithMarker(normalized, useBudget)}`);
|
|
4410
|
+
rendered = prefixAndUse.join("\n\n");
|
|
4411
|
+
return rendered.length <= NATIVE_DESCRIPTION_MAX_CHARS ? rendered : trimWithMarker(rendered, NATIVE_DESCRIPTION_MAX_CHARS);
|
|
4412
|
+
}
|
|
4413
|
+
function yamlLiteral(value) {
|
|
4414
|
+
return value.replace(/\r\n?/g, "\n").split("\n").map((line) => ` ${line}`).join("\n");
|
|
4415
|
+
}
|
|
4416
|
+
function sourceBindingState(skill) {
|
|
4417
|
+
const skillMd = skill.files.find((file) => file.path === "SKILL.md")?.content || "";
|
|
4418
|
+
return /<!--\s*greprag-source-bindings\b/.test(skillMd) ? "bound" : "none";
|
|
4419
|
+
}
|
|
4420
|
+
function canonicalContentHash(skill) {
|
|
4421
|
+
return skill.contentHash || computeSkillFileHash(skill.files);
|
|
4422
|
+
}
|
|
4423
|
+
function canonicalLookupArgs(skill) {
|
|
4424
|
+
return `--skill-id ${canonicalSkillId(skill)}`;
|
|
4425
|
+
}
|
|
4426
|
+
function canonicalWriteArgs(skill) {
|
|
4427
|
+
if (skill.scope !== "project" || !skill.projectId)
|
|
4428
|
+
return "";
|
|
4429
|
+
const replaces = (skill.replaces || []).map((id) => ` --replaces ${id}`).join("");
|
|
4430
|
+
return `--scope project --project-id ${skill.projectId}${replaces}`;
|
|
4431
|
+
}
|
|
4432
|
+
function canonicalEditArgs(skill) {
|
|
4433
|
+
return [canonicalLookupArgs(skill), canonicalWriteArgs(skill)].filter(Boolean).join(" ");
|
|
4434
|
+
}
|
|
4435
|
+
function nativeCreationLocations(platform) {
|
|
4436
|
+
if (platform === "codex") {
|
|
4437
|
+
return "`~/.codex/skills/<new-name>/SKILL.md` (global) or `<repo>/.agents/skills/<new-name>/SKILL.md` (project)";
|
|
4438
|
+
}
|
|
4439
|
+
if (platform === "opencode") {
|
|
4440
|
+
return "`~/.config/opencode/skills/<new-name>/SKILL.md` (global) or `<repo>/.opencode/skills/<new-name>/SKILL.md` (project)";
|
|
4441
|
+
}
|
|
4442
|
+
return "`~/.claude/skills/<new-name>/SKILL.md` (global) or `<repo>/.claude/skills/<new-name>/SKILL.md` (project)";
|
|
4443
|
+
}
|
|
4444
|
+
function renderChangeSkillLine(params) {
|
|
4445
|
+
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
4446
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash>`;
|
|
4447
|
+
if (params.platform === "codex") {
|
|
4448
|
+
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}\`.`;
|
|
4449
|
+
}
|
|
4450
|
+
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}\`.`;
|
|
4451
|
+
}
|
|
4452
|
+
function renderEditingSection(platform, params) {
|
|
4453
|
+
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
4454
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash>`;
|
|
4455
|
+
if (platform === "codex") {
|
|
4456
|
+
return `## Editing
|
|
4457
|
+
|
|
4458
|
+
Codex agents must use the non-interactive mirror path:
|
|
4459
|
+
|
|
4460
|
+
\`\`\`bash
|
|
4461
|
+
${exportCommand}
|
|
4462
|
+
# patch the exported Markdown files with normal file edits
|
|
4463
|
+
${applyCommand}
|
|
4464
|
+
\`\`\`
|
|
4465
|
+
|
|
4466
|
+
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.
|
|
4467
|
+
`;
|
|
4468
|
+
}
|
|
4469
|
+
return `## Editing
|
|
4470
|
+
|
|
4471
|
+
To edit this skill, run the guarded editor flow:
|
|
4472
|
+
|
|
4473
|
+
\`\`\`bash
|
|
4474
|
+
greprag skill edit ${params.skillName} ${params.editArgs}
|
|
4475
|
+
\`\`\`
|
|
4476
|
+
|
|
4477
|
+
Manual path:
|
|
4478
|
+
|
|
4479
|
+
\`\`\`bash
|
|
4480
|
+
${exportCommand}
|
|
4481
|
+
${applyCommand}
|
|
4482
|
+
\`\`\`
|
|
4483
|
+
`;
|
|
4484
|
+
}
|
|
4485
|
+
function renderAdapterBody(platform, skill) {
|
|
4486
|
+
const skillName = skill.skillName;
|
|
4487
|
+
const skillId = canonicalSkillId(skill);
|
|
4488
|
+
const identityArg = canonicalLookupArgs(skill);
|
|
4489
|
+
const editArgs = canonicalEditArgs(skill);
|
|
4490
|
+
const creationLocations = nativeCreationLocations(platform);
|
|
4491
|
+
const syncCommand = nativeAdapterInstallCommand(platform, [skillName]);
|
|
4492
|
+
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.`;
|
|
4493
|
+
const companions = skill.files.map((file) => file.path).filter((relative) => relative !== "SKILL.md").sort();
|
|
4494
|
+
const harnessArg = `--harness ${platform}`;
|
|
4495
|
+
const loadCommand = `greprag load ${skillName} ${identityArg} ${harnessArg}`;
|
|
4496
|
+
const changeSkillLine = renderChangeSkillLine({
|
|
4497
|
+
platform,
|
|
4498
|
+
skillName,
|
|
4499
|
+
editArgs,
|
|
4500
|
+
identityArg,
|
|
4501
|
+
syncCommand,
|
|
4502
|
+
loadCommand
|
|
4503
|
+
});
|
|
4504
|
+
const editingSection = renderEditingSection(platform, { skillName, editArgs, identityArg });
|
|
4505
|
+
const companionLines = companions.length ? companions.map((relative) => `- \`greprag load ${skillName} ${relative} ${identityArg} ${harnessArg}\``).join("\n") : "- No companion docs are mirrored into this native adapter.";
|
|
4506
|
+
return `# ${skillName}
|
|
4507
|
+
|
|
4508
|
+
## GENERATED ADAPTER \u2014 NEVER EDIT
|
|
4509
|
+
|
|
4510
|
+
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.
|
|
4511
|
+
|
|
4512
|
+
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.
|
|
4513
|
+
|
|
4514
|
+
Required action chains:
|
|
4515
|
+
|
|
4516
|
+
- **Use this skill:** run \`${loadCommand}\` before acting.
|
|
4517
|
+
${changeSkillLine}
|
|
4518
|
+
${creationLine}
|
|
4519
|
+
|
|
4520
|
+
Authority: ${skillId}
|
|
4521
|
+
Source binding: ${sourceBindingState(skill)}
|
|
4522
|
+
Canonical content hash: ${canonicalContentHash(skill)}
|
|
4523
|
+
${typeof skill.revision === "number" ? `Mirror revision: ${skill.revision}` : "Mirror revision: unknown"}
|
|
4524
|
+
|
|
4525
|
+
${editingSection}
|
|
4526
|
+
|
|
4527
|
+
Snapshot rollback:
|
|
4528
|
+
|
|
4529
|
+
\`\`\`bash
|
|
4530
|
+
greprag skill mirror snapshot list ${skillName} ${identityArg}
|
|
4531
|
+
greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash>
|
|
4532
|
+
\`\`\`
|
|
4533
|
+
|
|
4534
|
+
## Mandatory Invocation
|
|
4535
|
+
|
|
4536
|
+
When this skill activates, run:
|
|
4537
|
+
|
|
4538
|
+
\`\`\`bash
|
|
4539
|
+
greprag load ${skillName} ${identityArg} ${harnessArg}
|
|
4540
|
+
\`\`\`
|
|
4541
|
+
|
|
4542
|
+
Read and follow that canonical payload before responding or taking action.
|
|
4543
|
+
|
|
4544
|
+
## Companion Docs
|
|
4545
|
+
|
|
4546
|
+
${companionLines}
|
|
4547
|
+
`;
|
|
4548
|
+
}
|
|
4549
|
+
function renderNativeSkillAdapter(platform, skill, stamps) {
|
|
4550
|
+
if (!NATIVE_SKILL_NAME_RE.test(skill.skillName))
|
|
4551
|
+
return null;
|
|
4552
|
+
if (!skill.files.some((file) => file.path === "SKILL.md") || !skill.files.every(isSafeMirroredSkillFile))
|
|
4553
|
+
return null;
|
|
4554
|
+
const description = adapterDescription(skill);
|
|
4555
|
+
if (!description || description.length > NATIVE_DESCRIPTION_MAX_CHARS)
|
|
4556
|
+
return null;
|
|
4557
|
+
const skillId = canonicalSkillId(skill);
|
|
4558
|
+
const scope = skillScope(skill);
|
|
4559
|
+
const extraFrontmatter = [
|
|
4560
|
+
`greprag_skill_id: ${skillId}`,
|
|
4561
|
+
`greprag_scope: ${scope}`,
|
|
4562
|
+
`greprag_source_binding: ${sourceBindingState(skill)}`,
|
|
4563
|
+
`greprag_content_hash: ${canonicalContentHash(skill)}`,
|
|
4564
|
+
...typeof skill.revision === "number" ? [`greprag_revision: ${skill.revision}`] : [],
|
|
4565
|
+
...scope === "project" && skill.projectId ? [`greprag_project_id: ${skill.projectId}`] : [],
|
|
4566
|
+
...skill.replaces?.length ? [`replaces: ${skill.replaces.join(", ")}`] : [],
|
|
4567
|
+
...stamps
|
|
4568
|
+
].join("\n");
|
|
4569
|
+
return `---
|
|
4570
|
+
name: ${skill.skillName}
|
|
4571
|
+
description: |-
|
|
4572
|
+
${yamlLiteral(description)}
|
|
4573
|
+
${extraFrontmatter}
|
|
4574
|
+
---
|
|
4575
|
+
|
|
4576
|
+
${renderAdapterBody(platform, skill)}`;
|
|
4577
|
+
}
|
|
4578
|
+
var PROBE_PROJECT_ID = "00000000-0000-4000-8000-000000000000";
|
|
4579
|
+
var ADAPTER_FORMAT_PROBES = [
|
|
4580
|
+
{
|
|
4581
|
+
skillName: "format-probe",
|
|
4582
|
+
skillId: "global:format-probe",
|
|
4583
|
+
contentHash: "0".repeat(64),
|
|
4584
|
+
revision: 1,
|
|
4585
|
+
triggerDescription: "Format probe.",
|
|
4586
|
+
files: [{ path: "SKILL.md", content: "# Format probe\n" }, { path: "docs/probe.md", content: "" }]
|
|
4587
|
+
},
|
|
4588
|
+
{
|
|
4589
|
+
skillName: "format-probe",
|
|
4590
|
+
skillId: `project:${PROBE_PROJECT_ID}:format-probe`,
|
|
4591
|
+
scope: "project",
|
|
4592
|
+
projectId: PROBE_PROJECT_ID,
|
|
4593
|
+
replaces: ["global:format-probe"],
|
|
4594
|
+
triggerDescription: "Format probe.",
|
|
4595
|
+
files: [{ path: "SKILL.md", content: "<!-- greprag-source-bindings -->\n" }]
|
|
4596
|
+
}
|
|
4597
|
+
];
|
|
4598
|
+
var adapterFormats = /* @__PURE__ */ new Map();
|
|
4599
|
+
function nativeAdapterFormat(platform) {
|
|
4600
|
+
let format = adapterFormats.get(platform);
|
|
4601
|
+
if (!format) {
|
|
4602
|
+
format = computeSkillFileHash(ADAPTER_FORMAT_PROBES.map((probe, index) => ({
|
|
4603
|
+
path: String(index),
|
|
4604
|
+
content: renderNativeSkillAdapter(platform, probe, []) || ""
|
|
4605
|
+
}))).slice(0, 12);
|
|
4606
|
+
adapterFormats.set(platform, format);
|
|
4607
|
+
}
|
|
4608
|
+
return format;
|
|
4609
|
+
}
|
|
4610
|
+
function buildNativeSkillAdapter(platform, skill, options = {}) {
|
|
4611
|
+
if (!nativeSkillMirrorEnabled(platform) && !(platform === "claude-code" && options.allowClaudeCode))
|
|
4612
|
+
return null;
|
|
4613
|
+
const content = renderNativeSkillAdapter(platform, skill, [
|
|
4614
|
+
`greprag_adapter_format: ${nativeAdapterFormat(platform)}`
|
|
4615
|
+
]);
|
|
4616
|
+
return content === null ? null : [{ path: "SKILL.md", content }];
|
|
4617
|
+
}
|
|
4618
|
+
function removeTrackedFiles(target, relativeFiles) {
|
|
4619
|
+
const directories = /* @__PURE__ */ new Set();
|
|
4620
|
+
for (const relative of relativeFiles) {
|
|
4621
|
+
const file = filePath(target, relative);
|
|
4622
|
+
try {
|
|
4623
|
+
fs10.unlinkSync(file);
|
|
4624
|
+
} catch {
|
|
4625
|
+
}
|
|
4626
|
+
let dir = path11.dirname(file);
|
|
4627
|
+
while (dir.startsWith(path11.resolve(target) + path11.sep)) {
|
|
4628
|
+
directories.add(dir);
|
|
4629
|
+
dir = path11.dirname(dir);
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
for (const dir of [...directories].sort((a, b) => b.length - a.length)) {
|
|
4633
|
+
try {
|
|
4634
|
+
fs10.rmdirSync(dir);
|
|
4635
|
+
} catch {
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
try {
|
|
4639
|
+
fs10.rmdirSync(target);
|
|
4640
|
+
} catch {
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
function applyNativeSkillMirror(params) {
|
|
4644
|
+
if (!nativeSkillMirrorEnabled(params.platform)) {
|
|
4645
|
+
return {
|
|
4646
|
+
installed: 0,
|
|
4647
|
+
updated: 0,
|
|
4648
|
+
current: 0,
|
|
4649
|
+
removed: 0,
|
|
4650
|
+
skipped: params.skills.length,
|
|
4651
|
+
blocked: 0
|
|
4652
|
+
};
|
|
4653
|
+
}
|
|
4654
|
+
assertNoNativeSkillCollision(params.skills);
|
|
4655
|
+
const homeDir = params.homeDir || os3.homedir();
|
|
4656
|
+
const projectDir = params.projectDir ? path11.resolve(params.projectDir) : void 0;
|
|
4657
|
+
const result = {
|
|
4658
|
+
installed: 0,
|
|
4659
|
+
updated: 0,
|
|
4660
|
+
current: 0,
|
|
4661
|
+
removed: 0,
|
|
4662
|
+
skipped: 0,
|
|
4663
|
+
blocked: 0
|
|
4664
|
+
};
|
|
4665
|
+
const roots = /* @__PURE__ */ new Map();
|
|
4666
|
+
const rootState = (root) => {
|
|
4667
|
+
let state = roots.get(root);
|
|
4668
|
+
if (!state) {
|
|
4669
|
+
state = { state: readState(root), present: /* @__PURE__ */ new Set(), touched: false };
|
|
4670
|
+
roots.set(root, state);
|
|
4671
|
+
}
|
|
4672
|
+
return state;
|
|
4673
|
+
};
|
|
4674
|
+
for (const skill of params.skills) {
|
|
4675
|
+
if (!SKILL_NAME_RE.test(skill.skillName)) {
|
|
4676
|
+
result.skipped++;
|
|
4677
|
+
continue;
|
|
4678
|
+
}
|
|
4679
|
+
const root = rootForSkill(params.platform, skill, homeDir, projectDir);
|
|
4680
|
+
if (!root) {
|
|
4681
|
+
result.skipped++;
|
|
4682
|
+
continue;
|
|
4683
|
+
}
|
|
4684
|
+
const rootEntry = rootState(root);
|
|
4685
|
+
if (params.platform === "codex" && skillScope(skill) === "global" && skill.skillName === "greprag") {
|
|
4686
|
+
if (rootEntry.state.skills[skill.skillName]) {
|
|
4687
|
+
delete rootEntry.state.skills[skill.skillName];
|
|
4688
|
+
rootEntry.touched = true;
|
|
4689
|
+
}
|
|
4690
|
+
continue;
|
|
4691
|
+
}
|
|
4692
|
+
rootEntry.present.add(skill.skillName);
|
|
4693
|
+
const adapterFiles = buildNativeSkillAdapter(params.platform, skill);
|
|
4694
|
+
if (!adapterFiles) {
|
|
4695
|
+
result.skipped++;
|
|
4696
|
+
continue;
|
|
4697
|
+
}
|
|
4698
|
+
const target = targetPath(root, skill.skillName);
|
|
4699
|
+
const managed = rootEntry.state.skills[skill.skillName];
|
|
4700
|
+
if (isSkillFolderOwnedElsewhere(target)) {
|
|
4701
|
+
if (managed)
|
|
4702
|
+
result.blocked++;
|
|
4703
|
+
else
|
|
4704
|
+
result.skipped++;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
if (fs10.existsSync(target) && !managed) {
|
|
4708
|
+
result.skipped++;
|
|
4709
|
+
continue;
|
|
4710
|
+
}
|
|
4711
|
+
const nextHash = computeSkillFileHash(adapterFiles);
|
|
4712
|
+
if (managed) {
|
|
4713
|
+
const targetExists = managedTargetExists(target);
|
|
4714
|
+
const currentFiles = targetExists ? readManagedFiles(target, managed) : null;
|
|
4715
|
+
if (targetExists && (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash)) {
|
|
4716
|
+
result.blocked++;
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
if (targetExists && managed.hash === nextHash) {
|
|
4720
|
+
result.current++;
|
|
4721
|
+
continue;
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
fs10.mkdirSync(target, { recursive: true });
|
|
4725
|
+
for (const file of adapterFiles) {
|
|
4726
|
+
const destination = filePath(target, file.path);
|
|
4727
|
+
fs10.mkdirSync(path11.dirname(destination), { recursive: true });
|
|
4728
|
+
fs10.writeFileSync(destination, file.content, "utf8");
|
|
4729
|
+
}
|
|
4730
|
+
if (managed) {
|
|
4731
|
+
const nextPaths = new Set(adapterFiles.map((file) => file.path));
|
|
4732
|
+
removeTrackedFiles(target, managed.files.filter((relative) => !nextPaths.has(relative)));
|
|
4733
|
+
result.updated++;
|
|
4734
|
+
} else {
|
|
4735
|
+
result.installed++;
|
|
4736
|
+
}
|
|
4737
|
+
rootEntry.state.skills[skill.skillName] = {
|
|
4738
|
+
hash: nextHash,
|
|
4739
|
+
files: adapterFiles.map((file) => file.path).sort()
|
|
4740
|
+
};
|
|
4741
|
+
rootEntry.touched = true;
|
|
4742
|
+
}
|
|
4743
|
+
if (params.prune) {
|
|
4744
|
+
const globalRoot = nativeSkillRoot(params.platform, homeDir);
|
|
4745
|
+
if (params.pruneGlobalRoot !== false)
|
|
4746
|
+
rootState(globalRoot);
|
|
4747
|
+
const projectRoot = projectDir ? writerOwnedProjectSkillRoot(params.platform, projectDir, homeDir) : null;
|
|
4748
|
+
if (projectRoot)
|
|
4749
|
+
rootState(projectRoot);
|
|
4750
|
+
for (const [root, entry] of roots) {
|
|
4751
|
+
if (params.pruneGlobalRoot === false && root === globalRoot)
|
|
4752
|
+
continue;
|
|
4753
|
+
for (const [skillName, managed] of Object.entries(entry.state.skills)) {
|
|
4754
|
+
if (entry.present.has(skillName))
|
|
4755
|
+
continue;
|
|
4756
|
+
const target = targetPath(root, skillName);
|
|
4757
|
+
const currentFiles = isSkillFolderOwnedElsewhere(target) ? null : readManagedFiles(target, managed);
|
|
4758
|
+
if (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash) {
|
|
4759
|
+
result.blocked++;
|
|
4760
|
+
continue;
|
|
4761
|
+
}
|
|
4762
|
+
removeTrackedFiles(target, managed.files);
|
|
4763
|
+
delete entry.state.skills[skillName];
|
|
4764
|
+
entry.touched = true;
|
|
4765
|
+
result.removed++;
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
for (const [root, entry] of roots) {
|
|
4770
|
+
if (entry.touched || fs10.existsSync(path11.join(root, STATE_FILE)))
|
|
4771
|
+
writeState(root, entry.state);
|
|
4772
|
+
}
|
|
4773
|
+
return result;
|
|
4774
|
+
}
|
|
4775
|
+
async function fetchNativeMirroredSkill(apiUrl, apiKey, row) {
|
|
4776
|
+
const skillQs = new URLSearchParams();
|
|
4777
|
+
if (row.skillId)
|
|
4778
|
+
skillQs.set("skillId", row.skillId);
|
|
4779
|
+
else if (row.scope) {
|
|
4780
|
+
skillQs.set("scope", row.scope);
|
|
4781
|
+
if (row.projectId)
|
|
4782
|
+
skillQs.set("projectId", row.projectId);
|
|
4783
|
+
}
|
|
4784
|
+
const response = await fetch(
|
|
4785
|
+
`${apiUrl}/v1/skillmirror/${encodeURIComponent(row.skillName)}${skillQs.size ? `?${skillQs.toString()}` : ""}`,
|
|
4786
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
4787
|
+
);
|
|
4788
|
+
if (!response.ok)
|
|
4789
|
+
return null;
|
|
4790
|
+
const body = await response.json();
|
|
4791
|
+
return Array.isArray(body.files) ? {
|
|
4792
|
+
skillName: row.skillName,
|
|
4793
|
+
skillId: typeof row.skillId === "string" ? row.skillId : void 0,
|
|
4794
|
+
scope: row.scope === "project" ? "project" : "global",
|
|
4795
|
+
projectId: typeof row.projectId === "string" ? row.projectId : void 0,
|
|
4796
|
+
projectName: typeof row.projectName === "string" ? row.projectName : void 0,
|
|
4797
|
+
replaces: Array.isArray(row.replaces) ? row.replaces : [],
|
|
4798
|
+
contentHash: typeof body.contentHash === "string" ? body.contentHash : typeof row.contentHash === "string" ? row.contentHash : void 0,
|
|
4799
|
+
revision: typeof body.revision === "number" ? body.revision : typeof row.revision === "number" ? row.revision : void 0,
|
|
4800
|
+
files: body.files,
|
|
4801
|
+
description: typeof row.description === "string" ? row.description : void 0,
|
|
4802
|
+
triggerDescription: typeof row.triggerDescription === "string" ? row.triggerDescription : void 0
|
|
4803
|
+
} : null;
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
// src/claude-lifecycle-cutover.ts
|
|
4807
|
+
var CLAUDE_LIFECYCLE_SKILLS = ["commit", "deploy", "release"];
|
|
4808
|
+
function adapterFor(skill) {
|
|
4809
|
+
const files = buildNativeSkillAdapter("claude-code", skill, { allowClaudeCode: true });
|
|
4810
|
+
if (!files || files.length !== 1 || files[0].path !== "SKILL.md") {
|
|
4811
|
+
throw new Error(`Claude lifecycle adapter is invalid for ${skill.skillName}.`);
|
|
4812
|
+
}
|
|
4813
|
+
return files;
|
|
4814
|
+
}
|
|
4815
|
+
function safeTimestamp(now) {
|
|
4816
|
+
return now.toISOString().replace(/[:.]/g, "-");
|
|
4817
|
+
}
|
|
4818
|
+
function requiredSkills(skills, names) {
|
|
4819
|
+
const byName = new Map(skills.filter((skill) => skill.scope !== "project").map((skill) => [skill.skillName, skill]));
|
|
4820
|
+
return names.map((name) => {
|
|
4821
|
+
const skill = byName.get(name);
|
|
4822
|
+
if (!skill)
|
|
4823
|
+
throw new Error(`Canonical Claude lifecycle skill missing: ${name}.`);
|
|
4824
|
+
return skill;
|
|
4825
|
+
});
|
|
4826
|
+
}
|
|
4827
|
+
function applyClaudeLifecycleCutover(params) {
|
|
4828
|
+
const homeDir = params.homeDir || os4.homedir();
|
|
4829
|
+
const root = nativeSkillRoot("claude-code", homeDir);
|
|
4830
|
+
const state = readState(root);
|
|
4831
|
+
const names = params.names?.length ? params.names : CLAUDE_LIFECYCLE_SKILLS;
|
|
4832
|
+
const prepared = requiredSkills(params.skills, names).map((skill) => {
|
|
4833
|
+
const target = path12.join(root, skill.skillName);
|
|
4834
|
+
if (isSkillFolderOwnedElsewhere(target)) {
|
|
4835
|
+
throw new Error(`Claude skill folder is owned elsewhere (a link or another installer's package): ${skill.skillName}; not written.`);
|
|
4836
|
+
}
|
|
4837
|
+
const skillMd = path12.join(target, "SKILL.md");
|
|
4838
|
+
const adapterFiles = adapterFor(skill);
|
|
4839
|
+
const nextHash = computeSkillFileHash(adapterFiles);
|
|
4840
|
+
const managed = state.skills[skill.skillName];
|
|
4841
|
+
let currentContent;
|
|
4842
|
+
if (fs11.existsSync(skillMd))
|
|
4843
|
+
currentContent = fs11.readFileSync(skillMd, "utf8");
|
|
4844
|
+
if (managed) {
|
|
4845
|
+
const currentFiles = currentContent === void 0 ? null : [{ path: "SKILL.md", content: currentContent }];
|
|
4846
|
+
if (!currentFiles || computeSkillFileHash(currentFiles) !== managed.hash) {
|
|
4847
|
+
throw new Error(`Claude lifecycle adapter diverged: ${skill.skillName}.`);
|
|
4848
|
+
}
|
|
4849
|
+
} else if (fs11.existsSync(target) && currentContent === void 0) {
|
|
4850
|
+
throw new Error(`Claude lifecycle package has no SKILL.md: ${skill.skillName}.`);
|
|
4851
|
+
}
|
|
4852
|
+
return { skill, target, skillMd, adapterFiles, nextHash, managed, currentContent };
|
|
4853
|
+
});
|
|
4854
|
+
const replacements = prepared.filter((item) => !item.managed && item.currentContent !== void 0);
|
|
4855
|
+
let backupDir;
|
|
4856
|
+
if (replacements.length > 0) {
|
|
4857
|
+
backupDir = path12.join(
|
|
4858
|
+
homeDir,
|
|
4859
|
+
".greprag",
|
|
4860
|
+
"backups",
|
|
4861
|
+
"claude-lifecycle-cutover",
|
|
4862
|
+
safeTimestamp(params.now || /* @__PURE__ */ new Date())
|
|
4863
|
+
);
|
|
4864
|
+
for (const item of replacements) {
|
|
4865
|
+
const destination = path12.join(backupDir, item.skill.skillName, "SKILL.md");
|
|
4866
|
+
fs11.mkdirSync(path12.dirname(destination), { recursive: true });
|
|
4867
|
+
fs11.writeFileSync(destination, item.currentContent, "utf8");
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
const result = {
|
|
4871
|
+
installed: 0,
|
|
4872
|
+
updated: 0,
|
|
4873
|
+
current: 0,
|
|
4874
|
+
blocked: 0,
|
|
4875
|
+
backupDir
|
|
4876
|
+
};
|
|
4877
|
+
for (const item of prepared) {
|
|
4878
|
+
if (item.managed && item.managed.hash === item.nextHash) {
|
|
4879
|
+
result.current++;
|
|
4880
|
+
continue;
|
|
4881
|
+
}
|
|
4882
|
+
fs11.mkdirSync(item.target, { recursive: true });
|
|
4883
|
+
fs11.writeFileSync(item.skillMd, item.adapterFiles[0].content, "utf8");
|
|
4884
|
+
state.skills[item.skill.skillName] = { hash: item.nextHash, files: ["SKILL.md"] };
|
|
4885
|
+
if (item.managed)
|
|
4886
|
+
result.updated++;
|
|
4887
|
+
else
|
|
4888
|
+
result.installed++;
|
|
4889
|
+
}
|
|
4890
|
+
writeState(root, state);
|
|
4891
|
+
return result;
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4894
|
+
// src/native-adapter-refresh.ts
|
|
4895
|
+
function readAdapterStamps(content) {
|
|
4896
|
+
const frontmatter = /^---\n([\s\S]*?)\n---/.exec(content.replace(/\r\n?/g, "\n"))?.[1] || "";
|
|
4897
|
+
const field = (key) => new RegExp(`^${key}:[ \\t]*(\\S+)`, "m").exec(frontmatter)?.[1];
|
|
4898
|
+
const revision = field("greprag_revision");
|
|
4899
|
+
return {
|
|
4900
|
+
skillId: field("greprag_skill_id"),
|
|
4901
|
+
contentHash: field("greprag_content_hash"),
|
|
4902
|
+
revision: revision === void 0 ? void 0 : Number(revision),
|
|
4903
|
+
format: field("greprag_adapter_format")
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
function staleNativeAdapters(params) {
|
|
4907
|
+
const globalRoot = nativeSkillRoot(params.platform, params.homeDir);
|
|
4908
|
+
const roots = [globalRoot];
|
|
4909
|
+
const projectRoot = params.projectDir && params.platform !== "claude-code" ? writerOwnedProjectSkillRoot(params.platform, params.projectDir, params.homeDir) : null;
|
|
4910
|
+
if (projectRoot)
|
|
4911
|
+
roots.push(projectRoot);
|
|
4912
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4913
|
+
for (const row of params.rows) {
|
|
4914
|
+
if (typeof row.skillName === "string" && typeof row.skillId === "string") {
|
|
4915
|
+
byId.set(row.skillId, row);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
const format = nativeAdapterFormat(params.platform);
|
|
4919
|
+
const stale = [];
|
|
4920
|
+
for (const root of roots) {
|
|
4921
|
+
for (const [skillName, managed] of Object.entries(readState(root).skills)) {
|
|
4922
|
+
let files;
|
|
4923
|
+
try {
|
|
4924
|
+
files = readManagedFiles(targetPath(root, skillName), managed);
|
|
4925
|
+
} catch {
|
|
4926
|
+
continue;
|
|
4927
|
+
}
|
|
4928
|
+
if (!files?.length || computeSkillFileHash(files) !== managed.hash)
|
|
4929
|
+
continue;
|
|
4930
|
+
const stamps = readAdapterStamps(files.find((file) => file.path === "SKILL.md")?.content || "");
|
|
4931
|
+
const row = byId.get(stamps.skillId || (root === globalRoot ? `global:${skillName}` : ""));
|
|
4932
|
+
if (!row || row.skillName !== skillName)
|
|
4933
|
+
continue;
|
|
4934
|
+
const canonMoved = row.contentHash ? row.contentHash !== stamps.contentHash : typeof row.revision === "number" && row.revision !== stamps.revision;
|
|
4935
|
+
if (canonMoved || stamps.format !== format)
|
|
4936
|
+
stale.push({ root, row });
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
return stale;
|
|
4940
|
+
}
|
|
4941
|
+
async function refreshNativeAdapters(params) {
|
|
4942
|
+
const platform = params.platform;
|
|
4943
|
+
if (platform !== "claude-code" && platform !== "codex" && platform !== "opencode") {
|
|
4944
|
+
return { refreshed: 0, blocked: 0 };
|
|
4945
|
+
}
|
|
4946
|
+
const stale = staleNativeAdapters({ ...params, platform });
|
|
4947
|
+
const skills = await Promise.all(stale.map(({ row }) => fetchNativeMirroredSkill(params.apiUrl, params.apiKey, row).catch(() => null)));
|
|
4948
|
+
let refreshed = 0;
|
|
4949
|
+
let blocked = 0;
|
|
4950
|
+
for (const skill of skills) {
|
|
4951
|
+
if (!skill)
|
|
4952
|
+
continue;
|
|
4953
|
+
try {
|
|
4954
|
+
const result = platform === "claude-code" ? applyClaudeLifecycleCutover({ skills: [skill], names: [skill.skillName], homeDir: params.homeDir }) : applyNativeSkillMirror({
|
|
4955
|
+
platform,
|
|
4956
|
+
skills: [skill],
|
|
4957
|
+
homeDir: params.homeDir,
|
|
4958
|
+
projectDir: params.projectDir,
|
|
4959
|
+
prune: false
|
|
4960
|
+
});
|
|
4961
|
+
refreshed += result.updated;
|
|
4962
|
+
blocked += result.blocked;
|
|
4963
|
+
} catch {
|
|
4964
|
+
blocked++;
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
return { refreshed, blocked };
|
|
4968
|
+
}
|
|
4969
|
+
|
|
4055
4970
|
// src/opencode-plugin.ts
|
|
4056
|
-
var DEBUG_LOG_PATH =
|
|
4971
|
+
var DEBUG_LOG_PATH = path13.join(os5.homedir(), ".greprag", "opencode-plugin-debug.log");
|
|
4057
4972
|
var _debugLogReady = false;
|
|
4058
4973
|
function dlogInit() {
|
|
4059
4974
|
if (_debugLogReady)
|
|
4060
4975
|
return;
|
|
4061
4976
|
_debugLogReady = true;
|
|
4062
4977
|
try {
|
|
4063
|
-
|
|
4978
|
+
fs12.writeFileSync(DEBUG_LOG_PATH, "");
|
|
4064
4979
|
} catch {
|
|
4065
4980
|
}
|
|
4066
4981
|
}
|
|
@@ -4069,17 +4984,17 @@ function dlog(msg) {
|
|
|
4069
4984
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [greprag-memory] ${msg}
|
|
4070
4985
|
`;
|
|
4071
4986
|
try {
|
|
4072
|
-
|
|
4987
|
+
fs12.appendFileSync(DEBUG_LOG_PATH, line);
|
|
4073
4988
|
} catch {
|
|
4074
4989
|
}
|
|
4075
4990
|
}
|
|
4076
4991
|
dlog(`module top reached: pid=${process.pid} argv0=${process.argv[0]} distPath=${__filename}`);
|
|
4077
4992
|
var API_URL = "https://api.greprag.com";
|
|
4078
|
-
function loadEnvFile(
|
|
4993
|
+
function loadEnvFile(filePath2) {
|
|
4079
4994
|
try {
|
|
4080
|
-
if (!
|
|
4995
|
+
if (!fs12.existsSync(filePath2))
|
|
4081
4996
|
return;
|
|
4082
|
-
const raw =
|
|
4997
|
+
const raw = fs12.readFileSync(filePath2, "utf-8");
|
|
4083
4998
|
for (const line of raw.split(/\r?\n/)) {
|
|
4084
4999
|
const trimmed = line.trim();
|
|
4085
5000
|
if (!trimmed || trimmed.startsWith("#"))
|
|
@@ -4099,12 +5014,12 @@ function loadEnvFile(filePath) {
|
|
|
4099
5014
|
}
|
|
4100
5015
|
}
|
|
4101
5016
|
function loadGrepragEnv() {
|
|
4102
|
-
loadEnvFile(
|
|
5017
|
+
loadEnvFile(path13.join(HOME, ".greprag", ".env"));
|
|
4103
5018
|
try {
|
|
4104
|
-
const p =
|
|
4105
|
-
if (!
|
|
5019
|
+
const p = path13.join(HOME, ".claude", "settings.json");
|
|
5020
|
+
if (!fs12.existsSync(p))
|
|
4106
5021
|
return;
|
|
4107
|
-
const data = JSON.parse(
|
|
5022
|
+
const data = JSON.parse(fs12.readFileSync(p, "utf-8"));
|
|
4108
5023
|
if (data && data.env && typeof data.env === "object") {
|
|
4109
5024
|
for (const [key, val] of Object.entries(data.env)) {
|
|
4110
5025
|
if (!process.env[key] && typeof val === "string") {
|
|
@@ -4117,11 +5032,11 @@ function loadGrepragEnv() {
|
|
|
4117
5032
|
}
|
|
4118
5033
|
loadGrepragEnv();
|
|
4119
5034
|
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 =
|
|
5035
|
+
var WATCHER_LOCK = path13.join(HOME || os5.homedir(), ".greprag", "opencode-watch.lock");
|
|
4121
5036
|
var WATCHER_STALE_MS = 3e4;
|
|
4122
5037
|
function findGrepragBinary() {
|
|
4123
5038
|
const override = process.env.GREPRAG_BIN;
|
|
4124
|
-
if (override &&
|
|
5039
|
+
if (override && fs12.existsSync(override))
|
|
4125
5040
|
return override;
|
|
4126
5041
|
const binaryName = process.platform === "win32" ? "greprag.cmd" : "greprag";
|
|
4127
5042
|
try {
|
|
@@ -4130,14 +5045,14 @@ function findGrepragBinary() {
|
|
|
4130
5045
|
const candidate = line.trim();
|
|
4131
5046
|
if (!candidate)
|
|
4132
5047
|
continue;
|
|
4133
|
-
if (!
|
|
5048
|
+
if (!fs12.existsSync(candidate))
|
|
4134
5049
|
continue;
|
|
4135
5050
|
if (process.platform === "win32") {
|
|
4136
|
-
const ext =
|
|
5051
|
+
const ext = path13.extname(candidate).toLowerCase();
|
|
4137
5052
|
if (ext === ".cmd" || ext === ".exe" || ext === ".bat")
|
|
4138
5053
|
return candidate;
|
|
4139
5054
|
const cmdSibling = candidate + ".cmd";
|
|
4140
|
-
if (
|
|
5055
|
+
if (fs12.existsSync(cmdSibling))
|
|
4141
5056
|
return cmdSibling;
|
|
4142
5057
|
continue;
|
|
4143
5058
|
}
|
|
@@ -4146,19 +5061,19 @@ function findGrepragBinary() {
|
|
|
4146
5061
|
} catch {
|
|
4147
5062
|
}
|
|
4148
5063
|
const candidates = process.platform === "win32" ? [
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
5064
|
+
path13.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
|
|
5065
|
+
path13.join(HOME, "AppData", "Roaming", "npm", "greprag"),
|
|
5066
|
+
path13.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
|
|
5067
|
+
path13.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
|
|
4153
5068
|
] : [
|
|
4154
5069
|
"/usr/local/bin/greprag",
|
|
4155
5070
|
"/opt/homebrew/bin/greprag",
|
|
4156
|
-
|
|
4157
|
-
|
|
5071
|
+
path13.join(HOME || "", ".local", "bin", "greprag"),
|
|
5072
|
+
path13.join(HOME || "", ".yarn", "bin", "greprag")
|
|
4158
5073
|
];
|
|
4159
5074
|
for (const c of candidates) {
|
|
4160
5075
|
try {
|
|
4161
|
-
if (c &&
|
|
5076
|
+
if (c && fs12.existsSync(c))
|
|
4162
5077
|
return c;
|
|
4163
5078
|
} catch {
|
|
4164
5079
|
}
|
|
@@ -4167,18 +5082,18 @@ function findGrepragBinary() {
|
|
|
4167
5082
|
}
|
|
4168
5083
|
function tryClaimWatcherLock() {
|
|
4169
5084
|
try {
|
|
4170
|
-
const fd =
|
|
5085
|
+
const fd = fs12.openSync(WATCHER_LOCK, "wx");
|
|
4171
5086
|
return { ok: true, fd };
|
|
4172
5087
|
} catch (err) {
|
|
4173
5088
|
if (err.code !== "EEXIST") {
|
|
4174
5089
|
return { ok: false, reason: "lock-create-failed" };
|
|
4175
5090
|
}
|
|
4176
5091
|
try {
|
|
4177
|
-
const stat =
|
|
5092
|
+
const stat = fs12.statSync(WATCHER_LOCK);
|
|
4178
5093
|
const ageMs = Date.now() - stat.mtimeMs;
|
|
4179
5094
|
let pidAlive = false;
|
|
4180
5095
|
try {
|
|
4181
|
-
const pid = parseInt(
|
|
5096
|
+
const pid = parseInt(fs12.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
|
|
4182
5097
|
pidAlive = pid > 0 && isPidAlive(pid);
|
|
4183
5098
|
} catch {
|
|
4184
5099
|
}
|
|
@@ -4186,10 +5101,10 @@ function tryClaimWatcherLock() {
|
|
|
4186
5101
|
return { ok: false, reason: "busy" };
|
|
4187
5102
|
}
|
|
4188
5103
|
try {
|
|
4189
|
-
|
|
5104
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4190
5105
|
} catch {
|
|
4191
5106
|
}
|
|
4192
|
-
const fd =
|
|
5107
|
+
const fd = fs12.openSync(WATCHER_LOCK, "wx");
|
|
4193
5108
|
return { ok: true, fd };
|
|
4194
5109
|
} catch (err2) {
|
|
4195
5110
|
return { ok: false, reason: "lock-stale-replace-failed" };
|
|
@@ -4230,11 +5145,11 @@ function startCaptureWatcher() {
|
|
|
4230
5145
|
return;
|
|
4231
5146
|
}
|
|
4232
5147
|
try {
|
|
4233
|
-
|
|
4234
|
-
|
|
5148
|
+
fs12.writeSync(claim.fd, String(process.pid));
|
|
5149
|
+
fs12.closeSync(claim.fd);
|
|
4235
5150
|
} catch {
|
|
4236
5151
|
try {
|
|
4237
|
-
|
|
5152
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4238
5153
|
} catch {
|
|
4239
5154
|
}
|
|
4240
5155
|
return;
|
|
@@ -4250,11 +5165,11 @@ function startCaptureWatcher() {
|
|
|
4250
5165
|
const claim2 = tryClaimWatcherLock();
|
|
4251
5166
|
if (claim2.ok) {
|
|
4252
5167
|
try {
|
|
4253
|
-
|
|
4254
|
-
|
|
5168
|
+
fs12.writeSync(claim2.fd, String(process.pid));
|
|
5169
|
+
fs12.closeSync(claim2.fd);
|
|
4255
5170
|
} catch {
|
|
4256
5171
|
try {
|
|
4257
|
-
|
|
5172
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4258
5173
|
} catch {
|
|
4259
5174
|
}
|
|
4260
5175
|
return false;
|
|
@@ -4297,7 +5212,7 @@ function startCaptureWatcher() {
|
|
|
4297
5212
|
childStartTime = Date.now();
|
|
4298
5213
|
dlog(`startCaptureWatcher: spawned 'greprag opencode watch' pid=${child.pid ?? "<none>"}`);
|
|
4299
5214
|
try {
|
|
4300
|
-
|
|
5215
|
+
fs12.writeFileSync(WATCHER_LOCK, String(child.pid));
|
|
4301
5216
|
} catch {
|
|
4302
5217
|
}
|
|
4303
5218
|
child.stderr?.on("data", (chunk2) => {
|
|
@@ -4309,9 +5224,9 @@ function startCaptureWatcher() {
|
|
|
4309
5224
|
child.on("exit", (code, signal) => {
|
|
4310
5225
|
dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
|
|
4311
5226
|
try {
|
|
4312
|
-
const current =
|
|
5227
|
+
const current = fs12.readFileSync(WATCHER_LOCK, "utf-8").trim();
|
|
4313
5228
|
if (current === String(child.pid))
|
|
4314
|
-
|
|
5229
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4315
5230
|
} catch {
|
|
4316
5231
|
}
|
|
4317
5232
|
currentChild = null;
|
|
@@ -4340,7 +5255,7 @@ function startCaptureWatcher() {
|
|
|
4340
5255
|
`
|
|
4341
5256
|
);
|
|
4342
5257
|
try {
|
|
4343
|
-
|
|
5258
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4344
5259
|
} catch {
|
|
4345
5260
|
}
|
|
4346
5261
|
return;
|
|
@@ -4372,7 +5287,7 @@ function startCaptureWatcher() {
|
|
|
4372
5287
|
}, 2e3).unref();
|
|
4373
5288
|
}
|
|
4374
5289
|
try {
|
|
4375
|
-
|
|
5290
|
+
fs12.unlinkSync(WATCHER_LOCK);
|
|
4376
5291
|
} catch {
|
|
4377
5292
|
}
|
|
4378
5293
|
};
|
|
@@ -4437,6 +5352,14 @@ async function fetchSkillActivation(apiKey, cwd) {
|
|
|
4437
5352
|
const data = await response.json();
|
|
4438
5353
|
const rows = data.skills || [];
|
|
4439
5354
|
cachedSkillRows = { projectId: anchor.projectId, rows, fetchedAt: now };
|
|
5355
|
+
await refreshNativeAdapters({
|
|
5356
|
+
platform: "opencode",
|
|
5357
|
+
rows,
|
|
5358
|
+
apiUrl: API_URL,
|
|
5359
|
+
apiKey,
|
|
5360
|
+
homeDir: os5.homedir(),
|
|
5361
|
+
projectDir: cwd
|
|
5362
|
+
}).catch(() => void 0);
|
|
4440
5363
|
return activationFromApiRows({
|
|
4441
5364
|
rows,
|
|
4442
5365
|
platform: "opencode",
|
|
@@ -4509,11 +5432,11 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4509
5432
|
return;
|
|
4510
5433
|
}
|
|
4511
5434
|
try {
|
|
4512
|
-
|
|
4513
|
-
|
|
5435
|
+
fs12.writeSync(claim.fd, String(process.pid));
|
|
5436
|
+
fs12.closeSync(claim.fd);
|
|
4514
5437
|
} catch {
|
|
4515
5438
|
try {
|
|
4516
|
-
|
|
5439
|
+
fs12.unlinkSync(lockPath);
|
|
4517
5440
|
} catch {
|
|
4518
5441
|
}
|
|
4519
5442
|
return;
|
|
@@ -4528,11 +5451,11 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4528
5451
|
const claim2 = tryClaimRelayLock(lockPath);
|
|
4529
5452
|
if (claim2.ok) {
|
|
4530
5453
|
try {
|
|
4531
|
-
|
|
4532
|
-
|
|
5454
|
+
fs12.writeSync(claim2.fd, String(process.pid));
|
|
5455
|
+
fs12.closeSync(claim2.fd);
|
|
4533
5456
|
} catch {
|
|
4534
5457
|
try {
|
|
4535
|
-
|
|
5458
|
+
fs12.unlinkSync(lockPath);
|
|
4536
5459
|
} catch {
|
|
4537
5460
|
}
|
|
4538
5461
|
return false;
|
|
@@ -4568,7 +5491,7 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4568
5491
|
childStartTime = Date.now();
|
|
4569
5492
|
dlog(`startSessionRelay: spawned 'greprag opencode relay' pid=${child.pid ?? "<none>"}`);
|
|
4570
5493
|
try {
|
|
4571
|
-
|
|
5494
|
+
fs12.writeFileSync(lockPath, String(child.pid));
|
|
4572
5495
|
} catch {
|
|
4573
5496
|
}
|
|
4574
5497
|
child.stderr?.on("data", (chunk2) => {
|
|
@@ -4579,9 +5502,9 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4579
5502
|
});
|
|
4580
5503
|
child.on("exit", (code, signal) => {
|
|
4581
5504
|
try {
|
|
4582
|
-
const current =
|
|
5505
|
+
const current = fs12.readFileSync(lockPath, "utf-8").trim();
|
|
4583
5506
|
if (current === String(child.pid))
|
|
4584
|
-
|
|
5507
|
+
fs12.unlinkSync(lockPath);
|
|
4585
5508
|
} catch {
|
|
4586
5509
|
}
|
|
4587
5510
|
currentChild = null;
|
|
@@ -4608,7 +5531,7 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4608
5531
|
`
|
|
4609
5532
|
);
|
|
4610
5533
|
try {
|
|
4611
|
-
|
|
5534
|
+
fs12.unlinkSync(lockPath);
|
|
4612
5535
|
} catch {
|
|
4613
5536
|
}
|
|
4614
5537
|
return;
|
|
@@ -4640,7 +5563,7 @@ function startSessionRelay(sessionId, serverUrl) {
|
|
|
4640
5563
|
}, 2e3).unref();
|
|
4641
5564
|
}
|
|
4642
5565
|
try {
|
|
4643
|
-
|
|
5566
|
+
fs12.unlinkSync(lockPath);
|
|
4644
5567
|
} catch {
|
|
4645
5568
|
}
|
|
4646
5569
|
};
|
|
@@ -4701,7 +5624,7 @@ function runPngRecode(input, msgs) {
|
|
|
4701
5624
|
}
|
|
4702
5625
|
function makeCcrStash() {
|
|
4703
5626
|
return (original, sourceType) => {
|
|
4704
|
-
const hash =
|
|
5627
|
+
const hash = crypto6.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
|
|
4705
5628
|
const bin = grepragBinCached();
|
|
4706
5629
|
if (bin) {
|
|
4707
5630
|
try {
|