coding-friend-cli 1.37.0 → 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.
Files changed (71) hide show
  1. package/README.md +7 -2
  2. package/dist/{chunk-GZT3YPAP.js → chunk-32UAW3NX.js} +1 -1
  3. package/dist/chunk-5QCGEPQO.js +238 -0
  4. package/dist/{chunk-YIXAQ4R2.js → chunk-6NXZKAOV.js} +1 -1
  5. package/dist/{chunk-676YCW6T.js → chunk-776ZY62N.js} +99 -7
  6. package/dist/{chunk-DP6DFQ4G.js → chunk-BXSCYGWE.js} +2 -2
  7. package/dist/{chunk-W6D3J5DX.js → chunk-CGEMETKQ.js} +8 -6
  8. package/dist/{chunk-XEKQW4RN.js → chunk-ETTZRJ55.js} +1 -1
  9. package/dist/{chunk-IKLMHJH6.js → chunk-GIH6E7GJ.js} +62 -1
  10. package/dist/{chunk-6S7352JG.js → chunk-ONG4KRYP.js} +1 -1
  11. package/dist/{chunk-X2H7JJMR.js → chunk-PLSJW7QJ.js} +29 -1
  12. package/dist/{chunk-O5ZKLFSL.js → chunk-RCE4OWNX.js} +1 -1
  13. package/dist/{chunk-PI4P34KF.js → chunk-RFNKPAJW.js} +3 -1
  14. package/dist/{clean-WEYQZB34.js → clean-YWEMYDE5.js} +2 -2
  15. package/dist/{config-ZMR2AOBB.js → config-NG7SICM4.js} +12 -24
  16. package/dist/{dev-IGHMMEP4.js → dev-PTBNMKZT.js} +3 -3
  17. package/dist/{disable-HL5OEURT.js → disable-MO7AXYYF.js} +20 -3
  18. package/dist/{enable-CZIOCKKJ.js → enable-4GXLQEHD.js} +20 -3
  19. package/dist/{guide-CO2V4ZLZ.js → guide-HCR43LJN.js} +1 -1
  20. package/dist/{host-SSRI4JZV.js → host-EV6QIVTI.js} +2 -2
  21. package/dist/index.js +59 -47
  22. package/dist/{init-6MFQWPOP.js → init-KYE5CJMW.js} +115 -12
  23. package/dist/{install-37NN4CQH.js → install-XAFQY5YF.js} +79 -6
  24. package/dist/{learn-OPH3CI67.js → learn-MC4VDHZH.js} +2 -2
  25. package/dist/{mcp-DWKVMNC7.js → mcp-FV6YX3ZT.js} +7 -7
  26. package/dist/{mcp-serve-5GODJ4DJ.js → mcp-serve-PXREYQRZ.js} +2 -2
  27. package/dist/{mcp-serve-learn-OF3ZJUJC.js → mcp-serve-learn-UVXIDOEZ.js} +1 -1
  28. package/dist/{memory-TRDLADUM.js → memory-DF7OUOTV.js} +6 -6
  29. package/dist/{permission-I3OFJUYA.js → permission-VISPGMUK.js} +38 -4
  30. package/dist/{permissions-J46VP6US.js → permissions-2SBACQHV.js} +2 -2
  31. package/dist/{session-S4WKA4P5.js → session-QK6OZO3X.js} +2 -2
  32. package/dist/{status-YZEOUX46.js → status-FVFTJ6GS.js} +11 -10
  33. package/dist/{statusline-KKFJPTI7.js → statusline-AOZHEQSV.js} +2 -2
  34. package/dist/{uninstall-AVA3J554.js → uninstall-MF6FRUBO.js} +51 -4
  35. package/dist/{update-Q3ACCK4Z.js → update-QNUCSDSA.js} +6 -5
  36. package/dist/{update-check-25SCLHZO.js → update-check-7LKJLJPM.js} +1 -1
  37. package/lib/cf-memory/src/lib/claude-md.ts +82 -70
  38. package/lib/cf-memory/src/lib/status-frame.ts +2 -2
  39. package/lib/cf-memory/src/tools/delete.ts +1 -1
  40. package/lib/cf-memory/src/tools/store.ts +6 -6
  41. package/lib/cf-memory/src/tools/update.ts +5 -11
  42. package/package.json +32 -4
  43. package/lib/cf-memory/README.md +0 -317
  44. package/lib/cf-memory/src/__tests__/claude-md.test.ts +0 -343
  45. package/lib/cf-memory/src/__tests__/daemon-client.test.ts +0 -146
  46. package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +0 -223
  47. package/lib/cf-memory/src/__tests__/daemon.test.ts +0 -407
  48. package/lib/cf-memory/src/__tests__/dedup.test.ts +0 -103
  49. package/lib/cf-memory/src/__tests__/embeddings.test.ts +0 -292
  50. package/lib/cf-memory/src/__tests__/lazy-install.test.ts +0 -210
  51. package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +0 -450
  52. package/lib/cf-memory/src/__tests__/migration.test.ts +0 -255
  53. package/lib/cf-memory/src/__tests__/migrations.test.ts +0 -288
  54. package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +0 -262
  55. package/lib/cf-memory/src/__tests__/ollama.test.ts +0 -48
  56. package/lib/cf-memory/src/__tests__/project-id.test.ts +0 -43
  57. package/lib/cf-memory/src/__tests__/schema.test.ts +0 -128
  58. package/lib/cf-memory/src/__tests__/search.test.ts +0 -115
  59. package/lib/cf-memory/src/__tests__/sqlite-lazy-db.test.ts +0 -213
  60. package/lib/cf-memory/src/__tests__/status-frame.test.ts +0 -136
  61. package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +0 -54
  62. package/lib/cf-memory/src/__tests__/tier.test.ts +0 -388
  63. package/lib/cf-memory/src/__tests__/tools.test.ts +0 -83
  64. package/lib/cf-memory/vitest.config.ts +0 -7
  65. package/lib/learn-host/.prettierignore +0 -4
  66. package/lib/learn-host/.prettierrc +0 -8
  67. package/lib/learn-host/README.md +0 -113
  68. package/lib/learn-host/eslint.config.mjs +0 -6
  69. package/lib/learn-mcp/README.md +0 -187
  70. package/lib/learn-mcp/src/__tests__/docs.test.ts +0 -117
  71. package/lib/learn-mcp/vitest.config.ts +0 -8
@@ -1,146 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { mkdirSync, rmSync } from "fs";
3
- import { join } from "path";
4
- import { tmpdir } from "os";
5
- import { DaemonClient } from "../lib/daemon-client.js";
6
- import { startDaemonServer, type DaemonPaths } from "../daemon/process.js";
7
- import { MiniSearchBackend } from "../backends/minisearch.js";
8
-
9
- let testDir: string;
10
- let testPaths: DaemonPaths;
11
- let counter = 0;
12
-
13
- beforeEach(() => {
14
- testDir = join(tmpdir(), `cf-daemon-client-test-${Date.now()}-${++counter}`);
15
- mkdirSync(testDir, { recursive: true });
16
-
17
- const daemonDir = join(testDir, "daemon");
18
- mkdirSync(daemonDir, { recursive: true });
19
-
20
- testPaths = {
21
- socketPath: join(daemonDir, "daemon.sock"),
22
- pidFile: join(daemonDir, "daemon.pid"),
23
- logFile: join(daemonDir, "daemon.log"),
24
- };
25
- });
26
-
27
- afterEach(() => {
28
- rmSync(testDir, { recursive: true, force: true });
29
- });
30
-
31
- describe("DaemonClient auto-reconnect", () => {
32
- it("calls respawn and retries on connection error", async () => {
33
- const docsDir = join(testDir, "docs");
34
- mkdirSync(docsDir, { recursive: true });
35
-
36
- // Start daemon
37
- const backend = new MiniSearchBackend(docsDir);
38
- let handle = startDaemonServer(backend, {
39
- paths: testPaths,
40
- idleTimeoutMs: 0,
41
- });
42
- await new Promise<void>((resolve) => {
43
- handle.server.once("listening", resolve);
44
- });
45
-
46
- const respawn = vi.fn(async () => {
47
- // Re-create a fresh backend and server on same paths
48
- const newBackend = new MiniSearchBackend(docsDir);
49
- handle = startDaemonServer(newBackend, {
50
- paths: testPaths,
51
- idleTimeoutMs: 0,
52
- });
53
- await new Promise<void>((resolve) => {
54
- handle.server.once("listening", resolve);
55
- });
56
- return true;
57
- });
58
-
59
- const client = new DaemonClient(testPaths.socketPath, { respawn });
60
-
61
- // Verify initial connection works
62
- const alive = await client.ping();
63
- expect(alive).toBe(true);
64
-
65
- // Kill daemon and wait for socket cleanup
66
- await new Promise<void>((resolve) => {
67
- handle.server.close(() => resolve());
68
- });
69
- rmSync(testPaths.socketPath, { force: true });
70
- rmSync(testPaths.pidFile, { force: true });
71
- // Small delay to ensure OS fully releases the socket
72
- await new Promise((r) => setTimeout(r, 50));
73
-
74
- // Next request should trigger respawn and succeed
75
- const stats = await client.stats();
76
- expect(stats.total).toBe(0);
77
- expect(respawn).toHaveBeenCalledOnce();
78
-
79
- // Cleanup
80
- await new Promise<void>((resolve) => {
81
- handle.server.close(() => resolve());
82
- });
83
- rmSync(testPaths.socketPath, { force: true });
84
- rmSync(testPaths.pidFile, { force: true });
85
- });
86
-
87
- it("throws original error when respawn fails", async () => {
88
- const respawn = vi.fn(async () => false);
89
- const client = new DaemonClient(
90
- "/tmp/nonexistent-cf-test-sock-" + Date.now(),
91
- { respawn },
92
- );
93
-
94
- await expect(client.stats()).rejects.toThrow();
95
- expect(respawn).toHaveBeenCalledOnce();
96
- });
97
-
98
- it("throws directly without respawn callback", async () => {
99
- const client = new DaemonClient(
100
- "/tmp/nonexistent-cf-test-sock-" + Date.now(),
101
- );
102
-
103
- await expect(client.stats()).rejects.toThrow();
104
- });
105
-
106
- it("ping does not trigger respawn", async () => {
107
- const respawn = vi.fn(async () => true);
108
- const client = new DaemonClient(
109
- "/tmp/nonexistent-cf-test-sock-" + Date.now(),
110
- { respawn },
111
- );
112
-
113
- // ping() should return false without attempting respawn
114
- const alive = await client.ping();
115
- expect(alive).toBe(false);
116
- expect(respawn).not.toHaveBeenCalled();
117
- });
118
-
119
- it("does not call respawn for HTTP 4xx errors", async () => {
120
- const docsDir = join(testDir, "docs");
121
- mkdirSync(docsDir, { recursive: true });
122
-
123
- const backend = new MiniSearchBackend(docsDir);
124
- const handle = startDaemonServer(backend, {
125
- paths: testPaths,
126
- idleTimeoutMs: 0,
127
- });
128
- await new Promise<void>((resolve) => {
129
- handle.server.once("listening", resolve);
130
- });
131
-
132
- const respawn = vi.fn(async () => true);
133
- const client = new DaemonClient(testPaths.socketPath, { respawn });
134
-
135
- // 404 is not a connection error — should not trigger respawn
136
- const result = await client.retrieve("nonexistent/id");
137
- expect(result).toBeNull();
138
- expect(respawn).not.toHaveBeenCalled();
139
-
140
- await new Promise<void>((resolve) => {
141
- handle.server.close(() => resolve());
142
- });
143
- rmSync(testPaths.socketPath, { force: true });
144
- rmSync(testPaths.pidFile, { force: true });
145
- });
146
- });
@@ -1,223 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { mkdirSync, rmSync, existsSync, readFileSync } from "fs";
3
- import { join } from "path";
4
- import { tmpdir } from "os";
5
- import http from "node:http";
6
- import matter from "gray-matter";
7
- import { MiniSearchBackend } from "../backends/minisearch.js";
8
- import { startDaemonServer, type DaemonPaths } from "../daemon/process.js";
9
- import { DaemonClient } from "../lib/daemon-client.js";
10
-
11
- let testDir: string;
12
- let docsDir: string;
13
- let testPaths: DaemonPaths;
14
- let handle: ReturnType<typeof startDaemonServer> | null = null;
15
- let client: DaemonClient;
16
- let counter = 0;
17
-
18
- beforeEach(async () => {
19
- testDir = join(tmpdir(), `cf-memory-e2e-${Date.now()}-${++counter}`);
20
- docsDir = join(testDir, "docs");
21
- mkdirSync(docsDir, { recursive: true });
22
-
23
- const daemonDir = join(testDir, "daemon");
24
- mkdirSync(daemonDir, { recursive: true });
25
-
26
- testPaths = {
27
- socketPath: join(daemonDir, "daemon.sock"),
28
- pidFile: join(daemonDir, "daemon.pid"),
29
- logFile: join(daemonDir, "daemon.log"),
30
- };
31
-
32
- const backend = new MiniSearchBackend(docsDir);
33
- handle = startDaemonServer(backend, {
34
- paths: testPaths,
35
- idleTimeoutMs: 0,
36
- });
37
-
38
- client = new DaemonClient(testPaths.socketPath);
39
-
40
- // Wait for server to be listening
41
- await new Promise<void>((resolve) => {
42
- handle!.server.once("listening", resolve);
43
- });
44
- });
45
-
46
- afterEach(async () => {
47
- if (handle) {
48
- await new Promise<void>((resolve) => {
49
- handle!.server.close(() => resolve());
50
- });
51
- try {
52
- rmSync(testPaths.socketPath, { force: true });
53
- rmSync(testPaths.pidFile, { force: true });
54
- } catch {
55
- // ignore
56
- }
57
- handle = null;
58
- }
59
- rmSync(testDir, { recursive: true, force: true });
60
- });
61
-
62
- describe("Daemon E2E: DaemonClient → Daemon → MiniSearch → Markdown", () => {
63
- it("full pipeline: store → search → retrieve → update → delete", async () => {
64
- // Store
65
- const memory = await client.store({
66
- title: "E2E Auth Pattern",
67
- description: "JWT auth for API endpoints",
68
- type: "fact",
69
- tags: ["auth", "jwt"],
70
- content: "# Auth\n\nUses RS256 signed JWT tokens.",
71
- });
72
- expect(memory.id).toBe("features/e2e-auth-pattern");
73
-
74
- // Verify file on disk
75
- const filePath = join(docsDir, "features", "e2e-auth-pattern.md");
76
- expect(existsSync(filePath)).toBe(true);
77
- const raw = matter(readFileSync(filePath, "utf-8"));
78
- expect(raw.data.title).toBe("E2E Auth Pattern");
79
- expect(raw.data.type).toBe("fact");
80
-
81
- // Search via daemon
82
- const searchResults = await client.search({ query: "auth" });
83
- expect(searchResults.length).toBeGreaterThan(0);
84
-
85
- // Retrieve
86
- const retrieved = await client.retrieve("features/e2e-auth-pattern");
87
- expect(retrieved).not.toBeNull();
88
- expect(retrieved!.frontmatter.title).toBe("E2E Auth Pattern");
89
- expect(retrieved!.content).toContain("RS256");
90
-
91
- // Update
92
- const updated = await client.update({
93
- id: "features/e2e-auth-pattern",
94
- title: "Updated E2E Auth Pattern",
95
- tags: ["security"],
96
- });
97
- expect(updated).not.toBeNull();
98
- expect(updated!.frontmatter.title).toBe("Updated E2E Auth Pattern");
99
- expect(updated!.frontmatter.tags).toContain("security");
100
- expect(updated!.frontmatter.tags).toContain("auth");
101
-
102
- // Verify update on disk
103
- const updatedRaw = matter(readFileSync(filePath, "utf-8"));
104
- expect(updatedRaw.data.title).toBe("Updated E2E Auth Pattern");
105
-
106
- // Delete
107
- const deleted = await client.delete("features/e2e-auth-pattern");
108
- expect(deleted).toBe(true);
109
-
110
- // Verify deleted from disk
111
- expect(existsSync(filePath)).toBe(false);
112
-
113
- // Verify search no longer finds it
114
- const afterDelete = await client.search({ query: "e2e auth" });
115
- expect(afterDelete.length).toBe(0);
116
- });
117
-
118
- it("list returns all stored memories", async () => {
119
- await client.store({
120
- title: "Memory One",
121
- description: "First memory",
122
- type: "fact",
123
- tags: ["one"],
124
- content: "Content one.",
125
- });
126
- await client.store({
127
- title: "Memory Two",
128
- description: "Second memory",
129
- type: "episode",
130
- tags: ["two"],
131
- content: "Content two.",
132
- });
133
-
134
- const list = await client.list({});
135
- expect(list.length).toBe(2);
136
- });
137
-
138
- it("stats reflects stored memories", async () => {
139
- await client.store({
140
- title: "A Fact",
141
- description: "Fact desc",
142
- type: "fact",
143
- tags: [],
144
- content: "Fact content.",
145
- });
146
- await client.store({
147
- title: "A Bug",
148
- description: "Bug desc",
149
- type: "episode",
150
- tags: [],
151
- content: "Bug content.",
152
- });
153
-
154
- const stats = await client.stats();
155
- expect(stats.total).toBe(2);
156
- expect(stats.byType.fact).toBe(1);
157
- expect(stats.byType.episode).toBe(1);
158
- });
159
-
160
- it("rebuild rebuilds the search index", async () => {
161
- await client.store({
162
- title: "Before Rebuild",
163
- description: "Test rebuild",
164
- type: "fact",
165
- tags: ["rebuild"],
166
- content: "Before rebuild content.",
167
- });
168
-
169
- await client.rebuild();
170
-
171
- // Search still works after rebuild
172
- const results = await client.search({ query: "rebuild" });
173
- expect(results.length).toBeGreaterThan(0);
174
- });
175
-
176
- it("ping returns true when daemon is running", async () => {
177
- const alive = await client.ping();
178
- expect(alive).toBe(true);
179
- });
180
-
181
- it("retrieve returns null for non-existent memory", async () => {
182
- const result = await client.retrieve("features/nonexistent");
183
- expect(result).toBeNull();
184
- });
185
-
186
- it("delete returns false for non-existent memory", async () => {
187
- const result = await client.delete("features/nonexistent");
188
- expect(result).toBe(false);
189
- });
190
-
191
- it("update returns null for non-existent memory", async () => {
192
- const result = await client.update({
193
- id: "features/nonexistent",
194
- title: "Test",
195
- });
196
- expect(result).toBeNull();
197
- });
198
- });
199
-
200
- describe("DaemonClient fallback", () => {
201
- it("ping returns false when daemon not reachable", async () => {
202
- const deadClient = new DaemonClient(
203
- "/tmp/nonexistent-socket-" + Date.now(),
204
- );
205
- const alive = await deadClient.ping();
206
- expect(alive).toBe(false);
207
- });
208
-
209
- it("store throws when daemon not reachable", async () => {
210
- const deadClient = new DaemonClient(
211
- "/tmp/nonexistent-socket-" + Date.now(),
212
- );
213
- await expect(
214
- deadClient.store({
215
- title: "Test",
216
- description: "Test",
217
- type: "fact",
218
- tags: [],
219
- content: "Test",
220
- }),
221
- ).rejects.toThrow();
222
- });
223
- });