engrm 0.4.17 → 0.4.19
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 +74 -17
- package/dist/hooks/stop.js +191 -83
- 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.19";
|
|
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
|
}
|
|
@@ -3157,13 +3210,13 @@ function formatSplashScreen(data) {
|
|
|
3157
3210
|
}
|
|
3158
3211
|
}
|
|
3159
3212
|
const contextIndex = formatContextIndex(data.context, handoffShownItems);
|
|
3160
|
-
if (contextIndex.length > 0) {
|
|
3213
|
+
if (contextIndex.lines.length > 0) {
|
|
3161
3214
|
lines.push("");
|
|
3162
|
-
for (const line of contextIndex) {
|
|
3215
|
+
for (const line of contextIndex.lines) {
|
|
3163
3216
|
lines.push(` ${line}`);
|
|
3164
3217
|
}
|
|
3165
3218
|
}
|
|
3166
|
-
const inspectHints = formatInspectHints(data.context);
|
|
3219
|
+
const inspectHints = formatInspectHints(data.context, contextIndex.observationIds);
|
|
3167
3220
|
if (inspectHints.length > 0) {
|
|
3168
3221
|
lines.push("");
|
|
3169
3222
|
for (const line of inspectHints) {
|
|
@@ -3300,19 +3353,23 @@ function formatLegend() {
|
|
|
3300
3353
|
];
|
|
3301
3354
|
}
|
|
3302
3355
|
function formatContextIndex(context, shownItems) {
|
|
3303
|
-
const
|
|
3356
|
+
const selected = pickContextIndexObservations(context, shownItems);
|
|
3357
|
+
const rows = selected.map((obs) => {
|
|
3304
3358
|
const icon = observationIcon(obs.type);
|
|
3305
3359
|
const fileHint = extractPrimaryFileHint(obs);
|
|
3306
3360
|
return `${icon} #${obs.id} ${truncateInline(obs.title, 110)}${fileHint ? ` ${c2.dim}(${fileHint})${c2.reset}` : ""}`;
|
|
3307
3361
|
});
|
|
3308
3362
|
if (rows.length === 0)
|
|
3309
|
-
return [];
|
|
3310
|
-
return
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3363
|
+
return { lines: [], observationIds: [] };
|
|
3364
|
+
return {
|
|
3365
|
+
lines: [
|
|
3366
|
+
`${c2.dim}Handoff index:${c2.reset} use IDs when you want the deeper thread`,
|
|
3367
|
+
...rows
|
|
3368
|
+
],
|
|
3369
|
+
observationIds: selected.map((obs) => obs.id)
|
|
3370
|
+
};
|
|
3314
3371
|
}
|
|
3315
|
-
function formatInspectHints(context) {
|
|
3372
|
+
function formatInspectHints(context, visibleObservationIds = []) {
|
|
3316
3373
|
const hints = [];
|
|
3317
3374
|
if ((context.recentSessions?.length ?? 0) > 0) {
|
|
3318
3375
|
hints.push("recent_sessions");
|
|
@@ -3327,7 +3384,7 @@ function formatInspectHints(context) {
|
|
|
3327
3384
|
const unique = Array.from(new Set(hints)).slice(0, 4);
|
|
3328
3385
|
if (unique.length === 0)
|
|
3329
3386
|
return [];
|
|
3330
|
-
const ids =
|
|
3387
|
+
const ids = visibleObservationIds.slice(0, 5);
|
|
3331
3388
|
const fetchHint = ids.length > 0 ? `get_observations([${ids.join(", ")}])` : null;
|
|
3332
3389
|
return [
|
|
3333
3390
|
`${c2.dim}Next look:${c2.reset} ${unique.join(" \xB7 ")}`,
|
package/dist/hooks/stop.js
CHANGED
|
@@ -237,6 +237,84 @@ function normalizeObservationKey(value) {
|
|
|
237
237
|
return value.toLowerCase().replace(/\([^)]*\)/g, "").replace(/\b(modified|updated|edited|touched|changed)\b/g, "").replace(/\s+/g, " ").trim();
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
// src/intelligence/summary-sections.ts
|
|
241
|
+
function extractSummaryItems(section, limit) {
|
|
242
|
+
if (!section || !section.trim())
|
|
243
|
+
return [];
|
|
244
|
+
const rawLines = section.split(`
|
|
245
|
+
`).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
246
|
+
const items = [];
|
|
247
|
+
const seen = new Set;
|
|
248
|
+
let heading = null;
|
|
249
|
+
for (const rawLine of rawLines) {
|
|
250
|
+
const line = stripSectionPrefix(rawLine);
|
|
251
|
+
if (!line)
|
|
252
|
+
continue;
|
|
253
|
+
const headingOnly = parseHeading(line);
|
|
254
|
+
if (headingOnly) {
|
|
255
|
+
heading = headingOnly;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const isBullet = /^[-*•]\s+/.test(line);
|
|
259
|
+
const stripped = line.replace(/^[-*•]\s+/, "").trim();
|
|
260
|
+
if (!stripped)
|
|
261
|
+
continue;
|
|
262
|
+
const item = heading && isBullet ? `${heading}: ${stripped}` : stripped;
|
|
263
|
+
const normalized = normalizeItem(item);
|
|
264
|
+
if (!normalized || seen.has(normalized))
|
|
265
|
+
continue;
|
|
266
|
+
seen.add(normalized);
|
|
267
|
+
items.push(item);
|
|
268
|
+
if (limit && items.length >= limit)
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
return items;
|
|
272
|
+
}
|
|
273
|
+
function formatSummaryItems(section, maxLen) {
|
|
274
|
+
const items = extractSummaryItems(section);
|
|
275
|
+
if (items.length === 0)
|
|
276
|
+
return null;
|
|
277
|
+
const cleaned = items.map((item) => `- ${item}`).join(`
|
|
278
|
+
`);
|
|
279
|
+
if (cleaned.length <= maxLen)
|
|
280
|
+
return cleaned;
|
|
281
|
+
const truncated = cleaned.slice(0, maxLen).trimEnd();
|
|
282
|
+
const lastBreak = Math.max(truncated.lastIndexOf(`
|
|
283
|
+
`), truncated.lastIndexOf(" "));
|
|
284
|
+
const safe = lastBreak > maxLen * 0.5 ? truncated.slice(0, lastBreak) : truncated;
|
|
285
|
+
return `${safe.trimEnd()}…`;
|
|
286
|
+
}
|
|
287
|
+
function normalizeSummarySection(section) {
|
|
288
|
+
const items = extractSummaryItems(section);
|
|
289
|
+
if (items.length === 0) {
|
|
290
|
+
const cleaned = section?.replace(/\s+/g, " ").trim() ?? "";
|
|
291
|
+
return cleaned || null;
|
|
292
|
+
}
|
|
293
|
+
return items.map((item) => `- ${item}`).join(`
|
|
294
|
+
`);
|
|
295
|
+
}
|
|
296
|
+
function normalizeSummaryRequest(value) {
|
|
297
|
+
const cleaned = value?.replace(/\s+/g, " ").trim() ?? "";
|
|
298
|
+
return cleaned || null;
|
|
299
|
+
}
|
|
300
|
+
function stripSectionPrefix(value) {
|
|
301
|
+
return value.replace(/^(request|investigated|learned|completed|next steps|summary):\s*/i, "").trim();
|
|
302
|
+
}
|
|
303
|
+
function parseHeading(value) {
|
|
304
|
+
const boldMatch = value.match(/^\*{1,2}\s*(.+?)\s*:\*{1,2}$/);
|
|
305
|
+
if (boldMatch?.[1]) {
|
|
306
|
+
return boldMatch[1].trim().replace(/\s+/g, " ");
|
|
307
|
+
}
|
|
308
|
+
const plainMatch = value.match(/^(.+?):$/);
|
|
309
|
+
if (plainMatch?.[1]) {
|
|
310
|
+
return plainMatch[1].trim().replace(/\s+/g, " ");
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function normalizeItem(value) {
|
|
315
|
+
return value.toLowerCase().replace(/\*+/g, "").replace(/\s+/g, " ").trim();
|
|
316
|
+
}
|
|
317
|
+
|
|
240
318
|
// src/config.ts
|
|
241
319
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
242
320
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
@@ -953,86 +1031,6 @@ var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max,
|
|
|
953
1031
|
|
|
954
1032
|
// src/storage/sqlite.ts
|
|
955
1033
|
import { createHash as createHash2 } from "node:crypto";
|
|
956
|
-
|
|
957
|
-
// src/intelligence/summary-sections.ts
|
|
958
|
-
function extractSummaryItems(section, limit) {
|
|
959
|
-
if (!section || !section.trim())
|
|
960
|
-
return [];
|
|
961
|
-
const rawLines = section.split(`
|
|
962
|
-
`).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
963
|
-
const items = [];
|
|
964
|
-
const seen = new Set;
|
|
965
|
-
let heading = null;
|
|
966
|
-
for (const rawLine of rawLines) {
|
|
967
|
-
const line = stripSectionPrefix(rawLine);
|
|
968
|
-
if (!line)
|
|
969
|
-
continue;
|
|
970
|
-
const headingOnly = parseHeading(line);
|
|
971
|
-
if (headingOnly) {
|
|
972
|
-
heading = headingOnly;
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
const isBullet = /^[-*•]\s+/.test(line);
|
|
976
|
-
const stripped = line.replace(/^[-*•]\s+/, "").trim();
|
|
977
|
-
if (!stripped)
|
|
978
|
-
continue;
|
|
979
|
-
const item = heading && isBullet ? `${heading}: ${stripped}` : stripped;
|
|
980
|
-
const normalized = normalizeItem(item);
|
|
981
|
-
if (!normalized || seen.has(normalized))
|
|
982
|
-
continue;
|
|
983
|
-
seen.add(normalized);
|
|
984
|
-
items.push(item);
|
|
985
|
-
if (limit && items.length >= limit)
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
988
|
-
return items;
|
|
989
|
-
}
|
|
990
|
-
function formatSummaryItems(section, maxLen) {
|
|
991
|
-
const items = extractSummaryItems(section);
|
|
992
|
-
if (items.length === 0)
|
|
993
|
-
return null;
|
|
994
|
-
const cleaned = items.map((item) => `- ${item}`).join(`
|
|
995
|
-
`);
|
|
996
|
-
if (cleaned.length <= maxLen)
|
|
997
|
-
return cleaned;
|
|
998
|
-
const truncated = cleaned.slice(0, maxLen).trimEnd();
|
|
999
|
-
const lastBreak = Math.max(truncated.lastIndexOf(`
|
|
1000
|
-
`), truncated.lastIndexOf(" "));
|
|
1001
|
-
const safe = lastBreak > maxLen * 0.5 ? truncated.slice(0, lastBreak) : truncated;
|
|
1002
|
-
return `${safe.trimEnd()}…`;
|
|
1003
|
-
}
|
|
1004
|
-
function normalizeSummarySection(section) {
|
|
1005
|
-
const items = extractSummaryItems(section);
|
|
1006
|
-
if (items.length === 0) {
|
|
1007
|
-
const cleaned = section?.replace(/\s+/g, " ").trim() ?? "";
|
|
1008
|
-
return cleaned || null;
|
|
1009
|
-
}
|
|
1010
|
-
return items.map((item) => `- ${item}`).join(`
|
|
1011
|
-
`);
|
|
1012
|
-
}
|
|
1013
|
-
function normalizeSummaryRequest(value) {
|
|
1014
|
-
const cleaned = value?.replace(/\s+/g, " ").trim() ?? "";
|
|
1015
|
-
return cleaned || null;
|
|
1016
|
-
}
|
|
1017
|
-
function stripSectionPrefix(value) {
|
|
1018
|
-
return value.replace(/^(request|investigated|learned|completed|next steps|summary):\s*/i, "").trim();
|
|
1019
|
-
}
|
|
1020
|
-
function parseHeading(value) {
|
|
1021
|
-
const boldMatch = value.match(/^\*{1,2}\s*(.+?)\s*:\*{1,2}$/);
|
|
1022
|
-
if (boldMatch?.[1]) {
|
|
1023
|
-
return boldMatch[1].trim().replace(/\s+/g, " ");
|
|
1024
|
-
}
|
|
1025
|
-
const plainMatch = value.match(/^(.+?):$/);
|
|
1026
|
-
if (plainMatch?.[1]) {
|
|
1027
|
-
return plainMatch[1].trim().replace(/\s+/g, " ");
|
|
1028
|
-
}
|
|
1029
|
-
return null;
|
|
1030
|
-
}
|
|
1031
|
-
function normalizeItem(value) {
|
|
1032
|
-
return value.toLowerCase().replace(/\*+/g, "").replace(/\s+/g, " ").trim();
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// src/storage/sqlite.ts
|
|
1036
1034
|
var IS_BUN = typeof globalThis.Bun !== "undefined";
|
|
1037
1035
|
function openDatabase(dbPath) {
|
|
1038
1036
|
if (IS_BUN) {
|
|
@@ -1519,6 +1517,31 @@ class MemDatabase {
|
|
|
1519
1517
|
const id = Number(result.lastInsertRowid);
|
|
1520
1518
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1521
1519
|
}
|
|
1520
|
+
upsertSessionSummary(summary) {
|
|
1521
|
+
const existing = this.getSessionSummary(summary.session_id);
|
|
1522
|
+
if (!existing) {
|
|
1523
|
+
return this.insertSessionSummary(summary);
|
|
1524
|
+
}
|
|
1525
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1526
|
+
const normalized = {
|
|
1527
|
+
request: normalizeSummaryRequest(summary.request ?? existing.request),
|
|
1528
|
+
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1529
|
+
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1530
|
+
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1531
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1532
|
+
};
|
|
1533
|
+
this.db.query(`UPDATE session_summaries
|
|
1534
|
+
SET project_id = ?,
|
|
1535
|
+
user_id = ?,
|
|
1536
|
+
request = ?,
|
|
1537
|
+
investigated = ?,
|
|
1538
|
+
learned = ?,
|
|
1539
|
+
completed = ?,
|
|
1540
|
+
next_steps = ?,
|
|
1541
|
+
created_at_epoch = ?
|
|
1542
|
+
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);
|
|
1543
|
+
return this.getSessionSummary(summary.session_id);
|
|
1544
|
+
}
|
|
1522
1545
|
getSessionSummary(sessionId) {
|
|
1523
1546
|
return this.db.query("SELECT * FROM session_summaries WHERE session_id = ?").get(sessionId) ?? null;
|
|
1524
1547
|
}
|
|
@@ -2535,7 +2558,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
2535
2558
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
2536
2559
|
risk_score: riskScore,
|
|
2537
2560
|
stacks_detected: stacks,
|
|
2538
|
-
client_version: "0.4.
|
|
2561
|
+
client_version: "0.4.19",
|
|
2539
2562
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
2540
2563
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
2541
2564
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -3567,7 +3590,9 @@ async function main() {
|
|
|
3567
3590
|
if (!existing) {
|
|
3568
3591
|
const observations = db.getObservationsBySession(event.session_id);
|
|
3569
3592
|
const session = db.getSessionMetrics(event.session_id);
|
|
3570
|
-
const
|
|
3593
|
+
const retrospective = extractRetrospective(observations, event.session_id, session?.project_id ?? null, config.user_id);
|
|
3594
|
+
const assistantSections = extractAssistantSummarySections(event.last_assistant_message);
|
|
3595
|
+
const summary = mergeSessionSummary(retrospective, assistantSections, event.session_id, session?.project_id ?? null, config.user_id) ?? mergeSessionSummary(buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message), assistantSections, 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);
|
|
3571
3596
|
if (summary) {
|
|
3572
3597
|
const row = db.insertSessionSummary(summary);
|
|
3573
3598
|
db.addToOutbox("summary", row.id);
|
|
@@ -3676,6 +3701,85 @@ function buildCheckpointCompleted(checkpoint) {
|
|
|
3676
3701
|
return lines.join(`
|
|
3677
3702
|
`);
|
|
3678
3703
|
}
|
|
3704
|
+
function mergeSessionSummary(base, extra, sessionId, projectId, userId) {
|
|
3705
|
+
if (!base && !extra)
|
|
3706
|
+
return null;
|
|
3707
|
+
return {
|
|
3708
|
+
session_id: sessionId,
|
|
3709
|
+
project_id: projectId,
|
|
3710
|
+
user_id: userId,
|
|
3711
|
+
request: chooseRicherSummaryValue(base?.request ?? null, extra?.request ?? null, true),
|
|
3712
|
+
investigated: chooseRicherSummaryValue(base?.investigated ?? null, extra?.investigated ?? null, false),
|
|
3713
|
+
learned: chooseRicherSummaryValue(base?.learned ?? null, extra?.learned ?? null, false),
|
|
3714
|
+
completed: chooseRicherSummaryValue(base?.completed ?? null, extra?.completed ?? null, false),
|
|
3715
|
+
next_steps: chooseRicherSummaryValue(base?.next_steps ?? null, extra?.next_steps ?? null, false)
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
function chooseRicherSummaryValue(base, extra, isRequest) {
|
|
3719
|
+
const normalizedBase = isRequest ? normalizeSummaryRequest(base) : normalizeSummarySection(base);
|
|
3720
|
+
const normalizedExtra = isRequest ? normalizeSummaryRequest(extra) : normalizeSummarySection(extra);
|
|
3721
|
+
if (!normalizedBase)
|
|
3722
|
+
return normalizedExtra;
|
|
3723
|
+
if (!normalizedExtra)
|
|
3724
|
+
return normalizedBase;
|
|
3725
|
+
if (normalizedExtra.length > normalizedBase.length + 24)
|
|
3726
|
+
return normalizedExtra;
|
|
3727
|
+
if (isRequest && isGenericCheckpointLine(normalizedBase) && !isGenericCheckpointLine(normalizedExtra)) {
|
|
3728
|
+
return normalizedExtra;
|
|
3729
|
+
}
|
|
3730
|
+
return normalizedBase;
|
|
3731
|
+
}
|
|
3732
|
+
function extractAssistantSummarySections(message) {
|
|
3733
|
+
const compact = message?.replace(/\r/g, "").trim();
|
|
3734
|
+
if (!compact || compact.length < 80)
|
|
3735
|
+
return null;
|
|
3736
|
+
const sections = new Map;
|
|
3737
|
+
let current = null;
|
|
3738
|
+
for (const rawLine of compact.split(`
|
|
3739
|
+
`)) {
|
|
3740
|
+
const line = rawLine.trim();
|
|
3741
|
+
if (!line)
|
|
3742
|
+
continue;
|
|
3743
|
+
const heading = parseAssistantSectionHeading(line);
|
|
3744
|
+
if (heading) {
|
|
3745
|
+
current = heading;
|
|
3746
|
+
if (!sections.has(current))
|
|
3747
|
+
sections.set(current, []);
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
if (!current)
|
|
3751
|
+
continue;
|
|
3752
|
+
if (current === "request" && isGenericCheckpointLine(line))
|
|
3753
|
+
continue;
|
|
3754
|
+
sections.get(current)?.push(line);
|
|
3755
|
+
}
|
|
3756
|
+
const request = normalizeSummaryRequest(sections.get("request")?.join(" ") ?? null);
|
|
3757
|
+
const investigated = normalizeSummarySection(sections.get("investigated")?.join(`
|
|
3758
|
+
`) ?? null);
|
|
3759
|
+
const learned = normalizeSummarySection(sections.get("learned")?.join(`
|
|
3760
|
+
`) ?? null);
|
|
3761
|
+
const completed = normalizeSummarySection(sections.get("completed")?.join(`
|
|
3762
|
+
`) ?? null);
|
|
3763
|
+
const next_steps = normalizeSummarySection(sections.get("next_steps")?.join(`
|
|
3764
|
+
`) ?? null);
|
|
3765
|
+
if (!request && !investigated && !learned && !completed && !next_steps)
|
|
3766
|
+
return null;
|
|
3767
|
+
return { request, investigated, learned, completed, next_steps };
|
|
3768
|
+
}
|
|
3769
|
+
function parseAssistantSectionHeading(value) {
|
|
3770
|
+
const normalized = value.toLowerCase().replace(/\*+/g, "").trim();
|
|
3771
|
+
if (/^request:/.test(normalized))
|
|
3772
|
+
return "request";
|
|
3773
|
+
if (/^investigated:/.test(normalized))
|
|
3774
|
+
return "investigated";
|
|
3775
|
+
if (/^learned:/.test(normalized))
|
|
3776
|
+
return "learned";
|
|
3777
|
+
if (/^completed:/.test(normalized))
|
|
3778
|
+
return "completed";
|
|
3779
|
+
if (/^next steps?:/.test(normalized))
|
|
3780
|
+
return "next_steps";
|
|
3781
|
+
return null;
|
|
3782
|
+
}
|
|
3679
3783
|
function createSessionDigest(db, sessionId, cwd) {
|
|
3680
3784
|
const observations = db.getObservationsBySession(sessionId);
|
|
3681
3785
|
if (observations.length < 2)
|
|
@@ -3831,9 +3935,13 @@ function extractAssistantCheckpoint(message) {
|
|
|
3831
3935
|
};
|
|
3832
3936
|
}
|
|
3833
3937
|
function pickAssistantCheckpointTitle(substantiveLines, bulletLines) {
|
|
3834
|
-
const candidates = [...bulletLines, ...substantiveLines].map((line) => line.replace(/^Completed:\s*/i, "").trim()).filter((line) => line.length > 20).filter((line) => !/^Next Steps?:/i.test(line)).filter((line) => !/^Investigated:/i.test(line)).filter((line) => !/^Learned:/i.test(line));
|
|
3938
|
+
const candidates = [...bulletLines, ...substantiveLines].map((line) => line.replace(/^Completed:\s*/i, "").trim()).filter((line) => line.length > 20).filter((line) => !isGenericCheckpointLine(line)).filter((line) => !/^Next Steps?:/i.test(line)).filter((line) => !/^Investigated:/i.test(line)).filter((line) => !/^Learned:/i.test(line));
|
|
3835
3939
|
return candidates[0] ?? null;
|
|
3836
3940
|
}
|
|
3941
|
+
function isGenericCheckpointLine(value) {
|
|
3942
|
+
const normalized = value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3943
|
+
return normalized === "here's where things stand:" || normalized === "here's where things stand" || normalized === "where things stand:" || normalized === "where things stand" || normalized === "current status:" || normalized === "current status" || normalized === "status update:" || normalized === "status update";
|
|
3944
|
+
}
|
|
3837
3945
|
function detectUnsavedPlans(message) {
|
|
3838
3946
|
const hints = [];
|
|
3839
3947
|
const lower = message.toLowerCase();
|
|
@@ -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.19"
|
|
19768
19821
|
});
|
|
19769
19822
|
server.tool("save_observation", "Save an observation to memory", {
|
|
19770
19823
|
type: exports_external.enum([
|