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.
Files changed (43) hide show
  1. package/dist/claude-lifecycle-cutover.js +5 -16
  2. package/dist/commands/codex.js +4 -0
  3. package/dist/commands/collision-check.js +9 -6
  4. package/dist/commands/corpus/client.js +16 -0
  5. package/dist/commands/corpus/index.js +5 -2
  6. package/dist/commands/corpus/section.js +3 -3
  7. package/dist/commands/corpus/walk.js +3 -3
  8. package/dist/commands/delivery-reminder.js +13 -9
  9. package/dist/commands/delivery.js +64 -0
  10. package/dist/commands/docptr.js +10 -24
  11. package/dist/commands/init.js +4 -5
  12. package/dist/commands/load-primer-reminder.js +2 -2
  13. package/dist/commands/load.js +45 -12
  14. package/dist/commands/merge-guard.js +94 -9
  15. package/dist/commands/parity.js +0 -2
  16. package/dist/commands/pipe-wrap.js +4 -2
  17. package/dist/commands/skill-mirror-reminder.js +24 -6
  18. package/dist/commands/skill.js +24 -8
  19. package/dist/commands/skillgain.js +5 -1
  20. package/dist/commands/state-trigger.js +67 -31
  21. package/dist/delivery-config.js +69 -0
  22. package/dist/deploy-locks.js +18 -3
  23. package/dist/doc-mirror-client.js +39 -11
  24. package/dist/hook.js +35 -39
  25. package/dist/index.js +62 -27
  26. package/dist/native-adapter-refresh.js +114 -0
  27. package/dist/native-skill-adoption.js +20 -53
  28. package/dist/native-skill-mirror.js +130 -52
  29. package/dist/native-skill-pending.js +5 -2
  30. package/dist/opencode-plugin.bundle.js +1083 -160
  31. package/dist/opencode-plugin.js +6 -0
  32. package/dist/skill-activation-manifest.js +98 -22
  33. package/dist/skill-landing.js +6 -0
  34. package/dist/skill-mirror-files.js +59 -0
  35. package/package.json +1 -1
  36. package/scripts/postinstall.js +4 -19
  37. package/skill/greprag/SKILL.md +1 -1
  38. package/skill/greprag/docs/corpus.md +2 -2
  39. package/skill/greprag/docs/setup.md +1 -1
  40. package/skill/mechanic/SKILL.md +15 -40
  41. package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
  42. package/skill/templates/chip-spawn.md +36 -20
  43. 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 crypto5 = __toESM(require("crypto"));
27
- var fs9 = __toESM(require("fs"));
28
- var os3 = __toESM(require("os"));
29
- var path9 = __toESM(require("path"));
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(filePath) {
50
+ function readAnchorFile(filePath2) {
51
51
  try {
52
- const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
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 filePath = findExistingAnchorFile(worktree);
131
- const file = filePath ? readAnchorFile(filePath) : null;
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 + Block 2 report-back); it carries the multi-chip upgrade path inline. Non-negotiable \u2014 the spawn validator rejects an unstructured chip."
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 fs2 = __toESM(require("fs"));
2179
- var path2 = __toESM(require("path"));
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 [path2.join(homeDir, ".claude", "skills")];
2250
+ return [path4.join(homeDir, ".claude", "skills")];
2185
2251
  }
2186
2252
  if (platform === "codex") {
2187
2253
  return [
2188
- path2.join(homeDir, ".codex", "skills"),
2189
- path2.join(homeDir, ".agents", "skills"),
2190
- path2.join(homeDir, ".codex", "plugins", "cache")
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 [path2.join(homeDir, ".config", "opencode", "skills")];
2259
+ return [path4.join(homeDir, ".config", "opencode", "skills")];
2194
2260
  }
2195
2261
  function ancestorDirs(cwd) {
2196
2262
  const dirs = [];
2197
- let current = path2.resolve(cwd);
2263
+ let current = path4.resolve(cwd);
2198
2264
  for (let depth = 0; depth < 16; depth++) {
2199
2265
  dirs.push(current);
2200
- const parent = path2.dirname(current);
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 [path2.join(dir, ".claude", "skills")];
2276
+ return [path4.join(dir, ".claude", "skills")];
2211
2277
  if (platform === "codex") {
2212
- return [path2.join(dir, ".codex", "skills"), path2.join(dir, ".agents", "skills")];
2278
+ return [path4.join(dir, ".codex", "skills"), path4.join(dir, ".agents", "skills")];
2213
2279
  }
2214
- return [path2.join(dir, ".opencode", "skills")];
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 Set();
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 = fs2.readdirSync(dir, { withFileTypes: true });
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 full = path2.join(dir, entry.name);
2238
- if (entry.isDirectory()) {
2239
- walk(full, depth + 1);
2240
- continue;
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 buildMirroredSkillActivation(entries, nativeNames) {
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) => !nativeNames.has(entry.skillName.toLowerCase()));
2267
- return { count: valid.length, skills, nativeCount: valid.length - skills.length };
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
- return "[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.]";
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.skills.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:`,
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
- const how = platform === "codex" ? "codex_app.list_threads + codex_app.send_message_to_thread" : platform === "claude-code" ? "this harness's own session-list + send tools (never a greprag id)" : platform === "opencode" ? "the GrepRAG registry" : "your harness's own peer tools";
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 fs4 = __toESM(require("node:fs"));
2713
+ var fs5 = __toESM(require("node:fs"));
2589
2714
  var os2 = __toESM(require("node:os"));
2590
- var path4 = __toESM(require("node:path"));
2715
+ var path6 = __toESM(require("node:path"));
2591
2716
 
2592
2717
  // src/project-anchor.ts
2593
- var path3 = __toESM(require("path"));
2594
- var fs3 = __toESM(require("fs"));
2595
- var crypto2 = __toESM(require("crypto"));
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 path3.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2726
+ return path5.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2602
2727
  }
2603
2728
  function legacyAnchorPathIn(dir) {
2604
- return path3.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
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 = path3.resolve(startDir);
2740
+ let dir = path5.resolve(startDir);
2616
2741
  while (true) {
2617
2742
  const candidate = anchorPathIn(dir);
2618
- if (candidate !== homeAnchor && fs3.existsSync(candidate))
2743
+ if (candidate !== homeAnchor && fs4.existsSync(candidate))
2619
2744
  return candidate;
2620
2745
  const legacyCandidate = legacyAnchorPathIn(dir);
2621
- if (legacyCandidate !== legacyHomeAnchor && fs3.existsSync(legacyCandidate))
2746
+ if (legacyCandidate !== legacyHomeAnchor && fs4.existsSync(legacyCandidate))
2622
2747
  return legacyCandidate;
2623
- const parent = path3.dirname(dir);
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 = path3.resolve(cwd).replace(/\\/g, "/").toLowerCase();
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 = crypto2.createHash("sha256").update(roots.join("\n")).digest("hex");
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 = path3.resolve(workingDir).toLowerCase();
2669
- const hash = crypto2.createHash("sha256").update(normalized).digest("hex");
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(filePath) {
2805
+ function tryReadAnchorFileContents(filePath2) {
2681
2806
  try {
2682
- const raw = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
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 = path3.resolve(cwd);
2846
+ const root2 = path5.resolve(cwd);
2722
2847
  return {
2723
2848
  projectId: gitId,
2724
- projectName: fileContents?.projectName || path3.basename(root2).toLowerCase(),
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 = fs3.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
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 = path3.resolve(cwd);
2882
+ const root = path5.resolve(cwd);
2758
2883
  return {
2759
2884
  projectId: deterministicProjectId2(root),
2760
- projectName: fileContents?.projectName || path3.basename(root).toLowerCase(),
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(fs4.readFileSync(file, "utf8"));
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 path4.join(homeDir, ".greprag", "settings.json");
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 path6 = __toESM(require("path"));
2842
- var fs6 = __toESM(require("fs"));
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 path5 = __toESM(require("path"));
2867
- var fs5 = __toESM(require("fs"));
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 = path5.resolve(startDir);
3052
+ let dir = path7.resolve(startDir);
2928
3053
  for (; ; ) {
2929
3054
  try {
2930
- if (fs5.existsSync(path5.join(dir, ".git")))
3055
+ if (fs6.existsSync(path7.join(dir, ".git")))
2931
3056
  return dir;
2932
3057
  } catch {
2933
3058
  }
2934
- const parent = path5.dirname(dir);
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 = path5.join(checkoutRoot, ".git");
3066
+ const dotGit = path7.join(checkoutRoot, ".git");
2942
3067
  try {
2943
- if (!fs5.statSync(dotGit).isFile())
3068
+ if (!fs6.statSync(dotGit).isFile())
2944
3069
  return null;
2945
- const m = /^\s*gitdir:\s*(.+?)\s*$/m.exec(fs5.readFileSync(dotGit, "utf-8"));
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 = path5.resolve(checkoutRoot, m[1]);
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 path5.dirname(gitDir.slice(0, marker));
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 ? [path5.resolve(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 path6.join(home, ".greprag", "state");
3094
+ return path8.join(home, ".greprag", "state");
2970
3095
  }
2971
3096
  function procedureStorePath(projectId) {
2972
- return path6.join(stateDir(), `procedures-${projectId}.json`);
3097
+ return path8.join(stateDir(), `procedures-${projectId}.json`);
2973
3098
  }
2974
3099
  function readProcedureStore(projectId) {
2975
3100
  try {
2976
- const raw = fs6.readFileSync(procedureStorePath(projectId), "utf-8");
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 crypto4 = __toESM(require("crypto"));
3384
+ var crypto5 = __toESM(require("crypto"));
3260
3385
 
3261
3386
  // src/procedure-watch.ts
3262
- var path7 = __toESM(require("path"));
3263
- var fs7 = __toESM(require("fs"));
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 path7.join(home, ".greprag", "state");
3415
+ return path9.join(home, ".greprag", "state");
3291
3416
  }
3292
3417
  function procedureWatchPath(projectId) {
3293
- return path7.join(stateDir2(), `procedure-watch-${projectId}.json`);
3418
+ return path9.join(stateDir2(), `procedure-watch-${projectId}.json`);
3294
3419
  }
3295
3420
  function hasProcedureWatch(projectId) {
3296
3421
  try {
3297
- return fs7.existsSync(procedureWatchPath(projectId));
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 = path7.dirname(file);
3305
- if (!fs7.existsSync(dir))
3306
- fs7.mkdirSync(dir, { recursive: true });
3307
- fs7.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
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 crypto3 = __toESM(require("crypto"));
3321
- var fs8 = __toESM(require("fs"));
3322
- var path8 = __toESM(require("path"));
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 path8.join(home, ".greprag", "state");
3450
+ return path10.join(home, ".greprag", "state");
3326
3451
  }
3327
3452
  function procedureShadowPath(projectId) {
3328
- return path8.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
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
- fs8.mkdirSync(path8.dirname(file), { recursive: true });
3334
- fs8.appendFileSync(file, JSON.stringify(event) + "\n");
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 crypto3.randomUUID();
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 crypto3.createHash("sha256").update(payload).digest("hex");
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 = crypto4.createHash("sha256").update(latest.prompt).digest("hex");
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 = path9.join(os3.homedir(), ".greprag", "opencode-plugin-debug.log");
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
- fs9.writeFileSync(DEBUG_LOG_PATH, "");
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
- fs9.appendFileSync(DEBUG_LOG_PATH, line);
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(filePath) {
4993
+ function loadEnvFile(filePath2) {
4079
4994
  try {
4080
- if (!fs9.existsSync(filePath))
4995
+ if (!fs12.existsSync(filePath2))
4081
4996
  return;
4082
- const raw = fs9.readFileSync(filePath, "utf-8");
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(path9.join(HOME, ".greprag", ".env"));
5017
+ loadEnvFile(path13.join(HOME, ".greprag", ".env"));
4103
5018
  try {
4104
- const p = path9.join(HOME, ".claude", "settings.json");
4105
- if (!fs9.existsSync(p))
5019
+ const p = path13.join(HOME, ".claude", "settings.json");
5020
+ if (!fs12.existsSync(p))
4106
5021
  return;
4107
- const data = JSON.parse(fs9.readFileSync(p, "utf-8"));
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 = path9.join(HOME || os3.homedir(), ".greprag", "opencode-watch.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 && fs9.existsSync(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 (!fs9.existsSync(candidate))
5048
+ if (!fs12.existsSync(candidate))
4134
5049
  continue;
4135
5050
  if (process.platform === "win32") {
4136
- const ext = path9.extname(candidate).toLowerCase();
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 (fs9.existsSync(cmdSibling))
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
- path9.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
4150
- path9.join(HOME, "AppData", "Roaming", "npm", "greprag"),
4151
- path9.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
4152
- path9.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
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
- path9.join(HOME || "", ".local", "bin", "greprag"),
4157
- path9.join(HOME || "", ".yarn", "bin", "greprag")
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 && fs9.existsSync(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 = fs9.openSync(WATCHER_LOCK, "wx");
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 = fs9.statSync(WATCHER_LOCK);
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(fs9.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
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
- fs9.unlinkSync(WATCHER_LOCK);
5104
+ fs12.unlinkSync(WATCHER_LOCK);
4190
5105
  } catch {
4191
5106
  }
4192
- const fd = fs9.openSync(WATCHER_LOCK, "wx");
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
- fs9.writeSync(claim.fd, String(process.pid));
4234
- fs9.closeSync(claim.fd);
5148
+ fs12.writeSync(claim.fd, String(process.pid));
5149
+ fs12.closeSync(claim.fd);
4235
5150
  } catch {
4236
5151
  try {
4237
- fs9.unlinkSync(WATCHER_LOCK);
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
- fs9.writeSync(claim2.fd, String(process.pid));
4254
- fs9.closeSync(claim2.fd);
5168
+ fs12.writeSync(claim2.fd, String(process.pid));
5169
+ fs12.closeSync(claim2.fd);
4255
5170
  } catch {
4256
5171
  try {
4257
- fs9.unlinkSync(WATCHER_LOCK);
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
- fs9.writeFileSync(WATCHER_LOCK, String(child.pid));
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 = fs9.readFileSync(WATCHER_LOCK, "utf-8").trim();
5227
+ const current = fs12.readFileSync(WATCHER_LOCK, "utf-8").trim();
4313
5228
  if (current === String(child.pid))
4314
- fs9.unlinkSync(WATCHER_LOCK);
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
- fs9.unlinkSync(WATCHER_LOCK);
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
- fs9.unlinkSync(WATCHER_LOCK);
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
- fs9.writeSync(claim.fd, String(process.pid));
4513
- fs9.closeSync(claim.fd);
5435
+ fs12.writeSync(claim.fd, String(process.pid));
5436
+ fs12.closeSync(claim.fd);
4514
5437
  } catch {
4515
5438
  try {
4516
- fs9.unlinkSync(lockPath);
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
- fs9.writeSync(claim2.fd, String(process.pid));
4532
- fs9.closeSync(claim2.fd);
5454
+ fs12.writeSync(claim2.fd, String(process.pid));
5455
+ fs12.closeSync(claim2.fd);
4533
5456
  } catch {
4534
5457
  try {
4535
- fs9.unlinkSync(lockPath);
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
- fs9.writeFileSync(lockPath, String(child.pid));
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 = fs9.readFileSync(lockPath, "utf-8").trim();
5505
+ const current = fs12.readFileSync(lockPath, "utf-8").trim();
4583
5506
  if (current === String(child.pid))
4584
- fs9.unlinkSync(lockPath);
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
- fs9.unlinkSync(lockPath);
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
- fs9.unlinkSync(lockPath);
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 = crypto5.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
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 {