neuronlayer 0.1.3 → 0.1.4

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.

Potentially problematic release.


This version of neuronlayer might be problematic. Click here for more details.

package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuronlayer",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Persistent memory layer for AI coding assistants - MCP server that makes AI truly understand your codebase",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,16 +9,9 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "node esbuild.config.js",
12
- "build:benchmark": "node esbuild.benchmark.cjs",
13
- "build:all": "npm run build && npm run build:benchmark",
14
12
  "dev": "node --watch dist/index.js",
15
13
  "start": "node dist/index.js",
16
- "typecheck": "tsc --noEmit",
17
- "test": "vitest",
18
- "test:run": "vitest run",
19
- "benchmark": "npm run build:all && node dist/benchmark/working-benchmark.js",
20
- "benchmark:quick": "npm run build:all && node dist/benchmark/working-benchmark.js --quick",
21
- "benchmark:full": "npm run build:all && node dist/benchmark/working-benchmark.js --iterations 50"
14
+ "typecheck": "tsc --noEmit"
22
15
  },
23
16
  "keywords": [
24
17
  "mcp",
@@ -28,7 +21,6 @@
28
21
  "codebase",
29
22
  "claude",
30
23
  "embeddings",
31
- "agent",
32
24
  "coding-assistant",
33
25
  "llm"
34
26
  ],
@@ -54,8 +46,7 @@
54
46
  "@types/better-sqlite3": "^7.6.0",
55
47
  "@types/node": "^22.0.0",
56
48
  "esbuild": "^0.25.0",
57
- "typescript": "^5.7.0",
58
- "vitest": "^3.0.0"
49
+ "typescript": "^5.7.0"
59
50
  },
60
51
  "engines": {
61
52
  "node": ">=18.0.0"
@@ -413,6 +413,106 @@ export class BugCorrelator {
413
413
  return null;
414
414
  }
415
415
 
416
+ /**
417
+ * Scan git history for fix commits and auto-record as bugs
418
+ * Looks for commits with "fix:", "bugfix:", "hotfix:" prefixes or "fixes #" references
419
+ */
420
+ scanForBugFixes(): number {
421
+ try {
422
+ // Get commits from change_history that look like bug fixes
423
+ const fixCommits = this.db.prepare(`
424
+ SELECT DISTINCT commit_hash, commit_message, file, diff, timestamp
425
+ FROM change_history
426
+ WHERE (
427
+ LOWER(commit_message) LIKE 'fix:%' OR
428
+ LOWER(commit_message) LIKE 'fix(%' OR
429
+ LOWER(commit_message) LIKE 'bugfix:%' OR
430
+ LOWER(commit_message) LIKE 'hotfix:%' OR
431
+ LOWER(commit_message) LIKE '%fixes #%' OR
432
+ LOWER(commit_message) LIKE '%fixed #%' OR
433
+ LOWER(commit_message) LIKE '%closes #%'
434
+ )
435
+ ORDER BY timestamp DESC
436
+ LIMIT 50
437
+ `).all() as Array<{
438
+ commit_hash: string;
439
+ commit_message: string;
440
+ file: string;
441
+ diff: string | null;
442
+ timestamp: number;
443
+ }>;
444
+
445
+ let recorded = 0;
446
+
447
+ for (const commit of fixCommits) {
448
+ // Check if we already have this bug recorded (by commit hash in related_changes)
449
+ const existing = this.db.prepare(`
450
+ SELECT id FROM bug_history
451
+ WHERE fixed_by = ? OR related_changes LIKE ?
452
+ `).get(commit.commit_hash, `%${commit.commit_hash}%`);
453
+
454
+ if (existing) continue;
455
+
456
+ // Extract the bug description from commit message
457
+ const errorDescription = this.extractErrorFromCommitMessage(commit.commit_message);
458
+
459
+ // Record the bug as already fixed
460
+ const id = randomUUID();
461
+
462
+ this.db.prepare(`
463
+ INSERT INTO bug_history (id, error, file, timestamp, status, fixed_by, fixed_at, fix_diff, cause)
464
+ VALUES (?, ?, ?, ?, 'fixed', ?, ?, ?, ?)
465
+ `).run(
466
+ id,
467
+ errorDescription,
468
+ commit.file,
469
+ commit.timestamp,
470
+ commit.commit_hash,
471
+ commit.timestamp,
472
+ commit.diff?.slice(0, 2000) || null,
473
+ commit.commit_message
474
+ );
475
+
476
+ recorded++;
477
+ }
478
+
479
+ return recorded;
480
+ } catch {
481
+ return 0;
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Extract a bug/error description from a fix commit message
487
+ */
488
+ private extractErrorFromCommitMessage(message: string): string {
489
+ // Remove common prefixes
490
+ let cleaned = message
491
+ .replace(/^fix\s*[:\(]/i, '')
492
+ .replace(/^bugfix\s*[:\(]/i, '')
493
+ .replace(/^hotfix\s*[:\(]/i, '')
494
+ .replace(/\):\s*/, ': ')
495
+ .trim();
496
+
497
+ // Extract issue references
498
+ const issueMatch = cleaned.match(/(?:fixes|fixed|closes)\s*#(\d+)/i);
499
+ if (issueMatch) {
500
+ cleaned = cleaned.replace(/(?:fixes|fixed|closes)\s*#\d+/gi, '').trim();
501
+ if (cleaned) {
502
+ cleaned = `Issue #${issueMatch[1]}: ${cleaned}`;
503
+ } else {
504
+ cleaned = `Issue #${issueMatch[1]}`;
505
+ }
506
+ }
507
+
508
+ // Capitalize first letter
509
+ if (cleaned.length > 0) {
510
+ cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
511
+ }
512
+
513
+ return cleaned || message;
514
+ }
515
+
416
516
  // Get bug statistics
417
517
  getBugStats(): {
418
518
  total: number;
@@ -19,6 +19,8 @@ export class ChangeIntelligence {
19
19
  private bugCorrelator: BugCorrelator;
20
20
  private fixSuggester: FixSuggester;
21
21
  private initialized = false;
22
+ private lastKnownHead: string | null = null;
23
+ private lastBugScanHead: string | null = null;
22
24
 
23
25
  constructor(
24
26
  projectPath: string,
@@ -40,6 +42,39 @@ export class ChangeIntelligence {
40
42
  return synced;
41
43
  }
42
44
 
45
+ // Sync recent git changes on-demand (event-driven alternative to polling)
46
+ syncFromGit(limit: number = 20): number {
47
+ return this.changeTracker.syncFromGit(limit);
48
+ }
49
+
50
+ /**
51
+ * Get the last known HEAD commit
52
+ */
53
+ getLastKnownHead(): string | null {
54
+ return this.lastKnownHead;
55
+ }
56
+
57
+ /**
58
+ * Update the last known HEAD (call this after syncing)
59
+ */
60
+ setLastKnownHead(head: string): void {
61
+ this.lastKnownHead = head;
62
+ }
63
+
64
+ /**
65
+ * Get the last HEAD where we scanned for bug fixes
66
+ */
67
+ getLastBugScanHead(): string | null {
68
+ return this.lastBugScanHead;
69
+ }
70
+
71
+ /**
72
+ * Update the last bug scan HEAD (call this after scanning)
73
+ */
74
+ setLastBugScanHead(head: string): void {
75
+ this.lastBugScanHead = head;
76
+ }
77
+
43
78
  // Query what changed
44
79
  whatChanged(options: ChangeQueryOptions = {}): ChangeQueryResult {
45
80
  return this.changeTracker.queryChanges(options);
@@ -85,6 +120,14 @@ export class ChangeIntelligence {
85
120
  return this.bugCorrelator.recordFix(bugId, fixDiff, cause);
86
121
  }
87
122
 
123
+ /**
124
+ * Scan git history for fix commits and auto-record as bugs
125
+ * Called by background intelligence loop
126
+ */
127
+ scanForBugFixes(): number {
128
+ return this.bugCorrelator.scanForBugFixes();
129
+ }
130
+
88
131
  // Search changes by keyword
89
132
  searchChanges(keyword: string, limit?: number): Change[] {
90
133
  return this.changeTracker.searchChanges(keyword, limit);
@@ -22,6 +22,7 @@ import { TestAwareness } from './test-awareness/index.js';
22
22
  import { GhostMode, type GhostInsight, type ConflictWarning } from './ghost-mode.js';
23
23
  import { DejaVuDetector, type DejaVuMatch } from './deja-vu.js';
24
24
  import { CodeVerifier, type VerificationResult, type VerificationCheck, type ImportVerification, type SecurityScanResult, type DependencyCheckResult } from './code-verifier.js';
25
+ import { GitStalenessChecker, ActivityGate } from './refresh/index.js';
25
26
  import { detectLanguage, getPreview, countLines } from '../utils/files.js';
26
27
  import type { MemoryLayerConfig, AssembledContext, Decision, ProjectSummary, SearchResult, CodeSymbol, SymbolKind, ActiveFeatureContext, HotContext } from '../types/index.js';
27
28
  import type { ArchitectureDoc, ComponentDoc, DailyChangelog, ChangelogOptions, ValidationResult, ActivityResult, UndocumentedItem, ContextHealth, CompactionResult, CompactionOptions, CriticalContext, DriftResult, ConfidenceResult, ConfidenceLevel, ConfidenceSources, ConflictResult, ChangeQueryResult, ChangeQueryOptions, Diagnosis, PastBug, FixSuggestion, Change, Pattern, PatternCategory, PatternValidationResult, ExistingFunction, TestInfo, TestFramework, TestValidationResult, TestUpdate, TestCoverage } from '../types/documentation.js';
@@ -53,14 +54,12 @@ export class MemoryLayerEngine {
53
54
  private ghostMode: GhostMode;
54
55
  private dejaVu: DejaVuDetector;
55
56
  private codeVerifier: CodeVerifier;
56
- private backgroundInterval: NodeJS.Timeout | null = null;
57
+ private gitStalenessChecker: GitStalenessChecker;
58
+ private activityGate: ActivityGate;
57
59
  private initialized = false;
58
60
  private initializationStatus: 'pending' | 'indexing' | 'ready' | 'error' = 'pending';
59
61
  private indexingProgress: { indexed: number; total: number } = { indexed: 0, total: 0 };
60
62
 
61
- // Background intelligence settings
62
- private readonly BACKGROUND_REFRESH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
63
-
64
63
  constructor(config: MemoryLayerConfig) {
65
64
  this.config = config;
66
65
 
@@ -165,6 +164,10 @@ export class MemoryLayerEngine {
165
164
  // Phase 13: Initialize Code Verifier (pre-commit quality gate)
166
165
  this.codeVerifier = new CodeVerifier(config.projectPath);
167
166
 
167
+ // Intelligent Refresh System
168
+ this.gitStalenessChecker = new GitStalenessChecker(config.projectPath);
169
+ this.activityGate = new ActivityGate();
170
+
168
171
  // Register this project
169
172
  const projectInfo = this.projectManager.registerProject(config.projectPath);
170
173
  this.projectManager.setActiveProject(projectInfo.id);
@@ -197,6 +200,13 @@ export class MemoryLayerEngine {
197
200
 
198
201
  if (stats.indexed > 0) {
199
202
  console.error(`Indexing complete: ${stats.indexed} files indexed`);
203
+ // Log activity for indexing
204
+ this.livingDocs.getActivityTracker().logActivity(
205
+ 'indexing_complete',
206
+ `Indexed ${stats.indexed} files`,
207
+ undefined,
208
+ { total: stats.total, indexed: stats.indexed }
209
+ );
200
210
  } else {
201
211
  console.error(`Index up to date (${stats.total} files)`);
202
212
  }
@@ -209,6 +219,9 @@ export class MemoryLayerEngine {
209
219
  this.indexer.on('fileIndexed', (path) => {
210
220
  // Track file in feature context
211
221
  this.featureContextManager.onFileOpened(path);
222
+
223
+ // Invalidate cached summary when file changes (event-driven, not polling)
224
+ this.summarizer.invalidateSummaryByPath(path);
212
225
  });
213
226
 
214
227
  this.indexer.on('error', (error) => {
@@ -247,8 +260,17 @@ export class MemoryLayerEngine {
247
260
  console.error(`Test awareness: ${testResult.testsIndexed} tests indexed (${testResult.framework})`);
248
261
  }
249
262
 
250
- // Start background intelligence loop
251
- this.startBackgroundIntelligence();
263
+ // Scan for bug fixes from git history (one-time on init, not polling)
264
+ this.changeIntelligence.scanForBugFixes();
265
+
266
+ // Initialize git staleness checker with current HEAD
267
+ this.gitStalenessChecker.updateCachedHead();
268
+
269
+ // Register idle-time maintenance tasks
270
+ this.registerIdleTasks();
271
+
272
+ // Start idle monitoring
273
+ this.activityGate.startIdleMonitoring(10_000);
252
274
 
253
275
  this.initialized = true;
254
276
  this.initializationStatus = 'ready';
@@ -271,30 +293,129 @@ export class MemoryLayerEngine {
271
293
  }
272
294
 
273
295
  /**
274
- * Background Intelligence Loop - continuously learn and update without user intervention
296
+ * Register idle-time maintenance tasks
275
297
  */
276
- private startBackgroundIntelligence(): void {
277
- // Clear any existing interval
278
- if (this.backgroundInterval) {
279
- clearInterval(this.backgroundInterval);
280
- }
281
-
282
- // Refresh git changes every 5 minutes
283
- this.backgroundInterval = setInterval(() => {
284
- try {
285
- // Sync recent git changes
298
+ private registerIdleTasks(): void {
299
+ // Git sync when idle and HEAD has changed
300
+ this.activityGate.registerIdleTask('git-sync', () => {
301
+ if (this.gitStalenessChecker.hasNewCommits()) {
286
302
  const synced = this.changeIntelligence.syncFromGit(20);
287
303
  if (synced > 0) {
288
- console.error(`[Background] Synced ${synced} recent changes from git`);
304
+ this.changeIntelligence.scanForBugFixes();
305
+ console.error(`Idle sync: ${synced} git changes synced`);
289
306
  }
307
+ }
308
+ }, {
309
+ minIdleMs: 30_000, // 30 seconds idle
310
+ intervalMs: 60_000 // Check at most every minute
311
+ });
312
+
313
+ // Importance score updates when idle for longer
314
+ this.activityGate.registerIdleTask('importance-update', () => {
315
+ this.learningEngine.updateImportanceScores();
316
+ }, {
317
+ minIdleMs: 60_000, // 1 minute idle
318
+ intervalMs: 300_000 // Max once per 5 minutes
319
+ });
320
+ }
290
321
 
291
- // Update importance scores for recently accessed files
292
- this.learningEngine.updateImportanceScores();
293
- } catch (error) {
294
- // Silent fail for background tasks
295
- console.error('[Background] Error in intelligence loop:', error);
322
+ /**
323
+ * Sync git changes on-demand with cheap pre-check
324
+ * Only syncs if HEAD has changed, otherwise returns 0
325
+ */
326
+ syncGitChanges(limit: number = 20): number {
327
+ // Record activity
328
+ this.activityGate.recordActivity();
329
+
330
+ try {
331
+ // Cheap pre-check: has HEAD changed?
332
+ if (!this.gitStalenessChecker.hasNewCommits()) {
333
+ return 0; // No new commits, skip expensive sync
334
+ }
335
+
336
+ const synced = this.changeIntelligence.syncFromGit(limit);
337
+ if (synced > 0) {
338
+ // Also scan for bug fixes when syncing
339
+ this.changeIntelligence.scanForBugFixes();
340
+ }
341
+ return synced;
342
+ } catch {
343
+ return 0;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Force sync git changes, bypassing the staleness check
349
+ */
350
+ forceSyncGitChanges(limit: number = 20): number {
351
+ this.activityGate.recordActivity();
352
+ this.gitStalenessChecker.updateCachedHead();
353
+
354
+ try {
355
+ const synced = this.changeIntelligence.syncFromGit(limit);
356
+ if (synced > 0) {
357
+ this.changeIntelligence.scanForBugFixes();
296
358
  }
297
- }, this.BACKGROUND_REFRESH_INTERVAL_MS);
359
+ return synced;
360
+ } catch {
361
+ return 0;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Trigger a full refresh of the memory layer
367
+ * Syncs git changes, updates importance scores, etc.
368
+ */
369
+ triggerRefresh(): {
370
+ gitSynced: number;
371
+ importanceUpdated: boolean;
372
+ tasksExecuted: string[];
373
+ } {
374
+ this.activityGate.recordActivity();
375
+
376
+ // Force git sync
377
+ this.gitStalenessChecker.updateCachedHead();
378
+ const gitSynced = this.forceSyncGitChanges();
379
+
380
+ // Update importance scores
381
+ this.learningEngine.updateImportanceScores();
382
+
383
+ return {
384
+ gitSynced,
385
+ importanceUpdated: true,
386
+ tasksExecuted: ['git-sync', 'importance-update']
387
+ };
388
+ }
389
+
390
+ /**
391
+ * Get refresh system status
392
+ */
393
+ getRefreshStatus(): {
394
+ lastActivity: number;
395
+ isIdle: boolean;
396
+ idleDuration: number;
397
+ gitHead: string | null;
398
+ hasNewCommits: boolean;
399
+ idleTasks: Array<{
400
+ name: string;
401
+ lastRun: number;
402
+ readyToRun: boolean;
403
+ }>;
404
+ } {
405
+ const status = this.activityGate.getStatus();
406
+
407
+ return {
408
+ lastActivity: this.activityGate.getLastActivity(),
409
+ isIdle: status.isIdle,
410
+ idleDuration: status.idleDuration,
411
+ gitHead: this.gitStalenessChecker.getCachedHead(),
412
+ hasNewCommits: this.gitStalenessChecker.hasNewCommits(),
413
+ idleTasks: status.tasks.map(t => ({
414
+ name: t.name,
415
+ lastRun: t.lastRun,
416
+ readyToRun: t.readyToRun
417
+ }))
418
+ };
298
419
  }
299
420
 
300
421
  /**
@@ -313,6 +434,9 @@ export class MemoryLayerEngine {
313
434
  }
314
435
 
315
436
  async getContext(query: string, currentFile?: string, maxTokens?: number): Promise<AssembledContext> {
437
+ // Record activity for refresh system
438
+ this.activityGate.recordActivity();
439
+
316
440
  // Track the query
317
441
  this.learningEngine.trackEvent({ eventType: 'query', query });
318
442
 
@@ -339,6 +463,7 @@ export class MemoryLayerEngine {
339
463
  }
340
464
 
341
465
  async searchCodebase(query: string, limit: number = 10): Promise<SearchResult[]> {
466
+ this.activityGate.recordActivity();
342
467
  const embedding = await this.indexer.getEmbeddingGenerator().embed(query);
343
468
  let results = this.tier2.search(embedding, limit * 2); // Get more for re-ranking
344
469
 
@@ -354,7 +479,28 @@ export class MemoryLayerEngine {
354
479
  files?: string[],
355
480
  tags?: string[]
356
481
  ): Promise<Decision> {
357
- return this.decisionTracker.recordDecision(title, description, files || [], tags || []);
482
+ this.activityGate.recordActivity();
483
+ const decision = await this.decisionTracker.recordDecision(title, description, files || [], tags || []);
484
+
485
+ // Log activity for decision recording
486
+ this.livingDocs.getActivityTracker().logActivity(
487
+ 'decision_recorded',
488
+ `Decision: ${title}`,
489
+ undefined,
490
+ { decisionId: decision.id }
491
+ );
492
+
493
+ // Auto-mark decisions as critical context
494
+ this.contextRotPrevention.markCritical(
495
+ `Decision: ${title}\n${description}`,
496
+ {
497
+ type: 'decision',
498
+ reason: 'Architectural decision',
499
+ source: 'auto'
500
+ }
501
+ );
502
+
503
+ return decision;
358
504
  }
359
505
 
360
506
  getRecentDecisions(limit: number = 10): Decision[] {
@@ -370,6 +516,7 @@ export class MemoryLayerEngine {
370
516
  }
371
517
 
372
518
  async getFileContext(filePath: string): Promise<{ content: string; language: string; lines: number } | null> {
519
+ this.activityGate.recordActivity();
373
520
  const absolutePath = join(this.config.projectPath, filePath);
374
521
 
375
522
  if (!existsSync(absolutePath)) {
@@ -1440,14 +1587,8 @@ export class MemoryLayerEngine {
1440
1587
 
1441
1588
  shutdown(): void {
1442
1589
  console.error('Shutting down MemoryLayer...');
1443
-
1444
- // Stop background intelligence
1445
- if (this.backgroundInterval) {
1446
- clearInterval(this.backgroundInterval);
1447
- this.backgroundInterval = null;
1448
- }
1449
-
1450
1590
  this.indexer.stopWatching();
1591
+ this.activityGate.shutdown();
1451
1592
  this.tier1.save();
1452
1593
  this.featureContextManager.shutdown();
1453
1594
  closeDatabase(this.db);
@@ -37,6 +37,8 @@ export class LearningEngine {
37
37
  private hotCache: Map<string, { content: string; accessCount: number; lastAccessed: number }> = new Map();
38
38
  private readonly MAX_CACHE_SIZE = 50;
39
39
  private readonly CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
40
+ private lastImportanceUpdate: number = 0;
41
+ private readonly IMPORTANCE_UPDATE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
40
42
 
41
43
  constructor(db: Database.Database) {
42
44
  this.db = db;
@@ -385,11 +387,40 @@ export class LearningEngine {
385
387
  return result.changes;
386
388
  }
387
389
 
390
+ /**
391
+ * Check if importance update should be skipped
392
+ * Returns true if we've already updated within the last 5 minutes
393
+ */
394
+ shouldSkipImportanceUpdate(): boolean {
395
+ const now = Date.now();
396
+ return now - this.lastImportanceUpdate < this.IMPORTANCE_UPDATE_INTERVAL_MS;
397
+ }
398
+
399
+ /**
400
+ * Get time since last importance update in milliseconds
401
+ */
402
+ getTimeSinceLastImportanceUpdate(): number {
403
+ return Date.now() - this.lastImportanceUpdate;
404
+ }
405
+
406
+ /**
407
+ * Get the last importance update timestamp
408
+ */
409
+ getLastImportanceUpdateTime(): number {
410
+ return this.lastImportanceUpdate;
411
+ }
412
+
388
413
  /**
389
414
  * Update importance scores for all files based on usage patterns
390
415
  * Called by background intelligence loop
416
+ * Gated to run at most once per 5 minutes
391
417
  */
392
418
  updateImportanceScores(): void {
419
+ // Gate: skip if we've already updated recently
420
+ if (this.shouldSkipImportanceUpdate()) {
421
+ return;
422
+ }
423
+
393
424
  try {
394
425
  // Get all files with access stats
395
426
  const stmt = this.db.prepare(`
@@ -428,6 +459,9 @@ export class LearningEngine {
428
459
  importance
429
460
  );
430
461
  }
462
+
463
+ // Update the timestamp to prevent running again within the interval
464
+ this.lastImportanceUpdate = Date.now();
431
465
  } catch (error) {
432
466
  console.error('Error updating importance scores:', error);
433
467
  }
@@ -40,6 +40,9 @@ export class LivingDocumentationEngine {
40
40
  async generateArchitectureDocs(): Promise<ArchitectureDoc> {
41
41
  const doc = await this.archGen.generate();
42
42
 
43
+ // Store architecture docs in documentation table
44
+ this.storeDocumentation('_architecture', 'architecture', JSON.stringify(doc));
45
+
43
46
  // Log activity
44
47
  this.activityTracker.logActivity(
45
48
  'doc_generation',
@@ -51,6 +54,13 @@ export class LivingDocumentationEngine {
51
54
  return doc;
52
55
  }
53
56
 
57
+ /**
58
+ * Get the activity tracker for external use
59
+ */
60
+ getActivityTracker(): ActivityTracker {
61
+ return this.activityTracker;
62
+ }
63
+
54
64
  async generateComponentDoc(filePath: string): Promise<ComponentDoc> {
55
65
  const doc = await this.compGen.generate(filePath);
56
66
 
@@ -89,6 +99,20 @@ export class LivingDocumentationEngine {
89
99
 
90
100
  private storeDocumentation(filePath: string, docType: string, content: string): void {
91
101
  try {
102
+ // Special handling for architecture docs (no file ID)
103
+ if (filePath === '_architecture') {
104
+ // Use file_id = 0 for special docs like architecture
105
+ const stmt = this.db.prepare(`
106
+ INSERT INTO documentation (file_id, doc_type, content, generated_at)
107
+ VALUES (0, ?, ?, unixepoch())
108
+ ON CONFLICT(file_id, doc_type) DO UPDATE SET
109
+ content = excluded.content,
110
+ generated_at = unixepoch()
111
+ `);
112
+ stmt.run(docType, content);
113
+ return;
114
+ }
115
+
92
116
  // Get file ID
93
117
  const fileStmt = this.db.prepare('SELECT id FROM files WHERE path = ?');
94
118
  const fileRow = fileStmt.get(filePath) as { id: number } | undefined;