brainbank 0.7.0 → 0.8.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 +76 -1398
- package/bin/brainbank +5 -1
- package/dist/{chunk-N2OJRXSB.js → chunk-3HVCONGF.js} +1 -1
- package/dist/{chunk-N2OJRXSB.js.map → chunk-3HVCONGF.js.map} +1 -1
- package/dist/{chunk-CCXVL56V.js → chunk-3JZIM5AU.js} +6 -3
- package/dist/chunk-3JZIM5AU.js.map +1 -0
- package/dist/{chunk-6XOXM7MI.js → chunk-5KU2PP34.js} +2 -2
- package/dist/{chunk-6XOXM7MI.js.map → chunk-5KU2PP34.js.map} +1 -1
- package/dist/chunk-7JDCHUJV.js +89 -0
- package/dist/chunk-7JDCHUJV.js.map +1 -0
- package/dist/{chunk-B77KABWH.js → chunk-7T2ZCZQA.js} +17 -15
- package/dist/chunk-7T2ZCZQA.js.map +1 -0
- package/dist/chunk-E3J37GDA.js +74 -0
- package/dist/chunk-E3J37GDA.js.map +1 -0
- package/dist/chunk-JEFWMS5Z.js +217 -0
- package/dist/chunk-JEFWMS5Z.js.map +1 -0
- package/dist/chunk-JRVYSTMP.js +3256 -0
- package/dist/chunk-JRVYSTMP.js.map +1 -0
- package/dist/chunk-OLDHLOMT.js +69 -0
- package/dist/chunk-OLDHLOMT.js.map +1 -0
- package/dist/{chunk-424UFCY7.js → chunk-QTZNB6AK.js} +6 -2
- package/dist/chunk-QTZNB6AK.js.map +1 -0
- package/dist/chunk-RFF7HMP6.js +109 -0
- package/dist/chunk-RFF7HMP6.js.map +1 -0
- package/dist/{chunk-ZNLN2VWV.js → chunk-TD3TEFI3.js} +1 -1
- package/dist/chunk-TD3TEFI3.js.map +1 -0
- package/dist/cli.js +1036 -341
- package/dist/cli.js.map +1 -1
- package/dist/haiku-expander-WOVJIVXD.js +8 -0
- package/dist/haiku-pruner-DB77ZQLJ.js +8 -0
- package/dist/http-server-GIRELCCL.js +9 -0
- package/dist/index.d.ts +1774 -611
- package/dist/index.js +282 -70
- package/dist/index.js.map +1 -1
- package/dist/{local-embedding-ZIMTK6PU.js → local-embedding-2RNCC5EU.js} +2 -2
- package/dist/{openai-embedding-VQZCZQYT.js → openai-embedding-Z5I4K4CN.js} +2 -2
- package/dist/perplexity-context-embedding-V5YUMXDR.js +9 -0
- package/dist/{perplexity-embedding-227WQY4R.js → perplexity-embedding-X2S72OAC.js} +2 -2
- package/dist/plugin-FF4Q34TI.js +32 -0
- package/dist/{qwen3-reranker-3MHEENT5.js → qwen3-reranker-HVIQOLKS.js} +2 -2
- package/dist/{resolve-CUJWY6HP.js → resolve-Q5D6HECY.js} +2 -2
- package/package.json +25 -52
- package/src/brainbank.ts +620 -0
- package/src/cli/commands/collection.ts +77 -0
- package/src/cli/commands/context.ts +171 -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 +72 -0
- package/src/cli/commands/index.ts +282 -0
- package/src/cli/commands/kv.ts +140 -0
- package/src/cli/commands/mcp.ts +13 -0
- package/src/cli/commands/reembed.ts +30 -0
- package/src/cli/commands/scan.ts +365 -0
- package/src/cli/commands/search.ts +130 -0
- package/src/cli/commands/stats.ts +44 -0
- package/src/cli/commands/status.ts +47 -0
- package/src/cli/commands/watch.ts +43 -0
- package/src/cli/factory/brain-context.ts +43 -0
- package/src/cli/factory/builtin-registration.ts +123 -0
- package/src/cli/factory/config-loader.ts +72 -0
- package/src/cli/factory/index.ts +65 -0
- package/src/cli/factory/plugin-loader.ts +146 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/server-client.ts +135 -0
- package/src/cli/utils.ts +121 -0
- package/src/config.ts +50 -0
- package/src/constants.ts +13 -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 +208 -0
- package/src/db/tracker.ts +91 -0
- package/src/engine/index-api.ts +85 -0
- package/src/engine/reembed.ts +206 -0
- package/src/engine/search-api.ts +222 -0
- package/src/index.ts +159 -0
- package/src/lib/fts.ts +57 -0
- package/src/lib/languages.ts +180 -0
- package/src/lib/logger.ts +125 -0
- package/src/lib/math.ts +87 -0
- package/src/lib/provider-key.ts +20 -0
- package/src/lib/prune.ts +71 -0
- package/src/lib/rerank.ts +33 -0
- package/src/lib/rrf.ts +133 -0
- package/src/lib/write-lock.ts +108 -0
- package/src/plugin.ts +323 -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 +152 -0
- package/src/providers/pruners/haiku-pruner.ts +112 -0
- package/src/providers/rerankers/qwen3-reranker.ts +180 -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 +61 -0
- package/src/search/context-builder.ts +298 -0
- package/src/search/keyword/composite-bm25-search.ts +62 -0
- package/src/search/types.ts +35 -0
- package/src/search/vector/composite-vector-search.ts +76 -0
- package/src/search/vector/mmr.ts +64 -0
- package/src/services/collection.ts +405 -0
- package/src/services/daemon.ts +87 -0
- package/src/services/http-server.ts +288 -0
- package/src/services/kv-service.ts +65 -0
- package/src/services/plugin-registry.ts +109 -0
- package/src/services/watch.ts +348 -0
- package/src/services/webhook-server.ts +100 -0
- package/src/types.ts +504 -0
- package/dist/base-3SNc_CeY.d.ts +0 -593
- package/dist/chunk-424UFCY7.js.map +0 -1
- package/dist/chunk-7EZR47JV.js +0 -232
- package/dist/chunk-7EZR47JV.js.map +0 -1
- package/dist/chunk-B77KABWH.js.map +0 -1
- package/dist/chunk-CCXVL56V.js.map +0 -1
- package/dist/chunk-DI3H6JVZ.js +0 -2432
- package/dist/chunk-DI3H6JVZ.js.map +0 -1
- package/dist/chunk-FGL32LUJ.js +0 -754
- package/dist/chunk-FGL32LUJ.js.map +0 -1
- package/dist/chunk-JRSKWF6K.js +0 -313
- package/dist/chunk-JRSKWF6K.js.map +0 -1
- package/dist/chunk-U2Q2XGPZ.js +0 -42
- package/dist/chunk-U2Q2XGPZ.js.map +0 -1
- package/dist/chunk-VQ27YUHH.js +0 -629
- package/dist/chunk-VQ27YUHH.js.map +0 -1
- package/dist/chunk-VVXYZIIB.js +0 -304
- package/dist/chunk-VVXYZIIB.js.map +0 -1
- package/dist/chunk-YOLKSYWK.js +0 -79
- package/dist/chunk-YOLKSYWK.js.map +0 -1
- package/dist/chunk-ZNLN2VWV.js.map +0 -1
- package/dist/code.d.ts +0 -33
- package/dist/code.js +0 -9
- package/dist/docs.d.ts +0 -21
- package/dist/docs.js +0 -9
- package/dist/git.d.ts +0 -33
- package/dist/git.js +0 -9
- package/dist/memory.d.ts +0 -17
- package/dist/memory.js +0 -9
- package/dist/notes.d.ts +0 -17
- package/dist/notes.js +0 -10
- package/dist/perplexity-context-embedding-KSVSZXMD.js +0 -9
- package/dist/resolve-CUJWY6HP.js.map +0 -1
- /package/dist/{code.js.map → haiku-expander-WOVJIVXD.js.map} +0 -0
- /package/dist/{docs.js.map → haiku-pruner-DB77ZQLJ.js.map} +0 -0
- /package/dist/{git.js.map → http-server-GIRELCCL.js.map} +0 -0
- /package/dist/{local-embedding-ZIMTK6PU.js.map → local-embedding-2RNCC5EU.js.map} +0 -0
- /package/dist/{memory.js.map → openai-embedding-Z5I4K4CN.js.map} +0 -0
- /package/dist/{notes.js.map → perplexity-context-embedding-V5YUMXDR.js.map} +0 -0
- /package/dist/{openai-embedding-VQZCZQYT.js.map → perplexity-embedding-X2S72OAC.js.map} +0 -0
- /package/dist/{perplexity-context-embedding-KSVSZXMD.js.map → plugin-FF4Q34TI.js.map} +0 -0
- /package/dist/{perplexity-embedding-227WQY4R.js.map → qwen3-reranker-HVIQOLKS.js.map} +0 -0
- /package/dist/{qwen3-reranker-3MHEENT5.js.map → resolve-Q5D6HECY.js.map} +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_PORT,
|
|
3
|
+
removePid,
|
|
4
|
+
writePid
|
|
5
|
+
} from "./chunk-OLDHLOMT.js";
|
|
6
|
+
import {
|
|
7
|
+
__name
|
|
8
|
+
} from "./chunk-7QVYU63E.js";
|
|
9
|
+
|
|
10
|
+
// src/services/http-server.ts
|
|
11
|
+
import * as http from "http";
|
|
12
|
+
var SimplePool = class _SimplePool {
|
|
13
|
+
static {
|
|
14
|
+
__name(this, "SimplePool");
|
|
15
|
+
}
|
|
16
|
+
_pool = /* @__PURE__ */ new Map();
|
|
17
|
+
_factory;
|
|
18
|
+
_onError;
|
|
19
|
+
_timer;
|
|
20
|
+
static TTL_MS = 30 * 60 * 1e3;
|
|
21
|
+
// 30 minutes
|
|
22
|
+
static EVICT_INTERVAL_MS = 5 * 60 * 1e3;
|
|
23
|
+
// 5 minutes
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this._factory = options.factory;
|
|
26
|
+
this._onError = options.onError;
|
|
27
|
+
this._timer = setInterval(() => this._evictStale(), _SimplePool.EVICT_INTERVAL_MS);
|
|
28
|
+
if (this._timer.unref) this._timer.unref();
|
|
29
|
+
}
|
|
30
|
+
async get(repoPath) {
|
|
31
|
+
const key = repoPath.replace(/\/+$/, "");
|
|
32
|
+
const existing = this._pool.get(key);
|
|
33
|
+
if (existing) {
|
|
34
|
+
existing.lastAccess = Date.now();
|
|
35
|
+
try {
|
|
36
|
+
await existing.brain.ensureFresh();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return existing.brain;
|
|
40
|
+
}
|
|
41
|
+
const brain = await this._factory(key);
|
|
42
|
+
this._pool.set(key, { brain, lastAccess: Date.now() });
|
|
43
|
+
return brain;
|
|
44
|
+
}
|
|
45
|
+
close() {
|
|
46
|
+
clearInterval(this._timer);
|
|
47
|
+
for (const [key, entry] of this._pool) {
|
|
48
|
+
try {
|
|
49
|
+
entry.brain.close();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
this._onError?.(key, err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this._pool.clear();
|
|
55
|
+
}
|
|
56
|
+
get size() {
|
|
57
|
+
return this._pool.size;
|
|
58
|
+
}
|
|
59
|
+
_evictStale() {
|
|
60
|
+
const cutoff = Date.now() - _SimplePool.TTL_MS;
|
|
61
|
+
for (const [key, entry] of this._pool) {
|
|
62
|
+
if (entry.lastAccess < cutoff) {
|
|
63
|
+
try {
|
|
64
|
+
entry.brain.close();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
this._onError?.(key, err);
|
|
67
|
+
}
|
|
68
|
+
this._pool.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var HttpServer = class {
|
|
74
|
+
static {
|
|
75
|
+
__name(this, "HttpServer");
|
|
76
|
+
}
|
|
77
|
+
_server = null;
|
|
78
|
+
_pool;
|
|
79
|
+
_port;
|
|
80
|
+
_defaultRepo;
|
|
81
|
+
_startTime = 0;
|
|
82
|
+
_log;
|
|
83
|
+
constructor(options) {
|
|
84
|
+
this._port = options.port ?? DEFAULT_PORT;
|
|
85
|
+
this._defaultRepo = options.defaultRepo ?? process.cwd();
|
|
86
|
+
this._log = options.onLog ?? console.log;
|
|
87
|
+
this._pool = new SimplePool({
|
|
88
|
+
factory: options.factory,
|
|
89
|
+
onError: options.onError
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** Start listening. Writes PID file for daemon detection. */
|
|
93
|
+
start() {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
this._server = http.createServer((req, res) => {
|
|
96
|
+
this._handleRequest(req, res).catch((err) => {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
this._log(`Request error: ${msg}`);
|
|
99
|
+
if (!res.headersSent) {
|
|
100
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
101
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
this._server.on("error", (err) => {
|
|
106
|
+
if (err.code === "EADDRINUSE") {
|
|
107
|
+
reject(new Error(`Port ${this._port} already in use. Is another server running?`));
|
|
108
|
+
} else {
|
|
109
|
+
reject(err);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
this._server.listen(this._port, "127.0.0.1", () => {
|
|
113
|
+
this._startTime = Date.now();
|
|
114
|
+
writePid(process.pid, this._port);
|
|
115
|
+
this._log(`BrainBank HTTP server listening on http://localhost:${this._port}`);
|
|
116
|
+
resolve();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Stop the server and clean up. */
|
|
121
|
+
close() {
|
|
122
|
+
this._pool.close();
|
|
123
|
+
this._server?.close();
|
|
124
|
+
this._server = null;
|
|
125
|
+
removePid();
|
|
126
|
+
}
|
|
127
|
+
get port() {
|
|
128
|
+
return this._port;
|
|
129
|
+
}
|
|
130
|
+
// ── Request Router ──────────────────────────────────
|
|
131
|
+
async _handleRequest(req, res) {
|
|
132
|
+
res.setHeader("Content-Type", "application/json");
|
|
133
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
134
|
+
return this._handleHealth(res);
|
|
135
|
+
}
|
|
136
|
+
if (req.method !== "POST") {
|
|
137
|
+
res.writeHead(405);
|
|
138
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const body = await this._readBody(req);
|
|
142
|
+
switch (req.url) {
|
|
143
|
+
case "/context":
|
|
144
|
+
return this._handleContext(body, res);
|
|
145
|
+
case "/index":
|
|
146
|
+
return this._handleIndex(body, res);
|
|
147
|
+
default:
|
|
148
|
+
res.writeHead(404);
|
|
149
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// ── Handlers ────────────────────────────────────────
|
|
153
|
+
_handleHealth(res) {
|
|
154
|
+
const uptime = Math.round((Date.now() - this._startTime) / 1e3);
|
|
155
|
+
res.writeHead(200);
|
|
156
|
+
res.end(JSON.stringify({
|
|
157
|
+
ok: true,
|
|
158
|
+
pid: process.pid,
|
|
159
|
+
port: this._port,
|
|
160
|
+
uptime,
|
|
161
|
+
workspaces: this._pool.size
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
async _handleContext(body, res) {
|
|
165
|
+
const req = body;
|
|
166
|
+
if (!req.task) {
|
|
167
|
+
res.writeHead(400);
|
|
168
|
+
res.end(JSON.stringify({ error: "Missing required field: task" }));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const repoPath = req.repo ?? this._defaultRepo;
|
|
172
|
+
const brain = await this._pool.get(repoPath);
|
|
173
|
+
const base = {
|
|
174
|
+
code: req.codeResults ?? 20,
|
|
175
|
+
git: req.gitResults ?? 5
|
|
176
|
+
};
|
|
177
|
+
if (req.docsResults !== void 0) base.docs = req.docsResults;
|
|
178
|
+
const resolvedSources = req.sources ? { ...base, ...req.sources } : base;
|
|
179
|
+
const context = await brain.getContext(req.task, {
|
|
180
|
+
affectedFiles: req.affectedFiles,
|
|
181
|
+
sources: resolvedSources,
|
|
182
|
+
pathPrefix: req.pathPrefix,
|
|
183
|
+
source: "daemon"
|
|
184
|
+
});
|
|
185
|
+
res.writeHead(200);
|
|
186
|
+
res.end(JSON.stringify({ context }));
|
|
187
|
+
}
|
|
188
|
+
async _handleIndex(body, res) {
|
|
189
|
+
const req = body;
|
|
190
|
+
const repoPath = req.repo ?? this._defaultRepo;
|
|
191
|
+
const brain = await this._pool.get(repoPath);
|
|
192
|
+
const result = await brain.index({ forceReindex: req.forceReindex });
|
|
193
|
+
res.writeHead(200);
|
|
194
|
+
res.end(JSON.stringify({ result }));
|
|
195
|
+
}
|
|
196
|
+
// ── Helpers ─────────────────────────────────────────
|
|
197
|
+
_readBody(req) {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const chunks = [];
|
|
200
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
201
|
+
req.on("error", reject);
|
|
202
|
+
req.on("end", () => {
|
|
203
|
+
try {
|
|
204
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
205
|
+
resolve(raw ? JSON.parse(raw) : {});
|
|
206
|
+
} catch {
|
|
207
|
+
resolve({});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export {
|
|
215
|
+
HttpServer
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=chunk-JEFWMS5Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/http-server.ts"],"sourcesContent":["/**\n * HttpServer — Lightweight JSON API for BrainBank.\n *\n * Exposes BrainBank operations over HTTP so CLI commands can delegate\n * to a running server instead of cold-loading models each time.\n *\n * Routes:\n * POST /context → brain.getContext()\n * POST /index → brain.index()\n * GET /health → { ok, pid, uptime, port }\n *\n * Multi-repo: a single server handles all repos via WorkspacePool.\n * Each request includes a `repo` field to select the workspace.\n */\n\nimport type { BrainBank } from '@/brainbank.ts';\n\nimport * as http from 'node:http';\n\nimport { DEFAULT_PORT, writePid, removePid } from './daemon.ts';\n\n// ── Types ─────────────────────────────────────────────\n\ninterface ContextRequest {\n task: string;\n repo?: string;\n sources?: Record<string, number>;\n pathPrefix?: string;\n affectedFiles?: string[];\n codeResults?: number;\n gitResults?: number;\n docsResults?: number;\n}\n\ninterface IndexRequest {\n repo?: string;\n forceReindex?: boolean;\n}\n\ninterface PoolOptions {\n /** Factory function to create a BrainBank for a repo path. */\n factory: (repoPath: string) => Promise<BrainBank>;\n /** Called when an error occurs. */\n onError?: (repo: string, err: unknown) => void;\n}\n\n// ── Simple Workspace Pool ─────────────────────────────\n\n/**\n * Minimal in-memory pool for multi-repo support.\n * Creates BrainBank instances on demand and caches them.\n * Eviction by TTL (30 min inactivity).\n */\nclass SimplePool {\n private _pool = new Map<string, { brain: BrainBank; lastAccess: number }>();\n private _factory: (repoPath: string) => Promise<BrainBank>;\n private _onError?: (repo: string, err: unknown) => void;\n private _timer: ReturnType<typeof setInterval>;\n\n private static readonly TTL_MS = 30 * 60 * 1000; // 30 minutes\n private static readonly EVICT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes\n\n constructor(options: PoolOptions) {\n this._factory = options.factory;\n this._onError = options.onError;\n this._timer = setInterval(() => this._evictStale(), SimplePool.EVICT_INTERVAL_MS);\n if (this._timer.unref) this._timer.unref();\n }\n\n async get(repoPath: string): Promise<BrainBank> {\n const key = repoPath.replace(/\\/+$/, '');\n const existing = this._pool.get(key);\n if (existing) {\n existing.lastAccess = Date.now();\n try { await existing.brain.ensureFresh(); } catch { /* stale is better than nothing */ }\n return existing.brain;\n }\n\n const brain = await this._factory(key);\n this._pool.set(key, { brain, lastAccess: Date.now() });\n return brain;\n }\n\n close(): void {\n clearInterval(this._timer);\n for (const [key, entry] of this._pool) {\n try { entry.brain.close(); } catch (err: unknown) {\n this._onError?.(key, err);\n }\n }\n this._pool.clear();\n }\n\n get size(): number {\n return this._pool.size;\n }\n\n private _evictStale(): void {\n const cutoff = Date.now() - SimplePool.TTL_MS;\n for (const [key, entry] of this._pool) {\n if (entry.lastAccess < cutoff) {\n try { entry.brain.close(); } catch (err: unknown) {\n this._onError?.(key, err);\n }\n this._pool.delete(key);\n }\n }\n }\n}\n\n// ── HTTP Server ───────────────────────────────────────\n\nexport interface HttpServerOptions {\n port?: number;\n /** Factory to create a BrainBank instance for a given repo path. */\n factory: (repoPath: string) => Promise<BrainBank>;\n /** Default repo path when request doesn't specify one. */\n defaultRepo?: string;\n /** Called when an error occurs during pool operations. */\n onError?: (repo: string, err: unknown) => void;\n /** Called on server lifecycle events. */\n onLog?: (msg: string) => void;\n}\n\nexport class HttpServer {\n private _server: http.Server | null = null;\n private _pool: SimplePool;\n private _port: number;\n private _defaultRepo: string;\n private _startTime = 0;\n private _log: (msg: string) => void;\n\n constructor(options: HttpServerOptions) {\n this._port = options.port ?? DEFAULT_PORT;\n this._defaultRepo = options.defaultRepo ?? process.cwd();\n this._log = options.onLog ?? console.log;\n this._pool = new SimplePool({\n factory: options.factory,\n onError: options.onError,\n });\n }\n\n /** Start listening. Writes PID file for daemon detection. */\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this._server = http.createServer((req, res) => {\n this._handleRequest(req, res).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err);\n this._log(`Request error: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n });\n\n this._server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${this._port} already in use. Is another server running?`));\n } else {\n reject(err);\n }\n });\n\n this._server.listen(this._port, '127.0.0.1', () => {\n this._startTime = Date.now();\n writePid(process.pid, this._port);\n this._log(`BrainBank HTTP server listening on http://localhost:${this._port}`);\n resolve();\n });\n });\n }\n\n /** Stop the server and clean up. */\n close(): void {\n this._pool.close();\n this._server?.close();\n this._server = null;\n removePid();\n }\n\n get port(): number {\n return this._port;\n }\n\n // ── Request Router ──────────────────────────────────\n\n private async _handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n // CORS + content type\n res.setHeader('Content-Type', 'application/json');\n\n // Health check — no body parsing needed\n if (req.method === 'GET' && req.url === '/health') {\n return this._handleHealth(res);\n }\n\n if (req.method !== 'POST') {\n res.writeHead(405);\n res.end(JSON.stringify({ error: 'Method not allowed' }));\n return;\n }\n\n const body = await this._readBody(req);\n\n switch (req.url) {\n case '/context':\n return this._handleContext(body, res);\n case '/index':\n return this._handleIndex(body, res);\n default:\n res.writeHead(404);\n res.end(JSON.stringify({ error: 'Not found' }));\n }\n }\n\n // ── Handlers ────────────────────────────────────────\n\n private _handleHealth(res: http.ServerResponse): void {\n const uptime = Math.round((Date.now() - this._startTime) / 1000);\n res.writeHead(200);\n res.end(JSON.stringify({\n ok: true,\n pid: process.pid,\n port: this._port,\n uptime,\n workspaces: this._pool.size,\n }));\n }\n\n private async _handleContext(body: unknown, res: http.ServerResponse): Promise<void> {\n const req = body as ContextRequest;\n if (!req.task) {\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Missing required field: task' }));\n return;\n }\n\n const repoPath = req.repo ?? this._defaultRepo;\n const brain = await this._pool.get(repoPath);\n\n // Build sources from explicit params, then let `sources` override\n const base: Record<string, number> = {\n code: req.codeResults ?? 20,\n git: req.gitResults ?? 5,\n };\n if (req.docsResults !== undefined) base.docs = req.docsResults;\n const resolvedSources = req.sources ? { ...base, ...req.sources } : base;\n\n const context = await brain.getContext(req.task, {\n affectedFiles: req.affectedFiles,\n sources: resolvedSources,\n pathPrefix: req.pathPrefix,\n source: 'daemon',\n });\n\n res.writeHead(200);\n res.end(JSON.stringify({ context }));\n }\n\n private async _handleIndex(body: unknown, res: http.ServerResponse): Promise<void> {\n const req = body as IndexRequest;\n const repoPath = req.repo ?? this._defaultRepo;\n const brain = await this._pool.get(repoPath);\n\n const result = await brain.index({ forceReindex: req.forceReindex });\n\n res.writeHead(200);\n res.end(JSON.stringify({ result }));\n }\n\n // ── Helpers ─────────────────────────────────────────\n\n private _readBody(req: http.IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('error', reject);\n req.on('end', () => {\n try {\n const raw = Buffer.concat(chunks).toString('utf8');\n resolve(raw ? JSON.parse(raw) as unknown : {});\n } catch {\n resolve({});\n }\n });\n });\n }\n}\n"],"mappings":";;;;;;;;;;AAiBA,YAAY,UAAU;AAoCtB,IAAM,aAAN,MAAM,YAAW;AAAA,EArDjB,OAqDiB;AAAA;AAAA;AAAA,EACL,QAAQ,oBAAI,IAAsD;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EAER,OAAwB,SAAS,KAAK,KAAK;AAAA;AAAA,EAC3C,OAAwB,oBAAoB,IAAI,KAAK;AAAA;AAAA,EAErD,YAAY,SAAsB;AAC9B,SAAK,WAAW,QAAQ;AACxB,SAAK,WAAW,QAAQ;AACxB,SAAK,SAAS,YAAY,MAAM,KAAK,YAAY,GAAG,YAAW,iBAAiB;AAChF,QAAI,KAAK,OAAO,MAAO,MAAK,OAAO,MAAM;AAAA,EAC7C;AAAA,EAEA,MAAM,IAAI,UAAsC;AAC5C,UAAM,MAAM,SAAS,QAAQ,QAAQ,EAAE;AACvC,UAAM,WAAW,KAAK,MAAM,IAAI,GAAG;AACnC,QAAI,UAAU;AACV,eAAS,aAAa,KAAK,IAAI;AAC/B,UAAI;AAAE,cAAM,SAAS,MAAM,YAAY;AAAA,MAAG,QAAQ;AAAA,MAAqC;AACvF,aAAO,SAAS;AAAA,IACpB;AAEA,UAAM,QAAQ,MAAM,KAAK,SAAS,GAAG;AACrC,SAAK,MAAM,IAAI,KAAK,EAAE,OAAO,YAAY,KAAK,IAAI,EAAE,CAAC;AACrD,WAAO;AAAA,EACX;AAAA,EAEA,QAAc;AACV,kBAAc,KAAK,MAAM;AACzB,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,OAAO;AACnC,UAAI;AAAE,cAAM,MAAM,MAAM;AAAA,MAAG,SAAS,KAAc;AAC9C,aAAK,WAAW,KAAK,GAAG;AAAA,MAC5B;AAAA,IACJ;AACA,SAAK,MAAM,MAAM;AAAA,EACrB;AAAA,EAEA,IAAI,OAAe;AACf,WAAO,KAAK,MAAM;AAAA,EACtB;AAAA,EAEQ,cAAoB;AACxB,UAAM,SAAS,KAAK,IAAI,IAAI,YAAW;AACvC,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,OAAO;AACnC,UAAI,MAAM,aAAa,QAAQ;AAC3B,YAAI;AAAE,gBAAM,MAAM,MAAM;AAAA,QAAG,SAAS,KAAc;AAC9C,eAAK,WAAW,KAAK,GAAG;AAAA,QAC5B;AACA,aAAK,MAAM,OAAO,GAAG;AAAA,MACzB;AAAA,IACJ;AAAA,EACJ;AACJ;AAgBO,IAAM,aAAN,MAAiB;AAAA,EA5HxB,OA4HwB;AAAA;AAAA;AAAA,EACZ,UAA8B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EAER,YAAY,SAA4B;AACpC,SAAK,QAAQ,QAAQ,QAAQ;AAC7B,SAAK,eAAe,QAAQ,eAAe,QAAQ,IAAI;AACvD,SAAK,OAAO,QAAQ,SAAS,QAAQ;AACrC,SAAK,QAAQ,IAAI,WAAW;AAAA,MACxB,SAAS,QAAQ;AAAA,MACjB,SAAS,QAAQ;AAAA,IACrB,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,QAAuB;AACnB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,WAAK,UAAe,kBAAa,CAAC,KAAK,QAAQ;AAC3C,aAAK,eAAe,KAAK,GAAG,EAAE,MAAM,CAAC,QAAiB;AAClD,gBAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAK,KAAK,kBAAkB,GAAG,EAAE;AACjC,cAAI,CAAC,IAAI,aAAa;AAClB,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,CAAC;AAAA,UAC9D;AAAA,QACJ,CAAC;AAAA,MACL,CAAC;AAED,WAAK,QAAQ,GAAG,SAAS,CAAC,QAA+B;AACrD,YAAI,IAAI,SAAS,cAAc;AAC3B,iBAAO,IAAI,MAAM,QAAQ,KAAK,KAAK,6CAA6C,CAAC;AAAA,QACrF,OAAO;AACH,iBAAO,GAAG;AAAA,QACd;AAAA,MACJ,CAAC;AAED,WAAK,QAAQ,OAAO,KAAK,OAAO,aAAa,MAAM;AAC/C,aAAK,aAAa,KAAK,IAAI;AAC3B,iBAAS,QAAQ,KAAK,KAAK,KAAK;AAChC,aAAK,KAAK,uDAAuD,KAAK,KAAK,EAAE;AAC7E,gBAAQ;AAAA,MACZ,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,QAAc;AACV,SAAK,MAAM,MAAM;AACjB,SAAK,SAAS,MAAM;AACpB,SAAK,UAAU;AACf,cAAU;AAAA,EACd;AAAA,EAEA,IAAI,OAAe;AACf,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,eAAe,KAA2B,KAAyC;AAE7F,QAAI,UAAU,gBAAgB,kBAAkB;AAGhD,QAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,WAAW;AAC/C,aAAO,KAAK,cAAc,GAAG;AAAA,IACjC;AAEA,QAAI,IAAI,WAAW,QAAQ;AACvB,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,qBAAqB,CAAC,CAAC;AACvD;AAAA,IACJ;AAEA,UAAM,OAAO,MAAM,KAAK,UAAU,GAAG;AAErC,YAAQ,IAAI,KAAK;AAAA,MACb,KAAK;AACD,eAAO,KAAK,eAAe,MAAM,GAAG;AAAA,MACxC,KAAK;AACD,eAAO,KAAK,aAAa,MAAM,GAAG;AAAA,MACtC;AACI,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAAA,IACtD;AAAA,EACJ;AAAA;AAAA,EAIQ,cAAc,KAAgC;AAClD,UAAM,SAAS,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,cAAc,GAAI;AAC/D,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,KAAK,UAAU;AAAA,MACnB,IAAI;AAAA,MACJ,KAAK,QAAQ;AAAA,MACb,MAAM,KAAK;AAAA,MACX;AAAA,MACA,YAAY,KAAK,MAAM;AAAA,IAC3B,CAAC,CAAC;AAAA,EACN;AAAA,EAEA,MAAc,eAAe,MAAe,KAAyC;AACjF,UAAM,MAAM;AACZ,QAAI,CAAC,IAAI,MAAM;AACX,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,+BAA+B,CAAC,CAAC;AACjE;AAAA,IACJ;AAEA,UAAM,WAAW,IAAI,QAAQ,KAAK;AAClC,UAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,QAAQ;AAG3C,UAAM,OAA+B;AAAA,MACjC,MAAM,IAAI,eAAe;AAAA,MACzB,KAAK,IAAI,cAAc;AAAA,IAC3B;AACA,QAAI,IAAI,gBAAgB,OAAW,MAAK,OAAO,IAAI;AACnD,UAAM,kBAAkB,IAAI,UAAU,EAAE,GAAG,MAAM,GAAG,IAAI,QAAQ,IAAI;AAEpE,UAAM,UAAU,MAAM,MAAM,WAAW,IAAI,MAAM;AAAA,MAC7C,eAAe,IAAI;AAAA,MACnB,SAAS;AAAA,MACT,YAAY,IAAI;AAAA,MAChB,QAAQ;AAAA,IACZ,CAAC;AAED,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;AAAA,EACvC;AAAA,EAEA,MAAc,aAAa,MAAe,KAAyC;AAC/E,UAAM,MAAM;AACZ,UAAM,WAAW,IAAI,QAAQ,KAAK;AAClC,UAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,QAAQ;AAE3C,UAAM,SAAS,MAAM,MAAM,MAAM,EAAE,cAAc,IAAI,aAAa,CAAC;AAEnE,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,KAAK,UAAU,EAAE,OAAO,CAAC,CAAC;AAAA,EACtC;AAAA;AAAA,EAIQ,UAAU,KAA6C;AAC3D,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACpC,YAAM,SAAmB,CAAC;AAC1B,UAAI,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,KAAK,CAAC;AACpD,UAAI,GAAG,SAAS,MAAM;AACtB,UAAI,GAAG,OAAO,MAAM;AAChB,YAAI;AACA,gBAAM,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AACjD,kBAAQ,MAAM,KAAK,MAAM,GAAG,IAAe,CAAC,CAAC;AAAA,QACjD,QAAQ;AACJ,kBAAQ,CAAC,CAAC;AAAA,QACd;AAAA,MACJ,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AACJ;","names":[]}
|