icopilot 2.2.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 (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,412 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ const DEFAULT_PRUNE_AGE_DAYS = 28;
6
+ const DEFAULT_PROMPT_LIMIT = 12;
7
+ const MAX_FACT_LENGTH = 180;
8
+ export class AutoMemory {
9
+ storePath;
10
+ memories = [];
11
+ constructor(storePath = resolveAutoMemoryPath()) {
12
+ this.storePath = storePath;
13
+ }
14
+ addMemory(fact, source) {
15
+ const normalizedFact = normalizeFact(fact);
16
+ const normalizedSource = normalizeSource(source);
17
+ if (!normalizedFact || !normalizedSource)
18
+ return null;
19
+ const now = new Date();
20
+ const existing = this.memories.find((entry) => entry.fact.toLowerCase() === normalizedFact.toLowerCase());
21
+ if (existing) {
22
+ existing.fact = normalizedFact;
23
+ existing.source = normalizedSource;
24
+ existing.confidence = Math.max(existing.confidence, defaultConfidence(normalizedSource));
25
+ existing.lastUsedAt = now;
26
+ existing.usageCount += 1;
27
+ return cloneMemory(existing);
28
+ }
29
+ const created = {
30
+ id: randomUUID(),
31
+ fact: normalizedFact,
32
+ source: normalizedSource,
33
+ confidence: defaultConfidence(normalizedSource),
34
+ createdAt: now,
35
+ lastUsedAt: now,
36
+ usageCount: 1,
37
+ };
38
+ this.memories.push(created);
39
+ return cloneMemory(created);
40
+ }
41
+ getRelevantMemories(context, limit = 10) {
42
+ this.prune();
43
+ const normalizedLimit = normalizeLimit(limit);
44
+ const ranked = rankMemories(this.memories, context).slice(0, normalizedLimit);
45
+ if (ranked.length === 0)
46
+ return [];
47
+ const now = new Date();
48
+ const selectedIds = new Set(ranked.map((entry) => entry.id));
49
+ for (const memory of this.memories) {
50
+ if (!selectedIds.has(memory.id))
51
+ continue;
52
+ memory.lastUsedAt = now;
53
+ memory.usageCount += 1;
54
+ }
55
+ return ranked.map(cloneMemory);
56
+ }
57
+ toPromptContext() {
58
+ return formatMemoriesAsPrompt(topMemories(this.memories, DEFAULT_PROMPT_LIMIT));
59
+ }
60
+ prune(maxAge = DEFAULT_PRUNE_AGE_DAYS) {
61
+ const maxAgeDays = Number.isFinite(maxAge)
62
+ ? Math.max(0, Math.trunc(maxAge))
63
+ : DEFAULT_PRUNE_AGE_DAYS;
64
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
65
+ this.memories = this.memories.filter((entry) => {
66
+ const freshest = Math.max(entry.createdAt.getTime(), entry.lastUsedAt.getTime());
67
+ return freshest >= cutoff;
68
+ });
69
+ }
70
+ forget(id) {
71
+ const normalizedId = id.trim();
72
+ const before = this.memories.length;
73
+ this.memories = this.memories.filter((entry) => entry.id !== normalizedId);
74
+ return this.memories.length !== before;
75
+ }
76
+ clear() {
77
+ this.memories = [];
78
+ }
79
+ save() {
80
+ fs.mkdirSync(path.dirname(this.storePath), { recursive: true });
81
+ const payload = this.memories.map((entry) => ({
82
+ ...entry,
83
+ createdAt: entry.createdAt.toISOString(),
84
+ lastUsedAt: entry.lastUsedAt.toISOString(),
85
+ }));
86
+ fs.writeFileSync(this.storePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
87
+ }
88
+ load() {
89
+ this.memories = [];
90
+ if (!fs.existsSync(this.storePath) || !fs.statSync(this.storePath).isFile())
91
+ return;
92
+ try {
93
+ const parsed = JSON.parse(fs.readFileSync(this.storePath, 'utf8'));
94
+ if (!Array.isArray(parsed))
95
+ return;
96
+ this.memories = parsed.flatMap((value) => {
97
+ const normalized = normalizeStoredMemory(value);
98
+ return normalized ? [normalized] : [];
99
+ });
100
+ this.prune();
101
+ }
102
+ catch {
103
+ this.memories = [];
104
+ }
105
+ }
106
+ }
107
+ export function extractMemories(userMessage, aiResponse) {
108
+ return detectMemoryCandidates(userMessage, aiResponse).map((candidate) => candidate.fact);
109
+ }
110
+ export function learnAutoMemories(userMessage, aiResponse) {
111
+ try {
112
+ const memory = new AutoMemory();
113
+ memory.load();
114
+ const learned = [];
115
+ for (const candidate of detectMemoryCandidates(userMessage, aiResponse)) {
116
+ const entry = memory.addMemory(candidate.fact, candidate.source);
117
+ if (entry)
118
+ learned.push(entry);
119
+ }
120
+ if (learned.length > 0) {
121
+ memory.prune();
122
+ memory.save();
123
+ }
124
+ return learned;
125
+ }
126
+ catch {
127
+ return [];
128
+ }
129
+ }
130
+ export function loadAutoMemoryPromptContext(context, limit = DEFAULT_PROMPT_LIMIT) {
131
+ try {
132
+ const memory = new AutoMemory();
133
+ memory.load();
134
+ const relevant = memory.getRelevantMemories(context, limit);
135
+ if (relevant.length === 0)
136
+ return null;
137
+ memory.save();
138
+ return formatMemoriesAsPrompt(relevant);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ export function resolveAutoMemoryPath() {
145
+ const configured = process.env.ICOPILOT_AUTO_MEMORY_PATH ||
146
+ path.join(os.homedir(), '.icopilot', 'auto-memory.json');
147
+ if (configured === '~')
148
+ return os.homedir();
149
+ if (/^~[\\/]/.test(configured))
150
+ return path.join(os.homedir(), configured.slice(2));
151
+ return path.resolve(configured);
152
+ }
153
+ function detectMemoryCandidates(userMessage, aiResponse) {
154
+ const candidates = [];
155
+ candidates.push(...extractCorrectionCandidates(userMessage));
156
+ candidates.push(...extractPreferenceCandidates(userMessage));
157
+ candidates.push(...extractDiscoveryCandidates(aiResponse));
158
+ return dedupeCandidates(candidates);
159
+ }
160
+ function extractCorrectionCandidates(message) {
161
+ const trimmed = message.trim();
162
+ if (!trimmed)
163
+ return [];
164
+ const candidates = [];
165
+ const useInsteadPattern = /\b(?:use|prefer)\s+(.+?)\s+instead of\s+(.+?)(?:[.!?]|$)/gi;
166
+ for (const match of trimmed.matchAll(useInsteadPattern)) {
167
+ const preferred = cleanupClause(match[1]);
168
+ const previous = cleanupClause(match[2]);
169
+ if (!preferred || !previous)
170
+ continue;
171
+ candidates.push({
172
+ fact: `Use ${preferred} instead of ${previous}.`,
173
+ source: 'correction',
174
+ });
175
+ }
176
+ const shouldBePattern = /\b(?:actually|instead|correction:?|it should be|the command is|it is)\s+(.+?)(?:[.!?]|$)/i;
177
+ const correctionMatch = trimmed.match(shouldBePattern);
178
+ if (correctionMatch?.[1]) {
179
+ const clause = cleanupClause(correctionMatch[1]);
180
+ if (clause) {
181
+ candidates.push({
182
+ fact: clause.endsWith('.') ? clause : `${clause}.`,
183
+ source: 'correction',
184
+ });
185
+ }
186
+ }
187
+ return candidates;
188
+ }
189
+ function extractPreferenceCandidates(message) {
190
+ const trimmed = message.trim();
191
+ if (!trimmed)
192
+ return [];
193
+ const patterns = [
194
+ { regex: /\bI prefer\s+(.+?)(?:[.!?]|$)/i, prefix: 'User preference: prefer ' },
195
+ { regex: /\balways use\s+(.+?)(?:[.!?]|$)/i, prefix: 'User preference: always use ' },
196
+ { regex: /\bplease use\s+(.+?)(?:[.!?]|$)/i, prefix: 'User preference: use ' },
197
+ ];
198
+ const candidates = [];
199
+ for (const { regex, prefix } of patterns) {
200
+ const match = trimmed.match(regex);
201
+ if (!match?.[1])
202
+ continue;
203
+ const clause = cleanupClause(match[1]);
204
+ if (!clause)
205
+ continue;
206
+ candidates.push({
207
+ fact: `${prefix}${clause}.`,
208
+ source: 'preference',
209
+ });
210
+ }
211
+ return candidates;
212
+ }
213
+ function extractDiscoveryCandidates(response) {
214
+ const text = response.trim();
215
+ if (!text)
216
+ return [];
217
+ const candidates = [];
218
+ const seen = new Set();
219
+ for (const rawCommand of text.matchAll(/`([^`\n]+)`/g)) {
220
+ const command = cleanupClause(rawCommand[1]);
221
+ if (!command)
222
+ continue;
223
+ const lower = command.toLowerCase();
224
+ const fact = classifyCommandMemory(command, lower);
225
+ if (!fact)
226
+ continue;
227
+ if (seen.has(fact))
228
+ continue;
229
+ seen.add(fact);
230
+ candidates.push({ fact, source: 'discovery' });
231
+ }
232
+ for (const sentence of splitSentences(text)) {
233
+ if (!/[A-Za-z0-9._-]+[\\/][A-Za-z0-9._/-]+/.test(sentence))
234
+ continue;
235
+ if (!/\b(?:lives?|located|under|contains?|entry point|root|folder|directory|structure)\b/i.test(sentence)) {
236
+ continue;
237
+ }
238
+ const fact = normalizeFact(sentence);
239
+ if (!fact || seen.has(fact))
240
+ continue;
241
+ seen.add(fact);
242
+ candidates.push({ fact, source: 'discovery' });
243
+ }
244
+ return candidates;
245
+ }
246
+ function classifyCommandMemory(command, lower) {
247
+ if (/\b(?:vitest|jest|mocha|ava|pytest|cargo test|go test|npm test|pnpm test|yarn test)\b/.test(lower)) {
248
+ return `Project test command: ${command}.`;
249
+ }
250
+ if (/\b(?:tsc|build|compile|webpack|vite build|mvn package|gradle build|cargo build)\b/.test(lower)) {
251
+ return `Project build command: ${command}.`;
252
+ }
253
+ if (/\b(?:eslint|lint|ruff|flake8|golangci-lint|clippy)\b/.test(lower)) {
254
+ return `Project lint command: ${command}.`;
255
+ }
256
+ return null;
257
+ }
258
+ function formatMemoriesAsPrompt(memories) {
259
+ if (memories.length === 0)
260
+ return '';
261
+ return [
262
+ 'Auto-learned project memories:',
263
+ ...memories.map((entry) => `- [${entry.source}, ${entry.confidence.toFixed(2)}] ${entry.fact}`),
264
+ ].join('\n');
265
+ }
266
+ function normalizeStoredMemory(value) {
267
+ if (!value || typeof value !== 'object' || Array.isArray(value))
268
+ return null;
269
+ const record = value;
270
+ const fact = typeof record.fact === 'string' ? normalizeFact(record.fact) : '';
271
+ const source = normalizeSource(typeof record.source === 'string' ? record.source : '');
272
+ const createdAt = coerceDate(record.createdAt);
273
+ const lastUsedAt = coerceDate(record.lastUsedAt);
274
+ if (!fact || !source || typeof record.id !== 'string' || !createdAt || !lastUsedAt)
275
+ return null;
276
+ const confidence = coerceConfidence(record.confidence, defaultConfidence(source));
277
+ const usageCount = typeof record.usageCount === 'number' && Number.isFinite(record.usageCount)
278
+ ? Math.max(1, Math.trunc(record.usageCount))
279
+ : 1;
280
+ return {
281
+ id: record.id,
282
+ fact,
283
+ source,
284
+ confidence,
285
+ createdAt,
286
+ lastUsedAt,
287
+ usageCount,
288
+ };
289
+ }
290
+ function rankMemories(memories, context) {
291
+ const normalizedContext = context.trim().toLowerCase();
292
+ if (!normalizedContext)
293
+ return topMemories(memories, DEFAULT_PROMPT_LIMIT);
294
+ return [...memories]
295
+ .map((entry) => ({ entry, score: scoreMemory(entry, normalizedContext) }))
296
+ .filter((candidate) => candidate.score > 0)
297
+ .sort((left, right) => {
298
+ if (right.score !== left.score)
299
+ return right.score - left.score;
300
+ return compareMemories(left.entry, right.entry);
301
+ })
302
+ .map((candidate) => candidate.entry);
303
+ }
304
+ function topMemories(memories, limit) {
305
+ return [...memories].sort(compareMemories).slice(0, normalizeLimit(limit));
306
+ }
307
+ function compareMemories(left, right) {
308
+ if (right.usageCount !== left.usageCount)
309
+ return right.usageCount - left.usageCount;
310
+ if (right.confidence !== left.confidence)
311
+ return right.confidence - left.confidence;
312
+ return right.lastUsedAt.getTime() - left.lastUsedAt.getTime();
313
+ }
314
+ function scoreMemory(entry, context) {
315
+ let score = 0;
316
+ const fact = entry.fact.toLowerCase();
317
+ const tokens = tokenize(context);
318
+ if (fact.includes(context) || context.includes(fact))
319
+ score += 8;
320
+ for (const token of tokens) {
321
+ if (token.length < 3)
322
+ continue;
323
+ if (fact.includes(token))
324
+ score += 2;
325
+ }
326
+ score += Math.min(entry.usageCount, 5);
327
+ score += entry.confidence;
328
+ return score;
329
+ }
330
+ function tokenize(value) {
331
+ return value
332
+ .toLowerCase()
333
+ .split(/[^a-z0-9]+/)
334
+ .filter(Boolean);
335
+ }
336
+ function cleanupClause(value) {
337
+ return value
338
+ .replace(/[`*_]/g, '')
339
+ .replace(/\s+/g, ' ')
340
+ .replace(/^[,:;\s-]+/, '')
341
+ .replace(/[,:;\s-]+$/, '')
342
+ .trim();
343
+ }
344
+ function normalizeFact(fact) {
345
+ const normalized = cleanupClause(fact).replace(/\s+/g, ' ');
346
+ if (!normalized)
347
+ return '';
348
+ const withPunctuation = /[.!?]$/.test(normalized) ? normalized : `${normalized}.`;
349
+ return withPunctuation.slice(0, MAX_FACT_LENGTH).trim();
350
+ }
351
+ function normalizeSource(source) {
352
+ if (source === 'correction' || source === 'discovery' || source === 'preference')
353
+ return source;
354
+ return null;
355
+ }
356
+ function defaultConfidence(source) {
357
+ switch (source) {
358
+ case 'correction':
359
+ return 0.95;
360
+ case 'preference':
361
+ return 0.9;
362
+ case 'discovery':
363
+ default:
364
+ return 0.75;
365
+ }
366
+ }
367
+ function coerceDate(value) {
368
+ if (value instanceof Date && !Number.isNaN(value.getTime()))
369
+ return value;
370
+ if (typeof value === 'string' || typeof value === 'number') {
371
+ const parsed = new Date(value);
372
+ if (!Number.isNaN(parsed.getTime()))
373
+ return parsed;
374
+ }
375
+ return null;
376
+ }
377
+ function coerceConfidence(value, fallback) {
378
+ if (typeof value !== 'number' || !Number.isFinite(value))
379
+ return fallback;
380
+ return Math.min(1, Math.max(0, value));
381
+ }
382
+ function normalizeLimit(limit) {
383
+ if (!Number.isFinite(limit))
384
+ return 10;
385
+ return Math.max(1, Math.trunc(limit));
386
+ }
387
+ function dedupeCandidates(candidates) {
388
+ const deduped = new Map();
389
+ for (const candidate of candidates) {
390
+ const fact = normalizeFact(candidate.fact);
391
+ if (!fact)
392
+ continue;
393
+ const key = fact.toLowerCase();
394
+ if (!deduped.has(key)) {
395
+ deduped.set(key, { fact, source: candidate.source });
396
+ }
397
+ }
398
+ return [...deduped.values()];
399
+ }
400
+ function splitSentences(text) {
401
+ return text
402
+ .split(/(?<=[.!?])\s+/)
403
+ .map((sentence) => sentence.trim())
404
+ .filter(Boolean);
405
+ }
406
+ function cloneMemory(entry) {
407
+ return {
408
+ ...entry,
409
+ createdAt: new Date(entry.createdAt),
410
+ lastUsedAt: new Date(entry.lastUsedAt),
411
+ };
412
+ }