brainbank 0.1.3-beta.1 → 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/dist/{chunk-ZEUCCE6P.js → chunk-3UIWA32X.js} +32 -91
- package/dist/chunk-3UIWA32X.js.map +1 -0
- package/dist/{chunk-JMHBINOJ.js → chunk-DAGVUEXL.js} +4 -2
- package/dist/chunk-DAGVUEXL.js.map +1 -0
- package/dist/chunk-NNDY7P2R.js +211 -0
- package/dist/chunk-NNDY7P2R.js.map +1 -0
- package/dist/cli.js +111 -73
- package/dist/cli.js.map +1 -1
- package/dist/{haiku-pruner-MT4JFKUC.js → haiku-pruner-5KVT5AI2.js} +2 -2
- package/dist/{http-server-RQHCN3QG.js → http-server-2ZQ6I43B.js} +2 -2
- package/dist/index.d.ts +25 -57
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +58 -6
- package/dist/mcp.js.map +1 -1
- package/dist/{stats-tui-SEHCVG5K.js → stats-tui-AD3AMYGV.js} +2 -2
- package/package.json +1 -1
- package/src/cli/commands/context.ts +8 -130
- package/src/cli/commands/help.ts +8 -4
- package/src/cli/commands/index.ts +4 -8
- package/src/cli/commands/query.ts +167 -0
- package/src/cli/factory/plugin-loader.ts +12 -13
- package/src/cli/index.ts +3 -0
- package/src/cli/server-client.ts +4 -0
- package/src/config.ts +0 -1
- package/src/engine/search-api.ts +1 -1
- package/src/index.ts +0 -4
- package/src/lib/languages.ts +0 -1
- package/src/lib/prune.ts +2 -1
- package/src/mcp/mcp-server.ts +77 -4
- package/src/providers/pruners/haiku-expander.ts +6 -1
- package/src/providers/pruners/haiku-pruner.ts +166 -25
- package/src/search/bm25-boost.ts +8 -1
- package/src/search/context-builder.ts +21 -90
- package/src/services/http-server.ts +4 -0
- package/src/types.ts +16 -2
- package/dist/chunk-3QVAKPTK.js +0 -102
- package/dist/chunk-3QVAKPTK.js.map +0 -1
- package/dist/chunk-5QUZ6CYK.js +0 -134
- package/dist/chunk-5QUZ6CYK.js.map +0 -1
- package/dist/chunk-JMHBINOJ.js.map +0 -1
- package/dist/chunk-ZEUCCE6P.js.map +0 -1
- package/dist/haiku-expander-CGEHFJIA.js +0 -8
- package/dist/http-server-RQHCN3QG.js.map +0 -1
- /package/dist/{haiku-expander-CGEHFJIA.js.map → haiku-pruner-5KVT5AI2.js.map} +0 -0
- /package/dist/{haiku-pruner-MT4JFKUC.js.map → http-server-2ZQ6I43B.js.map} +0 -0
- /package/dist/{stats-tui-SEHCVG5K.js.map → stats-tui-AD3AMYGV.js.map} +0 -0
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
isContextFieldPlugin,
|
|
8
8
|
isContextFormatterPlugin,
|
|
9
9
|
isDocsPlugin,
|
|
10
|
-
isExpandablePlugin,
|
|
11
10
|
isFileResolvable,
|
|
12
11
|
isIndexable,
|
|
13
12
|
isReembeddable,
|
|
@@ -51,7 +50,6 @@ function resolveConfig(partial = {}) {
|
|
|
51
50
|
maxElements: partial.maxElements ?? DEFAULTS.maxElements,
|
|
52
51
|
embeddingProvider: partial.embeddingProvider,
|
|
53
52
|
pruner: partial.pruner,
|
|
54
|
-
expander: partial.expander,
|
|
55
53
|
webhookPort: partial.webhookPort,
|
|
56
54
|
contextFields: partial.contextFields
|
|
57
55
|
};
|
|
@@ -533,7 +531,7 @@ __name(fuseRankedLists, "fuseRankedLists");
|
|
|
533
531
|
|
|
534
532
|
// src/lib/prune.ts
|
|
535
533
|
var MAX_PREVIEW_CHARS = 8e3;
|
|
536
|
-
async function pruneResults(query, results, pruner) {
|
|
534
|
+
async function pruneResults(query, results, pruner, context) {
|
|
537
535
|
if (results.length <= 1) return results;
|
|
538
536
|
const items = results.map((r, i) => ({
|
|
539
537
|
id: i,
|
|
@@ -541,7 +539,7 @@ async function pruneResults(query, results, pruner) {
|
|
|
541
539
|
preview: _buildPreview(r.content),
|
|
542
540
|
metadata: r.metadata
|
|
543
541
|
}));
|
|
544
|
-
const keepIds = await pruner.prune(query, items);
|
|
542
|
+
const keepIds = await pruner.prune(query, items, context);
|
|
545
543
|
const validIds = new Set(Array.from({ length: results.length }, (_, i) => i));
|
|
546
544
|
return keepIds.filter((id) => validIds.has(id)).map((id) => results[id]);
|
|
547
545
|
}
|
|
@@ -568,7 +566,11 @@ function filterByPath(results, prefix) {
|
|
|
568
566
|
if (!prefix) return results;
|
|
569
567
|
const prefixes = Array.isArray(prefix) ? prefix : [prefix];
|
|
570
568
|
if (prefixes.length === 0) return results;
|
|
571
|
-
|
|
569
|
+
const normalized = prefixes.map((p) => {
|
|
570
|
+
if (p.endsWith("/") || p.includes(".")) return p;
|
|
571
|
+
return p + "/";
|
|
572
|
+
});
|
|
573
|
+
return results.filter((r) => normalized.some((p) => r.filePath?.startsWith(p)));
|
|
572
574
|
}
|
|
573
575
|
__name(filterByPath, "filterByPath");
|
|
574
576
|
function filterByIgnore(results, ignorePaths) {
|
|
@@ -652,20 +654,18 @@ function dbg(msg) {
|
|
|
652
654
|
}
|
|
653
655
|
__name(dbg, "dbg");
|
|
654
656
|
var ContextBuilder = class {
|
|
655
|
-
constructor(_search, _registry, _pruner, _embedding, _configFields = {}
|
|
657
|
+
constructor(_search, _registry, _pruner, _embedding, _configFields = {}) {
|
|
656
658
|
this._search = _search;
|
|
657
659
|
this._registry = _registry;
|
|
658
660
|
this._pruner = _pruner;
|
|
659
661
|
this._embedding = _embedding;
|
|
660
662
|
this._configFields = _configFields;
|
|
661
|
-
this._expander = _expander;
|
|
662
663
|
}
|
|
663
664
|
_search;
|
|
664
665
|
_registry;
|
|
665
666
|
_pruner;
|
|
666
667
|
_embedding;
|
|
667
668
|
_configFields;
|
|
668
|
-
_expander;
|
|
669
669
|
static {
|
|
670
670
|
__name(this, "ContextBuilder");
|
|
671
671
|
}
|
|
@@ -673,10 +673,6 @@ var ContextBuilder = class {
|
|
|
673
673
|
set configFields(fields) {
|
|
674
674
|
this._configFields = fields;
|
|
675
675
|
}
|
|
676
|
-
/** Set the expander instance. */
|
|
677
|
-
set expander(expander) {
|
|
678
|
-
this._expander = expander;
|
|
679
|
-
}
|
|
680
676
|
/** Build a full context block for a task. Returns markdown for system prompt. */
|
|
681
677
|
async build(task, options = {}) {
|
|
682
678
|
const t0 = Date.now();
|
|
@@ -692,10 +688,20 @@ var ContextBuilder = class {
|
|
|
692
688
|
results = filterByIgnore(results, options.ignorePaths);
|
|
693
689
|
const pruner = options.pruner ?? this._pruner;
|
|
694
690
|
const beforePrune = results;
|
|
691
|
+
if (pruner && results.length > 40) {
|
|
692
|
+
const topScore = Math.max(...results.map((r) => r.score));
|
|
693
|
+
const threshold = topScore * 0.35;
|
|
694
|
+
const preFiltered = results.filter((r) => r.score >= threshold);
|
|
695
|
+
if (preFiltered.length < results.length && preFiltered.length >= 3) {
|
|
696
|
+
dbg(`[pre-filter] Dropped ${results.length - preFiltered.length} low-score results (threshold: ${threshold.toFixed(3)}, top: ${topScore.toFixed(3)})`);
|
|
697
|
+
results = preFiltered;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
695
700
|
if (pruner && results.length > 1) {
|
|
696
701
|
dbg(`[pruner] Running ${_prunerName(pruner)} on ${results.length} results...`);
|
|
697
702
|
const pruneT0 = Date.now();
|
|
698
|
-
|
|
703
|
+
const prunerDesc = [options.context, options.prunerContext].filter(Boolean).join("\n\n") || void 0;
|
|
704
|
+
results = await pruneResults(task, results, pruner, prunerDesc);
|
|
699
705
|
const pruneMs = Date.now() - pruneT0;
|
|
700
706
|
const dropped = beforePrune.filter((r) => !results.includes(r));
|
|
701
707
|
dbg(`[pruner] ${beforePrune.length} \u2192 ${results.length} in ${pruneMs}ms (${dropped.length} dropped)`);
|
|
@@ -714,40 +720,18 @@ var ContextBuilder = class {
|
|
|
714
720
|
results = results.filter((r) => !r.filePath || !options.excludeFiles.has(r.filePath));
|
|
715
721
|
}
|
|
716
722
|
const resolvedFields = this._resolveFields(options);
|
|
717
|
-
let expanderNote;
|
|
718
|
-
if (resolvedFields.expander === true && this._expander && results.length > 0) {
|
|
719
|
-
dbg(`[expander] Running expansion on ${results.length} results...`);
|
|
720
|
-
const expansion = await this._expand(task, results);
|
|
721
|
-
dbg(`[expander] Expansion returned ${expansion.results.length} new chunks${expansion.note ? `, note: "${expansion.note}"` : ""}`);
|
|
722
|
-
if (expansion.results.length > 0) {
|
|
723
|
-
results = [...results, ...expansion.results];
|
|
724
|
-
}
|
|
725
|
-
expanderNote = expansion.note;
|
|
726
|
-
} else if (resolvedFields.expander === true && !this._expander) {
|
|
727
|
-
dbg(`[expander] Field enabled but no expander instance! Check config.json "expander" key.`);
|
|
728
|
-
}
|
|
729
723
|
const parts = [`# Context for: "${task}"
|
|
730
724
|
`];
|
|
731
725
|
this._appendFormatterResults(results, parts, options, resolvedFields);
|
|
732
726
|
await this._appendSearchableResults(task, src, minScore, parts);
|
|
733
|
-
if (expanderNote) {
|
|
734
|
-
parts.push(`
|
|
735
|
-
## Expansion Notes
|
|
736
|
-
|
|
737
|
-
${expanderNote}
|
|
738
|
-
`);
|
|
739
|
-
}
|
|
740
727
|
const prunedResults = pruner ? beforePrune.filter((r) => !results.includes(r)) : [];
|
|
741
|
-
const expanderEnabled = resolvedFields.expander === true;
|
|
742
|
-
const expandedResults = expanderNote !== void 0 ? results.filter((r) => !beforePrune.includes(r) && !prunedResults.includes(r)) : [];
|
|
743
728
|
logQuery({
|
|
744
729
|
source: options.source ?? "api",
|
|
745
730
|
method: "getContext",
|
|
746
731
|
query: task,
|
|
747
732
|
embedding: this._embedding ? providerKey(this._embedding) : "unknown",
|
|
748
733
|
pruner: pruner ? _prunerName(pruner) : null,
|
|
749
|
-
expander:
|
|
750
|
-
expandedCount: expandedResults.length > 0 ? expandedResults.length : void 0,
|
|
734
|
+
expander: null,
|
|
751
735
|
options: {
|
|
752
736
|
sources: src,
|
|
753
737
|
pathPrefix: options.pathPrefix,
|
|
@@ -787,44 +771,6 @@ ${expanderNote}
|
|
|
787
771
|
}
|
|
788
772
|
return { ...defaults, ...this._configFields, ...options.fields ?? {} };
|
|
789
773
|
}
|
|
790
|
-
/**
|
|
791
|
-
* Run LLM expansion: build manifest of candidate chunks from files
|
|
792
|
-
* NOT already in search results, call expander, resolve selected IDs.
|
|
793
|
-
*/
|
|
794
|
-
async _expand(task, results) {
|
|
795
|
-
if (!this._expander) return { results: [] };
|
|
796
|
-
const excludeFilePaths = [...new Set(
|
|
797
|
-
results.filter((r) => r.filePath).map((r) => r.filePath)
|
|
798
|
-
)];
|
|
799
|
-
const excludeIds = [];
|
|
800
|
-
for (const r of results) {
|
|
801
|
-
const meta = r.metadata;
|
|
802
|
-
const id = meta?.id;
|
|
803
|
-
if (id !== void 0) excludeIds.push(id);
|
|
804
|
-
}
|
|
805
|
-
const manifest = [];
|
|
806
|
-
let resolver;
|
|
807
|
-
for (const mod of this._registry.all) {
|
|
808
|
-
if (!isExpandablePlugin(mod)) continue;
|
|
809
|
-
manifest.push(...mod.buildManifest(excludeFilePaths, excludeIds, excludeFilePaths));
|
|
810
|
-
if (!resolver) {
|
|
811
|
-
resolver = /* @__PURE__ */ __name((ids) => mod.resolveChunks(ids), "resolver");
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (manifest.length === 0 || !resolver) {
|
|
815
|
-
dbg(`[expander] No manifest items (no ExpandablePlugin or all chunks excluded)`);
|
|
816
|
-
return { results: [] };
|
|
817
|
-
}
|
|
818
|
-
const priorityCount = manifest.filter((m) => m.priority).length;
|
|
819
|
-
dbg(`[expander] Manifest: ${manifest.length} chunks (${priorityCount} priority, ${manifest.length - priorityCount} other), excluding ${excludeFilePaths.length} files`);
|
|
820
|
-
try {
|
|
821
|
-
const expandResult = await this._expander.expand(task, excludeIds, manifest);
|
|
822
|
-
if (expandResult.ids.length === 0) return { results: [], note: expandResult.note };
|
|
823
|
-
return { results: resolver(expandResult.ids), note: expandResult.note };
|
|
824
|
-
} catch {
|
|
825
|
-
return { results: [] };
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
774
|
/** Collect results from SearchablePlugins that don't have their own formatter. */
|
|
829
775
|
async _appendSearchableResults(task, sources, minScore, parts) {
|
|
830
776
|
for (const mod of this._registry.all) {
|
|
@@ -856,10 +802,6 @@ function _prunerName(pruner) {
|
|
|
856
802
|
return pruner.constructor?.name ?? "custom";
|
|
857
803
|
}
|
|
858
804
|
__name(_prunerName, "_prunerName");
|
|
859
|
-
function _expanderName(expander) {
|
|
860
|
-
return expander.constructor?.name ?? "custom";
|
|
861
|
-
}
|
|
862
|
-
__name(_expanderName, "_expanderName");
|
|
863
805
|
|
|
864
806
|
// src/search/keyword/composite-bm25-search.ts
|
|
865
807
|
var DEFAULT_K = 8;
|
|
@@ -1598,7 +1540,6 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
|
1598
1540
|
".svelte-kit",
|
|
1599
1541
|
// Auto-generated code
|
|
1600
1542
|
"generated",
|
|
1601
|
-
"sdk",
|
|
1602
1543
|
"openapi",
|
|
1603
1544
|
// Version control
|
|
1604
1545
|
".git",
|
|
@@ -2206,7 +2147,7 @@ function createSearchAPI(_db, embedding, config, registry, kvService, sharedHnsw
|
|
|
2206
2147
|
embedding
|
|
2207
2148
|
}) : void 0;
|
|
2208
2149
|
const bm25 = new CompositeBM25Search(registry);
|
|
2209
|
-
const contextBuilder = new ContextBuilder(search, registry, config.pruner, embedding, config.contextFields ?? {}
|
|
2150
|
+
const contextBuilder = new ContextBuilder(search, registry, config.pruner, embedding, config.contextFields ?? {});
|
|
2210
2151
|
return new SearchAPI({
|
|
2211
2152
|
search,
|
|
2212
2153
|
bm25,
|
|
@@ -3191,23 +3132,23 @@ async function setupProviders(brainOpts, config, flags, env) {
|
|
|
3191
3132
|
if (perplexityKey) process.env.PERPLEXITY_API_KEY = perplexityKey;
|
|
3192
3133
|
if (openaiKey) process.env.OPENAI_API_KEY = openaiKey;
|
|
3193
3134
|
const prunerFlag = flags?.pruner ?? config?.pruner;
|
|
3194
|
-
if (prunerFlag === "haiku") {
|
|
3195
|
-
const { HaikuPruner } = await import("./haiku-pruner-
|
|
3135
|
+
if (prunerFlag === "haiku" || prunerFlag === "sonnet") {
|
|
3136
|
+
const { HaikuPruner } = await import("./haiku-pruner-5KVT5AI2.js");
|
|
3137
|
+
const model = prunerFlag === "sonnet" ? "claude-sonnet-4-6" : void 0;
|
|
3138
|
+
brainOpts.pruner = new HaikuPruner({ apiKey: anthropicKey, model });
|
|
3139
|
+
} else if (!prunerFlag && anthropicKey) {
|
|
3140
|
+
const { HaikuPruner } = await import("./haiku-pruner-5KVT5AI2.js");
|
|
3196
3141
|
brainOpts.pruner = new HaikuPruner({ apiKey: anthropicKey });
|
|
3197
3142
|
}
|
|
3198
|
-
const expanderFlag = flags?.expander ?? config?.expander;
|
|
3199
|
-
if (expanderFlag === "haiku") {
|
|
3200
|
-
try {
|
|
3201
|
-
const { HaikuExpander } = await import("./haiku-expander-CGEHFJIA.js");
|
|
3202
|
-
brainOpts.expander = new HaikuExpander({ apiKey: anthropicKey });
|
|
3203
|
-
} catch {
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
3143
|
const embFlag = flags?.embedding ?? config?.embedding ?? env?.BRAINBANK_EMBEDDING ?? process.env.BRAINBANK_EMBEDDING;
|
|
3207
3144
|
if (embFlag) {
|
|
3208
3145
|
const provider = await resolveEmbeddingKey(embFlag);
|
|
3209
3146
|
brainOpts.embeddingProvider = provider;
|
|
3210
3147
|
brainOpts.embeddingDims = provider.dims;
|
|
3148
|
+
} else if (perplexityKey) {
|
|
3149
|
+
const provider = await resolveEmbeddingKey("perplexity");
|
|
3150
|
+
brainOpts.embeddingProvider = provider;
|
|
3151
|
+
brainOpts.embeddingDims = provider.dims;
|
|
3211
3152
|
}
|
|
3212
3153
|
if (config?.context) {
|
|
3213
3154
|
brainOpts.contextFields = config.context;
|
|
@@ -3397,4 +3338,4 @@ export {
|
|
|
3397
3338
|
resetFactoryCache,
|
|
3398
3339
|
createBrain
|
|
3399
3340
|
};
|
|
3400
|
-
//# sourceMappingURL=chunk-
|
|
3341
|
+
//# sourceMappingURL=chunk-3UIWA32X.js.map
|