coding-friend-cli 1.16.0 → 1.17.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.
Files changed (73) hide show
  1. package/README.md +12 -0
  2. package/dist/{chunk-D4EWPGBL.js → chunk-C5LYVVEI.js} +1 -1
  3. package/dist/{chunk-X5WEODUD.js → chunk-CYQU33FY.js} +1 -0
  4. package/dist/{chunk-QNLL3ZDF.js → chunk-G6CEEMAR.js} +3 -3
  5. package/dist/{chunk-4DB4XTSL.js → chunk-KTX4MGMR.js} +15 -1
  6. package/dist/{chunk-KJUGTLPQ.js → chunk-YO6JKGR3.js} +38 -2
  7. package/dist/{config-AIZJJ5D2.js → config-LZFXXOI4.js} +276 -14
  8. package/dist/{dev-WJ5QQ35B.js → dev-R3IYWZ3M.js} +2 -2
  9. package/dist/{disable-JDVOQNZG.js → disable-R6K5YJN4.js} +2 -2
  10. package/dist/{enable-JBJ4Q2S7.js → enable-HF4PYVJN.js} +2 -2
  11. package/dist/{host-NA7LZ4HX.js → host-SYZH3FVC.js} +4 -4
  12. package/dist/index.js +78 -18
  13. package/dist/{init-FZ3GG53E.js → init-MF7ISADJ.js} +102 -6
  14. package/dist/{install-I3GOS56Q.js → install-Q4PWEU43.js} +4 -4
  15. package/dist/{mcp-DLS3J6QJ.js → mcp-TBEDYELW.js} +4 -4
  16. package/dist/memory-RGLM35HC.js +647 -0
  17. package/dist/postinstall.js +1 -1
  18. package/dist/{session-E3CZJJZQ.js → session-H4XW2WXH.js} +1 -1
  19. package/dist/{statusline-6HQCDWBD.js → statusline-6Y2EBAFQ.js} +1 -1
  20. package/dist/{uninstall-JN5YIKKM.js → uninstall-3PSUDGI4.js} +3 -3
  21. package/dist/{update-OWS4IJTG.js → update-WL6SFGGO.js} +4 -4
  22. package/lib/cf-memory/CHANGELOG.md +25 -0
  23. package/lib/cf-memory/README.md +284 -0
  24. package/lib/cf-memory/package-lock.json +2790 -0
  25. package/lib/cf-memory/package.json +31 -0
  26. package/lib/cf-memory/scripts/migrate-frontmatter.ts +134 -0
  27. package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +223 -0
  28. package/lib/cf-memory/src/__tests__/daemon.test.ts +407 -0
  29. package/lib/cf-memory/src/__tests__/dedup.test.ts +103 -0
  30. package/lib/cf-memory/src/__tests__/embeddings.test.ts +292 -0
  31. package/lib/cf-memory/src/__tests__/lazy-install.test.ts +210 -0
  32. package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +410 -0
  33. package/lib/cf-memory/src/__tests__/migration.test.ts +255 -0
  34. package/lib/cf-memory/src/__tests__/migrations.test.ts +288 -0
  35. package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +262 -0
  36. package/lib/cf-memory/src/__tests__/ollama.test.ts +48 -0
  37. package/lib/cf-memory/src/__tests__/schema.test.ts +128 -0
  38. package/lib/cf-memory/src/__tests__/search.test.ts +115 -0
  39. package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +54 -0
  40. package/lib/cf-memory/src/__tests__/tier.test.ts +293 -0
  41. package/lib/cf-memory/src/__tests__/tools.test.ts +83 -0
  42. package/lib/cf-memory/src/backends/markdown.ts +318 -0
  43. package/lib/cf-memory/src/backends/minisearch.ts +203 -0
  44. package/lib/cf-memory/src/backends/sqlite/embeddings.ts +286 -0
  45. package/lib/cf-memory/src/backends/sqlite/index.ts +549 -0
  46. package/lib/cf-memory/src/backends/sqlite/migrations.ts +188 -0
  47. package/lib/cf-memory/src/backends/sqlite/schema.ts +120 -0
  48. package/lib/cf-memory/src/backends/sqlite/search.ts +296 -0
  49. package/lib/cf-memory/src/bin/cf-memory.ts +2 -0
  50. package/lib/cf-memory/src/daemon/entry.ts +99 -0
  51. package/lib/cf-memory/src/daemon/process.ts +271 -0
  52. package/lib/cf-memory/src/daemon/server.ts +166 -0
  53. package/lib/cf-memory/src/daemon/watcher.ts +90 -0
  54. package/lib/cf-memory/src/index.ts +53 -0
  55. package/lib/cf-memory/src/lib/backend.ts +23 -0
  56. package/lib/cf-memory/src/lib/daemon-client.ts +163 -0
  57. package/lib/cf-memory/src/lib/dedup.ts +80 -0
  58. package/lib/cf-memory/src/lib/lazy-install.ts +274 -0
  59. package/lib/cf-memory/src/lib/ollama.ts +76 -0
  60. package/lib/cf-memory/src/lib/temporal-decay.ts +19 -0
  61. package/lib/cf-memory/src/lib/tier.ts +107 -0
  62. package/lib/cf-memory/src/lib/types.ts +109 -0
  63. package/lib/cf-memory/src/resources/index.ts +62 -0
  64. package/lib/cf-memory/src/server.ts +20 -0
  65. package/lib/cf-memory/src/tools/delete.ts +38 -0
  66. package/lib/cf-memory/src/tools/list.ts +38 -0
  67. package/lib/cf-memory/src/tools/retrieve.ts +52 -0
  68. package/lib/cf-memory/src/tools/search.ts +47 -0
  69. package/lib/cf-memory/src/tools/store.ts +70 -0
  70. package/lib/cf-memory/src/tools/update.ts +62 -0
  71. package/lib/cf-memory/tsconfig.json +15 -0
  72. package/lib/cf-memory/vitest.config.ts +7 -0
  73. package/package.json +1 -1
@@ -0,0 +1,107 @@
1
+ import type { MemoryBackend } from "./backend.js";
2
+ import { DaemonClient } from "./daemon-client.js";
3
+ import { MarkdownBackend } from "../backends/markdown.js";
4
+ import { getDaemonPaths, isDaemonRunning } from "../daemon/process.js";
5
+ import { areSqliteDepsAvailable } from "./lazy-install.js";
6
+ import type { EmbeddingConfig } from "../backends/sqlite/embeddings.js";
7
+ import type { SqliteBackendOptions } from "../backends/sqlite/index.js";
8
+
9
+ export type TierName = "full" | "lite" | "markdown";
10
+ export type TierConfig = "auto" | TierName;
11
+
12
+ export interface TierInfo {
13
+ name: TierName;
14
+ label: string;
15
+ number: 1 | 2 | 3;
16
+ }
17
+
18
+ export const TIERS: Record<TierName, TierInfo> = {
19
+ full: { name: "full", label: "Tier 1 (SQLite + Hybrid)", number: 1 },
20
+ lite: { name: "lite", label: "Tier 2 (MiniSearch + Daemon)", number: 2 },
21
+ markdown: { name: "markdown", label: "Tier 3 (Markdown)", number: 3 },
22
+ };
23
+
24
+ /**
25
+ * Detect the best available tier.
26
+ *
27
+ * Priority: SQLite (Tier 1) → Daemon running (Tier 2) → Markdown (Tier 3)
28
+ */
29
+ export async function detectTier(configTier?: TierConfig): Promise<TierInfo> {
30
+ // Explicit config override
31
+ if (configTier && configTier !== "auto") {
32
+ return TIERS[configTier];
33
+ }
34
+
35
+ // Check if SQLite deps are available → Tier 1
36
+ if (areSqliteDepsAvailable()) {
37
+ return TIERS.full;
38
+ }
39
+
40
+ // Check if daemon is running → Tier 2
41
+ if (await isDaemonRunning()) {
42
+ return TIERS.lite;
43
+ }
44
+
45
+ // Default: Tier 3
46
+ return TIERS.markdown;
47
+ }
48
+
49
+ /**
50
+ * Create the appropriate backend for the detected tier.
51
+ */
52
+ export async function createBackendForTier(
53
+ docsDir: string,
54
+ configTier?: TierConfig,
55
+ embeddingConfig?: Partial<EmbeddingConfig>,
56
+ sqliteOptions?: Pick<SqliteBackendOptions, "dbPath">,
57
+ ): Promise<{ backend: MemoryBackend; tier: TierInfo }> {
58
+ const tier = await detectTier(configTier);
59
+
60
+ switch (tier.name) {
61
+ case "full": {
62
+ // Try to create SqliteBackend, fall back if it fails
63
+ try {
64
+ const { SqliteBackend } = await import("../backends/sqlite/index.js");
65
+ const backend = new SqliteBackend(docsDir, {
66
+ ...(embeddingConfig ? { embedding: embeddingConfig } : {}),
67
+ ...sqliteOptions,
68
+ });
69
+ return { backend, tier };
70
+ } catch {
71
+ // SQLite backend failed — fall through to daemon or markdown
72
+ if (await isDaemonRunning()) {
73
+ const paths = getDaemonPaths();
74
+ const client = new DaemonClient(paths.socketPath);
75
+ const alive = await client.ping();
76
+ if (alive) {
77
+ return { backend: client, tier: TIERS.lite };
78
+ }
79
+ }
80
+ return {
81
+ backend: new MarkdownBackend(docsDir),
82
+ tier: TIERS.markdown,
83
+ };
84
+ }
85
+ }
86
+ case "lite": {
87
+ // Use daemon client
88
+ const paths = getDaemonPaths();
89
+ const client = new DaemonClient(paths.socketPath);
90
+ const alive = await client.ping();
91
+ if (alive) {
92
+ return { backend: client, tier };
93
+ }
94
+ // Daemon not reachable, fall back to Tier 3
95
+ return {
96
+ backend: new MarkdownBackend(docsDir),
97
+ tier: TIERS.markdown,
98
+ };
99
+ }
100
+ case "markdown":
101
+ default:
102
+ return {
103
+ backend: new MarkdownBackend(docsDir),
104
+ tier: TIERS.markdown,
105
+ };
106
+ }
107
+ }
@@ -0,0 +1,109 @@
1
+ export const MEMORY_TYPES = [
2
+ "fact",
3
+ "preference",
4
+ "context",
5
+ "episode",
6
+ "procedure",
7
+ ] as const;
8
+
9
+ export type MemoryType = (typeof MEMORY_TYPES)[number];
10
+
11
+ export const MEMORY_CATEGORIES: Record<MemoryType, string> = {
12
+ fact: "features",
13
+ preference: "conventions",
14
+ context: "decisions",
15
+ episode: "bugs",
16
+ procedure: "infrastructure",
17
+ };
18
+
19
+ export const CATEGORY_TO_TYPE: Record<string, MemoryType> = {
20
+ features: "fact",
21
+ conventions: "preference",
22
+ decisions: "context",
23
+ bugs: "episode",
24
+ infrastructure: "procedure",
25
+ };
26
+
27
+ export interface MemoryFrontmatter {
28
+ title: string;
29
+ description: string;
30
+ type: MemoryType;
31
+ tags: string[];
32
+ importance: number;
33
+ created: string;
34
+ updated: string;
35
+ source: string;
36
+ }
37
+
38
+ export interface Memory {
39
+ id: string;
40
+ slug: string;
41
+ category: string;
42
+ frontmatter: MemoryFrontmatter;
43
+ content: string;
44
+ }
45
+
46
+ export interface MemoryMeta {
47
+ id: string;
48
+ slug: string;
49
+ category: string;
50
+ frontmatter: MemoryFrontmatter;
51
+ excerpt: string;
52
+ }
53
+
54
+ export interface SearchResult {
55
+ memory: MemoryMeta;
56
+ score: number;
57
+ matchedOn: string[];
58
+ }
59
+
60
+ export interface MemoryStats {
61
+ total: number;
62
+ byCategory: Record<string, number>;
63
+ byType: Record<string, number>;
64
+ }
65
+
66
+ export interface StoreInput {
67
+ title: string;
68
+ description: string;
69
+ type: MemoryType;
70
+ tags: string[];
71
+ content: string;
72
+ importance?: number;
73
+ source?: string;
74
+ }
75
+
76
+ export interface SearchInput {
77
+ query: string;
78
+ type?: MemoryType;
79
+ tags?: string[];
80
+ limit?: number;
81
+ }
82
+
83
+ export interface ListInput {
84
+ type?: MemoryType;
85
+ category?: string;
86
+ limit?: number;
87
+ }
88
+
89
+ export interface UpdateInput {
90
+ id: string;
91
+ title?: string;
92
+ description?: string;
93
+ tags?: string[];
94
+ content?: string;
95
+ importance?: number;
96
+ }
97
+
98
+ /**
99
+ * Create a short excerpt from markdown content.
100
+ */
101
+ export function makeExcerpt(content: string, maxLen = 160): string {
102
+ const text = content
103
+ .replace(/^#+\s.*/gm, "")
104
+ .replace(/```[\s\S]*?```/g, "")
105
+ .replace(/\n{2,}/g, " ")
106
+ .replace(/\n/g, " ")
107
+ .trim();
108
+ return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
109
+ }
@@ -0,0 +1,62 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MemoryBackend } from "../lib/backend.js";
3
+
4
+ export function registerAllResources(
5
+ server: McpServer,
6
+ backend: MemoryBackend,
7
+ ): void {
8
+ // memory://index — browse all memories
9
+ server.registerResource(
10
+ "memory-index",
11
+ "memory://index",
12
+ {
13
+ title: "Memory Index",
14
+ description: "Browse all stored memories with titles, types, and tags",
15
+ mimeType: "application/json",
16
+ },
17
+ async (uri) => {
18
+ const metas = await backend.list({});
19
+ return {
20
+ contents: [
21
+ {
22
+ uri: uri.href,
23
+ text: JSON.stringify(
24
+ metas.map((m) => ({
25
+ id: m.id,
26
+ title: m.frontmatter.title,
27
+ description: m.frontmatter.description,
28
+ type: m.frontmatter.type,
29
+ tags: m.frontmatter.tags,
30
+ updated: m.frontmatter.updated,
31
+ })),
32
+ null,
33
+ 2,
34
+ ),
35
+ },
36
+ ],
37
+ };
38
+ },
39
+ );
40
+
41
+ // memory://stats — storage statistics
42
+ server.registerResource(
43
+ "memory-stats",
44
+ "memory://stats",
45
+ {
46
+ title: "Memory Stats",
47
+ description: "Storage statistics: total count, by category, by type",
48
+ mimeType: "application/json",
49
+ },
50
+ async (uri) => {
51
+ const stats = await backend.stats();
52
+ return {
53
+ contents: [
54
+ {
55
+ uri: uri.href,
56
+ text: JSON.stringify(stats, null, 2),
57
+ },
58
+ ],
59
+ };
60
+ },
61
+ );
62
+ }
@@ -0,0 +1,20 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MemoryBackend } from "./lib/backend.js";
3
+ import { registerStore } from "./tools/store.js";
4
+ import { registerSearch } from "./tools/search.js";
5
+ import { registerRetrieve } from "./tools/retrieve.js";
6
+ import { registerList } from "./tools/list.js";
7
+ import { registerUpdate } from "./tools/update.js";
8
+ import { registerDelete } from "./tools/delete.js";
9
+
10
+ export function registerAllTools(
11
+ server: McpServer,
12
+ backend: MemoryBackend,
13
+ ): void {
14
+ registerStore(server, backend);
15
+ registerSearch(server, backend);
16
+ registerRetrieve(server, backend);
17
+ registerList(server, backend);
18
+ registerUpdate(server, backend);
19
+ registerDelete(server, backend);
20
+ }
@@ -0,0 +1,38 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { MemoryBackend } from "../lib/backend.js";
4
+
5
+ export function registerDelete(
6
+ server: McpServer,
7
+ backend: MemoryBackend,
8
+ ): void {
9
+ server.tool(
10
+ "memory_delete",
11
+ "Delete a memory by ID.",
12
+ {
13
+ id: z.string().describe("Memory ID (e.g. features/auth-pattern)"),
14
+ },
15
+ async ({ id }) => {
16
+ const deleted = await backend.delete(id);
17
+ if (!deleted) {
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text" as const,
22
+ text: JSON.stringify({ error: "Memory not found", id }),
23
+ },
24
+ ],
25
+ isError: true,
26
+ };
27
+ }
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text" as const,
32
+ text: JSON.stringify({ id, deleted: true }, null, 2),
33
+ },
34
+ ],
35
+ };
36
+ },
37
+ );
38
+ }
@@ -0,0 +1,38 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { MemoryBackend } from "../lib/backend.js";
4
+ import { MEMORY_TYPES } from "../lib/types.js";
5
+
6
+ export function registerList(server: McpServer, backend: MemoryBackend): void {
7
+ server.tool(
8
+ "memory_list",
9
+ "List memories with optional filtering by type or category.",
10
+ {
11
+ type: z.enum(MEMORY_TYPES).optional().describe("Filter by memory type"),
12
+ category: z.string().optional().describe("Filter by category folder"),
13
+ limit: z.number().optional().describe("Max results, default 50"),
14
+ },
15
+ async ({ type, category, limit }) => {
16
+ const metas = await backend.list({ type, category, limit });
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text" as const,
21
+ text: JSON.stringify(
22
+ metas.map((m) => ({
23
+ id: m.id,
24
+ title: m.frontmatter.title,
25
+ description: m.frontmatter.description,
26
+ type: m.frontmatter.type,
27
+ tags: m.frontmatter.tags,
28
+ updated: m.frontmatter.updated,
29
+ })),
30
+ null,
31
+ 2,
32
+ ),
33
+ },
34
+ ],
35
+ };
36
+ },
37
+ );
38
+ }
@@ -0,0 +1,52 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { MemoryBackend } from "../lib/backend.js";
4
+
5
+ export function registerRetrieve(
6
+ server: McpServer,
7
+ backend: MemoryBackend,
8
+ ): void {
9
+ server.tool(
10
+ "memory_retrieve",
11
+ "Retrieve a specific memory by ID (format: category/slug).",
12
+ {
13
+ id: z.string().describe("Memory ID (e.g. features/auth-pattern)"),
14
+ },
15
+ async ({ id }) => {
16
+ const memory = await backend.retrieve(id);
17
+ if (!memory) {
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text" as const,
22
+ text: JSON.stringify({ error: "Memory not found", id }),
23
+ },
24
+ ],
25
+ isError: true,
26
+ };
27
+ }
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text" as const,
32
+ text: JSON.stringify(
33
+ {
34
+ id: memory.id,
35
+ title: memory.frontmatter.title,
36
+ description: memory.frontmatter.description,
37
+ type: memory.frontmatter.type,
38
+ tags: memory.frontmatter.tags,
39
+ importance: memory.frontmatter.importance,
40
+ created: memory.frontmatter.created,
41
+ updated: memory.frontmatter.updated,
42
+ content: memory.content,
43
+ },
44
+ null,
45
+ 2,
46
+ ),
47
+ },
48
+ ],
49
+ };
50
+ },
51
+ );
52
+ }
@@ -0,0 +1,47 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { MemoryBackend } from "../lib/backend.js";
4
+ import { MEMORY_TYPES } from "../lib/types.js";
5
+
6
+ export function registerSearch(
7
+ server: McpServer,
8
+ backend: MemoryBackend,
9
+ ): void {
10
+ server.tool(
11
+ "memory_search",
12
+ "Search memories by query. Returns ranked results matching title, description, tags, or content.",
13
+ {
14
+ query: z.string().describe("Search query"),
15
+ type: z.enum(MEMORY_TYPES).optional().describe("Filter by memory type"),
16
+ tags: z
17
+ .array(z.string())
18
+ .optional()
19
+ .describe("Filter by tags (OR match)"),
20
+ limit: z.number().optional().describe("Max results, default 10"),
21
+ },
22
+ async ({ query, type, tags, limit }) => {
23
+ const results = await backend.search({ query, type, tags, limit });
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text" as const,
28
+ text: JSON.stringify(
29
+ results.map((r) => ({
30
+ id: r.memory.id,
31
+ title: r.memory.frontmatter.title,
32
+ description: r.memory.frontmatter.description,
33
+ type: r.memory.frontmatter.type,
34
+ tags: r.memory.frontmatter.tags,
35
+ score: r.score,
36
+ matchedOn: r.matchedOn,
37
+ excerpt: r.memory.excerpt,
38
+ })),
39
+ null,
40
+ 2,
41
+ ),
42
+ },
43
+ ],
44
+ };
45
+ },
46
+ );
47
+ }
@@ -0,0 +1,70 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { MemoryBackend } from "../lib/backend.js";
4
+ import { MEMORY_TYPES } from "../lib/types.js";
5
+ import { checkDuplicate } from "../lib/dedup.js";
6
+
7
+ export function registerStore(server: McpServer, backend: MemoryBackend): void {
8
+ server.tool(
9
+ "memory_store",
10
+ "Store a new memory. Provide title, description, type, tags, and content.",
11
+ {
12
+ title: z.string().describe("Memory title"),
13
+ description: z
14
+ .string()
15
+ .describe("One-line searchable summary, under 100 chars"),
16
+ type: z.enum(MEMORY_TYPES).describe("Memory type"),
17
+ tags: z.array(z.string()).describe("3-5 keyword tags"),
18
+ content: z.string().describe("Markdown content (without frontmatter)"),
19
+ importance: z
20
+ .number()
21
+ .min(1)
22
+ .max(5)
23
+ .optional()
24
+ .describe("Importance 1-5, default 3"),
25
+ source: z
26
+ .string()
27
+ .optional()
28
+ .describe("Source: conversation, auto-capture, manual"),
29
+ },
30
+ async ({ title, description, type, tags, content, importance, source }) => {
31
+ const input = {
32
+ title,
33
+ description,
34
+ type,
35
+ tags,
36
+ content,
37
+ importance,
38
+ source,
39
+ };
40
+
41
+ let dedup: Awaited<ReturnType<typeof checkDuplicate>> | null = null;
42
+ try {
43
+ dedup = await checkDuplicate(backend, input);
44
+ } catch {
45
+ // Dedup is best-effort — don't block store on search errors
46
+ }
47
+
48
+ const memory = await backend.store(input);
49
+
50
+ const response: Record<string, unknown> = {
51
+ id: memory.id,
52
+ title: memory.frontmatter.title,
53
+ stored: true,
54
+ };
55
+
56
+ if (dedup?.isDuplicate) {
57
+ response.warning = `Near-duplicate found: ${dedup.similarId} (similarity: ${dedup.similarity.toFixed(2)}). Memory stored anyway.`;
58
+ }
59
+
60
+ return {
61
+ content: [
62
+ {
63
+ type: "text" as const,
64
+ text: JSON.stringify(response, null, 2),
65
+ },
66
+ ],
67
+ };
68
+ },
69
+ );
70
+ }
@@ -0,0 +1,62 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { MemoryBackend } from "../lib/backend.js";
4
+
5
+ export function registerUpdate(
6
+ server: McpServer,
7
+ backend: MemoryBackend,
8
+ ): void {
9
+ server.tool(
10
+ "memory_update",
11
+ "Update an existing memory. Provide the ID and fields to update. Content is appended.",
12
+ {
13
+ id: z.string().describe("Memory ID (e.g. features/auth-pattern)"),
14
+ title: z.string().optional().describe("New title"),
15
+ description: z.string().optional().describe("New description"),
16
+ tags: z
17
+ .array(z.string())
18
+ .optional()
19
+ .describe("Tags to add (merged with existing)"),
20
+ content: z.string().optional().describe("Content to append to existing"),
21
+ importance: z
22
+ .number()
23
+ .min(1)
24
+ .max(5)
25
+ .optional()
26
+ .describe("New importance"),
27
+ },
28
+ async ({ id, title, description, tags, content, importance }) => {
29
+ const memory = await backend.update({
30
+ id,
31
+ title,
32
+ description,
33
+ tags,
34
+ content,
35
+ importance,
36
+ });
37
+ if (!memory) {
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text" as const,
42
+ text: JSON.stringify({ error: "Memory not found", id }),
43
+ },
44
+ ],
45
+ isError: true,
46
+ };
47
+ }
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text" as const,
52
+ text: JSON.stringify(
53
+ { id: memory.id, title: memory.frontmatter.title, updated: true },
54
+ null,
55
+ 2,
56
+ ),
57
+ },
58
+ ],
59
+ };
60
+ },
61
+ );
62
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ },
7
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-friend-cli",
3
- "version": "1.16.0",
3
+ "version": "1.17.1",
4
4
  "description": "CLI for coding-friend — host learning docs, setup MCP server, initialize projects",
5
5
  "type": "module",
6
6
  "bin": {