@equationalapplications/core-llm-wiki 7.7.5 → 7.7.7
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-JV2OQ5CM.mjs → chunk-46X3HO77.mjs} +142 -14
- package/dist/chunk-46X3HO77.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +173 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +36 -6
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-CH_Kk34-.d.mts → testing-BIFNLUva.d.mts} +69 -0
- package/dist/{testing-CH_Kk34-.d.ts → testing-BIFNLUva.d.ts} +69 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +140 -12
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-JV2OQ5CM.mjs.map +0 -1
|
@@ -432,7 +432,31 @@ var _SearchService = class _SearchService {
|
|
|
432
432
|
* auto-vacuum debt behind the TypeError in #64.
|
|
433
433
|
*/
|
|
434
434
|
this.syncChain = Promise.resolve();
|
|
435
|
-
|
|
435
|
+
/**
|
|
436
|
+
* Entities whose index may have drifted from SQLite: an incremental update
|
|
437
|
+
* failed part-way, or core wrote rows it could not index (upsertGraph runs in
|
|
438
|
+
* the host's transaction). Their next syncEntries rebuilds the entity in full.
|
|
439
|
+
* See spec 2026-09-28 §5.
|
|
440
|
+
*/
|
|
441
|
+
this.staleEntities = /* @__PURE__ */ new Set();
|
|
442
|
+
/**
|
|
443
|
+
* Per-entity count of markStale() calls. A rebuild clears an entity's stale
|
|
444
|
+
* flag only when the count it snapshotted before its read still holds
|
|
445
|
+
* afterwards: markStale() runs inside the host's still-open transaction, so
|
|
446
|
+
* a call landing mid-read belongs to rows the read cannot have seen, and
|
|
447
|
+
* its flag must outlive the turn (#233 review).
|
|
448
|
+
*/
|
|
449
|
+
this.staleEpochs = /* @__PURE__ */ new Map();
|
|
450
|
+
this.miniSearch = this.createMiniSearch();
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* A fresh index with the production options. clearAll() swaps one in because
|
|
454
|
+
* MiniSearch.removeAll() empties the index but leaves dirtCount (and its
|
|
455
|
+
* vacuum bookkeeping) at its old value, which would trip syncEntries'
|
|
456
|
+
* conditional vacuum early after a clear.
|
|
457
|
+
*/
|
|
458
|
+
createMiniSearch() {
|
|
459
|
+
return new MiniSearch({
|
|
436
460
|
fields: ["title", "body", "tags"],
|
|
437
461
|
storeFields: ["entity_id"],
|
|
438
462
|
// Vacuuming is driven explicitly at the end of each serialized rebuild
|
|
@@ -459,7 +483,19 @@ var _SearchService = class _SearchService {
|
|
|
459
483
|
const work = this.syncChain.then(async () => {
|
|
460
484
|
try {
|
|
461
485
|
try {
|
|
486
|
+
const epochsBefore = new Map(this.staleEpochs);
|
|
462
487
|
await this.rebuildIndex(entityId);
|
|
488
|
+
if (entityId) {
|
|
489
|
+
if ((this.staleEpochs.get(entityId) ?? 0) === (epochsBefore.get(entityId) ?? 0)) {
|
|
490
|
+
this.staleEntities.delete(entityId);
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
for (const id of [...this.staleEntities]) {
|
|
494
|
+
if ((this.staleEpochs.get(id) ?? 0) === (epochsBefore.get(id) ?? 0)) {
|
|
495
|
+
this.staleEntities.delete(id);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
463
499
|
await this.miniSearch.vacuum();
|
|
464
500
|
} finally {
|
|
465
501
|
this.evictCache(entityId);
|
|
@@ -471,6 +507,68 @@ var _SearchService = class _SearchService {
|
|
|
471
507
|
this.syncChain = work;
|
|
472
508
|
return work;
|
|
473
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Re-indexes only `ids` for `entityId`: drops each from the index, then
|
|
512
|
+
* re-adds the ones still live in SQLite, so soft-deleted or missing ids end
|
|
513
|
+
* up absent. Costs O(ids), where sync(entityId) costs O(entity) — callers that
|
|
514
|
+
* know what a write touched use this so chunked imports stay linear (#232).
|
|
515
|
+
*
|
|
516
|
+
* Serialized with sync() on the same chain and, like it, never rejects. An
|
|
517
|
+
* entity that is stale or has never been indexed gets a full rebuild instead
|
|
518
|
+
* — including on an empty `ids` list, which is a no-op only for an entity
|
|
519
|
+
* the index already tracks, and even then one that waits for rebuilds
|
|
520
|
+
* already queued on the chain.
|
|
521
|
+
*/
|
|
522
|
+
async syncEntries(entityId, ids) {
|
|
523
|
+
const uniqueIds = [...new Set(ids)];
|
|
524
|
+
if (uniqueIds.length === 0 && !this.staleEntities.has(entityId) && this.miniSearchEntryIdsByEntity.has(entityId)) {
|
|
525
|
+
return this.syncChain;
|
|
526
|
+
}
|
|
527
|
+
const work = this.syncChain.then(async () => {
|
|
528
|
+
try {
|
|
529
|
+
try {
|
|
530
|
+
const epochsBefore = new Map(this.staleEpochs);
|
|
531
|
+
const needsRebuild = () => this.staleEntities.has(entityId) || !this.miniSearchEntryIdsByEntity.has(entityId);
|
|
532
|
+
if (!needsRebuild()) {
|
|
533
|
+
const rows = await this.entryRepo.findMiniSearchRowsByIds(entityId, uniqueIds);
|
|
534
|
+
const tracked = this.miniSearchEntryIdsByEntity.get(entityId);
|
|
535
|
+
if (tracked && !this.staleEntities.has(entityId)) {
|
|
536
|
+
for (const id of uniqueIds) {
|
|
537
|
+
if (tracked.delete(id) && this.miniSearch.has(id)) this.miniSearch.discard(id);
|
|
538
|
+
}
|
|
539
|
+
const documents = rows.map((row) => this.normalizeMiniSearchRow(row));
|
|
540
|
+
if (documents.length > 0) this.miniSearch.addAll(documents);
|
|
541
|
+
for (const document of documents) tracked.add(document.id);
|
|
542
|
+
if (this.miniSearch.dirtCount > 0) {
|
|
543
|
+
await this.miniSearch.vacuum();
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
await this.rebuildIndex(entityId);
|
|
549
|
+
if ((this.staleEpochs.get(entityId) ?? 0) === (epochsBefore.get(entityId) ?? 0)) {
|
|
550
|
+
this.staleEntities.delete(entityId);
|
|
551
|
+
}
|
|
552
|
+
await this.miniSearch.vacuum();
|
|
553
|
+
} finally {
|
|
554
|
+
this.evictCache(entityId);
|
|
555
|
+
}
|
|
556
|
+
} catch (err) {
|
|
557
|
+
this.staleEntities.add(entityId);
|
|
558
|
+
console.warn(`[WikiMemory] search index incremental sync failed for ${entityId}:`, err);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
this.syncChain = work;
|
|
562
|
+
return work;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Forces the entity's next syncEntries to rebuild it in full. For writes core
|
|
566
|
+
* cannot index itself, such as upsertGraph inside a host transaction.
|
|
567
|
+
*/
|
|
568
|
+
markStale(entityId) {
|
|
569
|
+
this.staleEntities.add(entityId);
|
|
570
|
+
this.staleEpochs.set(entityId, (this.staleEpochs.get(entityId) ?? 0) + 1);
|
|
571
|
+
}
|
|
474
572
|
/**
|
|
475
573
|
* Clears the parsed vector cache. Useful for mid-loop flush guarantees
|
|
476
574
|
* or memory pressure evictions.
|
|
@@ -487,8 +585,10 @@ var _SearchService = class _SearchService {
|
|
|
487
585
|
*/
|
|
488
586
|
clearAll() {
|
|
489
587
|
this.vectorCache.clear();
|
|
490
|
-
this.miniSearch.
|
|
588
|
+
this.miniSearch = this.createMiniSearch();
|
|
491
589
|
this.miniSearchEntryIdsByEntity.clear();
|
|
590
|
+
this.staleEntities.clear();
|
|
591
|
+
this.staleEpochs.clear();
|
|
492
592
|
}
|
|
493
593
|
/**
|
|
494
594
|
* Executes a keyword search against the active MiniSearch index.
|
|
@@ -499,7 +599,7 @@ var _SearchService = class _SearchService {
|
|
|
499
599
|
filter: (r) => entityIdSet.has(r.entity_id),
|
|
500
600
|
combineWith: "OR"
|
|
501
601
|
});
|
|
502
|
-
return results.slice(0, limit);
|
|
602
|
+
return results.sort((a, b) => this._compareSearchResults(a, b)).slice(0, limit);
|
|
503
603
|
}
|
|
504
604
|
/**
|
|
505
605
|
* Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
|
|
@@ -509,7 +609,7 @@ var _SearchService = class _SearchService {
|
|
|
509
609
|
let results = this.miniSearch.search(query, {
|
|
510
610
|
filter: (r) => entityIdSet.has(r.entity_id),
|
|
511
611
|
combineWith: "OR"
|
|
512
|
-
});
|
|
612
|
+
}).sort((a, b) => this._compareSearchResults(a, b));
|
|
513
613
|
if (preFilterLimit !== void 0) {
|
|
514
614
|
results = results.slice(0, preFilterLimit);
|
|
515
615
|
}
|
|
@@ -637,6 +737,18 @@ var _SearchService = class _SearchService {
|
|
|
637
737
|
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
638
738
|
return a.id.localeCompare(b.id);
|
|
639
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* MiniSearch breaks equal-score ties by internal insertion order, which
|
|
742
|
+
* syncEntries changes: a discarded-and-re-added id moves to the end. Every
|
|
743
|
+
* caller truncates these results to a limit, so which ids survive a tie at
|
|
744
|
+
* the boundary must not depend on write history. Re-sort exact score ties
|
|
745
|
+
* by id — the same final tie-break _compareScoredRows applies.
|
|
746
|
+
*/
|
|
747
|
+
_compareSearchResults(a, b) {
|
|
748
|
+
const scoreDiff = b.score - a.score;
|
|
749
|
+
if (!Number.isNaN(scoreDiff) && scoreDiff !== 0) return scoreDiff;
|
|
750
|
+
return a.id.localeCompare(b.id);
|
|
751
|
+
}
|
|
640
752
|
};
|
|
641
753
|
/**
|
|
642
754
|
* Maximum number of entities whose parsed embedding vectors are held in
|
|
@@ -2290,7 +2402,10 @@ var IngestionService = class {
|
|
|
2290
2402
|
return zeroChunkResult(canonical);
|
|
2291
2403
|
}
|
|
2292
2404
|
diagBuffer.flush(this.options);
|
|
2293
|
-
await this.searchService.
|
|
2405
|
+
await this.searchService.syncEntries(
|
|
2406
|
+
entityId,
|
|
2407
|
+
[...deletedSourceFactIds, ...insertedFacts.map((fact) => fact.id)]
|
|
2408
|
+
);
|
|
2294
2409
|
if (failedChunks === 0) {
|
|
2295
2410
|
const uniqueDeletedSourceFactIds = Array.from(new Set(deletedSourceFactIds));
|
|
2296
2411
|
for (const factId of uniqueDeletedSourceFactIds) {
|
|
@@ -3118,6 +3233,7 @@ var MaintenanceService = class {
|
|
|
3118
3233
|
this._validatePruneDuration(retainSoftDeletedFor, "retainSoftDeletedFor");
|
|
3119
3234
|
this._validatePruneDuration(retainEventsFor, "retainEventsFor");
|
|
3120
3235
|
const now = Date.now();
|
|
3236
|
+
let syncedIds = [];
|
|
3121
3237
|
let deletedEntries = 0;
|
|
3122
3238
|
let deletedTasks = 0;
|
|
3123
3239
|
let deletedEvents = 0;
|
|
@@ -3136,6 +3252,7 @@ var MaintenanceService = class {
|
|
|
3136
3252
|
}
|
|
3137
3253
|
}
|
|
3138
3254
|
const succeededIds = succeeded.map((r) => r.id);
|
|
3255
|
+
syncedIds = succeededIds;
|
|
3139
3256
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3140
3257
|
if (succeededIds.length > 0) {
|
|
3141
3258
|
deletedEntries = await this.entryRepo.bulkDeletePruned(entityId, cutoff, succeededIds, tx);
|
|
@@ -3143,7 +3260,7 @@ var MaintenanceService = class {
|
|
|
3143
3260
|
deletedTasks = await this.taskRepo.bulkDeletePruned(entityId, cutoff, tx);
|
|
3144
3261
|
});
|
|
3145
3262
|
if (failure) {
|
|
3146
|
-
await this.searchService.
|
|
3263
|
+
await this.searchService.syncEntries(entityId, syncedIds);
|
|
3147
3264
|
const remaining = entriesToDelete.length - succeeded.length - 1;
|
|
3148
3265
|
const isTimeout = failure.cause?.[HOOK_TIMEOUT_MARKER] === true;
|
|
3149
3266
|
if (isTimeout) {
|
|
@@ -3177,7 +3294,7 @@ var MaintenanceService = class {
|
|
|
3177
3294
|
if (vacuum) {
|
|
3178
3295
|
await this.metadataRepo.vacuum();
|
|
3179
3296
|
}
|
|
3180
|
-
await this.searchService.
|
|
3297
|
+
await this.searchService.syncEntries(entityId, syncedIds);
|
|
3181
3298
|
return { entries: deletedEntries, tasks: deletedTasks, events: deletedEvents };
|
|
3182
3299
|
} finally {
|
|
3183
3300
|
this.jobManager.releaseLock("prune", entityId);
|
|
@@ -3347,8 +3464,8 @@ var MaintenanceService = class {
|
|
|
3347
3464
|
deletedEntries += refResult;
|
|
3348
3465
|
}
|
|
3349
3466
|
});
|
|
3350
|
-
await this.searchService.sync(entityId);
|
|
3351
3467
|
const uniqueDeletedIds = Array.from(new Set(deletedEntryIds));
|
|
3468
|
+
await this.searchService.syncEntries(entityId, uniqueDeletedIds);
|
|
3352
3469
|
for (const factId of uniqueDeletedIds) {
|
|
3353
3470
|
try {
|
|
3354
3471
|
await this.embeddingService.notifyEmbeddingPersistedOrThrow(entityId, factId, null);
|
|
@@ -3546,7 +3663,7 @@ var MaintenanceService = class {
|
|
|
3546
3663
|
}
|
|
3547
3664
|
});
|
|
3548
3665
|
diagBuffer.flush(this.options);
|
|
3549
|
-
await this.searchService.
|
|
3666
|
+
await this.searchService.syncEntries(entityId, insertedFacts.map((f) => f.id));
|
|
3550
3667
|
for (const fact of insertedFacts) {
|
|
3551
3668
|
await this.embeddingService.embedFact(fact, { operation: "librarian", trigger });
|
|
3552
3669
|
}
|
|
@@ -3604,7 +3721,7 @@ var MaintenanceService = class {
|
|
|
3604
3721
|
}
|
|
3605
3722
|
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
|
|
3606
3723
|
if (healCandidates.length === 0) {
|
|
3607
|
-
await this.searchService.
|
|
3724
|
+
await this.searchService.syncEntries(entityId, Array.from(/* @__PURE__ */ new Set([...orphanedIds, ...staleDowngradedIds])));
|
|
3608
3725
|
this.searchService.evictCache(entityId);
|
|
3609
3726
|
const counts2 = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
|
|
3610
3727
|
return {
|
|
@@ -3774,7 +3891,14 @@ var MaintenanceService = class {
|
|
|
3774
3891
|
diagBuffer.push({ ...diagBase, code: "heal_skipped", detail: { factId: item.id, reason } });
|
|
3775
3892
|
}
|
|
3776
3893
|
diagBuffer.flush(this.options);
|
|
3777
|
-
|
|
3894
|
+
const healSyncIds = Array.from(/* @__PURE__ */ new Set([
|
|
3895
|
+
...orphanedIds,
|
|
3896
|
+
...staleDowngradedIds,
|
|
3897
|
+
...safeDowngraded,
|
|
3898
|
+
...uniqueDeletedFactIds,
|
|
3899
|
+
...insertedFacts.map((f) => f.id)
|
|
3900
|
+
]));
|
|
3901
|
+
await this.searchService.syncEntries(entityId, healSyncIds);
|
|
3778
3902
|
for (const factId of uniqueDeletedFactIds) {
|
|
3779
3903
|
try {
|
|
3780
3904
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
@@ -4479,7 +4603,11 @@ var ImportExportService = class {
|
|
|
4479
4603
|
);
|
|
4480
4604
|
}
|
|
4481
4605
|
});
|
|
4482
|
-
|
|
4606
|
+
if (merge) {
|
|
4607
|
+
await this.searchService.syncEntries(entityId, [...upsertedFactIds]);
|
|
4608
|
+
} else {
|
|
4609
|
+
await this.searchService.sync(entityId);
|
|
4610
|
+
}
|
|
4483
4611
|
for (const fact of bundle.facts) {
|
|
4484
4612
|
if (!fact.deleted_at && upsertedFactIds.has(fact.id) && !factsWithPreservedBlob.has(fact.id)) {
|
|
4485
4613
|
const clipped = clippedTextByFactId.get(fact.id);
|
|
@@ -5663,5 +5791,5 @@ var WriteService = class {
|
|
|
5663
5791
|
};
|
|
5664
5792
|
|
|
5665
5793
|
export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, DiagnosticBuffer, EMBED_CHARS_CEILING, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiDraftNotFound, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, edgeTripleAllowed, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveGrounding, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
|
|
5666
|
-
//# sourceMappingURL=chunk-
|
|
5667
|
-
//# sourceMappingURL=chunk-
|
|
5794
|
+
//# sourceMappingURL=chunk-46X3HO77.mjs.map
|
|
5795
|
+
//# sourceMappingURL=chunk-46X3HO77.mjs.map
|