engrm 0.4.19 → 0.4.22

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: 12,
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",
@@ -664,6 +674,9 @@ function inferLegacySchemaVersion(db) {
664
674
  version = Math.max(version, 10);
665
675
  if (columnExists(db, "observations", "source_tool"))
666
676
  version = Math.max(version, 11);
677
+ if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
678
+ version = Math.max(version, 12);
679
+ }
667
680
  return version;
668
681
  }
669
682
  function runMigrations(db) {
@@ -742,6 +755,23 @@ function ensureObservationTypes(db) {
742
755
  }
743
756
  }
744
757
  }
758
+ function ensureSessionSummaryColumns(db) {
759
+ const required = [
760
+ "capture_state",
761
+ "recent_tool_names",
762
+ "hot_files",
763
+ "recent_outcomes"
764
+ ];
765
+ for (const column of required) {
766
+ if (columnExists(db, "session_summaries", column))
767
+ continue;
768
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
769
+ }
770
+ const current = getSchemaVersion(db);
771
+ if (current < 12) {
772
+ db.exec("PRAGMA user_version = 12");
773
+ }
774
+ }
745
775
  function getSchemaVersion(db) {
746
776
  const result = db.query("PRAGMA user_version").get();
747
777
  return result.user_version;
@@ -900,6 +930,7 @@ class MemDatabase {
900
930
  this.vecAvailable = this.loadVecExtension();
901
931
  runMigrations(this.db);
902
932
  ensureObservationTypes(this.db);
933
+ ensureSessionSummaryColumns(this.db);
903
934
  }
904
935
  loadVecExtension() {
905
936
  try {
@@ -1125,6 +1156,10 @@ class MemDatabase {
1125
1156
  p.name AS project_name,
1126
1157
  ss.request AS request,
1127
1158
  ss.completed AS completed,
1159
+ ss.capture_state AS capture_state,
1160
+ ss.recent_tool_names AS recent_tool_names,
1161
+ ss.hot_files AS hot_files,
1162
+ ss.recent_outcomes AS recent_outcomes,
1128
1163
  (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
1129
1164
  (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
1130
1165
  FROM sessions s
@@ -1139,6 +1174,10 @@ class MemDatabase {
1139
1174
  p.name AS project_name,
1140
1175
  ss.request AS request,
1141
1176
  ss.completed AS completed,
1177
+ ss.capture_state AS capture_state,
1178
+ ss.recent_tool_names AS recent_tool_names,
1179
+ ss.hot_files AS hot_files,
1180
+ ss.recent_outcomes AS recent_outcomes,
1142
1181
  (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
1143
1182
  (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
1144
1183
  FROM sessions s
@@ -1311,8 +1350,11 @@ class MemDatabase {
1311
1350
  completed: normalizeSummarySection(summary.completed),
1312
1351
  next_steps: normalizeSummarySection(summary.next_steps)
1313
1352
  };
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);
1353
+ const result = this.db.query(`INSERT INTO session_summaries (
1354
+ session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
1355
+ capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
1356
+ )
1357
+ 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
1358
  const id = Number(result.lastInsertRowid);
1317
1359
  return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
1318
1360
  }
@@ -1327,7 +1369,11 @@ class MemDatabase {
1327
1369
  investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
1328
1370
  learned: normalizeSummarySection(summary.learned ?? existing.learned),
1329
1371
  completed: normalizeSummarySection(summary.completed ?? existing.completed),
1330
- next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
1372
+ next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
1373
+ capture_state: summary.capture_state ?? existing.capture_state,
1374
+ recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
1375
+ hot_files: summary.hot_files ?? existing.hot_files,
1376
+ recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
1331
1377
  };
1332
1378
  this.db.query(`UPDATE session_summaries
1333
1379
  SET project_id = ?,
@@ -1337,8 +1383,12 @@ class MemDatabase {
1337
1383
  learned = ?,
1338
1384
  completed = ?,
1339
1385
  next_steps = ?,
1386
+ capture_state = ?,
1387
+ recent_tool_names = ?,
1388
+ hot_files = ?,
1389
+ recent_outcomes = ?,
1340
1390
  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);
1391
+ 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
1392
  return this.getSessionSummary(summary.session_id);
1343
1393
  }
1344
1394
  getSessionSummary(sessionId) {
@@ -1843,6 +1893,16 @@ var MIGRATIONS2 = [
1843
1893
  ON tool_events(created_at_epoch DESC, id DESC);
1844
1894
  `
1845
1895
  },
1896
+ {
1897
+ version: 12,
1898
+ description: "Add synced handoff metadata to session summaries",
1899
+ sql: `
1900
+ ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
1901
+ ALTER TABLE session_summaries ADD COLUMN recent_tool_names TEXT;
1902
+ ALTER TABLE session_summaries ADD COLUMN hot_files TEXT;
1903
+ ALTER TABLE session_summaries ADD COLUMN recent_outcomes TEXT;
1904
+ `
1905
+ },
1846
1906
  {
1847
1907
  version: 11,
1848
1908
  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: 12,
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",
@@ -1497,6 +1507,9 @@ function inferLegacySchemaVersion(db) {
1497
1507
  version = Math.max(version, 10);
1498
1508
  if (columnExists(db, "observations", "source_tool"))
1499
1509
  version = Math.max(version, 11);
1510
+ if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
1511
+ version = Math.max(version, 12);
1512
+ }
1500
1513
  return version;
1501
1514
  }
1502
1515
  function runMigrations(db) {
@@ -1575,6 +1588,27 @@ function ensureObservationTypes(db) {
1575
1588
  }
1576
1589
  }
1577
1590
  }
1591
+ function ensureSessionSummaryColumns(db) {
1592
+ const required = [
1593
+ "capture_state",
1594
+ "recent_tool_names",
1595
+ "hot_files",
1596
+ "recent_outcomes"
1597
+ ];
1598
+ for (const column of required) {
1599
+ if (columnExists(db, "session_summaries", column))
1600
+ continue;
1601
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
1602
+ }
1603
+ const current = getSchemaVersion(db);
1604
+ if (current < 12) {
1605
+ db.exec("PRAGMA user_version = 12");
1606
+ }
1607
+ }
1608
+ function getSchemaVersion(db) {
1609
+ const result = db.query("PRAGMA user_version").get();
1610
+ return result.user_version;
1611
+ }
1578
1612
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
1579
1613
 
1580
1614
  // src/storage/sqlite.ts
@@ -1729,6 +1763,7 @@ class MemDatabase {
1729
1763
  this.vecAvailable = this.loadVecExtension();
1730
1764
  runMigrations(this.db);
1731
1765
  ensureObservationTypes(this.db);
1766
+ ensureSessionSummaryColumns(this.db);
1732
1767
  }
1733
1768
  loadVecExtension() {
1734
1769
  try {
@@ -1954,6 +1989,10 @@ class MemDatabase {
1954
1989
  p.name AS project_name,
1955
1990
  ss.request AS request,
1956
1991
  ss.completed AS completed,
1992
+ ss.capture_state AS capture_state,
1993
+ ss.recent_tool_names AS recent_tool_names,
1994
+ ss.hot_files AS hot_files,
1995
+ ss.recent_outcomes AS recent_outcomes,
1957
1996
  (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
1958
1997
  (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
1959
1998
  FROM sessions s
@@ -1968,6 +2007,10 @@ class MemDatabase {
1968
2007
  p.name AS project_name,
1969
2008
  ss.request AS request,
1970
2009
  ss.completed AS completed,
2010
+ ss.capture_state AS capture_state,
2011
+ ss.recent_tool_names AS recent_tool_names,
2012
+ ss.hot_files AS hot_files,
2013
+ ss.recent_outcomes AS recent_outcomes,
1971
2014
  (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
1972
2015
  (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
1973
2016
  FROM sessions s
@@ -2140,8 +2183,11 @@ class MemDatabase {
2140
2183
  completed: normalizeSummarySection(summary.completed),
2141
2184
  next_steps: normalizeSummarySection(summary.next_steps)
2142
2185
  };
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);
2186
+ const result = this.db.query(`INSERT INTO session_summaries (
2187
+ session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
2188
+ capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
2189
+ )
2190
+ 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
2191
  const id = Number(result.lastInsertRowid);
2146
2192
  return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
2147
2193
  }
@@ -2156,7 +2202,11 @@ class MemDatabase {
2156
2202
  investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
2157
2203
  learned: normalizeSummarySection(summary.learned ?? existing.learned),
2158
2204
  completed: normalizeSummarySection(summary.completed ?? existing.completed),
2159
- next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
2205
+ next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
2206
+ capture_state: summary.capture_state ?? existing.capture_state,
2207
+ recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
2208
+ hot_files: summary.hot_files ?? existing.hot_files,
2209
+ recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
2160
2210
  };
2161
2211
  this.db.query(`UPDATE session_summaries
2162
2212
  SET project_id = ?,
@@ -2166,8 +2216,12 @@ class MemDatabase {
2166
2216
  learned = ?,
2167
2217
  completed = ?,
2168
2218
  next_steps = ?,
2219
+ capture_state = ?,
2220
+ recent_tool_names = ?,
2221
+ hot_files = ?,
2222
+ recent_outcomes = ?,
2169
2223
  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);
2224
+ 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
2225
  return this.getSessionSummary(summary.session_id);
2172
2226
  }
2173
2227
  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: 12,
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",
@@ -842,6 +852,9 @@ function inferLegacySchemaVersion(db) {
842
852
  version = Math.max(version, 10);
843
853
  if (columnExists(db, "observations", "source_tool"))
844
854
  version = Math.max(version, 11);
855
+ if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
856
+ version = Math.max(version, 12);
857
+ }
845
858
  return version;
846
859
  }
847
860
  function runMigrations(db) {
@@ -920,6 +933,27 @@ function ensureObservationTypes(db) {
920
933
  }
921
934
  }
922
935
  }
936
+ function ensureSessionSummaryColumns(db) {
937
+ const required = [
938
+ "capture_state",
939
+ "recent_tool_names",
940
+ "hot_files",
941
+ "recent_outcomes"
942
+ ];
943
+ for (const column of required) {
944
+ if (columnExists(db, "session_summaries", column))
945
+ continue;
946
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
947
+ }
948
+ const current = getSchemaVersion(db);
949
+ if (current < 12) {
950
+ db.exec("PRAGMA user_version = 12");
951
+ }
952
+ }
953
+ function getSchemaVersion(db) {
954
+ const result = db.query("PRAGMA user_version").get();
955
+ return result.user_version;
956
+ }
923
957
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
924
958
 
925
959
  // src/storage/sqlite.ts
@@ -1074,6 +1108,7 @@ class MemDatabase {
1074
1108
  this.vecAvailable = this.loadVecExtension();
1075
1109
  runMigrations(this.db);
1076
1110
  ensureObservationTypes(this.db);
1111
+ ensureSessionSummaryColumns(this.db);
1077
1112
  }
1078
1113
  loadVecExtension() {
1079
1114
  try {
@@ -1299,6 +1334,10 @@ class MemDatabase {
1299
1334
  p.name AS project_name,
1300
1335
  ss.request AS request,
1301
1336
  ss.completed AS completed,
1337
+ ss.capture_state AS capture_state,
1338
+ ss.recent_tool_names AS recent_tool_names,
1339
+ ss.hot_files AS hot_files,
1340
+ ss.recent_outcomes AS recent_outcomes,
1302
1341
  (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
1303
1342
  (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
1304
1343
  FROM sessions s
@@ -1313,6 +1352,10 @@ class MemDatabase {
1313
1352
  p.name AS project_name,
1314
1353
  ss.request AS request,
1315
1354
  ss.completed AS completed,
1355
+ ss.capture_state AS capture_state,
1356
+ ss.recent_tool_names AS recent_tool_names,
1357
+ ss.hot_files AS hot_files,
1358
+ ss.recent_outcomes AS recent_outcomes,
1316
1359
  (SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
1317
1360
  (SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
1318
1361
  FROM sessions s
@@ -1485,8 +1528,11 @@ class MemDatabase {
1485
1528
  completed: normalizeSummarySection(summary.completed),
1486
1529
  next_steps: normalizeSummarySection(summary.next_steps)
1487
1530
  };
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);
1531
+ const result = this.db.query(`INSERT INTO session_summaries (
1532
+ session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
1533
+ capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
1534
+ )
1535
+ 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
1536
  const id = Number(result.lastInsertRowid);
1491
1537
  return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
1492
1538
  }
@@ -1501,7 +1547,11 @@ class MemDatabase {
1501
1547
  investigated: normalizeSummarySection(summary.investigated ?? existing.investigated),
1502
1548
  learned: normalizeSummarySection(summary.learned ?? existing.learned),
1503
1549
  completed: normalizeSummarySection(summary.completed ?? existing.completed),
1504
- next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps)
1550
+ next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
1551
+ capture_state: summary.capture_state ?? existing.capture_state,
1552
+ recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
1553
+ hot_files: summary.hot_files ?? existing.hot_files,
1554
+ recent_outcomes: summary.recent_outcomes ?? existing.recent_outcomes
1505
1555
  };
1506
1556
  this.db.query(`UPDATE session_summaries
1507
1557
  SET project_id = ?,
@@ -1511,8 +1561,12 @@ class MemDatabase {
1511
1561
  learned = ?,
1512
1562
  completed = ?,
1513
1563
  next_steps = ?,
1564
+ capture_state = ?,
1565
+ recent_tool_names = ?,
1566
+ hot_files = ?,
1567
+ recent_outcomes = ?,
1514
1568
  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);
1569
+ 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
1570
  return this.getSessionSummary(summary.session_id);
1517
1571
  }
1518
1572
  getSessionSummary(sessionId) {
@@ -3166,6 +3220,107 @@ function checkSessionFatigue(db, sessionId) {
3166
3220
  };
3167
3221
  }
3168
3222
 
3223
+ // src/capture/live-summary.ts
3224
+ var LOW_SIGNAL_TITLE_PATTERNS = [
3225
+ /^(Modified|Extended|Reduced|Created)\s+\S+/i,
3226
+ /^Dependency change:/i,
3227
+ /^[a-z0-9._-]+\s*\(error\)$/i,
3228
+ /^(engrm|unknown):\s+/i
3229
+ ];
3230
+ function buildLiveSummaryUpdate(observation) {
3231
+ const title = compactSummaryTitle(observation.title);
3232
+ if (!title)
3233
+ return null;
3234
+ switch (observation.type) {
3235
+ case "discovery":
3236
+ case "pattern":
3237
+ return { investigated: title };
3238
+ case "decision":
3239
+ return { learned: title };
3240
+ case "bugfix":
3241
+ case "feature":
3242
+ case "change":
3243
+ case "refactor":
3244
+ return { completed: title };
3245
+ default:
3246
+ return null;
3247
+ }
3248
+ }
3249
+ function compactSummaryTitle(title) {
3250
+ const trimmed = title?.trim();
3251
+ if (!trimmed)
3252
+ return null;
3253
+ if (LOW_SIGNAL_TITLE_PATTERNS.some((pattern) => pattern.test(trimmed))) {
3254
+ return null;
3255
+ }
3256
+ return trimmed.length > 180 ? `${trimmed.slice(0, 177)}...` : trimmed;
3257
+ }
3258
+ function mergeLiveSummarySections(existing, update) {
3259
+ return {
3260
+ investigated: mergeSectionItem(existing?.investigated ?? null, update.investigated ?? null),
3261
+ learned: mergeSectionItem(existing?.learned ?? null, update.learned ?? null),
3262
+ completed: mergeSectionItem(existing?.completed ?? null, update.completed ?? null)
3263
+ };
3264
+ }
3265
+ function mergeSectionItem(existing, item) {
3266
+ if (!item)
3267
+ return existing;
3268
+ if (!existing)
3269
+ return item;
3270
+ const lines = existing.split(`
3271
+ `).map((line) => line.replace(/^\s*-\s*/, "").trim()).filter(Boolean);
3272
+ const normalizedItem = item.trim();
3273
+ if (lines.some((line) => line.toLowerCase() === normalizedItem.toLowerCase())) {
3274
+ return existing;
3275
+ }
3276
+ return `${existing}
3277
+ - ${normalizedItem}`;
3278
+ }
3279
+
3280
+ // src/capture/session-handoff.ts
3281
+ function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
3282
+ const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
3283
+ const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
3284
+ const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
3285
+ const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
3286
+ const hotFiles = [...new Set(observations.flatMap((obs) => [
3287
+ ...parseJsonArray(obs.files_modified),
3288
+ ...parseJsonArray(obs.files_read)
3289
+ ]).filter(Boolean))].slice(0, 6);
3290
+ const recentOutcomes = observations.filter((obs) => ["bugfix", "feature", "refactor", "change", "decision"].includes(obs.type)).map((obs) => obs.title.trim()).filter((title) => title.length > 0).slice(0, 6);
3291
+ const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
3292
+ const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
3293
+ if (!obs.source_tool)
3294
+ return acc;
3295
+ acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
3296
+ return acc;
3297
+ }, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
3298
+ const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
3299
+ return {
3300
+ prompt_count: prompts.length,
3301
+ tool_event_count: toolEvents.length,
3302
+ recent_request_prompts: recentRequestPrompts,
3303
+ latest_request: latestRequest,
3304
+ recent_tool_names: recentToolNames,
3305
+ recent_tool_commands: recentToolCommands,
3306
+ capture_state: captureState,
3307
+ hot_files: hotFiles,
3308
+ recent_outcomes: recentOutcomes,
3309
+ observation_source_tools: observationSourceTools,
3310
+ latest_observation_prompt_number: latestObservationPromptNumber
3311
+ };
3312
+ }
3313
+ function parseJsonArray(value) {
3314
+ if (!value)
3315
+ return [];
3316
+ try {
3317
+ const parsed = JSON.parse(value);
3318
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
3319
+ } catch {
3320
+ return [];
3321
+ }
3322
+ }
3323
+
3169
3324
  // hooks/post-tool-use.ts
3170
3325
  async function main() {
3171
3326
  const raw = await readStdin();
@@ -3269,7 +3424,8 @@ async function main() {
3269
3424
  timeoutMs: 800
3270
3425
  }), 1000);
3271
3426
  if (observed) {
3272
- await saveObservation(db, config, observed);
3427
+ const result = await saveObservation(db, config, observed);
3428
+ updateRollingSummaryFromObservation(db, result.observation_id, event, config.user_id);
3273
3429
  incrementObserverSaveCount(event.session_id);
3274
3430
  saved = true;
3275
3431
  }
@@ -3278,7 +3434,7 @@ async function main() {
3278
3434
  if (!saved) {
3279
3435
  const extracted = extractObservation(event);
3280
3436
  if (extracted) {
3281
- await saveObservation(db, config, {
3437
+ const result = await saveObservation(db, config, {
3282
3438
  type: extracted.type,
3283
3439
  title: extracted.title,
3284
3440
  narrative: extracted.narrative,
@@ -3288,6 +3444,7 @@ async function main() {
3288
3444
  cwd: event.cwd,
3289
3445
  source_tool: event.tool_name
3290
3446
  });
3447
+ updateRollingSummaryFromObservation(db, result.observation_id, event, config.user_id);
3291
3448
  incrementObserverSaveCount(event.session_id);
3292
3449
  }
3293
3450
  }
@@ -3352,6 +3509,38 @@ function detectProjectForEvent(event) {
3352
3509
  const touchedPaths = extractTouchedPaths(event);
3353
3510
  return touchedPaths.length > 0 ? detectProjectFromTouchedPaths(touchedPaths, event.cwd) : detectProject(event.cwd);
3354
3511
  }
3512
+ function updateRollingSummaryFromObservation(db, observationId, event, userId) {
3513
+ if (!observationId || !event.session_id)
3514
+ return;
3515
+ const observation = db.getObservationById(observationId);
3516
+ if (!observation)
3517
+ return;
3518
+ const update = buildLiveSummaryUpdate(observation);
3519
+ if (!update)
3520
+ return;
3521
+ const existing = db.getSessionSummary(event.session_id);
3522
+ const sessionPrompts = db.getSessionUserPrompts(event.session_id, 20);
3523
+ const sessionToolEvents = db.getSessionToolEvents(event.session_id, 20);
3524
+ const sessionObservations = db.getObservationsBySession(event.session_id);
3525
+ const merged = mergeLiveSummarySections(existing, update);
3526
+ const handoff = buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, sessionObservations);
3527
+ const currentRequest = existing?.request ?? handoff.latest_request ?? null;
3528
+ const summary = db.upsertSessionSummary({
3529
+ session_id: event.session_id,
3530
+ project_id: observation.project_id,
3531
+ user_id: userId,
3532
+ request: currentRequest,
3533
+ investigated: merged.investigated,
3534
+ learned: merged.learned,
3535
+ completed: merged.completed,
3536
+ next_steps: existing?.next_steps ?? null,
3537
+ capture_state: handoff.capture_state,
3538
+ recent_tool_names: JSON.stringify(handoff.recent_tool_names),
3539
+ hot_files: JSON.stringify(handoff.hot_files),
3540
+ recent_outcomes: JSON.stringify(handoff.recent_outcomes)
3541
+ });
3542
+ db.addToOutbox("summary", summary.id);
3543
+ }
3355
3544
  function extractTouchedPaths(event) {
3356
3545
  const paths = [];
3357
3546
  const filePath = event.tool_input["file_path"];