@sashabogi/foundation 2.0.1 → 2.1.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 (45) hide show
  1. package/README.md +3 -158
  2. package/dist/cli.js +127 -253
  3. package/dist/cli.js.map +1 -1
  4. package/dist/providers/anthropic.d.ts +2 -2
  5. package/dist/providers/anthropic.d.ts.map +1 -1
  6. package/dist/providers/anthropic.js +2 -2
  7. package/dist/providers/anthropic.js.map +1 -1
  8. package/dist/providers/types.d.ts +1 -1
  9. package/dist/providers/types.d.ts.map +1 -1
  10. package/dist/providers/zai.d.ts +17 -8
  11. package/dist/providers/zai.d.ts.map +1 -1
  12. package/dist/providers/zai.js +34 -13
  13. package/dist/providers/zai.js.map +1 -1
  14. package/dist/services/config.service.d.ts +1 -5
  15. package/dist/services/config.service.d.ts.map +1 -1
  16. package/dist/services/config.service.js +0 -20
  17. package/dist/services/config.service.js.map +1 -1
  18. package/dist/services/git.service.d.ts +0 -4
  19. package/dist/services/git.service.d.ts.map +1 -1
  20. package/dist/services/git.service.js +0 -30
  21. package/dist/services/git.service.js.map +1 -1
  22. package/dist/services/storage.service.d.ts +1 -24
  23. package/dist/services/storage.service.d.ts.map +1 -1
  24. package/dist/services/storage.service.js +0 -108
  25. package/dist/services/storage.service.js.map +1 -1
  26. package/dist/tools/gaia/index.d.ts +5 -8
  27. package/dist/tools/gaia/index.d.ts.map +1 -1
  28. package/dist/tools/gaia/index.js +16 -115
  29. package/dist/tools/gaia/index.js.map +1 -1
  30. package/dist/tools/gaia/storage.d.ts +13 -0
  31. package/dist/tools/gaia/storage.d.ts.map +1 -1
  32. package/dist/tools/gaia/storage.js +285 -1
  33. package/dist/tools/gaia/storage.js.map +1 -1
  34. package/dist/tools/seldon/index.d.ts +1 -12
  35. package/dist/tools/seldon/index.d.ts.map +1 -1
  36. package/dist/tools/seldon/index.js +1 -183
  37. package/dist/tools/seldon/index.js.map +1 -1
  38. package/dist/types/index.d.ts +0 -78
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/packages/ui/dist/assets/index-DVS_pYGH.css +1 -0
  42. package/packages/ui/dist/assets/index-WNO_oIqP.js +312 -0
  43. package/packages/ui/dist/index.html +2 -2
  44. package/packages/ui/dist/assets/index-oiJTDMJ1.css +0 -1
  45. package/packages/ui/dist/assets/index-oivszLTx.js +0 -352
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@
14
14
  import { Command } from 'commander';
15
15
  import chalk from 'chalk';
16
16
  import ora from 'ora';
17
- import { existsSync, mkdirSync, writeFileSync, readFileSync, createReadStream, readdirSync, chmodSync } from 'fs';
17
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, createReadStream, readdirSync } from 'fs';
18
18
  import { execSync } from 'child_process';
19
19
  import { homedir } from 'os';
20
20
  import { join, dirname } from 'path';
@@ -25,6 +25,7 @@ const __filename = fileURLToPath(import.meta.url);
25
25
  const __dirname = dirname(__filename);
26
26
  import { ConfigService } from './services/config.service.js';
27
27
  import { runSetupWizard, addProvider, testProvider, listProviders, } from './cli/setup-wizard.js';
28
+ import { MemoriaStorage } from './tools/gaia/storage.js';
28
29
  // Read version from package.json dynamically
29
30
  const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
30
31
  const VERSION = packageJson.version;
@@ -66,14 +67,9 @@ function registerProject(projectPath) {
66
67
  const FOUNDATION_CLAUDE_MD = `
67
68
  ## Foundation MCP Tools (Demerzel, Seldon, Gaia)
68
69
 
69
- > **Tools are DEFERRED in Claude Code 2.1+.** Use \`ToolSearch\` with \`+foundation\` keyword to load them before first use in each session. One search loads all matching tools.
70
+ > **CRITICAL: These tools are ALREADY LOADED. Use them directly without ToolSearch.**
70
71
 
71
- Foundation provides 47 tools for AI-assisted development. All tools use \`mcp__foundation__\` prefix.
72
-
73
- **Quick load patterns:**
74
- - \`ToolSearch("+foundation demerzel")\` — loads all Demerzel tools
75
- - \`ToolSearch("+foundation seldon")\` — loads all Seldon tools
76
- - \`ToolSearch("+foundation gaia")\` — loads all Gaia tools
72
+ Foundation provides 37 tools for AI-assisted development. All tools use \`mcp__foundation__\` prefix.
77
73
 
78
74
  ### Demerzel — Codebase Intelligence
79
75
  *"I have been watching for 20,000 years."*
@@ -89,65 +85,48 @@ Foundation provides 47 tools for AI-assisted development. All tools use \`mcp__f
89
85
  | \`demerzel_find_importers\` | FREE | What files import X? |
90
86
  | \`demerzel_get_deps\` | FREE | What does file X import? |
91
87
  | \`demerzel_get_context\` | FREE | Get code around a location |
92
- | \`demerzel_analyze\` | ~500 tokens | AI-powered architecture analysis |
88
+ | \`demerzel_analyze\` | ~500 tokens | AI-powered analysis |
93
89
  | \`demerzel_semantic_search\` | ~tokens | Natural language search |
94
90
 
95
- **Visualize:** Run \`foundation ui\` to open the codebase graph in your browser.
96
-
97
91
  ### Seldon — Multi-Agent Orchestration
98
92
  *"The future is not set, but it can be guided."*
99
93
 
100
94
  | Tool | Purpose |
101
95
  |------|---------|
102
- | \`seldon_invoke\` | Invoke agent role (coder, critic, reviewer, designer, researcher) |
103
- | \`seldon_compare\` | Run same task through multiple agents |
104
- | \`seldon_critique\` | Get adversarial plan critique |
96
+ | \`seldon_invoke\` | Invoke agent role (coder, critic, reviewer) |
97
+ | \`seldon_compare\` | Compare multiple agent perspectives |
98
+ | \`seldon_critique\` | Get critical review |
105
99
  | \`seldon_review\` | Code review |
106
- | \`seldon_design\` | UI/UX design feedback |
107
100
  | \`seldon_plan\` | Generate implementation plan |
108
- | \`seldon_phase_create\` | Break plan into execution phases |
109
- | \`seldon_phase_list\` | List phases and their status |
110
- | \`seldon_verify\` | Verify implementation against plan |
111
- | \`seldon_fix\` | Generate fixes for verification issues |
101
+ | \`seldon_phase_create\` | Break plan into phases |
102
+ | \`seldon_phase_list\` | List phases and status |
103
+ | \`seldon_verify\` | Verify implementation |
104
+ | \`seldon_fix\` | Generate fixes |
112
105
  | \`seldon_execute_verified\` | Execute with verification loop |
113
- | \`seldon_execute_parallel\` | Execute phases in parallel (per-worktree) |
114
- | \`seldon_pipeline_create\` | Create reusable multi-step DAG pipeline |
115
- | \`seldon_pipeline_execute\` | Run a pipeline |
116
- | \`seldon_pipeline_status\` | Get pipeline execution status |
117
- | \`seldon_task_execute\` | Execute task in isolated worktree |
118
- | \`seldon_task_claim\` | Claim next available task |
119
- | \`seldon_providers_list\` | List configured providers |
120
- | \`seldon_providers_test\` | Test provider connectivity |
106
+ | \`seldon_providers_list\` | List providers |
107
+ | \`seldon_providers_test\` | Test provider health |
121
108
 
122
- ### Gaia — Workflow + Memory
109
+ ### Gaia — Workflow Patterns
123
110
  *"We are all one, and one is all."*
124
111
 
125
- #### Workflow Tools
126
112
  | Tool | Purpose |
127
113
  |------|---------|
128
- | \`gaia_checkpoint\` | Save full structured session state to disk |
129
- | \`gaia_status\` | Lightweight index card (~150 tokens) |
130
- | \`gaia_query\` | Keyword search across checkpoint |
131
- | \`gaia_get_decisions\` | List architectural decisions |
132
- | \`gaia_get_progress\` | Task progress summary |
133
- | \`gaia_get_changes\` | Files changed in session |
134
- | \`gaia_handoff\` | Create session handoff document |
135
- | \`gaia_observe\` | Auto-detect patterns and observations |
136
- | \`gaia_migrate\` | Migrate v1 checkpoint data to v2 |
137
- | \`gaia_learn\` | Record correction for CLAUDE.md |
138
- | \`gaia_apply\` | Write learnings to CLAUDE.md |
139
- | \`gaia_review\` | Review accumulated learnings |
140
-
141
- #### Memory Tools (SQLite + FTS5)
142
- | Tool | Purpose |
143
- |------|---------|
144
- | \`gaia_save\` | Save memory (5 tiers: session/project/global/note/observation) |
145
- | \`gaia_search\` | Search with BM25 + composite scoring |
114
+ | \`gaia_checkpoint\` | Save structured session state |
115
+ | \`gaia_status\` | Get checkpoint index card (~150 tokens) |
116
+ | \`gaia_query\` | Search checkpoint by keyword |
117
+ | \`gaia_get_decisions\` | Get architectural decisions |
118
+ | \`gaia_get_progress\` | Get task progress |
119
+ | \`gaia_get_changes\` | Get files changed |
120
+ | \`gaia_handoff\` | Create handoff document |
121
+ | \`gaia_observe\` | Analyze session patterns |
122
+ | \`gaia_migrate\` | Migrate v1 checkpoints to v2 |
123
+ | \`gaia_save\` | Save a memory |
124
+ | \`gaia_search\` | Search memories |
146
125
  | \`gaia_get\` | Get memory by ID |
147
- | \`gaia_delete\` | Delete memory + cascade links |
148
- | \`gaia_stats\` | Database statistics |
126
+ | \`gaia_delete\` | Delete a memory |
127
+ | \`gaia_stats\` | Get memory statistics |
149
128
  | \`gaia_link\` | Create typed link between memories |
150
- | \`gaia_graph\` | Get link graph for a memory |
129
+ | \`gaia_graph\` | Get memory link graph |
151
130
  `;
152
131
  const program = new Command();
153
132
  program
@@ -266,16 +245,6 @@ worktrees:
266
245
  max_count: 10
267
246
  auto_cleanup: true
268
247
  stale_after_hours: 48
269
-
270
- learning:
271
- auto_apply: false
272
- categories:
273
- - code_style
274
- - architecture
275
- - testing
276
- - performance
277
- - security
278
- - documentation
279
248
  `;
280
249
  writeFileSync(CONFIG_FILE, defaultConfig);
281
250
  spinner.succeed('Foundation initialized!');
@@ -369,9 +338,9 @@ mcpCommand
369
338
  console.log(chalk.bold('Foundation is now available in all Claude Code sessions.'));
370
339
  console.log();
371
340
  console.log('Available tools:');
372
- console.log(chalk.cyan(' Demerzel (9)') + ' - Codebase intelligence');
373
- console.log(chalk.cyan(' Seldon (19)') + ' - Multi-agent orchestration');
374
- console.log(chalk.cyan(' Gaia (13)') + ' - Workflow patterns');
341
+ console.log(chalk.cyan(' Demerzel (9)') + ' - Codebase intelligence');
342
+ console.log(chalk.cyan(' Seldon (12)') + ' - Multi-agent orchestration');
343
+ console.log(chalk.cyan(' Gaia (16)') + ' - Workflow patterns + memory');
375
344
  console.log();
376
345
  console.log(chalk.bold('Optional:'));
377
346
  console.log(' Run ' + chalk.cyan('foundation hooks install') + ' in a project to enable');
@@ -414,72 +383,26 @@ mcpCommand
414
383
  const hooksCommand = program
415
384
  .command('hooks')
416
385
  .description('Manage Gaia lifecycle hooks');
417
- // Hook script templates (embedded so they work when installed from npm)
418
- const HOOK_SESSION_START = `#!/bin/bash
419
- # Gaia v2 SessionStart hook
420
- # Injects the latest checkpoint index card into Claude's context.
421
-
422
- find_foundation_dir() {
423
- local dir="\${CLAUDE_PROJECT_DIR:-\$PWD}"
424
- local count=0
425
- while [ \$count -lt 5 ]; do
426
- if [ -f "\$dir/.foundation/sessions/latest/index.txt" ]; then
427
- echo "\$dir"
428
- return 0
429
- fi
430
- dir="\$(dirname "\$dir")"
431
- count=\$((count + 1))
432
- done
433
- return 1
434
- }
435
-
436
- PROJECT_DIR=\$(find_foundation_dir)
437
- if [ \$? -eq 0 ]; then
438
- INDEX_FILE="\$PROJECT_DIR/.foundation/sessions/latest/index.txt"
439
- python3 -c "
440
- import json, sys
441
- try:
442
- with open(sys.argv[1], 'r') as f:
443
- content = f.read().strip()
444
- if content:
445
- output = {'hookSpecificOutput': {'additionalContext': '[Gaia Checkpoint]\\n' + content}}
446
- print(json.dumps(output))
447
- except Exception:
448
- pass
449
- " "\$INDEX_FILE"
450
- fi
451
- `;
452
- const HOOK_PRE_COMPACT = `#!/bin/bash
453
- # Gaia v2 PreCompact hook — reminds to checkpoint before context compaction
454
- printf '{"hookSpecificOutput":{"additionalContext":"[Gaia] Context compaction imminent. If you have unsaved session state (decisions, progress, changes), run gaia_checkpoint now to preserve it before context is lost."}}'
455
- `;
456
- const HOOK_POST_TASK = `#!/bin/bash
457
- # Gaia v2 PostToolUse hook (Task matcher)
458
- INPUT=\$(cat)
459
- AGENT_TYPE=\$(echo "\$INPUT" | python3 -c "
460
- import json, sys
461
- try:
462
- data = json.load(sys.stdin)
463
- tool_input = data.get('tool_input', {})
464
- print(tool_input.get('subagent_type', ''))
465
- except Exception:
466
- print('')
467
- " 2>/dev/null)
468
-
469
- if [ "\$AGENT_TYPE" = "Explore" ]; then
470
- exit 0
471
- fi
472
-
473
- printf '{"hookSpecificOutput":{"additionalContext":"[Gaia] Sub-agent completed. Consider updating your checkpoint if significant progress was made."}}'
474
- `;
386
+ // Inline hook commands (no external .sh files needed)
475
387
  const HOOKS_SETTINGS_CONFIG = {
476
388
  hooks: {
477
389
  SessionStart: [{
478
390
  matcher: '',
479
391
  hooks: [{
480
392
  type: 'command',
481
- command: '.claude/hooks/gaia-session-start.sh',
482
- timeout: 5,
393
+ command: `python3 -c "
394
+ import json, os, pathlib
395
+ d = os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())
396
+ for _ in range(5):
397
+ f = os.path.join(d, '.foundation', 'sessions', 'latest', 'index.txt')
398
+ if os.path.isfile(f):
399
+ c = pathlib.Path(f).read_text().strip()
400
+ if c:
401
+ print(json.dumps({'hookSpecificOutput':{'additionalContext':'[Gaia Checkpoint]\\\\n'+c}}))
402
+ break
403
+ d = os.path.dirname(d)
404
+ "`,
405
+ timeout: 5000,
483
406
  statusMessage: 'Loading Gaia checkpoint...',
484
407
  }],
485
408
  }],
@@ -487,8 +410,8 @@ const HOOKS_SETTINGS_CONFIG = {
487
410
  matcher: '',
488
411
  hooks: [{
489
412
  type: 'command',
490
- command: '.claude/hooks/gaia-pre-compact.sh',
491
- timeout: 5,
413
+ command: 'echo \'{"hookSpecificOutput":{"additionalContext":"[Gaia] Context compaction imminent. If you have unsaved session state (decisions, progress, changes), run gaia_checkpoint now to preserve it before context is lost."}}\'',
414
+ timeout: 5000,
492
415
  statusMessage: 'Gaia checkpoint reminder',
493
416
  }],
494
417
  }],
@@ -496,8 +419,17 @@ const HOOKS_SETTINGS_CONFIG = {
496
419
  matcher: 'Task',
497
420
  hooks: [{
498
421
  type: 'command',
499
- command: '.claude/hooks/gaia-post-task.sh',
500
- timeout: 5,
422
+ command: `python3 -c "
423
+ import json, sys
424
+ try:
425
+ data = json.load(sys.stdin)
426
+ if data.get('tool_input',{}).get('subagent_type','') == 'Explore':
427
+ sys.exit(0)
428
+ except Exception:
429
+ pass
430
+ print(json.dumps({'hookSpecificOutput':{'additionalContext':'[Gaia] Sub-agent completed. Consider updating your checkpoint if significant progress was made.'}}))
431
+ "`,
432
+ timeout: 5000,
501
433
  }],
502
434
  }],
503
435
  },
@@ -509,23 +441,10 @@ hooksCommand
509
441
  const spinner = ora('Installing Gaia hooks...').start();
510
442
  const cwd = process.cwd();
511
443
  try {
512
- // 1. Create .claude/hooks/ directory
513
- const hooksDir = join(cwd, '.claude', 'hooks');
514
- mkdirSync(hooksDir, { recursive: true });
515
- spinner.text = 'Created .claude/hooks/ directory';
516
- // 2. Write hook scripts
517
- const hookFiles = [
518
- { name: 'gaia-session-start.sh', content: HOOK_SESSION_START },
519
- { name: 'gaia-pre-compact.sh', content: HOOK_PRE_COMPACT },
520
- { name: 'gaia-post-task.sh', content: HOOK_POST_TASK },
521
- ];
522
- for (const hook of hookFiles) {
523
- const hookPath = join(hooksDir, hook.name);
524
- writeFileSync(hookPath, hook.content);
525
- chmodSync(hookPath, 0o755);
526
- }
527
- spinner.text = 'Wrote hook scripts';
528
- // 3. Merge hook configuration into .claude/settings.json
444
+ // 1. Ensure .claude/ directory exists
445
+ const claudeDir = join(cwd, '.claude');
446
+ mkdirSync(claudeDir, { recursive: true });
447
+ // 2. Merge hook configuration into .claude/settings.json
529
448
  const settingsPath = join(cwd, '.claude', 'settings.json');
530
449
  let settings = {};
531
450
  try {
@@ -542,15 +461,12 @@ hooksCommand
542
461
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
543
462
  spinner.succeed('Gaia hooks installed!');
544
463
  console.log();
545
- console.log(chalk.bold('Installed hooks:'));
546
- console.log(chalk.green(' ✓') + ' ' + chalk.cyan('gaia-session-start.sh') + ' — Injects checkpoint on session start');
547
- console.log(chalk.green(' ✓') + ' ' + chalk.cyan('gaia-pre-compact.sh') + ' — Reminds to checkpoint before compaction');
548
- console.log(chalk.green(' ✓') + ' ' + chalk.cyan('gaia-post-task.sh') + ' — Reminds to checkpoint after sub-agent tasks');
464
+ console.log(chalk.bold('Configured hooks (inline commands):'));
465
+ console.log(chalk.green(' ✓') + ' ' + chalk.cyan('SessionStart') + ' — Injects checkpoint on session start');
466
+ console.log(chalk.green(' ✓') + ' ' + chalk.cyan('PreCompact') + ' — Reminds to checkpoint before compaction');
467
+ console.log(chalk.green(' ✓') + ' ' + chalk.cyan('PostToolUse') + ' — Reminds to checkpoint after sub-agent tasks');
549
468
  console.log();
550
- console.log(chalk.bold('Files written:'));
551
- console.log(' ' + chalk.gray(join(hooksDir, 'gaia-session-start.sh')));
552
- console.log(' ' + chalk.gray(join(hooksDir, 'gaia-pre-compact.sh')));
553
- console.log(' ' + chalk.gray(join(hooksDir, 'gaia-post-task.sh')));
469
+ console.log(chalk.bold('Updated:'));
554
470
  console.log(' ' + chalk.gray(settingsPath));
555
471
  console.log();
556
472
  console.log(chalk.gray('Hooks will activate on the next Claude Code session in this project.'));
@@ -759,7 +675,7 @@ program
759
675
  '.svg': 'image/svg+xml',
760
676
  '.ico': 'image/x-icon',
761
677
  };
762
- const server = createServer(async (req, res) => {
678
+ const server = createServer((req, res) => {
763
679
  const url = req.url || '/';
764
680
  // CORS headers for API endpoints
765
681
  const corsHeaders = {
@@ -816,128 +732,86 @@ program
816
732
  }
817
733
  return;
818
734
  }
819
- // API endpoint for checkpoint data
820
- if (url.startsWith('/api/checkpoint')) {
821
- const urlObj = new URL(url, `http://localhost:${port}`);
822
- const projectParam = urlObj.searchParams.get('project');
823
- const targetPath = projectParam
824
- ? join(projectParam, '.foundation', 'sessions', 'latest', 'checkpoint.json')
825
- : join(cwd, '.foundation', 'sessions', 'latest', 'checkpoint.json');
826
- if (existsSync(targetPath)) {
735
+ // ---- Memory API endpoints ----
736
+ if (url.startsWith('/api/memories/stats')) {
737
+ try {
738
+ const dbPath = join(homedir(), '.foundation', 'gaia-memory.db');
739
+ const storage = new MemoriaStorage(dbPath);
827
740
  try {
828
- const content = readFileSync(targetPath, 'utf-8');
829
- res.writeHead(200, {
830
- 'Content-Type': 'application/json',
831
- ...corsHeaders,
832
- });
833
- res.end(content);
741
+ const gaia = storage.getStats();
742
+ const rescue = storage.getRescueStats();
743
+ res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
744
+ res.end(JSON.stringify({ gaia, rescue }));
834
745
  }
835
- catch {
836
- res.writeHead(500, { 'Content-Type': 'application/json', ...corsHeaders });
837
- res.end(JSON.stringify({ error: 'Failed to read checkpoint' }));
746
+ finally {
747
+ storage.close();
838
748
  }
839
749
  }
840
- else {
841
- res.writeHead(404, { 'Content-Type': 'application/json', ...corsHeaders });
842
- res.end(JSON.stringify({
843
- error: 'Checkpoint not found',
844
- hint: 'Run `gaia_checkpoint` to save session state',
845
- }));
750
+ catch (err) {
751
+ res.writeHead(500, { 'Content-Type': 'application/json', ...corsHeaders });
752
+ res.end(JSON.stringify({ error: err.message }));
846
753
  }
847
754
  return;
848
755
  }
849
- // API endpoint for memories
850
- if (url.startsWith('/api/memories')) {
851
- const urlObj = new URL(url, `http://localhost:${port}`);
852
- const tier = urlObj.searchParams.get('tier');
853
- const q = urlObj.searchParams.get('q');
854
- const dbPath = join(homedir(), '.foundation', 'gaia-memory.db');
855
- if (!existsSync(dbPath)) {
856
- res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
857
- res.end(JSON.stringify({ memories: [] }));
858
- return;
859
- }
756
+ if (url.startsWith('/api/memories/recent')) {
860
757
  try {
861
- const BetterSqlite3 = await import('better-sqlite3');
862
- const db = new BetterSqlite3.default(dbPath, { readonly: true });
863
- let query = 'SELECT id, tier, content, tags, related_files, session_id, project_path, created_at, accessed_at, access_count, metadata FROM memories WHERE 1=1';
864
- const params = [];
865
- if (tier) {
866
- query += ' AND tier = ?';
867
- params.push(tier);
758
+ const urlObj = new URL(url, `http://localhost:${port}`);
759
+ const limit = parseInt(urlObj.searchParams.get('limit') || '50', 10);
760
+ const tierParam = urlObj.searchParams.get('tier');
761
+ const tiers = tierParam ? tierParam.split(',') : undefined;
762
+ const dbPath = join(homedir(), '.foundation', 'gaia-memory.db');
763
+ const storage = new MemoriaStorage(dbPath);
764
+ try {
765
+ const results = storage.getRecent({ limit, tiers });
766
+ res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
767
+ res.end(JSON.stringify(results));
868
768
  }
869
- if (q) {
870
- query += ' AND content LIKE ?';
871
- params.push(`%${q}%`);
769
+ finally {
770
+ storage.close();
872
771
  }
873
- query += ' ORDER BY created_at DESC LIMIT 500';
874
- const rows = db.prepare(query).all(...params);
875
- db.close();
876
- const memories = rows.map((r) => ({
877
- ...r,
878
- tags: (() => { try {
879
- return JSON.parse(r.tags);
880
- }
881
- catch {
882
- return [];
883
- } })(),
884
- related_files: (() => { try {
885
- return JSON.parse(r.related_files);
886
- }
887
- catch {
888
- return [];
889
- } })(),
890
- metadata: (() => { try {
891
- return JSON.parse(r.metadata);
892
- }
893
- catch {
894
- return null;
895
- } })(),
896
- }));
897
- res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
898
- res.end(JSON.stringify({ memories }));
899
772
  }
900
773
  catch (err) {
901
774
  res.writeHead(500, { 'Content-Type': 'application/json', ...corsHeaders });
902
- res.end(JSON.stringify({ memories: [], error: String(err) }));
775
+ res.end(JSON.stringify({ error: err.message }));
903
776
  }
904
777
  return;
905
778
  }
906
- // API endpoint for memory graph
907
- if (url.startsWith('/api/memory-graph')) {
908
- const dbPath = join(homedir(), '.foundation', 'gaia-memory.db');
909
- if (!existsSync(dbPath)) {
910
- res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
911
- res.end(JSON.stringify({ memories: [], links: [] }));
912
- return;
913
- }
779
+ if (url.startsWith('/api/memories/search')) {
914
780
  try {
915
- const BetterSqlite3 = await import('better-sqlite3');
916
- const db = new BetterSqlite3.default(dbPath, { readonly: true });
917
- const memRows = db.prepare('SELECT id, tier, content, tags, related_files, created_at FROM memories ORDER BY created_at DESC LIMIT 500').all();
918
- const linkRows = db.prepare('SELECT from_memory_id, to_memory_id, link_type FROM memory_links').all();
919
- db.close();
920
- const memories = memRows.map((r) => ({
921
- ...r,
922
- tags: (() => { try {
923
- return JSON.parse(r.tags);
781
+ const urlObj = new URL(url, `http://localhost:${port}`);
782
+ const query = urlObj.searchParams.get('q') || '';
783
+ const tierParam = urlObj.searchParams.get('tier');
784
+ const tiers = tierParam ? tierParam.split(',') : undefined;
785
+ const limit = parseInt(urlObj.searchParams.get('limit') || '25', 10);
786
+ const sourceFilter = urlObj.searchParams.get('source') || 'all';
787
+ const dbPath = join(homedir(), '.foundation', 'gaia-memory.db');
788
+ const storage = new MemoriaStorage(dbPath);
789
+ try {
790
+ let results;
791
+ if (!query) {
792
+ // No query = return recent
793
+ results = storage.getRecent({ limit, tiers });
924
794
  }
925
- catch {
926
- return [];
927
- } })(),
928
- related_files: (() => { try {
929
- return JSON.parse(r.related_files);
795
+ else {
796
+ results = storage.search({ query, tiers, limit });
930
797
  }
931
- catch {
932
- return [];
933
- } })(),
934
- }));
935
- res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
936
- res.end(JSON.stringify({ memories, links: linkRows }));
798
+ // Apply source filter client-side
799
+ if (sourceFilter === 'gaia') {
800
+ results = results.filter(r => !r.memory.id.startsWith('rescue_'));
801
+ }
802
+ else if (sourceFilter === 'rescued') {
803
+ results = results.filter(r => r.memory.id.startsWith('rescue_'));
804
+ }
805
+ res.writeHead(200, { 'Content-Type': 'application/json', ...corsHeaders });
806
+ res.end(JSON.stringify(results));
807
+ }
808
+ finally {
809
+ storage.close();
810
+ }
937
811
  }
938
812
  catch (err) {
939
813
  res.writeHead(500, { 'Content-Type': 'application/json', ...corsHeaders });
940
- res.end(JSON.stringify({ memories: [], links: [], error: String(err) }));
814
+ res.end(JSON.stringify({ error: err.message }));
941
815
  }
942
816
  return;
943
817
  }