claude-memory-layer 1.0.31 → 1.0.33
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 +9 -2
- package/dist/cli/index.js +1110 -72
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +414 -25
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +416 -27
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +416 -27
- package/dist/hooks/semantic-daemon.js.map +2 -2
- package/dist/hooks/session-end.js +416 -27
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +416 -27
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +416 -27
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +504 -34
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/index.js +416 -27
- package/dist/index.js.map +2 -2
- package/dist/mcp/index.js +407 -32
- package/dist/mcp/index.js.map +2 -2
- package/dist/server/api/index.js +850 -44
- package/dist/server/api/index.js.map +3 -3
- package/dist/server/index.js +1073 -64
- package/dist/server/index.js.map +3 -3
- package/dist/services/memory-service.js +416 -27
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/assets/js/bootstrap.js +2 -0
- package/dist/ui/assets/js/overview.js +166 -3
- package/dist/ui/assets/js/state.js +3 -0
- package/dist/ui/index.html +20 -0
- package/dist/ui/style.css +193 -0
- package/package.json +15 -2
- package/scripts/postinstall-embedding-backend.cjs +16 -12
- package/AGENTS.md +0 -71
- package/CLAUDE.md +0 -30
- package/HANDOFF.md +0 -92
- package/Memo.txt +0 -558
- package/benchmarks/replay/anonymized-real-sessions.json +0 -48
- package/config/kpi-thresholds.json +0 -7
- package/context.md +0 -636
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +0 -627
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +0 -440
- package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +0 -271
- package/docs/MEMORY_USEFULNESS_AUDIT.md +0 -371
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +0 -80
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +0 -333
- package/docs/MEMU_ADOPTION.md +0 -40
- package/docs/OPERATIONS.md +0 -18
- package/docs/PRODUCT_VALIDATION_MATRIX.md +0 -82
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +0 -421
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +0 -501
- package/docs/REFACTORING_PLAN_THIN_CORE.md +0 -414
- package/docs/REFERENCE_PROJECT_ANALYSES.md +0 -25
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +0 -452
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +0 -446
- package/docs/architecture/comparison-index.md +0 -47
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +0 -46
- package/plan.md +0 -1642
- package/scripts/build.ts +0 -159
- package/scripts/bump-patch-version.sh +0 -18
- package/scripts/delete-unknown-projects.js +0 -154
- package/scripts/fix-sync-gap.js +0 -32
- package/scripts/generate-session-qrels.ts +0 -126
- package/scripts/heartbeat-memory-orchestrator.sh +0 -28
- package/scripts/replay-retrieval-benchmark.ts +0 -69
- package/scripts/report-sync-gap.js +0 -26
- package/scripts/review-queue-auto-resolve.js +0 -21
- package/scripts/sync-gap-auto-heal.sh +0 -17
- package/spec.md +0 -624
- package/specs/20260207-dashboard-upgrade/context.md +0 -38
- package/specs/20260207-dashboard-upgrade/spec.md +0 -96
- package/specs/citations-system/context.md +0 -243
- package/specs/citations-system/plan.md +0 -495
- package/specs/citations-system/spec.md +0 -371
- package/specs/endless-mode/context.md +0 -305
- package/specs/endless-mode/plan.md +0 -620
- package/specs/endless-mode/spec.md +0 -455
- package/specs/entity-edge-model/context.md +0 -401
- package/specs/entity-edge-model/plan.md +0 -459
- package/specs/entity-edge-model/spec.md +0 -391
- package/specs/evidence-aligner-v2/context.md +0 -401
- package/specs/evidence-aligner-v2/plan.md +0 -303
- package/specs/evidence-aligner-v2/spec.md +0 -312
- package/specs/mcp-desktop-integration/context.md +0 -278
- package/specs/mcp-desktop-integration/plan.md +0 -550
- package/specs/mcp-desktop-integration/spec.md +0 -494
- package/specs/memory-utilization-improvements/context.md +0 -145
- package/specs/memory-utilization-improvements/plan.md +0 -361
- package/specs/memory-utilization-improvements/spec.md +0 -361
- package/specs/post-tool-use-hook/context.md +0 -319
- package/specs/post-tool-use-hook/plan.md +0 -469
- package/specs/post-tool-use-hook/spec.md +0 -364
- package/specs/private-tags/context.md +0 -288
- package/specs/private-tags/plan.md +0 -412
- package/specs/private-tags/spec.md +0 -345
- package/specs/progressive-disclosure/context.md +0 -346
- package/specs/progressive-disclosure/plan.md +0 -663
- package/specs/progressive-disclosure/spec.md +0 -415
- package/specs/selective-tool-observation/context.md +0 -100
- package/specs/selective-tool-observation/plan.md +0 -158
- package/specs/selective-tool-observation/spec.md +0 -127
- package/specs/task-entity-system/context.md +0 -297
- package/specs/task-entity-system/plan.md +0 -301
- package/specs/task-entity-system/spec.md +0 -314
- package/specs/thin-core-refactor/context.md +0 -275
- package/specs/thin-core-refactor/plan.md +0 -536
- package/specs/thin-core-refactor/spec.md +0 -465
- package/specs/vector-outbox-v2/context.md +0 -470
- package/specs/vector-outbox-v2/plan.md +0 -562
- package/specs/vector-outbox-v2/spec.md +0 -466
- package/specs/web-viewer-ui/context.md +0 -384
- package/specs/web-viewer-ui/plan.md +0 -797
- package/specs/web-viewer-ui/spec.md +0 -516
- package/src/adapters/claude/capture/index.ts +0 -3
- package/src/adapters/claude/context/index.ts +0 -3
- package/src/adapters/claude/hooks/index.ts +0 -21
- package/src/adapters/claude/hooks/post-tool-use.ts +0 -239
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +0 -104
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +0 -209
- package/src/adapters/claude/hooks/semantic-daemon.ts +0 -283
- package/src/adapters/claude/hooks/session-end.ts +0 -59
- package/src/adapters/claude/hooks/session-start.ts +0 -73
- package/src/adapters/claude/hooks/stop.ts +0 -128
- package/src/adapters/claude/hooks/user-prompt-submit.ts +0 -361
- package/src/adapters/claude/index.ts +0 -4
- package/src/adapters/claude/transcript/index.ts +0 -4
- package/src/adapters/claude/transcript/transcript-reader.ts +0 -57
- package/src/adapters/claude/transcript/turn-reconstructor.ts +0 -65
- package/src/apps/cli/claude-settings-hooks.ts +0 -138
- package/src/apps/cli/codex-import-runner.ts +0 -125
- package/src/apps/cli/codex-validation-output.ts +0 -95
- package/src/apps/cli/hermes-import-runner.ts +0 -130
- package/src/apps/cli/hermes-validation-output.ts +0 -91
- package/src/apps/cli/index.ts +0 -1735
- package/src/apps/cli/mcp-install.ts +0 -106
- package/src/apps/cli/retrieval-disclosure-output.ts +0 -196
- package/src/apps/dashboard/assets/js/bootstrap.js +0 -244
- package/src/apps/dashboard/assets/js/chat.js +0 -373
- package/src/apps/dashboard/assets/js/disclosure.js +0 -232
- package/src/apps/dashboard/assets/js/modals.js +0 -298
- package/src/apps/dashboard/assets/js/overview.js +0 -655
- package/src/apps/dashboard/assets/js/state.js +0 -72
- package/src/apps/dashboard/assets/js/views.js +0 -468
- package/src/apps/dashboard/index.html +0 -543
- package/src/apps/dashboard/index.ts +0 -3
- package/src/apps/dashboard/style.css +0 -1750
- package/src/apps/index.ts +0 -5
- package/src/apps/server/api/chat.ts +0 -244
- package/src/apps/server/api/citations.ts +0 -105
- package/src/apps/server/api/events.ts +0 -137
- package/src/apps/server/api/health.ts +0 -53
- package/src/apps/server/api/index.ts +0 -26
- package/src/apps/server/api/projects.ts +0 -74
- package/src/apps/server/api/search.ts +0 -184
- package/src/apps/server/api/sessions.ts +0 -115
- package/src/apps/server/api/stats.ts +0 -723
- package/src/apps/server/api/turns.ts +0 -143
- package/src/apps/server/api/utils.ts +0 -65
- package/src/apps/server/index.ts +0 -111
- package/src/cli/index.ts +0 -3
- package/src/cli/retrieval-disclosure-output.ts +0 -2
- package/src/compat/index.ts +0 -5
- package/src/core/canonical-key.ts +0 -186
- package/src/core/citation-generator.ts +0 -63
- package/src/core/consolidated-store.ts +0 -356
- package/src/core/consolidation-worker.ts +0 -493
- package/src/core/context-formatter.ts +0 -276
- package/src/core/continuity-manager.ts +0 -341
- package/src/core/db-wrapper.ts +0 -64
- package/src/core/derive/fact-deriver.ts +0 -170
- package/src/core/derive/index.ts +0 -2
- package/src/core/derive/summary-deriver.ts +0 -76
- package/src/core/edge-repo.ts +0 -333
- package/src/core/embedder.ts +0 -4
- package/src/core/engine/embedding-maintenance-service.ts +0 -187
- package/src/core/engine/endless-memory-services.ts +0 -4
- package/src/core/engine/index.ts +0 -19
- package/src/core/engine/memory-engine-services.ts +0 -170
- package/src/core/engine/memory-ingest-service.ts +0 -317
- package/src/core/engine/memory-query-service.ts +0 -173
- package/src/core/engine/memory-runtime-service.ts +0 -162
- package/src/core/engine/memory-service-composition.ts +0 -231
- package/src/core/engine/retrieval-analytics-service.ts +0 -181
- package/src/core/engine/retrieval-disclosure-service.ts +0 -420
- package/src/core/engine/retrieval-orchestrator.ts +0 -377
- package/src/core/engine/retrieval-services.ts +0 -176
- package/src/core/engine/shared-memory-services.ts +0 -4
- package/src/core/entity-repo.ts +0 -349
- package/src/core/event-store.ts +0 -779
- package/src/core/evidence-aligner.ts +0 -635
- package/src/core/external-market-context.ts +0 -582
- package/src/core/graduation-worker.ts +0 -171
- package/src/core/graduation.ts +0 -377
- package/src/core/index.ts +0 -64
- package/src/core/ingest-interceptor.ts +0 -80
- package/src/core/markdown-mirror.ts +0 -70
- package/src/core/matcher.ts +0 -208
- package/src/core/md-mirror.ts +0 -92
- package/src/core/metadata-extractor.ts +0 -203
- package/src/core/model/memory-fact.ts +0 -30
- package/src/core/model/memory-rule.ts +0 -14
- package/src/core/model/memory-summary.ts +0 -21
- package/src/core/model/raw-event.ts +0 -28
- package/src/core/model/retrieval-result.ts +0 -35
- package/src/core/mongo-sync-config.ts +0 -165
- package/src/core/mongo-sync-worker.ts +0 -381
- package/src/core/privacy/filter.ts +0 -190
- package/src/core/privacy/index.ts +0 -20
- package/src/core/privacy/tag-parser.ts +0 -145
- package/src/core/product-validation-matrix.ts +0 -314
- package/src/core/progressive-retriever.ts +0 -414
- package/src/core/registry/project-path.ts +0 -54
- package/src/core/registry/session-registry.ts +0 -69
- package/src/core/replay-evaluator.ts +0 -625
- package/src/core/retrieval-benchmark.ts +0 -117
- package/src/core/retrieval-quality.ts +0 -109
- package/src/core/retriever.ts +0 -800
- package/src/core/session-qrels.ts +0 -360
- package/src/core/shared-event-store.ts +0 -114
- package/src/core/shared-promoter.ts +0 -249
- package/src/core/shared-store.ts +0 -289
- package/src/core/shared-vector-store.ts +0 -203
- package/src/core/sqlite-event-store.ts +0 -1846
- package/src/core/sqlite-wrapper.ts +0 -116
- package/src/core/sync-worker.ts +0 -228
- package/src/core/tag-taxonomy.ts +0 -51
- package/src/core/task/blocker-resolver.ts +0 -333
- package/src/core/task/index.ts +0 -9
- package/src/core/task/task-matcher.ts +0 -240
- package/src/core/task/task-projector.ts +0 -358
- package/src/core/task/task-resolver.ts +0 -421
- package/src/core/turn-state.ts +0 -207
- package/src/core/types.ts +0 -952
- package/src/core/vector-outbox.ts +0 -299
- package/src/core/vector-store.ts +0 -231
- package/src/core/vector-worker.ts +0 -521
- package/src/core/working-set-store.ts +0 -257
- package/src/extensions/endless-memory/endless-memory-services.ts +0 -350
- package/src/extensions/endless-memory/index.ts +0 -1
- package/src/extensions/index.ts +0 -5
- package/src/extensions/mcp/handlers.ts +0 -960
- package/src/extensions/mcp/index.ts +0 -48
- package/src/extensions/mcp/tools.ts +0 -252
- package/src/extensions/shared-memory/index.ts +0 -1
- package/src/extensions/shared-memory/shared-memory-services.ts +0 -211
- package/src/extensions/vector/embedder.ts +0 -234
- package/src/extensions/vector/index.ts +0 -1
- package/src/hooks/post-tool-use.ts +0 -9
- package/src/hooks/semantic-daemon-client.ts +0 -1
- package/src/hooks/semantic-daemon.ts +0 -11
- package/src/hooks/session-end.ts +0 -9
- package/src/hooks/session-start.ts +0 -9
- package/src/hooks/stop.ts +0 -9
- package/src/hooks/user-prompt-submit.ts +0 -9
- package/src/index.ts +0 -13
- package/src/mcp/handlers.ts +0 -2
- package/src/mcp/index.ts +0 -4
- package/src/mcp/tools.ts +0 -2
- package/src/server/api/chat.ts +0 -2
- package/src/server/api/citations.ts +0 -2
- package/src/server/api/events.ts +0 -2
- package/src/server/api/health.ts +0 -2
- package/src/server/api/index.ts +0 -2
- package/src/server/api/projects.ts +0 -2
- package/src/server/api/search.ts +0 -2
- package/src/server/api/sessions.ts +0 -2
- package/src/server/api/stats.ts +0 -2
- package/src/server/api/turns.ts +0 -2
- package/src/server/api/utils.ts +0 -2
- package/src/server/index.ts +0 -2
- package/src/services/bootstrap-organizer.ts +0 -463
- package/src/services/codex-session-history-importer.ts +0 -966
- package/src/services/hermes-session-history-importer.ts +0 -733
- package/src/services/memory-service-config.ts +0 -36
- package/src/services/memory-service-registry.ts +0 -150
- package/src/services/memory-service.ts +0 -688
- package/src/services/session-history-importer.ts +0 -629
- package/tests/README.md +0 -23
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +0 -54
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +0 -98
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +0 -99
- package/tests/apps/app-layer-boundary.test.ts +0 -48
- package/tests/apps/claude-settings-hooks.test.ts +0 -107
- package/tests/apps/cli-disclosure-output.test.ts +0 -212
- package/tests/apps/codex-import-runner.test.ts +0 -99
- package/tests/apps/codex-validation-output.test.ts +0 -100
- package/tests/apps/hermes-import-runner.test.ts +0 -99
- package/tests/apps/mcp-install-command.test.ts +0 -59
- package/tests/apps/package-build-entrypoints.test.ts +0 -30
- package/tests/apps/postinstall-embedding-backend.test.ts +0 -185
- package/tests/apps/search-api-disclosure.test.ts +0 -162
- package/tests/apps/stats-api-lightweight.test.ts +0 -67
- package/tests/apps/ui-disclosure-output.test.ts +0 -140
- package/tests/core/bootstrap-organizer.test.ts +0 -111
- package/tests/core/canonical-key.test.ts +0 -101
- package/tests/core/codex-session-history-importer-validation.test.ts +0 -185
- package/tests/core/consolidation-worker.test.ts +0 -75
- package/tests/core/embedding-maintenance-service.test.ts +0 -282
- package/tests/core/evidence-aligner.test.ts +0 -152
- package/tests/core/external-market-context.test.ts +0 -209
- package/tests/core/fact-deriver.test.ts +0 -79
- package/tests/core/hermes-session-history-importer-validation.test.ts +0 -609
- package/tests/core/ingest-interceptor.test.ts +0 -38
- package/tests/core/markdown-mirror.test.ts +0 -85
- package/tests/core/matcher.test.ts +0 -112
- package/tests/core/md-mirror.test.ts +0 -50
- package/tests/core/memory-engine-services.test.ts +0 -240
- package/tests/core/memory-ingest-service.test.ts +0 -296
- package/tests/core/memory-query-service.test.ts +0 -129
- package/tests/core/memory-runtime-service.test.ts +0 -201
- package/tests/core/memory-service-composition.test.ts +0 -192
- package/tests/core/memory-service-config.test.ts +0 -41
- package/tests/core/memory-service-facade.test.ts +0 -30
- package/tests/core/memory-service-registry.test.ts +0 -206
- package/tests/core/product-validation-matrix.test.ts +0 -61
- package/tests/core/project-registry.test.ts +0 -78
- package/tests/core/replay-evaluator.test.ts +0 -181
- package/tests/core/retrieval-analytics-service.test.ts +0 -210
- package/tests/core/retrieval-benchmark.test.ts +0 -93
- package/tests/core/retrieval-disclosure-service.test.ts +0 -264
- package/tests/core/retrieval-orchestrator.test.ts +0 -403
- package/tests/core/retrieval-quality.test.ts +0 -31
- package/tests/core/retrieval-services.test.ts +0 -185
- package/tests/core/retriever-fallback-chain.test.ts +0 -223
- package/tests/core/retriever-strategy-scope.test.ts +0 -164
- package/tests/core/retriever.memu-adoption.test.ts +0 -122
- package/tests/core/session-history-importer-filter.test.ts +0 -78
- package/tests/core/session-qrels.test.ts +0 -250
- package/tests/core/sqlite-event-store-replication.test.ts +0 -127
- package/tests/core/summary-deriver.test.ts +0 -66
- package/tests/extensions/embedder-warning-suppression.test.ts +0 -84
- package/tests/extensions/endless-memory-extension-boundary.test.ts +0 -17
- package/tests/extensions/endless-memory-services.test.ts +0 -325
- package/tests/extensions/mcp-context-tools.test.ts +0 -905
- package/tests/extensions/mcp-extension-boundary.test.ts +0 -21
- package/tests/extensions/mcp-package-build.test.ts +0 -22
- package/tests/extensions/mcp-project-aware-tools.test.ts +0 -102
- package/tests/extensions/shared-memory-extension-boundary.test.ts +0 -24
- package/tests/extensions/shared-memory-services.test.ts +0 -309
- package/tests/extensions/vector-extension-boundary.test.ts +0 -21
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -15
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
createRetrievalServices,
|
|
4
|
-
type RetrievalEventStore
|
|
5
|
-
} from '../../src/core/engine/retrieval-services.js';
|
|
6
|
-
import type { Embedder } from '../../src/core/embedder.js';
|
|
7
|
-
import type { Matcher } from '../../src/core/matcher.js';
|
|
8
|
-
import type { Retriever, UnifiedRetrievalResult } from '../../src/core/retriever.js';
|
|
9
|
-
import type { MemoryEvent } from '../../src/core/types.js';
|
|
10
|
-
import type { VectorStore } from '../../src/core/vector-store.js';
|
|
11
|
-
|
|
12
|
-
function event(id: string): MemoryEvent {
|
|
13
|
-
return {
|
|
14
|
-
id,
|
|
15
|
-
sessionId: 's1',
|
|
16
|
-
eventType: 'agent_response',
|
|
17
|
-
content: 'retrieval factory keeps MemoryService thin',
|
|
18
|
-
canonicalKey: `test/${id}`,
|
|
19
|
-
dedupeKey: `s1:${id}`,
|
|
20
|
-
timestamp: new Date('2026-02-25T00:00:00.000Z'),
|
|
21
|
-
metadata: {}
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function retrievalResult(id = 'e1'): UnifiedRetrievalResult {
|
|
26
|
-
const memoryEvent = event(id);
|
|
27
|
-
return {
|
|
28
|
-
memories: [{ event: memoryEvent, score: 0.92, sessionContext: 'nearby context' }],
|
|
29
|
-
matchResult: {
|
|
30
|
-
match: { event: memoryEvent, score: 0.92 },
|
|
31
|
-
confidence: 'high'
|
|
32
|
-
},
|
|
33
|
-
totalTokens: 10,
|
|
34
|
-
context: memoryEvent.content,
|
|
35
|
-
fallbackTrace: ['stage:primary:fast'],
|
|
36
|
-
selectedDebug: [{ eventId: id, score: 0.92, semanticScore: 0.8, lexicalScore: 0.4, recencyScore: 0.1 }],
|
|
37
|
-
candidateDebug: [{ eventId: id, score: 0.92, semanticScore: 0.8, lexicalScore: 0.4, recencyScore: 0.1 }]
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe('createRetrievalServices', () => {
|
|
42
|
-
it('builds one retriever and wires the retrieval service bundle to shared ports', async () => {
|
|
43
|
-
let initializeCalls = 0;
|
|
44
|
-
const traceInputs: Array<Record<string, unknown>> = [];
|
|
45
|
-
const retrieveCalls: Array<{ query: string; options: Record<string, unknown> }> = [];
|
|
46
|
-
const traceStats = {
|
|
47
|
-
totalQueries: 2,
|
|
48
|
-
avgCandidateCount: 1,
|
|
49
|
-
avgSelectedCount: 1,
|
|
50
|
-
selectionRate: 1
|
|
51
|
-
};
|
|
52
|
-
const vectorStore = { marker: 'vector' } as unknown as VectorStore;
|
|
53
|
-
const embedder = { marker: 'embedder' } as unknown as Embedder;
|
|
54
|
-
const matcher = { marker: 'matcher' } as unknown as Matcher;
|
|
55
|
-
const store = {
|
|
56
|
-
getHelpfulnessStats: async () => ({
|
|
57
|
-
avgScore: 0.8,
|
|
58
|
-
totalEvaluated: 25,
|
|
59
|
-
totalRetrievals: 30,
|
|
60
|
-
helpful: 20,
|
|
61
|
-
neutral: 3,
|
|
62
|
-
unhelpful: 2
|
|
63
|
-
}),
|
|
64
|
-
recordRetrievalTrace: async (input: Record<string, unknown>) => { traceInputs.push(input); },
|
|
65
|
-
incrementAccessCount: async (_eventIds: string[]) => {},
|
|
66
|
-
recordRetrieval: async (_eventId: string, _sessionId: string, _score: number, _query: string) => {},
|
|
67
|
-
getEvent: async (id: string) => event(id),
|
|
68
|
-
getSessionEvents: async (_sessionId: string) => [event('e1')],
|
|
69
|
-
getRecentEvents: async (_limit?: number) => [event('e1')],
|
|
70
|
-
getRetrievalTraceStats: async () => traceStats,
|
|
71
|
-
getRecentRetrievalTraces: async () => [],
|
|
72
|
-
getMostAccessed: async () => [],
|
|
73
|
-
evaluateSessionHelpfulness: async (_sessionId: string) => {},
|
|
74
|
-
getUnevaluatedSessions: async () => [],
|
|
75
|
-
getHelpfulMemories: async () => []
|
|
76
|
-
};
|
|
77
|
-
let createArgs: {
|
|
78
|
-
eventStore: unknown;
|
|
79
|
-
vectorStore: unknown;
|
|
80
|
-
embedder: unknown;
|
|
81
|
-
matcher: unknown;
|
|
82
|
-
} | null = null;
|
|
83
|
-
let registeredRewriter: ((query: string) => Promise<string | null>) | null = null;
|
|
84
|
-
const fakeRetriever = {
|
|
85
|
-
setQueryRewriter(rewriter: (query: string) => Promise<string | null>) {
|
|
86
|
-
registeredRewriter = rewriter;
|
|
87
|
-
},
|
|
88
|
-
async retrieve(query: string, options: Record<string, unknown>) {
|
|
89
|
-
retrieveCalls.push({ query, options });
|
|
90
|
-
return retrievalResult('e1');
|
|
91
|
-
}
|
|
92
|
-
} as unknown as Retriever;
|
|
93
|
-
|
|
94
|
-
const services = createRetrievalServices({
|
|
95
|
-
initialize: async () => { initializeCalls += 1; },
|
|
96
|
-
eventStore: store,
|
|
97
|
-
vectorStore,
|
|
98
|
-
embedder,
|
|
99
|
-
matcher,
|
|
100
|
-
getProjectHash: () => 'project-1',
|
|
101
|
-
hasSharedStore: () => false,
|
|
102
|
-
createRetriever: (eventStore, vectorStoreArg, embedderArg, matcherArg) => {
|
|
103
|
-
createArgs = {
|
|
104
|
-
eventStore,
|
|
105
|
-
vectorStore: vectorStoreArg,
|
|
106
|
-
embedder: embedderArg,
|
|
107
|
-
matcher: matcherArg
|
|
108
|
-
};
|
|
109
|
-
return fakeRetriever;
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
expect(services.retriever).toBe(fakeRetriever);
|
|
114
|
-
expect(createArgs).toEqual({ eventStore: store, vectorStore, embedder, matcher });
|
|
115
|
-
expect(registeredRewriter).toEqual(expect.any(Function));
|
|
116
|
-
|
|
117
|
-
await services.retrievalOrchestrator.retrieveMemories('thin core', { sessionId: 's1', topK: 2 });
|
|
118
|
-
|
|
119
|
-
expect(retrieveCalls[0]).toMatchObject({
|
|
120
|
-
query: 'thin core',
|
|
121
|
-
options: {
|
|
122
|
-
topK: 2,
|
|
123
|
-
projectHash: 'project-1',
|
|
124
|
-
projectScopeMode: 'strict'
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
expect(traceInputs[0]).toMatchObject({
|
|
128
|
-
sessionId: 's1',
|
|
129
|
-
projectHash: 'project-1',
|
|
130
|
-
queryText: 'thin core',
|
|
131
|
-
selectedEventIds: ['e1'],
|
|
132
|
-
candidateEventIds: ['e1'],
|
|
133
|
-
confidence: 'high'
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const disclosure = await services.retrievalDisclosureService.search('thin core disclosure', { topK: 1 });
|
|
137
|
-
expect(disclosure.results[0]).toMatchObject({
|
|
138
|
-
id: 'event:e1',
|
|
139
|
-
sourceRef: 'event:e1',
|
|
140
|
-
sessionId: 's1'
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await expect(services.retrievalAnalyticsService.getRetrievalTraceStats()).resolves.toEqual(traceStats);
|
|
144
|
-
expect(initializeCalls).toBe(3);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('rejects default retriever stores that lack retriever read methods', () => {
|
|
148
|
-
const incompleteStore = {
|
|
149
|
-
recordRetrievalTrace: async (_input: Record<string, unknown>) => {},
|
|
150
|
-
incrementAccessCount: async (_eventIds: string[]) => {},
|
|
151
|
-
recordRetrieval: async (_eventId: string, _sessionId: string, _score: number, _query: string) => {},
|
|
152
|
-
getEvent: async (id: string) => event(id),
|
|
153
|
-
getSessionEvents: async (_sessionId: string) => [event('e1')],
|
|
154
|
-
getRetrievalTraceStats: async () => ({
|
|
155
|
-
totalQueries: 0,
|
|
156
|
-
avgCandidateCount: 0,
|
|
157
|
-
avgSelectedCount: 0,
|
|
158
|
-
selectionRate: 0
|
|
159
|
-
}),
|
|
160
|
-
getRecentRetrievalTraces: async () => [],
|
|
161
|
-
getMostAccessed: async () => [],
|
|
162
|
-
evaluateSessionHelpfulness: async (_sessionId: string) => {},
|
|
163
|
-
getUnevaluatedSessions: async () => [],
|
|
164
|
-
getHelpfulMemories: async () => [],
|
|
165
|
-
getHelpfulnessStats: async () => ({
|
|
166
|
-
avgScore: 0,
|
|
167
|
-
totalEvaluated: 0,
|
|
168
|
-
totalRetrievals: 0,
|
|
169
|
-
helpful: 0,
|
|
170
|
-
neutral: 0,
|
|
171
|
-
unhelpful: 0
|
|
172
|
-
})
|
|
173
|
-
} as unknown as RetrievalEventStore;
|
|
174
|
-
|
|
175
|
-
expect(() => createRetrievalServices({
|
|
176
|
-
initialize: async () => {},
|
|
177
|
-
eventStore: incompleteStore,
|
|
178
|
-
vectorStore: { marker: 'vector' } as unknown as VectorStore,
|
|
179
|
-
embedder: { marker: 'embedder' } as unknown as Embedder,
|
|
180
|
-
matcher: { marker: 'matcher' } as unknown as Matcher,
|
|
181
|
-
getProjectHash: () => null,
|
|
182
|
-
hasSharedStore: () => false
|
|
183
|
-
})).toThrow(/getRecentEvents/);
|
|
184
|
-
});
|
|
185
|
-
});
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Retriever } from '../../src/core/retriever.js';
|
|
3
|
-
import { Matcher } from '../../src/core/matcher.js';
|
|
4
|
-
import type { MemoryEvent } from '../../src/core/types.js';
|
|
5
|
-
|
|
6
|
-
function ev(id: string, content: string): MemoryEvent {
|
|
7
|
-
return {
|
|
8
|
-
id,
|
|
9
|
-
eventType: 'user_prompt',
|
|
10
|
-
sessionId: 's1',
|
|
11
|
-
timestamp: new Date('2026-02-24T00:00:00.000Z'),
|
|
12
|
-
content,
|
|
13
|
-
canonicalKey: id,
|
|
14
|
-
dedupeKey: id,
|
|
15
|
-
metadata: {}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('Retriever fallback chain', () => {
|
|
20
|
-
it('falls back from fast to deep when fast has no result', async () => {
|
|
21
|
-
const e = ev('e1', 'deep result memory');
|
|
22
|
-
let vectorCalls = 0;
|
|
23
|
-
|
|
24
|
-
const fakeEventStore = {
|
|
25
|
-
async keywordSearch() {
|
|
26
|
-
return [];
|
|
27
|
-
},
|
|
28
|
-
async getRecentEvents() {
|
|
29
|
-
return [e];
|
|
30
|
-
},
|
|
31
|
-
async getEvent(id: string) {
|
|
32
|
-
return id === 'e1' ? e : null;
|
|
33
|
-
},
|
|
34
|
-
async getSessionEvents() {
|
|
35
|
-
return [e];
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const fakeVectorStore = {
|
|
40
|
-
async search() {
|
|
41
|
-
vectorCalls += 1;
|
|
42
|
-
return [{ id: 'v1', eventId: 'e1', content: e.content, score: 0.9, sessionId: 's1', eventType: e.eventType, timestamp: e.timestamp.toISOString() }];
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const fakeEmbedder = { async embed() { return { vector: [0.1, 0.2] }; } };
|
|
47
|
-
|
|
48
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
49
|
-
const out = await retriever.retrieve('result', { strategy: 'auto', topK: 3, includeSessionContext: false });
|
|
50
|
-
|
|
51
|
-
expect(out.memories.length).toBeGreaterThan(0);
|
|
52
|
-
expect(vectorCalls).toBeGreaterThan(0);
|
|
53
|
-
expect(out.fallbackTrace).toContain('fallback:deep');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('applies custom rerank weights when provided', async () => {
|
|
57
|
-
const e1 = ev('a', 'keyword hit exact');
|
|
58
|
-
const e2 = ev('b', 'less related');
|
|
59
|
-
|
|
60
|
-
const fakeEventStore = {
|
|
61
|
-
async keywordSearch() { return []; },
|
|
62
|
-
async getRecentEvents() { return [e1, e2]; },
|
|
63
|
-
async getEvent(id: string) { return id === 'a' ? e1 : id === 'b' ? e2 : null; },
|
|
64
|
-
async getSessionEvents() { return [e1, e2]; }
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const fakeVectorStore = {
|
|
68
|
-
async search() {
|
|
69
|
-
return [
|
|
70
|
-
{ id: 'v1', eventId: 'b', content: e2.content, score: 0.95, sessionId: 's1', eventType: e2.eventType, timestamp: e2.timestamp.toISOString() },
|
|
71
|
-
{ id: 'v2', eventId: 'a', content: e1.content, score: 0.7, sessionId: 's1', eventType: e1.eventType, timestamp: e1.timestamp.toISOString() },
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const fakeEmbedder = { async embed() { return { vector: [0.1, 0.2] }; } };
|
|
77
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
78
|
-
|
|
79
|
-
const out = await retriever.retrieve('keyword hit', {
|
|
80
|
-
strategy: 'deep',
|
|
81
|
-
topK: 3,
|
|
82
|
-
includeSessionContext: false,
|
|
83
|
-
rerankWeights: { semantic: 0.2, lexical: 0.7, recency: 0.1 }
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
expect(out.memories[0]?.event.id).toBe('a');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('applies TTL/decay penalty for stale low-overlap memories', async () => {
|
|
90
|
-
const old = {
|
|
91
|
-
...ev('old', 'generic memory'),
|
|
92
|
-
timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24 * 120),
|
|
93
|
-
};
|
|
94
|
-
const fresh = {
|
|
95
|
-
...ev('fresh', 'generic memory'),
|
|
96
|
-
timestamp: new Date(),
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const fakeEventStore = {
|
|
100
|
-
async keywordSearch() { return []; },
|
|
101
|
-
async getRecentEvents() { return [old, fresh]; },
|
|
102
|
-
async getEvent(id: string) { return id === 'old' ? old : id === 'fresh' ? fresh : null; },
|
|
103
|
-
async getSessionEvents() { return [old, fresh]; }
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const fakeVectorStore = {
|
|
107
|
-
async search() {
|
|
108
|
-
return [
|
|
109
|
-
{ id: 'v1', eventId: 'old', content: old.content, score: 0.9, sessionId: old.sessionId, eventType: old.eventType, timestamp: old.timestamp.toISOString() },
|
|
110
|
-
{ id: 'v2', eventId: 'fresh', content: fresh.content, score: 0.85, sessionId: fresh.sessionId, eventType: fresh.eventType, timestamp: fresh.timestamp.toISOString() },
|
|
111
|
-
];
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const fakeEmbedder = { async embed() { return { vector: [0.1, 0.2] }; } };
|
|
116
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
117
|
-
|
|
118
|
-
const out = await retriever.retrieve('different query', {
|
|
119
|
-
strategy: 'deep',
|
|
120
|
-
topK: 2,
|
|
121
|
-
includeSessionContext: false,
|
|
122
|
-
decayPolicy: { enabled: true, windowDays: 30, maxPenalty: 0.3 }
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
expect(out.memories[0]?.event.id).toBe('fresh');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('merges rewritten deep query results when intentRewrite is enabled', async () => {
|
|
129
|
-
const a = ev('a', '원문 질의에서는 약한 결과');
|
|
130
|
-
const b = ev('b', '재작성 질의에서 강한 결과');
|
|
131
|
-
|
|
132
|
-
const fakeEventStore = {
|
|
133
|
-
async keywordSearch() { return []; },
|
|
134
|
-
async getRecentEvents() { return [a, b]; },
|
|
135
|
-
async getEvent(id: string) { return id === 'a' ? a : id === 'b' ? b : null; },
|
|
136
|
-
async getSessionEvents() { return [a, b]; }
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
let call = 0;
|
|
140
|
-
const fakeVectorStore = {
|
|
141
|
-
async search() {
|
|
142
|
-
call += 1;
|
|
143
|
-
if (call === 1) {
|
|
144
|
-
return [{ id: 'v1', eventId: 'a', content: a.content, score: 0.8, sessionId: 's1', eventType: a.eventType, timestamp: a.timestamp.toISOString() }];
|
|
145
|
-
}
|
|
146
|
-
return [{ id: 'v2', eventId: 'b', content: b.content, score: 0.95, sessionId: 's1', eventType: b.eventType, timestamp: b.timestamp.toISOString() }];
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const fakeEmbedder = { async embed() { return { vector: [0.1, 0.2] }; } };
|
|
151
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
152
|
-
retriever.setQueryRewriter(async () => '확장된 재작성 질의');
|
|
153
|
-
|
|
154
|
-
const out = await retriever.retrieve('원문 질의', {
|
|
155
|
-
strategy: 'deep',
|
|
156
|
-
topK: 3,
|
|
157
|
-
includeSessionContext: false,
|
|
158
|
-
intentRewrite: true,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
expect(out.memories[0]?.event.id).toBe('b');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('expands related events with graph-hop retrieval', async () => {
|
|
165
|
-
const seed = ev('seed', 'seed event');
|
|
166
|
-
const neighbor = {
|
|
167
|
-
...ev('neighbor', 'related artifact memory'),
|
|
168
|
-
metadata: { relatedEventIds: ['seed'] },
|
|
169
|
-
};
|
|
170
|
-
const seedWithEdge = { ...seed, metadata: { relatedEventIds: ['neighbor'] } };
|
|
171
|
-
|
|
172
|
-
const fakeEventStore = {
|
|
173
|
-
async keywordSearch() { return []; },
|
|
174
|
-
async getRecentEvents() { return [seedWithEdge, neighbor]; },
|
|
175
|
-
async getEvent(id: string) {
|
|
176
|
-
if (id === 'seed') return seedWithEdge;
|
|
177
|
-
if (id === 'neighbor') return neighbor;
|
|
178
|
-
return null;
|
|
179
|
-
},
|
|
180
|
-
async getSessionEvents() { return [seedWithEdge, neighbor]; }
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const fakeVectorStore = {
|
|
184
|
-
async search() {
|
|
185
|
-
return [{ id: 'v1', eventId: 'seed', content: seedWithEdge.content, score: 0.95, sessionId: 's1', eventType: seedWithEdge.eventType, timestamp: seedWithEdge.timestamp.toISOString() }];
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const fakeEmbedder = { async embed() { return { vector: [0.1, 0.2] }; } };
|
|
190
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
191
|
-
|
|
192
|
-
const out = await retriever.retrieve('seed event', {
|
|
193
|
-
strategy: 'deep',
|
|
194
|
-
topK: 5,
|
|
195
|
-
includeSessionContext: false,
|
|
196
|
-
graphHop: { enabled: true, maxHops: 1, hopPenalty: 0.1 }
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const ids = out.memories.map((m) => m.event.id);
|
|
200
|
-
expect(ids).toContain('seed');
|
|
201
|
-
expect(ids).toContain('neighbor');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('uses summary fallback when both fast and deep fail', async () => {
|
|
205
|
-
const e = ev('e2', 'keyword overlap fallback candidate');
|
|
206
|
-
|
|
207
|
-
const fakeEventStore = {
|
|
208
|
-
async keywordSearch() { return []; },
|
|
209
|
-
async getRecentEvents() { return [e]; },
|
|
210
|
-
async getEvent(id: string) { return id === 'e2' ? e : null; },
|
|
211
|
-
async getSessionEvents() { return [e]; }
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const fakeVectorStore = { async search() { return []; } };
|
|
215
|
-
const fakeEmbedder = { async embed() { return { vector: [0.1, 0.2] }; } };
|
|
216
|
-
|
|
217
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
218
|
-
const out = await retriever.retrieve('fallback candidate', { strategy: 'auto', topK: 3, includeSessionContext: false });
|
|
219
|
-
|
|
220
|
-
expect(out.fallbackTrace).toContain('fallback:summary');
|
|
221
|
-
expect(out.memories[0]?.event.id).toBe('e2');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Retriever } from '../../src/core/retriever.js';
|
|
3
|
-
import { Matcher } from '../../src/core/matcher.js';
|
|
4
|
-
import type { MemoryEvent } from '../../src/core/types.js';
|
|
5
|
-
|
|
6
|
-
function ev(id: string, sessionId: string, eventType: MemoryEvent['eventType'], content: string, canonicalKey: string): MemoryEvent {
|
|
7
|
-
return {
|
|
8
|
-
id,
|
|
9
|
-
sessionId,
|
|
10
|
-
eventType,
|
|
11
|
-
content,
|
|
12
|
-
canonicalKey,
|
|
13
|
-
dedupeKey: `${sessionId}:${id}`,
|
|
14
|
-
timestamp: new Date('2026-02-24T00:00:00.000Z'),
|
|
15
|
-
metadata: {}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('Retriever strategy/scope', () => {
|
|
20
|
-
const e1 = ev('e1', 'agent:main:alpha', 'user_prompt', '아침 브리핑 선호', 'pref/briefing/morning');
|
|
21
|
-
const e2 = ev('e2', 'agent:main:beta', 'agent_response', '점심 이후 요약은 잘 안봄', 'pref/briefing/lunch');
|
|
22
|
-
|
|
23
|
-
const fakeEventStore = {
|
|
24
|
-
async keywordSearch() {
|
|
25
|
-
return [
|
|
26
|
-
{ event: e1, rank: -0.1 },
|
|
27
|
-
{ event: e2, rank: -0.2 }
|
|
28
|
-
];
|
|
29
|
-
},
|
|
30
|
-
async getRecentEvents() {
|
|
31
|
-
return [e1, e2];
|
|
32
|
-
},
|
|
33
|
-
async getEvent(id: string) {
|
|
34
|
-
return id === 'e1' ? e1 : id === 'e2' ? e2 : null;
|
|
35
|
-
},
|
|
36
|
-
async getSessionEvents(sessionId: string) {
|
|
37
|
-
return [e1, e2].filter((x) => x.sessionId === sessionId);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const fakeVectorStore = {
|
|
42
|
-
async search() {
|
|
43
|
-
return [
|
|
44
|
-
{
|
|
45
|
-
id: 'v1',
|
|
46
|
-
eventId: 'e2',
|
|
47
|
-
content: e2.content,
|
|
48
|
-
score: 0.92,
|
|
49
|
-
sessionId: e2.sessionId,
|
|
50
|
-
eventType: e2.eventType,
|
|
51
|
-
timestamp: e2.timestamp.toISOString()
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 'v2',
|
|
55
|
-
eventId: 'e1',
|
|
56
|
-
content: e1.content,
|
|
57
|
-
score: 0.8,
|
|
58
|
-
sessionId: e1.sessionId,
|
|
59
|
-
eventType: e1.eventType,
|
|
60
|
-
timestamp: e1.timestamp.toISOString()
|
|
61
|
-
}
|
|
62
|
-
];
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const fakeEmbedder = {
|
|
67
|
-
async embed() {
|
|
68
|
-
return { vector: [0.1, 0.2, 0.3] };
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
it('uses fast strategy keyword path', async () => {
|
|
73
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
74
|
-
const out = await retriever.retrieve('브리핑', { strategy: 'fast', topK: 2, includeSessionContext: false });
|
|
75
|
-
|
|
76
|
-
expect(out.memories.length).toBe(2);
|
|
77
|
-
expect(out.memories[0].event.id).toBe('e1');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('applies scoped filters (session prefix + canonical prefix + includes)', async () => {
|
|
81
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
82
|
-
|
|
83
|
-
const out = await retriever.retrieve('브리핑', {
|
|
84
|
-
strategy: 'deep',
|
|
85
|
-
topK: 5,
|
|
86
|
-
includeSessionContext: false,
|
|
87
|
-
scope: {
|
|
88
|
-
sessionIdPrefix: 'agent:main:alpha',
|
|
89
|
-
canonicalKeyPrefix: 'pref/briefing/morning',
|
|
90
|
-
contentIncludes: ['아침']
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
expect(out.memories.length).toBe(1);
|
|
95
|
-
expect(out.memories[0].event.id).toBe('e1');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('returns no memories for command-artifact queries instead of semantic false positives', async () => {
|
|
99
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
100
|
-
|
|
101
|
-
const out = await retriever.retrieve('local-command-stdout command-name opus', {
|
|
102
|
-
strategy: 'deep',
|
|
103
|
-
topK: 5,
|
|
104
|
-
minScore: 0.1,
|
|
105
|
-
includeSessionContext: false
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
expect(out.memories).toEqual([]);
|
|
109
|
-
expect(out.fallbackTrace).toContain('guard:command-artifact-query');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('filters technical identifier queries when candidates have no exact technical overlap', async () => {
|
|
113
|
-
const retriever = new Retriever(fakeEventStore as any, fakeVectorStore as any, fakeEmbedder as any, new Matcher());
|
|
114
|
-
|
|
115
|
-
const out = await retriever.retrieve('DuckDB legacy storage migrate', {
|
|
116
|
-
strategy: 'deep',
|
|
117
|
-
topK: 5,
|
|
118
|
-
minScore: 0.1,
|
|
119
|
-
includeSessionContext: false
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
expect(out.memories).toEqual([]);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('keeps technical identifier matches when candidate content contains the identifier', async () => {
|
|
126
|
-
const technical = ev(
|
|
127
|
-
'e3',
|
|
128
|
-
'agent:main:alpha',
|
|
129
|
-
'agent_response',
|
|
130
|
-
'SQLite FTS failure no such column T.event_id is fixed with event_id UNINDEXED.',
|
|
131
|
-
'fix/sqlite/fts'
|
|
132
|
-
);
|
|
133
|
-
const eventStore = {
|
|
134
|
-
...fakeEventStore,
|
|
135
|
-
async getEvent(id: string) {
|
|
136
|
-
if (id === 'e3') return technical;
|
|
137
|
-
return fakeEventStore.getEvent(id);
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const vectorStore = {
|
|
141
|
-
async search() {
|
|
142
|
-
return [{
|
|
143
|
-
id: 'v3',
|
|
144
|
-
eventId: 'e3',
|
|
145
|
-
content: technical.content,
|
|
146
|
-
score: 0.93,
|
|
147
|
-
sessionId: technical.sessionId,
|
|
148
|
-
eventType: technical.eventType,
|
|
149
|
-
timestamp: technical.timestamp.toISOString()
|
|
150
|
-
}];
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
const retriever = new Retriever(eventStore as any, vectorStore as any, fakeEmbedder as any, new Matcher());
|
|
154
|
-
|
|
155
|
-
const out = await retriever.retrieve('T.event_id FTS rebuild', {
|
|
156
|
-
strategy: 'deep',
|
|
157
|
-
topK: 5,
|
|
158
|
-
minScore: 0.1,
|
|
159
|
-
includeSessionContext: false
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
expect(out.memories.map((memory) => memory.event.id)).toEqual(['e3']);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Retriever } from '../../src/core/retriever.js';
|
|
3
|
-
import type { MemoryEvent, MatchResult } from '../../src/core/types.js';
|
|
4
|
-
import type { SearchResult } from '../../src/core/vector-store.js';
|
|
5
|
-
|
|
6
|
-
class FakeEventStore {
|
|
7
|
-
constructor(private readonly events: Record<string, MemoryEvent>) {}
|
|
8
|
-
|
|
9
|
-
async getEvent(id: string): Promise<MemoryEvent | null> {
|
|
10
|
-
return this.events[id] || null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async getSessionEvents(sessionId: string): Promise<MemoryEvent[]> {
|
|
14
|
-
return Object.values(this.events).filter((e) => e.sessionId === sessionId);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async getRecentEvents(): Promise<MemoryEvent[]> {
|
|
18
|
-
return Object.values(this.events);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async keywordSearch(query: string): Promise<Array<{ event: MemoryEvent; rank: number }>> {
|
|
22
|
-
const lowered = query.toLowerCase();
|
|
23
|
-
const matches = Object.values(this.events)
|
|
24
|
-
.filter((event) => event.content.toLowerCase().includes(lowered))
|
|
25
|
-
.map((event, index) => ({ event, rank: -(index + 1) }));
|
|
26
|
-
return matches;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
class FakeVectorStore {
|
|
31
|
-
constructor(private readonly results: SearchResult[]) {}
|
|
32
|
-
|
|
33
|
-
async search(): Promise<SearchResult[]> {
|
|
34
|
-
return this.results;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
class FakeEmbedder {
|
|
39
|
-
async embed(): Promise<{ vector: number[] }> {
|
|
40
|
-
return { vector: [0.1, 0.2, 0.3] };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
class FakeMatcher {
|
|
45
|
-
matchSearchResults(results: SearchResult[]): MatchResult {
|
|
46
|
-
if (results.length === 0) {
|
|
47
|
-
return { match: null, confidence: 'none' };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const top = results[0];
|
|
51
|
-
return {
|
|
52
|
-
match: {
|
|
53
|
-
event: {
|
|
54
|
-
id: top.eventId,
|
|
55
|
-
eventType: top.eventType as MemoryEvent['eventType'],
|
|
56
|
-
sessionId: top.sessionId,
|
|
57
|
-
timestamp: new Date(top.timestamp),
|
|
58
|
-
content: top.content,
|
|
59
|
-
canonicalKey: top.eventId,
|
|
60
|
-
dedupeKey: top.eventId,
|
|
61
|
-
metadata: {}
|
|
62
|
-
},
|
|
63
|
-
score: top.score
|
|
64
|
-
},
|
|
65
|
-
confidence: 'suggested'
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function makeEvent(id: string, content: string, metadata?: Record<string, unknown>): MemoryEvent {
|
|
71
|
-
return {
|
|
72
|
-
id,
|
|
73
|
-
eventType: 'user_prompt',
|
|
74
|
-
sessionId: 's1',
|
|
75
|
-
timestamp: new Date('2026-01-01T00:00:00.000Z'),
|
|
76
|
-
content,
|
|
77
|
-
canonicalKey: id,
|
|
78
|
-
dedupeKey: id,
|
|
79
|
-
metadata
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
describe('Retriever memU-inspired enhancements', () => {
|
|
84
|
-
it('applies metadata scope filter with hierarchical key path', async () => {
|
|
85
|
-
const e1 = makeEvent('e1', 'first memory', { scope: { project: { id: 'alpha' } } });
|
|
86
|
-
const e2 = makeEvent('e2', 'second memory', { scope: { project: { id: 'beta' } } });
|
|
87
|
-
|
|
88
|
-
const retriever = new Retriever(
|
|
89
|
-
new FakeEventStore({ e1, e2 }) as any,
|
|
90
|
-
new FakeVectorStore([
|
|
91
|
-
{ id: '1', eventId: 'e1', content: e1.content, score: 0.9, sessionId: 's1', eventType: e1.eventType, timestamp: e1.timestamp.toISOString() },
|
|
92
|
-
{ id: '2', eventId: 'e2', content: e2.content, score: 0.89, sessionId: 's1', eventType: e2.eventType, timestamp: e2.timestamp.toISOString() }
|
|
93
|
-
]) as any,
|
|
94
|
-
new FakeEmbedder() as any,
|
|
95
|
-
new FakeMatcher() as any
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const result = await retriever.retrieve('memory', {
|
|
99
|
-
scope: { metadata: { 'scope.project.id': 'alpha' } }
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(result.memories).toHaveLength(1);
|
|
103
|
-
expect(result.memories[0].event.id).toBe('e1');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('uses fast strategy keyword retrieval when requested', async () => {
|
|
107
|
-
const e1 = makeEvent('e1', 'fix deployment issue with nginx');
|
|
108
|
-
const e2 = makeEvent('e2', 'random unrelated text');
|
|
109
|
-
|
|
110
|
-
const retriever = new Retriever(
|
|
111
|
-
new FakeEventStore({ e1, e2 }) as any,
|
|
112
|
-
new FakeVectorStore([]) as any,
|
|
113
|
-
new FakeEmbedder() as any,
|
|
114
|
-
new FakeMatcher() as any
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const result = await retriever.retrieve('deployment', { strategy: 'fast', topK: 5 });
|
|
118
|
-
|
|
119
|
-
expect(result.memories.length).toBeGreaterThan(0);
|
|
120
|
-
expect(result.memories[0].event.id).toBe('e1');
|
|
121
|
-
});
|
|
122
|
-
});
|