@devflow-tools/knowledge-engine 0.17.8 → 0.18.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.
- package/LICENSE +21 -0
- package/dist/dependency-source-compiler.d.ts +1 -0
- package/dist/dependency-source-compiler.d.ts.map +1 -1
- package/dist/dependency-source-compiler.js +37 -87
- package/dist/dependency-source-compiler.js.map +1 -1
- package/dist/embedding-provider.d.ts +1 -1
- package/dist/embedding-provider.d.ts.map +1 -1
- package/dist/embedding-provider.js +54 -0
- package/dist/embedding-provider.js.map +1 -1
- package/dist/hybrid-search.d.ts +3 -1
- package/dist/hybrid-search.d.ts.map +1 -1
- package/dist/hybrid-search.js +26 -10
- package/dist/hybrid-search.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledge-compiler.d.ts +1 -4
- package/dist/knowledge-compiler.d.ts.map +1 -1
- package/dist/knowledge-compiler.js +3 -13
- package/dist/knowledge-compiler.js.map +1 -1
- package/dist/knowledge-engine.d.ts +26 -58
- package/dist/knowledge-engine.d.ts.map +1 -1
- package/dist/knowledge-engine.js +159 -412
- package/dist/knowledge-engine.js.map +1 -1
- package/dist/knowledge-ingest-runtime.d.ts +4 -1
- package/dist/knowledge-ingest-runtime.d.ts.map +1 -1
- package/dist/knowledge-ingest-runtime.js +47 -33
- package/dist/knowledge-ingest-runtime.js.map +1 -1
- package/dist/knowledge-maintenance.d.ts.map +1 -1
- package/dist/knowledge-maintenance.js +38 -54
- package/dist/knowledge-maintenance.js.map +1 -1
- package/dist/knowledge-quality.js +1 -1
- package/dist/knowledge-quality.js.map +1 -1
- package/dist/knowledge-query-planner.d.ts +1 -1
- package/dist/knowledge-query-planner.d.ts.map +1 -1
- package/dist/knowledge-query-planner.js +4 -7
- package/dist/knowledge-query-planner.js.map +1 -1
- package/dist/knowledge-query-service.d.ts +62 -0
- package/dist/knowledge-query-service.d.ts.map +1 -0
- package/dist/knowledge-query-service.js +258 -0
- package/dist/knowledge-query-service.js.map +1 -0
- package/dist/knowledge-source-lifecycle.d.ts +91 -0
- package/dist/knowledge-source-lifecycle.d.ts.map +1 -0
- package/dist/knowledge-source-lifecycle.js +152 -0
- package/dist/knowledge-source-lifecycle.js.map +1 -0
- package/dist/knowledge-source-service.d.ts +66 -0
- package/dist/knowledge-source-service.d.ts.map +1 -0
- package/dist/knowledge-source-service.js +444 -0
- package/dist/knowledge-source-service.js.map +1 -0
- package/dist/knowledge-store.d.ts +59 -16
- package/dist/knowledge-store.d.ts.map +1 -1
- package/dist/knowledge-store.js +345 -133
- package/dist/knowledge-store.js.map +1 -1
- package/dist/markdown-converter.d.ts +0 -3
- package/dist/markdown-converter.d.ts.map +1 -1
- package/dist/markdown-converter.js +5 -5
- package/dist/markdown-converter.js.map +1 -1
- package/dist/semantic-output-contract.d.ts +3 -2
- package/dist/semantic-output-contract.d.ts.map +1 -1
- package/dist/semantic-output-contract.js +13 -0
- package/dist/semantic-output-contract.js.map +1 -1
- package/dist/source-catalog.d.ts +8 -6
- package/dist/source-catalog.d.ts.map +1 -1
- package/dist/source-catalog.js +26 -22
- package/dist/source-catalog.js.map +1 -1
- package/dist/source-readiness.d.ts +3 -2
- package/dist/source-readiness.d.ts.map +1 -1
- package/dist/source-readiness.js +10 -11
- package/dist/source-readiness.js.map +1 -1
- package/dist/source-selector.d.ts +1 -1
- package/dist/source-selector.d.ts.map +1 -1
- package/dist/source-selector.js +8 -3
- package/dist/source-selector.js.map +1 -1
- package/package.json +12 -4
package/dist/knowledge-engine.js
CHANGED
|
@@ -14,13 +14,16 @@ import { estimateKnowledgeTokens as estimateTokens, lexicalRelevance, resolveKno
|
|
|
14
14
|
import { detectProjectKnowledgeVersions, expectedVersionForSource, isKnowledgeVersionCompatible, } from './source-readiness.js';
|
|
15
15
|
import { evaluateKnowledgeQuality } from './knowledge-quality.js';
|
|
16
16
|
import { KnowledgeCompiler } from './knowledge-compiler.js';
|
|
17
|
-
import { SourceCatalog } from './source-catalog.js';
|
|
17
|
+
import { buildProjectKnowledgeProfile, SourceCatalog } from './source-catalog.js';
|
|
18
18
|
import { compileDependencySource } from './dependency-source-compiler.js';
|
|
19
19
|
import { expandKnowledgeEvidenceGraph } from './evidence-graph.js';
|
|
20
20
|
import { KNOWLEDGE_RETRIEVAL_SCORING_PROFILE } from './retrieval-scoring-profile.js';
|
|
21
21
|
import { planKnowledgeQuery } from './knowledge-query-planner.js';
|
|
22
22
|
import { emitSemanticControlLog } from '@devflow-tools/telemetry';
|
|
23
23
|
import { createLexicalOnlyEmbeddingProvider } from './embedding-provider.js';
|
|
24
|
+
import { requestValidatedEmbeddingBatch } from '@devflow-tools/provider-runtime';
|
|
25
|
+
import { KnowledgeSourceService } from './knowledge-source-service.js';
|
|
26
|
+
import { KnowledgeQueryService, } from './knowledge-query-service.js';
|
|
24
27
|
export const DEFAULT_KNOWLEDGE_TOKEN_BUDGET = 3000;
|
|
25
28
|
export const estimateKnowledgeTokens = estimateTokens;
|
|
26
29
|
function normalizeActiveSkill(value) {
|
|
@@ -45,13 +48,16 @@ function dependencyPackagesForMissingNeeds(needs, dependencies) {
|
|
|
45
48
|
return packageName ? [packageName] : [];
|
|
46
49
|
}))];
|
|
47
50
|
}
|
|
48
|
-
function
|
|
51
|
+
function compileKnowledgeFtsQuery(value) {
|
|
49
52
|
const terms = value.normalize('NFKC')
|
|
50
53
|
.match(/[\p{L}\p{N}_@.-]{2,}/gu)
|
|
51
54
|
?.map(term => term.replace(/^\.+|\.+$/gu, '').replace(/"/gu, ''))
|
|
52
55
|
.filter(Boolean)
|
|
53
56
|
.slice(0, 24) ?? [];
|
|
54
|
-
|
|
57
|
+
const uniqueTerms = [...new Set(terms)];
|
|
58
|
+
return uniqueTerms.length > 0
|
|
59
|
+
? uniqueTerms.map(term => `"${term}"`).join(' OR ')
|
|
60
|
+
: undefined;
|
|
55
61
|
}
|
|
56
62
|
function dedupeKnowledgeEntries(entries) {
|
|
57
63
|
const seen = new Set();
|
|
@@ -171,27 +177,36 @@ function normalizeLookup(value) {
|
|
|
171
177
|
}
|
|
172
178
|
export class KnowledgeEngine {
|
|
173
179
|
projectRoot;
|
|
180
|
+
projectId;
|
|
181
|
+
knowledgeAssetRoot;
|
|
174
182
|
hybridSearch;
|
|
175
183
|
store;
|
|
176
184
|
embedder;
|
|
177
185
|
compiler;
|
|
178
186
|
catalog;
|
|
187
|
+
sources;
|
|
188
|
+
query;
|
|
179
189
|
compatibilityRejectionCount = 0;
|
|
180
190
|
lowScoreRejectionCount = 0;
|
|
181
191
|
constructor(opts) {
|
|
182
192
|
this.projectRoot = opts.projectRoot;
|
|
193
|
+
this.projectId = opts.projectId;
|
|
194
|
+
this.knowledgeAssetRoot = opts.dataSource?.assetRoot ?? join(opts.projectRoot, '.devflow', 'knowledge');
|
|
183
195
|
this.embedder = opts.embedder ?? createLexicalOnlyEmbeddingProvider(opts.projectRoot);
|
|
184
|
-
this.store = new KnowledgeStore(join(this.knowledgeDir, 'knowledge.db'));
|
|
185
|
-
this.compiler = new KnowledgeCompiler(
|
|
186
|
-
this.catalog = new SourceCatalog(this.store, this.projectRoot);
|
|
187
|
-
this.catalog.
|
|
196
|
+
this.store = new KnowledgeStore(opts.dataSource?.databasePath ?? join(this.knowledgeDir, 'knowledge.db'), this.embedder.getCapability('knowledge_retrieval').profile);
|
|
197
|
+
this.compiler = new KnowledgeCompiler();
|
|
198
|
+
this.catalog = new SourceCatalog(this.store, this.projectRoot, this.projectId);
|
|
199
|
+
this.sources = new KnowledgeSourceService(this.store, this.catalog, this.projectRoot);
|
|
200
|
+
this.query = new KnowledgeQueryService({
|
|
201
|
+
searchReadOnly: (query, options) => this.searchReadOnlyInternal(query, options),
|
|
202
|
+
});
|
|
188
203
|
this.hybridSearch = new KnowledgeHybridSearch(this.store, this.embedder);
|
|
189
204
|
for (const suffix of ['', '-wal', '-shm']) {
|
|
190
205
|
rmSync(join(this.knowledgeDir, `vectors.db${suffix}`), { force: true });
|
|
191
206
|
}
|
|
192
207
|
}
|
|
193
208
|
get knowledgeDir() {
|
|
194
|
-
return
|
|
209
|
+
return this.knowledgeAssetRoot;
|
|
195
210
|
}
|
|
196
211
|
pluginDir(pluginName) {
|
|
197
212
|
return join(this.knowledgeDir, pluginName);
|
|
@@ -199,7 +214,7 @@ export class KnowledgeEngine {
|
|
|
199
214
|
ensureManifestSourceSchema(name, manifest, version) {
|
|
200
215
|
const sourceSchemaId = manifest.sourceSchemaId ?? `schema:${name}:v1`;
|
|
201
216
|
const sourceKind = manifest.sourceKind ?? (manifest.pluginName === 'builtin' ? 'official' : 'plugin');
|
|
202
|
-
this.
|
|
217
|
+
this.sources.registerSourceSchema({
|
|
203
218
|
id: sourceSchemaId,
|
|
204
219
|
sourceId: name,
|
|
205
220
|
sourceKind,
|
|
@@ -214,17 +229,18 @@ export class KnowledgeEngine {
|
|
|
214
229
|
resolvePersistedSourceVersion(name, manifest, existingVersion) {
|
|
215
230
|
if (manifest.version && manifest.version !== 'current')
|
|
216
231
|
return manifest.version;
|
|
217
|
-
const
|
|
232
|
+
const sourceKind = manifest.sourceKind ?? (manifest.pluginName === 'builtin' ? 'official' : 'plugin');
|
|
233
|
+
const expectedVersion = expectedVersionForSource(name, detectProjectKnowledgeVersions(this.projectRoot), sourceKind);
|
|
218
234
|
const persistedVersion = existingVersion && existingVersion !== 'current' ? existingVersion : undefined;
|
|
219
235
|
return expectedVersion ?? persistedVersion;
|
|
220
236
|
}
|
|
221
|
-
async registerSource(source) {
|
|
237
|
+
async registerSource(source, operationTimestamp = Date.now()) {
|
|
222
238
|
const dir = this.pluginDir(source.name);
|
|
223
239
|
mkdirSync(dir, { recursive: true });
|
|
224
240
|
const manifestPath = join(dir, "manifest.json");
|
|
225
241
|
const sourceKind = source.pluginName === 'builtin' ? 'official' : 'plugin';
|
|
226
242
|
const sourceSchemaId = `schema:${source.name}:v1`;
|
|
227
|
-
this.
|
|
243
|
+
this.sources.registerSourceSchema({
|
|
228
244
|
id: sourceSchemaId,
|
|
229
245
|
sourceId: source.name,
|
|
230
246
|
sourceKind,
|
|
@@ -235,7 +251,7 @@ export class KnowledgeEngine {
|
|
|
235
251
|
allowedEntities: [source.name],
|
|
236
252
|
supportedTaskKinds: ['overview', 'create', 'modify', 'debug', 'test', 'build', 'migrate', 'explain'],
|
|
237
253
|
refreshStrategy: 'on_demand',
|
|
238
|
-
});
|
|
254
|
+
}, operationTimestamp);
|
|
239
255
|
if (existsSync(manifestPath)) {
|
|
240
256
|
const existing = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
241
257
|
// Merge URLs — don't overwrite existing URLs
|
|
@@ -327,7 +343,7 @@ export class KnowledgeEngine {
|
|
|
327
343
|
})),
|
|
328
344
|
...gitResult,
|
|
329
345
|
].flatMap(c => {
|
|
330
|
-
const chunks = sliceByHeadings(c.content, {
|
|
346
|
+
const chunks = sliceByHeadings(c.content, { targetTokens: 1200, maxTokens: 3000 });
|
|
331
347
|
return chunks.length > 0
|
|
332
348
|
? chunks.map(chunk => ({ ...c, title: resolveKnowledgeTitle({ headingPath: chunk.headingPath, documentTitle: c.title, content: chunk.content, source: name, url: c.path ?? c.sourceUrl }), content: chunk.content, headingPath: chunk.headingPath, codeBlocks: chunk.codeBlocks }))
|
|
333
349
|
: [{ ...c, title: resolveKnowledgeTitle({ headingPath: c.headingPath, documentTitle: c.title, content: c.content, source: name, url: c.path ?? c.sourceUrl }) }];
|
|
@@ -357,19 +373,6 @@ export class KnowledgeEngine {
|
|
|
357
373
|
errors: [error.message],
|
|
358
374
|
}];
|
|
359
375
|
}
|
|
360
|
-
// Also write JSON files for backward compat
|
|
361
|
-
this.clearChunkFiles(dir);
|
|
362
|
-
for (const [i, c] of totalResult.entries()) {
|
|
363
|
-
const cf = {
|
|
364
|
-
id: `chunk-${String(i + 1).padStart(3, "0")}`,
|
|
365
|
-
title: c.title ?? name,
|
|
366
|
-
section: c.path,
|
|
367
|
-
content: c.content,
|
|
368
|
-
sourceUrl: `https://github.com/${sourceConfig.githubRepo}`,
|
|
369
|
-
fetchedAt: Date.now(),
|
|
370
|
-
};
|
|
371
|
-
writeFileSync(join(dir, `${cf.id}.json`), JSON.stringify(cf, null, 2), "utf-8");
|
|
372
|
-
}
|
|
373
376
|
writeFileSync(manifestPath, JSON.stringify({
|
|
374
377
|
...manifest,
|
|
375
378
|
urls: manifest.urls,
|
|
@@ -406,18 +409,6 @@ export class KnowledgeEngine {
|
|
|
406
409
|
version: manifest.version,
|
|
407
410
|
deferVectorBackfill: options.deferVectorBackfill,
|
|
408
411
|
});
|
|
409
|
-
this.clearChunkFiles(dir);
|
|
410
|
-
for (const [index, chunk] of compiled.chunks.entries()) {
|
|
411
|
-
const file = {
|
|
412
|
-
id: `chunk-${String(index + 1).padStart(3, '0')}`,
|
|
413
|
-
title: chunk.headingBreadcrumbs.at(-1) ?? compiled.page.title,
|
|
414
|
-
section: chunk.headingBreadcrumbs.join(' > '),
|
|
415
|
-
content: chunk.content,
|
|
416
|
-
sourceUrl: fetchedDocuments[0].url,
|
|
417
|
-
fetchedAt: Date.now(),
|
|
418
|
-
};
|
|
419
|
-
writeFileSync(join(dir, `${file.id}.json`), JSON.stringify(file, null, 2), 'utf8');
|
|
420
|
-
}
|
|
421
412
|
const successful = results.find(result => result.errors.length === 0);
|
|
422
413
|
if (successful)
|
|
423
414
|
successful.added = compiled.chunks.length;
|
|
@@ -449,139 +440,33 @@ export class KnowledgeEngine {
|
|
|
449
440
|
await closeBrowser();
|
|
450
441
|
return results;
|
|
451
442
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
? [{
|
|
477
|
-
title: chunk.title?.trim() || name,
|
|
478
|
-
content: chunk.content,
|
|
479
|
-
sourceUrl: chunk.sourceUrl || manifest.sourceUrl || manifest.urls?.[0] || '',
|
|
480
|
-
}]
|
|
481
|
-
: [];
|
|
482
|
-
}
|
|
483
|
-
catch {
|
|
484
|
-
return [];
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
if (chunkFiles.length === 0) {
|
|
488
|
-
chunkFiles = this.store.getDocChunks(name, 100_000).flatMap(chunk => typeof chunk.content === 'string' && chunk.content.trim()
|
|
489
|
-
? [{
|
|
490
|
-
title: String(chunk.heading_path || name),
|
|
491
|
-
content: chunk.content,
|
|
492
|
-
sourceUrl: manifest.sourceUrl || manifest.urls?.[0] || '',
|
|
493
|
-
}]
|
|
494
|
-
: []);
|
|
495
|
-
}
|
|
496
|
-
if (chunkFiles.length === 0) {
|
|
497
|
-
results.push({ source: name, documentsRetired: 0, chunks: 0, errors: ['no local chunks'] });
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
const combined = chunkFiles.map(chunk => `# ${chunk.title}\n\n${chunk.content}`).join('\n\n');
|
|
501
|
-
const existingVersion = this.store.listChunks({ sources: [name], limit: 1 })[0]?.version;
|
|
502
|
-
const version = this.resolvePersistedSourceVersion(name, manifest, existingVersion);
|
|
503
|
-
const sourceSchemaId = this.ensureManifestSourceSchema(name, manifest, version);
|
|
504
|
-
const url = chunkFiles.find(chunk => chunk.sourceUrl)?.sourceUrl
|
|
505
|
-
|| manifest.urls?.[0]
|
|
506
|
-
|| `devflow://knowledge/${name}`;
|
|
507
|
-
const compiled = this.compiler.compileAndStore({
|
|
508
|
-
projectRoot: this.projectRoot,
|
|
509
|
-
sourceId: name,
|
|
510
|
-
sourceSchemaId,
|
|
511
|
-
sourceKind: manifest.sourceKind ?? (manifest.pluginName === 'builtin' ? 'official' : 'plugin'),
|
|
512
|
-
sourceUri: url,
|
|
513
|
-
markdown: combined,
|
|
514
|
-
title: `${name} local knowledge`,
|
|
515
|
-
version,
|
|
516
|
-
});
|
|
517
|
-
const documentsRetired = this.store.deactivateSourceDocuments(name, compiled.page.id);
|
|
518
|
-
const storedChunks = this.store.listChunks({ sources: [name] });
|
|
519
|
-
const vectors = this.store.countEmbeddings(name);
|
|
520
|
-
const coverage = storedChunks.length > 0 ? vectors / storedChunks.length : 0;
|
|
521
|
-
const entities = [...new Set([
|
|
522
|
-
name,
|
|
523
|
-
...storedChunks.flatMap(chunk => parseJsonStrings(chunk.entities)),
|
|
524
|
-
])];
|
|
525
|
-
const symbols = [...new Set(storedChunks.flatMap(chunk => [
|
|
526
|
-
...parseJsonStrings(chunk.symbols),
|
|
527
|
-
...parseJsonStrings(chunk.api_identifiers),
|
|
528
|
-
]))];
|
|
529
|
-
this.catalog.activateSource({
|
|
530
|
-
schemaId: sourceSchemaId,
|
|
531
|
-
version,
|
|
532
|
-
contentHash: compiled.page.contentHash,
|
|
533
|
-
readiness: coverage >= 0.95
|
|
534
|
-
? {
|
|
535
|
-
status: 'ready', activeContentHash: compiled.page.contentHash,
|
|
536
|
-
documents: 1, chunks: storedChunks.length, vectors, coverage,
|
|
537
|
-
lastSuccessfulRefresh: Date.now(), lastAttempt: Date.now(),
|
|
538
|
-
}
|
|
539
|
-
: {
|
|
540
|
-
status: 'degraded', activeContentHash: compiled.page.contentHash,
|
|
541
|
-
documents: 1, chunks: storedChunks.length, vectors, coverage,
|
|
542
|
-
lastAttempt: Date.now(),
|
|
543
|
-
degradation: {
|
|
544
|
-
reason: 'vector_backfill_pending', category: 'vector_degraded', recoverable: true,
|
|
545
|
-
previousHealthyRevisionRetained: false,
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
entities,
|
|
549
|
-
symbols,
|
|
550
|
-
});
|
|
551
|
-
this.clearChunkFiles(dir);
|
|
552
|
-
for (const [index, chunk] of compiled.chunks.entries()) {
|
|
553
|
-
const file = {
|
|
554
|
-
id: `chunk-${String(index + 1).padStart(3, '0')}`,
|
|
555
|
-
title: chunk.headingBreadcrumbs.at(-1) ?? compiled.page.title,
|
|
556
|
-
section: chunk.headingBreadcrumbs.join(' > '),
|
|
557
|
-
content: chunk.content,
|
|
558
|
-
sourceUrl: url,
|
|
559
|
-
fetchedAt: Date.now(),
|
|
560
|
-
};
|
|
561
|
-
writeFileSync(join(dir, `${file.id}.json`), JSON.stringify(file, null, 2), 'utf8');
|
|
562
|
-
}
|
|
563
|
-
writeFileSync(manifestPath, JSON.stringify({
|
|
564
|
-
...manifest,
|
|
565
|
-
version: version ?? manifest.version,
|
|
566
|
-
fetchedAt: Date.now(),
|
|
567
|
-
chunkCount: compiled.chunks.length,
|
|
568
|
-
chunkPolicyVersion: KNOWLEDGE_CHUNK_POLICY_VERSION,
|
|
569
|
-
}, null, 2), 'utf8');
|
|
570
|
-
results.push({ source: name, documentsRetired, chunks: compiled.chunks.length, errors: [] });
|
|
571
|
-
}
|
|
572
|
-
catch (error) {
|
|
573
|
-
results.push({
|
|
574
|
-
source: name,
|
|
575
|
-
documentsRetired: 0,
|
|
576
|
-
chunks: 0,
|
|
577
|
-
errors: [error instanceof Error ? error.message : String(error)],
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
this.store.refreshSourceReadiness();
|
|
582
|
-
return results;
|
|
443
|
+
async ingestSourceMarkdown(input) {
|
|
444
|
+
const operationTimestamp = input.operationTimestamp ?? Date.now();
|
|
445
|
+
const manifestPath = join(this.pluginDir(input.source), 'manifest.json');
|
|
446
|
+
if (!existsSync(manifestPath))
|
|
447
|
+
throw new Error(`KNOWLEDGE_SOURCE_NOT_REGISTERED:${input.source}`);
|
|
448
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
449
|
+
const compiled = await this.replaceCompiledSource({
|
|
450
|
+
source: input.source,
|
|
451
|
+
manifest,
|
|
452
|
+
sourceUri: input.sourceUri,
|
|
453
|
+
title: input.title,
|
|
454
|
+
markdown: input.markdown,
|
|
455
|
+
version: this.resolvePersistedSourceVersion(input.source, manifest, input.version ?? manifest.version),
|
|
456
|
+
deferVectorBackfill: input.deferVectorBackfill,
|
|
457
|
+
operationTimestamp,
|
|
458
|
+
});
|
|
459
|
+
writeFileSync(manifestPath, JSON.stringify({
|
|
460
|
+
...manifest,
|
|
461
|
+
version: this.resolvePersistedSourceVersion(input.source, manifest, input.version ?? manifest.version),
|
|
462
|
+
fetchedAt: operationTimestamp,
|
|
463
|
+
chunkCount: compiled.chunks.length,
|
|
464
|
+
chunkPolicyVersion: KNOWLEDGE_CHUNK_POLICY_VERSION,
|
|
465
|
+
}, null, 2), 'utf8');
|
|
466
|
+
return compiled;
|
|
583
467
|
}
|
|
584
468
|
activatePersistedLocalSources(source) {
|
|
469
|
+
const operationTimestamp = Date.now();
|
|
585
470
|
if (!existsSync(this.knowledgeDir))
|
|
586
471
|
return [];
|
|
587
472
|
const activeCatalogSources = new Map(this.catalog.getCatalog().sources.map(entry => [entry.sourceId, entry]));
|
|
@@ -613,27 +498,20 @@ export class KnowledgeEngine {
|
|
|
613
498
|
if (currentPolicy && currentSemantic && currentCatalog)
|
|
614
499
|
continue;
|
|
615
500
|
if (!currentPolicy || !currentSemantic) {
|
|
616
|
-
const migrated = this.reingestLocalSources(name)[0];
|
|
617
|
-
const migratedChunks = this.store.countChunks(name);
|
|
618
|
-
const migratedVectors = this.store.countEmbeddings(name);
|
|
619
|
-
const errors = migrated?.errors ?? ['local policy migration produced no result'];
|
|
620
|
-
if (errors.length === 0) {
|
|
621
|
-
const activated = this.catalog.getCatalog().sources.find(entry => entry.sourceId === name);
|
|
622
|
-
if (activated)
|
|
623
|
-
activeCatalogSources.set(name, activated);
|
|
624
|
-
}
|
|
625
501
|
results.push({
|
|
626
502
|
source: name,
|
|
627
|
-
chunks:
|
|
628
|
-
vectors:
|
|
629
|
-
activated:
|
|
630
|
-
errors,
|
|
503
|
+
chunks: this.store.countChunks(name),
|
|
504
|
+
vectors: this.store.countEmbeddings(name),
|
|
505
|
+
activated: false,
|
|
506
|
+
errors: [`KNOWLEDGE_SOURCE_UNSUPPORTED:${name}:chunk_policy_or_provenance`],
|
|
631
507
|
});
|
|
632
508
|
continue;
|
|
633
509
|
}
|
|
634
510
|
const sourceSchemaId = this.ensureManifestSourceSchema(name, manifest, version);
|
|
511
|
+
const schema = this.store.getSourceSchema(sourceSchemaId);
|
|
512
|
+
if (!schema)
|
|
513
|
+
throw new Error(`Knowledge source schema ${sourceSchemaId} is not registered`);
|
|
635
514
|
const vectors = this.store.countEmbeddings(name);
|
|
636
|
-
const coverage = vectors / chunks.length;
|
|
637
515
|
const contentHash = createHash('sha256')
|
|
638
516
|
.update(chunks.map(chunk => `${chunk.id}\0${chunk.content_hash || chunk.content}`).sort().join('\n'))
|
|
639
517
|
.digest('hex');
|
|
@@ -645,30 +523,17 @@ export class KnowledgeEngine {
|
|
|
645
523
|
...parseJsonStrings(chunk.symbols),
|
|
646
524
|
...parseJsonStrings(chunk.api_identifiers),
|
|
647
525
|
]))];
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
526
|
+
this.sources.activateExistingRevision({
|
|
527
|
+
schema,
|
|
528
|
+
sourceLocator: chunks.find(chunk => chunk.source_url)?.source_url
|
|
529
|
+
|| chunks.find(chunk => chunk.url)?.url
|
|
530
|
+
|| `devflow://knowledge/${name}`,
|
|
651
531
|
version,
|
|
652
532
|
contentHash,
|
|
653
|
-
|
|
654
|
-
? {
|
|
655
|
-
status: 'ready', activeContentHash: contentHash,
|
|
656
|
-
documents: new Set(chunks.map(chunk => chunk.doc_id)).size,
|
|
657
|
-
chunks: chunks.length, vectors, coverage,
|
|
658
|
-
lastSuccessfulRefresh: now, lastAttempt: now,
|
|
659
|
-
}
|
|
660
|
-
: {
|
|
661
|
-
status: 'degraded', activeContentHash: contentHash,
|
|
662
|
-
documents: new Set(chunks.map(chunk => chunk.doc_id)).size,
|
|
663
|
-
chunks: chunks.length, vectors, coverage,
|
|
664
|
-
lastAttempt: now,
|
|
665
|
-
degradation: {
|
|
666
|
-
reason: 'vector_backfill_pending', category: 'vector_degraded', recoverable: true,
|
|
667
|
-
previousHealthyRevisionRetained: vectors > 0,
|
|
668
|
-
},
|
|
669
|
-
},
|
|
533
|
+
documents: new Set(chunks.map(chunk => chunk.doc_id)).size,
|
|
670
534
|
entities,
|
|
671
535
|
symbols,
|
|
536
|
+
operationTimestamp,
|
|
672
537
|
});
|
|
673
538
|
const activated = this.catalog.getCatalog().sources.find(entry => entry.sourceId === name);
|
|
674
539
|
if (activated)
|
|
@@ -687,12 +552,6 @@ export class KnowledgeEngine {
|
|
|
687
552
|
}
|
|
688
553
|
return results;
|
|
689
554
|
}
|
|
690
|
-
clearChunkFiles(dir) {
|
|
691
|
-
for (const file of readdirSync(dir)) {
|
|
692
|
-
if (/^chunk-\d+\.json$/.test(file))
|
|
693
|
-
rmSync(join(dir, file), { force: true });
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
555
|
async replaceDocument(doc, chunks, deferVectorBackfill = false) {
|
|
697
556
|
if (deferVectorBackfill) {
|
|
698
557
|
this.store.replaceDocumentChunks({ doc, chunks });
|
|
@@ -704,14 +563,19 @@ export class KnowledgeEngine {
|
|
|
704
563
|
?? new Error(this.embedder.getLastError() ?? 'Knowledge embedding warmup failed');
|
|
705
564
|
}
|
|
706
565
|
const texts = chunks.map(chunk => `${chunk.headingPath}\n${chunk.content}`);
|
|
707
|
-
const embeddings = await
|
|
566
|
+
const embeddings = await requestValidatedEmbeddingBatch({
|
|
567
|
+
provider: this.embedder,
|
|
568
|
+
purpose: 'knowledge_retrieval',
|
|
569
|
+
texts,
|
|
570
|
+
});
|
|
708
571
|
this.store.replaceDocument({ doc, chunks, embeddings });
|
|
709
572
|
}
|
|
710
573
|
async replaceCompiledSource(input) {
|
|
574
|
+
const operationTimestamp = input.operationTimestamp ?? Date.now();
|
|
711
575
|
const sourceSchemaId = input.manifest.sourceSchemaId ?? `schema:${input.source}:v1`;
|
|
712
576
|
const sourceKind = input.manifest.sourceKind ?? 'plugin';
|
|
713
577
|
if (!this.store.getSourceSchema(sourceSchemaId)) {
|
|
714
|
-
this.
|
|
578
|
+
this.sources.registerSourceSchema({
|
|
715
579
|
id: sourceSchemaId,
|
|
716
580
|
sourceId: input.source,
|
|
717
581
|
sourceKind,
|
|
@@ -724,88 +588,55 @@ export class KnowledgeEngine {
|
|
|
724
588
|
refreshStrategy: 'on_demand',
|
|
725
589
|
});
|
|
726
590
|
}
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
throw new Error(`Knowledge embedding count mismatch: ${embeddings.length}/${compiled.chunks.length}`);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
this.store.replaceCompiledPage(compiled.page, compiled.chunks);
|
|
753
|
-
this.store.replaceKnowledgeAnalysisManifest(compiled.analysis.manifest, compiled.analysis.quarantined);
|
|
754
|
-
if (embeddings.length > 0) {
|
|
755
|
-
this.store.insertEmbeddingBatch(compiled.chunks.map((chunk, index) => ({
|
|
756
|
-
id: chunk.id,
|
|
757
|
-
embedding: embeddings[index],
|
|
758
|
-
})));
|
|
591
|
+
const schema = this.store.getSourceSchema(sourceSchemaId);
|
|
592
|
+
if (!schema)
|
|
593
|
+
throw new Error(`Knowledge source schema ${sourceSchemaId} is not registered`);
|
|
594
|
+
const compiled = this.compiler.compile({
|
|
595
|
+
projectRoot: this.projectRoot,
|
|
596
|
+
projectId: this.catalog.projectId,
|
|
597
|
+
sourceId: input.source,
|
|
598
|
+
sourceSchemaId,
|
|
599
|
+
sourceKind,
|
|
600
|
+
sourceUri: input.sourceUri,
|
|
601
|
+
markdown: input.markdown,
|
|
602
|
+
title: input.title,
|
|
603
|
+
version: input.version === 'current' ? undefined : input.version,
|
|
604
|
+
compiledAt: operationTimestamp,
|
|
605
|
+
retrievedAt: operationTimestamp,
|
|
606
|
+
});
|
|
607
|
+
let embeddings = [];
|
|
608
|
+
if (!input.deferVectorBackfill) {
|
|
609
|
+
const warmed = await this.embedder.warmup();
|
|
610
|
+
if (!warmed) {
|
|
611
|
+
throw this.embedder.getLastFailure?.()
|
|
612
|
+
?? new Error(this.embedder.getLastError() ?? 'Knowledge embedding warmup failed');
|
|
759
613
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
schemaId: sourceSchemaId,
|
|
765
|
-
version: input.version === 'current' ? undefined : input.version,
|
|
766
|
-
contentHash: compiled.page.contentHash,
|
|
767
|
-
readiness: coverage >= 0.95
|
|
768
|
-
? {
|
|
769
|
-
status: 'ready', activeContentHash: compiled.page.contentHash,
|
|
770
|
-
documents: 1, chunks: compiled.chunks.length, vectors, coverage,
|
|
771
|
-
lastSuccessfulRefresh: Date.now(), lastAttempt: Date.now(),
|
|
772
|
-
}
|
|
773
|
-
: {
|
|
774
|
-
status: 'degraded', activeContentHash: compiled.page.contentHash,
|
|
775
|
-
documents: 1, chunks: compiled.chunks.length, vectors, coverage,
|
|
776
|
-
lastAttempt: Date.now(),
|
|
777
|
-
degradation: {
|
|
778
|
-
reason: 'vector_backfill_pending', category: 'vector_degraded', recoverable: true,
|
|
779
|
-
previousHealthyRevisionRetained: false,
|
|
780
|
-
},
|
|
781
|
-
},
|
|
782
|
-
entities: [input.source, ...compiled.page.entities],
|
|
783
|
-
symbols: compiled.page.symbols,
|
|
784
|
-
refreshJobId: refresh.id,
|
|
614
|
+
embeddings = await requestValidatedEmbeddingBatch({
|
|
615
|
+
provider: this.embedder,
|
|
616
|
+
purpose: 'knowledge_retrieval',
|
|
617
|
+
texts: compiled.chunks.map(chunk => chunk.embeddingText),
|
|
785
618
|
});
|
|
786
|
-
return compiled;
|
|
787
|
-
}
|
|
788
|
-
catch (error) {
|
|
789
|
-
this.catalog.failRefresh(refresh.id, error);
|
|
790
|
-
throw error;
|
|
791
619
|
}
|
|
620
|
+
this.sources.publishCompiledSource({ compiled, schema, embeddings, operationTimestamp });
|
|
621
|
+
return compiled;
|
|
792
622
|
}
|
|
793
623
|
compileDependency(packageName) {
|
|
794
624
|
return compileDependencySource({
|
|
795
625
|
projectRoot: this.projectRoot,
|
|
626
|
+
projectId: this.projectId,
|
|
796
627
|
packageName,
|
|
797
628
|
store: this.store,
|
|
798
629
|
deferVectorBackfill: true,
|
|
799
630
|
});
|
|
800
631
|
}
|
|
801
632
|
async getProjectProfile() {
|
|
802
|
-
return this.store.getProjectProfile(this.
|
|
633
|
+
return this.store.getProjectProfile(this.catalog.projectId) ?? this.catalog.refreshProjectProfile();
|
|
803
634
|
}
|
|
804
635
|
async getSourceCatalog() {
|
|
805
636
|
return this.catalog.getCatalog();
|
|
806
637
|
}
|
|
807
638
|
async getCoverageGaps() {
|
|
808
|
-
return this.store.listCoverageGaps(this.
|
|
639
|
+
return this.store.listCoverageGaps(this.catalog.projectId);
|
|
809
640
|
}
|
|
810
641
|
async search(query, options) {
|
|
811
642
|
return (await this.searchWithStatus(query, options)).entries;
|
|
@@ -822,7 +653,7 @@ export class KnowledgeEngine {
|
|
|
822
653
|
queryPlanHash: options?.queryPlanHash,
|
|
823
654
|
};
|
|
824
655
|
try {
|
|
825
|
-
const result = await this.
|
|
656
|
+
const result = await this.query.search(query, options);
|
|
826
657
|
const abstained = result.retrievalDiagnostics?.abstentionReason;
|
|
827
658
|
emitSemanticControlLog({
|
|
828
659
|
event: 'knowledge.query.executed',
|
|
@@ -866,44 +697,18 @@ export class KnowledgeEngine {
|
|
|
866
697
|
throw error;
|
|
867
698
|
}
|
|
868
699
|
}
|
|
869
|
-
async
|
|
700
|
+
async searchReadOnlyInternal(query, options) {
|
|
870
701
|
const limit = options?.limit ?? 10;
|
|
871
702
|
const queryDecision = planKnowledgeQuery({
|
|
872
703
|
action: options?.action,
|
|
873
704
|
knowledgeNeeds: options?.knowledgeNeeds,
|
|
874
705
|
retrievalIntent: options?.retrievalIntent ?? 'explicit',
|
|
875
706
|
});
|
|
876
|
-
if (queryDecision.requirement === 'knowledge_not_required') {
|
|
877
|
-
return {
|
|
878
|
-
entries: [],
|
|
879
|
-
warnings: ['knowledge_not_required'],
|
|
880
|
-
budgetTokens: Math.max(0, options?.budgetTokens ?? DEFAULT_KNOWLEDGE_TOKEN_BUDGET),
|
|
881
|
-
tokenCount: 0,
|
|
882
|
-
truncatedCount: 0,
|
|
883
|
-
rejectedLowScore: 0,
|
|
884
|
-
rejectedIncompatibleSources: 0,
|
|
885
|
-
retrievalDiagnostics: {
|
|
886
|
-
selectedSources: [],
|
|
887
|
-
sourceSignals: [],
|
|
888
|
-
sourceRejections: [],
|
|
889
|
-
coverageGaps: [],
|
|
890
|
-
compatibleChunks: 0,
|
|
891
|
-
hybridCandidates: 0,
|
|
892
|
-
qualityRejected: {},
|
|
893
|
-
abstentionReason: queryDecision.reason,
|
|
894
|
-
},
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
707
|
const activeSkill = options?.activeSkill ?? options?.projectSignals?.activeSkill;
|
|
898
708
|
const taskAware = Boolean(options?.action || queryDecision.needs.length);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
if (availableSources.some(source => !catalogSources.has(source))) {
|
|
903
|
-
this.activatePersistedLocalSources();
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
const projectProfile = this.catalog.refreshProjectProfile({
|
|
709
|
+
const availableSources = this.store.getDistinctSources();
|
|
710
|
+
const persistedProfile = this.store.getProjectProfile(this.catalog.projectId);
|
|
711
|
+
const projectProfile = persistedProfile ?? buildProjectKnowledgeProfile(this.projectRoot, this.catalog.projectId, {
|
|
907
712
|
activeSkills: activeSkill ? [normalizeActiveSkill(activeSkill)] : [],
|
|
908
713
|
});
|
|
909
714
|
const selectionInput = () => ({
|
|
@@ -921,65 +726,36 @@ export class KnowledgeEngine {
|
|
|
921
726
|
action: options?.action,
|
|
922
727
|
knowledgeNeeds: queryDecision.needs,
|
|
923
728
|
});
|
|
924
|
-
|
|
925
|
-
const compileFailures =
|
|
926
|
-
|
|
927
|
-
?
|
|
928
|
-
: []) {
|
|
929
|
-
try {
|
|
930
|
-
this.compileDependency(packageName);
|
|
931
|
-
}
|
|
932
|
-
catch (error) {
|
|
933
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
934
|
-
this.catalog.recordCoverageGap({
|
|
729
|
+
const selection = selectKnowledgeSources(selectionInput());
|
|
730
|
+
const compileFailures = selection.missingNeeds.flatMap(need => {
|
|
731
|
+
const packageName = dependencyPackagesForMissingNeeds([need], projectProfile.dependencies)[0];
|
|
732
|
+
return packageName ? [{
|
|
935
733
|
sourceId: `dependency:${packageName}`,
|
|
936
|
-
kind: '
|
|
734
|
+
kind: 'missing_source',
|
|
937
735
|
entity: packageName,
|
|
938
|
-
reason
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
if (selection.missingNeeds.length > 0) {
|
|
944
|
-
availableSources = this.store.getDistinctSources();
|
|
945
|
-
selection = selectKnowledgeSources(selectionInput());
|
|
946
|
-
}
|
|
947
|
-
for (const gap of selection.coverageGaps)
|
|
948
|
-
this.catalog.recordCoverageGap(gap);
|
|
736
|
+
reason: `Dependency knowledge source ${packageName} requires an explicit ingest command`,
|
|
737
|
+
}] : [];
|
|
738
|
+
});
|
|
949
739
|
this.compatibilityRejectionCount += selection.rejectedSources.length;
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (!legacy)
|
|
956
|
-
continue;
|
|
957
|
-
const migrated = this.reingestLocalSources(source)[0];
|
|
958
|
-
if (!migrated || migrated.errors.length > 0 || migrated.chunks === 0) {
|
|
959
|
-
semanticMigrationWarnings.push(`knowledge-semantic-migration-failed:${source}:${migrated?.errors[0] ?? 'no_result'}`);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
semanticMigrationWarnings.push(`knowledge-semantic-migrated:${source}:${migrated.chunks}`);
|
|
963
|
-
try {
|
|
964
|
-
const backfill = await this.backfillVectors({ source, batchSize: 32 });
|
|
965
|
-
semanticMigrationWarnings.push(`knowledge-semantic-vectors:${source}:${backfill.processed}:${backfill.remaining}`);
|
|
966
|
-
}
|
|
967
|
-
catch (error) {
|
|
968
|
-
semanticMigrationWarnings.push(`knowledge-semantic-vector-degraded:${source}:${error instanceof Error ? error.message : String(error)}`);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
}
|
|
740
|
+
const unsupportedSources = new Set(selection.sources.filter(source => this.store.listChunks({ sources: [source], limit: 200_000 })
|
|
741
|
+
.some(chunk => !provenanceComplete(chunk.provenance))));
|
|
742
|
+
const sources = selection.sources.filter(source => !unsupportedSources.has(source));
|
|
743
|
+
const unsupportedSourceWarnings = [...unsupportedSources]
|
|
744
|
+
.map(source => `knowledge-source-unsupported:${source}`);
|
|
972
745
|
const budgetTokens = Math.max(0, options?.budgetTokens ?? DEFAULT_KNOWLEDGE_TOKEN_BUDGET);
|
|
973
746
|
const projectVersions = detectProjectKnowledgeVersions(this.projectRoot);
|
|
974
|
-
this.
|
|
975
|
-
const readinessWarnings =
|
|
976
|
-
|
|
977
|
-
|
|
747
|
+
const catalogBySource = new Map(this.catalog.getCatalog().sources.map(entry => [entry.sourceId, entry]));
|
|
748
|
+
const readinessWarnings = sources.flatMap(source => {
|
|
749
|
+
const readiness = catalogBySource.get(source)?.readiness;
|
|
750
|
+
return readiness && readiness.status !== 'ready'
|
|
751
|
+
? [`knowledge-source-degraded:${source}:${readiness.degradation?.category ?? readiness.status}`]
|
|
752
|
+
: [];
|
|
753
|
+
});
|
|
978
754
|
const coverageWarnings = selection.coverageGaps.length > 0 ? ['knowledge_source_missing'] : [];
|
|
979
755
|
if (sources.length === 0) {
|
|
980
756
|
return {
|
|
981
757
|
entries: [],
|
|
982
|
-
warnings: [...
|
|
758
|
+
warnings: [...unsupportedSourceWarnings, ...coverageWarnings, ...readinessWarnings, 'knowledge_insufficient'],
|
|
983
759
|
budgetTokens,
|
|
984
760
|
tokenCount: 0,
|
|
985
761
|
truncatedCount: 0,
|
|
@@ -997,11 +773,11 @@ export class KnowledgeEngine {
|
|
|
997
773
|
}
|
|
998
774
|
const allChunks = this.store.listChunks({ sources });
|
|
999
775
|
const chunks = allChunks.filter(chunk => chunk.chunk_policy_version === KNOWLEDGE_CHUNK_POLICY_VERSION
|
|
1000
|
-
&& isKnowledgeVersionCompatible(chunk.source, chunk.version, projectVersions));
|
|
776
|
+
&& isKnowledgeVersionCompatible(chunk.source, chunk.version, projectVersions, catalogBySource.get(chunk.source)?.sourceKind ?? 'plugin'));
|
|
1001
777
|
const rejectedByQualityGate = allChunks.length - chunks.length;
|
|
1002
778
|
if (chunks.length === 0) {
|
|
1003
779
|
return {
|
|
1004
|
-
entries: [], warnings: [...
|
|
780
|
+
entries: [], warnings: [...unsupportedSourceWarnings, ...coverageWarnings, ...readinessWarnings, 'knowledge_insufficient'], budgetTokens, tokenCount: 0, truncatedCount: 0,
|
|
1005
781
|
rejectedIncompatibleSources: selection.rejectedSources.length + rejectedByQualityGate,
|
|
1006
782
|
retrievalDiagnostics: {
|
|
1007
783
|
selectedSources: sources,
|
|
@@ -1033,7 +809,7 @@ export class KnowledgeEngine {
|
|
|
1033
809
|
const packed = this.packEntries(candidates, chunks, budgetTokens);
|
|
1034
810
|
return {
|
|
1035
811
|
...packed,
|
|
1036
|
-
warnings: [...
|
|
812
|
+
warnings: [...unsupportedSourceWarnings, ...coverageWarnings, ...readinessWarnings],
|
|
1037
813
|
budgetTokens,
|
|
1038
814
|
rejectedIncompatibleSources: selection.rejectedSources.length + rejectedByQualityGate,
|
|
1039
815
|
retrievalDiagnostics: {
|
|
@@ -1047,25 +823,11 @@ export class KnowledgeEngine {
|
|
|
1047
823
|
},
|
|
1048
824
|
};
|
|
1049
825
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
// Retry below with a bounded literal-term query.
|
|
1056
|
-
}
|
|
1057
|
-
if (bm25Results.length === 0) {
|
|
1058
|
-
const safeQuery = buildSafeFtsQuery(query);
|
|
1059
|
-
if (safeQuery) {
|
|
1060
|
-
try {
|
|
1061
|
-
bm25Results = this.store.searchBM25(safeQuery, limit * 3, sources);
|
|
1062
|
-
}
|
|
1063
|
-
catch {
|
|
1064
|
-
// Vector retrieval can still answer when FTS cannot parse any bounded fallback.
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
const hybrid = await this.hybridSearch.searchWithStatus(query, chunks, bm25Results, limit, sources, options?.minScore, options?.knowledgeNeeds);
|
|
826
|
+
const lexicalQuery = compileKnowledgeFtsQuery(query);
|
|
827
|
+
const bm25Results = lexicalQuery
|
|
828
|
+
? this.store.searchBM25(lexicalQuery, limit * 3, sources)
|
|
829
|
+
: [];
|
|
830
|
+
const hybrid = await this.hybridSearch.searchWithStatus(query, chunks, bm25Results, limit, sources, options?.minScore, options?.knowledgeNeeds, options?.embeddingProfileHash);
|
|
1069
831
|
this.lowScoreRejectionCount += hybrid.rejectedLowScore;
|
|
1070
832
|
const chunkMap = new Map(chunks.map(chunk => [chunk.id, chunk]));
|
|
1071
833
|
const qualityRejected = {};
|
|
@@ -1075,7 +837,7 @@ export class KnowledgeEngine {
|
|
|
1075
837
|
query,
|
|
1076
838
|
chunk,
|
|
1077
839
|
retrievalScore: result.score,
|
|
1078
|
-
versionCompatible: isKnowledgeVersionCompatible(chunk.source, chunk.version, projectVersions),
|
|
840
|
+
versionCompatible: isKnowledgeVersionCompatible(chunk.source, chunk.version, projectVersions, catalogBySource.get(chunk.source)?.sourceKind ?? 'plugin'),
|
|
1079
841
|
action: options?.action,
|
|
1080
842
|
knowledgeNeeds: options?.knowledgeNeeds,
|
|
1081
843
|
});
|
|
@@ -1120,7 +882,7 @@ export class KnowledgeEngine {
|
|
|
1120
882
|
query,
|
|
1121
883
|
chunk,
|
|
1122
884
|
retrievalScore: expansion.score,
|
|
1123
|
-
versionCompatible: isKnowledgeVersionCompatible(chunk.source, chunk.version, projectVersions),
|
|
885
|
+
versionCompatible: isKnowledgeVersionCompatible(chunk.source, chunk.version, projectVersions, catalogBySource.get(chunk.source)?.sourceKind ?? 'plugin'),
|
|
1124
886
|
action: options?.action,
|
|
1125
887
|
knowledgeNeeds: options?.knowledgeNeeds,
|
|
1126
888
|
});
|
|
@@ -1163,7 +925,7 @@ export class KnowledgeEngine {
|
|
|
1163
925
|
const packed = this.packEntries(candidates, chunks, budgetTokens);
|
|
1164
926
|
return {
|
|
1165
927
|
...packed,
|
|
1166
|
-
warnings: [...
|
|
928
|
+
warnings: [...unsupportedSourceWarnings, ...hybrid.warnings, ...coverageWarnings, ...readinessWarnings, ...(candidates.length === 0 ? ['knowledge_insufficient'] : [])],
|
|
1167
929
|
budgetTokens,
|
|
1168
930
|
rejectedLowScore: hybrid.rejectedLowScore,
|
|
1169
931
|
rejectedIncompatibleSources: selection.rejectedSources.length + rejectedByQualityGate,
|
|
@@ -1176,6 +938,9 @@ export class KnowledgeEngine {
|
|
|
1176
938
|
hybridCandidates: hybrid.results.length,
|
|
1177
939
|
qualityRejected,
|
|
1178
940
|
channelCandidateCounts: hybrid.channelCandidateCounts,
|
|
941
|
+
vectorStatus: hybrid.vectorStatus,
|
|
942
|
+
embeddingProfileHash: hybrid.embeddingCapability.profile.profileHash,
|
|
943
|
+
embeddingGeneration: hybrid.embeddingCapability.profile.generation,
|
|
1179
944
|
graphExpansions: graphCandidates.length,
|
|
1180
945
|
},
|
|
1181
946
|
};
|
|
@@ -1185,6 +950,11 @@ export class KnowledgeEngine {
|
|
|
1185
950
|
const entries = [];
|
|
1186
951
|
const sourceTokens = new Map();
|
|
1187
952
|
const pageTokens = new Map();
|
|
953
|
+
const structuredPages = new Set(candidates.flatMap(entry => {
|
|
954
|
+
const pageId = chunkMap.get(entry.id)?.page_id;
|
|
955
|
+
return pageId ? [pageId] : [];
|
|
956
|
+
}));
|
|
957
|
+
const enforcePageDiversity = structuredPages.size > 1;
|
|
1188
958
|
let tokenCount = 0;
|
|
1189
959
|
let truncatedCount = 0;
|
|
1190
960
|
for (const entry of candidates) {
|
|
@@ -1199,7 +969,7 @@ export class KnowledgeEngine {
|
|
|
1199
969
|
const pageLimit = Math.max(entryTokens, Math.floor(budgetTokens * 0.40));
|
|
1200
970
|
const overSourceBudget = Boolean(structuredPage)
|
|
1201
971
|
&& (sourceTokens.get(source) ?? 0) + entryTokens > sourceLimit;
|
|
1202
|
-
const overPageBudget = Boolean(structuredPage)
|
|
972
|
+
const overPageBudget = enforcePageDiversity && Boolean(structuredPage)
|
|
1203
973
|
&& (pageTokens.get(structuredPage) ?? 0) + entryTokens > pageLimit;
|
|
1204
974
|
if (tokenCount + entryTokens > budgetTokens || overSourceBudget || overPageBudget) {
|
|
1205
975
|
truncatedCount++;
|
|
@@ -1230,7 +1000,11 @@ export class KnowledgeEngine {
|
|
|
1230
1000
|
});
|
|
1231
1001
|
if (chunks.length === 0)
|
|
1232
1002
|
break;
|
|
1233
|
-
const embeddings = await
|
|
1003
|
+
const embeddings = await requestValidatedEmbeddingBatch({
|
|
1004
|
+
provider: this.embedder,
|
|
1005
|
+
purpose: 'knowledge_retrieval',
|
|
1006
|
+
texts: chunks.map(chunk => `${chunk.heading_path}\n${chunk.content}`),
|
|
1007
|
+
});
|
|
1234
1008
|
this.store.insertEmbeddingBatch(chunks.map((chunk, index) => ({
|
|
1235
1009
|
id: chunk.id,
|
|
1236
1010
|
embedding: embeddings[index],
|
|
@@ -1241,37 +1015,10 @@ export class KnowledgeEngine {
|
|
|
1241
1015
|
- this.store.countEmbeddings(options.source);
|
|
1242
1016
|
options.onProgress?.({ processed, remaining, lastId });
|
|
1243
1017
|
}
|
|
1244
|
-
const versions = detectProjectKnowledgeVersions(this.projectRoot);
|
|
1245
|
-
this.store.refreshSourceReadiness(Object.fromEntries(this.store.getDistinctSources().map(source => [source, expectedVersionForSource(source, versions)])));
|
|
1246
1018
|
for (const entry of this.catalog.getCatalog().sources) {
|
|
1247
1019
|
if (options.source && entry.sourceId !== options.source)
|
|
1248
1020
|
continue;
|
|
1249
|
-
|
|
1250
|
-
const vectors = this.store.countEmbeddings(entry.sourceId);
|
|
1251
|
-
const coverage = chunks > 0 ? vectors / chunks : 0;
|
|
1252
|
-
this.catalog.activateSource({
|
|
1253
|
-
schemaId: entry.schemaId,
|
|
1254
|
-
version: entry.version,
|
|
1255
|
-
contentHash: entry.contentHash,
|
|
1256
|
-
readiness: coverage >= 0.95
|
|
1257
|
-
? {
|
|
1258
|
-
status: 'ready', activeContentHash: entry.contentHash,
|
|
1259
|
-
documents: entry.readiness.documents, chunks, vectors, coverage,
|
|
1260
|
-
lastSuccessfulRefresh: Date.now(), lastAttempt: Date.now(),
|
|
1261
|
-
}
|
|
1262
|
-
: {
|
|
1263
|
-
status: 'degraded', activeContentHash: entry.contentHash,
|
|
1264
|
-
documents: entry.readiness.documents, chunks, vectors, coverage,
|
|
1265
|
-
lastAttempt: Date.now(),
|
|
1266
|
-
degradation: {
|
|
1267
|
-
reason: 'vector_backfill_pending', category: 'vector_degraded', recoverable: true,
|
|
1268
|
-
previousHealthyRevisionRetained: true,
|
|
1269
|
-
},
|
|
1270
|
-
},
|
|
1271
|
-
entities: entry.entities,
|
|
1272
|
-
symbols: entry.symbols,
|
|
1273
|
-
taskKinds: entry.taskKinds,
|
|
1274
|
-
});
|
|
1021
|
+
this.sources.refreshIndexReadiness(entry, options.operationTimestamp ?? Date.now());
|
|
1275
1022
|
}
|
|
1276
1023
|
return {
|
|
1277
1024
|
processed,
|