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 +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
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
33472
|
+
* Register idle-time maintenance tasks
|
|
33009
33473
|
*/
|
|
33010
|
-
|
|
33011
|
-
|
|
33012
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
}
|