engrm 0.4.18 → 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 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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
1315
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
2144
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
1489
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
1283
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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')
@@ -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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
1359
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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) {