greprag 5.82.1 → 5.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-lifecycle-cutover.js +5 -16
- package/dist/commands/codex.js +4 -0
- package/dist/commands/corpus/client.js +16 -0
- package/dist/commands/corpus/index.js +5 -2
- package/dist/commands/corpus/section.js +3 -3
- package/dist/commands/corpus/walk.js +3 -3
- package/dist/commands/delivery.js +64 -0
- package/dist/commands/docptr.js +10 -24
- package/dist/commands/init.js +4 -5
- package/dist/commands/load-primer-reminder.js +2 -2
- package/dist/commands/load.js +45 -12
- package/dist/commands/merge-guard.js +94 -9
- package/dist/commands/parity.js +0 -2
- package/dist/commands/pipe-wrap.js +4 -2
- package/dist/commands/skill-mirror-reminder.js +24 -6
- package/dist/commands/skill.js +24 -8
- package/dist/commands/skillgain.js +5 -1
- package/dist/commands/state-trigger.js +67 -31
- package/dist/delivery-config.js +69 -0
- package/dist/deploy-locks.js +18 -3
- package/dist/doc-mirror-client.js +39 -11
- package/dist/hook.js +35 -39
- package/dist/index.js +62 -27
- package/dist/native-adapter-refresh.js +114 -0
- package/dist/native-skill-adoption.js +20 -53
- package/dist/native-skill-mirror.js +130 -52
- package/dist/native-skill-pending.js +5 -2
- package/dist/opencode-plugin.bundle.js +1079 -159
- package/dist/opencode-plugin.js +6 -0
- package/dist/skill-activation-manifest.js +98 -22
- package/dist/skill-landing.js +6 -0
- package/dist/skill-mirror-files.js +59 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +4 -19
- package/skill/greprag/docs/corpus.md +2 -2
- package/skill/greprag/docs/setup.md +1 -1
- package/skill/mechanic/SKILL.md +14 -39
- package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
- package/skill/templates/chip-spawn.md +36 -20
package/dist/opencode-plugin.js
CHANGED
|
@@ -102,6 +102,7 @@ const procedure_1 = require("./procedure");
|
|
|
102
102
|
const procedure_runtime_1 = require("./procedure-runtime");
|
|
103
103
|
const opencode_plugin_png_1 = require("./opencode-plugin-png");
|
|
104
104
|
const skill_activation_manifest_1 = require("./skill-activation-manifest");
|
|
105
|
+
const native_adapter_refresh_1 = require("./native-adapter-refresh");
|
|
105
106
|
// ============================================================================
|
|
106
107
|
// File-based debug log. opencode's plugin loader (Bun) does NOT route plugin
|
|
107
108
|
// stderr into the opencode log file at ~/.local/share/opencode/log/*.log —
|
|
@@ -587,6 +588,11 @@ async function fetchSkillActivation(apiKey, cwd) {
|
|
|
587
588
|
const data = await response.json();
|
|
588
589
|
const rows = data.skills || [];
|
|
589
590
|
cachedSkillRows = { projectId: anchor.projectId, rows, fetchedAt: now };
|
|
591
|
+
// Installed adapters follow canon, checked against this same roster.
|
|
592
|
+
// adr: adr/skill-bundle-shape.md
|
|
593
|
+
await (0, native_adapter_refresh_1.refreshNativeAdapters)({
|
|
594
|
+
platform: 'opencode', rows, apiUrl: API_URL, apiKey, homeDir: os.homedir(), projectDir: cwd,
|
|
595
|
+
}).catch(() => undefined);
|
|
590
596
|
return (0, skill_activation_manifest_1.activationFromApiRows)({
|
|
591
597
|
rows,
|
|
592
598
|
platform: 'opencode',
|
|
@@ -42,11 +42,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
})();
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
44
|
exports.readNativeSkillNames = readNativeSkillNames;
|
|
45
|
+
exports.nativeAdapterGap = nativeAdapterGap;
|
|
45
46
|
exports.buildMirroredSkillActivation = buildMirroredSkillActivation;
|
|
46
47
|
exports.nativeAdapterInstallCommand = nativeAdapterInstallCommand;
|
|
48
|
+
exports.localCopyCutoverCommand = localCopyCutoverCommand;
|
|
47
49
|
exports.activationFromApiRows = activationFromApiRows;
|
|
48
50
|
const fs = __importStar(require("fs"));
|
|
49
51
|
const path = __importStar(require("path"));
|
|
52
|
+
const skill_landing_1 = require("./skill-landing");
|
|
53
|
+
const skill_mirror_files_1 = require("./skill-mirror-files");
|
|
50
54
|
const MAX_NATIVE_SKILL_FILES = 1_500;
|
|
51
55
|
const MAX_SCAN_DEPTH = 7;
|
|
52
56
|
function homeRoots(homeDir, platform) {
|
|
@@ -89,11 +93,42 @@ function frontmatterName(content) {
|
|
|
89
93
|
const name = fm && /(?:^|\r?\n)name:\s*["']?([^\r\n"']+)/.exec(fm[1]);
|
|
90
94
|
return name?.[1]?.trim() || '';
|
|
91
95
|
}
|
|
92
|
-
/** Bounded native-skill census
|
|
93
|
-
*
|
|
96
|
+
/** Bounded native-skill census: every name the harness lists, and whether the
|
|
97
|
+
* package under it is a generated adapter. A directory basename is always
|
|
98
|
+
* accepted; a frontmatter name is added when present. A folder holding a
|
|
99
|
+
* SKILL.md is one package: folders below it are its own files (docs, a
|
|
100
|
+
* published copy), never skills the harness lists. A linked folder is read
|
|
101
|
+
* through its link and recorded 'external', never descended; so is a package
|
|
102
|
+
* another installer owns (isSkillFolderOwnedElsewhere). Roots are read in
|
|
103
|
+
* the harness's precedence order (Claude Code runs a personal skill over a
|
|
104
|
+
* project one of the same name), and the first root that lists a name decides
|
|
105
|
+
* its kind. Within one root a name stays 'local' once any local package claims
|
|
106
|
+
* it, and 'external' outranks 'adapter'. Missing/unreadable roots are silent. */
|
|
94
107
|
function readNativeSkillNames(params) {
|
|
95
|
-
const names = new
|
|
108
|
+
const names = new Map();
|
|
109
|
+
let rootNames = new Map();
|
|
110
|
+
const rank = { adapter: 0, external: 1, local: 2 };
|
|
111
|
+
const note = (name, kind) => {
|
|
112
|
+
const held = rootNames.get(name);
|
|
113
|
+
if (!held || rank[kind] > rank[held])
|
|
114
|
+
rootNames.set(name, kind);
|
|
115
|
+
};
|
|
96
116
|
let visited = 0;
|
|
117
|
+
const readPackage = (dir, fileName, linked) => {
|
|
118
|
+
visited++;
|
|
119
|
+
let content = '';
|
|
120
|
+
try {
|
|
121
|
+
content = fs.readFileSync(path.join(dir, fileName), 'utf8');
|
|
122
|
+
}
|
|
123
|
+
catch { /* directory name still gives a useful identity */ }
|
|
124
|
+
const kind = linked || (0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(dir)
|
|
125
|
+
? 'external'
|
|
126
|
+
: (0, skill_mirror_files_1.isGeneratedSkillAdapter)(content) ? 'adapter' : 'local';
|
|
127
|
+
note(path.basename(dir).toLowerCase(), kind);
|
|
128
|
+
const declared = frontmatterName(content);
|
|
129
|
+
if (declared)
|
|
130
|
+
note(declared.toLowerCase(), kind);
|
|
131
|
+
};
|
|
97
132
|
const walk = (dir, depth) => {
|
|
98
133
|
if (depth > MAX_SCAN_DEPTH || visited >= MAX_NATIVE_SKILL_FILES)
|
|
99
134
|
return;
|
|
@@ -104,41 +139,74 @@ function readNativeSkillNames(params) {
|
|
|
104
139
|
catch {
|
|
105
140
|
return;
|
|
106
141
|
}
|
|
142
|
+
const skillMd = depth > 0
|
|
143
|
+
? entries.find(entry => entry.isFile() && entry.name.toLowerCase() === 'skill.md')
|
|
144
|
+
: undefined;
|
|
145
|
+
if (skillMd) {
|
|
146
|
+
readPackage(dir, skillMd.name, false);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
107
149
|
for (const entry of entries) {
|
|
108
150
|
if (visited >= MAX_NATIVE_SKILL_FILES)
|
|
109
151
|
return;
|
|
110
|
-
const
|
|
111
|
-
if (entry.isDirectory())
|
|
112
|
-
walk(
|
|
113
|
-
|
|
152
|
+
const child = path.join(dir, entry.name);
|
|
153
|
+
if (entry.isDirectory())
|
|
154
|
+
walk(child, depth + 1);
|
|
155
|
+
else if ((0, skill_mirror_files_1.isLinkedSkillFolder)(child) && fs.existsSync(path.join(child, 'SKILL.md'))) {
|
|
156
|
+
readPackage(child, 'SKILL.md', true);
|
|
114
157
|
}
|
|
115
|
-
if (!entry.isFile() || entry.name.toLowerCase() !== 'skill.md')
|
|
116
|
-
continue;
|
|
117
|
-
visited++;
|
|
118
|
-
names.add(path.basename(path.dirname(full)).toLowerCase());
|
|
119
|
-
try {
|
|
120
|
-
const declared = frontmatterName(fs.readFileSync(full, 'utf8'));
|
|
121
|
-
if (declared)
|
|
122
|
-
names.add(declared.toLowerCase());
|
|
123
|
-
}
|
|
124
|
-
catch { /* directory name still gives a useful identity */ }
|
|
125
158
|
}
|
|
126
159
|
};
|
|
127
160
|
const roots = [
|
|
128
161
|
...homeRoots(params.homeDir, params.platform),
|
|
129
162
|
...repoRoots(params.cwd, params.platform),
|
|
130
163
|
];
|
|
131
|
-
for (const root of roots)
|
|
164
|
+
for (const root of roots) {
|
|
165
|
+
rootNames = new Map();
|
|
132
166
|
walk(root, 0);
|
|
167
|
+
for (const [name, kind] of rootNames)
|
|
168
|
+
if (!names.has(name))
|
|
169
|
+
names.set(name, kind);
|
|
170
|
+
}
|
|
133
171
|
return names;
|
|
134
172
|
}
|
|
173
|
+
/** Mirrored skills this harness does not run from canon. `missing` have no
|
|
174
|
+
* native package, so they never reach the harness's own skill list.
|
|
175
|
+
* `localCopies` are listed from a hand-kept package instead of an adapter, so
|
|
176
|
+
* the harness keeps running that copy after canon moves on. Only Claude Code
|
|
177
|
+
* reports them: its targeted cutover is the one writer that replaces a global
|
|
178
|
+
* package GrepRAG does not own, while the Codex/OpenCode sync leaves such
|
|
179
|
+
* packages in place. An external package is neither: the harness lists it, and
|
|
180
|
+
* its owner (a link target or another installer) is not GrepRAG. A bundled skill is never a gap; its canon is the bundle
|
|
181
|
+
* the installer writes as a real body. adr: adr/skill-bundle-shape.md */
|
|
182
|
+
function nativeAdapterGap(platform, mirrored, native) {
|
|
183
|
+
const missing = [];
|
|
184
|
+
const localCopies = [];
|
|
185
|
+
if (!platform)
|
|
186
|
+
return { missing, localCopies };
|
|
187
|
+
for (const { skillName, scope } of mirrored) {
|
|
188
|
+
if (!skillName || skill_landing_1.BUNDLED_SKILLS.includes(skillName))
|
|
189
|
+
continue;
|
|
190
|
+
const kind = native.get(skillName.toLowerCase());
|
|
191
|
+
if (!kind)
|
|
192
|
+
missing.push(skillName);
|
|
193
|
+
else if (kind === 'local' && platform === 'claude-code' && scope !== 'project')
|
|
194
|
+
localCopies.push(skillName);
|
|
195
|
+
}
|
|
196
|
+
return { missing, localCopies };
|
|
197
|
+
}
|
|
135
198
|
/** Remove native-advertised duplicates while retaining the canonical count. */
|
|
136
|
-
function buildMirroredSkillActivation(entries,
|
|
199
|
+
function buildMirroredSkillActivation(entries, native, platform) {
|
|
137
200
|
const valid = entries.filter(entry => entry.skillName && entry.triggerDescription);
|
|
138
201
|
if (valid.length === 0)
|
|
139
202
|
return undefined;
|
|
140
|
-
const skills = valid.filter(entry => !
|
|
141
|
-
return {
|
|
203
|
+
const skills = valid.filter(entry => !native.has(entry.skillName.toLowerCase()));
|
|
204
|
+
return {
|
|
205
|
+
count: valid.length,
|
|
206
|
+
skills,
|
|
207
|
+
nativeCount: valid.length - skills.length,
|
|
208
|
+
...nativeAdapterGap(platform, valid, native),
|
|
209
|
+
};
|
|
142
210
|
}
|
|
143
211
|
/** The one command that materializes native adapters for the named mirrored
|
|
144
212
|
* skills on this harness. claude-code is not swept wholesale (adapters there
|
|
@@ -155,6 +223,13 @@ function nativeAdapterInstallCommand(platform, missingNames) {
|
|
|
155
223
|
}
|
|
156
224
|
return null;
|
|
157
225
|
}
|
|
226
|
+
/** The command that replaces one hand-kept copy with its adapter, backing up
|
|
227
|
+
* the copy's SKILL.md first. Replacing a live copy is a per-skill call, so the
|
|
228
|
+
* command names one placeholder rather than the whole list. Null when there
|
|
229
|
+
* are none. */
|
|
230
|
+
function localCopyCutoverCommand(platform, localCopies) {
|
|
231
|
+
return localCopies.length > 0 ? nativeAdapterInstallCommand(platform, ['<name>']) : null;
|
|
232
|
+
}
|
|
158
233
|
/** Convert the mirror API response into a harness-specific activation signal. */
|
|
159
234
|
function activationFromApiRows(params) {
|
|
160
235
|
const entries = params.rows
|
|
@@ -164,6 +239,7 @@ function activationFromApiRows(params) {
|
|
|
164
239
|
skillName: row.skillName,
|
|
165
240
|
description: typeof row.description === 'string' ? row.description : '',
|
|
166
241
|
triggerDescription: row.triggerDescription,
|
|
242
|
+
scope: typeof row.scope === 'string' ? row.scope : undefined,
|
|
167
243
|
}));
|
|
168
|
-
return buildMirroredSkillActivation(entries, readNativeSkillNames(params));
|
|
244
|
+
return buildMirroredSkillActivation(entries, readNativeSkillNames(params), params.platform);
|
|
169
245
|
}
|
package/dist/skill-landing.js
CHANGED
|
@@ -170,6 +170,12 @@ function landBriefcase(gain, cwd, homeDir, projectName) {
|
|
|
170
170
|
const dir = resolveSkillDir(gain.skill, cwd, homeDir);
|
|
171
171
|
if (!dir)
|
|
172
172
|
return 'deferred';
|
|
173
|
+
// A linked skill folder belongs to its target (a tracked repo file, another
|
|
174
|
+
// installer's store), so the fuse is not GrepRAG's to write there. The gain
|
|
175
|
+
// stays pending; a mirrored skill still gets it from `greprag load`.
|
|
176
|
+
// adr: adr/skill-fuse-delivery.md
|
|
177
|
+
if ((0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(dir))
|
|
178
|
+
return 'deferred';
|
|
173
179
|
const skillMd = path.join(dir, 'SKILL.md');
|
|
174
180
|
let body;
|
|
175
181
|
try {
|
|
@@ -35,8 +35,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.computeSkillFileHash = computeSkillFileHash;
|
|
37
37
|
exports.isGeneratedSkillAdapter = isGeneratedSkillAdapter;
|
|
38
|
+
exports.isLinkedSkillFolder = isLinkedSkillFolder;
|
|
39
|
+
exports.isSkillFolderOwnedElsewhere = isSkillFolderOwnedElsewhere;
|
|
38
40
|
exports.isSafeMirroredSkillFile = isSafeMirroredSkillFile;
|
|
39
41
|
const crypto = __importStar(require("node:crypto"));
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
40
44
|
function computeSkillFileHash(files) {
|
|
41
45
|
const hash = crypto.createHash('sha256');
|
|
42
46
|
for (const file of [...files].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
@@ -61,6 +65,61 @@ function isGeneratedSkillAdapter(skillMdBody) {
|
|
|
61
65
|
const body = skillMdBody.replace(/^?---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
|
|
62
66
|
return body.slice(0, 400).includes(GENERATED_ADAPTER_MARKER);
|
|
63
67
|
}
|
|
68
|
+
/** Harness folders whose `skills` folder holds installed copies. */
|
|
69
|
+
const INSTALLED_SKILL_HARNESS_DIRS = ['.claude', '.agents', '.codex', '.opencode', 'opencode'];
|
|
70
|
+
/** True when a skill folder is a link (symlink or Windows junction), live or
|
|
71
|
+
* broken. A linked folder belongs to whatever it points at, often a tracked
|
|
72
|
+
* repo file or another installer's store.
|
|
73
|
+
*
|
|
74
|
+
* adr: adr/skill-bundle-shape.md */
|
|
75
|
+
function isLinkedSkillFolder(dir) {
|
|
76
|
+
try {
|
|
77
|
+
return fs.lstatSync(dir).isSymbolicLink();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** True when another owner holds a skill folder: it is a link, its SKILL.md
|
|
84
|
+
* names the repository that publishes it, or `npx skills` installed it. A
|
|
85
|
+
* published package (OpenWriter's `openwriter setup`, GrepRAG's own bundle)
|
|
86
|
+
* is copied in by its product's installer from a release, and that installer
|
|
87
|
+
* rewrites it on upgrade. `npx skills` records every package it installs in
|
|
88
|
+
* `~/.agents/.skill-lock.json` and copies it into `~/.agents/skills` and
|
|
89
|
+
* `~/.claude/skills`. Both rules read only installed copies, in a harness's
|
|
90
|
+
* `skills` folder: a product's own source tree (GrepRAG's
|
|
91
|
+
* `packages/cli/skill/<name>`) names its repository too and is its author's
|
|
92
|
+
* to edit. The census reads such a folder as external, and no GrepRAG writer
|
|
93
|
+
* writes, replaces or prunes it. A generated adapter is always GrepRAG's own.
|
|
94
|
+
*
|
|
95
|
+
* adr: adr/skill-bundle-shape.md */
|
|
96
|
+
function isSkillFolderOwnedElsewhere(dir) {
|
|
97
|
+
if (isLinkedSkillFolder(dir))
|
|
98
|
+
return true;
|
|
99
|
+
const root = path.dirname(path.resolve(dir));
|
|
100
|
+
const harnessDir = path.basename(path.dirname(root));
|
|
101
|
+
if (path.basename(root) !== 'skills' || !INSTALLED_SKILL_HARNESS_DIRS.includes(harnessDir))
|
|
102
|
+
return false;
|
|
103
|
+
let skillMd = '';
|
|
104
|
+
try {
|
|
105
|
+
skillMd = fs.readFileSync(path.join(dir, 'SKILL.md'), 'utf8');
|
|
106
|
+
}
|
|
107
|
+
catch { /* no package yet */ }
|
|
108
|
+
if (isGeneratedSkillAdapter(skillMd))
|
|
109
|
+
return false;
|
|
110
|
+
const frontmatter = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(skillMd)?.[1] || '';
|
|
111
|
+
if (/^\s*repository:\s*\S/m.test(frontmatter))
|
|
112
|
+
return true;
|
|
113
|
+
if (harnessDir !== '.claude' && harnessDir !== '.agents')
|
|
114
|
+
return false;
|
|
115
|
+
try {
|
|
116
|
+
const lock = JSON.parse(fs.readFileSync(path.join(path.dirname(path.dirname(root)), '.agents', '.skill-lock.json'), 'utf8'));
|
|
117
|
+
return Boolean(lock?.skills && Object.prototype.hasOwnProperty.call(lock.skills, path.basename(dir)));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
64
123
|
function isSafeMirroredSkillFile(file) {
|
|
65
124
|
if (file.path === 'SKILL.md')
|
|
66
125
|
return true;
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
// immediately after installing the npm package.
|
|
8
8
|
// * Other bundled skills — refresh-only. If the operator opted in earlier,
|
|
9
9
|
// keep them current; otherwise leave their skill registry alone.
|
|
10
|
-
//
|
|
10
|
+
//
|
|
11
|
+
// The installed Claude chip-spawn doc (~/.claude/docs/chip-spawn.md) is retired:
|
|
12
|
+
// `greprag load chip-spawn` serves that doctrine from the package and cannot
|
|
13
|
+
// drift. Refreshing a leftover copy here kept a retired surface alive.
|
|
11
14
|
//
|
|
12
15
|
// OpenCode has no slash-skill directory in this package; `greprag init
|
|
13
16
|
// --opencode` installs the plugin.
|
|
@@ -24,15 +27,11 @@ const platformTargets = [
|
|
|
24
27
|
name: 'Claude Code',
|
|
25
28
|
agentDir: path.join(home, '.claude'),
|
|
26
29
|
skillsTarget: path.join(home, '.claude', 'skills'),
|
|
27
|
-
docsTarget: path.join(home, '.claude', 'docs'),
|
|
28
|
-
refreshClaudeDocs: true,
|
|
29
30
|
},
|
|
30
31
|
{
|
|
31
32
|
name: 'Codex',
|
|
32
33
|
agentDir: path.join(home, '.codex'),
|
|
33
34
|
skillsTarget: path.join(home, '.codex', 'skills'),
|
|
34
|
-
docsTarget: path.join(home, '.codex', 'docs'),
|
|
35
|
-
refreshClaudeDocs: false,
|
|
36
35
|
},
|
|
37
36
|
];
|
|
38
37
|
|
|
@@ -76,20 +75,6 @@ try {
|
|
|
76
75
|
console.error(' greprag postinstall: skill sync skipped (' + err.message + ')');
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
const chipSpawnSrc = path.join(skillRoot, 'templates', 'chip-spawn.md');
|
|
80
|
-
try {
|
|
81
|
-
for (const target of platformTargets) {
|
|
82
|
-
if (!target.refreshClaudeDocs) continue;
|
|
83
|
-
const chipSpawnDest = path.join(target.docsTarget, 'chip-spawn.md');
|
|
84
|
-
if (fs.existsSync(chipSpawnSrc) && fs.existsSync(chipSpawnDest)) {
|
|
85
|
-
fs.copyFileSync(chipSpawnSrc, chipSpawnDest);
|
|
86
|
-
refreshed.push(`${target.name} docs/chip-spawn.md`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.error(' greprag postinstall: chip-spawn refresh skipped (' + err.message + ')');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
78
|
// The Codex startup watcher was retired in v5.x, but upgrades left its login
|
|
94
79
|
// files in place. On Windows the leftover script loops every 5s, reruns the
|
|
95
80
|
// retired command, and appends its error to codex-watch.log forever (600 MB
|
|
@@ -96,10 +96,10 @@ Omit `storeIds` (or use `--all`) to search every store under the calling tenant.
|
|
|
96
96
|
## Walk
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
greprag corpus walk "<store>" <nodeId> [--before 2 --after 2]
|
|
99
|
+
greprag corpus walk "<store>" <nodeId> [--before 2 --after 2] [--max-chars N]
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
Surrounding-context fetch. ~50ms, no LLM.
|
|
102
|
+
Surrounding-context fetch. ~50ms, no LLM. `walk` and `section` print each node's full text (search snippets stay short); `--max-chars N` is the opt-in cut.
|
|
103
103
|
|
|
104
104
|
## List / delete / status
|
|
105
105
|
|
|
@@ -145,7 +145,7 @@ Append missing entries to the existing arrays in `~/.claude/settings.json` (crea
|
|
|
145
145
|
```json
|
|
146
146
|
{ "matcher": "mcp__ccd_session__spawn_task", "hooks": [{ "type": "command", "command": "greprag-hook pre-spawn-check", "timeout": 3000 }] }
|
|
147
147
|
```
|
|
148
|
-
Fires before every `spawn_task` chip dispatch. Validates against
|
|
148
|
+
Fires before every `spawn_task` chip dispatch. Validates against `greprag load chip-spawn`: title prefix `Chip: ` and the current Block 1 (its `git branch -m` step that keeps the app-provided worktree and its `git worktree add` local-start step). Returns `permissionDecision: "deny"` with remediation on failure; the agent re-composes.
|
|
149
149
|
|
|
150
150
|
The legacy `user_prompt_submit_notify` hook was removed in v5.6.1 — live inbox delivery is now the Monitor watcher's job. Do not install it.
|
|
151
151
|
|
package/skill/mechanic/SKILL.md
CHANGED
|
@@ -19,8 +19,7 @@ license: MIT
|
|
|
19
19
|
|
|
20
20
|
# Mechanic
|
|
21
21
|
|
|
22
|
-
> **Memory-first friction reflex (
|
|
23
|
-
> supersedes the routing/queueing reflex below wherever they conflict).** Search
|
|
22
|
+
> **Memory-first friction reflex (grepragOS laws 3–4).** Search
|
|
24
23
|
> Memory for the exact error/friction plus repo/tool first. Apply a clear
|
|
25
24
|
> remembered overcome and continue. Only when Memory has no answer, the overcome
|
|
26
25
|
> fails, or the same friction repeats does
|
|
@@ -30,12 +29,9 @@ license: MIT
|
|
|
30
29
|
> fix → explain the friction and fix in human terms → commit useful work → hand it to
|
|
31
30
|
> the mission owner. A full-goal chip with no live parent follows
|
|
32
31
|
> `.greprag/delivery.json` as mission owner; no second lifecycle approval is
|
|
33
|
-
> introduced. `greprag
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
> method still applies to the EXISTING backlog and to audit notes. A chip titled
|
|
37
|
-
> `FIX: <friction>` follows this contract; legacy `MECHANIC: <Mission>` chips
|
|
38
|
-
> read the same way.
|
|
32
|
+
> introduced. `greprag fix log` is for audit/design-input notes only; this
|
|
33
|
+
> skill's queue-digestion method applies to the existing backlog and to audit
|
|
34
|
+
> notes. A chip titled `FIX: <friction>` follows this contract.
|
|
39
35
|
|
|
40
36
|
**FIX type router.** Type means durable repair surface, not symptom:
|
|
41
37
|
`harness` = hooks/watchers/task dispatch/harness behavior; `doctrine` =
|
|
@@ -69,7 +65,7 @@ The seam: **author freely (shadow), arm deliberately (operator).** Full doctrine
|
|
|
69
65
|
- **Phase 0 — Fix digestion.** Census the queue tenant-wide FIRST (queues silo per project), then drain the chosen one: per fix, repair the broken mechanism (hooks first, code second) and/or record the durable gotcha, then retire. Deliberate + auto signals outrank mined ones, so this runs first.
|
|
70
66
|
- **Phase A — Drift check.** Audit standing fixes for rot: stale paths, dead conventions (derived live), misfiled static knowledge, age.
|
|
71
67
|
- **Phase B — Episodic mining.** Mine episodic prose summaries for friction worth a fix.
|
|
72
|
-
- **Phase C — Global promotion.** Promote project-agnostic repairs
|
|
68
|
+
- **Phase C — Global promotion.** Promote project-agnostic repairs into the chip-spawn template (`packages/cli/skill/templates/chip-spawn.md`), the one copy `greprag load chip-spawn` serves.
|
|
73
69
|
|
|
74
70
|
## The "worth repairing?" gate (core discipline)
|
|
75
71
|
|
|
@@ -86,7 +82,7 @@ Same gate the hourly compactor applies at write-time — Mechanic and compactor
|
|
|
86
82
|
- **Tier-2 actions need explicit confirm** (arm/graduate, deny/transform, secrets·money·external communication, deleting claimed user work). Ordinary profile-declared repo delivery does not. Tier 0/1 just do + report.
|
|
87
83
|
- **Removal-first repair.** Remove the offending text, gate, control, duplicated doctrine, or wrong boundary before adding new guidance. Add prose or controls only when no removal action can solve the friction; never wrap bad doctrine in explanatory caveats when deleting the bad boundary fixes it.
|
|
88
84
|
- **Bulk is fine for Tier-0 when the set is named aloud** (clearing verified-stale / noise fixes); never bulk a Tier-2 action.
|
|
89
|
-
- **Never
|
|
85
|
+
- **Never promote into the chip-spawn template without confirm.** Global rules are sticky.
|
|
90
86
|
- **Cross-project carve-out.** Default to the current project (resolved via `greprag fix list`). The Phase 0 census MAY read sibling queues to show where fixes piled up; *draining* one requires the operator to name it, and every `add`/`delete` against it MUST carry `--project <that-project>`. Never touch a sibling silently.
|
|
91
87
|
- **Harness scope is mandatory.** Friction carries its source harness (`claude-code`, `codex`, `opencode`). Repairs to hooks/skills/watchers must target that harness with `greprag mechanic install ... --harness <name>`. In a Codex session, default to `--harness codex`; use `--harness all` only for deliberately portable mechanisms.
|
|
92
88
|
- **Delivery is committed-state convergence.** Before merging, pushing, deploying, publishing, tagging, or cutting a release, send one fire-and-forget notice to same-repo peers and continue immediately. Recipients reply only for a concrete conflict; silence is approval. Never poll, wait, inspect peer status, or follow up. Sweep every ready same-repo commit and ignore all uncommitted and untracked work. Resolve Git conflicts directly; no deploy lease or peer ownership ceremony exists.
|
|
@@ -157,7 +153,7 @@ mechanism and/or **record** the durable gotcha — and runs FIRST.
|
|
|
157
153
|
|
|
158
154
|
- **Is there a broken MECHANISM?** Repair it on the lowest rung that holds:
|
|
159
155
|
1. **Hooks first.** A PreToolUse guard, a UserPromptSubmit injection, a notification — same machinery as fix-triggers, ships as DATA, no deploy. Most mechanism fixes repair here.
|
|
160
|
-
2. **Code second.** Only when a hook can't express it: server logic, a bounce, a schema/CLI change. Don't implement code-tier inline — spawn a chip (`spawn_task`,
|
|
156
|
+
2. **Code second.** Only when a hook can't express it: server logic, a bounce, a schema/CLI change. Don't implement code-tier inline — spawn a chip (`spawn_task`, method from `greprag load chip-spawn`) or hand to the operator.
|
|
161
157
|
3. **Lock it (regression rung).** A code/mechanism repair MUST also mint a regression test from the REAL failure signal (the smell's evidence / a repro from the trace), so the *behavior* is locked, not just the lore. The queue's product is **fix + lore + locked test** — an ADR locks the *why* in prose; this locks the *what* in an executable. Don't retire the fix until the test exists (fails without the repair, passes with it).
|
|
162
158
|
|
|
163
159
|
Recurring failure class → route through `/root-cause`: repair the pattern, never guard today's trigger.
|
|
@@ -217,38 +213,29 @@ mechanism and/or **record** the durable gotcha — and runs FIRST.
|
|
|
217
213
|
|
|
218
214
|
Episodic memory is prose-only (the claim layer was dropped, 2026-06-08) — mine the daily/hourly **prose summaries** for friction worth recording. ~90% of raw learnings fail the gate; that's expected.
|
|
219
215
|
|
|
220
|
-
1. `greprag
|
|
221
|
-
2.
|
|
222
|
-
|
|
223
|
-
FROM=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
|
224
|
-
TO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
225
|
-
curl -sf "https://api.greprag.com/v1/memory/by-period?projectId=<projectId>&type=daily&from=$FROM&to=$TO&limit=30" \
|
|
226
|
-
-H "Authorization: Bearer $GREPRAG_API_KEY"
|
|
227
|
-
```
|
|
228
|
-
Parse `rows[]` (`created_at`, `body`).
|
|
229
|
-
3. Scan the prose for durable-property sentences. Markers: `learned that`, `the gotcha is`, `won't work because`, `turns out`, `requires`, `only … if`, `caps at`, `blocks`. Skip < 30 or > 300 chars.
|
|
230
|
-
4. **Gate every candidate.** Surface only passers, one at a time:
|
|
216
|
+
1. Pull the last 30 daily summaries: `greprag memory daily --last 30 --format json` (add `--project <name>` for a sibling). Parse `rows[]` (`content`).
|
|
217
|
+
2. Read each summary and pull sentences that state a durable property (a cause, a limit, a gotcha).
|
|
218
|
+
3. **Gate every candidate.** Surface only passers, one at a time:
|
|
231
219
|
```
|
|
232
220
|
Candidate (durable · reusable · non-obvious):
|
|
233
221
|
"<sentence>"
|
|
234
222
|
Record as a fix? (rec)ord / (e)dit / (s)kip
|
|
235
223
|
```
|
|
236
224
|
record → ask scope (`general`; `env` for creds/vars; `<subsystem>-touch`) → `greprag fix log "<sentence>" --repaired --scope <scope>`. edit → polish to a clean rule first.
|
|
237
|
-
|
|
225
|
+
4. Summary: `Phase B: scanned N summaries, M passed the gate. Recorded A. Skipped S.`
|
|
238
226
|
|
|
239
227
|
## Phase C — Global promotion
|
|
240
228
|
|
|
241
229
|
1. Re-pull `greprag fix list --repaired --format json`.
|
|
242
230
|
2. Classify each: **project-specific** (skip — mentions `packages/`, a table, an internal service) vs **project-agnostic** (universal only — `worktree`, `npm`, `git`, `Claude Code`, `hook`, `~/.claude/`, generic env vars).
|
|
243
|
-
3.
|
|
244
|
-
4. Present each:
|
|
231
|
+
3. Present each:
|
|
245
232
|
```
|
|
246
233
|
[<nodeId>] universal-only:
|
|
247
234
|
"<text>"
|
|
248
235
|
Promote to global chip-spawn rule? (y)es / (n)o / (e)dit
|
|
249
236
|
```
|
|
250
|
-
yes →
|
|
251
|
-
|
|
237
|
+
yes → add it to `packages/cli/skill/templates/chip-spawn.md` in the greprag repo (HARD-RULE styling when apt) → `greprag fix delete <nodeId>` → confirm `Promoted [<nodeId>] → global.`
|
|
238
|
+
4. Summary: `Phase C: evaluated N. Promoted P. Skipped S.`
|
|
252
239
|
|
|
253
240
|
## Report
|
|
254
241
|
|
|
@@ -278,9 +265,6 @@ Repairs (D3 rows attached to fixes) are managed with `greprag mechanic`, not by
|
|
|
278
265
|
greprag mechanic set legacy role marker for mechanic follow-up rails;
|
|
279
266
|
fan out `mechanic_live` to open sessions
|
|
280
267
|
greprag mechanic role show whether this project is the live Mechanic
|
|
281
|
-
greprag mechanic friction "obs" RETIRED as the reflex (2026-07-12): spawn a fix chip
|
|
282
|
-
instead — `greprag fix spawn --type <type> "<unit>"`. The CLI refuses
|
|
283
|
-
new working-session use.
|
|
284
268
|
greprag mechanic reply --to <8hex> typed follow-up from Mechanic to source session;
|
|
285
269
|
"msg" [--friction-id <id>] do not use generic send for mechanic workflow
|
|
286
270
|
greprag mechanic status inventory: live (shadow|active) repairs grouped by status,
|
|
@@ -293,15 +277,6 @@ greprag mechanic why <nodeId> provenance: the friction → fix → repai
|
|
|
293
277
|
greprag mechanic off / on PANIC SWITCH — local file, no network; suspends ALL repairs
|
|
294
278
|
```
|
|
295
279
|
|
|
296
|
-
**Legacy Mechanic role.** A `--mechanic` inbox watcher can still receive
|
|
297
|
-
legacy `mechanic_friction` / `mechanic_reply` rows in addition to its own session
|
|
298
|
-
lane, but live working sessions no longer route friction there. Laws 3–4 search
|
|
299
|
-
Memory first, then route each unresolved unit through
|
|
300
|
-
`greprag fix spawn --type <type> "<one unit>"`. `greprag mechanic set` only
|
|
301
|
-
fans out a notice that teaches this reflex and preserves the reply rail for old
|
|
302
|
-
mechanic threads. The Mechanic replies with `greprag mechanic reply --to <source-session>
|
|
303
|
-
"..."` only for legacy follow-up coordination.
|
|
304
|
-
|
|
305
280
|
**Born shadow → graduate.** Every repair is born `shadow`: it matches and logs would-have-fired
|
|
306
281
|
evidence but injects nothing. Graduation is evidence-first — when `status` flags a row `⚑`, review
|
|
307
282
|
its fire history (`why`) and `enable` it. Nothing goes live without the operator; the gates are
|
|
@@ -22,7 +22,7 @@ Each: **signature** (how it shows up in the run) · **location** (where it lives
|
|
|
22
22
|
### 1. mis-route
|
|
23
23
|
- **Signature:** the skill fired for a task outside its lane, OR you reached for it by an obvious phrase and it didn't trigger.
|
|
24
24
|
- **Location:** the `description` / `Use when:` trigger list, or the `NOT for:` boundary.
|
|
25
|
-
- **Fix:** add the
|
|
25
|
+
- **Fix:** broaden or add the intent category the description names (a verbatim phrase only when no category can express it; fold near-synonyms into one category), OR add/sharpen a `NOT for:` line naming the lane it wrongly claimed.
|
|
26
26
|
|
|
27
27
|
### 2. wrong-altitude
|
|
28
28
|
- **Signature:** the skill did work it should have delegated (implemented inline when it should hand to a chip; drafted when it should route to a leaf), or the reverse.
|
|
@@ -53,6 +53,6 @@ Each: **signature** (how it shows up in the run) · **location** (where it lives
|
|
|
53
53
|
|
|
54
54
|
- **One friction, one edit.** If a run surfaced two defects, make two separate edits, each shown for its own accept/revert. Never batch.
|
|
55
55
|
- **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
|
-
- **
|
|
56
|
+
- **Edit the canonical source.** Managed skills: `greprag skill edit <name> --skill-id <id>`; greprag-bundled skills: `packages/cli/skill/<name>/`. Never edit a generated `~/.claude/skills/<name>/SKILL.md` adapter or install artifact.
|
|
57
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
58
|
- **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.
|