greprag 5.78.7 → 5.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hook.js CHANGED
@@ -40,8 +40,10 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  };
41
41
  })();
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.announceCachePath = announceCachePath;
43
44
  const path = __importStar(require("path"));
44
45
  const fs = __importStar(require("fs"));
46
+ const os = __importStar(require("os"));
45
47
  const crypto = __importStar(require("crypto"));
46
48
  const proc_1 = require("./proc");
47
49
  const docptr_refs_1 = require("./docptr-refs");
@@ -1334,6 +1336,43 @@ function grokSidecarHead(short, full) {
1334
1336
  + 'Chip: `greprag load grok-chip-spawn` then `greprag grok spawn`. Helper: spawn_subagent. Parent keeps ONE watch. Peers: `greprag inbox watchers` then `greprag send`.\n'
1335
1337
  + 'If a greprag inbox monitor is already listed, do not start another. Instant exit = already armed.\n\n';
1336
1338
  }
1339
+ /** Where the full, unabridged announce is parked for on-demand reading.
1340
+ * adr: adr/announce-inline-budget.md */
1341
+ function announceCachePath(short) {
1342
+ return path.join(os.homedir(), '.greprag', 'announce', `${short}.md`);
1343
+ }
1344
+ /** Assemble the SessionStart announce so it SURVIVES the harness's inline cap.
1345
+ *
1346
+ * Writes the full text to the announce cache, inlines the highest-value blocks
1347
+ * that fit the budget, and — when anything was left out — appends one line that
1348
+ * NAMES the omitted sections and the command that prints them. The old behavior
1349
+ * emitted all 18.5KB and let the harness silently keep the first 2KB.
1350
+ *
1351
+ * Returns null when there is nothing to announce. */
1352
+ function buildInlineAnnounce(blocks, short) {
1353
+ if (!blocks.length)
1354
+ return null;
1355
+ const full = blocks.map(b => b.text).join('\n\n');
1356
+ if (short) {
1357
+ try {
1358
+ const file = announceCachePath(short);
1359
+ fs.mkdirSync(path.dirname(file), { recursive: true });
1360
+ fs.writeFileSync(file, full, 'utf-8');
1361
+ }
1362
+ catch { /* cache is a convenience; never block session start */ }
1363
+ }
1364
+ // Reserve room for the pointer line before fitting, so adding it can never be
1365
+ // what pushes the payload over the cap.
1366
+ const POINTER_RESERVE = 220;
1367
+ const { kept, droppedIds } = (0, reminder_registry_1.fitAnnounceBudget)(blocks, reminder_registry_1.ANNOUNCE_INLINE_BUDGET, POINTER_RESERVE);
1368
+ if (!droppedIds.length)
1369
+ return kept.map(b => b.text).join('\n\n') || null;
1370
+ const names = droppedIds.join(', ');
1371
+ const pointer = `[greprag announce — ${droppedIds.length} more section(s) not inlined `
1372
+ + `(harness caps SessionStart context at ${reminder_registry_1.ANNOUNCE_INLINE_BUDGET}b): ${names}. `
1373
+ + `Read them with \`greprag announce\`.]`;
1374
+ return [...kept.map(b => b.text), pointer].join('\n\n');
1375
+ }
1337
1376
  function writeRecapOutput(text, mode, grokShort, grokFull) {
1338
1377
  if (grokShort)
1339
1378
  (0, grok_session_1.writeGrokSidecar)(grokShort, grokSidecarHead(grokShort, grokFull) + (text || ''));
@@ -1737,7 +1776,12 @@ async function recap(input, mode = 'plain', opts = {}) {
1737
1776
  // adr: adr/monitor-resilience.md, docs/reminder-interrupt.md
1738
1777
  if (opts.compact)
1739
1778
  announceReg = (0, reminder_registry_1.compactReannounceModules)(announceReg);
1740
- const announceBlock = (0, reminder_registry_1.collectAnnounces)(announceEnv, announceReg).join('\n\n') || null;
1779
+ // Fit the announce to what the harness will actually inline (2048 bytes; see
1780
+ // ANNOUNCE_INLINE_BUDGET). The full text is persisted first and the overflow is
1781
+ // NAMED, so nothing is lost silently — the failure this replaces was an 18.5KB
1782
+ // announce of which only the first ~2KB ever reached a session, with no signal
1783
+ // that the rest existed. adr: adr/announce-inline-budget.md
1784
+ const announceBlock = buildInlineAnnounce((0, reminder_registry_1.collectAnnounceBlocks)(announceEnv, announceReg), (0, session_id_1.truncateSessionId)(input.session_id));
1741
1785
  if (opts.compact) {
1742
1786
  writeRecapOutput(announceBlock ? announceBlock + '\n' : '', mode, grokShort, input.session_id);
1743
1787
  return;
@@ -1773,7 +1817,17 @@ async function recap(input, mode = 'plain', opts = {}) {
1773
1817
  parts.push(announceBlock);
1774
1818
  parts.push('');
1775
1819
  }
1776
- parts.push(body);
1820
+ // The memory recap shares the SAME inline cap as the announce — it is appended
1821
+ // to the very payload the harness truncates. Inline it only if it fits in what
1822
+ // the announce left; otherwise name the command that prints it. Silently
1823
+ // emitting a body that pushes the payload over the cap is what made the whole
1824
+ // announce disappear. adr: adr/announce-inline-budget.md
1825
+ const usedSoFar = parts.join('\n').length;
1826
+ const roomForBody = reminder_registry_1.ANNOUNCE_INLINE_BUDGET - usedSoFar;
1827
+ parts.push(body.length <= roomForBody
1828
+ ? body
1829
+ : '[greprag memory — session recap not inlined (over the SessionStart cap). '
1830
+ + 'Read it with `greprag memory recap`.]');
1777
1831
  writeRecapOutput(parts.join('\n') + '\n', mode, grokShort, input.session_id);
1778
1832
  }
1779
1833
  /** Arm-state detection moved LOCAL (2026-06-04). The former `isSessionArmed`
@@ -2084,12 +2138,11 @@ function validateChip(title, prompt) {
2084
2138
  `Block 1 requires the chip to open with: \`git worktree add .claude/worktrees/<slug> -b chip/<slug>\` ` +
2085
2139
  `then \`cd\` into it. Chips never edit the main checkout.`);
2086
2140
  }
2087
- if (!/\bgreprag\s+send\b/.test(prompt)) {
2088
- violations.push(`prompt missing report-back instruction. ` +
2089
- `Block 2 requires the chip to send a status message via: ` +
2090
- `\`greprag send "<status>: <commit> on chip/<slug>" --to <handle>@greprag.com/<parent-session-id> ` +
2091
- `--from-session <own-session-id> --artifact commit:<hash>\`.`);
2092
- }
2141
+ // NO report-back check. Chips report to the parent with native
2142
+ // `SendMessage to: "<parent-name>"`, which needs no greprag address and no
2143
+ // armed watcher on the parent. Requiring `greprag send` here denied every
2144
+ // correctly-composed native chip. See adr/prespawn-augmentation.md
2145
+ // 2026-09-03 entry.
2093
2146
  return violations;
2094
2147
  }
2095
2148
  /** Validate the chip prompt at the PreToolUse boundary. Validation-only —
@@ -2118,6 +2171,25 @@ function handlePreSpawnCheck(input) {
2118
2171
  },
2119
2172
  }) + '\n');
2120
2173
  }
2174
+ /** A validator that receives no payload has validated nothing. Fail-open is
2175
+ * the worse failure here: the caller trusts the gate, so a silent exit 0
2176
+ * ALLOWS an unchecked chip. Only `pre-spawn-check` is a gate — every other
2177
+ * subcommand is additive (recap/store/notify), so a lost payload there is
2178
+ * correctly a no-op. Exits the process; never returns for the gate case. */
2179
+ function denyOnMissingPayload(subcommand, why) {
2180
+ if (subcommand !== 'pre-spawn-check')
2181
+ return;
2182
+ process.stdout.write(JSON.stringify({
2183
+ hookSpecificOutput: {
2184
+ hookEventName: 'PreToolUse',
2185
+ permissionDecision: 'deny',
2186
+ permissionDecisionReason: `greprag pre-spawn validator received no payload (${why}), so the chip ` +
2187
+ `prompt was never checked. Re-call spawn_task; if this repeats, the ` +
2188
+ `greprag hook is misconfigured — run \`greprag doctor\`.`,
2189
+ },
2190
+ }) + '\n');
2191
+ process.exit(0);
2192
+ }
2121
2193
  /** PreToolUse mechanic dispatcher (Chip B) — `greprag-hook guard`. All real
2122
2194
  * logic lives in ./guard (runGuard); this wrapper resolves the projectId and
2123
2195
  * emits the hook JSON. Hard fail-open posture: any error → emit nothing,
@@ -2202,9 +2274,12 @@ async function main() {
2202
2274
  chunks.push(chunk);
2203
2275
  }
2204
2276
  const raw = Buffer.concat(chunks).toString('utf-8').trim();
2205
- input = (0, hook_runtime_1.normalizeHookInput)(raw ? JSON.parse(raw) : {});
2277
+ if (!raw)
2278
+ denyOnMissingPayload(subcommand, 'empty stdin');
2279
+ input = (0, hook_runtime_1.normalizeHookInput)(JSON.parse(raw));
2206
2280
  }
2207
2281
  catch {
2282
+ denyOnMissingPayload(subcommand, 'unparseable stdin');
2208
2283
  process.exit(0);
2209
2284
  }
2210
2285
  if (!(0, hook_once_1.claimHookOnce)(input.session_id, input.hook_event_name, subcommand, input.turn_id)) {
@@ -2212,7 +2287,12 @@ async function main() {
2212
2287
  }
2213
2288
  const harness = (0, harness_1.inferCurrentHarness)();
2214
2289
  if (subcommand === 'recap') {
2215
- await recap(input, harness === 'grok' ? 'additionalContext' : 'plain', {
2290
+ // ALWAYS additionalContext. Raw stdout is treated by the Claude Code harness
2291
+ // like command output: past ~2KB it spills to a file and injects a preview,
2292
+ // which silently ate ~89% of an 18.7KB announce (Persona sits at byte 10,841).
2293
+ // additionalContext is the documented injection channel. Grok keeps its
2294
+ // sidecar via the platform arg. adr: adr/announce-delivery-channel.md
2295
+ await recap(input, 'additionalContext', {
2216
2296
  platform: harness === 'grok' ? 'grok' : undefined,
2217
2297
  });
2218
2298
  }
package/dist/index.js CHANGED
@@ -299,6 +299,10 @@ function readProjectRegistry() {
299
299
  * desk ensure idempotently spawn a detached desk-line if none is running
300
300
  * desk status is a desk-line up + what local truth would it report now
301
301
  * adr: adr/desk-line-relay.md */
302
+ async function runAnnounce(args) {
303
+ const { runAnnounce: run } = await Promise.resolve().then(() => __importStar(require('./commands/announce')));
304
+ run(args);
305
+ }
302
306
  async function runDesk(args) {
303
307
  ensureEnv(); // load ~/.greprag/.env so GREPRAG_API_KEY is present (self-heals in the detached child)
304
308
  const sub = args[0] || 'status';
@@ -1906,6 +1910,10 @@ async function main() {
1906
1910
  return;
1907
1911
  }
1908
1912
  case 'inbox': return inbox(subArgs);
1913
+ // The full SessionStart announce. The harness inlines only ~2KB of it, so the
1914
+ // overflow is parked here and the inline pointer names this command.
1915
+ // adr: adr/announce-inline-budget.md
1916
+ case 'announce': return runAnnounce(subArgs);
1909
1917
  case 'desk': return runDesk(subArgs);
1910
1918
  case 'email': return (0, email_1.runEmail)(subArgs);
1911
1919
  case 'send': return send(subArgs);
@@ -2183,6 +2183,118 @@ var skillGainAnnounceModule = {
2183
2183
  reminder: () => null
2184
2184
  };
2185
2185
 
2186
+ // src/skill-activation-manifest.ts
2187
+ var fs2 = __toESM(require("fs"));
2188
+ var path2 = __toESM(require("path"));
2189
+ var MAX_NATIVE_SKILL_FILES = 1500;
2190
+ var MAX_SCAN_DEPTH = 7;
2191
+ function homeRoots(homeDir, platform) {
2192
+ if (platform === "claude-code") {
2193
+ return [path2.join(homeDir, ".claude", "skills")];
2194
+ }
2195
+ if (platform === "codex") {
2196
+ return [
2197
+ path2.join(homeDir, ".codex", "skills"),
2198
+ path2.join(homeDir, ".agents", "skills"),
2199
+ path2.join(homeDir, ".codex", "plugins", "cache")
2200
+ ];
2201
+ }
2202
+ return [path2.join(homeDir, ".config", "opencode", "skills")];
2203
+ }
2204
+ function ancestorDirs(cwd) {
2205
+ const dirs = [];
2206
+ let current = path2.resolve(cwd);
2207
+ for (let depth = 0; depth < 16; depth++) {
2208
+ dirs.push(current);
2209
+ const parent = path2.dirname(current);
2210
+ if (parent === current)
2211
+ break;
2212
+ current = parent;
2213
+ }
2214
+ return dirs;
2215
+ }
2216
+ function repoRoots(cwd, platform) {
2217
+ return ancestorDirs(cwd).flatMap((dir) => {
2218
+ if (platform === "claude-code")
2219
+ return [path2.join(dir, ".claude", "skills")];
2220
+ if (platform === "codex") {
2221
+ return [path2.join(dir, ".codex", "skills"), path2.join(dir, ".agents", "skills")];
2222
+ }
2223
+ return [path2.join(dir, ".opencode", "skills")];
2224
+ });
2225
+ }
2226
+ function frontmatterName(content) {
2227
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
2228
+ const name = fm && /(?:^|\r?\n)name:\s*["']?([^\r\n"']+)/.exec(fm[1]);
2229
+ return name?.[1]?.trim() || "";
2230
+ }
2231
+ function readNativeSkillNames(params) {
2232
+ const names = /* @__PURE__ */ new Set();
2233
+ let visited = 0;
2234
+ const walk = (dir, depth) => {
2235
+ if (depth > MAX_SCAN_DEPTH || visited >= MAX_NATIVE_SKILL_FILES)
2236
+ return;
2237
+ let entries;
2238
+ try {
2239
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
2240
+ } catch {
2241
+ return;
2242
+ }
2243
+ for (const entry of entries) {
2244
+ if (visited >= MAX_NATIVE_SKILL_FILES)
2245
+ return;
2246
+ const full = path2.join(dir, entry.name);
2247
+ if (entry.isDirectory()) {
2248
+ walk(full, depth + 1);
2249
+ continue;
2250
+ }
2251
+ if (!entry.isFile() || entry.name.toLowerCase() !== "skill.md")
2252
+ continue;
2253
+ visited++;
2254
+ names.add(path2.basename(path2.dirname(full)).toLowerCase());
2255
+ try {
2256
+ const declared = frontmatterName(fs2.readFileSync(full, "utf8"));
2257
+ if (declared)
2258
+ names.add(declared.toLowerCase());
2259
+ } catch {
2260
+ }
2261
+ }
2262
+ };
2263
+ const roots = [
2264
+ ...homeRoots(params.homeDir, params.platform),
2265
+ ...repoRoots(params.cwd, params.platform)
2266
+ ];
2267
+ for (const root of roots)
2268
+ walk(root, 0);
2269
+ return names;
2270
+ }
2271
+ function buildMirroredSkillActivation(entries, nativeNames) {
2272
+ const valid = entries.filter((entry) => entry.skillName && entry.triggerDescription);
2273
+ if (valid.length === 0)
2274
+ return void 0;
2275
+ const skills = valid.filter((entry) => !nativeNames.has(entry.skillName.toLowerCase()));
2276
+ return { count: valid.length, skills, nativeCount: valid.length - skills.length };
2277
+ }
2278
+ function nativeAdapterInstallCommand(platform, missingNames) {
2279
+ if (missingNames.length === 0)
2280
+ return null;
2281
+ if (platform === "claude-code") {
2282
+ return `greprag skill mirror sync claude-code --only ${[...missingNames].sort().join(",")}`;
2283
+ }
2284
+ if (platform === "codex" || platform === "opencode") {
2285
+ return `greprag skill mirror sync ${platform}`;
2286
+ }
2287
+ return null;
2288
+ }
2289
+ function activationFromApiRows(params) {
2290
+ const entries = params.rows.filter((row) => typeof row.skillName === "string" && !!row.skillName && typeof row.triggerDescription === "string" && !!row.triggerDescription).map((row) => ({
2291
+ skillName: row.skillName,
2292
+ description: typeof row.description === "string" ? row.description : "",
2293
+ triggerDescription: row.triggerDescription
2294
+ }));
2295
+ return buildMirroredSkillActivation(entries, readNativeSkillNames(params));
2296
+ }
2297
+
2186
2298
  // src/commands/skill-mirror-reminder.ts
2187
2299
  var SKILL_ACTIVATION_MANIFEST_MAX_CHARS = 64e3;
2188
2300
  function renderEntry(skill) {
@@ -2191,17 +2303,31 @@ function renderEntry(skill) {
2191
2303
  return `- ${skill.skillName}: ${lines[0]}${lines.slice(1).map((line) => `
2192
2304
  ${line}`).join("")}`;
2193
2305
  }
2194
- function buildSkillMirrorAnnounce(m) {
2306
+ function buildSkillMirrorAnnounce(m, platform) {
2195
2307
  if (!m || m.count <= 0)
2196
2308
  return null;
2197
2309
  if (m.skills.length === 0) {
2198
2310
  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.]";
2199
2311
  }
2312
+ const installCommand = nativeAdapterInstallCommand(
2313
+ platform || "",
2314
+ m.skills.map((s) => s.skillName)
2315
+ );
2316
+ const gapSection = installCommand ? [
2317
+ "### Missing Native Adapters",
2318
+ `${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:`,
2319
+ "",
2320
+ ` ${installCommand}`,
2321
+ "",
2322
+ "Offer that command when the user asks why a skill is missing, or when you needed one of them this session. Until adapters are installed, the trigger rules below are the only way these skills activate.",
2323
+ ""
2324
+ ] : [];
2200
2325
  const header = [
2201
2326
  "## GrepRAG Skills",
2202
2327
  `Slash skills are available through \`greprag load\`; ${m.skills.length} skills need this startup trigger list` + (m.nativeCount > 0 ? ` (${m.nativeCount} already-listed duplicate${m.nativeCount === 1 ? "" : "s"} omitted)` : "") + ".",
2203
2328
  "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.",
2204
2329
  "",
2330
+ ...gapSection,
2205
2331
  "### Trigger Rules",
2206
2332
  "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.",
2207
2333
  "",
@@ -2232,7 +2358,7 @@ var skillMirrorAnnounceModule = {
2232
2358
  // references `greprag load` — the primer establishes it
2233
2359
  detect: (_env) => ({ tier: "silent" }),
2234
2360
  // announce-only
2235
- announce: (env) => buildSkillMirrorAnnounce(env.mirroredSkills),
2361
+ announce: (env) => buildSkillMirrorAnnounce(env.mirroredSkills, env.platform),
2236
2362
  reminder: () => null
2237
2363
  };
2238
2364
 
@@ -2430,23 +2556,23 @@ function collectAnnounces(env, registry = REGISTRY) {
2430
2556
  }
2431
2557
 
2432
2558
  // src/app-settings.ts
2433
- var fs3 = __toESM(require("node:fs"));
2559
+ var fs4 = __toESM(require("node:fs"));
2434
2560
  var os2 = __toESM(require("node:os"));
2435
- var path3 = __toESM(require("node:path"));
2561
+ var path4 = __toESM(require("node:path"));
2436
2562
 
2437
2563
  // src/project-anchor.ts
2438
- var path2 = __toESM(require("path"));
2439
- var fs2 = __toESM(require("fs"));
2564
+ var path3 = __toESM(require("path"));
2565
+ var fs3 = __toESM(require("fs"));
2440
2566
  var crypto2 = __toESM(require("crypto"));
2441
2567
  var os = __toESM(require("os"));
2442
2568
  var ANCHOR_DIR = ".greprag";
2443
2569
  var ANCHOR_FILE = "project.json";
2444
2570
  var LEGACY_ANCHOR_DIR = ".claude";
2445
2571
  function anchorPathIn(dir) {
2446
- return path2.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2572
+ return path3.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2447
2573
  }
2448
2574
  function legacyAnchorPathIn(dir) {
2449
- return path2.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
2575
+ return path3.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
2450
2576
  }
2451
2577
  function globalAnchorPath() {
2452
2578
  return anchorPathIn(os.homedir());
@@ -2457,22 +2583,22 @@ function legacyGlobalAnchorPath() {
2457
2583
  function findExistingAnchor(startDir) {
2458
2584
  const homeAnchor = globalAnchorPath();
2459
2585
  const legacyHomeAnchor = legacyGlobalAnchorPath();
2460
- let dir = path2.resolve(startDir);
2586
+ let dir = path3.resolve(startDir);
2461
2587
  while (true) {
2462
2588
  const candidate = anchorPathIn(dir);
2463
- if (candidate !== homeAnchor && fs2.existsSync(candidate))
2589
+ if (candidate !== homeAnchor && fs3.existsSync(candidate))
2464
2590
  return candidate;
2465
2591
  const legacyCandidate = legacyAnchorPathIn(dir);
2466
- if (legacyCandidate !== legacyHomeAnchor && fs2.existsSync(legacyCandidate))
2592
+ if (legacyCandidate !== legacyHomeAnchor && fs3.existsSync(legacyCandidate))
2467
2593
  return legacyCandidate;
2468
- const parent = path2.dirname(dir);
2594
+ const parent = path3.dirname(dir);
2469
2595
  if (parent === dir)
2470
2596
  return null;
2471
2597
  dir = parent;
2472
2598
  }
2473
2599
  }
2474
2600
  function isEphemeralCwd2(cwd) {
2475
- const norm = path2.resolve(cwd).replace(/\\/g, "/").toLowerCase();
2601
+ const norm = path3.resolve(cwd).replace(/\\/g, "/").toLowerCase();
2476
2602
  if (norm.includes("/appdata/roaming/claude/local-agent-mode-sessions/"))
2477
2603
  return true;
2478
2604
  if (norm.includes("/appdata/local/claude/local-agent-mode-sessions/"))
@@ -2510,7 +2636,7 @@ function computeGitDerivedProjectId2(workingDir) {
2510
2636
  }
2511
2637
  }
2512
2638
  function deterministicProjectId2(workingDir) {
2513
- const normalized = path2.resolve(workingDir).toLowerCase();
2639
+ const normalized = path3.resolve(workingDir).toLowerCase();
2514
2640
  const hash = crypto2.createHash("sha256").update(normalized).digest("hex");
2515
2641
  return [
2516
2642
  hash.slice(0, 8),
@@ -2524,7 +2650,7 @@ function deterministicProjectId2(workingDir) {
2524
2650
  }
2525
2651
  function tryReadAnchorFileContents(filePath) {
2526
2652
  try {
2527
- const raw = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
2653
+ const raw = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
2528
2654
  const notifyRaw = raw.inbox_notify;
2529
2655
  const inboxNotify = notifyRaw === "off" || notifyRaw === "session_start_only" ? notifyRaw : "every_turn";
2530
2656
  return {
@@ -2563,10 +2689,10 @@ function readAnchor2(cwd) {
2563
2689
  }
2564
2690
  const gitId = computeGitDerivedProjectId2(cwd);
2565
2691
  if (gitId) {
2566
- const root2 = path2.resolve(cwd);
2692
+ const root2 = path3.resolve(cwd);
2567
2693
  return {
2568
2694
  projectId: gitId,
2569
- projectName: fileContents?.projectName || path2.basename(root2).toLowerCase(),
2695
+ projectName: fileContents?.projectName || path3.basename(root2).toLowerCase(),
2570
2696
  initialized: true,
2571
2697
  source: "git",
2572
2698
  anchorPath: existingPath || anchorPathIn(root2),
@@ -2580,7 +2706,7 @@ function readAnchor2(cwd) {
2580
2706
  };
2581
2707
  }
2582
2708
  if (isEphemeralCwd2(cwd)) {
2583
- const globalPath = fs2.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
2709
+ const globalPath = fs3.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
2584
2710
  const globalContents = tryReadAnchorFileContents(globalPath);
2585
2711
  if (globalContents && globalContents.projectId && globalContents.projectName) {
2586
2712
  return {
@@ -2599,10 +2725,10 @@ function readAnchor2(cwd) {
2599
2725
  };
2600
2726
  }
2601
2727
  }
2602
- const root = path2.resolve(cwd);
2728
+ const root = path3.resolve(cwd);
2603
2729
  return {
2604
2730
  projectId: deterministicProjectId2(root),
2605
- projectName: fileContents?.projectName || path2.basename(root).toLowerCase(),
2731
+ projectName: fileContents?.projectName || path3.basename(root).toLowerCase(),
2606
2732
  initialized: false,
2607
2733
  source: "hash",
2608
2734
  anchorPath: existingPath || anchorPathIn(root),
@@ -2619,14 +2745,14 @@ function readAnchor2(cwd) {
2619
2745
  // src/app-settings.ts
2620
2746
  function readJson(file) {
2621
2747
  try {
2622
- const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
2748
+ const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
2623
2749
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2624
2750
  } catch {
2625
2751
  return {};
2626
2752
  }
2627
2753
  }
2628
2754
  function localAppSettingsPath(homeDir = os2.homedir()) {
2629
- return path3.join(homeDir, ".greprag", "settings.json");
2755
+ return path4.join(homeDir, ".greprag", "settings.json");
2630
2756
  }
2631
2757
  function readLocalAppSettings(homeDir = os2.homedir()) {
2632
2758
  const raw = readJson(localAppSettingsPath(homeDir));
@@ -2683,8 +2809,8 @@ function getOpenCodeReminders(env) {
2683
2809
  }
2684
2810
 
2685
2811
  // src/procedure.ts
2686
- var path4 = __toESM(require("path"));
2687
- var fs4 = __toESM(require("fs"));
2812
+ var path5 = __toESM(require("path"));
2813
+ var fs5 = __toESM(require("fs"));
2688
2814
 
2689
2815
  // src/delivery-lifecycle.ts
2690
2816
  var DELIVERY_LIFECYCLE_VERBS = [
@@ -2703,14 +2829,14 @@ function isDeliveryLifecycleVerb(verb) {
2703
2829
  var PROCEDURE_STORE_VERSION = "2";
2704
2830
  function stateDir() {
2705
2831
  const home = process.env.HOME || process.env.USERPROFILE || "";
2706
- return path4.join(home, ".greprag", "state");
2832
+ return path5.join(home, ".greprag", "state");
2707
2833
  }
2708
2834
  function procedureStorePath(projectId) {
2709
- return path4.join(stateDir(), `procedures-${projectId}.json`);
2835
+ return path5.join(stateDir(), `procedures-${projectId}.json`);
2710
2836
  }
2711
2837
  function readProcedureStore(projectId) {
2712
2838
  try {
2713
- const raw = fs4.readFileSync(procedureStorePath(projectId), "utf-8");
2839
+ const raw = fs5.readFileSync(procedureStorePath(projectId), "utf-8");
2714
2840
  const parsed = JSON.parse(raw);
2715
2841
  if (parsed && Array.isArray(parsed.procedures)) {
2716
2842
  return normalizeProcedureStore({
@@ -2944,8 +3070,8 @@ function activeProcedureAnnounces(store) {
2944
3070
  var crypto4 = __toESM(require("crypto"));
2945
3071
 
2946
3072
  // src/procedure-watch.ts
2947
- var path5 = __toESM(require("path"));
2948
- var fs5 = __toESM(require("fs"));
3073
+ var path6 = __toESM(require("path"));
3074
+ var fs6 = __toESM(require("fs"));
2949
3075
  var TIER1_LEARN_TRIGGERS = [
2950
3076
  { verb: "deploy", triggers: ["deploy", "deploy the api", "deploy the worker", "redeploy"], steps: "", status: "seeded" },
2951
3077
  { verb: "push", triggers: ["push", "git push", "push to remote", "push it up", "push upstream"], steps: "", status: "seeded", destructive: true },
@@ -2978,24 +3104,24 @@ function openWatch(verb, phase, openedAt, shadowRunId) {
2978
3104
  }
2979
3105
  function stateDir2() {
2980
3106
  const home = process.env.HOME || process.env.USERPROFILE || "";
2981
- return path5.join(home, ".greprag", "state");
3107
+ return path6.join(home, ".greprag", "state");
2982
3108
  }
2983
3109
  function procedureWatchPath(projectId) {
2984
- return path5.join(stateDir2(), `procedure-watch-${projectId}.json`);
3110
+ return path6.join(stateDir2(), `procedure-watch-${projectId}.json`);
2985
3111
  }
2986
3112
  function hasProcedureWatch(projectId) {
2987
3113
  try {
2988
- return fs5.existsSync(procedureWatchPath(projectId));
3114
+ return fs6.existsSync(procedureWatchPath(projectId));
2989
3115
  } catch {
2990
3116
  return false;
2991
3117
  }
2992
3118
  }
2993
3119
  function writeProcedureWatch(projectId, watch) {
2994
3120
  const file = procedureWatchPath(projectId);
2995
- const dir = path5.dirname(file);
2996
- if (!fs5.existsSync(dir))
2997
- fs5.mkdirSync(dir, { recursive: true });
2998
- fs5.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
3121
+ const dir = path6.dirname(file);
3122
+ if (!fs6.existsSync(dir))
3123
+ fs6.mkdirSync(dir, { recursive: true });
3124
+ fs6.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
2999
3125
  }
3000
3126
  function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
3001
3127
  if (hasProcedureWatch(projectId))
@@ -3006,20 +3132,20 @@ function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
3006
3132
 
3007
3133
  // src/procedure-shadow.ts
3008
3134
  var crypto3 = __toESM(require("crypto"));
3009
- var fs6 = __toESM(require("fs"));
3010
- var path6 = __toESM(require("path"));
3135
+ var fs7 = __toESM(require("fs"));
3136
+ var path7 = __toESM(require("path"));
3011
3137
  function stateDir3() {
3012
3138
  const home = process.env.HOME || process.env.USERPROFILE || "";
3013
- return path6.join(home, ".greprag", "state");
3139
+ return path7.join(home, ".greprag", "state");
3014
3140
  }
3015
3141
  function procedureShadowPath(projectId) {
3016
- return path6.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
3142
+ return path7.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
3017
3143
  }
3018
3144
  function appendShadowEvent(projectId, event) {
3019
3145
  try {
3020
3146
  const file = procedureShadowPath(projectId);
3021
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
3022
- fs6.appendFileSync(file, JSON.stringify(event) + "\n");
3147
+ fs7.mkdirSync(path7.dirname(file), { recursive: true });
3148
+ fs7.appendFileSync(file, JSON.stringify(event) + "\n");
3023
3149
  } catch {
3024
3150
  }
3025
3151
  }
@@ -3738,107 +3864,6 @@ function recodeMessagesToPng(messages, opts) {
3738
3864
  return stats;
3739
3865
  }
3740
3866
 
3741
- // src/skill-activation-manifest.ts
3742
- var fs7 = __toESM(require("fs"));
3743
- var path7 = __toESM(require("path"));
3744
- var MAX_NATIVE_SKILL_FILES = 1500;
3745
- var MAX_SCAN_DEPTH = 7;
3746
- function homeRoots(homeDir, platform) {
3747
- if (platform === "claude-code") {
3748
- return [path7.join(homeDir, ".claude", "skills")];
3749
- }
3750
- if (platform === "codex") {
3751
- return [
3752
- path7.join(homeDir, ".codex", "skills"),
3753
- path7.join(homeDir, ".agents", "skills"),
3754
- path7.join(homeDir, ".codex", "plugins", "cache")
3755
- ];
3756
- }
3757
- return [path7.join(homeDir, ".config", "opencode", "skills")];
3758
- }
3759
- function ancestorDirs(cwd) {
3760
- const dirs = [];
3761
- let current = path7.resolve(cwd);
3762
- for (let depth = 0; depth < 16; depth++) {
3763
- dirs.push(current);
3764
- const parent = path7.dirname(current);
3765
- if (parent === current)
3766
- break;
3767
- current = parent;
3768
- }
3769
- return dirs;
3770
- }
3771
- function repoRoots(cwd, platform) {
3772
- return ancestorDirs(cwd).flatMap((dir) => {
3773
- if (platform === "claude-code")
3774
- return [path7.join(dir, ".claude", "skills")];
3775
- if (platform === "codex") {
3776
- return [path7.join(dir, ".codex", "skills"), path7.join(dir, ".agents", "skills")];
3777
- }
3778
- return [path7.join(dir, ".opencode", "skills")];
3779
- });
3780
- }
3781
- function frontmatterName(content) {
3782
- const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
3783
- const name = fm && /(?:^|\r?\n)name:\s*["']?([^\r\n"']+)/.exec(fm[1]);
3784
- return name?.[1]?.trim() || "";
3785
- }
3786
- function readNativeSkillNames(params) {
3787
- const names = /* @__PURE__ */ new Set();
3788
- let visited = 0;
3789
- const walk = (dir, depth) => {
3790
- if (depth > MAX_SCAN_DEPTH || visited >= MAX_NATIVE_SKILL_FILES)
3791
- return;
3792
- let entries;
3793
- try {
3794
- entries = fs7.readdirSync(dir, { withFileTypes: true });
3795
- } catch {
3796
- return;
3797
- }
3798
- for (const entry of entries) {
3799
- if (visited >= MAX_NATIVE_SKILL_FILES)
3800
- return;
3801
- const full = path7.join(dir, entry.name);
3802
- if (entry.isDirectory()) {
3803
- walk(full, depth + 1);
3804
- continue;
3805
- }
3806
- if (!entry.isFile() || entry.name.toLowerCase() !== "skill.md")
3807
- continue;
3808
- visited++;
3809
- names.add(path7.basename(path7.dirname(full)).toLowerCase());
3810
- try {
3811
- const declared = frontmatterName(fs7.readFileSync(full, "utf8"));
3812
- if (declared)
3813
- names.add(declared.toLowerCase());
3814
- } catch {
3815
- }
3816
- }
3817
- };
3818
- const roots = [
3819
- ...homeRoots(params.homeDir, params.platform),
3820
- ...repoRoots(params.cwd, params.platform)
3821
- ];
3822
- for (const root of roots)
3823
- walk(root, 0);
3824
- return names;
3825
- }
3826
- function buildMirroredSkillActivation(entries, nativeNames) {
3827
- const valid = entries.filter((entry) => entry.skillName && entry.triggerDescription);
3828
- if (valid.length === 0)
3829
- return void 0;
3830
- const skills = valid.filter((entry) => !nativeNames.has(entry.skillName.toLowerCase()));
3831
- return { count: valid.length, skills, nativeCount: valid.length - skills.length };
3832
- }
3833
- function activationFromApiRows(params) {
3834
- const entries = params.rows.filter((row) => typeof row.skillName === "string" && !!row.skillName && typeof row.triggerDescription === "string" && !!row.triggerDescription).map((row) => ({
3835
- skillName: row.skillName,
3836
- description: typeof row.description === "string" ? row.description : "",
3837
- triggerDescription: row.triggerDescription
3838
- }));
3839
- return buildMirroredSkillActivation(entries, readNativeSkillNames(params));
3840
- }
3841
-
3842
3867
  // src/opencode-plugin.ts
3843
3868
  var DEBUG_LOG_PATH = path8.join(os3.homedir(), ".greprag", "opencode-plugin-debug.log");
3844
3869
  var _debugLogReady = false;