greprag 5.83.2 → 5.84.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/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/load.js +4 -2
- package/dist/commands/reminder-registry.js +1 -8
- package/dist/commands/session.js +2 -2
- package/dist/commands/skill-mirror-reminder.js +0 -1
- package/dist/commands/skill.js +241 -24
- package/dist/hook.js +2 -1
- package/dist/index.js +14 -147
- package/dist/native-skill-mirror.js +9 -6
- package/dist/opencode-plugin.bundle.js +10 -8
- package/dist/reminder-injector.js +1 -1
- package/dist/skill-mirror-client.js +29 -0
- package/dist/skill-staleness.js +2 -2
- 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/skill-change.md +3 -1
- package/dist/commands/sms.js +0 -215
package/dist/index.js
CHANGED
|
@@ -75,7 +75,6 @@ const agents_1 = require("./commands/agents");
|
|
|
75
75
|
const loadout_1 = require("./commands/loadout");
|
|
76
76
|
const persona_1 = require("./commands/persona");
|
|
77
77
|
const email_1 = require("./commands/email");
|
|
78
|
-
const sms_1 = require("./commands/sms");
|
|
79
78
|
const codex_1 = require("./commands/codex");
|
|
80
79
|
const opencode_relay_1 = require("./commands/opencode-relay");
|
|
81
80
|
const opencode_watch_1 = require("./commands/opencode-watch");
|
|
@@ -395,7 +394,7 @@ async function inbox(args) {
|
|
|
395
394
|
// --local: the ground-truth view. Skip the cloud entirely and report the
|
|
396
395
|
// sessions whose watcher process is alive on THIS machine right now (pidfile
|
|
397
396
|
// ∩ OS liveness). This is what the desk-line returns when the cloud asks the
|
|
398
|
-
// machine "what's truly active?"
|
|
397
|
+
// machine "what's truly active?".
|
|
399
398
|
// The cloud `/attached` registry below can list dead sessions for minutes;
|
|
400
399
|
// this never can. adr: adr/monitor-resilience.md
|
|
401
400
|
if (subArgs.includes('--local')) {
|
|
@@ -635,11 +634,6 @@ async function inbox(args) {
|
|
|
635
634
|
// Indent each body line so multi-line markdown stays visually grouped
|
|
636
635
|
for (const line of msg.body.split('\n'))
|
|
637
636
|
console.log(` ${line}`);
|
|
638
|
-
// Front Desk relay: the operator reached this session through the
|
|
639
|
-
// receptionist — answer on the rail they came in on, NOT the bare handle.
|
|
640
|
-
if (msg.references?.reply_to) {
|
|
641
|
-
console.log(` ↩ reply on the operator's rail: greprag send --to ${msg.references.reply_to} "your reply"`);
|
|
642
|
-
}
|
|
643
637
|
// References block — only printed when present, formatted as a compact section.
|
|
644
638
|
const refs = msg.references;
|
|
645
639
|
if (refs && (refs.memory_ids?.length || refs.artifacts?.length || refs.files?.length)) {
|
|
@@ -725,16 +719,6 @@ function parseSendAddress(addr) {
|
|
|
725
719
|
}
|
|
726
720
|
return { ok: true, targetKind: 'discord', target: snowflake };
|
|
727
721
|
}
|
|
728
|
-
// sms: prefix — the Front Desk return leg (a connected session replies to the
|
|
729
|
-
// operator's phone). Own addressing grammar like discord:; server-side
|
|
730
|
-
// /v1/inbox/send resolves the phone against sms_pairings. adr: docs/front-desk-switchboard.md §4
|
|
731
|
-
if (addr.startsWith('sms:')) {
|
|
732
|
-
const phone = addr.slice('sms:'.length);
|
|
733
|
-
if (!/^\+?[0-9]{8,15}$/.test(phone)) {
|
|
734
|
-
return { ok: false, error: `sms address "${addr}" must be sms:<E.164 phone, e.g. sms:+16045551234>.` };
|
|
735
|
-
}
|
|
736
|
-
return { ok: true, targetKind: 'sms', target: phone };
|
|
737
|
-
}
|
|
738
722
|
const segments = addr.split('/');
|
|
739
723
|
if (segments.length > 2) {
|
|
740
724
|
return { ok: false, error: `address "${addr}" has too many segments. The v0.12 grammar is\n` +
|
|
@@ -843,12 +827,6 @@ function buildReferences(args) {
|
|
|
843
827
|
if (inReplyTo) {
|
|
844
828
|
references = { ...(references ?? {}), in_reply_to: inReplyTo };
|
|
845
829
|
}
|
|
846
|
-
// --approval: flag this front-desk ping as an approval request so the Front
|
|
847
|
-
// Desk offers a one-tap APPROVE (relay "yes") + CONNECT. Applies on top of any
|
|
848
|
-
// references shape (incl. --ref-json). adr: adr/address-grammar.md
|
|
849
|
-
if (args.includes('--approval')) {
|
|
850
|
-
references = { ...(references ?? {}), approval: true };
|
|
851
|
-
}
|
|
852
830
|
return references;
|
|
853
831
|
}
|
|
854
832
|
/** greprag send "<text>" --to-desk --type <internal-type>
|
|
@@ -949,7 +927,7 @@ async function send(args) {
|
|
|
949
927
|
// Project broadcasts were removed (2026-06-07). A project-name target has no
|
|
950
928
|
// scope to send to — reject it. Session/chip traffic belongs on a session id;
|
|
951
929
|
// first contact goes to the bare-handle front desk.
|
|
952
|
-
// adr: adr/address-grammar.md
|
|
930
|
+
// adr: adr/address-grammar.md
|
|
953
931
|
if (parsed.ok && parsed.targetKind === 'project') {
|
|
954
932
|
console.error(`address "${to}" targets a whole project — project broadcasts were removed.\n` +
|
|
955
933
|
` Send to one session: --to <handle>@greprag.com/<session-uuid>\n` +
|
|
@@ -978,11 +956,11 @@ async function send(args) {
|
|
|
978
956
|
process.exit(1);
|
|
979
957
|
}
|
|
980
958
|
// A positional body carrying a LITERAL \n (backslash-n) is sent verbatim and
|
|
981
|
-
// renders as raw "\n"
|
|
959
|
+
// renders as raw "\n", not a line break (fix e8aa3f83). Warn at the
|
|
982
960
|
// moment so the author switches to --body-file/--stdin (real newlines) instead
|
|
983
961
|
// of silently shipping a mangled message.
|
|
984
962
|
if (/\\n/.test(body) && !args.includes('--stdin') && getFlag(args, '--body-file') === undefined) {
|
|
985
|
-
console.error('Warning: body contains a literal "\\n" — sent as-is
|
|
963
|
+
console.error('Warning: body contains a literal "\\n" — sent as-is, not as a line break. For real newlines use --body-file or --stdin.');
|
|
986
964
|
}
|
|
987
965
|
const references = buildReferences(args);
|
|
988
966
|
const payload = { to, body };
|
|
@@ -1002,12 +980,6 @@ async function send(args) {
|
|
|
1002
980
|
console.log(`Sent to discord:${delivered.snowflake}`);
|
|
1003
981
|
return;
|
|
1004
982
|
}
|
|
1005
|
-
// SMS destinations (Front Desk return leg) also short-circuit the inbox table
|
|
1006
|
-
// — delivered to the operator's phone via Twilio, no row/id/retract.
|
|
1007
|
-
if (delivered?.kind === 'sms' && delivered.phone) {
|
|
1008
|
-
console.log(`Sent to sms:${delivered.phone}`);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
983
|
// Render the delivered-to in the v0.11 single-segment grammar.
|
|
1012
984
|
// adr: adr/numeric-handles.md, adr/address-grammar.md
|
|
1013
985
|
const base = delivered.handle || `${delivered.tenant}@greprag.com`;
|
|
@@ -1050,7 +1022,7 @@ async function send(args) {
|
|
|
1050
1022
|
if (retractCode)
|
|
1051
1023
|
console.log(`Retract: greprag retract ${retractCode}`);
|
|
1052
1024
|
}
|
|
1053
|
-
/** greprag discord <pair|unpair|me> — Discord
|
|
1025
|
+
/** greprag discord <pair|unpair|me|typing> — pair Discord so sessions can DM you. */
|
|
1054
1026
|
async function discord(args) {
|
|
1055
1027
|
const sub = args[0];
|
|
1056
1028
|
if (!sub || sub === 'help' || sub === '--help') {
|
|
@@ -1102,112 +1074,6 @@ async function discord(args) {
|
|
|
1102
1074
|
console.log(`Project: ${result.project_name}`);
|
|
1103
1075
|
console.log(`Paired at: ${result.paired_at}`);
|
|
1104
1076
|
console.log(`Last inbound DM: ${result.last_seen_inbound_at ?? '(never)'}`);
|
|
1105
|
-
if (result.handoff) {
|
|
1106
|
-
console.log(`Handoff (active):`);
|
|
1107
|
-
console.log(` Project: ${result.handoff.project_name}`);
|
|
1108
|
-
console.log(` Session: ${result.handoff.session_id}`);
|
|
1109
|
-
console.log(` Expires: ${result.handoff.expires_at}`);
|
|
1110
|
-
}
|
|
1111
|
-
else {
|
|
1112
|
-
console.log(`Handoff: (none — DMs route to default project)`);
|
|
1113
|
-
}
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
if (sub === 'handoff') {
|
|
1117
|
-
// Resolve project: --project <name> overrides anchor's project_id.
|
|
1118
|
-
const projectFlag = getFlag(args, '--project');
|
|
1119
|
-
let projectId;
|
|
1120
|
-
let projectNameForWatch;
|
|
1121
|
-
if (projectFlag) {
|
|
1122
|
-
projectNameForWatch = projectFlag;
|
|
1123
|
-
const res = await fetch(`${cfg.apiUrl}/v1/inbox/projects`, {
|
|
1124
|
-
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
|
|
1125
|
-
});
|
|
1126
|
-
const json = (await res.json());
|
|
1127
|
-
const hit = json.projects?.find(p => p.project_name.toLowerCase() === projectFlag.toLowerCase());
|
|
1128
|
-
if (!hit) {
|
|
1129
|
-
console.error(`Error: project "${projectFlag}" not found under this tenant`);
|
|
1130
|
-
process.exit(1);
|
|
1131
|
-
}
|
|
1132
|
-
projectId = hit.project_id;
|
|
1133
|
-
}
|
|
1134
|
-
else {
|
|
1135
|
-
const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
|
|
1136
|
-
if (!anchor.projectId) {
|
|
1137
|
-
console.error('No project anchor in current directory. Run: greprag init, or pass --project <name>.');
|
|
1138
|
-
process.exit(1);
|
|
1139
|
-
}
|
|
1140
|
-
projectId = anchor.projectId;
|
|
1141
|
-
projectNameForWatch = anchor.projectName;
|
|
1142
|
-
}
|
|
1143
|
-
// Resolve session id: --session <id> overrides CLAUDE_SESSION_ID env.
|
|
1144
|
-
const sessionFlag = getFlag(args, '--session');
|
|
1145
|
-
const rawSession = sessionFlag || (0, session_id_1.readSessionEnv)() || '';
|
|
1146
|
-
const sessionId = (0, session_id_1.truncateSessionId)(rawSession);
|
|
1147
|
-
if (!sessionId) {
|
|
1148
|
-
console.error('No session id available. Pass --session <8-hex>, or run inside a Claude Code session\n' +
|
|
1149
|
-
'(Claude Code sets CLAUDE_CODE_SESSION_ID).');
|
|
1150
|
-
process.exit(1);
|
|
1151
|
-
}
|
|
1152
|
-
// Optional TTL.
|
|
1153
|
-
const ttlFlag = getFlag(args, '--ttl');
|
|
1154
|
-
const ttlMinutes = ttlFlag ? parseInt(ttlFlag, 10) : undefined;
|
|
1155
|
-
if (ttlFlag && (!Number.isFinite(ttlMinutes) || ttlMinutes <= 0)) {
|
|
1156
|
-
console.error(`Error: --ttl must be a positive integer (got "${ttlFlag}")`);
|
|
1157
|
-
process.exit(1);
|
|
1158
|
-
}
|
|
1159
|
-
const result = await (0, discord_1.callDiscordHandoff)({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey }, { projectId: projectId, sessionId, ttlMinutes });
|
|
1160
|
-
if (!result.ok) {
|
|
1161
|
-
console.error(`Error: ${result.error}`);
|
|
1162
|
-
process.exit(1);
|
|
1163
|
-
}
|
|
1164
|
-
// --no-watch skips the inline watcher (for scripted "just pin" usage).
|
|
1165
|
-
if (args.includes('--no-watch')) {
|
|
1166
|
-
console.log(`Pinned ${result.project_name} / ${result.session_id} for ${result.ttl_minutes} min.`);
|
|
1167
|
-
console.log(`Expires: ${result.expires_at}`);
|
|
1168
|
-
console.log(`Bot DM'd you a confirmation. Reply on Discord to route to this session.`);
|
|
1169
|
-
console.log(`(skipped inbox watcher — pass without --no-watch to stream DMs to stdout.)`);
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
// --orchestrator: tenant-wide watcher (sees inbox traffic to/from any
|
|
1173
|
-
// project under this tenant). The pin still routes the user's DMs to
|
|
1174
|
-
// (this project, this session) — only the watcher's scope changes.
|
|
1175
|
-
// Used by an orchestrator session that wants portfolio-wide visibility
|
|
1176
|
-
// while keeping its own DM conversation anchored to itself.
|
|
1177
|
-
const orchestrator = args.includes('--orchestrator');
|
|
1178
|
-
// Inline watcher — the whole point of handoff. Pin the route AND listen
|
|
1179
|
-
// for inbox events on the same SSE stream. When invoked under Monitor,
|
|
1180
|
-
// each printed line becomes one notification, so the agent sees every
|
|
1181
|
-
// event the moment it lands. Process blocks until killed.
|
|
1182
|
-
console.error(`[handoff] Pinned ${result.project_name} / ${result.session_id} for ${result.ttl_minutes} min.`);
|
|
1183
|
-
console.error(`[handoff] Bot DM'd you a confirmation. Reply on Discord — replies stream here.`);
|
|
1184
|
-
if (orchestrator) {
|
|
1185
|
-
console.error(`[handoff] Orchestrator mode — watcher is tenant-wide. You will see inbox events from any project under this tenant.`);
|
|
1186
|
-
}
|
|
1187
|
-
else {
|
|
1188
|
-
console.error(`[handoff] Listening for DMs to session ${result.session_id} on project ${result.project_name}...`);
|
|
1189
|
-
}
|
|
1190
|
-
await (0, inbox_watch_1.runInboxWatch)({
|
|
1191
|
-
// Orchestrator mode: tenant-wide watcher (no project/session filter).
|
|
1192
|
-
// Otherwise: filter to this project + session.
|
|
1193
|
-
project: orchestrator ? undefined : projectNameForWatch,
|
|
1194
|
-
session: orchestrator ? undefined : sessionId,
|
|
1195
|
-
json: args.includes('--json'),
|
|
1196
|
-
// Direct mode (no supervisor): handoff PINS the route before watching,
|
|
1197
|
-
// so a supervised respawn would re-invoke `handoff` and re-pin on every
|
|
1198
|
-
// child restart. Supervision is scoped to `inbox watch` only; keep
|
|
1199
|
-
// handoff's prior in-process behavior. adr: adr/monitor-resilience.md
|
|
1200
|
-
noSupervise: true,
|
|
1201
|
-
});
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
if (sub === 'unhandoff') {
|
|
1205
|
-
const result = await (0, discord_1.callDiscordUnhandoff)({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey });
|
|
1206
|
-
if (!result.ok) {
|
|
1207
|
-
console.error(`Error: ${result.error}`);
|
|
1208
|
-
process.exit(1);
|
|
1209
|
-
}
|
|
1210
|
-
console.log('Handoff cleared. DMs revert to default project routing.');
|
|
1211
1077
|
return;
|
|
1212
1078
|
}
|
|
1213
1079
|
if (sub === 'typing') {
|
|
@@ -1328,8 +1194,8 @@ Commands:
|
|
|
1328
1194
|
agents <adopt|optimize|sync|status>
|
|
1329
1195
|
Canonical global/repo CLAUDE.md + AGENTS.md doctrine.
|
|
1330
1196
|
doctor [--inspect] [--yes] Diagnose + repair orphan project_ids and identity drift
|
|
1331
|
-
desk
|
|
1332
|
-
|
|
1197
|
+
desk <run|ensure|status> The desk line: this machine answers which sessions
|
|
1198
|
+
are live when the cloud asks.
|
|
1333
1199
|
smell "<text>" Shortcut for \`greprag fix log "<text>"\`.
|
|
1334
1200
|
context status [--session <id>] Context-size governor readout: live tokens, threshold,
|
|
1335
1201
|
% consumed, turns since the crossover advisory last fired.
|
|
@@ -1414,10 +1280,8 @@ Inbox (email-style messaging across tenants):
|
|
|
1414
1280
|
retract <code> Pull back a previously-sent message (code is printed on send).
|
|
1415
1281
|
discord pair Pair Discord DMs to this project — generates a code to DM the bot.
|
|
1416
1282
|
discord unpair Remove the Discord pairing.
|
|
1417
|
-
discord me Show current Discord pairing
|
|
1418
|
-
discord
|
|
1419
|
-
discord unhandoff Clear the handoff pin (DMs revert to default project).
|
|
1420
|
-
discord typing Refresh the "typing…" indicator on the paired DM thread.
|
|
1283
|
+
discord me Show current Discord pairing state.
|
|
1284
|
+
discord typing Refresh the "typing…" indicator on the paired DM thread.
|
|
1421
1285
|
send "body" --to discord:<snowflake>
|
|
1422
1286
|
Outbound DM to a paired Discord user from any session.
|
|
1423
1287
|
Unread → hard delete. Read → body replaced with a retracted notice.
|
|
@@ -1576,6 +1440,11 @@ Skills (opt-in advisor skills bundled with the CLI):
|
|
|
1576
1440
|
skill uninstall <name> Remove a previously-installed advisor
|
|
1577
1441
|
skill source stamp <name> --project <p> --path <rel> [--path <rel>...] [--baseline <ref>]
|
|
1578
1442
|
Stamp a skill-staleness baseline against local Git
|
|
1443
|
+
skill history <name> Every revision of a mirrored skill: date, author, reason, changes
|
|
1444
|
+
skill diff <name> <revA> [<revB>]
|
|
1445
|
+
Unified diff between two revisions (revB defaults to current)
|
|
1446
|
+
skill revert <name> --to <rev> --reason "<why>" --expected-hash <current-hash>
|
|
1447
|
+
Make an old revision current again, as a new revision
|
|
1579
1448
|
|
|
1580
1449
|
Load (on-demand doctrine — the CLI is the payload, no .md files):
|
|
1581
1450
|
load Browse the catalog (chip-spawn, codex chips, mechanic, …)
|
|
@@ -1663,7 +1532,6 @@ const HELP_GROUPS = [
|
|
|
1663
1532
|
['fix', fix_1.runFix],
|
|
1664
1533
|
['mechanic', mechanic_1.runMechanic],
|
|
1665
1534
|
['discord', discord],
|
|
1666
|
-
['sms', sms_1.runSms],
|
|
1667
1535
|
['codex', codex_1.runCodex],
|
|
1668
1536
|
['identity', identity_1.runIdentity],
|
|
1669
1537
|
['project', project_1.runProject],
|
|
@@ -1974,7 +1842,6 @@ async function main() {
|
|
|
1974
1842
|
case 'send': return send(subArgs);
|
|
1975
1843
|
case 'retract': return retract(subArgs);
|
|
1976
1844
|
case 'discord': return discord(subArgs);
|
|
1977
|
-
case 'sms': return (0, sms_1.runSms)(subArgs);
|
|
1978
1845
|
case 'codex': return (0, codex_1.runCodex)(subArgs);
|
|
1979
1846
|
case 'expand': return expand(subArgs);
|
|
1980
1847
|
case 'search': return (0, search_1.runSearch)(subArgs); // one lexical search across everything (adr/searchable-store-kinds.md)
|
|
@@ -341,15 +341,15 @@ function nativeCreationLocations(platform) {
|
|
|
341
341
|
}
|
|
342
342
|
function renderChangeSkillLine(params) {
|
|
343
343
|
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
344
|
-
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash
|
|
344
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash> --reason "<why>"`;
|
|
345
345
|
if (params.platform === 'codex') {
|
|
346
346
|
return `- **Change this skill:** run \`greprag load skill-change\`, then \`${exportCommand}\`; patch the exported Markdown files non-interactively; run \`${applyCommand}\`; then \`${params.syncCommand}\`; then verify with \`${params.loadCommand}\`.`;
|
|
347
347
|
}
|
|
348
|
-
return `- **Change this skill:** run \`greprag load skill-change\`, then \`greprag skill edit ${params.skillName} ${params.editArgs}\`, then \`${params.syncCommand}\`, then verify with \`${params.loadCommand}\`.`;
|
|
348
|
+
return `- **Change this skill:** run \`greprag load skill-change\`, then \`greprag skill edit ${params.skillName} ${params.editArgs} --reason "<why>"\`, then \`${params.syncCommand}\`, then verify with \`${params.loadCommand}\`.`;
|
|
349
349
|
}
|
|
350
350
|
function renderEditingSection(platform, params) {
|
|
351
351
|
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
352
|
-
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash
|
|
352
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash> --reason "<why>"`;
|
|
353
353
|
if (platform === 'codex') {
|
|
354
354
|
return `## Editing
|
|
355
355
|
|
|
@@ -369,7 +369,7 @@ Do not launch an interactive editor from Codex. If using \`greprag skill edit\`
|
|
|
369
369
|
To edit this skill, run the guarded editor flow:
|
|
370
370
|
|
|
371
371
|
\`\`\`bash
|
|
372
|
-
greprag skill edit ${params.skillName} ${params.editArgs}
|
|
372
|
+
greprag skill edit ${params.skillName} ${params.editArgs} --reason "<why>"
|
|
373
373
|
\`\`\`
|
|
374
374
|
|
|
375
375
|
Manual path:
|
|
@@ -426,11 +426,14 @@ ${typeof skill.revision === 'number' ? `Mirror revision: ${skill.revision}` : 'M
|
|
|
426
426
|
|
|
427
427
|
${editingSection}
|
|
428
428
|
|
|
429
|
-
|
|
429
|
+
History and rollback (every canon write keeps its bytes and reason):
|
|
430
430
|
|
|
431
431
|
\`\`\`bash
|
|
432
|
+
greprag skill history ${skillName} ${identityArg}
|
|
433
|
+
greprag skill diff ${skillName} <revA> [<revB>] ${identityArg}
|
|
434
|
+
greprag skill revert ${skillName} --to <rev> ${identityArg} --reason "<why>" --expected-hash <current-hash>
|
|
432
435
|
greprag skill mirror snapshot list ${skillName} ${identityArg}
|
|
433
|
-
greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash>
|
|
436
|
+
greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash> --reason "<why>"
|
|
434
437
|
\`\`\`
|
|
435
438
|
|
|
436
439
|
## Mandatory Invocation
|
|
@@ -2466,7 +2466,6 @@ function buildSkillMirrorAnnounce(m, platform) {
|
|
|
2466
2466
|
}
|
|
2467
2467
|
var skillMirrorAnnounceModule = {
|
|
2468
2468
|
id: "skill-mirror-announce",
|
|
2469
|
-
source: "prompt",
|
|
2470
2469
|
dependsOn: ["load-primer"],
|
|
2471
2470
|
// references `greprag load` — the primer establishes it
|
|
2472
2471
|
detect: (_env) => ({ tier: "silent" }),
|
|
@@ -2549,7 +2548,7 @@ function buildDeliveryAnnounce(env) {
|
|
|
2549
2548
|
`[DELIVERY \u2014 ${project}]`,
|
|
2550
2549
|
"Commit useful, passing work. Only committed state delivers; ignore uncommitted and untracked work in every checkout.",
|
|
2551
2550
|
coordinationLine(env.platform),
|
|
2552
|
-
"Land every ready same-repo commit: update the default branch, rebase your worktree onto its tip, retest there, take greprag merge-lock in the canonical checkout, recheck the tip, merge --ff-only, release --landed. Tip moved? Free the lock and prepare again. Never rebase a shared default branch.",
|
|
2551
|
+
"Land every ready same-repo commit: update the default branch, rebase your worktree onto its tip, retest there, take greprag merge-lock in the canonical checkout, recheck the tip, merge --ff-only, release --landed. Tip moved? Free the lock and prepare again. A chip's branch arrives rebased by the chip: only fast-forward it; refused, free the lock and send it back. Never rebase in another session's worktree or a shared default branch.",
|
|
2553
2552
|
"Deploy through this repo's delivery profile, from its canonical primary checkout unless the profile says otherwise. Worktrees build; they do not deploy. Then verify production serves the commit you shipped.",
|
|
2554
2553
|
"THE GOAL ALREADY AUTHORIZES ALL OF THIS. Do not stop to ask permission to commit, merge, or deploy. Separate approval is for effects outside code delivery: destructive data, secrets, money, customer-facing messages.",
|
|
2555
2554
|
"Why each rule, and the details: `greprag load delivery`."
|
|
@@ -4443,15 +4442,15 @@ function nativeCreationLocations(platform) {
|
|
|
4443
4442
|
}
|
|
4444
4443
|
function renderChangeSkillLine(params) {
|
|
4445
4444
|
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
4446
|
-
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash
|
|
4445
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash> --reason "<why>"`;
|
|
4447
4446
|
if (params.platform === "codex") {
|
|
4448
4447
|
return `- **Change this skill:** run \`greprag load skill-change\`, then \`${exportCommand}\`; patch the exported Markdown files non-interactively; run \`${applyCommand}\`; then \`${params.syncCommand}\`; then verify with \`${params.loadCommand}\`.`;
|
|
4449
4448
|
}
|
|
4450
|
-
return `- **Change this skill:** run \`greprag load skill-change\`, then \`greprag skill edit ${params.skillName} ${params.editArgs}\`, then \`${params.syncCommand}\`, then verify with \`${params.loadCommand}\`.`;
|
|
4449
|
+
return `- **Change this skill:** run \`greprag load skill-change\`, then \`greprag skill edit ${params.skillName} ${params.editArgs} --reason "<why>"\`, then \`${params.syncCommand}\`, then verify with \`${params.loadCommand}\`.`;
|
|
4451
4450
|
}
|
|
4452
4451
|
function renderEditingSection(platform, params) {
|
|
4453
4452
|
const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
|
|
4454
|
-
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash
|
|
4453
|
+
const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash> --reason "<why>"`;
|
|
4455
4454
|
if (platform === "codex") {
|
|
4456
4455
|
return `## Editing
|
|
4457
4456
|
|
|
@@ -4471,7 +4470,7 @@ Do not launch an interactive editor from Codex. If using \`greprag skill edit\`
|
|
|
4471
4470
|
To edit this skill, run the guarded editor flow:
|
|
4472
4471
|
|
|
4473
4472
|
\`\`\`bash
|
|
4474
|
-
greprag skill edit ${params.skillName} ${params.editArgs}
|
|
4473
|
+
greprag skill edit ${params.skillName} ${params.editArgs} --reason "<why>"
|
|
4475
4474
|
\`\`\`
|
|
4476
4475
|
|
|
4477
4476
|
Manual path:
|
|
@@ -4524,11 +4523,14 @@ ${typeof skill.revision === "number" ? `Mirror revision: ${skill.revision}` : "M
|
|
|
4524
4523
|
|
|
4525
4524
|
${editingSection}
|
|
4526
4525
|
|
|
4527
|
-
|
|
4526
|
+
History and rollback (every canon write keeps its bytes and reason):
|
|
4528
4527
|
|
|
4529
4528
|
\`\`\`bash
|
|
4529
|
+
greprag skill history ${skillName} ${identityArg}
|
|
4530
|
+
greprag skill diff ${skillName} <revA> [<revB>] ${identityArg}
|
|
4531
|
+
greprag skill revert ${skillName} --to <rev> ${identityArg} --reason "<why>" --expected-hash <current-hash>
|
|
4530
4532
|
greprag skill mirror snapshot list ${skillName} ${identityArg}
|
|
4531
|
-
greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash>
|
|
4533
|
+
greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash> --reason "<why>"
|
|
4532
4534
|
\`\`\`
|
|
4533
4535
|
|
|
4534
4536
|
## Mandatory Invocation
|
|
@@ -195,7 +195,7 @@ async function injectReminders(input, cfg, short, platform = 'claude-code') {
|
|
|
195
195
|
// Harness scoping comes from each module's own `harnesses:` tag (the whitelist-drift
|
|
196
196
|
// killer), NOT from an ad hoc id filter here. The old hardcoded codex/watcher-arm
|
|
197
197
|
// exclusion made the per-turn surface disagree with the announce surface.
|
|
198
|
-
const base = (0, app_settings_1.filterEnabledInterrupts)((0, reminder_registry_1.harnessModules)(platform
|
|
198
|
+
const base = (0, app_settings_1.filterEnabledInterrupts)((0, reminder_registry_1.harnessModules)(platform));
|
|
199
199
|
const reg = mechanicKilled() ? base.filter(m => m.id !== 'mechanic-friction') : base;
|
|
200
200
|
const fired = (0, reminder_registry_1.collectReminders)(env, reg);
|
|
201
201
|
if (!fired.length)
|
|
@@ -44,6 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
};
|
|
45
45
|
})();
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.skillRevisionAuthor = skillRevisionAuthor;
|
|
47
48
|
exports.collectSkillFiles = collectSkillFiles;
|
|
48
49
|
exports.computeMirrorHash = computeMirrorHash;
|
|
49
50
|
exports.dryRunNativeSkillAdoption = dryRunNativeSkillAdoption;
|
|
@@ -57,6 +58,30 @@ const skill_landing_1 = require("./skill-landing");
|
|
|
57
58
|
const native_skill_adoption_1 = require("./native-skill-adoption");
|
|
58
59
|
const native_skill_mirror_1 = require("./native-skill-mirror");
|
|
59
60
|
const skill_mirror_files_1 = require("./skill-mirror-files");
|
|
61
|
+
const proc_1 = require("./proc");
|
|
62
|
+
/** Who is writing skill canon, for the revision row every write records:
|
|
63
|
+
* the writer (`via`), the harness session, and the chip branch when the
|
|
64
|
+
* write runs inside one. adr: adr/skill-revision-history.md */
|
|
65
|
+
function skillRevisionAuthor(via, opts = {}) {
|
|
66
|
+
const env = process.env;
|
|
67
|
+
const harness = opts.harness
|
|
68
|
+
|| (env.CLAUDE_CODE_SESSION_ID || env.CLAUDECODE ? 'claude-code'
|
|
69
|
+
: env.CODEX_THREAD_ID || env.CODEX_CI || env.CODEX_SHELL ? 'codex'
|
|
70
|
+
: env.OPENCODE_SESSION_ID ? 'opencode' : 'cli');
|
|
71
|
+
const sessionId = opts.sessionId || env.CLAUDE_CODE_SESSION_ID || env.CODEX_THREAD_ID
|
|
72
|
+
|| env.OPENCODE_SESSION_ID || env.GREPRAG_SESSION_ID || '';
|
|
73
|
+
let chip = '';
|
|
74
|
+
try {
|
|
75
|
+
const branch = (0, proc_1.safeSpawnSync)('git', ['branch', '--show-current'], {
|
|
76
|
+
cwd: opts.cwd || process.cwd(), encoding: 'utf8', timeout: 3000,
|
|
77
|
+
});
|
|
78
|
+
const name = String(branch.stdout || '').trim();
|
|
79
|
+
if (branch.status === 0 && name.startsWith('chip/'))
|
|
80
|
+
chip = name;
|
|
81
|
+
}
|
|
82
|
+
catch { /* not a git checkout */ }
|
|
83
|
+
return { via, harness, ...(sessionId ? { sessionId } : {}), ...(chip ? { chip } : {}) };
|
|
84
|
+
}
|
|
60
85
|
/** Mirrors the server's caps (SKILL_MIRROR_* in @greprag/core — the CLI is
|
|
61
86
|
* core-free, so the numbers are restated; the server re-validates anyway). */
|
|
62
87
|
const MAX_FILES = 20;
|
|
@@ -184,6 +209,8 @@ async function syncNativeSkillAdoptions(params) {
|
|
|
184
209
|
scope: candidate.scope,
|
|
185
210
|
projectId: candidate.projectId,
|
|
186
211
|
projectName: candidate.projectName,
|
|
212
|
+
reason: `adoption of native ${params.platform} skill from ${path.join(candidate.root, candidate.skillName)}`,
|
|
213
|
+
author: skillRevisionAuthor('adoption', { cwd: params.projectDir }),
|
|
187
214
|
};
|
|
188
215
|
const response = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(candidate.skillName)}`, {
|
|
189
216
|
method: 'POST',
|
|
@@ -335,6 +362,8 @@ async function maybeMirrorSkill(params) {
|
|
|
335
362
|
contentHash: hash,
|
|
336
363
|
scope: 'global',
|
|
337
364
|
expectedHash: localLease,
|
|
365
|
+
reason: params.reason || `shadow upload of local edit from ${dir}`,
|
|
366
|
+
author: skillRevisionAuthor('shadow-upload', { cwd: params.cwd, sessionId: params.sessionId }),
|
|
338
367
|
}),
|
|
339
368
|
});
|
|
340
369
|
if (!res.ok)
|
package/dist/skill-staleness.js
CHANGED
|
@@ -374,8 +374,8 @@ function formatSkillNoSourceBinding(skillName) {
|
|
|
374
374
|
`/${skillName} has no source bindings.`,
|
|
375
375
|
'Authority: canonical mirrored payload exists in GrepRAG; local Codex/Claude/OpenCode SKILL.md files are generated adapter-only mirrors.',
|
|
376
376
|
'Do not edit a local native adapter as skill content.',
|
|
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.`,
|
|
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> --reason "<why>"\`.`,
|
|
378
|
+
`Human editor shortcut: \`greprag skill edit ${skillName} --reason "<why>"\`; Codex auto-skips the editor, and agents may pass \`--no-open\` explicitly.`,
|
|
379
379
|
`After binding a real source repo, stamp it with \`greprag skill source stamp ${skillName} --project <p> --path <rel>\`.`,
|
|
380
380
|
].join('\n') + '\n';
|
|
381
381
|
}
|
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 canonical source.** Managed skills: `greprag skill edit <name> --skill-id <id
|
|
56
|
+
- **Edit the canonical source.** Managed skills: `greprag skill edit <name> --skill-id <id> --reason "<the friction this fixes>"`; 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.
|
|
@@ -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.
|