engrm 0.4.10 → 0.4.11

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
@@ -607,9 +607,59 @@ function isVecExtensionLoaded(db) {
607
607
  return false;
608
608
  }
609
609
  }
610
+ function tableExists(db, name) {
611
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
612
+ return Boolean(row?.name);
613
+ }
614
+ function columnExists(db, table, column) {
615
+ if (!tableExists(db, table))
616
+ return false;
617
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
618
+ return rows.some((row) => row.name === column);
619
+ }
620
+ function ensureLegacyBaseTables(db) {
621
+ db.exec(`
622
+ CREATE TABLE IF NOT EXISTS sync_state (
623
+ key TEXT PRIMARY KEY,
624
+ value TEXT NOT NULL
625
+ );
626
+ `);
627
+ }
628
+ function inferLegacySchemaVersion(db) {
629
+ const hasProjects = tableExists(db, "projects");
630
+ const hasObservations = tableExists(db, "observations");
631
+ const hasSessions = tableExists(db, "sessions");
632
+ if (!(hasProjects && hasObservations && hasSessions))
633
+ return 0;
634
+ ensureLegacyBaseTables(db);
635
+ let version = 1;
636
+ if (columnExists(db, "observations", "superseded_by"))
637
+ version = Math.max(version, 2);
638
+ if (columnExists(db, "observations", "remote_source_id"))
639
+ version = Math.max(version, 3);
640
+ if (tableExists(db, "vec_observations"))
641
+ version = Math.max(version, 4);
642
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
643
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
644
+ version = Math.max(version, 5);
645
+ if (columnExists(db, "sessions", "risk_score"))
646
+ version = Math.max(version, 6);
647
+ if (tableExists(db, "packs_installed"))
648
+ version = Math.max(version, 7);
649
+ if (tableExists(db, "user_prompts"))
650
+ version = Math.max(version, 9);
651
+ if (tableExists(db, "tool_events"))
652
+ version = Math.max(version, 10);
653
+ return version;
654
+ }
610
655
  function runMigrations(db) {
611
656
  const currentVersion = db.query("PRAGMA user_version").get();
612
657
  let version = currentVersion.user_version;
658
+ const inferred = inferLegacySchemaVersion(db);
659
+ if (inferred > version) {
660
+ db.exec(`PRAGMA user_version = ${inferred}`);
661
+ version = inferred;
662
+ }
613
663
  for (const migration of MIGRATIONS) {
614
664
  if (migration.version <= version)
615
665
  continue;
@@ -1437,9 +1437,59 @@ function isVecExtensionLoaded(db) {
1437
1437
  return false;
1438
1438
  }
1439
1439
  }
1440
+ function tableExists(db, name) {
1441
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
1442
+ return Boolean(row?.name);
1443
+ }
1444
+ function columnExists(db, table, column) {
1445
+ if (!tableExists(db, table))
1446
+ return false;
1447
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
1448
+ return rows.some((row) => row.name === column);
1449
+ }
1450
+ function ensureLegacyBaseTables(db) {
1451
+ db.exec(`
1452
+ CREATE TABLE IF NOT EXISTS sync_state (
1453
+ key TEXT PRIMARY KEY,
1454
+ value TEXT NOT NULL
1455
+ );
1456
+ `);
1457
+ }
1458
+ function inferLegacySchemaVersion(db) {
1459
+ const hasProjects = tableExists(db, "projects");
1460
+ const hasObservations = tableExists(db, "observations");
1461
+ const hasSessions = tableExists(db, "sessions");
1462
+ if (!(hasProjects && hasObservations && hasSessions))
1463
+ return 0;
1464
+ ensureLegacyBaseTables(db);
1465
+ let version = 1;
1466
+ if (columnExists(db, "observations", "superseded_by"))
1467
+ version = Math.max(version, 2);
1468
+ if (columnExists(db, "observations", "remote_source_id"))
1469
+ version = Math.max(version, 3);
1470
+ if (tableExists(db, "vec_observations"))
1471
+ version = Math.max(version, 4);
1472
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
1473
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
1474
+ version = Math.max(version, 5);
1475
+ if (columnExists(db, "sessions", "risk_score"))
1476
+ version = Math.max(version, 6);
1477
+ if (tableExists(db, "packs_installed"))
1478
+ version = Math.max(version, 7);
1479
+ if (tableExists(db, "user_prompts"))
1480
+ version = Math.max(version, 9);
1481
+ if (tableExists(db, "tool_events"))
1482
+ version = Math.max(version, 10);
1483
+ return version;
1484
+ }
1440
1485
  function runMigrations(db) {
1441
1486
  const currentVersion = db.query("PRAGMA user_version").get();
1442
1487
  let version = currentVersion.user_version;
1488
+ const inferred = inferLegacySchemaVersion(db);
1489
+ if (inferred > version) {
1490
+ db.exec(`PRAGMA user_version = ${inferred}`);
1491
+ version = inferred;
1492
+ }
1443
1493
  for (const migration of MIGRATIONS) {
1444
1494
  if (migration.version <= version)
1445
1495
  continue;
@@ -785,9 +785,59 @@ function isVecExtensionLoaded(db) {
785
785
  return false;
786
786
  }
787
787
  }
788
+ function tableExists(db, name) {
789
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
790
+ return Boolean(row?.name);
791
+ }
792
+ function columnExists(db, table, column) {
793
+ if (!tableExists(db, table))
794
+ return false;
795
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
796
+ return rows.some((row) => row.name === column);
797
+ }
798
+ function ensureLegacyBaseTables(db) {
799
+ db.exec(`
800
+ CREATE TABLE IF NOT EXISTS sync_state (
801
+ key TEXT PRIMARY KEY,
802
+ value TEXT NOT NULL
803
+ );
804
+ `);
805
+ }
806
+ function inferLegacySchemaVersion(db) {
807
+ const hasProjects = tableExists(db, "projects");
808
+ const hasObservations = tableExists(db, "observations");
809
+ const hasSessions = tableExists(db, "sessions");
810
+ if (!(hasProjects && hasObservations && hasSessions))
811
+ return 0;
812
+ ensureLegacyBaseTables(db);
813
+ let version = 1;
814
+ if (columnExists(db, "observations", "superseded_by"))
815
+ version = Math.max(version, 2);
816
+ if (columnExists(db, "observations", "remote_source_id"))
817
+ version = Math.max(version, 3);
818
+ if (tableExists(db, "vec_observations"))
819
+ version = Math.max(version, 4);
820
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
821
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
822
+ version = Math.max(version, 5);
823
+ if (columnExists(db, "sessions", "risk_score"))
824
+ version = Math.max(version, 6);
825
+ if (tableExists(db, "packs_installed"))
826
+ version = Math.max(version, 7);
827
+ if (tableExists(db, "user_prompts"))
828
+ version = Math.max(version, 9);
829
+ if (tableExists(db, "tool_events"))
830
+ version = Math.max(version, 10);
831
+ return version;
832
+ }
788
833
  function runMigrations(db) {
789
834
  const currentVersion = db.query("PRAGMA user_version").get();
790
835
  let version = currentVersion.user_version;
836
+ const inferred = inferLegacySchemaVersion(db);
837
+ if (inferred > version) {
838
+ db.exec(`PRAGMA user_version = ${inferred}`);
839
+ version = inferred;
840
+ }
791
841
  for (const migration of MIGRATIONS) {
792
842
  if (migration.version <= version)
793
843
  continue;
@@ -579,9 +579,59 @@ function isVecExtensionLoaded(db) {
579
579
  return false;
580
580
  }
581
581
  }
582
+ function tableExists(db, name) {
583
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
584
+ return Boolean(row?.name);
585
+ }
586
+ function columnExists(db, table, column) {
587
+ if (!tableExists(db, table))
588
+ return false;
589
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
590
+ return rows.some((row) => row.name === column);
591
+ }
592
+ function ensureLegacyBaseTables(db) {
593
+ db.exec(`
594
+ CREATE TABLE IF NOT EXISTS sync_state (
595
+ key TEXT PRIMARY KEY,
596
+ value TEXT NOT NULL
597
+ );
598
+ `);
599
+ }
600
+ function inferLegacySchemaVersion(db) {
601
+ const hasProjects = tableExists(db, "projects");
602
+ const hasObservations = tableExists(db, "observations");
603
+ const hasSessions = tableExists(db, "sessions");
604
+ if (!(hasProjects && hasObservations && hasSessions))
605
+ return 0;
606
+ ensureLegacyBaseTables(db);
607
+ let version = 1;
608
+ if (columnExists(db, "observations", "superseded_by"))
609
+ version = Math.max(version, 2);
610
+ if (columnExists(db, "observations", "remote_source_id"))
611
+ version = Math.max(version, 3);
612
+ if (tableExists(db, "vec_observations"))
613
+ version = Math.max(version, 4);
614
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
615
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
616
+ version = Math.max(version, 5);
617
+ if (columnExists(db, "sessions", "risk_score"))
618
+ version = Math.max(version, 6);
619
+ if (tableExists(db, "packs_installed"))
620
+ version = Math.max(version, 7);
621
+ if (tableExists(db, "user_prompts"))
622
+ version = Math.max(version, 9);
623
+ if (tableExists(db, "tool_events"))
624
+ version = Math.max(version, 10);
625
+ return version;
626
+ }
582
627
  function runMigrations(db) {
583
628
  const currentVersion = db.query("PRAGMA user_version").get();
584
629
  let version = currentVersion.user_version;
630
+ const inferred = inferLegacySchemaVersion(db);
631
+ if (inferred > version) {
632
+ db.exec(`PRAGMA user_version = ${inferred}`);
633
+ version = inferred;
634
+ }
585
635
  for (const migration of MIGRATIONS) {
586
636
  if (migration.version <= version)
587
637
  continue;
@@ -655,9 +655,59 @@ function isVecExtensionLoaded(db) {
655
655
  return false;
656
656
  }
657
657
  }
658
+ function tableExists(db, name) {
659
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
660
+ return Boolean(row?.name);
661
+ }
662
+ function columnExists(db, table, column) {
663
+ if (!tableExists(db, table))
664
+ return false;
665
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
666
+ return rows.some((row) => row.name === column);
667
+ }
668
+ function ensureLegacyBaseTables(db) {
669
+ db.exec(`
670
+ CREATE TABLE IF NOT EXISTS sync_state (
671
+ key TEXT PRIMARY KEY,
672
+ value TEXT NOT NULL
673
+ );
674
+ `);
675
+ }
676
+ function inferLegacySchemaVersion(db) {
677
+ const hasProjects = tableExists(db, "projects");
678
+ const hasObservations = tableExists(db, "observations");
679
+ const hasSessions = tableExists(db, "sessions");
680
+ if (!(hasProjects && hasObservations && hasSessions))
681
+ return 0;
682
+ ensureLegacyBaseTables(db);
683
+ let version = 1;
684
+ if (columnExists(db, "observations", "superseded_by"))
685
+ version = Math.max(version, 2);
686
+ if (columnExists(db, "observations", "remote_source_id"))
687
+ version = Math.max(version, 3);
688
+ if (tableExists(db, "vec_observations"))
689
+ version = Math.max(version, 4);
690
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
691
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
692
+ version = Math.max(version, 5);
693
+ if (columnExists(db, "sessions", "risk_score"))
694
+ version = Math.max(version, 6);
695
+ if (tableExists(db, "packs_installed"))
696
+ version = Math.max(version, 7);
697
+ if (tableExists(db, "user_prompts"))
698
+ version = Math.max(version, 9);
699
+ if (tableExists(db, "tool_events"))
700
+ version = Math.max(version, 10);
701
+ return version;
702
+ }
658
703
  function runMigrations(db) {
659
704
  const currentVersion = db.query("PRAGMA user_version").get();
660
705
  let version = currentVersion.user_version;
706
+ const inferred = inferLegacySchemaVersion(db);
707
+ if (inferred > version) {
708
+ db.exec(`PRAGMA user_version = ${inferred}`);
709
+ version = inferred;
710
+ }
661
711
  for (const migration of MIGRATIONS) {
662
712
  if (migration.version <= version)
663
713
  continue;
@@ -2166,9 +2166,59 @@ function isVecExtensionLoaded(db) {
2166
2166
  return false;
2167
2167
  }
2168
2168
  }
2169
+ function tableExists(db, name) {
2170
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
2171
+ return Boolean(row?.name);
2172
+ }
2173
+ function columnExists(db, table, column) {
2174
+ if (!tableExists(db, table))
2175
+ return false;
2176
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
2177
+ return rows.some((row) => row.name === column);
2178
+ }
2179
+ function ensureLegacyBaseTables(db) {
2180
+ db.exec(`
2181
+ CREATE TABLE IF NOT EXISTS sync_state (
2182
+ key TEXT PRIMARY KEY,
2183
+ value TEXT NOT NULL
2184
+ );
2185
+ `);
2186
+ }
2187
+ function inferLegacySchemaVersion(db) {
2188
+ const hasProjects = tableExists(db, "projects");
2189
+ const hasObservations = tableExists(db, "observations");
2190
+ const hasSessions = tableExists(db, "sessions");
2191
+ if (!(hasProjects && hasObservations && hasSessions))
2192
+ return 0;
2193
+ ensureLegacyBaseTables(db);
2194
+ let version = 1;
2195
+ if (columnExists(db, "observations", "superseded_by"))
2196
+ version = Math.max(version, 2);
2197
+ if (columnExists(db, "observations", "remote_source_id"))
2198
+ version = Math.max(version, 3);
2199
+ if (tableExists(db, "vec_observations"))
2200
+ version = Math.max(version, 4);
2201
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
2202
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
2203
+ version = Math.max(version, 5);
2204
+ if (columnExists(db, "sessions", "risk_score"))
2205
+ version = Math.max(version, 6);
2206
+ if (tableExists(db, "packs_installed"))
2207
+ version = Math.max(version, 7);
2208
+ if (tableExists(db, "user_prompts"))
2209
+ version = Math.max(version, 9);
2210
+ if (tableExists(db, "tool_events"))
2211
+ version = Math.max(version, 10);
2212
+ return version;
2213
+ }
2169
2214
  function runMigrations(db) {
2170
2215
  const currentVersion = db.query("PRAGMA user_version").get();
2171
2216
  let version = currentVersion.user_version;
2217
+ const inferred = inferLegacySchemaVersion(db);
2218
+ if (inferred > version) {
2219
+ db.exec(`PRAGMA user_version = ${inferred}`);
2220
+ version = inferred;
2221
+ }
2172
2222
  for (const migration of MIGRATIONS) {
2173
2223
  if (migration.version <= version)
2174
2224
  continue;
@@ -3014,19 +3064,28 @@ function formatVisibleStartupBrief(context) {
3014
3064
  const sessionFallbacks = sessionFallbacksFromContext(context);
3015
3065
  const recentOutcomeLines = buildRecentOutcomeLines(context, latest);
3016
3066
  const projectSignals = buildProjectSignalLine(context);
3067
+ const shownItems = new Set;
3017
3068
  if (promptLines.length > 0) {
3018
3069
  lines.push(`${c2.cyan}Recent Requests:${c2.reset}`);
3019
3070
  for (const item of promptLines) {
3020
3071
  lines.push(` - ${truncateInline(item, 160)}`);
3072
+ rememberShownItem(shownItems, item);
3021
3073
  }
3022
3074
  }
3023
3075
  if (latest) {
3076
+ const sanitizedNextSteps = sanitizeNextSteps(latest.next_steps, {
3077
+ request: currentRequest,
3078
+ investigated: chooseSection(latest.investigated, observationFallbacks.investigated, "Investigated"),
3079
+ learned: latest.learned,
3080
+ completed: chooseSection(latest.completed, observationFallbacks.completed, "Completed"),
3081
+ recentOutcomes: recentOutcomeLines
3082
+ });
3024
3083
  const sections = [
3025
3084
  ["Request", currentRequest, 1],
3026
3085
  ["Investigated", chooseSection(latest.investigated, observationFallbacks.investigated, "Investigated"), 2],
3027
3086
  ["Learned", latest.learned, 2],
3028
3087
  ["Completed", chooseSection(latest.completed, observationFallbacks.completed, "Completed"), 2],
3029
- ["Next Steps", latest.next_steps, 2]
3088
+ ["Next Steps", sanitizedNextSteps, 2]
3030
3089
  ];
3031
3090
  for (const [label, value, maxItems] of sections) {
3032
3091
  const formatted = toSplashLines(value, maxItems ?? 2);
@@ -3034,33 +3093,45 @@ function formatVisibleStartupBrief(context) {
3034
3093
  lines.push(`${c2.cyan}${label}:${c2.reset}`);
3035
3094
  for (const item of formatted) {
3036
3095
  lines.push(` ${item}`);
3096
+ rememberShownItem(shownItems, item);
3037
3097
  }
3038
3098
  }
3039
3099
  }
3040
3100
  } else if (currentRequest && !duplicatesPromptLine(currentRequest, latestPromptLine)) {
3041
3101
  lines.push(`${c2.cyan}Current Request:${c2.reset}`);
3042
3102
  lines.push(` - ${truncateInline(currentRequest, 160)}`);
3103
+ rememberShownItem(shownItems, currentRequest);
3043
3104
  if (toolFallbacks.length > 0) {
3044
- lines.push(`${c2.cyan}Recent Tools:${c2.reset}`);
3045
- for (const item of toolFallbacks) {
3046
- lines.push(` - ${truncateInline(item, 160)}`);
3105
+ const additiveTools = filterAdditiveToolFallbacks(toolFallbacks, shownItems);
3106
+ if (additiveTools.length > 0) {
3107
+ lines.push(`${c2.cyan}Recent Tools:${c2.reset}`);
3108
+ for (const item of additiveTools) {
3109
+ lines.push(` - ${truncateInline(item, 160)}`);
3110
+ rememberShownItem(shownItems, item);
3111
+ }
3047
3112
  }
3048
3113
  }
3049
3114
  }
3050
3115
  if (latest && currentRequest && !hasRequestSection(lines) && !duplicatesPromptLine(currentRequest, latestPromptLine)) {
3051
3116
  lines.push(`${c2.cyan}Current Request:${c2.reset}`);
3052
3117
  lines.push(` - ${truncateInline(currentRequest, 160)}`);
3118
+ rememberShownItem(shownItems, currentRequest);
3053
3119
  }
3054
3120
  if (recentOutcomeLines.length > 0) {
3055
3121
  lines.push(`${c2.cyan}Recent Work:${c2.reset}`);
3056
3122
  for (const item of recentOutcomeLines) {
3057
3123
  lines.push(` - ${truncateInline(item, 160)}`);
3124
+ rememberShownItem(shownItems, item);
3058
3125
  }
3059
3126
  }
3060
3127
  if (toolFallbacks.length > 0 && latest) {
3061
- lines.push(`${c2.cyan}Recent Tools:${c2.reset}`);
3062
- for (const item of toolFallbacks) {
3063
- lines.push(` - ${truncateInline(item, 160)}`);
3128
+ const additiveTools = filterAdditiveToolFallbacks(toolFallbacks, shownItems);
3129
+ if (additiveTools.length > 0) {
3130
+ lines.push(`${c2.cyan}Recent Tools:${c2.reset}`);
3131
+ for (const item of additiveTools) {
3132
+ lines.push(` - ${truncateInline(item, 160)}`);
3133
+ rememberShownItem(shownItems, item);
3134
+ }
3064
3135
  }
3065
3136
  }
3066
3137
  if (sessionFallbacks.length > 0) {
@@ -3085,6 +3156,46 @@ function formatVisibleStartupBrief(context) {
3085
3156
  }
3086
3157
  return lines.slice(0, 14);
3087
3158
  }
3159
+ function rememberShownItem(shown, value) {
3160
+ if (!value)
3161
+ return;
3162
+ for (const item of value.split(`
3163
+ `)) {
3164
+ const normalized = normalizeStartupItem(item);
3165
+ if (normalized)
3166
+ shown.add(normalized);
3167
+ }
3168
+ }
3169
+ function extractNormalizedSplashItems(value) {
3170
+ if (!value)
3171
+ return [];
3172
+ return value.split(`
3173
+ `).map((line) => normalizeStartupItem(line)).filter(Boolean);
3174
+ }
3175
+ function sanitizeNextSteps(nextSteps, context) {
3176
+ if (!nextSteps)
3177
+ return null;
3178
+ const covered = new Set([
3179
+ normalizeStartupItem(context.request ?? ""),
3180
+ ...extractNormalizedSplashItems(context.investigated),
3181
+ ...extractNormalizedSplashItems(context.learned),
3182
+ ...extractNormalizedSplashItems(context.completed),
3183
+ ...context.recentOutcomes.map((item) => normalizeStartupItem(item))
3184
+ ].filter(Boolean));
3185
+ const kept = nextSteps.split(`
3186
+ `).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^[-*]\s*/, "")).filter((line) => {
3187
+ const normalized = normalizeStartupItem(line.replace(/^(investigate|follow through):\s*/i, ""));
3188
+ return normalized && !covered.has(normalized);
3189
+ });
3190
+ return kept.length > 0 ? kept.map((line) => `- ${line}`).join(`
3191
+ `) : null;
3192
+ }
3193
+ function filterAdditiveToolFallbacks(toolFallbacks, shownItems) {
3194
+ return toolFallbacks.filter((item) => {
3195
+ const normalized = normalizeStartupItem(item);
3196
+ return normalized && !shownItems.has(normalized);
3197
+ });
3198
+ }
3088
3199
  function buildPromptFallback(context) {
3089
3200
  const latest = (context.recentPrompts ?? []).find((prompt) => isMeaningfulPrompt2(prompt.prompt));
3090
3201
  if (!latest?.prompt)
@@ -43,7 +43,10 @@ function extractRetrospective(observations, sessionId, projectId, userId) {
43
43
  const investigated = extractInvestigated(observations);
44
44
  const learned = extractLearned(observations);
45
45
  const completed = extractCompleted(observations);
46
- const nextSteps = extractNextSteps(observations);
46
+ const nextSteps = extractNextSteps(observations, {
47
+ request,
48
+ completed
49
+ });
47
50
  if (!request && !investigated && !learned && !completed && !nextSteps) {
48
51
  return null;
49
52
  }
@@ -101,13 +104,17 @@ ${facts}` : `- ${title}`;
101
104
  return dedupeBulletLines(lines).join(`
102
105
  `);
103
106
  }
104
- function extractNextSteps(observations) {
107
+ function extractNextSteps(observations, existing) {
105
108
  if (observations.length < 2)
106
109
  return null;
107
110
  const lastQuarterStart = Math.max(0, Math.min(observations.length - 1, observations.length - 3, Math.floor(observations.length * 0.75)));
108
111
  const lastQuarter = observations.slice(lastQuarterStart);
109
- const unresolved = lastQuarter.filter((o) => o.type === "bugfix" && o.narrative && /error|fail|exception/i.test(o.narrative));
110
- const explicitDecisions = lastQuarter.filter((o) => o.type === "decision").sort((a, b) => scoreNarrativeObservation(b) - scoreNarrativeObservation(a)).slice(0, 2).map((o) => `- Follow through: ${o.title}`);
112
+ const alreadyCovered = new Set([
113
+ normalizeObservationKey(existing.request ?? ""),
114
+ ...extractNormalizedSummaryItems(existing.completed)
115
+ ].filter(Boolean));
116
+ const unresolved = lastQuarter.filter((o) => o.type === "bugfix" && o.narrative && /error|fail|exception/i.test(o.narrative) && !alreadyCovered.has(normalizeObservationKey(o.title)));
117
+ const explicitDecisions = lastQuarter.filter((o) => o.type === "decision").sort((a, b) => scoreNarrativeObservation(b) - scoreNarrativeObservation(a)).slice(0, 2).filter((o) => !alreadyCovered.has(normalizeObservationKey(o.title))).map((o) => `- Follow through: ${o.title}`);
111
118
  if (unresolved.length === 0 && explicitDecisions.length === 0)
112
119
  return null;
113
120
  const lines = unresolved.map((o) => `- Investigate: ${o.title}`).slice(0, 3).concat(explicitDecisions);
@@ -148,6 +155,12 @@ function dedupeObservationsByTitle(observations) {
148
155
  }
149
156
  return deduped;
150
157
  }
158
+ function extractNormalizedSummaryItems(value) {
159
+ if (!value)
160
+ return [];
161
+ return value.split(`
162
+ `).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^[-*]\s*/, "")).map((line) => line.replace(/^(investigate|follow through):\s*/i, "")).map((line) => normalizeObservationKey(line)).filter(Boolean);
163
+ }
151
164
  function scoreCompletedObservation(obs) {
152
165
  let score = scoreNarrativeObservation(obs);
153
166
  if (obs.type === "feature")
@@ -801,9 +814,59 @@ function isVecExtensionLoaded(db) {
801
814
  return false;
802
815
  }
803
816
  }
817
+ function tableExists(db, name) {
818
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
819
+ return Boolean(row?.name);
820
+ }
821
+ function columnExists(db, table, column) {
822
+ if (!tableExists(db, table))
823
+ return false;
824
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
825
+ return rows.some((row) => row.name === column);
826
+ }
827
+ function ensureLegacyBaseTables(db) {
828
+ db.exec(`
829
+ CREATE TABLE IF NOT EXISTS sync_state (
830
+ key TEXT PRIMARY KEY,
831
+ value TEXT NOT NULL
832
+ );
833
+ `);
834
+ }
835
+ function inferLegacySchemaVersion(db) {
836
+ const hasProjects = tableExists(db, "projects");
837
+ const hasObservations = tableExists(db, "observations");
838
+ const hasSessions = tableExists(db, "sessions");
839
+ if (!(hasProjects && hasObservations && hasSessions))
840
+ return 0;
841
+ ensureLegacyBaseTables(db);
842
+ let version = 1;
843
+ if (columnExists(db, "observations", "superseded_by"))
844
+ version = Math.max(version, 2);
845
+ if (columnExists(db, "observations", "remote_source_id"))
846
+ version = Math.max(version, 3);
847
+ if (tableExists(db, "vec_observations"))
848
+ version = Math.max(version, 4);
849
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
850
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
851
+ version = Math.max(version, 5);
852
+ if (columnExists(db, "sessions", "risk_score"))
853
+ version = Math.max(version, 6);
854
+ if (tableExists(db, "packs_installed"))
855
+ version = Math.max(version, 7);
856
+ if (tableExists(db, "user_prompts"))
857
+ version = Math.max(version, 9);
858
+ if (tableExists(db, "tool_events"))
859
+ version = Math.max(version, 10);
860
+ return version;
861
+ }
804
862
  function runMigrations(db) {
805
863
  const currentVersion = db.query("PRAGMA user_version").get();
806
864
  let version = currentVersion.user_version;
865
+ const inferred = inferLegacySchemaVersion(db);
866
+ if (inferred > version) {
867
+ db.exec(`PRAGMA user_version = ${inferred}`);
868
+ version = inferred;
869
+ }
807
870
  for (const migration of MIGRATIONS) {
808
871
  if (migration.version <= version)
809
872
  continue;
@@ -1862,8 +1925,13 @@ function buildSummaryVectorDocument(summary, config, project, observations = [],
1862
1925
  next_step_items: extractSectionItems(summary.next_steps),
1863
1926
  prompt_count: captureContext?.prompt_count ?? 0,
1864
1927
  tool_event_count: captureContext?.tool_event_count ?? 0,
1928
+ capture_state: captureContext?.capture_state ?? "summary-only",
1929
+ recent_request_prompts: captureContext?.recent_request_prompts ?? [],
1865
1930
  latest_request: captureContext?.latest_request ?? null,
1866
1931
  recent_tool_names: captureContext?.recent_tool_names ?? [],
1932
+ recent_tool_commands: captureContext?.recent_tool_commands ?? [],
1933
+ hot_files: captureContext?.hot_files ?? [],
1934
+ recent_outcomes: captureContext?.recent_outcomes ?? [],
1867
1935
  decisions_count: valueSignals.decisions_count,
1868
1936
  lessons_count: valueSignals.lessons_count,
1869
1937
  discoveries_count: valueSignals.discoveries_count,
@@ -1907,7 +1975,7 @@ async function pushOutbox(db, client, config, batchSize = 50) {
1907
1975
  const doc2 = buildSummaryVectorDocument(summary, config, {
1908
1976
  canonical_id: project2.canonical_id,
1909
1977
  name: project2.name
1910
- }, summaryObservations, buildSummaryCaptureContext(sessionPrompts, sessionToolEvents));
1978
+ }, summaryObservations, buildSummaryCaptureContext(sessionPrompts, sessionToolEvents, summaryObservations));
1911
1979
  batch.push({ entryId: entry.id, doc: doc2 });
1912
1980
  continue;
1913
1981
  }
@@ -1981,16 +2049,39 @@ function extractSectionItems(section) {
1981
2049
  return section.split(`
1982
2050
  `).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).slice(0, 4);
1983
2051
  }
1984
- function buildSummaryCaptureContext(prompts, toolEvents) {
2052
+ function buildSummaryCaptureContext(prompts, toolEvents, observations) {
1985
2053
  const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
2054
+ const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
1986
2055
  const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
2056
+ const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
2057
+ const hotFiles = [...new Set(observations.flatMap((obs) => [
2058
+ ...parseJsonArray2(obs.files_modified),
2059
+ ...parseJsonArray2(obs.files_read)
2060
+ ]).filter(Boolean))].slice(0, 6);
2061
+ 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);
2062
+ const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
1987
2063
  return {
1988
2064
  prompt_count: prompts.length,
1989
2065
  tool_event_count: toolEvents.length,
2066
+ recent_request_prompts: recentRequestPrompts,
1990
2067
  latest_request: latestRequest,
1991
- recent_tool_names: recentToolNames
2068
+ recent_tool_names: recentToolNames,
2069
+ recent_tool_commands: recentToolCommands,
2070
+ capture_state: captureState,
2071
+ hot_files: hotFiles,
2072
+ recent_outcomes: recentOutcomes
1992
2073
  };
1993
2074
  }
2075
+ function parseJsonArray2(value) {
2076
+ if (!value)
2077
+ return [];
2078
+ try {
2079
+ const parsed = JSON.parse(value);
2080
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
2081
+ } catch {
2082
+ return [];
2083
+ }
2084
+ }
1994
2085
 
1995
2086
  // src/embeddings/embedder.ts
1996
2087
  var _available = null;
@@ -723,9 +723,59 @@ function isVecExtensionLoaded(db) {
723
723
  return false;
724
724
  }
725
725
  }
726
+ function tableExists(db, name) {
727
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
728
+ return Boolean(row?.name);
729
+ }
730
+ function columnExists(db, table, column) {
731
+ if (!tableExists(db, table))
732
+ return false;
733
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
734
+ return rows.some((row) => row.name === column);
735
+ }
736
+ function ensureLegacyBaseTables(db) {
737
+ db.exec(`
738
+ CREATE TABLE IF NOT EXISTS sync_state (
739
+ key TEXT PRIMARY KEY,
740
+ value TEXT NOT NULL
741
+ );
742
+ `);
743
+ }
744
+ function inferLegacySchemaVersion(db) {
745
+ const hasProjects = tableExists(db, "projects");
746
+ const hasObservations = tableExists(db, "observations");
747
+ const hasSessions = tableExists(db, "sessions");
748
+ if (!(hasProjects && hasObservations && hasSessions))
749
+ return 0;
750
+ ensureLegacyBaseTables(db);
751
+ let version = 1;
752
+ if (columnExists(db, "observations", "superseded_by"))
753
+ version = Math.max(version, 2);
754
+ if (columnExists(db, "observations", "remote_source_id"))
755
+ version = Math.max(version, 3);
756
+ if (tableExists(db, "vec_observations"))
757
+ version = Math.max(version, 4);
758
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
759
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
760
+ version = Math.max(version, 5);
761
+ if (columnExists(db, "sessions", "risk_score"))
762
+ version = Math.max(version, 6);
763
+ if (tableExists(db, "packs_installed"))
764
+ version = Math.max(version, 7);
765
+ if (tableExists(db, "user_prompts"))
766
+ version = Math.max(version, 9);
767
+ if (tableExists(db, "tool_events"))
768
+ version = Math.max(version, 10);
769
+ return version;
770
+ }
726
771
  function runMigrations(db) {
727
772
  const currentVersion = db.query("PRAGMA user_version").get();
728
773
  let version = currentVersion.user_version;
774
+ const inferred = inferLegacySchemaVersion(db);
775
+ if (inferred > version) {
776
+ db.exec(`PRAGMA user_version = ${inferred}`);
777
+ version = inferred;
778
+ }
729
779
  for (const migration of MIGRATIONS) {
730
780
  if (migration.version <= version)
731
781
  continue;
package/dist/server.js CHANGED
@@ -14129,9 +14129,59 @@ function isVecExtensionLoaded(db) {
14129
14129
  return false;
14130
14130
  }
14131
14131
  }
14132
+ function tableExists(db, name) {
14133
+ const row = db.query(`SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?`).get(name);
14134
+ return Boolean(row?.name);
14135
+ }
14136
+ function columnExists(db, table, column) {
14137
+ if (!tableExists(db, table))
14138
+ return false;
14139
+ const rows = db.query(`PRAGMA table_info(${table})`).all();
14140
+ return rows.some((row) => row.name === column);
14141
+ }
14142
+ function ensureLegacyBaseTables(db) {
14143
+ db.exec(`
14144
+ CREATE TABLE IF NOT EXISTS sync_state (
14145
+ key TEXT PRIMARY KEY,
14146
+ value TEXT NOT NULL
14147
+ );
14148
+ `);
14149
+ }
14150
+ function inferLegacySchemaVersion(db) {
14151
+ const hasProjects = tableExists(db, "projects");
14152
+ const hasObservations = tableExists(db, "observations");
14153
+ const hasSessions = tableExists(db, "sessions");
14154
+ if (!(hasProjects && hasObservations && hasSessions))
14155
+ return 0;
14156
+ ensureLegacyBaseTables(db);
14157
+ let version2 = 1;
14158
+ if (columnExists(db, "observations", "superseded_by"))
14159
+ version2 = Math.max(version2, 2);
14160
+ if (columnExists(db, "observations", "remote_source_id"))
14161
+ version2 = Math.max(version2, 3);
14162
+ if (tableExists(db, "vec_observations"))
14163
+ version2 = Math.max(version2, 4);
14164
+ const hasSessionMetrics = columnExists(db, "sessions", "files_touched_count") && columnExists(db, "sessions", "searches_performed") && columnExists(db, "sessions", "tool_calls_count");
14165
+ if (hasSessionMetrics || tableExists(db, "security_findings"))
14166
+ version2 = Math.max(version2, 5);
14167
+ if (columnExists(db, "sessions", "risk_score"))
14168
+ version2 = Math.max(version2, 6);
14169
+ if (tableExists(db, "packs_installed"))
14170
+ version2 = Math.max(version2, 7);
14171
+ if (tableExists(db, "user_prompts"))
14172
+ version2 = Math.max(version2, 9);
14173
+ if (tableExists(db, "tool_events"))
14174
+ version2 = Math.max(version2, 10);
14175
+ return version2;
14176
+ }
14132
14177
  function runMigrations(db) {
14133
14178
  const currentVersion = db.query("PRAGMA user_version").get();
14134
14179
  let version2 = currentVersion.user_version;
14180
+ const inferred = inferLegacySchemaVersion(db);
14181
+ if (inferred > version2) {
14182
+ db.exec(`PRAGMA user_version = ${inferred}`);
14183
+ version2 = inferred;
14184
+ }
14135
14185
  for (const migration of MIGRATIONS) {
14136
14186
  if (migration.version <= version2)
14137
14187
  continue;
@@ -17336,18 +17386,44 @@ function getSessionContext(db, input) {
17336
17386
  });
17337
17387
  if (!context)
17338
17388
  return null;
17389
+ const recentRequests = context.recentPrompts?.length ?? 0;
17390
+ const recentTools = context.recentToolEvents?.length ?? 0;
17391
+ const captureState = recentRequests > 0 && recentTools > 0 ? "rich" : recentRequests > 0 || recentTools > 0 ? "partial" : "summary-only";
17392
+ const hotFiles = buildHotFiles(context);
17339
17393
  return {
17340
17394
  project_name: context.project_name,
17341
17395
  canonical_id: context.canonical_id,
17342
17396
  session_count: context.session_count,
17343
17397
  total_active: context.total_active,
17344
- recent_requests: context.recentPrompts?.length ?? 0,
17345
- recent_tools: context.recentToolEvents?.length ?? 0,
17398
+ recent_requests: recentRequests,
17399
+ recent_tools: recentTools,
17346
17400
  recent_sessions: context.recentSessions?.length ?? 0,
17347
- raw_capture_active: (context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0,
17401
+ recent_outcomes: context.recentOutcomes ?? [],
17402
+ hot_files: hotFiles,
17403
+ capture_state: captureState,
17404
+ raw_capture_active: recentRequests > 0 || recentTools > 0,
17348
17405
  preview: formatContextForInjection(context)
17349
17406
  };
17350
17407
  }
17408
+ function buildHotFiles(context) {
17409
+ const counts = new Map;
17410
+ for (const obs of context.observations) {
17411
+ for (const path of [...parseJsonArray(obs.files_read), ...parseJsonArray(obs.files_modified)]) {
17412
+ counts.set(path, (counts.get(path) ?? 0) + 1);
17413
+ }
17414
+ }
17415
+ return Array.from(counts.entries()).map(([path, count]) => ({ path, count })).sort((a, b) => b.count - a.count || a.path.localeCompare(b.path)).slice(0, 6);
17416
+ }
17417
+ function parseJsonArray(value) {
17418
+ if (!value)
17419
+ return [];
17420
+ try {
17421
+ const parsed = JSON.parse(value);
17422
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
17423
+ } catch {
17424
+ return [];
17425
+ }
17426
+ }
17351
17427
 
17352
17428
  // src/tools/send-message.ts
17353
17429
  async function sendMessage(db, config2, input) {
@@ -17964,8 +18040,13 @@ function buildSummaryVectorDocument(summary, config2, project, observations = []
17964
18040
  next_step_items: extractSectionItems2(summary.next_steps),
17965
18041
  prompt_count: captureContext?.prompt_count ?? 0,
17966
18042
  tool_event_count: captureContext?.tool_event_count ?? 0,
18043
+ capture_state: captureContext?.capture_state ?? "summary-only",
18044
+ recent_request_prompts: captureContext?.recent_request_prompts ?? [],
17967
18045
  latest_request: captureContext?.latest_request ?? null,
17968
18046
  recent_tool_names: captureContext?.recent_tool_names ?? [],
18047
+ recent_tool_commands: captureContext?.recent_tool_commands ?? [],
18048
+ hot_files: captureContext?.hot_files ?? [],
18049
+ recent_outcomes: captureContext?.recent_outcomes ?? [],
17969
18050
  decisions_count: valueSignals.decisions_count,
17970
18051
  lessons_count: valueSignals.lessons_count,
17971
18052
  discoveries_count: valueSignals.discoveries_count,
@@ -18009,7 +18090,7 @@ async function pushOutbox(db, client, config2, batchSize = 50) {
18009
18090
  const doc3 = buildSummaryVectorDocument(summary, config2, {
18010
18091
  canonical_id: project2.canonical_id,
18011
18092
  name: project2.name
18012
- }, summaryObservations, buildSummaryCaptureContext(sessionPrompts, sessionToolEvents));
18093
+ }, summaryObservations, buildSummaryCaptureContext(sessionPrompts, sessionToolEvents, summaryObservations));
18013
18094
  batch.push({ entryId: entry.id, doc: doc3 });
18014
18095
  continue;
18015
18096
  }
@@ -18083,16 +18164,39 @@ function extractSectionItems2(section) {
18083
18164
  return section.split(`
18084
18165
  `).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).slice(0, 4);
18085
18166
  }
18086
- function buildSummaryCaptureContext(prompts, toolEvents) {
18167
+ function buildSummaryCaptureContext(prompts, toolEvents, observations) {
18087
18168
  const latestRequest = prompts.length > 0 ? prompts[prompts.length - 1]?.prompt ?? null : null;
18169
+ const recentRequestPrompts = prompts.slice(-3).map((prompt) => prompt.prompt.trim()).filter(Boolean);
18088
18170
  const recentToolNames = [...new Set(toolEvents.slice(-8).map((tool) => tool.tool_name).filter(Boolean))];
18171
+ const recentToolCommands = [...new Set(toolEvents.slice(-5).map((tool) => (tool.command ?? tool.file_path ?? "").trim()).filter(Boolean))];
18172
+ const hotFiles = [...new Set(observations.flatMap((obs) => [
18173
+ ...parseJsonArray2(obs.files_modified),
18174
+ ...parseJsonArray2(obs.files_read)
18175
+ ]).filter(Boolean))].slice(0, 6);
18176
+ 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);
18177
+ const captureState = prompts.length > 0 && toolEvents.length > 0 ? "rich" : prompts.length > 0 || toolEvents.length > 0 ? "partial" : "summary-only";
18089
18178
  return {
18090
18179
  prompt_count: prompts.length,
18091
18180
  tool_event_count: toolEvents.length,
18181
+ recent_request_prompts: recentRequestPrompts,
18092
18182
  latest_request: latestRequest,
18093
- recent_tool_names: recentToolNames
18183
+ recent_tool_names: recentToolNames,
18184
+ recent_tool_commands: recentToolCommands,
18185
+ capture_state: captureState,
18186
+ hot_files: hotFiles,
18187
+ recent_outcomes: recentOutcomes
18094
18188
  };
18095
18189
  }
18190
+ function parseJsonArray2(value) {
18191
+ if (!value)
18192
+ return [];
18193
+ try {
18194
+ const parsed = JSON.parse(value);
18195
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
18196
+ } catch {
18197
+ return [];
18198
+ }
18199
+ }
18096
18200
 
18097
18201
  // src/sync/pull.ts
18098
18202
  var PULL_CURSOR_KEY = "pull_cursor";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Shared memory across devices, sessions, and coding agents",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",