@waynesutton/agent-memory 0.0.1-alpha.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 (200) hide show
  1. package/.claude/settings.json +9 -0
  2. package/.claude/settings.local.json +7 -0
  3. package/AGENTS.md +113 -0
  4. package/CLAUDE.md +79 -0
  5. package/README.md +1003 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +192 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/parsers/claude-code.d.ts +3 -0
  11. package/dist/cli/parsers/claude-code.d.ts.map +1 -0
  12. package/dist/cli/parsers/claude-code.js +75 -0
  13. package/dist/cli/parsers/claude-code.js.map +1 -0
  14. package/dist/cli/parsers/codex.d.ts +3 -0
  15. package/dist/cli/parsers/codex.d.ts.map +1 -0
  16. package/dist/cli/parsers/codex.js +42 -0
  17. package/dist/cli/parsers/codex.js.map +1 -0
  18. package/dist/cli/parsers/conductor.d.ts +3 -0
  19. package/dist/cli/parsers/conductor.d.ts.map +1 -0
  20. package/dist/cli/parsers/conductor.js +43 -0
  21. package/dist/cli/parsers/conductor.js.map +1 -0
  22. package/dist/cli/parsers/cursor.d.ts +3 -0
  23. package/dist/cli/parsers/cursor.d.ts.map +1 -0
  24. package/dist/cli/parsers/cursor.js +50 -0
  25. package/dist/cli/parsers/cursor.js.map +1 -0
  26. package/dist/cli/parsers/index.d.ts +12 -0
  27. package/dist/cli/parsers/index.d.ts.map +1 -0
  28. package/dist/cli/parsers/index.js +27 -0
  29. package/dist/cli/parsers/index.js.map +1 -0
  30. package/dist/cli/parsers/opencode.d.ts +3 -0
  31. package/dist/cli/parsers/opencode.d.ts.map +1 -0
  32. package/dist/cli/parsers/opencode.js +72 -0
  33. package/dist/cli/parsers/opencode.js.map +1 -0
  34. package/dist/cli/parsers/parsers.test.d.ts +2 -0
  35. package/dist/cli/parsers/parsers.test.d.ts.map +1 -0
  36. package/dist/cli/parsers/parsers.test.js +151 -0
  37. package/dist/cli/parsers/parsers.test.js.map +1 -0
  38. package/dist/cli/parsers/pi.d.ts +3 -0
  39. package/dist/cli/parsers/pi.d.ts.map +1 -0
  40. package/dist/cli/parsers/pi.js +43 -0
  41. package/dist/cli/parsers/pi.js.map +1 -0
  42. package/dist/cli/parsers/types.d.ts +25 -0
  43. package/dist/cli/parsers/types.d.ts.map +1 -0
  44. package/dist/cli/parsers/types.js +2 -0
  45. package/dist/cli/parsers/types.js.map +1 -0
  46. package/dist/cli/parsers/vscode-copilot.d.ts +3 -0
  47. package/dist/cli/parsers/vscode-copilot.d.ts.map +1 -0
  48. package/dist/cli/parsers/vscode-copilot.js +69 -0
  49. package/dist/cli/parsers/vscode-copilot.js.map +1 -0
  50. package/dist/cli/parsers/zed.d.ts +3 -0
  51. package/dist/cli/parsers/zed.d.ts.map +1 -0
  52. package/dist/cli/parsers/zed.js +43 -0
  53. package/dist/cli/parsers/zed.js.map +1 -0
  54. package/dist/cli/sync.d.ts +21 -0
  55. package/dist/cli/sync.d.ts.map +1 -0
  56. package/dist/cli/sync.js +78 -0
  57. package/dist/cli/sync.js.map +1 -0
  58. package/dist/cli/type-extractor.d.ts +25 -0
  59. package/dist/cli/type-extractor.d.ts.map +1 -0
  60. package/dist/cli/type-extractor.js +254 -0
  61. package/dist/cli/type-extractor.js.map +1 -0
  62. package/dist/cli/type-extractor.test.d.ts +2 -0
  63. package/dist/cli/type-extractor.test.d.ts.map +1 -0
  64. package/dist/cli/type-extractor.test.js +173 -0
  65. package/dist/cli/type-extractor.test.js.map +1 -0
  66. package/dist/client/http.d.ts +44 -0
  67. package/dist/client/http.d.ts.map +1 -0
  68. package/dist/client/http.js +311 -0
  69. package/dist/client/http.js.map +1 -0
  70. package/dist/client/index.d.ts +158 -0
  71. package/dist/client/index.d.ts.map +1 -0
  72. package/dist/client/index.js +256 -0
  73. package/dist/client/index.js.map +1 -0
  74. package/dist/component/_generated/api.d.ts +12 -0
  75. package/dist/component/_generated/api.d.ts.map +1 -0
  76. package/dist/component/_generated/api.js +13 -0
  77. package/dist/component/_generated/api.js.map +1 -0
  78. package/dist/component/_generated/dataModel.d.ts +18 -0
  79. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  80. package/dist/component/_generated/dataModel.js +11 -0
  81. package/dist/component/_generated/dataModel.js.map +1 -0
  82. package/dist/component/_generated/server.d.ts +42 -0
  83. package/dist/component/_generated/server.d.ts.map +1 -0
  84. package/dist/component/_generated/server.js +39 -0
  85. package/dist/component/_generated/server.js.map +1 -0
  86. package/dist/component/actions.d.ts +42 -0
  87. package/dist/component/actions.d.ts.map +1 -0
  88. package/dist/component/actions.js +405 -0
  89. package/dist/component/actions.js.map +1 -0
  90. package/dist/component/apiKeyMutations.d.ts +29 -0
  91. package/dist/component/apiKeyMutations.d.ts.map +1 -0
  92. package/dist/component/apiKeyMutations.js +149 -0
  93. package/dist/component/apiKeyMutations.js.map +1 -0
  94. package/dist/component/apiKeyQueries.d.ts +37 -0
  95. package/dist/component/apiKeyQueries.d.ts.map +1 -0
  96. package/dist/component/apiKeyQueries.js +127 -0
  97. package/dist/component/apiKeyQueries.js.map +1 -0
  98. package/dist/component/checksum.d.ts +6 -0
  99. package/dist/component/checksum.d.ts.map +1 -0
  100. package/dist/component/checksum.js +14 -0
  101. package/dist/component/checksum.js.map +1 -0
  102. package/dist/component/checksum.test.d.ts +2 -0
  103. package/dist/component/checksum.test.d.ts.map +1 -0
  104. package/dist/component/checksum.test.js +27 -0
  105. package/dist/component/checksum.test.js.map +1 -0
  106. package/dist/component/convex.config.d.ts +3 -0
  107. package/dist/component/convex.config.d.ts.map +1 -0
  108. package/dist/component/convex.config.js +4 -0
  109. package/dist/component/convex.config.js.map +1 -0
  110. package/dist/component/cronActions.d.ts +3 -0
  111. package/dist/component/cronActions.d.ts.map +1 -0
  112. package/dist/component/cronActions.js +38 -0
  113. package/dist/component/cronActions.js.map +1 -0
  114. package/dist/component/cronQueries.d.ts +6 -0
  115. package/dist/component/cronQueries.d.ts.map +1 -0
  116. package/dist/component/cronQueries.js +38 -0
  117. package/dist/component/cronQueries.js.map +1 -0
  118. package/dist/component/crons.d.ts +3 -0
  119. package/dist/component/crons.d.ts.map +1 -0
  120. package/dist/component/crons.js +18 -0
  121. package/dist/component/crons.js.map +1 -0
  122. package/dist/component/format.d.ts +11 -0
  123. package/dist/component/format.d.ts.map +1 -0
  124. package/dist/component/format.js +175 -0
  125. package/dist/component/format.js.map +1 -0
  126. package/dist/component/format.test.d.ts +2 -0
  127. package/dist/component/format.test.d.ts.map +1 -0
  128. package/dist/component/format.test.js +118 -0
  129. package/dist/component/format.test.js.map +1 -0
  130. package/dist/component/mutations.d.ts +158 -0
  131. package/dist/component/mutations.d.ts.map +1 -0
  132. package/dist/component/mutations.js +745 -0
  133. package/dist/component/mutations.js.map +1 -0
  134. package/dist/component/queries.d.ts +94 -0
  135. package/dist/component/queries.d.ts.map +1 -0
  136. package/dist/component/queries.js +574 -0
  137. package/dist/component/queries.js.map +1 -0
  138. package/dist/component/schema.d.ts +278 -0
  139. package/dist/component/schema.d.ts.map +1 -0
  140. package/dist/component/schema.js +161 -0
  141. package/dist/component/schema.js.map +1 -0
  142. package/dist/mcp/server.d.ts +11 -0
  143. package/dist/mcp/server.d.ts.map +1 -0
  144. package/dist/mcp/server.js +571 -0
  145. package/dist/mcp/server.js.map +1 -0
  146. package/dist/shared.d.ts +126 -0
  147. package/dist/shared.d.ts.map +1 -0
  148. package/dist/shared.js +67 -0
  149. package/dist/shared.js.map +1 -0
  150. package/dist/test.d.ts +23 -0
  151. package/dist/test.d.ts.map +1 -0
  152. package/dist/test.js +21 -0
  153. package/dist/test.js.map +1 -0
  154. package/eslint.config.js +15 -0
  155. package/example/convex/convex.config.ts +7 -0
  156. package/example/convex/memory.ts +129 -0
  157. package/llms.md +175 -0
  158. package/llms.txt +126 -0
  159. package/package.json +80 -0
  160. package/prds/API-REFERENCE.md +935 -0
  161. package/prds/SETUP.md +682 -0
  162. package/src/cli/index.ts +254 -0
  163. package/src/cli/parsers/claude-code.ts +80 -0
  164. package/src/cli/parsers/codex.ts +45 -0
  165. package/src/cli/parsers/conductor.ts +47 -0
  166. package/src/cli/parsers/cursor.ts +55 -0
  167. package/src/cli/parsers/index.ts +30 -0
  168. package/src/cli/parsers/opencode.ts +84 -0
  169. package/src/cli/parsers/parsers.test.ts +201 -0
  170. package/src/cli/parsers/pi.ts +47 -0
  171. package/src/cli/parsers/types.ts +26 -0
  172. package/src/cli/parsers/vscode-copilot.ts +78 -0
  173. package/src/cli/parsers/zed.ts +47 -0
  174. package/src/cli/sync.ts +110 -0
  175. package/src/cli/type-extractor.test.ts +241 -0
  176. package/src/cli/type-extractor.ts +331 -0
  177. package/src/client/http.ts +415 -0
  178. package/src/client/index.ts +519 -0
  179. package/src/component/_generated/api.ts +14 -0
  180. package/src/component/_generated/dataModel.ts +20 -0
  181. package/src/component/_generated/server.ts +64 -0
  182. package/src/component/actions.ts +558 -0
  183. package/src/component/apiKeyMutations.ts +175 -0
  184. package/src/component/apiKeyQueries.ts +156 -0
  185. package/src/component/checksum.test.ts +31 -0
  186. package/src/component/checksum.ts +13 -0
  187. package/src/component/convex.config.ts +5 -0
  188. package/src/component/cronActions.ts +52 -0
  189. package/src/component/cronQueries.ts +42 -0
  190. package/src/component/crons.ts +34 -0
  191. package/src/component/format.test.ts +133 -0
  192. package/src/component/format.ts +232 -0
  193. package/src/component/mutations.ts +824 -0
  194. package/src/component/queries.ts +684 -0
  195. package/src/component/schema.ts +207 -0
  196. package/src/mcp/server.ts +695 -0
  197. package/src/shared.ts +251 -0
  198. package/src/test.ts +32 -0
  199. package/tsconfig.json +21 -0
  200. package/vitest.config.ts +8 -0
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { push, pull, detectTools } from "./sync.js";
5
+ import { extractTypeMemories } from "./type-extractor.js";
6
+ import { ConvexHttpClient } from "convex/browser";
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name("agent-memory")
12
+ .description(
13
+ "CLI for syncing agent memories between local files and Convex",
14
+ )
15
+ .version("0.0.1");
16
+
17
+ // ── init ────────────────────────────────────────────────────────────
18
+
19
+ program
20
+ .command("init")
21
+ .description("Detect tools and register project")
22
+ .option("--project <id>", "Project ID", "default")
23
+ .option("--name <name>", "Project display name")
24
+ .action(async (opts) => {
25
+ const convexUrl = requireConvexUrl();
26
+ const dir = process.cwd();
27
+
28
+ console.log("Detecting tools...");
29
+ const tools = await detectTools(dir);
30
+
31
+ if (tools.length === 0) {
32
+ console.log(
33
+ "No tool configurations detected. Supported: Claude Code, Cursor, OpenCode, Codex, Conductor, Zed, VS Code Copilot, Pi",
34
+ );
35
+ } else {
36
+ console.log(`Found: ${tools.join(", ")}`);
37
+ }
38
+
39
+ const client = new ConvexHttpClient(convexUrl);
40
+ await client.mutation("agentMemory/mutations:upsertProject" as any, {
41
+ projectId: opts.project,
42
+ name: opts.name ?? opts.project,
43
+ settings: {
44
+ autoSync: false,
45
+ syncFormats: tools,
46
+ },
47
+ });
48
+
49
+ console.log(`Project "${opts.project}" registered.`);
50
+ });
51
+
52
+ // ── push ────────────────────────────────────────────────────────────
53
+
54
+ program
55
+ .command("push")
56
+ .description("Push local memory files to Convex")
57
+ .option("--project <id>", "Project ID", "default")
58
+ .option("--format <format>", "Tool format to parse")
59
+ .option("--user <id>", "User ID for user-scoped memories")
60
+ .action(async (opts) => {
61
+ const convexUrl = requireConvexUrl();
62
+ console.log("Pushing local memories to Convex...");
63
+
64
+ await push({
65
+ convexUrl,
66
+ projectId: opts.project,
67
+ format: opts.format,
68
+ userId: opts.user,
69
+ dir: process.cwd(),
70
+ });
71
+ });
72
+
73
+ // ── pull ────────────────────────────────────────────────────────────
74
+
75
+ program
76
+ .command("pull")
77
+ .description("Pull memories from Convex to local files")
78
+ .option("--project <id>", "Project ID", "default")
79
+ .option("--format <format>", "Output format", "raw")
80
+ .option("--user <id>", "User ID for user-scoped memories")
81
+ .action(async (opts) => {
82
+ const convexUrl = requireConvexUrl();
83
+ console.log("Pulling memories from Convex...");
84
+
85
+ await pull({
86
+ convexUrl,
87
+ projectId: opts.project,
88
+ format: opts.format,
89
+ userId: opts.user,
90
+ dir: process.cwd(),
91
+ });
92
+ });
93
+
94
+ // ── list ────────────────────────────────────────────────────────────
95
+
96
+ program
97
+ .command("list")
98
+ .description("List memories in the terminal")
99
+ .option("--project <id>", "Project ID", "default")
100
+ .option("--type <type>", "Filter by memory type")
101
+ .action(async (opts) => {
102
+ const convexUrl = requireConvexUrl();
103
+ const client = new ConvexHttpClient(convexUrl);
104
+
105
+ const memories = await client.query(
106
+ "agentMemory/queries:list" as any,
107
+ {
108
+ projectId: opts.project,
109
+ memoryType: opts.type,
110
+ archived: false,
111
+ },
112
+ );
113
+
114
+ if (memories.length === 0) {
115
+ console.log("No memories found.");
116
+ return;
117
+ }
118
+
119
+ console.log(`\n${memories.length} memories:\n`);
120
+ for (const m of memories) {
121
+ const priority = m.priority !== undefined ? ` [p=${m.priority}]` : "";
122
+ console.log(
123
+ ` ${m._id} ${m.memoryType.padEnd(12)} ${m.title}${priority}`,
124
+ );
125
+ }
126
+ });
127
+
128
+ // ── search ──────────────────────────────────────────────────────────
129
+
130
+ program
131
+ .command("search <query>")
132
+ .description("Search memories")
133
+ .option("--project <id>", "Project ID", "default")
134
+ .option("--limit <n>", "Max results", "10")
135
+ .action(async (query, opts) => {
136
+ const convexUrl = requireConvexUrl();
137
+ const client = new ConvexHttpClient(convexUrl);
138
+
139
+ const results = await client.query(
140
+ "agentMemory/queries:search" as any,
141
+ {
142
+ projectId: opts.project,
143
+ query,
144
+ limit: parseInt(opts.limit),
145
+ },
146
+ );
147
+
148
+ if (results.length === 0) {
149
+ console.log("No results found.");
150
+ return;
151
+ }
152
+
153
+ console.log(`\n${results.length} results:\n`);
154
+ for (const m of results) {
155
+ console.log(` ${m._id} ${m.title}`);
156
+ // Show first 100 chars of content
157
+ const preview = m.content.slice(0, 100).replace(/\n/g, " ");
158
+ console.log(` ${preview}${m.content.length > 100 ? "..." : ""}`);
159
+ console.log();
160
+ }
161
+ });
162
+
163
+ // ── ingest-types ────────────────────────────────────────────────────
164
+
165
+ program
166
+ .command("ingest-types <glob>")
167
+ .description(
168
+ "Generate type documentation from TypeScript files and store as reference memories",
169
+ )
170
+ .option("--project <id>", "Project ID", "default")
171
+ .option("--user <id>", "User ID for user-scoped memories")
172
+ .option("--tags <tags>", "Comma-separated additional tags")
173
+ .option("--priority <n>", "Priority (0-1)", "0.6")
174
+ .option(
175
+ "--exclude <patterns>",
176
+ "Comma-separated glob patterns to exclude",
177
+ )
178
+ .action(async (glob: string, opts) => {
179
+ const convexUrl = requireConvexUrl();
180
+
181
+ console.log(`Extracting types from: ${glob}`);
182
+
183
+ const result = await extractTypeMemories({
184
+ globPattern: glob,
185
+ cwd: process.cwd(),
186
+ tags: opts.tags ? opts.tags.split(",") : [],
187
+ priority: parseFloat(opts.priority),
188
+ exclude: opts.exclude ? opts.exclude.split(",") : undefined,
189
+ });
190
+
191
+ if (result.memories.length === 0) {
192
+ console.log(
193
+ `No exported types found in ${result.filesProcessed} files.`,
194
+ );
195
+ return;
196
+ }
197
+
198
+ console.log(
199
+ `Found ${result.memories.length} type definitions across ${result.filesProcessed} files.`,
200
+ );
201
+ console.log("Pushing to Convex...");
202
+
203
+ const client = new ConvexHttpClient(convexUrl);
204
+ const importResult = await client.mutation(
205
+ "agentMemory/mutations:importFromLocal" as any,
206
+ {
207
+ projectId: opts.project,
208
+ userId: opts.user,
209
+ memories: result.memories,
210
+ },
211
+ );
212
+
213
+ console.log(
214
+ `Done: ${importResult.created} created, ${importResult.updated} updated, ${importResult.unchanged} unchanged`,
215
+ );
216
+ });
217
+
218
+ // ── mcp ─────────────────────────────────────────────────────────────
219
+
220
+ program
221
+ .command("mcp")
222
+ .description("Start the MCP server")
223
+ .option("--project <id>", "Project ID", "default")
224
+ .option("--read-only", "Disable write operations")
225
+ .option("--disable-tools <tools>", "Comma-separated list of tools to disable")
226
+ .option("--embedding-api-key <key>", "API key for vector search")
227
+ .action(async (opts) => {
228
+ const convexUrl = requireConvexUrl();
229
+ // Dynamic import to avoid loading MCP deps when not needed
230
+ const { startMcpServer } = await import("../mcp/server.js");
231
+ await startMcpServer({
232
+ convexUrl,
233
+ projectId: opts.project,
234
+ readOnly: opts.readOnly ?? false,
235
+ disabledTools: opts.disableTools
236
+ ? opts.disableTools.split(",")
237
+ : [],
238
+ embeddingApiKey: opts.embeddingApiKey,
239
+ });
240
+ });
241
+
242
+ // ── Helpers ─────────────────────────────────────────────────────────
243
+
244
+ function requireConvexUrl(): string {
245
+ const url = process.env.CONVEX_URL;
246
+ if (!url) {
247
+ console.error("Error: CONVEX_URL environment variable is required.");
248
+ console.error("Set it to your Convex deployment URL.");
249
+ process.exit(1);
250
+ }
251
+ return url;
252
+ }
253
+
254
+ program.parse();
@@ -0,0 +1,80 @@
1
+ import { readdir, readFile, access } from "node:fs/promises";
2
+ import { join, basename } from "node:path";
3
+ import matter from "gray-matter";
4
+ import type { Parser, ParsedMemory } from "./types.js";
5
+ import { computeChecksum } from "../../component/checksum.js";
6
+
7
+ export const claudeCodeParser: Parser = {
8
+ name: "Claude Code",
9
+
10
+ async detect(dir: string): Promise<boolean> {
11
+ try {
12
+ await access(join(dir, ".claude"));
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ },
18
+
19
+ async parse(dir: string): Promise<ParsedMemory[]> {
20
+ const memories: ParsedMemory[] = [];
21
+
22
+ // Parse .claude/rules/*.md
23
+ const rulesDir = join(dir, ".claude", "rules");
24
+ const ruleFiles = await safeReaddir(rulesDir);
25
+ for (const file of ruleFiles) {
26
+ if (!file.endsWith(".md")) continue;
27
+ const fullPath = join(rulesDir, file);
28
+ const raw = await readFile(fullPath, "utf-8");
29
+ const { data, content } = matter(raw);
30
+
31
+ memories.push({
32
+ title: basename(file, ".md"),
33
+ content: content.trim(),
34
+ memoryType: "instruction",
35
+ scope: "project",
36
+ tags: [],
37
+ paths: data.paths as string[] | undefined,
38
+ priority: 0.9, // rules are high priority
39
+ source: "claude-code",
40
+ checksum: computeChecksum(content.trim()),
41
+ });
42
+ }
43
+
44
+ // Parse memory files from ~/.claude/projects/*/memory/*.md
45
+ const homeDir = process.env.HOME ?? "";
46
+ const projectsDir = join(homeDir, ".claude", "projects");
47
+ const projectDirs = await safeReaddir(projectsDir);
48
+ for (const projDir of projectDirs) {
49
+ const memDir = join(projectsDir, projDir, "memory");
50
+ const memFiles = await safeReaddir(memDir);
51
+ for (const file of memFiles) {
52
+ if (!file.endsWith(".md")) continue;
53
+ const fullPath = join(memDir, file);
54
+ const raw = await readFile(fullPath, "utf-8");
55
+ const { data, content } = matter(raw);
56
+
57
+ memories.push({
58
+ title: basename(file, ".md"),
59
+ content: content.trim(),
60
+ memoryType: (data.type as string) === "feedback" ? "feedback" : "learning",
61
+ scope: "user",
62
+ tags: [],
63
+ priority: 0.5,
64
+ source: "claude-code",
65
+ checksum: computeChecksum(content.trim()),
66
+ });
67
+ }
68
+ }
69
+
70
+ return memories;
71
+ },
72
+ };
73
+
74
+ async function safeReaddir(dir: string): Promise<string[]> {
75
+ try {
76
+ return await readdir(dir);
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
@@ -0,0 +1,45 @@
1
+ import { readFile, access } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { Parser, ParsedMemory } from "./types.js";
4
+ import { computeChecksum } from "../../component/checksum.js";
5
+
6
+ export const codexParser: Parser = {
7
+ name: "Codex",
8
+
9
+ async detect(dir: string): Promise<boolean> {
10
+ try {
11
+ await access(join(dir, "AGENTS.md"));
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ },
17
+
18
+ async parse(dir: string): Promise<ParsedMemory[]> {
19
+ const memories: ParsedMemory[] = [];
20
+
21
+ // Check AGENTS.override.md first (takes priority)
22
+ for (const filename of ["AGENTS.override.md", "AGENTS.md"]) {
23
+ try {
24
+ const raw = await readFile(join(dir, filename), "utf-8");
25
+ const content = raw.trim();
26
+ if (content.length === 0) continue;
27
+
28
+ memories.push({
29
+ title: filename.replace(".md", "").toLowerCase(),
30
+ content,
31
+ memoryType: "instruction",
32
+ scope: "project",
33
+ tags: [],
34
+ priority: filename.includes("override") ? 1.0 : 0.8,
35
+ source: "codex",
36
+ checksum: computeChecksum(content),
37
+ });
38
+ } catch {
39
+ // File doesn't exist
40
+ }
41
+ }
42
+
43
+ return memories;
44
+ },
45
+ };
@@ -0,0 +1,47 @@
1
+ import { readdir, readFile, access } from "node:fs/promises";
2
+ import { join, basename } from "node:path";
3
+ import type { Parser, ParsedMemory } from "./types.js";
4
+ import { computeChecksum } from "../../component/checksum.js";
5
+
6
+ export const conductorParser: Parser = {
7
+ name: "Conductor",
8
+
9
+ async detect(dir: string): Promise<boolean> {
10
+ try {
11
+ await access(join(dir, ".conductor"));
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ },
17
+
18
+ async parse(dir: string): Promise<ParsedMemory[]> {
19
+ const memories: ParsedMemory[] = [];
20
+ const rulesDir = join(dir, ".conductor", "rules");
21
+
22
+ let files: string[] = [];
23
+ try {
24
+ files = await readdir(rulesDir);
25
+ } catch {
26
+ return memories;
27
+ }
28
+
29
+ for (const file of files) {
30
+ if (!file.endsWith(".md")) continue;
31
+ const content = (await readFile(join(rulesDir, file), "utf-8")).trim();
32
+
33
+ memories.push({
34
+ title: basename(file, ".md"),
35
+ content,
36
+ memoryType: "instruction",
37
+ scope: "project",
38
+ tags: [],
39
+ priority: 0.8,
40
+ source: "conductor",
41
+ checksum: computeChecksum(content),
42
+ });
43
+ }
44
+
45
+ return memories;
46
+ },
47
+ };
@@ -0,0 +1,55 @@
1
+ import { readdir, readFile, access } from "node:fs/promises";
2
+ import { join, basename } from "node:path";
3
+ import matter from "gray-matter";
4
+ import type { Parser, ParsedMemory } from "./types.js";
5
+ import { computeChecksum } from "../../component/checksum.js";
6
+
7
+ export const cursorParser: Parser = {
8
+ name: "Cursor",
9
+
10
+ async detect(dir: string): Promise<boolean> {
11
+ try {
12
+ await access(join(dir, ".cursor", "rules"));
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ },
18
+
19
+ async parse(dir: string): Promise<ParsedMemory[]> {
20
+ const memories: ParsedMemory[] = [];
21
+ const rulesDir = join(dir, ".cursor", "rules");
22
+ const files = await safeReaddir(rulesDir);
23
+
24
+ for (const file of files) {
25
+ if (!file.endsWith(".mdc") && !file.endsWith(".md")) continue;
26
+ const fullPath = join(rulesDir, file);
27
+ const raw = await readFile(fullPath, "utf-8");
28
+ const { data, content } = matter(raw);
29
+
30
+ const alwaysApply = data.alwaysApply === true;
31
+
32
+ memories.push({
33
+ title: basename(file, file.endsWith(".mdc") ? ".mdc" : ".md"),
34
+ content: content.trim(),
35
+ memoryType: "instruction",
36
+ scope: "project",
37
+ tags: [],
38
+ paths: data.paths as string[] | undefined,
39
+ priority: alwaysApply ? 0.9 : 0.5,
40
+ source: "cursor",
41
+ checksum: computeChecksum(content.trim()),
42
+ });
43
+ }
44
+
45
+ return memories;
46
+ },
47
+ };
48
+
49
+ async function safeReaddir(dir: string): Promise<string[]> {
50
+ try {
51
+ return await readdir(dir);
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
@@ -0,0 +1,30 @@
1
+ export type { Parser, ParsedMemory } from "./types.js";
2
+ export { claudeCodeParser } from "./claude-code.js";
3
+ export { cursorParser } from "./cursor.js";
4
+ export { openCodeParser } from "./opencode.js";
5
+ export { codexParser } from "./codex.js";
6
+ export { conductorParser } from "./conductor.js";
7
+ export { zedParser } from "./zed.js";
8
+ export { vscodeCopilotParser } from "./vscode-copilot.js";
9
+ export { piParser } from "./pi.js";
10
+
11
+ import type { Parser } from "./types.js";
12
+ import { claudeCodeParser } from "./claude-code.js";
13
+ import { cursorParser } from "./cursor.js";
14
+ import { openCodeParser } from "./opencode.js";
15
+ import { codexParser } from "./codex.js";
16
+ import { conductorParser } from "./conductor.js";
17
+ import { zedParser } from "./zed.js";
18
+ import { vscodeCopilotParser } from "./vscode-copilot.js";
19
+ import { piParser } from "./pi.js";
20
+
21
+ export const ALL_PARSERS: Record<string, Parser> = {
22
+ "claude-code": claudeCodeParser,
23
+ cursor: cursorParser,
24
+ opencode: openCodeParser,
25
+ codex: codexParser,
26
+ conductor: conductorParser,
27
+ zed: zedParser,
28
+ "vscode-copilot": vscodeCopilotParser,
29
+ pi: piParser,
30
+ };
@@ -0,0 +1,84 @@
1
+ import { readFile, access } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { Parser, ParsedMemory } from "./types.js";
4
+ import { computeChecksum } from "../../component/checksum.js";
5
+
6
+ export const openCodeParser: Parser = {
7
+ name: "OpenCode",
8
+
9
+ async detect(dir: string): Promise<boolean> {
10
+ try {
11
+ await access(join(dir, "AGENTS.md"));
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ },
17
+
18
+ async parse(dir: string): Promise<ParsedMemory[]> {
19
+ const memories: ParsedMemory[] = [];
20
+
21
+ // Parse AGENTS.md — split by ## headings into separate memories
22
+ try {
23
+ const raw = await readFile(join(dir, "AGENTS.md"), "utf-8");
24
+ const sections = splitByHeadings(raw);
25
+
26
+ for (const section of sections) {
27
+ memories.push({
28
+ title: section.title,
29
+ content: section.content.trim(),
30
+ memoryType: "instruction",
31
+ scope: "project",
32
+ tags: [],
33
+ priority: 0.8,
34
+ source: "opencode",
35
+ checksum: computeChecksum(section.content.trim()),
36
+ });
37
+ }
38
+ } catch {
39
+ // AGENTS.md doesn't exist
40
+ }
41
+
42
+ return memories;
43
+ },
44
+ };
45
+
46
+ interface Section {
47
+ title: string;
48
+ content: string;
49
+ }
50
+
51
+ function splitByHeadings(markdown: string): Section[] {
52
+ const lines = markdown.split("\n");
53
+ const sections: Section[] = [];
54
+ let currentTitle = "agents-md";
55
+ let currentContent: string[] = [];
56
+
57
+ for (const line of lines) {
58
+ const match = line.match(/^##\s+(.+)$/);
59
+ if (match) {
60
+ if (currentContent.length > 0) {
61
+ sections.push({
62
+ title: currentTitle,
63
+ content: currentContent.join("\n"),
64
+ });
65
+ }
66
+ currentTitle = match[1]
67
+ .toLowerCase()
68
+ .replace(/[^a-z0-9-]/g, "-")
69
+ .replace(/-+/g, "-");
70
+ currentContent = [];
71
+ } else {
72
+ currentContent.push(line);
73
+ }
74
+ }
75
+
76
+ if (currentContent.length > 0) {
77
+ sections.push({
78
+ title: currentTitle,
79
+ content: currentContent.join("\n"),
80
+ });
81
+ }
82
+
83
+ return sections;
84
+ }