brain-cache 0.4.2 → 3.0.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/.claude/skills/brain-cache/SKILL.md +52 -0
- package/README.md +49 -100
- package/dist/{askCodebase-BZIXS3EV.js → askCodebase-EE32B7BP.js} +9 -9
- package/dist/buildContext-GWVDAYH6.js +14 -0
- package/dist/{chunk-Y7BU7IYX.js → chunk-3HQRTLBH.js} +70 -6
- package/dist/{chunk-ZKVZTDND.js → chunk-4IOR54GU.js} +2 -1
- package/dist/chunk-6C2OYMKD.js +16 -0
- package/dist/{workflows-KYCBR7TC.js → chunk-CY34XQ2O.js} +115 -24
- package/dist/chunk-DFFMV3RR.js +171 -0
- package/dist/{chunk-PJQNHMQH.js → chunk-DPH5X5HL.js} +1 -1
- package/dist/{chunk-FQL4HV4R.js → chunk-HRJ3OT6Q.js} +1 -1
- package/dist/chunk-KMRPAVMM.js +967 -0
- package/dist/{chunk-KQZSBRRH.js → chunk-RKPICQU7.js} +1 -1
- package/dist/{chunk-EEC7KYPY.js → chunk-TXLCXXKY.js} +7 -8
- package/dist/claude-md-section-K47HUTE4.js +38 -0
- package/dist/cli.js +13 -9
- package/dist/{doctor-KRNLXE4R.js → doctor-FCET2MNJ.js} +3 -3
- package/dist/{embedder-ZLHAZZUI.js → embedder-HVEXDJAU.js} +2 -2
- package/dist/{init-QNN5H3DR.js → init-2E4JMZZC.js} +71 -6
- package/dist/mcp.js +1450 -130
- package/dist/{search-O4CFAH45.js → search-7ISZ7EXI.js} +16 -15
- package/dist/{status-7MT4IROA.js → status-VKTSG2SN.js} +3 -3
- package/dist/statusline-script-NFUDFOWK.js +95 -0
- package/dist/watch-QPMAB62P.js +128 -0
- package/dist/workflows-MWEY7OAI.js +14 -0
- package/package.json +5 -1
- package/dist/buildContext-APWOPZMJ.js +0 -14
- package/dist/chunk-JZQWPHAQ.js +0 -103
- package/dist/chunk-SBSMKI4B.js +0 -109
- package/dist/chunk-ZGYLHFHJ.js +0 -17
- package/dist/claude-md-section-6ZJ3TMO4.js +0 -34
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
RETRIEVAL_STRATEGIES,
|
|
4
|
-
|
|
4
|
+
classifyRetrievalMode,
|
|
5
5
|
deduplicateChunks,
|
|
6
6
|
searchChunks
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DFFMV3RR.js";
|
|
8
|
+
import {
|
|
9
|
+
openDatabase,
|
|
10
|
+
readIndexState
|
|
11
|
+
} from "./chunk-3HQRTLBH.js";
|
|
8
12
|
import {
|
|
9
13
|
embedBatchWithRetry
|
|
10
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-RKPICQU7.js";
|
|
11
15
|
import {
|
|
12
16
|
isOllamaRunning
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import {
|
|
15
|
-
openDatabase,
|
|
16
|
-
readIndexState
|
|
17
|
-
} from "./chunk-Y7BU7IYX.js";
|
|
17
|
+
} from "./chunk-HRJ3OT6Q.js";
|
|
18
18
|
import {
|
|
19
19
|
readProfile
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-DPH5X5HL.js";
|
|
21
|
+
import "./chunk-TXLCXXKY.js";
|
|
22
22
|
|
|
23
23
|
// src/workflows/search.ts
|
|
24
24
|
import { resolve } from "path";
|
|
@@ -52,18 +52,19 @@ async function runSearch(query, opts) {
|
|
|
52
52
|
`Index is empty at ${rootDir}. No source files were indexed.`
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
|
-
const
|
|
55
|
+
const mode = classifyRetrievalMode(query);
|
|
56
56
|
const strategy = {
|
|
57
|
-
limit: opts?.limit ?? RETRIEVAL_STRATEGIES[
|
|
58
|
-
distanceThreshold: RETRIEVAL_STRATEGIES[
|
|
57
|
+
limit: opts?.limit ?? RETRIEVAL_STRATEGIES[mode].limit,
|
|
58
|
+
distanceThreshold: RETRIEVAL_STRATEGIES[mode].distanceThreshold,
|
|
59
|
+
keywordBoostWeight: RETRIEVAL_STRATEGIES[mode].keywordBoostWeight
|
|
59
60
|
};
|
|
60
61
|
process.stderr.write(
|
|
61
|
-
`brain-cache: searching (
|
|
62
|
+
`brain-cache: searching (mode=${mode}, limit=${strategy.limit})
|
|
62
63
|
`
|
|
63
64
|
);
|
|
64
65
|
const { embeddings: vectors } = await embedBatchWithRetry(indexState.embeddingModel, [query]);
|
|
65
66
|
const queryVector = vectors[0];
|
|
66
|
-
const results = await searchChunks(table, queryVector, strategy);
|
|
67
|
+
const results = await searchChunks(table, queryVector, strategy, query);
|
|
67
68
|
const deduped = deduplicateChunks(results);
|
|
68
69
|
process.stderr.write(
|
|
69
70
|
`brain-cache: found ${deduped.length} chunks (${results.length} before dedup)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readIndexState
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3HQRTLBH.js";
|
|
5
5
|
import {
|
|
6
6
|
readProfile
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-DPH5X5HL.js";
|
|
8
|
+
import "./chunk-TXLCXXKY.js";
|
|
9
9
|
|
|
10
10
|
// src/workflows/status.ts
|
|
11
11
|
import { resolve } from "path";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/statusline-script.ts
|
|
4
|
+
var STATUSLINE_SCRIPT_CONTENT = `#!/usr/bin/env node
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
|
|
9
|
+
const STATS_PATH = join(homedir(), '.brain-cache', 'session-stats.json');
|
|
10
|
+
const STATS_TTL_MS = 2 * 60 * 60 * 1000; // 2 hours \u2014 must match sessionStats.ts STATS_TTL_MS
|
|
11
|
+
export const IDLE_OUTPUT = 'brain-cache idle\\n';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Formats a token count into a human-readable string.
|
|
15
|
+
* - < 1000: plain number (e.g. "500")
|
|
16
|
+
* - >= 1000 and < 1,000,000: rounded k suffix (e.g. "2k")
|
|
17
|
+
* - >= 1,000,000: M suffix with one decimal (e.g. "1.5M")
|
|
18
|
+
*
|
|
19
|
+
* @param {number} n
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export function formatTokenCount(n) {
|
|
23
|
+
if (n >= 1_000_000) return \`\${(n / 1_000_000).toFixed(1)}M\`;
|
|
24
|
+
if (n >= 1_000) return \`\${Math.round(n / 1_000)}k\`;
|
|
25
|
+
return String(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reads and validates stats from a given file path.
|
|
30
|
+
* Returns null if the file does not exist, is malformed, has no lastUpdatedAt,
|
|
31
|
+
* is older than STATS_TTL_MS, or has estimatedWithoutBraincache <= 0.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} filePath
|
|
34
|
+
* @returns {import('../services/sessionStats.js').SessionStats | null}
|
|
35
|
+
*/
|
|
36
|
+
export function _readStatsFromPath(filePath) {
|
|
37
|
+
try {
|
|
38
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
39
|
+
const stats = JSON.parse(raw);
|
|
40
|
+
if (!stats.lastUpdatedAt) return null;
|
|
41
|
+
const age = Date.now() - Date.parse(stats.lastUpdatedAt);
|
|
42
|
+
if (age > STATS_TTL_MS) return null;
|
|
43
|
+
if (!stats.estimatedWithoutBraincache || stats.estimatedWithoutBraincache <= 0) return null;
|
|
44
|
+
return stats;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reads session stats from the default stats path (~/.brain-cache/session-stats.json).
|
|
52
|
+
* Returns null if the file does not exist, is invalid, or is expired.
|
|
53
|
+
*
|
|
54
|
+
* @returns {import('../services/sessionStats.js').SessionStats | null}
|
|
55
|
+
*/
|
|
56
|
+
export function readStats() {
|
|
57
|
+
return _readStatsFromPath(STATS_PATH);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Renders the status line output string.
|
|
62
|
+
* Returns IDLE_OUTPUT when stats is null, savings <= 0, or pct <= 0.
|
|
63
|
+
* Otherwise returns a formatted savings string.
|
|
64
|
+
*
|
|
65
|
+
* @param {import('../services/sessionStats.js').SessionStats | null} stats
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
export function renderOutput(stats) {
|
|
69
|
+
if (!stats) return IDLE_OUTPUT;
|
|
70
|
+
const saved = stats.estimatedWithoutBraincache - stats.tokensSent;
|
|
71
|
+
const pct = Math.round((1 - stats.tokensSent / stats.estimatedWithoutBraincache) * 100);
|
|
72
|
+
if (pct <= 0 || saved <= 0) return IDLE_OUTPUT;
|
|
73
|
+
return \`brain-cache \\u2193\${pct}% \${formatTokenCount(saved)} saved\\n\`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Stdin/stdout protocol \u2014 only when executed directly (not imported for testing)
|
|
77
|
+
if (import.meta.url === \`file://\${process.argv[1]}\`) {
|
|
78
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
79
|
+
let input = '';
|
|
80
|
+
process.stdin.setEncoding('utf8');
|
|
81
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
82
|
+
process.stdin.on('end', () => {
|
|
83
|
+
clearTimeout(stdinTimeout);
|
|
84
|
+
try {
|
|
85
|
+
const stats = readStats();
|
|
86
|
+
process.stdout.write(renderOutput(stats));
|
|
87
|
+
} catch {
|
|
88
|
+
process.stdout.write(IDLE_OUTPUT);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
export {
|
|
94
|
+
STATUSLINE_SCRIPT_CONTENT
|
|
95
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALWAYS_EXCLUDE_GLOBS,
|
|
4
|
+
loadIgnorePatterns,
|
|
5
|
+
runIndex
|
|
6
|
+
} from "./chunk-CY34XQ2O.js";
|
|
7
|
+
import "./chunk-4IOR54GU.js";
|
|
8
|
+
import "./chunk-3HQRTLBH.js";
|
|
9
|
+
import "./chunk-6C2OYMKD.js";
|
|
10
|
+
import "./chunk-RKPICQU7.js";
|
|
11
|
+
import "./chunk-HRJ3OT6Q.js";
|
|
12
|
+
import "./chunk-DPH5X5HL.js";
|
|
13
|
+
import "./chunk-TXLCXXKY.js";
|
|
14
|
+
|
|
15
|
+
// src/workflows/watch.ts
|
|
16
|
+
import { resolve, join } from "path";
|
|
17
|
+
import { open, unlink, mkdir } from "fs/promises";
|
|
18
|
+
|
|
19
|
+
// src/services/fileWatcher.ts
|
|
20
|
+
import { watch } from "chokidar";
|
|
21
|
+
import { relative } from "path";
|
|
22
|
+
import ignore from "ignore";
|
|
23
|
+
async function createWatcher(projectRoot) {
|
|
24
|
+
const userPatterns = await loadIgnorePatterns(projectRoot);
|
|
25
|
+
const ig = ignore();
|
|
26
|
+
ig.add(userPatterns);
|
|
27
|
+
ig.add(ALWAYS_EXCLUDE_GLOBS);
|
|
28
|
+
const ignored = (filePath) => {
|
|
29
|
+
if (filePath.includes("/.brain-cache/") || filePath.endsWith("/.brain-cache")) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const rel = relative(projectRoot, filePath);
|
|
33
|
+
if (!rel || rel.startsWith("..")) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return ig.ignores(rel);
|
|
37
|
+
};
|
|
38
|
+
return watch(projectRoot, {
|
|
39
|
+
persistent: true,
|
|
40
|
+
ignoreInitial: true,
|
|
41
|
+
ignored
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/workflows/watch.ts
|
|
46
|
+
var debounceTimer = null;
|
|
47
|
+
var pendingFiles = /* @__PURE__ */ new Set();
|
|
48
|
+
function resetState() {
|
|
49
|
+
if (debounceTimer !== null) {
|
|
50
|
+
clearTimeout(debounceTimer);
|
|
51
|
+
debounceTimer = null;
|
|
52
|
+
}
|
|
53
|
+
pendingFiles.clear();
|
|
54
|
+
}
|
|
55
|
+
async function acquireIndexLock(projectRoot) {
|
|
56
|
+
const lockPath = join(projectRoot, ".brain-cache", ".indexing");
|
|
57
|
+
await mkdir(join(projectRoot, ".brain-cache"), { recursive: true });
|
|
58
|
+
try {
|
|
59
|
+
const handle = await open(lockPath, "wx");
|
|
60
|
+
await handle.close();
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function releaseIndexLock(projectRoot) {
|
|
67
|
+
await unlink(join(projectRoot, ".brain-cache", ".indexing")).catch(() => void 0);
|
|
68
|
+
}
|
|
69
|
+
function scheduleReindex(filePath, eventType, projectRoot) {
|
|
70
|
+
process.stderr.write(`brain-cache: ${eventType} ${filePath}
|
|
71
|
+
`);
|
|
72
|
+
pendingFiles.add(filePath);
|
|
73
|
+
if (debounceTimer !== null) {
|
|
74
|
+
clearTimeout(debounceTimer);
|
|
75
|
+
}
|
|
76
|
+
debounceTimer = setTimeout(async () => {
|
|
77
|
+
debounceTimer = null;
|
|
78
|
+
pendingFiles.clear();
|
|
79
|
+
const locked = await acquireIndexLock(projectRoot);
|
|
80
|
+
if (!locked) {
|
|
81
|
+
process.stderr.write("brain-cache: skipping reindex \u2014 another index process is running\n");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await runIndex(projectRoot);
|
|
86
|
+
} finally {
|
|
87
|
+
await releaseIndexLock(projectRoot);
|
|
88
|
+
}
|
|
89
|
+
}, 500);
|
|
90
|
+
}
|
|
91
|
+
async function runWatch(targetPath) {
|
|
92
|
+
const rootDir = resolve(targetPath ?? ".");
|
|
93
|
+
process.stderr.write(`brain-cache: watching ${rootDir}
|
|
94
|
+
`);
|
|
95
|
+
const watcher = await createWatcher(rootDir);
|
|
96
|
+
watcher.on("add", (fp) => scheduleReindex(fp, "add", rootDir));
|
|
97
|
+
watcher.on("change", (fp) => scheduleReindex(fp, "change", rootDir));
|
|
98
|
+
watcher.on("unlink", (fp) => scheduleReindex(fp, "unlink", rootDir));
|
|
99
|
+
watcher.on("addDir", (fp) => scheduleReindex(fp, "addDir", rootDir));
|
|
100
|
+
watcher.on("unlinkDir", (fp) => scheduleReindex(fp, "unlinkDir", rootDir));
|
|
101
|
+
watcher.on("error", (err) => {
|
|
102
|
+
process.stderr.write(`brain-cache: watcher error: ${String(err)}
|
|
103
|
+
`);
|
|
104
|
+
});
|
|
105
|
+
watcher.on("ready", () => {
|
|
106
|
+
process.stderr.write("brain-cache: watcher ready\n");
|
|
107
|
+
});
|
|
108
|
+
const cleanup = async () => {
|
|
109
|
+
if (debounceTimer !== null) {
|
|
110
|
+
clearTimeout(debounceTimer);
|
|
111
|
+
debounceTimer = null;
|
|
112
|
+
}
|
|
113
|
+
pendingFiles.clear();
|
|
114
|
+
await watcher.close();
|
|
115
|
+
process.exit(0);
|
|
116
|
+
};
|
|
117
|
+
process.once("SIGINT", cleanup);
|
|
118
|
+
process.once("SIGTERM", cleanup);
|
|
119
|
+
await new Promise(() => {
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
acquireIndexLock,
|
|
124
|
+
releaseIndexLock,
|
|
125
|
+
resetState,
|
|
126
|
+
runWatch,
|
|
127
|
+
scheduleReindex
|
|
128
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runIndex
|
|
4
|
+
} from "./chunk-CY34XQ2O.js";
|
|
5
|
+
import "./chunk-4IOR54GU.js";
|
|
6
|
+
import "./chunk-3HQRTLBH.js";
|
|
7
|
+
import "./chunk-6C2OYMKD.js";
|
|
8
|
+
import "./chunk-RKPICQU7.js";
|
|
9
|
+
import "./chunk-HRJ3OT6Q.js";
|
|
10
|
+
import "./chunk-DPH5X5HL.js";
|
|
11
|
+
import "./chunk-TXLCXXKY.js";
|
|
12
|
+
export {
|
|
13
|
+
runIndex
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brain-cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Local MCP-first context engine for Claude. Index your codebase, retrieve only what matters, and cut token usage.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/",
|
|
12
|
+
".claude/skills/",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -37,7 +38,9 @@
|
|
|
37
38
|
"@lancedb/lancedb": "^0.27.1",
|
|
38
39
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
40
|
"apache-arrow": "^18.1.0",
|
|
41
|
+
"chokidar": "^5.0.0",
|
|
40
42
|
"commander": "14.0.3",
|
|
43
|
+
"dedent": "^1.7.2",
|
|
41
44
|
"fast-glob": "^3.3.3",
|
|
42
45
|
"ignore": "^7.0.5",
|
|
43
46
|
"ollama": "^0.6.3",
|
|
@@ -50,6 +53,7 @@
|
|
|
50
53
|
"zod": "^4.3.6"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
56
|
+
"@types/dedent": "^0.7.2",
|
|
53
57
|
"@types/node": "^22.0.0",
|
|
54
58
|
"pino-pretty": "^11.0.0",
|
|
55
59
|
"tsup": "^8.0.0",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
runBuildContext
|
|
4
|
-
} from "./chunk-JZQWPHAQ.js";
|
|
5
|
-
import "./chunk-ZKVZTDND.js";
|
|
6
|
-
import "./chunk-SBSMKI4B.js";
|
|
7
|
-
import "./chunk-KQZSBRRH.js";
|
|
8
|
-
import "./chunk-FQL4HV4R.js";
|
|
9
|
-
import "./chunk-Y7BU7IYX.js";
|
|
10
|
-
import "./chunk-PJQNHMQH.js";
|
|
11
|
-
import "./chunk-EEC7KYPY.js";
|
|
12
|
-
export {
|
|
13
|
-
runBuildContext
|
|
14
|
-
};
|
package/dist/chunk-JZQWPHAQ.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
assembleContext,
|
|
4
|
-
countChunkTokens
|
|
5
|
-
} from "./chunk-ZKVZTDND.js";
|
|
6
|
-
import {
|
|
7
|
-
RETRIEVAL_STRATEGIES,
|
|
8
|
-
classifyQueryIntent,
|
|
9
|
-
deduplicateChunks,
|
|
10
|
-
searchChunks
|
|
11
|
-
} from "./chunk-SBSMKI4B.js";
|
|
12
|
-
import {
|
|
13
|
-
embedBatchWithRetry
|
|
14
|
-
} from "./chunk-KQZSBRRH.js";
|
|
15
|
-
import {
|
|
16
|
-
isOllamaRunning
|
|
17
|
-
} from "./chunk-FQL4HV4R.js";
|
|
18
|
-
import {
|
|
19
|
-
openDatabase,
|
|
20
|
-
readIndexState
|
|
21
|
-
} from "./chunk-Y7BU7IYX.js";
|
|
22
|
-
import {
|
|
23
|
-
readProfile
|
|
24
|
-
} from "./chunk-PJQNHMQH.js";
|
|
25
|
-
import {
|
|
26
|
-
DEFAULT_TOKEN_BUDGET,
|
|
27
|
-
TOOL_CALL_OVERHEAD_TOKENS
|
|
28
|
-
} from "./chunk-EEC7KYPY.js";
|
|
29
|
-
|
|
30
|
-
// src/workflows/buildContext.ts
|
|
31
|
-
import { readFile } from "fs/promises";
|
|
32
|
-
import { resolve } from "path";
|
|
33
|
-
async function runBuildContext(query, opts) {
|
|
34
|
-
const profile = await readProfile();
|
|
35
|
-
if (profile === null) {
|
|
36
|
-
throw new Error("No profile found. Run 'brain-cache init' first.");
|
|
37
|
-
}
|
|
38
|
-
const running = await isOllamaRunning();
|
|
39
|
-
if (!running) {
|
|
40
|
-
throw new Error("Ollama is not running. Start it with 'ollama serve' or run 'brain-cache init'.");
|
|
41
|
-
}
|
|
42
|
-
const rootDir = resolve(opts?.path ?? ".");
|
|
43
|
-
const indexState = await readIndexState(rootDir);
|
|
44
|
-
if (indexState === null) {
|
|
45
|
-
throw new Error(`No index found at ${rootDir}. Run 'brain-cache index' first.`);
|
|
46
|
-
}
|
|
47
|
-
const db = await openDatabase(rootDir);
|
|
48
|
-
const tableNames = await db.tableNames();
|
|
49
|
-
if (!tableNames.includes("chunks")) {
|
|
50
|
-
throw new Error("No chunks table found. Run 'brain-cache index' first.");
|
|
51
|
-
}
|
|
52
|
-
const table = await db.openTable("chunks");
|
|
53
|
-
const intent = classifyQueryIntent(query);
|
|
54
|
-
const strategy = {
|
|
55
|
-
limit: opts?.limit ?? RETRIEVAL_STRATEGIES[intent].limit,
|
|
56
|
-
distanceThreshold: RETRIEVAL_STRATEGIES[intent].distanceThreshold
|
|
57
|
-
};
|
|
58
|
-
const maxTokens = opts?.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
59
|
-
process.stderr.write(
|
|
60
|
-
`brain-cache: building context (intent=${intent}, budget=${maxTokens} tokens)
|
|
61
|
-
`
|
|
62
|
-
);
|
|
63
|
-
const { embeddings: vectors } = await embedBatchWithRetry(indexState.embeddingModel, [query]);
|
|
64
|
-
const queryVector = vectors[0];
|
|
65
|
-
const results = await searchChunks(table, queryVector, strategy);
|
|
66
|
-
const deduped = deduplicateChunks(results);
|
|
67
|
-
const assembled = assembleContext(deduped, { maxTokens });
|
|
68
|
-
const uniqueFiles = [...new Set(assembled.chunks.map((c) => c.filePath))];
|
|
69
|
-
const numFiles = uniqueFiles.length;
|
|
70
|
-
let fileContentTokens = 0;
|
|
71
|
-
for (const filePath of uniqueFiles) {
|
|
72
|
-
try {
|
|
73
|
-
const fileContent = await readFile(filePath, "utf-8");
|
|
74
|
-
fileContentTokens += countChunkTokens(fileContent);
|
|
75
|
-
} catch {
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const toolCalls = 1 + numFiles;
|
|
79
|
-
const toolCallOverhead = toolCalls * TOOL_CALL_OVERHEAD_TOKENS;
|
|
80
|
-
const estimatedWithoutBraincache = fileContentTokens + toolCallOverhead;
|
|
81
|
-
const reductionPct = estimatedWithoutBraincache > 0 ? Math.max(0, Math.round((1 - assembled.tokenCount / estimatedWithoutBraincache) * 100)) : 0;
|
|
82
|
-
const result = {
|
|
83
|
-
content: assembled.content,
|
|
84
|
-
chunks: assembled.chunks,
|
|
85
|
-
metadata: {
|
|
86
|
-
tokensSent: assembled.tokenCount,
|
|
87
|
-
estimatedWithoutBraincache,
|
|
88
|
-
reductionPct,
|
|
89
|
-
filesInContext: numFiles,
|
|
90
|
-
localTasksPerformed: ["embed_query", "vector_search", "dedup", "token_budget"],
|
|
91
|
-
cloudCallsMade: 0
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
process.stderr.write(
|
|
95
|
-
`brain-cache: context assembled (${assembled.tokenCount} tokens, ${reductionPct}% reduction, ${assembled.chunks.length} chunks)
|
|
96
|
-
`
|
|
97
|
-
);
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export {
|
|
102
|
-
runBuildContext
|
|
103
|
-
};
|
package/dist/chunk-SBSMKI4B.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_DISTANCE_THRESHOLD,
|
|
4
|
-
DEFAULT_SEARCH_LIMIT,
|
|
5
|
-
DIAGNOSTIC_DISTANCE_THRESHOLD,
|
|
6
|
-
DIAGNOSTIC_SEARCH_LIMIT,
|
|
7
|
-
childLogger
|
|
8
|
-
} from "./chunk-EEC7KYPY.js";
|
|
9
|
-
|
|
10
|
-
// src/services/retriever.ts
|
|
11
|
-
var log = childLogger("retriever");
|
|
12
|
-
var DIAGNOSTIC_KEYWORDS = [
|
|
13
|
-
"why",
|
|
14
|
-
"broken",
|
|
15
|
-
"error",
|
|
16
|
-
"bug",
|
|
17
|
-
"fail",
|
|
18
|
-
"crash",
|
|
19
|
-
"exception",
|
|
20
|
-
"undefined",
|
|
21
|
-
"null",
|
|
22
|
-
"wrong",
|
|
23
|
-
"issue",
|
|
24
|
-
"problem",
|
|
25
|
-
"causes",
|
|
26
|
-
"caused",
|
|
27
|
-
"debug",
|
|
28
|
-
"fix",
|
|
29
|
-
"incorrect",
|
|
30
|
-
"unexpected"
|
|
31
|
-
];
|
|
32
|
-
var DIAGNOSTIC_BIGRAMS = [
|
|
33
|
-
"stack trace",
|
|
34
|
-
"null pointer",
|
|
35
|
-
"not defined",
|
|
36
|
-
"type error",
|
|
37
|
-
"reference error",
|
|
38
|
-
"syntax error",
|
|
39
|
-
"runtime error",
|
|
40
|
-
"segmentation fault",
|
|
41
|
-
"not working",
|
|
42
|
-
"throws exception"
|
|
43
|
-
];
|
|
44
|
-
var DIAGNOSTIC_EXCLUSIONS = [
|
|
45
|
-
"error handler",
|
|
46
|
-
"error handling",
|
|
47
|
-
"error boundary",
|
|
48
|
-
"error type",
|
|
49
|
-
"error message",
|
|
50
|
-
"error code",
|
|
51
|
-
"error class",
|
|
52
|
-
"null object",
|
|
53
|
-
"null check",
|
|
54
|
-
"null pattern",
|
|
55
|
-
"undefined behavior",
|
|
56
|
-
"fix the style",
|
|
57
|
-
"fix the format",
|
|
58
|
-
"fix the lint",
|
|
59
|
-
"fix the config",
|
|
60
|
-
"fix the setup"
|
|
61
|
-
];
|
|
62
|
-
function classifyQueryIntent(query) {
|
|
63
|
-
const lower = query.toLowerCase();
|
|
64
|
-
if (DIAGNOSTIC_BIGRAMS.some((bg) => lower.includes(bg))) {
|
|
65
|
-
return "diagnostic";
|
|
66
|
-
}
|
|
67
|
-
const hasKeyword = DIAGNOSTIC_KEYWORDS.some((kw) => lower.includes(kw));
|
|
68
|
-
if (hasKeyword) {
|
|
69
|
-
const isExcluded = DIAGNOSTIC_EXCLUSIONS.some((ex) => lower.includes(ex));
|
|
70
|
-
if (!isExcluded) {
|
|
71
|
-
return "diagnostic";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return "knowledge";
|
|
75
|
-
}
|
|
76
|
-
var RETRIEVAL_STRATEGIES = {
|
|
77
|
-
diagnostic: { limit: DIAGNOSTIC_SEARCH_LIMIT, distanceThreshold: DIAGNOSTIC_DISTANCE_THRESHOLD },
|
|
78
|
-
knowledge: { limit: DEFAULT_SEARCH_LIMIT, distanceThreshold: DEFAULT_DISTANCE_THRESHOLD }
|
|
79
|
-
};
|
|
80
|
-
async function searchChunks(table, queryVector, opts) {
|
|
81
|
-
log.debug({ limit: opts.limit, distanceThreshold: opts.distanceThreshold }, "Searching chunks");
|
|
82
|
-
const rows = await table.query().nearestTo(queryVector).distanceType("cosine").limit(opts.limit).toArray();
|
|
83
|
-
return rows.filter((r) => r._distance <= opts.distanceThreshold).map((r) => ({
|
|
84
|
-
id: r.id,
|
|
85
|
-
filePath: r.file_path,
|
|
86
|
-
chunkType: r.chunk_type,
|
|
87
|
-
scope: r.scope,
|
|
88
|
-
name: r.name,
|
|
89
|
-
content: r.content,
|
|
90
|
-
startLine: r.start_line,
|
|
91
|
-
endLine: r.end_line,
|
|
92
|
-
similarity: 1 - r._distance
|
|
93
|
-
})).sort((a, b) => b.similarity - a.similarity);
|
|
94
|
-
}
|
|
95
|
-
function deduplicateChunks(chunks) {
|
|
96
|
-
const seen = /* @__PURE__ */ new Set();
|
|
97
|
-
return chunks.filter((c) => {
|
|
98
|
-
if (seen.has(c.id)) return false;
|
|
99
|
-
seen.add(c.id);
|
|
100
|
-
return true;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export {
|
|
105
|
-
classifyQueryIntent,
|
|
106
|
-
RETRIEVAL_STRATEGIES,
|
|
107
|
-
searchChunks,
|
|
108
|
-
deduplicateChunks
|
|
109
|
-
};
|
package/dist/chunk-ZGYLHFHJ.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/lib/format.ts
|
|
4
|
-
function formatTokenSavings(input) {
|
|
5
|
-
const PAD = 27;
|
|
6
|
-
const fileSuffix = input.filesInContext !== 1 ? "s" : "";
|
|
7
|
-
const lines = [
|
|
8
|
-
["Tokens sent to Claude:", input.tokensSent.toLocaleString()],
|
|
9
|
-
["Estimated without:", `~${input.estimatedWithout.toLocaleString()} (${input.filesInContext} file${fileSuffix} + overhead)`],
|
|
10
|
-
["Reduction:", `${input.reductionPct}%`]
|
|
11
|
-
];
|
|
12
|
-
return lines.map(([label, value]) => `${label.padEnd(PAD)}${value}`).join("\n");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export {
|
|
16
|
-
formatTokenSavings
|
|
17
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/lib/claude-md-section.ts
|
|
4
|
-
var CLAUDE_MD_SECTION = `
|
|
5
|
-
## Brain-Cache MCP Tools
|
|
6
|
-
|
|
7
|
-
Use brain-cache tools before reading files or using Grep/Glob for codebase questions.
|
|
8
|
-
|
|
9
|
-
### build_context (use for understanding)
|
|
10
|
-
|
|
11
|
-
Call \`mcp__brain-cache__build_context\` with the user's question before reading individual files. It retrieves semantically relevant code from across the repo, deduplicates results, and fits them to a token budget.
|
|
12
|
-
|
|
13
|
-
Use for questions like:
|
|
14
|
-
- "How does X work?" / "Explain X end to end"
|
|
15
|
-
- "Walk me through the flow of X"
|
|
16
|
-
- "What does this page/feature/component do?"
|
|
17
|
-
- "Explain the architecture" / "How is the project structured?"
|
|
18
|
-
- "What happens when Y is called?"
|
|
19
|
-
- Any question that requires understanding code across multiple files
|
|
20
|
-
- Debugging unfamiliar code paths or understanding error flows
|
|
21
|
-
|
|
22
|
-
This returns better results with fewer tokens than reading files individually.
|
|
23
|
-
|
|
24
|
-
### search_codebase (use for locating)
|
|
25
|
-
|
|
26
|
-
Call \`mcp__brain-cache__search_codebase\` to find functions, types, definitions, or implementations by meaning rather than keyword match.
|
|
27
|
-
|
|
28
|
-
### doctor (use for diagnosing)
|
|
29
|
-
|
|
30
|
-
Call \`mcp__brain-cache__doctor\` when any brain-cache tool fails or returns unexpected results.
|
|
31
|
-
`;
|
|
32
|
-
export {
|
|
33
|
-
CLAUDE_MD_SECTION
|
|
34
|
-
};
|