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.
- package/README.md +100 -181
- package/dist/index.js +618 -22
- package/package.json +3 -12
- package/src/core/change-intelligence/bug-correlator.ts +100 -0
- package/src/core/change-intelligence/change-intelligence.ts +43 -0
- package/src/core/engine.ts +173 -32
- package/src/core/learning.ts +34 -0
- package/src/core/living-docs/doc-engine.ts +24 -0
- package/src/core/refresh/activity-gate.ts +256 -0
- package/src/core/refresh/git-staleness-checker.ts +108 -0
- package/src/core/refresh/index.ts +27 -0
- package/src/core/summarizer.ts +23 -0
- package/src/server/tools.ts +70 -0
- package/src/storage/database.ts +9 -0
- package/CONTRIBUTING.md +0 -127
|
@@ -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';
|
package/src/core/summarizer.ts
CHANGED
|
@@ -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(`
|
package/src/server/tools.ts
CHANGED
|
@@ -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
|
}
|
package/src/storage/database.ts
CHANGED
|
@@ -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.
|