@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,684 @@
1
+ import { query, internalQuery } from "./_generated/server.js";
2
+ import { v } from "convex/values";
3
+ import {
4
+ memoryTypeValidator,
5
+ scopeValidator,
6
+ historyEventValidator,
7
+ feedbackSentimentValidator,
8
+ } from "./schema.js";
9
+ import { formatMemoryForTool } from "./format.js";
10
+ import type { ToolFormat } from "./format.js";
11
+
12
+ // ── Shared return validators ────────────────────────────────────────
13
+
14
+ const memoryDocValidator = v.object({
15
+ _id: v.string(),
16
+ _creationTime: v.float64(),
17
+ projectId: v.string(),
18
+ scope: scopeValidator,
19
+ userId: v.optional(v.string()),
20
+ agentId: v.optional(v.string()),
21
+ sessionId: v.optional(v.string()),
22
+ title: v.string(),
23
+ content: v.string(),
24
+ memoryType: memoryTypeValidator,
25
+ tags: v.array(v.string()),
26
+ paths: v.optional(v.array(v.string())),
27
+ priority: v.optional(v.float64()),
28
+ source: v.optional(v.string()),
29
+ lastSyncedAt: v.optional(v.float64()),
30
+ checksum: v.string(),
31
+ archived: v.boolean(),
32
+ embeddingId: v.optional(v.string()),
33
+ accessCount: v.optional(v.float64()),
34
+ lastAccessedAt: v.optional(v.float64()),
35
+ positiveCount: v.optional(v.float64()),
36
+ negativeCount: v.optional(v.float64()),
37
+ });
38
+
39
+ const memorySummaryValidator = v.object({
40
+ _id: v.string(),
41
+ title: v.string(),
42
+ memoryType: memoryTypeValidator,
43
+ priority: v.float64(),
44
+ });
45
+
46
+ const historyEntryValidator = v.object({
47
+ _id: v.string(),
48
+ _creationTime: v.float64(),
49
+ memoryId: v.string(),
50
+ projectId: v.string(),
51
+ previousContent: v.optional(v.string()),
52
+ newContent: v.optional(v.string()),
53
+ previousTitle: v.optional(v.string()),
54
+ newTitle: v.optional(v.string()),
55
+ event: historyEventValidator,
56
+ actor: v.string(),
57
+ timestamp: v.float64(),
58
+ });
59
+
60
+ const feedbackEntryValidator = v.object({
61
+ _id: v.string(),
62
+ _creationTime: v.float64(),
63
+ memoryId: v.string(),
64
+ projectId: v.string(),
65
+ sentiment: feedbackSentimentValidator,
66
+ comment: v.optional(v.string()),
67
+ actor: v.string(),
68
+ timestamp: v.float64(),
69
+ });
70
+
71
+ const relationValidator = v.object({
72
+ _id: v.string(),
73
+ _creationTime: v.float64(),
74
+ projectId: v.string(),
75
+ fromMemoryId: v.string(),
76
+ toMemoryId: v.string(),
77
+ relationship: v.string(),
78
+ metadata: v.optional(v.object({
79
+ confidence: v.optional(v.float64()),
80
+ createdBy: v.optional(v.string()),
81
+ })),
82
+ timestamp: v.float64(),
83
+ });
84
+
85
+ const toolFormatValidator = v.union(
86
+ v.literal("claude-code"),
87
+ v.literal("cursor"),
88
+ v.literal("opencode"),
89
+ v.literal("codex"),
90
+ v.literal("conductor"),
91
+ v.literal("zed"),
92
+ v.literal("vscode-copilot"),
93
+ v.literal("pi"),
94
+ v.literal("raw"),
95
+ );
96
+
97
+ // ── Helper: map doc to return type ──────────────────────────────────
98
+
99
+ function mapDoc(m: any) {
100
+ return {
101
+ ...m,
102
+ _id: m._id as unknown as string,
103
+ embeddingId: m.embeddingId
104
+ ? (m.embeddingId as unknown as string)
105
+ : undefined,
106
+ };
107
+ }
108
+
109
+ // ── list ────────────────────────────────────────────────────────────
110
+
111
+ export const list = query({
112
+ args: {
113
+ projectId: v.string(),
114
+ scope: v.optional(scopeValidator),
115
+ userId: v.optional(v.string()),
116
+ agentId: v.optional(v.string()),
117
+ sessionId: v.optional(v.string()),
118
+ memoryType: v.optional(memoryTypeValidator),
119
+ source: v.optional(v.string()),
120
+ archived: v.optional(v.boolean()),
121
+ minPriority: v.optional(v.float64()),
122
+ tags: v.optional(v.array(v.string())),
123
+ createdAfter: v.optional(v.float64()),
124
+ createdBefore: v.optional(v.float64()),
125
+ limit: v.optional(v.float64()),
126
+ },
127
+ returns: v.array(memoryDocValidator),
128
+ handler: async (ctx, args) => {
129
+ const limit = args.limit ?? 100;
130
+ const archived = args.archived ?? false;
131
+
132
+ // Select index based on available filters
133
+ let results;
134
+ if (args.agentId) {
135
+ results = await ctx.db
136
+ .query("memories")
137
+ .withIndex("by_agent", (q: any) =>
138
+ q
139
+ .eq("projectId", args.projectId)
140
+ .eq("agentId", args.agentId!)
141
+ .eq("archived", archived),
142
+ )
143
+ .take(limit);
144
+ } else if (args.sessionId) {
145
+ results = await ctx.db
146
+ .query("memories")
147
+ .withIndex("by_session", (q: any) =>
148
+ q
149
+ .eq("projectId", args.projectId)
150
+ .eq("sessionId", args.sessionId!)
151
+ .eq("archived", archived),
152
+ )
153
+ .take(limit);
154
+ } else if (args.source) {
155
+ results = await ctx.db
156
+ .query("memories")
157
+ .withIndex("by_source", (q: any) =>
158
+ q
159
+ .eq("projectId", args.projectId)
160
+ .eq("source", args.source!)
161
+ .eq("archived", archived),
162
+ )
163
+ .take(limit);
164
+ } else if (args.scope && args.userId) {
165
+ results = await ctx.db
166
+ .query("memories")
167
+ .withIndex("by_project_scope", (q: any) =>
168
+ q
169
+ .eq("projectId", args.projectId)
170
+ .eq("scope", args.scope!)
171
+ .eq("userId", args.userId!)
172
+ .eq("archived", archived),
173
+ )
174
+ .take(limit);
175
+ } else if (args.memoryType) {
176
+ results = await ctx.db
177
+ .query("memories")
178
+ .withIndex("by_project", (q: any) =>
179
+ q
180
+ .eq("projectId", args.projectId)
181
+ .eq("archived", archived)
182
+ .eq("memoryType", args.memoryType!),
183
+ )
184
+ .take(limit);
185
+ } else {
186
+ results = await ctx.db
187
+ .query("memories")
188
+ .withIndex("by_project", (q: any) =>
189
+ q.eq("projectId", args.projectId).eq("archived", archived),
190
+ )
191
+ .take(limit);
192
+ }
193
+
194
+ // Post-filters
195
+ let filtered = results;
196
+ if (args.minPriority !== undefined) {
197
+ filtered = filtered.filter(
198
+ (m) => (m.priority ?? 0) >= args.minPriority!,
199
+ );
200
+ }
201
+ if (args.tags && args.tags.length > 0) {
202
+ filtered = filtered.filter((m) =>
203
+ args.tags!.some((tag) => m.tags.includes(tag)),
204
+ );
205
+ }
206
+ if (args.createdAfter !== undefined) {
207
+ filtered = filtered.filter(
208
+ (m) => m._creationTime > args.createdAfter!,
209
+ );
210
+ }
211
+ if (args.createdBefore !== undefined) {
212
+ filtered = filtered.filter(
213
+ (m) => m._creationTime < args.createdBefore!,
214
+ );
215
+ }
216
+
217
+ return filtered.map(mapDoc);
218
+ },
219
+ });
220
+
221
+ // ── get ─────────────────────────────────────────────────────────────
222
+
223
+ export const get = query({
224
+ args: {
225
+ memoryId: v.string(),
226
+ },
227
+ returns: v.union(memoryDocValidator, v.null()),
228
+ handler: async (ctx, args) => {
229
+ const id = ctx.db.normalizeId("memories", args.memoryId);
230
+ if (!id) return null;
231
+
232
+ const doc = await ctx.db.get(id);
233
+ if (!doc) return null;
234
+
235
+ return mapDoc(doc);
236
+ },
237
+ });
238
+
239
+ // ── search (full-text) ──────────────────────────────────────────────
240
+
241
+ export const search = query({
242
+ args: {
243
+ projectId: v.string(),
244
+ query: v.string(),
245
+ memoryType: v.optional(memoryTypeValidator),
246
+ scope: v.optional(scopeValidator),
247
+ limit: v.optional(v.float64()),
248
+ },
249
+ returns: v.array(memoryDocValidator),
250
+ handler: async (ctx, args) => {
251
+ const limit = args.limit ?? 20;
252
+
253
+ const results = await ctx.db
254
+ .query("memories")
255
+ .withSearchIndex("search_content", (q) => {
256
+ let sq = q
257
+ .search("content", args.query)
258
+ .eq("projectId", args.projectId)
259
+ .eq("archived", false);
260
+ if (args.memoryType) {
261
+ sq = sq.eq("memoryType", args.memoryType);
262
+ }
263
+ if (args.scope) {
264
+ sq = sq.eq("scope", args.scope);
265
+ }
266
+ return sq;
267
+ })
268
+ .take(limit);
269
+
270
+ return results.map(mapDoc);
271
+ },
272
+ });
273
+
274
+ // ── getContextBundle (progressive disclosure) ───────────────────────
275
+
276
+ export const getContextBundle = query({
277
+ args: {
278
+ projectId: v.string(),
279
+ scope: scopeValidator,
280
+ userId: v.optional(v.string()),
281
+ agentId: v.optional(v.string()),
282
+ activePaths: v.optional(v.array(v.string())),
283
+ maxTokens: v.optional(v.float64()),
284
+ },
285
+ returns: v.object({
286
+ pinned: v.array(memoryDocValidator),
287
+ relevant: v.array(memoryDocValidator),
288
+ available: v.array(memorySummaryValidator),
289
+ }),
290
+ handler: async (ctx, args) => {
291
+ // Get all non-archived memories for this project+scope
292
+ const all = await ctx.db
293
+ .query("memories")
294
+ .withIndex("by_project_scope", (q: any) => {
295
+ let sq = q
296
+ .eq("projectId", args.projectId)
297
+ .eq("scope", args.scope);
298
+ if (args.userId) {
299
+ sq = sq.eq("userId", args.userId);
300
+ }
301
+ return sq.eq("archived", false);
302
+ })
303
+ .take(500);
304
+
305
+ // Tier 1: Pinned — priority >= 0.8, boosted by positive feedback
306
+ const pinned = all
307
+ .filter((m) => {
308
+ const effectivePriority = getEffectivePriority(m);
309
+ return effectivePriority >= 0.8;
310
+ })
311
+ .map(mapDoc);
312
+
313
+ // Tier 2: Relevant — path-matched against activePaths
314
+ const relevant = all
315
+ .filter((m) => {
316
+ if (getEffectivePriority(m) >= 0.8) return false;
317
+ if (!args.activePaths || args.activePaths.length === 0) return false;
318
+ if (!m.paths || m.paths.length === 0) return false;
319
+ return m.paths.some((pattern: string) =>
320
+ args.activePaths!.some((active: string) => matchGlob(pattern, active)),
321
+ );
322
+ })
323
+ .map(mapDoc);
324
+
325
+ // Tier 3: Available — everything else as summaries, sorted by effective priority
326
+ const pinnedIds = new Set(pinned.map((m) => m._id));
327
+ const relevantIds = new Set(relevant.map((m) => m._id));
328
+ const available = all
329
+ .filter((m) => {
330
+ const id = m._id as unknown as string;
331
+ return !pinnedIds.has(id) && !relevantIds.has(id);
332
+ })
333
+ .map((m) => ({
334
+ _id: m._id as unknown as string,
335
+ title: m.title,
336
+ memoryType: m.memoryType,
337
+ priority: getEffectivePriority(m),
338
+ }))
339
+ .sort((a, b) => b.priority - a.priority);
340
+
341
+ return { pinned, relevant, available };
342
+ },
343
+ });
344
+
345
+ // ── history ─────────────────────────────────────────────────────────
346
+
347
+ export const history = query({
348
+ args: {
349
+ memoryId: v.string(),
350
+ limit: v.optional(v.float64()),
351
+ },
352
+ returns: v.array(historyEntryValidator),
353
+ handler: async (ctx, args) => {
354
+ const id = ctx.db.normalizeId("memories", args.memoryId);
355
+ if (!id) return [];
356
+
357
+ const entries = await ctx.db
358
+ .query("memoryHistory")
359
+ .withIndex("by_memory", (q: any) => q.eq("memoryId", id))
360
+ .order("desc")
361
+ .take(args.limit ?? 50);
362
+
363
+ return entries.map((e) => ({
364
+ ...e,
365
+ _id: e._id as unknown as string,
366
+ memoryId: e.memoryId as unknown as string,
367
+ }));
368
+ },
369
+ });
370
+
371
+ // ── projectHistory ──────────────────────────────────────────────────
372
+
373
+ export const projectHistory = query({
374
+ args: {
375
+ projectId: v.string(),
376
+ limit: v.optional(v.float64()),
377
+ },
378
+ returns: v.array(historyEntryValidator),
379
+ handler: async (ctx, args) => {
380
+ const entries = await ctx.db
381
+ .query("memoryHistory")
382
+ .withIndex("by_project", (q: any) =>
383
+ q.eq("projectId", args.projectId),
384
+ )
385
+ .order("desc")
386
+ .take(args.limit ?? 100);
387
+
388
+ return entries.map((e) => ({
389
+ ...e,
390
+ _id: e._id as unknown as string,
391
+ memoryId: e.memoryId as unknown as string,
392
+ }));
393
+ },
394
+ });
395
+
396
+ // ── getFeedback ─────────────────────────────────────────────────────
397
+
398
+ export const getFeedback = query({
399
+ args: {
400
+ memoryId: v.string(),
401
+ limit: v.optional(v.float64()),
402
+ },
403
+ returns: v.array(feedbackEntryValidator),
404
+ handler: async (ctx, args) => {
405
+ const id = ctx.db.normalizeId("memories", args.memoryId);
406
+ if (!id) return [];
407
+
408
+ const entries = await ctx.db
409
+ .query("memoryFeedback")
410
+ .withIndex("by_memory", (q: any) => q.eq("memoryId", id))
411
+ .order("desc")
412
+ .take(args.limit ?? 50);
413
+
414
+ return entries.map((e) => ({
415
+ ...e,
416
+ _id: e._id as unknown as string,
417
+ memoryId: e.memoryId as unknown as string,
418
+ }));
419
+ },
420
+ });
421
+
422
+ // ── getRelations ────────────────────────────────────────────────────
423
+
424
+ export const getRelations = query({
425
+ args: {
426
+ memoryId: v.string(),
427
+ direction: v.optional(v.union(v.literal("from"), v.literal("to"), v.literal("both"))),
428
+ relationship: v.optional(v.string()),
429
+ limit: v.optional(v.float64()),
430
+ },
431
+ returns: v.array(relationValidator),
432
+ handler: async (ctx, args) => {
433
+ const id = ctx.db.normalizeId("memories", args.memoryId);
434
+ if (!id) return [];
435
+
436
+ const direction = args.direction ?? "both";
437
+ const limit = args.limit ?? 50;
438
+ const results: any[] = [];
439
+
440
+ if (direction === "from" || direction === "both") {
441
+ let fromQuery = ctx.db
442
+ .query("memoryRelations")
443
+ .withIndex("by_from", (q: any) => {
444
+ let sq = q.eq("fromMemoryId", id);
445
+ if (args.relationship) sq = sq.eq("relationship", args.relationship);
446
+ return sq;
447
+ });
448
+ const fromResults = await fromQuery.take(limit);
449
+ results.push(...fromResults);
450
+ }
451
+
452
+ if (direction === "to" || direction === "both") {
453
+ let toQuery = ctx.db
454
+ .query("memoryRelations")
455
+ .withIndex("by_to", (q: any) => {
456
+ let sq = q.eq("toMemoryId", id);
457
+ if (args.relationship) sq = sq.eq("relationship", args.relationship);
458
+ return sq;
459
+ });
460
+ const toResults = await toQuery.take(limit);
461
+ results.push(...toResults);
462
+ }
463
+
464
+ // Deduplicate
465
+ const seen = new Set<string>();
466
+ return results
467
+ .filter((r) => {
468
+ const rid = r._id as unknown as string;
469
+ if (seen.has(rid)) return false;
470
+ seen.add(rid);
471
+ return true;
472
+ })
473
+ .map((r) => ({
474
+ ...r,
475
+ _id: r._id as unknown as string,
476
+ fromMemoryId: r.fromMemoryId as unknown as string,
477
+ toMemoryId: r.toMemoryId as unknown as string,
478
+ }));
479
+ },
480
+ });
481
+
482
+ // ── exportForTool ───────────────────────────────────────────────────
483
+
484
+ export const exportForTool = query({
485
+ args: {
486
+ projectId: v.string(),
487
+ format: toolFormatValidator,
488
+ scope: v.optional(scopeValidator),
489
+ userId: v.optional(v.string()),
490
+ since: v.optional(v.float64()),
491
+ },
492
+ returns: v.array(
493
+ v.object({
494
+ path: v.string(),
495
+ content: v.string(),
496
+ checksum: v.string(),
497
+ }),
498
+ ),
499
+ handler: async (ctx, args) => {
500
+ let memories = await ctx.db
501
+ .query("memories")
502
+ .withIndex("by_project", (q: any) =>
503
+ q.eq("projectId", args.projectId).eq("archived", false),
504
+ )
505
+ .take(500);
506
+
507
+ if (args.scope) {
508
+ memories = memories.filter((m) => m.scope === args.scope);
509
+ }
510
+ if (args.userId) {
511
+ memories = memories.filter(
512
+ (m) => m.userId === args.userId || m.scope !== "user",
513
+ );
514
+ }
515
+ if (args.since) {
516
+ memories = memories.filter(
517
+ (m) => m._creationTime > args.since! || (m.lastSyncedAt ?? 0) > args.since!,
518
+ );
519
+ }
520
+
521
+ const format = args.format as ToolFormat;
522
+ const projectSlug = args.projectId
523
+ .replace(/[^a-z0-9-]/gi, "-")
524
+ .toLowerCase();
525
+
526
+ return memories.map((m) => formatMemoryForTool(m, format, projectSlug));
527
+ },
528
+ });
529
+
530
+ // ── Internal queries (used by actions) ──────────────────────────────
531
+
532
+ export const getEmbeddingMemory = internalQuery({
533
+ args: {
534
+ embeddingId: v.string(),
535
+ },
536
+ returns: v.union(memoryDocValidator, v.null()),
537
+ handler: async (ctx, args) => {
538
+ const embId = ctx.db.normalizeId("embeddings", args.embeddingId);
539
+ if (!embId) return null;
540
+
541
+ const embedding = await ctx.db.get(embId);
542
+ if (!embedding) return null;
543
+
544
+ const memory = await ctx.db.get(embedding.memoryId);
545
+ if (!memory) return null;
546
+
547
+ return mapDoc(memory);
548
+ },
549
+ });
550
+
551
+ export const listUnembedded = internalQuery({
552
+ args: {
553
+ projectId: v.string(),
554
+ },
555
+ returns: v.array(
556
+ v.object({
557
+ _id: v.string(),
558
+ title: v.string(),
559
+ }),
560
+ ),
561
+ handler: async (ctx, args) => {
562
+ const memories = await ctx.db
563
+ .query("memories")
564
+ .withIndex("by_project", (q: any) =>
565
+ q.eq("projectId", args.projectId).eq("archived", false),
566
+ )
567
+ .take(500);
568
+
569
+ return memories
570
+ .filter((m) => !m.embeddingId)
571
+ .map((m) => ({
572
+ _id: m._id as unknown as string,
573
+ title: m.title,
574
+ }));
575
+ },
576
+ });
577
+
578
+ // Internal query for ingest pipeline — returns existing memories for dedup
579
+ export const listForIngest = internalQuery({
580
+ args: {
581
+ projectId: v.string(),
582
+ limit: v.optional(v.float64()),
583
+ },
584
+ returns: v.array(
585
+ v.object({
586
+ _id: v.string(),
587
+ title: v.string(),
588
+ content: v.string(),
589
+ memoryType: memoryTypeValidator,
590
+ }),
591
+ ),
592
+ handler: async (ctx, args) => {
593
+ const memories = await ctx.db
594
+ .query("memories")
595
+ .withIndex("by_project", (q: any) =>
596
+ q.eq("projectId", args.projectId).eq("archived", false),
597
+ )
598
+ .take(args.limit ?? 200);
599
+
600
+ return memories.map((m) => ({
601
+ _id: m._id as unknown as string,
602
+ title: m.title,
603
+ content: m.content,
604
+ memoryType: m.memoryType,
605
+ }));
606
+ },
607
+ });
608
+
609
+ // Internal query to get project settings (used by cron/actions)
610
+ export const getProjectSettings = internalQuery({
611
+ args: {
612
+ projectId: v.string(),
613
+ },
614
+ returns: v.union(
615
+ v.object({
616
+ projectId: v.string(),
617
+ name: v.string(),
618
+ description: v.optional(v.string()),
619
+ settings: v.object({
620
+ autoSync: v.boolean(),
621
+ syncFormats: v.array(v.string()),
622
+ embeddingModel: v.optional(v.string()),
623
+ embeddingDimensions: v.optional(v.float64()),
624
+ factExtractionPrompt: v.optional(v.string()),
625
+ updateDecisionPrompt: v.optional(v.string()),
626
+ decayEnabled: v.optional(v.boolean()),
627
+ decayHalfLifeDays: v.optional(v.float64()),
628
+ }),
629
+ }),
630
+ v.null(),
631
+ ),
632
+ handler: async (ctx, args) => {
633
+ const project = await ctx.db
634
+ .query("projects")
635
+ .withIndex("by_projectId", (q: any) =>
636
+ q.eq("projectId", args.projectId),
637
+ )
638
+ .first();
639
+
640
+ if (!project) return null;
641
+
642
+ return {
643
+ projectId: project.projectId,
644
+ name: project.name,
645
+ description: project.description,
646
+ settings: project.settings,
647
+ };
648
+ },
649
+ });
650
+
651
+ // ── Helpers ─────────────────────────────────────────────────────────
652
+
653
+ /**
654
+ * Simple glob matching: supports * and ** patterns.
655
+ */
656
+ function matchGlob(pattern: string, path: string): boolean {
657
+ const regex = pattern
658
+ .replace(/\*\*/g, "{{GLOBSTAR}}")
659
+ .replace(/\*/g, "[^/]*")
660
+ .replace(/{{GLOBSTAR}}/g, ".*")
661
+ .replace(/\//g, "\\/");
662
+ return new RegExp(`^${regex}$`).test(path);
663
+ }
664
+
665
+ /**
666
+ * Calculate effective priority considering feedback signals.
667
+ * Positive feedback boosts priority; negative feedback reduces it.
668
+ */
669
+ function getEffectivePriority(m: {
670
+ priority?: number;
671
+ positiveCount?: number;
672
+ negativeCount?: number;
673
+ }): number {
674
+ const base = m.priority ?? 0;
675
+ const positive = m.positiveCount ?? 0;
676
+ const negative = m.negativeCount ?? 0;
677
+
678
+ if (positive === 0 && negative === 0) return base;
679
+
680
+ // Feedback adjustment: each positive adds up to +0.05, each negative subtracts up to -0.1
681
+ const boost = Math.min(positive * 0.05, 0.2);
682
+ const penalty = Math.min(negative * 0.1, 0.5);
683
+ return Math.max(0, Math.min(1, base + boost - penalty));
684
+ }