tribunal-kit 7.0.0 → 8.0.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 (95) hide show
  1. package/.agent/config/claude.json +18 -0
  2. package/.agent/config/plugin.json +102 -0
  3. package/.agent/config/slash-commands.json +103 -0
  4. package/.agent/config/specialist-registry.json +415 -0
  5. package/.agent/config/system-prompt.md +191 -0
  6. package/.agent/history/architecture-graph.yaml +302 -3
  7. package/.agent/history/graph-cache.json +515 -45
  8. package/.agent/history/memory.db +0 -0
  9. package/.agent/history/snapshots/bin__adapter-install.js.json +13 -0
  10. package/.agent/history/snapshots/bin__global-store.js.json +15 -0
  11. package/.agent/history/snapshots/bin__mcp-server.js.json +19 -0
  12. package/.agent/history/snapshots/bin__proxy-server.js.json +20 -0
  13. package/.agent/history/snapshots/bin__spawn-agent.js.json +12 -0
  14. package/.agent/history/snapshots/bin__tk-proxy.js.json +21 -0
  15. package/.agent/history/snapshots/bin__tribunal-kit.js.json +9 -7
  16. package/.agent/history/snapshots/bin__wrapper.js.json +14 -0
  17. package/.agent/history/snapshots/eslint.config.js.json +1 -2
  18. package/.agent/history/snapshots/scripts__benchmark.js.json +14 -0
  19. package/.agent/history/snapshots/scripts__changelog.js.json +1 -2
  20. package/.agent/history/snapshots/scripts__fix-vbc.js.json +11 -0
  21. package/.agent/history/snapshots/scripts__stress_benchmark.js.json +15 -0
  22. package/.agent/history/snapshots/scripts__sync-version.js.json +1 -2
  23. package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -2
  24. package/.agent/history/snapshots/scripts__visual_audit.js.json +11 -0
  25. package/.agent/history/snapshots/test__integration__bridges.test.js.json +1 -2
  26. package/.agent/history/snapshots/test__integration__graceful_degradation.test.js.json +12 -0
  27. package/.agent/history/snapshots/test__integration__init.test.js.json +1 -2
  28. package/.agent/history/snapshots/test__integration__minimal_change_pipeline.test.js.json +13 -0
  29. package/.agent/history/snapshots/test__integration__parallel_tribunal.test.js.json +12 -0
  30. package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -2
  31. package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +1 -2
  32. package/.agent/history/snapshots/test__integration__sync_status.test.js.json +13 -0
  33. package/.agent/history/snapshots/test__integration__wave2.test.js.json +1 -2
  34. package/.agent/history/snapshots/test__integration__wrapper.test.js.json +13 -0
  35. package/.agent/history/snapshots/test__unit__align.test.js.json +10 -0
  36. package/.agent/history/snapshots/test__unit__args.test.js.json +3 -3
  37. package/.agent/history/snapshots/test__unit__audit_release.test.js.json +16 -0
  38. package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -2
  39. package/.agent/history/snapshots/test__unit__cicd_validator.test.js.json +12 -0
  40. package/.agent/history/snapshots/test__unit__compile.test.js.json +10 -0
  41. package/.agent/history/snapshots/test__unit__context_broker.test.js.json +1 -2
  42. package/.agent/history/snapshots/test__unit__contract_engine.test.js.json +13 -0
  43. package/.agent/history/snapshots/test__unit__copyDir.test.js.json +3 -3
  44. package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -2
  45. package/.agent/history/snapshots/test__unit__guardrail_engine.test.js.json +10 -0
  46. package/.agent/history/snapshots/test__unit__impact_classifier.test.js.json +12 -0
  47. package/.agent/history/snapshots/test__unit__init.test.js.json +14 -0
  48. package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +1 -2
  49. package/.agent/history/snapshots/test__unit__integrity_manifest.test.js.json +13 -0
  50. package/.agent/history/snapshots/test__unit__learn.test.js.json +11 -0
  51. package/.agent/history/snapshots/test__unit__marathon.test.js.json +22 -0
  52. package/.agent/history/snapshots/test__unit__mcp_server.test.js.json +16 -0
  53. package/.agent/history/snapshots/test__unit__memory.test.js.json +13 -0
  54. package/.agent/history/snapshots/test__unit__minimal_change.test.js.json +10 -0
  55. package/.agent/history/snapshots/test__unit__native.test.js.json +10 -0
  56. package/.agent/history/snapshots/test__unit__optimize.test.js.json +13 -0
  57. package/.agent/history/snapshots/test__unit__path_resolution.test.js.json +14 -0
  58. package/.agent/history/snapshots/test__unit__production_readiness_evidence.test.js.json +21 -0
  59. package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +3 -3
  60. package/.agent/history/snapshots/test__unit__semver.test.js.json +3 -3
  61. package/.agent/history/snapshots/test__unit__skill_evolution.test.js.json +11 -0
  62. package/.agent/history/snapshots/test__unit__stress.test.js.json +15 -0
  63. package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +3 -3
  64. package/.agent/history/snapshots/test__unit__utils.test.js.json +10 -0
  65. package/.agent/scripts/ast_context_loader.js +137 -0
  66. package/.agent/scripts/memory_engine.js +581 -0
  67. package/.agent/scripts/payload_schemas.js +146 -0
  68. package/.agent/scripts/prompt_compiler.js +59 -11
  69. package/.agent/scripts/swarm_dispatcher.js +295 -67
  70. package/.agent/scripts/token_budget_broker.js +117 -6
  71. package/.claude/CLAUDE.md +442 -0
  72. package/README.md +22 -12
  73. package/SECURITY.md +3 -3
  74. package/bin/adapter-install.js +240 -0
  75. package/bin/global-store.js +48 -0
  76. package/bin/mcp-server.js +568 -252
  77. package/bin/proxy-server.js +113 -0
  78. package/bin/spawn-agent.js +39 -0
  79. package/bin/tk-proxy.js +34 -0
  80. package/dist/commands/init.js +8 -51
  81. package/dist/commands/status.js +61 -36
  82. package/dist/commands/validate.js +22 -31
  83. package/dist/tui/banner.js +77 -0
  84. package/dist/tui/index.js +21 -0
  85. package/dist/tui/reviewer-grid.js +90 -0
  86. package/dist/tui/shimmer.js +91 -0
  87. package/dist/tui/theme.js +130 -0
  88. package/dist/tui/tree.js +93 -0
  89. package/dist/tui/wizard.js +148 -0
  90. package/dist/utils/helpers.js +5 -41
  91. package/package.json +27 -10
  92. package/scripts/build-graph.js +71 -0
  93. package/.agent/history/snapshots/migrate_refs.js.json +0 -11
  94. package/.agent/scripts/compile_router.py +0 -5
  95. package/.agent/scripts/migrate_skills_frontmatter.py +0 -5
@@ -0,0 +1,581 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * memory_engine.js — Tribunal Kit 4-Type Taxonomy Persistent Memory Engine
4
+ * ==============================================================================
5
+ * SQLite-backed memory storage with budget-constrained recall.
6
+ *
7
+ * Memory Types:
8
+ * - semantic: Permanent facts (no expiration)
9
+ * - procedural: How-to recipes (no expiration)
10
+ * - episodic: Events with 30-day TTL
11
+ * - working: Session scratch (cleared on restart)
12
+ *
13
+ * Recall Algorithm:
14
+ * score = relevance × recency × priority
15
+ * where relevance = token overlap with query
16
+ * recency = -0.1 × days since last access
17
+ * priority = explicit priority (default: 1.0)
18
+ *
19
+ * Usage:
20
+ * const { MemoryEngine } = require('./memory_engine');
21
+ * const mem = new MemoryEngine(agentDir);
22
+ * mem.store('semantic', 'User prefers TypeScript', ['typescript', 'preference']);
23
+ * const { results } = mem.recall('typescript', 2000);
24
+ *
25
+ * Zero external dependencies beyond better-sqlite3 (already installed).
26
+ */
27
+
28
+ 'use strict';
29
+
30
+ const fs = require('fs');
31
+ const path = require('path');
32
+
33
+ // Lazy-load better-sqlite3 (optional dependency for environments without native compilation)
34
+ let Database = null;
35
+ try {
36
+ Database = require('better-sqlite3');
37
+ } catch (_err) {
38
+ // Fallback to in-memory storage if better-sqlite3 not available
39
+ console.warn('[Memory] better-sqlite3 not available, using in-memory fallback');
40
+ }
41
+
42
+ // ─── Memory Types ─────────────────────────────────────────────────────────────
43
+
44
+ const MEMORY_TYPES = {
45
+ SEMANTIC: 'semantic', // Permanent facts
46
+ PROCEDURAL: 'procedural', // How-to recipes
47
+ EPISODIC: 'episodic', // Events (30-day TTL)
48
+ WORKING: 'working', // Session scratch
49
+ };
50
+
51
+ const VALID_TYPES = Object.values(MEMORY_TYPES);
52
+
53
+ // ─── In-Memory Fallback Storage ───────────────────────────────────────────────
54
+
55
+ class InMemoryStore {
56
+ constructor() {
57
+ this.memories = new Map();
58
+ this.nextId = 1;
59
+ }
60
+
61
+ prepare(sql) {
62
+ const self = this;
63
+
64
+ // Simple SQL-like operations on in-memory Map
65
+ return {
66
+ run(...params) {
67
+ // INSERT
68
+ if (sql.includes('INSERT INTO memories')) {
69
+ const [type, content, tags, priority, expiresAt] = params;
70
+ const id = self.nextId++;
71
+ self.memories.set(id, {
72
+ id,
73
+ type,
74
+ content,
75
+ tags: JSON.parse(tags),
76
+ priority,
77
+ created_at: new Date().toISOString(),
78
+ accessed_at: new Date().toISOString(),
79
+ expires_at: expiresAt,
80
+ access_count: 0,
81
+ });
82
+ return { lastInsertRowid: id, changes: 1 };
83
+ }
84
+ // UPDATE
85
+ if (sql.includes('UPDATE memories')) {
86
+ for (const [_id, mem] of self.memories) {
87
+ mem.accessed_at = new Date().toISOString();
88
+ mem.access_count++;
89
+ }
90
+ return { changes: self.memories.size };
91
+ }
92
+ // DELETE
93
+ if (sql.includes('DELETE FROM memories')) {
94
+ const before = self.memories.size;
95
+ for (const [id, mem] of self.memories) {
96
+ if (
97
+ mem.type === 'episodic' &&
98
+ mem.expires_at &&
99
+ new Date(mem.expires_at) < new Date()
100
+ ) {
101
+ self.memories.delete(id);
102
+ }
103
+ }
104
+ return { changes: before - self.memories.size };
105
+ }
106
+ return { changes: 0 };
107
+ },
108
+
109
+ all(...params) {
110
+ const [query] = params;
111
+ const results = [];
112
+
113
+ for (const [_id, mem] of self.memories) {
114
+ // Filter expired
115
+ if (mem.expires_at && new Date(mem.expires_at) < new Date()) {
116
+ continue;
117
+ }
118
+
119
+ // Calculate score
120
+ const relevance = (
121
+ mem.content.toLowerCase().match(new RegExp(query.toLowerCase(), 'g')) || []
122
+ ).length;
123
+ const daysSinceAccess =
124
+ (Date.now() - new Date(mem.accessed_at).getTime()) / (1000 * 60 * 60 * 24);
125
+ const score = relevance * 3.0 + daysSinceAccess * -0.1 + mem.priority;
126
+
127
+ results.push({ ...mem, score });
128
+ }
129
+
130
+ return results.sort((a, b) => b.score - a.score);
131
+ },
132
+
133
+ get(...params) {
134
+ const [id] = params;
135
+ return self.memories.get(id) || undefined;
136
+ },
137
+ };
138
+ }
139
+
140
+ exec(_sql) {
141
+ // No-op for in-memory
142
+ }
143
+
144
+ close() {
145
+ this.memories.clear();
146
+ }
147
+ }
148
+
149
+ // ─── Memory Engine Class ──────────────────────────────────────────────────────
150
+
151
+ class MemoryEngine {
152
+ /**
153
+ * Initialize the Memory Engine.
154
+ * @param {string} agentDir - Path to .agent/ directory
155
+ * @param {object} options - Configuration options
156
+ */
157
+ constructor(agentDir, options = {}) {
158
+ this.agentDir = agentDir;
159
+ this.dbPath = path.join(agentDir, 'history', 'memory.db');
160
+ this.options = {
161
+ defaultTTL: 30 * 24 * 60 * 60 * 1000, // 30 days for episodic
162
+ maxWorkingMemorySize: 100, // Max working memory entries
163
+ ...options,
164
+ };
165
+
166
+ // Ensure history directory exists
167
+ const historyDir = path.dirname(this.dbPath);
168
+ if (!fs.existsSync(historyDir)) {
169
+ fs.mkdirSync(historyDir, { recursive: true });
170
+ }
171
+
172
+ // Initialize database
173
+ this.db = this.initDatabase();
174
+ this.initSchema();
175
+ }
176
+
177
+ /**
178
+ * Initialize database connection (SQLite or in-memory fallback).
179
+ */
180
+ initDatabase() {
181
+ if (Database) {
182
+ return new Database(this.dbPath);
183
+ }
184
+ return new InMemoryStore();
185
+ }
186
+
187
+ /**
188
+ * Create database schema if not exists.
189
+ */
190
+ initSchema() {
191
+ this.db.exec(`
192
+ CREATE TABLE IF NOT EXISTS memories (
193
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
194
+ type TEXT NOT NULL CHECK(type IN ('semantic', 'procedural', 'episodic', 'working')),
195
+ content TEXT NOT NULL,
196
+ tags TEXT,
197
+ priority REAL DEFAULT 1.0,
198
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
199
+ accessed_at TEXT DEFAULT CURRENT_TIMESTAMP,
200
+ expires_at TEXT,
201
+ access_count INTEGER DEFAULT 0
202
+ );
203
+
204
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
205
+ CREATE INDEX IF NOT EXISTS idx_memories_accessed ON memories(accessed_at);
206
+ `);
207
+ }
208
+
209
+ /**
210
+ * Store a new memory entry.
211
+ * @param {string} type - Memory type (semantic, procedural, episodic, working)
212
+ * @param {string} content - Memory content
213
+ * @param {string[]} tags - Searchable tags
214
+ * @param {object} options - Additional options (priority, expiresIn)
215
+ * @returns {{ id: number, type: string, token_estimate: number }}
216
+ */
217
+ store(type, content, tags = [], options = {}) {
218
+ // Validate type
219
+ if (!VALID_TYPES.includes(type)) {
220
+ throw new Error(`Invalid memory type: "${type}". Must be one of: ${VALID_TYPES.join(', ')}`);
221
+ }
222
+
223
+ // Validate content
224
+ if (!content || typeof content !== 'string') {
225
+ throw new Error('Memory content must be a non-empty string');
226
+ }
227
+
228
+ // Calculate expiration
229
+ let expiresAt = null;
230
+ if (type === MEMORY_TYPES.EPISODIC) {
231
+ const expiresIn = options.expiresIn || this.options.defaultTTL;
232
+ expiresAt = new Date(Date.now() + expiresIn).toISOString();
233
+ }
234
+
235
+ // Insert into database
236
+ const stmt = this.db.prepare(`
237
+ INSERT INTO memories (type, content, tags, priority, expires_at)
238
+ VALUES (?, ?, ?, ?, ?)
239
+ `);
240
+
241
+ const result = stmt.run(
242
+ type,
243
+ content,
244
+ JSON.stringify(tags),
245
+ options.priority || 1.0,
246
+ expiresAt,
247
+ );
248
+
249
+ return {
250
+ id: result.lastInsertRowid,
251
+ type,
252
+ token_estimate: this.estimateTokens(content),
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Recall memories matching a query, within token budget.
258
+ * @param {string} query - Search query
259
+ * @param {number} budget - Maximum token budget
260
+ * @returns {{ results: array, tokens_used: number }}
261
+ */
262
+ recall(query, budget = 2000) {
263
+ const _tokens = this.tokenize(query);
264
+
265
+ // Score by relevance × recency × priority
266
+ const memories = this.db
267
+ .prepare(
268
+ `
269
+ SELECT *, (
270
+ (length(content) - length(replace(lower(content), lower(?), ''))) / length(?) * 3.0
271
+ + (julianday('now') - julianday(accessed_at)) * -0.1
272
+ + priority
273
+ ) as score
274
+ FROM memories
275
+ WHERE expires_at IS NULL OR expires_at > datetime('now')
276
+ ORDER BY score DESC
277
+ `,
278
+ )
279
+ .all(query, query);
280
+
281
+ // Fit within budget
282
+ const selected = [];
283
+ let usedTokens = 0;
284
+
285
+ for (const mem of memories) {
286
+ const tokens = this.estimateTokens(mem.content);
287
+ if (usedTokens + tokens <= budget) {
288
+ selected.push({
289
+ id: mem.id,
290
+ memory_type: mem.type,
291
+ content: mem.content,
292
+ tags: mem.tags ? JSON.parse(mem.tags) : [],
293
+ priority: mem.priority,
294
+ score: mem.score,
295
+ });
296
+ usedTokens += tokens;
297
+
298
+ // Update access stats
299
+ this.db
300
+ .prepare(
301
+ `
302
+ UPDATE memories
303
+ SET accessed_at = CURRENT_TIMESTAMP, access_count = access_count + 1
304
+ WHERE id = ?
305
+ `,
306
+ )
307
+ .run(mem.id);
308
+ }
309
+ }
310
+
311
+ return { results: selected, tokens_used: usedTokens };
312
+ }
313
+
314
+ /**
315
+ * Get a specific memory by ID.
316
+ * @param {number} id - Memory ID
317
+ * @returns {object|null}
318
+ */
319
+ get(id) {
320
+ const mem = this.db
321
+ .prepare(
322
+ `
323
+ SELECT * FROM memories WHERE id = ?
324
+ `,
325
+ )
326
+ .get(id);
327
+
328
+ if (!mem) return null;
329
+
330
+ return {
331
+ id: mem.id,
332
+ memory_type: mem.type,
333
+ content: mem.content,
334
+ tags: mem.tags ? JSON.parse(mem.tags) : [],
335
+ priority: mem.priority,
336
+ created_at: mem.created_at,
337
+ accessed_at: mem.accessed_at,
338
+ access_count: mem.access_count,
339
+ expires_at: mem.expires_at,
340
+ };
341
+ }
342
+
343
+ /**
344
+ * Delete a memory by ID.
345
+ * @param {number} id - Memory ID
346
+ * @returns {boolean} True if deleted
347
+ */
348
+ delete(id) {
349
+ const result = this.db
350
+ .prepare(
351
+ `
352
+ DELETE FROM memories WHERE id = ?
353
+ `,
354
+ )
355
+ .run(id);
356
+
357
+ return result.changes > 0;
358
+ }
359
+
360
+ /**
361
+ * Clear all working memories (session scratch).
362
+ */
363
+ clearWorking() {
364
+ this.db
365
+ .prepare(
366
+ `
367
+ DELETE FROM memories WHERE type = 'working'
368
+ `,
369
+ )
370
+ .run();
371
+ }
372
+
373
+ /**
374
+ * Auto-expire episodic memories.
375
+ */
376
+ expire() {
377
+ this.db
378
+ .prepare(
379
+ `
380
+ DELETE FROM memories
381
+ WHERE type = 'episodic' AND expires_at < datetime('now')
382
+ `,
383
+ )
384
+ .run();
385
+ }
386
+
387
+ /**
388
+ * Get memory statistics.
389
+ */
390
+ stats() {
391
+ const stats = this.db
392
+ .prepare(
393
+ `
394
+ SELECT
395
+ type,
396
+ COUNT(*) as count,
397
+ SUM(length(content)) as total_chars
398
+ FROM memories
399
+ WHERE expires_at IS NULL OR expires_at > datetime('now')
400
+ GROUP BY type
401
+ `,
402
+ )
403
+ .all();
404
+
405
+ const result = {
406
+ total: 0,
407
+ by_type: {},
408
+ };
409
+
410
+ for (const stat of stats) {
411
+ result.total += stat.count;
412
+ result.by_type[stat.type] = {
413
+ count: stat.count,
414
+ total_chars: stat.total_chars,
415
+ estimated_tokens: Math.ceil(stat.total_chars / 4),
416
+ };
417
+ }
418
+
419
+ return result;
420
+ }
421
+
422
+ /**
423
+ * Close database connection.
424
+ */
425
+ close() {
426
+ if (this.db && this.db.close) {
427
+ this.db.close();
428
+ }
429
+ }
430
+
431
+ // ─── Helpers ───────────────────────────────────────────────────────────────
432
+
433
+ /**
434
+ * Estimate token count for text.
435
+ * Uses rough approximation: 1 token ≈ 4 characters.
436
+ */
437
+ estimateTokens(text) {
438
+ if (!text) return 0;
439
+ return Math.ceil(text.length / 4);
440
+ }
441
+
442
+ /**
443
+ * Tokenize text into lowercase words.
444
+ */
445
+ tokenize(text) {
446
+ if (!text) return [];
447
+ return (text.match(/\b[a-zA-Z_][a-zA-Z0-9_]{2,}\b/g) || []).map(t => t.toLowerCase());
448
+ }
449
+ }
450
+
451
+ // ─── Module Exports ────────────────────────────────────────────────────────────
452
+
453
+ module.exports = {
454
+ MemoryEngine,
455
+ MEMORY_TYPES,
456
+ VALID_TYPES,
457
+ };
458
+
459
+ // ─── CLI Entry Point ───────────────────────────────────────────────────────────
460
+
461
+ if (require.main === module) {
462
+ const args = process.argv.slice(2);
463
+
464
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
465
+ console.log(`
466
+ \x1b[1mmemory_engine.js\x1b[0m — Tribunal Kit 4-Type Taxonomy Persistent Memory Engine
467
+
468
+ \x1b[1mUsage:\x1b[0m
469
+ node .agent/scripts/memory_engine.js store <type> <content> [--tags tag1,tag2]
470
+ node .agent/scripts/memory_engine.js recall <query> [--budget 2000]
471
+ node .agent/scripts/memory_engine.js get <id>
472
+ node .agent/scripts/memory_engine.js delete <id>
473
+ node .agent/scripts/memory_engine.js stats
474
+ node .agent/scripts/memory_engine.js expire
475
+
476
+ \x1b[1mMemory Types:\x1b[0m
477
+ semantic — Permanent facts (no expiration)
478
+ procedural — How-to recipes (no expiration)
479
+ episodic — Events with 30-day TTL
480
+ working — Session scratch (cleared on restart)
481
+
482
+ \x1b[1mExamples:\x1b[0m
483
+ node .agent/scripts/memory_engine.js store semantic "User prefers TypeScript strict mode" --tags typescript,preference
484
+ node .agent/scripts/memory_engine.js recall "typescript" --budget 1000
485
+ node .agent/scripts/memory_engine.js stats
486
+ `);
487
+ process.exit(0);
488
+ }
489
+
490
+ const { findAgentDir } = require('./_utils');
491
+ const agentDir = findAgentDir() || path.resolve(__dirname, '..');
492
+
493
+ const mem = new MemoryEngine(agentDir);
494
+
495
+ const command = args[0];
496
+
497
+ try {
498
+ switch (command) {
499
+ case 'store': {
500
+ const type = args[1];
501
+ const content = args[2];
502
+ const tagsIdx = args.indexOf('--tags');
503
+ const tags =
504
+ tagsIdx !== -1 && args[tagsIdx + 1]
505
+ ? args[tagsIdx + 1].split(',').map(t => t.trim())
506
+ : [];
507
+
508
+ const result = mem.store(type, content, tags);
509
+ console.log(
510
+ `\x1b[32m✓\x1b[0m Memory stored: #${result.id} (${result.type}, ~${result.token_estimate} tokens)`,
511
+ );
512
+ break;
513
+ }
514
+
515
+ case 'recall': {
516
+ const query = args[1];
517
+ const budgetIdx = args.indexOf('--budget');
518
+ const budget =
519
+ budgetIdx !== -1 && args[budgetIdx + 1] ? parseInt(args[budgetIdx + 1], 10) : 2000;
520
+
521
+ const { results, tokens_used } = mem.recall(query, budget);
522
+ console.log(
523
+ `\n## Memory Recall (${results.length} results, ~${tokens_used}/${budget} tokens)\n`,
524
+ );
525
+ for (const entry of results) {
526
+ console.log(`- **[${entry.memory_type.toUpperCase()}]** #${entry.id}: ${entry.content}`);
527
+ if (entry.tags.length > 0) console.log(` _(${entry.tags.join(', ')})_`);
528
+ }
529
+ break;
530
+ }
531
+
532
+ case 'get': {
533
+ const id = parseInt(args[1], 10);
534
+ const result = mem.get(id);
535
+ if (!result) {
536
+ console.error(`\x1b[31m✖ Memory #${id} not found\x1b[0m`);
537
+ process.exit(1);
538
+ }
539
+ console.log(JSON.stringify(result, null, 2));
540
+ break;
541
+ }
542
+
543
+ case 'delete': {
544
+ const id = parseInt(args[1], 10);
545
+ const deleted = mem.delete(id);
546
+ if (deleted) {
547
+ console.log(`\x1b[32m✓\x1b[0m Memory #${id} deleted`);
548
+ } else {
549
+ console.error(`\x1b[31m✖ Memory #${id} not found\x1b[0m`);
550
+ process.exit(1);
551
+ }
552
+ break;
553
+ }
554
+
555
+ case 'stats': {
556
+ const stats = mem.stats();
557
+ console.log(`\n## Memory Statistics\n`);
558
+ console.log(`Total memories: ${stats.total}`);
559
+ for (const [type, data] of Object.entries(stats.by_type)) {
560
+ console.log(` ${type}: ${data.count} (~${data.estimated_tokens} tokens)`);
561
+ }
562
+ break;
563
+ }
564
+
565
+ case 'expire': {
566
+ mem.expire();
567
+ console.log(`\x1b[32m✓\x1b[0m Expired episodic memories cleaned up`);
568
+ break;
569
+ }
570
+
571
+ default:
572
+ console.error(`\x1b[31m✖ Unknown command: ${command}\x1b[0m`);
573
+ process.exit(1);
574
+ }
575
+ } catch (err) {
576
+ console.error(`\x1b[31m✖ Error: ${err.message}\x1b[0m`);
577
+ process.exit(1);
578
+ } finally {
579
+ mem.close();
580
+ }
581
+ }