greprag 5.16.0 → 5.17.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.
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // adr: adr/memory-provenance-capture.md
3
+ /** Turn provenance — deterministic, pre-LLM classification of captured
4
+ * user-side turn text.
5
+ *
6
+ * MIRROR of packages/core/src/turn-provenance.ts (the canonical source). The
7
+ * CLI ships as a zero-dependency `tsc` artifact — the Stop hook cannot import
8
+ * @greprag/core — so the classifier is duplicated here. KEEP IN SYNC with core.
9
+ * Only the classify + marker helpers are mirrored; the SQL read-side predicate
10
+ * is server-only and stays in core. See adr/memory-provenance-capture.md.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.classifyUserText = classifyUserText;
14
+ exports.isInjectedProvenance = isInjectedProvenance;
15
+ exports.provenanceHint = provenanceHint;
16
+ exports.provenanceElisionMarker = provenanceElisionMarker;
17
+ /** Classify captured user-side text. Deterministic; never throws. */
18
+ function classifyUserText(text) {
19
+ const t = (text || '').trimStart();
20
+ if (!t)
21
+ return 'session-turn';
22
+ if (/^Base directory for this skill:/.test(t))
23
+ return 'skill-injection';
24
+ if (/^This session is being continued from a previous conversation/.test(t)) {
25
+ return 'continuation-summary';
26
+ }
27
+ if (/^You are a .{0,60}\bchip\b/.test(t) ||
28
+ (/\bgit worktree add\b/.test(t) &&
29
+ /(^|\n)\s*(\*\*Setup\b|Chip:|#\s*Chip\b|report back\b)/m.test(t))) {
30
+ return 'chip-prompt';
31
+ }
32
+ return 'session-turn';
33
+ }
34
+ function isInjectedProvenance(p) {
35
+ return p !== 'session-turn';
36
+ }
37
+ /** Best-effort short label for an injected block, used in the elision marker. */
38
+ function provenanceHint(p, text) {
39
+ if (p === 'skill-injection') {
40
+ const m = (text || '').match(/skills[/\\]([A-Za-z0-9._-]+)/) ||
41
+ (text || '').match(/^#\s+(.+)$/m);
42
+ return m ? m[1].trim().slice(0, 40) : undefined;
43
+ }
44
+ return undefined;
45
+ }
46
+ /** Compact marker that replaces injected text in the searchable node content.
47
+ * Starts with `[greprag:` so classifyUserText(marker) === 'session-turn'. */
48
+ function provenanceElisionMarker(p, hint) {
49
+ const label = p === 'skill-injection' ? 'skill body' :
50
+ p === 'continuation-summary' ? 'context-continuation summary' :
51
+ p === 'chip-prompt' ? 'chip task prompt' :
52
+ 'injected text';
53
+ const tail = hint ? ` (${hint})` : '';
54
+ return `[greprag: harness-injected ${label}${tail} elided from episodic capture]`;
55
+ }
56
+ //# sourceMappingURL=turn-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-provenance.js","sourceRoot":"","sources":["../src/turn-provenance.ts"],"names":[],"mappings":";AAAA,wCAAwC;AACxC;;;;;;;;GAQG;;AASH,4CAiBC;AAED,oDAEC;AAGD,wCAOC;AAID,0DAQC;AA5CD,qEAAqE;AACrE,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;IACnC,IAAI,CAAC,CAAC;QAAE,OAAO,cAAc,CAAC;IAE9B,IAAI,iCAAiC,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAExE,IAAI,+DAA+D,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,OAAO,sBAAsB,CAAC;IAChC,CAAC;IAED,IAAI,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9B,wDAAwD,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAgB,oBAAoB,CAAC,CAAiB;IACpD,OAAO,CAAC,KAAK,cAAc,CAAC;AAC9B,CAAC;AAED,iFAAiF;AACjF,SAAgB,cAAc,CAAC,CAAiB,EAAE,IAAY;IAC5D,IAAI,CAAC,KAAK,iBAAiB,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,8BAA8B,CAAC;YAClD,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC5C,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;8EAC8E;AAC9E,SAAgB,uBAAuB,CAAC,CAAiB,EAAE,IAAa;IACtE,MAAM,KAAK,GACT,CAAC,KAAK,iBAAiB,CAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC,KAAK,sBAAsB,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC;YAC/D,CAAC,KAAK,aAAa,CAAU,CAAC,CAAC,kBAAkB,CAAC,CAAC;gBACpB,eAAe,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,OAAO,8BAA8B,KAAK,GAAG,IAAI,gCAAgC,CAAC;AACpF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "greprag",
3
- "version": "5.16.0",
3
+ "version": "5.17.0",
4
4
  "description": "GrepRAG — agent memory for Claude Code. 2-command setup.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -17,141 +17,113 @@ description: |
17
17
  session to take this DM".
18
18
  metadata:
19
19
  author: travsteward
20
- version: "2.0.0"
20
+ version: "3.0.0"
21
21
  repository: https://github.com/travsteward/greprag
22
22
  license: MIT
23
23
  ---
24
24
 
25
25
  # Commander — Discord DM bridge
26
26
 
27
- The operator commands an open Claude session from Discord. The Discord side is the *channel*; "commander" is the *role* the operator inhabits when the DMs land.
27
+ The operator commands an open Claude session from Discord. Pairing links the account; **handoff** pins the operator's DMs to one session so the thread follows them off the desk.
28
28
 
29
- The agent runs `greprag discord me` to learn the current state, then branches into pair / handoff / status. Everything else is one shell command per branch.
29
+ **Commander writes the routing pin. It does not arm the watcher.** Every greprag session already keeps a session-scoped inbox watcher live — the detection-gated `notify` hook re-arms it on any unarmed turn (`adr/session-id-awareness.md`). A Discord DM is filed with `to_session_id = this session`, which that auto-armed watcher already delivers (its filter is `to_session_id = session OR NULL`). So **session handoff is one Bash call that writes the pin and exits** — no Monitor, no second watcher. Only **orchestrator** mode arms (the session auto-arm is session-scoped; portfolio-wide eyes need a tenant-wide stream).
30
30
 
31
- ## Step 1 — Get the state
31
+ ## Step 1 — State first
32
32
 
33
33
  ```bash
34
34
  greprag discord me
35
35
  ```
36
36
 
37
- Parse the JSON. Then **default to the handoff flow** unless the user's message is an explicit status query.
37
+ Parse the JSON, then **default to handoff** unless the message is an explicit status query:
38
38
 
39
- - **Default action:** the user invoked `/commander` (or any handoff trigger phrase). They want a handoff armed. Branch by pairing state:
40
- - `paired: false` run **pair flow** (Step 2), then continue into **handoff flow** (Step 3) once paired.
41
- - `paired: true` and `handoff === null` → run **handoff flow** (Step 3) immediately.
42
- - `paired: true` and `handoff !== null` the pin is already live. Print **status with handoff details** (Step 5), then ask whether to re-arm to a different session or leave it alone.
43
- - **Status-only branch:** ONLY skip the handoff flow when the user's message is an explicit status query — "am I paired?", "commander status", "what's my discord state?", or a direct request to inspect without arming. In that case go straight to Step 5.
39
+ - `paired: false` **pair** (Step 2), then **handoff** (Step 3).
40
+ - `paired: true`, `handoff === null` **handoff** (Step 3).
41
+ - `paired: true`, `handoff !== null` → pin already live: print **status** (Step 5), then ask whether to re-arm to a different session or leave it.
42
+ - Explicit status query ("am I paired?", "commander status", "what's my discord state?") **status** (Step 5) only.
44
43
 
45
- The bare slash-command invocation `/commander` is itself a handoff trigger. Do not treat it as ambiguous; arm the watcher. If the user wanted status only, they'd say so.
44
+ Bare `/commander` is itself a handoff trigger arm the pin, don't treat it as ambiguous. If the user wanted status only, they'd say so.
46
45
 
47
- ## Step 2 — Pair flow (only if not paired)
46
+ ## Step 2 — Pair (only if `paired: false`)
48
47
 
49
48
  ```bash
50
49
  greprag discord pair
51
50
  ```
52
51
 
53
- Prints the 6-char code. Tell the user verbatim:
52
+ Prints a 6-char code. Tell the user verbatim:
54
53
 
55
54
  > DM `@greprag` on Discord: `/pair <CODE>` (expires in 15 min).
56
55
 
57
- When they confirm the bot replied with "Paired," re-run `greprag discord me` to verify, then continue. If the user wanted a handoff (their original request mentioned stepping away), proceed to Step 3 once paired.
56
+ On "Paired" confirmation, re-run `greprag discord me` to verify, then continue to Step 3 if a handoff was intended.
58
57
 
59
- ## Step 3 — Handoff flow (the killer feature)
58
+ ## Step 3 — Handoff (write the pin)
60
59
 
61
- Trigger: user said anything implying "I'm leaving the desk and want this thread to follow me to Discord" — *step away*, *continue on phone*, *handoff*, *DM me*, *afk*, *I'll be walking*.
60
+ Resolve the session id (8-hex, from the SessionStart hook) and project (current anchor, or `--project <name>`).
62
61
 
63
- Resolve the session id from the SessionStart hook's `additionalContext` (8-hex form). Resolve the project name from the current anchor (`greprag project-id`'s sibling lookup, or the explicit `--project` flag).
62
+ ### Session handoff (default) one Bash call, no Monitor
64
63
 
65
- ### Two modes — pick by session role
66
-
67
- - **Session handoff (default).** This session is a focused per-project session. The watcher only surfaces inbox events for *this* `(project, session)`. Use when you're deep in one project's work.
68
- - **Orchestrator handoff** (`--orchestrator`). This session is the user's portfolio brain — `/business-advisor` or any `/<name>-advisor` is loaded, or the project is named `orchestrator` / `business` / a portfolio repo. The watcher is **tenant-wide**: this session sees inbox traffic from every project under the tenant. The DM routing pin still points to *this* session, so the user's DMs land here, but the agent has portfolio-wide eyes.
64
+ ```bash
65
+ greprag discord handoff --session <8-hex> --project <name> --ttl 60 --no-watch
66
+ ```
69
67
 
70
- Detect: if `/business-advisor` is loaded, or the project name suggests orchestrator-class, default to `--orchestrator`. Ask the user if ambiguous.
68
+ `--no-watch` writes the server-side pin (`active_project_id`, `active_session_id`, `active_expires_at`) and fires the confirmation DM, then exits. The DMs are delivered by this session's already-live auto-armed watcher — commander adds nothing.
71
69
 
72
- ### Arm the watcher
70
+ Tell the user: *"Handoff pinned for 60 min. Reply on Discord — your messages route to this session."*
73
71
 
74
- Under **Monitor with `persistent: true`**, wrapped in the `while true` restart loop so a process death (npm install mid-session, network blip, supervisor bug) auto-recovers.
72
+ > Safety net: if `greprag inbox watchers` shows no watcher for your 8-hex (auto-arm disabled, or it died and the next turn hasn't re-armed yet), arm one under **Monitor (persistent:true)**: `while true; do greprag inbox watch --session <8-hex> --json; echo "[restart]" >&2; sleep 1; done`.
75
73
 
76
- **Session handoff (default):**
74
+ ### Orchestrator handoff (`--orchestrator`) — the only mode that arms
77
75
 
78
- ```bash
79
- greprag discord handoff --session <8-hex> --project <name> --ttl 60 --no-watch && \
80
- while true; do
81
- greprag inbox watch --project <name> --session <8-hex> --json --no-supervise
82
- echo "[wrapper] watcher exited, restarting in 1s" >&2
83
- sleep 1
84
- done
85
- ```
76
+ This session is the user's portfolio brain (`/business-advisor` or any `/<name>-advisor` loaded; project named `orchestrator` / `business` / a portfolio repo). The DM pin still routes here, but the watcher is **tenant-wide** — it sees inbox traffic from every project under the tenant, which the session-scoped auto-arm does not cover. Detect from loaded skills / project name; ask if ambiguous.
86
77
 
87
- **Orchestrator handoff:**
78
+ Run under **Monitor (persistent:true)**, wrapped so a process death auto-recovers:
88
79
 
89
80
  ```bash
90
81
  greprag discord handoff --session <8-hex> --project <name> --ttl 60 --orchestrator --no-watch && \
91
82
  while true; do
92
- greprag inbox watch --json --no-supervise
83
+ greprag inbox watch --json
93
84
  echo "[wrapper] watcher exited, restarting in 1s" >&2
94
85
  sleep 1
95
86
  done
96
87
  ```
97
88
 
98
- Two halves on purpose:
99
- 1. `discord handoff --no-watch` writes the pin server-side (`active_project_id`, `active_session_id`, `active_expires_at`) and fires the confirmation DM to the user's phone. Returns immediately. `--orchestrator` only changes downstream behavior in the help text and skill detection; the pin itself is identical.
100
- 2. The `while true` loop tails the inbox via SSE. Each line printed = one event landing as a Monitor notification. Scope differs: session-filtered (default) vs. tenant-wide (orchestrator).
89
+ The pin comes from `discord handoff --no-watch`; the tenant-wide scope comes from the unfiltered `inbox watch --json`. (The per-turn auto-arm may also add a session-scoped watcher — harmless, it's a subset of the tenant-wide stream.)
101
90
 
102
- **Never use the bare `greprag discord handoff` form without `--no-watch` for production handoff.** It self-streams, but a process death (e.g. `npm i -g greprag@latest` from another shell wipes the install dir) takes the watcher down with no restart. The wrapped two-step is the resilient form.
91
+ Tell the user: *"Orchestrator pinned for 60 min. I now see inbox events across all your projects. DM me 'roundup' or 'anything happening on <project>?' to query, or just chat your messages land here."*
103
92
 
104
- Tell the user (session mode): "Handoff pinned for 60 min. Reply on Discord — your messages route to this session."
105
- Tell the user (orchestrator mode): "Orchestrator pinned for 60 min. I now see inbox events across all your projects. DM me 'roundup' or 'anything happening on <project>?' to query, or just chat — your messages land here."
93
+ ## Step 4 Reply to inbound DMs
106
94
 
107
- ## Step 4Replying to inbound DMs
108
-
109
- Each Monitor notification carries the full event payload: `body` is the user's message text, `references.discord.snowflake` is their Discord ID, and `references.discord.emergency_route` is non-null when the bot auto-routed an unassigned DM here (see Step 4b).
110
-
111
- Send your reply via the CLI:
95
+ Each watcher event carries the payload: `body` (message text), `references.discord.snowflake` (their Discord ID), `references.discord.emergency_route` (non-null when auto-routed see Step 4b).
112
96
 
113
97
  ```bash
114
98
  greprag send --to discord:<snowflake> "your reply text"
115
99
  ```
116
100
 
117
- The CLI uses Node fetch (UTF-8 native) — em-dashes, curly quotes, emoji all render correctly. **Do NOT use curl from bash on Windows** for Discord sends — the local code page (Win-1252) mangles non-ASCII bytes into `?` on the user's phone.
118
-
119
- **Voice-note DMs.** When the operator records a voice note instead of typing, the gateway transcribes it via Gemini before the inbox event reaches you. The `body` will be prefixed with `[voice]` (e.g. `[voice] step away for a bit`), or with `[voice transcription failed: ...]` if Gemini erred — in that case, ask the operator to re-record or fall back to the original audio URL in `references.discord.voice_attachments`. Reply path is unchanged: always text, via `greprag send --to discord:<snowflake>`.
120
-
121
- ## Step 4b — Emergency-routed DMs (self-identify before answering)
122
-
123
- If `references.discord.emergency_route` is non-null, this session was auto-linked because no handoff pin was active. The user may have meant to reach a different session. **Open your first reply by self-identifying** so the operator can redirect if needed.
101
+ The CLI uses Node fetch (UTF-8 native). **Never `curl` from bash on Windows** for Discord sends — the local code page (Win-1252) mangles non-ASCII into `?` on the user's phone.
124
102
 
125
- Example first reply:
103
+ **Voice-note DMs.** The gateway transcribes before the event reaches you; `body` is prefixed `[voice]` (or `[voice transcription failed: ...]` — then ask for a re-record, or fall back to `references.discord.voice_attachments`). Reply path unchanged: text, via `greprag send`.
126
104
 
127
- > Picked this up in `<project-name>` / session `<session-8hex>` (no handoff was active, so I'm the most-recently-active armed session). Auto-pin is 10 min; it extends to 30 once you reply again. Want to redirect? `/commander handoff` from another session, or just say "redirect to <project>".
105
+ Long turn (drafting >10s)? Refresh the indicator: `greprag discord typing` (silent on success; shows "GrepRAG is typing…" for ~10s). Reply >2000 chars split on paragraph boundaries with continuation markers.
128
106
 
129
- How the bot chose this session: among all armed session-scoped watchers under your tenant, it picks the **most-recently-active** one — ranked by `max(last turn end, arm time)` — so a session that just finished a turn (idle, waiting) outranks one grinding a long task or gone dark. The auto-pin starts at a **genuine 10-min leash**, so one stray DM into the wrong session self-heals fast if you go quiet; your *next* DM (pin now live) extends it to ≥30 min so a real back-and-forth keeps flowing here. If the picked session turns out to be dead — or nothing is armed at all — the bot replies that your message is filed and will be delivered the moment a session reconnects (it never vanishes), and clears the dead auto-pin so the next DM re-runs the router.
130
-
131
- For long agent turns where you're drafting >10s, refresh the typing indicator between work steps:
132
-
133
- ```bash
134
- greprag discord typing
135
- ```
107
+ ## Step 4b Emergency-routed DMs (self-identify first)
136
108
 
137
- Silent on success. Tells Discord to show "GrepRAG is typing…" for another 10s.
109
+ `references.discord.emergency_route` non-null = this session was auto-linked because no handoff pin was active; the user may have meant another session. **Open the first reply by self-identifying** so they can redirect:
138
110
 
139
- If the reply exceeds 2000 chars (Discord's hard limit), split on paragraph boundaries and send multiple messages with continuation markers.
111
+ > Picked this up in `<project>` / session `<8-hex>` (no handoff was active, so I'm the most-recently-active armed session). Auto-pin is 10 min; it extends to 30 once you reply again. Redirect? `/commander handoff` from another session, or say "redirect to <project>".
140
112
 
141
- ## Step 5 Status (when paired with no handoff intent, or to inspect)
113
+ How the bot chose: among armed session-scoped watchers under the tenant, it picks the **most-recently-active** (ranked by `max(last turn end, arm time)`), so an idle just-finished session outranks one grinding a long task. The 10-min leash self-heals a wrong-session DM fast; the next DM (pin now live) extends to ≥30 min. If the picked session is dead or nothing is armed, the bot files the message (delivered when a session reconnects) and clears the dead auto-pin so the next DM re-runs the router.
142
114
 
143
- Print verbatim:
115
+ ## Step 5 — Status (paired, no handoff intent, or to inspect)
144
116
 
145
117
  ```
146
118
  Discord pairing:
147
- Tenant: travis (or whatever me.snowflake points to)
119
+ Tenant: <me.snowflake's tenant>
148
120
  Default route: <me.project_name> (project_id <me.project_id>)
149
121
  Snowflake: <me.snowflake>
150
122
  Paired at: <me.paired_at>
151
123
  Last DM in: <me.last_seen_inbound_at or "(never)">
152
124
  ```
153
125
 
154
- If `handoff !== null`:
126
+ If `handoff !== null`, append:
155
127
 
156
128
  ```
157
129
  Active handoff pin:
@@ -164,24 +136,24 @@ Offer: *"Run `greprag discord unhandoff` to clear the pin and revert DMs to defa
164
136
 
165
137
  ## Orchestrator-mode interaction rules
166
138
 
167
- These apply only when handoff was armed with `--orchestrator`. The orchestrator is a *passive observer* of tenant-wide inbox traffic, NOT a forwarder.
139
+ Apply only under `--orchestrator`. The orchestrator is a *passive observer* of tenant-wide traffic, NOT a forwarder.
168
140
 
169
- - **NEVER proactively DM the user.** The tenant-wide stream feeds the agent's context. Events accumulate silently. Do not surface them as DMs unless the user has explicitly asked or you're fulfilling a standing order.
170
- - **NEVER call plain `greprag inbox`.** The list endpoint defaults to `autoMarkRead: true`, which burns the unread state for recipient sessions. Use **`greprag inbox --peek`** (non-mutating) when you need to inspect historical messages, or rely on the Monitor stream's accumulated context.
171
- - **Pull on demand.** When the user DMs "roundup" / "what's been happening?" / "anything on <project>?", reconstruct from your accumulated Monitor events. If your context window has rotated, `greprag inbox --peek --all` (with optional `--project <name>`) is the safe retrieve.
172
- - **Standing orders.** When the user says "ping me when <X> happens" / "let me know when chip <id> reports done," remember the directive in working context. On each Monitor event, scan against directives. On match, DM the user once and drop the directive.
173
- - **Intervention.** When the user directs you to act on another project ("pause paybot's webhook chip"), look up the live session via `greprag inbox watchers`, then send the directive: `greprag send --to <handle>@greprag.com/<session-8-hex> "pause"`. That target session's own watcher fires the directive into its agent's turn.
174
- - **Discovery.** `greprag inbox watchers` lists currently-attached watchers under your tenant — every live session that has a watcher armed. Use it when the user references a project by name and you need the session id to message into.
141
+ - **Never proactively DM the user.** Events feed your context silently; surface them only on explicit ask or a standing order.
142
+ - **Never call plain `greprag inbox`** its list defaults to `autoMarkRead: true`, burning recipient sessions' unread state. Use `greprag inbox --peek` (non-mutating) to inspect history, or rely on the accumulated watcher stream.
143
+ - **Pull on demand.** On "roundup" / "anything on <project>?", reconstruct from accumulated events; if context rotated, `greprag inbox --peek --all [--project <name>]`.
144
+ - **Standing orders.** "Ping me when <X>" hold the directive in working context, scan each event, DM once on match, drop it.
145
+ - **Intervention.** "Pause paybot's webhook chip" find the live session (`greprag inbox watchers`), then `greprag send --to <handle>@greprag.com/<session-8hex> "pause"` that session's own watcher fires it into its turn.
146
+ - **Discovery.** `greprag inbox watchers` lists live armed sessions under the tenant — use it to resolve a project name to a session id.
175
147
 
176
- ## Hard rules (all modes)
148
+ ## Hard rules
177
149
 
178
- - **Don't auto-handoff without user intent.** If the user just asked "am I paired?", show status don't arm a Monitor.
179
- - **Always use the wrapped form** in production. The bare `discord handoff` is for testing only.
180
- - **Send replies via the CLI**, not curl. UTF-8 correctness, parser correctness, and the success-print all break in subtle ways otherwise.
181
- - **Never `npm i -g greprag@latest`** from a session with a running handoff watcher it kills the watcher mid-stream. Either land the upgrade before arming, or stop the Monitor first.
182
- - **Pin scope is one snowflake one session at a time.** A new handoff in another session supersedes the prior one silently. Orchestrator-mode does NOT change this the pin is still one-at-a-time.
150
+ - **Status query status, not arm.** "Am I paired?" shows state; it never writes a pin.
151
+ - **Session handoff writes the pin only** the auto-arm hook owns the watcher. **Orchestrator is the sole arming path** (tenant-wide, wrapped under Monitor).
152
+ - **Replies via the CLI, not curl** UTF-8, parser, and success-print all break otherwise.
153
+ - **Pin scope: one snowflake one session at a time.** A new handoff supersedes the prior one silently; orchestrator mode does not change this.
154
+ - **In orchestrator mode, don't `npm i -g greprag@latest`** while the tenant-wide Monitor watcher runs it kills the watcher mid-stream. Land the upgrade first, or stop the Monitor.
183
155
 
184
156
  ## Related skills
185
157
 
186
- - `/greprag` — broader greprag setup (memory, inbox, corpus). Step 5g there carries the same handoff guidance for sessions invoked through greprag-as-orchestrator.
187
- - The `discord-notify` skill (separate, global) sends one-off Discord webhook pings for attention. Not related to this skill that's a webhook, this is a paired DM bridge.
158
+ - `/greprag` — broader greprag setup (memory, inbox, corpus); carries the same handoff guidance for orchestrator-class sessions.
159
+ - `discord-notify` (separate global skill) sends one-off webhook pings for attention — a webhook, not this paired DM bridge.
@@ -70,13 +70,17 @@ Per-project flag flips (turn off recaps here, etc.): `docs/per-project-flags.md`
70
70
 
71
71
  ## Step 3 — Memory access (when configured)
72
72
 
73
- Two access patterns pick by intent.
73
+ Two access patterns. They have OPPOSITE biases, so pick by intent — don't default to one.
74
74
 
75
- **A. Search by topic** when the operator references a past problem, bug, decision, or topic. Reach for this first.
75
+ - **Recent / open-ended** ("what did we do", "catch me up", "this week", "where did we leave off") → **recap or daily**. Sorted by time; immune to lexical noise.
76
+ - **Specific old bug / decision / topic** → **search**.
77
+ - **Search came back weak or doc-heavy** (low scores, or the hits are skill/chip boilerplate) → do NOT keep rephrasing. A topic-grep can't fix a recency problem. Switch to the chronological pull, or narrow with `--shape daily`. The CLI prints this hint for you when it detects it.
78
+
79
+ **A. Search by topic**
76
80
  ```bash
77
81
  greprag memory search "<query>"
78
82
  ```
79
- Query syntax is `websearch_to_tsquery`: AND is default, `OR` explicit, `"phrases"` quoted, `-negation` drops terms. Prints ranked hits (top 5) with node IDs, shape tag, score, content. Flags: `--shape daily`, `--limit 10`, `--format json`.
83
+ Query syntax is `websearch_to_tsquery`: AND is default, `OR` explicit, `"phrases"` quoted, `-negation` drops terms. Searches genuine session history by default (skill bodies, chip prompts, and continuation summaries are excluded — they used to dominate results). Each hit shows shape/role, age, and a confidence band; weak/doc-heavy results print a coaching hint. Flags: `--shape daily`, `--limit 10`, `--include-docs` (also search documentation turns), `--format json`.
80
84
 
81
85
  **B. Recap by time** — an open-ended "catch me up". Most recent weekly + last seven daily summaries.
82
86
  ```bash
@@ -50,6 +50,12 @@ Substitute `<own-session-id>` from your SessionStart hook output.
50
50
  **Cleanup discipline (HARD RULE):** chip prompts forbid `git clean`, `git reset --hard`, `git worktree remove`, `git checkout <other>`, raw `rm -rf` outside the worktree's tracked files.
51
51
  ````
52
52
 
53
+ ## After spawning — your watcher is already armed
54
+
55
+ The chip reports back to **your** session id via `greprag send`, and the detection-gated `notify` hook keeps a session-scoped watcher live for you on any unarmed turn (`adr/session-id-awareness.md`). The chip's report is filed with `to_session_id = your session`, which that watcher delivers (filter: `to_session_id = session OR NULL`). So you do **not** arm a watcher per spawn — the one auto-armed watcher covers every chip you spawn. The spawn is the whole loop.
56
+
57
+ Safety net: if `greprag inbox watchers` shows none for your 8-hex (auto-arm disabled, or it died and the next turn hasn't re-armed yet), arm one under **Monitor (persistent:true)**: `while true; do greprag inbox watch --session <your-8hex> --json; echo "[restart]" >&2; sleep 1; done`. Never double-arm a session that already has a live watcher.
58
+
53
59
  ## Before composing
54
60
 
55
61
  `greprag lore query --scope chip-startup --project <chip-project> --limit 10 --format markdown`. If non-empty, paste at top of body as `**Project Lore (do not re-discover):**`. Saves 10-20 startup turns.