amalfa 1.0.0 → 1.0.2

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 (85) hide show
  1. package/README.md +226 -247
  2. package/amalfa.config.example.ts +8 -6
  3. package/docs/AGENT-METADATA-PATTERNS.md +1021 -0
  4. package/docs/CONFIG_E2E_VALIDATION.md +147 -0
  5. package/docs/CONFIG_UNIFICATION.md +187 -0
  6. package/docs/CONFIG_VALIDATION.md +103 -0
  7. package/docs/LEGACY_DEPRECATION.md +174 -0
  8. package/docs/MCP_SETUP.md +317 -0
  9. package/docs/QUICK_START_MCP.md +168 -0
  10. package/docs/SESSION-2026-01-06-METADATA-PATTERNS.md +346 -0
  11. package/docs/SETUP.md +464 -0
  12. package/docs/SETUP_COMPLETE.md +464 -0
  13. package/docs/VISION-AGENT-LEARNING.md +1242 -0
  14. package/docs/_current-config-status.md +93 -0
  15. package/package.json +6 -3
  16. package/polyvis.settings.json.bak +38 -0
  17. package/src/cli.ts +159 -31
  18. package/src/config/defaults.ts +73 -15
  19. package/src/core/VectorEngine.ts +18 -9
  20. package/src/daemon/index.ts +12 -8
  21. package/src/mcp/index.ts +62 -7
  22. package/src/pipeline/AmalfaIngestor.ts +22 -12
  23. package/src/pipeline/PreFlightAnalyzer.ts +434 -0
  24. package/src/resonance/DatabaseFactory.ts +3 -4
  25. package/src/resonance/db.ts +8 -6
  26. package/src/resonance/schema.ts +19 -1
  27. package/src/resonance/services/vector-daemon.ts +151 -0
  28. package/src/utils/DaemonManager.ts +147 -0
  29. package/src/utils/ZombieDefense.ts +5 -1
  30. package/:memory: +0 -0
  31. package/:memory:-shm +0 -0
  32. package/:memory:-wal +0 -0
  33. package/README.old.md +0 -112
  34. package/agents.config.json +0 -11
  35. package/drizzle/0000_minor_iron_fist.sql +0 -19
  36. package/drizzle/meta/0000_snapshot.json +0 -139
  37. package/drizzle/meta/_journal.json +0 -13
  38. package/example_usage.ts +0 -39
  39. package/experiment.sh +0 -35
  40. package/hello +0 -2
  41. package/index.html +0 -52
  42. package/knowledge/excalibur.md +0 -12
  43. package/plans/experience-graph-integration.md +0 -60
  44. package/prompts/gemini-king-mode-prompt.md +0 -46
  45. package/public/docs/MCP_TOOLS.md +0 -372
  46. package/schemas/README.md +0 -20
  47. package/schemas/cda.schema.json +0 -84
  48. package/schemas/conceptual-lexicon.schema.json +0 -75
  49. package/scratchpads/dummy-debrief-boxed.md +0 -39
  50. package/scratchpads/dummy-debrief.md +0 -27
  51. package/scratchpads/scratchpad-design.md +0 -50
  52. package/scratchpads/scratchpad-scrolling.md +0 -20
  53. package/scratchpads/scratchpad-toc-disappearance.md +0 -23
  54. package/scratchpads/scratchpad-toc.md +0 -28
  55. package/scratchpads/test_gardener.md +0 -7
  56. package/src/core/LLMClient.ts +0 -93
  57. package/src/core/TagEngine.ts +0 -56
  58. package/src/db/schema.ts +0 -46
  59. package/src/gardeners/AutoTagger.ts +0 -116
  60. package/src/pipeline/HarvesterPipeline.ts +0 -101
  61. package/src/pipeline/Ingestor.ts +0 -555
  62. package/src/resonance/cli/ingest.ts +0 -41
  63. package/src/resonance/cli/migrate.ts +0 -54
  64. package/src/resonance/config.ts +0 -40
  65. package/src/resonance/daemon.ts +0 -236
  66. package/src/resonance/pipeline/extract.ts +0 -89
  67. package/src/resonance/pipeline/transform_docs.ts +0 -60
  68. package/src/resonance/services/tokenizer.ts +0 -159
  69. package/src/resonance/transform/cda.ts +0 -393
  70. package/src/utils/EnvironmentVerifier.ts +0 -67
  71. package/substack/substack-playbook-1.md +0 -95
  72. package/substack/substack-playbook-2.md +0 -78
  73. package/tasks/ui-investigation.md +0 -26
  74. package/test-db +0 -0
  75. package/test-db-shm +0 -0
  76. package/test-db-wal +0 -0
  77. package/tests/canary/verify_pinch_check.ts +0 -44
  78. package/tests/fixtures/ingest_test.md +0 -12
  79. package/tests/fixtures/ingest_test_boxed.md +0 -13
  80. package/tests/fixtures/safety_test.md +0 -45
  81. package/tests/fixtures/safety_test_boxed.md +0 -49
  82. package/tests/fixtures/tagged_output.md +0 -49
  83. package/tests/fixtures/tagged_test.md +0 -49
  84. package/tests/mcp-server-settings.json +0 -8
  85. package/verify-embedder.ts +0 -54
@@ -0,0 +1,93 @@
1
+ # Current Configuration Status
2
+
3
+ **Last Updated:** 2026-01-06
4
+
5
+ ## Configuration Files (Single Source of Truth)
6
+
7
+ ### 1. AMALFA Core Configuration
8
+ **File:** `amalfa.config.json` (root)
9
+ **Purpose:** Main AMALFA system settings
10
+ **Loaded by:** `src/config/defaults.ts`
11
+
12
+ ```json
13
+ {
14
+ "sources": ["../polyvis/docs", "../polyvis/playbooks"],
15
+ "database": ".amalfa/multi-source-test.db",
16
+ "embeddings": {
17
+ "model": "BAAI/bge-small-en-v1.5",
18
+ "dimensions": 384
19
+ },
20
+ "watch": {
21
+ "enabled": true,
22
+ "debounce": 1000
23
+ },
24
+ "excludePatterns": ["node_modules", ".git", ".amalfa"]
25
+ }
26
+ ```
27
+
28
+ **Status:** ✅ Active, primary config for AMALFA operations
29
+
30
+ ### 2. Resonance Configuration (Legacy)
31
+ **File:** `polyvis.settings.json` (root)
32
+ **Purpose:** Resonance database and pipeline settings
33
+ **Loaded by:** `src/resonance/config.ts`
34
+
35
+ ```json
36
+ {
37
+ "paths": {
38
+ "database": { "resonance": "public/resonance.db" },
39
+ "docs": { ... },
40
+ "sources": {
41
+ "experience": [...],
42
+ "persona": { ... }
43
+ }
44
+ },
45
+ "graph": { "tuning": { ... } },
46
+ "schema": { "version": "1.0.0" }
47
+ }
48
+ ```
49
+
50
+ **Status:** 🔄 Legacy, used by resonance pipeline only
51
+
52
+ ### 3. Beads Issue Tracking
53
+ **File:** `.beads/config.yaml`
54
+ **Purpose:** Beads issue tracking system configuration
55
+ **Loaded by:** Beads CLI (`bd` commands)
56
+
57
+ **Status:** ✅ Active, separate system
58
+
59
+ ## Configuration Strategy
60
+
61
+ ### Current Approach (2026-01-06)
62
+ - **AMALFA** and **Resonance** have separate configs
63
+ - This is **intentional** - they serve different purposes:
64
+ - AMALFA: User-facing MCP server and CLI
65
+ - Resonance: Internal database/pipeline layer
66
+
67
+ ### Future Unification (v1.0+)
68
+ - Consider consolidating if Resonance becomes AMALFA-exclusive
69
+ - Keep separate if Resonance remains standalone library
70
+
71
+ ## Configuration Hierarchy
72
+
73
+ ```
74
+ Project Root
75
+ ├── amalfa.config.json ← AMALFA core (MCP, CLI, daemon)
76
+ ├── polyvis.settings.json ← Resonance (database, pipeline)
77
+ └── .beads/
78
+ └── config.yaml ← Beads (issue tracking)
79
+ ```
80
+
81
+ ## Action Items
82
+
83
+ - [x] Decide: Keep separate, deprecate polyvis.settings.json gradually (See CONFIG_UNIFICATION.md)
84
+ - [x] Document: Which config controls what (See CONFIG_UNIFICATION.md)
85
+ - [ ] Add deprecation warning when polyvis.settings.json is loaded
86
+ - [ ] Migrate graph tuning to amalfa.config.json (optional)
87
+ - [ ] Remove polyvis.settings.json in v2.0
88
+
89
+ ## Notes
90
+
91
+ - No `_current-*` files existed prior to 2026-01-06
92
+ - Configuration is stable but not unified
93
+ - Each system has clear ownership of its config file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalfa",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Local-first knowledge graph engine for AI agents. Transforms markdown into searchable memory with MCP protocol.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/pjsvis/amalfa#readme",
@@ -10,7 +10,7 @@
10
10
  "author": "Peter John Smith <729613+pjsvis@users.noreply.github.com>",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/pjsvis/amalfa"
13
+ "url": "git+https://github.com/pjsvis/amalfa.git"
14
14
  },
15
15
  "keywords": [
16
16
  "mcp",
@@ -29,7 +29,7 @@
29
29
  "pkm"
30
30
  ],
31
31
  "bin": {
32
- "amalfa": "./src/cli.ts"
32
+ "amalfa": "src/cli.ts"
33
33
  },
34
34
  "engines": {
35
35
  "bun": "1.3.x",
@@ -48,6 +48,9 @@
48
48
  "start": "bun run src/mcp/index.ts start",
49
49
  "stop": "bun run src/mcp/index.ts stop",
50
50
  "status": "bun run src/mcp/index.ts status",
51
+ "servers": "bun run scripts/cli/servers.ts",
52
+ "validate-config": "bun run scripts/validate-config.ts",
53
+ "release": "bun run scripts/release.ts",
51
54
  "check": "biome check .",
52
55
  "format": "biome format --write ."
53
56
  },
@@ -0,0 +1,38 @@
1
+ {
2
+ "_comment": "⚠️ DEPRECATED: This file will be removed in v2.0. Use amalfa.config.json for user-facing settings. See docs/CONFIG_UNIFICATION.md",
3
+ "paths": {
4
+ "database": {
5
+ "resonance": "public/resonance.db"
6
+ },
7
+ "docs": {
8
+ "root": "docs",
9
+ "webdocs": "docs/webdocs",
10
+ "architecture": "docs/architecture",
11
+ "public": "public/docs"
12
+ },
13
+ "sources": {
14
+ "experience": [
15
+ { "path": "debriefs", "name": "Debrief" },
16
+ { "path": "playbooks", "name": "Playbook" },
17
+ { "path": "briefs", "name": "Brief" },
18
+ { "path": "docs", "name": "Docs" }
19
+ ],
20
+ "persona": {
21
+ "lexicon": "scripts/fixtures/conceptual-lexicon-ref-v1.79.json",
22
+ "cda": "scripts/fixtures/cda-ref-v63.json"
23
+ }
24
+ }
25
+ },
26
+ "graph": {
27
+ "tuning": {
28
+ "louvain": {
29
+ "persona": 0.3,
30
+ "experience": 0.25
31
+ }
32
+ }
33
+ },
34
+
35
+ "schema": {
36
+ "version": "1.0.0"
37
+ }
38
+ }
package/src/cli.ts CHANGED
@@ -3,8 +3,10 @@ import { existsSync, statSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
 
6
- const VERSION = "1.0.0";
7
- const DB_PATH = join(process.cwd(), ".amalfa/resonance.db");
6
+ const VERSION = "1.0.2";
7
+
8
+ // Database path loaded from config (lazy loaded per command)
9
+ let DB_PATH: string | null = null;
8
10
 
9
11
  // Parse command line arguments
10
12
  const args = process.argv.slice(2);
@@ -18,14 +20,21 @@ Usage:
18
20
  amalfa <command> [options]
19
21
 
20
22
  Commands:
21
- init Initialize database from markdown files
23
+ init [--force] Initialize database from markdown files
22
24
  serve Start MCP server (stdio transport)
23
25
  stats Show database statistics
24
26
  doctor Check installation and configuration
27
+ setup-mcp Generate MCP configuration JSON
25
28
  daemon <action> Manage file watcher (start|stop|status|restart)
26
29
 
30
+ Options:
31
+ --force Override pre-flight warnings (errors still block)
32
+ --version, -v Show version number
33
+ --help, -h Show this help message
34
+
27
35
  Examples:
28
- amalfa init # Initialize from ./docs markdown files
36
+ amalfa init # Initialize with pre-flight validation
37
+ amalfa init --force # Override warnings (use with caution)
29
38
  amalfa serve # Start MCP server for Claude Desktop
30
39
  amalfa stats # Show knowledge graph statistics
31
40
  amalfa doctor # Verify installation
@@ -38,10 +47,21 @@ function showVersion() {
38
47
  console.log(`amalfa v${VERSION}`);
39
48
  }
40
49
 
50
+ async function getDbPath(): Promise<string> {
51
+ if (DB_PATH) return DB_PATH;
52
+
53
+ // Load from config
54
+ const { loadConfig } = await import("./config/defaults");
55
+ const config = await loadConfig();
56
+ DB_PATH = join(process.cwd(), config.database);
57
+ return DB_PATH;
58
+ }
59
+
41
60
  async function checkDatabase(): Promise<boolean> {
42
- if (!existsSync(DB_PATH)) {
61
+ const dbPath = await getDbPath();
62
+ if (!existsSync(dbPath)) {
43
63
  console.error(`
44
- ❌ Database not found at: ${DB_PATH}
64
+ ❌ Database not found at: ${dbPath}
45
65
 
46
66
  To initialize AMALFA:
47
67
  1. Create markdown files in ./docs/ (or your preferred location)
@@ -60,8 +80,9 @@ async function cmdServe() {
60
80
  process.exit(1);
61
81
  }
62
82
 
83
+ const dbPath = await getDbPath();
63
84
  console.error("🚀 Starting AMALFA MCP Server...");
64
- console.error(`📊 Database: ${DB_PATH}`);
85
+ console.error(`📊 Database: ${dbPath}`);
65
86
  console.error("");
66
87
 
67
88
  // Run MCP server (it handles stdio transport)
@@ -83,18 +104,19 @@ async function cmdStats() {
83
104
  }
84
105
 
85
106
  // Import database wrapper
107
+ const dbPath = await getDbPath();
86
108
  const { ResonanceDB } = await import("./resonance/db");
87
- const db = new ResonanceDB(DB_PATH);
109
+ const db = new ResonanceDB(dbPath);
88
110
 
89
111
  try {
90
112
  const stats = db.getStats();
91
- const fileSize = statSync(DB_PATH).size;
113
+ const fileSize = statSync(dbPath).size;
92
114
  const fileSizeMB = (fileSize / 1024 / 1024).toFixed(2);
93
115
 
94
- console.log(`
116
+ console.log(`
95
117
  📊 AMALFA Database Statistics
96
118
 
97
- Database: ${DB_PATH}
119
+ Database: ${dbPath}
98
120
  Size: ${fileSizeMB} MB
99
121
 
100
122
  Nodes: ${stats.nodes.toLocaleString()}
@@ -102,7 +124,7 @@ Edges: ${stats.edges.toLocaleString()}
102
124
  Embeddings: ${stats.vectors.toLocaleString()} (384-dim)
103
125
 
104
126
  Source: ./docs (markdown files)
105
- Last modified: ${new Date(statSync(DB_PATH).mtime).toISOString()}
127
+ Last modified: ${new Date(statSync(dbPath).mtime).toISOString()}
106
128
 
107
129
  🔍 To search: Use with Claude Desktop or other MCP client
108
130
  📝 To update: Run 'amalfa daemon start' (coming soon)
@@ -118,24 +140,64 @@ Last modified: ${new Date(statSync(DB_PATH).mtime).toISOString()}
118
140
  async function cmdInit() {
119
141
  console.log("🚀 AMALFA Initialization\n");
120
142
 
143
+ // Check for --force flag
144
+ const forceMode = args.includes("--force");
145
+
121
146
  // Load configuration
122
147
  const { loadConfig } = await import("./config/defaults");
123
148
  const config = await loadConfig();
124
149
 
125
- console.log(`📁 Source: ${config.source}`);
150
+ const sources = config.sources || ["./docs"];
151
+ console.log(`📁 Sources: ${sources.join(", ")}`);
126
152
  console.log(`💾 Database: ${config.database}`);
127
153
  console.log(`🧠 Model: ${config.embeddings.model}\n`);
128
154
 
129
- // Check if source directory exists
130
- const sourcePath = join(process.cwd(), config.source);
131
- if (!existsSync(sourcePath)) {
132
- console.error(`❌ Source directory not found: ${sourcePath}`);
133
- console.error("\nCreate it first:");
134
- console.error(` mkdir -p ${config.source}`);
135
- console.error(" # Add some markdown files");
155
+ // Run pre-flight analysis
156
+ console.log("🔍 Running pre-flight analysis...\n");
157
+ const { PreFlightAnalyzer } = await import("./pipeline/PreFlightAnalyzer");
158
+ const analyzer = new PreFlightAnalyzer(config);
159
+ const report = await analyzer.analyze();
160
+
161
+ // Display summary
162
+ console.log("📊 Pre-Flight Summary:");
163
+ console.log(` Total files: ${report.totalFiles}`);
164
+ console.log(` Valid files: ${report.validFiles}`);
165
+ console.log(` Skipped files: ${report.skippedFiles}`);
166
+ console.log(` Total size: ${(report.totalSizeBytes / 1024 / 1024).toFixed(2)} MB`);
167
+ console.log(` Estimated nodes: ${report.estimatedNodes}\n`);
168
+
169
+ if (report.hasErrors) {
170
+ console.error("❌ Pre-flight check failed with errors\n");
171
+ console.error("Errors detected:");
172
+ for (const issue of report.issues.filter((i) => i.severity === "error")) {
173
+ console.error(` - ${issue.path}: ${issue.details}`);
174
+ }
175
+ console.error("\nSee .amalfa-pre-flight.log for details and recommendations");
176
+ console.error("\nFix these issues and try again.");
177
+ process.exit(1);
178
+ }
179
+
180
+ if (report.hasWarnings && !forceMode) {
181
+ console.warn("⚠️ Pre-flight check completed with warnings\n");
182
+ console.warn("Warnings detected:");
183
+ for (const issue of report.issues.filter((i) => i.severity === "warning")) {
184
+ console.warn(` - ${issue.path}: ${issue.details}`);
185
+ }
186
+ console.warn("\nSee .amalfa-pre-flight.log for recommendations");
187
+ console.warn("\nTo proceed anyway, use: amalfa init --force");
188
+ process.exit(1);
189
+ }
190
+
191
+ if (report.validFiles === 0) {
192
+ console.error("\n❌ No valid markdown files found");
193
+ console.error("See .amalfa-pre-flight.log for details");
136
194
  process.exit(1);
137
195
  }
138
196
 
197
+ if (forceMode && report.hasWarnings) {
198
+ console.warn("⚠️ Proceeding with --force despite warnings\n");
199
+ }
200
+
139
201
  // Create .amalfa directory
140
202
  const amalfaDir = join(process.cwd(), ".amalfa");
141
203
  if (!existsSync(amalfaDir)) {
@@ -204,6 +266,55 @@ async function cmdDaemon() {
204
266
  });
205
267
  }
206
268
 
269
+ async function cmdSetupMcp() {
270
+ const { resolve } = await import("node:path");
271
+
272
+ const cwd = resolve(process.cwd());
273
+ const mcpScript = resolve(cwd, "src/mcp/index.ts");
274
+
275
+ // Minimal PATH for MCP - only include essential directories
276
+ const bunPath = process.execPath.replace(/\/bun$/, ''); // Directory containing bun
277
+ const minimalPath = [
278
+ bunPath,
279
+ '/usr/local/bin',
280
+ '/usr/bin',
281
+ '/bin',
282
+ '/usr/sbin',
283
+ '/sbin',
284
+ '/opt/homebrew/bin', // Apple Silicon Homebrew
285
+ ].join(':');
286
+
287
+ const config = {
288
+ mcpServers: {
289
+ amalfa: {
290
+ command: "bun",
291
+ args: ["run", mcpScript],
292
+ env: {
293
+ PATH: minimalPath,
294
+ },
295
+ },
296
+ },
297
+ };
298
+
299
+ console.log("\n✅ AMALFA MCP Configuration");
300
+ console.log("=".repeat(60));
301
+ console.log(`📂 Installation: ${cwd}`);
302
+ console.log("=".repeat(60));
303
+ console.log("\n📋 Copy this JSON to your MCP client config:");
304
+ console.log(" Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json");
305
+ console.log(" Warp Preview: MCP settings\n");
306
+ console.log("=".repeat(60));
307
+ console.log();
308
+
309
+ console.log(JSON.stringify(config, null, 2));
310
+
311
+ console.log();
312
+ console.log("=".repeat(60));
313
+ console.log("💡 Tip: If you move this folder, run 'amalfa setup-mcp' again");
314
+ console.log("=".repeat(60));
315
+ console.log();
316
+ }
317
+
207
318
  async function cmdDoctor() {
208
319
  console.log("🩺 AMALFA Health Check\n");
209
320
 
@@ -212,12 +323,14 @@ async function cmdDoctor() {
212
323
  // Check Bun runtime
213
324
  console.log("✓ Bun runtime: OK");
214
325
 
215
- // Check database
216
- if (existsSync(DB_PATH)) {
217
- const fileSizeMB = (statSync(DB_PATH).size / 1024 / 1024).toFixed(2);
218
- console.log(`✓ Database found: ${DB_PATH} (${fileSizeMB} MB)`);
326
+ // Check config and database
327
+ const dbPath = await getDbPath();
328
+ if (existsSync(dbPath)) {
329
+ const fileSizeMB = (statSync(dbPath).size / 1024 / 1024).toFixed(2);
330
+ console.log(`✓ Database found: ${dbPath} (${fileSizeMB} MB)`);
219
331
  } else {
220
- console.log(`✗ Database not found: ${DB_PATH}`);
332
+ console.log(`✗ Database not found: ${dbPath}`);
333
+ console.log(` Run: amalfa init`);
221
334
  issues++;
222
335
  }
223
336
 
@@ -230,12 +343,23 @@ async function cmdDoctor() {
230
343
  issues++;
231
344
  }
232
345
 
233
- // Check source directories
234
- const docsDir = join(process.cwd(), "docs");
235
- if (existsSync(docsDir)) {
236
- console.log(`✓ Source directory: ${docsDir}`);
237
- } else {
238
- console.log(`⚠ Source directory not found: ${docsDir} (optional)`);
346
+ // Check source directories from config
347
+ const { loadConfig } = await import("./config/defaults");
348
+ const config = await loadConfig();
349
+ const sources = config.sources || ["./docs"];
350
+ let sourcesFound = 0;
351
+ for (const source of sources) {
352
+ const sourcePath = join(process.cwd(), source);
353
+ if (existsSync(sourcePath)) {
354
+ console.log(`✓ Source directory: ${sourcePath}`);
355
+ sourcesFound++;
356
+ } else {
357
+ console.log(`✗ Source directory not found: ${sourcePath}`);
358
+ issues++;
359
+ }
360
+ }
361
+ if (sourcesFound === 0) {
362
+ console.log(` Configure sources in amalfa.config.json`);
239
363
  }
240
364
 
241
365
  // Check dependencies (FastEmbed)
@@ -292,6 +416,10 @@ async function main() {
292
416
  await cmdDaemon();
293
417
  break;
294
418
 
419
+ case "setup-mcp":
420
+ await cmdSetupMcp();
421
+ break;
422
+
295
423
  case "version":
296
424
  case "--version":
297
425
  case "-v":
@@ -4,7 +4,9 @@
4
4
  */
5
5
 
6
6
  export interface AmalfaConfig {
7
- source: string;
7
+ /** @deprecated Use sources array instead */
8
+ source?: string;
9
+ sources?: string[];
8
10
  database: string;
9
11
  embeddings: {
10
12
  model: string;
@@ -15,10 +17,24 @@ export interface AmalfaConfig {
15
17
  debounce: number;
16
18
  };
17
19
  excludePatterns: string[];
20
+ /** Graph analysis tuning parameters (optional) */
21
+ graph?: {
22
+ tuning?: {
23
+ louvain?: {
24
+ persona?: number;
25
+ experience?: number;
26
+ };
27
+ };
28
+ };
29
+ /** Persona fixture paths (optional, for legacy Resonance features) */
30
+ fixtures?: {
31
+ lexicon?: string;
32
+ cda?: string;
33
+ };
18
34
  }
19
35
 
20
36
  export const DEFAULT_CONFIG: AmalfaConfig = {
21
- source: "./docs",
37
+ sources: ["./docs"],
22
38
  database: ".amalfa/resonance.db",
23
39
  embeddings: {
24
40
  model: "BAAI/bge-small-en-v1.5",
@@ -29,6 +45,20 @@ export const DEFAULT_CONFIG: AmalfaConfig = {
29
45
  debounce: 1000,
30
46
  },
31
47
  excludePatterns: ["node_modules", ".git", ".amalfa"],
48
+ // Optional graph tuning (for advanced use)
49
+ graph: {
50
+ tuning: {
51
+ louvain: {
52
+ persona: 0.3,
53
+ experience: 0.25,
54
+ },
55
+ },
56
+ },
57
+ // Optional fixtures (for legacy Resonance features)
58
+ fixtures: {
59
+ lexicon: "scripts/fixtures/conceptual-lexicon-ref-v1.79.json",
60
+ cda: "scripts/fixtures/cda-ref-v63.json",
61
+ },
32
62
  };
33
63
 
34
64
  /**
@@ -56,19 +86,42 @@ export async function loadConfig(): Promise<AmalfaConfig> {
56
86
  userConfig = imported.default || imported;
57
87
  }
58
88
 
59
- // Merge with defaults
60
- return {
61
- ...DEFAULT_CONFIG,
62
- ...userConfig,
63
- embeddings: {
64
- ...DEFAULT_CONFIG.embeddings,
65
- ...(userConfig.embeddings || {}),
66
- },
67
- watch: {
68
- ...DEFAULT_CONFIG.watch,
69
- ...(userConfig.watch || {}),
89
+ // Merge with defaults
90
+ const merged = {
91
+ ...DEFAULT_CONFIG,
92
+ ...userConfig,
93
+ embeddings: {
94
+ ...DEFAULT_CONFIG.embeddings,
95
+ ...(userConfig.embeddings || {}),
96
+ },
97
+ watch: {
98
+ ...DEFAULT_CONFIG.watch,
99
+ ...(userConfig.watch || {}),
100
+ },
101
+ graph: {
102
+ ...DEFAULT_CONFIG.graph,
103
+ ...(userConfig.graph || {}),
104
+ tuning: {
105
+ ...(DEFAULT_CONFIG.graph?.tuning || {}),
106
+ ...(userConfig.graph?.tuning || {}),
70
107
  },
71
- };
108
+ },
109
+ fixtures: {
110
+ ...DEFAULT_CONFIG.fixtures,
111
+ ...(userConfig.fixtures || {}),
112
+ },
113
+ };
114
+
115
+ // Normalize: Convert legacy 'source' to 'sources' array
116
+ if (merged.source && !merged.sources) {
117
+ merged.sources = [merged.source];
118
+ }
119
+ if (!merged.sources || merged.sources.length === 0) {
120
+ merged.sources = ["./docs"];
121
+ }
122
+ delete merged.source; // Clean up legacy field
123
+
124
+ return merged;
72
125
  }
73
126
  } catch (e) {
74
127
  // Silently continue to next config file
@@ -77,5 +130,10 @@ export async function loadConfig(): Promise<AmalfaConfig> {
77
130
  }
78
131
 
79
132
  // Return defaults if no config found
80
- return DEFAULT_CONFIG;
133
+ const defaultCopy = { ...DEFAULT_CONFIG };
134
+ // Ensure sources is always an array
135
+ if (!defaultCopy.sources || defaultCopy.sources.length === 0) {
136
+ defaultCopy.sources = ["./docs"];
137
+ }
138
+ return defaultCopy;
81
139
  }
@@ -1,7 +1,5 @@
1
1
  import { Database } from "bun:sqlite";
2
- import { join } from "node:path";
3
2
  import { EmbeddingModel, FlagEmbedding } from "fastembed";
4
- import settings from "@/polyvis.settings.json";
5
3
 
6
4
  // Types
7
5
  export interface SearchResult {
@@ -89,9 +87,7 @@ export class VectorEngine {
89
87
  console.warn(
90
88
  "⚠️ DEPRECATED: VectorEngine string path constructor bypasses DatabaseFactory. Pass Database object instead. Will be removed in v2.0.",
91
89
  );
92
- const path =
93
- dbOrPath || join(process.cwd(), settings.paths.database.resonance);
94
- this.db = new Database(path);
90
+ this.db = new Database(dbOrPath);
95
91
 
96
92
  // Apply Safeguards if we created it
97
93
  this.db.run("PRAGMA journal_mode = WAL;");
@@ -192,22 +188,35 @@ export class VectorEngine {
192
188
  const topK = scored.sort((a, b) => b.score - a.score).slice(0, limit);
193
189
 
194
190
  // 5. Hydrate Content (for top K only)
191
+ // Note: Hollow Nodes have content=NULL, use meta.source to read from filesystem if needed
195
192
  const results: SearchResult[] = [];
196
193
  const contentStmt = this.db.prepare(
197
- "SELECT title, content FROM nodes WHERE id = ?",
194
+ "SELECT title, content, meta FROM nodes WHERE id = ?",
198
195
  );
199
196
 
200
197
  for (const item of topK) {
201
198
  const row = contentStmt.get(item.id) as {
202
199
  title: string;
203
- content: string;
200
+ content: string | null;
201
+ meta: string | null;
204
202
  };
205
203
  if (row) {
204
+ // For hollow nodes, extract a preview from title or meta
205
+ let content = row.content || "";
206
+ if (!content && row.meta) {
207
+ try {
208
+ const meta = JSON.parse(row.meta);
209
+ // Provide source path as content placeholder for hollow nodes
210
+ content = `[Hollow Node: ${meta.source || "no source"}]`;
211
+ } catch {
212
+ content = "[Hollow Node: parse error]";
213
+ }
214
+ }
206
215
  results.push({
207
216
  id: item.id,
208
217
  score: item.score,
209
- title: row.title, // Add title
210
- content: row.content,
218
+ title: row.title,
219
+ content: content,
211
220
  });
212
221
  }
213
222
  }
@@ -45,21 +45,25 @@ async function main() {
45
45
  const config = await loadConfig();
46
46
  const DEBOUNCE_MS = config.watch.debounce;
47
47
 
48
+ const sources = config.sources || ["./docs"];
48
49
  log.info({
49
- source: config.source,
50
+ sources,
50
51
  database: config.database,
51
52
  debounce: DEBOUNCE_MS,
52
53
  }, "🚀 AMALFA Daemon starting...");
53
54
 
54
- // Verify source directory exists
55
- const sourcePath = join(process.cwd(), config.source);
56
- if (!existsSync(sourcePath)) {
57
- log.fatal({ path: sourcePath }, "❌ Source directory not found");
58
- process.exit(1);
55
+ // Verify source directories exist
56
+ for (const source of sources) {
57
+ const sourcePath = join(process.cwd(), source);
58
+ if (!existsSync(sourcePath)) {
59
+ log.warn({ path: sourcePath }, "⚠️ Source directory not found, skipping");
60
+ }
59
61
  }
60
62
 
61
- // Start file watcher
62
- startWatcher(config.source, DEBOUNCE_MS);
63
+ // Start file watchers for all sources
64
+ for (const source of sources) {
65
+ startWatcher(source, DEBOUNCE_MS);
66
+ }
63
67
 
64
68
  log.info("✅ Daemon ready. Watching for changes...");
65
69