neuronlayer 0.1.2 → 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.

@@ -0,0 +1,256 @@
1
+ /**
2
+ * ActivityGate - Track user activity and manage idle-time maintenance tasks
3
+ *
4
+ * This component enables intelligent refresh by:
5
+ * 1. Tracking when the user was last active
6
+ * 2. Detecting idle periods
7
+ * 3. Executing maintenance tasks during idle time
8
+ * 4. Ensuring only one idle task runs at a time
9
+ */
10
+
11
+ export interface IdleTask {
12
+ name: string;
13
+ callback: () => void | Promise<void>;
14
+ minIdleMs: number; // Minimum idle time before this task can run
15
+ lastRun: number;
16
+ intervalMs: number; // Minimum time between runs
17
+ }
18
+
19
+ export class ActivityGate {
20
+ private lastActivity: number = Date.now();
21
+ private idleTasks: IdleTask[] = [];
22
+ private monitoringInterval: ReturnType<typeof setInterval> | null = null;
23
+ private isExecutingTask: boolean = false;
24
+ private currentTaskIndex: number = 0;
25
+
26
+ constructor() {
27
+ // Activity is recorded at construction time
28
+ this.recordActivity();
29
+ }
30
+
31
+ /**
32
+ * Record user activity (call this on API method invocations)
33
+ */
34
+ recordActivity(): void {
35
+ this.lastActivity = Date.now();
36
+ }
37
+
38
+ /**
39
+ * Get the timestamp of the last recorded activity
40
+ */
41
+ getLastActivity(): number {
42
+ return this.lastActivity;
43
+ }
44
+
45
+ /**
46
+ * Check if the user has been idle for at least the given threshold
47
+ */
48
+ isIdle(thresholdMs: number = 30_000): boolean {
49
+ return Date.now() - this.lastActivity > thresholdMs;
50
+ }
51
+
52
+ /**
53
+ * Get the duration of the current idle period in milliseconds
54
+ */
55
+ getIdleDuration(): number {
56
+ return Date.now() - this.lastActivity;
57
+ }
58
+
59
+ /**
60
+ * Register an idle-time task
61
+ * Tasks are executed during idle periods, one at a time
62
+ */
63
+ registerIdleTask(
64
+ name: string,
65
+ callback: () => void | Promise<void>,
66
+ options: {
67
+ minIdleMs?: number;
68
+ intervalMs?: number;
69
+ } = {}
70
+ ): void {
71
+ const { minIdleMs = 30_000, intervalMs = 300_000 } = options;
72
+
73
+ // Remove existing task with same name
74
+ this.idleTasks = this.idleTasks.filter(t => t.name !== name);
75
+
76
+ this.idleTasks.push({
77
+ name,
78
+ callback,
79
+ minIdleMs,
80
+ lastRun: 0,
81
+ intervalMs
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Unregister an idle-time task
87
+ */
88
+ unregisterIdleTask(name: string): boolean {
89
+ const initialLength = this.idleTasks.length;
90
+ this.idleTasks = this.idleTasks.filter(t => t.name !== name);
91
+ return this.idleTasks.length < initialLength;
92
+ }
93
+
94
+ /**
95
+ * Get all registered idle tasks
96
+ */
97
+ getIdleTasks(): readonly IdleTask[] {
98
+ return this.idleTasks;
99
+ }
100
+
101
+ /**
102
+ * Start monitoring for idle periods
103
+ * Will check every checkIntervalMs and execute tasks when idle
104
+ */
105
+ startIdleMonitoring(checkIntervalMs: number = 10_000): void {
106
+ if (this.monitoringInterval) {
107
+ return; // Already monitoring
108
+ }
109
+
110
+ this.monitoringInterval = setInterval(() => {
111
+ this.executeNextIdleTaskIfReady();
112
+ }, checkIntervalMs);
113
+ }
114
+
115
+ /**
116
+ * Stop idle monitoring
117
+ */
118
+ stopIdleMonitoring(): void {
119
+ if (this.monitoringInterval) {
120
+ clearInterval(this.monitoringInterval);
121
+ this.monitoringInterval = null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Execute the next idle task if conditions are met
127
+ * - User must be idle
128
+ * - No other task is currently executing
129
+ * - Task's minimum idle time must be met
130
+ * - Task's minimum interval since last run must be met
131
+ */
132
+ private async executeNextIdleTaskIfReady(): Promise<void> {
133
+ if (this.isExecutingTask) {
134
+ return; // Already executing a task
135
+ }
136
+
137
+ if (this.idleTasks.length === 0) {
138
+ return; // No tasks registered
139
+ }
140
+
141
+ const now = Date.now();
142
+ const idleDuration = this.getIdleDuration();
143
+
144
+ // Find the next task that's ready to run
145
+ for (let i = 0; i < this.idleTasks.length; i++) {
146
+ const taskIndex = (this.currentTaskIndex + i) % this.idleTasks.length;
147
+ const task = this.idleTasks[taskIndex];
148
+
149
+ // Safety check (TypeScript strictness)
150
+ if (!task) continue;
151
+
152
+ // Check if task is ready
153
+ if (idleDuration < task.minIdleMs) {
154
+ continue; // Not idle long enough
155
+ }
156
+
157
+ if (now - task.lastRun < task.intervalMs) {
158
+ continue; // Too soon since last run
159
+ }
160
+
161
+ // Execute this task
162
+ this.currentTaskIndex = (taskIndex + 1) % this.idleTasks.length;
163
+ await this.executeTask(task);
164
+ return; // Only execute one task per check
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Execute a specific task
170
+ */
171
+ private async executeTask(task: IdleTask): Promise<void> {
172
+ this.isExecutingTask = true;
173
+
174
+ try {
175
+ await Promise.resolve(task.callback());
176
+ task.lastRun = Date.now();
177
+ } catch (error) {
178
+ console.error(`Idle task '${task.name}' failed:`, error);
179
+ // Still update lastRun to prevent rapid retries
180
+ task.lastRun = Date.now();
181
+ } finally {
182
+ this.isExecutingTask = false;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Manually trigger execution of all ready idle tasks
188
+ * Useful for testing or manual refresh
189
+ */
190
+ async executeReadyTasks(): Promise<string[]> {
191
+ const executed: string[] = [];
192
+ const now = Date.now();
193
+ const idleDuration = this.getIdleDuration();
194
+
195
+ for (const task of this.idleTasks) {
196
+ if (idleDuration >= task.minIdleMs && now - task.lastRun >= task.intervalMs) {
197
+ await this.executeTask(task);
198
+ executed.push(task.name);
199
+ }
200
+ }
201
+
202
+ return executed;
203
+ }
204
+
205
+ /**
206
+ * Force immediate execution of a specific task by name
207
+ * Ignores idle and interval requirements
208
+ */
209
+ async forceExecuteTask(name: string): Promise<boolean> {
210
+ const task = this.idleTasks.find(t => t.name === name);
211
+ if (!task) {
212
+ return false;
213
+ }
214
+
215
+ await this.executeTask(task);
216
+ return true;
217
+ }
218
+
219
+ /**
220
+ * Get status of all idle tasks
221
+ */
222
+ getStatus(): {
223
+ isIdle: boolean;
224
+ idleDuration: number;
225
+ isExecutingTask: boolean;
226
+ tasks: Array<{
227
+ name: string;
228
+ lastRun: number;
229
+ timeSinceRun: number;
230
+ readyToRun: boolean;
231
+ }>;
232
+ } {
233
+ const now = Date.now();
234
+ const idleDuration = this.getIdleDuration();
235
+
236
+ return {
237
+ isIdle: this.isIdle(),
238
+ idleDuration,
239
+ isExecutingTask: this.isExecutingTask,
240
+ tasks: this.idleTasks.map(task => ({
241
+ name: task.name,
242
+ lastRun: task.lastRun,
243
+ timeSinceRun: now - task.lastRun,
244
+ readyToRun: idleDuration >= task.minIdleMs && now - task.lastRun >= task.intervalMs
245
+ }))
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Shutdown the activity gate
251
+ */
252
+ shutdown(): void {
253
+ this.stopIdleMonitoring();
254
+ this.idleTasks = [];
255
+ }
256
+ }
@@ -0,0 +1,108 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ /**
4
+ * GitStalenessChecker - Cheap HEAD caching and change detection
5
+ *
6
+ * Instead of running expensive `git log` operations, we cache the HEAD commit
7
+ * and only run full syncs when HEAD has changed. This reduces polling overhead
8
+ * from ~100ms+ to ~5ms per check.
9
+ */
10
+ export class GitStalenessChecker {
11
+ private cachedHead: string | null = null;
12
+ private lastCheckTime: number = 0;
13
+ private projectPath: string;
14
+
15
+ constructor(projectPath: string) {
16
+ this.projectPath = projectPath;
17
+ }
18
+
19
+ /**
20
+ * Get the current HEAD commit hash
21
+ * This is a cheap operation (~5ms)
22
+ */
23
+ getCurrentHead(): string | null {
24
+ try {
25
+ const head = execSync('git rev-parse HEAD', {
26
+ cwd: this.projectPath,
27
+ encoding: 'utf-8',
28
+ timeout: 5000,
29
+ stdio: ['pipe', 'pipe', 'pipe']
30
+ }).trim();
31
+ return head;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Check if there are new commits since the last check
39
+ * Returns true if HEAD has changed (meaning we need to sync)
40
+ */
41
+ hasNewCommits(): boolean {
42
+ const current = this.getCurrentHead();
43
+ this.lastCheckTime = Date.now();
44
+
45
+ if (current === null) {
46
+ return false; // Not a git repo or git command failed
47
+ }
48
+
49
+ if (this.cachedHead === null) {
50
+ // First check - cache the current HEAD
51
+ this.cachedHead = current;
52
+ return false; // No need to sync on first check (assume we synced on init)
53
+ }
54
+
55
+ if (current !== this.cachedHead) {
56
+ // HEAD has changed - update cache and signal sync needed
57
+ this.cachedHead = current;
58
+ return true;
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Force update the cached HEAD (call after successful sync)
66
+ */
67
+ updateCachedHead(): void {
68
+ this.cachedHead = this.getCurrentHead();
69
+ this.lastCheckTime = Date.now();
70
+ }
71
+
72
+ /**
73
+ * Get the cached HEAD without checking git
74
+ */
75
+ getCachedHead(): string | null {
76
+ return this.cachedHead;
77
+ }
78
+
79
+ /**
80
+ * Get the time of the last check
81
+ */
82
+ getLastCheckTime(): number {
83
+ return this.lastCheckTime;
84
+ }
85
+
86
+ /**
87
+ * Get time since last check in milliseconds
88
+ */
89
+ getTimeSinceLastCheck(): number {
90
+ return Date.now() - this.lastCheckTime;
91
+ }
92
+
93
+ /**
94
+ * Check if enough time has passed since last check
95
+ * Default threshold is 30 seconds
96
+ */
97
+ shouldCheck(thresholdMs: number = 30_000): boolean {
98
+ return this.getTimeSinceLastCheck() > thresholdMs;
99
+ }
100
+
101
+ /**
102
+ * Reset the checker state (useful for testing)
103
+ */
104
+ reset(): void {
105
+ this.cachedHead = null;
106
+ this.lastCheckTime = 0;
107
+ }
108
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Intelligent Refresh System for MemoryLayer
3
+ *
4
+ * This module provides a tiered refresh architecture:
5
+ *
6
+ * TIER 1: REAL-TIME (via file watcher)
7
+ * - File changes → Chokidar watcher → immediate invalidation
8
+ * - User queries → immediate tracking
9
+ * - File access → immediate hot cache update
10
+ *
11
+ * TIER 2: ON-DEMAND WITH CHEAP PRE-CHECK
12
+ * - Git sync → check HEAD first (5ms), only sync if changed
13
+ * - Summaries → check lastModified before regenerating
14
+ * - Bug diagnosis → sync git first if HEAD changed
15
+ *
16
+ * TIER 3: IDLE-TIME MAINTENANCE
17
+ * - When user idle > 30s AND git changed → sync git
18
+ * - When idle > 5min since last update → update importance scores
19
+ * - One task at a time, non-blocking
20
+ *
21
+ * TIER 4: SESSION-BASED
22
+ * - Engine init → full git sync
23
+ * - Shutdown → persist state
24
+ */
25
+
26
+ export { GitStalenessChecker } from './git-staleness-checker.js';
27
+ export { ActivityGate, type IdleTask } from './activity-gate.js';
@@ -246,6 +246,29 @@ export class FileSummarizer {
246
246
  return fileLastModified > summary.generatedAt;
247
247
  }
248
248
 
249
+ // Invalidate summary when file changes (event-driven)
250
+ invalidateSummary(fileId: number): boolean {
251
+ try {
252
+ const result = this.db.prepare('DELETE FROM file_summaries WHERE file_id = ?').run(fileId);
253
+ return result.changes > 0;
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+
259
+ // Invalidate summary by file path
260
+ invalidateSummaryByPath(filePath: string): boolean {
261
+ try {
262
+ const file = this.db.prepare('SELECT id FROM files WHERE path = ?').get(filePath) as { id: number } | undefined;
263
+ if (file) {
264
+ return this.invalidateSummary(file.id);
265
+ }
266
+ return false;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
249
272
  // Get compression ratio stats
250
273
  getCompressionStats(): { totalFiles: number; avgCompression: number; totalTokensSaved: number } {
251
274
  const stmt = this.db.prepare(`
@@ -890,6 +890,28 @@ export const toolDefinitions: ToolDefinition[] = [
890
890
  },
891
891
  required: ['file']
892
892
  }
893
+ },
894
+ // Intelligent Refresh System tools
895
+ {
896
+ name: 'memory_refresh',
897
+ description: 'Trigger a manual refresh of the memory layer. Syncs git changes, updates importance scores, and executes pending maintenance tasks. Use this when you need the latest state after external changes (e.g., git pull).',
898
+ inputSchema: {
899
+ type: 'object',
900
+ properties: {
901
+ force: {
902
+ type: 'boolean',
903
+ description: 'Force refresh even if no changes detected (default: false)'
904
+ }
905
+ }
906
+ }
907
+ },
908
+ {
909
+ name: 'get_refresh_status',
910
+ description: 'Get the current status of the intelligent refresh system including idle tasks, last activity, and git state.',
911
+ inputSchema: {
912
+ type: 'object',
913
+ properties: {}
914
+ }
893
915
  }
894
916
  ];
895
917
 
@@ -2255,6 +2277,54 @@ export async function handleToolCall(
2255
2277
  };
2256
2278
  }
2257
2279
 
2280
+ // Intelligent Refresh System tools
2281
+ case 'memory_refresh': {
2282
+ const force = args.force as boolean | undefined;
2283
+
2284
+ if (force) {
2285
+ const result = engine.triggerRefresh();
2286
+ return {
2287
+ success: true,
2288
+ forced: true,
2289
+ git_synced: result.gitSynced,
2290
+ importance_updated: result.importanceUpdated,
2291
+ tasks_executed: result.tasksExecuted,
2292
+ message: `Force refresh complete: ${result.gitSynced} git changes synced`
2293
+ };
2294
+ } else {
2295
+ // Normal refresh - only sync if there are new commits
2296
+ const gitSynced = engine.syncGitChanges();
2297
+ return {
2298
+ success: true,
2299
+ forced: false,
2300
+ git_synced: gitSynced,
2301
+ message: gitSynced > 0
2302
+ ? `Refresh complete: ${gitSynced} git changes synced`
2303
+ : 'No new changes to sync'
2304
+ };
2305
+ }
2306
+ }
2307
+
2308
+ case 'get_refresh_status': {
2309
+ const status = engine.getRefreshStatus();
2310
+
2311
+ return {
2312
+ last_activity: new Date(status.lastActivity).toISOString(),
2313
+ is_idle: status.isIdle,
2314
+ idle_duration_seconds: Math.round(status.idleDuration / 1000),
2315
+ git_head: status.gitHead?.slice(0, 7) || null,
2316
+ has_pending_changes: status.hasNewCommits,
2317
+ idle_tasks: status.idleTasks.map(t => ({
2318
+ name: t.name,
2319
+ last_run: t.lastRun > 0 ? new Date(t.lastRun).toISOString() : 'never',
2320
+ ready_to_run: t.readyToRun
2321
+ })),
2322
+ message: status.isIdle
2323
+ ? `System idle for ${Math.round(status.idleDuration / 1000)}s, ${status.idleTasks.filter(t => t.readyToRun).length} task(s) ready`
2324
+ : 'System active'
2325
+ };
2326
+ }
2327
+
2258
2328
  default:
2259
2329
  throw new Error(`Unknown tool: ${toolName}`);
2260
2330
  }
@@ -252,6 +252,15 @@ export function initializeDatabase(dbPath: string): Database.Database {
252
252
  CREATE INDEX IF NOT EXISTS idx_test_index_file ON test_index(file_path);
253
253
  CREATE INDEX IF NOT EXISTS idx_test_index_name ON test_index(test_name);
254
254
  CREATE INDEX IF NOT EXISTS idx_test_covers_files ON test_index(covers_files);
255
+
256
+ -- Intelligent Refresh System: State persistence
257
+ CREATE TABLE IF NOT EXISTS refresh_state (
258
+ key TEXT PRIMARY KEY,
259
+ value TEXT NOT NULL,
260
+ updated_at INTEGER DEFAULT (unixepoch())
261
+ );
262
+
263
+ CREATE INDEX IF NOT EXISTS idx_refresh_state_key ON refresh_state(key);
255
264
  `);
256
265
 
257
266
  return db;
package/CONTRIBUTING.md DELETED
@@ -1,127 +0,0 @@
1
- # Contributing to MemoryLayer
2
-
3
- Thank you for your interest in contributing to MemoryLayer! This document provides guidelines and information for contributors.
4
-
5
- ## Code of Conduct
6
-
7
- Be respectful, inclusive, and constructive. We're all here to build something great together.
8
-
9
- ## How to Contribute
10
-
11
- ### Reporting Bugs
12
-
13
- 1. Check existing issues to avoid duplicates
14
- 2. Use a clear, descriptive title
15
- 3. Include:
16
- - Steps to reproduce
17
- - Expected vs actual behavior
18
- - Environment (OS, Node.js version)
19
- - Relevant logs or error messages
20
-
21
- ### Suggesting Features
22
-
23
- 1. Check if the feature is already planned (see Roadmap in README)
24
- 2. Open an issue with `[Feature]` prefix
25
- 3. Describe the use case and proposed solution
26
- 4. Be open to discussion and alternatives
27
-
28
- ### Pull Requests
29
-
30
- 1. Fork the repository
31
- 2. Create a feature branch from `main`
32
- 3. Make your changes
33
- 4. Write/update tests as needed
34
- 5. Run `npm run typecheck` and `npm test`
35
- 6. Submit a PR with a clear description
36
-
37
- ## Development Setup
38
-
39
- ```bash
40
- # Clone your fork
41
- git clone https://github.com/YOUR_USERNAME/memorylayer.git
42
- cd memorylayer
43
-
44
- # Install dependencies
45
- npm install
46
-
47
- # Build
48
- npm run build
49
-
50
- # Run tests
51
- npm test
52
-
53
- # Type check
54
- npm run typecheck
55
- ```
56
-
57
- ## Project Structure
58
-
59
- ```
60
- src/
61
- ├── core/ # Business logic (engine, features)
62
- ├── server/ # MCP server and tools
63
- ├── storage/ # Data persistence (SQLite, vectors)
64
- ├── indexing/ # File indexing and AST
65
- ├── types/ # TypeScript type definitions
66
- ├── utils/ # Shared utilities
67
- └── agent/ # Standalone agent (optional)
68
- ```
69
-
70
- ## Coding Standards
71
-
72
- ### TypeScript
73
-
74
- - Use TypeScript strict mode
75
- - Prefer explicit types over `any`
76
- - Use interfaces for object shapes
77
- - Export types from `src/types/`
78
-
79
- ### Code Style
80
-
81
- - Use meaningful variable/function names
82
- - Keep functions focused and small
83
- - Add comments for non-obvious logic
84
- - Follow existing patterns in the codebase
85
-
86
- ### Testing
87
-
88
- - Write tests for new features
89
- - Tests go in `__tests__/` directories or `*.test.ts` files
90
- - Use Vitest for testing
91
-
92
- ### Commits
93
-
94
- - Use clear, descriptive commit messages
95
- - Follow conventional commits when possible:
96
- - `feat:` new features
97
- - `fix:` bug fixes
98
- - `docs:` documentation
99
- - `refactor:` code restructuring
100
- - `test:` test additions/changes
101
-
102
- ## Areas Where Help Is Needed
103
-
104
- ### High Priority
105
-
106
- 1. **Language Support** - Add AST parsing for Python, Go, Rust
107
- 2. **Bug Fixes** - Check open issues
108
- 3. **Test Coverage** - Increase test coverage
109
-
110
- ### Medium Priority
111
-
112
- 1. **Documentation** - Improve README, add examples
113
- 2. **Performance** - Optimize indexing and search
114
- 3. **IDE Extensions** - VS Code extension
115
-
116
- ### Good First Issues
117
-
118
- Look for issues labeled `good-first-issue` for beginner-friendly tasks.
119
-
120
- ## Questions?
121
-
122
- - Open a Discussion on GitHub
123
- - Check existing issues and documentation
124
-
125
- ## License
126
-
127
- By contributing, you agree that your contributions will be licensed under the MIT License.