@cleocode/core 2026.4.35 → 2026.4.36
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/config.d.ts.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/hooks/handlers/conduit-hooks.d.ts +72 -0
- package/dist/hooks/handlers/conduit-hooks.d.ts.map +1 -0
- package/dist/hooks/handlers/conduit-hooks.js +229 -0
- package/dist/hooks/handlers/conduit-hooks.js.map +1 -0
- package/dist/hooks/handlers/index.d.ts +2 -0
- package/dist/hooks/handlers/index.d.ts.map +1 -1
- package/dist/hooks/handlers/index.js +3 -0
- package/dist/hooks/handlers/index.js.map +1 -1
- package/dist/hooks/handlers/session-hooks.d.ts +14 -0
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.js +33 -0
- package/dist/hooks/handlers/session-hooks.js.map +1 -1
- package/dist/hooks/handlers/task-hooks.d.ts +2 -0
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.js +14 -0
- package/dist/hooks/handlers/task-hooks.js.map +1 -1
- package/dist/index.js +54918 -46845
- package/dist/index.js.map +4 -4
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/memory/anthropic-key-resolver.d.ts +35 -0
- package/dist/memory/anthropic-key-resolver.d.ts.map +1 -0
- package/dist/memory/anthropic-key-resolver.js +105 -0
- package/dist/memory/anthropic-key-resolver.js.map +1 -0
- package/dist/memory/auto-extract.d.ts +38 -42
- package/dist/memory/auto-extract.d.ts.map +1 -1
- package/dist/memory/auto-extract.js +38 -57
- package/dist/memory/auto-extract.js.map +1 -1
- package/dist/memory/brain-retrieval.d.ts +6 -0
- package/dist/memory/brain-retrieval.d.ts.map +1 -1
- package/dist/memory/brain-retrieval.js +145 -13
- package/dist/memory/brain-retrieval.js.map +1 -1
- package/dist/memory/brain-search.d.ts +82 -15
- package/dist/memory/brain-search.d.ts.map +1 -1
- package/dist/memory/brain-search.js +178 -93
- package/dist/memory/brain-search.js.map +1 -1
- package/dist/memory/engine-compat.d.ts +16 -1
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +0 -3
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/learnings.d.ts.map +1 -1
- package/dist/memory/learnings.js +4 -3
- package/dist/memory/learnings.js.map +1 -1
- package/dist/memory/llm-extraction.d.ts +107 -0
- package/dist/memory/llm-extraction.d.ts.map +1 -0
- package/dist/memory/llm-extraction.js +425 -0
- package/dist/memory/llm-extraction.js.map +1 -0
- package/dist/memory/memory-bridge.js +23 -11
- package/dist/memory/memory-bridge.js.map +1 -1
- package/dist/memory/observer-reflector.d.ts +157 -0
- package/dist/memory/observer-reflector.d.ts.map +1 -0
- package/dist/memory/observer-reflector.js +626 -0
- package/dist/memory/observer-reflector.js.map +1 -0
- package/dist/store/brain-schema.d.ts +131 -0
- package/dist/store/brain-schema.d.ts.map +1 -1
- package/dist/store/brain-schema.js +30 -0
- package/dist/store/brain-schema.js.map +1 -1
- package/dist/store/brain-sqlite.js +41 -1
- package/dist/store/brain-sqlite.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +7 -8
- package/dist/tasks/complete.js.map +1 -1
- package/package.json +13 -12
- package/src/config.ts +7 -0
- package/src/hooks/handlers/__tests__/conduit-hooks.test.ts +356 -0
- package/src/hooks/handlers/conduit-hooks.ts +258 -0
- package/src/hooks/handlers/index.ts +7 -0
- package/src/hooks/handlers/session-hooks.ts +37 -0
- package/src/hooks/handlers/task-hooks.ts +14 -0
- package/src/internal.ts +8 -0
- package/src/memory/__tests__/auto-extract.test.ts +43 -114
- package/src/memory/__tests__/brain-automation.test.ts +16 -39
- package/src/memory/__tests__/brain-rrf.test.ts +431 -0
- package/src/memory/__tests__/llm-extraction.test.ts +342 -0
- package/src/memory/__tests__/observer-reflector.test.ts +475 -0
- package/src/memory/anthropic-key-resolver.ts +113 -0
- package/src/memory/auto-extract.ts +40 -72
- package/src/memory/brain-retrieval.ts +187 -18
- package/src/memory/brain-search.ts +196 -128
- package/src/memory/engine-compat.ts +16 -4
- package/src/memory/learnings.ts +4 -3
- package/src/memory/llm-extraction.ts +524 -0
- package/src/memory/memory-bridge.ts +29 -12
- package/src/memory/observer-reflector.ts +829 -0
- package/src/store/brain-schema.ts +44 -0
- package/src/tasks/complete.ts +7 -10
|
@@ -17,7 +17,6 @@ import type {
|
|
|
17
17
|
BrainPatternRow,
|
|
18
18
|
} from '../store/brain-schema.js';
|
|
19
19
|
import { typedAll } from '../store/typed-query.js';
|
|
20
|
-
import type { BrainSearchHit } from './brain-row-types.js';
|
|
21
20
|
import type { SimilarityResult } from './brain-similarity.js';
|
|
22
21
|
import { searchSimilar } from './brain-similarity.js';
|
|
23
22
|
import { QUALITY_SCORE_THRESHOLD } from './quality-scoring.js';
|
|
@@ -598,44 +597,174 @@ export function resetFts5Cache(): void {
|
|
|
598
597
|
}
|
|
599
598
|
|
|
600
599
|
// ============================================================================
|
|
601
|
-
//
|
|
600
|
+
// Reciprocal Rank Fusion (RRF) — Hybrid Retrieval
|
|
601
|
+
// ============================================================================
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The RRF smoothing constant (research-proven at 60).
|
|
605
|
+
*
|
|
606
|
+
* Balances noise vs. signal: small values amplify top-rank differences;
|
|
607
|
+
* large values compress ranks toward a flat distribution. 60 is the
|
|
608
|
+
* standard value from Cormack, Clarke & Buettcher (SIGIR 2009).
|
|
609
|
+
*/
|
|
610
|
+
export const RRF_K = 60;
|
|
611
|
+
|
|
612
|
+
/** A single ranked hit from one retrieval source before fusion. */
|
|
613
|
+
export interface RrfHit {
|
|
614
|
+
id: string;
|
|
615
|
+
type: string;
|
|
616
|
+
title: string;
|
|
617
|
+
text: string;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Fused result produced by reciprocalRankFusion. */
|
|
621
|
+
export interface RrfResult {
|
|
622
|
+
id: string;
|
|
623
|
+
/** Combined RRF score: sum of 1/(rank+RRF_K) across all source lists. */
|
|
624
|
+
rrfScore: number;
|
|
625
|
+
type: string;
|
|
626
|
+
title: string;
|
|
627
|
+
text: string;
|
|
628
|
+
/** Which retrieval sources contributed to this result. */
|
|
629
|
+
sources: Array<'fts' | 'vec' | 'graph'>;
|
|
630
|
+
/** BM25-derived FTS rank (0-based) — undefined if not in FTS results. */
|
|
631
|
+
ftsRank?: number;
|
|
632
|
+
/** Vector distance rank (0-based) — undefined if not in vector results. */
|
|
633
|
+
vecRank?: number;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Fuse ranked lists from multiple retrieval sources using Reciprocal Rank Fusion.
|
|
638
|
+
*
|
|
639
|
+
* Implements the RRF algorithm from Cormack, Clarke & Buettcher (SIGIR 2009):
|
|
640
|
+
*
|
|
641
|
+
* score(d) = Σ 1 / (k + rank(d, list)) for each list containing d
|
|
642
|
+
*
|
|
643
|
+
* where k=60 is the research-proven smoothing constant.
|
|
644
|
+
*
|
|
645
|
+
* Properties:
|
|
646
|
+
* - Rank-based: actual scores from each source are ignored (only rank matters).
|
|
647
|
+
* - Additive: items appearing in multiple lists accumulate higher scores.
|
|
648
|
+
* - Robust: the +60 constant prevents rank-1 items from dominating.
|
|
649
|
+
*
|
|
650
|
+
* @param sources - Named arrays of ranked hits (order = rank, index 0 = best)
|
|
651
|
+
* @param k - RRF smoothing constant (default: RRF_K = 60)
|
|
652
|
+
* @returns Array of fused results sorted by rrfScore descending
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* ```ts
|
|
656
|
+
* const fused = reciprocalRankFusion([
|
|
657
|
+
* { source: 'fts', hits: ftsHits },
|
|
658
|
+
* { source: 'vec', hits: vecHits },
|
|
659
|
+
* ]);
|
|
660
|
+
* ```
|
|
661
|
+
*/
|
|
662
|
+
export function reciprocalRankFusion(
|
|
663
|
+
sources: Array<{
|
|
664
|
+
source: 'fts' | 'vec' | 'graph';
|
|
665
|
+
hits: RrfHit[];
|
|
666
|
+
}>,
|
|
667
|
+
k: number = RRF_K,
|
|
668
|
+
): RrfResult[] {
|
|
669
|
+
// Accumulator: id -> mutable result record
|
|
670
|
+
const accum = new Map<
|
|
671
|
+
string,
|
|
672
|
+
{
|
|
673
|
+
rrfScore: number;
|
|
674
|
+
type: string;
|
|
675
|
+
title: string;
|
|
676
|
+
text: string;
|
|
677
|
+
sources: Set<'fts' | 'vec' | 'graph'>;
|
|
678
|
+
ftsRank?: number;
|
|
679
|
+
vecRank?: number;
|
|
680
|
+
}
|
|
681
|
+
>();
|
|
682
|
+
|
|
683
|
+
for (const { source, hits } of sources) {
|
|
684
|
+
for (let rank = 0; rank < hits.length; rank++) {
|
|
685
|
+
const hit = hits[rank]!;
|
|
686
|
+
const contribution = 1 / (k + rank);
|
|
687
|
+
|
|
688
|
+
const existing = accum.get(hit.id);
|
|
689
|
+
if (existing) {
|
|
690
|
+
existing.rrfScore += contribution;
|
|
691
|
+
existing.sources.add(source);
|
|
692
|
+
if (source === 'fts') existing.ftsRank = rank;
|
|
693
|
+
if (source === 'vec') existing.vecRank = rank;
|
|
694
|
+
} else {
|
|
695
|
+
accum.set(hit.id, {
|
|
696
|
+
rrfScore: contribution,
|
|
697
|
+
type: hit.type,
|
|
698
|
+
title: hit.title,
|
|
699
|
+
text: hit.text,
|
|
700
|
+
sources: new Set([source]),
|
|
701
|
+
ftsRank: source === 'fts' ? rank : undefined,
|
|
702
|
+
vecRank: source === 'vec' ? rank : undefined,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return [...accum.entries()]
|
|
709
|
+
.map(([id, data]) => ({
|
|
710
|
+
id,
|
|
711
|
+
rrfScore: data.rrfScore,
|
|
712
|
+
type: data.type,
|
|
713
|
+
title: data.title,
|
|
714
|
+
text: data.text,
|
|
715
|
+
sources: [...data.sources] as Array<'fts' | 'vec' | 'graph'>,
|
|
716
|
+
ftsRank: data.ftsRank,
|
|
717
|
+
vecRank: data.vecRank,
|
|
718
|
+
}))
|
|
719
|
+
.sort((a, b) => b.rrfScore - a.rrfScore);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ============================================================================
|
|
723
|
+
// Hybrid Search (FTS5 + Vector + Graph) — RRF-powered
|
|
602
724
|
// ============================================================================
|
|
603
725
|
|
|
604
726
|
/** Result from hybridSearch combining multiple search signals. */
|
|
605
727
|
export interface HybridResult {
|
|
606
728
|
id: string;
|
|
729
|
+
/** RRF-fused score: sum of 1/(rank+60) across all source lists. */
|
|
607
730
|
score: number;
|
|
608
731
|
type: string;
|
|
609
732
|
title: string;
|
|
610
733
|
text: string;
|
|
611
734
|
sources: Array<'fts' | 'vec' | 'graph'>;
|
|
735
|
+
/** Raw FTS rank (0-based) for transparency — undefined if FTS did not return this item. */
|
|
736
|
+
ftsRank?: number;
|
|
737
|
+
/** Raw vector rank (0-based) for transparency — undefined if vector did not return this item. */
|
|
738
|
+
vecRank?: number;
|
|
612
739
|
}
|
|
613
740
|
|
|
614
|
-
/** Options for hybridSearch
|
|
741
|
+
/** Options for hybridSearch. */
|
|
615
742
|
export interface HybridSearchOptions {
|
|
616
|
-
ftsWeight?: number;
|
|
617
|
-
vecWeight?: number;
|
|
618
|
-
graphWeight?: number;
|
|
619
743
|
limit?: number;
|
|
744
|
+
/**
|
|
745
|
+
* RRF smoothing constant k. Default: 60 (research-proven).
|
|
746
|
+
* Larger k flattens rank differences; smaller k amplifies top-rank advantage.
|
|
747
|
+
*/
|
|
748
|
+
rrfK?: number;
|
|
620
749
|
}
|
|
621
750
|
|
|
622
751
|
/**
|
|
623
|
-
* Hybrid search across FTS5, vector similarity, and graph neighbors
|
|
752
|
+
* Hybrid search across FTS5, vector similarity, and graph neighbors using
|
|
753
|
+
* Reciprocal Rank Fusion (RRF) for result combination.
|
|
624
754
|
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
* 6. Deduplicates by ID, keeping highest combined score.
|
|
631
|
-
* 7. Returns top-N sorted by score descending.
|
|
755
|
+
* Algorithm:
|
|
756
|
+
* 1. Run FTS5 search and vector similarity search in parallel.
|
|
757
|
+
* 2. Optionally expand via graph neighbors (best-effort).
|
|
758
|
+
* 3. Fuse all ranked lists with RRF: score = Σ 1/(rank+60).
|
|
759
|
+
* 4. Return top-N sorted by fused RRF score.
|
|
632
760
|
*
|
|
633
|
-
* Graceful
|
|
761
|
+
* Graceful degradation: vector and graph sources are silently skipped when
|
|
762
|
+
* unavailable — RRF naturally handles partial source lists.
|
|
634
763
|
*
|
|
635
764
|
* @param query - Search query text
|
|
636
765
|
* @param projectRoot - Project root directory
|
|
637
|
-
* @param options -
|
|
638
|
-
* @returns Array of hybrid results ranked by
|
|
766
|
+
* @param options - Limit and RRF tuning
|
|
767
|
+
* @returns Array of hybrid results ranked by RRF score descending
|
|
639
768
|
*/
|
|
640
769
|
export async function hybridSearch(
|
|
641
770
|
query: string,
|
|
@@ -645,52 +774,21 @@ export async function hybridSearch(
|
|
|
645
774
|
if (!query?.trim()) return [];
|
|
646
775
|
|
|
647
776
|
const maxResults = options?.limit ?? 10;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const addScore = (
|
|
665
|
-
id: string,
|
|
666
|
-
normalizedScore: number,
|
|
667
|
-
weight: number,
|
|
668
|
-
source: 'fts' | 'vec' | 'graph',
|
|
669
|
-
type: string,
|
|
670
|
-
title: string,
|
|
671
|
-
text: string,
|
|
672
|
-
) => {
|
|
673
|
-
const existing = scoreMap.get(id);
|
|
674
|
-
if (existing) {
|
|
675
|
-
existing.score += normalizedScore * weight;
|
|
676
|
-
existing.sources.add(source);
|
|
677
|
-
} else {
|
|
678
|
-
scoreMap.set(id, {
|
|
679
|
-
score: normalizedScore * weight,
|
|
680
|
-
type,
|
|
681
|
-
title,
|
|
682
|
-
text,
|
|
683
|
-
sources: new Set([source]),
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
|
-
|
|
688
|
-
// --- 1. FTS5 search ---
|
|
689
|
-
const ftsResults = await searchBrain(projectRoot, query, { limit: maxResults * 2 });
|
|
690
|
-
|
|
691
|
-
// Collect all FTS hits into a flat list with position-based scores
|
|
692
|
-
const ftsHits: BrainSearchHit[] = [];
|
|
693
|
-
|
|
777
|
+
const rrfK = options?.rrfK ?? RRF_K;
|
|
778
|
+
|
|
779
|
+
// --- 1. Run FTS5 and vector in parallel ---
|
|
780
|
+
const [ftsResults, vecResults] = await Promise.all([
|
|
781
|
+
searchBrain(projectRoot, query, { limit: maxResults * 3 }).catch(() => ({
|
|
782
|
+
decisions: [],
|
|
783
|
+
patterns: [],
|
|
784
|
+
learnings: [],
|
|
785
|
+
observations: [],
|
|
786
|
+
})),
|
|
787
|
+
searchSimilar(query, projectRoot, maxResults * 3).catch(() => [] as SimilarityResult[]),
|
|
788
|
+
]);
|
|
789
|
+
|
|
790
|
+
// --- 2. Project FTS results into ranked RrfHit list ---
|
|
791
|
+
const ftsHits: RrfHit[] = [];
|
|
694
792
|
for (const d of ftsResults.decisions) {
|
|
695
793
|
ftsHits.push({
|
|
696
794
|
id: d.id,
|
|
@@ -719,88 +817,58 @@ export async function hybridSearch(
|
|
|
719
817
|
ftsHits.push({ id: o.id, type: 'observation', title: o.title, text: o.narrative ?? o.title });
|
|
720
818
|
}
|
|
721
819
|
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
// --- 2. Vector similarity search ---
|
|
730
|
-
let vecResults: SimilarityResult[] = [];
|
|
731
|
-
try {
|
|
732
|
-
vecResults = await searchSimilar(query, projectRoot, maxResults * 2);
|
|
733
|
-
} catch {
|
|
734
|
-
// Vector search unavailable
|
|
735
|
-
}
|
|
820
|
+
// --- 3. Project vector results into ranked RrfHit list (ascending distance = descending quality) ---
|
|
821
|
+
const vecHits: RrfHit[] = vecResults.map((r) => ({
|
|
822
|
+
id: r.id,
|
|
823
|
+
type: r.type,
|
|
824
|
+
title: r.title,
|
|
825
|
+
text: r.text,
|
|
826
|
+
}));
|
|
736
827
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const normalizedScore = 1.0 - r.distance / maxDist;
|
|
742
|
-
addScore(r.id, normalizedScore, vecWeight, 'vec', r.type, r.title, r.text);
|
|
743
|
-
}
|
|
744
|
-
} else {
|
|
745
|
-
// Redistribute vec weight to FTS if vector unavailable
|
|
746
|
-
ftsWeight += vecWeight;
|
|
747
|
-
vecWeight = 0;
|
|
748
|
-
|
|
749
|
-
// Re-score FTS hits with updated weight
|
|
750
|
-
scoreMap.clear();
|
|
751
|
-
for (let i = 0; i < ftsHits.length; i++) {
|
|
752
|
-
const hit = ftsHits[i]!;
|
|
753
|
-
const normalizedScore = ftsHits.length > 1 ? 1.0 - i / (ftsHits.length - 1) : 1.0;
|
|
754
|
-
addScore(hit.id, normalizedScore, ftsWeight, 'fts', hit.type, hit.title, hit.text);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
828
|
+
// --- 4. Build source list for RRF ---
|
|
829
|
+
const rrfSources: Array<{ source: 'fts' | 'vec' | 'graph'; hits: RrfHit[] }> = [];
|
|
830
|
+
if (ftsHits.length > 0) rrfSources.push({ source: 'fts', hits: ftsHits });
|
|
831
|
+
if (vecHits.length > 0) rrfSources.push({ source: 'vec', hits: vecHits });
|
|
757
832
|
|
|
758
|
-
// ---
|
|
833
|
+
// --- 5. Graph neighbor expansion (best-effort) ---
|
|
759
834
|
try {
|
|
760
835
|
const accessor = await getBrainAccessor(projectRoot);
|
|
761
|
-
|
|
762
|
-
// Check if query matches a known graph node ID pattern
|
|
763
836
|
const possibleNodeIds = [
|
|
764
837
|
`concept:${query.toLowerCase().replace(/\s+/g, '-')}`,
|
|
765
838
|
`task:${query}`,
|
|
766
839
|
`doc:${query}`,
|
|
767
840
|
];
|
|
768
841
|
|
|
842
|
+
const graphHits: RrfHit[] = [];
|
|
769
843
|
for (const nodeId of possibleNodeIds) {
|
|
770
844
|
const node = await accessor.getPageNode(nodeId);
|
|
771
845
|
if (!node) continue;
|
|
772
|
-
|
|
773
846
|
const neighbors = await accessor.getNeighbors(nodeId);
|
|
774
|
-
for (
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
neighbor.
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
'graph',
|
|
782
|
-
neighbor.nodeType,
|
|
783
|
-
neighbor.label,
|
|
784
|
-
neighbor.label,
|
|
785
|
-
);
|
|
847
|
+
for (const neighbor of neighbors) {
|
|
848
|
+
graphHits.push({
|
|
849
|
+
id: neighbor.id,
|
|
850
|
+
type: neighbor.nodeType,
|
|
851
|
+
title: neighbor.label,
|
|
852
|
+
text: neighbor.label,
|
|
853
|
+
});
|
|
786
854
|
}
|
|
787
855
|
}
|
|
856
|
+
if (graphHits.length > 0) rrfSources.push({ source: 'graph', hits: graphHits });
|
|
788
857
|
} catch {
|
|
789
|
-
// Graph
|
|
858
|
+
// Graph unavailable — RRF handles gracefully with remaining sources
|
|
790
859
|
}
|
|
791
860
|
|
|
792
|
-
// ---
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
.
|
|
803
|
-
.
|
|
804
|
-
|
|
805
|
-
return sorted;
|
|
861
|
+
// --- 6. Fuse with RRF and return top-N ---
|
|
862
|
+
const fused = reciprocalRankFusion(rrfSources, rrfK);
|
|
863
|
+
|
|
864
|
+
return fused.slice(0, maxResults).map((r) => ({
|
|
865
|
+
id: r.id,
|
|
866
|
+
score: r.rrfScore,
|
|
867
|
+
type: r.type,
|
|
868
|
+
title: r.title,
|
|
869
|
+
text: r.text,
|
|
870
|
+
sources: r.sources,
|
|
871
|
+
ftsRank: r.ftsRank,
|
|
872
|
+
vecRank: r.vecRank,
|
|
873
|
+
}));
|
|
806
874
|
}
|
|
@@ -1569,10 +1569,25 @@ export async function memoryReasonSimilar(
|
|
|
1569
1569
|
export async function memorySearchHybrid(
|
|
1570
1570
|
params: {
|
|
1571
1571
|
query: string;
|
|
1572
|
+
limit?: number;
|
|
1573
|
+
/**
|
|
1574
|
+
* @deprecated Weight parameters are unused — hybrid search now uses
|
|
1575
|
+
* Reciprocal Rank Fusion (RRF) which is rank-based and does not require
|
|
1576
|
+
* per-source weights. This field is accepted but silently ignored.
|
|
1577
|
+
*/
|
|
1572
1578
|
ftsWeight?: number;
|
|
1579
|
+
/**
|
|
1580
|
+
* @deprecated Weight parameters are unused — hybrid search now uses
|
|
1581
|
+
* Reciprocal Rank Fusion (RRF) which is rank-based and does not require
|
|
1582
|
+
* per-source weights. This field is accepted but silently ignored.
|
|
1583
|
+
*/
|
|
1573
1584
|
vecWeight?: number;
|
|
1585
|
+
/**
|
|
1586
|
+
* @deprecated Weight parameters are unused — hybrid search now uses
|
|
1587
|
+
* Reciprocal Rank Fusion (RRF) which is rank-based and does not require
|
|
1588
|
+
* per-source weights. This field is accepted but silently ignored.
|
|
1589
|
+
*/
|
|
1574
1590
|
graphWeight?: number;
|
|
1575
|
-
limit?: number;
|
|
1576
1591
|
},
|
|
1577
1592
|
projectRoot?: string,
|
|
1578
1593
|
): Promise<EngineResult> {
|
|
@@ -1584,9 +1599,6 @@ export async function memorySearchHybrid(
|
|
|
1584
1599
|
const root = resolveRoot(projectRoot);
|
|
1585
1600
|
const { hybridSearch } = await import('./brain-search.js');
|
|
1586
1601
|
const results = await hybridSearch(params.query, root, {
|
|
1587
|
-
ftsWeight: params.ftsWeight,
|
|
1588
|
-
vecWeight: params.vecWeight,
|
|
1589
|
-
graphWeight: params.graphWeight,
|
|
1590
1602
|
limit: params.limit,
|
|
1591
1603
|
});
|
|
1592
1604
|
return { success: true, data: { results, total: results.length } };
|
package/src/memory/learnings.ts
CHANGED
|
@@ -105,8 +105,9 @@ export async function storeLearning(projectRoot: string, params: StoreLearningPa
|
|
|
105
105
|
// memoryType routing (spec §4.1 Decision Tree for memoryType):
|
|
106
106
|
// - source contains 'transcript:ses_' → 'episodic' (event-specific insight)
|
|
107
107
|
// - otherwise → 'semantic' (declarative factual learning)
|
|
108
|
-
//
|
|
109
|
-
|
|
108
|
+
// Owner-stated learnings are ground truth (auto-verified).
|
|
109
|
+
// Transcript-extracted and agent-inferred start unverified — consolidator promotes.
|
|
110
|
+
const isManual = params.source.includes('manual') || params.source.includes('owner');
|
|
110
111
|
const isTranscript = params.source.includes('transcript:ses_');
|
|
111
112
|
const sourceConfidence = isManual
|
|
112
113
|
? ('owner' as const)
|
|
@@ -115,7 +116,7 @@ export async function storeLearning(projectRoot: string, params: StoreLearningPa
|
|
|
115
116
|
: ('agent' as const);
|
|
116
117
|
const memoryTier = isManual ? ('medium' as const) : ('short' as const);
|
|
117
118
|
const memoryType = isTranscript ? ('episodic' as const) : ('semantic' as const);
|
|
118
|
-
const verified =
|
|
119
|
+
const verified = isManual;
|
|
119
120
|
|
|
120
121
|
// Compute quality score from confidence, actionability, content richness,
|
|
121
122
|
// and T549 source multiplier.
|