@byterover/claude-plugin 1.0.0 → 1.1.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/README.md +129 -30
- package/dist/build-recall-output.d.ts +39 -0
- package/dist/build-recall-output.js +59 -0
- package/dist/build-visible-summary.d.ts +17 -0
- package/dist/build-visible-summary.js +40 -0
- package/dist/cli.js +26 -1
- package/dist/commands/doctor.js +39 -2
- package/dist/commands/install-statusline.d.ts +19 -0
- package/dist/commands/install-statusline.js +124 -0
- package/dist/commands/install.js +2 -3
- package/dist/commands/recall.js +8 -13
- package/dist/commands/status.d.ts +11 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/uninstall-statusline.d.ts +8 -0
- package/dist/commands/uninstall-statusline.js +54 -0
- package/dist/commands/uninstall.js +42 -36
- package/dist/format-status-line.d.ts +2 -0
- package/dist/format-status-line.js +21 -0
- package/dist/parse-sources.d.ts +16 -0
- package/dist/parse-sources.js +46 -0
- package/dist/project-data-dir.d.ts +7 -0
- package/dist/project-data-dir.js +82 -0
- package/dist/resolve-context-tree-age.d.ts +14 -0
- package/dist/resolve-context-tree-age.js +82 -0
- package/dist/state-detector.d.ts +31 -0
- package/dist/state-detector.js +123 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# @byterover/claude-
|
|
1
|
+
# @byterover/claude-plugin
|
|
2
2
|
|
|
3
|
-
Native
|
|
3
|
+
Native plugin between [ByteRover](https://www.byterover.dev) and [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Enriches Claude's auto-memory with ByteRover's full multi-tier retrieval — BM25 search, importance scoring, performance correlation, and LLM synthesis — giving Claude persistent, cross-referenced context that improves over time.
|
|
4
4
|
|
|
5
5
|
## Table of contents
|
|
6
6
|
|
|
@@ -15,13 +15,14 @@ Native bridge between [ByteRover](https://www.byterover.dev) and [Claude Code](h
|
|
|
15
15
|
|
|
16
16
|
## What it does
|
|
17
17
|
|
|
18
|
-
Claude Code has a built-in auto-memory system — it saves observations across sessions as flat markdown files. ByteRover has a richer context tree with multi-tier retrieval: BM25 text search, importance/recency scoring, maturity tier boosting, performance-memory correlation, and LLM-powered synthesis. This
|
|
18
|
+
Claude Code has a built-in auto-memory system — it saves observations across sessions as flat markdown files. ByteRover has a richer context tree with multi-tier retrieval: BM25 text search, importance/recency scoring, maturity tier boosting, performance-memory correlation, and LLM-powered synthesis. This plugin connects the two:
|
|
19
19
|
|
|
20
20
|
1. **Ingesting memories** — when Claude's extraction agent writes a memory file, a hook fires and sends the content to `brv curate`, which enriches it with tags, keywords, scoring metadata, and stores it in the context tree
|
|
21
21
|
2. **Syncing context** — after each turn, a hook queries ByteRover for ranked knowledge and writes a cross-reference file that Claude's recall system can pick up
|
|
22
|
-
3. **
|
|
22
|
+
3. **Visible activation** — every prompt that retrieves curated knowledge gets a one-line `🧠 ByteRover returns …` summary in the chat showing which memories were used. An opt-in status line at the bottom of Claude Code surfaces daemon activity (idle / curating / dreaming) so you can see when the context tree is being updated in the background
|
|
23
|
+
4. **Zero workflow change** — you use `claude` exactly as before. The plugin runs in the background via Claude Code's hook system
|
|
23
24
|
|
|
24
|
-
The result: Claude's memories are indexed, scored, and searchable alongside the rest of your project knowledge in ByteRover.
|
|
25
|
+
The result: Claude's memories are indexed, scored, and searchable alongside the rest of your project knowledge in ByteRover — and you can see the system working as you go.
|
|
25
26
|
|
|
26
27
|
## Prerequisites
|
|
27
28
|
|
|
@@ -31,10 +32,10 @@ The result: Claude's memories are indexed, scored, and searchable alongside the
|
|
|
31
32
|
|
|
32
33
|
## Quick start
|
|
33
34
|
|
|
34
|
-
### 1. Install the
|
|
35
|
+
### 1. Install the plugin
|
|
35
36
|
|
|
36
37
|
```bash
|
|
37
|
-
npm install -g @byterover/claude-
|
|
38
|
+
npm install -g @byterover/claude-plugin
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
### 2. Install hooks
|
|
@@ -52,7 +53,27 @@ This adds four hooks to `~/.claude/settings.json`:
|
|
|
52
53
|
|
|
53
54
|
Your existing settings and hooks are preserved. A backup is saved to `settings.json.brv-backup`.
|
|
54
55
|
|
|
55
|
-
### 3.
|
|
56
|
+
### 3. Install the status line (optional)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
brv-claude-plugin install-statusline
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Adds a `statusLine` entry to `~/.claude/settings.json` that renders one of three states at the bottom of Claude Code:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
🧠 ByteRover · idle (dim gray — no daemon work in progress)
|
|
66
|
+
🧠 ByteRover · 📝 curating (yellow — a curate task is running)
|
|
67
|
+
🧠 ByteRover · 💭 dreaming (cyan — a dream consolidation cycle is running)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The line is hidden when you're not in a brv-initialized project. Refreshes every 5 seconds in addition to Claude Code's event-driven triggers, so it picks up daemon state changes that happen while the assistant is mid-tool-call.
|
|
71
|
+
|
|
72
|
+
If you already have a custom `statusLine` configured, the installer prompts you (in a TTY) to keep it, replace it, or abort. Pass `--force` to overwrite without prompting; non-interactive shells fail-fast with a hint to use `--force`.
|
|
73
|
+
|
|
74
|
+
The status line is opt-in and removable via `brv-claude-plugin uninstall-statusline` (leaves foreign user statuslines alone) or as part of the full `brv-claude-plugin uninstall`.
|
|
75
|
+
|
|
76
|
+
### 4. Verify
|
|
56
77
|
|
|
57
78
|
```bash
|
|
58
79
|
brv-claude-plugin doctor
|
|
@@ -63,31 +84,34 @@ You should see all checks passing:
|
|
|
63
84
|
```
|
|
64
85
|
brv-claude-plugin doctor
|
|
65
86
|
|
|
66
|
-
✓ brv CLI — byterover-cli/
|
|
87
|
+
✓ brv CLI — byterover-cli/3.10.3 darwin-arm64 node-v22.21.1
|
|
67
88
|
✓ Context tree — /path/to/project/.brv/context-tree
|
|
68
89
|
✓ Claude settings — ~/.claude/settings.json
|
|
69
90
|
✓ Bridge hooks — PostToolUse + Stop + UserPromptSubmit hooks found
|
|
70
91
|
✓ Bridge executable — /usr/local/bin/brv-claude-plugin
|
|
92
|
+
✓ Status line — registered, points to our binary
|
|
71
93
|
✓ Memory directory — ~/.claude/projects/-path-to-project/memory/
|
|
72
94
|
|
|
73
95
|
All checks passed.
|
|
74
96
|
```
|
|
75
97
|
|
|
76
|
-
|
|
98
|
+
The `Status line` check is informational — it shows ✓ when our status line is registered and ✗ otherwise (with a hint to run `install-statusline`), but a missing status line does not flip the overall outcome since it's opt-in.
|
|
99
|
+
|
|
100
|
+
### 5. Use Claude normally
|
|
77
101
|
|
|
78
102
|
```bash
|
|
79
103
|
claude "fix the auth bug"
|
|
80
104
|
```
|
|
81
105
|
|
|
82
|
-
Memories are ingested into `.brv/context-tree
|
|
106
|
+
Memories are ingested into `.brv/context-tree/` automatically. No changes to your workflow.
|
|
83
107
|
|
|
84
|
-
###
|
|
108
|
+
### 6. Uninstall (if needed)
|
|
85
109
|
|
|
86
110
|
```bash
|
|
87
111
|
brv-claude-plugin uninstall
|
|
88
112
|
```
|
|
89
113
|
|
|
90
|
-
Removes
|
|
114
|
+
Removes plugin hooks **and** our status line entry (if installed) in a single pass. Your other hooks, settings, and any foreign `statusLine` entry are untouched. To remove only the status line and keep the hooks, run `brv-claude-plugin uninstall-statusline` instead.
|
|
91
115
|
|
|
92
116
|
## Commands
|
|
93
117
|
|
|
@@ -104,12 +128,43 @@ Idempotent — safe to run multiple times. Deduplicates by checking for the `#br
|
|
|
104
128
|
|
|
105
129
|
### `brv-claude-plugin uninstall`
|
|
106
130
|
|
|
107
|
-
Removes
|
|
131
|
+
Removes plugin hooks **and** the plugin's status line entry from settings. Per-hook removal — if a matcher entry contains both a plugin hook and your own hook, only the plugin hook is deleted. Foreign user `statusLine` entries are left intact.
|
|
108
132
|
|
|
109
133
|
| Option | Description |
|
|
110
134
|
| ------------------- | ------------------------------------------ |
|
|
111
135
|
| `--settings-path` | Override path to Claude Code settings.json |
|
|
112
136
|
|
|
137
|
+
### `brv-claude-plugin install-statusline`
|
|
138
|
+
|
|
139
|
+
Installs an opt-in `statusLine` entry into `~/.claude/settings.json` that surfaces daemon activity at the bottom of Claude Code. Idempotent. Backs up `settings.json` before writing.
|
|
140
|
+
|
|
141
|
+
If a `statusLine` is already configured:
|
|
142
|
+
|
|
143
|
+
- **Carries our `#brv-claude-plugin` marker:** idempotent — re-runs to upgrade fields if our shape has drifted (e.g., new `refreshInterval` value)
|
|
144
|
+
- **Foreign (someone else's):** prompts on TTY (keep / replace / abort, default abort); fails fast on non-TTY with instructions to use `--force`
|
|
145
|
+
|
|
146
|
+
| Option | Description |
|
|
147
|
+
| ------------------- | ----------------------------------------------------------------- |
|
|
148
|
+
| `--dry-run` | Show what would be written without saving |
|
|
149
|
+
| `--force` | Overwrite an existing foreign statusLine without prompting |
|
|
150
|
+
| `--settings-path` | Override path to Claude Code settings.json |
|
|
151
|
+
|
|
152
|
+
### `brv-claude-plugin uninstall-statusline`
|
|
153
|
+
|
|
154
|
+
Removes only the plugin's `statusLine` entry. Marker-gated — leaves foreign user statuslines alone.
|
|
155
|
+
|
|
156
|
+
| Option | Description |
|
|
157
|
+
| ------------------- | ------------------------------------------ |
|
|
158
|
+
| `--settings-path` | Override path to Claude Code settings.json |
|
|
159
|
+
|
|
160
|
+
### `brv-claude-plugin status`
|
|
161
|
+
|
|
162
|
+
Called by the `statusLine` command in Claude Code's settings. Reads CC's status payload from stdin, walks up from the current directory to find `.brv/`, classifies daemon state via filesystem inspection, and prints one ANSI-colored line.
|
|
163
|
+
|
|
164
|
+
- Outputs nothing (line hidden) when no `.brv/` is reachable
|
|
165
|
+
- Inspects `.brv/dream-log/`, `.brv/dream.lock`, and the daemon's per-project storage directory under the platform's user-data dir for `curate-log/`
|
|
166
|
+
- Always exits 0; runs in well under 100ms in normal cases
|
|
167
|
+
|
|
113
168
|
### `brv-claude-plugin ingest`
|
|
114
169
|
|
|
115
170
|
Called by the PostToolUse hook. Reads hook JSON from stdin, parses the memory file, and sends it to `brv curate --detach`.
|
|
@@ -139,20 +194,26 @@ Called by the Stop hook. Copies the context tree index from `.brv/context-tree/_
|
|
|
139
194
|
|
|
140
195
|
### `brv-claude-plugin recall`
|
|
141
196
|
|
|
142
|
-
Called by the UserPromptSubmit hook. Reads the user's prompt from stdin, queries ByteRover with it, and
|
|
197
|
+
Called by the UserPromptSubmit hook. Reads the user's prompt from stdin, queries ByteRover with it, and emits two things to Claude Code:
|
|
198
|
+
|
|
199
|
+
- **`additionalContext`** — the retrieved context wrapped in `<byterover-context>` tags, injected before the model call so the response can use it
|
|
200
|
+
- **`systemMessage`** — a one-line `🧠 ByteRover returns N memories: <path1> (3d ago), <path2> (1w ago), …` summary visible in the chat above Claude's response (capped at 3 paths, with humanized age suffixes)
|
|
201
|
+
|
|
202
|
+
Behavior:
|
|
143
203
|
|
|
144
204
|
- Runs **synchronously** — delays the model call until ByteRover responds (6s internal timeout, 8s hook timeout)
|
|
145
205
|
- Skips trivially short prompts (< 5 chars)
|
|
206
|
+
- Per-entry score filter (≥0.5) trims low-relevance hits from the visible summary; if no qualifying entries exist, the summary line is suppressed entirely
|
|
207
|
+
- On cache hits or older `brv` versions that don't return structured `matchedDocs`, falls back to regex-parsing the `**Sources**:` block at the end of the response prose
|
|
146
208
|
- If ByteRover query fails or times out, exits silently — prompt proceeds without context
|
|
147
|
-
- Wraps results in `<byterover-context>` tags
|
|
148
209
|
|
|
149
210
|
### `brv-claude-plugin doctor`
|
|
150
211
|
|
|
151
|
-
Runs
|
|
212
|
+
Runs 7 diagnostic checks: brv CLI, context tree, settings file, installed hooks, plugin executable, status line registration, and memory directory resolution. The status line entry is informational — its absence does not flip the overall outcome since it's opt-in.
|
|
152
213
|
|
|
153
214
|
## How it works
|
|
154
215
|
|
|
155
|
-
The
|
|
216
|
+
The plugin uses Claude Code's [hook system](https://docs.anthropic.com/en/docs/claude-code/hooks) to intercept memory operations without modifying Claude Code's source.
|
|
156
217
|
|
|
157
218
|
### Recall flow (UserPromptSubmit hook)
|
|
158
219
|
|
|
@@ -163,10 +224,14 @@ User types: "fix the combo scoring bug"
|
|
|
163
224
|
brv-claude-plugin recall
|
|
164
225
|
│ brv query "fix the combo scoring bug" → targeted results
|
|
165
226
|
▼
|
|
166
|
-
Claude sees <byterover-context> as system reminder
|
|
167
|
-
|
|
227
|
+
• Claude sees <byterover-context> as system reminder
|
|
228
|
+
(live, relevant to this specific prompt)
|
|
229
|
+
• A one-line `🧠 ByteRover returns N memories: <path>(3d ago), …`
|
|
230
|
+
summary is shown in the chat above the response (capped at 3 paths)
|
|
168
231
|
```
|
|
169
232
|
|
|
233
|
+
The visible summary is built from the structured `matchedDocs` returned by `brv query --format json` when available; on cache hits or older `brv` versions the plugin falls back to parsing the `**Sources**:` block at the end of the response prose. Per-entry score filtering (≥0.5) keeps the line concise — if no qualifying entries exist, the line is suppressed entirely.
|
|
234
|
+
|
|
170
235
|
### Ingest flow (PostToolUse hook)
|
|
171
236
|
|
|
172
237
|
```
|
|
@@ -175,12 +240,12 @@ Claude writes ~/.claude/projects/.../memory/feedback_testing.md
|
|
|
175
240
|
▼ PostToolUse hook fires, pipes JSON to stdin
|
|
176
241
|
brv-claude-plugin ingest
|
|
177
242
|
│ Parse cc-ts frontmatter (name, description, type)
|
|
178
|
-
│ Map type → domain (
|
|
243
|
+
│ Map type → domain (/user, /feedback, /project, /reference)
|
|
179
244
|
▼
|
|
180
245
|
brv curate --detach --format json -- "context string"
|
|
181
246
|
│ Daemon queues curation (async, non-blocking)
|
|
182
247
|
▼
|
|
183
|
-
.brv/context-tree/
|
|
248
|
+
.brv/context-tree/feedback/testing.md
|
|
184
249
|
(enriched with tags, keywords, importance scoring)
|
|
185
250
|
```
|
|
186
251
|
|
|
@@ -197,9 +262,33 @@ brv-claude-plugin sync
|
|
|
197
262
|
(context tree index + guide to full tree)
|
|
198
263
|
```
|
|
199
264
|
|
|
265
|
+
### Status line flow (statusLine entry)
|
|
266
|
+
|
|
267
|
+
The status line uses the same `statusLine` mechanism Claude Code documents — it runs the configured command after each assistant message, after `/compact`, on permission/vim-mode changes, AND every 5 seconds (`refreshInterval: 5`). The 5-second tick is what catches daemon state changes during long tool calls when no event-driven trigger fires.
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
Claude Code refresh tick (event or every 5s)
|
|
271
|
+
│
|
|
272
|
+
▼ runs statusLine command (with stdin JSON payload)
|
|
273
|
+
brv-claude-plugin status
|
|
274
|
+
│ Walk up from cwd → find `.brv/`
|
|
275
|
+
│ (none → empty stdout → line hidden)
|
|
276
|
+
│ Inspect filesystem signals:
|
|
277
|
+
│ • <brvDir>/dream-log/*.json (project-local)
|
|
278
|
+
│ • <brvDir>/dream.lock (project-local)
|
|
279
|
+
│ • <getProjectDataDir(cwd)>/curate-log/*.json
|
|
280
|
+
│ (per-project storage under the OS user-data dir —
|
|
281
|
+
│ mirrors the daemon's path resolution)
|
|
282
|
+
▼
|
|
283
|
+
Print one ANSI-colored line:
|
|
284
|
+
🧠 ByteRover · idle / 📝 curating / 💭 dreaming
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
State classification follows a confident-signals-first order: an active `dream-log` entry wins; an active `curate-log` entry wins over the `dream.lock` fallback; the lock alone is honored only if it's fresh (within 15 minutes) and the latest `dream-log` entry isn't already marked `completed`. This combination catches stale locks left behind by interrupted daemons and avoids masking active curate work. A residual edge case remains for daemons that crash mid-dream within the 15-minute window — the proper fix lives daemon-side and is tracked separately.
|
|
288
|
+
|
|
200
289
|
### Memory path resolution
|
|
201
290
|
|
|
202
|
-
The
|
|
291
|
+
The plugin resolves Claude's memory directory using the same logic as Claude Code:
|
|
203
292
|
|
|
204
293
|
1. `CLAUDE_COWORK_MEMORY_PATH_OVERRIDE` env var (full override)
|
|
205
294
|
2. `autoMemoryDirectory` from settings (local → user, excluding project settings for security)
|
|
@@ -209,7 +298,7 @@ Git worktrees are resolved to their canonical root so all worktrees share one me
|
|
|
209
298
|
|
|
210
299
|
### Multi-project support
|
|
211
300
|
|
|
212
|
-
The
|
|
301
|
+
The plugin naturally supports multiple projects. Claude Code scopes memory per git repository, and ByteRover scopes its context tree per `.brv/` directory. Both use the same `cwd` from the hook input as their anchor:
|
|
213
302
|
|
|
214
303
|
```
|
|
215
304
|
/Users/x/project-a/ ← claude session here
|
|
@@ -227,7 +316,7 @@ Each project's memories are ingested into its own context tree and synced back t
|
|
|
227
316
|
|
|
228
317
|
**Important: initialize `.brv/` at the git root.** Claude Code resolves memory directories from the canonical git root. ByteRover walks up from `cwd` to find `.brv/`. When both are at the same level, everything maps correctly.
|
|
229
318
|
|
|
230
|
-
If you initialize `.brv/` in a subdirectory instead of the git root, the
|
|
319
|
+
If you initialize `.brv/` in a subdirectory instead of the git root, the plugin will encounter mismatches:
|
|
231
320
|
|
|
232
321
|
```
|
|
233
322
|
/monorepo/ ← git root (Claude memory lives here)
|
|
@@ -272,10 +361,10 @@ node dist/cli.js doctor
|
|
|
272
361
|
### Testing locally
|
|
273
362
|
|
|
274
363
|
1. Initialize a brv project: `cd /your/project && brv init`
|
|
275
|
-
2. Build the
|
|
364
|
+
2. Build the plugin: `npm run build`
|
|
276
365
|
3. Install hooks (dev mode): `node dist/cli.js install`
|
|
277
366
|
4. Start a Claude session and make a few changes
|
|
278
|
-
5. Check `.brv/context-tree
|
|
367
|
+
5. Check `.brv/context-tree/` for ingested memories
|
|
279
368
|
6. Check `~/.claude/projects/.../memory/_brv_context.md` for synced context
|
|
280
369
|
|
|
281
370
|
## Project structure
|
|
@@ -287,16 +376,26 @@ src/
|
|
|
287
376
|
memory-path.ts # Full cc-ts memory path resolution (git root, worktrees, env vars)
|
|
288
377
|
stdin.ts # Read + validate JSON from stdin
|
|
289
378
|
bridge-command.ts # Executable resolution + #brv-claude-plugin marker
|
|
379
|
+
build-recall-output.ts # Recall orchestrator — combines query result, score filter, fallback
|
|
380
|
+
build-visible-summary.ts # Recall preamble formatter — `🧠 ByteRover returns N memories…` line
|
|
381
|
+
parse-sources.ts # Regex fallback over **Sources**: blocks for cache-hit recovery
|
|
382
|
+
resolve-context-tree-age.ts # Frontmatter-based age resolution (updatedAt → mtime → undefined)
|
|
383
|
+
state-detector.ts # Filesystem state classification for the status line
|
|
384
|
+
format-status-line.ts # ANSI-colored status line formatter (idle / curating / dreaming)
|
|
385
|
+
project-data-dir.ts # Mirrors daemon's getGlobalDataDir + sanitizeProjectPath
|
|
290
386
|
schemas/
|
|
291
387
|
cc-hook-input.ts # Zod schemas for PostToolUse and Stop hook input
|
|
292
388
|
cc-settings.ts # Raw JSON read/write for settings.json (lossless)
|
|
293
389
|
commands/
|
|
294
390
|
install.ts # Install hooks into settings.json (per-hook dedupe, backup)
|
|
295
|
-
uninstall.ts # Remove
|
|
391
|
+
uninstall.ts # Remove plugin hooks AND status line (per-hook removal)
|
|
392
|
+
install-statusline.ts # Opt-in statusLine installer (prompt / --force / upgrade-in-place)
|
|
393
|
+
uninstall-statusline.ts # Marker-gated statusLine removal
|
|
296
394
|
ingest.ts # PostToolUse handler — Write/Edit split, brv curate
|
|
297
395
|
sync.ts # Stop handler — _index.md copy, _brv_context.md, MEMORY.md pointer
|
|
298
|
-
recall.ts # UserPromptSubmit handler — live brv query
|
|
299
|
-
|
|
396
|
+
recall.ts # UserPromptSubmit handler — live brv query, visible summary
|
|
397
|
+
status.ts # statusLine handler — daemon state classification → one-line output
|
|
398
|
+
doctor.ts # 7 diagnostic checks (incl. status line registration)
|
|
300
399
|
```
|
|
301
400
|
|
|
302
401
|
## License
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Resolve a path's age. Injectable so unit tests can avoid touching the filesystem. */
|
|
2
|
+
export type AgeResolver = (cwd: string, relativePath: string) => Date | undefined;
|
|
3
|
+
export interface BuildRecallOutputArgs {
|
|
4
|
+
/** Synthesized prose returned by `brv query`. */
|
|
5
|
+
content: string;
|
|
6
|
+
/**
|
|
7
|
+
* Structured matches surfaced by newer brv CLIs. When undefined we fall back to
|
|
8
|
+
* regex-parsing the `**Sources**:` block in `content` for graceful degradation against
|
|
9
|
+
* older brv CLIs that do not emit this field.
|
|
10
|
+
*/
|
|
11
|
+
matchedDocs?: ReadonlyArray<{
|
|
12
|
+
path: string;
|
|
13
|
+
score?: number;
|
|
14
|
+
}>;
|
|
15
|
+
/** Project root used to resolve frontmatter timestamps and mtime fallbacks. */
|
|
16
|
+
cwd: string;
|
|
17
|
+
/** Override the age resolver (test seam). Defaults to filesystem-backed resolver. */
|
|
18
|
+
getAge?: AgeResolver;
|
|
19
|
+
/** Override the reference clock (test seam). */
|
|
20
|
+
now?: Date;
|
|
21
|
+
}
|
|
22
|
+
export interface RecallHookOutput {
|
|
23
|
+
/**
|
|
24
|
+
* Visible single-line summary rendered by Claude Code beneath the prompt.
|
|
25
|
+
* Omitted when there are no recall paths to surface (cache hits with empty matchedDocs,
|
|
26
|
+
* older CLIs that returned content without a Sources block).
|
|
27
|
+
*/
|
|
28
|
+
systemMessage?: string;
|
|
29
|
+
hookSpecificOutput: {
|
|
30
|
+
hookEventName: "UserPromptSubmit";
|
|
31
|
+
additionalContext: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Assemble the dual-channel UserPromptSubmit hook output (visible systemMessage + invisible
|
|
36
|
+
* additionalContext) from a recall result. Returns undefined when there is nothing to emit
|
|
37
|
+
* (no synthesized content), letting the caller exit silently without writing JSON to stdout.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildRecallOutput(args: BuildRecallOutputArgs): RecallHookOutput | undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { buildVisibleSummary, } from "./build-visible-summary.js";
|
|
2
|
+
import { parseSourcesFromContent } from "./parse-sources.js";
|
|
3
|
+
import { resolveContextTreeAge } from "./resolve-context-tree-age.js";
|
|
4
|
+
const MAX_VISIBLE_PATHS = 3;
|
|
5
|
+
/** Drop individual matches whose score is below this threshold; if nothing qualifies, suppress the visible summary entirely. */
|
|
6
|
+
const MIN_QUALIFYING_SCORE = 0.5;
|
|
7
|
+
/**
|
|
8
|
+
* Assemble the dual-channel UserPromptSubmit hook output (visible systemMessage + invisible
|
|
9
|
+
* additionalContext) from a recall result. Returns undefined when there is nothing to emit
|
|
10
|
+
* (no synthesized content), letting the caller exit silently without writing JSON to stdout.
|
|
11
|
+
*/
|
|
12
|
+
export function buildRecallOutput(args) {
|
|
13
|
+
if (!args.content)
|
|
14
|
+
return undefined;
|
|
15
|
+
const paths = qualifyingPaths(args.matchedDocs, args.content).slice(0, MAX_VISIBLE_PATHS);
|
|
16
|
+
const summary = paths.length > 0 ? renderSummary(paths, args) : "";
|
|
17
|
+
return {
|
|
18
|
+
...(summary ? { systemMessage: summary } : {}),
|
|
19
|
+
hookSpecificOutput: {
|
|
20
|
+
hookEventName: "UserPromptSubmit",
|
|
21
|
+
additionalContext: `<byterover-context>\n` +
|
|
22
|
+
`The following knowledge is from ByteRover context engine:\n\n` +
|
|
23
|
+
`${args.content}\n` +
|
|
24
|
+
`</byterover-context>`,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function qualifyingPaths(matchedDocs, content) {
|
|
29
|
+
// Newer CLI with structured matches: matchedDocs is the source of truth. Drop entries
|
|
30
|
+
// whose score falls below the qualifying threshold.
|
|
31
|
+
//
|
|
32
|
+
// Empty matchedDocs has two meanings depending on CLI behaviour:
|
|
33
|
+
// 1. Cache hits (Tier 0/1) — QueryExecutor intentionally returns [] because the cache
|
|
34
|
+
// stores only the response string, not match metadata. The synthesised content still
|
|
35
|
+
// carries a `**Sources**:` block listing the docs that informed the cached answer.
|
|
36
|
+
// 2. True "no matches found" — no Sources block in content, parse returns [].
|
|
37
|
+
// In both cases we fall through to the Sources-block parser: case 1 recovers the paths,
|
|
38
|
+
// case 2 returns [] and the caller suppresses systemMessage.
|
|
39
|
+
if (matchedDocs !== undefined && matchedDocs.length > 0) {
|
|
40
|
+
return matchedDocs
|
|
41
|
+
.filter((d) => d.score === undefined || d.score >= MIN_QUALIFYING_SCORE)
|
|
42
|
+
.map((d) => d.path);
|
|
43
|
+
}
|
|
44
|
+
// Fallback path used by older CLIs (no matchedDocs at all) AND by cache hits (empty
|
|
45
|
+
// matchedDocs but `**Sources**:` block present in content). Per-entry score is not
|
|
46
|
+
// recoverable here, so paths flow through without per-entry filtering — acceptable
|
|
47
|
+
// because the cached/synthesised response was already deemed answer-worthy upstream.
|
|
48
|
+
return parseSourcesFromContent(content);
|
|
49
|
+
}
|
|
50
|
+
function renderSummary(paths, args) {
|
|
51
|
+
const resolve = args.getAge ?? resolveContextTreeAge;
|
|
52
|
+
const entries = paths.map((path) => {
|
|
53
|
+
const age = resolve(args.cwd, path);
|
|
54
|
+
return age ? { path, age } : { path };
|
|
55
|
+
});
|
|
56
|
+
const summaryOptions = args.now ? { now: args.now } : {};
|
|
57
|
+
return buildVisibleSummary(entries, summaryOptions);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=build-recall-output.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format the visible "ByteRover returns N memories: …" line that the
|
|
3
|
+
* UserPromptSubmit hook emits via `systemMessage`. Pure function so it can
|
|
4
|
+
* be unit-tested without filesystem or clock dependencies — the caller is
|
|
5
|
+
* responsible for resolving each entry's age before calling.
|
|
6
|
+
*/
|
|
7
|
+
export interface VisibleSummaryEntry {
|
|
8
|
+
/** Path as it should be rendered. Caller is responsible for stripping the .brv/context-tree/ prefix. */
|
|
9
|
+
path: string;
|
|
10
|
+
/** Last-modified (or originally-curated) date; omit when no signal is available. */
|
|
11
|
+
age?: Date;
|
|
12
|
+
}
|
|
13
|
+
export interface BuildVisibleSummaryOptions {
|
|
14
|
+
/** Override the reference clock for deterministic age computation in tests. */
|
|
15
|
+
now?: Date;
|
|
16
|
+
}
|
|
17
|
+
export declare function buildVisibleSummary(entries: readonly VisibleSummaryEntry[], options?: BuildVisibleSummaryOptions): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format the visible "ByteRover returns N memories: …" line that the
|
|
3
|
+
* UserPromptSubmit hook emits via `systemMessage`. Pure function so it can
|
|
4
|
+
* be unit-tested without filesystem or clock dependencies — the caller is
|
|
5
|
+
* responsible for resolving each entry's age before calling.
|
|
6
|
+
*/
|
|
7
|
+
const MAX_ENTRIES = 3;
|
|
8
|
+
const MS_PER_DAY = 86_400_000;
|
|
9
|
+
export function buildVisibleSummary(entries, options) {
|
|
10
|
+
if (entries.length === 0)
|
|
11
|
+
return "";
|
|
12
|
+
const shown = entries.slice(0, MAX_ENTRIES);
|
|
13
|
+
const now = options?.now ?? new Date();
|
|
14
|
+
const noun = shown.length === 1 ? "memory" : "memories";
|
|
15
|
+
const items = shown.map((entry) => renderEntry(entry, now)).join(", ");
|
|
16
|
+
return `🧠 ByteRover returns ${shown.length} ${noun}: ${items}`;
|
|
17
|
+
}
|
|
18
|
+
function renderEntry(entry, now) {
|
|
19
|
+
if (!entry.age)
|
|
20
|
+
return entry.path;
|
|
21
|
+
const elapsedMs = now.getTime() - entry.age.getTime();
|
|
22
|
+
// Defensive: future-dated entries (clock skew, bad frontmatter) get rendered without an age tag
|
|
23
|
+
// rather than something nonsensical like "-5d ago".
|
|
24
|
+
if (elapsedMs < 0)
|
|
25
|
+
return entry.path;
|
|
26
|
+
return `${entry.path} (${humanizeAge(elapsedMs)})`;
|
|
27
|
+
}
|
|
28
|
+
function humanizeAge(elapsedMs) {
|
|
29
|
+
const days = Math.floor(elapsedMs / MS_PER_DAY);
|
|
30
|
+
if (days < 1)
|
|
31
|
+
return "today";
|
|
32
|
+
if (days < 7)
|
|
33
|
+
return `${days}d ago`;
|
|
34
|
+
if (days < 30)
|
|
35
|
+
return `${Math.floor(days / 7)}w ago`;
|
|
36
|
+
if (days < 365)
|
|
37
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
38
|
+
return `${Math.floor(days / 365)}y ago`;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=build-visible-summary.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
2
5
|
import { program } from "commander";
|
|
3
6
|
import { registerInstallCommand } from "./commands/install.js";
|
|
7
|
+
import { registerInstallStatuslineCommand } from "./commands/install-statusline.js";
|
|
4
8
|
import { registerUninstallCommand } from "./commands/uninstall.js";
|
|
9
|
+
import { registerUninstallStatuslineCommand } from "./commands/uninstall-statusline.js";
|
|
5
10
|
import { registerIngestCommand } from "./commands/ingest.js";
|
|
6
11
|
import { registerSyncCommand } from "./commands/sync.js";
|
|
7
12
|
import { registerRecallCommand } from "./commands/recall.js";
|
|
13
|
+
import { registerStatusCommand } from "./commands/status.js";
|
|
8
14
|
import { registerDoctorCommand } from "./commands/doctor.js";
|
|
15
|
+
function readPackageVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
18
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
19
|
+
if (typeof pkg === "object" &&
|
|
20
|
+
pkg !== null &&
|
|
21
|
+
"version" in pkg &&
|
|
22
|
+
typeof pkg.version === "string") {
|
|
23
|
+
return pkg.version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Best-effort — return fallback
|
|
28
|
+
}
|
|
29
|
+
return "unknown";
|
|
30
|
+
}
|
|
9
31
|
program
|
|
10
32
|
.name("brv-claude-plugin")
|
|
11
33
|
.description("Native bridge between ByteRover context engine and Claude Code auto-memory")
|
|
12
|
-
.version(
|
|
34
|
+
.version(readPackageVersion());
|
|
13
35
|
registerInstallCommand(program);
|
|
36
|
+
registerInstallStatuslineCommand(program);
|
|
14
37
|
registerUninstallCommand(program);
|
|
38
|
+
registerUninstallStatuslineCommand(program);
|
|
15
39
|
registerIngestCommand(program);
|
|
16
40
|
registerSyncCommand(program);
|
|
17
41
|
registerRecallCommand(program);
|
|
42
|
+
registerStatusCommand(program);
|
|
18
43
|
registerDoctorCommand(program);
|
|
19
44
|
program.parse();
|
|
20
45
|
//# sourceMappingURL=cli.js.map
|
package/dist/commands/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import pc from "picocolors";
|
|
|
5
5
|
import { isBridgeHook, resolveBridgeExecutable } from "../bridge-command.js";
|
|
6
6
|
import { getCcMemoryDir, getClaudeConfigHome } from "../memory-path.js";
|
|
7
7
|
import { readSettingsRaw } from "../schemas/cc-settings.js";
|
|
8
|
+
import { diagnoseStatuslineConflict } from "./install-statusline.js";
|
|
8
9
|
export function registerDoctorCommand(program) {
|
|
9
10
|
program
|
|
10
11
|
.command("doctor")
|
|
@@ -94,7 +95,43 @@ export function registerDoctorCommand(program) {
|
|
|
94
95
|
detail: err instanceof Error ? err.message : String(err),
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
|
-
// 6.
|
|
98
|
+
// 6. Status line registered (opt-in)
|
|
99
|
+
if (settingsValid) {
|
|
100
|
+
const settings = readSettingsRaw(settingsPath);
|
|
101
|
+
const slState = diagnoseStatuslineConflict(settings);
|
|
102
|
+
if (slState === "ours") {
|
|
103
|
+
results.push({
|
|
104
|
+
label: "Status line",
|
|
105
|
+
pass: true,
|
|
106
|
+
detail: "registered, points to our binary",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else if (slState === "absent") {
|
|
110
|
+
results.push({
|
|
111
|
+
label: "Status line",
|
|
112
|
+
pass: false,
|
|
113
|
+
detail: "not installed (run `brv-claude-plugin install-statusline` to enable)",
|
|
114
|
+
optional: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
results.push({
|
|
119
|
+
label: "Status line",
|
|
120
|
+
pass: false,
|
|
121
|
+
detail: "another statusLine is configured (run `brv-claude-plugin install-statusline --force` to overwrite)",
|
|
122
|
+
optional: true,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
results.push({
|
|
128
|
+
label: "Status line",
|
|
129
|
+
pass: false,
|
|
130
|
+
detail: "cannot check — settings invalid",
|
|
131
|
+
optional: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// 7. cc memory dir resolves
|
|
98
135
|
try {
|
|
99
136
|
const memDir = getCcMemoryDir(cwd);
|
|
100
137
|
const memExists = existsSync(memDir);
|
|
@@ -120,7 +157,7 @@ export function registerDoctorCommand(program) {
|
|
|
120
157
|
const icon = r.pass ? pc.green("\u2713") : pc.red("\u2717");
|
|
121
158
|
const detail = r.detail ? pc.dim(` — ${r.detail}`) : "";
|
|
122
159
|
console.log(` ${icon} ${r.label}${detail}`);
|
|
123
|
-
if (!r.pass)
|
|
160
|
+
if (!r.pass && !r.optional)
|
|
124
161
|
allPass = false;
|
|
125
162
|
}
|
|
126
163
|
console.log();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
export type StatuslineConflictState = "absent" | "ours" | "foreign";
|
|
3
|
+
/**
|
|
4
|
+
* Inspect a settings.json object and classify the existing `statusLine` entry:
|
|
5
|
+
* - "absent": no `statusLine` field
|
|
6
|
+
* - "ours": `statusLine.command` carries our bridge marker
|
|
7
|
+
* - "foreign": some other statusLine is configured (or malformed)
|
|
8
|
+
*/
|
|
9
|
+
export declare function diagnoseStatuslineConflict(settings: Record<string, unknown>): StatuslineConflictState;
|
|
10
|
+
/**
|
|
11
|
+
* Set our `statusLine` entry on a settings object. Padding is intentionally
|
|
12
|
+
* omitted so Claude Code's default applies. `refreshInterval` is set so the
|
|
13
|
+
* line reflects daemon-side state changes (curate, dream) that happen while
|
|
14
|
+
* the assistant is mid-tool-call — Claude Code's event triggers don't fire
|
|
15
|
+
* during those windows. Mutates and returns `settings`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function setStatuslineEntry(settings: Record<string, unknown>, command: string): Record<string, unknown>;
|
|
18
|
+
export declare function registerInstallStatuslineCommand(program: Command): void;
|
|
19
|
+
export declare function isPromptCancelled(err: unknown): boolean;
|