engrm 0.4.5 → 0.4.7
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/README.md +160 -90
- package/dist/cli.js +128 -1
- package/dist/hooks/elicitation-result.js +82 -1
- package/dist/hooks/post-tool-use.js +82 -1
- package/dist/hooks/pre-compact.js +30 -5
- package/dist/hooks/session-start.js +127 -22
- package/dist/hooks/stop.js +389 -29
- package/dist/server.js +361 -19
- package/package.json +3 -2
package/dist/server.js
CHANGED
|
@@ -14827,6 +14827,80 @@ function findDuplicate(newTitle, candidates) {
|
|
|
14827
14827
|
return bestMatch;
|
|
14828
14828
|
}
|
|
14829
14829
|
|
|
14830
|
+
// src/capture/facts.ts
|
|
14831
|
+
var FACT_ELIGIBLE_TYPES = new Set([
|
|
14832
|
+
"bugfix",
|
|
14833
|
+
"decision",
|
|
14834
|
+
"discovery",
|
|
14835
|
+
"pattern",
|
|
14836
|
+
"feature",
|
|
14837
|
+
"refactor",
|
|
14838
|
+
"change"
|
|
14839
|
+
]);
|
|
14840
|
+
function buildStructuredFacts(input) {
|
|
14841
|
+
const seedFacts = dedupeFacts(input.facts ?? []);
|
|
14842
|
+
if (!FACT_ELIGIBLE_TYPES.has(input.type)) {
|
|
14843
|
+
return seedFacts;
|
|
14844
|
+
}
|
|
14845
|
+
const derived = [...seedFacts];
|
|
14846
|
+
if (seedFacts.length === 0 && looksMeaningful(input.title)) {
|
|
14847
|
+
derived.push(input.title.trim());
|
|
14848
|
+
}
|
|
14849
|
+
for (const sentence of extractNarrativeFacts(input.narrative)) {
|
|
14850
|
+
derived.push(sentence);
|
|
14851
|
+
}
|
|
14852
|
+
const fileFact = buildFilesFact(input.filesModified);
|
|
14853
|
+
if (fileFact) {
|
|
14854
|
+
derived.push(fileFact);
|
|
14855
|
+
}
|
|
14856
|
+
return dedupeFacts(derived).slice(0, 4);
|
|
14857
|
+
}
|
|
14858
|
+
function extractNarrativeFacts(narrative) {
|
|
14859
|
+
if (!narrative)
|
|
14860
|
+
return [];
|
|
14861
|
+
const cleaned = narrative.replace(/\s+/g, " ").trim();
|
|
14862
|
+
if (cleaned.length < 24)
|
|
14863
|
+
return [];
|
|
14864
|
+
const parts = cleaned.split(/(?<=[.!?;])\s+/).map((part) => part.trim().replace(/[.!?;]+$/, "")).filter(Boolean).filter(looksMeaningful);
|
|
14865
|
+
return parts.slice(0, 2);
|
|
14866
|
+
}
|
|
14867
|
+
function buildFilesFact(filesModified) {
|
|
14868
|
+
if (!filesModified || filesModified.length === 0)
|
|
14869
|
+
return null;
|
|
14870
|
+
const cleaned = filesModified.map((file2) => file2.trim()).filter(Boolean).slice(0, 3);
|
|
14871
|
+
if (cleaned.length === 0)
|
|
14872
|
+
return null;
|
|
14873
|
+
if (cleaned.length === 1) {
|
|
14874
|
+
return `Touched ${cleaned[0]}`;
|
|
14875
|
+
}
|
|
14876
|
+
return `Touched ${cleaned.join(", ")}`;
|
|
14877
|
+
}
|
|
14878
|
+
function dedupeFacts(facts) {
|
|
14879
|
+
const seen = new Set;
|
|
14880
|
+
const result = [];
|
|
14881
|
+
for (const fact of facts) {
|
|
14882
|
+
const cleaned = fact.trim().replace(/\s+/g, " ");
|
|
14883
|
+
if (!looksMeaningful(cleaned))
|
|
14884
|
+
continue;
|
|
14885
|
+
const key = cleaned.toLowerCase().replace(/\([^)]*\)/g, "").replace(/\s+/g, " ").trim();
|
|
14886
|
+
if (!key || seen.has(key))
|
|
14887
|
+
continue;
|
|
14888
|
+
seen.add(key);
|
|
14889
|
+
result.push(cleaned);
|
|
14890
|
+
}
|
|
14891
|
+
return result;
|
|
14892
|
+
}
|
|
14893
|
+
function looksMeaningful(value) {
|
|
14894
|
+
const cleaned = value.trim();
|
|
14895
|
+
if (cleaned.length < 12)
|
|
14896
|
+
return false;
|
|
14897
|
+
if (/^[A-Za-z0-9_.\-\/]+\.[A-Za-z0-9]+$/.test(cleaned))
|
|
14898
|
+
return false;
|
|
14899
|
+
if (/^(updated|modified|edited|changed|touched)\s+[A-Za-z0-9_.\-\/]+$/i.test(cleaned))
|
|
14900
|
+
return false;
|
|
14901
|
+
return true;
|
|
14902
|
+
}
|
|
14903
|
+
|
|
14830
14904
|
// src/storage/projects.ts
|
|
14831
14905
|
import { execSync } from "node:child_process";
|
|
14832
14906
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -15218,10 +15292,17 @@ async function saveObservation(db, config2, input) {
|
|
|
15218
15292
|
const customPatterns = config2.scrubbing.enabled ? config2.scrubbing.custom_patterns : [];
|
|
15219
15293
|
const title = config2.scrubbing.enabled ? scrubSecrets(input.title, customPatterns) : input.title;
|
|
15220
15294
|
const narrative = input.narrative ? config2.scrubbing.enabled ? scrubSecrets(input.narrative, customPatterns) : input.narrative : null;
|
|
15221
|
-
const factsJson = input.facts ? config2.scrubbing.enabled ? scrubSecrets(JSON.stringify(input.facts), customPatterns) : JSON.stringify(input.facts) : null;
|
|
15222
15295
|
const conceptsJson = input.concepts ? JSON.stringify(input.concepts) : null;
|
|
15223
15296
|
const filesRead = input.files_read ? input.files_read.map((f) => toRelativePath(f, cwd)) : null;
|
|
15224
15297
|
const filesModified = input.files_modified ? input.files_modified.map((f) => toRelativePath(f, cwd)) : null;
|
|
15298
|
+
const structuredFacts = buildStructuredFacts({
|
|
15299
|
+
type: input.type,
|
|
15300
|
+
title: input.title,
|
|
15301
|
+
narrative: input.narrative,
|
|
15302
|
+
facts: input.facts,
|
|
15303
|
+
filesModified
|
|
15304
|
+
});
|
|
15305
|
+
const factsJson = structuredFacts.length > 0 ? config2.scrubbing.enabled ? scrubSecrets(JSON.stringify(structuredFacts), customPatterns) : JSON.stringify(structuredFacts) : null;
|
|
15225
15306
|
const filesReadJson = filesRead ? JSON.stringify(filesRead) : null;
|
|
15226
15307
|
const filesModifiedJson = filesModified ? JSON.stringify(filesModified) : null;
|
|
15227
15308
|
let sensitivity = input.sensitivity ?? config2.scrubbing.default_sensitivity;
|
|
@@ -15326,6 +15407,69 @@ function toRelativePath(filePath, projectRoot) {
|
|
|
15326
15407
|
return rel;
|
|
15327
15408
|
}
|
|
15328
15409
|
|
|
15410
|
+
// src/intelligence/observation-priority.ts
|
|
15411
|
+
var RECENCY_WINDOW_SECONDS = 30 * 86400;
|
|
15412
|
+
function computeBlendedScore(quality, createdAtEpoch, nowEpoch) {
|
|
15413
|
+
const age = nowEpoch - createdAtEpoch;
|
|
15414
|
+
const recencyNorm = Math.max(0, Math.min(1, 1 - age / RECENCY_WINDOW_SECONDS));
|
|
15415
|
+
return quality * 0.6 + recencyNorm * 0.4;
|
|
15416
|
+
}
|
|
15417
|
+
function observationTypeBoost(type) {
|
|
15418
|
+
switch (type) {
|
|
15419
|
+
case "decision":
|
|
15420
|
+
return 0.2;
|
|
15421
|
+
case "pattern":
|
|
15422
|
+
return 0.18;
|
|
15423
|
+
case "bugfix":
|
|
15424
|
+
return 0.14;
|
|
15425
|
+
case "feature":
|
|
15426
|
+
return 0.12;
|
|
15427
|
+
case "discovery":
|
|
15428
|
+
return 0.1;
|
|
15429
|
+
case "refactor":
|
|
15430
|
+
return 0.05;
|
|
15431
|
+
case "digest":
|
|
15432
|
+
return 0.03;
|
|
15433
|
+
case "change":
|
|
15434
|
+
return 0;
|
|
15435
|
+
default:
|
|
15436
|
+
return 0;
|
|
15437
|
+
}
|
|
15438
|
+
}
|
|
15439
|
+
function computeObservationPriority(obs, nowEpoch) {
|
|
15440
|
+
return computeBlendedScore(obs.quality, obs.created_at_epoch, nowEpoch) + observationTypeBoost(obs.type);
|
|
15441
|
+
}
|
|
15442
|
+
function textIncludesQuery(text, query) {
|
|
15443
|
+
return Boolean(text && query && text.toLowerCase().includes(query));
|
|
15444
|
+
}
|
|
15445
|
+
function tokenOverlapBoost(text, queryTokens) {
|
|
15446
|
+
if (!text || queryTokens.length === 0)
|
|
15447
|
+
return 0;
|
|
15448
|
+
const lower = text.toLowerCase();
|
|
15449
|
+
const matched = queryTokens.filter((token) => lower.includes(token)).length;
|
|
15450
|
+
if (matched === 0)
|
|
15451
|
+
return 0;
|
|
15452
|
+
return Math.min(0.12, matched * 0.03);
|
|
15453
|
+
}
|
|
15454
|
+
function computeSearchRank(obs, baseScore, query, nowEpoch) {
|
|
15455
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
15456
|
+
const queryTokens = normalizedQuery.split(/\s+/).map((token) => token.trim()).filter((token) => token.length >= 3);
|
|
15457
|
+
let matchBoost = 0;
|
|
15458
|
+
if (textIncludesQuery(obs.title, normalizedQuery))
|
|
15459
|
+
matchBoost += 0.2;
|
|
15460
|
+
if (textIncludesQuery(obs.facts, normalizedQuery))
|
|
15461
|
+
matchBoost += 0.15;
|
|
15462
|
+
if (textIncludesQuery(obs.narrative, normalizedQuery))
|
|
15463
|
+
matchBoost += 0.08;
|
|
15464
|
+
matchBoost += tokenOverlapBoost(obs.title, queryTokens);
|
|
15465
|
+
matchBoost += tokenOverlapBoost(obs.facts, queryTokens);
|
|
15466
|
+
matchBoost += tokenOverlapBoost(obs.narrative, queryTokens) * 0.6;
|
|
15467
|
+
const lifecycleWeight = obs.lifecycle === "aging" ? 0.7 : 1;
|
|
15468
|
+
const retrievalScore = baseScore * 20 * lifecycleWeight;
|
|
15469
|
+
const priorityBoost = computeObservationPriority(obs, nowEpoch) * 0.12;
|
|
15470
|
+
return retrievalScore + priorityBoost + Math.min(0.35, matchBoost);
|
|
15471
|
+
}
|
|
15472
|
+
|
|
15329
15473
|
// src/tools/search.ts
|
|
15330
15474
|
async function searchObservations(db, input) {
|
|
15331
15475
|
const query = input.query.trim();
|
|
@@ -15370,9 +15514,9 @@ async function searchObservations(db, input) {
|
|
|
15370
15514
|
}
|
|
15371
15515
|
}
|
|
15372
15516
|
}
|
|
15517
|
+
const nowEpoch = Math.floor(Date.now() / 1000);
|
|
15373
15518
|
const entries = active.map((obs) => {
|
|
15374
15519
|
const baseScore = scoreMap.get(obs.id) ?? 0;
|
|
15375
|
-
const lifecycleWeight = obs.lifecycle === "aging" ? 0.7 : 1;
|
|
15376
15520
|
return {
|
|
15377
15521
|
id: obs.id,
|
|
15378
15522
|
type: obs.type,
|
|
@@ -15384,7 +15528,7 @@ async function searchObservations(db, input) {
|
|
|
15384
15528
|
quality: obs.quality,
|
|
15385
15529
|
lifecycle: obs.lifecycle,
|
|
15386
15530
|
created_at: obs.created_at,
|
|
15387
|
-
rank: baseScore
|
|
15531
|
+
rank: computeSearchRank(obs, baseScore, query, nowEpoch),
|
|
15388
15532
|
...!projectScoped ? { project_name: projectNameCache.get(obs.project_id) } : {}
|
|
15389
15533
|
};
|
|
15390
15534
|
});
|
|
@@ -15575,16 +15719,136 @@ function getOutboxStats(db) {
|
|
|
15575
15719
|
return stats;
|
|
15576
15720
|
}
|
|
15577
15721
|
|
|
15722
|
+
// src/intelligence/value-signals.ts
|
|
15723
|
+
var LESSON_TYPES = new Set(["bugfix", "decision", "pattern"]);
|
|
15724
|
+
function computeSessionValueSignals(observations, securityFindings = []) {
|
|
15725
|
+
const decisionsCount = observations.filter((o) => o.type === "decision").length;
|
|
15726
|
+
const lessonsCount = observations.filter((o) => LESSON_TYPES.has(o.type)).length;
|
|
15727
|
+
const discoveriesCount = observations.filter((o) => o.type === "discovery").length;
|
|
15728
|
+
const featuresCount = observations.filter((o) => o.type === "feature").length;
|
|
15729
|
+
const refactorsCount = observations.filter((o) => o.type === "refactor").length;
|
|
15730
|
+
const repeatedPatternsCount = observations.filter((o) => o.type === "pattern").length;
|
|
15731
|
+
const hasRequestSignal = observations.some((o) => ["feature", "decision", "change", "bugfix", "discovery"].includes(o.type));
|
|
15732
|
+
const hasCompletionSignal = observations.some((o) => ["feature", "change", "refactor", "bugfix"].includes(o.type));
|
|
15733
|
+
return {
|
|
15734
|
+
decisions_count: decisionsCount,
|
|
15735
|
+
lessons_count: lessonsCount,
|
|
15736
|
+
discoveries_count: discoveriesCount,
|
|
15737
|
+
features_count: featuresCount,
|
|
15738
|
+
refactors_count: refactorsCount,
|
|
15739
|
+
repeated_patterns_count: repeatedPatternsCount,
|
|
15740
|
+
security_findings_count: securityFindings.length,
|
|
15741
|
+
critical_security_findings_count: securityFindings.filter((f) => f.severity === "critical").length,
|
|
15742
|
+
delivery_review_ready: hasRequestSignal && hasCompletionSignal,
|
|
15743
|
+
vibe_guardian_active: securityFindings.length > 0
|
|
15744
|
+
};
|
|
15745
|
+
}
|
|
15746
|
+
|
|
15747
|
+
// src/intelligence/session-insights.ts
|
|
15748
|
+
function computeSessionInsights(summaries, observations) {
|
|
15749
|
+
const orderedSummaries = [...summaries].sort((a, b) => (b.created_at_epoch ?? 0) - (a.created_at_epoch ?? 0));
|
|
15750
|
+
const summaryCount = orderedSummaries.length;
|
|
15751
|
+
const summariesWithLearned = orderedSummaries.filter((s) => hasContent(s.learned)).length;
|
|
15752
|
+
const summariesWithCompleted = orderedSummaries.filter((s) => hasContent(s.completed)).length;
|
|
15753
|
+
const summariesWithNextSteps = orderedSummaries.filter((s) => hasContent(s.next_steps)).length;
|
|
15754
|
+
const totalSummarySectionsPresent = orderedSummaries.reduce((total, summary) => total + countPresentSections(summary), 0);
|
|
15755
|
+
const recentRequests = dedupeLines(orderedSummaries.map((summary) => summary.request?.trim() ?? "").filter(Boolean)).slice(0, 3);
|
|
15756
|
+
const recentLessons = dedupeLines([
|
|
15757
|
+
...orderedSummaries.flatMap((summary) => extractSectionItems(summary.learned)),
|
|
15758
|
+
...extractObservationTitles(observations, ["decision", "pattern", "bugfix"])
|
|
15759
|
+
]).slice(0, 4);
|
|
15760
|
+
const recentCompleted = dedupeLines([
|
|
15761
|
+
...orderedSummaries.flatMap((summary) => extractSectionItems(summary.completed)),
|
|
15762
|
+
...extractObservationTitles(observations, ["feature", "refactor", "change"])
|
|
15763
|
+
]).slice(0, 4);
|
|
15764
|
+
const nextSteps = dedupeLines([
|
|
15765
|
+
...orderedSummaries.flatMap((summary) => extractSectionItems(summary.next_steps)),
|
|
15766
|
+
...extractObservationTitles(observations, ["decision"]).map((title) => `Follow through: ${title}`)
|
|
15767
|
+
]).slice(0, 4);
|
|
15768
|
+
return {
|
|
15769
|
+
summary_count: summaryCount,
|
|
15770
|
+
summaries_with_learned: summariesWithLearned,
|
|
15771
|
+
summaries_with_completed: summariesWithCompleted,
|
|
15772
|
+
summaries_with_next_steps: summariesWithNextSteps,
|
|
15773
|
+
total_summary_sections_present: totalSummarySectionsPresent,
|
|
15774
|
+
recent_requests: recentRequests,
|
|
15775
|
+
recent_lessons: recentLessons,
|
|
15776
|
+
recent_completed: recentCompleted,
|
|
15777
|
+
next_steps: nextSteps
|
|
15778
|
+
};
|
|
15779
|
+
}
|
|
15780
|
+
function countPresentSections(summary) {
|
|
15781
|
+
return [
|
|
15782
|
+
summary.request,
|
|
15783
|
+
summary.investigated,
|
|
15784
|
+
summary.learned,
|
|
15785
|
+
summary.completed,
|
|
15786
|
+
summary.next_steps
|
|
15787
|
+
].filter(hasContent).length;
|
|
15788
|
+
}
|
|
15789
|
+
function extractSectionItems(section) {
|
|
15790
|
+
if (!hasContent(section))
|
|
15791
|
+
return [];
|
|
15792
|
+
return section.split(`
|
|
15793
|
+
`).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean);
|
|
15794
|
+
}
|
|
15795
|
+
function extractObservationTitles(observations, types) {
|
|
15796
|
+
const typeSet = new Set(types);
|
|
15797
|
+
return observations.filter((obs) => typeSet.has(obs.type)).sort((a, b) => b.created_at_epoch - a.created_at_epoch).map((obs) => obs.title.trim()).filter(Boolean);
|
|
15798
|
+
}
|
|
15799
|
+
function dedupeLines(lines) {
|
|
15800
|
+
const seen = new Set;
|
|
15801
|
+
const result = [];
|
|
15802
|
+
for (const line of lines) {
|
|
15803
|
+
const cleaned = line.replace(/\s+/g, " ").trim();
|
|
15804
|
+
if (!cleaned)
|
|
15805
|
+
continue;
|
|
15806
|
+
const key = cleaned.toLowerCase();
|
|
15807
|
+
if (seen.has(key))
|
|
15808
|
+
continue;
|
|
15809
|
+
seen.add(key);
|
|
15810
|
+
result.push(cleaned);
|
|
15811
|
+
}
|
|
15812
|
+
return result;
|
|
15813
|
+
}
|
|
15814
|
+
function hasContent(value) {
|
|
15815
|
+
return Boolean(value && value.trim());
|
|
15816
|
+
}
|
|
15817
|
+
|
|
15578
15818
|
// src/tools/stats.ts
|
|
15579
15819
|
function getMemoryStats(db) {
|
|
15580
15820
|
const activeObservations = db.getActiveObservationCount();
|
|
15581
15821
|
const messages = db.db.query(`SELECT COUNT(*) as count FROM observations
|
|
15582
15822
|
WHERE type = 'message' AND lifecycle IN ('active', 'aging', 'pinned')`).get()?.count ?? 0;
|
|
15583
15823
|
const sessionSummaries = db.db.query("SELECT COUNT(*) as count FROM session_summaries").get()?.count ?? 0;
|
|
15824
|
+
const observations = db.db.query(`SELECT * FROM observations
|
|
15825
|
+
WHERE lifecycle IN ('active', 'aging', 'pinned') AND superseded_by IS NULL`).all();
|
|
15826
|
+
const securityFindings = db.db.query("SELECT * FROM security_findings ORDER BY created_at_epoch DESC LIMIT 500").all();
|
|
15827
|
+
const summaries = db.db.query("SELECT * FROM session_summaries ORDER BY created_at_epoch DESC LIMIT 50").all();
|
|
15828
|
+
const signals = computeSessionValueSignals(observations, securityFindings);
|
|
15829
|
+
const insights = computeSessionInsights(summaries, observations);
|
|
15584
15830
|
return {
|
|
15585
15831
|
active_observations: activeObservations,
|
|
15586
15832
|
messages,
|
|
15587
15833
|
session_summaries: sessionSummaries,
|
|
15834
|
+
decisions: signals.decisions_count,
|
|
15835
|
+
lessons: signals.lessons_count,
|
|
15836
|
+
discoveries: signals.discoveries_count,
|
|
15837
|
+
features: signals.features_count,
|
|
15838
|
+
refactors: signals.refactors_count,
|
|
15839
|
+
repeated_patterns: signals.repeated_patterns_count,
|
|
15840
|
+
security_findings: signals.security_findings_count,
|
|
15841
|
+
critical_security_findings: signals.critical_security_findings_count,
|
|
15842
|
+
delivery_review_ready: signals.delivery_review_ready,
|
|
15843
|
+
vibe_guardian_active: signals.vibe_guardian_active,
|
|
15844
|
+
summaries_with_learned: insights.summaries_with_learned,
|
|
15845
|
+
summaries_with_completed: insights.summaries_with_completed,
|
|
15846
|
+
summaries_with_next_steps: insights.summaries_with_next_steps,
|
|
15847
|
+
total_summary_sections_present: insights.total_summary_sections_present,
|
|
15848
|
+
recent_requests: insights.recent_requests,
|
|
15849
|
+
recent_lessons: insights.recent_lessons,
|
|
15850
|
+
recent_completed: insights.recent_completed,
|
|
15851
|
+
next_steps: insights.next_steps,
|
|
15588
15852
|
installed_packs: db.getInstalledPacks(),
|
|
15589
15853
|
outbox: getOutboxStats(db)
|
|
15590
15854
|
};
|
|
@@ -15764,12 +16028,6 @@ function findStaleDecisionsGlobal(db, options) {
|
|
|
15764
16028
|
}
|
|
15765
16029
|
|
|
15766
16030
|
// src/context/inject.ts
|
|
15767
|
-
var RECENCY_WINDOW_SECONDS = 30 * 86400;
|
|
15768
|
-
function computeBlendedScore(quality, createdAtEpoch, nowEpoch) {
|
|
15769
|
-
const age = nowEpoch - createdAtEpoch;
|
|
15770
|
-
const recencyNorm = Math.max(0, Math.min(1, 1 - age / RECENCY_WINDOW_SECONDS));
|
|
15771
|
-
return quality * 0.6 + recencyNorm * 0.4;
|
|
15772
|
-
}
|
|
15773
16031
|
function estimateTokens(text) {
|
|
15774
16032
|
if (!text)
|
|
15775
16033
|
return 0;
|
|
@@ -15862,10 +16120,8 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15862
16120
|
}
|
|
15863
16121
|
const nowEpoch = Math.floor(Date.now() / 1000);
|
|
15864
16122
|
const sorted = [...deduped].sort((a, b) => {
|
|
15865
|
-
const
|
|
15866
|
-
const
|
|
15867
|
-
const scoreA = computeBlendedScore(a.quality, a.created_at_epoch, nowEpoch) + boostA;
|
|
15868
|
-
const scoreB = computeBlendedScore(b.quality, b.created_at_epoch, nowEpoch) + boostB;
|
|
16123
|
+
const scoreA = computeObservationPriority(a, nowEpoch);
|
|
16124
|
+
const scoreB = computeObservationPriority(b, nowEpoch);
|
|
15869
16125
|
return scoreB - scoreA;
|
|
15870
16126
|
});
|
|
15871
16127
|
const projectName = project?.name ?? detected.name;
|
|
@@ -16504,7 +16760,7 @@ function buildVectorDocument(obs, config2, project) {
|
|
|
16504
16760
|
}
|
|
16505
16761
|
};
|
|
16506
16762
|
}
|
|
16507
|
-
function buildSummaryVectorDocument(summary, config2, project) {
|
|
16763
|
+
function buildSummaryVectorDocument(summary, config2, project, observations = []) {
|
|
16508
16764
|
const parts = [];
|
|
16509
16765
|
if (summary.request)
|
|
16510
16766
|
parts.push(`Request: ${summary.request}`);
|
|
@@ -16516,6 +16772,7 @@ function buildSummaryVectorDocument(summary, config2, project) {
|
|
|
16516
16772
|
parts.push(`Completed: ${summary.completed}`);
|
|
16517
16773
|
if (summary.next_steps)
|
|
16518
16774
|
parts.push(`Next Steps: ${summary.next_steps}`);
|
|
16775
|
+
const valueSignals = computeSessionValueSignals(observations, []);
|
|
16519
16776
|
return {
|
|
16520
16777
|
site_id: config2.site_id,
|
|
16521
16778
|
namespace: config2.namespace,
|
|
@@ -16534,6 +16791,19 @@ function buildSummaryVectorDocument(summary, config2, project) {
|
|
|
16534
16791
|
learned: summary.learned,
|
|
16535
16792
|
completed: summary.completed,
|
|
16536
16793
|
next_steps: summary.next_steps,
|
|
16794
|
+
summary_sections_present: countPresentSections2(summary),
|
|
16795
|
+
investigated_items: extractSectionItems2(summary.investigated),
|
|
16796
|
+
learned_items: extractSectionItems2(summary.learned),
|
|
16797
|
+
completed_items: extractSectionItems2(summary.completed),
|
|
16798
|
+
next_step_items: extractSectionItems2(summary.next_steps),
|
|
16799
|
+
decisions_count: valueSignals.decisions_count,
|
|
16800
|
+
lessons_count: valueSignals.lessons_count,
|
|
16801
|
+
discoveries_count: valueSignals.discoveries_count,
|
|
16802
|
+
features_count: valueSignals.features_count,
|
|
16803
|
+
refactors_count: valueSignals.refactors_count,
|
|
16804
|
+
repeated_patterns_count: valueSignals.repeated_patterns_count,
|
|
16805
|
+
delivery_review_ready: valueSignals.delivery_review_ready,
|
|
16806
|
+
vibe_guardian_active: valueSignals.vibe_guardian_active,
|
|
16537
16807
|
created_at_epoch: summary.created_at_epoch,
|
|
16538
16808
|
local_id: summary.id
|
|
16539
16809
|
}
|
|
@@ -16563,10 +16833,11 @@ async function pushOutbox(db, client, config2, batchSize = 50) {
|
|
|
16563
16833
|
continue;
|
|
16564
16834
|
}
|
|
16565
16835
|
markSyncing(db, entry.id);
|
|
16836
|
+
const summaryObservations = db.getObservationsBySession(summary.session_id);
|
|
16566
16837
|
const doc3 = buildSummaryVectorDocument(summary, config2, {
|
|
16567
16838
|
canonical_id: project2.canonical_id,
|
|
16568
16839
|
name: project2.name
|
|
16569
|
-
});
|
|
16840
|
+
}, summaryObservations);
|
|
16570
16841
|
batch.push({ entryId: entry.id, doc: doc3 });
|
|
16571
16842
|
continue;
|
|
16572
16843
|
}
|
|
@@ -16625,6 +16896,21 @@ async function pushOutbox(db, client, config2, batchSize = 50) {
|
|
|
16625
16896
|
}
|
|
16626
16897
|
return { pushed, failed, skipped };
|
|
16627
16898
|
}
|
|
16899
|
+
function countPresentSections2(summary) {
|
|
16900
|
+
return [
|
|
16901
|
+
summary.request,
|
|
16902
|
+
summary.investigated,
|
|
16903
|
+
summary.learned,
|
|
16904
|
+
summary.completed,
|
|
16905
|
+
summary.next_steps
|
|
16906
|
+
].filter((value) => Boolean(value && value.trim())).length;
|
|
16907
|
+
}
|
|
16908
|
+
function extractSectionItems2(section) {
|
|
16909
|
+
if (!section)
|
|
16910
|
+
return [];
|
|
16911
|
+
return section.split(`
|
|
16912
|
+
`).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).slice(0, 4);
|
|
16913
|
+
}
|
|
16628
16914
|
|
|
16629
16915
|
// src/sync/pull.ts
|
|
16630
16916
|
var PULL_CURSOR_KEY = "pull_cursor";
|
|
@@ -16947,7 +17233,7 @@ process.on("SIGTERM", () => {
|
|
|
16947
17233
|
});
|
|
16948
17234
|
var server = new McpServer({
|
|
16949
17235
|
name: "engrm",
|
|
16950
|
-
version: "0.
|
|
17236
|
+
version: "0.4.6"
|
|
16951
17237
|
});
|
|
16952
17238
|
server.tool("save_observation", "Save an observation to memory", {
|
|
16953
17239
|
type: exports_external.enum([
|
|
@@ -17031,13 +17317,22 @@ server.tool("search", "Search memory for observations", {
|
|
|
17031
17317
|
]
|
|
17032
17318
|
};
|
|
17033
17319
|
}
|
|
17034
|
-
const
|
|
17035
|
-
const
|
|
17320
|
+
const includeProjectColumn = result.observations.some((obs) => obs.project_name);
|
|
17321
|
+
const header = includeProjectColumn ? "| ID | Type | Q | Title | Project | Created |" : "| ID | Type | Q | Title | Created |";
|
|
17322
|
+
const separator = includeProjectColumn ? "|---|---|---|---|---|---|" : "|---|---|---|---|---|";
|
|
17036
17323
|
const rows = result.observations.map((obs) => {
|
|
17037
17324
|
const qualityDots = qualityIndicator(obs.quality);
|
|
17038
17325
|
const date5 = obs.created_at.split("T")[0];
|
|
17326
|
+
if (includeProjectColumn) {
|
|
17327
|
+
return `| ${obs.id} | ${obs.type} | ${qualityDots} | ${obs.title} | ${obs.project_name ?? "-"} | ${date5} |`;
|
|
17328
|
+
}
|
|
17039
17329
|
return `| ${obs.id} | ${obs.type} | ${qualityDots} | ${obs.title} | ${date5} |`;
|
|
17040
17330
|
});
|
|
17331
|
+
const previews = result.observations.slice(0, Math.min(3, result.observations.length)).map((obs) => {
|
|
17332
|
+
const preview = formatFactPreview(obs.facts, obs.narrative);
|
|
17333
|
+
const projectSuffix = obs.project_name ? ` [${obs.project_name}]` : "";
|
|
17334
|
+
return preview ? `- #${obs.id} [${obs.type}] ${obs.title}${projectSuffix}: ${preview}` : `- #${obs.id} [${obs.type}] ${obs.title}${projectSuffix}`;
|
|
17335
|
+
});
|
|
17041
17336
|
const projectLine = result.project ? `Project: ${result.project}
|
|
17042
17337
|
` : "";
|
|
17043
17338
|
return {
|
|
@@ -17049,6 +17344,10 @@ server.tool("search", "Search memory for observations", {
|
|
|
17049
17344
|
${header}
|
|
17050
17345
|
${separator}
|
|
17051
17346
|
${rows.join(`
|
|
17347
|
+
`)}
|
|
17348
|
+
|
|
17349
|
+
Top context:
|
|
17350
|
+
${previews.join(`
|
|
17052
17351
|
`)}`
|
|
17053
17352
|
}
|
|
17054
17353
|
]
|
|
@@ -17269,6 +17568,14 @@ ${rows.join(`
|
|
|
17269
17568
|
server.tool("memory_stats", "Show high-level Engrm capture and sync statistics", {}, async () => {
|
|
17270
17569
|
const stats = getMemoryStats(db);
|
|
17271
17570
|
const packs = stats.installed_packs.length > 0 ? stats.installed_packs.join(", ") : "(none)";
|
|
17571
|
+
const recentRequests = stats.recent_requests.length > 0 ? stats.recent_requests.map((item) => `- ${item}`).join(`
|
|
17572
|
+
`) : "- (none)";
|
|
17573
|
+
const recentLessons = stats.recent_lessons.length > 0 ? stats.recent_lessons.map((item) => `- ${item}`).join(`
|
|
17574
|
+
`) : "- (none)";
|
|
17575
|
+
const recentCompleted = stats.recent_completed.length > 0 ? stats.recent_completed.map((item) => `- ${item}`).join(`
|
|
17576
|
+
`) : "- (none)";
|
|
17577
|
+
const nextSteps = stats.next_steps.length > 0 ? stats.next_steps.map((item) => `- ${item}`).join(`
|
|
17578
|
+
`) : "- (none)";
|
|
17272
17579
|
return {
|
|
17273
17580
|
content: [
|
|
17274
17581
|
{
|
|
@@ -17276,8 +17583,21 @@ server.tool("memory_stats", "Show high-level Engrm capture and sync statistics",
|
|
|
17276
17583
|
text: `Active observations: ${stats.active_observations}
|
|
17277
17584
|
` + `Messages: ${stats.messages}
|
|
17278
17585
|
` + `Session summaries: ${stats.session_summaries}
|
|
17586
|
+
` + `Summary coverage: learned ${stats.summaries_with_learned}, completed ${stats.summaries_with_completed}, next steps ${stats.summaries_with_next_steps}
|
|
17279
17587
|
` + `Installed packs: ${packs}
|
|
17280
|
-
` + `Outbox: pending ${stats.outbox.pending ?? 0}, failed ${stats.outbox.failed ?? 0}, synced ${stats.outbox.synced ?? 0}
|
|
17588
|
+
` + `Outbox: pending ${stats.outbox.pending ?? 0}, failed ${stats.outbox.failed ?? 0}, synced ${stats.outbox.synced ?? 0}
|
|
17589
|
+
|
|
17590
|
+
` + `Recent requests:
|
|
17591
|
+
${recentRequests}
|
|
17592
|
+
|
|
17593
|
+
` + `Recent lessons:
|
|
17594
|
+
${recentLessons}
|
|
17595
|
+
|
|
17596
|
+
` + `Recent completed:
|
|
17597
|
+
${recentCompleted}
|
|
17598
|
+
|
|
17599
|
+
` + `Next steps:
|
|
17600
|
+
${nextSteps}`
|
|
17281
17601
|
}
|
|
17282
17602
|
]
|
|
17283
17603
|
};
|
|
@@ -17381,6 +17701,28 @@ function qualityIndicator(quality) {
|
|
|
17381
17701
|
const filled = Math.round(quality * 5);
|
|
17382
17702
|
return "●".repeat(filled) + "○".repeat(5 - filled);
|
|
17383
17703
|
}
|
|
17704
|
+
function formatFactPreview(factsRaw, narrative) {
|
|
17705
|
+
if (factsRaw) {
|
|
17706
|
+
try {
|
|
17707
|
+
const parsed = JSON.parse(factsRaw);
|
|
17708
|
+
if (Array.isArray(parsed)) {
|
|
17709
|
+
const facts = parsed.filter((item) => typeof item === "string" && item.trim().length > 0).slice(0, 2);
|
|
17710
|
+
if (facts.length > 0) {
|
|
17711
|
+
return facts.join("; ");
|
|
17712
|
+
}
|
|
17713
|
+
}
|
|
17714
|
+
} catch {
|
|
17715
|
+
const trimmedFacts = factsRaw.trim();
|
|
17716
|
+
if (trimmedFacts.length > 0) {
|
|
17717
|
+
return trimmedFacts.length > 160 ? `${trimmedFacts.slice(0, 157)}...` : trimmedFacts;
|
|
17718
|
+
}
|
|
17719
|
+
}
|
|
17720
|
+
}
|
|
17721
|
+
if (!narrative)
|
|
17722
|
+
return null;
|
|
17723
|
+
const trimmed = narrative.trim().replace(/\s+/g, " ");
|
|
17724
|
+
return trimmed.length > 160 ? `${trimmed.slice(0, 157)}...` : trimmed;
|
|
17725
|
+
}
|
|
17384
17726
|
async function main() {
|
|
17385
17727
|
runDueJobs(db);
|
|
17386
17728
|
if (db.vecAvailable) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engrm",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.7",
|
|
4
|
+
"description": "Shared memory across devices, sessions, and coding agents",
|
|
5
|
+
"mcpName": "io.github.dr12hes/engrm",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "dist/server.js",
|
|
7
8
|
"bin": {
|