@lincoln504/pi-research 1.6.18 → 1.6.20
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 +3 -4
- package/agent-skill/pi-research/README.md +1 -1
- package/agent-skill/pi-research/SKILL.md +1 -1
- package/dist/cli.mjs +343 -55
- package/dist/thread-worker.mjs +7 -0
- package/docs/AGENT-SKILL.md +1 -1
- package/docs/CHANGELOG.md +50 -5
- package/docs/CONFIGURATION.md +3 -1
- package/docs/KNOWLEDGE-STORE.md +38 -4
- package/docs/SDK.md +1 -1
- package/package.json +12 -11
- package/scripts/setup.cjs +6 -3
- package/src/cli.ts +60 -7
- package/src/config.ts +35 -15
- package/src/core/pi-version.ts +12 -3
- package/src/healthcheck/index.ts +18 -0
- package/src/index.ts +5 -3
- package/src/infrastructure/browser/thread-worker.mjs +7 -0
- package/src/infrastructure/knowledge-store-service.ts +29 -4
- package/src/knowledge/availability.ts +33 -10
- package/src/knowledge/index.ts +17 -6
- package/src/knowledge/store-operations.ts +177 -0
- package/src/knowledge/store-schema.ts +58 -0
- package/src/knowledge/store.ts +118 -21
- package/src/research-config.ts +58 -18
package/dist/cli.mjs
CHANGED
|
@@ -2389,10 +2389,13 @@ var init_atomic_replace = __esm({
|
|
|
2389
2389
|
|
|
2390
2390
|
// src/knowledge/availability.ts
|
|
2391
2391
|
import { createRequire as createRequire3 } from "node:module";
|
|
2392
|
-
function
|
|
2393
|
-
|
|
2392
|
+
function requiredPackages(retrieval) {
|
|
2393
|
+
return retrieval === "bm25" ? ["@lancedb/lancedb"] : ["@lancedb/lancedb", "@huggingface/transformers"];
|
|
2394
|
+
}
|
|
2395
|
+
function probeKnowledgeStoreAvailability(retrieval = "vector", resolver = defaultResolver) {
|
|
2396
|
+
if (cachedIsValidFor(retrieval, resolver)) return _cached;
|
|
2394
2397
|
const missing = [];
|
|
2395
|
-
for (const pkg of
|
|
2398
|
+
for (const pkg of requiredPackages(retrieval)) {
|
|
2396
2399
|
try {
|
|
2397
2400
|
resolver(pkg);
|
|
2398
2401
|
} catch {
|
|
@@ -2400,11 +2403,14 @@ function probeKnowledgeStoreAvailability(resolver = defaultResolver) {
|
|
|
2400
2403
|
}
|
|
2401
2404
|
}
|
|
2402
2405
|
const result = { available: missing.length === 0, missing };
|
|
2403
|
-
if (resolver === defaultResolver)
|
|
2406
|
+
if (resolver === defaultResolver) {
|
|
2407
|
+
_cached = result;
|
|
2408
|
+
_cachedRetrieval = retrieval;
|
|
2409
|
+
}
|
|
2404
2410
|
return result;
|
|
2405
2411
|
}
|
|
2406
|
-
function cachedIsValidFor(resolver) {
|
|
2407
|
-
return _cached !== null && resolver === defaultResolver;
|
|
2412
|
+
function cachedIsValidFor(retrieval, resolver) {
|
|
2413
|
+
return _cached !== null && _cachedRetrieval === retrieval && resolver === defaultResolver;
|
|
2408
2414
|
}
|
|
2409
2415
|
function describeKnowledgeStoreUnavailability(availability) {
|
|
2410
2416
|
if (availability.available) return "";
|
|
@@ -2413,12 +2419,12 @@ function describeKnowledgeStoreUnavailability(availability) {
|
|
|
2413
2419
|
);
|
|
2414
2420
|
return `knowledge store is OFF: required package${parts.length > 1 ? "s" : ""} not resolvable \u2014 ${parts.join("; ")}. Repair with a full install of optional dependencies (e.g. 'npm install' including optional deps, or pi install npm:@lincoln504/pi-research without --omit=optional).`;
|
|
2415
2421
|
}
|
|
2416
|
-
var
|
|
2422
|
+
var _cached, _cachedRetrieval, defaultResolver;
|
|
2417
2423
|
var init_availability = __esm({
|
|
2418
2424
|
"src/knowledge/availability.ts"() {
|
|
2419
2425
|
"use strict";
|
|
2420
|
-
REQUIRED_PACKAGES = ["@lancedb/lancedb", "@huggingface/transformers"];
|
|
2421
2426
|
_cached = null;
|
|
2427
|
+
_cachedRetrieval = null;
|
|
2422
2428
|
defaultResolver = (specifier) => (
|
|
2423
2429
|
// import.meta.url survives esbuild's ESM output; createRequire only builds a
|
|
2424
2430
|
// resolver — it never evaluates the specifier.
|
|
@@ -2611,37 +2617,39 @@ function getDbDir(config, cwd = process.cwd()) {
|
|
|
2611
2617
|
}
|
|
2612
2618
|
function describeKnowledgeStoreMode(cwd = process.cwd(), iface) {
|
|
2613
2619
|
const cfg = getConfig(cwd, iface);
|
|
2614
|
-
const
|
|
2615
|
-
const fileHasKey = (p) => {
|
|
2620
|
+
const fileHasKey = (key, p) => {
|
|
2616
2621
|
try {
|
|
2617
|
-
return fs5.existsSync(p) && parseDotEnv(fs5.readFileSync(p, "utf-8"))[
|
|
2622
|
+
return fs5.existsSync(p) && parseDotEnv(fs5.readFileSync(p, "utf-8"))[key] !== void 0;
|
|
2618
2623
|
} catch {
|
|
2619
2624
|
return false;
|
|
2620
2625
|
}
|
|
2621
2626
|
};
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2627
|
+
const originFor = (KEY) => {
|
|
2628
|
+
if (process.env[KEY] !== void 0) {
|
|
2629
|
+
return "environment variable";
|
|
2630
|
+
}
|
|
2626
2631
|
let registryHasKey = false;
|
|
2627
2632
|
try {
|
|
2628
2633
|
const registry = loadProjectSettingsRegistry();
|
|
2629
2634
|
registryHasKey = findRegistryEntry(registry, normalizeWorkspacePath(cwd))?.[KEY] !== void 0;
|
|
2630
2635
|
} catch {
|
|
2631
2636
|
}
|
|
2632
|
-
if (registryHasKey)
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2637
|
+
if (registryHasKey) return "this directory (project settings)";
|
|
2638
|
+
if (iface && fileHasKey(KEY, getInterfaceEnvFilePath(iface))) return `${iface}.env overlay`;
|
|
2639
|
+
if (fileHasKey(KEY, getGlobalEnvFilePath())) return "config.env (machine-wide default)";
|
|
2640
|
+
return "built-in default";
|
|
2641
|
+
};
|
|
2636
2642
|
return {
|
|
2637
2643
|
mode: cfg.KNOWLEDGE_STORE_MODE,
|
|
2638
|
-
origin,
|
|
2644
|
+
origin: originFor("PI_RESEARCH_KNOWLEDGE_STORE_MODE"),
|
|
2645
|
+
retrieval: cfg.KNOWLEDGE_STORE_RETRIEVAL,
|
|
2646
|
+
retrievalOrigin: originFor("PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL"),
|
|
2639
2647
|
dbDir: getDbDir(cfg, cwd),
|
|
2640
|
-
...availabilityFields()
|
|
2648
|
+
...availabilityFields(cfg)
|
|
2641
2649
|
};
|
|
2642
2650
|
}
|
|
2643
|
-
function availabilityFields() {
|
|
2644
|
-
const availability = probeKnowledgeStoreAvailability();
|
|
2651
|
+
function availabilityFields(cfg) {
|
|
2652
|
+
const availability = probeKnowledgeStoreAvailability(cfg?.KNOWLEDGE_STORE_RETRIEVAL ?? getConfig().KNOWLEDGE_STORE_RETRIEVAL);
|
|
2645
2653
|
return {
|
|
2646
2654
|
available: availability.available,
|
|
2647
2655
|
unavailableReason: describeKnowledgeStoreUnavailability(availability)
|
|
@@ -2787,6 +2795,7 @@ function saveConfig(config, scope = "local", cwd = process.cwd(), changedKeys) {
|
|
|
2787
2795
|
PI_RESEARCH_WORKER_THREADS: String(config.WORKER_THREADS),
|
|
2788
2796
|
PI_RESEARCH_WORKER_CONCURRENCY: String(config.WORKER_CONCURRENCY),
|
|
2789
2797
|
PI_RESEARCH_KNOWLEDGE_STORE_MODE: config.KNOWLEDGE_STORE_MODE,
|
|
2798
|
+
PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL: config.KNOWLEDGE_STORE_RETRIEVAL,
|
|
2790
2799
|
PI_RESEARCH_EMBEDDING_MODEL: config.EMBEDDING_MODEL,
|
|
2791
2800
|
PI_RESEARCH_EMBEDDING_DEVICE: config.EMBEDDING_DEVICE,
|
|
2792
2801
|
PI_RESEARCH_SCRAPE_TIMEOUT_MS: String(config.SCRAPE_TIMEOUT_MS),
|
|
@@ -2947,6 +2956,7 @@ function createConfig(env, processEnv) {
|
|
|
2947
2956
|
WORKER_THREADS: parseEnvNumber(e, "PI_RESEARCH_WORKER_THREADS", DEFAULTS.WORKER_THREADS, 1, 10, true),
|
|
2948
2957
|
WORKER_CONCURRENCY: parseEnvNumber(e, "PI_RESEARCH_WORKER_CONCURRENCY", DEFAULTS.WORKER_CONCURRENCY, 1, 10, true),
|
|
2949
2958
|
KNOWLEDGE_STORE_MODE: parseEnvEnum(e, "PI_RESEARCH_KNOWLEDGE_STORE_MODE", ["none", "project", "global"], "global"),
|
|
2959
|
+
KNOWLEDGE_STORE_RETRIEVAL: parseEnvEnum(e, "PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL", ["vector", "bm25"], "vector"),
|
|
2950
2960
|
EMBEDDING_MODEL: parseEnvString(e, "PI_RESEARCH_EMBEDDING_MODEL", DEFAULTS.EMBEDDING_MODEL),
|
|
2951
2961
|
EMBEDDING_DEVICE: parseEnvEnum(e, "PI_RESEARCH_EMBEDDING_DEVICE", ["auto", "webgpu", "cpu"], DEFAULTS.EMBEDDING_DEVICE),
|
|
2952
2962
|
SCRAPE_TIMEOUT_MS: parseEnvNumber(e, "PI_RESEARCH_SCRAPE_TIMEOUT_MS", DEFAULTS.SCRAPE_TIMEOUT_MS, 5e3, 12e4),
|
|
@@ -3082,6 +3092,13 @@ var init_config = __esm({
|
|
|
3082
3092
|
WORKER_CONCURRENCY: Type.Number({ minimum: 1, maximum: 10, default: 2 }),
|
|
3083
3093
|
/** Knowledge store isolation mode (default: 'global' — one shared store across every directory) */
|
|
3084
3094
|
KNOWLEDGE_STORE_MODE: Type.Union([Type.Literal("none"), Type.Literal("project"), Type.Literal("global")], { default: "global" }),
|
|
3095
|
+
/** Knowledge store retrieval strategy (default: 'vector' — current hybrid dense+sparse behavior).
|
|
3096
|
+
* 'vector' = hybrid search (embedding similarity + BM25/FTS fused via RRF; requires the
|
|
3097
|
+
* optional @huggingface/transformers dependency and a downloaded embedding model).
|
|
3098
|
+
* 'bm25' = pure lexical/BM25 retrieval over the same stored findings — NO embedding model:
|
|
3099
|
+
* never initialized, downloaded, or invoked. Uses a separate lexical table in the same
|
|
3100
|
+
* LanceDB database, so existing vector stores are untouched (no migration). */
|
|
3101
|
+
KNOWLEDGE_STORE_RETRIEVAL: Type.Union([Type.Literal("vector"), Type.Literal("bm25")], { default: "vector" }),
|
|
3085
3102
|
/** Embedding model to use for the knowledge store */
|
|
3086
3103
|
EMBEDDING_MODEL: Type.String({ default: "onnx-community/granite-embedding-small-english-r2-ONNX" }),
|
|
3087
3104
|
/** Hardware backend for embeddings: 'auto' (probe WebGPU viability out-of-process, fall back to CPU), 'webgpu' (force), or 'cpu' (force).
|
|
@@ -3225,7 +3242,8 @@ var init_config = __esm({
|
|
|
3225
3242
|
DEFAULTS = Value.Create(ConfigSchema);
|
|
3226
3243
|
LOCAL_SCOPE_KEYS = /* @__PURE__ */ new Set([
|
|
3227
3244
|
"PI_RESEARCH_DEFAULT_RESEARCH_DEPTH",
|
|
3228
|
-
"PI_RESEARCH_KNOWLEDGE_STORE_MODE"
|
|
3245
|
+
"PI_RESEARCH_KNOWLEDGE_STORE_MODE",
|
|
3246
|
+
"PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL"
|
|
3229
3247
|
]);
|
|
3230
3248
|
USER_MIGRATION_KEYS = [
|
|
3231
3249
|
// Core user-scoped from schema
|
|
@@ -7400,8 +7418,35 @@ function createStoreSchema(dim, modelName) {
|
|
|
7400
7418
|
["schema_version", CURRENT_SCHEMA_VERSION]
|
|
7401
7419
|
]));
|
|
7402
7420
|
}
|
|
7421
|
+
function createBm25StoreSchema() {
|
|
7422
|
+
return new Schema([
|
|
7423
|
+
new Field("url", new Utf8(), false),
|
|
7424
|
+
new Field("text", new Utf8(), false),
|
|
7425
|
+
new Field("content", new Utf8(), true),
|
|
7426
|
+
// full page markdown, nullable
|
|
7427
|
+
new Field("metadata", new Utf8(), false),
|
|
7428
|
+
// JSON stringified
|
|
7429
|
+
new Field("workspace", new Utf8(), false),
|
|
7430
|
+
// local workspace path or 'global'
|
|
7431
|
+
new Field("is_global", new Bool(), false),
|
|
7432
|
+
// boolean indicating if it's shared globally
|
|
7433
|
+
new Field("ingestion_type", new Utf8(), false),
|
|
7434
|
+
// e.g. 'synthesis-description'
|
|
7435
|
+
new Field("timestamp", new Int64(), false)
|
|
7436
|
+
], /* @__PURE__ */ new Map([
|
|
7437
|
+
["embedding_model", BM25_EMBEDDING_MODEL_TAG],
|
|
7438
|
+
["schema_version", CURRENT_SCHEMA_VERSION]
|
|
7439
|
+
]));
|
|
7440
|
+
}
|
|
7403
7441
|
async function createStoreTable(db, name, dim, modelName) {
|
|
7404
7442
|
const schema = createStoreSchema(dim, modelName);
|
|
7443
|
+
return createKnowledgeTable(db, name, schema);
|
|
7444
|
+
}
|
|
7445
|
+
async function createBm25StoreTable(db, name = BM25_TABLE_NAME) {
|
|
7446
|
+
const schema = createBm25StoreSchema();
|
|
7447
|
+
return createKnowledgeTable(db, name, schema);
|
|
7448
|
+
}
|
|
7449
|
+
async function createKnowledgeTable(db, name, schema) {
|
|
7405
7450
|
const { Index } = await getLancedb();
|
|
7406
7451
|
const table = await db.createTable({
|
|
7407
7452
|
name,
|
|
@@ -7417,12 +7462,14 @@ async function createStoreTable(db, name, dim, modelName) {
|
|
|
7417
7462
|
await table.createIndex("ingestion_type", { config: Index.btree() });
|
|
7418
7463
|
return table;
|
|
7419
7464
|
}
|
|
7420
|
-
var CURRENT_SCHEMA_VERSION;
|
|
7465
|
+
var CURRENT_SCHEMA_VERSION, BM25_TABLE_NAME, BM25_EMBEDDING_MODEL_TAG;
|
|
7421
7466
|
var init_store_schema = __esm({
|
|
7422
7467
|
"src/knowledge/store-schema.ts"() {
|
|
7423
7468
|
"use strict";
|
|
7424
7469
|
init_lancedb_loader();
|
|
7425
7470
|
CURRENT_SCHEMA_VERSION = "4";
|
|
7471
|
+
BM25_TABLE_NAME = "knowledge-bm25";
|
|
7472
|
+
BM25_EMBEDDING_MODEL_TAG = "bm25-lexical";
|
|
7426
7473
|
}
|
|
7427
7474
|
});
|
|
7428
7475
|
|
|
@@ -7522,6 +7569,110 @@ async function searchStore(table, embedder, query, getReranker, limit, scopeFilt
|
|
|
7522
7569
|
metrics.increment("knowledge_store_search_results_total", filteredResults.length);
|
|
7523
7570
|
return filteredResults;
|
|
7524
7571
|
}
|
|
7572
|
+
async function addDocumentsBm25ToStore(table, docs, isClosing, workspace, isGlobal) {
|
|
7573
|
+
if (docs.length === 0) return "empty";
|
|
7574
|
+
if (isClosing()) {
|
|
7575
|
+
logger.warn("[store] Ignoring addDocuments during close");
|
|
7576
|
+
return "ignored_closing";
|
|
7577
|
+
}
|
|
7578
|
+
const startTime = Date.now();
|
|
7579
|
+
try {
|
|
7580
|
+
const data = docs.map((doc) => ({
|
|
7581
|
+
url: doc.url,
|
|
7582
|
+
text: doc.text,
|
|
7583
|
+
content: doc.content ?? null,
|
|
7584
|
+
metadata: JSON.stringify(doc.metadata),
|
|
7585
|
+
workspace,
|
|
7586
|
+
is_global: isGlobal,
|
|
7587
|
+
ingestion_type: doc.ingestion_type || doc.metadata["ingestionType"] || "unknown",
|
|
7588
|
+
timestamp: BigInt(doc.timestamp)
|
|
7589
|
+
}));
|
|
7590
|
+
await table.add(data);
|
|
7591
|
+
const duration = Date.now() - startTime;
|
|
7592
|
+
metrics.observe("knowledge_store_add_documents_duration_ms", duration, { retrieval: "bm25" });
|
|
7593
|
+
metrics.increment("knowledge_store_chunks_added_total", docs.length);
|
|
7594
|
+
logger.log(`[store] Added ${docs.length} chunk(s) for ${docs[0]?.url} [retrieval=bm25, workspace=${workspace}, global=${isGlobal}]`);
|
|
7595
|
+
return "written";
|
|
7596
|
+
} catch (err) {
|
|
7597
|
+
const duration = Date.now() - startTime;
|
|
7598
|
+
metrics.observe("knowledge_store_add_documents_duration_ms", duration, { status: "error", retrieval: "bm25" });
|
|
7599
|
+
logger.debug(`[store] bm25 add attempt failed after ${duration}ms: ${err instanceof Error ? err.message : String(err)}`);
|
|
7600
|
+
throw err;
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
async function searchStoreBm25(table, query, limit, scopeFilter) {
|
|
7604
|
+
const startTime = Date.now();
|
|
7605
|
+
const rowCount = scopeFilter ? await table.countRows(scopeFilter) : await table.countRows();
|
|
7606
|
+
if (rowCount === 0) {
|
|
7607
|
+
metrics.increment("knowledge_store_search_total", 1, { status: "empty", retrieval: "bm25" });
|
|
7608
|
+
return [];
|
|
7609
|
+
}
|
|
7610
|
+
let filter = "ingestion_type = 'synthesis-description'";
|
|
7611
|
+
if (scopeFilter) {
|
|
7612
|
+
filter = `(${filter}) AND (${scopeFilter})`;
|
|
7613
|
+
}
|
|
7614
|
+
const results = await table.query().where(filter).fullTextSearch(query, { columns: ["text", "content"] }).limit(limit).toArray();
|
|
7615
|
+
const filteredResults = results.map((r) => {
|
|
7616
|
+
let metadata;
|
|
7617
|
+
try {
|
|
7618
|
+
metadata = JSON.parse(r.metadata);
|
|
7619
|
+
} catch {
|
|
7620
|
+
logger.debug("[store] Corrupted metadata in searchStoreBm25, skipping row");
|
|
7621
|
+
return null;
|
|
7622
|
+
}
|
|
7623
|
+
return {
|
|
7624
|
+
url: r.url,
|
|
7625
|
+
text: r.text,
|
|
7626
|
+
content: r.content ?? void 0,
|
|
7627
|
+
metadata,
|
|
7628
|
+
timestamp: Number(r.timestamp)
|
|
7629
|
+
};
|
|
7630
|
+
}).filter((doc) => doc !== null);
|
|
7631
|
+
const duration = Date.now() - startTime;
|
|
7632
|
+
metrics.observe("knowledge_store_search_duration_ms", duration);
|
|
7633
|
+
metrics.increment("knowledge_store_search_total", 1, { status: "success", retrieval: "bm25" });
|
|
7634
|
+
metrics.increment("knowledge_store_search_results_total", filteredResults.length);
|
|
7635
|
+
return filteredResults;
|
|
7636
|
+
}
|
|
7637
|
+
async function findRelevantUrlsBm25(table, query, limit, scopeFilter) {
|
|
7638
|
+
const startTime = Date.now();
|
|
7639
|
+
const rowCount = scopeFilter ? await table.countRows(scopeFilter) : await table.countRows();
|
|
7640
|
+
if (rowCount === 0) {
|
|
7641
|
+
metrics.increment("knowledge_store_find_urls_total", 1, { status: "empty", retrieval: "bm25" });
|
|
7642
|
+
return [];
|
|
7643
|
+
}
|
|
7644
|
+
let filter = "ingestion_type = 'synthesis-description'";
|
|
7645
|
+
if (scopeFilter) {
|
|
7646
|
+
filter = `(${filter}) AND (${scopeFilter})`;
|
|
7647
|
+
}
|
|
7648
|
+
const results = await table.query().where(filter).fullTextSearch(query, { columns: ["text", "content"] }).limit(limit).toArray();
|
|
7649
|
+
const entries = [];
|
|
7650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7651
|
+
for (const r of results) {
|
|
7652
|
+
const url = r.url;
|
|
7653
|
+
if (seen.has(url)) continue;
|
|
7654
|
+
seen.add(url);
|
|
7655
|
+
let description = "";
|
|
7656
|
+
let provenance = "description-unverified";
|
|
7657
|
+
try {
|
|
7658
|
+
const meta = JSON.parse(r.metadata);
|
|
7659
|
+
description = meta.description ?? "";
|
|
7660
|
+
if (meta.provenance) provenance = meta.provenance;
|
|
7661
|
+
else if (meta.hasContent === true) provenance = "scraped-verified";
|
|
7662
|
+
} catch {
|
|
7663
|
+
logger.debug("[store] Corrupted metadata in findRelevantUrlsBm25, skipping row");
|
|
7664
|
+
}
|
|
7665
|
+
if (!description) description = (r.text ?? "").substring(0, 300);
|
|
7666
|
+
const scope = r.is_global === true ? "Global Store" : `Local Project (${r.workspace || "unknown"})`;
|
|
7667
|
+
const finalProvenance = `${provenance} [${scope}]`;
|
|
7668
|
+
entries.push({ url, description, provenance: finalProvenance });
|
|
7669
|
+
}
|
|
7670
|
+
const duration = Date.now() - startTime;
|
|
7671
|
+
metrics.observe("knowledge_store_find_urls_duration_ms", duration);
|
|
7672
|
+
metrics.increment("knowledge_store_find_urls_total", 1, { status: "success", retrieval: "bm25" });
|
|
7673
|
+
metrics.increment("knowledge_store_urls_found_total", entries.length);
|
|
7674
|
+
return entries;
|
|
7675
|
+
}
|
|
7525
7676
|
async function findDocumentsByUrl(table, url, scopeFilter) {
|
|
7526
7677
|
const startTime = Date.now();
|
|
7527
7678
|
const escapedUrl = url.replace(/'/g, "''");
|
|
@@ -7631,7 +7782,12 @@ var init_store = __esm({
|
|
|
7631
7782
|
// Coalesces concurrent open() calls (see open()).
|
|
7632
7783
|
_openPromise = null;
|
|
7633
7784
|
options;
|
|
7634
|
-
tableName
|
|
7785
|
+
tableName;
|
|
7786
|
+
// The OTHER retrieval mode's active table name, preserved across manifest
|
|
7787
|
+
// save/load so the two modes can share one dbDir without clobbering each
|
|
7788
|
+
// other's migration-renamed tables ('vector' owns activeTableName,
|
|
7789
|
+
// 'bm25' owns activeBm25TableName).
|
|
7790
|
+
otherModeTableName = null;
|
|
7635
7791
|
manifestPath;
|
|
7636
7792
|
isClosing = false;
|
|
7637
7793
|
activeWrites = /* @__PURE__ */ new Set();
|
|
@@ -7660,8 +7816,23 @@ var init_store = __esm({
|
|
|
7660
7816
|
});
|
|
7661
7817
|
constructor(options) {
|
|
7662
7818
|
this.options = options;
|
|
7819
|
+
this.tableName = this.retrieval === "bm25" ? BM25_TABLE_NAME : "knowledge";
|
|
7663
7820
|
this.manifestPath = path15.join(this.options.dbDir, "store-manifest.json");
|
|
7664
7821
|
}
|
|
7822
|
+
/** Normalized retrieval strategy: an unset option means the historical
|
|
7823
|
+
* 'vector' (hybrid) behavior, not bm25. Every mode check goes through this. */
|
|
7824
|
+
get retrieval() {
|
|
7825
|
+
return this.options.retrieval ?? "vector";
|
|
7826
|
+
}
|
|
7827
|
+
/** The embedding backend. Only callable on the vector (hybrid) path — the
|
|
7828
|
+
* single choke point that makes a null embedder in bm25 mode fail loud here
|
|
7829
|
+
* instead of surfacing as a mysterious TypeError deep in an embed call. */
|
|
7830
|
+
get embedder() {
|
|
7831
|
+
if (this.retrieval === "bm25" || !this.options.embedder) {
|
|
7832
|
+
throw new Error("[store] embedder accessed in bm25 (lexical) mode \u2014 internal invariant violated");
|
|
7833
|
+
}
|
|
7834
|
+
return this.options.embedder;
|
|
7835
|
+
}
|
|
7665
7836
|
getScopeFilter() {
|
|
7666
7837
|
const ws = this.getWorkspace().replace(/'/g, "''");
|
|
7667
7838
|
switch (this.options.knowledgeMode) {
|
|
@@ -7687,8 +7858,12 @@ var init_store = __esm({
|
|
|
7687
7858
|
if (fs13.existsSync(this.manifestPath)) {
|
|
7688
7859
|
const content = await fsPromises.readFile(this.manifestPath, "utf-8");
|
|
7689
7860
|
const manifest = JSON.parse(content);
|
|
7690
|
-
|
|
7691
|
-
|
|
7861
|
+
const savedVectorTable = typeof manifest.activeTableName === "string" ? manifest.activeTableName : null;
|
|
7862
|
+
const savedBm25Table = typeof manifest.activeBm25TableName === "string" ? manifest.activeBm25TableName : null;
|
|
7863
|
+
this.otherModeTableName = this.retrieval === "bm25" ? savedVectorTable : savedBm25Table;
|
|
7864
|
+
const savedOwnTable = this.retrieval === "bm25" ? savedBm25Table : savedVectorTable;
|
|
7865
|
+
if (savedOwnTable) {
|
|
7866
|
+
this.tableName = savedOwnTable;
|
|
7692
7867
|
logger.debug(`[store] Loaded active table name from manifest: ${this.tableName}`);
|
|
7693
7868
|
}
|
|
7694
7869
|
if (manifest.lastMigration && typeof manifest.lastMigration.model === "string") {
|
|
@@ -7707,7 +7882,11 @@ var init_store = __esm({
|
|
|
7707
7882
|
const tempPath = `${this.manifestPath}.tmp`;
|
|
7708
7883
|
try {
|
|
7709
7884
|
const content = JSON.stringify({
|
|
7710
|
-
activeTableName: this.tableName,
|
|
7885
|
+
activeTableName: this.retrieval === "vector" ? this.tableName : this.otherModeTableName,
|
|
7886
|
+
// bm25 keeps its OWN manifest key so a lexical store can share the dbDir
|
|
7887
|
+
// with a vector store — neither mode's table renames (migrations,
|
|
7888
|
+
// backups) can strand the other's manifest pointer.
|
|
7889
|
+
activeBm25TableName: this.retrieval === "bm25" ? this.tableName : this.otherModeTableName,
|
|
7711
7890
|
// Record of the two most recent model/schema migrations. Read back on open
|
|
7712
7891
|
// to detect ALTERNATION (two configurations sharing this dbDir each
|
|
7713
7892
|
// migrating the table back and forth) — previously that thrash was
|
|
@@ -7732,6 +7911,7 @@ var init_store = __esm({
|
|
|
7732
7911
|
}
|
|
7733
7912
|
async open() {
|
|
7734
7913
|
if (this.db) return;
|
|
7914
|
+
await this.loadManifest();
|
|
7735
7915
|
if (this.isClosing) {
|
|
7736
7916
|
throw new Error("KnowledgeStore.open() called after close() \u2014 construct a fresh store instead.");
|
|
7737
7917
|
}
|
|
@@ -7757,7 +7937,13 @@ var init_store = __esm({
|
|
|
7757
7937
|
}
|
|
7758
7938
|
this.db = await (await getLancedb()).connect(this.options.dbDir);
|
|
7759
7939
|
const tableNames = await this.db.tableNames();
|
|
7760
|
-
if (
|
|
7940
|
+
if (this.retrieval === "bm25") {
|
|
7941
|
+
if (tableNames.includes(this.tableName)) {
|
|
7942
|
+
this.table = await this.db.openTable(this.tableName);
|
|
7943
|
+
} else {
|
|
7944
|
+
this.table = await this.createTable();
|
|
7945
|
+
}
|
|
7946
|
+
} else if (tableNames.includes(this.tableName)) {
|
|
7761
7947
|
this.table = await this.db.openTable(this.tableName);
|
|
7762
7948
|
let migrationInProgress = false;
|
|
7763
7949
|
try {
|
|
@@ -7776,8 +7962,8 @@ var init_store = __esm({
|
|
|
7776
7962
|
const vectorField = schema.fields.find((f) => f.name === "vector");
|
|
7777
7963
|
if (vectorField && vectorField.type.constructor.name === "FixedSizeList") {
|
|
7778
7964
|
const dim = vectorField.type.listSize;
|
|
7779
|
-
if (this.
|
|
7780
|
-
this.
|
|
7965
|
+
if (this.embedder.getDimension() === null) {
|
|
7966
|
+
this.embedder.setDimension(dim);
|
|
7781
7967
|
logger.debug(`[store] Extracted dimension ${dim} from existing table schema`);
|
|
7782
7968
|
}
|
|
7783
7969
|
}
|
|
@@ -7787,7 +7973,7 @@ var init_store = __esm({
|
|
|
7787
7973
|
logger.warn(`[store] ${reason} detected: ${storedModel} (v${storedVersion}) \u2192 ${this.options.modelName} (v${CURRENT_SCHEMA_VERSION})`);
|
|
7788
7974
|
const strategy = this.options.migrationStrategy || "backup";
|
|
7789
7975
|
migrationInProgress = true;
|
|
7790
|
-
if (this.
|
|
7976
|
+
if (this.embedder.getDimension() === null) {
|
|
7791
7977
|
await this.withEmbedderReconnect((embedder) => embedder.embedMany([" "]));
|
|
7792
7978
|
}
|
|
7793
7979
|
try {
|
|
@@ -7822,7 +8008,7 @@ var init_store = __esm({
|
|
|
7822
8008
|
logger.warn("[store] Failed to read table schema:", schemaErr);
|
|
7823
8009
|
}
|
|
7824
8010
|
} else {
|
|
7825
|
-
if (this.
|
|
8011
|
+
if (this.embedder.getDimension() === null) {
|
|
7826
8012
|
await this.withEmbedderReconnect((embedder) => embedder.embedMany([" "]));
|
|
7827
8013
|
}
|
|
7828
8014
|
this.table = await this.createTable();
|
|
@@ -7934,7 +8120,7 @@ var init_store = __esm({
|
|
|
7934
8120
|
});
|
|
7935
8121
|
const texts = batchDocs.map((d) => d.text);
|
|
7936
8122
|
const vectors = await this.withEmbedderReconnect((embedder) => embedder.embedMany(texts));
|
|
7937
|
-
assertValidEmbeddingVectors(vectors, batchDocs.length, this.
|
|
8123
|
+
assertValidEmbeddingVectors(vectors, batchDocs.length, this.embedder.getDimension?.());
|
|
7938
8124
|
const records = batchDocs.map((doc, idx) => ({
|
|
7939
8125
|
vector: Array.from(vectors[idx]),
|
|
7940
8126
|
url: doc.url,
|
|
@@ -8014,11 +8200,17 @@ var init_store = __esm({
|
|
|
8014
8200
|
}
|
|
8015
8201
|
async createTable(name = this.tableName) {
|
|
8016
8202
|
if (!this.db) throw new Error("Database not connected");
|
|
8017
|
-
|
|
8203
|
+
if (this.retrieval === "bm25") {
|
|
8204
|
+
return createBm25StoreTable(this.db, name);
|
|
8205
|
+
}
|
|
8206
|
+
const dim = this.embedder.getDimension();
|
|
8018
8207
|
if (dim === null) throw new Error("[store] Cannot create table: embedder dimension unknown (not yet initialized)");
|
|
8019
8208
|
return createStoreTable(this.db, name, dim, this.options.modelName);
|
|
8020
8209
|
}
|
|
8021
8210
|
async withEmbedderReconnect(fn) {
|
|
8211
|
+
if (this.retrieval === "bm25" || !this.options.embedder) {
|
|
8212
|
+
throw new Error("[store] withEmbedderReconnect called without an embedder (bm25 mode?) \u2014 internal invariant violated");
|
|
8213
|
+
}
|
|
8022
8214
|
try {
|
|
8023
8215
|
return await fn(this.options.embedder);
|
|
8024
8216
|
} catch (err) {
|
|
@@ -8100,17 +8292,26 @@ var init_store = __esm({
|
|
|
8100
8292
|
}
|
|
8101
8293
|
let table = await this.getFreshTable();
|
|
8102
8294
|
const workspace = this.getWorkspace();
|
|
8103
|
-
const
|
|
8295
|
+
const runWriteAttempt = async () => {
|
|
8104
8296
|
let retryCount = 0;
|
|
8105
8297
|
const MAX_RETRIES = 5;
|
|
8106
8298
|
const BASE_DELAY = 100;
|
|
8107
8299
|
while (retryCount <= MAX_RETRIES) {
|
|
8108
8300
|
try {
|
|
8109
8301
|
const isGlobal = this.options.knowledgeMode === "global";
|
|
8302
|
+
if (this.retrieval === "bm25") {
|
|
8303
|
+
return await addDocumentsBm25ToStore(
|
|
8304
|
+
table,
|
|
8305
|
+
docs,
|
|
8306
|
+
() => this.isClosing,
|
|
8307
|
+
workspace,
|
|
8308
|
+
isGlobal
|
|
8309
|
+
);
|
|
8310
|
+
}
|
|
8110
8311
|
return await addDocumentsToStore(
|
|
8111
8312
|
table,
|
|
8112
8313
|
docs,
|
|
8113
|
-
embedder,
|
|
8314
|
+
this.embedder,
|
|
8114
8315
|
() => this.isClosing,
|
|
8115
8316
|
workspace,
|
|
8116
8317
|
isGlobal
|
|
@@ -8129,7 +8330,8 @@ var init_store = __esm({
|
|
|
8129
8330
|
}
|
|
8130
8331
|
}
|
|
8131
8332
|
throw new Error("[store] addDocuments retry loop exited without result");
|
|
8132
|
-
}
|
|
8333
|
+
};
|
|
8334
|
+
const outcome = this.retrieval === "bm25" ? await runWriteAttempt() : await this.withEmbedderReconnect(runWriteAttempt);
|
|
8133
8335
|
if (outcome !== "empty") {
|
|
8134
8336
|
metrics.increment("knowledge_store_add_documents_total", 1, {
|
|
8135
8337
|
status: outcome === "ignored_closing" ? "ignored_closing" : "success"
|
|
@@ -8154,6 +8356,9 @@ var init_store = __esm({
|
|
|
8154
8356
|
return this.trackOperation("search", [], async () => {
|
|
8155
8357
|
const table = await this.getFreshTable();
|
|
8156
8358
|
const scopeFilter = this.getScopeFilter();
|
|
8359
|
+
if (this.retrieval === "bm25") {
|
|
8360
|
+
return this.circuitBreaker.execute(() => searchStoreBm25(table, query, options.limit ?? 5, scopeFilter));
|
|
8361
|
+
}
|
|
8157
8362
|
return this.circuitBreaker.execute(
|
|
8158
8363
|
() => this.withEmbedderReconnect(
|
|
8159
8364
|
(embedder) => searchStore(table, embedder, query, this.getReranker.bind(this), options.limit ?? 5, scopeFilter)
|
|
@@ -8308,8 +8513,9 @@ var init_store = __esm({
|
|
|
8308
8513
|
url: r.url,
|
|
8309
8514
|
// The text is the summary/description
|
|
8310
8515
|
text: r.text,
|
|
8311
|
-
// The vector is the model-dimension embedding
|
|
8312
|
-
|
|
8516
|
+
// The vector is the model-dimension embedding — vector/hybrid mode only;
|
|
8517
|
+
// a bm25 store has no vector column, so the export omits it entirely.
|
|
8518
|
+
...this.retrieval === "vector" ? { v: Array.from(r.vector) } : {},
|
|
8313
8519
|
// Minimal metadata needed for the UI
|
|
8314
8520
|
m: {
|
|
8315
8521
|
d: metadata["description"] || "",
|
|
@@ -8390,6 +8596,9 @@ var init_store = __esm({
|
|
|
8390
8596
|
return this.trackOperation("findRelevantUrls", [], async () => {
|
|
8391
8597
|
const table = await this.getFreshTable();
|
|
8392
8598
|
const scopeFilter = this.getScopeFilter();
|
|
8599
|
+
if (this.retrieval === "bm25") {
|
|
8600
|
+
return this.circuitBreaker.execute(() => findRelevantUrlsBm25(table, query, options.limit ?? 20, scopeFilter));
|
|
8601
|
+
}
|
|
8393
8602
|
return this.circuitBreaker.execute(
|
|
8394
8603
|
() => this.withEmbedderReconnect(
|
|
8395
8604
|
(embedder) => findRelevantUrls(table, embedder, query, this.getReranker.bind(this), options.limit ?? 20, scopeFilter)
|
|
@@ -9078,7 +9287,7 @@ function getMigrationStrategy(config) {
|
|
|
9078
9287
|
logger.warn(`[knowledge] Invalid migration strategy '${strategy}'. Valid options: drop, re-embed, backup. Falling back to default (backup).`);
|
|
9079
9288
|
return void 0;
|
|
9080
9289
|
}
|
|
9081
|
-
async function createKnowledgeStoreComponents(embedderFactory, reconnectFactory, withLock, config = getConfig(), workspace = process.cwd(), availability = probeKnowledgeStoreAvailability()) {
|
|
9290
|
+
async function createKnowledgeStoreComponents(embedderFactory, reconnectFactory, withLock, config = getConfig(), workspace = process.cwd(), availability = probeKnowledgeStoreAvailability(config.KNOWLEDGE_STORE_RETRIEVAL)) {
|
|
9082
9291
|
if (config.KNOWLEDGE_STORE_MODE === "none") {
|
|
9083
9292
|
logger.debug("[knowledge] Knowledge store is disabled in configuration");
|
|
9084
9293
|
return null;
|
|
@@ -9091,13 +9300,14 @@ async function createKnowledgeStoreComponents(embedderFactory, reconnectFactory,
|
|
|
9091
9300
|
for (let attempt = 1; attempt <= MAX_INIT_RETRIES; attempt++) {
|
|
9092
9301
|
let store;
|
|
9093
9302
|
try {
|
|
9094
|
-
logger.info(`[knowledge] Creating Knowledge Store components (attempt ${attempt}/${MAX_INIT_RETRIES})...`);
|
|
9095
|
-
const embedder = await embedderFactory();
|
|
9303
|
+
logger.info(`[knowledge] Creating Knowledge Store components (attempt ${attempt}/${MAX_INIT_RETRIES}, retrieval=${config.KNOWLEDGE_STORE_RETRIEVAL})...`);
|
|
9304
|
+
const embedder = config.KNOWLEDGE_STORE_RETRIEVAL === "bm25" ? null : await embedderFactory();
|
|
9096
9305
|
const migrationStrategy = getMigrationStrategy(config);
|
|
9097
9306
|
store = new KnowledgeStore({
|
|
9098
9307
|
dbDir: getDbDir(config, workspace),
|
|
9099
9308
|
embedder,
|
|
9100
9309
|
modelName: config.EMBEDDING_MODEL,
|
|
9310
|
+
retrieval: config.KNOWLEDGE_STORE_RETRIEVAL,
|
|
9101
9311
|
migrationStrategy,
|
|
9102
9312
|
reconnectFactory,
|
|
9103
9313
|
withLock,
|
|
@@ -9139,7 +9349,9 @@ async function forceDeleteKnowledgeStore(config, workspace) {
|
|
|
9139
9349
|
try {
|
|
9140
9350
|
if (fs14.existsSync(manifestPath)) {
|
|
9141
9351
|
const manifest = JSON.parse(fs14.readFileSync(manifestPath, "utf-8"));
|
|
9142
|
-
|
|
9352
|
+
const key = config?.KNOWLEDGE_STORE_RETRIEVAL === "bm25" ? "activeBm25TableName" : "activeTableName";
|
|
9353
|
+
const saved = key === "activeBm25TableName" ? manifest.activeBm25TableName : manifest.activeTableName;
|
|
9354
|
+
if (saved) tableName = saved;
|
|
9143
9355
|
}
|
|
9144
9356
|
} catch (err) {
|
|
9145
9357
|
logger.warn(`[knowledge] Failed to read manifest before forced delete, defaulting to table '${tableName}':`, err);
|
|
@@ -9209,6 +9421,10 @@ var init_knowledge_store_service = __esm({
|
|
|
9209
9421
|
// mode change (project↔global re-scope, or enabled→none) and re-initialize so a /research-config
|
|
9210
9422
|
// change applies without a Pi restart — not just the DISABLED→enabled revival.
|
|
9211
9423
|
_initializedMode = void 0;
|
|
9424
|
+
// The retrieval strategy the current build reflects ('vector' hybrid / 'bm25'
|
|
9425
|
+
// lexical) — drives the embedder-optional invariants below and the live
|
|
9426
|
+
// re-init detection on a /research-config retrieval change.
|
|
9427
|
+
_initializedRetrieval = void 0;
|
|
9212
9428
|
// Initialization promise to prevent concurrent initialization
|
|
9213
9429
|
_initializationPromise = null;
|
|
9214
9430
|
// Disposal promise: lets a concurrent initialize() wait out an in-flight
|
|
@@ -9224,13 +9440,16 @@ var init_knowledge_store_service = __esm({
|
|
|
9224
9440
|
}
|
|
9225
9441
|
const newCwd = ctx?.cwd || this._cwd;
|
|
9226
9442
|
if ((this.lifecycle === "initialized" /* INITIALIZED */ || this.lifecycle === "disabled" /* DISABLED */) && this._cwd === newCwd) {
|
|
9227
|
-
const
|
|
9443
|
+
const liveConfig = ctx?.config || getConfig(this._cwd);
|
|
9444
|
+
const liveMode = liveConfig.KNOWLEDGE_STORE_MODE;
|
|
9445
|
+
const liveRetrieval = liveConfig.KNOWLEDGE_STORE_RETRIEVAL;
|
|
9228
9446
|
const reviveFromMode = this.lifecycle === "disabled" /* DISABLED */ && this._disabledReason === "mode" && liveMode !== "none";
|
|
9229
9447
|
const modeChanged = this.lifecycle === "initialized" /* INITIALIZED */ && ctx?.config !== void 0 && this._initializedMode !== void 0 && this._initializedMode !== ctx.config.KNOWLEDGE_STORE_MODE;
|
|
9230
|
-
|
|
9448
|
+
const retrievalChanged = this.lifecycle === "initialized" /* INITIALIZED */ && ctx?.config !== void 0 && this._initializedRetrieval !== void 0 && this._initializedRetrieval !== ctx.config.KNOWLEDGE_STORE_RETRIEVAL;
|
|
9449
|
+
if (!reviveFromMode && !modeChanged && !retrievalChanged) {
|
|
9231
9450
|
return;
|
|
9232
9451
|
}
|
|
9233
|
-
logger.log(`[KnowledgeStoreService] Knowledge
|
|
9452
|
+
logger.log(`[KnowledgeStoreService] Knowledge config changed (${this._initializedMode ?? this._disabledReason} \u2192 ${liveMode}, retrieval ${this._initializedRetrieval ?? "?"} \u2192 ${liveRetrieval}); re-initializing store (no restart needed).`);
|
|
9234
9453
|
if (this.lifecycle === "initialized" /* INITIALIZED */) {
|
|
9235
9454
|
await this.dispose();
|
|
9236
9455
|
}
|
|
@@ -9265,6 +9484,7 @@ var init_knowledge_store_service = __esm({
|
|
|
9265
9484
|
this._cwd = ctx?.cwd || this._cwd;
|
|
9266
9485
|
const config = ctx?.config || getConfig(this._cwd);
|
|
9267
9486
|
this._initializedMode = config.KNOWLEDGE_STORE_MODE;
|
|
9487
|
+
this._initializedRetrieval = config.KNOWLEDGE_STORE_RETRIEVAL;
|
|
9268
9488
|
const embedderFactory = () => getEmbedder(config);
|
|
9269
9489
|
const reconnectFactory = async () => {
|
|
9270
9490
|
await clearEmbeddingInstance();
|
|
@@ -9332,7 +9552,7 @@ var init_knowledge_store_service = __esm({
|
|
|
9332
9552
|
}
|
|
9333
9553
|
}
|
|
9334
9554
|
if (!components) {
|
|
9335
|
-
const depsMissing = !probeKnowledgeStoreAvailability().available;
|
|
9555
|
+
const depsMissing = !probeKnowledgeStoreAvailability(config.KNOWLEDGE_STORE_RETRIEVAL).available;
|
|
9336
9556
|
logger.debug(`[KnowledgeStoreService] Knowledge store is disabled (${depsMissing ? "dependencies missing" : "mode = none"}). Setting lifecycle to DISABLED.`);
|
|
9337
9557
|
this.lifecycle = "disabled" /* DISABLED */;
|
|
9338
9558
|
this._disabledReason = depsMissing ? "native" : "mode";
|
|
@@ -9430,6 +9650,9 @@ var init_knowledge_store_service = __esm({
|
|
|
9430
9650
|
* Check if the knowledge store is ready
|
|
9431
9651
|
*/
|
|
9432
9652
|
isReady() {
|
|
9653
|
+
if (this._initializedRetrieval === "bm25") {
|
|
9654
|
+
return this._store !== null && this._writerQueue !== null;
|
|
9655
|
+
}
|
|
9433
9656
|
return this._embedder !== null && this._store !== null && this._writerQueue !== null;
|
|
9434
9657
|
}
|
|
9435
9658
|
/** The working directory this service initialized against (from ctx.cwd). */
|
|
@@ -9453,6 +9676,9 @@ var init_knowledge_store_service = __esm({
|
|
|
9453
9676
|
if (this.lifecycle === "disabled" /* DISABLED */) {
|
|
9454
9677
|
return null;
|
|
9455
9678
|
}
|
|
9679
|
+
if (this._initializedRetrieval === "bm25") {
|
|
9680
|
+
return null;
|
|
9681
|
+
}
|
|
9456
9682
|
if (!this._embedder) {
|
|
9457
9683
|
throw new Error("[KnowledgeStoreService] Embedder not initialized");
|
|
9458
9684
|
}
|
|
@@ -18127,7 +18353,21 @@ function registerHealthChecks(registry, container = getServiceContainer()) {
|
|
|
18127
18353
|
}
|
|
18128
18354
|
const store = await service.getStore();
|
|
18129
18355
|
const counts = store ? await store.countScoped() : { local: 0, global: 0, projects: 0 };
|
|
18356
|
+
const retrieval = getConfig(service.getCwd()).KNOWLEDGE_STORE_RETRIEVAL;
|
|
18130
18357
|
const embedder = await service.getEmbedder();
|
|
18358
|
+
if (retrieval === "bm25") {
|
|
18359
|
+
return {
|
|
18360
|
+
healthy: true,
|
|
18361
|
+
diagnostic: {
|
|
18362
|
+
status: "initialized",
|
|
18363
|
+
retrieval: "bm25",
|
|
18364
|
+
model: "none (lexical BM25)",
|
|
18365
|
+
localEntries: counts.local,
|
|
18366
|
+
globalEntries: counts.global,
|
|
18367
|
+
totalProjects: counts.projects
|
|
18368
|
+
}
|
|
18369
|
+
};
|
|
18370
|
+
}
|
|
18131
18371
|
if (!embedder) {
|
|
18132
18372
|
return { healthy: false, error: "Embedder service not available" };
|
|
18133
18373
|
}
|
|
@@ -18158,6 +18398,7 @@ function registerHealthChecks(registry, container = getServiceContainer()) {
|
|
|
18158
18398
|
healthy: true,
|
|
18159
18399
|
diagnostic: {
|
|
18160
18400
|
status: "initialized",
|
|
18401
|
+
retrieval,
|
|
18161
18402
|
device,
|
|
18162
18403
|
model: config2.EMBEDDING_MODEL,
|
|
18163
18404
|
localEntries: counts.local,
|
|
@@ -27400,6 +27641,29 @@ knowledge store mode: ${cfg.KNOWLEDGE_STORE_MODE} (default depth: ${cfg.DEFAUL
|
|
|
27400
27641
|
async function cmdKnowledgeConfig(kc) {
|
|
27401
27642
|
const cwd = process.cwd();
|
|
27402
27643
|
try {
|
|
27644
|
+
if (kc.action === "set-retrieval") {
|
|
27645
|
+
const before = getConfig(cwd, "cli").KNOWLEDGE_STORE_RETRIEVAL;
|
|
27646
|
+
const cfg = { ...getConfig(cwd, "cli"), KNOWLEDGE_STORE_RETRIEVAL: kc.retrieval };
|
|
27647
|
+
saveConfig(cfg, "local", cwd, ["PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL"]);
|
|
27648
|
+
resetConfig();
|
|
27649
|
+
const info2 = describeKnowledgeStoreMode(cwd, "cli");
|
|
27650
|
+
const overridden = info2.retrieval !== kc.retrieval;
|
|
27651
|
+
if (kc.json) {
|
|
27652
|
+
toStdout(pretty({ command: "knowledge-config", action: "set-retrieval", requested: kc.retrieval, previous: before, saved: true, effectiveOverriddenBy: overridden ? info2.retrievalOrigin : null, ...info2, cwd }));
|
|
27653
|
+
return EXIT.OK;
|
|
27654
|
+
}
|
|
27655
|
+
toStdout(
|
|
27656
|
+
`knowledge store retrieval for this directory: ${before} -> ${kc.retrieval} (saved per-directory)
|
|
27657
|
+
strategy: ${kc.retrieval === "bm25" ? "lexical BM25 \u2014 no embedding model needed" : "hybrid (embedding + BM25, fused) \u2014 needs the optional embedding dependency"}
|
|
27658
|
+
saved: per-directory (${cwd})
|
|
27659
|
+
db dir: ${info2.dbDir}
|
|
27660
|
+
` + (info2.available ? "" : ` state: ${info2.unavailableReason}
|
|
27661
|
+
`) + (overridden ? `
|
|
27662
|
+
NOTE: the effective retrieval here is still '${info2.retrieval}' because a higher-precedence ${info2.retrievalOrigin} overrides it. Clear that source for this setting to take effect.
|
|
27663
|
+
` : "")
|
|
27664
|
+
);
|
|
27665
|
+
return EXIT.OK;
|
|
27666
|
+
}
|
|
27403
27667
|
if (kc.action === "set") {
|
|
27404
27668
|
const before = getConfig(cwd, "cli").KNOWLEDGE_STORE_MODE;
|
|
27405
27669
|
const cfg = { ...getConfig(cwd, "cli"), KNOWLEDGE_STORE_MODE: kc.mode };
|
|
@@ -27430,12 +27694,15 @@ NOTE: the effective mode here is still '${info2.mode}' because a higher-preceden
|
|
|
27430
27694
|
}
|
|
27431
27695
|
toStdout(
|
|
27432
27696
|
`knowledge store mode: ${info.mode} (source: ${info.origin})
|
|
27433
|
-
|
|
27434
|
-
|
|
27435
|
-
|
|
27697
|
+
knowledge store retrieval: ${info.retrieval} (source: ${info.retrievalOrigin})
|
|
27698
|
+
scope: ${info.mode === "none" ? "disabled here" : info.mode === "project" ? "this directory only" : "shared across all directories"}
|
|
27699
|
+
strategy: ${info.retrieval === "bm25" ? "lexical BM25 \u2014 no embedding model needed" : "hybrid (embedding + BM25, fused)"}
|
|
27700
|
+
db dir: ${info.dbDir}
|
|
27701
|
+
` + (info.available ? "" : ` state: ${info.unavailableReason}
|
|
27436
27702
|
`) + `
|
|
27437
|
-
change
|
|
27438
|
-
change
|
|
27703
|
+
change scope for THIS directory: ${BINARY_NAME} knowledge-config set <none|project|global>
|
|
27704
|
+
change retrieval for THIS directory: ${BINARY_NAME} knowledge-config set retrieval <vector|bm25>
|
|
27705
|
+
change the machine-wide defaults: set PI_RESEARCH_KNOWLEDGE_STORE_MODE=<mode> and/or PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL=<vector|bm25> in ${resolvedConfigPaths().configEnv}
|
|
27439
27706
|
`
|
|
27440
27707
|
);
|
|
27441
27708
|
return EXIT.OK;
|
|
@@ -27680,6 +27947,10 @@ COMMANDS
|
|
|
27680
27947
|
--json Emit a JSON object.
|
|
27681
27948
|
knowledge-config set <mode> Set the mode for THIS directory (none | project | global);
|
|
27682
27949
|
persisted per-directory. Takes effect on the next run.
|
|
27950
|
+
knowledge-config set retrieval <vector|bm25>
|
|
27951
|
+
Set the retrieval strategy for THIS directory: 'vector'
|
|
27952
|
+
(hybrid embedding+BM25, default) or 'bm25' (pure lexical,
|
|
27953
|
+
no embedding model). Persisted per-directory.
|
|
27683
27954
|
|
|
27684
27955
|
status Show detected config, model/key, and readiness.
|
|
27685
27956
|
--config <path> Layer this config file on top (highest file precedence; base
|
|
@@ -27713,6 +27984,7 @@ CONFIGURE
|
|
|
27713
27984
|
|
|
27714
27985
|
Knowledge store \u2014 ON by default in every directory (mode 'global', one shared store).
|
|
27715
27986
|
per-directory: ${BINARY_NAME} knowledge-config set <none|project|global>
|
|
27987
|
+
${BINARY_NAME} knowledge-config set retrieval <vector|bm25>
|
|
27716
27988
|
machine-wide: PI_RESEARCH_KNOWLEDGE_STORE_MODE=<mode> in ${p.configEnv}
|
|
27717
27989
|
'project' scopes the store to the current directory; 'none' disables it here.
|
|
27718
27990
|
|
|
@@ -27814,6 +28086,22 @@ function parseArgs(argv) {
|
|
|
27814
28086
|
return out;
|
|
27815
28087
|
}
|
|
27816
28088
|
if (action === "set") {
|
|
28089
|
+
if (positional[1] === "retrieval") {
|
|
28090
|
+
const retrieval = positional[2];
|
|
28091
|
+
if (retrieval === void 0) {
|
|
28092
|
+
throw new UsageError("knowledge-config set retrieval requires a strategy: vector | bm25.");
|
|
28093
|
+
}
|
|
28094
|
+
if (retrieval !== "vector" && retrieval !== "bm25") {
|
|
28095
|
+
throw new UsageError(`invalid retrieval strategy "${retrieval}". Use one of: vector | bm25.`);
|
|
28096
|
+
}
|
|
28097
|
+
if (positional.length > 3) {
|
|
28098
|
+
throw new UsageError(`unexpected argument "${positional[3]}" after "knowledge-config set retrieval ${retrieval}".`);
|
|
28099
|
+
}
|
|
28100
|
+
out.command = "knowledge-config";
|
|
28101
|
+
out.knowledgeConfig = { action: "set-retrieval", retrieval, json };
|
|
28102
|
+
out.configPath = configPath;
|
|
28103
|
+
return out;
|
|
28104
|
+
}
|
|
27817
28105
|
const mode = positional[1];
|
|
27818
28106
|
if (mode === void 0) {
|
|
27819
28107
|
throw new UsageError("knowledge-config set requires a mode: none | project | global.");
|
|
@@ -27829,7 +28117,7 @@ function parseArgs(argv) {
|
|
|
27829
28117
|
out.configPath = configPath;
|
|
27830
28118
|
return out;
|
|
27831
28119
|
}
|
|
27832
|
-
throw new UsageError(`unknown knowledge-config action "${action}". Use: show | set <none|project|global>.`);
|
|
28120
|
+
throw new UsageError(`unknown knowledge-config action "${action}". Use: show | set <none|project|global> | set retrieval <vector|bm25>.`);
|
|
27833
28121
|
}
|
|
27834
28122
|
if (cmd === "skill") {
|
|
27835
28123
|
let json = false;
|