greprag 5.83.2 → 5.85.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/commands/agent-doctrine-file.js +5 -128
- package/dist/commands/agents.js +125 -846
- package/dist/commands/app-model.js +1 -1
- package/dist/commands/delivery-reminder.js +1 -1
- package/dist/commands/discord.js +12 -61
- package/dist/commands/inbox-watch.js +0 -13
- package/dist/commands/init.js +17 -29
- package/dist/commands/load.js +15 -35
- package/dist/commands/parity.js +1 -1
- package/dist/commands/reminder-registry.js +1 -8
- package/dist/commands/session.js +2 -2
- package/dist/commands/skill-mirror-reminder.js +1 -19
- package/dist/commands/skill.js +236 -448
- package/dist/hook.js +31 -54
- package/dist/index.js +19 -152
- package/dist/native-adapter-refresh.js +15 -18
- package/dist/native-skill-mirror.js +11 -89
- package/dist/opencode-plugin.bundle.js +82 -282
- package/dist/reminder-injector.js +1 -1
- package/dist/skill-activation-manifest.js +14 -37
- package/dist/skill-git-ingest.js +297 -0
- package/dist/skill-landing.js +7 -173
- package/dist/skill-mirror-client.js +4 -251
- package/dist/skill-staleness.js +3 -6
- package/package.json +1 -1
- package/skill/commander/SKILL.md +10 -12
- package/skill/greprag/SKILL.md +1 -1
- package/skill/greprag/docs/discord-handoff.md +6 -30
- package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
- package/skill/templates/chip-spawn.md +6 -2
- package/skill/templates/delivery.md +16 -0
- package/skill/templates/os.md +1 -1
- package/skill/templates/prompt-audit.md +26 -49
- package/skill/templates/skill-change.md +15 -11
- package/dist/commands/sms.js +0 -215
package/dist/commands/skill.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// `greprag skill` —
|
|
2
|
+
// `greprag skill` — bundled advisor skills, and read access to the skill mirror.
|
|
3
3
|
//
|
|
4
4
|
// The package bundles `greprag/` (the front-door, auto-installed by init)
|
|
5
|
-
// alongside opt-in advisor skills (`
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// alongside opt-in advisor skills (`content-advisor/`, `mechanic/`, future
|
|
6
|
+
// ones). Operators choose which advisors to add to their Claude Code skill
|
|
7
|
+
// registry via this verb instead of having them force-installed on every
|
|
8
|
+
// `npm i -g greprag@<new>`.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// Every other skill lives in git: dotclaude `skills/<name>/` (global) or
|
|
11
|
+
// `<repo>/.claude/skills/<name>/` (project). GrepRAG mirrors the committed
|
|
12
|
+
// files read-only; `skill mirror sync` ingests them. Commands that used to
|
|
13
|
+
// write the mirror (edit, apply, snapshot create/promote, revert) refuse
|
|
14
|
+
// and name the git folder to edit.
|
|
14
15
|
//
|
|
15
16
|
// adr: adr/skill-bundle-shape.md
|
|
17
|
+
// adr: adr/skill-ownership-git.md
|
|
16
18
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
19
|
if (k2 === undefined) k2 = k;
|
|
18
20
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -47,106 +49,85 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
47
49
|
};
|
|
48
50
|
})();
|
|
49
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
-
exports.readOnlyNames = readOnlyNames;
|
|
51
52
|
exports.runSkill = runSkill;
|
|
52
53
|
const fs = __importStar(require("fs"));
|
|
53
54
|
const os = __importStar(require("os"));
|
|
54
55
|
const path = __importStar(require("path"));
|
|
55
|
-
const proc_1 = require("../proc");
|
|
56
|
-
const claude_lifecycle_cutover_1 = require("../claude-lifecycle-cutover");
|
|
57
56
|
const native_skill_mirror_1 = require("../native-skill-mirror");
|
|
58
57
|
const project_anchor_1 = require("../project-anchor");
|
|
59
58
|
const skill_mirror_client_1 = require("../skill-mirror-client");
|
|
60
|
-
const
|
|
59
|
+
const skill_git_ingest_1 = require("../skill-git-ingest");
|
|
61
60
|
const skill_staleness_1 = require("../skill-staleness");
|
|
62
|
-
const
|
|
61
|
+
const skill_landing_1 = require("../skill-landing");
|
|
63
62
|
const CORE_SKILL = 'greprag';
|
|
64
|
-
const
|
|
65
|
-
/** `--only a,b` / `--only=a,b` / repeated `--only a --only b` → skill names. */
|
|
66
|
-
function readOnlyNames(args) {
|
|
67
|
-
const raw = [];
|
|
68
|
-
for (let i = 0; i < args.length; i++) {
|
|
69
|
-
if (args[i] === '--only' && args[i + 1])
|
|
70
|
-
raw.push(args[++i]);
|
|
71
|
-
else if (args[i].startsWith('--only='))
|
|
72
|
-
raw.push(args[i].slice('--only='.length));
|
|
73
|
-
}
|
|
74
|
-
const names = [...new Set(raw.flatMap(value => value.split(',')).map(v => v.trim()).filter(Boolean))];
|
|
75
|
-
const invalid = names.filter(name => !SKILL_NAME_RE.test(name));
|
|
76
|
-
if (invalid.length > 0)
|
|
77
|
-
throw new Error(`--only got invalid skill name(s): ${invalid.join(', ')}`);
|
|
78
|
-
return names;
|
|
79
|
-
}
|
|
80
|
-
const SKILL_HELP = `greprag skill — manage bundled advisor skills
|
|
63
|
+
const SKILL_HELP = `greprag skill — bundled advisor skills and the read-only skill mirror
|
|
81
64
|
|
|
82
65
|
USAGE
|
|
83
66
|
greprag skill list Show bundled skills and install status
|
|
84
67
|
greprag skill install <name> Install a bundled advisor skill
|
|
85
68
|
greprag skill uninstall <name> Remove a previously-installed advisor
|
|
86
|
-
greprag skill
|
|
87
|
-
|
|
88
|
-
greprag skill
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
69
|
+
greprag skill history <name> [--skill-id <id>] [--limit <n>]
|
|
70
|
+
Every mirrored revision: date, source commit, changes
|
|
71
|
+
greprag skill diff <name> <revA> [<revB>]
|
|
72
|
+
Unified diff between two revisions (revB defaults to current)
|
|
73
|
+
greprag skill mirror sync [codex|opencode|all] [--force]
|
|
74
|
+
Ingest committed skills from git into the mirror,
|
|
75
|
+
then refresh Codex/OpenCode launchers
|
|
92
76
|
greprag skill mirror export <name> --to <dir>
|
|
93
|
-
|
|
94
|
-
greprag skill mirror
|
|
95
|
-
Promote edited bytes with stale-write guard
|
|
96
|
-
greprag skill mirror snapshot create <name> --name <snapshot>
|
|
97
|
-
greprag skill mirror snapshot list <name>
|
|
98
|
-
greprag skill mirror snapshot show <name> --name <snapshot>
|
|
77
|
+
Copy the mirrored files into a folder (read only)
|
|
78
|
+
greprag skill mirror snapshot list|show <name> [--name <snapshot>]
|
|
99
79
|
greprag skill mirror snapshot restore <name> --name <snapshot> --to <dir>
|
|
100
|
-
|
|
101
|
-
Promote snapshot bytes as the newest revision
|
|
102
|
-
Named package rollback points
|
|
80
|
+
Archived named snapshots (read only)
|
|
103
81
|
greprag skill source stamp <name> --project <p> --path <rel> [--path <rel>...]
|
|
104
|
-
|
|
105
|
-
|
|
82
|
+
Stamp a source binding into the skill's git SKILL.md
|
|
83
|
+
|
|
84
|
+
WHERE SKILLS LIVE
|
|
85
|
+
Git owns every skill. Edit the folder, commit, and the mirror follows at the
|
|
86
|
+
next session start or \`greprag skill mirror sync\`:
|
|
87
|
+
global ~/.claude/skills/<name>/ (the dotclaude repo)
|
|
88
|
+
project <repo>/.claude/skills/<name>/
|
|
89
|
+
bundled packages/cli/skill/<name>/ (the greprag repo)
|
|
90
|
+
Claude Code reads those folders directly. Codex and OpenCode get launchers
|
|
91
|
+
that run \`greprag load <name>\`, which serves the committed content.
|
|
92
|
+
Uncommitted edits are never mirrored.
|
|
106
93
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
and prints the guarded apply command; \`--no-open\` does the same explicitly.
|
|
120
|
-
• Source stamps are local Git facts. They update the local canonical skill
|
|
121
|
-
file, then mirror to GrepRAG when GREPRAG_API_KEY is configured.`;
|
|
122
|
-
function formatAdoptionLine(prefix, entry) {
|
|
123
|
-
return entry.reason
|
|
124
|
-
? ` ${prefix} ${entry.scope}:${entry.skillName} — ${entry.reason}`
|
|
125
|
-
: ` ${prefix} ${entry.scope}:${entry.skillName}`;
|
|
126
|
-
}
|
|
127
|
-
function printAdoptionDryRun(platform, result) {
|
|
128
|
-
const plan = result.plan;
|
|
129
|
-
const candidateCount = plan.candidates.length;
|
|
130
|
-
const managedCount = plan.managed.length;
|
|
131
|
-
const blockedCount = plan.blocked.length;
|
|
132
|
-
const skippedCount = plan.skipped.length;
|
|
133
|
-
const ignoredCount = plan.ignoredRoots.reduce((sum, root) => sum + root.skillCount, 0);
|
|
134
|
-
console.log(`${platform}: adoption dry-run: ${candidateCount} candidate(s), ${managedCount} managed, ${blockedCount} blocked, ${skippedCount} skipped, ${ignoredCount} ignored in unowned roots`);
|
|
135
|
-
for (const candidate of plan.candidates)
|
|
136
|
-
console.log(formatAdoptionLine('adopt', candidate));
|
|
137
|
-
for (const blocked of plan.blocked)
|
|
138
|
-
console.log(formatAdoptionLine('block', blocked));
|
|
139
|
-
for (const skipped of plan.skipped)
|
|
140
|
-
console.log(formatAdoptionLine('skip ', skipped));
|
|
141
|
-
for (const root of plan.ignoredRoots) {
|
|
142
|
-
console.log(` ignore ${root.skillCount} skill(s) under ${root.root} — ${root.reason}`);
|
|
94
|
+
REMOVED
|
|
95
|
+
skill edit, skill mirror apply, skill mirror snapshot promote|create,
|
|
96
|
+
and skill revert refuse: they wrote the mirror, which is
|
|
97
|
+
now read only. Use git (git log, git diff, git revert) on the skill folder.`;
|
|
98
|
+
/** The current repo's own copy of a skill, if it has one. */
|
|
99
|
+
function projectSkillDir(skill) {
|
|
100
|
+
for (const source of (0, skill_git_ingest_1.gitSkillSources)(process.cwd())) {
|
|
101
|
+
if (source.scope !== 'project')
|
|
102
|
+
continue;
|
|
103
|
+
const dir = path.join(source.repo, ...source.prefix.split('/'), skill);
|
|
104
|
+
if (fs.existsSync(path.join(dir, 'SKILL.md')))
|
|
105
|
+
return { dir, projectName: source.projectName };
|
|
143
106
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/** The folder holding a skill's one true copy: this repo's, else the global one. */
|
|
110
|
+
function gitSkillDir(skill) {
|
|
111
|
+
const project = projectSkillDir(skill);
|
|
112
|
+
if (project)
|
|
113
|
+
return project.dir;
|
|
114
|
+
const global = path.join(os.homedir(), '.claude', 'skills', skill);
|
|
115
|
+
return fs.existsSync(path.join(global, 'SKILL.md')) ? global : null;
|
|
116
|
+
}
|
|
117
|
+
/** Where a skill's one true copy lives, for refusal text. */
|
|
118
|
+
function gitSkillLocation(skill) {
|
|
119
|
+
if (skill_landing_1.BUNDLED_SKILLS.includes(skill))
|
|
120
|
+
return `packages/cli/skill/${skill}/ in the greprag repo`;
|
|
121
|
+
const project = projectSkillDir(skill);
|
|
122
|
+
if (project)
|
|
123
|
+
return `${project.dir} (the ${project.projectName || 'project'} repo)`;
|
|
124
|
+
return `${path.join(os.homedir(), '.claude', 'skills', skill)} (the dotclaude repo)`;
|
|
125
|
+
}
|
|
126
|
+
function refuseMirrorWrite(command, skill) {
|
|
127
|
+
const where = skill && !skill.startsWith('-') ? gitSkillLocation(skill) : 'the skill\'s git folder';
|
|
128
|
+
throw new Error(`Refusing \`greprag skill ${command}\`: the GrepRAG skill mirror is read only; git owns the skill. `
|
|
129
|
+
+ `Edit ${where}, commit, then run \`greprag skill mirror sync\` (session start runs it too). `
|
|
130
|
+
+ 'History and rollback are git log / git revert on that folder.');
|
|
150
131
|
}
|
|
151
132
|
function bundleRoot() {
|
|
152
133
|
// Same resolution as init.ts Step 6 + postinstall.js
|
|
@@ -183,16 +164,6 @@ function getArg(args, name) {
|
|
|
183
164
|
const idx = args.indexOf(name);
|
|
184
165
|
return idx >= 0 ? args[idx + 1] : undefined;
|
|
185
166
|
}
|
|
186
|
-
function getMany(args, name) {
|
|
187
|
-
const out = [];
|
|
188
|
-
for (let i = 0; i < args.length; i++) {
|
|
189
|
-
if (args[i] === name && args[i + 1]) {
|
|
190
|
-
out.push(args[i + 1]);
|
|
191
|
-
i++;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return out;
|
|
195
|
-
}
|
|
196
167
|
function mirrorApiConfig() {
|
|
197
168
|
const apiKey = process.env.GREPRAG_API_KEY || '';
|
|
198
169
|
if (!apiKey)
|
|
@@ -234,18 +205,6 @@ function mirrorLookupQuery(args) {
|
|
|
234
205
|
const qs = params.toString();
|
|
235
206
|
return qs ? `?${qs}` : '';
|
|
236
207
|
}
|
|
237
|
-
function mirrorBodyScope(args) {
|
|
238
|
-
const scope = getArg(args, '--scope');
|
|
239
|
-
const projectId = getArg(args, '--project-id') || getArg(args, '--projectId');
|
|
240
|
-
const projectName = getArg(args, '--project-name') || getArg(args, '--projectName');
|
|
241
|
-
const replaces = getMany(args, '--replaces');
|
|
242
|
-
return {
|
|
243
|
-
...(scope ? { scope } : {}),
|
|
244
|
-
...(projectId ? { projectId } : {}),
|
|
245
|
-
...(projectName ? { projectName } : {}),
|
|
246
|
-
...(replaces.length ? { replaces } : {}),
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
208
|
function validMirrorRelPath(relative) {
|
|
250
209
|
return /^(?!\.{1,2}(\/|$))[A-Za-z0-9._\/-]{1,200}\.md$/.test(relative) && !relative.includes('..');
|
|
251
210
|
}
|
|
@@ -288,12 +247,6 @@ function listMarkdownFiles(root) {
|
|
|
288
247
|
walk(root, '');
|
|
289
248
|
return out;
|
|
290
249
|
}
|
|
291
|
-
function collectSkillPackageOrThrow(root) {
|
|
292
|
-
const files = (0, skill_mirror_client_1.collectSkillFiles)(path.resolve(root));
|
|
293
|
-
if (!files)
|
|
294
|
-
throw new Error(`No valid SKILL.md package found at ${path.resolve(root)}.`);
|
|
295
|
-
return files;
|
|
296
|
-
}
|
|
297
250
|
function fileMap(files) {
|
|
298
251
|
return new Map(files.map(file => [file.path, file.content]));
|
|
299
252
|
}
|
|
@@ -311,38 +264,6 @@ function diffSkillPackages(before, after) {
|
|
|
311
264
|
}
|
|
312
265
|
return diff;
|
|
313
266
|
}
|
|
314
|
-
function printSkillPackageDiff(label, diff) {
|
|
315
|
-
if (!diff.length) {
|
|
316
|
-
console.log(`${label}: no markdown changes`);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
console.log(`${label}:`);
|
|
320
|
-
for (const item of diff)
|
|
321
|
-
console.log(` ${item.status.padEnd(7)} ${item.path}`);
|
|
322
|
-
}
|
|
323
|
-
function shellArg(value) {
|
|
324
|
-
return /^[A-Za-z0-9._:\/\\-]+$/.test(value)
|
|
325
|
-
? value
|
|
326
|
-
: `"${value.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
327
|
-
}
|
|
328
|
-
function forwardedMirrorArgs(args) {
|
|
329
|
-
const forwarded = new Set([
|
|
330
|
-
'--skill-id', '--skillId',
|
|
331
|
-
'--scope',
|
|
332
|
-
'--project-id', '--projectId',
|
|
333
|
-
'--project-name', '--projectName',
|
|
334
|
-
'--replaces',
|
|
335
|
-
]);
|
|
336
|
-
const parts = [];
|
|
337
|
-
for (let i = 1; i < args.length; i++) {
|
|
338
|
-
const arg = args[i];
|
|
339
|
-
if (!forwarded.has(arg) || !args[i + 1])
|
|
340
|
-
continue;
|
|
341
|
-
parts.push(arg, shellArg(args[i + 1]));
|
|
342
|
-
i++;
|
|
343
|
-
}
|
|
344
|
-
return parts.join(' ');
|
|
345
|
-
}
|
|
346
267
|
/** Returns whether it wrote: under `check` it only lists the plan, so a caller reports success only on true. */
|
|
347
268
|
function writeSkillPackage(targetDir, files, opts) {
|
|
348
269
|
const target = path.resolve(targetDir);
|
|
@@ -368,26 +289,20 @@ function writeSkillPackage(targetDir, files, opts) {
|
|
|
368
289
|
for (const file of files) {
|
|
369
290
|
const destination = safePackagePath(target, file.path);
|
|
370
291
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
371
|
-
fs.writeFileSync(destination, file.content
|
|
292
|
+
fs.writeFileSync(destination, file.content, 'utf8');
|
|
372
293
|
}
|
|
373
294
|
return true;
|
|
374
295
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
: local.get(relative) === remote.get(relative) ? 'same'
|
|
386
|
-
: 'changed';
|
|
387
|
-
if (status !== 'same')
|
|
388
|
-
console.error(` ${status.padEnd(11)} ${relative}`);
|
|
389
|
-
}
|
|
390
|
-
console.error(`Export current mirror: greprag skill mirror export ${params.skill} --to <dir> --force`);
|
|
296
|
+
/** Read the folder back and fail unless it holds exactly the files the mirror
|
|
297
|
+
* sent, byte for byte. Catches a partial write and any file the package
|
|
298
|
+
* reader would skip (size or count limits). */
|
|
299
|
+
function verifyWrittenPackage(targetDir, files) {
|
|
300
|
+
const written = (0, skill_mirror_client_1.collectSkillFiles)(path.resolve(targetDir)) || [];
|
|
301
|
+
const diff = diffSkillPackages(files, written);
|
|
302
|
+
if (!diff.length)
|
|
303
|
+
return;
|
|
304
|
+
const detail = diff.map(item => ` ${item.status === 'removed' ? 'missing' : item.status} ${item.path}`).join('\n');
|
|
305
|
+
throw new Error(`export did not write the revision it fetched to ${path.resolve(targetDir)}:\n${detail}`);
|
|
391
306
|
}
|
|
392
307
|
async function runSkillMirrorExport(args) {
|
|
393
308
|
const name = args[0];
|
|
@@ -401,142 +316,8 @@ async function runSkillMirrorExport(args) {
|
|
|
401
316
|
throw new Error(`No mirrored files returned for ${name}.`);
|
|
402
317
|
if (!writeSkillPackage(to, files, { force: args.includes('--force'), check: args.includes('--check'), exact: true }))
|
|
403
318
|
return;
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
async function loadMirrorPackage(skill, args) {
|
|
407
|
-
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}${mirrorLookupQuery(args)}`);
|
|
408
|
-
const files = (data.files || [])
|
|
409
|
-
.filter((file) => typeof file.path === 'string' && typeof file.content === 'string');
|
|
410
|
-
if (!files.length)
|
|
411
|
-
throw new Error(`No mirrored files returned for ${skill}.`);
|
|
412
|
-
return {
|
|
413
|
-
files,
|
|
414
|
-
revision: Number(data.revision || 0),
|
|
415
|
-
contentHash: typeof data.contentHash === 'string' ? data.contentHash : (0, skill_mirror_client_1.computeMirrorHash)(files),
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
function editorCommand() {
|
|
419
|
-
return process.env.GREPRAG_EDITOR
|
|
420
|
-
|| process.env.VISUAL
|
|
421
|
-
|| process.env.EDITOR
|
|
422
|
-
|| (process.platform === 'win32' ? 'notepad' : 'vi');
|
|
423
|
-
}
|
|
424
|
-
function codexHarnessDetected() {
|
|
425
|
-
return Boolean(process.env.CODEX_THREAD_ID || process.env.CODEX_CI || process.env.CODEX_SHELL);
|
|
426
|
-
}
|
|
427
|
-
function runEditor(target) {
|
|
428
|
-
const editor = editorCommand();
|
|
429
|
-
const result = (0, proc_1.safeSpawnSync)(editor, [target], { stdio: 'inherit', shell: true });
|
|
430
|
-
if (result.error)
|
|
431
|
-
throw new Error(`editor failed: ${result.error.message}`);
|
|
432
|
-
if (typeof result.status === 'number' && result.status !== 0) {
|
|
433
|
-
throw new Error(`editor exited with status ${result.status}`);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
/** A skill the CLI ships as its own bundle has one canon, the bundle, and
|
|
437
|
-
* `greprag load` serves it for that skill's global id. A mirror write under
|
|
438
|
-
* its name would land in a row nothing reads. */
|
|
439
|
-
function refuseBundledSkillWrite(skill) {
|
|
440
|
-
if (!(0, load_1.bundleOwnsSkill)(skill))
|
|
441
|
-
return;
|
|
442
|
-
throw new Error(`${skill} ships with the greprag CLI, so its canon is the bundle, not the skill mirror. Edit packages/cli/skill/${skill}/ in the greprag repo.`);
|
|
443
|
-
}
|
|
444
|
-
async function applySkillPackage(params) {
|
|
445
|
-
refuseBundledSkillWrite(params.skill);
|
|
446
|
-
const hash = (0, skill_mirror_client_1.computeMirrorHash)(params.files);
|
|
447
|
-
const body = {
|
|
448
|
-
files: params.files,
|
|
449
|
-
contentHash: hash,
|
|
450
|
-
...mirrorBodyScope(params.args),
|
|
451
|
-
expectedHash: params.expectedHash,
|
|
452
|
-
...(typeof params.expectedRevision === 'number' ? { expectedRevision: params.expectedRevision } : {}),
|
|
453
|
-
};
|
|
454
|
-
return mirrorApiJson('POST', `/v1/skillmirror/${encodeURIComponent(params.skill)}`, body);
|
|
455
|
-
}
|
|
456
|
-
async function assertCurrentMirrorHash(params) {
|
|
457
|
-
try {
|
|
458
|
-
const current = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(params.skill)}${mirrorLookupQuery(params.args)}`);
|
|
459
|
-
const currentHash = typeof current.contentHash === 'string' ? current.contentHash : '';
|
|
460
|
-
if (currentHash && currentHash !== params.expectedHash) {
|
|
461
|
-
const remoteFiles = (current.files || [])
|
|
462
|
-
.filter((file) => typeof file.path === 'string' && typeof file.content === 'string');
|
|
463
|
-
printSkillMirrorConflict({
|
|
464
|
-
skill: params.skill,
|
|
465
|
-
expectedHash: params.expectedHash,
|
|
466
|
-
currentHash,
|
|
467
|
-
localFiles: params.localFiles,
|
|
468
|
-
remoteFiles,
|
|
469
|
-
});
|
|
470
|
-
throw new Error('Refusing stale apply; re-export or merge against the current mirror.');
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
catch (err) {
|
|
474
|
-
if (!(err instanceof Error) || !/HTTP 404\b/.test(err.message))
|
|
475
|
-
throw err;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
async function runSkillEdit(args) {
|
|
479
|
-
const skill = args[0];
|
|
480
|
-
if (!skill)
|
|
481
|
-
throw new Error('Usage: greprag skill edit <name> [--dir <dir>] [--no-open] [--force] [--keep]');
|
|
482
|
-
refuseBundledSkillWrite(skill);
|
|
483
|
-
const explicitDir = getArg(args, '--dir') || getArg(args, '--to');
|
|
484
|
-
const noOpen = args.includes('--no-open');
|
|
485
|
-
const keep = args.includes('--keep') || Boolean(explicitDir);
|
|
486
|
-
const workDir = explicitDir
|
|
487
|
-
? path.resolve(explicitDir)
|
|
488
|
-
: fs.mkdtempSync(path.join(os.tmpdir(), `greprag-skill-edit-${skill}-`));
|
|
489
|
-
const loaded = await loadMirrorPackage(skill, args);
|
|
490
|
-
writeSkillPackage(workDir, loaded.files, {
|
|
491
|
-
force: args.includes('--force') || !explicitDir,
|
|
492
|
-
exact: true,
|
|
493
|
-
});
|
|
494
|
-
console.log(`Editing ${skill} revision ${loaded.revision} (${loaded.contentHash})`);
|
|
495
|
-
console.log(`Work dir: ${workDir}`);
|
|
496
|
-
const skipEditor = noOpen || codexHarnessDetected();
|
|
497
|
-
if (skipEditor) {
|
|
498
|
-
console.log(noOpen
|
|
499
|
-
? 'Editor skipped.'
|
|
500
|
-
: 'Codex harness detected; editor skipped to avoid launching an interactive editor.');
|
|
501
|
-
const forward = forwardedMirrorArgs(args);
|
|
502
|
-
console.log(`Apply after editing: greprag skill mirror apply ${shellArg(workDir)} --skill ${skill}${forward ? ` ${forward}` : ''} --expected-hash ${loaded.contentHash}`);
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
runEditor(path.join(workDir, 'SKILL.md'));
|
|
506
|
-
const edited = collectSkillPackageOrThrow(workDir);
|
|
507
|
-
const diff = diffSkillPackages(loaded.files, edited);
|
|
508
|
-
printSkillPackageDiff('Diff', diff);
|
|
509
|
-
if (!diff.length) {
|
|
510
|
-
if (!keep)
|
|
511
|
-
fs.rmSync(workDir, { recursive: true, force: true });
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
await assertCurrentMirrorHash({ skill, expectedHash: loaded.contentHash, localFiles: edited, args });
|
|
515
|
-
const data = await applySkillPackage({ skill, files: edited, expectedHash: loaded.contentHash, args });
|
|
516
|
-
console.log(`Applied ${skill}: revision ${Number(data.revision || 0)} (${String(data.contentHash || (0, skill_mirror_client_1.computeMirrorHash)(edited))})`);
|
|
517
|
-
if (!keep)
|
|
518
|
-
fs.rmSync(workDir, { recursive: true, force: true });
|
|
519
|
-
}
|
|
520
|
-
async function runSkillMirrorApply(args) {
|
|
521
|
-
const dir = args[0];
|
|
522
|
-
const skill = getArg(args, '--skill') || (dir ? path.basename(path.resolve(dir)) : '');
|
|
523
|
-
if (!dir || !skill)
|
|
524
|
-
throw new Error('Usage: greprag skill mirror apply <dir> --skill <name> --expected-hash <hash>');
|
|
525
|
-
const expectedRevisionRaw = getArg(args, '--expected-revision') || getArg(args, '--expectedRevision');
|
|
526
|
-
const expectedHash = getArg(args, '--expected-hash') || getArg(args, '--expectedHash');
|
|
527
|
-
if (!expectedHash) {
|
|
528
|
-
throw new Error('Refusing unguarded apply; export first, then pass the exported --expected-hash <hash>.');
|
|
529
|
-
}
|
|
530
|
-
const files = collectSkillPackageOrThrow(dir);
|
|
531
|
-
await assertCurrentMirrorHash({ skill, expectedHash, localFiles: files, args });
|
|
532
|
-
const data = await applySkillPackage({
|
|
533
|
-
skill,
|
|
534
|
-
files,
|
|
535
|
-
expectedHash,
|
|
536
|
-
expectedRevision: expectedRevisionRaw ? Number(expectedRevisionRaw) : undefined,
|
|
537
|
-
args,
|
|
538
|
-
});
|
|
539
|
-
console.log(`Applied ${skill}: revision ${Number(data.revision || 0)} (${String(data.contentHash || (0, skill_mirror_client_1.computeMirrorHash)(files))})`);
|
|
319
|
+
verifyWrittenPackage(to, files);
|
|
320
|
+
console.log(`Exported ${name} revision ${Number(data.revision || 0)} (${String(data.contentHash || '')}) — a read-only copy; edit ${gitSkillLocation(name)} instead.`);
|
|
540
321
|
}
|
|
541
322
|
function snapshotNameArg(args) {
|
|
542
323
|
const explicit = getArg(args, '--name');
|
|
@@ -561,9 +342,10 @@ async function runSkillMirrorSnapshot(args) {
|
|
|
561
342
|
const verb = args[0];
|
|
562
343
|
const skill = args[1];
|
|
563
344
|
const rest = args.slice(2);
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
345
|
+
if (verb === 'promote' || verb === 'create')
|
|
346
|
+
refuseMirrorWrite(`mirror snapshot ${verb}`, skill);
|
|
347
|
+
if (!verb || !skill)
|
|
348
|
+
throw new Error('Usage: greprag skill mirror snapshot list|show|restore <skill> [--name <snapshot>]');
|
|
567
349
|
if (verb === 'list') {
|
|
568
350
|
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}/snapshots${mirrorLookupQuery(rest)}`);
|
|
569
351
|
const snapshots = data.snapshots || [];
|
|
@@ -579,17 +361,6 @@ async function runSkillMirrorSnapshot(args) {
|
|
|
579
361
|
const name = snapshotNameArg(rest);
|
|
580
362
|
if (!name)
|
|
581
363
|
throw new Error(`Usage: greprag skill mirror snapshot ${verb} <skill> --name <snapshot>`);
|
|
582
|
-
if (verb === 'create') {
|
|
583
|
-
const body = {
|
|
584
|
-
...mirrorBodyScope(rest),
|
|
585
|
-
reason: getArg(rest, '--reason'),
|
|
586
|
-
force: rest.includes('--force'),
|
|
587
|
-
};
|
|
588
|
-
const data = await mirrorApiJson('POST', `/v1/skillmirror/${encodeURIComponent(skill)}/snapshots/${encodeURIComponent(name)}`, body);
|
|
589
|
-
const snap = data.snapshot;
|
|
590
|
-
console.log(`Snapshot ${String(snap.snapshotName || name)} stored for ${skill}: revision ${Number(snap.revision || 0)}`);
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
364
|
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}/snapshots/${encodeURIComponent(name)}${mirrorLookupQuery(rest)}`);
|
|
594
365
|
const snapshot = data.snapshot;
|
|
595
366
|
if (verb === 'show') {
|
|
@@ -610,30 +381,140 @@ async function runSkillMirrorSnapshot(args) {
|
|
|
610
381
|
const files = snapshotPresentFiles(snapshot);
|
|
611
382
|
if (!writeSkillPackage(to, files, { force: rest.includes('--force'), check: rest.includes('--check'), exact: true }))
|
|
612
383
|
return;
|
|
613
|
-
console.log(`Restored snapshot ${name} for ${skill} to ${path.resolve(to)}.
|
|
384
|
+
console.log(`Restored snapshot ${name} for ${skill} to ${path.resolve(to)}. To make it current, copy what you need into ${gitSkillLocation(skill)} and commit.`);
|
|
614
385
|
return;
|
|
615
386
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
387
|
+
throw new Error('Usage: greprag skill mirror snapshot list|show|restore <skill> [--name <snapshot>]');
|
|
388
|
+
}
|
|
389
|
+
async function runSkillMirrorSync(args) {
|
|
390
|
+
if (args.includes('--cutover-lifecycle') || args.some(arg => arg === '--only' || arg.startsWith('--only='))) {
|
|
391
|
+
throw new Error('Claude Code reads skills from their git folders directly; there are no Claude Code adapters to install.');
|
|
392
|
+
}
|
|
393
|
+
const requested = args.find(arg => !arg.startsWith('-')) || 'all';
|
|
394
|
+
if (requested === 'claude-code') {
|
|
395
|
+
console.log('claude-code: no adapters; Claude Code reads ~/.claude/skills and <repo>/.claude/skills from git.');
|
|
396
|
+
}
|
|
397
|
+
const platforms = requested === 'all' || requested === 'claude-code'
|
|
398
|
+
? [...native_skill_mirror_1.NATIVE_SKILL_ADAPTER_PLATFORMS]
|
|
399
|
+
: native_skill_mirror_1.NATIVE_SKILL_ADAPTER_PLATFORMS.includes(requested)
|
|
400
|
+
? [requested]
|
|
401
|
+
: [];
|
|
402
|
+
if (!platforms.length)
|
|
403
|
+
throw new Error('Harness must be codex, opencode, claude-code or all.');
|
|
404
|
+
const { apiUrl, apiKey } = mirrorApiConfig();
|
|
405
|
+
const sources = (0, skill_git_ingest_1.gitSkillSources)(process.cwd());
|
|
406
|
+
const projectId = sources.find(source => source.scope === 'project')?.projectId;
|
|
407
|
+
if (args.includes('--dry-run')) {
|
|
408
|
+
for (const source of sources)
|
|
409
|
+
console.log(`would ingest committed skills from ${source.repo} ${source.prefix}/ (${source.scope})`);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const hashes = await (0, skill_git_ingest_1.fetchMirrorHashes)(apiUrl, apiKey, projectId);
|
|
413
|
+
const ingest = await (0, skill_git_ingest_1.ingestCommittedSkills)({ apiUrl, apiKey, sources, mirrorHashes: hashes, force: args.includes('--force') });
|
|
414
|
+
console.log(`mirror: ${ingest.uploaded.length} skill(s) ingested from git${ingest.uploaded.length ? ` (${ingest.uploaded.join(', ')})` : ''}, ${ingest.current} current`);
|
|
415
|
+
for (const failure of ingest.failed)
|
|
416
|
+
console.error(`mirror: ${failure.name} failed — ${failure.error}`);
|
|
417
|
+
if (requested === 'claude-code')
|
|
418
|
+
return;
|
|
419
|
+
const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
|
|
420
|
+
for (const platform of platforms) {
|
|
421
|
+
const result = await (0, native_skill_mirror_1.syncNativeSkillMirror)({
|
|
422
|
+
platform, apiUrl, apiKey,
|
|
423
|
+
projectId: anchor.projectId,
|
|
424
|
+
projectDir: process.cwd(),
|
|
425
|
+
});
|
|
426
|
+
console.log(`${platform}: ${(0, native_skill_mirror_1.nativeSkillMirrorSummary)(result)}`);
|
|
427
|
+
}
|
|
428
|
+
console.log('Restart open Codex/OpenCode sessions to refresh their native skill manifest.');
|
|
429
|
+
}
|
|
430
|
+
// ---------- Revision history: history / diff (read only) ------------------------
|
|
431
|
+
// adr: adr/skill-revision-history.md
|
|
432
|
+
function revisionArg(raw, label) {
|
|
433
|
+
const value = Number(String(raw || '').replace(/^r/i, ''));
|
|
434
|
+
if (!raw || !Number.isInteger(value) || value < 0)
|
|
435
|
+
throw new Error(`${label} must be a revision number such as 12 or r12`);
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
function positionals(args) {
|
|
439
|
+
const out = [];
|
|
440
|
+
for (let i = 0; i < args.length; i++) {
|
|
441
|
+
if (args[i].startsWith('--')) {
|
|
442
|
+
if (args[i + 1] && !args[i + 1].startsWith('--'))
|
|
443
|
+
i++;
|
|
444
|
+
continue;
|
|
620
445
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
446
|
+
out.push(args[i]);
|
|
447
|
+
}
|
|
448
|
+
return out;
|
|
449
|
+
}
|
|
450
|
+
function authorLabel(author) {
|
|
451
|
+
if (!author)
|
|
452
|
+
return '';
|
|
453
|
+
const parts = [
|
|
454
|
+
typeof author.harness === 'string' ? author.harness : '',
|
|
455
|
+
typeof author.sessionId === 'string' ? author.sessionId.slice(0, 8) : '',
|
|
456
|
+
typeof author.chip === 'string' ? author.chip : '',
|
|
457
|
+
].filter(Boolean);
|
|
458
|
+
const via = typeof author.via === 'string' ? author.via : '';
|
|
459
|
+
return [via, parts.join(' ')].filter(Boolean).join(' · ');
|
|
460
|
+
}
|
|
461
|
+
async function runSkillHistory(args) {
|
|
462
|
+
const skill = args[0];
|
|
463
|
+
if (!skill)
|
|
464
|
+
throw new Error('Usage: greprag skill history <name> [--skill-id <id>] [--limit <n>]');
|
|
465
|
+
const limit = getArg(args, '--limit');
|
|
466
|
+
const qs = mirrorLookupQuery(args);
|
|
467
|
+
const sep = qs ? '&' : '?';
|
|
468
|
+
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}/history${qs}${limit ? `${sep}limit=${encodeURIComponent(limit)}` : ''}`);
|
|
469
|
+
const revisions = data.revisions || [];
|
|
470
|
+
if (!revisions.length) {
|
|
471
|
+
console.log(`No recorded revisions for ${skill}.`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
console.log(`${skill} (${String(data.skillId || '')}) — newest first`);
|
|
475
|
+
console.log(`current: r${String(revisions[0].revision)} ${String(revisions[0].contentHash || '')}`);
|
|
476
|
+
for (const r of revisions) {
|
|
477
|
+
const date = String(r.createdAt || '').replace('T', ' ').slice(0, 16);
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log(`r${String(r.revision).padEnd(4)} ${date} ${String(r.kind).padEnd(8)} ${authorLabel(r.author)}`);
|
|
480
|
+
console.log(` ${String(r.reason || '')}`);
|
|
481
|
+
const labels = r.labels || [];
|
|
482
|
+
if (labels.length)
|
|
483
|
+
console.log(` snapshots: ${labels.join(', ')}`);
|
|
484
|
+
if (typeof r.revertedFrom === 'number')
|
|
485
|
+
console.log(` bytes of r${r.revertedFrom}`);
|
|
486
|
+
const changes = r.changes || [];
|
|
487
|
+
if (r.comparedTo === null || r.comparedTo === undefined) {
|
|
488
|
+
console.log(` first recorded revision (${Number(r.fileCount || 0)} files)`);
|
|
624
489
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
console.log(`Would promote snapshot ${name} for ${skill} with expected hash ${expectedHash}.`);
|
|
628
|
-
printSkillPackageDiff('Snapshot files', diff);
|
|
629
|
-
return;
|
|
490
|
+
else if (!changes.length) {
|
|
491
|
+
console.log(` no file changes vs r${String(r.comparedTo)}`);
|
|
630
492
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
493
|
+
else {
|
|
494
|
+
const files = changes.map(c => `${String(c.status)} ${String(c.path)}`).join(', ');
|
|
495
|
+
console.log(` +${Number(r.added || 0)} -${Number(r.removed || 0)} vs r${String(r.comparedTo)}: ${files}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function runSkillDiff(args) {
|
|
500
|
+
const [skill, revA, revB] = positionals(args);
|
|
501
|
+
if (!skill || !revA)
|
|
502
|
+
throw new Error('Usage: greprag skill diff <name> <revA> [<revB>] [--skill-id <id>]');
|
|
503
|
+
const from = revisionArg(revA, 'revA');
|
|
504
|
+
const to = revB === undefined ? undefined : revisionArg(revB, 'revB');
|
|
505
|
+
const qs = mirrorLookupQuery(args);
|
|
506
|
+
const params = `${qs ? `${qs}&` : '?'}from=${from}${to === undefined ? '' : `&to=${to}`}`;
|
|
507
|
+
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}/diff${params}`);
|
|
508
|
+
const files = data.files || [];
|
|
509
|
+
console.log(`${skill}: r${String(data.from)} → r${String(data.to)}`);
|
|
510
|
+
if (!files.length) {
|
|
511
|
+
console.log('No differences.');
|
|
634
512
|
return;
|
|
635
513
|
}
|
|
636
|
-
|
|
514
|
+
for (const file of files) {
|
|
515
|
+
console.log('');
|
|
516
|
+
console.log(String(file.patch || `${String(file.status)} ${String(file.path)}`));
|
|
517
|
+
}
|
|
637
518
|
}
|
|
638
519
|
async function runSkill(args) {
|
|
639
520
|
const sub = args[0];
|
|
@@ -644,7 +525,13 @@ async function runSkill(args) {
|
|
|
644
525
|
const bundled = listBundled();
|
|
645
526
|
switch (sub) {
|
|
646
527
|
case 'edit':
|
|
647
|
-
|
|
528
|
+
case 'revert':
|
|
529
|
+
refuseMirrorWrite(sub, args[1]);
|
|
530
|
+
case 'history':
|
|
531
|
+
await runSkillHistory(args.slice(1));
|
|
532
|
+
return;
|
|
533
|
+
case 'diff':
|
|
534
|
+
await runSkillDiff(args.slice(1));
|
|
648
535
|
return;
|
|
649
536
|
case 'mirror': {
|
|
650
537
|
const mirrorVerb = args[1] || 'sync';
|
|
@@ -653,139 +540,40 @@ async function runSkill(args) {
|
|
|
653
540
|
return;
|
|
654
541
|
}
|
|
655
542
|
if (mirrorVerb === 'apply') {
|
|
656
|
-
|
|
657
|
-
|
|
543
|
+
const skill = getArg(args, '--skill') || (args[2] ? path.basename(path.resolve(args[2])) : undefined);
|
|
544
|
+
refuseMirrorWrite('mirror apply', skill);
|
|
658
545
|
}
|
|
659
546
|
if (mirrorVerb === 'snapshot') {
|
|
660
547
|
await runSkillMirrorSnapshot(args.slice(2));
|
|
661
548
|
return;
|
|
662
549
|
}
|
|
663
|
-
if (mirrorVerb !== 'sync')
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
const dryRun = args.includes('--dry-run');
|
|
668
|
-
const claudeLifecycleCutover = args.includes('--cutover-lifecycle');
|
|
669
|
-
// `--only a,b` names the mirrored skills to materialize as claude-code
|
|
670
|
-
// adapters — the one command that closes an adapter gap the catalog and
|
|
671
|
-
// the SessionStart announce report, without arming a fleet-wide sweep.
|
|
672
|
-
const onlyNames = readOnlyNames(args.slice(2));
|
|
673
|
-
const positional = args.slice(2).filter((arg, index, all) => !arg.startsWith('-') && all[index - 1] !== '--only');
|
|
674
|
-
const requested = positional[0] || 'all';
|
|
675
|
-
const platforms = requested === 'all'
|
|
676
|
-
? [...native_skill_mirror_1.NATIVE_SKILL_ADAPTER_PLATFORMS]
|
|
677
|
-
: ['claude-code', 'codex', 'opencode'].includes(requested)
|
|
678
|
-
? [requested]
|
|
679
|
-
: [];
|
|
680
|
-
if (platforms.length === 0) {
|
|
681
|
-
console.error('Harness must be claude-code, codex, opencode, or all.');
|
|
682
|
-
process.exit(1);
|
|
683
|
-
}
|
|
684
|
-
if (claudeLifecycleCutover && requested !== 'claude-code') {
|
|
685
|
-
throw new Error('--cutover-lifecycle requires harness claude-code.');
|
|
686
|
-
}
|
|
687
|
-
if (onlyNames.length > 0 && requested !== 'claude-code') {
|
|
688
|
-
throw new Error('--only requires harness claude-code.');
|
|
689
|
-
}
|
|
690
|
-
const claudeTargeted = claudeLifecycleCutover || onlyNames.length > 0;
|
|
691
|
-
const apiUrl = process.env.GREPRAG_API_URL || 'https://api.greprag.com';
|
|
692
|
-
const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
|
|
693
|
-
if (requested === 'all') {
|
|
694
|
-
console.log('claude-code: native skill adapter sync disabled until Claude cutover approval.');
|
|
695
|
-
}
|
|
696
|
-
for (const platform of platforms) {
|
|
697
|
-
if (platform === 'claude-code' && claudeTargeted) {
|
|
698
|
-
const names = onlyNames.length > 0 ? onlyNames : [...claude_lifecycle_cutover_1.CLAUDE_LIFECYCLE_SKILLS];
|
|
699
|
-
if (dryRun) {
|
|
700
|
-
console.log(`claude-code: targeted cutover would manage only ${names.join(', ')}; companion files remain untouched.`);
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
const apiKey = process.env.GREPRAG_API_KEY || '';
|
|
704
|
-
if (!apiKey)
|
|
705
|
-
throw new Error('GREPRAG_API_KEY not set — run `greprag init` first.');
|
|
706
|
-
const result = await (0, claude_lifecycle_cutover_1.syncClaudeLifecycleCutover)({ apiUrl, apiKey, names });
|
|
707
|
-
const active = result.installed + result.updated + result.current;
|
|
708
|
-
console.log(`claude-code: ${active} adapter(s) active (${names.join(', ')}); all other Claude skills remain unmanaged.`);
|
|
709
|
-
if (result.backupDir)
|
|
710
|
-
console.log(`claude-code: replaced prompts backed up at ${result.backupDir}`);
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
if (!(0, native_skill_mirror_1.nativeSkillMirrorEnabled)(platform)) {
|
|
714
|
-
console.log(`${platform}: native skill adapter sync disabled until Claude cutover approval.`);
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
if (dryRun) {
|
|
718
|
-
printAdoptionDryRun(platform, {
|
|
719
|
-
plan: (0, skill_mirror_client_1.dryRunNativeSkillAdoption)({
|
|
720
|
-
platform,
|
|
721
|
-
projectId: anchor.projectId,
|
|
722
|
-
projectName: anchor.projectName,
|
|
723
|
-
projectDir: process.cwd(),
|
|
724
|
-
}),
|
|
725
|
-
adopted: 0,
|
|
726
|
-
blocked: 0,
|
|
727
|
-
skipped: 0,
|
|
728
|
-
});
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
const apiKey = process.env.GREPRAG_API_KEY || '';
|
|
732
|
-
if (!apiKey)
|
|
733
|
-
throw new Error('GREPRAG_API_KEY not set — run `greprag init` first.');
|
|
734
|
-
const adoption = await (0, skill_mirror_client_1.syncNativeSkillAdoptions)({
|
|
735
|
-
platform, apiUrl, apiKey,
|
|
736
|
-
projectId: anchor.projectId,
|
|
737
|
-
projectName: anchor.projectName,
|
|
738
|
-
projectDir: process.cwd(),
|
|
739
|
-
});
|
|
740
|
-
printAdoptionSync(platform, adoption);
|
|
741
|
-
const result = await (0, native_skill_mirror_1.syncNativeSkillMirror)({
|
|
742
|
-
platform, apiUrl, apiKey,
|
|
743
|
-
projectId: anchor.projectId,
|
|
744
|
-
projectDir: process.cwd(),
|
|
745
|
-
});
|
|
746
|
-
console.log(`${platform}: ${(0, native_skill_mirror_1.nativeSkillMirrorSummary)(result)}`);
|
|
747
|
-
}
|
|
748
|
-
if (!dryRun)
|
|
749
|
-
console.log('Restart open harness sessions to refresh their native skill manifest.');
|
|
550
|
+
if (mirrorVerb !== 'sync')
|
|
551
|
+
throw new Error('Usage: greprag skill mirror sync|export|snapshot ...');
|
|
552
|
+
await runSkillMirrorSync(args.slice(2));
|
|
750
553
|
return;
|
|
751
554
|
}
|
|
752
555
|
case 'source': {
|
|
753
|
-
|
|
754
|
-
console.error('Usage: greprag skill source stamp <name> --project <p> --path <rel> [--path <rel>...] [--root <dir>] [--baseline <ref>]');
|
|
755
|
-
process.exit(1);
|
|
756
|
-
}
|
|
556
|
+
const usage = 'Usage: greprag skill source stamp <name> --project <p> --path <rel> [--path <rel>...] [--root <dir>] [--baseline <ref>]';
|
|
757
557
|
const name = args[2];
|
|
758
558
|
const rest = args.slice(3);
|
|
759
559
|
const project = getArg(rest, '--project');
|
|
760
|
-
const paths =
|
|
761
|
-
if (!name || !project || paths.length === 0)
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
766
|
-
const skillDir = (0, skill_landing_1.resolveSkillDir)(name, process.cwd(), homeDir);
|
|
560
|
+
const paths = rest.flatMap((arg, i) => (arg === '--path' && rest[i + 1] ? [rest[i + 1]] : []));
|
|
561
|
+
if (args[1] !== 'stamp' || !name || !project || paths.length === 0)
|
|
562
|
+
throw new Error(usage);
|
|
563
|
+
const skillDir = gitSkillDir(name);
|
|
767
564
|
if (!skillDir)
|
|
768
|
-
throw new Error(`No
|
|
565
|
+
throw new Error(`No git folder with a SKILL.md found for ${name}; looked at ${gitSkillLocation(name)}.`);
|
|
769
566
|
const root = getArg(rest, '--root') || (0, skill_staleness_1.resolveSourceRoot)(project, process.cwd());
|
|
770
567
|
if (!root)
|
|
771
568
|
throw new Error(`No local Git checkout found for source project ${project}.`);
|
|
772
569
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
773
|
-
const before = fs.readFileSync(skillPath, 'utf-8');
|
|
774
570
|
const stamped = (0, skill_staleness_1.stampSourceBinding)({
|
|
775
|
-
skillMd:
|
|
571
|
+
skillMd: fs.readFileSync(skillPath, 'utf-8'), project, root, paths,
|
|
776
572
|
baselineCommit: getArg(rest, '--baseline'),
|
|
777
573
|
});
|
|
778
574
|
fs.writeFileSync(skillPath, stamped.content.endsWith('\n') ? stamped.content : stamped.content + '\n');
|
|
779
|
-
console.log(`Stamped ${name}: ${project} @ ${stamped.binding.baselineCommit.slice(0, 8)} (${stamped.binding.paths.length} paths)`);
|
|
780
|
-
|
|
781
|
-
if (apiKey) {
|
|
782
|
-
const apiUrl = process.env.GREPRAG_API_URL || 'https://api.greprag.com';
|
|
783
|
-
const outcome = await (0, skill_mirror_client_1.maybeMirrorSkill)({ skill: name, cwd: process.cwd(), apiUrl, apiKey });
|
|
784
|
-
console.log(`Mirror: ${outcome}`);
|
|
785
|
-
}
|
|
786
|
-
else {
|
|
787
|
-
console.log('Mirror: skipped (GREPRAG_API_KEY not set; next skill use will retry).');
|
|
788
|
-
}
|
|
575
|
+
console.log(`Stamped ${name}: ${project} @ ${stamped.binding.baselineCommit.slice(0, 8)} (${stamped.binding.paths.length} paths) in ${skillPath}`);
|
|
576
|
+
console.log('Commit it; the mirror picks it up at the next session start or `greprag skill mirror sync`.');
|
|
789
577
|
return;
|
|
790
578
|
}
|
|
791
579
|
case 'list': {
|