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.
package/dist/index.js CHANGED
@@ -21072,6 +21072,15 @@ function initializeDatabase(dbPath) {
21072
21072
  CREATE INDEX IF NOT EXISTS idx_test_index_file ON test_index(file_path);
21073
21073
  CREATE INDEX IF NOT EXISTS idx_test_index_name ON test_index(test_name);
21074
21074
  CREATE INDEX IF NOT EXISTS idx_test_covers_files ON test_index(covers_files);
21075
+
21076
+ -- Intelligent Refresh System: State persistence
21077
+ CREATE TABLE IF NOT EXISTS refresh_state (
21078
+ key TEXT PRIMARY KEY,
21079
+ value TEXT NOT NULL,
21080
+ updated_at INTEGER DEFAULT (unixepoch())
21081
+ );
21082
+
21083
+ CREATE INDEX IF NOT EXISTS idx_refresh_state_key ON refresh_state(key);
21075
21084
  `);
21076
21085
  return db;
21077
21086
  }
@@ -23303,6 +23312,9 @@ var LearningEngine = class {
23303
23312
  MAX_CACHE_SIZE = 50;
23304
23313
  CACHE_TTL_MS = 30 * 60 * 1e3;
23305
23314
  // 30 minutes
23315
+ lastImportanceUpdate = 0;
23316
+ IMPORTANCE_UPDATE_INTERVAL_MS = 5 * 60 * 1e3;
23317
+ // 5 minutes
23306
23318
  constructor(db) {
23307
23319
  this.db = db;
23308
23320
  }
@@ -23571,11 +23583,35 @@ var LearningEngine = class {
23571
23583
  const result = stmt.run(daysToKeep);
23572
23584
  return result.changes;
23573
23585
  }
23586
+ /**
23587
+ * Check if importance update should be skipped
23588
+ * Returns true if we've already updated within the last 5 minutes
23589
+ */
23590
+ shouldSkipImportanceUpdate() {
23591
+ const now = Date.now();
23592
+ return now - this.lastImportanceUpdate < this.IMPORTANCE_UPDATE_INTERVAL_MS;
23593
+ }
23594
+ /**
23595
+ * Get time since last importance update in milliseconds
23596
+ */
23597
+ getTimeSinceLastImportanceUpdate() {
23598
+ return Date.now() - this.lastImportanceUpdate;
23599
+ }
23600
+ /**
23601
+ * Get the last importance update timestamp
23602
+ */
23603
+ getLastImportanceUpdateTime() {
23604
+ return this.lastImportanceUpdate;
23605
+ }
23574
23606
  /**
23575
23607
  * Update importance scores for all files based on usage patterns
23576
23608
  * Called by background intelligence loop
23609
+ * Gated to run at most once per 5 minutes
23577
23610
  */
23578
23611
  updateImportanceScores() {
23612
+ if (this.shouldSkipImportanceUpdate()) {
23613
+ return;
23614
+ }
23579
23615
  try {
23580
23616
  const stmt = this.db.prepare(`
23581
23617
  SELECT f.id, f.path, fa.access_count, fa.last_accessed, fa.relevance_score
@@ -23602,6 +23638,7 @@ var LearningEngine = class {
23602
23638
  importance
23603
23639
  );
23604
23640
  }
23641
+ this.lastImportanceUpdate = Date.now();
23605
23642
  } catch (error2) {
23606
23643
  console.error("Error updating importance scores:", error2);
23607
23644
  }
@@ -23819,6 +23856,27 @@ var FileSummarizer = class {
23819
23856
  if (!summary) return true;
23820
23857
  return fileLastModified > summary.generatedAt;
23821
23858
  }
23859
+ // Invalidate summary when file changes (event-driven)
23860
+ invalidateSummary(fileId) {
23861
+ try {
23862
+ const result = this.db.prepare("DELETE FROM file_summaries WHERE file_id = ?").run(fileId);
23863
+ return result.changes > 0;
23864
+ } catch {
23865
+ return false;
23866
+ }
23867
+ }
23868
+ // Invalidate summary by file path
23869
+ invalidateSummaryByPath(filePath) {
23870
+ try {
23871
+ const file2 = this.db.prepare("SELECT id FROM files WHERE path = ?").get(filePath);
23872
+ if (file2) {
23873
+ return this.invalidateSummary(file2.id);
23874
+ }
23875
+ return false;
23876
+ } catch {
23877
+ return false;
23878
+ }
23879
+ }
23822
23880
  // Get compression ratio stats
23823
23881
  getCompressionStats() {
23824
23882
  const stmt = this.db.prepare(`
@@ -25989,6 +26047,7 @@ var LivingDocumentationEngine = class {
25989
26047
  }
25990
26048
  async generateArchitectureDocs() {
25991
26049
  const doc = await this.archGen.generate();
26050
+ this.storeDocumentation("_architecture", "architecture", JSON.stringify(doc));
25992
26051
  this.activityTracker.logActivity(
25993
26052
  "doc_generation",
25994
26053
  "Generated architecture documentation",
@@ -25997,6 +26056,12 @@ var LivingDocumentationEngine = class {
25997
26056
  );
25998
26057
  return doc;
25999
26058
  }
26059
+ /**
26060
+ * Get the activity tracker for external use
26061
+ */
26062
+ getActivityTracker() {
26063
+ return this.activityTracker;
26064
+ }
26000
26065
  async generateComponentDoc(filePath) {
26001
26066
  const doc = await this.compGen.generate(filePath);
26002
26067
  this.storeDocumentation(filePath, "component", JSON.stringify(doc));
@@ -26022,6 +26087,17 @@ var LivingDocumentationEngine = class {
26022
26087
  }
26023
26088
  storeDocumentation(filePath, docType, content) {
26024
26089
  try {
26090
+ if (filePath === "_architecture") {
26091
+ const stmt = this.db.prepare(`
26092
+ INSERT INTO documentation (file_id, doc_type, content, generated_at)
26093
+ VALUES (0, ?, ?, unixepoch())
26094
+ ON CONFLICT(file_id, doc_type) DO UPDATE SET
26095
+ content = excluded.content,
26096
+ generated_at = unixepoch()
26097
+ `);
26098
+ stmt.run(docType, content);
26099
+ return;
26100
+ }
26025
26101
  const fileStmt = this.db.prepare("SELECT id FROM files WHERE path = ?");
26026
26102
  const fileRow = fileStmt.get(filePath);
26027
26103
  if (fileRow) {
@@ -28227,6 +28303,75 @@ ${bestMatch.fixDiff.slice(0, 200)}`;
28227
28303
  }
28228
28304
  return null;
28229
28305
  }
28306
+ /**
28307
+ * Scan git history for fix commits and auto-record as bugs
28308
+ * Looks for commits with "fix:", "bugfix:", "hotfix:" prefixes or "fixes #" references
28309
+ */
28310
+ scanForBugFixes() {
28311
+ try {
28312
+ const fixCommits = this.db.prepare(`
28313
+ SELECT DISTINCT commit_hash, commit_message, file, diff, timestamp
28314
+ FROM change_history
28315
+ WHERE (
28316
+ LOWER(commit_message) LIKE 'fix:%' OR
28317
+ LOWER(commit_message) LIKE 'fix(%' OR
28318
+ LOWER(commit_message) LIKE 'bugfix:%' OR
28319
+ LOWER(commit_message) LIKE 'hotfix:%' OR
28320
+ LOWER(commit_message) LIKE '%fixes #%' OR
28321
+ LOWER(commit_message) LIKE '%fixed #%' OR
28322
+ LOWER(commit_message) LIKE '%closes #%'
28323
+ )
28324
+ ORDER BY timestamp DESC
28325
+ LIMIT 50
28326
+ `).all();
28327
+ let recorded = 0;
28328
+ for (const commit of fixCommits) {
28329
+ const existing = this.db.prepare(`
28330
+ SELECT id FROM bug_history
28331
+ WHERE fixed_by = ? OR related_changes LIKE ?
28332
+ `).get(commit.commit_hash, `%${commit.commit_hash}%`);
28333
+ if (existing) continue;
28334
+ const errorDescription = this.extractErrorFromCommitMessage(commit.commit_message);
28335
+ const id = randomUUID6();
28336
+ this.db.prepare(`
28337
+ INSERT INTO bug_history (id, error, file, timestamp, status, fixed_by, fixed_at, fix_diff, cause)
28338
+ VALUES (?, ?, ?, ?, 'fixed', ?, ?, ?, ?)
28339
+ `).run(
28340
+ id,
28341
+ errorDescription,
28342
+ commit.file,
28343
+ commit.timestamp,
28344
+ commit.commit_hash,
28345
+ commit.timestamp,
28346
+ commit.diff?.slice(0, 2e3) || null,
28347
+ commit.commit_message
28348
+ );
28349
+ recorded++;
28350
+ }
28351
+ return recorded;
28352
+ } catch {
28353
+ return 0;
28354
+ }
28355
+ }
28356
+ /**
28357
+ * Extract a bug/error description from a fix commit message
28358
+ */
28359
+ extractErrorFromCommitMessage(message) {
28360
+ let cleaned = message.replace(/^fix\s*[:\(]/i, "").replace(/^bugfix\s*[:\(]/i, "").replace(/^hotfix\s*[:\(]/i, "").replace(/\):\s*/, ": ").trim();
28361
+ const issueMatch = cleaned.match(/(?:fixes|fixed|closes)\s*#(\d+)/i);
28362
+ if (issueMatch) {
28363
+ cleaned = cleaned.replace(/(?:fixes|fixed|closes)\s*#\d+/gi, "").trim();
28364
+ if (cleaned) {
28365
+ cleaned = `Issue #${issueMatch[1]}: ${cleaned}`;
28366
+ } else {
28367
+ cleaned = `Issue #${issueMatch[1]}`;
28368
+ }
28369
+ }
28370
+ if (cleaned.length > 0) {
28371
+ cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
28372
+ }
28373
+ return cleaned || message;
28374
+ }
28230
28375
  // Get bug statistics
28231
28376
  getBugStats() {
28232
28377
  const stats = this.db.prepare(`
@@ -28547,6 +28692,8 @@ var ChangeIntelligence = class {
28547
28692
  bugCorrelator;
28548
28693
  fixSuggester;
28549
28694
  initialized = false;
28695
+ lastKnownHead = null;
28696
+ lastBugScanHead = null;
28550
28697
  constructor(projectPath, db, tier2, embeddingGenerator) {
28551
28698
  this.changeTracker = new ChangeTracker(projectPath, db);
28552
28699
  this.bugCorrelator = new BugCorrelator(db, this.changeTracker, tier2, embeddingGenerator);
@@ -28559,6 +28706,34 @@ var ChangeIntelligence = class {
28559
28706
  this.initialized = true;
28560
28707
  return synced;
28561
28708
  }
28709
+ // Sync recent git changes on-demand (event-driven alternative to polling)
28710
+ syncFromGit(limit = 20) {
28711
+ return this.changeTracker.syncFromGit(limit);
28712
+ }
28713
+ /**
28714
+ * Get the last known HEAD commit
28715
+ */
28716
+ getLastKnownHead() {
28717
+ return this.lastKnownHead;
28718
+ }
28719
+ /**
28720
+ * Update the last known HEAD (call this after syncing)
28721
+ */
28722
+ setLastKnownHead(head) {
28723
+ this.lastKnownHead = head;
28724
+ }
28725
+ /**
28726
+ * Get the last HEAD where we scanned for bug fixes
28727
+ */
28728
+ getLastBugScanHead() {
28729
+ return this.lastBugScanHead;
28730
+ }
28731
+ /**
28732
+ * Update the last bug scan HEAD (call this after scanning)
28733
+ */
28734
+ setLastBugScanHead(head) {
28735
+ this.lastBugScanHead = head;
28736
+ }
28562
28737
  // Query what changed
28563
28738
  whatChanged(options = {}) {
28564
28739
  return this.changeTracker.queryChanges(options);
@@ -28591,6 +28766,13 @@ var ChangeIntelligence = class {
28591
28766
  recordFix(bugId, fixDiff, cause) {
28592
28767
  return this.bugCorrelator.recordFix(bugId, fixDiff, cause);
28593
28768
  }
28769
+ /**
28770
+ * Scan git history for fix commits and auto-record as bugs
28771
+ * Called by background intelligence loop
28772
+ */
28773
+ scanForBugFixes() {
28774
+ return this.bugCorrelator.scanForBugFixes();
28775
+ }
28594
28776
  // Search changes by keyword
28595
28777
  searchChanges(keyword, limit) {
28596
28778
  return this.changeTracker.searchChanges(keyword, limit);
@@ -32835,6 +33017,278 @@ var CodeVerifier = class {
32835
33017
  }
32836
33018
  };
32837
33019
 
33020
+ // src/core/refresh/git-staleness-checker.ts
33021
+ import { execSync as execSync6 } from "child_process";
33022
+ var GitStalenessChecker = class {
33023
+ cachedHead = null;
33024
+ lastCheckTime = 0;
33025
+ projectPath;
33026
+ constructor(projectPath) {
33027
+ this.projectPath = projectPath;
33028
+ }
33029
+ /**
33030
+ * Get the current HEAD commit hash
33031
+ * This is a cheap operation (~5ms)
33032
+ */
33033
+ getCurrentHead() {
33034
+ try {
33035
+ const head = execSync6("git rev-parse HEAD", {
33036
+ cwd: this.projectPath,
33037
+ encoding: "utf-8",
33038
+ timeout: 5e3,
33039
+ stdio: ["pipe", "pipe", "pipe"]
33040
+ }).trim();
33041
+ return head;
33042
+ } catch {
33043
+ return null;
33044
+ }
33045
+ }
33046
+ /**
33047
+ * Check if there are new commits since the last check
33048
+ * Returns true if HEAD has changed (meaning we need to sync)
33049
+ */
33050
+ hasNewCommits() {
33051
+ const current = this.getCurrentHead();
33052
+ this.lastCheckTime = Date.now();
33053
+ if (current === null) {
33054
+ return false;
33055
+ }
33056
+ if (this.cachedHead === null) {
33057
+ this.cachedHead = current;
33058
+ return false;
33059
+ }
33060
+ if (current !== this.cachedHead) {
33061
+ this.cachedHead = current;
33062
+ return true;
33063
+ }
33064
+ return false;
33065
+ }
33066
+ /**
33067
+ * Force update the cached HEAD (call after successful sync)
33068
+ */
33069
+ updateCachedHead() {
33070
+ this.cachedHead = this.getCurrentHead();
33071
+ this.lastCheckTime = Date.now();
33072
+ }
33073
+ /**
33074
+ * Get the cached HEAD without checking git
33075
+ */
33076
+ getCachedHead() {
33077
+ return this.cachedHead;
33078
+ }
33079
+ /**
33080
+ * Get the time of the last check
33081
+ */
33082
+ getLastCheckTime() {
33083
+ return this.lastCheckTime;
33084
+ }
33085
+ /**
33086
+ * Get time since last check in milliseconds
33087
+ */
33088
+ getTimeSinceLastCheck() {
33089
+ return Date.now() - this.lastCheckTime;
33090
+ }
33091
+ /**
33092
+ * Check if enough time has passed since last check
33093
+ * Default threshold is 30 seconds
33094
+ */
33095
+ shouldCheck(thresholdMs = 3e4) {
33096
+ return this.getTimeSinceLastCheck() > thresholdMs;
33097
+ }
33098
+ /**
33099
+ * Reset the checker state (useful for testing)
33100
+ */
33101
+ reset() {
33102
+ this.cachedHead = null;
33103
+ this.lastCheckTime = 0;
33104
+ }
33105
+ };
33106
+
33107
+ // src/core/refresh/activity-gate.ts
33108
+ var ActivityGate = class {
33109
+ lastActivity = Date.now();
33110
+ idleTasks = [];
33111
+ monitoringInterval = null;
33112
+ isExecutingTask = false;
33113
+ currentTaskIndex = 0;
33114
+ constructor() {
33115
+ this.recordActivity();
33116
+ }
33117
+ /**
33118
+ * Record user activity (call this on API method invocations)
33119
+ */
33120
+ recordActivity() {
33121
+ this.lastActivity = Date.now();
33122
+ }
33123
+ /**
33124
+ * Get the timestamp of the last recorded activity
33125
+ */
33126
+ getLastActivity() {
33127
+ return this.lastActivity;
33128
+ }
33129
+ /**
33130
+ * Check if the user has been idle for at least the given threshold
33131
+ */
33132
+ isIdle(thresholdMs = 3e4) {
33133
+ return Date.now() - this.lastActivity > thresholdMs;
33134
+ }
33135
+ /**
33136
+ * Get the duration of the current idle period in milliseconds
33137
+ */
33138
+ getIdleDuration() {
33139
+ return Date.now() - this.lastActivity;
33140
+ }
33141
+ /**
33142
+ * Register an idle-time task
33143
+ * Tasks are executed during idle periods, one at a time
33144
+ */
33145
+ registerIdleTask(name, callback, options = {}) {
33146
+ const { minIdleMs = 3e4, intervalMs = 3e5 } = options;
33147
+ this.idleTasks = this.idleTasks.filter((t) => t.name !== name);
33148
+ this.idleTasks.push({
33149
+ name,
33150
+ callback,
33151
+ minIdleMs,
33152
+ lastRun: 0,
33153
+ intervalMs
33154
+ });
33155
+ }
33156
+ /**
33157
+ * Unregister an idle-time task
33158
+ */
33159
+ unregisterIdleTask(name) {
33160
+ const initialLength = this.idleTasks.length;
33161
+ this.idleTasks = this.idleTasks.filter((t) => t.name !== name);
33162
+ return this.idleTasks.length < initialLength;
33163
+ }
33164
+ /**
33165
+ * Get all registered idle tasks
33166
+ */
33167
+ getIdleTasks() {
33168
+ return this.idleTasks;
33169
+ }
33170
+ /**
33171
+ * Start monitoring for idle periods
33172
+ * Will check every checkIntervalMs and execute tasks when idle
33173
+ */
33174
+ startIdleMonitoring(checkIntervalMs = 1e4) {
33175
+ if (this.monitoringInterval) {
33176
+ return;
33177
+ }
33178
+ this.monitoringInterval = setInterval(() => {
33179
+ this.executeNextIdleTaskIfReady();
33180
+ }, checkIntervalMs);
33181
+ }
33182
+ /**
33183
+ * Stop idle monitoring
33184
+ */
33185
+ stopIdleMonitoring() {
33186
+ if (this.monitoringInterval) {
33187
+ clearInterval(this.monitoringInterval);
33188
+ this.monitoringInterval = null;
33189
+ }
33190
+ }
33191
+ /**
33192
+ * Execute the next idle task if conditions are met
33193
+ * - User must be idle
33194
+ * - No other task is currently executing
33195
+ * - Task's minimum idle time must be met
33196
+ * - Task's minimum interval since last run must be met
33197
+ */
33198
+ async executeNextIdleTaskIfReady() {
33199
+ if (this.isExecutingTask) {
33200
+ return;
33201
+ }
33202
+ if (this.idleTasks.length === 0) {
33203
+ return;
33204
+ }
33205
+ const now = Date.now();
33206
+ const idleDuration = this.getIdleDuration();
33207
+ for (let i = 0; i < this.idleTasks.length; i++) {
33208
+ const taskIndex = (this.currentTaskIndex + i) % this.idleTasks.length;
33209
+ const task = this.idleTasks[taskIndex];
33210
+ if (!task) continue;
33211
+ if (idleDuration < task.minIdleMs) {
33212
+ continue;
33213
+ }
33214
+ if (now - task.lastRun < task.intervalMs) {
33215
+ continue;
33216
+ }
33217
+ this.currentTaskIndex = (taskIndex + 1) % this.idleTasks.length;
33218
+ await this.executeTask(task);
33219
+ return;
33220
+ }
33221
+ }
33222
+ /**
33223
+ * Execute a specific task
33224
+ */
33225
+ async executeTask(task) {
33226
+ this.isExecutingTask = true;
33227
+ try {
33228
+ await Promise.resolve(task.callback());
33229
+ task.lastRun = Date.now();
33230
+ } catch (error2) {
33231
+ console.error(`Idle task '${task.name}' failed:`, error2);
33232
+ task.lastRun = Date.now();
33233
+ } finally {
33234
+ this.isExecutingTask = false;
33235
+ }
33236
+ }
33237
+ /**
33238
+ * Manually trigger execution of all ready idle tasks
33239
+ * Useful for testing or manual refresh
33240
+ */
33241
+ async executeReadyTasks() {
33242
+ const executed = [];
33243
+ const now = Date.now();
33244
+ const idleDuration = this.getIdleDuration();
33245
+ for (const task of this.idleTasks) {
33246
+ if (idleDuration >= task.minIdleMs && now - task.lastRun >= task.intervalMs) {
33247
+ await this.executeTask(task);
33248
+ executed.push(task.name);
33249
+ }
33250
+ }
33251
+ return executed;
33252
+ }
33253
+ /**
33254
+ * Force immediate execution of a specific task by name
33255
+ * Ignores idle and interval requirements
33256
+ */
33257
+ async forceExecuteTask(name) {
33258
+ const task = this.idleTasks.find((t) => t.name === name);
33259
+ if (!task) {
33260
+ return false;
33261
+ }
33262
+ await this.executeTask(task);
33263
+ return true;
33264
+ }
33265
+ /**
33266
+ * Get status of all idle tasks
33267
+ */
33268
+ getStatus() {
33269
+ const now = Date.now();
33270
+ const idleDuration = this.getIdleDuration();
33271
+ return {
33272
+ isIdle: this.isIdle(),
33273
+ idleDuration,
33274
+ isExecutingTask: this.isExecutingTask,
33275
+ tasks: this.idleTasks.map((task) => ({
33276
+ name: task.name,
33277
+ lastRun: task.lastRun,
33278
+ timeSinceRun: now - task.lastRun,
33279
+ readyToRun: idleDuration >= task.minIdleMs && now - task.lastRun >= task.intervalMs
33280
+ }))
33281
+ };
33282
+ }
33283
+ /**
33284
+ * Shutdown the activity gate
33285
+ */
33286
+ shutdown() {
33287
+ this.stopIdleMonitoring();
33288
+ this.idleTasks = [];
33289
+ }
33290
+ };
33291
+
32838
33292
  // src/core/engine.ts
32839
33293
  var MemoryLayerEngine = class {
32840
33294
  config;
@@ -32859,13 +33313,11 @@ var MemoryLayerEngine = class {
32859
33313
  ghostMode;
32860
33314
  dejaVu;
32861
33315
  codeVerifier;
32862
- backgroundInterval = null;
33316
+ gitStalenessChecker;
33317
+ activityGate;
32863
33318
  initialized = false;
32864
33319
  initializationStatus = "pending";
32865
33320
  indexingProgress = { indexed: 0, total: 0 };
32866
- // Background intelligence settings
32867
- BACKGROUND_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
32868
- // 5 minutes
32869
33321
  constructor(config2) {
32870
33322
  this.config = config2;
32871
33323
  if (!existsSync13(config2.dataDir)) {
@@ -32931,6 +33383,8 @@ var MemoryLayerEngine = class {
32931
33383
  this.indexer.getEmbeddingGenerator()
32932
33384
  );
32933
33385
  this.codeVerifier = new CodeVerifier(config2.projectPath);
33386
+ this.gitStalenessChecker = new GitStalenessChecker(config2.projectPath);
33387
+ this.activityGate = new ActivityGate();
32934
33388
  const projectInfo = this.projectManager.registerProject(config2.projectPath);
32935
33389
  this.projectManager.setActiveProject(projectInfo.id);
32936
33390
  this.setupIndexerEvents();
@@ -32952,6 +33406,12 @@ var MemoryLayerEngine = class {
32952
33406
  this.indexingProgress = { indexed: stats.indexed, total: stats.total };
32953
33407
  if (stats.indexed > 0) {
32954
33408
  console.error(`Indexing complete: ${stats.indexed} files indexed`);
33409
+ this.livingDocs.getActivityTracker().logActivity(
33410
+ "indexing_complete",
33411
+ `Indexed ${stats.indexed} files`,
33412
+ void 0,
33413
+ { total: stats.total, indexed: stats.indexed }
33414
+ );
32955
33415
  } else {
32956
33416
  console.error(`Index up to date (${stats.total} files)`);
32957
33417
  }
@@ -32961,6 +33421,7 @@ var MemoryLayerEngine = class {
32961
33421
  });
32962
33422
  this.indexer.on("fileIndexed", (path) => {
32963
33423
  this.featureContextManager.onFileOpened(path);
33424
+ this.summarizer.invalidateSummaryByPath(path);
32964
33425
  });
32965
33426
  this.indexer.on("error", (error2) => {
32966
33427
  console.error("Indexer error:", error2);
@@ -32985,7 +33446,10 @@ var MemoryLayerEngine = class {
32985
33446
  if (testResult.testsIndexed > 0) {
32986
33447
  console.error(`Test awareness: ${testResult.testsIndexed} tests indexed (${testResult.framework})`);
32987
33448
  }
32988
- this.startBackgroundIntelligence();
33449
+ this.changeIntelligence.scanForBugFixes();
33450
+ this.gitStalenessChecker.updateCachedHead();
33451
+ this.registerIdleTasks();
33452
+ this.activityGate.startIdleMonitoring(1e4);
32989
33453
  this.initialized = true;
32990
33454
  this.initializationStatus = "ready";
32991
33455
  console.error("MemoryLayer initialized");
@@ -33005,23 +33469,99 @@ var MemoryLayerEngine = class {
33005
33469
  };
33006
33470
  }
33007
33471
  /**
33008
- * Background Intelligence Loop - continuously learn and update without user intervention
33472
+ * Register idle-time maintenance tasks
33009
33473
  */
33010
- startBackgroundIntelligence() {
33011
- if (this.backgroundInterval) {
33012
- clearInterval(this.backgroundInterval);
33013
- }
33014
- this.backgroundInterval = setInterval(() => {
33015
- try {
33474
+ registerIdleTasks() {
33475
+ this.activityGate.registerIdleTask("git-sync", () => {
33476
+ if (this.gitStalenessChecker.hasNewCommits()) {
33016
33477
  const synced = this.changeIntelligence.syncFromGit(20);
33017
33478
  if (synced > 0) {
33018
- console.error(`[Background] Synced ${synced} recent changes from git`);
33479
+ this.changeIntelligence.scanForBugFixes();
33480
+ console.error(`Idle sync: ${synced} git changes synced`);
33019
33481
  }
33020
- this.learningEngine.updateImportanceScores();
33021
- } catch (error2) {
33022
- console.error("[Background] Error in intelligence loop:", error2);
33023
33482
  }
33024
- }, this.BACKGROUND_REFRESH_INTERVAL_MS);
33483
+ }, {
33484
+ minIdleMs: 3e4,
33485
+ // 30 seconds idle
33486
+ intervalMs: 6e4
33487
+ // Check at most every minute
33488
+ });
33489
+ this.activityGate.registerIdleTask("importance-update", () => {
33490
+ this.learningEngine.updateImportanceScores();
33491
+ }, {
33492
+ minIdleMs: 6e4,
33493
+ // 1 minute idle
33494
+ intervalMs: 3e5
33495
+ // Max once per 5 minutes
33496
+ });
33497
+ }
33498
+ /**
33499
+ * Sync git changes on-demand with cheap pre-check
33500
+ * Only syncs if HEAD has changed, otherwise returns 0
33501
+ */
33502
+ syncGitChanges(limit = 20) {
33503
+ this.activityGate.recordActivity();
33504
+ try {
33505
+ if (!this.gitStalenessChecker.hasNewCommits()) {
33506
+ return 0;
33507
+ }
33508
+ const synced = this.changeIntelligence.syncFromGit(limit);
33509
+ if (synced > 0) {
33510
+ this.changeIntelligence.scanForBugFixes();
33511
+ }
33512
+ return synced;
33513
+ } catch {
33514
+ return 0;
33515
+ }
33516
+ }
33517
+ /**
33518
+ * Force sync git changes, bypassing the staleness check
33519
+ */
33520
+ forceSyncGitChanges(limit = 20) {
33521
+ this.activityGate.recordActivity();
33522
+ this.gitStalenessChecker.updateCachedHead();
33523
+ try {
33524
+ const synced = this.changeIntelligence.syncFromGit(limit);
33525
+ if (synced > 0) {
33526
+ this.changeIntelligence.scanForBugFixes();
33527
+ }
33528
+ return synced;
33529
+ } catch {
33530
+ return 0;
33531
+ }
33532
+ }
33533
+ /**
33534
+ * Trigger a full refresh of the memory layer
33535
+ * Syncs git changes, updates importance scores, etc.
33536
+ */
33537
+ triggerRefresh() {
33538
+ this.activityGate.recordActivity();
33539
+ this.gitStalenessChecker.updateCachedHead();
33540
+ const gitSynced = this.forceSyncGitChanges();
33541
+ this.learningEngine.updateImportanceScores();
33542
+ return {
33543
+ gitSynced,
33544
+ importanceUpdated: true,
33545
+ tasksExecuted: ["git-sync", "importance-update"]
33546
+ };
33547
+ }
33548
+ /**
33549
+ * Get refresh system status
33550
+ */
33551
+ getRefreshStatus() {
33552
+ const status = this.activityGate.getStatus();
33553
+ return {
33554
+ lastActivity: this.activityGate.getLastActivity(),
33555
+ isIdle: status.isIdle,
33556
+ idleDuration: status.idleDuration,
33557
+ gitHead: this.gitStalenessChecker.getCachedHead(),
33558
+ hasNewCommits: this.gitStalenessChecker.hasNewCommits(),
33559
+ idleTasks: status.tasks.map((t) => ({
33560
+ name: t.name,
33561
+ lastRun: t.lastRun,
33562
+ readyToRun: t.readyToRun
33563
+ }))
33564
+ };
33025
33565
  }
33026
33566
  /**
33027
33567
  * Record AI feedback - learn from what suggestions were actually used
@@ -33036,6 +33576,7 @@ var MemoryLayerEngine = class {
33036
33576
  }
33037
33577
  }
33038
33578
  async getContext(query, currentFile, maxTokens) {
33579
+ this.activityGate.recordActivity();
33039
33580
  this.learningEngine.trackEvent({ eventType: "query", query });
33040
33581
  const expandedQueries = this.learningEngine.expandQuery(query);
33041
33582
  const result = await this.contextAssembler.assemble(query, {
@@ -33050,13 +33591,31 @@ var MemoryLayerEngine = class {
33050
33591
  return result;
33051
33592
  }
33052
33593
  async searchCodebase(query, limit = 10) {
33594
+ this.activityGate.recordActivity();
33053
33595
  const embedding = await this.indexer.getEmbeddingGenerator().embed(query);
33054
33596
  let results = this.tier2.search(embedding, limit * 2);
33055
33597
  results = this.learningEngine.applyPersonalizedRanking(results);
33056
33598
  return results.slice(0, limit);
33057
33599
  }
33058
33600
  async recordDecision(title, description, files, tags) {
33059
- return this.decisionTracker.recordDecision(title, description, files || [], tags || []);
33601
+ this.activityGate.recordActivity();
33602
+ const decision = await this.decisionTracker.recordDecision(title, description, files || [], tags || []);
33603
+ this.livingDocs.getActivityTracker().logActivity(
33604
+ "decision_recorded",
33605
+ `Decision: ${title}`,
33606
+ void 0,
33607
+ { decisionId: decision.id }
33608
+ );
33609
+ this.contextRotPrevention.markCritical(
33610
+ `Decision: ${title}
33611
+ ${description}`,
33612
+ {
33613
+ type: "decision",
33614
+ reason: "Architectural decision",
33615
+ source: "auto"
33616
+ }
33617
+ );
33618
+ return decision;
33060
33619
  }
33061
33620
  getRecentDecisions(limit = 10) {
33062
33621
  return this.decisionTracker.getRecentDecisions(limit);
@@ -33069,6 +33628,7 @@ var MemoryLayerEngine = class {
33069
33628
  return this.tier2.searchDecisions(embedding, limit);
33070
33629
  }
33071
33630
  async getFileContext(filePath) {
33631
+ this.activityGate.recordActivity();
33072
33632
  const absolutePath = join14(this.config.projectPath, filePath);
33073
33633
  if (!existsSync13(absolutePath)) {
33074
33634
  return null;
@@ -33837,11 +34397,8 @@ ${description}`;
33837
34397
  }
33838
34398
  shutdown() {
33839
34399
  console.error("Shutting down MemoryLayer...");
33840
- if (this.backgroundInterval) {
33841
- clearInterval(this.backgroundInterval);
33842
- this.backgroundInterval = null;
33843
- }
33844
34400
  this.indexer.stopWatching();
34401
+ this.activityGate.shutdown();
33845
34402
  this.tier1.save();
33846
34403
  this.featureContextManager.shutdown();
33847
34404
  closeDatabase(this.db);
@@ -37241,6 +37798,45 @@ async function handleToolCall(engine, toolName, args) {
37241
37798
  message: coverage.coveragePercent === 100 ? `Full test coverage for ${file2}` : coverage.uncoveredFunctions.length > 0 ? `${coverage.coveragePercent}% coverage - ${coverage.uncoveredFunctions.length} function(s) need tests` : `${coverage.totalTests} test(s) cover ${file2}`
37242
37799
  };
37243
37800
  }
37801
+ // Intelligent Refresh System tools
37802
+ case "memory_refresh": {
37803
+ const force = args.force;
37804
+ if (force) {
37805
+ const result = engine.triggerRefresh();
37806
+ return {
37807
+ success: true,
37808
+ forced: true,
37809
+ git_synced: result.gitSynced,
37810
+ importance_updated: result.importanceUpdated,
37811
+ tasks_executed: result.tasksExecuted,
37812
+ message: `Force refresh complete: ${result.gitSynced} git changes synced`
37813
+ };
37814
+ } else {
37815
+ const gitSynced = engine.syncGitChanges();
37816
+ return {
37817
+ success: true,
37818
+ forced: false,
37819
+ git_synced: gitSynced,
37820
+ message: gitSynced > 0 ? `Refresh complete: ${gitSynced} git changes synced` : "No new changes to sync"
37821
+ };
37822
+ }
37823
+ }
37824
+ case "get_refresh_status": {
37825
+ const status = engine.getRefreshStatus();
37826
+ return {
37827
+ last_activity: new Date(status.lastActivity).toISOString(),
37828
+ is_idle: status.isIdle,
37829
+ idle_duration_seconds: Math.round(status.idleDuration / 1e3),
37830
+ git_head: status.gitHead?.slice(0, 7) || null,
37831
+ has_pending_changes: status.hasNewCommits,
37832
+ idle_tasks: status.idleTasks.map((t) => ({
37833
+ name: t.name,
37834
+ last_run: t.lastRun > 0 ? new Date(t.lastRun).toISOString() : "never",
37835
+ ready_to_run: t.readyToRun
37836
+ })),
37837
+ message: status.isIdle ? `System idle for ${Math.round(status.idleDuration / 1e3)}s, ${status.idleTasks.filter((t) => t.readyToRun).length} task(s) ready` : "System active"
37838
+ };
37839
+ }
37244
37840
  default:
37245
37841
  throw new Error(`Unknown tool: ${toolName}`);
37246
37842
  }