coding-friend-cli 1.36.4 → 1.38.0
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 +8 -3
- package/dist/{chunk-GZT3YPAP.js → chunk-32UAW3NX.js} +1 -1
- package/dist/chunk-5QCGEPQO.js +238 -0
- package/dist/{chunk-YIXAQ4R2.js → chunk-6NXZKAOV.js} +1 -1
- package/dist/{chunk-XHP3EBR3.js → chunk-776ZY62N.js} +112 -4
- package/dist/{chunk-N4WZWRCI.js → chunk-BXSCYGWE.js} +85 -37
- package/dist/{chunk-AK6RMCAC.js → chunk-CGEMETKQ.js} +44 -30
- package/dist/{chunk-XEKQW4RN.js → chunk-ETTZRJ55.js} +1 -1
- package/dist/{chunk-IKLMHJH6.js → chunk-GIH6E7GJ.js} +62 -1
- package/dist/{chunk-56YJWGAN.js → chunk-ONG4KRYP.js} +60 -47
- package/dist/{chunk-X2H7JJMR.js → chunk-PLSJW7QJ.js} +29 -1
- package/dist/{chunk-O5ZKLFSL.js → chunk-RCE4OWNX.js} +1 -1
- package/dist/{chunk-K4WSC62G.js → chunk-RFNKPAJW.js} +42 -2
- package/dist/{clean-Q4VU662V.js → clean-YWEMYDE5.js} +3 -2
- package/dist/{config-CTTHWJH5.js → config-NG7SICM4.js} +15 -27
- package/dist/{dev-IGHMMEP4.js → dev-PTBNMKZT.js} +3 -3
- package/dist/{disable-HL5OEURT.js → disable-MO7AXYYF.js} +20 -3
- package/dist/{enable-CZIOCKKJ.js → enable-4GXLQEHD.js} +20 -3
- package/dist/{guide-CO2V4ZLZ.js → guide-HCR43LJN.js} +1 -1
- package/dist/{host-NCJMUSME.js → host-EV6QIVTI.js} +3 -3
- package/dist/index.js +62 -48
- package/dist/{init-UABKVDVT.js → init-KYE5CJMW.js} +135 -17
- package/dist/{install-R2HDM3N4.js → install-XAFQY5YF.js} +80 -5
- package/dist/{learn-K64AUUFB.js → learn-MC4VDHZH.js} +2 -2
- package/dist/{mcp-OMQFSJP4.js → mcp-FV6YX3ZT.js} +24 -24
- package/dist/{mcp-serve-3FEPFVVQ.js → mcp-serve-PXREYQRZ.js} +10 -1
- package/dist/{mcp-serve-learn-OF3ZJUJC.js → mcp-serve-learn-UVXIDOEZ.js} +1 -1
- package/dist/{memory-Y6GNALG3.js → memory-DF7OUOTV.js} +7 -7
- package/dist/{permission-I3OFJUYA.js → permission-VISPGMUK.js} +38 -4
- package/dist/{permissions-J46VP6US.js → permissions-2SBACQHV.js} +2 -2
- package/dist/{session-Q2DLQPYP.js → session-QK6OZO3X.js} +3 -2
- package/dist/{status-ML4XEDYL.js → status-FVFTJ6GS.js} +18 -14
- package/dist/{statusline-KKFJPTI7.js → statusline-AOZHEQSV.js} +2 -2
- package/dist/{uninstall-ASLHT3MH.js → uninstall-MF6FRUBO.js} +64 -3
- package/dist/{update-DIOH45TE.js → update-QNUCSDSA.js} +7 -4
- package/dist/{update-check-25SCLHZO.js → update-check-7LKJLJPM.js} +1 -1
- package/lib/cf-memory/src/backends/sqlite/index.ts +142 -85
- package/lib/cf-memory/src/lib/claude-md.ts +82 -70
- package/lib/cf-memory/src/lib/status-frame.ts +2 -2
- package/lib/cf-memory/src/tools/delete.ts +1 -1
- package/lib/cf-memory/src/tools/store.ts +6 -6
- package/lib/cf-memory/src/tools/update.ts +5 -11
- package/package.json +32 -4
- package/lib/cf-memory/README.md +0 -317
- package/lib/cf-memory/src/__tests__/claude-md.test.ts +0 -343
- package/lib/cf-memory/src/__tests__/daemon-client.test.ts +0 -146
- package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +0 -223
- package/lib/cf-memory/src/__tests__/daemon.test.ts +0 -407
- package/lib/cf-memory/src/__tests__/dedup.test.ts +0 -103
- package/lib/cf-memory/src/__tests__/embeddings.test.ts +0 -292
- package/lib/cf-memory/src/__tests__/lazy-install.test.ts +0 -210
- package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +0 -450
- package/lib/cf-memory/src/__tests__/migration.test.ts +0 -255
- package/lib/cf-memory/src/__tests__/migrations.test.ts +0 -288
- package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +0 -262
- package/lib/cf-memory/src/__tests__/ollama.test.ts +0 -48
- package/lib/cf-memory/src/__tests__/project-id.test.ts +0 -43
- package/lib/cf-memory/src/__tests__/schema.test.ts +0 -128
- package/lib/cf-memory/src/__tests__/search.test.ts +0 -115
- package/lib/cf-memory/src/__tests__/status-frame.test.ts +0 -136
- package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +0 -54
- package/lib/cf-memory/src/__tests__/tier.test.ts +0 -367
- package/lib/cf-memory/src/__tests__/tools.test.ts +0 -83
- package/lib/cf-memory/vitest.config.ts +0 -7
- package/lib/learn-host/.prettierignore +0 -4
- package/lib/learn-host/.prettierrc +0 -8
- package/lib/learn-host/README.md +0 -113
- package/lib/learn-host/eslint.config.mjs +0 -6
- package/lib/learn-mcp/README.md +0 -187
- package/lib/learn-mcp/src/__tests__/docs.test.ts +0 -117
- package/lib/learn-mcp/vitest.config.ts +0 -8
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
SCHEMA_V1,
|
|
4
|
-
SCHEMA_VERSION,
|
|
5
|
-
PRAGMA_SETTINGS,
|
|
6
|
-
EMBEDDING_DIMS,
|
|
7
|
-
SCHEMA_V2_METADATA,
|
|
8
|
-
getVecTableSQL,
|
|
9
|
-
} from "../backends/sqlite/schema.js";
|
|
10
|
-
|
|
11
|
-
describe("Schema constants", () => {
|
|
12
|
-
it("SCHEMA_VERSION is 2", () => {
|
|
13
|
-
expect(SCHEMA_VERSION).toBe(2);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("EMBEDDING_DIMS is 384 (all-MiniLM-L6-v2)", () => {
|
|
17
|
-
expect(EMBEDDING_DIMS).toBe(384);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("SCHEMA_V1 contains all required table creation statements", () => {
|
|
21
|
-
const combined = SCHEMA_V1.join(" ");
|
|
22
|
-
|
|
23
|
-
// Main tables
|
|
24
|
-
expect(combined).toContain("CREATE TABLE IF NOT EXISTS schema_version");
|
|
25
|
-
expect(combined).toContain("CREATE TABLE IF NOT EXISTS memories");
|
|
26
|
-
expect(combined).toContain("CREATE TABLE IF NOT EXISTS embedding_cache");
|
|
27
|
-
|
|
28
|
-
// FTS5 virtual table
|
|
29
|
-
expect(combined).toContain(
|
|
30
|
-
"CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5",
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// FTS5 sync triggers
|
|
34
|
-
expect(combined).toContain("CREATE TRIGGER IF NOT EXISTS memories_ai");
|
|
35
|
-
expect(combined).toContain("CREATE TRIGGER IF NOT EXISTS memories_ad");
|
|
36
|
-
expect(combined).toContain("CREATE TRIGGER IF NOT EXISTS memories_au");
|
|
37
|
-
|
|
38
|
-
// Indexes
|
|
39
|
-
expect(combined).toContain("CREATE INDEX IF NOT EXISTS idx_memories_type");
|
|
40
|
-
expect(combined).toContain(
|
|
41
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_category",
|
|
42
|
-
);
|
|
43
|
-
expect(combined).toContain(
|
|
44
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_updated",
|
|
45
|
-
);
|
|
46
|
-
expect(combined).toContain(
|
|
47
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_content_hash",
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("memories table has all required columns", () => {
|
|
52
|
-
const memoriesTable = SCHEMA_V1.find((s) =>
|
|
53
|
-
s.includes("CREATE TABLE IF NOT EXISTS memories"),
|
|
54
|
-
)!;
|
|
55
|
-
|
|
56
|
-
const requiredColumns = [
|
|
57
|
-
"id TEXT PRIMARY KEY",
|
|
58
|
-
"slug TEXT NOT NULL",
|
|
59
|
-
"category TEXT NOT NULL",
|
|
60
|
-
"title TEXT NOT NULL",
|
|
61
|
-
"description TEXT NOT NULL",
|
|
62
|
-
"type TEXT NOT NULL",
|
|
63
|
-
"tags TEXT NOT NULL",
|
|
64
|
-
"importance INTEGER NOT NULL",
|
|
65
|
-
"created TEXT NOT NULL",
|
|
66
|
-
"updated TEXT NOT NULL",
|
|
67
|
-
"source TEXT NOT NULL",
|
|
68
|
-
"content TEXT NOT NULL",
|
|
69
|
-
"content_hash TEXT NOT NULL",
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
for (const col of requiredColumns) {
|
|
73
|
-
expect(memoriesTable).toContain(col);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("FTS5 table uses porter tokenizer", () => {
|
|
78
|
-
const ftsTable = SCHEMA_V1.find((s) => s.includes("memories_fts"))!;
|
|
79
|
-
expect(ftsTable).toContain("tokenize='porter unicode61'");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("embedding_cache table has required columns", () => {
|
|
83
|
-
const cacheTable = SCHEMA_V1.find((s) =>
|
|
84
|
-
s.includes("CREATE TABLE IF NOT EXISTS embedding_cache"),
|
|
85
|
-
)!;
|
|
86
|
-
expect(cacheTable).toContain("content_hash TEXT PRIMARY KEY");
|
|
87
|
-
expect(cacheTable).toContain("embedding BLOB NOT NULL");
|
|
88
|
-
expect(cacheTable).toContain("model TEXT NOT NULL");
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe("PRAGMA_SETTINGS", () => {
|
|
93
|
-
it("enables WAL mode", () => {
|
|
94
|
-
expect(PRAGMA_SETTINGS).toContain("PRAGMA journal_mode = WAL");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("sets synchronous to NORMAL", () => {
|
|
98
|
-
expect(PRAGMA_SETTINGS).toContain("PRAGMA synchronous = NORMAL");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("sets cache size", () => {
|
|
102
|
-
const cachePragma = PRAGMA_SETTINGS.find((p) => p.includes("cache_size"));
|
|
103
|
-
expect(cachePragma).toBeDefined();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("getVecTableSQL()", () => {
|
|
108
|
-
it("creates vec0 table with default dims", () => {
|
|
109
|
-
const sql = getVecTableSQL();
|
|
110
|
-
expect(sql).toContain(
|
|
111
|
-
"CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0",
|
|
112
|
-
);
|
|
113
|
-
expect(sql).toContain("float[384]");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("supports custom dimensions", () => {
|
|
117
|
-
const sql = getVecTableSQL(768);
|
|
118
|
-
expect(sql).toContain("float[768]");
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe("SCHEMA_V2_METADATA", () => {
|
|
123
|
-
it("creates metadata table with key-value structure", () => {
|
|
124
|
-
expect(SCHEMA_V2_METADATA).toContain("CREATE TABLE IF NOT EXISTS metadata");
|
|
125
|
-
expect(SCHEMA_V2_METADATA).toContain("key TEXT PRIMARY KEY");
|
|
126
|
-
expect(SCHEMA_V2_METADATA).toContain("value TEXT NOT NULL");
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { detectSearchMode, rrfFuse } from "../backends/sqlite/search.js";
|
|
3
|
-
|
|
4
|
-
describe("detectSearchMode()", () => {
|
|
5
|
-
it("returns 'keyword' for quoted strings", () => {
|
|
6
|
-
expect(detectSearchMode('"exact phrase"')).toBe("keyword");
|
|
7
|
-
expect(detectSearchMode("'single quoted'")).toBe("keyword");
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("returns 'keyword' for code-like patterns", () => {
|
|
11
|
-
expect(detectSearchMode("auth.middleware")).toBe("keyword");
|
|
12
|
-
expect(detectSearchMode("std::vector")).toBe("keyword");
|
|
13
|
-
expect(detectSearchMode("user->name")).toBe("keyword");
|
|
14
|
-
expect(detectSearchMode("src/lib/auth")).toBe("keyword");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("returns 'semantic' for questions", () => {
|
|
18
|
-
expect(detectSearchMode("how does auth work")).toBe("semantic");
|
|
19
|
-
expect(detectSearchMode("What is the login flow")).toBe("semantic");
|
|
20
|
-
expect(detectSearchMode("why did we choose JWT")).toBe("semantic");
|
|
21
|
-
expect(detectSearchMode("where is the config stored")).toBe("semantic");
|
|
22
|
-
expect(detectSearchMode("when was this added")).toBe("semantic");
|
|
23
|
-
expect(detectSearchMode("who implemented this")).toBe("semantic");
|
|
24
|
-
expect(detectSearchMode("is this deprecated")).toBe("semantic");
|
|
25
|
-
expect(detectSearchMode("can we use OAuth")).toBe("semantic");
|
|
26
|
-
expect(detectSearchMode("does this support SSO")).toBe("semantic");
|
|
27
|
-
expect(detectSearchMode("should we migrate")).toBe("semantic");
|
|
28
|
-
expect(detectSearchMode("which database to use")).toBe("semantic");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("returns 'hybrid' for general queries", () => {
|
|
32
|
-
expect(detectSearchMode("authentication")).toBe("hybrid");
|
|
33
|
-
expect(detectSearchMode("JWT tokens")).toBe("hybrid");
|
|
34
|
-
expect(detectSearchMode("database migration")).toBe("hybrid");
|
|
35
|
-
expect(detectSearchMode("CORS fix")).toBe("hybrid");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("handles edge cases", () => {
|
|
39
|
-
expect(detectSearchMode(" authentication ")).toBe("hybrid");
|
|
40
|
-
expect(detectSearchMode("How")).toBe("semantic");
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("rrfFuse()", () => {
|
|
45
|
-
it("merges two ranked lists using RRF", () => {
|
|
46
|
-
const list1 = [
|
|
47
|
-
{ id: "a", score: 10, matchedOn: ["title"] },
|
|
48
|
-
{ id: "b", score: 5, matchedOn: ["content"] },
|
|
49
|
-
];
|
|
50
|
-
const list2 = [
|
|
51
|
-
{ id: "b", score: 0.9, matchedOn: ["semantic"] },
|
|
52
|
-
{ id: "c", score: 0.5, matchedOn: ["semantic"] },
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const fused = rrfFuse(list1, list2);
|
|
56
|
-
|
|
57
|
-
// "b" appears in both lists, should have highest RRF score
|
|
58
|
-
expect(fused[0].id).toBe("b");
|
|
59
|
-
expect(fused[0].matchedOn).toContain("content");
|
|
60
|
-
expect(fused[0].matchedOn).toContain("semantic");
|
|
61
|
-
|
|
62
|
-
// All three items should be in the result
|
|
63
|
-
expect(fused.map((r) => r.id).sort()).toEqual(["a", "b", "c"]);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("handles single list", () => {
|
|
67
|
-
const list = [
|
|
68
|
-
{ id: "a", score: 10, matchedOn: ["title"] },
|
|
69
|
-
{ id: "b", score: 5, matchedOn: ["content"] },
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
const fused = rrfFuse(list);
|
|
73
|
-
expect(fused).toHaveLength(2);
|
|
74
|
-
expect(fused[0].id).toBe("a"); // rank 0 in first list
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("handles empty lists", () => {
|
|
78
|
-
expect(rrfFuse([], [])).toEqual([]);
|
|
79
|
-
expect(rrfFuse([])).toEqual([]);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("handles three lists", () => {
|
|
83
|
-
const list1 = [{ id: "a", score: 10, matchedOn: ["title"] }];
|
|
84
|
-
const list2 = [{ id: "a", score: 0.9, matchedOn: ["semantic"] }];
|
|
85
|
-
const list3 = [{ id: "b", score: 5, matchedOn: ["tags"] }];
|
|
86
|
-
|
|
87
|
-
const fused = rrfFuse(list1, list2, list3);
|
|
88
|
-
|
|
89
|
-
// "a" appears in 2 lists, should rank higher than "b" in 1 list
|
|
90
|
-
expect(fused[0].id).toBe("a");
|
|
91
|
-
expect(fused[0].score).toBeGreaterThan(fused[1].score);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("RRF scores are positive", () => {
|
|
95
|
-
const list = [
|
|
96
|
-
{ id: "a", score: 10, matchedOn: ["title"] },
|
|
97
|
-
{ id: "b", score: 5, matchedOn: ["content"] },
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
const fused = rrfFuse(list);
|
|
101
|
-
for (const item of fused) {
|
|
102
|
-
expect(item.score).toBeGreaterThan(0);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("merges matchedOn from multiple lists", () => {
|
|
107
|
-
const list1 = [{ id: "a", score: 10, matchedOn: ["title", "tags"] }];
|
|
108
|
-
const list2 = [{ id: "a", score: 0.9, matchedOn: ["semantic"] }];
|
|
109
|
-
|
|
110
|
-
const fused = rrfFuse(list1, list2);
|
|
111
|
-
expect(fused[0].matchedOn).toContain("title");
|
|
112
|
-
expect(fused[0].matchedOn).toContain("tags");
|
|
113
|
-
expect(fused[0].matchedOn).toContain("semantic");
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { buildStoreStatus, buildUpdateStatus } from "../lib/status-frame.js";
|
|
3
|
-
|
|
4
|
-
describe("buildStoreStatus", () => {
|
|
5
|
-
it("returns a list with markdown and database info", () => {
|
|
6
|
-
const result = buildStoreStatus({
|
|
7
|
-
id: "features/auth-pattern",
|
|
8
|
-
title: "Auth Pattern",
|
|
9
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
10
|
-
dbPath: "/home/.coding-friend/memory/projects/-foo/db.sqlite",
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
expect(result).toContain("features/auth-pattern");
|
|
14
|
-
expect(result).toContain("Auth Pattern");
|
|
15
|
-
expect(result).toContain("/docs/memory/features/auth-pattern.md");
|
|
16
|
-
expect(result).toContain("db.sqlite");
|
|
17
|
-
// No box-drawing characters
|
|
18
|
-
expect(result).not.toContain("╭");
|
|
19
|
-
expect(result).not.toContain("╰");
|
|
20
|
-
expect(result).not.toContain("│");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("shows markdown-only when dbPath is null", () => {
|
|
24
|
-
const result = buildStoreStatus({
|
|
25
|
-
id: "features/auth-pattern",
|
|
26
|
-
title: "Auth Pattern",
|
|
27
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
28
|
-
dbPath: null,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
expect(result).toContain("/docs/memory/features/auth-pattern.md");
|
|
32
|
-
expect(result).not.toContain("db.sqlite");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("shows CLAUDE.md updated message when claudeMdUpdated is true", () => {
|
|
36
|
-
const result = buildStoreStatus({
|
|
37
|
-
id: "conventions/code-style",
|
|
38
|
-
title: "Code Style",
|
|
39
|
-
markdownPath: "/docs/memory/conventions/code-style.md",
|
|
40
|
-
dbPath: null,
|
|
41
|
-
claudeMdUpdated: true,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(result).toContain("CLAUDE.md updated");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("omits CLAUDE.md line when claudeMdUpdated is false", () => {
|
|
48
|
-
const result = buildStoreStatus({
|
|
49
|
-
id: "features/auth-pattern",
|
|
50
|
-
title: "Auth Pattern",
|
|
51
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
52
|
-
dbPath: null,
|
|
53
|
-
claudeMdUpdated: false,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
expect(result).not.toContain("CLAUDE.md");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("omits CLAUDE.md line when claudeMdUpdated is not provided", () => {
|
|
60
|
-
const result = buildStoreStatus({
|
|
61
|
-
id: "features/auth-pattern",
|
|
62
|
-
title: "Auth Pattern",
|
|
63
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
64
|
-
dbPath: null,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
expect(result).not.toContain("CLAUDE.md");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("includes duplicate warning when provided", () => {
|
|
71
|
-
const result = buildStoreStatus({
|
|
72
|
-
id: "features/auth-pattern",
|
|
73
|
-
title: "Auth Pattern",
|
|
74
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
75
|
-
dbPath: null,
|
|
76
|
-
warning: "Near-duplicate found: features/old-auth (similarity: 0.82)",
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect(result).toContain("Near-duplicate");
|
|
80
|
-
expect(result).toContain("⚠");
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("buildUpdateStatus", () => {
|
|
85
|
-
it("returns a list with markdown and database info", () => {
|
|
86
|
-
const result = buildUpdateStatus({
|
|
87
|
-
id: "features/auth-pattern",
|
|
88
|
-
title: "Auth Pattern",
|
|
89
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
90
|
-
dbPath: "/home/.coding-friend/memory/projects/-foo/db.sqlite",
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
expect(result).toContain("features/auth-pattern");
|
|
94
|
-
expect(result).toContain("Auth Pattern");
|
|
95
|
-
expect(result).toContain("/docs/memory/features/auth-pattern.md");
|
|
96
|
-
expect(result).toContain("db.sqlite");
|
|
97
|
-
expect(result).not.toContain("╭");
|
|
98
|
-
expect(result).not.toContain("╰");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("shows markdown-only when dbPath is null", () => {
|
|
102
|
-
const result = buildUpdateStatus({
|
|
103
|
-
id: "features/auth-pattern",
|
|
104
|
-
title: "Auth Pattern",
|
|
105
|
-
markdownPath: "/docs/memory/features/auth-pattern.md",
|
|
106
|
-
dbPath: null,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(result).toContain("/docs/memory/features/auth-pattern.md");
|
|
110
|
-
expect(result).not.toContain("db.sqlite");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("shows CLAUDE.md updated message when claudeMdUpdated is true", () => {
|
|
114
|
-
const result = buildUpdateStatus({
|
|
115
|
-
id: "conventions/code-style",
|
|
116
|
-
title: "Code Style",
|
|
117
|
-
markdownPath: "/docs/memory/conventions/code-style.md",
|
|
118
|
-
dbPath: null,
|
|
119
|
-
claudeMdUpdated: true,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
expect(result).toContain("CLAUDE.md updated");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("omits CLAUDE.md line when claudeMdUpdated is false", () => {
|
|
126
|
-
const result = buildUpdateStatus({
|
|
127
|
-
id: "conventions/code-style",
|
|
128
|
-
title: "Code Style",
|
|
129
|
-
markdownPath: "/docs/memory/conventions/code-style.md",
|
|
130
|
-
dbPath: null,
|
|
131
|
-
claudeMdUpdated: false,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
expect(result).not.toContain("CLAUDE.md");
|
|
135
|
-
});
|
|
136
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { applyTemporalDecay } from "../lib/temporal-decay.js";
|
|
3
|
-
|
|
4
|
-
describe("applyTemporalDecay()", () => {
|
|
5
|
-
it("keeps ~100% of score for today's date", () => {
|
|
6
|
-
const today = new Date().toISOString().split("T")[0];
|
|
7
|
-
const result = applyTemporalDecay(10, today);
|
|
8
|
-
// Should be very close to original score
|
|
9
|
-
expect(result).toBeGreaterThan(9.9);
|
|
10
|
-
expect(result).toBeLessThanOrEqual(10);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("decays score for 90-day-old memories", () => {
|
|
14
|
-
const date90ago = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000)
|
|
15
|
-
.toISOString()
|
|
16
|
-
.split("T")[0];
|
|
17
|
-
const result = applyTemporalDecay(10, date90ago);
|
|
18
|
-
// 0.7 + 0.3 * exp(-1) ≈ 0.7 + 0.3 * 0.368 ≈ 0.81
|
|
19
|
-
expect(result).toBeGreaterThan(7.5);
|
|
20
|
-
expect(result).toBeLessThan(8.5);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("keeps at least 70% of score for very old memories", () => {
|
|
24
|
-
const result = applyTemporalDecay(10, "2020-01-01");
|
|
25
|
-
// 0.7 + 0.3 * exp(-very_large) ≈ 0.7
|
|
26
|
-
expect(result).toBeGreaterThanOrEqual(6.9);
|
|
27
|
-
expect(result).toBeLessThan(7.2);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("boosts score based on access count", () => {
|
|
31
|
-
const today = new Date().toISOString().split("T")[0];
|
|
32
|
-
const withoutAccess = applyTemporalDecay(10, today);
|
|
33
|
-
const withAccess = applyTemporalDecay(10, today, 5);
|
|
34
|
-
expect(withAccess).toBeGreaterThan(withoutAccess);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("caps access boost at 10", () => {
|
|
38
|
-
const today = new Date().toISOString().split("T")[0];
|
|
39
|
-
const at10 = applyTemporalDecay(10, today, 10);
|
|
40
|
-
const at100 = applyTemporalDecay(10, today, 100);
|
|
41
|
-
expect(at10).toBe(at100);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("handles zero score", () => {
|
|
45
|
-
const result = applyTemporalDecay(0, "2026-01-01");
|
|
46
|
-
expect(result).toBe(0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("handles negative score (BM25 negated)", () => {
|
|
50
|
-
const today = new Date().toISOString().split("T")[0];
|
|
51
|
-
const result = applyTemporalDecay(-5, today);
|
|
52
|
-
expect(result).toBeLessThan(0);
|
|
53
|
-
});
|
|
54
|
-
});
|