greprag 5.84.0 → 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/init.js +17 -29
- package/dist/commands/load.js +14 -36
- package/dist/commands/parity.js +1 -1
- package/dist/commands/skill-mirror-reminder.js +1 -18
- package/dist/commands/skill.js +137 -566
- package/dist/hook.js +30 -54
- package/dist/index.js +5 -5
- package/dist/native-adapter-refresh.js +15 -18
- package/dist/native-skill-mirror.js +11 -92
- package/dist/opencode-plugin.bundle.js +81 -283
- 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 -280
- package/dist/skill-staleness.js +3 -6
- package/package.json +1 -1
- package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
- package/skill/templates/os.md +1 -1
- package/skill/templates/prompt-audit.md +26 -49
- package/skill/templates/skill-change.md +15 -13
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,118 +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 edit <name> --reason "<why>" [--skill-id <id>] [--scope project --project-id <id>]
|
|
87
|
-
Export/edit/apply helper; opens $EDITOR unless --no-open or Codex
|
|
88
69
|
greprag skill history <name> [--skill-id <id>] [--limit <n>]
|
|
89
|
-
Every
|
|
70
|
+
Every mirrored revision: date, source commit, changes
|
|
90
71
|
greprag skill diff <name> <revA> [<revB>]
|
|
91
72
|
Unified diff between two revisions (revB defaults to current)
|
|
92
|
-
greprag skill
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
claude-code --cutover-lifecycle Cut over commit/deploy/release only
|
|
96
|
-
claude-code --only <a,b> Install adapters for named mirrored skills
|
|
97
|
-
(closes the gaps greprag load marks with *)
|
|
73
|
+
greprag skill mirror sync [codex|opencode|all] [--force]
|
|
74
|
+
Ingest committed skills from git into the mirror,
|
|
75
|
+
then refresh Codex/OpenCode launchers
|
|
98
76
|
greprag skill mirror export <name> --to <dir>
|
|
99
|
-
|
|
100
|
-
greprag skill mirror
|
|
101
|
-
Promote edited bytes with stale-write guard;
|
|
102
|
-
the folder must come from export (it records
|
|
103
|
-
the revision it holds), and a missing file
|
|
104
|
-
is refused unless --allow-remove
|
|
105
|
-
greprag skill mirror snapshot create <name> --name <snapshot>
|
|
106
|
-
greprag skill mirror snapshot list <name>
|
|
107
|
-
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>]
|
|
108
79
|
greprag skill mirror snapshot restore <name> --name <snapshot> --to <dir>
|
|
109
|
-
|
|
110
|
-
Promote snapshot bytes as the newest revision
|
|
111
|
-
Named package rollback points
|
|
80
|
+
Archived named snapshots (read only)
|
|
112
81
|
greprag skill source stamp <name> --project <p> --path <rel> [--path <rel>...]
|
|
113
|
-
|
|
114
|
-
Stamp a skill's local source baseline
|
|
82
|
+
Stamp a source binding into the skill's git SKILL.md
|
|
115
83
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
93
|
+
|
|
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 };
|
|
106
|
+
}
|
|
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.');
|
|
162
131
|
}
|
|
163
132
|
function bundleRoot() {
|
|
164
133
|
// Same resolution as init.ts Step 6 + postinstall.js
|
|
@@ -195,16 +164,6 @@ function getArg(args, name) {
|
|
|
195
164
|
const idx = args.indexOf(name);
|
|
196
165
|
return idx >= 0 ? args[idx + 1] : undefined;
|
|
197
166
|
}
|
|
198
|
-
function getMany(args, name) {
|
|
199
|
-
const out = [];
|
|
200
|
-
for (let i = 0; i < args.length; i++) {
|
|
201
|
-
if (args[i] === name && args[i + 1]) {
|
|
202
|
-
out.push(args[i + 1]);
|
|
203
|
-
i++;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return out;
|
|
207
|
-
}
|
|
208
167
|
function mirrorApiConfig() {
|
|
209
168
|
const apiKey = process.env.GREPRAG_API_KEY || '';
|
|
210
169
|
if (!apiKey)
|
|
@@ -246,18 +205,6 @@ function mirrorLookupQuery(args) {
|
|
|
246
205
|
const qs = params.toString();
|
|
247
206
|
return qs ? `?${qs}` : '';
|
|
248
207
|
}
|
|
249
|
-
function mirrorBodyScope(args) {
|
|
250
|
-
const scope = getArg(args, '--scope');
|
|
251
|
-
const projectId = getArg(args, '--project-id') || getArg(args, '--projectId');
|
|
252
|
-
const projectName = getArg(args, '--project-name') || getArg(args, '--projectName');
|
|
253
|
-
const replaces = getMany(args, '--replaces');
|
|
254
|
-
return {
|
|
255
|
-
...(scope ? { scope } : {}),
|
|
256
|
-
...(projectId ? { projectId } : {}),
|
|
257
|
-
...(projectName ? { projectName } : {}),
|
|
258
|
-
...(replaces.length ? { replaces } : {}),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
208
|
function validMirrorRelPath(relative) {
|
|
262
209
|
return /^(?!\.{1,2}(\/|$))[A-Za-z0-9._\/-]{1,200}\.md$/.test(relative) && !relative.includes('..');
|
|
263
210
|
}
|
|
@@ -300,12 +247,6 @@ function listMarkdownFiles(root) {
|
|
|
300
247
|
walk(root, '');
|
|
301
248
|
return out;
|
|
302
249
|
}
|
|
303
|
-
function collectSkillPackageOrThrow(root) {
|
|
304
|
-
const files = (0, skill_mirror_client_1.collectSkillFiles)(path.resolve(root));
|
|
305
|
-
if (!files)
|
|
306
|
-
throw new Error(`No valid SKILL.md package found at ${path.resolve(root)}.`);
|
|
307
|
-
return files;
|
|
308
|
-
}
|
|
309
250
|
function fileMap(files) {
|
|
310
251
|
return new Map(files.map(file => [file.path, file.content]));
|
|
311
252
|
}
|
|
@@ -323,38 +264,6 @@ function diffSkillPackages(before, after) {
|
|
|
323
264
|
}
|
|
324
265
|
return diff;
|
|
325
266
|
}
|
|
326
|
-
function printSkillPackageDiff(label, diff) {
|
|
327
|
-
if (!diff.length) {
|
|
328
|
-
console.log(`${label}: no markdown changes`);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
console.log(`${label}:`);
|
|
332
|
-
for (const item of diff)
|
|
333
|
-
console.log(` ${item.status.padEnd(7)} ${item.path}`);
|
|
334
|
-
}
|
|
335
|
-
function shellArg(value) {
|
|
336
|
-
return /^[A-Za-z0-9._:\/\\-]+$/.test(value)
|
|
337
|
-
? value
|
|
338
|
-
: `"${value.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
339
|
-
}
|
|
340
|
-
function forwardedMirrorArgs(args) {
|
|
341
|
-
const forwarded = new Set([
|
|
342
|
-
'--skill-id', '--skillId',
|
|
343
|
-
'--scope',
|
|
344
|
-
'--project-id', '--projectId',
|
|
345
|
-
'--project-name', '--projectName',
|
|
346
|
-
'--replaces',
|
|
347
|
-
]);
|
|
348
|
-
const parts = [];
|
|
349
|
-
for (let i = 1; i < args.length; i++) {
|
|
350
|
-
const arg = args[i];
|
|
351
|
-
if (!forwarded.has(arg) || !args[i + 1])
|
|
352
|
-
continue;
|
|
353
|
-
parts.push(arg, shellArg(args[i + 1]));
|
|
354
|
-
i++;
|
|
355
|
-
}
|
|
356
|
-
return parts.join(' ');
|
|
357
|
-
}
|
|
358
267
|
/** Returns whether it wrote: under `check` it only lists the plan, so a caller reports success only on true. */
|
|
359
268
|
function writeSkillPackage(targetDir, files, opts) {
|
|
360
269
|
const target = path.resolve(targetDir);
|
|
@@ -384,38 +293,9 @@ function writeSkillPackage(targetDir, files, opts) {
|
|
|
384
293
|
}
|
|
385
294
|
return true;
|
|
386
295
|
}
|
|
387
|
-
/** The record an export leaves beside the files it wrote: which revision the
|
|
388
|
-
* folder holds and the bytes of every file in it. Apply takes its base from
|
|
389
|
-
* here, never from a hash the caller fetched separately, so an old folder
|
|
390
|
-
* cannot be paired with a newer revision's hash and written over it.
|
|
391
|
-
* adr: adr/skill-bundle-shape.md */
|
|
392
|
-
const MIRROR_BASE_FILE = '.greprag-mirror-base.json';
|
|
393
|
-
function fileDigest(content) {
|
|
394
|
-
return (0, skill_mirror_client_1.computeMirrorHash)([{ path: '', content }]);
|
|
395
|
-
}
|
|
396
|
-
function writeMirrorBase(targetDir, base) {
|
|
397
|
-
const record = {
|
|
398
|
-
skill: base.skill,
|
|
399
|
-
revision: base.revision,
|
|
400
|
-
contentHash: base.contentHash,
|
|
401
|
-
files: Object.fromEntries(base.files.map(file => [file.path, fileDigest(file.content)])),
|
|
402
|
-
};
|
|
403
|
-
fs.writeFileSync(path.join(path.resolve(targetDir), MIRROR_BASE_FILE), JSON.stringify(record, null, 2) + '\n', 'utf8');
|
|
404
|
-
}
|
|
405
|
-
function readMirrorBase(dir) {
|
|
406
|
-
try {
|
|
407
|
-
const raw = JSON.parse(fs.readFileSync(path.join(path.resolve(dir), MIRROR_BASE_FILE), 'utf8'));
|
|
408
|
-
if (typeof raw.skill !== 'string' || typeof raw.contentHash !== 'string' || !raw.files || typeof raw.files !== 'object')
|
|
409
|
-
return null;
|
|
410
|
-
return { skill: raw.skill, revision: Number(raw.revision || 0), contentHash: raw.contentHash, files: raw.files };
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
296
|
/** Read the folder back and fail unless it holds exactly the files the mirror
|
|
417
297
|
* sent, byte for byte. Catches a partial write and any file the package
|
|
418
|
-
* reader would skip (size or count limits)
|
|
298
|
+
* reader would skip (size or count limits). */
|
|
419
299
|
function verifyWrittenPackage(targetDir, files) {
|
|
420
300
|
const written = (0, skill_mirror_client_1.collectSkillFiles)(path.resolve(targetDir)) || [];
|
|
421
301
|
const diff = diffSkillPackages(files, written);
|
|
@@ -424,205 +304,20 @@ function verifyWrittenPackage(targetDir, files) {
|
|
|
424
304
|
const detail = diff.map(item => ` ${item.status === 'removed' ? 'missing' : item.status} ${item.path}`).join('\n');
|
|
425
305
|
throw new Error(`export did not write the revision it fetched to ${path.resolve(targetDir)}:\n${detail}`);
|
|
426
306
|
}
|
|
427
|
-
function printSkillMirrorConflict(params) {
|
|
428
|
-
const local = new Map(params.localFiles.map(file => [file.path, file.content]));
|
|
429
|
-
const remote = new Map(params.remoteFiles.map(file => [file.path, file.content]));
|
|
430
|
-
const paths = [...new Set([...local.keys(), ...remote.keys()])].sort();
|
|
431
|
-
console.error(`Conflict: ${params.skill} mirror changed since export.`);
|
|
432
|
-
console.error(` expected: ${params.expectedHash}`);
|
|
433
|
-
console.error(` current: ${params.currentHash}`);
|
|
434
|
-
for (const relative of paths) {
|
|
435
|
-
const status = !local.has(relative) ? 'remote-only'
|
|
436
|
-
: !remote.has(relative) ? 'local-only'
|
|
437
|
-
: local.get(relative) === remote.get(relative) ? 'same'
|
|
438
|
-
: 'changed';
|
|
439
|
-
if (status !== 'same')
|
|
440
|
-
console.error(` ${status.padEnd(11)} ${relative}`);
|
|
441
|
-
}
|
|
442
|
-
console.error(`Export current mirror: greprag skill mirror export ${params.skill} --to <dir> --force`);
|
|
443
|
-
}
|
|
444
307
|
async function runSkillMirrorExport(args) {
|
|
445
308
|
const name = args[0];
|
|
446
309
|
const to = getArg(args, '--to');
|
|
447
310
|
if (!name || !to)
|
|
448
311
|
throw new Error('Usage: greprag skill mirror export <name> --to <dir> [--force] [--check]');
|
|
449
|
-
const
|
|
450
|
-
if (!writeSkillPackage(to, loaded.files, { force: args.includes('--force'), check: args.includes('--check'), exact: true }))
|
|
451
|
-
return;
|
|
452
|
-
verifyWrittenPackage(to, loaded.files);
|
|
453
|
-
writeMirrorBase(to, { skill: name, ...loaded });
|
|
454
|
-
console.log(`Exported ${name} revision ${loaded.revision} (${loaded.contentHash})`);
|
|
455
|
-
}
|
|
456
|
-
async function loadMirrorPackage(skill, args) {
|
|
457
|
-
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}${mirrorLookupQuery(args)}`);
|
|
312
|
+
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(name)}${mirrorLookupQuery(args)}`);
|
|
458
313
|
const files = (data.files || [])
|
|
459
314
|
.filter((file) => typeof file.path === 'string' && typeof file.content === 'string');
|
|
460
315
|
if (!files.length)
|
|
461
|
-
throw new Error(`No mirrored files returned for ${
|
|
462
|
-
|
|
463
|
-
files,
|
|
464
|
-
revision: Number(data.revision || 0),
|
|
465
|
-
contentHash: typeof data.contentHash === 'string' ? data.contentHash : (0, skill_mirror_client_1.computeMirrorHash)(files),
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
function editorCommand() {
|
|
469
|
-
return process.env.GREPRAG_EDITOR
|
|
470
|
-
|| process.env.VISUAL
|
|
471
|
-
|| process.env.EDITOR
|
|
472
|
-
|| (process.platform === 'win32' ? 'notepad' : 'vi');
|
|
473
|
-
}
|
|
474
|
-
function codexHarnessDetected() {
|
|
475
|
-
return Boolean(process.env.CODEX_THREAD_ID || process.env.CODEX_CI || process.env.CODEX_SHELL);
|
|
476
|
-
}
|
|
477
|
-
function runEditor(target) {
|
|
478
|
-
const editor = editorCommand();
|
|
479
|
-
const result = (0, proc_1.safeSpawnSync)(editor, [target], { stdio: 'inherit', shell: true });
|
|
480
|
-
if (result.error)
|
|
481
|
-
throw new Error(`editor failed: ${result.error.message}`);
|
|
482
|
-
if (typeof result.status === 'number' && result.status !== 0) {
|
|
483
|
-
throw new Error(`editor exited with status ${result.status}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
/** A skill the CLI ships as its own bundle has one canon, the bundle, and
|
|
487
|
-
* `greprag load` serves it for that skill's global id. A mirror write under
|
|
488
|
-
* its name would land in a row nothing reads. */
|
|
489
|
-
function refuseBundledSkillWrite(skill) {
|
|
490
|
-
if (!(0, load_1.bundleOwnsSkill)(skill))
|
|
491
|
-
return;
|
|
492
|
-
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.`);
|
|
493
|
-
}
|
|
494
|
-
/** Every canon write carries a one-line reason into the skill's revision
|
|
495
|
-
* history; the server refuses a write without one, so refuse it here first,
|
|
496
|
-
* naming the flag. adr: adr/skill-revision-history.md */
|
|
497
|
-
function requireReason(args, usage) {
|
|
498
|
-
const reason = (getArg(args, '--reason') || '').trim();
|
|
499
|
-
if (!reason || reason.startsWith('--')) {
|
|
500
|
-
throw new Error(`Refusing: --reason "<one line>" is required. GrepRAG records it in the skill's revision history.\nUsage: ${usage}`);
|
|
501
|
-
}
|
|
502
|
-
return reason;
|
|
503
|
-
}
|
|
504
|
-
async function applySkillPackage(params) {
|
|
505
|
-
refuseBundledSkillWrite(params.skill);
|
|
506
|
-
const hash = (0, skill_mirror_client_1.computeMirrorHash)(params.files);
|
|
507
|
-
const body = {
|
|
508
|
-
files: params.files,
|
|
509
|
-
contentHash: hash,
|
|
510
|
-
...mirrorBodyScope(params.args),
|
|
511
|
-
reason: params.reason,
|
|
512
|
-
author: (0, skill_mirror_client_1.skillRevisionAuthor)(params.via),
|
|
513
|
-
expectedHash: params.expectedHash,
|
|
514
|
-
...(typeof params.expectedRevision === 'number' ? { expectedRevision: params.expectedRevision } : {}),
|
|
515
|
-
};
|
|
516
|
-
return mirrorApiJson('POST', `/v1/skillmirror/${encodeURIComponent(params.skill)}`, body);
|
|
517
|
-
}
|
|
518
|
-
async function assertCurrentMirrorHash(params) {
|
|
519
|
-
try {
|
|
520
|
-
const current = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(params.skill)}${mirrorLookupQuery(params.args)}`);
|
|
521
|
-
const currentHash = typeof current.contentHash === 'string' ? current.contentHash : '';
|
|
522
|
-
if (currentHash && currentHash !== params.expectedHash) {
|
|
523
|
-
const remoteFiles = (current.files || [])
|
|
524
|
-
.filter((file) => typeof file.path === 'string' && typeof file.content === 'string');
|
|
525
|
-
printSkillMirrorConflict({
|
|
526
|
-
skill: params.skill,
|
|
527
|
-
expectedHash: params.expectedHash,
|
|
528
|
-
currentHash,
|
|
529
|
-
localFiles: params.localFiles,
|
|
530
|
-
remoteFiles,
|
|
531
|
-
});
|
|
532
|
-
throw new Error('Refusing stale apply; re-export or merge against the current mirror.');
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
catch (err) {
|
|
536
|
-
if (!(err instanceof Error) || !/HTTP 404\b/.test(err.message))
|
|
537
|
-
throw err;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
async function runSkillEdit(args) {
|
|
541
|
-
const skill = args[0];
|
|
542
|
-
const usage = 'greprag skill edit <name> --reason "<why>" [--dir <dir>] [--no-open] [--force] [--keep]';
|
|
543
|
-
if (!skill)
|
|
544
|
-
throw new Error(`Usage: ${usage}`);
|
|
545
|
-
refuseBundledSkillWrite(skill);
|
|
546
|
-
const explicitDir = getArg(args, '--dir') || getArg(args, '--to');
|
|
547
|
-
const noOpen = args.includes('--no-open');
|
|
548
|
-
const skipEditor = noOpen || codexHarnessDetected();
|
|
549
|
-
// The editor path applies in this run, so it needs its reason before export;
|
|
550
|
-
// --no-open only prints the apply command, which carries the reason instead.
|
|
551
|
-
const reason = skipEditor ? (getArg(args, '--reason') || '').trim() : requireReason(args, usage);
|
|
552
|
-
const keep = args.includes('--keep') || Boolean(explicitDir);
|
|
553
|
-
const workDir = explicitDir
|
|
554
|
-
? path.resolve(explicitDir)
|
|
555
|
-
: fs.mkdtempSync(path.join(os.tmpdir(), `greprag-skill-edit-${skill}-`));
|
|
556
|
-
const loaded = await loadMirrorPackage(skill, args);
|
|
557
|
-
writeSkillPackage(workDir, loaded.files, {
|
|
558
|
-
force: args.includes('--force') || !explicitDir,
|
|
559
|
-
exact: true,
|
|
560
|
-
});
|
|
561
|
-
verifyWrittenPackage(workDir, loaded.files);
|
|
562
|
-
writeMirrorBase(workDir, { skill, ...loaded });
|
|
563
|
-
console.log(`Editing ${skill} revision ${loaded.revision} (${loaded.contentHash})`);
|
|
564
|
-
console.log(`Work dir: ${workDir}`);
|
|
565
|
-
if (skipEditor) {
|
|
566
|
-
console.log(noOpen
|
|
567
|
-
? 'Editor skipped.'
|
|
568
|
-
: 'Codex harness detected; editor skipped to avoid launching an interactive editor.');
|
|
569
|
-
const forward = forwardedMirrorArgs(args);
|
|
570
|
-
console.log(`Apply after editing: greprag skill mirror apply ${shellArg(workDir)} --skill ${skill}${forward ? ` ${forward}` : ''} --expected-hash ${loaded.contentHash} --reason ${reason ? shellArg(reason) : '"<why>"'}`);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
runEditor(path.join(workDir, 'SKILL.md'));
|
|
574
|
-
const edited = collectSkillPackageOrThrow(workDir);
|
|
575
|
-
const diff = diffSkillPackages(loaded.files, edited);
|
|
576
|
-
printSkillPackageDiff('Diff', diff);
|
|
577
|
-
if (!diff.length) {
|
|
578
|
-
if (!keep)
|
|
579
|
-
fs.rmSync(workDir, { recursive: true, force: true });
|
|
316
|
+
throw new Error(`No mirrored files returned for ${name}.`);
|
|
317
|
+
if (!writeSkillPackage(to, files, { force: args.includes('--force'), check: args.includes('--check'), exact: true }))
|
|
580
318
|
return;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const data = await applySkillPackage({ skill, files: edited, expectedHash: loaded.contentHash, reason, via: 'skill-edit', args });
|
|
584
|
-
console.log(`Applied ${skill}: revision ${Number(data.revision || 0)} (${String(data.contentHash || (0, skill_mirror_client_1.computeMirrorHash)(edited))})`);
|
|
585
|
-
if (!keep)
|
|
586
|
-
fs.rmSync(workDir, { recursive: true, force: true });
|
|
587
|
-
}
|
|
588
|
-
async function runSkillMirrorApply(args) {
|
|
589
|
-
const dir = args[0];
|
|
590
|
-
const skill = getArg(args, '--skill') || (dir ? path.basename(path.resolve(dir)) : '');
|
|
591
|
-
const usage = 'greprag skill mirror apply <dir> --skill <name> --expected-hash <hash> --reason "<why>"';
|
|
592
|
-
if (!dir || !skill)
|
|
593
|
-
throw new Error(`Usage: ${usage}`);
|
|
594
|
-
refuseBundledSkillWrite(skill);
|
|
595
|
-
const reason = requireReason(args, usage);
|
|
596
|
-
const expectedRevisionRaw = getArg(args, '--expected-revision') || getArg(args, '--expectedRevision');
|
|
597
|
-
const passedHash = getArg(args, '--expected-hash') || getArg(args, '--expectedHash');
|
|
598
|
-
const base = readMirrorBase(dir);
|
|
599
|
-
if (!base) {
|
|
600
|
-
throw new Error(`Refusing apply: ${path.resolve(dir)} has no export record (${MIRROR_BASE_FILE}). Run greprag skill mirror export ${skill} --to <dir>, edit that folder, then apply it.`);
|
|
601
|
-
}
|
|
602
|
-
if (base.skill !== skill) {
|
|
603
|
-
throw new Error(`Refusing apply: ${path.resolve(dir)} was exported from ${base.skill}, not ${skill}.`);
|
|
604
|
-
}
|
|
605
|
-
if (passedHash && passedHash !== base.contentHash) {
|
|
606
|
-
throw new Error(`Refusing apply: this folder was exported at ${base.contentHash} (revision ${base.revision}), but --expected-hash is ${passedHash}. A hash from a different export does not describe these files; export again and redo the edits there.`);
|
|
607
|
-
}
|
|
608
|
-
const expectedHash = base.contentHash;
|
|
609
|
-
const files = collectSkillPackageOrThrow(dir);
|
|
610
|
-
const present = new Set(files.map(file => file.path));
|
|
611
|
-
const missing = Object.keys(base.files).filter(relative => !present.has(relative));
|
|
612
|
-
if (missing.length && !args.includes('--allow-remove')) {
|
|
613
|
-
throw new Error(`Refusing apply: files from the export are missing in ${path.resolve(dir)}:\n${missing.map(relative => ` missing ${relative}`).join('\n')}\nRestore them, or pass --allow-remove if deleting them is intended.`);
|
|
614
|
-
}
|
|
615
|
-
await assertCurrentMirrorHash({ skill, expectedHash, localFiles: files, args });
|
|
616
|
-
const data = await applySkillPackage({
|
|
617
|
-
skill,
|
|
618
|
-
files,
|
|
619
|
-
expectedHash,
|
|
620
|
-
expectedRevision: expectedRevisionRaw ? Number(expectedRevisionRaw) : undefined,
|
|
621
|
-
reason,
|
|
622
|
-
via: 'mirror-apply',
|
|
623
|
-
args,
|
|
624
|
-
});
|
|
625
|
-
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.`);
|
|
626
321
|
}
|
|
627
322
|
function snapshotNameArg(args) {
|
|
628
323
|
const explicit = getArg(args, '--name');
|
|
@@ -647,9 +342,10 @@ async function runSkillMirrorSnapshot(args) {
|
|
|
647
342
|
const verb = args[0];
|
|
648
343
|
const skill = args[1];
|
|
649
344
|
const rest = args.slice(2);
|
|
650
|
-
if (
|
|
651
|
-
|
|
652
|
-
|
|
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>]');
|
|
653
349
|
if (verb === 'list') {
|
|
654
350
|
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}/snapshots${mirrorLookupQuery(rest)}`);
|
|
655
351
|
const snapshots = data.snapshots || [];
|
|
@@ -665,19 +361,6 @@ async function runSkillMirrorSnapshot(args) {
|
|
|
665
361
|
const name = snapshotNameArg(rest);
|
|
666
362
|
if (!name)
|
|
667
363
|
throw new Error(`Usage: greprag skill mirror snapshot ${verb} <skill> --name <snapshot>`);
|
|
668
|
-
if (verb === 'create') {
|
|
669
|
-
const body = {
|
|
670
|
-
...mirrorBodyScope(rest),
|
|
671
|
-
reason: getArg(rest, '--reason'),
|
|
672
|
-
force: rest.includes('--force'),
|
|
673
|
-
};
|
|
674
|
-
const data = await mirrorApiJson('POST', `/v1/skillmirror/${encodeURIComponent(skill)}/snapshots/${encodeURIComponent(name)}`, body);
|
|
675
|
-
const snap = data.snapshot;
|
|
676
|
-
console.log(`Snapshot ${String(snap.snapshotName || name)} stored for ${skill}: revision ${Number(snap.revision || 0)}`);
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
const promoteUsage = 'greprag skill mirror snapshot promote <skill> --name <snapshot> --expected-hash <current-live-hash> --reason "<why>"';
|
|
680
|
-
const promoteReason = verb === 'promote' && !rest.includes('--check') ? requireReason(rest, promoteUsage) : '';
|
|
681
364
|
const data = await mirrorApiJson('GET', `/v1/skillmirror/${encodeURIComponent(skill)}/snapshots/${encodeURIComponent(name)}${mirrorLookupQuery(rest)}`);
|
|
682
365
|
const snapshot = data.snapshot;
|
|
683
366
|
if (verb === 'show') {
|
|
@@ -698,34 +381,53 @@ async function runSkillMirrorSnapshot(args) {
|
|
|
698
381
|
const files = snapshotPresentFiles(snapshot);
|
|
699
382
|
if (!writeSkillPackage(to, files, { force: rest.includes('--force'), check: rest.includes('--check'), exact: true }))
|
|
700
383
|
return;
|
|
701
|
-
console.log(`Restored snapshot ${name} for ${skill} to ${path.resolve(to)}. To make it
|
|
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.`);
|
|
702
385
|
return;
|
|
703
386
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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})`);
|
|
724
410
|
return;
|
|
725
411
|
}
|
|
726
|
-
|
|
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.');
|
|
727
429
|
}
|
|
728
|
-
// ---------- Revision history: history / diff
|
|
430
|
+
// ---------- Revision history: history / diff (read only) ------------------------
|
|
729
431
|
// adr: adr/skill-revision-history.md
|
|
730
432
|
function revisionArg(raw, label) {
|
|
731
433
|
const value = Number(String(raw || '').replace(/^r/i, ''));
|
|
@@ -814,32 +516,6 @@ async function runSkillDiff(args) {
|
|
|
814
516
|
console.log(String(file.patch || `${String(file.status)} ${String(file.path)}`));
|
|
815
517
|
}
|
|
816
518
|
}
|
|
817
|
-
async function runSkillRevert(args) {
|
|
818
|
-
const usage = 'greprag skill revert <name> --to <rev> --reason "<why>" --expected-hash <current-hash> [--skill-id <id>]';
|
|
819
|
-
const skill = args[0];
|
|
820
|
-
if (!skill || skill.startsWith('--'))
|
|
821
|
-
throw new Error(`Usage: ${usage}`);
|
|
822
|
-
refuseBundledSkillWrite(skill);
|
|
823
|
-
const to = revisionArg(getArg(args, '--to'), '--to');
|
|
824
|
-
const reason = requireReason(args, usage);
|
|
825
|
-
const expectedHash = getArg(args, '--expected-hash') || getArg(args, '--expectedHash');
|
|
826
|
-
if (!expectedHash) {
|
|
827
|
-
throw new Error(`Refusing: --expected-hash <current-hash> is required (the hash \`greprag skill history ${skill}\` or the adapter shows for the current revision).\nUsage: ${usage}`);
|
|
828
|
-
}
|
|
829
|
-
const skillId = getArg(args, '--skill-id') || getArg(args, '--skillId');
|
|
830
|
-
const scope = getArg(args, '--scope');
|
|
831
|
-
const projectId = getArg(args, '--project-id') || getArg(args, '--projectId');
|
|
832
|
-
const data = await mirrorApiJson('POST', `/v1/skillmirror/${encodeURIComponent(skill)}/revert`, {
|
|
833
|
-
to,
|
|
834
|
-
reason,
|
|
835
|
-
expectedHash,
|
|
836
|
-
author: (0, skill_mirror_client_1.skillRevisionAuthor)('revert'),
|
|
837
|
-
...(skillId ? { skillId } : {}),
|
|
838
|
-
...(scope ? { scope } : {}),
|
|
839
|
-
...(projectId ? { projectId } : {}),
|
|
840
|
-
});
|
|
841
|
-
console.log(`Reverted ${skill} to the bytes of r${to}: new revision ${Number(data.revision || 0)} (${String(data.contentHash || '')})`);
|
|
842
|
-
}
|
|
843
519
|
async function runSkill(args) {
|
|
844
520
|
const sub = args[0];
|
|
845
521
|
if (!sub || sub === '--help' || sub === '-h' || sub === 'help') {
|
|
@@ -849,17 +525,14 @@ async function runSkill(args) {
|
|
|
849
525
|
const bundled = listBundled();
|
|
850
526
|
switch (sub) {
|
|
851
527
|
case 'edit':
|
|
852
|
-
|
|
853
|
-
|
|
528
|
+
case 'revert':
|
|
529
|
+
refuseMirrorWrite(sub, args[1]);
|
|
854
530
|
case 'history':
|
|
855
531
|
await runSkillHistory(args.slice(1));
|
|
856
532
|
return;
|
|
857
533
|
case 'diff':
|
|
858
534
|
await runSkillDiff(args.slice(1));
|
|
859
535
|
return;
|
|
860
|
-
case 'revert':
|
|
861
|
-
await runSkillRevert(args.slice(1));
|
|
862
|
-
return;
|
|
863
536
|
case 'mirror': {
|
|
864
537
|
const mirrorVerb = args[1] || 'sync';
|
|
865
538
|
if (mirrorVerb === 'export') {
|
|
@@ -867,142 +540,40 @@ async function runSkill(args) {
|
|
|
867
540
|
return;
|
|
868
541
|
}
|
|
869
542
|
if (mirrorVerb === 'apply') {
|
|
870
|
-
|
|
871
|
-
|
|
543
|
+
const skill = getArg(args, '--skill') || (args[2] ? path.basename(path.resolve(args[2])) : undefined);
|
|
544
|
+
refuseMirrorWrite('mirror apply', skill);
|
|
872
545
|
}
|
|
873
546
|
if (mirrorVerb === 'snapshot') {
|
|
874
547
|
await runSkillMirrorSnapshot(args.slice(2));
|
|
875
548
|
return;
|
|
876
549
|
}
|
|
877
|
-
if (mirrorVerb !== 'sync')
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
const dryRun = args.includes('--dry-run');
|
|
882
|
-
const claudeLifecycleCutover = args.includes('--cutover-lifecycle');
|
|
883
|
-
// `--only a,b` names the mirrored skills to materialize as claude-code
|
|
884
|
-
// adapters — the one command that closes an adapter gap the catalog and
|
|
885
|
-
// the SessionStart announce report, without arming a fleet-wide sweep.
|
|
886
|
-
const onlyNames = readOnlyNames(args.slice(2));
|
|
887
|
-
const positional = args.slice(2).filter((arg, index, all) => !arg.startsWith('-') && all[index - 1] !== '--only');
|
|
888
|
-
const requested = positional[0] || 'all';
|
|
889
|
-
const platforms = requested === 'all'
|
|
890
|
-
? [...native_skill_mirror_1.NATIVE_SKILL_ADAPTER_PLATFORMS]
|
|
891
|
-
: ['claude-code', 'codex', 'opencode'].includes(requested)
|
|
892
|
-
? [requested]
|
|
893
|
-
: [];
|
|
894
|
-
if (platforms.length === 0) {
|
|
895
|
-
console.error('Harness must be claude-code, codex, opencode, or all.');
|
|
896
|
-
process.exit(1);
|
|
897
|
-
}
|
|
898
|
-
if (claudeLifecycleCutover && requested !== 'claude-code') {
|
|
899
|
-
throw new Error('--cutover-lifecycle requires harness claude-code.');
|
|
900
|
-
}
|
|
901
|
-
if (onlyNames.length > 0 && requested !== 'claude-code') {
|
|
902
|
-
throw new Error('--only requires harness claude-code.');
|
|
903
|
-
}
|
|
904
|
-
const claudeTargeted = claudeLifecycleCutover || onlyNames.length > 0;
|
|
905
|
-
const apiUrl = process.env.GREPRAG_API_URL || 'https://api.greprag.com';
|
|
906
|
-
const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
|
|
907
|
-
if (requested === 'all') {
|
|
908
|
-
console.log('claude-code: native skill adapter sync disabled until Claude cutover approval.');
|
|
909
|
-
}
|
|
910
|
-
for (const platform of platforms) {
|
|
911
|
-
if (platform === 'claude-code' && claudeTargeted) {
|
|
912
|
-
const names = onlyNames.length > 0 ? onlyNames : [...claude_lifecycle_cutover_1.CLAUDE_LIFECYCLE_SKILLS];
|
|
913
|
-
if (dryRun) {
|
|
914
|
-
console.log(`claude-code: targeted cutover would manage only ${names.join(', ')}; companion files remain untouched.`);
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
const apiKey = process.env.GREPRAG_API_KEY || '';
|
|
918
|
-
if (!apiKey)
|
|
919
|
-
throw new Error('GREPRAG_API_KEY not set — run `greprag init` first.');
|
|
920
|
-
const result = await (0, claude_lifecycle_cutover_1.syncClaudeLifecycleCutover)({ apiUrl, apiKey, names });
|
|
921
|
-
const active = result.installed + result.updated + result.current;
|
|
922
|
-
console.log(`claude-code: ${active} adapter(s) active (${names.join(', ')}); all other Claude skills remain unmanaged.`);
|
|
923
|
-
if (result.backupDir)
|
|
924
|
-
console.log(`claude-code: replaced prompts backed up at ${result.backupDir}`);
|
|
925
|
-
continue;
|
|
926
|
-
}
|
|
927
|
-
if (!(0, native_skill_mirror_1.nativeSkillMirrorEnabled)(platform)) {
|
|
928
|
-
console.log(`${platform}: native skill adapter sync disabled until Claude cutover approval.`);
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
|
-
if (dryRun) {
|
|
932
|
-
printAdoptionDryRun(platform, {
|
|
933
|
-
plan: (0, skill_mirror_client_1.dryRunNativeSkillAdoption)({
|
|
934
|
-
platform,
|
|
935
|
-
projectId: anchor.projectId,
|
|
936
|
-
projectName: anchor.projectName,
|
|
937
|
-
projectDir: process.cwd(),
|
|
938
|
-
}),
|
|
939
|
-
adopted: 0,
|
|
940
|
-
blocked: 0,
|
|
941
|
-
skipped: 0,
|
|
942
|
-
});
|
|
943
|
-
continue;
|
|
944
|
-
}
|
|
945
|
-
const apiKey = process.env.GREPRAG_API_KEY || '';
|
|
946
|
-
if (!apiKey)
|
|
947
|
-
throw new Error('GREPRAG_API_KEY not set — run `greprag init` first.');
|
|
948
|
-
const adoption = await (0, skill_mirror_client_1.syncNativeSkillAdoptions)({
|
|
949
|
-
platform, apiUrl, apiKey,
|
|
950
|
-
projectId: anchor.projectId,
|
|
951
|
-
projectName: anchor.projectName,
|
|
952
|
-
projectDir: process.cwd(),
|
|
953
|
-
});
|
|
954
|
-
printAdoptionSync(platform, adoption);
|
|
955
|
-
const result = await (0, native_skill_mirror_1.syncNativeSkillMirror)({
|
|
956
|
-
platform, apiUrl, apiKey,
|
|
957
|
-
projectId: anchor.projectId,
|
|
958
|
-
projectDir: process.cwd(),
|
|
959
|
-
});
|
|
960
|
-
console.log(`${platform}: ${(0, native_skill_mirror_1.nativeSkillMirrorSummary)(result)}`);
|
|
961
|
-
}
|
|
962
|
-
if (!dryRun)
|
|
963
|
-
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));
|
|
964
553
|
return;
|
|
965
554
|
}
|
|
966
555
|
case 'source': {
|
|
967
|
-
|
|
968
|
-
console.error('Usage: greprag skill source stamp <name> --project <p> --path <rel> [--path <rel>...] [--root <dir>] [--baseline <ref>]');
|
|
969
|
-
process.exit(1);
|
|
970
|
-
}
|
|
556
|
+
const usage = 'Usage: greprag skill source stamp <name> --project <p> --path <rel> [--path <rel>...] [--root <dir>] [--baseline <ref>]';
|
|
971
557
|
const name = args[2];
|
|
972
558
|
const rest = args.slice(3);
|
|
973
559
|
const project = getArg(rest, '--project');
|
|
974
|
-
const paths =
|
|
975
|
-
if (!name || !project || paths.length === 0)
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}
|
|
979
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
980
|
-
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);
|
|
981
564
|
if (!skillDir)
|
|
982
|
-
throw new Error(`No
|
|
565
|
+
throw new Error(`No git folder with a SKILL.md found for ${name}; looked at ${gitSkillLocation(name)}.`);
|
|
983
566
|
const root = getArg(rest, '--root') || (0, skill_staleness_1.resolveSourceRoot)(project, process.cwd());
|
|
984
567
|
if (!root)
|
|
985
568
|
throw new Error(`No local Git checkout found for source project ${project}.`);
|
|
986
569
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
987
|
-
const before = fs.readFileSync(skillPath, 'utf-8');
|
|
988
570
|
const stamped = (0, skill_staleness_1.stampSourceBinding)({
|
|
989
|
-
skillMd:
|
|
571
|
+
skillMd: fs.readFileSync(skillPath, 'utf-8'), project, root, paths,
|
|
990
572
|
baselineCommit: getArg(rest, '--baseline'),
|
|
991
573
|
});
|
|
992
574
|
fs.writeFileSync(skillPath, stamped.content.endsWith('\n') ? stamped.content : stamped.content + '\n');
|
|
993
|
-
console.log(`Stamped ${name}: ${project} @ ${stamped.binding.baselineCommit.slice(0, 8)} (${stamped.binding.paths.length} paths)`);
|
|
994
|
-
|
|
995
|
-
if (apiKey) {
|
|
996
|
-
const apiUrl = process.env.GREPRAG_API_URL || 'https://api.greprag.com';
|
|
997
|
-
const outcome = await (0, skill_mirror_client_1.maybeMirrorSkill)({
|
|
998
|
-
skill: name, cwd: process.cwd(), apiUrl, apiKey,
|
|
999
|
-
reason: `source stamp ${project} @ ${stamped.binding.baselineCommit.slice(0, 8)}`,
|
|
1000
|
-
});
|
|
1001
|
-
console.log(`Mirror: ${outcome}`);
|
|
1002
|
-
}
|
|
1003
|
-
else {
|
|
1004
|
-
console.log('Mirror: skipped (GREPRAG_API_KEY not set; next skill use will retry).');
|
|
1005
|
-
}
|
|
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`.');
|
|
1006
577
|
return;
|
|
1007
578
|
}
|
|
1008
579
|
case 'list': {
|