engrm 0.4.25 → 0.4.26

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.
@@ -3009,7 +3009,7 @@ function buildBeacon(db, config, sessionId, metrics) {
3009
3009
  sentinel_used: valueSignals.security_findings_count > 0,
3010
3010
  risk_score: riskScore,
3011
3011
  stacks_detected: stacks,
3012
- client_version: "0.4.25",
3012
+ client_version: "0.4.26",
3013
3013
  context_observations_injected: metrics?.contextObsInjected ?? 0,
3014
3014
  context_total_available: metrics?.contextTotalAvailable ?? 0,
3015
3015
  recall_attempts: metrics?.recallAttempts ?? 0,
package/dist/server.js CHANGED
@@ -16458,8 +16458,12 @@ function getRecentRequests(db, input) {
16458
16458
  function getRecentChat(db, input) {
16459
16459
  const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
16460
16460
  if (input.session_id) {
16461
+ const messages2 = db.getSessionChatMessages(input.session_id, limit).slice(-limit).reverse();
16461
16462
  return {
16462
- messages: db.getSessionChatMessages(input.session_id, limit).slice(-limit).reverse()
16463
+ messages: messages2,
16464
+ session_count: countDistinctSessions(messages2),
16465
+ source_summary: summarizeChatSources(messages2),
16466
+ transcript_backed: messages2.some((message) => message.source_kind === "transcript")
16463
16467
  };
16464
16468
  }
16465
16469
  const projectScoped = input.project_scoped !== false;
@@ -16474,11 +16478,24 @@ function getRecentChat(db, input) {
16474
16478
  projectName = project.name;
16475
16479
  }
16476
16480
  }
16481
+ const messages = db.getRecentChatMessages(projectId, limit, input.user_id);
16477
16482
  return {
16478
- messages: db.getRecentChatMessages(projectId, limit, input.user_id),
16479
- project: projectName
16483
+ messages,
16484
+ project: projectName,
16485
+ session_count: countDistinctSessions(messages),
16486
+ source_summary: summarizeChatSources(messages),
16487
+ transcript_backed: messages.some((message) => message.source_kind === "transcript")
16480
16488
  };
16481
16489
  }
16490
+ function summarizeChatSources(messages) {
16491
+ return messages.reduce((summary, message) => {
16492
+ summary[message.source_kind] += 1;
16493
+ return summary;
16494
+ }, { transcript: 0, hook: 0 });
16495
+ }
16496
+ function countDistinctSessions(messages) {
16497
+ return new Set(messages.map((message) => message.session_id)).size;
16498
+ }
16482
16499
 
16483
16500
  // src/tools/search-chat.ts
16484
16501
  function searchChat(db, input) {
@@ -16495,11 +16512,24 @@ function searchChat(db, input) {
16495
16512
  projectName = project.name;
16496
16513
  }
16497
16514
  }
16515
+ const messages = db.searchChatMessages(input.query, projectId, limit, input.user_id);
16498
16516
  return {
16499
- messages: db.searchChatMessages(input.query, projectId, limit, input.user_id),
16500
- project: projectName
16517
+ messages,
16518
+ project: projectName,
16519
+ session_count: countDistinctSessions2(messages),
16520
+ source_summary: summarizeChatSources2(messages),
16521
+ transcript_backed: messages.some((message) => message.source_kind === "transcript")
16501
16522
  };
16502
16523
  }
16524
+ function summarizeChatSources2(messages) {
16525
+ return messages.reduce((summary, message) => {
16526
+ summary[message.source_kind] += 1;
16527
+ return summary;
16528
+ }, { transcript: 0, hook: 0 });
16529
+ }
16530
+ function countDistinctSessions2(messages) {
16531
+ return new Set(messages.map((message) => message.session_id)).size;
16532
+ }
16503
16533
 
16504
16534
  // src/tools/session-story.ts
16505
16535
  function getSessionStory(db, input) {
@@ -17230,6 +17260,7 @@ function findStaleDecisionsGlobal(db, options) {
17230
17260
  }
17231
17261
 
17232
17262
  // src/context/inject.ts
17263
+ var FRESH_CONTINUITY_WINDOW_DAYS = 3;
17233
17264
  function tokenizeProjectHint(text) {
17234
17265
  return Array.from(new Set((text.toLowerCase().match(/[a-z0-9_+-]{4,}/g) ?? []).filter(Boolean)));
17235
17266
  }
@@ -17369,20 +17400,21 @@ function buildSessionContext(db, cwd, options = {}) {
17369
17400
  const canonicalId = project?.canonical_id ?? detected.canonical_id;
17370
17401
  if (maxCount !== undefined) {
17371
17402
  const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
17372
- const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
17373
- const recentPrompts2 = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
17374
- const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
17403
+ let all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
17404
+ const recentPrompts2 = isNewProject ? [] : db.getRecentUserPrompts(projectId, 6, opts.userId);
17405
+ const recentToolEvents2 = isNewProject ? [] : db.getRecentToolEvents(projectId, 6, opts.userId);
17375
17406
  const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
17376
17407
  const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
17377
17408
  const recentOutcomes2 = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions2);
17378
- const recentHandoffs2 = getRecentHandoffs(db, {
17409
+ const recentHandoffs2 = isNewProject ? [] : getRecentHandoffs(db, {
17379
17410
  cwd,
17380
- project_scoped: !isNewProject,
17411
+ project_scoped: true,
17381
17412
  user_id: opts.userId,
17382
17413
  current_device_id: opts.currentDeviceId,
17383
17414
  limit: 3
17384
17415
  }).handoffs;
17385
17416
  const recentChatMessages2 = !isNewProject && project ? db.getRecentChatMessages(project.id, 4, opts.userId) : [];
17417
+ all = filterAutoLoadedObservationsForContinuity(all, pinned, isNewProject, recentPrompts2, recentToolEvents2, recentSessions2, recentHandoffs2, recentChatMessages2, summariesFromRecentSessions(db, projectId, recentSessions2));
17386
17418
  return {
17387
17419
  project_name: projectName,
17388
17420
  canonical_id: canonicalId,
@@ -17418,19 +17450,20 @@ function buildSessionContext(db, cwd, options = {}) {
17418
17450
  selected.push(obs);
17419
17451
  }
17420
17452
  const summaries = isNewProject ? [] : db.getRecentSummaries(projectId, 5);
17421
- const recentPrompts = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
17422
- const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
17453
+ const recentPrompts = isNewProject ? [] : db.getRecentUserPrompts(projectId, 6, opts.userId);
17454
+ const recentToolEvents = isNewProject ? [] : db.getRecentToolEvents(projectId, 6, opts.userId);
17423
17455
  const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
17424
17456
  const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
17425
17457
  const recentOutcomes = isNewProject ? undefined : getRecentOutcomes(db, projectId, opts.userId, recentSessions);
17426
- const recentHandoffs = getRecentHandoffs(db, {
17458
+ const recentHandoffs = isNewProject ? [] : getRecentHandoffs(db, {
17427
17459
  cwd,
17428
- project_scoped: !isNewProject,
17460
+ project_scoped: true,
17429
17461
  user_id: opts.userId,
17430
17462
  current_device_id: opts.currentDeviceId,
17431
17463
  limit: 3
17432
17464
  }).handoffs;
17433
17465
  const recentChatMessages = !isNewProject ? db.getRecentChatMessages(projectId, 4, opts.userId) : [];
17466
+ const filteredSelected = filterAutoLoadedObservationsForContinuity(selected, pinned, isNewProject, recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries);
17434
17467
  let securityFindings = [];
17435
17468
  if (!isNewProject) {
17436
17469
  try {
@@ -17478,8 +17511,8 @@ function buildSessionContext(db, cwd, options = {}) {
17478
17511
  return {
17479
17512
  project_name: projectName,
17480
17513
  canonical_id: canonicalId,
17481
- observations: selected.map(toContextObservation),
17482
- session_count: selected.length,
17514
+ observations: filteredSelected.map(toContextObservation),
17515
+ session_count: filteredSelected.length,
17483
17516
  total_active: totalActive,
17484
17517
  summaries: summaries.length > 0 ? summaries : undefined,
17485
17518
  securityFindings: securityFindings.length > 0 ? securityFindings : undefined,
@@ -17494,6 +17527,39 @@ function buildSessionContext(db, cwd, options = {}) {
17494
17527
  recentChatMessages: recentChatMessages.length > 0 ? recentChatMessages : undefined
17495
17528
  };
17496
17529
  }
17530
+ function filterAutoLoadedObservationsForContinuity(observations, pinned, isNewProject, recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries) {
17531
+ if (isNewProject)
17532
+ return observations;
17533
+ if (hasFreshProjectContinuity(recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries)) {
17534
+ return observations;
17535
+ }
17536
+ const pinnedIds = new Set(pinned.map((obs) => obs.id));
17537
+ return observations.filter((obs) => {
17538
+ if (pinnedIds.has(obs.id))
17539
+ return true;
17540
+ return observationAgeDays(obs.created_at_epoch) <= FRESH_CONTINUITY_WINDOW_DAYS;
17541
+ });
17542
+ }
17543
+ function hasFreshProjectContinuity(recentPrompts, recentToolEvents, recentSessions, recentHandoffs, recentChatMessages, summaries) {
17544
+ const freshEnough = (epoch) => typeof epoch === "number" && observationAgeDays(epoch) <= FRESH_CONTINUITY_WINDOW_DAYS;
17545
+ return recentPrompts.some((item) => freshEnough(item.created_at_epoch)) || recentToolEvents.some((item) => freshEnough(item.created_at_epoch)) || recentSessions.some((item) => freshEnough(item.completed_at_epoch ?? item.started_at_epoch)) || recentHandoffs.some((item) => freshEnough(item.created_at_epoch)) || recentChatMessages.some((item) => freshEnough(item.created_at_epoch)) || summaries.some((item) => freshEnough(item.created_at_epoch));
17546
+ }
17547
+ function summariesFromRecentSessions(db, projectId, recentSessions) {
17548
+ const seen = new Set;
17549
+ const rows = [];
17550
+ for (const session of recentSessions) {
17551
+ if (seen.has(session.session_id))
17552
+ continue;
17553
+ seen.add(session.session_id);
17554
+ const summary = db.getSessionSummary(session.session_id);
17555
+ if (summary && summary.project_id === projectId)
17556
+ rows.push(summary);
17557
+ }
17558
+ return rows;
17559
+ }
17560
+ function observationAgeDays(createdAtEpoch) {
17561
+ return Math.max(0, (Math.floor(Date.now() / 1000) - createdAtEpoch) / 86400);
17562
+ }
17497
17563
  function estimateObservationTokens(obs, index) {
17498
17564
  const DETAILED_THRESHOLD = 5;
17499
17565
  const titleCost = estimateTokens(`- **[${obs.type}]** ${obs.title} (2026-01-01, q=0.5)`);
@@ -17948,9 +18014,12 @@ function getProjectMemoryIndex(db, input) {
17948
18014
  `)
17949
18015
  ].filter(Boolean).join(`
17950
18016
  `));
18017
+ const continuityState = classifyContinuityState(recentRequestsCount, recentToolsCount, recentHandoffsCount.length, recentChatCount, recentSessions, recentOutcomes.length);
17951
18018
  return {
17952
18019
  project: project.name,
17953
18020
  canonical_id: project.canonical_id,
18021
+ continuity_state: continuityState,
18022
+ continuity_summary: describeContinuityState(continuityState),
17954
18023
  observation_counts: counts,
17955
18024
  recent_sessions: recentSessions,
17956
18025
  recent_outcomes: recentOutcomes,
@@ -17972,6 +18041,26 @@ function getProjectMemoryIndex(db, input) {
17972
18041
  suggested_tools: suggestedTools
17973
18042
  };
17974
18043
  }
18044
+ function classifyContinuityState(recentRequestsCount, recentToolsCount, recentHandoffsCount, recentChatCount, recentSessions, recentOutcomesCount) {
18045
+ const hasRaw = recentRequestsCount > 0 || recentToolsCount > 0;
18046
+ const hasResume = recentHandoffsCount > 0 || recentChatCount > 0;
18047
+ const hasSessionThread = recentSessions.length > 0 || recentOutcomesCount > 0;
18048
+ if (hasRaw && (hasResume || hasSessionThread))
18049
+ return "fresh";
18050
+ if (hasRaw || hasResume || hasSessionThread)
18051
+ return "thin";
18052
+ return "cold";
18053
+ }
18054
+ function describeContinuityState(state) {
18055
+ switch (state) {
18056
+ case "fresh":
18057
+ return "Fresh repo-local continuity is available.";
18058
+ case "thin":
18059
+ return "Only partial continuity is available; recent prompts/chat are safer than older memory.";
18060
+ default:
18061
+ return "No fresh repo-local continuity yet; older memory should be treated cautiously.";
18062
+ }
18063
+ }
17975
18064
  function extractPaths(value) {
17976
18065
  if (!value)
17977
18066
  return [];
@@ -18078,9 +18167,12 @@ function getMemoryConsole(db, input) {
18078
18167
  cwd,
18079
18168
  user_id: input.user_id
18080
18169
  }) : null;
18170
+ const continuityState = projectIndex?.continuity_state ?? classifyContinuityState(requests.length, tools.length, recentHandoffs.length, recentChat.length, sessions, (projectIndex?.recent_outcomes ?? []).length);
18081
18171
  return {
18082
18172
  project: project?.name,
18083
18173
  capture_mode: requests.length > 0 || tools.length > 0 ? "rich" : "observations-only",
18174
+ continuity_state: continuityState,
18175
+ continuity_summary: projectIndex?.continuity_summary ?? describeContinuityState(continuityState),
18084
18176
  sessions,
18085
18177
  requests,
18086
18178
  tools,
@@ -18622,16 +18714,65 @@ function getCaptureQuality(db, input = {}) {
18622
18714
  summary_only: allSessions.filter((s) => s.capture_state === "summary-only").length,
18623
18715
  legacy: allSessions.filter((s) => s.capture_state === "legacy").length
18624
18716
  };
18625
- const topProjects = workspace.projects.slice(0, limit).map((project) => ({
18626
- name: project.name,
18627
- canonical_id: project.canonical_id,
18628
- observation_count: project.observation_count,
18629
- session_count: project.session_count,
18630
- prompt_count: project.prompt_count,
18631
- tool_event_count: project.tool_event_count,
18632
- assistant_checkpoint_count: project.assistant_checkpoint_count,
18633
- raw_capture_state: project.prompt_count > 0 && project.tool_event_count > 0 ? "rich" : project.prompt_count > 0 || project.tool_event_count > 0 ? "partial" : "summary-only"
18634
- }));
18717
+ const chatCoverageRow = db.db.query(`SELECT
18718
+ COUNT(DISTINCT CASE WHEN source_kind = 'transcript' THEN session_id END) as transcript_backed_sessions,
18719
+ COUNT(DISTINCT CASE
18720
+ WHEN source_kind = 'hook'
18721
+ AND NOT EXISTS (
18722
+ SELECT 1 FROM chat_messages t2
18723
+ WHERE t2.session_id = chat_messages.session_id
18724
+ AND t2.source_kind = 'transcript'
18725
+ )
18726
+ THEN session_id
18727
+ END) as hook_only_sessions,
18728
+ COUNT(*) as chat_messages
18729
+ FROM chat_messages
18730
+ WHERE 1 = 1
18731
+ ${input.user_id ? " AND user_id = ?" : ""}`).get(...input.user_id ? [input.user_id] : []) ?? {
18732
+ transcript_backed_sessions: 0,
18733
+ hook_only_sessions: 0,
18734
+ chat_messages: 0
18735
+ };
18736
+ const chatCoverageByProject = new Map;
18737
+ const chatRows = db.db.query(`SELECT p.canonical_id, cm.source_kind, COUNT(*) as count
18738
+ FROM chat_messages cm
18739
+ JOIN projects p ON p.id = cm.project_id
18740
+ WHERE cm.project_id IS NOT NULL
18741
+ ${input.user_id ? " AND cm.user_id = ?" : ""}
18742
+ GROUP BY p.canonical_id, cm.source_kind`).all(...input.user_id ? [input.user_id] : []);
18743
+ for (const row of chatRows) {
18744
+ const current = chatCoverageByProject.get(row.canonical_id) ?? {
18745
+ chat_message_count: 0,
18746
+ transcript_count: 0,
18747
+ hook_only_count: 0
18748
+ };
18749
+ current.chat_message_count += row.count;
18750
+ if (row.source_kind === "transcript") {
18751
+ current.transcript_count += row.count;
18752
+ } else {
18753
+ current.hook_only_count += row.count;
18754
+ }
18755
+ chatCoverageByProject.set(row.canonical_id, current);
18756
+ }
18757
+ const topProjects = workspace.projects.slice(0, limit).map((project) => {
18758
+ const chat = chatCoverageByProject.get(project.canonical_id) ?? {
18759
+ chat_message_count: 0,
18760
+ transcript_count: 0,
18761
+ hook_only_count: 0
18762
+ };
18763
+ return {
18764
+ name: project.name,
18765
+ canonical_id: project.canonical_id,
18766
+ observation_count: project.observation_count,
18767
+ session_count: project.session_count,
18768
+ prompt_count: project.prompt_count,
18769
+ tool_event_count: project.tool_event_count,
18770
+ assistant_checkpoint_count: project.assistant_checkpoint_count,
18771
+ chat_message_count: chat.chat_message_count,
18772
+ chat_coverage_state: chat.transcript_count > 0 ? "transcript-backed" : chat.hook_only_count > 0 ? "hook-only" : "none",
18773
+ raw_capture_state: project.prompt_count > 0 && project.tool_event_count > 0 ? "rich" : project.prompt_count > 0 || project.tool_event_count > 0 ? "partial" : "summary-only"
18774
+ };
18775
+ });
18635
18776
  const checkpointTypeRows = db.db.query(`SELECT type, COUNT(*) as count
18636
18777
  FROM observations
18637
18778
  WHERE source_tool = 'assistant-stop'
@@ -18666,9 +18807,14 @@ function getCaptureQuality(db, input = {}) {
18666
18807
  sessions: workspace.totals.sessions,
18667
18808
  prompts: workspace.totals.prompts,
18668
18809
  tool_events: workspace.totals.tool_events,
18669
- assistant_checkpoints: workspace.totals.assistant_checkpoints
18810
+ assistant_checkpoints: workspace.totals.assistant_checkpoints,
18811
+ chat_messages: chatCoverageRow.chat_messages
18670
18812
  },
18671
18813
  session_states: sessionStates,
18814
+ chat_coverage: {
18815
+ transcript_backed_sessions: chatCoverageRow.transcript_backed_sessions,
18816
+ hook_only_sessions: chatCoverageRow.hook_only_sessions
18817
+ },
18672
18818
  projects_with_raw_capture: workspace.projects_with_raw_capture,
18673
18819
  provenance_summary: workspace.provenance_summary,
18674
18820
  provenance_type_mix: provenanceTypeMix,
@@ -18869,9 +19015,12 @@ function getSessionContext(db, input) {
18869
19015
  }).messages.length;
18870
19016
  const captureState = recentRequests > 0 && recentTools > 0 ? "rich" : recentRequests > 0 || recentTools > 0 ? "partial" : "summary-only";
18871
19017
  const hotFiles = buildHotFiles(context);
19018
+ const continuityState = classifyContinuityState(recentRequests, recentTools, recentHandoffs, recentChatMessages, context.recentSessions ?? [], (context.recentOutcomes ?? []).length);
18872
19019
  return {
18873
19020
  project_name: context.project_name,
18874
19021
  canonical_id: context.canonical_id,
19022
+ continuity_state: continuityState,
19023
+ continuity_summary: describeContinuityState(continuityState),
18875
19024
  session_count: context.session_count,
18876
19025
  total_active: context.total_active,
18877
19026
  recent_requests: recentRequests,
@@ -21003,7 +21152,7 @@ process.on("SIGTERM", () => {
21003
21152
  });
21004
21153
  var server = new McpServer({
21005
21154
  name: "engrm",
21006
- version: "0.4.25"
21155
+ version: "0.4.26"
21007
21156
  });
21008
21157
  server.tool("save_observation", "Save an observation to memory", {
21009
21158
  type: exports_external.enum([
@@ -21715,7 +21864,8 @@ server.tool("memory_console", "Show a high-signal local overview of what Engrm c
21715
21864
  content: [
21716
21865
  {
21717
21866
  type: "text",
21718
- text: `${projectLine}` + `${captureLine}` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
21867
+ text: `${projectLine}` + `${captureLine}` + `Continuity: ${result.continuity_state} ${result.continuity_summary}
21868
+ ` + `${typeof result.assistant_checkpoint_count === "number" ? `Assistant checkpoints: ${result.assistant_checkpoint_count}
21719
21869
  ` : ""}` + `Handoffs: ${result.saved_handoffs} saved, ${result.rolling_handoff_drafts} rolling drafts
21720
21870
  ` + `${typeof result.estimated_read_tokens === "number" ? `Estimated read cost: ~${result.estimated_read_tokens}t
21721
21871
  ` : ""}` + `Suggested tools: ${result.suggested_tools.join(", ") || "(none)"}
@@ -21798,16 +21948,18 @@ server.tool("capture_quality", "Show how healthy Engrm capture is across the wor
21798
21948
  `) : "- (none)";
21799
21949
  const checkpointTypeLines = result.assistant_checkpoint_types.length > 0 ? result.assistant_checkpoint_types.map((item) => `- ${item.type}: ${item.count}`).join(`
21800
21950
  `) : "- (none)";
21801
- const projectLines = result.top_projects.length > 0 ? result.top_projects.map((project) => `- ${project.name} [${project.raw_capture_state}] obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count}`).join(`
21951
+ const projectLines = result.top_projects.length > 0 ? result.top_projects.map((project) => `- ${project.name} [${project.raw_capture_state}] obs=${project.observation_count} sessions=${project.session_count} prompts=${project.prompt_count} tools=${project.tool_event_count} checkpoints=${project.assistant_checkpoint_count} chat=${project.chat_message_count} (${project.chat_coverage_state})`).join(`
21802
21952
  `) : "- (none)";
21803
21953
  return {
21804
21954
  content: [
21805
21955
  {
21806
21956
  type: "text",
21807
- text: `Workspace totals: projects=${result.totals.projects}, observations=${result.totals.observations}, sessions=${result.totals.sessions}, prompts=${result.totals.prompts}, tools=${result.totals.tool_events}, checkpoints=${result.totals.assistant_checkpoints}
21957
+ text: `Workspace totals: projects=${result.totals.projects}, observations=${result.totals.observations}, sessions=${result.totals.sessions}, prompts=${result.totals.prompts}, tools=${result.totals.tool_events}, checkpoints=${result.totals.assistant_checkpoints}, chat=${result.totals.chat_messages}
21808
21958
 
21809
21959
  ` + `Session capture states: rich=${result.session_states.rich}, partial=${result.session_states.partial}, summary-only=${result.session_states.summary_only}, legacy=${result.session_states.legacy}
21810
21960
 
21961
+ ` + `Chat recall coverage: transcript-backed sessions=${result.chat_coverage.transcript_backed_sessions}, hook-only sessions=${result.chat_coverage.hook_only_sessions}
21962
+
21811
21963
  ` + `Projects with raw capture: ${result.projects_with_raw_capture}
21812
21964
 
21813
21965
  ` + `Assistant checkpoints by type:
@@ -21910,6 +22062,7 @@ server.tool("session_context", "Preview the exact project memory context Engrm w
21910
22062
  type: "text",
21911
22063
  text: `Project: ${result.project_name}
21912
22064
  ` + `Canonical ID: ${result.canonical_id}
22065
+ ` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
21913
22066
  ` + `Loaded observations: ${result.session_count}
21914
22067
  ` + `Searchable total: ${result.total_active}
21915
22068
  ` + `Recent requests: ${result.recent_requests}
@@ -21990,6 +22143,7 @@ server.tool("project_memory_index", "Show a typed local memory index for the cur
21990
22143
  type: "text",
21991
22144
  text: `Project: ${result.project}
21992
22145
  ` + `Canonical ID: ${result.canonical_id}
22146
+ ` + `Continuity: ${result.continuity_state} — ${result.continuity_summary}
21993
22147
  ` + `Recent requests captured: ${result.recent_requests_count}
21994
22148
  ` + `Recent tools captured: ${result.recent_tools_count}
21995
22149
 
@@ -22258,6 +22412,9 @@ server.tool("recent_chat", "Inspect recently captured chat messages in the separ
22258
22412
  const result = getRecentChat(db, params);
22259
22413
  const projectLine = result.project ? `Project: ${result.project}
22260
22414
  ` : "";
22415
+ const coverageLine = `Coverage: ${result.messages.length} messages across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · hook ${result.source_summary.hook}
22416
+ ` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall if this looks under-captured.
22417
+ `}`;
22261
22418
  const rows = result.messages.length > 0 ? result.messages.map((msg) => {
22262
22419
  const stamp = new Date(msg.created_at_epoch * 1000).toISOString().split("T")[0];
22263
22420
  return `- ${stamp} [${msg.role}] [${msg.source_kind}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`;
@@ -22267,7 +22424,7 @@ server.tool("recent_chat", "Inspect recently captured chat messages in the separ
22267
22424
  content: [
22268
22425
  {
22269
22426
  type: "text",
22270
- text: `${projectLine}Recent chat:
22427
+ text: `${projectLine}${coverageLine}Recent chat:
22271
22428
  ${rows}`
22272
22429
  }
22273
22430
  ]
@@ -22283,6 +22440,9 @@ server.tool("search_chat", "Search the separate chat lane without mixing it into
22283
22440
  const result = searchChat(db, params);
22284
22441
  const projectLine = result.project ? `Project: ${result.project}
22285
22442
  ` : "";
22443
+ const coverageLine = `Coverage: ${result.messages.length} matches across ${result.session_count} session${result.session_count === 1 ? "" : "s"} ` + `· transcript ${result.source_summary.transcript} · hook ${result.source_summary.hook}
22444
+ ` + `${result.transcript_backed ? "" : `Hint: run refresh_chat_recall if this looks under-captured.
22445
+ `}`;
22286
22446
  const rows = result.messages.length > 0 ? result.messages.map((msg) => {
22287
22447
  const stamp = new Date(msg.created_at_epoch * 1000).toISOString().split("T")[0];
22288
22448
  return `- ${stamp} [${msg.role}] [${msg.source_kind}] ${msg.content.replace(/\s+/g, " ").trim().slice(0, 200)}`;
@@ -22292,7 +22452,7 @@ server.tool("search_chat", "Search the separate chat lane without mixing it into
22292
22452
  content: [
22293
22453
  {
22294
22454
  type: "text",
22295
- text: `${projectLine}Chat search for "${params.query}":
22455
+ text: `${projectLine}${coverageLine}Chat search for "${params.query}":
22296
22456
  ${rows}`
22297
22457
  }
22298
22458
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engrm",
3
- "version": "0.4.25",
3
+ "version": "0.4.26",
4
4
  "description": "Shared memory across devices, sessions, and coding agents",
5
5
  "mcpName": "io.github.dr12hes/engrm",
6
6
  "type": "module",