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/claude-lifecycle-cutover.js +14 -6
- package/dist/commands/announce.js +97 -0
- package/dist/commands/init.js +14 -1
- package/dist/commands/load.js +21 -1
- package/dist/commands/reminder-registry.js +89 -1
- package/dist/commands/skill-mirror-reminder.js +19 -2
- package/dist/commands/skill.js +33 -5
- package/dist/hook-once.js +12 -0
- package/dist/hook.js +90 -10
- package/dist/index.js +8 -0
- package/dist/opencode-plugin.bundle.js +168 -143
- package/dist/skill-activation-manifest.js +16 -0
- package/package.json +1 -1
- package/skill/templates/chip-spawn.md +15 -30
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2559
|
+
var fs4 = __toESM(require("node:fs"));
|
|
2434
2560
|
var os2 = __toESM(require("node:os"));
|
|
2435
|
-
var
|
|
2561
|
+
var path4 = __toESM(require("node:path"));
|
|
2436
2562
|
|
|
2437
2563
|
// src/project-anchor.ts
|
|
2438
|
-
var
|
|
2439
|
-
var
|
|
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
|
|
2572
|
+
return path3.join(dir, ANCHOR_DIR, ANCHOR_FILE);
|
|
2447
2573
|
}
|
|
2448
2574
|
function legacyAnchorPathIn(dir) {
|
|
2449
|
-
return
|
|
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 =
|
|
2586
|
+
let dir = path3.resolve(startDir);
|
|
2461
2587
|
while (true) {
|
|
2462
2588
|
const candidate = anchorPathIn(dir);
|
|
2463
|
-
if (candidate !== homeAnchor &&
|
|
2589
|
+
if (candidate !== homeAnchor && fs3.existsSync(candidate))
|
|
2464
2590
|
return candidate;
|
|
2465
2591
|
const legacyCandidate = legacyAnchorPathIn(dir);
|
|
2466
|
-
if (legacyCandidate !== legacyHomeAnchor &&
|
|
2592
|
+
if (legacyCandidate !== legacyHomeAnchor && fs3.existsSync(legacyCandidate))
|
|
2467
2593
|
return legacyCandidate;
|
|
2468
|
-
const parent =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
2692
|
+
const root2 = path3.resolve(cwd);
|
|
2567
2693
|
return {
|
|
2568
2694
|
projectId: gitId,
|
|
2569
|
-
projectName: fileContents?.projectName ||
|
|
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 =
|
|
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 =
|
|
2728
|
+
const root = path3.resolve(cwd);
|
|
2603
2729
|
return {
|
|
2604
2730
|
projectId: deterministicProjectId2(root),
|
|
2605
|
-
projectName: fileContents?.projectName ||
|
|
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(
|
|
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
|
|
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
|
|
2687
|
-
var
|
|
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
|
|
2832
|
+
return path5.join(home, ".greprag", "state");
|
|
2707
2833
|
}
|
|
2708
2834
|
function procedureStorePath(projectId) {
|
|
2709
|
-
return
|
|
2835
|
+
return path5.join(stateDir(), `procedures-${projectId}.json`);
|
|
2710
2836
|
}
|
|
2711
2837
|
function readProcedureStore(projectId) {
|
|
2712
2838
|
try {
|
|
2713
|
-
const raw =
|
|
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
|
|
2948
|
-
var
|
|
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
|
|
3107
|
+
return path6.join(home, ".greprag", "state");
|
|
2982
3108
|
}
|
|
2983
3109
|
function procedureWatchPath(projectId) {
|
|
2984
|
-
return
|
|
3110
|
+
return path6.join(stateDir2(), `procedure-watch-${projectId}.json`);
|
|
2985
3111
|
}
|
|
2986
3112
|
function hasProcedureWatch(projectId) {
|
|
2987
3113
|
try {
|
|
2988
|
-
return
|
|
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 =
|
|
2996
|
-
if (!
|
|
2997
|
-
|
|
2998
|
-
|
|
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
|
|
3010
|
-
var
|
|
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
|
|
3139
|
+
return path7.join(home, ".greprag", "state");
|
|
3014
3140
|
}
|
|
3015
3141
|
function procedureShadowPath(projectId) {
|
|
3016
|
-
return
|
|
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
|
-
|
|
3022
|
-
|
|
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;
|