engrm 0.4.19 → 0.4.21
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 +43 -4
- package/dist/hooks/elicitation-result.js +33 -4
- package/dist/hooks/post-tool-use.js +118 -6
- package/dist/hooks/pre-compact.js +66 -7
- package/dist/hooks/sentinel.js +33 -4
- package/dist/hooks/session-start.js +105 -10
- package/dist/hooks/stop.js +34 -5
- package/dist/hooks/user-prompt-submit.js +33 -4
- package/dist/server.js +78 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -598,6 +598,16 @@ var MIGRATIONS = [
|
|
|
598
598
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
599
599
|
`
|
|
600
600
|
},
|
|
601
|
+
{
|
|
602
|
+
version: 11,
|
|
603
|
+
description: "Add synced handoff metadata to session summaries",
|
|
604
|
+
sql: `
|
|
605
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
606
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
607
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
608
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
609
|
+
`
|
|
610
|
+
},
|
|
601
611
|
{
|
|
602
612
|
version: 11,
|
|
603
613
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1125,6 +1135,10 @@ class MemDatabase {
|
|
|
1125
1135
|
p.name AS project_name,
|
|
1126
1136
|
ss.request AS request,
|
|
1127
1137
|
ss.completed AS completed,
|
|
1138
|
+
ss.capture_state AS capture_state,
|
|
1139
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1140
|
+
ss.hot_files AS hot_files,
|
|
1141
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1128
1142
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1129
1143
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1130
1144
|
FROM sessions s
|
|
@@ -1139,6 +1153,10 @@ class MemDatabase {
|
|
|
1139
1153
|
p.name AS project_name,
|
|
1140
1154
|
ss.request AS request,
|
|
1141
1155
|
ss.completed AS completed,
|
|
1156
|
+
ss.capture_state AS capture_state,
|
|
1157
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1158
|
+
ss.hot_files AS hot_files,
|
|
1159
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1142
1160
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1143
1161
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1144
1162
|
FROM sessions s
|
|
@@ -1311,8 +1329,11 @@ class MemDatabase {
|
|
|
1311
1329
|
completed: normalizeSummarySection(summary.completed),
|
|
1312
1330
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1313
1331
|
};
|
|
1314
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1315
|
-
|
|
1332
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1333
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1334
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1335
|
+
)
|
|
1336
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1316
1337
|
const id = Number(result.lastInsertRowid);
|
|
1317
1338
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1318
1339
|
}
|
|
@@ -1327,7 +1348,11 @@ class MemDatabase {
|
|
|
1327
1348
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1328
1349
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1329
1350
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1330
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1351
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1352
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1353
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1354
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
1355
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
1331
1356
|
};
|
|
1332
1357
|
this.db.query(`UPDATE session_summaries
|
|
1333
1358
|
SET project_id = ?,
|
|
@@ -1337,8 +1362,12 @@ class MemDatabase {
|
|
|
1337
1362
|
learned = ?,
|
|
1338
1363
|
completed = ?,
|
|
1339
1364
|
next_steps = ?,
|
|
1365
|
+
capture_state = ?,
|
|
1366
|
+
recent_tool_names = ?,
|
|
1367
|
+
hot_files = ?,
|
|
1368
|
+
recent_outcomes = ?,
|
|
1340
1369
|
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);
|
|
1370
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1342
1371
|
return this.getSessionSummary(summary.session_id);
|
|
1343
1372
|
}
|
|
1344
1373
|
getSessionSummary(sessionId) {
|
|
@@ -1843,6 +1872,16 @@ var MIGRATIONS2 = [
|
|
|
1843
1872
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
1844
1873
|
`
|
|
1845
1874
|
},
|
|
1875
|
+
{
|
|
1876
|
+
version: 11,
|
|
1877
|
+
description: "Add synced handoff metadata to session summaries",
|
|
1878
|
+
sql: `
|
|
1879
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
1880
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
1881
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
1882
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
1883
|
+
`
|
|
1884
|
+
},
|
|
1846
1885
|
{
|
|
1847
1886
|
version: 11,
|
|
1848
1887
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1431,6 +1431,16 @@ var MIGRATIONS = [
|
|
|
1431
1431
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
1432
1432
|
`
|
|
1433
1433
|
},
|
|
1434
|
+
{
|
|
1435
|
+
version: 11,
|
|
1436
|
+
description: "Add synced handoff metadata to session summaries",
|
|
1437
|
+
sql: `
|
|
1438
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
1439
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
1440
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
1441
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
1442
|
+
`
|
|
1443
|
+
},
|
|
1434
1444
|
{
|
|
1435
1445
|
version: 11,
|
|
1436
1446
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1954,6 +1964,10 @@ class MemDatabase {
|
|
|
1954
1964
|
p.name AS project_name,
|
|
1955
1965
|
ss.request AS request,
|
|
1956
1966
|
ss.completed AS completed,
|
|
1967
|
+
ss.capture_state AS capture_state,
|
|
1968
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1969
|
+
ss.hot_files AS hot_files,
|
|
1970
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1957
1971
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1958
1972
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1959
1973
|
FROM sessions s
|
|
@@ -1968,6 +1982,10 @@ class MemDatabase {
|
|
|
1968
1982
|
p.name AS project_name,
|
|
1969
1983
|
ss.request AS request,
|
|
1970
1984
|
ss.completed AS completed,
|
|
1985
|
+
ss.capture_state AS capture_state,
|
|
1986
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1987
|
+
ss.hot_files AS hot_files,
|
|
1988
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1971
1989
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1972
1990
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1973
1991
|
FROM sessions s
|
|
@@ -2140,8 +2158,11 @@ class MemDatabase {
|
|
|
2140
2158
|
completed: normalizeSummarySection(summary.completed),
|
|
2141
2159
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
2142
2160
|
};
|
|
2143
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
2144
|
-
|
|
2161
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
2162
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
2163
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
2164
|
+
)
|
|
2165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
2145
2166
|
const id = Number(result.lastInsertRowid);
|
|
2146
2167
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
2147
2168
|
}
|
|
@@ -2156,7 +2177,11 @@ class MemDatabase {
|
|
|
2156
2177
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
2157
2178
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
2158
2179
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
2159
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
2180
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
2181
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
2182
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
2183
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
2184
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
2160
2185
|
};
|
|
2161
2186
|
this.db.query(`UPDATE session_summaries
|
|
2162
2187
|
SET project_id = ?,
|
|
@@ -2166,8 +2191,12 @@ class MemDatabase {
|
|
|
2166
2191
|
learned = ?,
|
|
2167
2192
|
completed = ?,
|
|
2168
2193
|
next_steps = ?,
|
|
2194
|
+
capture_state = ?,
|
|
2195
|
+
recent_tool_names = ?,
|
|
2196
|
+
hot_files = ?,
|
|
2197
|
+
recent_outcomes = ?,
|
|
2169
2198
|
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);
|
|
2199
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
2171
2200
|
return this.getSessionSummary(summary.session_id);
|
|
2172
2201
|
}
|
|
2173
2202
|
getSessionSummary(sessionId) {
|
|
@@ -776,6 +776,16 @@ var MIGRATIONS = [
|
|
|
776
776
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
777
777
|
`
|
|
778
778
|
},
|
|
779
|
+
{
|
|
780
|
+
version: 11,
|
|
781
|
+
description: "Add synced handoff metadata to session summaries",
|
|
782
|
+
sql: `
|
|
783
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
784
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
785
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
786
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
787
|
+
`
|
|
788
|
+
},
|
|
779
789
|
{
|
|
780
790
|
version: 11,
|
|
781
791
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1299,6 +1309,10 @@ class MemDatabase {
|
|
|
1299
1309
|
p.name AS project_name,
|
|
1300
1310
|
ss.request AS request,
|
|
1301
1311
|
ss.completed AS completed,
|
|
1312
|
+
ss.capture_state AS capture_state,
|
|
1313
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1314
|
+
ss.hot_files AS hot_files,
|
|
1315
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1302
1316
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1303
1317
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1304
1318
|
FROM sessions s
|
|
@@ -1313,6 +1327,10 @@ class MemDatabase {
|
|
|
1313
1327
|
p.name AS project_name,
|
|
1314
1328
|
ss.request AS request,
|
|
1315
1329
|
ss.completed AS completed,
|
|
1330
|
+
ss.capture_state AS capture_state,
|
|
1331
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1332
|
+
ss.hot_files AS hot_files,
|
|
1333
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1316
1334
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1317
1335
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1318
1336
|
FROM sessions s
|
|
@@ -1485,8 +1503,11 @@ class MemDatabase {
|
|
|
1485
1503
|
completed: normalizeSummarySection(summary.completed),
|
|
1486
1504
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1487
1505
|
};
|
|
1488
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1489
|
-
|
|
1506
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1507
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1508
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1509
|
+
)
|
|
1510
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1490
1511
|
const id = Number(result.lastInsertRowid);
|
|
1491
1512
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1492
1513
|
}
|
|
@@ -1501,7 +1522,11 @@ class MemDatabase {
|
|
|
1501
1522
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1502
1523
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1503
1524
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1504
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1525
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1526
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1527
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1528
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
1529
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
1505
1530
|
};
|
|
1506
1531
|
this.db.query(`UPDATE session_summaries
|
|
1507
1532
|
SET project_id = ?,
|
|
@@ -1511,8 +1536,12 @@ class MemDatabase {
|
|
|
1511
1536
|
learned = ?,
|
|
1512
1537
|
completed = ?,
|
|
1513
1538
|
next_steps = ?,
|
|
1539
|
+
capture_state = ?,
|
|
1540
|
+
recent_tool_names = ?,
|
|
1541
|
+
hot_files = ?,
|
|
1542
|
+
recent_outcomes = ?,
|
|
1514
1543
|
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);
|
|
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, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1516
1545
|
return this.getSessionSummary(summary.session_id);
|
|
1517
1546
|
}
|
|
1518
1547
|
getSessionSummary(sessionId) {
|
|
@@ -3166,6 +3195,63 @@ function checkSessionFatigue(db, sessionId) {
|
|
|
3166
3195
|
};
|
|
3167
3196
|
}
|
|
3168
3197
|
|
|
3198
|
+
// src/capture/live-summary.ts
|
|
3199
|
+
var LOW_SIGNAL_TITLE_PATTERNS = [
|
|
3200
|
+
/^(Modified|Extended|Reduced|Created)\s+\S+/i,
|
|
3201
|
+
/^Dependency change:/i,
|
|
3202
|
+
/^[a-z0-9._-]+\s*\(error\)$/i,
|
|
3203
|
+
/^(engrm|unknown):\s+/i
|
|
3204
|
+
];
|
|
3205
|
+
function buildLiveSummaryUpdate(observation) {
|
|
3206
|
+
const title = compactSummaryTitle(observation.title);
|
|
3207
|
+
if (!title)
|
|
3208
|
+
return null;
|
|
3209
|
+
switch (observation.type) {
|
|
3210
|
+
case "discovery":
|
|
3211
|
+
case "pattern":
|
|
3212
|
+
return { investigated: title };
|
|
3213
|
+
case "decision":
|
|
3214
|
+
return { learned: title };
|
|
3215
|
+
case "bugfix":
|
|
3216
|
+
case "feature":
|
|
3217
|
+
case "change":
|
|
3218
|
+
case "refactor":
|
|
3219
|
+
return { completed: title };
|
|
3220
|
+
default:
|
|
3221
|
+
return null;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
function compactSummaryTitle(title) {
|
|
3225
|
+
const trimmed = title?.trim();
|
|
3226
|
+
if (!trimmed)
|
|
3227
|
+
return null;
|
|
3228
|
+
if (LOW_SIGNAL_TITLE_PATTERNS.some((pattern) => pattern.test(trimmed))) {
|
|
3229
|
+
return null;
|
|
3230
|
+
}
|
|
3231
|
+
return trimmed.length > 180 ? `${trimmed.slice(0, 177)}...` : trimmed;
|
|
3232
|
+
}
|
|
3233
|
+
function mergeLiveSummarySections(existing, update) {
|
|
3234
|
+
return {
|
|
3235
|
+
investigated: mergeSectionItem(existing?.investigated ?? null, update.investigated ?? null),
|
|
3236
|
+
learned: mergeSectionItem(existing?.learned ?? null, update.learned ?? null),
|
|
3237
|
+
completed: mergeSectionItem(existing?.completed ?? null, update.completed ?? null)
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
function mergeSectionItem(existing, item) {
|
|
3241
|
+
if (!item)
|
|
3242
|
+
return existing;
|
|
3243
|
+
if (!existing)
|
|
3244
|
+
return item;
|
|
3245
|
+
const lines = existing.split(`
|
|
3246
|
+
`).map((line) => line.replace(/^\s*-\s*/, "").trim()).filter(Boolean);
|
|
3247
|
+
const normalizedItem = item.trim();
|
|
3248
|
+
if (lines.some((line) => line.toLowerCase() === normalizedItem.toLowerCase())) {
|
|
3249
|
+
return existing;
|
|
3250
|
+
}
|
|
3251
|
+
return `${existing}
|
|
3252
|
+
- ${normalizedItem}`;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3169
3255
|
// hooks/post-tool-use.ts
|
|
3170
3256
|
async function main() {
|
|
3171
3257
|
const raw = await readStdin();
|
|
@@ -3269,7 +3355,8 @@ async function main() {
|
|
|
3269
3355
|
timeoutMs: 800
|
|
3270
3356
|
}), 1000);
|
|
3271
3357
|
if (observed) {
|
|
3272
|
-
await saveObservation(db, config, observed);
|
|
3358
|
+
const result = await saveObservation(db, config, observed);
|
|
3359
|
+
updateRollingSummaryFromObservation(db, result.observation_id, event, config.user_id);
|
|
3273
3360
|
incrementObserverSaveCount(event.session_id);
|
|
3274
3361
|
saved = true;
|
|
3275
3362
|
}
|
|
@@ -3278,7 +3365,7 @@ async function main() {
|
|
|
3278
3365
|
if (!saved) {
|
|
3279
3366
|
const extracted = extractObservation(event);
|
|
3280
3367
|
if (extracted) {
|
|
3281
|
-
await saveObservation(db, config, {
|
|
3368
|
+
const result = await saveObservation(db, config, {
|
|
3282
3369
|
type: extracted.type,
|
|
3283
3370
|
title: extracted.title,
|
|
3284
3371
|
narrative: extracted.narrative,
|
|
@@ -3288,6 +3375,7 @@ async function main() {
|
|
|
3288
3375
|
cwd: event.cwd,
|
|
3289
3376
|
source_tool: event.tool_name
|
|
3290
3377
|
});
|
|
3378
|
+
updateRollingSummaryFromObservation(db, result.observation_id, event, config.user_id);
|
|
3291
3379
|
incrementObserverSaveCount(event.session_id);
|
|
3292
3380
|
}
|
|
3293
3381
|
}
|
|
@@ -3352,6 +3440,30 @@ function detectProjectForEvent(event) {
|
|
|
3352
3440
|
const touchedPaths = extractTouchedPaths(event);
|
|
3353
3441
|
return touchedPaths.length > 0 ? detectProjectFromTouchedPaths(touchedPaths, event.cwd) : detectProject(event.cwd);
|
|
3354
3442
|
}
|
|
3443
|
+
function updateRollingSummaryFromObservation(db, observationId, event, userId) {
|
|
3444
|
+
if (!observationId || !event.session_id)
|
|
3445
|
+
return;
|
|
3446
|
+
const observation = db.getObservationById(observationId);
|
|
3447
|
+
if (!observation)
|
|
3448
|
+
return;
|
|
3449
|
+
const update = buildLiveSummaryUpdate(observation);
|
|
3450
|
+
if (!update)
|
|
3451
|
+
return;
|
|
3452
|
+
const existing = db.getSessionSummary(event.session_id);
|
|
3453
|
+
const merged = mergeLiveSummarySections(existing, update);
|
|
3454
|
+
const currentRequest = existing?.request ?? db.getSessionUserPrompts(event.session_id, 1).at(-1)?.prompt ?? null;
|
|
3455
|
+
const summary = db.upsertSessionSummary({
|
|
3456
|
+
session_id: event.session_id,
|
|
3457
|
+
project_id: observation.project_id,
|
|
3458
|
+
user_id: userId,
|
|
3459
|
+
request: currentRequest,
|
|
3460
|
+
investigated: merged.investigated,
|
|
3461
|
+
learned: merged.learned,
|
|
3462
|
+
completed: merged.completed,
|
|
3463
|
+
next_steps: existing?.next_steps ?? null
|
|
3464
|
+
});
|
|
3465
|
+
db.addToOutbox("summary", summary.id);
|
|
3466
|
+
}
|
|
3355
3467
|
function extractTouchedPaths(event) {
|
|
3356
3468
|
const paths = [];
|
|
3357
3469
|
const filePath = event.tool_input["file_path"];
|
|
@@ -570,6 +570,16 @@ var MIGRATIONS = [
|
|
|
570
570
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
571
571
|
`
|
|
572
572
|
},
|
|
573
|
+
{
|
|
574
|
+
version: 11,
|
|
575
|
+
description: "Add synced handoff metadata to session summaries",
|
|
576
|
+
sql: `
|
|
577
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
578
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
579
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
580
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
581
|
+
`
|
|
582
|
+
},
|
|
573
583
|
{
|
|
574
584
|
version: 11,
|
|
575
585
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1093,6 +1103,10 @@ class MemDatabase {
|
|
|
1093
1103
|
p.name AS project_name,
|
|
1094
1104
|
ss.request AS request,
|
|
1095
1105
|
ss.completed AS completed,
|
|
1106
|
+
ss.capture_state AS capture_state,
|
|
1107
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1108
|
+
ss.hot_files AS hot_files,
|
|
1109
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1096
1110
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1097
1111
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1098
1112
|
FROM sessions s
|
|
@@ -1107,6 +1121,10 @@ class MemDatabase {
|
|
|
1107
1121
|
p.name AS project_name,
|
|
1108
1122
|
ss.request AS request,
|
|
1109
1123
|
ss.completed AS completed,
|
|
1124
|
+
ss.capture_state AS capture_state,
|
|
1125
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1126
|
+
ss.hot_files AS hot_files,
|
|
1127
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1110
1128
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1111
1129
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1112
1130
|
FROM sessions s
|
|
@@ -1279,8 +1297,11 @@ class MemDatabase {
|
|
|
1279
1297
|
completed: normalizeSummarySection(summary.completed),
|
|
1280
1298
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1281
1299
|
};
|
|
1282
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1283
|
-
|
|
1300
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1301
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1302
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1303
|
+
)
|
|
1304
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1284
1305
|
const id = Number(result.lastInsertRowid);
|
|
1285
1306
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1286
1307
|
}
|
|
@@ -1295,7 +1316,11 @@ class MemDatabase {
|
|
|
1295
1316
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1296
1317
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1297
1318
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1298
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1319
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1320
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1321
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1322
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
1323
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
1299
1324
|
};
|
|
1300
1325
|
this.db.query(`UPDATE session_summaries
|
|
1301
1326
|
SET project_id = ?,
|
|
@@ -1305,8 +1330,12 @@ class MemDatabase {
|
|
|
1305
1330
|
learned = ?,
|
|
1306
1331
|
completed = ?,
|
|
1307
1332
|
next_steps = ?,
|
|
1333
|
+
capture_state = ?,
|
|
1334
|
+
recent_tool_names = ?,
|
|
1335
|
+
hot_files = ?,
|
|
1336
|
+
recent_outcomes = ?,
|
|
1308
1337
|
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);
|
|
1338
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1310
1339
|
return this.getSessionSummary(summary.session_id);
|
|
1311
1340
|
}
|
|
1312
1341
|
getSessionSummary(sessionId) {
|
|
@@ -1790,6 +1819,16 @@ function computeObservationPriority(obs, nowEpoch) {
|
|
|
1790
1819
|
function tokenizeProjectHint(text) {
|
|
1791
1820
|
return Array.from(new Set((text.toLowerCase().match(/[a-z0-9_+-]{4,}/g) ?? []).filter(Boolean)));
|
|
1792
1821
|
}
|
|
1822
|
+
function parseSummaryJsonList(value) {
|
|
1823
|
+
if (!value)
|
|
1824
|
+
return [];
|
|
1825
|
+
try {
|
|
1826
|
+
const parsed = JSON.parse(value);
|
|
1827
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1828
|
+
} catch {
|
|
1829
|
+
return [];
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1793
1832
|
function isObservationRelatedToProject(obs, detected) {
|
|
1794
1833
|
const hints = new Set([
|
|
1795
1834
|
...tokenizeProjectHint(detected.name),
|
|
@@ -1921,7 +1960,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1921
1960
|
const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
1922
1961
|
const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
1923
1962
|
const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
1924
|
-
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId);
|
|
1963
|
+
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions2);
|
|
1925
1964
|
return {
|
|
1926
1965
|
project_name: projectName,
|
|
1927
1966
|
canonical_id: canonicalId,
|
|
@@ -1959,7 +1998,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1959
1998
|
const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
1960
1999
|
const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
1961
2000
|
const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
1962
|
-
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId);
|
|
2001
|
+
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions);
|
|
1963
2002
|
let securityFindings = [];
|
|
1964
2003
|
if (!isNewProject) {
|
|
1965
2004
|
try {
|
|
@@ -2302,7 +2341,7 @@ function getProjectTypeCounts(db, projectId, userId) {
|
|
|
2302
2341
|
}
|
|
2303
2342
|
return counts;
|
|
2304
2343
|
}
|
|
2305
|
-
function getRecentOutcomes(db, projectId, userId) {
|
|
2344
|
+
function getRecentOutcomes(db, projectId, userId, recentSessions) {
|
|
2306
2345
|
const visibilityClause = userId ? " AND (sensitivity != 'personal' OR user_id = ?)" : "";
|
|
2307
2346
|
const visibilityParams = userId ? [userId] : [];
|
|
2308
2347
|
const summaries = db.db.query(`SELECT * FROM session_summaries
|
|
@@ -2312,6 +2351,15 @@ function getRecentOutcomes(db, projectId, userId) {
|
|
|
2312
2351
|
const picked = [];
|
|
2313
2352
|
const seen = new Set;
|
|
2314
2353
|
for (const summary of summaries) {
|
|
2354
|
+
for (const item of parseSummaryJsonList(summary.recent_outcomes)) {
|
|
2355
|
+
const normalized = item.toLowerCase().replace(/\s+/g, " ").trim();
|
|
2356
|
+
if (!normalized || seen.has(normalized))
|
|
2357
|
+
continue;
|
|
2358
|
+
seen.add(normalized);
|
|
2359
|
+
picked.push(item);
|
|
2360
|
+
if (picked.length >= 5)
|
|
2361
|
+
return picked;
|
|
2362
|
+
}
|
|
2315
2363
|
for (const line of [
|
|
2316
2364
|
...extractMeaningfulLines(summary.completed, 2),
|
|
2317
2365
|
...extractMeaningfulLines(summary.learned, 1)
|
|
@@ -2325,6 +2373,17 @@ function getRecentOutcomes(db, projectId, userId) {
|
|
|
2325
2373
|
return picked;
|
|
2326
2374
|
}
|
|
2327
2375
|
}
|
|
2376
|
+
for (const session of recentSessions ?? []) {
|
|
2377
|
+
for (const item of parseSummaryJsonList(session.recent_outcomes)) {
|
|
2378
|
+
const normalized = item.toLowerCase().replace(/\s+/g, " ").trim();
|
|
2379
|
+
if (!normalized || seen.has(normalized))
|
|
2380
|
+
continue;
|
|
2381
|
+
seen.add(normalized);
|
|
2382
|
+
picked.push(item);
|
|
2383
|
+
if (picked.length >= 5)
|
|
2384
|
+
return picked;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2328
2387
|
const rows = db.db.query(`SELECT * FROM observations
|
|
2329
2388
|
WHERE project_id = ?
|
|
2330
2389
|
AND lifecycle IN ('active', 'aging', 'pinned')
|
package/dist/hooks/sentinel.js
CHANGED
|
@@ -646,6 +646,16 @@ var MIGRATIONS = [
|
|
|
646
646
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
647
647
|
`
|
|
648
648
|
},
|
|
649
|
+
{
|
|
650
|
+
version: 11,
|
|
651
|
+
description: "Add synced handoff metadata to session summaries",
|
|
652
|
+
sql: `
|
|
653
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
654
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
655
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
656
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
657
|
+
`
|
|
658
|
+
},
|
|
649
659
|
{
|
|
650
660
|
version: 11,
|
|
651
661
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1169,6 +1179,10 @@ class MemDatabase {
|
|
|
1169
1179
|
p.name AS project_name,
|
|
1170
1180
|
ss.request AS request,
|
|
1171
1181
|
ss.completed AS completed,
|
|
1182
|
+
ss.capture_state AS capture_state,
|
|
1183
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1184
|
+
ss.hot_files AS hot_files,
|
|
1185
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1172
1186
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1173
1187
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1174
1188
|
FROM sessions s
|
|
@@ -1183,6 +1197,10 @@ class MemDatabase {
|
|
|
1183
1197
|
p.name AS project_name,
|
|
1184
1198
|
ss.request AS request,
|
|
1185
1199
|
ss.completed AS completed,
|
|
1200
|
+
ss.capture_state AS capture_state,
|
|
1201
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1202
|
+
ss.hot_files AS hot_files,
|
|
1203
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1186
1204
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1187
1205
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1188
1206
|
FROM sessions s
|
|
@@ -1355,8 +1373,11 @@ class MemDatabase {
|
|
|
1355
1373
|
completed: normalizeSummarySection(summary.completed),
|
|
1356
1374
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1357
1375
|
};
|
|
1358
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1359
|
-
|
|
1376
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1377
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1378
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1379
|
+
)
|
|
1380
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1360
1381
|
const id = Number(result.lastInsertRowid);
|
|
1361
1382
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1362
1383
|
}
|
|
@@ -1371,7 +1392,11 @@ class MemDatabase {
|
|
|
1371
1392
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1372
1393
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1373
1394
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1374
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1395
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1396
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1397
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1398
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
1399
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
1375
1400
|
};
|
|
1376
1401
|
this.db.query(`UPDATE session_summaries
|
|
1377
1402
|
SET project_id = ?,
|
|
@@ -1381,8 +1406,12 @@ class MemDatabase {
|
|
|
1381
1406
|
learned = ?,
|
|
1382
1407
|
completed = ?,
|
|
1383
1408
|
next_steps = ?,
|
|
1409
|
+
capture_state = ?,
|
|
1410
|
+
recent_tool_names = ?,
|
|
1411
|
+
hot_files = ?,
|
|
1412
|
+
recent_outcomes = ?,
|
|
1384
1413
|
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);
|
|
1414
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1386
1415
|
return this.getSessionSummary(summary.session_id);
|
|
1387
1416
|
}
|
|
1388
1417
|
getSessionSummary(sessionId) {
|
|
@@ -477,6 +477,16 @@ function normalizeItem(value) {
|
|
|
477
477
|
function tokenizeProjectHint(text) {
|
|
478
478
|
return Array.from(new Set((text.toLowerCase().match(/[a-z0-9_+-]{4,}/g) ?? []).filter(Boolean)));
|
|
479
479
|
}
|
|
480
|
+
function parseSummaryJsonList(value) {
|
|
481
|
+
if (!value)
|
|
482
|
+
return [];
|
|
483
|
+
try {
|
|
484
|
+
const parsed = JSON.parse(value);
|
|
485
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
486
|
+
} catch {
|
|
487
|
+
return [];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
480
490
|
function isObservationRelatedToProject(obs, detected) {
|
|
481
491
|
const hints = new Set([
|
|
482
492
|
...tokenizeProjectHint(detected.name),
|
|
@@ -608,7 +618,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
608
618
|
const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
609
619
|
const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
610
620
|
const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
611
|
-
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId);
|
|
621
|
+
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions2);
|
|
612
622
|
return {
|
|
613
623
|
project_name: projectName,
|
|
614
624
|
canonical_id: canonicalId,
|
|
@@ -646,7 +656,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
646
656
|
const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
647
657
|
const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
648
658
|
const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
649
|
-
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId);
|
|
659
|
+
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions);
|
|
650
660
|
let securityFindings = [];
|
|
651
661
|
if (!isNewProject) {
|
|
652
662
|
try {
|
|
@@ -989,7 +999,7 @@ function getProjectTypeCounts(db, projectId, userId) {
|
|
|
989
999
|
}
|
|
990
1000
|
return counts;
|
|
991
1001
|
}
|
|
992
|
-
function getRecentOutcomes(db, projectId, userId) {
|
|
1002
|
+
function getRecentOutcomes(db, projectId, userId, recentSessions) {
|
|
993
1003
|
const visibilityClause = userId ? " AND (sensitivity != 'personal' OR user_id = ?)" : "";
|
|
994
1004
|
const visibilityParams = userId ? [userId] : [];
|
|
995
1005
|
const summaries = db.db.query(`SELECT * FROM session_summaries
|
|
@@ -999,6 +1009,15 @@ function getRecentOutcomes(db, projectId, userId) {
|
|
|
999
1009
|
const picked = [];
|
|
1000
1010
|
const seen = new Set;
|
|
1001
1011
|
for (const summary of summaries) {
|
|
1012
|
+
for (const item of parseSummaryJsonList(summary.recent_outcomes)) {
|
|
1013
|
+
const normalized = item.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1014
|
+
if (!normalized || seen.has(normalized))
|
|
1015
|
+
continue;
|
|
1016
|
+
seen.add(normalized);
|
|
1017
|
+
picked.push(item);
|
|
1018
|
+
if (picked.length >= 5)
|
|
1019
|
+
return picked;
|
|
1020
|
+
}
|
|
1002
1021
|
for (const line of [
|
|
1003
1022
|
...extractMeaningfulLines(summary.completed, 2),
|
|
1004
1023
|
...extractMeaningfulLines(summary.learned, 1)
|
|
@@ -1012,6 +1031,17 @@ function getRecentOutcomes(db, projectId, userId) {
|
|
|
1012
1031
|
return picked;
|
|
1013
1032
|
}
|
|
1014
1033
|
}
|
|
1034
|
+
for (const session of recentSessions ?? []) {
|
|
1035
|
+
for (const item of parseSummaryJsonList(session.recent_outcomes)) {
|
|
1036
|
+
const normalized = item.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1037
|
+
if (!normalized || seen.has(normalized))
|
|
1038
|
+
continue;
|
|
1039
|
+
seen.add(normalized);
|
|
1040
|
+
picked.push(item);
|
|
1041
|
+
if (picked.length >= 5)
|
|
1042
|
+
return picked;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1015
1045
|
const rows = db.db.query(`SELECT * FROM observations
|
|
1016
1046
|
WHERE project_id = ?
|
|
1017
1047
|
AND lifecycle IN ('active', 'aging', 'pinned')
|
|
@@ -1154,7 +1184,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
|
|
|
1154
1184
|
import { join as join3 } from "node:path";
|
|
1155
1185
|
import { homedir } from "node:os";
|
|
1156
1186
|
var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
|
|
1157
|
-
var CLIENT_VERSION = "0.4.
|
|
1187
|
+
var CLIENT_VERSION = "0.4.21";
|
|
1158
1188
|
function hashFile(filePath) {
|
|
1159
1189
|
try {
|
|
1160
1190
|
if (!existsSync3(filePath))
|
|
@@ -1696,10 +1726,20 @@ function mergeRemoteSummary(db, config, change, projectId) {
|
|
|
1696
1726
|
investigated: typeof change.metadata?.investigated === "string" ? change.metadata.investigated : null,
|
|
1697
1727
|
learned: typeof change.metadata?.learned === "string" ? change.metadata.learned : null,
|
|
1698
1728
|
completed: typeof change.metadata?.completed === "string" ? change.metadata.completed : null,
|
|
1699
|
-
next_steps: typeof change.metadata?.next_steps === "string" ? change.metadata.next_steps : null
|
|
1729
|
+
next_steps: typeof change.metadata?.next_steps === "string" ? change.metadata.next_steps : null,
|
|
1730
|
+
capture_state: typeof change.metadata?.capture_state === "string" ? change.metadata.capture_state : null,
|
|
1731
|
+
recent_tool_names: encodeStringArray(change.metadata?.recent_tool_names),
|
|
1732
|
+
hot_files: encodeStringArray(change.metadata?.hot_files),
|
|
1733
|
+
recent_outcomes: encodeStringArray(change.metadata?.recent_outcomes)
|
|
1700
1734
|
});
|
|
1701
1735
|
return Boolean(summary);
|
|
1702
1736
|
}
|
|
1737
|
+
function encodeStringArray(value) {
|
|
1738
|
+
if (!Array.isArray(value))
|
|
1739
|
+
return null;
|
|
1740
|
+
const normalized = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
1741
|
+
return normalized.length > 0 ? JSON.stringify(normalized) : null;
|
|
1742
|
+
}
|
|
1703
1743
|
function normalizeRemoteObservationType(rawType, sourceId) {
|
|
1704
1744
|
const type = typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
|
|
1705
1745
|
if (type === "bugfix" || type === "discovery" || type === "decision" || type === "pattern" || type === "change" || type === "feature" || type === "refactor" || type === "digest" || type === "standard" || type === "message") {
|
|
@@ -2246,6 +2286,16 @@ var MIGRATIONS = [
|
|
|
2246
2286
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
2247
2287
|
`
|
|
2248
2288
|
},
|
|
2289
|
+
{
|
|
2290
|
+
version: 11,
|
|
2291
|
+
description: "Add synced handoff metadata to session summaries",
|
|
2292
|
+
sql: `
|
|
2293
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
2294
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
2295
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
2296
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
2297
|
+
`
|
|
2298
|
+
},
|
|
2249
2299
|
{
|
|
2250
2300
|
version: 11,
|
|
2251
2301
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -2689,6 +2739,10 @@ class MemDatabase {
|
|
|
2689
2739
|
p.name AS project_name,
|
|
2690
2740
|
ss.request AS request,
|
|
2691
2741
|
ss.completed AS completed,
|
|
2742
|
+
ss.capture_state AS capture_state,
|
|
2743
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
2744
|
+
ss.hot_files AS hot_files,
|
|
2745
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
2692
2746
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
2693
2747
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
2694
2748
|
FROM sessions s
|
|
@@ -2703,6 +2757,10 @@ class MemDatabase {
|
|
|
2703
2757
|
p.name AS project_name,
|
|
2704
2758
|
ss.request AS request,
|
|
2705
2759
|
ss.completed AS completed,
|
|
2760
|
+
ss.capture_state AS capture_state,
|
|
2761
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
2762
|
+
ss.hot_files AS hot_files,
|
|
2763
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
2706
2764
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
2707
2765
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
2708
2766
|
FROM sessions s
|
|
@@ -2875,8 +2933,11 @@ class MemDatabase {
|
|
|
2875
2933
|
completed: normalizeSummarySection(summary.completed),
|
|
2876
2934
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
2877
2935
|
};
|
|
2878
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
2879
|
-
|
|
2936
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
2937
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
2938
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
2939
|
+
)
|
|
2940
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
2880
2941
|
const id = Number(result.lastInsertRowid);
|
|
2881
2942
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
2882
2943
|
}
|
|
@@ -2891,7 +2952,11 @@ class MemDatabase {
|
|
|
2891
2952
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
2892
2953
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
2893
2954
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
2894
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
2955
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
2956
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
2957
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
2958
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
2959
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
2895
2960
|
};
|
|
2896
2961
|
this.db.query(`UPDATE session_summaries
|
|
2897
2962
|
SET project_id = ?,
|
|
@@ -2901,8 +2966,12 @@ class MemDatabase {
|
|
|
2901
2966
|
learned = ?,
|
|
2902
2967
|
completed = ?,
|
|
2903
2968
|
next_steps = ?,
|
|
2969
|
+
capture_state = ?,
|
|
2970
|
+
recent_tool_names = ?,
|
|
2971
|
+
hot_files = ?,
|
|
2972
|
+
recent_outcomes = ?,
|
|
2904
2973
|
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);
|
|
2974
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
2906
2975
|
return this.getSessionSummary(summary.session_id);
|
|
2907
2976
|
}
|
|
2908
2977
|
getSessionSummary(sessionId) {
|
|
@@ -3450,10 +3519,13 @@ function duplicatesPromptLine(request, promptLine) {
|
|
|
3450
3519
|
return normalizeStartupItem(request) === normalizeStartupItem(promptBody);
|
|
3451
3520
|
}
|
|
3452
3521
|
function buildToolFallbacks(context) {
|
|
3453
|
-
|
|
3522
|
+
const fromEvents = (context.recentToolEvents ?? []).slice(0, 3).map((tool) => {
|
|
3454
3523
|
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
3455
3524
|
return `${tool.tool_name}${detail ? `: ${detail}` : ""}`.trim();
|
|
3456
3525
|
}).filter((item) => item.length > 0);
|
|
3526
|
+
if (fromEvents.length > 0)
|
|
3527
|
+
return fromEvents;
|
|
3528
|
+
return (context.recentSessions ?? []).flatMap((session) => parseSessionJsonList(session.recent_tool_names)).slice(0, 3).filter((item) => item.length > 0);
|
|
3457
3529
|
}
|
|
3458
3530
|
function sessionFallbacksFromContext(context) {
|
|
3459
3531
|
return (context.recentSessions ?? []).slice(0, 2).map((session) => {
|
|
@@ -3479,6 +3551,19 @@ function buildRecentOutcomeLines(context, summary) {
|
|
|
3479
3551
|
};
|
|
3480
3552
|
push(summary?.completed);
|
|
3481
3553
|
push(summary?.learned);
|
|
3554
|
+
if (picked.length < 2) {
|
|
3555
|
+
for (const session of context.recentSessions ?? []) {
|
|
3556
|
+
for (const item of parseSessionJsonList(session.recent_outcomes)) {
|
|
3557
|
+
const normalized = normalizeStartupItem(item);
|
|
3558
|
+
if (!normalized || seen.has(normalized))
|
|
3559
|
+
continue;
|
|
3560
|
+
seen.add(normalized);
|
|
3561
|
+
picked.push(item);
|
|
3562
|
+
if (picked.length >= 2)
|
|
3563
|
+
return picked;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3482
3567
|
if (picked.length < 2) {
|
|
3483
3568
|
for (const obs of context.observations) {
|
|
3484
3569
|
if (!["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type))
|
|
@@ -3508,6 +3593,16 @@ function chooseMeaningfulSessionSummary(request, completed) {
|
|
|
3508
3593
|
}
|
|
3509
3594
|
return request ?? completed ?? null;
|
|
3510
3595
|
}
|
|
3596
|
+
function parseSessionJsonList(value) {
|
|
3597
|
+
if (!value)
|
|
3598
|
+
return [];
|
|
3599
|
+
try {
|
|
3600
|
+
const parsed = JSON.parse(value);
|
|
3601
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
3602
|
+
} catch {
|
|
3603
|
+
return [];
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3511
3606
|
function buildProjectSignalLine(context) {
|
|
3512
3607
|
if (!context.projectTypeCounts)
|
|
3513
3608
|
return null;
|
package/dist/hooks/stop.js
CHANGED
|
@@ -883,6 +883,16 @@ var MIGRATIONS = [
|
|
|
883
883
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
884
884
|
`
|
|
885
885
|
},
|
|
886
|
+
{
|
|
887
|
+
version: 11,
|
|
888
|
+
description: "Add synced handoff metadata to session summaries",
|
|
889
|
+
sql: `
|
|
890
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
891
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
892
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
893
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
894
|
+
`
|
|
895
|
+
},
|
|
886
896
|
{
|
|
887
897
|
version: 11,
|
|
888
898
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1326,6 +1336,10 @@ class MemDatabase {
|
|
|
1326
1336
|
p.name AS project_name,
|
|
1327
1337
|
ss.request AS request,
|
|
1328
1338
|
ss.completed AS completed,
|
|
1339
|
+
ss.capture_state AS capture_state,
|
|
1340
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1341
|
+
ss.hot_files AS hot_files,
|
|
1342
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1329
1343
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1330
1344
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1331
1345
|
FROM sessions s
|
|
@@ -1340,6 +1354,10 @@ class MemDatabase {
|
|
|
1340
1354
|
p.name AS project_name,
|
|
1341
1355
|
ss.request AS request,
|
|
1342
1356
|
ss.completed AS completed,
|
|
1357
|
+
ss.capture_state AS capture_state,
|
|
1358
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1359
|
+
ss.hot_files AS hot_files,
|
|
1360
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1343
1361
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1344
1362
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1345
1363
|
FROM sessions s
|
|
@@ -1512,8 +1530,11 @@ class MemDatabase {
|
|
|
1512
1530
|
completed: normalizeSummarySection(summary.completed),
|
|
1513
1531
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1514
1532
|
};
|
|
1515
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1516
|
-
|
|
1533
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1534
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1535
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1536
|
+
)
|
|
1537
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1517
1538
|
const id = Number(result.lastInsertRowid);
|
|
1518
1539
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1519
1540
|
}
|
|
@@ -1528,7 +1549,11 @@ class MemDatabase {
|
|
|
1528
1549
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1529
1550
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1530
1551
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1531
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1552
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1553
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1554
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1555
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
1556
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
1532
1557
|
};
|
|
1533
1558
|
this.db.query(`UPDATE session_summaries
|
|
1534
1559
|
SET project_id = ?,
|
|
@@ -1538,8 +1563,12 @@ class MemDatabase {
|
|
|
1538
1563
|
learned = ?,
|
|
1539
1564
|
completed = ?,
|
|
1540
1565
|
next_steps = ?,
|
|
1566
|
+
capture_state = ?,
|
|
1567
|
+
recent_tool_names = ?,
|
|
1568
|
+
hot_files = ?,
|
|
1569
|
+
recent_outcomes = ?,
|
|
1541
1570
|
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);
|
|
1571
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1543
1572
|
return this.getSessionSummary(summary.session_id);
|
|
1544
1573
|
}
|
|
1545
1574
|
getSessionSummary(sessionId) {
|
|
@@ -2558,7 +2587,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
2558
2587
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
2559
2588
|
risk_score: riskScore,
|
|
2560
2589
|
stacks_detected: stacks,
|
|
2561
|
-
client_version: "0.4.
|
|
2590
|
+
client_version: "0.4.21",
|
|
2562
2591
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
2563
2592
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
2564
2593
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -714,6 +714,16 @@ var MIGRATIONS = [
|
|
|
714
714
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
715
715
|
`
|
|
716
716
|
},
|
|
717
|
+
{
|
|
718
|
+
version: 11,
|
|
719
|
+
description: "Add synced handoff metadata to session summaries",
|
|
720
|
+
sql: `
|
|
721
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
722
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
723
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
724
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
725
|
+
`
|
|
726
|
+
},
|
|
717
727
|
{
|
|
718
728
|
version: 11,
|
|
719
729
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -1237,6 +1247,10 @@ class MemDatabase {
|
|
|
1237
1247
|
p.name AS project_name,
|
|
1238
1248
|
ss.request AS request,
|
|
1239
1249
|
ss.completed AS completed,
|
|
1250
|
+
ss.capture_state AS capture_state,
|
|
1251
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1252
|
+
ss.hot_files AS hot_files,
|
|
1253
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1240
1254
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1241
1255
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1242
1256
|
FROM sessions s
|
|
@@ -1251,6 +1265,10 @@ class MemDatabase {
|
|
|
1251
1265
|
p.name AS project_name,
|
|
1252
1266
|
ss.request AS request,
|
|
1253
1267
|
ss.completed AS completed,
|
|
1268
|
+
ss.capture_state AS capture_state,
|
|
1269
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
1270
|
+
ss.hot_files AS hot_files,
|
|
1271
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
1254
1272
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
1255
1273
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
1256
1274
|
FROM sessions s
|
|
@@ -1423,8 +1441,11 @@ class MemDatabase {
|
|
|
1423
1441
|
completed: normalizeSummarySection(summary.completed),
|
|
1424
1442
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1425
1443
|
};
|
|
1426
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1427
|
-
|
|
1444
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1445
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1446
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1447
|
+
)
|
|
1448
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1428
1449
|
const id = Number(result.lastInsertRowid);
|
|
1429
1450
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1430
1451
|
}
|
|
@@ -1439,7 +1460,11 @@ class MemDatabase {
|
|
|
1439
1460
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
1440
1461
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1441
1462
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1442
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
1463
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1464
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1465
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1466
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
1467
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
1443
1468
|
};
|
|
1444
1469
|
this.db.query(`UPDATE session_summaries
|
|
1445
1470
|
SET project_id = ?,
|
|
@@ -1449,8 +1474,12 @@ class MemDatabase {
|
|
|
1449
1474
|
learned = ?,
|
|
1450
1475
|
completed = ?,
|
|
1451
1476
|
next_steps = ?,
|
|
1477
|
+
capture_state = ?,
|
|
1478
|
+
recent_tool_names = ?,
|
|
1479
|
+
hot_files = ?,
|
|
1480
|
+
recent_outcomes = ?,
|
|
1452
1481
|
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);
|
|
1482
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1454
1483
|
return this.getSessionSummary(summary.session_id);
|
|
1455
1484
|
}
|
|
1456
1485
|
getSessionSummary(sessionId) {
|
package/dist/server.js
CHANGED
|
@@ -14120,6 +14120,16 @@ var MIGRATIONS = [
|
|
|
14120
14120
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
14121
14121
|
`
|
|
14122
14122
|
},
|
|
14123
|
+
{
|
|
14124
|
+
version: 11,
|
|
14125
|
+
description: "Add synced handoff metadata to session summaries",
|
|
14126
|
+
sql: `
|
|
14127
|
+
ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
|
|
14128
|
+
ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
|
|
14129
|
+
ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
|
|
14130
|
+
ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
|
|
14131
|
+
`
|
|
14132
|
+
},
|
|
14123
14133
|
{
|
|
14124
14134
|
version: 11,
|
|
14125
14135
|
description: "Add observation provenance from tool and prompt chronology",
|
|
@@ -14647,6 +14657,10 @@ class MemDatabase {
|
|
|
14647
14657
|
p.name AS project_name,
|
|
14648
14658
|
ss.request AS request,
|
|
14649
14659
|
ss.completed AS completed,
|
|
14660
|
+
ss.capture_state AS capture_state,
|
|
14661
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
14662
|
+
ss.hot_files AS hot_files,
|
|
14663
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
14650
14664
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
14651
14665
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
14652
14666
|
FROM sessions s
|
|
@@ -14661,6 +14675,10 @@ class MemDatabase {
|
|
|
14661
14675
|
p.name AS project_name,
|
|
14662
14676
|
ss.request AS request,
|
|
14663
14677
|
ss.completed AS completed,
|
|
14678
|
+
ss.capture_state AS capture_state,
|
|
14679
|
+
ss.recent_tool_names AS recent_tool_names,
|
|
14680
|
+
ss.hot_files AS hot_files,
|
|
14681
|
+
ss.recent_outcomes AS recent_outcomes,
|
|
14664
14682
|
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
14665
14683
|
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
14666
14684
|
FROM sessions s
|
|
@@ -14833,8 +14851,11 @@ class MemDatabase {
|
|
|
14833
14851
|
completed: normalizeSummarySection(summary.completed),
|
|
14834
14852
|
next_steps: normalizeSummarySection(summary.next_steps)
|
|
14835
14853
|
};
|
|
14836
|
-
const result = this.db.query(`INSERT INTO session_summaries (
|
|
14837
|
-
|
|
14854
|
+
const result = this.db.query(`INSERT INTO session_summaries (
|
|
14855
|
+
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
14856
|
+
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
14857
|
+
)
|
|
14858
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
14838
14859
|
const id = Number(result.lastInsertRowid);
|
|
14839
14860
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
14840
14861
|
}
|
|
@@ -14849,7 +14870,11 @@ class MemDatabase {
|
|
|
14849
14870
|
investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
|
|
14850
14871
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
14851
14872
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
14852
|
-
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
|
|
14873
|
+
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
14874
|
+
capture_state: summary.capture_state ?? existing.capture_state,
|
|
14875
|
+
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
14876
|
+
hot_files: summary.hot_files ?? existing.hot_files,
|
|
14877
|
+
recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
|
|
14853
14878
|
};
|
|
14854
14879
|
this.db.query(`UPDATE session_summaries
|
|
14855
14880
|
SET project_id = ?,
|
|
@@ -14859,8 +14884,12 @@ class MemDatabase {
|
|
|
14859
14884
|
learned = ?,
|
|
14860
14885
|
completed = ?,
|
|
14861
14886
|
next_steps = ?,
|
|
14887
|
+
capture_state = ?,
|
|
14888
|
+
recent_tool_names = ?,
|
|
14889
|
+
hot_files = ?,
|
|
14890
|
+
recent_outcomes = ?,
|
|
14862
14891
|
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);
|
|
14892
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
14864
14893
|
return this.getSessionSummary(summary.session_id);
|
|
14865
14894
|
}
|
|
14866
14895
|
getSessionSummary(sessionId) {
|
|
@@ -16460,6 +16489,16 @@ function findStaleDecisionsGlobal(db, options) {
|
|
|
16460
16489
|
function tokenizeProjectHint(text) {
|
|
16461
16490
|
return Array.from(new Set((text.toLowerCase().match(/[a-z0-9_+-]{4,}/g) ?? []).filter(Boolean)));
|
|
16462
16491
|
}
|
|
16492
|
+
function parseSummaryJsonList(value) {
|
|
16493
|
+
if (!value)
|
|
16494
|
+
return [];
|
|
16495
|
+
try {
|
|
16496
|
+
const parsed = JSON.parse(value);
|
|
16497
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
16498
|
+
} catch {
|
|
16499
|
+
return [];
|
|
16500
|
+
}
|
|
16501
|
+
}
|
|
16463
16502
|
function isObservationRelatedToProject(obs, detected) {
|
|
16464
16503
|
const hints = new Set([
|
|
16465
16504
|
...tokenizeProjectHint(detected.name),
|
|
@@ -16591,7 +16630,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
16591
16630
|
const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
16592
16631
|
const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
16593
16632
|
const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
16594
|
-
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId);
|
|
16633
|
+
const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions2);
|
|
16595
16634
|
return {
|
|
16596
16635
|
project_name: projectName,
|
|
16597
16636
|
canonical_id: canonicalId,
|
|
@@ -16629,7 +16668,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
16629
16668
|
const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
16630
16669
|
const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
16631
16670
|
const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
16632
|
-
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId);
|
|
16671
|
+
const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions);
|
|
16633
16672
|
let securityFindings = [];
|
|
16634
16673
|
if (!isNewProject) {
|
|
16635
16674
|
try {
|
|
@@ -16972,7 +17011,7 @@ function getProjectTypeCounts(db, projectId, userId) {
|
|
|
16972
17011
|
}
|
|
16973
17012
|
return counts;
|
|
16974
17013
|
}
|
|
16975
|
-
function getRecentOutcomes(db, projectId, userId) {
|
|
17014
|
+
function getRecentOutcomes(db, projectId, userId, recentSessions) {
|
|
16976
17015
|
const visibilityClause = userId ? " AND (sensitivity != 'personal' OR user_id = ?)" : "";
|
|
16977
17016
|
const visibilityParams = userId ? [userId] : [];
|
|
16978
17017
|
const summaries = db.db.query(`SELECT * FROM session_summaries
|
|
@@ -16982,6 +17021,15 @@ function getRecentOutcomes(db, projectId, userId) {
|
|
|
16982
17021
|
const picked = [];
|
|
16983
17022
|
const seen = new Set;
|
|
16984
17023
|
for (const summary of summaries) {
|
|
17024
|
+
for (const item of parseSummaryJsonList(summary.recent_outcomes)) {
|
|
17025
|
+
const normalized = item.toLowerCase().replace(/\s+/g, " ").trim();
|
|
17026
|
+
if (!normalized || seen.has(normalized))
|
|
17027
|
+
continue;
|
|
17028
|
+
seen.add(normalized);
|
|
17029
|
+
picked.push(item);
|
|
17030
|
+
if (picked.length >= 5)
|
|
17031
|
+
return picked;
|
|
17032
|
+
}
|
|
16985
17033
|
for (const line of [
|
|
16986
17034
|
...extractMeaningfulLines(summary.completed, 2),
|
|
16987
17035
|
...extractMeaningfulLines(summary.learned, 1)
|
|
@@ -16995,6 +17043,17 @@ function getRecentOutcomes(db, projectId, userId) {
|
|
|
16995
17043
|
return picked;
|
|
16996
17044
|
}
|
|
16997
17045
|
}
|
|
17046
|
+
for (const session of recentSessions ?? []) {
|
|
17047
|
+
for (const item of parseSummaryJsonList(session.recent_outcomes)) {
|
|
17048
|
+
const normalized = item.toLowerCase().replace(/\s+/g, " ").trim();
|
|
17049
|
+
if (!normalized || seen.has(normalized))
|
|
17050
|
+
continue;
|
|
17051
|
+
seen.add(normalized);
|
|
17052
|
+
picked.push(item);
|
|
17053
|
+
if (picked.length >= 5)
|
|
17054
|
+
return picked;
|
|
17055
|
+
}
|
|
17056
|
+
}
|
|
16998
17057
|
const rows = db.db.query(`SELECT * FROM observations
|
|
16999
17058
|
WHERE project_id = ?
|
|
17000
17059
|
AND lifecycle IN ('active', 'aging', 'pinned')
|
|
@@ -19011,10 +19070,20 @@ function mergeRemoteSummary(db, config2, change, projectId) {
|
|
|
19011
19070
|
investigated: typeof change.metadata?.investigated === "string" ? change.metadata.investigated : null,
|
|
19012
19071
|
learned: typeof change.metadata?.learned === "string" ? change.metadata.learned : null,
|
|
19013
19072
|
completed: typeof change.metadata?.completed === "string" ? change.metadata.completed : null,
|
|
19014
|
-
next_steps: typeof change.metadata?.next_steps === "string" ? change.metadata.next_steps : null
|
|
19073
|
+
next_steps: typeof change.metadata?.next_steps === "string" ? change.metadata.next_steps : null,
|
|
19074
|
+
capture_state: typeof change.metadata?.capture_state === "string" ? change.metadata.capture_state : null,
|
|
19075
|
+
recent_tool_names: encodeStringArray(change.metadata?.recent_tool_names),
|
|
19076
|
+
hot_files: encodeStringArray(change.metadata?.hot_files),
|
|
19077
|
+
recent_outcomes: encodeStringArray(change.metadata?.recent_outcomes)
|
|
19015
19078
|
});
|
|
19016
19079
|
return Boolean(summary);
|
|
19017
19080
|
}
|
|
19081
|
+
function encodeStringArray(value) {
|
|
19082
|
+
if (!Array.isArray(value))
|
|
19083
|
+
return null;
|
|
19084
|
+
const normalized = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
19085
|
+
return normalized.length > 0 ? JSON.stringify(normalized) : null;
|
|
19086
|
+
}
|
|
19018
19087
|
function normalizeRemoteObservationType(rawType, sourceId) {
|
|
19019
19088
|
const type = typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
|
|
19020
19089
|
if (type === "bugfix" || type === "discovery" || type === "decision" || type === "pattern" || type === "change" || type === "feature" || type === "refactor" || type === "digest" || type === "standard" || type === "message") {
|
|
@@ -19817,7 +19886,7 @@ process.on("SIGTERM", () => {
|
|
|
19817
19886
|
});
|
|
19818
19887
|
var server = new McpServer({
|
|
19819
19888
|
name: "engrm",
|
|
19820
|
-
version: "0.4.
|
|
19889
|
+
version: "0.4.21"
|
|
19821
19890
|
});
|
|
19822
19891
|
server.tool("save_observation", "Save an observation to memory", {
|
|
19823
19892
|
type: exports_external.enum([
|