greprag 5.83.2 → 5.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent-doctrine-file.js +5 -128
- package/dist/commands/agents.js +125 -846
- package/dist/commands/app-model.js +1 -1
- package/dist/commands/delivery-reminder.js +1 -1
- package/dist/commands/discord.js +12 -61
- package/dist/commands/inbox-watch.js +0 -13
- package/dist/commands/init.js +17 -29
- package/dist/commands/load.js +15 -35
- package/dist/commands/parity.js +1 -1
- package/dist/commands/reminder-registry.js +1 -8
- package/dist/commands/session.js +2 -2
- package/dist/commands/skill-mirror-reminder.js +1 -19
- package/dist/commands/skill.js +236 -448
- package/dist/hook.js +31 -54
- package/dist/index.js +19 -152
- package/dist/native-adapter-refresh.js +15 -18
- package/dist/native-skill-mirror.js +11 -89
- package/dist/opencode-plugin.bundle.js +82 -282
- package/dist/reminder-injector.js +1 -1
- package/dist/skill-activation-manifest.js +14 -37
- package/dist/skill-git-ingest.js +297 -0
- package/dist/skill-landing.js +7 -173
- package/dist/skill-mirror-client.js +4 -251
- package/dist/skill-staleness.js +3 -6
- package/package.json +1 -1
- package/skill/commander/SKILL.md +10 -12
- package/skill/greprag/SKILL.md +1 -1
- package/skill/greprag/docs/discord-handoff.md +6 -30
- package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
- package/skill/templates/chip-spawn.md +6 -2
- package/skill/templates/delivery.md +16 -0
- package/skill/templates/os.md +1 -1
- package/skill/templates/prompt-audit.md +26 -49
- package/skill/templates/skill-change.md +15 -11
- package/dist/commands/sms.js +0 -215
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/** Skill
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* shadows "last used state." Best-effort end to end: a mirror failure never
|
|
7
|
-
* touches the turn.
|
|
8
|
-
*
|
|
9
|
-
* Local freshness state (`~/.greprag/state/skill-mirror.json`) remembers the
|
|
10
|
-
* last-mirrored hash per skill so an unchanged skill costs one hash pass and
|
|
11
|
-
* ZERO network. Markdown only, v1 — SKILL.md + docs/**\/*.md; script-bearing
|
|
12
|
-
* assets stay file-bound (see the spec's named gaps). */
|
|
2
|
+
/** Skill package reader: the markdown a mirrored skill carries (SKILL.md +
|
|
3
|
+
* docs/**\/*.md) and its content hash. Used to verify an export and to send a
|
|
4
|
+
* loadout. The mirror itself is written only by git ingestion
|
|
5
|
+
* (skill-git-ingest.ts, adr/skill-ownership-git.md). */
|
|
13
6
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
7
|
if (k2 === undefined) k2 = k;
|
|
15
8
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -46,16 +39,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
46
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
40
|
exports.collectSkillFiles = collectSkillFiles;
|
|
48
41
|
exports.computeMirrorHash = computeMirrorHash;
|
|
49
|
-
exports.dryRunNativeSkillAdoption = dryRunNativeSkillAdoption;
|
|
50
|
-
exports.syncNativeSkillAdoptions = syncNativeSkillAdoptions;
|
|
51
|
-
exports.readMirrorState = readMirrorState;
|
|
52
|
-
exports.writeMirrorState = writeMirrorState;
|
|
53
|
-
exports.maybeMirrorSkill = maybeMirrorSkill;
|
|
54
42
|
const fs = __importStar(require("fs"));
|
|
55
43
|
const path = __importStar(require("path"));
|
|
56
|
-
const skill_landing_1 = require("./skill-landing");
|
|
57
|
-
const native_skill_adoption_1 = require("./native-skill-adoption");
|
|
58
|
-
const native_skill_mirror_1 = require("./native-skill-mirror");
|
|
59
44
|
const skill_mirror_files_1 = require("./skill-mirror-files");
|
|
60
45
|
/** Mirrors the server's caps (SKILL_MIRROR_* in @greprag/core — the CLI is
|
|
61
46
|
* core-free, so the numbers are restated; the server re-validates anyway). */
|
|
@@ -116,235 +101,3 @@ function collectSkillFiles(skillDir) {
|
|
|
116
101
|
function computeMirrorHash(files) {
|
|
117
102
|
return (0, skill_mirror_files_1.computeSkillFileHash)(files);
|
|
118
103
|
}
|
|
119
|
-
function dryRunNativeSkillAdoption(params) {
|
|
120
|
-
return (0, native_skill_adoption_1.planNativeSkillAdoption)({ ...params, collectFiles: collectSkillFiles });
|
|
121
|
-
}
|
|
122
|
-
function adoptedSkillId(candidate) {
|
|
123
|
-
return candidate.scope === 'project'
|
|
124
|
-
? `project:${candidate.projectId}:${candidate.skillName}`
|
|
125
|
-
: `global:${candidate.skillName}`;
|
|
126
|
-
}
|
|
127
|
-
async function responseJson(response) {
|
|
128
|
-
try {
|
|
129
|
-
return await response.json();
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
return {};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async function ensureNativeAdoptionOriginalSnapshot(params) {
|
|
136
|
-
const skillId = adoptedSkillId(params.candidate);
|
|
137
|
-
const snapshotPath = `/v1/skillmirror/${encodeURIComponent(params.candidate.skillName)}/snapshots/original`;
|
|
138
|
-
const body = {
|
|
139
|
-
scope: params.candidate.scope,
|
|
140
|
-
projectId: params.candidate.projectId,
|
|
141
|
-
skillId,
|
|
142
|
-
source: 'disk',
|
|
143
|
-
sourceRef: path.join(params.candidate.root, params.candidate.skillName),
|
|
144
|
-
reason: 'captured native skill package before GrepRAG adapter adoption',
|
|
145
|
-
};
|
|
146
|
-
const created = await fetch(`${params.apiUrl}${snapshotPath}`, {
|
|
147
|
-
method: 'POST',
|
|
148
|
-
headers: { 'Authorization': `Bearer ${params.apiKey}`, 'Content-Type': 'application/json' },
|
|
149
|
-
body: JSON.stringify(body),
|
|
150
|
-
});
|
|
151
|
-
if (created.ok)
|
|
152
|
-
return true;
|
|
153
|
-
if (created.status !== 409)
|
|
154
|
-
return false;
|
|
155
|
-
const query = new URLSearchParams({
|
|
156
|
-
scope: params.candidate.scope,
|
|
157
|
-
skillId,
|
|
158
|
-
...(params.candidate.projectId ? { projectId: params.candidate.projectId } : {}),
|
|
159
|
-
});
|
|
160
|
-
const existing = await fetch(`${params.apiUrl}${snapshotPath}?${query.toString()}`, {
|
|
161
|
-
method: 'GET',
|
|
162
|
-
headers: { 'Authorization': `Bearer ${params.apiKey}` },
|
|
163
|
-
});
|
|
164
|
-
if (!existing.ok)
|
|
165
|
-
return false;
|
|
166
|
-
const data = await responseJson(existing);
|
|
167
|
-
const snapshot = data.snapshot;
|
|
168
|
-
return snapshot?.contentHash === params.candidate.sourceHash;
|
|
169
|
-
}
|
|
170
|
-
async function syncNativeSkillAdoptions(params) {
|
|
171
|
-
const plan = dryRunNativeSkillAdoption(params);
|
|
172
|
-
const result = {
|
|
173
|
-
plan,
|
|
174
|
-
adopted: 0,
|
|
175
|
-
blocked: plan.blocked.length,
|
|
176
|
-
skipped: plan.skipped.length,
|
|
177
|
-
};
|
|
178
|
-
if (params.dryRun)
|
|
179
|
-
return result;
|
|
180
|
-
for (const candidate of plan.candidates) {
|
|
181
|
-
const body = {
|
|
182
|
-
files: candidate.files,
|
|
183
|
-
contentHash: candidate.sourceHash,
|
|
184
|
-
scope: candidate.scope,
|
|
185
|
-
projectId: candidate.projectId,
|
|
186
|
-
projectName: candidate.projectName,
|
|
187
|
-
};
|
|
188
|
-
const response = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(candidate.skillName)}`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
headers: { 'Authorization': `Bearer ${params.apiKey}`, 'Content-Type': 'application/json' },
|
|
191
|
-
body: JSON.stringify(body),
|
|
192
|
-
});
|
|
193
|
-
if (!response.ok) {
|
|
194
|
-
result.blocked++;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
const snapshotted = await ensureNativeAdoptionOriginalSnapshot({
|
|
198
|
-
apiUrl: params.apiUrl,
|
|
199
|
-
apiKey: params.apiKey,
|
|
200
|
-
candidate,
|
|
201
|
-
});
|
|
202
|
-
if (!snapshotted) {
|
|
203
|
-
result.blocked++;
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
const installed = (0, native_skill_adoption_1.installAdoptedNativeSkill)({
|
|
207
|
-
platform: params.platform,
|
|
208
|
-
root: candidate.root,
|
|
209
|
-
expectedSourceHash: candidate.sourceHash,
|
|
210
|
-
collectFiles: collectSkillFiles,
|
|
211
|
-
skill: {
|
|
212
|
-
skillName: candidate.skillName,
|
|
213
|
-
skillId: adoptedSkillId(candidate),
|
|
214
|
-
scope: candidate.scope,
|
|
215
|
-
projectId: candidate.projectId,
|
|
216
|
-
projectName: candidate.projectName,
|
|
217
|
-
files: candidate.files,
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
if (installed === 'installed')
|
|
221
|
-
result.adopted++;
|
|
222
|
-
else if (installed === 'blocked')
|
|
223
|
-
result.blocked++;
|
|
224
|
-
else
|
|
225
|
-
result.skipped++;
|
|
226
|
-
}
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
function materializeLocally(skillName, files, cwd) {
|
|
230
|
-
for (const platform of native_skill_mirror_1.NATIVE_SKILL_ADAPTER_PLATFORMS) {
|
|
231
|
-
try {
|
|
232
|
-
(0, native_skill_mirror_1.applyNativeSkillMirror)({
|
|
233
|
-
platform,
|
|
234
|
-
skills: [{
|
|
235
|
-
skillName,
|
|
236
|
-
skillId: `global:${skillName}`,
|
|
237
|
-
scope: 'global',
|
|
238
|
-
contentHash: computeMirrorHash(files),
|
|
239
|
-
files,
|
|
240
|
-
}],
|
|
241
|
-
projectDir: cwd,
|
|
242
|
-
prune: false,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
catch { /* local adapter failure never breaks turn capture */ }
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
function statePath() {
|
|
249
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
250
|
-
return path.join(home, '.greprag', 'state', 'skill-mirror.json');
|
|
251
|
-
}
|
|
252
|
-
function readMirrorState() {
|
|
253
|
-
try {
|
|
254
|
-
const parsed = JSON.parse(fs.readFileSync(statePath(), 'utf-8'));
|
|
255
|
-
if (parsed && typeof parsed.skills === 'object' && parsed.skills) {
|
|
256
|
-
return { skills: parsed.skills };
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
catch { /* missing/unreadable → empty */ }
|
|
260
|
-
return { skills: {} };
|
|
261
|
-
}
|
|
262
|
-
function writeMirrorState(state) {
|
|
263
|
-
try {
|
|
264
|
-
const file = statePath();
|
|
265
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
266
|
-
fs.writeFileSync(file, JSON.stringify(state, null, 2) + '\n');
|
|
267
|
-
}
|
|
268
|
-
catch { /* best-effort — worst case is one redundant upload next use */ }
|
|
269
|
-
}
|
|
270
|
-
async function fetchRemoteSkillHash(params) {
|
|
271
|
-
try {
|
|
272
|
-
const res = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(params.skill)}?scope=global`, {
|
|
273
|
-
headers: { 'Authorization': `Bearer ${params.apiKey}` },
|
|
274
|
-
});
|
|
275
|
-
if (res.status === 404)
|
|
276
|
-
return null;
|
|
277
|
-
if (!res.ok)
|
|
278
|
-
return null;
|
|
279
|
-
const data = await responseJson(res);
|
|
280
|
-
return typeof data.contentHash === 'string' ? data.contentHash : null;
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/** Mirror one skill if its content changed since the last mirror. Never throws. */
|
|
287
|
-
async function maybeMirrorSkill(params) {
|
|
288
|
-
try {
|
|
289
|
-
if (!/^[A-Za-z0-9._-]{1,64}$/.test(params.skill))
|
|
290
|
-
return 'skipped';
|
|
291
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
292
|
-
const dir = (0, skill_landing_1.resolveSkillDir)(params.skill, params.cwd, homeDir);
|
|
293
|
-
if (!dir)
|
|
294
|
-
return 'skipped';
|
|
295
|
-
const files = collectSkillFiles(dir);
|
|
296
|
-
if (!files)
|
|
297
|
-
return 'skipped';
|
|
298
|
-
// The disk copy of an already-mirrored skill is the generated ADAPTER that
|
|
299
|
-
// `materializeLocally` wrote below — a launcher whose whole body says "run
|
|
300
|
-
// `greprag load <name>`". Uploading it makes it canon, and `greprag load`
|
|
301
|
-
// then serves the launcher back to the agent that was told to run it: the
|
|
302
|
-
// skill's real instructions are gone and nothing errors. That loop closed
|
|
303
|
-
// here, one turn after every first mirror, and had already eaten the
|
|
304
|
-
// canonical body of skills including anti-ai, chrome, gmail and root-cause.
|
|
305
|
-
//
|
|
306
|
-
// An adapter is never a mirror source. Canon changes through the guarded
|
|
307
|
-
// edit path (`greprag skill edit` / `skill mirror apply`), never by
|
|
308
|
-
// shadowing a file greprag itself generated. adr: adr/skill-adapter-never-canon.md
|
|
309
|
-
const main = files.find(file => file.path === 'SKILL.md');
|
|
310
|
-
if (main && (0, skill_mirror_files_1.isGeneratedSkillAdapter)(main.content))
|
|
311
|
-
return 'skipped';
|
|
312
|
-
const hash = computeMirrorHash(files);
|
|
313
|
-
const state = readMirrorState();
|
|
314
|
-
const localLease = state.skills[params.skill]?.hash;
|
|
315
|
-
if (localLease === hash) {
|
|
316
|
-
materializeLocally(params.skill, files, params.cwd);
|
|
317
|
-
return 'fresh';
|
|
318
|
-
}
|
|
319
|
-
if (!localLease) {
|
|
320
|
-
const remoteHash = await fetchRemoteSkillHash(params);
|
|
321
|
-
if (remoteHash === hash) {
|
|
322
|
-
state.skills[params.skill] = { hash, mirroredAt: new Date().toISOString() };
|
|
323
|
-
writeMirrorState(state);
|
|
324
|
-
materializeLocally(params.skill, files, params.cwd);
|
|
325
|
-
return 'fresh';
|
|
326
|
-
}
|
|
327
|
-
if (remoteHash)
|
|
328
|
-
return 'skipped';
|
|
329
|
-
}
|
|
330
|
-
const res = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(params.skill)}`, {
|
|
331
|
-
method: 'POST',
|
|
332
|
-
headers: { 'Authorization': `Bearer ${params.apiKey}`, 'Content-Type': 'application/json' },
|
|
333
|
-
body: JSON.stringify({
|
|
334
|
-
files,
|
|
335
|
-
contentHash: hash,
|
|
336
|
-
scope: 'global',
|
|
337
|
-
expectedHash: localLease,
|
|
338
|
-
}),
|
|
339
|
-
});
|
|
340
|
-
if (!res.ok)
|
|
341
|
-
return 'skipped';
|
|
342
|
-
state.skills[params.skill] = { hash, mirroredAt: new Date().toISOString() };
|
|
343
|
-
writeMirrorState(state);
|
|
344
|
-
materializeLocally(params.skill, files, params.cwd);
|
|
345
|
-
return 'mirrored';
|
|
346
|
-
}
|
|
347
|
-
catch {
|
|
348
|
-
return 'skipped';
|
|
349
|
-
}
|
|
350
|
-
}
|
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
|
|
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.');
|
|
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.');
|
|
@@ -372,11 +372,8 @@ function formatSkillNoSourceBinding(skillName) {
|
|
|
372
372
|
return [
|
|
373
373
|
'[greprag skill-staleness - no source binding]',
|
|
374
374
|
`/${skillName} has no source bindings.`,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
`Agent-safe edit path: \`greprag skill mirror export ${skillName} --to <dir>\`, patch the exported Markdown files non-interactively, then \`greprag skill mirror apply <dir> --skill ${skillName} --expected-hash <exported-hash>\`.`,
|
|
378
|
-
`Human editor shortcut: \`greprag skill edit ${skillName}\`; Codex auto-skips the editor, and agents may pass \`--no-open\` explicitly.`,
|
|
379
|
-
`After binding a real source repo, stamp it with \`greprag skill source stamp ${skillName} --project <p> --path <rel>\`.`,
|
|
375
|
+
`Authority: git. Edit ~/.claude/skills/${skillName}/ (dotclaude) or <repo>/.claude/skills/${skillName}/ and commit; GrepRAG mirrors the committed files read only. Codex/OpenCode SKILL.md files are generated launchers, never content.`,
|
|
376
|
+
`To watch a source repo, stamp a binding into the git SKILL.md with \`greprag skill source stamp ${skillName} --project <p> --path <rel>\`, then commit.`,
|
|
380
377
|
].join('\n') + '\n';
|
|
381
378
|
}
|
|
382
379
|
function stampSourceBinding(params) {
|
package/package.json
CHANGED
package/skill/commander/SKILL.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: commander
|
|
3
3
|
description: |
|
|
4
|
-
DEPRECATED 2026-06-07
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
retired. Pairing still works; "handoff" is a no-op. Kept for reference.
|
|
4
|
+
DEPRECATED 2026-06-07, retired 2026-10-06 — the handoff-pin +
|
|
5
|
+
emergency-router model this skill wrapped is gone, and so is the Front
|
|
6
|
+
Desk that replaced it. Nothing reads DMs sent to the bot any more.
|
|
7
|
+
Pairing still works, so sessions can DM the operator. Kept for reference.
|
|
9
8
|
|
|
10
9
|
Field-commander interface over greprag's Discord DM bridge. Pair your
|
|
11
10
|
Discord account once, then DM the bot to put any open Claude session
|
|
@@ -28,13 +27,12 @@ metadata:
|
|
|
28
27
|
license: MIT
|
|
29
28
|
---
|
|
30
29
|
|
|
31
|
-
> **⚠
|
|
32
|
-
>
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
> historical reference only; do not build on it.
|
|
30
|
+
> **⚠ RETIRED (2026-10-06).** The **handoff pin + emergency router this skill
|
|
31
|
+
> describes are gone**, and so is the Front Desk that replaced them on
|
|
32
|
+
> 2026-06-07 (`adr/front-desk-retirement.md`). Nothing reads DMs sent to the
|
|
33
|
+
> bot; it answers each with a fixed line. Pairing is unchanged, so sessions can
|
|
34
|
+
> still DM the operator with `greprag send --to discord:<snowflake>`. This file
|
|
35
|
+
> is kept for historical reference only; do not build on it.
|
|
38
36
|
|
|
39
37
|
# Commander — Discord DM bridge
|
|
40
38
|
|
package/skill/greprag/SKILL.md
CHANGED
|
@@ -237,7 +237,7 @@ Child watch events may appear on the parent TUI — resume the child to act. Do
|
|
|
237
237
|
- `docs/inbox-watch.md` — SSE watcher patterns, liveness model, parent-side / post-send patterns
|
|
238
238
|
- `docs/doctor.md` — `greprag doctor` (identity drift, orphan consolidation)
|
|
239
239
|
- `docs/corpus.md` — upload + search arbitrary text (books, codebases, voice samples)
|
|
240
|
-
- `docs/discord-handoff.md` —
|
|
240
|
+
- `docs/discord-handoff.md` — Discord DM bridge (`greprag discord pair`, `send --to discord:`; the handoff is retired)
|
|
241
241
|
- `docs/memory-advanced.md` — raw curl, `/v1/memory/by-period` `--type` values, naming note
|
|
242
242
|
- `docs/fix.md` — log + query project-specific fixes (the Mechanic's queue)
|
|
243
243
|
- `docs/discover.md` — cross-project advisor lookup (`greprag discover`)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
# Discord
|
|
1
|
+
# Discord DM bridge
|
|
2
2
|
|
|
3
|
-
Discord DM
|
|
4
|
-
|
|
5
|
-
Two surfaces: one-time pairing (identity) + per-session handoff (active routing). Emergency-router fallback: if the user DMs the bot with no handoff active, the bot auto-links to the most-recently-attached session watcher under their tenant. Tag `references.discord.emergency_route` on the inbox row flags the auto-link; reply with self-identification on first response.
|
|
3
|
+
The operator pairs their Discord account once. After that, any session can DM them. The bot does not read DMs sent to it: the Front Desk that used to answer them was retired 2026-10-06, so the bot replies to each with a fixed line. The handoff pin, `/switch` and the CLI's handoff commands were retired the same day.
|
|
6
4
|
|
|
7
5
|
## Pairing (one-time per tenant)
|
|
8
6
|
|
|
@@ -10,38 +8,16 @@ Two surfaces: one-time pairing (identity) + per-session handoff (active routing)
|
|
|
10
8
|
greprag discord pair
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
Generates a 6-char code. User DMs `@greprag` on Discord with `/pair <code>`. After that, the user's snowflake is bound to their greprag tenant.
|
|
11
|
+
Generates a 6-char code. User DMs `@greprag` on Discord with `/pair <code>`. After that, the user's snowflake is bound to their greprag tenant.
|
|
14
12
|
|
|
15
13
|
You don't run this — one-time setup the user does themselves. Detect "I'm paired" via `greprag discord me`: returns `paired: true` and the pairing's project_name.
|
|
16
14
|
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
The pattern the user almost always means by "let's continue on Discord":
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
greprag discord handoff --ttl 60 --json
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Run under `Monitor` (persistent: true). The command pins DMs from the user's snowflake to (this project, this session) for 60 min, fires a confirmation DM, then **blocks** streaming the session's inbox to stdout as JSON. Each line that prints is one Discord DM — Monitor delivers them as notifications back into your turn. Pin slides forward 30+ min on every inbound; an active thread stays anchored.
|
|
26
|
-
|
|
27
|
-
Replies go out via:
|
|
15
|
+
## Messaging the operator
|
|
28
16
|
|
|
29
17
|
```bash
|
|
30
18
|
greprag send --to discord:<snowflake> "your message body"
|
|
31
19
|
```
|
|
32
20
|
|
|
33
|
-
CLI handles UTF-8
|
|
34
|
-
|
|
35
|
-
For long agent turns where auto-typing's 10-second window expires, call `greprag discord typing` between work steps to refresh the indicator. Silent on success.
|
|
36
|
-
|
|
37
|
-
`greprag discord unhandoff` releases the pin early (DMs revert to default project). Pin also expires naturally via TTL.
|
|
38
|
-
|
|
39
|
-
## Canonical handoff flow
|
|
40
|
-
|
|
41
|
-
User says "let's keep going on Discord" / "DM me" / "I'm stepping out":
|
|
21
|
+
It arrives as a DM from the bot. CLI handles UTF-8 and Discord's 2000-char limit. **Do not use curl for this** — bash on Windows mangles em-dashes / curly quotes / emoji into Win-1252 bytes that render as `?` on Discord. Node fetch (which the CLI uses) is UTF-8 native.
|
|
42
22
|
|
|
43
|
-
|
|
44
|
-
2. Resolve current session_id (`CLAUDE_SESSION_ID` env, or the 8-hex from SessionStart context).
|
|
45
|
-
3. Arm: `Monitor` with command `greprag discord handoff --session <8-hex> --project <name> --ttl 60 --json`, persistent.
|
|
46
|
-
4. Tell the user the pin is live; the bot already DMed them. Each reply from Discord fires as a Monitor notification.
|
|
47
|
-
5. On each notification, parse the JSON body (`body` field = user's message text). Draft your reply. Send via `greprag send --to discord:<snowflake> "..."`. Snowflake is in `references.discord.snowflake`.
|
|
23
|
+
During long agent turns, call `greprag discord typing` between work steps to show the typing indicator (it lasts about 10 seconds). Silent on success.
|
|
@@ -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
|
-
- **Edit the
|
|
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
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.
|
|
@@ -78,7 +78,7 @@ Your parent session is named `<parent-name>`. When you finish, report to it with
|
|
|
78
78
|
greprag session retitle <your-own-8hex> "Chip <Label> — <workstream>"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
The leader dictates the exact string (e.g. `Chip A — Converter format coverage`); substitute your own 8-hex from SessionStart. This force-sets the registry title so the watcher list
|
|
81
|
+
The leader dictates the exact string (e.g. `Chip A — Converter format coverage`); substitute your own 8-hex from SessionStart. This force-sets the registry title so the watcher list lines up with the leader's labels. Single chips skip this.
|
|
82
82
|
|
|
83
83
|
## Report back — native `SendMessage`
|
|
84
84
|
|
|
@@ -87,7 +87,9 @@ Close the task body with this (substitute `<slug>` + `<parent-name>`):
|
|
|
87
87
|
````
|
|
88
88
|
---
|
|
89
89
|
|
|
90
|
-
**
|
|
90
|
+
**Rebase before you report:** when the work is committed, rebase this branch onto the current tip of your merge target (the default branch, or the integration branch on a multi-chip mission), resolve any conflicts here, and rerun the affected checks. Only you can edit this worktree, so the parent lands your branch by fast-forward alone and sends it back if the tip has moved again.
|
|
91
|
+
|
|
92
|
+
**Report back:** then send your result to the parent with native `SendMessage`, then end your turn:
|
|
91
93
|
|
|
92
94
|
- `to`: `"<parent-name>"`
|
|
93
95
|
- `message`: status, commit hash, branch `chip/<slug>`, and a one-line summary.
|
|
@@ -116,6 +118,8 @@ Chips never `npm run deploy` (or Wrangler / another provider deploy) from the wo
|
|
|
116
118
|
|
|
117
119
|
## Parent merge discipline — prune at the merge (HARD RULE)
|
|
118
120
|
|
|
121
|
+
Land a chip branch with `git merge --ff-only chip/<slug>` under the merge lock, nothing more: the chip rebased and retested it before reporting. Never rebase inside the chip's worktree; you cannot edit there, so a conflict strands a half-finished rebase. A refused fast-forward goes back to the chip over `SendMessage` (free the lock first).
|
|
122
|
+
|
|
119
123
|
When you (the parent) merge a chip branch — through the canonical commit path or a profile-declared merge — prune in the same breath: find the worktree holding `chip/<slug>` in `git worktree list` (the app's own worktree when the chip reused it, `.claude/worktrees/<slug>` on a local start), junction guard it (`~/.claude/skills/commit/guard-junctions.sh <worktree>`), `git worktree remove <worktree>`, and `git branch -d chip/<slug>` once merged. A chip that closed itself has already removed its own worktree, so no worktree holds the branch — delete the branch and move on, don't go hunting for it. Deferred cleanup = stale worktrees piling up (field state 2026-06-10: 3 leftovers). Multi-chip missions: worktree dies at the integration-branch merge; the branch lives until the configured default-branch merge (chip-leader Phase 4).
|
|
120
124
|
|
|
121
125
|
**FIX chips report to the mission owner.** A `greprag fix spawn` mission is the source of truth for FIX behavior. `fix spawn` detects usable Git history and emits `workspaceMode`: Git uses an isolated worktree; non-Git, unavailable Git, or no commit uses the project-local task with serialized writes. Dispatch honors that mode without trying a worktree first. The FIX chip identifies the exact friction, makes the smallest durable root-cause fix, verifies and commits useful work, then reports the commit/result and cleanup parameters to the parent mission owner. It creates no human landing gate. If no live parent exists and the chip carries the full-goal mission, it follows the repo profile as mission owner.
|
|
@@ -96,6 +96,22 @@ again. Never rebase a shared default branch. A busy merge lock means retry
|
|
|
96
96
|
after its holder finishes — never bypass it, and never take over someone
|
|
97
97
|
else's in-progress merge.
|
|
98
98
|
|
|
99
|
+
### Another session's branch is rebased by that session
|
|
100
|
+
|
|
101
|
+
Step 2 happens in the checkout of whoever owns the branch, because only that
|
|
102
|
+
session can edit there and so only it can resolve a conflict. A chip rebases
|
|
103
|
+
onto its merge target's tip and retests before it reports, so its parent lands
|
|
104
|
+
it with steps 3 to 5 alone. Never run `git -C <their worktree> rebase`: the
|
|
105
|
+
write guard refuses your edits there, so the first conflict leaves you
|
|
106
|
+
aborting, or leaves a half-finished rebase in someone else's checkout. If the
|
|
107
|
+
fast-forward is refused because the tip moved, free the lock and send the
|
|
108
|
+
branch back to its owner to rebase and retest. A chip that already closed left
|
|
109
|
+
no worktree behind, so rebase its branch in a checkout you can edit.
|
|
110
|
+
|
|
111
|
+
Field trigger (2026-10-06): a parent landing chip branches with
|
|
112
|
+
`git -C <chip-worktree> rebase main` hit the same ADR conflict twice and could
|
|
113
|
+
not resolve it either time.
|
|
114
|
+
|
|
99
115
|
This lock is a short Git critical section. It is not a delivery lease, and it
|
|
100
116
|
is not peer-acknowledgement polling.
|
|
101
117
|
|
package/skill/templates/os.md
CHANGED
|
@@ -31,7 +31,7 @@ conversation. The registration is part of the work, not an extra:
|
|
|
31
31
|
|
|
32
32
|
- **Docs** mirror through `greprag doc mirror sync` and can still auto-register
|
|
33
33
|
pointers when edited in a live session. Search/read them through the CLI.
|
|
34
|
-
- **Skills**
|
|
34
|
+
- **Skills** live in git (`~/.claude/skills/<name>/` or `<repo>/.claude/skills/<name>/`); commit them and the mirror follows (`greprag load` serves them cross-machine).
|
|
35
35
|
- **Decisions** get a dated entry in the owning ADR / decision log, in the
|
|
36
36
|
same commit as the code.
|
|
37
37
|
- **Everything else** (state files, artifacts, conventions) gets its path
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Prompt Audit — dated-model cruft in
|
|
1
|
+
# Prompt Audit — dated-model cruft in agent instructions
|
|
2
2
|
|
|
3
3
|
<!-- adr: adr/prompt-audit-canon.md -->
|
|
4
4
|
|
|
@@ -14,56 +14,35 @@ local skill adapters may not override it.
|
|
|
14
14
|
|
|
15
15
|
## The one invariant
|
|
16
16
|
|
|
17
|
-
**AUDIT FINDINGS LAND IN
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
That single command advances canon (optimistic concurrency, `expectedRevision`)
|
|
26
|
-
and materializes every follower for the scope. Nothing else writes a follower.
|
|
17
|
+
**AUDIT FINDINGS LAND IN THE GIT SOURCE, NEVER IN A RENDERED FILE.**
|
|
18
|
+
`~/.claude/CLAUDE.md` in the dotclaude repo is the global source; a repo's
|
|
19
|
+
`CLAUDE.md` and `AGENTS.md` are ordinary files in that repo.
|
|
20
|
+
`~/.codex/AGENTS.md` and `~/.config/opencode/AGENTS.md` are rendered from
|
|
21
|
+
`~/.claude/CLAUDE.md` plus `~/.claude/instructions/<codex|opencode>.md` by
|
|
22
|
+
`greprag instructions render` (session start runs it), so an edit there is
|
|
23
|
+
overwritten. GrepRAG's copy (`greprag load instructions`) is read only and
|
|
24
|
+
follows the last commit.
|
|
27
25
|
|
|
28
26
|
## Procedure
|
|
29
27
|
|
|
30
|
-
1. **Scope and target model.**
|
|
31
|
-
|
|
28
|
+
1. **Scope and target model.** Global audits `~/.claude/CLAUDE.md`; repo
|
|
29
|
+
audits the current repo's `CLAUDE.md`/`AGENTS.md`. Target
|
|
32
30
|
model is the one Claude Code sessions run today (Claude Fable 5.1 as of
|
|
33
31
|
2026-09). State both at the top of the report; do not ask.
|
|
34
|
-
2. **
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
auditing, or the audit will grade a stale canon.
|
|
40
|
-
Overlays: `instructions` canon is `shared` + per-harness raw suffixes. When
|
|
41
|
-
a suffix is non-empty, audit each harness render separately and land
|
|
42
|
-
suffix edits with `instructions overlay set|derive`, never by pulling a
|
|
43
|
-
rendered file as `shared`. When all suffixes are empty (check the document
|
|
44
|
-
via the API or `overlay` history), the Claude render **is** `shared` and
|
|
45
|
-
`pull` of the revised Claude render is exact.
|
|
46
|
-
3. **Provenance.** The Claude follower is usually Git-tracked
|
|
47
|
-
(`~/.claude` dotclaude repo; repo files in the repo). `git log`/`git blame`
|
|
32
|
+
2. **Read the source.** Global: `~/.claude/CLAUDE.md` plus any
|
|
33
|
+
`~/.claude/instructions/<harness>.md` addition; audit each addition
|
|
34
|
+
separately. Repo: that repo's `CLAUDE.md` and `AGENTS.md`. Confirm the
|
|
35
|
+
rendered files are current with `greprag instructions status`.
|
|
36
|
+
3. **Provenance.** Every source file is in git. `git log`/`git blame`
|
|
48
37
|
each emphatic or prohibitive line: which failure, on which model, did it
|
|
49
38
|
prevent, and does it still reproduce on the target model?
|
|
50
39
|
4. **Classify every line** with the rubric below. Produce the report and the
|
|
51
40
|
proposed diff, both, always.
|
|
52
|
-
5. **Land.**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
greprag instructions status --scope <scope> # every follower fresh
|
|
58
|
-
```
|
|
59
|
-
Medium findings and flags stay in the report for the operator; they are
|
|
60
|
-
applied by a later `pull` of an operator-approved revision, the same way.
|
|
61
|
-
If the follower lives in a Git repo, the push dirties it; the operator
|
|
62
|
-
commits that repo (the audit never runs git there).
|
|
63
|
-
6. **Write the report** to `tasks/prompt-audit-<scope>-<date>.md` in the
|
|
64
|
-
repo you are working in (or scratch if the repo has no `tasks/`), in the
|
|
65
|
-
shape under "Report shape". Persona (`greprag persona show`) is a separate
|
|
66
|
-
system: note Persona findings in the report; never edit it from here.
|
|
41
|
+
5. **Land.** Apply ONLY high-confidence hunks to the source file, show the
|
|
42
|
+
diff, and leave the commit to the operator (the audit never runs git in
|
|
43
|
+
their repos). After the commit, `greprag instructions render` rewrites the
|
|
44
|
+
AGENTS.md files and refreshes GrepRAG's copy. Medium findings and flags
|
|
45
|
+
stay in the report for the operator.
|
|
67
46
|
|
|
68
47
|
## Rubric (shipped copy — Anthropic prompt-audit, condensed, binding)
|
|
69
48
|
|
|
@@ -166,12 +145,12 @@ returns 400; prefill is replaced by structured outputs.
|
|
|
166
145
|
## Report shape
|
|
167
146
|
|
|
168
147
|
Top: Assumptions (scope, target model), Inventory (what was audited and its
|
|
169
|
-
|
|
148
|
+
source commit), Summary (counts per group; two or three highest-impact
|
|
170
149
|
findings in prose). Then one entry per finding, ordered by confidence:
|
|
171
150
|
|
|
172
151
|
| Field | Content |
|
|
173
152
|
|---|---|
|
|
174
|
-
| Location | `file:line` in the
|
|
153
|
+
| Location | `file:line` in the source file |
|
|
175
154
|
| Evidence | exact quoted text |
|
|
176
155
|
| Pattern | the group/row above |
|
|
177
156
|
| Why obsolete | one or two sentences tied to the target model's documented behavior |
|
|
@@ -185,12 +164,10 @@ only when tests, docs, and references to the removed text go too).
|
|
|
185
164
|
|
|
186
165
|
## Guardrails
|
|
187
166
|
|
|
188
|
-
- Never edit `~/.
|
|
189
|
-
|
|
190
|
-
The only follower writer is `instructions push` (or `pull --push`).
|
|
167
|
+
- Never edit `~/.codex/AGENTS.md` or `~/.config/opencode/AGENTS.md` by
|
|
168
|
+
hand: they are rendered. Edit `~/.claude/CLAUDE.md` or the harness addition.
|
|
191
169
|
- Apply high-confidence hunks only. Medium and flag items wait for the
|
|
192
170
|
operator.
|
|
193
|
-
-
|
|
194
|
-
--snapshot <name> --scope <scope>` followed by `pull` of the restored file.
|
|
171
|
+
- Rollback is `git revert` on the source file.
|
|
195
172
|
- Persona is out of scope for edits; note findings only.
|
|
196
173
|
- Out of taxonomy friction: `greprag fix spawn "<one unit>"`.
|