greprag 5.85.1 → 5.85.2
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/load.js +3 -1
- package/dist/native-skill-mirror.js +1 -0
- package/dist/opencode-plugin.bundle.js +1 -0
- package/dist/skill-staleness.js +1 -1
- package/package.json +1 -1
- package/skill/mechanic/docs/skill-fix-conventions.md +5 -14
- package/skill/templates/skill-change.md +1 -97
- package/dist/claude-lifecycle-cutover.js +0 -171
- package/dist/native-skill-adoption.js +0 -217
package/dist/commands/load.js
CHANGED
|
@@ -88,9 +88,11 @@ const LIBRARY = {
|
|
|
88
88
|
files: ['skill/templates/delivery.md'],
|
|
89
89
|
purpose: 'Why the delivery rules read the way they do: what the goal already authorizes, who counts as a peer (a chip does not), the merge-lock landing sequence, and where a profile says to deploy from.',
|
|
90
90
|
},
|
|
91
|
+
// adr: adr/skill-ownership-git.md — GrepRAG holds no skill authoring doctrine;
|
|
92
|
+
// this name survives only as a pointer for launchers and docs that still call it.
|
|
91
93
|
'skill-change': {
|
|
92
94
|
files: ['skill/templates/skill-change.md'],
|
|
93
|
-
purpose: '
|
|
95
|
+
purpose: 'Pointer: skill authoring and change rules live in /skill-instructions; git owns skills.',
|
|
94
96
|
},
|
|
95
97
|
mechanic: {
|
|
96
98
|
files: ['skill/mechanic/SKILL.md', 'skill/mechanic/docs/skill-fix-conventions.md'],
|
|
@@ -349,6 +349,7 @@ Required action chains:
|
|
|
349
349
|
- **Use this skill:** run \`${loadCommand}\` before acting.
|
|
350
350
|
- **Change this skill:** edit ${gitSkillFolder(skill)} with normal file edits, commit, then run \`${syncCommand}\` (session start does it too) and verify with \`${loadCommand}\`. Uncommitted edits are never served. History and rollback are git log / git revert on that folder.
|
|
351
351
|
- **Create another skill:** create \`~/.claude/skills/<new-name>/SKILL.md\` (dotclaude repo, global) or \`<repo>/.claude/skills/<new-name>/SKILL.md\` (project), commit, then run \`${syncCommand}\`.
|
|
352
|
+
- **How to write or change a skill:** follow \`/skill-instructions\` (\`~/.claude/skills/skill-instructions/\`).
|
|
352
353
|
|
|
353
354
|
Authority: ${skillId}
|
|
354
355
|
Source binding: ${sourceBindingState(skill)}
|
|
@@ -4413,6 +4413,7 @@ Required action chains:
|
|
|
4413
4413
|
- **Use this skill:** run \`${loadCommand}\` before acting.
|
|
4414
4414
|
- **Change this skill:** edit ${gitSkillFolder(skill)} with normal file edits, commit, then run \`${syncCommand}\` (session start does it too) and verify with \`${loadCommand}\`. Uncommitted edits are never served. History and rollback are git log / git revert on that folder.
|
|
4415
4415
|
- **Create another skill:** create \`~/.claude/skills/<new-name>/SKILL.md\` (dotclaude repo, global) or \`<repo>/.claude/skills/<new-name>/SKILL.md\` (project), commit, then run \`${syncCommand}\`.
|
|
4416
|
+
- **How to write or change a skill:** follow \`/skill-instructions\` (\`~/.claude/skills/skill-instructions/\`).
|
|
4416
4417
|
|
|
4417
4418
|
Authority: ${skillId}
|
|
4418
4419
|
Source binding: ${sourceBindingState(skill)}
|
package/dist/skill-staleness.js
CHANGED
|
@@ -358,7 +358,7 @@ function formatSkillStaleness(report, options = {}) {
|
|
|
358
358
|
lines.push('1. Pause trusting this skill; read the changed watched source files/diffs first.');
|
|
359
359
|
lines.push('2. Classify relevance: low relevance or small delta -> use repo source as truth; no chip.');
|
|
360
360
|
lines.push('3. If this task depends on the stale area and the delta is material, spawn a skill-refresh chip.');
|
|
361
|
-
lines.push('4. The refresh chip edits the skill\'s git folder (~/.claude/skills/<name>/ in dotclaude, or <repo>/.claude/skills/<name>/) and commits; the mirror follows.');
|
|
361
|
+
lines.push('4. The refresh chip edits the skill\'s git folder (~/.claude/skills/<name>/ in dotclaude, or <repo>/.claude/skills/<name>/) and commits, following /skill-instructions; the mirror follows.');
|
|
362
362
|
}
|
|
363
363
|
if (report.projects.some(p => p.dirtyPaths.length > 0)) {
|
|
364
364
|
lines.push('Baseline rule: do not stamp a new baseline while watched local edits are dirty.');
|
package/package.json
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# Skill-Fix Conventions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The six recipes the skill-tuning reflex applies when a skill shows friction (`docs/mechanic-skill-tuning.md` in the greprag repo). How a skill is written and changed (where it lives, the loud-trigger form, what to cut, the commit check) is owned by `/skill-instructions` (`~/.claude/skills/skill-instructions/`); load it before editing. This doc holds only the mechanic's friction-to-edit map.
|
|
4
4
|
|
|
5
5
|
## What this is for
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The loop: **detect a defined friction → locate it in the skill's SKILL.md → apply the fix per the matching recipe → show the diff for one accept/revert.**
|
|
8
8
|
|
|
9
9
|
A friction that doesn't match one of the six below is **not auto-fixable inline**. Search Memory for the exact skill/friction first; apply a clear overcome and continue. If Memory has no answer, the overcome fails, or the friction repeats, spawn one doctrine fix chip (`greprag fix spawn --type doctrine "<skill friction unit>"`). Use `greprag fix log` only for deliberate audit/design notes. Do not invent edits outside these recipes.
|
|
10
10
|
|
|
11
|
-
## The two edit primitives (skill-optimize vocabulary)
|
|
12
|
-
|
|
13
|
-
Every fix below resolves to one of two inline forms:
|
|
14
|
-
|
|
15
|
-
- **Convention A — one-liner + link.** Reference material (facts, recipes, paths). Inline: one sentence naming what the linked doc holds + the path.
|
|
16
|
-
- **Convention B — loud trigger + inline rule + link.** A proactive-fire rule with no external trigger. Three parts: (1) interrogative intent-catch — `ABOUT TO <do X>?` names the *moment*, not the topic; (2) hard stop — `STOP —`; (3) the inline rule, complete enough to comply without opening the link. A flat `IMPORTANT: do Y` reads as wallpaper and gets skipped — the interrogative + hard stop is what catches the model mid-generation.
|
|
17
|
-
|
|
18
11
|
## The six frictions → six fixes
|
|
19
12
|
|
|
20
13
|
Each: **signature** (how it shows up in the run) · **location** (where it lives in SKILL.md) · **fix** (the exact edit).
|
|
@@ -32,12 +25,12 @@ Each: **signature** (how it shows up in the run) · **location** (where it lives
|
|
|
32
25
|
### 3. flattened-relay
|
|
33
26
|
- **Signature:** the skill made you summarize/paraphrase content that should pass through verbatim (or forced verbatim where a summary was wanted).
|
|
34
27
|
- **Location:** the output-shape / relay rule.
|
|
35
|
-
- **Fix:** add a
|
|
28
|
+
- **Fix:** add a loud trigger pinning relay fidelity — `ABOUT TO SUMMARIZE <X>? STOP — relay it verbatim; <why>.`
|
|
36
29
|
|
|
37
30
|
### 4. wallpaper-rule
|
|
38
31
|
- **Signature:** a real rule in the skill got skipped because it's a flat `IMPORTANT:`/prose imperative the model read as decoration.
|
|
39
32
|
- **Location:** the rule's existing inline form.
|
|
40
|
-
- **Fix:** rewrite that rule in place
|
|
33
|
+
- **Fix:** rewrite that rule in place as a loud trigger. Same content; don't add a new rule — convert the existing one.
|
|
41
34
|
|
|
42
35
|
### 5. stale-reference
|
|
43
36
|
- **Signature:** a path, command, or name in the skill 404'd, errored, or pointed at a renamed/removed thing.
|
|
@@ -47,12 +40,10 @@ Each: **signature** (how it shows up in the run) · **location** (where it lives
|
|
|
47
40
|
### 6. missing-rule
|
|
48
41
|
- **Signature:** you hit a gotcha mid-run that the skill should have warned about but doesn't.
|
|
49
42
|
- **Location:** absent — a gap.
|
|
50
|
-
- **Fix:** add
|
|
43
|
+
- **Fix:** add one loud trigger at the spot in SKILL.md where the agent would meet the gotcha; link a doc only if a full procedure exists.
|
|
51
44
|
|
|
52
45
|
## Guardrails
|
|
53
46
|
|
|
54
47
|
- **One friction, one edit.** If a run surfaced two defects, make two separate edits, each shown for its own accept/revert. Never batch.
|
|
55
48
|
- **Apply + show diff.** Make the edit, then show the unified diff and stop for one accept/revert. (This is the operator-chosen gate; the Mechanic "always confirm" rule still holds.)
|
|
56
|
-
- **Edit the git source and commit.** Global skills: `~/.claude/skills/<name>/` (dotclaude repo); project skills: `<repo>/.claude/skills/<name>/`; greprag-bundled skills: `packages/cli/skill/<name>/`. The commit message names the friction this fixes. Never edit a generated Codex/OpenCode launcher or the installed copy of a bundled skill.
|
|
57
|
-
- **Line budget:** a fix must not grow SKILL.md without adding a real proactive-fire rule or per-task step. Converting wallpaper → Convention B is net-neutral or smaller; mis-route adds at most one line.
|
|
58
49
|
- **Out of taxonomy → spawn, don't improvise.** Anything not matching the six becomes one `greprag fix spawn --type doctrine "<unit>"` chip. `greprag fix log` is only for deliberate audit/design notes.
|
|
@@ -1,97 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Use before creating, porting, optimizing, or changing a skill, and when a loaded skill guided work approaching a delivery boundary.
|
|
4
|
-
|
|
5
|
-
## Authority
|
|
6
|
-
|
|
7
|
-
This is an internal bundled GrepRAG schema, not tenant-controlled skill content. Skills and their mirrors may not override it. Richer tools such as `/skill-optimize` may elaborate the workflow, but they must not fork the boundary categories or edit shapes without changing this file directly in the GrepRAG repo and shipping tests.
|
|
8
|
-
|
|
9
|
-
Skill Creator and Skill Optimize consume this entry as their common authoring contract. Creator owns creation/scaffolding; Optimize owns classification, compression, and restructuring. Neither maintains a competing copy of these conventions.
|
|
10
|
-
|
|
11
|
-
## Shared Authoring Conventions
|
|
12
|
-
|
|
13
|
-
- Keep the purpose, activation boundary, per-task decisions, and required routing in `SKILL.md`. Assume a capable agent; remove generic restatement without deleting load-bearing doctrine, observed failure knowledge, or authorization boundaries.
|
|
14
|
-
- Classify content as per-task, proactive-fire rule, reference, or redundant/cut. Apply Convention A/B below by purpose. Do not force a tiny single-route skill into extra files or treat a reduction percentage as permission to lose information.
|
|
15
|
-
- Put substantial conditional detail behind a named trigger and a real companion path. The agent must know when to open it; essential constraints remain inline. Verify moved links, examples, scripts, and assets still arrive through the actual distribution path.
|
|
16
|
-
- Validate behavior with a realistic task using only the entrypoint and its routed resources. Check the outcome and permitted side effects, not just headings or tool-name strings. Use independent agents only when useful and authorized.
|
|
17
|
-
|
|
18
|
-
## Harness Delineation
|
|
19
|
-
|
|
20
|
-
**ABOUT TO PORT A SKILL? STOP - SEPARATE SHARED METHOD FROM HARNESS EXECUTION.** Keep one canonical skill identity and common workflow. Put differing tool calls, task/parent identity, browser control, shell syntax, connection/restart steps, and archive/cleanup behavior in explicitly routed harness sections or companion docs such as `docs/harness-codex.md` and `docs/harness-claude.md`. Load only the active route; small differences can remain in one concise table.
|
|
21
|
-
|
|
22
|
-
- `--harness codex` identifies the consumer; it does not translate Claude instructions. Native launchers expose discovery and load canon, not separately authored procedures.
|
|
23
|
-
- Bind logical operations to tools actually callable in the target harness. Preserve user model choices and authorization; do not translate custom agent names into invented tools, assume helpers inherit MCP access, or equate a queued request with completion.
|
|
24
|
-
- Verify side effects from the current tool contract. Archive need not delete a worktree, reconnect need not restart a server, and an HTTP health response does not prove MCP is loaded. Report unavailable capabilities and retain a bounded fallback when the workflow permits one.
|
|
25
|
-
- Preserve the full required skill package. Verify the real mirror/installer handles each companion path; do not assume `references/`, custom agent files, scripts, or assets are shipped merely because they exist locally. Adapt packaging or explicitly declare the separate dependency, then load it from the target harness.
|
|
26
|
-
- Port verification covers discovery, canonical payload and companion loading, needed tool schemas, and at least one relevant execution/read-only connection check. Distinguish those checks from unrun publishing, deletion, image generation, or production tests.
|
|
27
|
-
|
|
28
|
-
**ABOUT TO RESTORE A SKILL? STOP - A GENERATED LAUNCHER IS NOT THE PROCEDURE.** If a SKILL.md contains a generated-adapter header or only points back to its own `greprag load`, recover the actual procedure from git history or the mirror's archived revisions and commit it to the git folder. Do not recursively load it or commit another launcher as the repair.
|
|
29
|
-
|
|
30
|
-
## Boundary Rule
|
|
31
|
-
|
|
32
|
-
**ABOUT TO CHANGE A SKILL? STOP - LOAD THIS RULE FIRST.** Skill edits are learning-capture, not cleanup. Edit only when the run exposed a durable rule, missing progressive-disclosure link, stale reference, trigger bug, or wrong handoff. If the change alters the skill's method or risk posture, propose it instead of silently landing it.
|
|
33
|
-
|
|
34
|
-
Explicitly requested creation, porting, or redesign is authorized work. The proposal rule applies to changes beyond that scope; do not ask again for the same approved change.
|
|
35
|
-
|
|
36
|
-
**ABOUT TO REFRESH A STALE SKILL? STOP - THIS IS A DECISION GATE, NOT AUTO-EDIT PERMISSION.** First read the changed watched source files/diffs and classify relevance. Low relevance or small deltas use repo source as truth and do not need a chip. Spawn a `skill-refresh` chip only when the stale area directly affects the task and the delta is material.
|
|
37
|
-
|
|
38
|
-
## Where To Edit
|
|
39
|
-
|
|
40
|
-
Git owns every skill; GrepRAG only mirrors the committed files and refuses every write to the mirror.
|
|
41
|
-
|
|
42
|
-
- Global skill: edit `~/.claude/skills/<name>/` (the dotclaude git repo) and commit.
|
|
43
|
-
- Project skill: edit `<repo>/.claude/skills/<name>/` and commit in that repo.
|
|
44
|
-
- Bundled GrepRAG skill: edit `packages/cli/skill/<name>/` in the greprag repo, never the installed artifact.
|
|
45
|
-
- Codex/OpenCode `SKILL.md` files are generated launchers: never edit them. Edit the git folder they name.
|
|
46
|
-
- Unknown source: stop and identify the git folder before editing.
|
|
47
|
-
- **ABOUT TO CALL A SKILL CHANGE DONE? STOP - COMMIT IT.** Uncommitted edits are never mirrored; the commit message carries the learning the edit captures, not "update skill". The next session start (or `greprag skill mirror sync`) copies the commit into the mirror for every harness.
|
|
48
|
-
- History and rollback: `git log`, `git diff` and `git revert` on the skill folder. `greprag skill history <skill>` and `greprag skill diff <skill> <revA> [<revB>]` still read the mirror's archived revisions.
|
|
49
|
-
|
|
50
|
-
## Skill-Refresh Chips
|
|
51
|
-
|
|
52
|
-
A `skill-refresh` chip must:
|
|
53
|
-
|
|
54
|
-
- load the skill with `greprag load <skill>`
|
|
55
|
-
- read the changed watched source files/diffs before trusting stale skill claims
|
|
56
|
-
- decide whether the right result is a skill edit or only a new source stamp (`greprag skill source stamp`, which writes the git SKILL.md)
|
|
57
|
-
- edit the skill's git folder and commit, never a generated launcher
|
|
58
|
-
- report checks and whether release/deploy is needed
|
|
59
|
-
|
|
60
|
-
## Edit Shapes
|
|
61
|
-
|
|
62
|
-
- **Convention A - one-liner + link.** Use for reference material: facts, recipes, paths, command syntax. Inline sentence names what the linked doc contains and its path.
|
|
63
|
-
- **Convention B - loud trigger + inline rule + optional link.** Use for proactive-fire rules. Shape: `ABOUT TO <do X>? STOP - <complete rule>.` Catch the intent at the moment of action, state the complete constraint inline, and link only the longer procedure. A topic heading or bare `IMPORTANT` label does not carry this convention.
|
|
64
|
-
|
|
65
|
-
## Auto-Land vs Propose
|
|
66
|
-
|
|
67
|
-
Auto-land only narrow, low-risk edits:
|
|
68
|
-
|
|
69
|
-
- add a missing trigger phrase or `NOT for` boundary
|
|
70
|
-
- repoint a verified stale path/name/command
|
|
71
|
-
- add a missing progressive-disclosure link
|
|
72
|
-
- convert skipped wallpaper prose into Convention B without changing meaning
|
|
73
|
-
- add one gotcha rule that directly follows from the run
|
|
74
|
-
|
|
75
|
-
Propose instead:
|
|
76
|
-
|
|
77
|
-
- method changes
|
|
78
|
-
- broader scope changes
|
|
79
|
-
- new autonomy/destructive behavior
|
|
80
|
-
- ambiguous doctrine
|
|
81
|
-
- anything that would need user taste, strategy, or risk judgment
|
|
82
|
-
|
|
83
|
-
## Defect Map
|
|
84
|
-
|
|
85
|
-
- Mis-route: update `description` / `Use when` / `NOT for`.
|
|
86
|
-
- Wrong altitude: add a handoff or out-of-scope line naming the delegate.
|
|
87
|
-
- Flattened relay: add a Convention-B relay-fidelity rule.
|
|
88
|
-
- Wallpaper rule: rewrite the existing rule to Convention B.
|
|
89
|
-
- Stale reference: verify and repoint; remove only if wholly dead.
|
|
90
|
-
- Missing rule: add one Convention-B gotcha rule at the point the agent meets it.
|
|
91
|
-
|
|
92
|
-
## Guardrails
|
|
93
|
-
|
|
94
|
-
- One learning, one edit.
|
|
95
|
-
- Do not grow `SKILL.md` unless adding a real per-task step or proactive-fire rule.
|
|
96
|
-
- Show the diff before finalizing if the operator has not already authorized the exact edit.
|
|
97
|
-
- Out of taxonomy: `greprag fix spawn "<skill friction unit>"`.
|
|
1
|
+
Skill authoring and change rules live in /skill-instructions (~/.claude/skills/skill-instructions). Git owns skills.
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CLAUDE_LIFECYCLE_SKILLS = void 0;
|
|
37
|
-
exports.applyClaudeLifecycleCutover = applyClaudeLifecycleCutover;
|
|
38
|
-
exports.syncClaudeLifecycleCutover = syncClaudeLifecycleCutover;
|
|
39
|
-
const fs = __importStar(require("fs"));
|
|
40
|
-
const os = __importStar(require("os"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const native_skill_mirror_1 = require("./native-skill-mirror");
|
|
43
|
-
const skill_mirror_files_1 = require("./skill-mirror-files");
|
|
44
|
-
exports.CLAUDE_LIFECYCLE_SKILLS = ['commit', 'deploy', 'release'];
|
|
45
|
-
function adapterFor(skill) {
|
|
46
|
-
const files = (0, native_skill_mirror_1.buildNativeSkillAdapter)('claude-code', skill, { allowClaudeCode: true });
|
|
47
|
-
if (!files || files.length !== 1 || files[0].path !== 'SKILL.md') {
|
|
48
|
-
throw new Error(`Claude lifecycle adapter is invalid for ${skill.skillName}.`);
|
|
49
|
-
}
|
|
50
|
-
return files;
|
|
51
|
-
}
|
|
52
|
-
function safeTimestamp(now) {
|
|
53
|
-
return now.toISOString().replace(/[:.]/g, '-');
|
|
54
|
-
}
|
|
55
|
-
function requiredSkills(skills, names) {
|
|
56
|
-
const byName = new Map(skills
|
|
57
|
-
.filter(skill => skill.scope !== 'project')
|
|
58
|
-
.map(skill => [skill.skillName, skill]));
|
|
59
|
-
return names.map(name => {
|
|
60
|
-
const skill = byName.get(name);
|
|
61
|
-
if (!skill)
|
|
62
|
-
throw new Error(`Canonical Claude lifecycle skill missing: ${name}.`);
|
|
63
|
-
return skill;
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Explicit, narrow Claude cutover. Only SKILL.md becomes mirror-managed;
|
|
68
|
-
* companion scripts and docs remain untouched in the native package.
|
|
69
|
-
*
|
|
70
|
-
* `names` selects which mirrored skills to materialize; it defaults to the
|
|
71
|
-
* lifecycle triple. Naming skills explicitly is how a Claude Code operator
|
|
72
|
-
* closes an adapter gap without arming a fleet-wide sweep.
|
|
73
|
-
*/
|
|
74
|
-
function applyClaudeLifecycleCutover(params) {
|
|
75
|
-
const homeDir = params.homeDir || os.homedir();
|
|
76
|
-
const root = (0, native_skill_mirror_1.nativeSkillRoot)('claude-code', homeDir);
|
|
77
|
-
const state = (0, native_skill_mirror_1.readState)(root);
|
|
78
|
-
const names = params.names?.length ? params.names : exports.CLAUDE_LIFECYCLE_SKILLS;
|
|
79
|
-
const prepared = requiredSkills(params.skills, names).map(skill => {
|
|
80
|
-
const target = path.join(root, skill.skillName);
|
|
81
|
-
if ((0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(target)) {
|
|
82
|
-
throw new Error(`Claude skill folder is owned elsewhere (a link or another installer's package): ${skill.skillName}; not written.`);
|
|
83
|
-
}
|
|
84
|
-
const skillMd = path.join(target, 'SKILL.md');
|
|
85
|
-
const adapterFiles = adapterFor(skill);
|
|
86
|
-
const nextHash = (0, skill_mirror_files_1.computeSkillFileHash)(adapterFiles);
|
|
87
|
-
const managed = state.skills[skill.skillName];
|
|
88
|
-
let currentContent;
|
|
89
|
-
if (fs.existsSync(skillMd))
|
|
90
|
-
currentContent = fs.readFileSync(skillMd, 'utf8');
|
|
91
|
-
if (managed) {
|
|
92
|
-
const currentFiles = currentContent === undefined
|
|
93
|
-
? null
|
|
94
|
-
: [{ path: 'SKILL.md', content: currentContent }];
|
|
95
|
-
if (!currentFiles || (0, skill_mirror_files_1.computeSkillFileHash)(currentFiles) !== managed.hash) {
|
|
96
|
-
throw new Error(`Claude lifecycle adapter diverged: ${skill.skillName}.`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else if (fs.existsSync(target) && currentContent === undefined) {
|
|
100
|
-
throw new Error(`Claude lifecycle package has no SKILL.md: ${skill.skillName}.`);
|
|
101
|
-
}
|
|
102
|
-
return { skill, target, skillMd, adapterFiles, nextHash, managed, currentContent };
|
|
103
|
-
});
|
|
104
|
-
const replacements = prepared.filter(item => !item.managed && item.currentContent !== undefined);
|
|
105
|
-
let backupDir;
|
|
106
|
-
if (replacements.length > 0) {
|
|
107
|
-
backupDir = path.join(homeDir, '.greprag', 'backups', 'claude-lifecycle-cutover', safeTimestamp(params.now || new Date()));
|
|
108
|
-
for (const item of replacements) {
|
|
109
|
-
const destination = path.join(backupDir, item.skill.skillName, 'SKILL.md');
|
|
110
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
111
|
-
fs.writeFileSync(destination, item.currentContent, 'utf8');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const result = {
|
|
115
|
-
installed: 0, updated: 0, current: 0, blocked: 0, backupDir,
|
|
116
|
-
};
|
|
117
|
-
for (const item of prepared) {
|
|
118
|
-
if (item.managed && item.managed.hash === item.nextHash) {
|
|
119
|
-
result.current++;
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
fs.mkdirSync(item.target, { recursive: true });
|
|
123
|
-
fs.writeFileSync(item.skillMd, item.adapterFiles[0].content, 'utf8');
|
|
124
|
-
state.skills[item.skill.skillName] = { hash: item.nextHash, files: ['SKILL.md'] };
|
|
125
|
-
if (item.managed)
|
|
126
|
-
result.updated++;
|
|
127
|
-
else
|
|
128
|
-
result.installed++;
|
|
129
|
-
}
|
|
130
|
-
(0, native_skill_mirror_1.writeState)(root, state);
|
|
131
|
-
return result;
|
|
132
|
-
}
|
|
133
|
-
async function syncClaudeLifecycleCutover(params) {
|
|
134
|
-
const names = params.names?.length ? [...new Set(params.names)] : [...exports.CLAUDE_LIFECYCLE_SKILLS];
|
|
135
|
-
const list = await fetch(`${params.apiUrl}/v1/skillmirror`, {
|
|
136
|
-
headers: { Authorization: `Bearer ${params.apiKey}` },
|
|
137
|
-
});
|
|
138
|
-
if (!list.ok)
|
|
139
|
-
throw new Error(`skill mirror list failed: HTTP ${list.status}`);
|
|
140
|
-
const data = await list.json();
|
|
141
|
-
const wanted = new Set(names);
|
|
142
|
-
const rows = (data.skills || []).filter(row => wanted.has(row.skillName) && row.scope !== 'project');
|
|
143
|
-
if (rows.length !== names.length) {
|
|
144
|
-
const missing = names.filter(name => !rows.some(row => row.skillName === name));
|
|
145
|
-
throw new Error(`Not in the global skill mirror: ${missing.join(', ')}; native files preserved.`);
|
|
146
|
-
}
|
|
147
|
-
const fetched = await Promise.all(rows.map(async (row) => {
|
|
148
|
-
const query = new URLSearchParams({ skillId: row.skillId || `global:${row.skillName}` });
|
|
149
|
-
const response = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(row.skillName)}?${query.toString()}`, { headers: { Authorization: `Bearer ${params.apiKey}` } });
|
|
150
|
-
if (!response.ok)
|
|
151
|
-
return null;
|
|
152
|
-
const body = await response.json();
|
|
153
|
-
if (!Array.isArray(body.files))
|
|
154
|
-
return null;
|
|
155
|
-
return {
|
|
156
|
-
...row,
|
|
157
|
-
scope: 'global',
|
|
158
|
-
files: body.files,
|
|
159
|
-
contentHash: body.contentHash || row.contentHash,
|
|
160
|
-
revision: body.revision ?? row.revision,
|
|
161
|
-
};
|
|
162
|
-
}));
|
|
163
|
-
if (fetched.some(skill => skill === null)) {
|
|
164
|
-
throw new Error('Claude lifecycle snapshot incomplete; native files preserved.');
|
|
165
|
-
}
|
|
166
|
-
return applyClaudeLifecycleCutover({
|
|
167
|
-
skills: fetched,
|
|
168
|
-
homeDir: params.homeDir,
|
|
169
|
-
names,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/** Adopt unmanaged native skills into the GrepRAG skill mirror. */
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.planNativeSkillAdoption = planNativeSkillAdoption;
|
|
38
|
-
exports.installAdoptedNativeSkill = installAdoptedNativeSkill;
|
|
39
|
-
const fs = __importStar(require("node:fs"));
|
|
40
|
-
const os = __importStar(require("node:os"));
|
|
41
|
-
const path = __importStar(require("node:path"));
|
|
42
|
-
const native_skill_mirror_1 = require("./native-skill-mirror");
|
|
43
|
-
const skill_mirror_files_1 = require("./skill-mirror-files");
|
|
44
|
-
const NATIVE_SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
45
|
-
function listSkillDirs(root) {
|
|
46
|
-
try {
|
|
47
|
-
return fs.readdirSync(root, { withFileTypes: true })
|
|
48
|
-
.filter(entry => entry.isDirectory() && fs.existsSync(path.join(root, entry.name, 'SKILL.md')))
|
|
49
|
-
.map(entry => entry.name)
|
|
50
|
-
.sort((a, b) => a.localeCompare(b));
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function countSkillFiles(root) {
|
|
57
|
-
let count = 0;
|
|
58
|
-
const walk = (dir, depth) => {
|
|
59
|
-
if (depth > 7 || count >= 1500)
|
|
60
|
-
return;
|
|
61
|
-
let entries;
|
|
62
|
-
try {
|
|
63
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (count >= 1500)
|
|
70
|
-
return;
|
|
71
|
-
const full = path.join(dir, entry.name);
|
|
72
|
-
if (entry.isDirectory())
|
|
73
|
-
walk(full, depth + 1);
|
|
74
|
-
else if (entry.isFile() && entry.name.toLowerCase() === 'skill.md')
|
|
75
|
-
count++;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
walk(root, 0);
|
|
79
|
-
return count;
|
|
80
|
-
}
|
|
81
|
-
function adoptionRoots(params) {
|
|
82
|
-
const roots = [{ root: (0, native_skill_mirror_1.nativeSkillRoot)(params.platform, params.homeDir), scope: 'global' }];
|
|
83
|
-
const projectRoot = params.projectDir && params.projectId
|
|
84
|
-
? (0, native_skill_mirror_1.writerOwnedProjectSkillRoot)(params.platform, params.projectDir, params.homeDir)
|
|
85
|
-
: null;
|
|
86
|
-
if (projectRoot) {
|
|
87
|
-
roots.push({
|
|
88
|
-
root: projectRoot,
|
|
89
|
-
scope: 'project',
|
|
90
|
-
projectId: params.projectId,
|
|
91
|
-
projectName: params.projectName,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return roots;
|
|
95
|
-
}
|
|
96
|
-
function ignoredDiscoveryRoots(params) {
|
|
97
|
-
const roots = [];
|
|
98
|
-
if (params.platform === 'codex') {
|
|
99
|
-
roots.push([path.join(params.homeDir, '.agents', 'skills'), 'discovered by Codex but not writer-owned'], [path.join(params.homeDir, '.codex', 'plugins', 'cache'), 'plugin cache is read-only']);
|
|
100
|
-
if (params.projectDir)
|
|
101
|
-
roots.push([path.join(params.projectDir, '.codex', 'skills'), 'discovered by Codex but not writer-owned']);
|
|
102
|
-
}
|
|
103
|
-
const owned = new Set(adoptionRoots({
|
|
104
|
-
platform: params.platform,
|
|
105
|
-
homeDir: params.homeDir,
|
|
106
|
-
projectDir: params.projectDir,
|
|
107
|
-
projectId: '00000000-0000-4000-8000-000000000000',
|
|
108
|
-
}).map(root => path.resolve(root.root).toLowerCase()));
|
|
109
|
-
return roots
|
|
110
|
-
.filter(([root]) => !owned.has(path.resolve(root).toLowerCase()))
|
|
111
|
-
.map(([root, reason]) => ({ root, reason, skillCount: countSkillFiles(root) }))
|
|
112
|
-
.filter(root => root.skillCount > 0);
|
|
113
|
-
}
|
|
114
|
-
function planNativeSkillAdoption(params) {
|
|
115
|
-
const homeDir = params.homeDir || os.homedir();
|
|
116
|
-
const projectDir = params.projectDir ? path.resolve(params.projectDir) : undefined;
|
|
117
|
-
const plan = {
|
|
118
|
-
candidates: [],
|
|
119
|
-
managed: [],
|
|
120
|
-
blocked: [],
|
|
121
|
-
skipped: [],
|
|
122
|
-
ignoredRoots: ignoredDiscoveryRoots({ platform: params.platform, homeDir, projectDir }),
|
|
123
|
-
};
|
|
124
|
-
if (!(0, native_skill_mirror_1.nativeSkillMirrorEnabled)(params.platform))
|
|
125
|
-
return plan;
|
|
126
|
-
for (const root of adoptionRoots({
|
|
127
|
-
platform: params.platform,
|
|
128
|
-
homeDir,
|
|
129
|
-
projectDir,
|
|
130
|
-
projectId: params.projectId,
|
|
131
|
-
projectName: params.projectName,
|
|
132
|
-
})) {
|
|
133
|
-
const state = (0, native_skill_mirror_1.readState)(root.root);
|
|
134
|
-
for (const skillName of listSkillDirs(root.root)) {
|
|
135
|
-
const target = (0, native_skill_mirror_1.targetPath)(root.root, skillName);
|
|
136
|
-
const managed = state.skills[skillName];
|
|
137
|
-
if (managed) {
|
|
138
|
-
const currentFiles = (0, native_skill_mirror_1.readManagedFiles)(target, managed);
|
|
139
|
-
const currentHash = currentFiles ? (0, skill_mirror_files_1.computeSkillFileHash)(currentFiles) : '';
|
|
140
|
-
const entry = { ...root, skillName, reason: currentHash === managed.hash ? 'managed/current' : 'managed/diverged' };
|
|
141
|
-
if (currentHash === managed.hash)
|
|
142
|
-
plan.managed.push(entry);
|
|
143
|
-
else
|
|
144
|
-
plan.blocked.push(entry);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (!NATIVE_SKILL_NAME_RE.test(skillName)) {
|
|
148
|
-
plan.skipped.push({ ...root, skillName, reason: 'platform-invalid name' });
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
const files = params.collectFiles(target);
|
|
152
|
-
if (!files) {
|
|
153
|
-
plan.skipped.push({ ...root, skillName, reason: 'invalid or oversized markdown payload' });
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
// A directory greprag already wrote an adapter into is not an unmanaged
|
|
157
|
-
// native skill, whatever the local state file says (it is per-root and
|
|
158
|
-
// per-machine, so a fresh checkout or a lost state file makes every
|
|
159
|
-
// adapter look adoptable). Adopting one would upload the launcher as
|
|
160
|
-
// canon and erase the real body. Same rule as the shadow upload path.
|
|
161
|
-
// adr: adr/skill-adapter-never-canon.md
|
|
162
|
-
const main = files.find(file => file.path === 'SKILL.md');
|
|
163
|
-
if (main && (0, skill_mirror_files_1.isGeneratedSkillAdapter)(main.content)) {
|
|
164
|
-
plan.skipped.push({ ...root, skillName, reason: 'generated adapter is never a mirror source' });
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
plan.candidates.push({ ...root, skillName, files, sourceHash: (0, skill_mirror_files_1.computeSkillFileHash)(files) });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const globalNames = new Set([
|
|
171
|
-
...plan.candidates.filter(candidate => candidate.scope === 'global').map(candidate => candidate.skillName.toLowerCase()),
|
|
172
|
-
...plan.managed.filter(managed => managed.scope === 'global').map(managed => managed.skillName.toLowerCase()),
|
|
173
|
-
]);
|
|
174
|
-
for (let i = plan.candidates.length - 1; i >= 0; i--) {
|
|
175
|
-
const candidate = plan.candidates[i];
|
|
176
|
-
if (candidate.scope !== 'project' || !globalNames.has(candidate.skillName.toLowerCase()))
|
|
177
|
-
continue;
|
|
178
|
-
plan.blocked.push({ ...candidate, reason: 'project/global collision requires explicit replaces metadata' });
|
|
179
|
-
plan.candidates.splice(i, 1);
|
|
180
|
-
}
|
|
181
|
-
return plan;
|
|
182
|
-
}
|
|
183
|
-
function installAdoptedNativeSkill(params) {
|
|
184
|
-
if (!(0, native_skill_mirror_1.nativeSkillMirrorEnabled)(params.platform))
|
|
185
|
-
return 'skipped';
|
|
186
|
-
if (!NATIVE_SKILL_NAME_RE.test(params.skill.skillName))
|
|
187
|
-
return 'skipped';
|
|
188
|
-
const target = (0, native_skill_mirror_1.targetPath)(params.root, params.skill.skillName);
|
|
189
|
-
const state = (0, native_skill_mirror_1.readState)(params.root);
|
|
190
|
-
if (state.skills[params.skill.skillName])
|
|
191
|
-
return 'blocked';
|
|
192
|
-
const currentFiles = params.collectFiles(target);
|
|
193
|
-
if (!currentFiles || (0, skill_mirror_files_1.computeSkillFileHash)(currentFiles) !== params.expectedSourceHash)
|
|
194
|
-
return 'blocked';
|
|
195
|
-
const adapterFiles = (0, native_skill_mirror_1.buildNativeSkillAdapter)(params.platform, params.skill);
|
|
196
|
-
if (!adapterFiles)
|
|
197
|
-
return 'skipped';
|
|
198
|
-
// Adoption replaces only the files it just moved into canon. The mirror
|
|
199
|
-
// carries SKILL.md and docs/**/*.md; everything else in the package
|
|
200
|
-
// (scripts/, references/, assets/, any non-markdown file) exists nowhere but
|
|
201
|
-
// this folder, and wiping the folder destroyed exactly those. Write the
|
|
202
|
-
// adapter, then remove the adopted files it did not overwrite, the same rule
|
|
203
|
-
// a managed adapter update follows. adr: adr/skill-bundle-shape.md
|
|
204
|
-
for (const file of adapterFiles) {
|
|
205
|
-
const destination = (0, native_skill_mirror_1.filePath)(target, file.path);
|
|
206
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
207
|
-
fs.writeFileSync(destination, file.content, 'utf8');
|
|
208
|
-
}
|
|
209
|
-
const adapterPaths = new Set(adapterFiles.map(file => file.path));
|
|
210
|
-
(0, native_skill_mirror_1.removeTrackedFiles)(target, currentFiles.map(file => file.path).filter(relative => !adapterPaths.has(relative)));
|
|
211
|
-
state.skills[params.skill.skillName] = {
|
|
212
|
-
hash: (0, skill_mirror_files_1.computeSkillFileHash)(adapterFiles),
|
|
213
|
-
files: adapterFiles.map(file => file.path).sort(),
|
|
214
|
-
};
|
|
215
|
-
(0, native_skill_mirror_1.writeState)(params.root, state);
|
|
216
|
-
return 'installed';
|
|
217
|
-
}
|