greprag 5.82.1 → 5.83.0

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