recallnest 1.4.1 → 2.2.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/.env +4 -0
- package/CLAUDE.md +7 -3
- package/README.md +202 -130
- package/README_CN.md +263 -190
- package/bin/recallnest.mjs +62 -0
- package/config.json +55 -0
- package/daily-recall.sh +23 -0
- package/integrations/setup-all.sh +31 -0
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +30 -0
- package/logs/ingest-2026-04-11.log +110 -0
- package/logs/ingest-2026-04-12.log +90 -0
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
- package/package.json +10 -3
- package/src/access-tracker.ts +3 -2
- package/src/activity-counter.ts +109 -0
- package/src/adaptive-retrieval.ts +6 -5
- package/src/admission-control.ts +163 -0
- package/src/anchor-generator.ts +80 -0
- package/src/api-server.ts +10 -0
- package/src/audit-log.ts +122 -0
- package/src/auto-gc.ts +98 -29
- package/src/auto-recall.ts +33 -4
- package/src/capture-engine.ts +372 -23
- package/src/cascade-forget.ts +3 -2
- package/src/cli.ts +107 -5
- package/src/compat.ts +22 -0
- package/src/confidence-tracker.ts +210 -47
- package/src/conflict-store.ts +12 -3
- package/src/consolidation-engine.ts +296 -8
- package/src/context-collapse-renderer.ts +222 -0
- package/src/context-composer-stable-selection.ts +5 -1
- package/src/context-composer.ts +239 -4
- package/src/context-reconstructor.ts +404 -0
- package/src/conversation-importer.ts +441 -0
- package/src/data-checkup.ts +232 -0
- package/src/decay-engine.ts +84 -0
- package/src/distill-lock.ts +122 -0
- package/src/doctor.ts +8 -10
- package/src/dream-pipeline.ts +227 -0
- package/src/emotion-detector.ts +90 -0
- package/src/eval.ts +3 -2
- package/src/forget-engine.ts +269 -0
- package/src/frequency-tracker.ts +175 -0
- package/src/graph-export.ts +644 -0
- package/src/ingest.ts +36 -6
- package/src/interference-detector.ts +187 -0
- package/src/kg-extractor.ts +15 -24
- package/src/kg-store.ts +2 -1
- package/src/language-hook.ts +67 -0
- package/src/llm-client.ts +289 -6
- package/src/mcp-server.ts +707 -80
- package/src/memory-assets.ts +22 -5
- package/src/memory-evolution.ts +365 -0
- package/src/memory-lint.ts +429 -0
- package/src/memory-output.ts +69 -0
- package/src/memory-schema.ts +74 -2
- package/src/narrative-schema.ts +79 -0
- package/src/narrative-tagger.ts +164 -0
- package/src/pii-detector.ts +98 -0
- package/src/post-retrieval-filter.ts +76 -0
- package/src/prediction-engine.ts +253 -0
- package/src/preference-slots.ts +65 -2
- package/src/prospective-memory.ts +215 -0
- package/src/query-expander.ts +67 -0
- package/src/recall-governor.ts +183 -0
- package/src/retention-policy.ts +90 -0
- package/src/retriever.ts +828 -55
- package/src/rif.ts +34 -8
- package/src/runtime-config.ts +23 -8
- package/src/scope-inventory.ts +4 -3
- package/src/scope-policy.ts +4 -0
- package/src/session-distiller.ts +307 -0
- package/src/session-output.ts +40 -0
- package/src/session-schema.ts +37 -0
- package/src/session-store.ts +7 -2
- package/src/skill-engine.ts +208 -0
- package/src/skill-promotion.ts +293 -0
- package/src/skill-schema.ts +39 -0
- package/src/store.ts +96 -10
- package/src/topic-tag.ts +66 -0
- package/src/tracker.ts +3 -1
- package/src/ui-server.ts +80 -8
- package/src/workflow-observation-scope-review.ts +2 -1
- package/src/workflow-observation-store.ts +2 -1
- package/src/write-verifier.ts +126 -0
- package/.claude-plugin/CLAUDE.md +0 -29
- package/.claude-plugin/marketplace.json +0 -18
- package/.github/workflows/ci.yml +0 -28
- package/.mcp.json +0 -9
- package/CHANGELOG.md +0 -51
- package/DEMO.md +0 -94
- package/EVAL.md +0 -89
- package/FAILURES.md +0 -75
- package/OPERATIONS.md +0 -59
- package/ROADMAP.md +0 -198
- package/assets/recallnest-workbench.png +0 -0
- package/assets/ui/app.js +0 -807
- package/assets/ui/index.html +0 -154
- package/assets/ui/styles.css +0 -605
- package/assets/wechat-qr.jpg +0 -0
- package/daily-reflections/2026-03-05.md +0 -15
- package/daily-reflections/2026-03-06.md +0 -15
- package/daily-reflections/2026-03-07.md +0 -7
- package/daily-reflections/2026-03-08.md +0 -34
- package/daily-reflections/2026-03-09.md +0 -35
- package/daily-reflections/2026-03-10.md +0 -36
- package/daily-reflections/2026-03-11.md +0 -35
- package/daily-reflections/2026-03-12.md +0 -36
- package/daily-reflections/2026-03-13.md +0 -35
- package/daily-reflections/2026-03-14.md +0 -35
- package/daily-reflections/2026-03-15.md +0 -35
- package/daily-reflections/2026-03-16.md +0 -41
- package/daily-reflections/2026-03-17.md +0 -10
- package/daily-reflections/2026-03-18.md +0 -10
- package/daily-reflections/2026-03-19.md +0 -10
- package/daily-reflections/2026-03-20.md +0 -10
- package/daily-reflections/2026-03-21.md +0 -10
- package/daily-reflections/2026-03-22.md +0 -10
- package/daily-reflections/2026-03-23.md +0 -10
- package/daily-reflections/2026-03-24.md +0 -10
- package/daily-reflections/2026-03-25.md +0 -32
- package/daily-reflections/2026-03-26.md +0 -37
- package/daily-reflections/2026-03-27.md +0 -35
- package/daily-reflections/2026-03-28.md +0 -35
- package/daily-reflections/2026-03-29.md +0 -35
- package/daily-reflections/2026-03-30.md +0 -36
- package/daily-reflections/2026-03-31.md +0 -35
- package/daily-reflections/2026-04-01.md +0 -35
- package/docs/api-reference.md +0 -1291
- package/docs/architecture.md +0 -168
- package/docs/memory-boundary-contract.md +0 -226
- package/docs/memory-categories.md +0 -119
- package/docs/pipeline-audit-ultramemory.md +0 -177
- package/docs/plans/2026-03-08-query-expansion.md +0 -270
- package/docs/plans/2026-03-16-continuity-layer.md +0 -196
- package/docs/plans/mcp-tiering-plan.md +0 -126
- package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
- package/docs/self-evolution.md +0 -198
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
- package/eval/continuity/results-2026-03-16.md +0 -117
- package/eval/continuity/results-2026-03-19.md +0 -453
- package/eval/continuity/runbook.md +0 -110
- package/eval/continuity/seed-cases.md +0 -54
- package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
- package/eval/reports/2026-03-06-baseline.md +0 -82
- package/eval/reports/query-expansion.md +0 -138
- package/lm +0 -19
- package/plan-v1.2-distributable.md +0 -105
- package/scripts/archive-distilled-junk.ts +0 -116
- package/scripts/backfill-l0l1l2.ts +0 -98
- package/scripts/blind-scan.ts +0 -588
- package/scripts/cleanup-step1-exact-dups.ts +0 -117
- package/scripts/cleanup-step2-template-dups.ts +0 -113
- package/scripts/cleanup-step3-stale-content.ts +0 -149
- package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
- package/scripts/delete-distilled-junk.ts +0 -88
- package/scripts/distill-facts.ts +0 -305
- package/scripts/health-check.ts +0 -343
- package/scripts/incremental-ingest.sh +0 -71
- package/scripts/mlp-health-check.ts +0 -382
- package/scripts/pull-claude-desktop.ts +0 -515
- package/scripts/re-ingest-cc.sh +0 -66
- package/scripts/rebalance-memory-health.ts +0 -181
- package/scripts/run-desktop-ingest.sh +0 -11
- package/scripts/start-server.sh +0 -12
- package/scripts/viz-dashboard.html +0 -423
- package/scripts/viz-export.ts +0 -703
- package/src/__tests__/access-tracker-novelty.test.ts +0 -228
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/capture-engine.test.ts +0 -1142
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -33
- package/src/__tests__/confidence-tracker.test.ts +0 -196
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -374
- package/src/__tests__/consolidation-engine.test.ts +0 -181
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/doctor.test.ts +0 -303
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/hotness-blend.test.ts +0 -76
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/kg-extractor.test.ts +0 -184
- package/src/__tests__/kg-store.test.ts +0 -198
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/memory-boundaries.test.ts +0 -237
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-output.test.ts +0 -215
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -230
- package/src/__tests__/preference-slots.test.ts +0 -106
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4743
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/session-checkpoint.test.ts +0 -259
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-output-compressor.test.ts +0 -133
- package/src/__tests__/version-manager.test.ts +0 -213
- package/src/__tests__/workflow-observation.test.ts +0 -400
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "bun:test";
|
|
2
|
-
import { compressToolOutput } from "../tool-output-compressor.js";
|
|
3
|
-
|
|
4
|
-
describe("compressToolOutput", () => {
|
|
5
|
-
// ========================================================================
|
|
6
|
-
// Base64 replacement
|
|
7
|
-
// ========================================================================
|
|
8
|
-
|
|
9
|
-
it("replaces large base64 blocks with size placeholder", () => {
|
|
10
|
-
const base64 = "A".repeat(2000);
|
|
11
|
-
const text = `Here is a screenshot:\n${base64}\nEnd of screenshot.`;
|
|
12
|
-
const result = compressToolOutput(text);
|
|
13
|
-
expect(result).toContain("[image:");
|
|
14
|
-
expect(result).toContain("base64");
|
|
15
|
-
expect(result).not.toContain("AAAA");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("preserves short base64-like strings", () => {
|
|
19
|
-
const text = "The hash is abc123==";
|
|
20
|
-
expect(compressToolOutput(text)).toBe(text);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// ========================================================================
|
|
24
|
-
// Git output compression
|
|
25
|
-
// ========================================================================
|
|
26
|
-
|
|
27
|
-
it("compresses git push boilerplate", () => {
|
|
28
|
-
const text = `$ git push origin main
|
|
29
|
-
Enumerating objects: 5, done.
|
|
30
|
-
Counting objects: 100% (5/5), done.
|
|
31
|
-
Delta compression using up to 8 threads
|
|
32
|
-
Compressing objects: 100% (3/3), done.
|
|
33
|
-
Writing objects: 100% (3/3), 450 bytes | 450.00 KiB/s, done.
|
|
34
|
-
Total 3 (delta 2), reused 0 (delta 0)
|
|
35
|
-
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
|
|
36
|
-
To github.com:user/repo.git
|
|
37
|
-
abc1234..def5678 main -> main
|
|
38
|
-
|
|
39
|
-
Next I'll check the status.`;
|
|
40
|
-
|
|
41
|
-
const result = compressToolOutput(text);
|
|
42
|
-
expect(result).toContain("[git: ok");
|
|
43
|
-
expect(result).toContain("main");
|
|
44
|
-
expect(result).not.toContain("Enumerating objects");
|
|
45
|
-
expect(result).toContain("Next I'll check the status");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("preserves git push failures", () => {
|
|
49
|
-
const text = `$ git push
|
|
50
|
-
Enumerating objects: 5, done.
|
|
51
|
-
error: failed to push some refs to 'origin'
|
|
52
|
-
hint: Updates were rejected because the tip of your branch is behind -> main`;
|
|
53
|
-
|
|
54
|
-
const result = compressToolOutput(text);
|
|
55
|
-
expect(result).toContain("error");
|
|
56
|
-
expect(result).toContain("rejected");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// ========================================================================
|
|
60
|
-
// Test output compression
|
|
61
|
-
// ========================================================================
|
|
62
|
-
|
|
63
|
-
it("compresses passing test summary", () => {
|
|
64
|
-
const text = `$ bun test
|
|
65
|
-
running 15 tests
|
|
66
|
-
test a ... ok
|
|
67
|
-
test b ... ok
|
|
68
|
-
test c ... ok
|
|
69
|
-
test result: ok. 15 passed; 0 failed; 0 ignored
|
|
70
|
-
|
|
71
|
-
All tests passed.`;
|
|
72
|
-
|
|
73
|
-
const result = compressToolOutput(text);
|
|
74
|
-
expect(result.length).toBeLessThanOrEqual(text.length);
|
|
75
|
-
expect(result).toContain("All tests passed");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("preserves failing test output", () => {
|
|
79
|
-
const text = `$ npm test
|
|
80
|
-
Tests: 2 failed, 8 passed
|
|
81
|
-
FAIL src/main.test.ts
|
|
82
|
-
✕ should handle edge case`;
|
|
83
|
-
|
|
84
|
-
const result = compressToolOutput(text);
|
|
85
|
-
expect(result).toContain("failed");
|
|
86
|
-
expect(result).toContain("FAIL");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ========================================================================
|
|
90
|
-
// Safety: user/AI content untouched
|
|
91
|
-
// ========================================================================
|
|
92
|
-
|
|
93
|
-
it("never modifies plain conversation text", () => {
|
|
94
|
-
const text = "User: How do I fix this git push error?\n\nAssistant: You need to pull first.";
|
|
95
|
-
expect(compressToolOutput(text)).toBe(text);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("preserves AI reasoning about tool results", () => {
|
|
99
|
-
const text = "The test result shows 15 passed, which means our fix works.";
|
|
100
|
-
expect(compressToolOutput(text)).toBe(text);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// ========================================================================
|
|
104
|
-
// Edge cases
|
|
105
|
-
// ========================================================================
|
|
106
|
-
|
|
107
|
-
it("handles empty input", () => {
|
|
108
|
-
expect(compressToolOutput("")).toBe("");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("handles short input", () => {
|
|
112
|
-
expect(compressToolOutput("hi")).toBe("hi");
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("handles text without tool outputs", () => {
|
|
116
|
-
const text = "This is a normal conversation.\nNo tool outputs here.";
|
|
117
|
-
expect(compressToolOutput(text)).toBe(text);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// ========================================================================
|
|
121
|
-
// Large output truncation
|
|
122
|
-
// ========================================================================
|
|
123
|
-
|
|
124
|
-
it("truncates large unmatched tool output", () => {
|
|
125
|
-
// Use content with spaces/special chars to avoid base64 regex match
|
|
126
|
-
const largeOutput = Array.from({ length: 150 }, (_, i) => `line ${i}: some detailed output here`).join("\n");
|
|
127
|
-
const text = `$ some-command\n${largeOutput}\n\nNext step.`;
|
|
128
|
-
const result = compressToolOutput(text);
|
|
129
|
-
expect(result).toContain("[...");
|
|
130
|
-
expect(result).toContain("chars truncated");
|
|
131
|
-
expect(result.length).toBeLessThan(text.length);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Tier 3.3: Version Coexistence + Competition
|
|
3
|
-
*
|
|
4
|
-
* Validates:
|
|
5
|
-
* 1. computeVersionRank formula
|
|
6
|
-
* 2. createVersionGroup tags both entries with same group
|
|
7
|
-
* 3. deduplicateByVersionGroup keeps top-ranked per group
|
|
8
|
-
* 4. Entries without version_group pass through unchanged
|
|
9
|
-
* 5. Multiple version groups are handled independently
|
|
10
|
-
* 6. Existing version_group is reused (not duplicated)
|
|
11
|
-
*/
|
|
12
|
-
import { describe, expect, it } from "bun:test";
|
|
13
|
-
import {
|
|
14
|
-
computeVersionRank,
|
|
15
|
-
deduplicateByVersionGroup,
|
|
16
|
-
createVersionGroup,
|
|
17
|
-
} from "../version-manager.js";
|
|
18
|
-
import type { MemoryEntry } from "../store.js";
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Helpers
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
function makeEntry(
|
|
25
|
-
id: string,
|
|
26
|
-
overrides: {
|
|
27
|
-
confidence?: number;
|
|
28
|
-
accessCount?: number;
|
|
29
|
-
version_group?: string;
|
|
30
|
-
version_rank?: number;
|
|
31
|
-
} = {},
|
|
32
|
-
): MemoryEntry {
|
|
33
|
-
const meta: Record<string, unknown> = {
|
|
34
|
-
confidence: overrides.confidence ?? 0.7,
|
|
35
|
-
accessCount: overrides.accessCount ?? 0,
|
|
36
|
-
};
|
|
37
|
-
if (overrides.version_group) meta.version_group = overrides.version_group;
|
|
38
|
-
if (overrides.version_rank !== undefined) meta.version_rank = overrides.version_rank;
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
id,
|
|
42
|
-
text: `memory-${id}`,
|
|
43
|
-
vector: [1, 0, 0],
|
|
44
|
-
category: "events",
|
|
45
|
-
scope: "project:test",
|
|
46
|
-
importance: 0.7,
|
|
47
|
-
timestamp: Date.now(),
|
|
48
|
-
metadata: JSON.stringify(meta),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function createMockStore(entries: MemoryEntry[]) {
|
|
53
|
-
const data = new Map(entries.map(e => [e.id, { ...e }]));
|
|
54
|
-
const updates: Array<{ id: string; metadata: string }> = [];
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
data,
|
|
58
|
-
updates,
|
|
59
|
-
async getById(id: string) {
|
|
60
|
-
return data.get(id) ?? null;
|
|
61
|
-
},
|
|
62
|
-
async update(id: string, upd: { metadata?: string }, _scope?: string[]) {
|
|
63
|
-
const entry = data.get(id);
|
|
64
|
-
if (!entry) return null;
|
|
65
|
-
if (upd.metadata) {
|
|
66
|
-
entry.metadata = upd.metadata;
|
|
67
|
-
updates.push({ id, metadata: upd.metadata });
|
|
68
|
-
}
|
|
69
|
-
return entry;
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// computeVersionRank
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
describe("computeVersionRank", () => {
|
|
79
|
-
it("computes rank from importance, confidence, and accessCount", () => {
|
|
80
|
-
const entry = makeEntry("a", { confidence: 1.0, accessCount: 10 });
|
|
81
|
-
const rank = computeVersionRank(entry);
|
|
82
|
-
// quality = 0.5*0.7 + 0.5*1.0 = 0.85 (importance=0.7 from makeEntry)
|
|
83
|
-
// rank = 0.85 * (1 + ln(11)) ≈ 0.85 * 3.398 = 2.888
|
|
84
|
-
expect(rank).toBeGreaterThan(2.8);
|
|
85
|
-
expect(rank).toBeLessThan(3.0);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("returns baseline for default confidence and zero access", () => {
|
|
89
|
-
const entry = makeEntry("b", { confidence: 0.7, accessCount: 0 });
|
|
90
|
-
const rank = computeVersionRank(entry);
|
|
91
|
-
// quality = 0.5*0.7 + 0.5*0.7 = 0.7
|
|
92
|
-
// rank = 0.7 * (1 + 0) = 0.7
|
|
93
|
-
expect(rank).toBeCloseTo(0.7, 5);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("higher confidence wins over lower with same access", () => {
|
|
97
|
-
const high = computeVersionRank(makeEntry("h", { confidence: 1.0, accessCount: 5 }));
|
|
98
|
-
const low = computeVersionRank(makeEntry("l", { confidence: 0.3, accessCount: 5 }));
|
|
99
|
-
expect(high).toBeGreaterThan(low);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("higher access wins over lower with same confidence", () => {
|
|
103
|
-
const frequent = computeVersionRank(makeEntry("f", { confidence: 0.7, accessCount: 20 }));
|
|
104
|
-
const rare = computeVersionRank(makeEntry("r", { confidence: 0.7, accessCount: 1 }));
|
|
105
|
-
expect(frequent).toBeGreaterThan(rare);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// createVersionGroup
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
describe("createVersionGroup", () => {
|
|
114
|
-
it("tags both entries with the same version_group", async () => {
|
|
115
|
-
const canonical = makeEntry("canon", { confidence: 1.0, accessCount: 5 });
|
|
116
|
-
const member = makeEntry("member", { confidence: 0.5, accessCount: 1 });
|
|
117
|
-
const store = createMockStore([canonical, member]);
|
|
118
|
-
|
|
119
|
-
const groupId = await createVersionGroup(store as any, canonical, member, "project:test");
|
|
120
|
-
|
|
121
|
-
expect(groupId).toBeTruthy();
|
|
122
|
-
expect(store.updates.length).toBe(2);
|
|
123
|
-
|
|
124
|
-
const canonMeta = JSON.parse(store.data.get("canon")!.metadata);
|
|
125
|
-
const memberMeta = JSON.parse(store.data.get("member")!.metadata);
|
|
126
|
-
|
|
127
|
-
expect(canonMeta.version_group).toBe(groupId);
|
|
128
|
-
expect(memberMeta.version_group).toBe(groupId);
|
|
129
|
-
expect(canonMeta.version_rank).toBeGreaterThan(memberMeta.version_rank);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("reuses existing version_group from canonical", async () => {
|
|
133
|
-
const canonical = makeEntry("canon", {
|
|
134
|
-
confidence: 1.0,
|
|
135
|
-
accessCount: 5,
|
|
136
|
-
version_group: "vg-existing",
|
|
137
|
-
version_rank: 3.0,
|
|
138
|
-
});
|
|
139
|
-
const member = makeEntry("new", { confidence: 0.5, accessCount: 0 });
|
|
140
|
-
const store = createMockStore([canonical, member]);
|
|
141
|
-
|
|
142
|
-
const groupId = await createVersionGroup(store as any, canonical, member, "project:test");
|
|
143
|
-
|
|
144
|
-
expect(groupId).toBe("vg-existing");
|
|
145
|
-
const memberMeta = JSON.parse(store.data.get("new")!.metadata);
|
|
146
|
-
expect(memberMeta.version_group).toBe("vg-existing");
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// deduplicateByVersionGroup
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
describe("deduplicateByVersionGroup", () => {
|
|
155
|
-
it("keeps only the top-ranked entry per version group", () => {
|
|
156
|
-
const results = [
|
|
157
|
-
{ entry: makeEntry("a", { version_group: "vg1", version_rank: 3.0 }), score: 0.9, sources: {} },
|
|
158
|
-
{ entry: makeEntry("b", { version_group: "vg1", version_rank: 1.0 }), score: 0.85, sources: {} },
|
|
159
|
-
{ entry: makeEntry("c"), score: 0.8, sources: {} },
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
const deduped = deduplicateByVersionGroup(results);
|
|
163
|
-
|
|
164
|
-
expect(deduped.length).toBe(2);
|
|
165
|
-
expect(deduped.map(r => r.entry.id)).toContain("a");
|
|
166
|
-
expect(deduped.map(r => r.entry.id)).toContain("c");
|
|
167
|
-
expect(deduped.map(r => r.entry.id)).not.toContain("b");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("passes through entries without version_group", () => {
|
|
171
|
-
const results = [
|
|
172
|
-
{ entry: makeEntry("x"), score: 0.9, sources: {} },
|
|
173
|
-
{ entry: makeEntry("y"), score: 0.8, sources: {} },
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
const deduped = deduplicateByVersionGroup(results);
|
|
177
|
-
|
|
178
|
-
expect(deduped.length).toBe(2);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("handles multiple independent version groups", () => {
|
|
182
|
-
const results = [
|
|
183
|
-
{ entry: makeEntry("a1", { version_group: "vg1", version_rank: 3.0 }), score: 0.95, sources: {} },
|
|
184
|
-
{ entry: makeEntry("a2", { version_group: "vg1", version_rank: 1.0 }), score: 0.90, sources: {} },
|
|
185
|
-
{ entry: makeEntry("b1", { version_group: "vg2", version_rank: 2.0 }), score: 0.85, sources: {} },
|
|
186
|
-
{ entry: makeEntry("b2", { version_group: "vg2", version_rank: 4.0 }), score: 0.80, sources: {} },
|
|
187
|
-
{ entry: makeEntry("solo"), score: 0.75, sources: {} },
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
const deduped = deduplicateByVersionGroup(results);
|
|
191
|
-
|
|
192
|
-
expect(deduped.length).toBe(3);
|
|
193
|
-
const ids = deduped.map(r => r.entry.id);
|
|
194
|
-
expect(ids).toContain("a1"); // vg1 winner (rank 3.0)
|
|
195
|
-
expect(ids).toContain("b2"); // vg2 winner (rank 4.0)
|
|
196
|
-
expect(ids).toContain("solo");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("handles empty array", () => {
|
|
200
|
-
expect(deduplicateByVersionGroup([])).toEqual([]);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("handles single entry in a version group", () => {
|
|
204
|
-
const results = [
|
|
205
|
-
{ entry: makeEntry("only", { version_group: "vg1", version_rank: 2.0 }), score: 0.9, sources: {} },
|
|
206
|
-
];
|
|
207
|
-
|
|
208
|
-
const deduped = deduplicateByVersionGroup(results);
|
|
209
|
-
|
|
210
|
-
expect(deduped.length).toBe(1);
|
|
211
|
-
expect(deduped[0].entry.id).toBe("only");
|
|
212
|
-
});
|
|
213
|
-
});
|