engrm 0.4.21 → 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
@@ -599,7 +599,7 @@ var MIGRATIONS = [
599
599
  `
600
600
  },
601
601
  {
602
- version: 11,
602
+ version: 12,
603
603
  description: "Add synced handoff metadata to session summaries",
604
604
  sql: `
605
605
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -674,6 +674,9 @@ function inferLegacySchemaVersion(db) {
674
674
  version = Math.max(version, 10);
675
675
  if (columnExists(db, "observations", "source_tool"))
676
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
+ }
677
680
  return version;
678
681
  }
679
682
  function runMigrations(db) {
@@ -752,6 +755,23 @@ function ensureObservationTypes(db) {
752
755
  }
753
756
  }
754
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
+ }
755
775
  function getSchemaVersion(db) {
756
776
  const result = db.query("PRAGMA user_version").get();
757
777
  return result.user_version;
@@ -910,6 +930,7 @@ class MemDatabase {
910
930
  this.vecAvailable = this.loadVecExtension();
911
931
  runMigrations(this.db);
912
932
  ensureObservationTypes(this.db);
933
+ ensureSessionSummaryColumns(this.db);
913
934
  }
914
935
  loadVecExtension() {
915
936
  try {
@@ -1873,7 +1894,7 @@ var MIGRATIONS2 = [
1873
1894
  `
1874
1895
  },
1875
1896
  {
1876
- version: 11,
1897
+ version: 12,
1877
1898
  description: "Add synced handoff metadata to session summaries",
1878
1899
  sql: `
1879
1900
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -1432,7 +1432,7 @@ var MIGRATIONS = [
1432
1432
  `
1433
1433
  },
1434
1434
  {
1435
- version: 11,
1435
+ version: 12,
1436
1436
  description: "Add synced handoff metadata to session summaries",
1437
1437
  sql: `
1438
1438
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -1507,6 +1507,9 @@ function inferLegacySchemaVersion(db) {
1507
1507
  version = Math.max(version, 10);
1508
1508
  if (columnExists(db, "observations", "source_tool"))
1509
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
+ }
1510
1513
  return version;
1511
1514
  }
1512
1515
  function runMigrations(db) {
@@ -1585,6 +1588,27 @@ function ensureObservationTypes(db) {
1585
1588
  }
1586
1589
  }
1587
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
+ }
1588
1612
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
1589
1613
 
1590
1614
  // src/storage/sqlite.ts
@@ -1739,6 +1763,7 @@ class MemDatabase {
1739
1763
  this.vecAvailable = this.loadVecExtension();
1740
1764
  runMigrations(this.db);
1741
1765
  ensureObservationTypes(this.db);
1766
+ ensureSessionSummaryColumns(this.db);
1742
1767
  }
1743
1768
  loadVecExtension() {
1744
1769
  try {
@@ -777,7 +777,7 @@ var MIGRATIONS = [
777
777
  `
778
778
  },
779
779
  {
780
- version: 11,
780
+ version: 12,
781
781
  description: "Add synced handoff metadata to session summaries",
782
782
  sql: `
783
783
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -852,6 +852,9 @@ function inferLegacySchemaVersion(db) {
852
852
  version = Math.max(version, 10);
853
853
  if (columnExists(db, "observations", "source_tool"))
854
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
+ }
855
858
  return version;
856
859
  }
857
860
  function runMigrations(db) {
@@ -930,6 +933,27 @@ function ensureObservationTypes(db) {
930
933
  }
931
934
  }
932
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
+ }
933
957
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
934
958
 
935
959
  // src/storage/sqlite.ts
@@ -1084,6 +1108,7 @@ class MemDatabase {
1084
1108
  this.vecAvailable = this.loadVecExtension();
1085
1109
  runMigrations(this.db);
1086
1110
  ensureObservationTypes(this.db);
1111
+ ensureSessionSummaryColumns(this.db);
1087
1112
  }
1088
1113
  loadVecExtension() {
1089
1114
  try {
@@ -3252,6 +3277,50 @@ function mergeSectionItem(existing, item) {
3252
3277
  - ${normalizedItem}`;
3253
3278
  }
3254
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
+
3255
3324
  // hooks/post-tool-use.ts
3256
3325
  async function main() {
3257
3326
  const raw = await readStdin();
@@ -3450,8 +3519,12 @@ function updateRollingSummaryFromObservation(db, observationId, event, userId) {
3450
3519
  if (!update)
3451
3520
  return;
3452
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);
3453
3525
  const merged = mergeLiveSummarySections(existing, update);
3454
- const currentRequest = existing?.request ?? db.getSessionUserPrompts(event.session_id, 1).at(-1)?.prompt ?? null;
3526
+ const handoff = buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, sessionObservations);
3527
+ const currentRequest = existing?.request ?? handoff.latest_request ?? null;
3455
3528
  const summary = db.upsertSessionSummary({
3456
3529
  session_id: event.session_id,
3457
3530
  project_id: observation.project_id,
@@ -3460,7 +3533,11 @@ function updateRollingSummaryFromObservation(db, observationId, event, userId) {
3460
3533
  investigated: merged.investigated,
3461
3534
  learned: merged.learned,
3462
3535
  completed: merged.completed,
3463
- next_steps: existing?.next_steps ?? null
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)
3464
3541
  });
3465
3542
  db.addToOutbox("summary", summary.id);
3466
3543
  }
@@ -571,7 +571,7 @@ var MIGRATIONS = [
571
571
  `
572
572
  },
573
573
  {
574
- version: 11,
574
+ version: 12,
575
575
  description: "Add synced handoff metadata to session summaries",
576
576
  sql: `
577
577
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -646,6 +646,9 @@ function inferLegacySchemaVersion(db) {
646
646
  version = Math.max(version, 10);
647
647
  if (columnExists(db, "observations", "source_tool"))
648
648
  version = Math.max(version, 11);
649
+ 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")) {
650
+ version = Math.max(version, 12);
651
+ }
649
652
  return version;
650
653
  }
651
654
  function runMigrations(db) {
@@ -724,6 +727,27 @@ function ensureObservationTypes(db) {
724
727
  }
725
728
  }
726
729
  }
730
+ function ensureSessionSummaryColumns(db) {
731
+ const required = [
732
+ "capture_state",
733
+ "recent_tool_names",
734
+ "hot_files",
735
+ "recent_outcomes"
736
+ ];
737
+ for (const column of required) {
738
+ if (columnExists(db, "session_summaries", column))
739
+ continue;
740
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
741
+ }
742
+ const current = getSchemaVersion(db);
743
+ if (current < 12) {
744
+ db.exec("PRAGMA user_version = 12");
745
+ }
746
+ }
747
+ function getSchemaVersion(db) {
748
+ const result = db.query("PRAGMA user_version").get();
749
+ return result.user_version;
750
+ }
727
751
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
728
752
 
729
753
  // src/storage/sqlite.ts
@@ -878,6 +902,7 @@ class MemDatabase {
878
902
  this.vecAvailable = this.loadVecExtension();
879
903
  runMigrations(this.db);
880
904
  ensureObservationTypes(this.db);
905
+ ensureSessionSummaryColumns(this.db);
881
906
  }
882
907
  loadVecExtension() {
883
908
  try {
@@ -647,7 +647,7 @@ var MIGRATIONS = [
647
647
  `
648
648
  },
649
649
  {
650
- version: 11,
650
+ version: 12,
651
651
  description: "Add synced handoff metadata to session summaries",
652
652
  sql: `
653
653
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -722,6 +722,9 @@ function inferLegacySchemaVersion(db) {
722
722
  version = Math.max(version, 10);
723
723
  if (columnExists(db, "observations", "source_tool"))
724
724
  version = Math.max(version, 11);
725
+ 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")) {
726
+ version = Math.max(version, 12);
727
+ }
725
728
  return version;
726
729
  }
727
730
  function runMigrations(db) {
@@ -800,6 +803,27 @@ function ensureObservationTypes(db) {
800
803
  }
801
804
  }
802
805
  }
806
+ function ensureSessionSummaryColumns(db) {
807
+ const required = [
808
+ "capture_state",
809
+ "recent_tool_names",
810
+ "hot_files",
811
+ "recent_outcomes"
812
+ ];
813
+ for (const column of required) {
814
+ if (columnExists(db, "session_summaries", column))
815
+ continue;
816
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
817
+ }
818
+ const current = getSchemaVersion(db);
819
+ if (current < 12) {
820
+ db.exec("PRAGMA user_version = 12");
821
+ }
822
+ }
823
+ function getSchemaVersion(db) {
824
+ const result = db.query("PRAGMA user_version").get();
825
+ return result.user_version;
826
+ }
803
827
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
804
828
 
805
829
  // src/storage/sqlite.ts
@@ -954,6 +978,7 @@ class MemDatabase {
954
978
  this.vecAvailable = this.loadVecExtension();
955
979
  runMigrations(this.db);
956
980
  ensureObservationTypes(this.db);
981
+ ensureSessionSummaryColumns(this.db);
957
982
  }
958
983
  loadVecExtension() {
959
984
  try {
@@ -1184,7 +1184,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
1184
1184
  import { join as join3 } from "node:path";
1185
1185
  import { homedir } from "node:os";
1186
1186
  var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
1187
- var CLIENT_VERSION = "0.4.21";
1187
+ var CLIENT_VERSION = "0.4.22";
1188
1188
  function hashFile(filePath) {
1189
1189
  try {
1190
1190
  if (!existsSync3(filePath))
@@ -2287,7 +2287,7 @@ var MIGRATIONS = [
2287
2287
  `
2288
2288
  },
2289
2289
  {
2290
- version: 11,
2290
+ version: 12,
2291
2291
  description: "Add synced handoff metadata to session summaries",
2292
2292
  sql: `
2293
2293
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -2362,6 +2362,9 @@ function inferLegacySchemaVersion(db) {
2362
2362
  version = Math.max(version, 10);
2363
2363
  if (columnExists(db, "observations", "source_tool"))
2364
2364
  version = Math.max(version, 11);
2365
+ 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")) {
2366
+ version = Math.max(version, 12);
2367
+ }
2365
2368
  return version;
2366
2369
  }
2367
2370
  function runMigrations(db) {
@@ -2440,6 +2443,27 @@ function ensureObservationTypes(db) {
2440
2443
  }
2441
2444
  }
2442
2445
  }
2446
+ function ensureSessionSummaryColumns(db) {
2447
+ const required = [
2448
+ "capture_state",
2449
+ "recent_tool_names",
2450
+ "hot_files",
2451
+ "recent_outcomes"
2452
+ ];
2453
+ for (const column of required) {
2454
+ if (columnExists(db, "session_summaries", column))
2455
+ continue;
2456
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
2457
+ }
2458
+ const current = getSchemaVersion(db);
2459
+ if (current < 12) {
2460
+ db.exec("PRAGMA user_version = 12");
2461
+ }
2462
+ }
2463
+ function getSchemaVersion(db) {
2464
+ const result = db.query("PRAGMA user_version").get();
2465
+ return result.user_version;
2466
+ }
2443
2467
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
2444
2468
 
2445
2469
  // src/storage/sqlite.ts
@@ -2514,6 +2538,7 @@ class MemDatabase {
2514
2538
  this.vecAvailable = this.loadVecExtension();
2515
2539
  runMigrations(this.db);
2516
2540
  ensureObservationTypes(this.db);
2541
+ ensureSessionSummaryColumns(this.db);
2517
2542
  }
2518
2543
  loadVecExtension() {
2519
2544
  try {
@@ -3833,7 +3858,15 @@ function isWeakCompletedSection(value) {
3833
3858
  return weakCount === items.length;
3834
3859
  }
3835
3860
  function looksLikeFileOperationTitle2(value) {
3836
- return /^(modified|updated|edited|touched|changed|extended|refactored|redesigned)\s+[A-Za-z0-9_.\-\/]+(?:\s*\([^)]*\))?$/i.test(value.trim());
3861
+ const trimmed = value.trim();
3862
+ if (/^(modified|updated|edited|touched|changed|extended|refactored|redesigned)\s+[A-Za-z0-9_.\-\/]+(?:\s*\([^)]*\))?$/i.test(trimmed)) {
3863
+ return true;
3864
+ }
3865
+ return looksLikeGenericSummaryWrapper(trimmed);
3866
+ }
3867
+ function looksLikeGenericSummaryWrapper(value) {
3868
+ const normalized = value.toLowerCase().replace(/\s+/g, " ").trim();
3869
+ return normalized.startsWith("all clean. here's a summary of what was fixed") || normalized.startsWith("all clean, here's a summary of what was fixed") || normalized.startsWith("now i have enough to give a clear, accurate assessment") || normalized.startsWith("here's the real picture") || normalized === "event log \u2192 existing events feed" || normalized.startsWith("event log -> existing events feed") || normalized.startsWith("tl;dr:");
3837
3870
  }
3838
3871
  function scoreSplashLine(value) {
3839
3872
  let score = 0;
@@ -884,7 +884,7 @@ var MIGRATIONS = [
884
884
  `
885
885
  },
886
886
  {
887
- version: 11,
887
+ version: 12,
888
888
  description: "Add synced handoff metadata to session summaries",
889
889
  sql: `
890
890
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -959,6 +959,9 @@ function inferLegacySchemaVersion(db) {
959
959
  version = Math.max(version, 10);
960
960
  if (columnExists(db, "observations", "source_tool"))
961
961
  version = Math.max(version, 11);
962
+ 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")) {
963
+ version = Math.max(version, 12);
964
+ }
962
965
  return version;
963
966
  }
964
967
  function runMigrations(db) {
@@ -1037,6 +1040,27 @@ function ensureObservationTypes(db) {
1037
1040
  }
1038
1041
  }
1039
1042
  }
1043
+ function ensureSessionSummaryColumns(db) {
1044
+ const required = [
1045
+ "capture_state",
1046
+ "recent_tool_names",
1047
+ "hot_files",
1048
+ "recent_outcomes"
1049
+ ];
1050
+ for (const column of required) {
1051
+ if (columnExists(db, "session_summaries", column))
1052
+ continue;
1053
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
1054
+ }
1055
+ const current = getSchemaVersion(db);
1056
+ if (current < 12) {
1057
+ db.exec("PRAGMA user_version = 12");
1058
+ }
1059
+ }
1060
+ function getSchemaVersion(db) {
1061
+ const result = db.query("PRAGMA user_version").get();
1062
+ return result.user_version;
1063
+ }
1040
1064
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
1041
1065
 
1042
1066
  // src/storage/sqlite.ts
@@ -1111,6 +1135,7 @@ class MemDatabase {
1111
1135
  this.vecAvailable = this.loadVecExtension();
1112
1136
  runMigrations(this.db);
1113
1137
  ensureObservationTypes(this.db);
1138
+ ensureSessionSummaryColumns(this.db);
1114
1139
  }
1115
1140
  loadVecExtension() {
1116
1141
  try {
@@ -2001,6 +2026,50 @@ function computeSessionValueSignals(observations, securityFindings = []) {
2001
2026
  };
2002
2027
  }
2003
2028
 
2029
+ // src/capture/session-handoff.ts
2030
+ function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
2031
+ const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
2032
+ const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
2033
+ const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
2034
+ const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
2035
+ const hotFiles = [...new Set(observations.flatMap((obs) => [
2036
+ ...parseJsonArray2(obs.files_modified),
2037
+ ...parseJsonArray2(obs.files_read)
2038
+ ]).filter(Boolean))].slice(0, 6);
2039
+ 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);
2040
+ const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
2041
+ const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
2042
+ if (!obs.source_tool)
2043
+ return acc;
2044
+ acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
2045
+ return acc;
2046
+ }, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
2047
+ const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
2048
+ return {
2049
+ prompt_count: prompts.length,
2050
+ tool_event_count: toolEvents.length,
2051
+ recent_request_prompts: recentRequestPrompts,
2052
+ latest_request: latestRequest,
2053
+ recent_tool_names: recentToolNames,
2054
+ recent_tool_commands: recentToolCommands,
2055
+ capture_state: captureState,
2056
+ hot_files: hotFiles,
2057
+ recent_outcomes: recentOutcomes,
2058
+ observation_source_tools: observationSourceTools,
2059
+ latest_observation_prompt_number: latestObservationPromptNumber
2060
+ };
2061
+ }
2062
+ function parseJsonArray2(value) {
2063
+ if (!value)
2064
+ return [];
2065
+ try {
2066
+ const parsed = JSON.parse(value);
2067
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
2068
+ } catch {
2069
+ return [];
2070
+ }
2071
+ }
2072
+
2004
2073
  // src/sync/push.ts
2005
2074
  function buildVectorDocument(obs, config, project) {
2006
2075
  const parts = [obs.title];
@@ -2141,7 +2210,7 @@ async function pushOutbox(db, client, config, batchSize = 50) {
2141
2210
  const doc2 = buildSummaryVectorDocument(summary, config, {
2142
2211
  canonical_id: project2.canonical_id,
2143
2212
  name: project2.name
2144
- }, summaryObservations, buildSummaryCaptureContext(sessionPrompts, sessionToolEvents, summaryObservations));
2213
+ }, summaryObservations, buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, summaryObservations));
2145
2214
  batch.push({ entryId: entry.id, doc: doc2 });
2146
2215
  continue;
2147
2216
  }
@@ -2212,48 +2281,6 @@ function countPresentSections(summary) {
2212
2281
  function extractSectionItems(section) {
2213
2282
  return extractSummaryItems(section, 4);
2214
2283
  }
2215
- function buildSummaryCaptureContext(prompts, toolEvents, observations) {
2216
- const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
2217
- const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
2218
- const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
2219
- const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
2220
- const hotFiles = [...new Set(observations.flatMap((obs) => [
2221
- ...parseJsonArray2(obs.files_modified),
2222
- ...parseJsonArray2(obs.files_read)
2223
- ]).filter(Boolean))].slice(0, 6);
2224
- 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);
2225
- const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
2226
- const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
2227
- if (!obs.source_tool)
2228
- return acc;
2229
- acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
2230
- return acc;
2231
- }, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
2232
- const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
2233
- return {
2234
- prompt_count: prompts.length,
2235
- tool_event_count: toolEvents.length,
2236
- recent_request_prompts: recentRequestPrompts,
2237
- latest_request: latestRequest,
2238
- recent_tool_names: recentToolNames,
2239
- recent_tool_commands: recentToolCommands,
2240
- capture_state: captureState,
2241
- hot_files: hotFiles,
2242
- recent_outcomes: recentOutcomes,
2243
- observation_source_tools: observationSourceTools,
2244
- latest_observation_prompt_number: latestObservationPromptNumber
2245
- };
2246
- }
2247
- function parseJsonArray2(value) {
2248
- if (!value)
2249
- return [];
2250
- try {
2251
- const parsed = JSON.parse(value);
2252
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
2253
- } catch {
2254
- return [];
2255
- }
2256
- }
2257
2284
 
2258
2285
  // src/embeddings/embedder.ts
2259
2286
  var _available = null;
@@ -2587,7 +2614,7 @@ function buildBeacon(db, config, sessionId, metrics) {
2587
2614
  sentinel_used: valueSignals.security_findings_count > 0,
2588
2615
  risk_score: riskScore,
2589
2616
  stacks_detected: stacks,
2590
- client_version: "0.4.21",
2617
+ client_version: "0.4.22",
2591
2618
  context_observations_injected: metrics?.contextObsInjected ?? 0,
2592
2619
  context_total_available: metrics?.contextTotalAvailable ?? 0,
2593
2620
  recall_attempts: metrics?.recallAttempts ?? 0,
@@ -3623,7 +3650,7 @@ async function main() {
3623
3650
  const assistantSections = extractAssistantSummarySections(event.last_assistant_message);
3624
3651
  const summary = mergeSessionSummary(retrospective, assistantSections, event.session_id, session?.project_id ?? null, config.user_id) ?? mergeSessionSummary(buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message), assistantSections, event.session_id, session?.project_id ?? null, config.user_id) ?? buildFallbackSessionSummary(db, event.session_id, session?.project_id ?? null, config.user_id, event.last_assistant_message);
3625
3652
  if (summary) {
3626
- const row = db.insertSessionSummary(summary);
3653
+ const row = db.upsertSessionSummary(summary);
3627
3654
  db.addToOutbox("summary", row.id);
3628
3655
  let securityFindings = [];
3629
3656
  try {
@@ -3969,7 +3996,7 @@ function pickAssistantCheckpointTitle(substantiveLines, bulletLines) {
3969
3996
  }
3970
3997
  function isGenericCheckpointLine(value) {
3971
3998
  const normalized = value.toLowerCase().replace(/\s+/g, " ").trim();
3972
- return normalized === "here's where things stand:" || normalized === "here's where things stand" || normalized === "where things stand:" || normalized === "where things stand" || normalized === "current status:" || normalized === "current status" || normalized === "status update:" || normalized === "status update";
3999
+ return normalized === "here's where things stand:" || normalized === "here's where things stand" || normalized === "where things stand:" || normalized === "where things stand" || normalized === "current status:" || normalized === "current status" || normalized === "status update:" || normalized === "status update" || normalized.startsWith("all clean. here's a summary of what was fixed") || normalized.startsWith("all clean, here's a summary of what was fixed") || normalized.startsWith("now i have enough to give a clear, accurate assessment") || normalized.startsWith("here's the real picture") || normalized === "tl;dr:" || normalized.startsWith("tl;dr:");
3973
4000
  }
3974
4001
  function detectUnsavedPlans(message) {
3975
4002
  const hints = [];
@@ -715,7 +715,7 @@ var MIGRATIONS = [
715
715
  `
716
716
  },
717
717
  {
718
- version: 11,
718
+ version: 12,
719
719
  description: "Add synced handoff metadata to session summaries",
720
720
  sql: `
721
721
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -790,6 +790,9 @@ function inferLegacySchemaVersion(db) {
790
790
  version = Math.max(version, 10);
791
791
  if (columnExists(db, "observations", "source_tool"))
792
792
  version = Math.max(version, 11);
793
+ 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")) {
794
+ version = Math.max(version, 12);
795
+ }
793
796
  return version;
794
797
  }
795
798
  function runMigrations(db) {
@@ -868,6 +871,27 @@ function ensureObservationTypes(db) {
868
871
  }
869
872
  }
870
873
  }
874
+ function ensureSessionSummaryColumns(db) {
875
+ const required = [
876
+ "capture_state",
877
+ "recent_tool_names",
878
+ "hot_files",
879
+ "recent_outcomes"
880
+ ];
881
+ for (const column of required) {
882
+ if (columnExists(db, "session_summaries", column))
883
+ continue;
884
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
885
+ }
886
+ const current = getSchemaVersion(db);
887
+ if (current < 12) {
888
+ db.exec("PRAGMA user_version = 12");
889
+ }
890
+ }
891
+ function getSchemaVersion(db) {
892
+ const result = db.query("PRAGMA user_version").get();
893
+ return result.user_version;
894
+ }
871
895
  var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
872
896
 
873
897
  // src/storage/sqlite.ts
@@ -1022,6 +1046,7 @@ class MemDatabase {
1022
1046
  this.vecAvailable = this.loadVecExtension();
1023
1047
  runMigrations(this.db);
1024
1048
  ensureObservationTypes(this.db);
1049
+ ensureSessionSummaryColumns(this.db);
1025
1050
  }
1026
1051
  loadVecExtension() {
1027
1052
  try {
@@ -1626,6 +1651,50 @@ function runHook(hookName, fn) {
1626
1651
  });
1627
1652
  }
1628
1653
 
1654
+ // src/capture/session-handoff.ts
1655
+ function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
1656
+ const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
1657
+ const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
1658
+ const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
1659
+ const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
1660
+ const hotFiles = [...new Set(observations.flatMap((obs) => [
1661
+ ...parseJsonArray(obs.files_modified),
1662
+ ...parseJsonArray(obs.files_read)
1663
+ ]).filter(Boolean))].slice(0, 6);
1664
+ 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);
1665
+ const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
1666
+ const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
1667
+ if (!obs.source_tool)
1668
+ return acc;
1669
+ acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
1670
+ return acc;
1671
+ }, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
1672
+ const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
1673
+ return {
1674
+ prompt_count: prompts.length,
1675
+ tool_event_count: toolEvents.length,
1676
+ recent_request_prompts: recentRequestPrompts,
1677
+ latest_request: latestRequest,
1678
+ recent_tool_names: recentToolNames,
1679
+ recent_tool_commands: recentToolCommands,
1680
+ capture_state: captureState,
1681
+ hot_files: hotFiles,
1682
+ recent_outcomes: recentOutcomes,
1683
+ observation_source_tools: observationSourceTools,
1684
+ latest_observation_prompt_number: latestObservationPromptNumber
1685
+ };
1686
+ }
1687
+ function parseJsonArray(value) {
1688
+ if (!value)
1689
+ return [];
1690
+ try {
1691
+ const parsed = JSON.parse(value);
1692
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
1693
+ } catch {
1694
+ return [];
1695
+ }
1696
+ }
1697
+
1629
1698
  // hooks/user-prompt-submit.ts
1630
1699
  async function main() {
1631
1700
  const event = await parseStdinJson();
@@ -1655,6 +1724,10 @@ async function main() {
1655
1724
  });
1656
1725
  const compactPrompt = event.prompt.replace(/\s+/g, " ").trim();
1657
1726
  if (compactPrompt.length >= 8) {
1727
+ const sessionPrompts = db.getSessionUserPrompts(event.session_id, 20);
1728
+ const sessionToolEvents = db.getSessionToolEvents(event.session_id, 20);
1729
+ const sessionObservations = db.getObservationsBySession(event.session_id);
1730
+ const handoff = buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, sessionObservations);
1658
1731
  const summary = db.upsertSessionSummary({
1659
1732
  session_id: event.session_id,
1660
1733
  project_id: project.id,
@@ -1663,7 +1736,11 @@ async function main() {
1663
1736
  investigated: null,
1664
1737
  learned: null,
1665
1738
  completed: null,
1666
- next_steps: null
1739
+ next_steps: null,
1740
+ capture_state: handoff.capture_state,
1741
+ recent_tool_names: JSON.stringify(handoff.recent_tool_names),
1742
+ hot_files: JSON.stringify(handoff.hot_files),
1743
+ recent_outcomes: JSON.stringify(handoff.recent_outcomes)
1667
1744
  });
1668
1745
  db.addToOutbox("summary", summary.id);
1669
1746
  }
package/dist/server.js CHANGED
@@ -14121,7 +14121,7 @@ var MIGRATIONS = [
14121
14121
  `
14122
14122
  },
14123
14123
  {
14124
- version: 11,
14124
+ version: 12,
14125
14125
  description: "Add synced handoff metadata to session summaries",
14126
14126
  sql: `
14127
14127
  ALTER TABLE session_summaries ADD COLUMN capture_state TEXT;
@@ -14196,6 +14196,9 @@ function inferLegacySchemaVersion(db) {
14196
14196
  version2 = Math.max(version2, 10);
14197
14197
  if (columnExists(db, "observations", "source_tool"))
14198
14198
  version2 = Math.max(version2, 11);
14199
+ 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")) {
14200
+ version2 = Math.max(version2, 12);
14201
+ }
14199
14202
  return version2;
14200
14203
  }
14201
14204
  function runMigrations(db) {
@@ -14274,6 +14277,23 @@ function ensureObservationTypes(db) {
14274
14277
  }
14275
14278
  }
14276
14279
  }
14280
+ function ensureSessionSummaryColumns(db) {
14281
+ const required2 = [
14282
+ "capture_state",
14283
+ "recent_tool_names",
14284
+ "hot_files",
14285
+ "recent_outcomes"
14286
+ ];
14287
+ for (const column of required2) {
14288
+ if (columnExists(db, "session_summaries", column))
14289
+ continue;
14290
+ db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
14291
+ }
14292
+ const current = getSchemaVersion(db);
14293
+ if (current < 12) {
14294
+ db.exec("PRAGMA user_version = 12");
14295
+ }
14296
+ }
14277
14297
  function getSchemaVersion(db) {
14278
14298
  const result = db.query("PRAGMA user_version").get();
14279
14299
  return result.user_version;
@@ -14432,6 +14452,7 @@ class MemDatabase {
14432
14452
  this.vecAvailable = this.loadVecExtension();
14433
14453
  runMigrations(this.db);
14434
14454
  ensureObservationTypes(this.db);
14455
+ ensureSessionSummaryColumns(this.db);
14435
14456
  }
14436
14457
  loadVecExtension() {
14437
14458
  try {
@@ -18712,6 +18733,50 @@ class VectorApiError extends Error {
18712
18733
  }
18713
18734
  }
18714
18735
 
18736
+ // src/capture/session-handoff.ts
18737
+ function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
18738
+ const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
18739
+ const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
18740
+ const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
18741
+ const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
18742
+ const hotFiles = [...new Set(observations.flatMap((obs) => [
18743
+ ...parseJsonArray3(obs.files_modified),
18744
+ ...parseJsonArray3(obs.files_read)
18745
+ ]).filter(Boolean))].slice(0, 6);
18746
+ 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);
18747
+ const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
18748
+ const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
18749
+ if (!obs.source_tool)
18750
+ return acc;
18751
+ acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
18752
+ return acc;
18753
+ }, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
18754
+ const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
18755
+ return {
18756
+ prompt_count: prompts.length,
18757
+ tool_event_count: toolEvents.length,
18758
+ recent_request_prompts: recentRequestPrompts,
18759
+ latest_request: latestRequest,
18760
+ recent_tool_names: recentToolNames,
18761
+ recent_tool_commands: recentToolCommands,
18762
+ capture_state: captureState,
18763
+ hot_files: hotFiles,
18764
+ recent_outcomes: recentOutcomes,
18765
+ observation_source_tools: observationSourceTools,
18766
+ latest_observation_prompt_number: latestObservationPromptNumber
18767
+ };
18768
+ }
18769
+ function parseJsonArray3(value) {
18770
+ if (!value)
18771
+ return [];
18772
+ try {
18773
+ const parsed = JSON.parse(value);
18774
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
18775
+ } catch {
18776
+ return [];
18777
+ }
18778
+ }
18779
+
18715
18780
  // src/sync/push.ts
18716
18781
  function buildVectorDocument(obs, config2, project) {
18717
18782
  const parts = [obs.title];
@@ -18852,7 +18917,7 @@ async function pushOutbox(db, client, config2, batchSize = 50) {
18852
18917
  const doc3 = buildSummaryVectorDocument(summary, config2, {
18853
18918
  canonical_id: project2.canonical_id,
18854
18919
  name: project2.name
18855
- }, summaryObservations, buildSummaryCaptureContext(sessionPrompts, sessionToolEvents, summaryObservations));
18920
+ }, summaryObservations, buildSessionHandoffMetadata(sessionPrompts, sessionToolEvents, summaryObservations));
18856
18921
  batch.push({ entryId: entry.id, doc: doc3 });
18857
18922
  continue;
18858
18923
  }
@@ -18923,48 +18988,6 @@ function countPresentSections2(summary) {
18923
18988
  function extractSectionItems2(section) {
18924
18989
  return extractSummaryItems(section, 4);
18925
18990
  }
18926
- function buildSummaryCaptureContext(prompts, toolEvents, observations) {
18927
- const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
18928
- const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
18929
- const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
18930
- const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
18931
- const hotFiles = [...new Set(observations.flatMap((obs) => [
18932
- ...parseJsonArray3(obs.files_modified),
18933
- ...parseJsonArray3(obs.files_read)
18934
- ]).filter(Boolean))].slice(0, 6);
18935
- 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);
18936
- const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
18937
- const observationSourceTools = Array.from(observations.reduce((acc, obs) => {
18938
- if (!obs.source_tool)
18939
- return acc;
18940
- acc.set(obs.source_tool, (acc.get(obs.source_tool) ?? 0) + 1);
18941
- return acc;
18942
- }, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
18943
- const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
18944
- return {
18945
- prompt_count: prompts.length,
18946
- tool_event_count: toolEvents.length,
18947
- recent_request_prompts: recentRequestPrompts,
18948
- latest_request: latestRequest,
18949
- recent_tool_names: recentToolNames,
18950
- recent_tool_commands: recentToolCommands,
18951
- capture_state: captureState,
18952
- hot_files: hotFiles,
18953
- recent_outcomes: recentOutcomes,
18954
- observation_source_tools: observationSourceTools,
18955
- latest_observation_prompt_number: latestObservationPromptNumber
18956
- };
18957
- }
18958
- function parseJsonArray3(value) {
18959
- if (!value)
18960
- return [];
18961
- try {
18962
- const parsed = JSON.parse(value);
18963
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
18964
- } catch {
18965
- return [];
18966
- }
18967
- }
18968
18991
 
18969
18992
  // src/sync/pull.ts
18970
18993
  var PULL_CURSOR_KEY = "pull_cursor";
@@ -19886,7 +19909,7 @@ process.on("SIGTERM", () => {
19886
19909
  });
19887
19910
  var server = new McpServer({
19888
19911
  name: "engrm",
19889
- version: "0.4.21"
19912
+ version: "0.4.22"
19890
19913
  });
19891
19914
  server.tool("save_observation", "Save an observation to memory", {
19892
19915
  type: exports_external.enum([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "description": "Shared memory across devices, sessions, and coding agents",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",