@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 CHANGED
@@ -1,6 +1,6 @@
1
- # @byterover/claude-bridge
1
+ # @byterover/claude-plugin
2
2
 
3
- Native bridge 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.
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 bridge connects the two:
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. **Zero workflow change** — you use `claude` exactly as before. The bridge runs in the background via Claude Code's hook system
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 bridge
35
+ ### 1. Install the plugin
35
36
 
36
37
  ```bash
37
- npm install -g @byterover/claude-bridge
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. Verify
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/2.5.0
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
- ### 4. Use Claude normally
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/_cc/` automatically. No changes to your workflow.
106
+ Memories are ingested into `.brv/context-tree/` automatically. No changes to your workflow.
83
107
 
84
- ### 5. Uninstall (if needed)
108
+ ### 6. Uninstall (if needed)
85
109
 
86
110
  ```bash
87
111
  brv-claude-plugin uninstall
88
112
  ```
89
113
 
90
- Removes only bridge hooks. Your other hooks and settings are untouched.
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 bridge hooks from settings. Per-hook removal — if a matcher entry contains both a bridge hook and your own hook, only the bridge hook is deleted.
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 returns targeted context as `additionalContext` that Claude sees before generating a response.
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 6 diagnostic checks: brv CLI, context tree, settings file, installed hooks, bridge executable, and memory directory resolution.
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 bridge 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.
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
- (live, relevant to this specific prompt)
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 (_cc/user, _cc/feedback, _cc/project, _cc/reference)
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/_cc/feedback/testing.md
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 bridge resolves Claude's memory directory using the same logic as Claude Code:
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 bridge 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:
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 bridge will encounter mismatches:
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 bridge: `npm run build`
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/_cc/` for ingested memories
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 bridge hooks (per-hook removal)
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 with user prompt
299
- doctor.ts # 6 diagnostic checks
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("0.1.0");
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
@@ -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. cc memory dir resolves
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;