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.
@@ -14,7 +14,7 @@ greprag inbox watch --since <id-or-iso> # resume from a known cursor
14
14
  greprag inbox watch --json # one JSON object per line (preferred under Monitor)
15
15
  ```
16
16
 
17
- `--project` and `--session` compose. `--session` is the strictest filter: only messages with matching `to_session_id` get through. The tenant catch-all (cold open / inbound email — `to_session_id IS NULL`) does NOT reach a session- or project-scoped watcher; only a `--receptionist` watcher wakes on it (Option C). adr: adr/address-grammar.md. This eliminates cross-session noise when several Claude sessions watch the same project's inbox at once.
17
+ `--project` and `--session` compose. `--session` is the strictest filter: only messages with matching `to_session_id` get through. The tenant catch-all (cold open / inbound email — `to_session_id IS NULL`) does NOT reach a session- or project-scoped watcher; only a `--receptionist` watcher wakes on it (Option C). adr: repo:greprag/adr/address-grammar.md. This eliminates cross-session noise when several Claude sessions watch the same project's inbox at once.
18
18
 
19
19
  `--mechanic` is a role subscription: it keeps the normal session/project lane and adds only tenant `mechanic_friction` events. It does not wake on ordinary cold opens or inbound email.
20
20
 
@@ -77,9 +77,9 @@ First-contact handshake: a sender who doesn't know your session sends to bare `<
77
77
  - `1834729@greprag.com` — canonical numeric handle. Opaque, leaks nothing. Get yours from `greprag status --json | jq -r .identity.handle` or `greprag whoami`.
78
78
  - `alice@greprag.com` — opt-in vanity alias (claim with `greprag identity claim alice`).
79
79
 
80
- **Real emails are NEVER routing addresses.** `users.email` is auth credential only. If you don't know the recipient's numeric handle, ask the user — don't guess from their email. adr: adr/numeric-handles.md
80
+ **Real emails are NEVER routing addresses.** `users.email` is auth credential only. If you don't know the recipient's numeric handle, ask the user — don't guess from their email. adr: repo:greprag/adr/numeric-handles.md
81
81
 
82
- Session targeting is the default and expected form; the bare handle is the cold-open front desk. Project broadcasts were removed (2026-06-07) — a project-name target errors out. adr: adr/address-grammar.md
82
+ Session targeting is the default and expected form; the bare handle is the cold-open front desk. Project broadcasts were removed (2026-06-07) — a project-name target errors out. adr: repo:greprag/adr/address-grammar.md
83
83
 
84
84
  ## retract
85
85
 
@@ -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>`; greprag-bundled skills: `packages/cli/skill/<name>/`. Never edit a generated `~/.claude/skills/<name>/SKILL.md` adapter or install artifact.
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 + Discord `/switch` line up with the leader's labels. Single chips skip this.
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
- **Report back:** when the work is committed, send your result to the parent with native `SendMessage`, then end your turn:
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.
@@ -88,13 +88,30 @@ checkout:
88
88
  tip.
89
89
  4. Land with `git merge --ff-only <branch>`. A fast-forward cannot produce a
90
90
  surprise merge commit or silently resolve someone else's conflict.
91
- 5. Record the landed sha with `merge-lock release --landed`.
91
+ 5. Record it with `merge-lock release --landed <branch>` — the branch, never
92
+ HEAD. Release exits 1 if that branch did not land.
92
93
 
93
94
  If the tip moved while you were preparing, free the lock, rebase, and test
94
95
  again. Never rebase a shared default branch. A busy merge lock means retry
95
96
  after its holder finishes — never bypass it, and never take over someone
96
97
  else's in-progress merge.
97
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
+
98
115
  This lock is a short Git critical section. It is not a delivery lease, and it
99
116
  is not peer-acknowledgement polling.
100
117
 
@@ -40,7 +40,9 @@ Explicitly requested creation, porting, or redesign is authorized work. The prop
40
40
  - Bundled GrepRAG skill: edit `packages/cli/skill/<name>/`, never the installed artifact.
41
41
  - Mirrored/local skill: edit its canonical source, not the generated Codex/Claude/OpenCode adapter.
42
42
  - Unknown source: stop and identify the canonical source before editing.
43
- - Codex/non-interactive harness: do not launch an editor. Use `greprag skill mirror export <skill> --to <dir>`, patch the exported Markdown files with file edits, then `greprag skill mirror apply <dir> --skill <skill> --expected-hash <exported-hash>`. If using `greprag skill edit` as the export helper, pass `--no-open`.
43
+ - Codex/non-interactive harness: do not launch an editor. Use `greprag skill mirror export <skill> --to <dir>`, patch the exported Markdown files with file edits, then `greprag skill mirror apply <dir> --skill <skill> --expected-hash <exported-hash> --reason "<why>"`. If using `greprag skill edit` as the export helper, pass `--no-open`.
44
+ - **ABOUT TO WRITE A MIRRORED SKILL? STOP - PASS `--reason "<one line>"`.** `skill edit`, `skill mirror apply`, `snapshot promote` and `skill revert` refuse without it. GrepRAG keeps every revision's bytes with that reason, so write the learning the edit captures, not "update skill".
45
+ - History: `greprag skill history <skill>` lists revisions (date, author, reason, changes); `greprag skill diff <skill> <revA> [<revB>]` shows a unified diff; `greprag skill revert <skill> --to <rev> --reason "<why>" --expected-hash <current-hash>` makes an old revision current as a new one. Add the adapter's `--skill-id` to each.
44
46
 
45
47
  ## Skill-Refresh Chips
46
48
 
@@ -1,215 +0,0 @@
1
- "use strict";
2
- /** greprag sms <pair|unpair|me> — Front Desk SMS rail CLI.
3
- *
4
- * pair POSTs /v1/sms/pair with the current project anchor; prints the
5
- * 6-char code + the greprag number to text it to.
6
- * unpair DELETEs /v1/sms/pair (optionally for one --phone).
7
- * me GETs /v1/sms/me — the tenant's bound phones.
8
- *
9
- * Self-contained HTTP client (does not import @greprag/core), mirroring
10
- * commands/email.ts. See docs/receptionist-sms-mvp.md.
11
- */
12
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- var desc = Object.getOwnPropertyDescriptor(m, k);
15
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
- desc = { enumerable: true, get: function() { return m[k]; } };
17
- }
18
- Object.defineProperty(o, k2, desc);
19
- }) : (function(o, m, k, k2) {
20
- if (k2 === undefined) k2 = k;
21
- o[k2] = m[k];
22
- }));
23
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
- Object.defineProperty(o, "default", { enumerable: true, value: v });
25
- }) : function(o, v) {
26
- o["default"] = v;
27
- });
28
- var __importStar = (this && this.__importStar) || (function () {
29
- var ownKeys = function(o) {
30
- ownKeys = Object.getOwnPropertyNames || function (o) {
31
- var ar = [];
32
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
- return ar;
34
- };
35
- return ownKeys(o);
36
- };
37
- return function (mod) {
38
- if (mod && mod.__esModule) return mod;
39
- var result = {};
40
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
- __setModuleDefault(result, mod);
42
- return result;
43
- };
44
- })();
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.runSms = runSms;
47
- const fs = __importStar(require("fs"));
48
- const path = __importStar(require("path"));
49
- const project_anchor_1 = require("../project-anchor");
50
- const API_URL_DEFAULT = 'https://api.greprag.com';
51
- function loadEnvFile(filePath) {
52
- try {
53
- const content = fs.readFileSync(filePath, 'utf-8');
54
- for (const line of content.split('\n')) {
55
- const trimmed = line.trim();
56
- if (!trimmed || trimmed.startsWith('#'))
57
- continue;
58
- const eqIdx = trimmed.indexOf('=');
59
- if (eqIdx < 1)
60
- continue;
61
- const key = trimmed.slice(0, eqIdx).trim();
62
- let value = trimmed.slice(eqIdx + 1).trim();
63
- if ((value.startsWith('"') && value.endsWith('"')) ||
64
- (value.startsWith("'") && value.endsWith("'"))) {
65
- value = value.slice(1, -1);
66
- }
67
- if (!process.env[key])
68
- process.env[key] = value;
69
- }
70
- }
71
- catch { /* file missing — fine */ }
72
- }
73
- function getConfig() {
74
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
75
- if (homeDir)
76
- loadEnvFile(path.join(homeDir, '.greprag', '.env'));
77
- if (!process.env.GREPRAG_API_KEY)
78
- loadEnvFile(path.join(process.cwd(), '.env'));
79
- return {
80
- apiUrl: process.env.GREPRAG_API_URL || API_URL_DEFAULT,
81
- apiKey: process.env.GREPRAG_API_KEY || '',
82
- };
83
- }
84
- function getFlag(args, flag) {
85
- const idx = args.indexOf(flag);
86
- if (idx === -1 || idx + 1 >= args.length)
87
- return undefined;
88
- return args[idx + 1];
89
- }
90
- async function callPair(cfg, phone, projectId) {
91
- const payload = { phone };
92
- if (projectId)
93
- payload.project_id = projectId;
94
- const res = await fetch(`${cfg.apiUrl}/v1/sms/pair`, {
95
- method: 'POST',
96
- headers: { 'Authorization': `Bearer ${cfg.apiKey}`, 'Content-Type': 'application/json' },
97
- body: JSON.stringify(payload),
98
- });
99
- return res.json();
100
- }
101
- async function callUnpair(cfg, phone) {
102
- const res = await fetch(`${cfg.apiUrl}/v1/sms/pair`, {
103
- method: 'DELETE',
104
- headers: { 'Authorization': `Bearer ${cfg.apiKey}`, 'Content-Type': 'application/json' },
105
- body: JSON.stringify(phone ? { phone } : {}),
106
- });
107
- return res.json();
108
- }
109
- async function callMe(cfg) {
110
- const res = await fetch(`${cfg.apiUrl}/v1/sms/me`, {
111
- method: 'GET',
112
- headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
113
- });
114
- return res.json();
115
- }
116
- const SMS_HELP = `
117
- greprag sms — Front Desk SMS rail. Reach a cheap receptionist by text to triage
118
- your inbox and route messages to your sessions from your phone.
119
-
120
- Subcommands:
121
- greprag sms pair --phone <e164> Generate a 15-min code BOUND to that
122
- phone; text it (or "PAIR <code>") FROM
123
- that exact number to the greprag number
124
- to bind it. --phone is required and must
125
- be full E.164 (e.g. +15551234567) — the
126
- code only binds when claimed from it. The
127
- current directory's project anchor becomes
128
- your home project.
129
- greprag sms unpair [--phone <e164>] Unbind one phone (or all phones).
130
- greprag sms me Show bound phones + the greprag number.
131
-
132
- Once paired, text the greprag number:
133
- "what's waiting?" → the receptionist reads your inbox.
134
- "tell session a1b2c3d4 to ship it" → routes a message to that session.
135
- `.trim();
136
- async function runSms(args) {
137
- const sub = args[0];
138
- if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
139
- console.log(SMS_HELP);
140
- return;
141
- }
142
- const cfg = getConfig();
143
- if (!cfg.apiKey) {
144
- console.error('Not configured. Run: greprag init');
145
- process.exit(1);
146
- }
147
- if (sub === 'pair') {
148
- // --phone is required: the code is bound to it, and only a claim texted from
149
- // that exact number succeeds (the identity guard against brute-force binds).
150
- const phone = getFlag(args, '--phone');
151
- if (!phone) {
152
- console.error('Error: --phone <e164> is required (the number you will text from, e.g. +15551234567).');
153
- process.exit(1);
154
- }
155
- // The home/default project is the current directory's anchor. The API keys
156
- // pairing by project_id; a `--project <name>` would need a server-side name
157
- // lookup, so for the MVP the anchor is the source of truth (run pair from
158
- // the project dir you want as home, or leave a message naming any project).
159
- const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
160
- const projectId = anchor.projectId || null;
161
- const result = await callPair(cfg, phone, projectId);
162
- if (!result.ok) {
163
- console.error(`Error: ${result.error}`);
164
- process.exit(1);
165
- }
166
- console.log(`Pairing code: ${result.code}`);
167
- if (result.bound_phone) {
168
- console.log(`Bound to ${result.bound_phone} — text the code FROM that number only.`);
169
- }
170
- if (result.sms_number) {
171
- console.log(`Text it to the greprag number: ${result.sms_number}`);
172
- console.log(` (send "${result.code}" or "PAIR ${result.code}")`);
173
- }
174
- else {
175
- console.log(`Text "${result.code}" (or "PAIR ${result.code}") to the greprag number.`);
176
- console.log(` (number not configured server-side yet — ask the operator)`);
177
- }
178
- console.log(`Code expires in ${result.expires_in_minutes} minutes.`);
179
- if (result.project_name) {
180
- console.log(`After pairing, your home project is "${result.project_name}".`);
181
- }
182
- else {
183
- console.log(`No default project set — name one when you leave a message.`);
184
- }
185
- return;
186
- }
187
- if (sub === 'unpair') {
188
- const phone = getFlag(args, '--phone');
189
- const result = await callUnpair(cfg, phone);
190
- if (!result.ok) {
191
- console.error(`Error: ${result.error}`);
192
- process.exit(1);
193
- }
194
- console.log(`Unpaired ${result.removed} phone(s).`);
195
- return;
196
- }
197
- if (sub === 'me') {
198
- const result = await callMe(cfg);
199
- if (!result.ok) {
200
- console.error(`Error: ${result.error}`);
201
- process.exit(1);
202
- }
203
- console.log(`greprag number: ${result.sms_number ?? '(not configured)'}`);
204
- if (!result.paired || !result.phones || result.phones.length === 0) {
205
- console.log('No phones paired. Run: greprag sms pair');
206
- return;
207
- }
208
- console.log(`Paired phones (${result.phones.length}):`);
209
- for (const p of result.phones) {
210
- console.log(` ${p.phone} paired ${p.paired_at} last inbound ${p.last_seen_inbound_at ?? '(never)'}`);
211
- }
212
- return;
213
- }
214
- console.log(SMS_HELP);
215
- }