brainbank 0.1.3 → 0.1.4
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 +84 -1107
- package/assets/architecture.png +0 -0
- package/bin/brainbank +8 -1
- package/bin/brainbank-mcp +19 -0
- package/dist/chunk-3UIWA32X.js +3341 -0
- package/dist/chunk-3UIWA32X.js.map +1 -0
- package/dist/chunk-3YBCD6DI.js +117 -0
- package/dist/chunk-3YBCD6DI.js.map +1 -0
- package/dist/chunk-DAGVUEXL.js +258 -0
- package/dist/chunk-DAGVUEXL.js.map +1 -0
- package/dist/chunk-DMFMTOHF.js +123 -0
- package/dist/chunk-DMFMTOHF.js.map +1 -0
- package/dist/chunk-FQYKWB2Q.js +136 -0
- package/dist/chunk-FQYKWB2Q.js.map +1 -0
- package/dist/chunk-IMJJ2VEM.js +74 -0
- package/dist/chunk-IMJJ2VEM.js.map +1 -0
- package/dist/chunk-M744PCJQ.js +43 -0
- package/dist/chunk-M744PCJQ.js.map +1 -0
- package/dist/chunk-NNDY7P2R.js +211 -0
- package/dist/chunk-NNDY7P2R.js.map +1 -0
- package/dist/chunk-O3J6ZIXK.js +82 -0
- package/dist/chunk-O3J6ZIXK.js.map +1 -0
- package/dist/chunk-RDQYDLYZ.js +69 -0
- package/dist/chunk-RDQYDLYZ.js.map +1 -0
- package/dist/chunk-WCQVDF3K.js +14 -0
- package/dist/cli.js +2713 -325
- package/dist/cli.js.map +1 -1
- package/dist/haiku-pruner-5KVT5AI2.js +8 -0
- package/dist/http-server-2ZQ6I43B.js +9 -0
- package/dist/index.d.ts +1886 -626
- package/dist/index.js +319 -46
- package/dist/index.js.map +1 -1
- package/dist/local-embedding-NZQTILGV.js +8 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +386 -0
- package/dist/mcp.js.map +1 -0
- package/dist/openai-embedding-ZP5TSUJG.js +8 -0
- package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
- package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
- package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
- package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
- package/dist/plugin-IKQ6IRSJ.js +32 -0
- package/dist/plugin-IKQ6IRSJ.js.map +1 -0
- package/dist/resolve-ASGLBNUC.js +10 -0
- package/dist/resolve-ASGLBNUC.js.map +1 -0
- package/dist/stats-tui-AD3AMYGV.js +1904 -0
- package/dist/stats-tui-AD3AMYGV.js.map +1 -0
- package/package.json +38 -53
- package/src/brainbank.ts +617 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +59 -0
- package/src/cli/commands/daemon.ts +100 -0
- package/src/cli/commands/docs.ts +71 -0
- package/src/cli/commands/files.ts +69 -0
- package/src/cli/commands/help.ts +82 -0
- package/src/cli/commands/index.ts +478 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp-export.ts +273 -0
- package/src/cli/commands/mcp.ts +6 -0
- package/src/cli/commands/query.ts +167 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/reindex.ts +40 -0
- package/src/cli/commands/scan.ts +336 -0
- package/src/cli/commands/search.ts +203 -0
- package/src/cli/commands/stats.ts +68 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +47 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +87 -0
- package/src/cli/factory/config-loader.ts +77 -0
- package/src/cli/factory/index.ts +69 -0
- package/src/cli/factory/plugin-loader.ts +324 -0
- package/src/cli/index.ts +76 -0
- package/src/cli/server-client.ts +186 -0
- package/src/cli/tui/index-tui.tsx +667 -0
- package/src/cli/tui/stats-data.ts +523 -0
- package/src/cli/tui/stats-search.ts +262 -0
- package/src/cli/tui/stats-tui.tsx +1465 -0
- package/src/cli/tui/tree-scanner.ts +650 -0
- package/src/cli/utils.ts +137 -0
- package/src/config.ts +48 -0
- package/src/constants.ts +21 -0
- package/src/db/adapter.ts +112 -0
- package/src/db/metadata.ts +130 -0
- package/src/db/migrations.ts +66 -0
- package/src/db/sqlite-adapter.ts +218 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +81 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +218 -0
- package/src/index.ts +150 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +179 -0
- package/src/lib/logger.ts +126 -0
- package/src/lib/math.ts +87 -0
- package/src/lib/provider-key.ts +20 -0
- package/src/lib/prune.ts +72 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/mcp/mcp-server.ts +268 -0
- package/src/mcp/workspace-factory.ts +68 -0
- package/src/mcp/workspace-pool.ts +224 -0
- package/src/plugin.ts +381 -0
- package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
- package/src/providers/embeddings/embedding-worker.ts +141 -0
- package/src/providers/embeddings/local-embedding.ts +115 -0
- package/src/providers/embeddings/openai-embedding.ts +167 -0
- package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
- package/src/providers/embeddings/perplexity-embedding.ts +165 -0
- package/src/providers/embeddings/resolve.ts +34 -0
- package/src/providers/pruners/haiku-expander.ts +178 -0
- package/src/providers/pruners/haiku-pruner.ts +263 -0
- package/src/providers/vector/hnsw-index.ts +174 -0
- package/src/providers/vector/hnsw-loader.ts +129 -0
- package/src/search/bm25-boost.ts +76 -0
- package/src/search/context-builder.ts +209 -0
- package/src/search/keyword/composite-bm25-search.ts +47 -0
- package/src/search/query-decomposer.ts +124 -0
- package/src/search/types.ts +37 -0
- package/src/search/vector/composite-vector-search.ts +105 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +384 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +344 -0
- package/src/services/kv-service.ts +64 -0
- package/src/services/plugin-registry.ts +77 -0
- package/src/services/watch.ts +340 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +509 -0
- package/dist/chunk-2P3EGY6S.js +0 -37
- package/dist/chunk-2P3EGY6S.js.map +0 -1
- package/dist/chunk-3GAIDXRW.js +0 -105
- package/dist/chunk-3GAIDXRW.js.map +0 -1
- package/dist/chunk-4ZKBQ33J.js +0 -56
- package/dist/chunk-4ZKBQ33J.js.map +0 -1
- package/dist/chunk-7QVYU63E.js +0 -7
- package/dist/chunk-GOUBW7UA.js +0 -373
- package/dist/chunk-GOUBW7UA.js.map +0 -1
- package/dist/chunk-MJ3Y24H6.js +0 -185
- package/dist/chunk-MJ3Y24H6.js.map +0 -1
- package/dist/chunk-N6ZMBFDE.js +0 -224
- package/dist/chunk-N6ZMBFDE.js.map +0 -1
- package/dist/chunk-RAEBYV75.js +0 -709
- package/dist/chunk-RAEBYV75.js.map +0 -1
- package/dist/chunk-TW5NTYYZ.js +0 -2066
- package/dist/chunk-TW5NTYYZ.js.map +0 -1
- package/dist/chunk-Z5SU54HP.js +0 -171
- package/dist/chunk-Z5SU54HP.js.map +0 -1
- package/dist/code.d.ts +0 -31
- package/dist/code.js +0 -8
- package/dist/docs.d.ts +0 -19
- package/dist/docs.js +0 -8
- package/dist/git.d.ts +0 -31
- package/dist/git.js +0 -8
- package/dist/memory.d.ts +0 -19
- package/dist/memory.js +0 -146
- package/dist/memory.js.map +0 -1
- package/dist/notes.d.ts +0 -19
- package/dist/notes.js +0 -57
- package/dist/notes.js.map +0 -1
- package/dist/openai-PCTYLOWI.js +0 -8
- package/dist/types-Da_zLLOl.d.ts +0 -474
- /package/dist/{chunk-7QVYU63E.js.map → chunk-WCQVDF3K.js.map} +0 -0
- /package/dist/{code.js.map → haiku-pruner-5KVT5AI2.js.map} +0 -0
- /package/dist/{docs.js.map → http-server-2ZQ6I43B.js.map} +0 -0
- /package/dist/{git.js.map → local-embedding-NZQTILGV.js.map} +0 -0
- /package/dist/{openai-PCTYLOWI.js.map → openai-embedding-ZP5TSUJG.js.map} +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brainbank mcp:export [target] — Export MCP server config for AI IDEs.
|
|
3
|
+
*
|
|
4
|
+
* Generates the MCP server config block for brainbank and merges it into
|
|
5
|
+
* the target IDE's config file. Currently supports: antigravity.
|
|
6
|
+
*
|
|
7
|
+
* Detects: node path, cli.js path, API keys from config or env vars.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ProjectConfig } from '@/cli/factory/config-loader.ts';
|
|
11
|
+
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { c, args, getFlag } from '@/cli/utils.ts';
|
|
16
|
+
import { getConfig } from '@/cli/factory/index.ts';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
|
|
21
|
+
/** Supported export targets and their config file paths. */
|
|
22
|
+
const TARGETS: Record<string, { configPath: string; label: string }> = {
|
|
23
|
+
antigravity: {
|
|
24
|
+
configPath: path.join(process.env.HOME ?? '~', '.gemini', 'antigravity', 'mcp_config.json'),
|
|
25
|
+
label: 'Gemini Antigravity',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
interface McpServerConfig {
|
|
30
|
+
command: string;
|
|
31
|
+
args: string[];
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface McpConfig {
|
|
37
|
+
mcpServers: Record<string, McpServerConfig>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the brainbank MCP server config block.
|
|
42
|
+
* Resolves node binary, dist/cli.js path, and API keys.
|
|
43
|
+
*/
|
|
44
|
+
function buildBrainbankMcpBlock(config: ProjectConfig | null): McpServerConfig {
|
|
45
|
+
const nodeBin = process.execPath;
|
|
46
|
+
|
|
47
|
+
// Resolve dist/cli.js from the global install location (node_prefix/lib/node_modules/brainbank/dist/cli.js)
|
|
48
|
+
const globalCliJs = path.join(path.dirname(nodeBin), '..', 'lib', 'node_modules', 'brainbank', 'dist', 'cli.js');
|
|
49
|
+
// Fallback: relative to this file (dev / npm link)
|
|
50
|
+
const localCliJs = path.resolve(__dirname, '..', '..', 'dist', 'cli.js');
|
|
51
|
+
const resolvedCliJs = fs.existsSync(globalCliJs) ? globalCliJs : localCliJs;
|
|
52
|
+
|
|
53
|
+
const env: Record<string, string> = {};
|
|
54
|
+
|
|
55
|
+
// Resolve API keys: config.keys > env vars
|
|
56
|
+
const keys = config?.keys;
|
|
57
|
+
const perplexityKey = keys?.perplexity ?? process.env.PERPLEXITY_API_KEY;
|
|
58
|
+
const anthropicKey = keys?.anthropic ?? process.env.ANTHROPIC_API_KEY;
|
|
59
|
+
const openaiKey = keys?.openai ?? process.env.OPENAI_API_KEY;
|
|
60
|
+
|
|
61
|
+
if (perplexityKey) env.PERPLEXITY_API_KEY = perplexityKey;
|
|
62
|
+
if (anthropicKey) env.ANTHROPIC_API_KEY = anthropicKey;
|
|
63
|
+
if (openaiKey) env.OPENAI_API_KEY = openaiKey;
|
|
64
|
+
|
|
65
|
+
const block: McpServerConfig = {
|
|
66
|
+
command: nodeBin,
|
|
67
|
+
args: ['--disable-warning=ExperimentalWarning', resolvedCliJs, 'mcp'],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (Object.keys(env).length > 0) {
|
|
71
|
+
block.env = env;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return block;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load existing MCP config, merge brainbank entry, and write back.
|
|
79
|
+
* Preserves all other server entries.
|
|
80
|
+
*/
|
|
81
|
+
function mergeAndWrite(targetPath: string, block: McpServerConfig): { created: boolean } {
|
|
82
|
+
let existing: McpConfig = { mcpServers: {} };
|
|
83
|
+
const created = !fs.existsSync(targetPath);
|
|
84
|
+
|
|
85
|
+
if (!created) {
|
|
86
|
+
try {
|
|
87
|
+
const raw = fs.readFileSync(targetPath, 'utf-8');
|
|
88
|
+
existing = JSON.parse(raw) as McpConfig;
|
|
89
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
90
|
+
} catch {
|
|
91
|
+
// Corrupt or empty file — start fresh
|
|
92
|
+
existing = { mcpServers: {} };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
existing.mcpServers.brainbank = block;
|
|
97
|
+
|
|
98
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
99
|
+
fs.writeFileSync(targetPath, JSON.stringify(existing, null, 2) + '\n');
|
|
100
|
+
|
|
101
|
+
return { created };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Check if an MCP config already has a brainbank entry. */
|
|
105
|
+
export function hasBrainbankMcpEntry(targetPath: string): boolean {
|
|
106
|
+
if (!fs.existsSync(targetPath)) return false;
|
|
107
|
+
try {
|
|
108
|
+
const raw = fs.readFileSync(targetPath, 'utf-8');
|
|
109
|
+
const config = JSON.parse(raw) as McpConfig;
|
|
110
|
+
return !!config.mcpServers?.brainbank;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Auto-export: called after index when Antigravity is detected. */
|
|
117
|
+
export async function autoExportMcp(repoPath: string): Promise<void> {
|
|
118
|
+
const target = TARGETS.antigravity;
|
|
119
|
+
if (!target) return;
|
|
120
|
+
|
|
121
|
+
// Only auto-export if Antigravity dir exists
|
|
122
|
+
const antigravityDir = path.dirname(target.configPath);
|
|
123
|
+
if (!fs.existsSync(antigravityDir)) return;
|
|
124
|
+
|
|
125
|
+
// Only auto-export if brainbank isn't already configured
|
|
126
|
+
if (hasBrainbankMcpEntry(target.configPath)) return;
|
|
127
|
+
|
|
128
|
+
const config = await getConfig(repoPath);
|
|
129
|
+
const block = buildBrainbankMcpBlock(config);
|
|
130
|
+
mergeAndWrite(target.configPath, block);
|
|
131
|
+
console.log(` ${c.green('✓')} Exported MCP config to ${c.dim(path.relative(process.env.HOME ?? '', target.configPath))}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── GEMINI.md BrainBank Section ──────────────────────────────
|
|
135
|
+
|
|
136
|
+
const BRAINBANK_SECTION_MARKER = '<!-- brainbank:start -->';
|
|
137
|
+
const BRAINBANK_SECTION_END = '<!-- brainbank:end -->';
|
|
138
|
+
|
|
139
|
+
/** Global GEMINI.md path — ~/.gemini/GEMINI.md (shared across all projects). */
|
|
140
|
+
const GLOBAL_GEMINI = path.join(process.env.HOME ?? '~', '.gemini', 'GEMINI.md');
|
|
141
|
+
|
|
142
|
+
function buildGeminiSection(): string {
|
|
143
|
+
return `${BRAINBANK_SECTION_MARKER}
|
|
144
|
+
|
|
145
|
+
## BrainBank — Code Intelligence
|
|
146
|
+
|
|
147
|
+
**\`brainbank_context\`** — semantic code discovery. Use it **only** when you need to understand functionality that spans multiple files (e.g. "how does auth work", "notification pipeline"). \`repo\` is **required**.
|
|
148
|
+
|
|
149
|
+
**Do NOT use it for:** finding a specific string, variable, function name, or exact match — use \`grep_search\` for that. BrainBank is for *understanding context*, not for *locating text*.
|
|
150
|
+
|
|
151
|
+
**Params:** \`compact: false\`, \`sources: { code: 25, docs: 0, git: 0 }\`. Only add \`docs\` when the user explicitly asks about documentation.
|
|
152
|
+
|
|
153
|
+
### Output handling — MANDATORY
|
|
154
|
+
|
|
155
|
+
BrainBank may return results **inline** or save them to an **output file**:
|
|
156
|
+
|
|
157
|
+
- **Inline:** read and proceed normally.
|
|
158
|
+
- **File result** (saved to \`output.txt\`): you **MUST STOP IMMEDIATELY**. Show the path as a clickable link, ask the user to paste, then **DO NOT PROCEED**:
|
|
159
|
+
|
|
160
|
+
📋 Output: [output.txt](file:///path/to/output.txt) — _"Could you paste the context output here?"_
|
|
161
|
+
|
|
162
|
+
**NEVER** read the file yourself. **NEVER** continue working. Wait for the user to paste the content. This is not optional.
|
|
163
|
+
|
|
164
|
+
${BRAINBANK_SECTION_END}
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Check if GEMINI.md already has the brainbank section. */
|
|
169
|
+
function hasGeminiSection(geminiPath: string): boolean {
|
|
170
|
+
if (!fs.existsSync(geminiPath)) return false;
|
|
171
|
+
const content = fs.readFileSync(geminiPath, 'utf-8');
|
|
172
|
+
return content.includes(BRAINBANK_SECTION_MARKER);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Append BrainBank section to GEMINI.md (creates if doesn't exist). */
|
|
176
|
+
function appendGeminiSection(geminiPath: string): void {
|
|
177
|
+
const section = buildGeminiSection();
|
|
178
|
+
if (fs.existsSync(geminiPath)) {
|
|
179
|
+
fs.appendFileSync(geminiPath, section);
|
|
180
|
+
} else {
|
|
181
|
+
fs.writeFileSync(geminiPath, `# GEMINI.md\n${section}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Replace BrainBank section between markers in GEMINI.md. */
|
|
186
|
+
function replaceGeminiSection(geminiPath: string): void {
|
|
187
|
+
const content = fs.readFileSync(geminiPath, 'utf-8');
|
|
188
|
+
const startIdx = content.indexOf(BRAINBANK_SECTION_MARKER);
|
|
189
|
+
const endIdx = content.indexOf(BRAINBANK_SECTION_END);
|
|
190
|
+
if (startIdx === -1 || endIdx === -1) return;
|
|
191
|
+
|
|
192
|
+
const before = content.slice(0, startIdx);
|
|
193
|
+
const after = content.slice(endIdx + BRAINBANK_SECTION_END.length);
|
|
194
|
+
const section = buildGeminiSection();
|
|
195
|
+
fs.writeFileSync(geminiPath, before + section + after);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** CLI command: brainbank mcp:export [target] [--force] */
|
|
199
|
+
export async function cmdMcpExport(): Promise<void> {
|
|
200
|
+
const targetName = args[1] || getFlag('target') || 'antigravity';
|
|
201
|
+
const repoPath = getFlag('repo') || '.';
|
|
202
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
203
|
+
|
|
204
|
+
const target = TARGETS[targetName];
|
|
205
|
+
if (!target) {
|
|
206
|
+
console.error(c.red(`Unknown export target: ${targetName}`));
|
|
207
|
+
console.error(c.dim(` Available: ${Object.keys(TARGETS).join(', ')}`));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const config = await getConfig(repoPath);
|
|
212
|
+
const block = buildBrainbankMcpBlock(config);
|
|
213
|
+
|
|
214
|
+
console.log(c.bold(`\n━━━ MCP Export: ${target.label} ━━━\n`));
|
|
215
|
+
|
|
216
|
+
// ── MCP Config ────────────────────────────────────────────
|
|
217
|
+
const mcpExists = hasBrainbankMcpEntry(target.configPath);
|
|
218
|
+
let writeMcp = true;
|
|
219
|
+
|
|
220
|
+
if (mcpExists && !force) {
|
|
221
|
+
console.log(` ${c.yellow('●')} MCP config already has brainbank entry`);
|
|
222
|
+
const cliPath = block.args.find(a => !a.startsWith('--')) ?? block.args[0];
|
|
223
|
+
console.log(` ${c.dim(' New:')} ${block.command} ${cliPath}`);
|
|
224
|
+
const envKeys = block.env ? Object.keys(block.env) : [];
|
|
225
|
+
if (envKeys.length > 0) console.log(` ${c.dim(' Keys:')} ${envKeys.join(', ')}`);
|
|
226
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
227
|
+
writeMcp = await confirm({ message: 'Override existing brainbank MCP entry?', default: true });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (writeMcp) {
|
|
231
|
+
const { created } = mergeAndWrite(target.configPath, block);
|
|
232
|
+
console.log(` ${c.green('✓')} ${created ? 'Created' : 'Updated'} ${c.dim(target.configPath)}`);
|
|
233
|
+
} else {
|
|
234
|
+
console.log(` ${c.dim('MCP config — skipped')}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Global GEMINI.md (~/.gemini/GEMINI.md) ────────────────
|
|
238
|
+
const geminiHasSection = hasGeminiSection(GLOBAL_GEMINI);
|
|
239
|
+
|
|
240
|
+
if (geminiHasSection) {
|
|
241
|
+
if (force) {
|
|
242
|
+
replaceGeminiSection(GLOBAL_GEMINI);
|
|
243
|
+
console.log(` ${c.green('✓')} Replaced BrainBank section in ${c.dim('~/.gemini/GEMINI.md')}`);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(` ${c.yellow('●')} ~/.gemini/GEMINI.md already has BrainBank section`);
|
|
246
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
247
|
+
const override = await confirm({ message: 'Override existing BrainBank section?', default: false });
|
|
248
|
+
if (override) {
|
|
249
|
+
replaceGeminiSection(GLOBAL_GEMINI);
|
|
250
|
+
console.log(` ${c.green('✓')} Replaced BrainBank section in ${c.dim('~/.gemini/GEMINI.md')}`);
|
|
251
|
+
} else {
|
|
252
|
+
console.log(` ${c.dim('GEMINI.md — skipped')}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
if (force) {
|
|
257
|
+
appendGeminiSection(GLOBAL_GEMINI);
|
|
258
|
+
console.log(` ${c.green('✓')} Added BrainBank section to ${c.dim('~/.gemini/GEMINI.md')}`);
|
|
259
|
+
} else {
|
|
260
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
261
|
+
const addGemini = await confirm({
|
|
262
|
+
message: 'Add BrainBank instructions to ~/.gemini/GEMINI.md? (teaches AI tools how to use BrainBank)',
|
|
263
|
+
default: true,
|
|
264
|
+
});
|
|
265
|
+
if (addGemini) {
|
|
266
|
+
appendGeminiSection(GLOBAL_GEMINI);
|
|
267
|
+
console.log(` ${c.green('✓')} Added BrainBank section to ${c.dim('~/.gemini/GEMINI.md')}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(`\n ${c.dim('Restart your IDE to apply changes.')}\n`);
|
|
273
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brainbank query <task> — Get formatted context for a task
|
|
3
|
+
*
|
|
4
|
+
* Primary command for semantic code search + LLM-pruned context.
|
|
5
|
+
*
|
|
6
|
+
* Flags:
|
|
7
|
+
* --context <desc> General task context for the pruner (inline or @file)
|
|
8
|
+
* --pruner <desc> Specific pruning instructions for the pruner
|
|
9
|
+
* --code 20 Max code results (default: 20)
|
|
10
|
+
* --git 5 Max git results
|
|
11
|
+
* --no-git Skip git results
|
|
12
|
+
* --no-code Skip code results
|
|
13
|
+
* --path <dir> Filter results to files under path prefix(es)
|
|
14
|
+
* --ignore <paths> Exclude paths (comma-separated or repeated)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from 'node:fs';
|
|
18
|
+
import { c, args, stripFlags, getFlag, getFlagAll } from '@/cli/utils.ts';
|
|
19
|
+
import { createBrain } from '@/cli/factory/index.ts';
|
|
20
|
+
import { tryServerContext } from '@/cli/server-client.ts';
|
|
21
|
+
|
|
22
|
+
/** Parse --code N, --git N, --no-git, --no-code flags into sources map. */
|
|
23
|
+
function parseSourceFlags(): Record<string, number> {
|
|
24
|
+
const NON_SOURCE = new Set([
|
|
25
|
+
'repo', 'depth', 'collection', 'pattern', 'context', 'pruner', 'name',
|
|
26
|
+
'keep', 'only', 'docs-path', 'mode', 'limit',
|
|
27
|
+
'ignore', 'meta', 'k', 'yes', 'y', 'force', 'verbose', 'path',
|
|
28
|
+
]);
|
|
29
|
+
const sources: Record<string, number> = {};
|
|
30
|
+
for (let i = 0; i < args.length; i++) {
|
|
31
|
+
if (!args[i].startsWith('--')) continue;
|
|
32
|
+
const name = args[i].slice(2);
|
|
33
|
+
// --no-git, --no-code → set to 0
|
|
34
|
+
if (name.startsWith('no-')) {
|
|
35
|
+
sources[name.slice(3)] = 0;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// --code 20, --git 5
|
|
39
|
+
const next = args[i + 1];
|
|
40
|
+
if (next !== undefined && /^\d+$/.test(next) && !NON_SOURCE.has(name)) {
|
|
41
|
+
sources[name] = parseInt(next, 10);
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return sources;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Read a flag value that supports inline string or @file references. */
|
|
49
|
+
function readFlagValue(flagName: string): string | undefined {
|
|
50
|
+
const raw = getFlag(flagName);
|
|
51
|
+
if (!raw) return undefined;
|
|
52
|
+
|
|
53
|
+
if (raw.startsWith('@')) {
|
|
54
|
+
const filePath = raw.slice(1);
|
|
55
|
+
try {
|
|
56
|
+
return fs.readFileSync(filePath, 'utf-8').trim();
|
|
57
|
+
} catch {
|
|
58
|
+
console.error(c.red(`Cannot read ${flagName} file: ${filePath}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return raw;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function cmdQuery(): Promise<void> {
|
|
66
|
+
const task = stripFlags(args).slice(1).join(' ');
|
|
67
|
+
if (!task) {
|
|
68
|
+
console.log(c.red('Usage: brainbank query <task description>'));
|
|
69
|
+
console.log(c.dim(' Options:'));
|
|
70
|
+
console.log(c.dim(' --context <desc|@file> General task context for the pruner'));
|
|
71
|
+
console.log(c.dim(' --pruner <desc> Specific pruning focus'));
|
|
72
|
+
console.log(c.dim(' --path <dir> Filter to files under path'));
|
|
73
|
+
console.log(c.dim(' --code N --git N Source limits'));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sources = parseSourceFlags();
|
|
78
|
+
const rawPath = getFlag('path');
|
|
79
|
+
const pathPrefix = rawPath
|
|
80
|
+
? rawPath.split(',').map(p => p.trim()).filter(Boolean)
|
|
81
|
+
: undefined;
|
|
82
|
+
const normalizedPath = pathPrefix && pathPrefix.length === 1 ? pathPrefix[0] : pathPrefix;
|
|
83
|
+
const ignorePaths = getFlagAll('ignore');
|
|
84
|
+
const repo = getFlag('repo');
|
|
85
|
+
|
|
86
|
+
// Parse --context and --pruner flags (both support inline or @file)
|
|
87
|
+
const contextDesc = readFlagValue('context');
|
|
88
|
+
const prunerDesc = readFlagValue('pruner');
|
|
89
|
+
|
|
90
|
+
// Parse BrainBankQL field flags
|
|
91
|
+
const fields = parseFieldFlags();
|
|
92
|
+
|
|
93
|
+
// Try HTTP server delegation first
|
|
94
|
+
const serverResult = await tryServerContext({
|
|
95
|
+
task,
|
|
96
|
+
repo: repo ?? process.cwd(),
|
|
97
|
+
sources: Object.keys(sources).length > 0 ? sources : undefined,
|
|
98
|
+
pathPrefix: normalizedPath,
|
|
99
|
+
ignorePaths: ignorePaths.length > 0 ? ignorePaths : undefined,
|
|
100
|
+
fields: Object.keys(fields).length > 0 ? fields : undefined,
|
|
101
|
+
context: contextDesc,
|
|
102
|
+
prunerContext: prunerDesc,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (serverResult !== null) {
|
|
106
|
+
console.log(serverResult);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fall back to local
|
|
111
|
+
const brain = await createBrain();
|
|
112
|
+
const result = await brain.getContext(task, {
|
|
113
|
+
sources: Object.keys(sources).length > 0 ? sources : undefined,
|
|
114
|
+
pathPrefix: normalizedPath,
|
|
115
|
+
ignorePaths: ignorePaths.length > 0 ? ignorePaths : undefined,
|
|
116
|
+
source: 'cli',
|
|
117
|
+
fields: Object.keys(fields).length > 0 ? fields : undefined,
|
|
118
|
+
context: contextDesc,
|
|
119
|
+
prunerContext: prunerDesc,
|
|
120
|
+
});
|
|
121
|
+
console.log(result);
|
|
122
|
+
brain.close();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Parse BrainBankQL field flags: --lines, --symbols, --compact, --no-callTree, --callTree.depth=N, etc. */
|
|
126
|
+
function parseFieldFlags(): Record<string, unknown> {
|
|
127
|
+
const FIELD_BOOLEANS = new Set(['lines', 'symbols', 'compact']);
|
|
128
|
+
const FIELD_NEGATABLE = new Set(['callTree', 'imports']);
|
|
129
|
+
const fields: Record<string, unknown> = {};
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < args.length; i++) {
|
|
132
|
+
if (!args[i].startsWith('--')) continue;
|
|
133
|
+
const raw = args[i].slice(2);
|
|
134
|
+
|
|
135
|
+
// --no-callTree, --no-imports → set to false
|
|
136
|
+
if (raw.startsWith('no-')) {
|
|
137
|
+
const name = raw.slice(3);
|
|
138
|
+
if (FIELD_NEGATABLE.has(name)) {
|
|
139
|
+
fields[name] = false;
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --callTree.depth=4 → { depth: 4 }
|
|
145
|
+
const dotIdx = raw.indexOf('.');
|
|
146
|
+
if (dotIdx > 0) {
|
|
147
|
+
const fieldName = raw.slice(0, dotIdx);
|
|
148
|
+
const rest = raw.slice(dotIdx + 1);
|
|
149
|
+
const eqIdx = rest.indexOf('=');
|
|
150
|
+
if (eqIdx > 0) {
|
|
151
|
+
const key = rest.slice(0, eqIdx);
|
|
152
|
+
const val = parseInt(rest.slice(eqIdx + 1), 10);
|
|
153
|
+
if (!isNaN(val)) {
|
|
154
|
+
fields[fieldName] = { [key]: val };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --lines, --symbols, --compact → true
|
|
161
|
+
if (FIELD_BOOLEANS.has(raw)) {
|
|
162
|
+
fields[raw] = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return fields;
|
|
167
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** brainbank reembed — Re-embed all vectors. */
|
|
2
|
+
|
|
3
|
+
import { c } from '@/cli/utils.ts';
|
|
4
|
+
import { createBrain } from '@/cli/factory/index.ts';
|
|
5
|
+
|
|
6
|
+
export async function cmdReembed(): Promise<void> {
|
|
7
|
+
const brain = await createBrain();
|
|
8
|
+
await brain.initialize();
|
|
9
|
+
|
|
10
|
+
console.log(c.bold('\n━━━ BrainBank Re-embed ━━━\n'));
|
|
11
|
+
console.log(c.dim(' Regenerating vectors with current embedding provider...'));
|
|
12
|
+
console.log(c.dim(' Text, FTS, and metadata remain unchanged.\n'));
|
|
13
|
+
|
|
14
|
+
const result = await brain.reembed({
|
|
15
|
+
onProgress: (table: string, current: number, total: number) => {
|
|
16
|
+
process.stdout.write(`\r ${c.cyan(table.padEnd(8))} ${current}/${total}`);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
console.log('\n');
|
|
21
|
+
for (const [name, count] of Object.entries(result.counts)) {
|
|
22
|
+
if (count > 0) {
|
|
23
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
24
|
+
console.log(` ${c.green('✓')} ${label.padEnd(8)} ${count} vectors`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log(`\n ${c.bold('Total')}: ${result.total} vectors regenerated\n`);
|
|
28
|
+
|
|
29
|
+
brain.close();
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brainbank reindex [path] — Nuke data and re-index from scratch.
|
|
3
|
+
*
|
|
4
|
+
* Deletes the .brainbank/data/ directory (SQLite DB + HNSW index)
|
|
5
|
+
* then runs a headless `index --yes` to rebuild everything.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { c, args, stripFlags } from '@/cli/utils.ts';
|
|
11
|
+
import { cmdIndex } from './index.ts';
|
|
12
|
+
|
|
13
|
+
export async function cmdReindex(): Promise<void> {
|
|
14
|
+
const positional = stripFlags(args);
|
|
15
|
+
const repoPath = path.resolve(positional[1] || '.');
|
|
16
|
+
const dataDir = path.join(repoPath, '.brainbank', 'data');
|
|
17
|
+
|
|
18
|
+
if (fs.existsSync(dataDir)) {
|
|
19
|
+
const entries = fs.readdirSync(dataDir);
|
|
20
|
+
console.log(c.bold('\n━━━ Reindex ━━━\n'));
|
|
21
|
+
console.log(` ${c.yellow('✗')} Deleting ${path.relative(process.cwd(), dataDir)}/ (${entries.length} files)`);
|
|
22
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
23
|
+
console.log(` ${c.green('✓')} Data cleared\n`);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(c.bold('\n━━━ Reindex ━━━\n'));
|
|
26
|
+
console.log(c.dim(' No existing data — fresh index\n'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Inject --yes into args so index runs headless (no TUI)
|
|
30
|
+
if (!args.includes('--yes') && !args.includes('-y')) {
|
|
31
|
+
args.push('--yes');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Force re-index all files
|
|
35
|
+
if (!args.includes('--force')) {
|
|
36
|
+
args.push('--force');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return cmdIndex();
|
|
40
|
+
}
|