engrm 0.4.16 → 0.4.18
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/cli.js +25 -0
- package/dist/hooks/elicitation-result.js +25 -0
- package/dist/hooks/post-tool-use.js +25 -0
- package/dist/hooks/pre-compact.js +25 -0
- package/dist/hooks/sentinel.js +25 -0
- package/dist/hooks/session-start.js +59 -6
- package/dist/hooks/stop.js +91 -30
- package/dist/hooks/user-prompt-submit.js +39 -0
- package/dist/server.js +59 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1316,6 +1316,31 @@ class MemDatabase {
|
|
|
1316
1316
|
const id = Number(result.lastInsertRowid);
|
|
1317
1317
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1318
1318
|
}
|
|
1319
|
+
upsertSessionSummary(summary) {
|
|
1320
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1321
|
+
if (!existing) {
|
|
1322
|
+
return this.insertSessionSummary(summary);
|
|
1323
|
+
}
|
|
1324
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1325
|
+
const normalized = {
|
|
1326
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1327
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1328
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1329
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1330
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1331
|
+
};
|
|
1332
|
+
this.db.query(`UPDATE session_summaries
|
|
1333
|
+
SET project_id = ?,
|
|
1334
|
+
user_id = ?,
|
|
1335
|
+
request = ?,
|
|
1336
|
+
investigated = ?,
|
|
1337
|
+
learned = ?,
|
|
1338
|
+
completed = ?,
|
|
1339
|
+
next_steps = ?,
|
|
1340
|
+
created_at_epoch = ?
|
|
1341
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
1342
|
+
return this.getSessionSummary(summary.session_id);
|
|
1343
|
+
}
|
|
1319
1344
|
getSessionSummary(sessionId) {
|
|
1320
1345
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1321
1346
|
}
|
|
@@ -2145,6 +2145,31 @@ class MemDatabase {
|
|
|
2145
2145
|
const id = Number(result.lastInsertRowid);
|
|
2146
2146
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
2147
2147
|
}
|
|
2148
|
+
upsertSessionSummary(summary) {
|
|
2149
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
2150
|
+
if (!existing) {
|
|
2151
|
+
return this.insertSessionSummary(summary);
|
|
2152
|
+
}
|
|
2153
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2154
|
+
const normalized = {
|
|
2155
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
2156
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
2157
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
2158
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
2159
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
2160
|
+
};
|
|
2161
|
+
this.db.query(`UPDATE session_summaries
|
|
2162
|
+
SET project_id = ?,
|
|
2163
|
+
user_id = ?,
|
|
2164
|
+
request = ?,
|
|
2165
|
+
investigated = ?,
|
|
2166
|
+
learned = ?,
|
|
2167
|
+
completed = ?,
|
|
2168
|
+
next_steps = ?,
|
|
2169
|
+
created_at_epoch = ?
|
|
2170
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
2171
|
+
return this.getSessionSummary(summary.session_id);
|
|
2172
|
+
}
|
|
2148
2173
|
getSessionSummary(sessionId) {
|
|
2149
2174
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
2150
2175
|
}
|
|
@@ -1490,6 +1490,31 @@ class MemDatabase {
|
|
|
1490
1490
|
const id = Number(result.lastInsertRowid);
|
|
1491
1491
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1492
1492
|
}
|
|
1493
|
+
upsertSessionSummary(summary) {
|
|
1494
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1495
|
+
if (!existing) {
|
|
1496
|
+
return this.insertSessionSummary(summary);
|
|
1497
|
+
}
|
|
1498
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1499
|
+
const normalized = {
|
|
1500
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1501
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1502
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1503
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1504
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1505
|
+
};
|
|
1506
|
+
this.db.query(`UPDATE session_summaries
|
|
1507
|
+
SET project_id = ?,
|
|
1508
|
+
user_id = ?,
|
|
1509
|
+
request = ?,
|
|
1510
|
+
investigated = ?,
|
|
1511
|
+
learned = ?,
|
|
1512
|
+
completed = ?,
|
|
1513
|
+
next_steps = ?,
|
|
1514
|
+
created_at_epoch = ?
|
|
1515
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
1516
|
+
return this.getSessionSummary(summary.session_id);
|
|
1517
|
+
}
|
|
1493
1518
|
getSessionSummary(sessionId) {
|
|
1494
1519
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1495
1520
|
}
|
|
@@ -1284,6 +1284,31 @@ class MemDatabase {
|
|
|
1284
1284
|
const id = Number(result.lastInsertRowid);
|
|
1285
1285
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1286
1286
|
}
|
|
1287
|
+
upsertSessionSummary(summary) {
|
|
1288
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1289
|
+
if (!existing) {
|
|
1290
|
+
return this.insertSessionSummary(summary);
|
|
1291
|
+
}
|
|
1292
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1293
|
+
const normalized = {
|
|
1294
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1295
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1296
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1297
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1298
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1299
|
+
};
|
|
1300
|
+
this.db.query(`UPDATE session_summaries
|
|
1301
|
+
SET project_id = ?,
|
|
1302
|
+
user_id = ?,
|
|
1303
|
+
request = ?,
|
|
1304
|
+
investigated = ?,
|
|
1305
|
+
learned = ?,
|
|
1306
|
+
completed = ?,
|
|
1307
|
+
next_steps = ?,
|
|
1308
|
+
created_at_epoch = ?
|
|
1309
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
1310
|
+
return this.getSessionSummary(summary.session_id);
|
|
1311
|
+
}
|
|
1287
1312
|
getSessionSummary(sessionId) {
|
|
1288
1313
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1289
1314
|
}
|
package/dist/hooks/sentinel.js
CHANGED
|
@@ -1360,6 +1360,31 @@ class MemDatabase {
|
|
|
1360
1360
|
const id = Number(result.lastInsertRowid);
|
|
1361
1361
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1362
1362
|
}
|
|
1363
|
+
upsertSessionSummary(summary) {
|
|
1364
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1365
|
+
if (!existing) {
|
|
1366
|
+
return this.insertSessionSummary(summary);
|
|
1367
|
+
}
|
|
1368
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1369
|
+
const normalized = {
|
|
1370
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1371
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1372
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1373
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1374
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1375
|
+
};
|
|
1376
|
+
this.db.query(`UPDATE session_summaries
|
|
1377
|
+
SET project_id = ?,
|
|
1378
|
+
user_id = ?,
|
|
1379
|
+
request = ?,
|
|
1380
|
+
investigated = ?,
|
|
1381
|
+
learned = ?,
|
|
1382
|
+
completed = ?,
|
|
1383
|
+
next_steps = ?,
|
|
1384
|
+
created_at_epoch = ?
|
|
1385
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
1386
|
+
return this.getSessionSummary(summary.session_id);
|
|
1387
|
+
}
|
|
1363
1388
|
getSessionSummary(sessionId) {
|
|
1364
1389
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1365
1390
|
}
|
|
@@ -1154,7 +1154,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
|
|
|
1154
1154
|
import { join as join3 } from "node:path";
|
|
1155
1155
|
import { homedir } from "node:os";
|
|
1156
1156
|
var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
|
|
1157
|
-
var CLIENT_VERSION = "0.4.
|
|
1157
|
+
var CLIENT_VERSION = "0.4.18";
|
|
1158
1158
|
function hashFile(filePath) {
|
|
1159
1159
|
try {
|
|
1160
1160
|
if (!existsSync3(filePath))
|
|
@@ -1620,15 +1620,11 @@ function mergeChanges(db, config, changes) {
|
|
|
1620
1620
|
let skipped = 0;
|
|
1621
1621
|
for (const change of changes) {
|
|
1622
1622
|
const parsed = parseSourceId(change.source_id);
|
|
1623
|
+
const remoteSummary = isRemoteSummary(change);
|
|
1623
1624
|
if (parsed && parsed.deviceId === config.device_id) {
|
|
1624
1625
|
skipped++;
|
|
1625
1626
|
continue;
|
|
1626
1627
|
}
|
|
1627
|
-
const existing = db.db.query("SELECT id FROM observations WHERE remote_source_id = ?").get(change.source_id);
|
|
1628
|
-
if (existing) {
|
|
1629
|
-
skipped++;
|
|
1630
|
-
continue;
|
|
1631
|
-
}
|
|
1632
1628
|
const projectCanonical = change.metadata?.project_canonical ?? null;
|
|
1633
1629
|
if (!projectCanonical) {
|
|
1634
1630
|
skipped++;
|
|
@@ -1641,6 +1637,18 @@ function mergeChanges(db, config, changes) {
|
|
|
1641
1637
|
name: change.metadata?.project_name ?? projectCanonical.split("/").pop() ?? "unknown"
|
|
1642
1638
|
});
|
|
1643
1639
|
}
|
|
1640
|
+
if (remoteSummary) {
|
|
1641
|
+
const mergedSummary = mergeRemoteSummary(db, config, change, project.id);
|
|
1642
|
+
if (mergedSummary) {
|
|
1643
|
+
merged++;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
const existing = db.db.query("SELECT id FROM observations WHERE remote_source_id = ?").get(change.source_id);
|
|
1647
|
+
if (existing) {
|
|
1648
|
+
if (!remoteSummary)
|
|
1649
|
+
skipped++;
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1644
1652
|
const normalizedType = normalizeRemoteObservationType(change.metadata?.type, change.source_id);
|
|
1645
1653
|
if (!normalizedType) {
|
|
1646
1654
|
skipped++;
|
|
@@ -1672,6 +1680,26 @@ function mergeChanges(db, config, changes) {
|
|
|
1672
1680
|
}
|
|
1673
1681
|
return { merged, skipped };
|
|
1674
1682
|
}
|
|
1683
|
+
function isRemoteSummary(change) {
|
|
1684
|
+
const rawType = typeof change.metadata?.type === "string" ? change.metadata.type.toLowerCase() : "";
|
|
1685
|
+
return rawType === "summary" || change.source_id.includes("-summary-");
|
|
1686
|
+
}
|
|
1687
|
+
function mergeRemoteSummary(db, config, change, projectId) {
|
|
1688
|
+
const sessionId = typeof change.metadata?.session_id === "string" ? change.metadata.session_id : null;
|
|
1689
|
+
if (!sessionId)
|
|
1690
|
+
return false;
|
|
1691
|
+
const summary = db.upsertSessionSummary({
|
|
1692
|
+
session_id: sessionId,
|
|
1693
|
+
project_id: projectId,
|
|
1694
|
+
user_id: (typeof change.metadata?.user_id === "string" ? change.metadata.user_id : null) ?? config.user_id,
|
|
1695
|
+
request: typeof change.metadata?.request === "string" ? change.metadata.request : null,
|
|
1696
|
+
investigated: typeof change.metadata?.investigated === "string" ? change.metadata.investigated : null,
|
|
1697
|
+
learned: typeof change.metadata?.learned === "string" ? change.metadata.learned : null,
|
|
1698
|
+
completed: typeof change.metadata?.completed === "string" ? change.metadata.completed : null,
|
|
1699
|
+
next_steps: typeof change.metadata?.next_steps === "string" ? change.metadata.next_steps : null
|
|
1700
|
+
});
|
|
1701
|
+
return Boolean(summary);
|
|
1702
|
+
}
|
|
1675
1703
|
function normalizeRemoteObservationType(rawType, sourceId) {
|
|
1676
1704
|
const type = typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
|
|
1677
1705
|
if (type === "bugfix" || type === "discovery" || type === "decision" || type === "pattern" || type === "change" || type === "feature" || type === "refactor" || type === "digest" || type === "standard" || type === "message") {
|
|
@@ -2852,6 +2880,31 @@ class MemDatabase {
|
|
|
2852
2880
|
const id = Number(result.lastInsertRowid);
|
|
2853
2881
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
2854
2882
|
}
|
|
2883
|
+
upsertSessionSummary(summary) {
|
|
2884
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
2885
|
+
if (!existing) {
|
|
2886
|
+
return this.insertSessionSummary(summary);
|
|
2887
|
+
}
|
|
2888
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2889
|
+
const normalized = {
|
|
2890
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
2891
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
2892
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
2893
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
2894
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
2895
|
+
};
|
|
2896
|
+
this.db.query(`UPDATE session_summaries
|
|
2897
|
+
SET project_id = ?,
|
|
2898
|
+
user_id = ?,
|
|
2899
|
+
request = ?,
|
|
2900
|
+
investigated = ?,
|
|
2901
|
+
learned = ?,
|
|
2902
|
+
completed = ?,
|
|
2903
|
+
next_steps = ?,
|
|
2904
|
+
created_at_epoch = ?
|
|
2905
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
2906
|
+
return this.getSessionSummary(summary.session_id);
|
|
2907
|
+
}
|
|
2855
2908
|
getSessionSummary(sessionId) {
|
|
2856
2909
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
2857
2910
|
}
|
package/dist/hooks/stop.js
CHANGED
|
@@ -1519,6 +1519,31 @@ class MemDatabase {
|
|
|
1519
1519
|
const id = Number(result.lastInsertRowid);
|
|
1520
1520
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1521
1521
|
}
|
|
1522
|
+
upsertSessionSummary(summary) {
|
|
1523
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1524
|
+
if (!existing) {
|
|
1525
|
+
return this.insertSessionSummary(summary);
|
|
1526
|
+
}
|
|
1527
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1528
|
+
const normalized = {
|
|
1529
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1530
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1531
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1532
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1533
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1534
|
+
};
|
|
1535
|
+
this.db.query(`UPDATE session_summaries
|
|
1536
|
+
SET project_id = ?,
|
|
1537
|
+
user_id = ?,
|
|
1538
|
+
request = ?,
|
|
1539
|
+
investigated = ?,
|
|
1540
|
+
learned = ?,
|
|
1541
|
+
completed = ?,
|
|
1542
|
+
next_steps = ?,
|
|
1543
|
+
created_at_epoch = ?
|
|
1544
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
1545
|
+
return this.getSessionSummary(summary.session_id);
|
|
1546
|
+
}
|
|
1522
1547
|
getSessionSummary(sessionId) {
|
|
1523
1548
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1524
1549
|
}
|
|
@@ -2535,7 +2560,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
2535
2560
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
2536
2561
|
risk_score: riskScore,
|
|
2537
2562
|
stacks_detected: stacks,
|
|
2538
|
-
client_version: "0.4.
|
|
2563
|
+
client_version: "0.4.18",
|
|
2539
2564
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
2540
2565
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
2541
2566
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -3558,42 +3583,40 @@ async function main() {
|
|
|
3558
3583
|
try {
|
|
3559
3584
|
if (event.session_id) {
|
|
3560
3585
|
db.completeSession(event.session_id);
|
|
3586
|
+
if (event.last_assistant_message) {
|
|
3587
|
+
try {
|
|
3588
|
+
createAssistantCheckpoint(db, event.session_id, event.cwd, event.last_assistant_message);
|
|
3589
|
+
} catch {}
|
|
3590
|
+
}
|
|
3561
3591
|
const existing = db.getSessionSummary(event.session_id);
|
|
3562
3592
|
if (!existing) {
|
|
3563
3593
|
const observations = db.getObservationsBySession(event.session_id);
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
console.log(formatRiskTrafficLight(riskResult));
|
|
3587
|
-
}
|
|
3594
|
+
const session = db.getSessionMetrics(event.session_id);
|
|
3595
|
+
const summary = extractRetrospective(observations, event.session_id, session?.project_id ?? null, config.user_id) ?? buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message);
|
|
3596
|
+
if (summary) {
|
|
3597
|
+
const row = db.insertSessionSummary(summary);
|
|
3598
|
+
db.addToOutbox("summary", row.id);
|
|
3599
|
+
let securityFindings = [];
|
|
3600
|
+
try {
|
|
3601
|
+
if (session?.project_id) {
|
|
3602
|
+
securityFindings = db.getSecurityFindings(session.project_id, { limit: 100 }).filter((f) => f.session_id === event.session_id);
|
|
3603
|
+
}
|
|
3604
|
+
} catch {}
|
|
3605
|
+
const riskResult = computeRiskScore({
|
|
3606
|
+
observations,
|
|
3607
|
+
securityFindings,
|
|
3608
|
+
filesTouchedCount: session?.files_touched_count ?? 0,
|
|
3609
|
+
toolCallsCount: session?.tool_calls_count ?? 0
|
|
3610
|
+
});
|
|
3611
|
+
try {
|
|
3612
|
+
db.setSessionRiskScore(event.session_id, riskResult.score);
|
|
3613
|
+
} catch {}
|
|
3614
|
+
printRetrospective(summary);
|
|
3615
|
+
console.log(formatRiskTrafficLight(riskResult));
|
|
3588
3616
|
}
|
|
3589
3617
|
}
|
|
3590
3618
|
}
|
|
3591
3619
|
if (event.last_assistant_message) {
|
|
3592
|
-
if (event.session_id) {
|
|
3593
|
-
try {
|
|
3594
|
-
createAssistantCheckpoint(db, event.session_id, event.cwd, event.last_assistant_message);
|
|
3595
|
-
} catch {}
|
|
3596
|
-
}
|
|
3597
3620
|
const unsaved = detectUnsavedPlans(event.last_assistant_message);
|
|
3598
3621
|
if (unsaved.length > 0) {
|
|
3599
3622
|
console.error("");
|
|
@@ -3640,6 +3663,44 @@ async function main() {
|
|
|
3640
3663
|
}
|
|
3641
3664
|
process.exit(0);
|
|
3642
3665
|
}
|
|
3666
|
+
function buildFallbackSessionSummary(db, sessionId, projectId, userId, lastAssistantMessage) {
|
|
3667
|
+
const prompts = db.getSessionUserPrompts(sessionId, 10).filter((prompt) => isMeaningfulSummaryPrompt(prompt));
|
|
3668
|
+
const checkpoint = lastAssistantMessage ? extractAssistantCheckpoint(lastAssistantMessage) : null;
|
|
3669
|
+
const request = selectFallbackRequest(prompts);
|
|
3670
|
+
const completed = checkpoint ? buildCheckpointCompleted(checkpoint) : null;
|
|
3671
|
+
if (!request && !completed)
|
|
3672
|
+
return null;
|
|
3673
|
+
return {
|
|
3674
|
+
session_id: sessionId,
|
|
3675
|
+
project_id: projectId,
|
|
3676
|
+
user_id: userId,
|
|
3677
|
+
request,
|
|
3678
|
+
investigated: null,
|
|
3679
|
+
learned: null,
|
|
3680
|
+
completed,
|
|
3681
|
+
next_steps: null
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
function selectFallbackRequest(prompts) {
|
|
3685
|
+
const preferred = [...prompts].reverse().find((prompt) => !/^\[;ease$/i.test(prompt.prompt.trim()));
|
|
3686
|
+
return preferred?.prompt?.replace(/\s+/g, " ").trim() ?? null;
|
|
3687
|
+
}
|
|
3688
|
+
function isMeaningfulSummaryPrompt(prompt) {
|
|
3689
|
+
const compact = prompt.prompt.replace(/\s+/g, " ").trim();
|
|
3690
|
+
if (compact.length < 8)
|
|
3691
|
+
return false;
|
|
3692
|
+
if (/^\[;ease$/i.test(compact))
|
|
3693
|
+
return false;
|
|
3694
|
+
return /[a-z]{3,}/i.test(compact);
|
|
3695
|
+
}
|
|
3696
|
+
function buildCheckpointCompleted(checkpoint) {
|
|
3697
|
+
const lines = [`- ${checkpoint.title}`];
|
|
3698
|
+
for (const fact of checkpoint.facts.slice(0, 2)) {
|
|
3699
|
+
lines.push(` - ${fact}`);
|
|
3700
|
+
}
|
|
3701
|
+
return lines.join(`
|
|
3702
|
+
`);
|
|
3703
|
+
}
|
|
3643
3704
|
function createSessionDigest(db, sessionId, cwd) {
|
|
3644
3705
|
const observations = db.getObservationsBySession(sessionId);
|
|
3645
3706
|
if (observations.length < 2)
|
|
@@ -1428,6 +1428,31 @@ class MemDatabase {
|
|
|
1428
1428
|
const id = Number(result.lastInsertRowid);
|
|
1429
1429
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1430
1430
|
}
|
|
1431
|
+
upsertSessionSummary(summary) {
|
|
1432
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1433
|
+
if (!existing) {
|
|
1434
|
+
return this.insertSessionSummary(summary);
|
|
1435
|
+
}
|
|
1436
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1437
|
+
const normalized = {
|
|
1438
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1439
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1440
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1441
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1442
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1443
|
+
};
|
|
1444
|
+
this.db.query(`UPDATE session_summaries
|
|
1445
|
+
SET project_id = ?,
|
|
1446
|
+
user_id = ?,
|
|
1447
|
+
request = ?,
|
|
1448
|
+
investigated = ?,
|
|
1449
|
+
learned = ?,
|
|
1450
|
+
completed = ?,
|
|
1451
|
+
next_steps = ?,
|
|
1452
|
+
created_at_epoch = ?
|
|
1453
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
1454
|
+
return this.getSessionSummary(summary.session_id);
|
|
1455
|
+
}
|
|
1431
1456
|
getSessionSummary(sessionId) {
|
|
1432
1457
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1433
1458
|
}
|
|
@@ -1599,6 +1624,20 @@ async function main() {
|
|
|
1599
1624
|
device_id: config.device_id,
|
|
1600
1625
|
agent: "claude-code"
|
|
1601
1626
|
});
|
|
1627
|
+
const compactPrompt = event.prompt.replace(/\s+/g, " ").trim();
|
|
1628
|
+
if (compactPrompt.length >= 8) {
|
|
1629
|
+
const summary = db.upsertSessionSummary({
|
|
1630
|
+
session_id: event.session_id,
|
|
1631
|
+
project_id: project.id,
|
|
1632
|
+
user_id: config.user_id,
|
|
1633
|
+
request: compactPrompt,
|
|
1634
|
+
investigated: null,
|
|
1635
|
+
learned: null,
|
|
1636
|
+
completed: null,
|
|
1637
|
+
next_steps: null
|
|
1638
|
+
});
|
|
1639
|
+
db.addToOutbox("summary", summary.id);
|
|
1640
|
+
}
|
|
1602
1641
|
} finally {
|
|
1603
1642
|
db.close();
|
|
1604
1643
|
}
|
package/dist/server.js
CHANGED
|
@@ -14838,6 +14838,31 @@ class MemDatabase {
|
|
|
14838
14838
|
const id = Number(result.lastInsertRowid);
|
|
14839
14839
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
14840
14840
|
}
|
|
14841
|
+
upsertSessionSummary(summary) {
|
|
14842
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
14843
|
+
if (!existing) {
|
|
14844
|
+
return this.insertSessionSummary(summary);
|
|
14845
|
+
}
|
|
14846
|
+
const now = Math.floor(Date.now() / 1000);
|
|
14847
|
+
const normalized = {
|
|
14848
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
14849
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
14850
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
14851
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
14852
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
14853
|
+
};
|
|
14854
|
+
this.db.query(`UPDATE session_summaries
|
|
14855
|
+
SET project_id = ?,
|
|
14856
|
+
user_id = ?,
|
|
14857
|
+
request = ?,
|
|
14858
|
+
investigated = ?,
|
|
14859
|
+
learned = ?,
|
|
14860
|
+
completed = ?,
|
|
14861
|
+
next_steps = ?,
|
|
14862
|
+
created_at_epoch = ?
|
|
14863
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now, summary.session_id);
|
|
14864
|
+
return this.getSessionSummary(summary.session_id);
|
|
14865
|
+
}
|
|
14841
14866
|
getSessionSummary(sessionId) {
|
|
14842
14867
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
14843
14868
|
}
|
|
@@ -18910,15 +18935,11 @@ function mergeChanges(db, config2, changes) {
|
|
|
18910
18935
|
let skipped = 0;
|
|
18911
18936
|
for (const change of changes) {
|
|
18912
18937
|
const parsed = parseSourceId(change.source_id);
|
|
18938
|
+
const remoteSummary = isRemoteSummary(change);
|
|
18913
18939
|
if (parsed && parsed.deviceId === config2.device_id) {
|
|
18914
18940
|
skipped++;
|
|
18915
18941
|
continue;
|
|
18916
18942
|
}
|
|
18917
|
-
const existing = db.db.query("SELECT id FROM observations WHERE remote_source_id = ?").get(change.source_id);
|
|
18918
|
-
if (existing) {
|
|
18919
|
-
skipped++;
|
|
18920
|
-
continue;
|
|
18921
|
-
}
|
|
18922
18943
|
const projectCanonical = change.metadata?.project_canonical ?? null;
|
|
18923
18944
|
if (!projectCanonical) {
|
|
18924
18945
|
skipped++;
|
|
@@ -18931,6 +18952,18 @@ function mergeChanges(db, config2, changes) {
|
|
|
18931
18952
|
name: change.metadata?.project_name ?? projectCanonical.split("/").pop() ?? "unknown"
|
|
18932
18953
|
});
|
|
18933
18954
|
}
|
|
18955
|
+
if (remoteSummary) {
|
|
18956
|
+
const mergedSummary = mergeRemoteSummary(db, config2, change, project.id);
|
|
18957
|
+
if (mergedSummary) {
|
|
18958
|
+
merged++;
|
|
18959
|
+
}
|
|
18960
|
+
}
|
|
18961
|
+
const existing = db.db.query("SELECT id FROM observations WHERE remote_source_id = ?").get(change.source_id);
|
|
18962
|
+
if (existing) {
|
|
18963
|
+
if (!remoteSummary)
|
|
18964
|
+
skipped++;
|
|
18965
|
+
continue;
|
|
18966
|
+
}
|
|
18934
18967
|
const normalizedType = normalizeRemoteObservationType(change.metadata?.type, change.source_id);
|
|
18935
18968
|
if (!normalizedType) {
|
|
18936
18969
|
skipped++;
|
|
@@ -18962,6 +18995,26 @@ function mergeChanges(db, config2, changes) {
|
|
|
18962
18995
|
}
|
|
18963
18996
|
return { merged, skipped };
|
|
18964
18997
|
}
|
|
18998
|
+
function isRemoteSummary(change) {
|
|
18999
|
+
const rawType = typeof change.metadata?.type === "string" ? change.metadata.type.toLowerCase() : "";
|
|
19000
|
+
return rawType === "summary" || change.source_id.includes("-summary-");
|
|
19001
|
+
}
|
|
19002
|
+
function mergeRemoteSummary(db, config2, change, projectId) {
|
|
19003
|
+
const sessionId = typeof change.metadata?.session_id === "string" ? change.metadata.session_id : null;
|
|
19004
|
+
if (!sessionId)
|
|
19005
|
+
return false;
|
|
19006
|
+
const summary = db.upsertSessionSummary({
|
|
19007
|
+
session_id: sessionId,
|
|
19008
|
+
project_id: projectId,
|
|
19009
|
+
user_id: (typeof change.metadata?.user_id === "string" ? change.metadata.user_id : null) ?? config2.user_id,
|
|
19010
|
+
request: typeof change.metadata?.request === "string" ? change.metadata.request : null,
|
|
19011
|
+
investigated: typeof change.metadata?.investigated === "string" ? change.metadata.investigated : null,
|
|
19012
|
+
learned: typeof change.metadata?.learned === "string" ? change.metadata.learned : null,
|
|
19013
|
+
completed: typeof change.metadata?.completed === "string" ? change.metadata.completed : null,
|
|
19014
|
+
next_steps: typeof change.metadata?.next_steps === "string" ? change.metadata.next_steps : null
|
|
19015
|
+
});
|
|
19016
|
+
return Boolean(summary);
|
|
19017
|
+
}
|
|
18965
19018
|
function normalizeRemoteObservationType(rawType, sourceId) {
|
|
18966
19019
|
const type = typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
|
|
18967
19020
|
if (type === "bugfix" || type === "discovery" || type === "decision" || type === "pattern" || type === "change" || type === "feature" || type === "refactor" || type === "digest" || type === "standard" || type === "message") {
|
|
@@ -19764,7 +19817,7 @@ process.on("SIGTERM", () => {
|
|
|
19764
19817
|
});
|
|
19765
19818
|
var server = new McpServer({
|
|
19766
19819
|
name: "engrm",
|
|
19767
|
-
version: "0.4.
|
|
19820
|
+
version: "0.4.18"
|
|
19768
19821
|
});
|
|
19769
19822
|
server.tool("save_observation", "Save an observation to memory", {
|
|
19770
19823
|
type: exports_external.enum([
|