@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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the "age" of a context-tree document for the visible recall summary.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order, falling through on any failure:
|
|
8
|
+
* 1. Frontmatter `updatedAt` — standard topic files (post-curate timestamp)
|
|
9
|
+
* 2. Frontmatter `synthesized_at` — synthesis files produced by `brv dream`
|
|
10
|
+
* 3. Frontmatter `createdAt` — older standard files that lack updatedAt
|
|
11
|
+
* 4. File `mtime` — last resort for files without timestamp frontmatter
|
|
12
|
+
* 5. `undefined` — file missing or path is a cross-project shared source
|
|
13
|
+
*
|
|
14
|
+
* Cross-project paths in the form `[alias]:relative/path.md` are not resolved here —
|
|
15
|
+
* they live in another project's `.brv/context-tree/`, which the plugin does not see.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveContextTreeAge(projectRoot, relativePath) {
|
|
18
|
+
// Shared-source paths point outside this project tree; we have no access to their mtime.
|
|
19
|
+
if (relativePath.startsWith("["))
|
|
20
|
+
return undefined;
|
|
21
|
+
const absolutePath = join(projectRoot, ".brv", "context-tree", relativePath);
|
|
22
|
+
let mtime;
|
|
23
|
+
try {
|
|
24
|
+
mtime = statSync(absolutePath).mtime;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// File missing or unreadable — no age to surface.
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const frontmatterAge = readFrontmatterAge(absolutePath);
|
|
31
|
+
return frontmatterAge ?? mtime;
|
|
32
|
+
}
|
|
33
|
+
const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/;
|
|
34
|
+
const TIMESTAMP_KEYS_IN_PRIORITY = [
|
|
35
|
+
"updatedAt",
|
|
36
|
+
"synthesized_at",
|
|
37
|
+
"createdAt",
|
|
38
|
+
];
|
|
39
|
+
function readFrontmatterAge(absolutePath) {
|
|
40
|
+
let head;
|
|
41
|
+
try {
|
|
42
|
+
// Frontmatter sits at the top of the file; reading the whole file is fine for context-tree
|
|
43
|
+
// documents (typically <50KB) and avoids the complexity of streaming a partial read.
|
|
44
|
+
head = readFileSync(absolutePath, "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const match = head.match(FRONTMATTER_REGEX);
|
|
50
|
+
if (!match)
|
|
51
|
+
return undefined;
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = yaml.load(match[1] ?? "");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
if (!isPlainObject(parsed))
|
|
60
|
+
return undefined;
|
|
61
|
+
for (const key of TIMESTAMP_KEYS_IN_PRIORITY) {
|
|
62
|
+
const candidate = parsed[key];
|
|
63
|
+
const date = coerceDate(candidate);
|
|
64
|
+
if (date)
|
|
65
|
+
return date;
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
function isPlainObject(value) {
|
|
70
|
+
return value !== null && typeof value === "object";
|
|
71
|
+
}
|
|
72
|
+
function coerceDate(value) {
|
|
73
|
+
if (value instanceof Date) {
|
|
74
|
+
return Number.isNaN(value.getTime()) ? undefined : value;
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
const parsed = new Date(value);
|
|
78
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=resolve-context-tree-age.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type BrvState = "dreaming" | "curating" | "idle";
|
|
2
|
+
/**
|
|
3
|
+
* Walk up from `startCwd` looking for a `.brv` directory.
|
|
4
|
+
* Returns the absolute path to `.brv` when found, or undefined when no
|
|
5
|
+
* ancestor contains one. Closest match wins.
|
|
6
|
+
*/
|
|
7
|
+
export declare function findBrvDir(startCwd: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Inspect the daemon's persisted activity and classify the project state.
|
|
10
|
+
* Pure filesystem inspection — no daemon RPC.
|
|
11
|
+
*
|
|
12
|
+
* Two storage locations are checked because the daemon writes them in
|
|
13
|
+
* different baseDirs (project-local vs global):
|
|
14
|
+
* - dream-log + dream.lock → `<brvDir>/dream-log/`, `<brvDir>/dream.lock`
|
|
15
|
+
* - curate-log → `<getProjectDataDir(cwd)>/curate-log/`
|
|
16
|
+
*
|
|
17
|
+
* Precedence (confident signals first, weak signals last):
|
|
18
|
+
* 1. dream-log latest === "processing" → dreaming (confident)
|
|
19
|
+
* 2. curate-log latest === "processing" → curating (confident)
|
|
20
|
+
* 3. dream.lock present, fresh, log isn't "completed" → dreaming (weak fallback)
|
|
21
|
+
* 4. otherwise → idle
|
|
22
|
+
*
|
|
23
|
+
* Confident signals (active log entries) outrank the weak dream.lock fallback
|
|
24
|
+
* so that an active curate isn't masked by a stale dream.lock. Real concurrent
|
|
25
|
+
* dream + curate is preserved (rule 1 wins). Stale-lock false-positives are
|
|
26
|
+
* bounded by `STALE_LOCK_THRESHOLD_MS`.
|
|
27
|
+
*
|
|
28
|
+
* `cwd` is needed to resolve the daemon's per-project storage directory; it
|
|
29
|
+
* defaults to the parent of `brvDir` (the project root).
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectState(brvDir: string, cwd?: string): BrvState;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getProjectDataDir } from "./project-data-dir.js";
|
|
4
|
+
/**
|
|
5
|
+
* Maximum age we will trust the standalone `dream.lock` signal. Beyond this
|
|
6
|
+
* the lock is treated as stale and discarded — the daemon has been observed
|
|
7
|
+
* to leave locks behind on abnormal exit, so a very old lock is more likely
|
|
8
|
+
* to be a leftover than a still-running dream.
|
|
9
|
+
*
|
|
10
|
+
* Set to 15 minutes: comfortably above the longest dream observed in this
|
|
11
|
+
* project's logs (~10 min) plus headroom for slower providers / bigger
|
|
12
|
+
* trees, but short enough that a stuck false-positive self-heals within a
|
|
13
|
+
* reasonable window. Tune later if real dreams exceed this on other projects.
|
|
14
|
+
*/
|
|
15
|
+
const STALE_LOCK_THRESHOLD_MS = 15 * 60 * 1000;
|
|
16
|
+
/**
|
|
17
|
+
* Walk up from `startCwd` looking for a `.brv` directory.
|
|
18
|
+
* Returns the absolute path to `.brv` when found, or undefined when no
|
|
19
|
+
* ancestor contains one. Closest match wins.
|
|
20
|
+
*/
|
|
21
|
+
export function findBrvDir(startCwd) {
|
|
22
|
+
let dir = startCwd;
|
|
23
|
+
while (true) {
|
|
24
|
+
const candidate = join(dir, ".brv");
|
|
25
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
const parent = dirname(dir);
|
|
29
|
+
if (parent === dir)
|
|
30
|
+
return undefined;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Inspect the daemon's persisted activity and classify the project state.
|
|
36
|
+
* Pure filesystem inspection — no daemon RPC.
|
|
37
|
+
*
|
|
38
|
+
* Two storage locations are checked because the daemon writes them in
|
|
39
|
+
* different baseDirs (project-local vs global):
|
|
40
|
+
* - dream-log + dream.lock → `<brvDir>/dream-log/`, `<brvDir>/dream.lock`
|
|
41
|
+
* - curate-log → `<getProjectDataDir(cwd)>/curate-log/`
|
|
42
|
+
*
|
|
43
|
+
* Precedence (confident signals first, weak signals last):
|
|
44
|
+
* 1. dream-log latest === "processing" → dreaming (confident)
|
|
45
|
+
* 2. curate-log latest === "processing" → curating (confident)
|
|
46
|
+
* 3. dream.lock present, fresh, log isn't "completed" → dreaming (weak fallback)
|
|
47
|
+
* 4. otherwise → idle
|
|
48
|
+
*
|
|
49
|
+
* Confident signals (active log entries) outrank the weak dream.lock fallback
|
|
50
|
+
* so that an active curate isn't masked by a stale dream.lock. Real concurrent
|
|
51
|
+
* dream + curate is preserved (rule 1 wins). Stale-lock false-positives are
|
|
52
|
+
* bounded by `STALE_LOCK_THRESHOLD_MS`.
|
|
53
|
+
*
|
|
54
|
+
* `cwd` is needed to resolve the daemon's per-project storage directory; it
|
|
55
|
+
* defaults to the parent of `brvDir` (the project root).
|
|
56
|
+
*/
|
|
57
|
+
export function detectState(brvDir, cwd) {
|
|
58
|
+
const lockPath = join(brvDir, "dream.lock");
|
|
59
|
+
const lockExists = existsSync(lockPath);
|
|
60
|
+
const latestDream = latestLogStatus(join(brvDir, "dream-log"));
|
|
61
|
+
// Rule 1 — confident dream signal
|
|
62
|
+
if (latestDream === "processing")
|
|
63
|
+
return "dreaming";
|
|
64
|
+
// Rule 2 — confident curate signal, beats the weak dream.lock fallback
|
|
65
|
+
const projectCwd = cwd ?? dirname(brvDir);
|
|
66
|
+
let projectDataDir;
|
|
67
|
+
try {
|
|
68
|
+
projectDataDir = getProjectDataDir(projectCwd);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
projectDataDir = undefined;
|
|
72
|
+
}
|
|
73
|
+
if (projectDataDir !== undefined) {
|
|
74
|
+
if (latestLogStatus(join(projectDataDir, "curate-log")) === "processing") {
|
|
75
|
+
return "curating";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Rule 3 — dream.lock fallback. Honored only when:
|
|
79
|
+
// (a) latest dream-log isn't already "completed" (cross-check)
|
|
80
|
+
// (b) lock is fresh (within STALE_LOCK_THRESHOLD_MS)
|
|
81
|
+
if (lockExists && latestDream !== "completed" && lockIsFresh(lockPath)) {
|
|
82
|
+
return "dreaming";
|
|
83
|
+
}
|
|
84
|
+
return "idle";
|
|
85
|
+
}
|
|
86
|
+
function lockIsFresh(lockPath) {
|
|
87
|
+
try {
|
|
88
|
+
const stat = statSync(lockPath);
|
|
89
|
+
return Date.now() - stat.mtimeMs < STALE_LOCK_THRESHOLD_MS;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Returns the `status` field of the lex-most-recent *.json log in `logDir`. */
|
|
96
|
+
function latestLogStatus(logDir) {
|
|
97
|
+
let entries;
|
|
98
|
+
try {
|
|
99
|
+
entries = readdirSync(logDir);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const jsonFiles = entries.filter((e) => e.endsWith(".json")).sort();
|
|
105
|
+
const latest = jsonFiles.at(-1);
|
|
106
|
+
if (latest === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(readFileSync(join(logDir, latest), "utf8"));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
if (typeof parsed === "object" &&
|
|
116
|
+
parsed !== null &&
|
|
117
|
+
"status" in parsed &&
|
|
118
|
+
typeof parsed.status === "string") {
|
|
119
|
+
return parsed.status;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=state-detector.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byterover/claude-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Native bridge between ByteRover context engine and Claude Code — enriches Claude's auto-memory with BM25-ranked knowledge from brv context tree",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"LICENSE"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@byterover/brv-bridge": "^1.0
|
|
26
|
+
"@byterover/brv-bridge": "^1.2.0",
|
|
27
|
+
"@inquirer/prompts": "^7.0.0",
|
|
27
28
|
"commander": "^13.0.0",
|
|
28
29
|
"js-yaml": "^4.1.0",
|
|
29
30
|
"picocolors": "^1.1.0",
|