greprag 5.64.0 → 5.65.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 (62) hide show
  1. package/dist/commands/agent-doctrine-file.d.ts +30 -0
  2. package/dist/commands/agent-doctrine-file.js +104 -0
  3. package/dist/commands/agent-doctrine-file.js.map +1 -0
  4. package/dist/commands/agents.d.ts +15 -0
  5. package/dist/commands/agents.js +370 -0
  6. package/dist/commands/agents.js.map +1 -0
  7. package/dist/commands/codex-chip/native.d.ts +5 -0
  8. package/dist/commands/codex-chip/native.js +29 -4
  9. package/dist/commands/codex-chip/native.js.map +1 -1
  10. package/dist/commands/codex-chip/prompt.js +1 -0
  11. package/dist/commands/codex-chip/prompt.js.map +1 -1
  12. package/dist/commands/codex.js +1 -1
  13. package/dist/commands/codex.js.map +1 -1
  14. package/dist/commands/fix.d.ts +9 -9
  15. package/dist/commands/fix.js +18 -19
  16. package/dist/commands/fix.js.map +1 -1
  17. package/dist/commands/inbox-drain.js +1 -1
  18. package/dist/commands/inbox-drain.js.map +1 -1
  19. package/dist/commands/inbox-watch.js +1 -1
  20. package/dist/commands/inbox-watch.js.map +1 -1
  21. package/dist/commands/init.js +40 -3
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/load.d.ts +2 -0
  24. package/dist/commands/load.js +52 -1
  25. package/dist/commands/load.js.map +1 -1
  26. package/dist/commands/mechanic-live.js +6 -12
  27. package/dist/commands/mechanic-live.js.map +1 -1
  28. package/dist/commands/mechanic-spawn.js +3 -2
  29. package/dist/commands/mechanic-spawn.js.map +1 -1
  30. package/dist/commands/mechanic.js +4 -5
  31. package/dist/commands/mechanic.js.map +1 -1
  32. package/dist/commands/opencode-interrupt.d.ts +1 -0
  33. package/dist/commands/opencode-interrupt.js +1 -0
  34. package/dist/commands/opencode-interrupt.js.map +1 -1
  35. package/dist/commands/opencode-relay.js +1 -1
  36. package/dist/commands/opencode-relay.js.map +1 -1
  37. package/dist/commands/parity.js +6 -4
  38. package/dist/commands/parity.js.map +1 -1
  39. package/dist/commands/reminder-registry.js +1 -1
  40. package/dist/commands/reminder-registry.js.map +1 -1
  41. package/dist/commands/reminder-types.d.ts +10 -5
  42. package/dist/commands/skill-mirror-reminder.d.ts +9 -13
  43. package/dist/commands/skill-mirror-reminder.js +38 -15
  44. package/dist/commands/skill-mirror-reminder.js.map +1 -1
  45. package/dist/hook.js +18 -34
  46. package/dist/hook.js.map +1 -1
  47. package/dist/index.js +10 -5
  48. package/dist/index.js.map +1 -1
  49. package/dist/mechanic-friction.d.ts +0 -17
  50. package/dist/mechanic-friction.js +10 -76
  51. package/dist/mechanic-friction.js.map +1 -1
  52. package/dist/opencode-plugin.bundle.js +220 -59
  53. package/dist/opencode-plugin.js +38 -1
  54. package/dist/opencode-plugin.js.map +1 -1
  55. package/dist/skill-activation-manifest.d.ts +37 -0
  56. package/dist/skill-activation-manifest.js +154 -0
  57. package/dist/skill-activation-manifest.js.map +1 -0
  58. package/package.json +1 -1
  59. package/skill/greprag/docs/codex-chip.md +3 -0
  60. package/skill/mechanic/SKILL.md +24 -25
  61. package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
  62. package/skill/templates/codex-chip-spawn.md +18 -0
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
 
25
25
  // src/opencode-plugin.ts
26
26
  var crypto2 = __toESM(require("crypto"));
27
- var fs3 = __toESM(require("fs"));
27
+ var fs4 = __toESM(require("fs"));
28
28
  var os = __toESM(require("os"));
29
- var path3 = __toESM(require("path"));
29
+ var path4 = __toESM(require("path"));
30
30
 
31
31
  // src/opencode-plugin-helpers.ts
32
32
  var crypto = __toESM(require("crypto"));
@@ -2210,15 +2210,40 @@ var skillGainAnnounceModule = {
2210
2210
  };
2211
2211
 
2212
2212
  // src/commands/skill-mirror-reminder.ts
2213
+ var SKILL_ACTIVATION_MANIFEST_MAX_CHARS = 64e3;
2214
+ function renderEntry(skill) {
2215
+ const description = skill.triggerDescription.trim().replace(/\r\n?/g, "\n");
2216
+ const lines = description.split("\n");
2217
+ return `- ${skill.skillName}: ${lines[0]}${lines.slice(1).map((line) => `
2218
+ ${line}`).join("")}`;
2219
+ }
2213
2220
  function buildSkillMirrorAnnounce(m) {
2214
2221
  if (!m || m.count <= 0)
2215
2222
  return null;
2216
- const sample = m.names.slice(0, 4).join(", ");
2217
- const more = m.count > 4 ? ", \u2026" : "";
2218
- return [
2219
- `[greprag skills \u2014 ${m.count} of your skills travel with the harness (auto-mirrored from use, always fresh): ${sample}${more}.]`,
2220
- "Any machine or harness with the greprag CLI can use them \u2014 `greprag load` to browse, `greprag load <skill>` to read one inline. No ~/.claude/skills needed."
2223
+ if (m.skills.length === 0) {
2224
+ return `[greprag load skills: all ${m.count} canonical mirrored skills are already advertised by this harness; use \`greprag load <skill>\` to read the canonical payload.]`;
2225
+ }
2226
+ const header = [
2227
+ "## GrepRAG Load Skills",
2228
+ `These ${m.skills.length} canonical mirrored skills are available through \`greprag load\`` + (m.nativeCount > 0 ? ` (${m.nativeCount} native-advertised duplicate${m.nativeCount === 1 ? "" : "s"} omitted)` : "") + ".",
2229
+ "When the user names a skill (with or without `/`) or the task clearly matches a description below, run `greprag load <skill>` before acting. The command returns the full canonical method; this list is its activation index.",
2230
+ ""
2221
2231
  ].join("\n");
2232
+ let output = header;
2233
+ let included = 0;
2234
+ for (const skill of m.skills) {
2235
+ const block = renderEntry(skill);
2236
+ if (output.length + block.length + 1 > SKILL_ACTIVATION_MANIFEST_MAX_CHARS)
2237
+ break;
2238
+ output += `${block}
2239
+ `;
2240
+ included++;
2241
+ }
2242
+ if (included < m.skills.length) {
2243
+ output += `[Manifest bound reached: ${m.skills.length - included} additional skills are discoverable with \`greprag load\`.]
2244
+ `;
2245
+ }
2246
+ return output.trimEnd();
2222
2247
  }
2223
2248
  var skillMirrorAnnounceModule = {
2224
2249
  id: "skill-mirror-announce",
@@ -2305,7 +2330,7 @@ var REGISTRY = [
2305
2330
  skillGainAnnounceModule,
2306
2331
  // announce-only skill-gain landings (docs/skill-learning-loop.md)
2307
2332
  skillMirrorAnnounceModule,
2308
- // one-line Tier-3 pointer: your skills travel via greprag load (docs/load-system.md)
2333
+ // native-equivalent activation index for load-only mirrored skills
2309
2334
  procedureAnnounceModule,
2310
2335
  // active project procedures, rendered generically from operator-owned installs
2311
2336
  loadoutRegistrarModule,
@@ -2403,7 +2428,8 @@ function buildOpenCodeEnv(params) {
2403
2428
  setupWarning: params.setupWarning ?? null,
2404
2429
  corpusApiDocs: params.corpusApiDocs ?? [],
2405
2430
  updateAvailable: params.updateAvailable ?? null,
2406
- procedureAnnounces: params.procedureAnnounces ?? []
2431
+ procedureAnnounces: params.procedureAnnounces ?? [],
2432
+ mirroredSkills: params.mirroredSkills
2407
2433
  };
2408
2434
  }
2409
2435
  function getOpenCodeAnnounces(env) {
@@ -3103,15 +3129,116 @@ function recodeMessagesToPng(messages, opts) {
3103
3129
  return stats;
3104
3130
  }
3105
3131
 
3132
+ // src/skill-activation-manifest.ts
3133
+ var fs3 = __toESM(require("fs"));
3134
+ var path3 = __toESM(require("path"));
3135
+ var MAX_NATIVE_SKILL_FILES = 1500;
3136
+ var MAX_SCAN_DEPTH = 7;
3137
+ function homeRoots(homeDir, platform) {
3138
+ if (platform === "claude-code") {
3139
+ return [path3.join(homeDir, ".claude", "skills")];
3140
+ }
3141
+ if (platform === "codex") {
3142
+ return [
3143
+ path3.join(homeDir, ".codex", "skills"),
3144
+ path3.join(homeDir, ".agents", "skills"),
3145
+ path3.join(homeDir, ".codex", "plugins", "cache")
3146
+ ];
3147
+ }
3148
+ return [path3.join(homeDir, ".config", "opencode", "skills")];
3149
+ }
3150
+ function ancestorDirs(cwd) {
3151
+ const dirs = [];
3152
+ let current = path3.resolve(cwd);
3153
+ for (let depth = 0; depth < 16; depth++) {
3154
+ dirs.push(current);
3155
+ const parent = path3.dirname(current);
3156
+ if (parent === current)
3157
+ break;
3158
+ current = parent;
3159
+ }
3160
+ return dirs;
3161
+ }
3162
+ function repoRoots(cwd, platform) {
3163
+ return ancestorDirs(cwd).flatMap((dir) => {
3164
+ if (platform === "claude-code")
3165
+ return [path3.join(dir, ".claude", "skills")];
3166
+ if (platform === "codex") {
3167
+ return [path3.join(dir, ".codex", "skills"), path3.join(dir, ".agents", "skills")];
3168
+ }
3169
+ return [path3.join(dir, ".opencode", "skills")];
3170
+ });
3171
+ }
3172
+ function frontmatterName(content) {
3173
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
3174
+ const name = fm && /(?:^|\r?\n)name:\s*["']?([^\r\n"']+)/.exec(fm[1]);
3175
+ return name?.[1]?.trim() || "";
3176
+ }
3177
+ function readNativeSkillNames(params) {
3178
+ const names = /* @__PURE__ */ new Set();
3179
+ let visited = 0;
3180
+ const walk = (dir, depth) => {
3181
+ if (depth > MAX_SCAN_DEPTH || visited >= MAX_NATIVE_SKILL_FILES)
3182
+ return;
3183
+ let entries;
3184
+ try {
3185
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
3186
+ } catch {
3187
+ return;
3188
+ }
3189
+ for (const entry of entries) {
3190
+ if (visited >= MAX_NATIVE_SKILL_FILES)
3191
+ return;
3192
+ const full = path3.join(dir, entry.name);
3193
+ if (entry.isDirectory()) {
3194
+ walk(full, depth + 1);
3195
+ continue;
3196
+ }
3197
+ if (!entry.isFile() || entry.name.toLowerCase() !== "skill.md")
3198
+ continue;
3199
+ visited++;
3200
+ names.add(path3.basename(path3.dirname(full)).toLowerCase());
3201
+ try {
3202
+ const declared = frontmatterName(fs3.readFileSync(full, "utf8"));
3203
+ if (declared)
3204
+ names.add(declared.toLowerCase());
3205
+ } catch {
3206
+ }
3207
+ }
3208
+ };
3209
+ const roots = [
3210
+ ...homeRoots(params.homeDir, params.platform),
3211
+ ...repoRoots(params.cwd, params.platform)
3212
+ ];
3213
+ for (const root of roots)
3214
+ walk(root, 0);
3215
+ return names;
3216
+ }
3217
+ function buildMirroredSkillActivation(entries, nativeNames) {
3218
+ const valid = entries.filter((entry) => entry.skillName && entry.triggerDescription);
3219
+ if (valid.length === 0)
3220
+ return void 0;
3221
+ const skills = valid.filter((entry) => !nativeNames.has(entry.skillName.toLowerCase()));
3222
+ return { count: valid.length, skills, nativeCount: valid.length - skills.length };
3223
+ }
3224
+ function activationFromApiRows(params) {
3225
+ const entries = params.rows.filter((row) => typeof row.skillName === "string" && !!row.skillName && typeof row.triggerDescription === "string" && !!row.triggerDescription).map((row) => ({
3226
+ skillName: row.skillName,
3227
+ description: typeof row.description === "string" ? row.description : "",
3228
+ triggerDescription: row.triggerDescription
3229
+ }));
3230
+ return buildMirroredSkillActivation(entries, readNativeSkillNames(params));
3231
+ }
3232
+
3106
3233
  // src/opencode-plugin.ts
3107
- var DEBUG_LOG_PATH = path3.join(os.homedir(), ".greprag", "opencode-plugin-debug.log");
3234
+ var DEBUG_LOG_PATH = path4.join(os.homedir(), ".greprag", "opencode-plugin-debug.log");
3108
3235
  var _debugLogReady = false;
3109
3236
  function dlogInit() {
3110
3237
  if (_debugLogReady)
3111
3238
  return;
3112
3239
  _debugLogReady = true;
3113
3240
  try {
3114
- fs3.writeFileSync(DEBUG_LOG_PATH, "");
3241
+ fs4.writeFileSync(DEBUG_LOG_PATH, "");
3115
3242
  } catch {
3116
3243
  }
3117
3244
  }
@@ -3120,7 +3247,7 @@ function dlog(msg) {
3120
3247
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [greprag-memory] ${msg}
3121
3248
  `;
3122
3249
  try {
3123
- fs3.appendFileSync(DEBUG_LOG_PATH, line);
3250
+ fs4.appendFileSync(DEBUG_LOG_PATH, line);
3124
3251
  } catch {
3125
3252
  }
3126
3253
  }
@@ -3128,9 +3255,9 @@ dlog(`module top reached: pid=${process.pid} argv0=${process.argv[0]} distPath=$
3128
3255
  var API_URL = "https://api.greprag.com";
3129
3256
  function loadEnvFile(filePath) {
3130
3257
  try {
3131
- if (!fs3.existsSync(filePath))
3258
+ if (!fs4.existsSync(filePath))
3132
3259
  return;
3133
- const raw = fs3.readFileSync(filePath, "utf-8");
3260
+ const raw = fs4.readFileSync(filePath, "utf-8");
3134
3261
  for (const line of raw.split(/\r?\n/)) {
3135
3262
  const trimmed = line.trim();
3136
3263
  if (!trimmed || trimmed.startsWith("#"))
@@ -3150,12 +3277,12 @@ function loadEnvFile(filePath) {
3150
3277
  }
3151
3278
  }
3152
3279
  function loadGrepragEnv() {
3153
- loadEnvFile(path3.join(HOME, ".greprag", ".env"));
3280
+ loadEnvFile(path4.join(HOME, ".greprag", ".env"));
3154
3281
  try {
3155
- const p = path3.join(HOME, ".claude", "settings.json");
3156
- if (!fs3.existsSync(p))
3282
+ const p = path4.join(HOME, ".claude", "settings.json");
3283
+ if (!fs4.existsSync(p))
3157
3284
  return;
3158
- const data = JSON.parse(fs3.readFileSync(p, "utf-8"));
3285
+ const data = JSON.parse(fs4.readFileSync(p, "utf-8"));
3159
3286
  if (data && data.env && typeof data.env === "object") {
3160
3287
  for (const [key, val] of Object.entries(data.env)) {
3161
3288
  if (!process.env[key] && typeof val === "string") {
@@ -3168,11 +3295,11 @@ function loadGrepragEnv() {
3168
3295
  }
3169
3296
  loadGrepragEnv();
3170
3297
  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>"}`);
3171
- var WATCHER_LOCK = path3.join(HOME || os.homedir(), ".greprag", "opencode-watch.lock");
3298
+ var WATCHER_LOCK = path4.join(HOME || os.homedir(), ".greprag", "opencode-watch.lock");
3172
3299
  var WATCHER_STALE_MS = 3e4;
3173
3300
  function findGrepragBinary() {
3174
3301
  const override = process.env.GREPRAG_BIN;
3175
- if (override && fs3.existsSync(override))
3302
+ if (override && fs4.existsSync(override))
3176
3303
  return override;
3177
3304
  const binaryName = process.platform === "win32" ? "greprag.cmd" : "greprag";
3178
3305
  try {
@@ -3181,14 +3308,14 @@ function findGrepragBinary() {
3181
3308
  const candidate = line.trim();
3182
3309
  if (!candidate)
3183
3310
  continue;
3184
- if (!fs3.existsSync(candidate))
3311
+ if (!fs4.existsSync(candidate))
3185
3312
  continue;
3186
3313
  if (process.platform === "win32") {
3187
- const ext = path3.extname(candidate).toLowerCase();
3314
+ const ext = path4.extname(candidate).toLowerCase();
3188
3315
  if (ext === ".cmd" || ext === ".exe" || ext === ".bat")
3189
3316
  return candidate;
3190
3317
  const cmdSibling = candidate + ".cmd";
3191
- if (fs3.existsSync(cmdSibling))
3318
+ if (fs4.existsSync(cmdSibling))
3192
3319
  return cmdSibling;
3193
3320
  continue;
3194
3321
  }
@@ -3197,19 +3324,19 @@ function findGrepragBinary() {
3197
3324
  } catch {
3198
3325
  }
3199
3326
  const candidates = process.platform === "win32" ? [
3200
- path3.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
3201
- path3.join(HOME, "AppData", "Roaming", "npm", "greprag"),
3202
- path3.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
3203
- path3.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
3327
+ path4.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
3328
+ path4.join(HOME, "AppData", "Roaming", "npm", "greprag"),
3329
+ path4.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
3330
+ path4.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
3204
3331
  ] : [
3205
3332
  "/usr/local/bin/greprag",
3206
3333
  "/opt/homebrew/bin/greprag",
3207
- path3.join(HOME || "", ".local", "bin", "greprag"),
3208
- path3.join(HOME || "", ".yarn", "bin", "greprag")
3334
+ path4.join(HOME || "", ".local", "bin", "greprag"),
3335
+ path4.join(HOME || "", ".yarn", "bin", "greprag")
3209
3336
  ];
3210
3337
  for (const c of candidates) {
3211
3338
  try {
3212
- if (c && fs3.existsSync(c))
3339
+ if (c && fs4.existsSync(c))
3213
3340
  return c;
3214
3341
  } catch {
3215
3342
  }
@@ -3218,18 +3345,18 @@ function findGrepragBinary() {
3218
3345
  }
3219
3346
  function tryClaimWatcherLock() {
3220
3347
  try {
3221
- const fd = fs3.openSync(WATCHER_LOCK, "wx");
3348
+ const fd = fs4.openSync(WATCHER_LOCK, "wx");
3222
3349
  return { ok: true, fd };
3223
3350
  } catch (err) {
3224
3351
  if (err.code !== "EEXIST") {
3225
3352
  return { ok: false, reason: "lock-create-failed" };
3226
3353
  }
3227
3354
  try {
3228
- const stat = fs3.statSync(WATCHER_LOCK);
3355
+ const stat = fs4.statSync(WATCHER_LOCK);
3229
3356
  const ageMs = Date.now() - stat.mtimeMs;
3230
3357
  let pidAlive = false;
3231
3358
  try {
3232
- const pid = parseInt(fs3.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
3359
+ const pid = parseInt(fs4.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
3233
3360
  pidAlive = pid > 0 && isPidAlive(pid);
3234
3361
  } catch {
3235
3362
  }
@@ -3237,10 +3364,10 @@ function tryClaimWatcherLock() {
3237
3364
  return { ok: false, reason: "busy" };
3238
3365
  }
3239
3366
  try {
3240
- fs3.unlinkSync(WATCHER_LOCK);
3367
+ fs4.unlinkSync(WATCHER_LOCK);
3241
3368
  } catch {
3242
3369
  }
3243
- const fd = fs3.openSync(WATCHER_LOCK, "wx");
3370
+ const fd = fs4.openSync(WATCHER_LOCK, "wx");
3244
3371
  return { ok: true, fd };
3245
3372
  } catch (err2) {
3246
3373
  return { ok: false, reason: "lock-stale-replace-failed" };
@@ -3281,11 +3408,11 @@ function startCaptureWatcher() {
3281
3408
  return;
3282
3409
  }
3283
3410
  try {
3284
- fs3.writeSync(claim.fd, String(process.pid));
3285
- fs3.closeSync(claim.fd);
3411
+ fs4.writeSync(claim.fd, String(process.pid));
3412
+ fs4.closeSync(claim.fd);
3286
3413
  } catch {
3287
3414
  try {
3288
- fs3.unlinkSync(WATCHER_LOCK);
3415
+ fs4.unlinkSync(WATCHER_LOCK);
3289
3416
  } catch {
3290
3417
  }
3291
3418
  return;
@@ -3301,11 +3428,11 @@ function startCaptureWatcher() {
3301
3428
  const claim2 = tryClaimWatcherLock();
3302
3429
  if (claim2.ok) {
3303
3430
  try {
3304
- fs3.writeSync(claim2.fd, String(process.pid));
3305
- fs3.closeSync(claim2.fd);
3431
+ fs4.writeSync(claim2.fd, String(process.pid));
3432
+ fs4.closeSync(claim2.fd);
3306
3433
  } catch {
3307
3434
  try {
3308
- fs3.unlinkSync(WATCHER_LOCK);
3435
+ fs4.unlinkSync(WATCHER_LOCK);
3309
3436
  } catch {
3310
3437
  }
3311
3438
  return false;
@@ -3348,7 +3475,7 @@ function startCaptureWatcher() {
3348
3475
  childStartTime = Date.now();
3349
3476
  dlog(`startCaptureWatcher: spawned 'greprag opencode watch' pid=${child.pid ?? "<none>"}`);
3350
3477
  try {
3351
- fs3.writeFileSync(WATCHER_LOCK, String(child.pid));
3478
+ fs4.writeFileSync(WATCHER_LOCK, String(child.pid));
3352
3479
  } catch {
3353
3480
  }
3354
3481
  child.stderr?.on("data", (chunk2) => {
@@ -3360,9 +3487,9 @@ function startCaptureWatcher() {
3360
3487
  child.on("exit", (code, signal) => {
3361
3488
  dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
3362
3489
  try {
3363
- const current = fs3.readFileSync(WATCHER_LOCK, "utf-8").trim();
3490
+ const current = fs4.readFileSync(WATCHER_LOCK, "utf-8").trim();
3364
3491
  if (current === String(child.pid))
3365
- fs3.unlinkSync(WATCHER_LOCK);
3492
+ fs4.unlinkSync(WATCHER_LOCK);
3366
3493
  } catch {
3367
3494
  }
3368
3495
  currentChild = null;
@@ -3391,7 +3518,7 @@ function startCaptureWatcher() {
3391
3518
  `
3392
3519
  );
3393
3520
  try {
3394
- fs3.unlinkSync(WATCHER_LOCK);
3521
+ fs4.unlinkSync(WATCHER_LOCK);
3395
3522
  } catch {
3396
3523
  }
3397
3524
  return;
@@ -3423,7 +3550,7 @@ function startCaptureWatcher() {
3423
3550
  }, 2e3).unref();
3424
3551
  }
3425
3552
  try {
3426
- fs3.unlinkSync(WATCHER_LOCK);
3553
+ fs4.unlinkSync(WATCHER_LOCK);
3427
3554
  } catch {
3428
3555
  }
3429
3556
  };
@@ -3462,6 +3589,37 @@ var announcedBySession = /* @__PURE__ */ new Set();
3462
3589
  var turnCountBySession = /* @__PURE__ */ new Map();
3463
3590
  var cachedRecap = null;
3464
3591
  var RECAP_CACHE_TTL_MS = 5 * 6e4;
3592
+ var cachedSkillRows = null;
3593
+ var SKILL_ACTIVATION_CACHE_TTL_MS = 5 * 6e4;
3594
+ async function fetchSkillActivation(apiKey, cwd) {
3595
+ const now = Date.now();
3596
+ if (cachedSkillRows && now - cachedSkillRows.fetchedAt < SKILL_ACTIVATION_CACHE_TTL_MS) {
3597
+ return activationFromApiRows({
3598
+ rows: cachedSkillRows.rows,
3599
+ platform: "opencode",
3600
+ cwd,
3601
+ homeDir: HOME
3602
+ });
3603
+ }
3604
+ try {
3605
+ const response = await fetch(`${API_URL}/v1/skillmirror`, {
3606
+ headers: { Authorization: `Bearer ${apiKey}` }
3607
+ });
3608
+ if (!response.ok)
3609
+ return void 0;
3610
+ const data = await response.json();
3611
+ const rows = data.skills || [];
3612
+ cachedSkillRows = { rows, fetchedAt: now };
3613
+ return activationFromApiRows({
3614
+ rows,
3615
+ platform: "opencode",
3616
+ cwd,
3617
+ homeDir: HOME
3618
+ });
3619
+ } catch {
3620
+ return void 0;
3621
+ }
3622
+ }
3465
3623
  function startSessionRelay(sessionId, serverUrl) {
3466
3624
  if (process.env.GREPRAG_OPENCODE_RELAY === "0")
3467
3625
  return;
@@ -3498,11 +3656,11 @@ function startSessionRelay(sessionId, serverUrl) {
3498
3656
  return;
3499
3657
  }
3500
3658
  try {
3501
- fs3.writeSync(claim.fd, String(process.pid));
3502
- fs3.closeSync(claim.fd);
3659
+ fs4.writeSync(claim.fd, String(process.pid));
3660
+ fs4.closeSync(claim.fd);
3503
3661
  } catch {
3504
3662
  try {
3505
- fs3.unlinkSync(lockPath);
3663
+ fs4.unlinkSync(lockPath);
3506
3664
  } catch {
3507
3665
  }
3508
3666
  return;
@@ -3517,11 +3675,11 @@ function startSessionRelay(sessionId, serverUrl) {
3517
3675
  const claim2 = tryClaimRelayLock(lockPath);
3518
3676
  if (claim2.ok) {
3519
3677
  try {
3520
- fs3.writeSync(claim2.fd, String(process.pid));
3521
- fs3.closeSync(claim2.fd);
3678
+ fs4.writeSync(claim2.fd, String(process.pid));
3679
+ fs4.closeSync(claim2.fd);
3522
3680
  } catch {
3523
3681
  try {
3524
- fs3.unlinkSync(lockPath);
3682
+ fs4.unlinkSync(lockPath);
3525
3683
  } catch {
3526
3684
  }
3527
3685
  return false;
@@ -3557,7 +3715,7 @@ function startSessionRelay(sessionId, serverUrl) {
3557
3715
  childStartTime = Date.now();
3558
3716
  dlog(`startSessionRelay: spawned 'greprag opencode relay' pid=${child.pid ?? "<none>"}`);
3559
3717
  try {
3560
- fs3.writeFileSync(lockPath, String(child.pid));
3718
+ fs4.writeFileSync(lockPath, String(child.pid));
3561
3719
  } catch {
3562
3720
  }
3563
3721
  child.stderr?.on("data", (chunk2) => {
@@ -3568,9 +3726,9 @@ function startSessionRelay(sessionId, serverUrl) {
3568
3726
  });
3569
3727
  child.on("exit", (code, signal) => {
3570
3728
  try {
3571
- const current = fs3.readFileSync(lockPath, "utf-8").trim();
3729
+ const current = fs4.readFileSync(lockPath, "utf-8").trim();
3572
3730
  if (current === String(child.pid))
3573
- fs3.unlinkSync(lockPath);
3731
+ fs4.unlinkSync(lockPath);
3574
3732
  } catch {
3575
3733
  }
3576
3734
  currentChild = null;
@@ -3597,7 +3755,7 @@ function startSessionRelay(sessionId, serverUrl) {
3597
3755
  `
3598
3756
  );
3599
3757
  try {
3600
- fs3.unlinkSync(lockPath);
3758
+ fs4.unlinkSync(lockPath);
3601
3759
  } catch {
3602
3760
  }
3603
3761
  return;
@@ -3629,7 +3787,7 @@ function startSessionRelay(sessionId, serverUrl) {
3629
3787
  }, 2e3).unref();
3630
3788
  }
3631
3789
  try {
3632
- fs3.unlinkSync(lockPath);
3790
+ fs4.unlinkSync(lockPath);
3633
3791
  } catch {
3634
3792
  }
3635
3793
  };
@@ -3745,7 +3903,9 @@ var GrepRAGMemoryPlugin = async (ctx) => {
3745
3903
  if (sid && !relayArmedBySession.has(sid)) {
3746
3904
  startSessionRelay(sid, ctx.serverUrl.toString());
3747
3905
  }
3748
- const anchor = sid ? (await resolveSessionContext(sid)).anchor : fallbackAnchor;
3906
+ const sessionContext = sid ? await resolveSessionContext(sid) : { anchor: fallbackAnchor, workingDir: fallbackWorkingDir };
3907
+ const anchor = sessionContext.anchor;
3908
+ const workingDir = sessionContext.workingDir;
3749
3909
  let body;
3750
3910
  if (cachedRecap && cachedRecap.projectId === anchor.projectId && Date.now() - cachedRecap.fetchedAt < RECAP_CACHE_TTL_MS) {
3751
3911
  body = cachedRecap.body;
@@ -3806,7 +3966,8 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
3806
3966
  } catch {
3807
3967
  return [];
3808
3968
  }
3809
- })()
3969
+ })(),
3970
+ mirroredSkills: !announcedBySession.has(sid8) ? await fetchSkillActivation(apiKey, workingDir) : void 0
3810
3971
  });
3811
3972
  if (!announcedBySession.has(sid8)) {
3812
3973
  announcedBySession.add(sid8);
@@ -100,6 +100,7 @@ const opencode_plugin_crush_1 = require("./opencode-plugin-crush");
100
100
  const opencode_interrupt_1 = require("./commands/opencode-interrupt");
101
101
  const procedure_1 = require("./procedure");
102
102
  const opencode_plugin_png_1 = require("./opencode-plugin-png");
103
+ const skill_activation_manifest_1 = require("./skill-activation-manifest");
103
104
  // ============================================================================
104
105
  // File-based debug log. opencode's plugin loader (Bun) does NOT route plugin
105
106
  // stderr into the opencode log file at ~/.local/share/opencode/log/*.log —
@@ -560,6 +561,35 @@ const turnCountBySession = new Map();
560
561
  * every LLM call — small enough to be cheap, big enough to be useful. */
561
562
  let cachedRecap = null;
562
563
  const RECAP_CACHE_TTL_MS = 5 * 60_000;
564
+ let cachedSkillRows = null;
565
+ const SKILL_ACTIVATION_CACHE_TTL_MS = 5 * 60_000;
566
+ async function fetchSkillActivation(apiKey, cwd) {
567
+ const now = Date.now();
568
+ if (cachedSkillRows && now - cachedSkillRows.fetchedAt < SKILL_ACTIVATION_CACHE_TTL_MS) {
569
+ return (0, skill_activation_manifest_1.activationFromApiRows)({
570
+ rows: cachedSkillRows.rows, platform: 'opencode', cwd, homeDir: opencode_plugin_helpers_1.HOME,
571
+ });
572
+ }
573
+ try {
574
+ const response = await fetch(`${API_URL}/v1/skillmirror`, {
575
+ headers: { Authorization: `Bearer ${apiKey}` },
576
+ });
577
+ if (!response.ok)
578
+ return undefined;
579
+ const data = await response.json();
580
+ const rows = data.skills || [];
581
+ cachedSkillRows = { rows, fetchedAt: now };
582
+ return (0, skill_activation_manifest_1.activationFromApiRows)({
583
+ rows,
584
+ platform: 'opencode',
585
+ cwd,
586
+ homeDir: opencode_plugin_helpers_1.HOME,
587
+ });
588
+ }
589
+ catch {
590
+ return undefined;
591
+ }
592
+ }
563
593
  // ============================================================================
564
594
  // Per-session relay spawner — "anytime opencode is opened and being used"
565
595
  // ============================================================================
@@ -895,7 +925,11 @@ const GrepRAGMemoryPlugin = async (ctx) => {
895
925
  // Resolve anchor — the session's directory is canonical,
896
926
  // fallbackAnchor is the closure's boot-time directory (used when sid
897
927
  // is absent)
898
- const anchor = sid ? (await resolveSessionContext(sid)).anchor : fallbackAnchor;
928
+ const sessionContext = sid
929
+ ? await resolveSessionContext(sid)
930
+ : { anchor: fallbackAnchor, workingDir: fallbackWorkingDir };
931
+ const anchor = sessionContext.anchor;
932
+ const workingDir = sessionContext.workingDir;
899
933
  // Recap body is per-project (not per-session) and changes slowly, so
900
934
  // we cache it. Push on every fire so the LLM sees the recap in ALL
901
935
  // turns, not just the first. See module-level `cachedRecap` comment
@@ -975,6 +1009,9 @@ const GrepRAGMemoryPlugin = async (ctx) => {
975
1009
  return [];
976
1010
  }
977
1011
  })(),
1012
+ mirroredSkills: !announcedBySession.has(sid8)
1013
+ ? await fetchSkillActivation(apiKey, workingDir)
1014
+ : undefined,
978
1015
  });
979
1016
  // Announces: inject on first fire per session (static primers)
980
1017
  if (!announcedBySession.has(sid8)) {