brainbank 0.1.0-beta.1

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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/assets/architecture.png +0 -0
  4. package/bin/brainbank +18 -0
  5. package/bin/brainbank-mcp +19 -0
  6. package/dist/chunk-3YBCD6DI.js +117 -0
  7. package/dist/chunk-3YBCD6DI.js.map +1 -0
  8. package/dist/chunk-63GBCDS5.js +3249 -0
  9. package/dist/chunk-63GBCDS5.js.map +1 -0
  10. package/dist/chunk-DMFMTOHF.js +123 -0
  11. package/dist/chunk-DMFMTOHF.js.map +1 -0
  12. package/dist/chunk-FQYKWB2Q.js +136 -0
  13. package/dist/chunk-FQYKWB2Q.js.map +1 -0
  14. package/dist/chunk-IMJJ2VEM.js +74 -0
  15. package/dist/chunk-IMJJ2VEM.js.map +1 -0
  16. package/dist/chunk-M744PCJQ.js +43 -0
  17. package/dist/chunk-M744PCJQ.js.map +1 -0
  18. package/dist/chunk-O3J6ZIXK.js +82 -0
  19. package/dist/chunk-O3J6ZIXK.js.map +1 -0
  20. package/dist/chunk-OPH7GZ7U.js +124 -0
  21. package/dist/chunk-OPH7GZ7U.js.map +1 -0
  22. package/dist/chunk-PXEWQMN7.js +89 -0
  23. package/dist/chunk-PXEWQMN7.js.map +1 -0
  24. package/dist/chunk-RDQYDLYZ.js +69 -0
  25. package/dist/chunk-RDQYDLYZ.js.map +1 -0
  26. package/dist/chunk-VIIHPCC4.js +254 -0
  27. package/dist/chunk-VIIHPCC4.js.map +1 -0
  28. package/dist/chunk-WCQVDF3K.js +14 -0
  29. package/dist/chunk-WCQVDF3K.js.map +1 -0
  30. package/dist/cli.d.ts +1 -0
  31. package/dist/cli.js +3076 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/haiku-expander-YRSIPGKP.js +8 -0
  34. package/dist/haiku-expander-YRSIPGKP.js.map +1 -0
  35. package/dist/haiku-pruner-SHAXUPY6.js +8 -0
  36. package/dist/haiku-pruner-SHAXUPY6.js.map +1 -0
  37. package/dist/http-server-QUXHLWUM.js +9 -0
  38. package/dist/http-server-QUXHLWUM.js.map +1 -0
  39. package/dist/index.d.ts +2161 -0
  40. package/dist/index.js +357 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/local-embedding-NZQTILGV.js +8 -0
  43. package/dist/local-embedding-NZQTILGV.js.map +1 -0
  44. package/dist/mcp.d.ts +2 -0
  45. package/dist/mcp.js +334 -0
  46. package/dist/mcp.js.map +1 -0
  47. package/dist/openai-embedding-ZP5TSUJG.js +8 -0
  48. package/dist/openai-embedding-ZP5TSUJG.js.map +1 -0
  49. package/dist/perplexity-context-embedding-GI5PHE6X.js +9 -0
  50. package/dist/perplexity-context-embedding-GI5PHE6X.js.map +1 -0
  51. package/dist/perplexity-embedding-KZRYGJRC.js +10 -0
  52. package/dist/perplexity-embedding-KZRYGJRC.js.map +1 -0
  53. package/dist/plugin-IKQ6IRSJ.js +32 -0
  54. package/dist/plugin-IKQ6IRSJ.js.map +1 -0
  55. package/dist/resolve-ASGLBNUC.js +10 -0
  56. package/dist/resolve-ASGLBNUC.js.map +1 -0
  57. package/dist/stats-tui-ZY2NQSEA.js +1904 -0
  58. package/dist/stats-tui-ZY2NQSEA.js.map +1 -0
  59. package/package.json +96 -0
  60. package/src/brainbank.ts +617 -0
  61. package/src/cli/commands/collection.ts +77 -0
  62. package/src/cli/commands/context.ts +179 -0
  63. package/src/cli/commands/daemon.ts +100 -0
  64. package/src/cli/commands/docs.ts +71 -0
  65. package/src/cli/commands/files.ts +69 -0
  66. package/src/cli/commands/help.ts +77 -0
  67. package/src/cli/commands/index.ts +482 -0
  68. package/src/cli/commands/kv.ts +140 -0
  69. package/src/cli/commands/mcp-export.ts +273 -0
  70. package/src/cli/commands/mcp.ts +6 -0
  71. package/src/cli/commands/reembed.ts +30 -0
  72. package/src/cli/commands/scan.ts +336 -0
  73. package/src/cli/commands/search.ts +203 -0
  74. package/src/cli/commands/stats.ts +68 -0
  75. package/src/cli/commands/status.ts +47 -0
  76. package/src/cli/commands/watch.ts +47 -0
  77. package/src/cli/factory/brain-context.ts +43 -0
  78. package/src/cli/factory/builtin-registration.ts +87 -0
  79. package/src/cli/factory/config-loader.ts +77 -0
  80. package/src/cli/factory/index.ts +69 -0
  81. package/src/cli/factory/plugin-loader.ts +325 -0
  82. package/src/cli/index.ts +71 -0
  83. package/src/cli/server-client.ts +178 -0
  84. package/src/cli/tui/index-tui.tsx +667 -0
  85. package/src/cli/tui/stats-data.ts +523 -0
  86. package/src/cli/tui/stats-search.ts +262 -0
  87. package/src/cli/tui/stats-tui.tsx +1465 -0
  88. package/src/cli/tui/tree-scanner.ts +650 -0
  89. package/src/cli/utils.ts +137 -0
  90. package/src/config.ts +49 -0
  91. package/src/constants.ts +21 -0
  92. package/src/db/adapter.ts +112 -0
  93. package/src/db/metadata.ts +130 -0
  94. package/src/db/migrations.ts +66 -0
  95. package/src/db/sqlite-adapter.ts +218 -0
  96. package/src/db/tracker.ts +91 -0
  97. package/src/engine/index-api.ts +81 -0
  98. package/src/engine/reembed.ts +206 -0
  99. package/src/engine/search-api.ts +218 -0
  100. package/src/index.ts +154 -0
  101. package/src/lib/fts.ts +57 -0
  102. package/src/lib/languages.ts +180 -0
  103. package/src/lib/logger.ts +126 -0
  104. package/src/lib/math.ts +87 -0
  105. package/src/lib/provider-key.ts +20 -0
  106. package/src/lib/prune.ts +71 -0
  107. package/src/lib/rrf.ts +133 -0
  108. package/src/lib/write-lock.ts +108 -0
  109. package/src/mcp/mcp-server.ts +195 -0
  110. package/src/mcp/workspace-factory.ts +68 -0
  111. package/src/mcp/workspace-pool.ts +224 -0
  112. package/src/plugin.ts +381 -0
  113. package/src/providers/embeddings/embedding-worker-thread.ts +95 -0
  114. package/src/providers/embeddings/embedding-worker.ts +141 -0
  115. package/src/providers/embeddings/local-embedding.ts +115 -0
  116. package/src/providers/embeddings/openai-embedding.ts +167 -0
  117. package/src/providers/embeddings/perplexity-context-embedding.ts +195 -0
  118. package/src/providers/embeddings/perplexity-embedding.ts +165 -0
  119. package/src/providers/embeddings/resolve.ts +34 -0
  120. package/src/providers/pruners/haiku-expander.ts +166 -0
  121. package/src/providers/pruners/haiku-pruner.ts +112 -0
  122. package/src/providers/vector/hnsw-index.ts +174 -0
  123. package/src/providers/vector/hnsw-loader.ts +129 -0
  124. package/src/search/bm25-boost.ts +69 -0
  125. package/src/search/context-builder.ts +251 -0
  126. package/src/search/keyword/composite-bm25-search.ts +47 -0
  127. package/src/search/types.ts +37 -0
  128. package/src/search/vector/composite-vector-search.ts +61 -0
  129. package/src/search/vector/mmr.ts +64 -0
  130. package/src/services/collection.ts +384 -0
  131. package/src/services/daemon.ts +87 -0
  132. package/src/services/http-server.ts +336 -0
  133. package/src/services/kv-service.ts +64 -0
  134. package/src/services/plugin-registry.ts +77 -0
  135. package/src/services/watch.ts +340 -0
  136. package/src/services/webhook-server.ts +100 -0
  137. package/src/types.ts +493 -0
@@ -0,0 +1,124 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-WCQVDF3K.js";
4
+
5
+ // src/providers/pruners/haiku-expander.ts
6
+ var DEFAULT_MODEL = "claude-haiku-4-5-20251001";
7
+ var HaikuExpander = class {
8
+ static {
9
+ __name(this, "HaikuExpander");
10
+ }
11
+ _apiKey;
12
+ _model;
13
+ constructor(options = {}) {
14
+ this._apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
15
+ this._model = options.model ?? DEFAULT_MODEL;
16
+ if (!this._apiKey) {
17
+ throw new Error(
18
+ "HaikuExpander: No API key provided. Set ANTHROPIC_API_KEY env var or pass apiKey option."
19
+ );
20
+ }
21
+ }
22
+ async expand(query, currentIds, manifest) {
23
+ if (manifest.length === 0) return { ids: [] };
24
+ const currentSet = new Set(currentIds);
25
+ const available = manifest.filter((m) => !currentSet.has(m.id));
26
+ if (available.length === 0) return { ids: [] };
27
+ const priorityChunks = available.filter((m) => m.priority);
28
+ const otherChunks = available.filter((m) => !m.priority);
29
+ const currentSummary = manifest.filter((m) => currentSet.has(m.id)).map((m) => `#${m.id} ${m.filePath} | ${m.chunkType} ${m.name}`).join("\n");
30
+ let manifestSection = "";
31
+ if (priorityChunks.length > 0) {
32
+ const prioLines = priorityChunks.map(
33
+ (m) => `#${m.id} ${m.filePath} | ${m.chunkType} ${m.name} ${m.lines}`
34
+ ).join("\n");
35
+ manifestSection += `DEPENDENCY chunks (imported by or importing the search result files):
36
+ ${prioLines}
37
+
38
+ `;
39
+ }
40
+ if (otherChunks.length > 0) {
41
+ const otherLines = otherChunks.map(
42
+ (m) => `#${m.id} ${m.filePath} | ${m.chunkType} ${m.name} ${m.lines}`
43
+ ).join("\n");
44
+ manifestSection += `Other available chunks:
45
+ ${otherLines}`;
46
+ }
47
+ const prompt = `Task: "${query}"
48
+
49
+ Already included chunks:
50
+ ${currentSummary}
51
+
52
+ ${manifestSection}
53
+
54
+ You are a code context expander. The search already found the "included" chunks above.
55
+ Review the available chunks and select any that would help an AI agent complete the task.
56
+
57
+ Rules:
58
+ - STRONGLY PREFER dependency chunks \u2014 they are structurally connected to the search results via imports
59
+ - Select type definitions, interfaces, models, or configs needed to understand included code
60
+ - Select initialization or setup code if the task involves debugging or modifying a feature
61
+ - Do NOT select test files, documentation, or unrelated utilities
62
+ - Be selective: only include chunks that fill clear gaps. Quality over quantity.
63
+ - If nothing useful is available, return an empty ids array
64
+
65
+ Respond with ONLY a JSON object:
66
+ { "ids": [42, 17, 89], "note": "Brief 1-2 sentence observation about the codebase relevant to the task" }
67
+
68
+ The "note" is optional \u2014 use it to mention things like missing files, architectural patterns, deprecated modules, or important relationships you noticed. If nothing notable, omit it.
69
+ If nothing to add: { "ids": [] }`;
70
+ try {
71
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
72
+ method: "POST",
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ "x-api-key": this._apiKey,
76
+ "anthropic-version": "2023-06-01"
77
+ },
78
+ body: JSON.stringify({
79
+ model: this._model,
80
+ max_tokens: 512,
81
+ messages: [{
82
+ role: "user",
83
+ content: prompt
84
+ }]
85
+ })
86
+ });
87
+ if (!response.ok) {
88
+ return { ids: [] };
89
+ }
90
+ const data = await response.json();
91
+ const text = data.content?.[0]?.text ?? "";
92
+ return this._parseResponse(text, available);
93
+ } catch {
94
+ return { ids: [] };
95
+ }
96
+ }
97
+ /** Parse Haiku response — handles both `{ ids, note }` and bare `[...]` formats. */
98
+ _parseResponse(text, available) {
99
+ const validIds = new Set(available.map((m) => m.id));
100
+ const objMatch = text.match(/\{[\s\S]*"ids"\s*:\s*\[[\d\s,]*\][\s\S]*\}/);
101
+ if (objMatch) {
102
+ try {
103
+ const parsed = JSON.parse(objMatch[0]);
104
+ const ids = parsed.ids.filter((id) => validIds.has(id));
105
+ const note = parsed.note?.trim() || void 0;
106
+ return { ids, note };
107
+ } catch {
108
+ }
109
+ }
110
+ const arrMatch = text.match(/\[[\d\s,]*\]/);
111
+ if (arrMatch) {
112
+ const ids = JSON.parse(arrMatch[0]).filter((id) => validIds.has(id));
113
+ return { ids };
114
+ }
115
+ return { ids: [] };
116
+ }
117
+ async close() {
118
+ }
119
+ };
120
+
121
+ export {
122
+ HaikuExpander
123
+ };
124
+ //# sourceMappingURL=chunk-OPH7GZ7U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/pruners/haiku-expander.ts"],"sourcesContent":["/**\n * BrainBank — Haiku Expander\n *\n * LLM-powered context expansion using Anthropic's Haiku 4.5 model.\n * After search + pruning, reviews a manifest of available chunks\n * and requests additional IDs to include.\n *\n * Flow:\n * 1. Receives lightweight manifest (~20 chars per chunk)\n * 2. Haiku selects additional chunk IDs (just numbers, fast)\n * 3. Caller fetches those chunks from DB and splices into results\n *\n * Designed for minimal token usage:\n * - Input: ~2,000-3,000 tokens (manifest)\n * - Output: ~50-100 tokens (ID array)\n * - Cost: ~$0.001 per call\n * - Latency: ~300-600ms\n *\n * Fail-open: any error returns empty array (no expansion).\n */\n\nimport type { Expander, ExpanderManifestItem, ExpanderResult } from '@/types.ts';\n\nconst DEFAULT_MODEL = 'claude-haiku-4-5-20251001';\n\nexport interface HaikuExpanderOptions {\n /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Model to use. Default: claude-haiku-4-5-20251001 */\n model?: string;\n}\n\nexport class HaikuExpander implements Expander {\n private readonly _apiKey: string;\n private readonly _model: string;\n\n constructor(options: HaikuExpanderOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n\n if (!this._apiKey) {\n throw new Error(\n 'HaikuExpander: No API key provided. Set ANTHROPIC_API_KEY env var or pass apiKey option.',\n );\n }\n }\n\n async expand(\n query: string,\n currentIds: number[],\n manifest: ExpanderManifestItem[],\n ): Promise<ExpanderResult> {\n if (manifest.length === 0) return { ids: [] };\n\n // Filter out chunks already in results\n const currentSet = new Set(currentIds);\n const available = manifest.filter(m => !currentSet.has(m.id));\n if (available.length === 0) return { ids: [] };\n\n // Split manifest into priority (import-graph neighbors) and general\n const priorityChunks = available.filter(m => m.priority);\n const otherChunks = available.filter(m => !m.priority);\n\n const currentSummary = manifest\n .filter(m => currentSet.has(m.id))\n .map(m => `#${m.id} ${m.filePath} | ${m.chunkType} ${m.name}`)\n .join('\\n');\n\n // Build manifest sections\n let manifestSection = '';\n if (priorityChunks.length > 0) {\n const prioLines = priorityChunks.map(m =>\n `#${m.id} ${m.filePath} | ${m.chunkType} ${m.name} ${m.lines}`\n ).join('\\n');\n manifestSection += `DEPENDENCY chunks (imported by or importing the search result files):\\n${prioLines}\\n\\n`;\n }\n if (otherChunks.length > 0) {\n const otherLines = otherChunks.map(m =>\n `#${m.id} ${m.filePath} | ${m.chunkType} ${m.name} ${m.lines}`\n ).join('\\n');\n manifestSection += `Other available chunks:\\n${otherLines}`;\n }\n\n const prompt =\n `Task: \"${query}\"\\n\\n` +\n `Already included chunks:\\n${currentSummary}\\n\\n` +\n `${manifestSection}\\n\\n` +\n `You are a code context expander. The search already found the \"included\" chunks above.\\n` +\n `Review the available chunks and select any that would help an AI agent complete the task.\\n\\n` +\n `Rules:\\n` +\n `- STRONGLY PREFER dependency chunks — they are structurally connected to the search results via imports\\n` +\n `- Select type definitions, interfaces, models, or configs needed to understand included code\\n` +\n `- Select initialization or setup code if the task involves debugging or modifying a feature\\n` +\n `- Do NOT select test files, documentation, or unrelated utilities\\n` +\n `- Be selective: only include chunks that fill clear gaps. Quality over quantity.\\n` +\n `- If nothing useful is available, return an empty ids array\\n\\n` +\n `Respond with ONLY a JSON object:\\n` +\n `{ \"ids\": [42, 17, 89], \"note\": \"Brief 1-2 sentence observation about the codebase relevant to the task\" }\\n\\n` +\n `The \"note\" is optional — use it to mention things like missing files, architectural patterns, ` +\n `deprecated modules, or important relationships you noticed. If nothing notable, omit it.\\n` +\n `If nothing to add: { \"ids\": [] }`;\n\n try {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this._apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: this._model,\n max_tokens: 512,\n messages: [{\n role: 'user',\n content: prompt,\n }],\n }),\n });\n\n if (!response.ok) {\n return { ids: [] };\n }\n\n const data = await response.json() as {\n content: { type: string; text: string }[];\n };\n\n const text = data.content?.[0]?.text ?? '';\n return this._parseResponse(text, available);\n } catch {\n return { ids: [] };\n }\n }\n\n /** Parse Haiku response — handles both `{ ids, note }` and bare `[...]` formats. */\n private _parseResponse(text: string, available: ExpanderManifestItem[]): ExpanderResult {\n const validIds = new Set(available.map(m => m.id));\n\n // Try JSON object first: { \"ids\": [...], \"note\": \"...\" }\n const objMatch = text.match(/\\{[\\s\\S]*\"ids\"\\s*:\\s*\\[[\\d\\s,]*\\][\\s\\S]*\\}/);\n if (objMatch) {\n try {\n const parsed = JSON.parse(objMatch[0]) as { ids: number[]; note?: string };\n const ids = parsed.ids.filter(id => validIds.has(id));\n const note = parsed.note?.trim() || undefined;\n return { ids, note };\n } catch {\n // Fall through to array parsing\n }\n }\n\n // Fallback: bare array [42, 17, 89]\n const arrMatch = text.match(/\\[[\\d\\s,]*\\]/);\n if (arrMatch) {\n const ids = (JSON.parse(arrMatch[0]) as number[]).filter(id => validIds.has(id));\n return { ids };\n }\n\n return { ids: [] };\n }\n\n async close(): Promise<void> {\n // No resources to release (stateless HTTP)\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,89 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-WCQVDF3K.js";
4
+
5
+ // src/providers/pruners/haiku-pruner.ts
6
+ var DEFAULT_MODEL = "claude-haiku-4-5-20251001";
7
+ var HaikuPruner = class {
8
+ static {
9
+ __name(this, "HaikuPruner");
10
+ }
11
+ _apiKey;
12
+ _model;
13
+ constructor(options = {}) {
14
+ this._apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
15
+ this._model = options.model ?? DEFAULT_MODEL;
16
+ if (!this._apiKey) {
17
+ throw new Error(
18
+ "HaikuPruner: No API key provided. Set ANTHROPIC_API_KEY env var or pass apiKey option."
19
+ );
20
+ }
21
+ }
22
+ async prune(query, items) {
23
+ if (items.length === 0) return [];
24
+ if (items.length === 1) return [items[0].id];
25
+ const itemLines = items.map((item) => {
26
+ const SKIP_KEYS = /* @__PURE__ */ new Set(["id", "chunkIds", "rrfScore", "filePath"]);
27
+ const meta = Object.entries(item.metadata).filter(([k, v]) => v !== void 0 && v !== null && !SKIP_KEYS.has(k)).map(([k, v]) => `${k}=${v}`).join(" | ");
28
+ return `#${item.id} ${item.filePath} | ${meta}
29
+ ${item.preview}`;
30
+ }).join("\n---\n");
31
+ const prompt = `Query: "${query}"
32
+
33
+ Search results (full file content):
34
+ ${itemLines}
35
+
36
+ You are a precision search filter and ranker. You have the FULL source code of each file.
37
+ Return a JSON array of #IDs to KEEP, ordered by relevance (most relevant FIRST).
38
+
39
+ Rules:
40
+ - Understand the SPECIFIC system/feature the query targets. Don't match on shared vocabulary alone.
41
+ Example: "snackbar toast notification" targets the toast popup system, NOT a notification center/bell icon.
42
+ - KEEP files that directly implement, define types for, or configure the queried system.
43
+ - KEEP files where the queried system is mounted, initialized, or composed into a workflow.
44
+ - DROP files that only CONSUME the system (e.g. a component that calls showNotification once but has 400 lines of unrelated logic).
45
+ - DROP files that implement a DIFFERENT system sharing similar vocabulary.
46
+ - DROP infrastructure/boilerplate (font loaders, theme definitions, CSS-only layout shells) unless they directly configure the queried feature.
47
+ - Aim for 40-70% keep rate. Returning fewer, highly relevant files is BETTER than returning many tangential ones.
48
+ - ORDER: core implementation \u2192 types/config \u2192 mount points \u2192 peripheral.
49
+
50
+ Respond with ONLY the JSON array. Example: [3, 0, 5, 1]`;
51
+ try {
52
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ "x-api-key": this._apiKey,
57
+ "anthropic-version": "2023-06-01"
58
+ },
59
+ body: JSON.stringify({
60
+ model: this._model,
61
+ max_tokens: 512,
62
+ messages: [{
63
+ role: "user",
64
+ content: prompt
65
+ }]
66
+ })
67
+ });
68
+ if (!response.ok) {
69
+ return items.map((i) => i.id);
70
+ }
71
+ const data = await response.json();
72
+ const text = data.content?.[0]?.text ?? "";
73
+ const match = text.match(/\[[\d\s,]+\]/);
74
+ if (!match) return items.map((i) => i.id);
75
+ const keepIds = JSON.parse(match[0]);
76
+ const validIds = new Set(items.map((i) => i.id));
77
+ return keepIds.filter((id) => validIds.has(id));
78
+ } catch {
79
+ return items.map((i) => i.id);
80
+ }
81
+ }
82
+ async close() {
83
+ }
84
+ };
85
+
86
+ export {
87
+ HaikuPruner
88
+ };
89
+ //# sourceMappingURL=chunk-PXEWQMN7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/pruners/haiku-pruner.ts"],"sourcesContent":["/**\n * BrainBank — Haiku Pruner\n *\n * LLM-based noise filter using Anthropic's Haiku 4.5 model.\n * Binary classification: for each search result, Haiku decides\n * \"relevant\" or \"noise\" based on filePath, metadata, and full\n * file content (capped at ~8K chars per item by prune.ts).\n *\n * Latency: ~300-600ms.\n */\n\nimport type { Pruner, PrunerItem } from '@/types.ts';\n\nconst DEFAULT_MODEL = 'claude-haiku-4-5-20251001';\n\nexport interface HaikuPrunerOptions {\n /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Model to use. Default: claude-haiku-4-5-20251001 */\n model?: string;\n}\n\nexport class HaikuPruner implements Pruner {\n private readonly _apiKey: string;\n private readonly _model: string;\n\n constructor(options: HaikuPrunerOptions = {}) {\n this._apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? '';\n this._model = options.model ?? DEFAULT_MODEL;\n\n if (!this._apiKey) {\n throw new Error(\n 'HaikuPruner: No API key provided. Set ANTHROPIC_API_KEY env var or pass apiKey option.',\n );\n }\n }\n\n async prune(query: string, items: PrunerItem[]): Promise<number[]> {\n if (items.length === 0) return [];\n if (items.length === 1) return [items[0].id];\n\n const itemLines = items.map(item => {\n // Only show useful metadata fields (skip raw scores, IDs, large arrays)\n const SKIP_KEYS = new Set(['id', 'chunkIds', 'rrfScore', 'filePath']);\n const meta = Object.entries(item.metadata)\n .filter(([k, v]) => v !== undefined && v !== null && !SKIP_KEYS.has(k))\n .map(([k, v]) => `${k}=${v}`)\n .join(' | ');\n return `#${item.id} ${item.filePath} | ${meta}\\n${item.preview}`;\n }).join('\\n---\\n');\n\n const prompt =\n `Query: \"${query}\"\\n\\nSearch results (full file content):\\n${itemLines}\\n\\n` +\n `You are a precision search filter and ranker. You have the FULL source code of each file.\\n` +\n `Return a JSON array of #IDs to KEEP, ordered by relevance (most relevant FIRST).\\n\\n` +\n `Rules:\\n` +\n `- Understand the SPECIFIC system/feature the query targets. Don't match on shared vocabulary alone.\\n` +\n ` Example: \"snackbar toast notification\" targets the toast popup system, NOT a notification center/bell icon.\\n` +\n `- KEEP files that directly implement, define types for, or configure the queried system.\\n` +\n `- KEEP files where the queried system is mounted, initialized, or composed into a workflow.\\n` +\n `- DROP files that only CONSUME the system (e.g. a component that calls showNotification once but has 400 lines of unrelated logic).\\n` +\n `- DROP files that implement a DIFFERENT system sharing similar vocabulary.\\n` +\n `- DROP infrastructure/boilerplate (font loaders, theme definitions, CSS-only layout shells) unless they directly configure the queried feature.\\n` +\n `- Aim for 40-70% keep rate. Returning fewer, highly relevant files is BETTER than returning many tangential ones.\\n` +\n `- ORDER: core implementation → types/config → mount points → peripheral.\\n\\n` +\n `Respond with ONLY the JSON array. Example: [3, 0, 5, 1]`;\n\n try {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this._apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: this._model,\n max_tokens: 512,\n messages: [{\n role: 'user',\n content: prompt,\n }],\n }),\n });\n\n if (!response.ok) {\n // API error → fail-open, return all\n return items.map(i => i.id);\n }\n\n const data = await response.json() as {\n content: { type: string; text: string }[];\n };\n\n const text = data.content?.[0]?.text ?? '';\n // Haiku may wrap in ```json ... ``` — extract any JSON array\n const match = text.match(/\\[[\\d\\s,]+\\]/);\n if (!match) return items.map(i => i.id);\n\n const keepIds = JSON.parse(match[0]) as number[];\n const validIds = new Set(items.map(i => i.id));\n return keepIds.filter(id => validIds.has(id));\n } catch {\n // Network error → fail-open, return all\n return items.map(i => i.id);\n }\n }\n\n async close(): Promise<void> {\n // No resources to release (stateless HTTP)\n }\n}\n"],"mappings":";;;;;AAaA,IAAM,gBAAgB;AASf,IAAM,cAAN,MAAoC;AAAA,EAtB3C,OAsB2C;AAAA;AAAA;AAAA,EACtB;AAAA,EACA;AAAA,EAEjB,YAAY,UAA8B,CAAC,GAAG;AAC1C,SAAK,UAAU,QAAQ,UAAU,QAAQ,IAAI,qBAAqB;AAClE,SAAK,SAAS,QAAQ,SAAS;AAE/B,QAAI,CAAC,KAAK,SAAS;AACf,YAAM,IAAI;AAAA,QACN;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAM,MAAM,OAAe,OAAwC;AAC/D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC,MAAM,CAAC,EAAE,EAAE;AAE3C,UAAM,YAAY,MAAM,IAAI,UAAQ;AAEhC,YAAM,YAAY,oBAAI,IAAI,CAAC,MAAM,YAAY,YAAY,UAAU,CAAC;AACpE,YAAM,OAAO,OAAO,QAAQ,KAAK,QAAQ,EACpC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,MAAM,UAAa,MAAM,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC,EACrE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAC3B,KAAK,KAAK;AACf,aAAO,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ,MAAM,IAAI;AAAA,EAAK,KAAK,OAAO;AAAA,IAClE,CAAC,EAAE,KAAK,SAAS;AAEjB,UAAM,SACF,WAAW,KAAK;AAAA;AAAA;AAAA,EAA6C,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAe1E,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,yCAAyC;AAAA,QAClE,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,UAChB,aAAa,KAAK;AAAA,UAClB,qBAAqB;AAAA,QACzB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACjB,OAAO,KAAK;AAAA,UACZ,YAAY;AAAA,UACZ,UAAU,CAAC;AAAA,YACP,MAAM;AAAA,YACN,SAAS;AAAA,UACb,CAAC;AAAA,QACL,CAAC;AAAA,MACL,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAEd,eAAO,MAAM,IAAI,OAAK,EAAE,EAAE;AAAA,MAC9B;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AAIjC,YAAM,OAAO,KAAK,UAAU,CAAC,GAAG,QAAQ;AAExC,YAAM,QAAQ,KAAK,MAAM,cAAc;AACvC,UAAI,CAAC,MAAO,QAAO,MAAM,IAAI,OAAK,EAAE,EAAE;AAEtC,YAAM,UAAU,KAAK,MAAM,MAAM,CAAC,CAAC;AACnC,YAAM,WAAW,IAAI,IAAI,MAAM,IAAI,OAAK,EAAE,EAAE,CAAC;AAC7C,aAAO,QAAQ,OAAO,QAAM,SAAS,IAAI,EAAE,CAAC;AAAA,IAChD,QAAQ;AAEJ,aAAO,MAAM,IAAI,OAAK,EAAE,EAAE;AAAA,IAC9B;AAAA,EACJ;AAAA,EAEA,MAAM,QAAuB;AAAA,EAE7B;AACJ;","names":[]}
@@ -0,0 +1,69 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-WCQVDF3K.js";
4
+
5
+ // src/services/daemon.ts
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import * as os from "os";
9
+ var DEFAULT_PORT = 8181;
10
+ function cacheDir() {
11
+ return path.join(os.homedir(), ".cache", "brainbank");
12
+ }
13
+ __name(cacheDir, "cacheDir");
14
+ function pidPath() {
15
+ return path.join(cacheDir(), "server.pid");
16
+ }
17
+ __name(pidPath, "pidPath");
18
+ function writePid(pid, port) {
19
+ const dir = cacheDir();
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ fs.writeFileSync(pidPath(), JSON.stringify({ pid, port }));
22
+ }
23
+ __name(writePid, "writePid");
24
+ function readPid() {
25
+ try {
26
+ const raw = fs.readFileSync(pidPath(), "utf8");
27
+ const info = JSON.parse(raw);
28
+ if (typeof info.pid !== "number" || typeof info.port !== "number") return null;
29
+ return info;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ __name(readPid, "readPid");
35
+ function removePid() {
36
+ try {
37
+ fs.unlinkSync(pidPath());
38
+ } catch {
39
+ }
40
+ }
41
+ __name(removePid, "removePid");
42
+ function isServerRunning() {
43
+ const info = readPid();
44
+ if (!info) return null;
45
+ try {
46
+ process.kill(info.pid, 0);
47
+ return info;
48
+ } catch {
49
+ removePid();
50
+ return null;
51
+ }
52
+ }
53
+ __name(isServerRunning, "isServerRunning");
54
+ function getServerUrl() {
55
+ const info = isServerRunning();
56
+ if (!info) return null;
57
+ return `http://localhost:${info.port}`;
58
+ }
59
+ __name(getServerUrl, "getServerUrl");
60
+
61
+ export {
62
+ DEFAULT_PORT,
63
+ writePid,
64
+ readPid,
65
+ removePid,
66
+ isServerRunning,
67
+ getServerUrl
68
+ };
69
+ //# sourceMappingURL=chunk-RDQYDLYZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/daemon.ts"],"sourcesContent":["/**\n * Daemon — PID file management for the BrainBank HTTP server.\n *\n * PID file: ~/.cache/brainbank/server.pid\n * Format: JSON { pid: number, port: number }\n *\n * Used by:\n * - `brainbank daemon` to write PID on start\n * - `brainbank daemon stop` to find and kill the daemon\n * - `brainbank status` to report server state\n * - CLI commands to detect running server for delegation\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\n\nexport const DEFAULT_PORT = 8181;\n\ninterface PidInfo {\n pid: number;\n port: number;\n}\n\n/** Directory for PID file and other cache data. */\nfunction cacheDir(): string {\n return path.join(os.homedir(), '.cache', 'brainbank');\n}\n\n/** Full path to the PID file. */\nfunction pidPath(): string {\n return path.join(cacheDir(), 'server.pid');\n}\n\n/** Write the PID file after server starts. */\nexport function writePid(pid: number, port: number): void {\n const dir = cacheDir();\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(pidPath(), JSON.stringify({ pid, port } as PidInfo));\n}\n\n/** Read PID info from file. Returns null if missing or malformed. */\nexport function readPid(): PidInfo | null {\n try {\n const raw = fs.readFileSync(pidPath(), 'utf8');\n const info = JSON.parse(raw) as PidInfo;\n if (typeof info.pid !== 'number' || typeof info.port !== 'number') return null;\n return info;\n } catch {\n return null;\n }\n}\n\n/** Remove the PID file. */\nexport function removePid(): void {\n try {\n fs.unlinkSync(pidPath());\n } catch {\n // File doesn't exist — safe to ignore\n }\n}\n\n/**\n * Check if the server process is still alive.\n * Uses `kill(pid, 0)` which doesn't actually send a signal —\n * it just checks whether the process exists.\n */\nexport function isServerRunning(): PidInfo | null {\n const info = readPid();\n if (!info) return null;\n\n try {\n process.kill(info.pid, 0);\n return info;\n } catch {\n // Process not running — stale PID file\n removePid();\n return null;\n }\n}\n\n/** Get the server URL if running, or null. */\nexport function getServerUrl(): string | null {\n const info = isServerRunning();\n if (!info) return null;\n return `http://localhost:${info.port}`;\n}\n"],"mappings":";;;;;AAaA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,YAAY,QAAQ;AAEb,IAAM,eAAe;AAQ5B,SAAS,WAAmB;AACxB,SAAY,UAAQ,WAAQ,GAAG,UAAU,WAAW;AACxD;AAFS;AAKT,SAAS,UAAkB;AACvB,SAAY,UAAK,SAAS,GAAG,YAAY;AAC7C;AAFS;AAKF,SAAS,SAAS,KAAa,MAAoB;AACtD,QAAM,MAAM,SAAS;AACrB,EAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACrC,EAAG,iBAAc,QAAQ,GAAG,KAAK,UAAU,EAAE,KAAK,KAAK,CAAY,CAAC;AACxE;AAJgB;AAOT,SAAS,UAA0B;AACtC,MAAI;AACA,UAAM,MAAS,gBAAa,QAAQ,GAAG,MAAM;AAC7C,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,QAAI,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,SAAS,SAAU,QAAO;AAC1E,WAAO;AAAA,EACX,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AATgB;AAYT,SAAS,YAAkB;AAC9B,MAAI;AACA,IAAG,cAAW,QAAQ,CAAC;AAAA,EAC3B,QAAQ;AAAA,EAER;AACJ;AANgB;AAaT,SAAS,kBAAkC;AAC9C,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI;AACA,YAAQ,KAAK,KAAK,KAAK,CAAC;AACxB,WAAO;AAAA,EACX,QAAQ;AAEJ,cAAU;AACV,WAAO;AAAA,EACX;AACJ;AAZgB;AAeT,SAAS,eAA8B;AAC1C,QAAM,OAAO,gBAAgB;AAC7B,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,oBAAoB,KAAK,IAAI;AACxC;AAJgB;","names":[]}
@@ -0,0 +1,254 @@
1
+ import {
2
+ DEFAULT_PORT,
3
+ removePid,
4
+ writePid
5
+ } from "./chunk-RDQYDLYZ.js";
6
+ import {
7
+ __name
8
+ } from "./chunk-WCQVDF3K.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 "/search":
146
+ return this._handleSearch(body, res, "search");
147
+ case "/hsearch":
148
+ return this._handleSearch(body, res, "hybrid");
149
+ case "/ksearch":
150
+ return this._handleSearch(body, res, "keyword");
151
+ case "/index":
152
+ return this._handleIndex(body, res);
153
+ default:
154
+ res.writeHead(404);
155
+ res.end(JSON.stringify({ error: "Not found" }));
156
+ }
157
+ }
158
+ // ── Handlers ────────────────────────────────────────
159
+ _handleHealth(res) {
160
+ const uptime = Math.round((Date.now() - this._startTime) / 1e3);
161
+ res.writeHead(200);
162
+ res.end(JSON.stringify({
163
+ ok: true,
164
+ pid: process.pid,
165
+ port: this._port,
166
+ uptime,
167
+ workspaces: this._pool.size
168
+ }));
169
+ }
170
+ async _handleContext(body, res) {
171
+ const req = body;
172
+ if (!req.task) {
173
+ res.writeHead(400);
174
+ res.end(JSON.stringify({ error: "Missing required field: task" }));
175
+ return;
176
+ }
177
+ const repoPath = req.repo ?? this._defaultRepo;
178
+ const brain = await this._pool.get(repoPath);
179
+ const base = {
180
+ code: req.codeResults ?? 20,
181
+ git: req.gitResults ?? 5
182
+ };
183
+ if (req.docsResults !== void 0) base.docs = req.docsResults;
184
+ const resolvedSources = req.sources ? { ...base, ...req.sources } : base;
185
+ const context = await brain.getContext(req.task, {
186
+ affectedFiles: req.affectedFiles,
187
+ sources: resolvedSources,
188
+ pathPrefix: req.pathPrefix,
189
+ source: "daemon"
190
+ });
191
+ res.writeHead(200);
192
+ res.end(JSON.stringify({ context }));
193
+ }
194
+ async _handleIndex(body, res) {
195
+ const req = body;
196
+ const repoPath = req.repo ?? this._defaultRepo;
197
+ const brain = await this._pool.get(repoPath);
198
+ const result = await brain.index({ forceReindex: req.forceReindex });
199
+ res.writeHead(200);
200
+ res.end(JSON.stringify({ result }));
201
+ }
202
+ async _handleSearch(body, res, mode) {
203
+ const req = body;
204
+ if (!req.query) {
205
+ res.writeHead(400);
206
+ res.end(JSON.stringify({ error: "Missing required field: query" }));
207
+ return;
208
+ }
209
+ const repoPath = req.repo ?? this._defaultRepo;
210
+ const brain = await this._pool.get(repoPath);
211
+ const opts = {
212
+ sources: req.sources,
213
+ pathPrefix: req.pathPrefix,
214
+ source: "daemon"
215
+ };
216
+ let results;
217
+ switch (mode) {
218
+ case "search":
219
+ results = await brain.search(req.query, opts);
220
+ break;
221
+ case "hybrid":
222
+ results = await brain.hybridSearch(req.query, opts);
223
+ break;
224
+ case "keyword":
225
+ results = await brain.searchBM25(req.query, opts);
226
+ break;
227
+ }
228
+ const maxResults = req.maxResults ?? 20;
229
+ results = results.slice(0, maxResults);
230
+ res.writeHead(200);
231
+ res.end(JSON.stringify({ results }));
232
+ }
233
+ // ── Helpers ─────────────────────────────────────────
234
+ _readBody(req) {
235
+ return new Promise((resolve, reject) => {
236
+ const chunks = [];
237
+ req.on("data", (chunk) => chunks.push(chunk));
238
+ req.on("error", reject);
239
+ req.on("end", () => {
240
+ try {
241
+ const raw = Buffer.concat(chunks).toString("utf8");
242
+ resolve(raw ? JSON.parse(raw) : {});
243
+ } catch {
244
+ resolve({});
245
+ }
246
+ });
247
+ });
248
+ }
249
+ };
250
+
251
+ export {
252
+ HttpServer
253
+ };
254
+ //# sourceMappingURL=chunk-VIIHPCC4.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 /search → brain.search()\n * POST /hsearch → brain.hybridSearch()\n * POST /ksearch → brain.searchBM25()\n * POST /index → brain.index()\n * GET /health → { ok, pid, uptime, port }\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 | 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 SearchRequest {\n query: string;\n repo?: string;\n sources?: Record<string, number>;\n pathPrefix?: string | string[];\n maxResults?: number;\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 workspace management.\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 '/search':\n return this._handleSearch(body, res, 'search');\n case '/hsearch':\n return this._handleSearch(body, res, 'hybrid');\n case '/ksearch':\n return this._handleSearch(body, res, 'keyword');\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 private async _handleSearch(\n body: unknown, res: http.ServerResponse,\n mode: 'search' | 'hybrid' | 'keyword',\n ): Promise<void> {\n const req = body as SearchRequest;\n if (!req.query) {\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Missing required field: query' }));\n return;\n }\n\n const repoPath = req.repo ?? this._defaultRepo;\n const brain = await this._pool.get(repoPath);\n\n const opts = {\n sources: req.sources,\n pathPrefix: req.pathPrefix,\n source: 'daemon' as const,\n };\n\n let results;\n switch (mode) {\n case 'search': results = await brain.search(req.query, opts); break;\n case 'hybrid': results = await brain.hybridSearch(req.query, opts); break;\n case 'keyword': results = await brain.searchBM25(req.query, opts); break;\n }\n\n const maxResults = req.maxResults ?? 20;\n results = results.slice(0, maxResults);\n\n res.writeHead(200);\n res.end(JSON.stringify({ results }));\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":"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","names":[]}
@@ -0,0 +1,14 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ export {
11
+ __name,
12
+ __require
13
+ };
14
+ //# sourceMappingURL=chunk-WCQVDF3K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node