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 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) {
@@ -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.19";
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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
2879
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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
- return (context.recentToolEvents ?? []).slice(0, 3).map((tool) => {
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;
@@ -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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
1516
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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.19",
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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
1427
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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 (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
14837
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
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.19"
19889
+ version: "0.4.21"
19821
19890
  });
19822
19891
  server.tool("save_observation", "Save an observation to memory", {
19823
19892
  type: exports_external.enum([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "Shared memory across devices, sessions, and coding agents",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",