greprag 5.83.1 → 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 -2
- package/dist/commands/collision-check.js +30 -16
- package/dist/commands/delivery-reminder.js +1 -1
- package/dist/commands/discord.js +12 -61
- package/dist/commands/inbox-primer-reminder.js +1 -1
- package/dist/commands/inbox-watch.js +0 -13
- package/dist/commands/load.js +4 -2
- package/dist/commands/merge-guard.js +1 -1
- package/dist/commands/merge-lock.js +31 -5
- 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/deploy-locks.js +48 -0
- package/dist/hook.js +14 -16
- package/dist/index.js +14 -147
- package/dist/native-skill-mirror.js +9 -6
- package/dist/opencode-plugin-helpers.js +1 -1
- package/dist/opencode-plugin.bundle.js +11 -9
- 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 +3 -3
- package/skill/greprag/docs/discord-handoff.md +6 -30
- package/skill/greprag/docs/inbox-watch.md +1 -1
- package/skill/greprag/docs/inbox.md +2 -2
- package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
- package/skill/templates/chip-spawn.md +6 -2
- package/skill/templates/delivery.md +18 -1
- package/skill/templates/skill-change.md +3 -1
- package/dist/commands/sms.js +0 -215
|
@@ -72,7 +72,6 @@ const INTERRUPT_COPY = {
|
|
|
72
72
|
'memory-primer': { label: 'Memory guidance', description: 'Teaches agents how and when to retrieve project memory.', group: 'Memory' },
|
|
73
73
|
'watcher-arm': { label: 'Live inbox checks', description: 'Reminds Claude Code to keep live message delivery connected.', group: 'Communication' },
|
|
74
74
|
'mechanic-friction': { label: 'Automatic repair prompts', description: 'Surfaces repeated harness friction for durable repair.', group: 'Automation' },
|
|
75
|
-
collision: { label: 'Collision warnings', description: 'Warns before shared Git actions when another session is active.', group: 'Delegation' },
|
|
76
75
|
};
|
|
77
76
|
function readObject(file) {
|
|
78
77
|
try {
|
|
@@ -293,7 +292,7 @@ async function buildAppModel(cwd, requestedProjectId, homeDir = os.homedir()) {
|
|
|
293
292
|
group: 'Other',
|
|
294
293
|
}),
|
|
295
294
|
enabled: !disabled.has(module.id),
|
|
296
|
-
surface:
|
|
295
|
+
surface: 'Agent guidance',
|
|
297
296
|
harnesses: module.harnesses || ['claude-code', 'codex', 'opencode', 'grok'],
|
|
298
297
|
})),
|
|
299
298
|
},
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/** Collision schematic — detect another
|
|
3
|
-
* and inject a directive telling the agent to
|
|
4
|
-
* work in a worktree). The operator does this by
|
|
5
|
-
* reflex. adr: docs/schematic-triggers.md,
|
|
2
|
+
/** Collision schematic — detect another session with an armed greprag watcher
|
|
3
|
+
* working in the SAME repo and inject a directive telling the agent to
|
|
4
|
+
* coordinate (message the peer + work in a worktree). The operator does this by
|
|
5
|
+
* hand today; this makes it a reflex. adr: docs/schematic-triggers.md,
|
|
6
|
+
* docs/stateful-injection.md
|
|
6
7
|
*
|
|
7
|
-
* WHERE IT FIRES:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* WHERE IT FIRES: nowhere since 2026-10-06. It was hand-wired as a Claude Code
|
|
9
|
+
* SessionStart hook (startup + resume + compact), which Grok also ran through
|
|
10
|
+
* its Claude-hook compatibility, until the operator removed it
|
|
11
|
+
* (docs/collision-schematic.md).
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT SEES: only sessions that arm a greprag watcher — OpenCode (its relay)
|
|
14
|
+
* and Grok (its monitor). Claude Code has armed none since 2026-09-04 and Codex
|
|
15
|
+
* never did, so the hook never detects a Claude Code or Codex peer: two Claude
|
|
16
|
+
* Code sessions in one repo get nothing from it, whichever starts last. That is
|
|
17
|
+
* the design, not a gap to close here. The harness owns Claude Code discovery,
|
|
18
|
+
* and Claude Code peers get no heads-up: each works in its own worktree and Git
|
|
19
|
+
* reconciles the overlap at landing (adr/delivery-announce-pilot.md). Silence
|
|
20
|
+
* from this hook is never evidence that the repo has no other session.
|
|
10
21
|
*
|
|
11
22
|
* THE SIGNAL: `GET /v1/inbox/watchers` (the cloud registry) lists every armed
|
|
12
23
|
* watcher under the tenant as `{project_id, project_name, session_id, title,
|
|
@@ -16,8 +27,7 @@
|
|
|
16
27
|
* service watchers (receptionist / desk-line) carry a different
|
|
17
28
|
* project_id (or none), so they never match a working session's repo and are
|
|
18
29
|
* excluded for free. (`wide` is NOT a usable filter — observed true on ordinary
|
|
19
|
-
* session-scoped watchers, not just tenant-wide ones.)
|
|
20
|
-
* the peer having an armed (Monitor) watcher; best-effort by construction.
|
|
30
|
+
* session-scoped watchers, not just tenant-wide ones.)
|
|
21
31
|
*
|
|
22
32
|
* AI NEVER AUTO-ACTS: the hook injects a directive naming the peers; the AGENT
|
|
23
33
|
* composes and sends the contextual heads-up. The hook never sends.
|
|
@@ -117,20 +127,24 @@ function dedupePeers(peers, announced) {
|
|
|
117
127
|
// adr: adr/delivery-announce-pilot.md
|
|
118
128
|
/** The LOUD directive — same `additionalContext` shape as the arm directive,
|
|
119
129
|
* naming each peer + instructing message-then-worktree. PURE.
|
|
130
|
+
* Only Claude Code runs this hook (Grok too, through its Claude-hook
|
|
131
|
+
* compatibility), and every peer it names is a watcher row, so the heads-up
|
|
132
|
+
* goes by `greprag send` alone; neither harness has Codex's native tools.
|
|
120
133
|
* It asks no heads-up of Claude Code peers: Claude Code holds a message to a
|
|
121
134
|
* session in another permission mode for that session's user and may let it
|
|
122
135
|
* expire, and the sender cannot see modes, so silence there is not approval.
|
|
123
|
-
* Delivery is the per-harness Delivery announce's alone, so step 3 defers.
|
|
136
|
+
* Delivery is the per-harness Delivery announce's alone, so step 3 defers.
|
|
137
|
+
* The count is of watcher sessions, never of every session in the repo: a
|
|
138
|
+
* Claude Code or Codex peer is never a registry row. */
|
|
124
139
|
function buildCollisionDirective(peers, myShort, alias) {
|
|
125
140
|
const handle = alias || '<handle>';
|
|
126
141
|
const lines = peers.map(p => ` · ${p.short}${p.title ? ` — "${p.title}"` : ''}`);
|
|
127
142
|
const plural = peers.length === 1 ? '' : 's';
|
|
128
|
-
return (`COLLISION DETECTED — ${peers.length}
|
|
143
|
+
return (`COLLISION DETECTED — ${peers.length} greprag watcher session${plural} working in this same repo right now:\n`
|
|
129
144
|
+ lines.join('\n') + '\n'
|
|
130
|
-
+ `Coordinate BEFORE touching shared files: (1) message each peer above now with a contextual heads-up
|
|
131
|
-
+
|
|
132
|
-
+ `
|
|
133
|
-
+ `(YOU compose and send it — this is a notice, not an auto-send; recipients reply only for a concrete conflict, silence is approval, and you never poll or wait; watcher rows are not the Codex peer source of truth); `
|
|
145
|
+
+ `Coordinate BEFORE touching shared files: (1) message each peer above now with a contextual heads-up: `
|
|
146
|
+
+ `\`greprag send "…what you're about to touch…" --to ${handle}@greprag.com/<their-8hex> --from-session ${myShort}\` `
|
|
147
|
+
+ `(YOU compose and send it — this is a notice, not an auto-send; recipients reply only for a concrete conflict, silence is approval, and you never poll or wait); `
|
|
134
148
|
+ `(2) do your code work in a worktree on its own branch (\`git worktree add .claude/worktrees/<slug> -b <branch>\`), never in the shared default checkout; `
|
|
135
149
|
+ `(3) at delivery, notify peers only as this harness's Delivery announce says.`);
|
|
136
150
|
}
|
|
@@ -165,7 +179,7 @@ function buildRosterWhisper(sessions) {
|
|
|
165
179
|
return null;
|
|
166
180
|
const parts = sessions.map(s => `${s.short}@${s.projectName}${s.title ? ` ("${s.title}")` : ''}`);
|
|
167
181
|
const plural = sessions.length === 1 ? '' : 's';
|
|
168
|
-
return `[Live elsewhere — ${sessions.length}
|
|
182
|
+
return `[Live elsewhere — ${sessions.length} greprag watcher session${plural} active in your tenant `
|
|
169
183
|
+ `(cross-repo, FYI only): ${parts.join(' · ')}]`;
|
|
170
184
|
}
|
|
171
185
|
/** Only-on-change gate for the whisper: emit iff the cross-repo session SET
|
|
@@ -50,7 +50,7 @@ function buildDeliveryAnnounce(env) {
|
|
|
50
50
|
`[DELIVERY — ${project}]`,
|
|
51
51
|
'Commit useful, passing work. Only committed state delivers; ignore uncommitted and untracked work in every checkout.',
|
|
52
52
|
coordinationLine(env.platform),
|
|
53
|
-
'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.',
|
|
53
|
+
'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.',
|
|
54
54
|
'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.',
|
|
55
55
|
'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.',
|
|
56
56
|
'Why each rule, and the details: `greprag load delivery`.',
|
package/dist/commands/discord.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/** greprag discord <pair|unpair|me|
|
|
2
|
+
/** greprag discord <pair|unpair|me|typing> — Discord bot CLI.
|
|
3
3
|
*
|
|
4
4
|
* pair POSTs /v1/discord/pair with the current project anchor; prints
|
|
5
5
|
* the 6-char code + DM-the-bot instructions.
|
|
6
6
|
* unpair DELETEs /v1/discord/pair.
|
|
7
|
-
* me GETs /v1/discord/me — pairing
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* me GETs /v1/discord/me — pairing state.
|
|
8
|
+
* typing POSTs /v1/discord/typing — refreshes the typing indicator.
|
|
9
|
+
*
|
|
10
|
+
* Sessions DM the operator with `greprag send --to discord:<snowflake>`.
|
|
11
|
+
* Nothing reads DMs sent to the bot: the Front Desk and the handoff pin were
|
|
12
|
+
* retired 2026-10-06, so the bot answers each one with a fixed line.
|
|
11
13
|
*/
|
|
12
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
15
|
exports.DISCORD_HELP = void 0;
|
|
14
16
|
exports.callDiscordPair = callDiscordPair;
|
|
15
17
|
exports.callDiscordUnpair = callDiscordUnpair;
|
|
16
18
|
exports.callDiscordMe = callDiscordMe;
|
|
17
|
-
exports.callDiscordHandoff = callDiscordHandoff;
|
|
18
|
-
exports.callDiscordUnhandoff = callDiscordUnhandoff;
|
|
19
19
|
exports.callDiscordTyping = callDiscordTyping;
|
|
20
20
|
async function callDiscordPair(deps, projectId) {
|
|
21
21
|
const f = deps.fetchImpl ?? fetch;
|
|
@@ -45,30 +45,6 @@ async function callDiscordMe(deps) {
|
|
|
45
45
|
});
|
|
46
46
|
return (await res.json());
|
|
47
47
|
}
|
|
48
|
-
async function callDiscordHandoff(deps, args) {
|
|
49
|
-
const f = deps.fetchImpl ?? fetch;
|
|
50
|
-
const res = await f(`${deps.apiUrl}/v1/discord/handoff`, {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
headers: {
|
|
53
|
-
'Authorization': `Bearer ${deps.apiKey}`,
|
|
54
|
-
'Content-Type': 'application/json',
|
|
55
|
-
},
|
|
56
|
-
body: JSON.stringify({
|
|
57
|
-
project_id: args.projectId,
|
|
58
|
-
session_id: args.sessionId,
|
|
59
|
-
ttl_minutes: args.ttlMinutes,
|
|
60
|
-
}),
|
|
61
|
-
});
|
|
62
|
-
return (await res.json());
|
|
63
|
-
}
|
|
64
|
-
async function callDiscordUnhandoff(deps) {
|
|
65
|
-
const f = deps.fetchImpl ?? fetch;
|
|
66
|
-
const res = await f(`${deps.apiUrl}/v1/discord/handoff`, {
|
|
67
|
-
method: 'DELETE',
|
|
68
|
-
headers: { 'Authorization': `Bearer ${deps.apiKey}` },
|
|
69
|
-
});
|
|
70
|
-
return (await res.json());
|
|
71
|
-
}
|
|
72
48
|
async function callDiscordTyping(deps) {
|
|
73
49
|
const f = deps.fetchImpl ?? fetch;
|
|
74
50
|
const res = await f(`${deps.apiUrl}/v1/discord/typing`, {
|
|
@@ -78,44 +54,19 @@ async function callDiscordTyping(deps) {
|
|
|
78
54
|
return (await res.json());
|
|
79
55
|
}
|
|
80
56
|
exports.DISCORD_HELP = `
|
|
81
|
-
greprag discord —
|
|
57
|
+
greprag discord — let your sessions DM you on Discord.
|
|
82
58
|
|
|
83
|
-
Pair once
|
|
84
|
-
|
|
85
|
-
away from the desk and want to keep the same thread alive on your phone).
|
|
59
|
+
Pair once; then any session can message you (see Outbound below). The bot does
|
|
60
|
+
not read DMs you send it: the Front Desk was retired 2026-10-06.
|
|
86
61
|
|
|
87
62
|
Subcommands:
|
|
88
63
|
greprag discord pair Generate a 15-min code; DM @greprag-bot
|
|
89
64
|
/pair <code> to link this Discord account
|
|
90
65
|
to the current project.
|
|
91
66
|
greprag discord unpair Remove the pairing.
|
|
92
|
-
greprag discord me Show current pairing
|
|
93
|
-
greprag discord handoff [--ttl N] Pin DMs to (current project, current
|
|
94
|
-
session) for N minutes (default 60,
|
|
95
|
-
max 1440). Bot DMs you a confirmation,
|
|
96
|
-
then this command BLOCKS and streams
|
|
97
|
-
inbound DMs to stdout (one per line)
|
|
98
|
-
until killed. Run it under Claude's
|
|
99
|
-
Monitor tool: each line = one
|
|
100
|
-
notification right back into the
|
|
101
|
-
agent's turn. Sliding TTL: each
|
|
102
|
-
inbound DM extends the pin by >=30 min.
|
|
103
|
-
[--session <8-hex>] Override session id (default: CLAUDE_SESSION_ID).
|
|
104
|
-
[--project <name>] Override project (default: cwd anchor).
|
|
105
|
-
[--orchestrator] Tenant-wide watcher — sees inbox traffic
|
|
106
|
-
from any project under this tenant. The
|
|
107
|
-
DM pin still routes here, but the agent
|
|
108
|
-
has portfolio-wide eyes. Use when this
|
|
109
|
-
session is the user's orchestrator
|
|
110
|
-
(/business-advisor loaded, etc.).
|
|
111
|
-
[--json] Emit raw JSON per line (for Monitor).
|
|
112
|
-
[--no-watch] Pin and exit — skip the inline watcher.
|
|
113
|
-
For scripted use; loses the live DM stream.
|
|
114
|
-
greprag discord unhandoff Clear the active handoff — DMs revert
|
|
115
|
-
to default project.
|
|
67
|
+
greprag discord me Show current pairing state.
|
|
116
68
|
greprag discord typing Refresh the "typing..." indicator on
|
|
117
|
-
Discord for ~10s.
|
|
118
|
-
inbound DM; call manually during long
|
|
69
|
+
Discord for ~10s. Call it during long
|
|
119
70
|
agent turns so the user sees activity
|
|
120
71
|
instead of silence.
|
|
121
72
|
|
|
@@ -46,7 +46,7 @@ function buildInboxPrimer(env) {
|
|
|
46
46
|
? '[CODEX INBOX — greprag is your agent-to-agent mesh. Codex-to-Codex: discover tasks and their repo/workspace with `codex_app.list_threads`, then coordinate with `codex_app.send_message_to_thread`. Reaching ANY non-Codex peer (Claude Code / opencode / Grok / another tenant\'s agent): `greprag send` IS the live primary path — native Codex messaging cannot reach them.]'
|
|
47
47
|
: opencode
|
|
48
48
|
? '[OPENCODE INBOX — greprag is your agent-to-agent mesh. Inbound delivery is AUTOMATIC here: the greprag plugin arms a relay for this session (nothing to arm or re-arm) and inbound peer messages arrive as injected turns.]'
|
|
49
|
-
: '[CLAUDE CODE INBOX — greprag is how the world OUTSIDE this harness reaches you: Codex / opencode / Grok peers, the operator by DM, inbound email. Inbound delivery is AUTOMATIC and TURN-BASED: unread mail is injected at the top of your turn, plus a drain at session start. There is NOTHING to arm — the Monitor inbox watcher is RETIRED here (2026-09-04). Reply to those on the same greprag rail they arrived on. Another CLAUDE CODE session is NEVER reached through greprag — use your own harness\'s session list + send tools, which know each peer\'s working directory. Every 8-hex id greprag shows you is a GREPRAG address: those tools do not accept it
|
|
49
|
+
: '[CLAUDE CODE INBOX — greprag is how the world OUTSIDE this harness reaches you: Codex / opencode / Grok peers, the operator by DM, inbound email. Inbound delivery is AUTOMATIC and TURN-BASED: unread mail is injected at the top of your turn, plus a drain at session start. There is NOTHING to arm — the Monitor inbox watcher is RETIRED here (2026-09-04). Reply to those on the same greprag rail they arrived on. Another CLAUDE CODE session is NEVER reached through greprag — use your own harness\'s session list + send tools, which know each peer\'s working directory. Every 8-hex id greprag shows you is a GREPRAG address: those tools do not accept it. Idle mail waits for the next turn instead of waking you.]',
|
|
50
50
|
grok
|
|
51
51
|
? `ARM (idle wake): Grok \`monitor\` tool, persistent:true, description:"greprag inbox ${env.short}", command: \`${arm}\`. \`--quiet\` is REQUIRED — Grok treats stderr as wake events; the Claude bash wrapper is PowerShell-invalid. Use full UUID / 16-hex, never 8-hex. A chip → \`greprag load grok-chip-spawn\` then \`greprag grok spawn\` (child arms its own watch; parent keeps ONE). A helper → spawn_subagent. Floor: Stop-hook drain injects unread mail even if you never arm. Then \`greprag inbox\`.`
|
|
52
52
|
: codex
|
|
@@ -251,19 +251,6 @@ function printMessage(msg) {
|
|
|
251
251
|
function emitDmReplyDirective(msg, session) {
|
|
252
252
|
if (!session)
|
|
253
253
|
return;
|
|
254
|
-
// Front Desk Switchboard relay: the operator reached THIS session through the
|
|
255
|
-
// receptionist and `references.reply_to` is the rail to answer on. Fires for
|
|
256
|
-
// ANY message_type (the relay uses plain 'message'), and takes precedence over
|
|
257
|
-
// the legacy Discord-DM path. Without this the session falls back to the bare
|
|
258
|
-
// sender handle (travis@greprag.com) — the reply lands in MAIN, never reaching
|
|
259
|
-
// the operator on Discord/SMS. Live failure 2026-06-08. spec:
|
|
260
|
-
// docs/front-desk-switchboard.md §4 (return leg).
|
|
261
|
-
const replyTo = msg.references?.reply_to;
|
|
262
|
-
if (replyTo) {
|
|
263
|
-
console.log(`[greprag] Operator via front desk — answer now, even mid-task: greprag send --to ${replyTo} "…" ` +
|
|
264
|
-
`(exact addr; bare handle is lost). If they reply inline here, don't echo to Discord/SMS.`);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
254
|
if (msg.message_type !== 'discord_dm')
|
|
268
255
|
return;
|
|
269
256
|
const snowflake = msg.references?.discord?.snowflake ?? '<snowflake>';
|
package/dist/commands/load.js
CHANGED
|
@@ -453,9 +453,11 @@ async function printMirroredSkill(name, args) {
|
|
|
453
453
|
const identityArg = args.skillId ? ` --skill-id ${args.skillId}` : '';
|
|
454
454
|
console.error(`Mirrored skill "${name}" has no canonical body — the mirror is holding the generated adapter.\n` +
|
|
455
455
|
`Refusing to serve it: the adapter is the launcher that sent you here, not the skill.\n\n` +
|
|
456
|
-
`Restore the real body from the pre-adoption snapshot:\n` +
|
|
456
|
+
`Restore the real body from an earlier revision or the pre-adoption snapshot:\n` +
|
|
457
|
+
` greprag skill history ${name}${identityArg}\n` +
|
|
458
|
+
` greprag skill revert ${name} --to <rev>${identityArg} --reason "<why>" --expected-hash <current-hash>\n` +
|
|
457
459
|
` greprag skill mirror snapshot list ${name}${identityArg}\n` +
|
|
458
|
-
` greprag skill mirror snapshot promote ${name} --name <snapshot>${identityArg} --expected-hash <current-hash
|
|
460
|
+
` greprag skill mirror snapshot promote ${name} --name <snapshot>${identityArg} --expected-hash <current-hash> --reason "<why>"\n`);
|
|
459
461
|
process.exitCode = 1;
|
|
460
462
|
return true;
|
|
461
463
|
}
|
|
@@ -302,7 +302,7 @@ function buildMergeGuardReason(held, ownership) {
|
|
|
302
302
|
'merges into it are serialised the way deploys already are.',
|
|
303
303
|
' greprag merge-lock acquire --label "<what you are merging>"',
|
|
304
304
|
' git merge --ff-only <rebased branch>',
|
|
305
|
-
' greprag merge-lock release --landed <
|
|
305
|
+
' greprag merge-lock release --landed <rebased branch> --label "<what it was>"',
|
|
306
306
|
'One call works too when the acquire comes first and failure stops the merge:',
|
|
307
307
|
'use an `&&` chain, or in PowerShell a try/finally block with an immediate',
|
|
308
308
|
'`if ($LASTEXITCODE -ne 0) { throw ... }` after acquire. Bare `;` and `||`',
|
|
@@ -7,7 +7,7 @@ const HELP = `greprag merge-lock — serialise merges into a shared canonical ch
|
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
9
|
greprag merge-lock acquire [--label <text>] [--json]
|
|
10
|
-
greprag merge-lock release [--landed <
|
|
10
|
+
greprag merge-lock release [--landed <branch>] [--label <text>] [--json]
|
|
11
11
|
greprag merge-lock status [--json]
|
|
12
12
|
greprag merge-lock claim [--drop <sha>] [--json]
|
|
13
13
|
|
|
@@ -28,9 +28,14 @@ serves HEAD — never that HEAD contains your work. On 2026-09-04 a session's me
|
|
|
28
28
|
redone by a peer under a new sha, its deploy exited 0 honestly, and it reported
|
|
29
29
|
shipping work that was on no branch.
|
|
30
30
|
|
|
31
|
+
Name the branch you merged, never HEAD. Release exits 1 when that commit is not on
|
|
32
|
+
the integration branch, or was already its tip when you took the lock: on 2026-10-06
|
|
33
|
+
a refused fast-forward piped through tail left HEAD unmoved, and \`--landed HEAD\`
|
|
34
|
+
claimed a peer's commit as this session's work.
|
|
35
|
+
|
|
31
36
|
Options:
|
|
32
37
|
--label <text> What this merge is, shown to whoever is blocked by it
|
|
33
|
-
--landed <
|
|
38
|
+
--landed <rev> The branch you merged; verified now and again at deploy time
|
|
34
39
|
--drop <sha> (claim) Forget a claim you have confirmed by hand
|
|
35
40
|
--json Machine-readable result`;
|
|
36
41
|
/** Resolve to a full sha, so a claim never depends on an abbreviation staying unique. */
|
|
@@ -67,7 +72,9 @@ function runMergeLock(args) {
|
|
|
67
72
|
if (sub === 'acquire') {
|
|
68
73
|
// Session-held, so never pid-checked: the process running this command exits
|
|
69
74
|
// immediately and a pid check would mark the lock stale the moment it is taken.
|
|
70
|
-
const result = (0, deploy_locks_1.acquireLock)(file, flagValue(args, '--label') || 'merge', deploy_locks_1.MERGE_STALE_MS, {
|
|
75
|
+
const result = (0, deploy_locks_1.acquireLock)(file, flagValue(args, '--label') || 'merge', deploy_locks_1.MERGE_STALE_MS, {
|
|
76
|
+
checkPid: false, base: (0, deploy_locks_1.refTip)(cwd, (0, deploy_locks_1.integrationRef)(cwd)),
|
|
77
|
+
});
|
|
71
78
|
if (json)
|
|
72
79
|
console.log(JSON.stringify(result));
|
|
73
80
|
else if (result.ok) {
|
|
@@ -104,10 +111,29 @@ function runMergeLock(args) {
|
|
|
104
111
|
process.exitCode = 1;
|
|
105
112
|
return;
|
|
106
113
|
}
|
|
114
|
+
// Checked here, not trusted: the exit code is what stops `release && deploy`
|
|
115
|
+
// even when a pipe swallowed the merge's own refusal.
|
|
116
|
+
const verdict = (0, deploy_locks_1.landedVerdict)(cwd, resolved, (0, deploy_locks_1.readMergeLock)(cwd)?.record.base);
|
|
117
|
+
if (verdict === 'nothing-new') {
|
|
118
|
+
(0, deploy_locks_1.releaseLock)(file);
|
|
119
|
+
console.error(`merge-lock: released, nothing claimed. ${resolved.slice(0, 10)} was already the tip of the integration branch`
|
|
120
|
+
+ ' when you took the lock, so nothing landed under it. A refused merge leaves exactly this state.'
|
|
121
|
+
+ '\n Name the branch you merged: --landed <branch>. It passes only once that branch is in.'
|
|
122
|
+
+ '\n If someone else already landed it before you took the lock, release without --landed.');
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
107
126
|
try {
|
|
108
127
|
const claim = (0, deploy_locks_1.recordLandedClaim)(cwd, resolved, flagValue(args, '--label') || 'merged work');
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
if (verdict === 'not-landed') {
|
|
129
|
+
console.error(`merge-lock: released, but ${claim.sha.slice(0, 10)} is NOT on the integration branch — your merge did not land.`);
|
|
130
|
+
console.error(' The claim is recorded, so the deploy gate refuses until that commit is in what ships.');
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log(`merge-lock: released; claimed ${claim.sha.slice(0, 10)} as landed`);
|
|
135
|
+
console.log(' The deploy gate will refuse until that commit is in what ships.');
|
|
136
|
+
}
|
|
111
137
|
}
|
|
112
138
|
catch (error) {
|
|
113
139
|
console.error(`merge-lock: released, but the claim was NOT recorded: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* PURE over ReminderEnv — the hook assembles env (i/o) and emits the returned lines;
|
|
6
6
|
* a broken module never blocks a turn (fail-open per module). */
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.ANNOUNCE_POINTER_RESERVE = exports.ANNOUNCE_HARNESS_CAP = exports.ANNOUNCE_INLINE_BUDGET = exports.compactReannounceModules = exports.
|
|
8
|
+
exports.ANNOUNCE_POINTER_RESERVE = exports.ANNOUNCE_HARNESS_CAP = exports.ANNOUNCE_INLINE_BUDGET = exports.compactReannounceModules = exports.harnessModules = exports.REGISTRY = void 0;
|
|
9
9
|
exports.collectReminders = collectReminders;
|
|
10
10
|
exports.bootOrder = bootOrder;
|
|
11
11
|
exports.collectAnnounces = collectAnnounces;
|
|
@@ -66,13 +66,6 @@ exports.REGISTRY = [
|
|
|
66
66
|
* update a parallel id list. (Root-cause repair, chip/opencode-fix-chip-system-parity.) */
|
|
67
67
|
const harnessModules = (harness, registry = exports.REGISTRY) => registry.filter((m) => !m.harnesses || m.harnesses.includes(harness));
|
|
68
68
|
exports.harnessModules = harnessModules;
|
|
69
|
-
/** Split the registry by READ surface (docs/reminder-interrupt.md). UserPromptSubmit
|
|
70
|
-
* evaluates `prompt`-source modules (the default); PreToolUse evaluates `command`-source
|
|
71
|
-
* modules (they read env.toolCommand). Announces are always prompt-side (SessionStart). */
|
|
72
|
-
const promptModules = (registry = exports.REGISTRY) => registry.filter((m) => (m.source ?? 'prompt') === 'prompt');
|
|
73
|
-
exports.promptModules = promptModules;
|
|
74
|
-
const commandModules = (registry = exports.REGISTRY) => registry.filter((m) => m.source === 'command');
|
|
75
|
-
exports.commandModules = commandModules;
|
|
76
69
|
/** PostCompact re-announce set (docs/reminder-interrupt.md). A compaction wipes the
|
|
77
70
|
* SessionStart announces from context, so the announce modules re-present afterward —
|
|
78
71
|
* by DEFAULT all of them; a module opts out with `reannounceOnCompact: false`. The
|
package/dist/commands/session.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* retitle <session-8hex> "<title>" — force-set a session's registry title,
|
|
5
5
|
* overriding the gpt-5.4-nano auto-title. Used by the multi-chip /chip-leader
|
|
6
6
|
* convention: a chip stamps its own label-aligned title ("Chip A — …") in
|
|
7
|
-
* Block 1 so the FleetView watcher list
|
|
8
|
-
*
|
|
7
|
+
* Block 1 so the FleetView watcher list lines up with the leader's chip
|
|
8
|
+
* labels. Thin HTTP client over POST /v1/memory/title. */
|
|
9
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
11
11
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -90,7 +90,6 @@ function buildSkillMirrorAnnounce(m, platform) {
|
|
|
90
90
|
}
|
|
91
91
|
exports.skillMirrorAnnounceModule = {
|
|
92
92
|
id: 'skill-mirror-announce',
|
|
93
|
-
source: 'prompt',
|
|
94
93
|
dependsOn: ['load-primer'], // references `greprag load` — the primer establishes it
|
|
95
94
|
detect: (_env) => ({ tier: 'silent' }), // announce-only
|
|
96
95
|
announce: (env) => buildSkillMirrorAnnounce(env.mirroredSkills, env.platform),
|