greprag 5.21.0 → 5.22.1

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.
@@ -1,30 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  // Runs automatically after `npm install -g greprag`.
3
3
  //
4
- // Sync model (v5.10.3):
4
+ // Public UX contract:
5
+ // * `greprag/` — the package's front-door skill. Always installed/refreshed
6
+ // for detected Claude Code and Codex homes so users can type `/greprag`
7
+ // immediately after installing the npm package.
8
+ // * Other bundled skills — refresh-only. If the operator opted in earlier,
9
+ // keep them current; otherwise leave their skill registry alone.
10
+ // * Claude chip-spawn doc — refresh-only, because it is Claude-specific.
5
11
  //
6
- // * `greprag/` the package's front-door skill. ALWAYS installed +
7
- // refreshed. This is the operator's manual for the CLI; without it
8
- // the agent has no idea how to drive `greprag` verbs. Treated as
9
- // part of the package's identity, not an opt-in advisor.
10
- //
11
- // * Every other bundled skill (`discord/`, `content-advisor/`,
12
- // `lore-advisor/`, future advisors) — REFRESH-ONLY. If the operator
13
- // opted in (via `greprag skill install <name>` or by accepting the
14
- // hint in `greprag init`'s closing summary), the dir exists locally
15
- // and we keep it current with the bundle. If they never opted in,
16
- // postinstall leaves it alone. Auto-installing every bundled advisor
17
- // on upgrade was a design defect — extra skills crowd the agent's
18
- // skill registry and should be consensual.
19
- //
20
- // * `chip-spawn.md` doc — refresh-only, same logic as opt-in skills.
21
- //
22
- // Closes the staleness gap where `npm i -g greprag@<new>` updated the
23
- // CLI binary but left `~/.claude/skills/greprag/` files pointing at the
24
- // old version's expected CLI surface. Without this, the agent's mental
25
- // model of the CLI drifts away from what the installed binary actually
26
- // does — see CHANGELOG v5.10.0 (odyssey verb shipped, but local skill
27
- // stayed at 3.7.0 with no odyssey refs).
12
+ // OpenCode has no slash-skill directory in this package; `greprag init
13
+ // --opencode` installs the plugin.
28
14
 
29
15
  const fs = require('fs');
30
16
  const path = require('path');
@@ -32,8 +18,23 @@ const os = require('os');
32
18
 
33
19
  const home = os.homedir();
34
20
  const skillRoot = path.join(__dirname, '..', 'skill');
35
- const skillsTarget = path.join(home, '.claude', 'skills');
36
- const docsTarget = path.join(home, '.claude', 'docs');
21
+
22
+ const platformTargets = [
23
+ {
24
+ name: 'Claude Code',
25
+ agentDir: path.join(home, '.claude'),
26
+ skillsTarget: path.join(home, '.claude', 'skills'),
27
+ docsTarget: path.join(home, '.claude', 'docs'),
28
+ refreshClaudeDocs: true,
29
+ },
30
+ {
31
+ name: 'Codex',
32
+ agentDir: path.join(home, '.codex'),
33
+ skillsTarget: path.join(home, '.codex', 'skills'),
34
+ docsTarget: path.join(home, '.codex', 'docs'),
35
+ refreshClaudeDocs: false,
36
+ },
37
+ ];
37
38
 
38
39
  function copyDir(src, dest) {
39
40
  fs.mkdirSync(dest, { recursive: true });
@@ -45,39 +46,45 @@ function copyDir(src, dest) {
45
46
  }
46
47
  }
47
48
 
48
- // `greprag/` always syncs; everything else is opt-in (refresh-only).
49
49
  const CORE_SKILL = 'greprag';
50
-
51
50
  const refreshed = [];
52
51
  const installed = [];
52
+ const detected = [];
53
53
 
54
54
  try {
55
- if (fs.existsSync(skillRoot) && fs.existsSync(skillsTarget)) {
56
- for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
57
- if (!entry.isDirectory()) continue;
58
- if (entry.name === 'templates') continue; // not a skill dir
59
- const dest = path.join(skillsTarget, entry.name);
60
- const existed = fs.existsSync(dest);
61
- const isCore = entry.name === CORE_SKILL;
62
- // Core skill: always sync. Opt-in skills: only refresh if already installed.
63
- if (!existed && !isCore) continue;
64
- copyDir(path.join(skillRoot, entry.name), dest);
65
- if (existed) refreshed.push('skills/' + entry.name);
66
- else installed.push('skills/' + entry.name);
55
+ if (fs.existsSync(skillRoot)) {
56
+ for (const target of platformTargets) {
57
+ if (!fs.existsSync(target.agentDir)) continue;
58
+ detected.push(target.name);
59
+ for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
60
+ if (!entry.isDirectory()) continue;
61
+ if (entry.name === 'templates') continue;
62
+
63
+ const dest = path.join(target.skillsTarget, entry.name);
64
+ const existed = fs.existsSync(dest);
65
+ const isCore = entry.name === CORE_SKILL;
66
+ if (!isCore && !existed) continue;
67
+
68
+ copyDir(path.join(skillRoot, entry.name), dest);
69
+ const label = `${target.name} skills/${entry.name}`;
70
+ if (existed) refreshed.push(label);
71
+ else installed.push(label);
72
+ }
67
73
  }
68
74
  }
69
75
  } catch (err) {
70
- // Postinstall must never break the install. Log and continue.
71
76
  console.error(' greprag postinstall: skill sync skipped (' + err.message + ')');
72
77
  }
73
78
 
74
- // Refresh chip-spawn template doc if installed.
75
79
  const chipSpawnSrc = path.join(skillRoot, 'templates', 'chip-spawn.md');
76
- const chipSpawnDest = path.join(docsTarget, 'chip-spawn.md');
77
80
  try {
78
- if (fs.existsSync(chipSpawnSrc) && fs.existsSync(chipSpawnDest)) {
79
- fs.copyFileSync(chipSpawnSrc, chipSpawnDest);
80
- refreshed.push('docs/chip-spawn.md');
81
+ for (const target of platformTargets) {
82
+ if (!target.refreshClaudeDocs) continue;
83
+ const chipSpawnDest = path.join(target.docsTarget, 'chip-spawn.md');
84
+ if (fs.existsSync(chipSpawnSrc) && fs.existsSync(chipSpawnDest)) {
85
+ fs.copyFileSync(chipSpawnSrc, chipSpawnDest);
86
+ refreshed.push(`${target.name} docs/chip-spawn.md`);
87
+ }
81
88
  }
82
89
  } catch (err) {
83
90
  console.error(' greprag postinstall: chip-spawn refresh skipped (' + err.message + ')');
@@ -88,5 +95,13 @@ if (changes.length > 0) {
88
95
  const verb = installed.length > 0 ? 'Synced' : 'Refreshed';
89
96
  console.log('\n greprag installed. ' + verb + ': ' + changes.join(', ') + '\n');
90
97
  } else {
91
- console.log('\n greprag installed. Run `greprag init` to connect Claude Code memory.\n');
98
+ console.log('\n greprag installed. Run `greprag init` to connect Claude Code, Codex, or OpenCode memory.\n');
99
+ }
100
+
101
+ if (detected.includes('Codex')) {
102
+ console.log(' Detected Codex. Next:');
103
+ console.log(' greprag init --codex --tenant-id <your-handle> --install-watcher');
104
+ console.log(' Example:');
105
+ console.log(' greprag init --codex --tenant-id tanya --install-watcher');
106
+ console.log(' Then open Codex Desktop Settings -> Settings -> Hooks and trust the 6 GrepRAG hooks.\n');
92
107
  }
@@ -25,7 +25,10 @@ license: MIT
25
25
 
26
26
  Single source of truth: `greprag status --json`. Check it → fix any gaps via `docs/setup.md` → search or recap the project's memory.
27
27
 
28
- OpenCode users: see `docs/setup.md § opencode` for plugin install (`greprag init --opencode`).
28
+ Platform setup is progressive:
29
+ - **Codex**: see `docs/setup.md § codex` after install (`greprag init --codex --tenant-id <handle> --install-watcher`, then Codex Desktop Settings -> Settings -> Hooks trust review).
30
+ - **Claude Code**: see `docs/setup.md § claude-code` for the default hook/Monitor/conventions path (`greprag init --claude` or detected `greprag init`).
31
+ - **OpenCode**: see `docs/setup.md § opencode` for plugin install (`greprag init --opencode`).
29
32
 
30
33
  ## Step 1 — Status
31
34
 
@@ -38,7 +41,16 @@ Parse JSON. Inspect in order:
38
41
  2. `project.anchor_found`
39
42
  3. `project.memory_capture`, `project.session_start_recap`, `project.inbox_notify`
40
43
 
41
- For Claude Code, also: `hooks.session_start_recap`, `hooks.stop_store`, `hooks.post_tool_use_spawn_reminder`.
44
+ For Claude Code, also: `platforms.claude.configured`, `platforms.claude.hooks.session_start_recap`, `platforms.claude.hooks.stop_store`, `platforms.claude.hooks.pre_tool_use_spawn_check`.
45
+
46
+ For OpenCode, also: `platforms.opencode.configured`, `platforms.opencode.plugin_installed`.
47
+
48
+ For Codex, also inspect:
49
+ ```bash
50
+ node -e "const fs=require('fs'),os=require('os'),p=require('path');const hp=p.join(os.homedir(),'.codex','hooks.json');const envp=p.join(os.homedir(),'.greprag','.env');let h={};try{h=JSON.parse(fs.readFileSync(hp,'utf8'))}catch{};const has=(evt,cmd)=>((h.hooks&&h.hooks[evt])||[]).some(e=>(e.hooks||[]).some(x=>(x.command||'').includes(cmd)));console.log(fs.existsSync(envp)?'CODEX_ENV_OK':'CODEX_ENV_MISSING');console.log(has('UserPromptSubmit','codex-notify')?'CODEX_NOTIFY_OK':'CODEX_NOTIFY_MISSING');console.log(has('PostToolUse','codex-inbox')?'CODEX_INBOX_OK':'CODEX_INBOX_MISSING');console.log(has('Stop','codex-store')?'CODEX_STORE_OK':'CODEX_STORE_MISSING');console.log(has('SessionStart','recap')?'CODEX_RECAP_OK':'CODEX_RECAP_MISSING');"
51
+ ```
52
+
53
+ If any Codex check is missing, route to `docs/setup.md § codex`. If hooks exist but memory is not being captured, remind the user to open Codex Desktop Settings -> Settings -> Hooks, trust the 6 GrepRAG commands, and start a fresh session.
42
54
 
43
55
  Chip-spawn convention marker (in `~/.claude/CLAUDE.md`):
44
56
  ```bash
@@ -59,6 +71,7 @@ node -e "const s=JSON.parse(require('fs').readFileSync(require('os').homedir()+'
59
71
  |---|---|
60
72
  | `auth.api_key_present === false` | `docs/setup.md § auth` |
61
73
  | Any hook is false | `docs/setup.md § hooks` |
74
+ | Any Codex check is missing, or hooks installed but not firing | `docs/setup.md § codex` |
62
75
  | Convention marker missing/outdated, or `DOC_MISSING` | `docs/setup.md § conventions` |
63
76
  | `MON_MISSING` | `docs/setup.md § permissions` |
64
77
  | `project.anchor_found === false` | `docs/setup.md § anchor` |
@@ -106,13 +119,18 @@ Aliases (silent back-compat): `greprag memory briefing` → `recap` (renamed v5.
106
119
 
107
120
  ## Proactive-fire rules
108
121
 
109
- **ABOUT TO BACKGROUND A `greprag inbox watch`? USE THE `Monitor` AGENT TOOL, NOT `Bash(run_in_background: true)`.** Bash background notifies only on process completion; watchers run forever, so the agent gets zero events until it manually reads the output file. Wrap with `while true; do greprag inbox watch ...; done` so the loop survives inner crashes. Full pattern: `docs/inbox-watch.md`.
122
+ **Claude Code: ABOUT TO BACKGROUND A `greprag inbox watch`? USE THE `Monitor` AGENT TOOL, NOT `Bash(run_in_background: true)`.** Bash background notifies only on process completion; watchers run forever, so the agent gets zero events until it manually reads the output file. Wrap with `while true; do greprag inbox watch ...; done` so the loop survives inner crashes. Full pattern: `docs/inbox-watch.md`.
123
+
124
+ **Codex: DO NOT CLAIM HOOKS ARE ACTIVE JUST BECAUSE `~/.codex/hooks.json` EXISTS.** Codex Desktop requires Settings -> Settings -> Hooks trust review before command hooks run automatically. If turn capture is missing after `greprag init --codex`, tell the user: open Codex Desktop Settings -> Settings -> Hooks, trust the 6 GrepRAG hooks, then start a fresh Codex session.
125
+
126
+ **Codex live inbox push requires the startup watcher.** Hooks only fire at Codex turn/tool/session boundaries. Public installs should use `greprag init --codex --tenant-id <handle> --install-watcher`; run `greprag codex doctor` to inspect state, and use `greprag codex watch --session <id>` only for foreground testing. The sidecar listens to GrepRAG inbox SSE and wakes Codex via `codex exec resume`. Without it, messages are stored but idle Codex is not woken.
110
127
 
111
128
  **ABOUT TO SEND TO A `@gmail.com` / `@anthropic.com` / REAL EMAIL ADDRESS? STOP — `users.email` IS NEVER A ROUTING ADDRESS.** Use the numeric handle (`1834729@greprag.com`) or claimed vanity alias (`travis@greprag.com`). If you don't know the recipient's handle, ASK — don't guess from their email. adr: adr/numeric-handles.md. Full grammar: `docs/inbox.md § address`.
112
129
 
113
130
  ## Reference index
114
131
 
115
- - `docs/setup.md` — auth · hooks · conventions · permissions · channels · anchor · opencode · bulk-register
132
+ - `docs/setup.md` — codex · claude-code · opencode · auth · hooks · conventions · permissions · channels · anchor · bulk-register
133
+ - `docs/platforms.md` — exact platform paths for Claude Code · Codex · OpenCode
116
134
  - `docs/per-project-flags.md` — flip `memory_capture` / `session_start_recap` / `inbox_notify`
117
135
  - `docs/inbox.md` — `greprag send`, `greprag inbox`, address grammar, retract
118
136
  - `docs/inbox-watch.md` — SSE watcher patterns, liveness model, parent-side / post-send patterns
@@ -11,7 +11,7 @@ What it does:
11
11
  2. Computes what the git-derived UUID *would* be (if in a git repo with commits)
12
12
  3. Queries the API for sibling project_ids under the same profile name (orphans — usually from old anchor files lost to gitignore or a hash-fallback period before init)
13
13
  4. Presents findings and offers actions:
14
- - **Migrate to git-derived UUID** (recommended on drift) — moves current + orphan rows onto the git-derived ID and strips `project_id` from `.claude/project.json` so identity flows from git history going forward
14
+ - **Migrate to git-derived UUID** (recommended on drift) — moves current + orphan rows onto the git-derived ID and strips `project_id` from the project anchor so identity flows from git history going forward
15
15
  - **Consolidate orphans into current UUID** — keeps current identity but pulls orphan rows in
16
16
  - **Dry-run** — runs the recommended action through the API with `dry_run: true` so the user sees exactly what would change before committing
17
17
 
@@ -2,6 +2,87 @@
2
2
 
3
3
  Routes for each gap surfaced by `greprag status --json`. Re-run status after each fix and continue down the list.
4
4
 
5
+ ## codex
6
+
7
+ Codex users install once:
8
+
9
+ ```bash
10
+ greprag init --codex --tenant-id <your-handle> --install-watcher
11
+ ```
12
+
13
+ Example: `greprag init --codex --tenant-id tanya --install-watcher`.
14
+ The handle is public identity: `tanya` becomes `tanya@greprag.com`.
15
+
16
+ This writes `~/.greprag/.env` plus `~/.codex/hooks.json`. The Codex hooks are:
17
+
18
+ - `SessionStart` → `greprag-hook recap`
19
+ - `SessionStart` → `greprag-hook session-id`
20
+ - `UserPromptSubmit` → `greprag-hook codex-notify`
21
+ - `PostToolUse` → `greprag-hook codex-inbox`
22
+ - `Stop` → `greprag-hook codex-store`
23
+ - `PostCompact` → `greprag-hook session-id`
24
+
25
+ It also installs the bundled `/greprag` skill at
26
+ `~/.codex/skills/greprag` and refreshes the shared identity cache at
27
+ `~/.greprag/identity.json`.
28
+
29
+ Interactive init offers to install the live inbox watcher at login. In
30
+ non-interactive contexts, pass `--install-watcher` or run it explicitly:
31
+
32
+ ```bash
33
+ greprag codex startup install
34
+ ```
35
+
36
+ After install, Codex still requires trust review. Tell the user:
37
+
38
+ > Open Codex Desktop Settings -> Settings -> Hooks, trust the 6 GrepRAG hooks,
39
+ > then start a fresh Codex session.
40
+
41
+ Do not try to approve hooks on the user's behalf. This is Codex Desktop's trust
42
+ gate for local command execution.
43
+
44
+ Codex-specific diagnostics:
45
+
46
+ ```bash
47
+ greprag codex doctor
48
+ test -f ~/.greprag/.env && echo CODEX_ENV_OK || echo CODEX_ENV_MISSING
49
+ test -f ~/.codex/hooks.json && echo CODEX_HOOKS_FILE_OK || echo CODEX_HOOKS_FILE_MISSING
50
+ node -e "const fs=require('fs'),os=require('os'),p=require('path');const hp=p.join(os.homedir(),'.codex','hooks.json');let h={};try{h=JSON.parse(fs.readFileSync(hp,'utf8'))}catch{};const has=(evt,cmd)=>((h.hooks&&h.hooks[evt])||[]).some(e=>(e.hooks||[]).some(x=>(x.command||'').includes(cmd)));console.log(has('UserPromptSubmit','codex-notify')?'CODEX_NOTIFY_OK':'CODEX_NOTIFY_MISSING');console.log(has('PostToolUse','codex-inbox')?'CODEX_INBOX_OK':'CODEX_INBOX_MISSING');console.log(has('Stop','codex-store')?'CODEX_STORE_OK':'CODEX_STORE_MISSING');console.log(has('SessionStart','recap')?'CODEX_RECAP_OK':'CODEX_RECAP_MISSING');console.log(has('PostCompact','session-id')?'CODEX_POSTCOMPACT_OK':'CODEX_POSTCOMPACT_MISSING');"
51
+ ```
52
+
53
+ If hook entries are missing, re-run `greprag init --codex`. If entries exist
54
+ but turns are not saving, the most likely cause is untrusted hooks or an old
55
+ session that started before trust. Tell the user to open Codex Desktop Settings
56
+ -> Settings -> Hooks, trust the 6 GrepRAG commands, and restart Codex.
57
+
58
+ Codex live inbox delivery requires the sidecar. Public installs should use the
59
+ startup helper above. For foreground/manual testing:
60
+
61
+ ```bash
62
+ greprag codex watch --session <8hex-or-full-codex-session-id>
63
+ ```
64
+
65
+ If `--session` is omitted, it uses the latest Codex session recorded in
66
+ `~/.codex/session_index.jsonl`. The sidecar stays attached to GrepRAG inbox SSE
67
+ and wakes Codex with `codex exec resume <session> -` whenever a message arrives.
68
+ Use `greprag codex startup status` to inspect the login entry and
69
+ `greprag codex startup remove` to uninstall it.
70
+
71
+ `codex-notify` and `codex-inbox` remain fallback steering. If the sidecar is not
72
+ running, messages are stored and can surface on the next user prompt or after a
73
+ later tool call, but they do not wake idle Codex.
74
+
75
+ ## claude-code
76
+
77
+ Claude Code users install once:
78
+
79
+ ```bash
80
+ greprag init --claude
81
+ ```
82
+
83
+ Bare `greprag init` may auto-detect Claude Code or ask which platform to
84
+ configure. The explicit flag is best for docs and scripts.
85
+
5
86
  ## opencode
6
87
 
7
88
  OpenCode users install once:
@@ -10,7 +91,12 @@ OpenCode users install once:
10
91
  greprag init --opencode
11
92
  ```
12
93
 
13
- Plugin at `~/.config/opencode/plugins/greprag-memory.ts` runs silently — injects recap via system prompt, stores turns automatically. The `/greprag` skill works on-demand in both. Both surfaces share the same anchor (`.claude/project.json` or `.opencode/project.json`) and API key.
94
+ Plugin at `~/.config/opencode/plugins/greprag-memory.js` runs silently:
95
+ injects recap via system prompt and stores turns automatically. It reads
96
+ `~/.greprag/.env` first and falls back to `~/.claude/settings.json` for older
97
+ installs. All platforms share the same anchor (`.greprag/project.json` or
98
+ git-derived identity). Legacy `.claude/project.json` anchors still read and
99
+ migrate on init.
14
100
 
15
101
  ## bulk-register
16
102
 
@@ -45,11 +131,17 @@ Trigger: `auth.api_key_present === false`.
45
131
  ```
46
132
  Response: `{"ok":true,"apiKey":"grp_live_...","userId":"...","tenantId":"..."}`.
47
133
 
48
- 5. Write the key into `~/.claude/settings.json` under `env.GREPRAG_API_KEY`. Also set `env.MEMORY_HOOK_ENABLED = "true"`. Read the file, edit the JSON in-place, write it back.
134
+ 5. Write the key into the platform's config:
135
+ - Claude Code: `~/.claude/settings.json` under `env.GREPRAG_API_KEY`; also set `env.MEMORY_HOOK_ENABLED = "true"`.
136
+ - Codex/OpenCode: `~/.greprag/.env` with `GREPRAG_API_KEY=<key>` and `MEMORY_HOOK_ENABLED=true`.
137
+
138
+ Prefer re-running the platform init command (`greprag init --claude`, `--codex`, or `--opencode`) because it writes the right files and refreshes identity/platform assets.
49
139
 
50
140
  ## hooks
51
141
 
52
- Trigger: any of `hooks.session_start_recap`, `hooks.stop_store`, `hooks.post_tool_use_spawn_reminder` is false.
142
+ Trigger: any of `platforms.claude.hooks.session_start_recap`,
143
+ `platforms.claude.hooks.stop_store`, or
144
+ `platforms.claude.hooks.pre_tool_use_spawn_check` is false.
53
145
 
54
146
  Append missing entries to the existing arrays in `~/.claude/settings.json` (create the arrays if absent). Don't overwrite hooks from other tools.
55
147
 
@@ -69,7 +161,7 @@ Append missing entries to the existing arrays in `~/.claude/settings.json` (crea
69
161
 
70
162
  The legacy `user_prompt_submit_notify` hook was removed in v5.6.1 — live inbox delivery is now the Monitor watcher's job. Do not install it.
71
163
 
72
- **Removing the legacy PostToolUse spawn-reminder hook** (only on upgrade from v0.x → v0.12+): if `hooks.PostToolUse` contains an entry with `matcher: "mcp__ccd_session__spawn_task"` and `command: "greprag-hook spawn-reminder"`, delete it. The behavior is now ambient SessionStart auto-arms the watcher in every greprag-enabled session.
164
+ **Removing the legacy PostToolUse spawn-reminder hook** (only on upgrade from v0.x → v0.12+): if `hooks.PostToolUse` contains an entry with `matcher: "mcp__ccd_session__spawn_task"` and `command: "greprag-hook spawn-reminder"`, delete it. The behavior is now handled by `UserPromptSubmit` watcher steering plus the PreToolUse validator.
73
165
 
74
166
  ## conventions
75
167
 
@@ -184,8 +276,8 @@ If yes, use the existing `project_id` in the new anchor. If no, mint fresh: `nod
184
276
 
185
277
  Write the file:
186
278
  ```bash
187
- mkdir -p <cwd>/.claude
188
- cat > <cwd>/.claude/project.json <<EOF
279
+ mkdir -p <cwd>/.greprag
280
+ cat > <cwd>/.greprag/project.json <<EOF
189
281
  {
190
282
  "project_id": "<UUID>",
191
283
  "project_name": "<basename, lowercased>",
@@ -1,37 +1,56 @@
1
1
  ---
2
2
  name: lore-advisor
3
3
  description: |
4
- Audit project lore for drift, mine episodic memory for emergent learnings,
5
- promote project-agnostic lore to global. One-at-a-time conversational
4
+ Digest the smell queue (raw field signals fix the broken mechanism + instantiate
5
+ durable lore), audit project lore for drift, mine episodic memory for emergent
6
+ learnings, promote project-agnostic lore to global. One-at-a-time conversational
6
7
  review — never bulk-prune.
7
8
 
8
- Trigger phrases: "/lore-advisor", "/lore", "review my lore", "audit lore",
9
- "lore is stale", "prune lore", "mine episodic for lore".
9
+ Trigger phrases: "/lore-advisor", "/lore", "digest smells", "drain the smell queue",
10
+ "any smells", "review my lore", "audit lore", "lore is stale", "prune lore",
11
+ "mine episodic for lore".
10
12
  metadata:
11
13
  author: travsteward
12
- version: "1.0.0"
14
+ version: "1.2.0"
13
15
  repository: https://github.com/travsteward/greprag
14
16
  license: MIT
15
17
  ---
16
18
 
17
19
  # Lore Advisor
18
20
 
19
- Project lore = LEARNINGS (emergent, drift-prone observations seeded via `greprag lore add`). Distinct from static project knowledge (CLAUDE.md, docs, code structure). Lore decays — paths change, conventions die, "learned that X" stops being true. This skill keeps the substrate healthy.
21
+ Project lore = durable LEARNINGS — reusable system properties, constraints, and gotchas worth re-surfacing in a future session. Seeded via `greprag lore add`, mined from episodic memory's `learned` / `ruled_out` claims. Distinct from static project knowledge (CLAUDE.md, docs, code) AND from session narration / transient run-state. Lore decays — paths change, conventions die, "learned that X" stops being true. This skill keeps the substrate healthy.
20
22
 
21
- The skill is **conversational**, not autonomous. Every prune / promote / edit decision goes through the operator one at a time. Bulk actions are forbidden.
23
+ **The advisor's core skill is one judgment: durable lore vs ephemeral noise.** Episodic memory is a firehose — only ~1 claim in 10 is durable lore; the rest is session narration, run-state, or docs material. No regex finds the keepers; the advisor's read does. The durability gate below makes that judgment explicit.
24
+
25
+ Lore is moving from a passive list toward **event-bound / semantic injection** — pushed at the agent when relevant, not browsed (greprag design: `docs/lore-triggers.md`). Until PUSH ships, add lore durable-first so it is injection-ready.
26
+
27
+ The skill is **conversational**, not autonomous. Every prune / promote / edit / add decision goes through the operator one at a time. Bulk actions are forbidden.
28
+
29
+ **Two feeds, one digestion.** Lore now has two supplies: the *deliberate* smell queue (`greprag lore smell`, raw field signals — `docs/lore-smell.md`) and the *automatic* episodic `learned` / `ruled_out` claims. Phase 0 drains the smell queue first; Phase B mines episodic. Both pass through the same durability gate — but a smell additionally asks whether a broken **mechanism** in the harness needs fixing (the fix ladder in Phase 0), not just whether a durable truth needs recording.
22
30
 
23
31
  ## Trigger phrases
24
32
 
25
- `/lore-advisor`, `/lore`, "review my lore", "audit lore", "lore is stale", "prune lore", "mine episodic for lore".
33
+ `/lore-advisor`, `/lore`, "digest smells", "drain the smell queue", "any smells", "review my lore", "audit lore", "lore is stale", "prune lore", "mine episodic for lore".
26
34
 
27
35
  ## What it does
28
36
 
29
- Three phases, run in order. Operator may skip any phase. Each phase walks one entry at a time — never bulk-prune.
37
+ Four phases, run in order (Phase 0 first). Operator may skip any phase. Each phase walks one entry at a time — never bulk-prune.
30
38
 
31
- - **Phase ADrift check.** Heuristic scan of current lore for stale file paths, references to killed conventions, age-based decay.
32
- - **Phase BEpisodic mining.** Scan recent daily summaries for sentences signalling new learnings; offer to promote to lore.
39
+ - **Phase 0Smell digestion.** Drain the raw smell queue (`greprag lore smells`): per smell, fix the broken mechanism (hooks first, code second) and/or instantiate the durable truth, then retire it. Deliberate field signals outrank auto-mined claims, so this runs first.
40
+ - **Phase ADrift check.** Scan current lore for rot: stale file paths, conventions the codebase no longer follows (derived live, NOT from a static list), misfiled static-knowledge, age-based decay.
41
+ - **Phase B — Episodic mining.** Mine episodic memory's `learned` / `ruled_out` claims; surface only candidates that pass the durability gate; offer to add.
33
42
  - **Phase C — Global promotion.** Identify project-agnostic lore that belongs in `~/.claude/docs/chip-spawn.md` as a universal rule.
34
43
 
44
+ ## The durability gate (core discipline)
45
+
46
+ Before lore is kept (Phase A), added (Phase B), or promoted (Phase C), it must pass three tests. Fail ANY → it is not lore.
47
+
48
+ 1. **DURABLE** — a standing property, rule, constraint, or gotcha true beyond one session. NOT point-in-time state. ✗ "no watcher was running", "the id was still e582edf0", "the corpus is proven end-to-end".
49
+ 2. **REUSABLE** — would help a FUTURE session understand the system or avoid a mistake. NOT narration of work done. ✗ "Chip C split corpus.ts into eight modules", "the CLI was bumped to v5.16.0". Abstract instances to rules: "sub 3006's price is archived" → "an inactive Stripe price blocks dunning-recovery".
50
+ 3. **NON-OBVIOUS** — a property you could not read off the surface. ✓ "Postgres caps a statement at 65,535 bind parameters". ✗ "the API runs on Cloudflare Workers" (ambient project knowledge → belongs in docs, not lore).
51
+
52
+ This is the same gate the hourly compactor applies at write-time (greprag, 2026-06-01) — advisor and compactor converge on one filter.
53
+
35
54
  ## Forbidden practices (HARD RULES)
36
55
 
37
56
  - **Never delete lore without explicit operator confirmation.** Each delete is its own y/n.
@@ -41,6 +60,54 @@ Three phases, run in order. Operator may skip any phase. Each phase walks one en
41
60
 
42
61
  ## Phases
43
62
 
63
+ ### Phase 0 — Smell digestion
64
+
65
+ Smells are the *deliberate* lore feed: raw, undigested signals dropped from the field via `greprag lore smell` (`docs/lore-smell.md`). Unlike an episodic claim, a smell often names a **broken mechanism in the harness**, not just a fact. Digestion does two jobs per smell — **fix** the mechanism and **instantiate** the durable truth — and runs FIRST, because a deliberate signal outranks auto-mined claims.
66
+
67
+ 1. Pull the queue:
68
+ ```bash
69
+ greprag lore smells --format json
70
+ ```
71
+ Parse `smells[]` (`nodeId`, `text`, `createdAt`). Empty → say "No smells awaiting digestion." and move to Phase A.
72
+
73
+ 2. Walk one smell at a time. First **untangle** it — a single drop often conflates several mechanisms and truths (e.g. a dead-mailbox bug AND a "re-task ≠ launch a chip" insight). Separate them before triaging.
74
+
75
+ 3. For each, **investigate** (reproduce / read the relevant code or hooks to confirm it's real), then ask the two questions:
76
+
77
+ - **Is there a broken MECHANISM?** Fix it via the **fix ladder** — lowest rung that holds the fix:
78
+ 1. **First pass — fix it in the hooks.** A PreToolUse guard, a UserPromptSubmit injection, a notification. Cheapest, reversible, and the same machinery as lore-triggers — ships as DATA, no deploy. Most mechanism smells resolve here.
79
+ 2. **Second pass — fancier coding.** Only when a hook can't express the fix: server-side logic, a bounce, a schema or CLI change. The advisor does NOT implement code-tier fixes inline — spawn a chip (`spawn_task`, per `~/.claude/docs/chip-spawn.md`) or hand to the operator.
80
+
81
+ If the fix is architectural (a recurring failure class), route through `/root-cause` — fix the pattern that makes the bug possible, never slap a guard on today's trigger.
82
+
83
+ - **Is there a durable TRUTH?** Apply the durability gate (below). If it passes, instantiate as durable lore — and, once triggers ship, attach its injection rule (`docs/lore-triggers.md`):
84
+ ```bash
85
+ greprag lore add "<clean standing rule>" --scope <scope>
86
+ ```
87
+
88
+ 4. **Summarize + confirm before ANY action** — a smell is a brief, and the hard rule is the advisor never auto-acts on a brief. Present:
89
+ ```
90
+ SMELL [<nodeId>] (<date>)
91
+ "<text>"
92
+ Triage:
93
+ • mechanism: <...> → fix: [hook|code|root-cause] <proposal>
94
+ • truth: <...> → lore: "<rule>" scope=<s>
95
+ Action? (f)ix / (i)nstantiate / (b)oth / (s)kip / (n)oise
96
+ ```
97
+ - **fix** → apply the hook-tier fix (after confirm), or spawn the chip for a code-tier fix.
98
+ - **instantiate** → `greprag lore add "<rule>" --scope <scope>` with the durability-gated rule.
99
+ - **both** → fix and instantiate.
100
+ - **skip** → leave the smell in the queue for a later pass (no retire).
101
+ - **noise** → already-handled or not real; retire without action.
102
+
103
+ 5. **Retire** only once the smell's content is preserved — the fix landed, a durable lore entry captures the constraint, OR a chip/TODO owns the code-tier fix. Never retire a smell whose fix is merely *planned*. Retire = delete (interim, until a real `status` field exists):
104
+ ```bash
105
+ greprag lore delete <nodeId>
106
+ ```
107
+ Confirm by printing `Retired smell [<nodeId>].`
108
+
109
+ 6. Phase summary: `Phase 0: N smells. Fixed F (H hooks, C chips). Instantiated I. Skipped S. Noise X.`
110
+
44
111
  ### Phase A — Drift check
45
112
 
46
113
  1. Pull the current project's lore as JSON:
@@ -57,17 +124,13 @@ Three phases, run in order. Operator may skip any phase. Each phase walks one en
57
124
  ```
58
125
  If MISSING → flag with `reason: "file-not-found:<path>"`.
59
126
 
60
- - **Killed-convention check.** Match the lore text against this drift list:
61
- - `Block 3` / `Block-3` (the old 3-block chip protocol)
62
- - `<email>/<project>/<session-id>` or `/<email>/<project>/<session>` (legacy 3-segment address grammar replaced by 1-segment in v0.11)
63
- - `spawn-reminder` (PostToolUse hook removed in v0.12)
64
- - `cp $main_root/.env` or `cp .env` from main into worktree (replaced by inline `set -a; source`)
65
- - `agent-coordination.md` (deep doc deleted in v5.6.0; replaced by `chip-spawn.md`)
66
- - `inline-conventions` / `greprag-conventions:start v1`, `v2`, `v3`, `v4` (superseded by v5 pointer)
67
- - `MEMORY_HOOK_ENABLED` legacy boolean (now ambient)
68
- - `greprag fact` (renamed to `greprag lore` in v5.7.0 — the alias is removed in v5.8.0)
127
+ - **Killed-convention check.** A convention can die — a renamed command, a replaced address grammar, a removed hook. The drift signal is NOT a hardcoded list: a static list of dead conventions rots like any other frozen artifact (it has no provenance and no one maintains it — the exact failure this skill exists to fight). Derive it live instead. Cross-reference the lore text against recent `ruled_out` claims and the project CHANGELOG / releases:
128
+ ```bash
129
+ greprag memory search "<key term> renamed OR removed OR replaced OR deprecated"
130
+ ```
131
+ If the lore asserts a convention the codebase no longer follows flag `reason: "dead-convention"`.
69
132
 
70
- Any match flag with `reason: "dead-convention:<which>"`.
133
+ - **Misfiled-convention check.** Flag lore that is really *static project knowledge* — a build/deploy command, a file-path map, an architecture rule, a coding standard — which belongs in CLAUDE.md / `docs/`, not in decay-prone lore. This is the most common rot: a bulk seed of conventions poured into lore that then drifts out of sync with the docs (it fails the durability gate's REUSABLE/NON-OBVIOUS tests — it is ambient knowledge, usually already documented elsewhere). Flag `reason: "misfiled-convention"`.
71
134
 
72
135
  - **Age check.** If `createdAt` is older than 90 days from today AND no other heuristic fired, flag with `reason: "age-90d:<days>"`. Not automatically stale — just worth re-verifying.
73
136
 
@@ -90,45 +153,38 @@ Three phases, run in order. Operator may skip any phase. Each phase walks one en
90
153
 
91
154
  ### Phase B — Episodic mining
92
155
 
156
+ The richest lore source is the compactor's structured `learned` / `ruled_out` claims (tagged nodes, one per durable learning). As of greprag 2026-06-01 the hourly compactor applies the durability gate at write-time, so these claims are already durable-first — but the advisor's read is still what separates lore-grade from the rest.
157
+
93
158
  1. Resolve the current project's `projectId`:
94
159
  ```bash
95
160
  greprag project-id
96
161
  ```
97
162
 
98
- 2. Pull the last 30 days of daily summaries:
163
+ 2. Pull recent episodic memory (last 30 days). Claims are not yet exposed via a dedicated CLI surface, so mine the daily/hourly summaries — which now carry the compactor's durable claims:
99
164
  ```bash
100
165
  FROM=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
101
166
  TO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
102
167
  curl -sf "https://api.greprag.com/v1/memory/by-period?projectId=<projectId>&type=daily&from=$FROM&to=$TO&limit=30" \
103
168
  -H "Authorization: Bearer $GREPRAG_API_KEY"
104
169
  ```
105
- Parse `rows[]`. Each row has `created_at`, `body`.
106
-
107
- 3. For each daily summary, scan the body for sentences containing any of these marker phrases (case-insensitive):
108
- - `learned that`
109
- - `the gotcha is`
110
- - `won't work because`
111
- - `had to`
112
- - `discovered that`
113
- - `turns out`
114
- - `surprised that`
170
+ Parse `rows[]` (`created_at`, `body`). *(When a `greprag memory claims --tag learned,ruled_out` surface exists, prefer it — it returns the structured claims directly, no prose scan.)*
115
171
 
116
- Extract the full sentence containing the marker (sentence boundary = `. ` / `! ` / `? ` or newline). Skip sentences shorter than 30 chars or longer than 300 chars.
172
+ 3. Find candidate learnings sentences signalling a durable property. Markers: `learned that`, `the gotcha is`, `won't work because`, `turns out`, `requires`, `only if`, `caps at`, `blocks`. Skip < 30 or > 300 chars.
117
173
 
118
- 4. Present candidates one at a time:
174
+ 4. **Apply the durability gate to every candidate.** Most fail — that is correct and expected (~90% of raw learnings are not lore). Surface ONLY candidates that pass all three tests, one at a time:
119
175
  ```
120
- Daily summary from <YYYY-MM-DD> contains:
176
+ Candidate (durable · reusable · non-obvious):
121
177
  "<sentence>"
122
- Promote to lore? (k)eep / (e)dit / (s)kip
178
+ Add to lore? (a)dd / (e)dit / (s)kip
123
179
  ```
124
- - **keep** → ask for the scope (suggest `chip-startup` if the sentence mentions paths/build/test; `general` otherwise; `env` if it mentions env vars / credentials). Then:
180
+ - **add** → ask for scope (`general`; `env` for credentials/vars; or a `<subsystem>-touch` tag naming the moment it would be injected). Then:
125
181
  ```bash
126
182
  greprag lore add "<sentence>" --scope <scope>
127
183
  ```
128
- - **edit** → ask for the polished text, then add with chosen scope.
184
+ - **edit** → polish to a clean standing rule (abstract any instance → rule), then add.
129
185
  - **skip** → no-op.
130
186
 
131
- 5. Phase summary: `Phase B: scanned N daily summaries, found M candidates. Added A to lore. Skipped S.`
187
+ 5. Phase summary: `Phase B: scanned N summaries, M passed the durability gate. Added A. Skipped S.`
132
188
 
133
189
  ### Phase C — Global promotion
134
190
 
@@ -165,19 +221,21 @@ Three phases, run in order. Operator may skip any phase. Each phase walks one en
165
221
  After all three phases, print one combined line:
166
222
 
167
223
  ```
168
- Reviewed N lore. Pruned X. Edited Y. Added Z from episodic. Promoted W to global.
224
+ Digested D smells (fixed F, instantiated I). Reviewed N lore. Pruned X. Edited Y. Added Z from episodic. Promoted W to global.
169
225
  ```
170
226
 
171
227
  If any phase was skipped (operator pressed q / Ctrl-C / typed "skip phase"), include `(phase <X> skipped)` per skipped phase.
172
228
 
173
229
  ## When this skill should be invoked
174
230
 
175
- - Operator types `/lore-advisor` or `/lore`.
231
+ - Operator types `/lore-advisor` or `/lore`, or says "digest smells," "drain the smell queue," "any smells."
232
+ - The `⚠ N smells awaiting digestion` footer fires on a greprag command — the smell-queue nudge. Offer to run Phase 0.
176
233
  - Operator says "audit my lore," "lore is stale," "prune lore," "mine episodic for lore," "any new learnings worth saving?"
177
- - Proactively offer the skill at the end of a `/greprag` briefing if `greprag lore list` shows entries older than 90 days OR if the operator just finished a refactor / rename and lore drift is likely.
234
+ - Proactively offer the skill at the end of a `/greprag` briefing if `greprag lore smells` is non-empty, if `greprag lore list` shows entries older than 90 days, OR if the operator just finished a refactor / rename and lore drift is likely.
178
235
 
179
236
  ## Notes
180
237
 
181
238
  - This skill is operator-driven. Each phase pauses for input. Don't batch decisions — that defeats the purpose.
182
- - For new lore added in Phase B, prefer the original sentence verbatim unless it's awkward. The compactor already polished it.
239
+ - For new lore added in Phase B, prefer the compactor's phrasing but rewrite to a clean standing rule when it embeds an instance ("sub 3006…") or transient framing.
183
240
  - For Phase C promotions, prefer the original phrasing too — but rewrite if the lore embeds a project-specific noun that needs generalizing.
241
+ - **Convergence:** the compactor's write-time durability gate (greprag `docs/episodic-memory-changelog.md`, 2026-06-01) and this skill's gate are the same filter. As the compactor tightens, Phase B's yield rises and this skill shifts from "find lore" toward "wire lore to its injection trigger" (`docs/lore-triggers.md`).