@vedtechsolutions/engram-mcp 1.0.6 → 1.0.8

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.
@@ -1737,7 +1737,15 @@ var CONTEXTUAL = {
1737
1737
  /** Boost when memory files share the same module directory as current file */
1738
1738
  MODULE_PROXIMITY_BOOST: 0.2,
1739
1739
  /** Penalty multiplier when memory files are all from a different module (0.3 = 70% reduction) */
1740
- MODULE_MISMATCH_PENALTY: 0.3
1740
+ MODULE_MISMATCH_PENALTY: 0.3,
1741
+ /** Minimum access_count before staleness penalty applies */
1742
+ STALE_ACCESS_THRESHOLD: 15,
1743
+ /** access_count / reinforcement ratio above which staleness kicks in */
1744
+ STALE_RATIO_THRESHOLD: 8,
1745
+ /** Penalty reduction per unit above STALE_RATIO_THRESHOLD */
1746
+ STALE_PENALTY_PER_UNIT: 0.05,
1747
+ /** Maximum staleness penalty (caps the reduction) */
1748
+ MAX_STALE_PENALTY: 0.4
1741
1749
  };
1742
1750
  var REWARD = {
1743
1751
  /** Seed activation boost for positively-reinforced memories */
@@ -9114,6 +9122,16 @@ function contextualBoost(memory, context) {
9114
9122
  if (isEpisodicData(memory.type_data) && memory.type_data.outcome === "negative" && memory.type_data.lesson) {
9115
9123
  boost += CONTEXTUAL.FAILURE_EXPERIENCE_BOOST;
9116
9124
  }
9125
+ if (memory.access_count >= CONTEXTUAL.STALE_ACCESS_THRESHOLD) {
9126
+ const accessToReinforcementRatio = memory.access_count / Math.max(memory.reinforcement, 0.5);
9127
+ if (accessToReinforcementRatio > CONTEXTUAL.STALE_RATIO_THRESHOLD) {
9128
+ const stalePenalty = Math.min(
9129
+ (accessToReinforcementRatio - CONTEXTUAL.STALE_RATIO_THRESHOLD) * CONTEXTUAL.STALE_PENALTY_PER_UNIT,
9130
+ CONTEXTUAL.MAX_STALE_PENALTY
9131
+ );
9132
+ boost -= stalePenalty;
9133
+ }
9134
+ }
9117
9135
  return 1 + boost;
9118
9136
  }
9119
9137
  function rewardModifier(memory) {
@@ -12654,4 +12672,4 @@ export {
12654
12672
  composeProjectUnderstanding,
12655
12673
  formatMentalModelInjection
12656
12674
  };
12657
- //# sourceMappingURL=chunk-V5TTXT4V.js.map
12675
+ //# sourceMappingURL=chunk-OY2XHPUF.js.map
package/dist/hook.js CHANGED
@@ -174,7 +174,7 @@ import {
174
174
  updateReasoningChain,
175
175
  updateSelfModelFromSession,
176
176
  updateTask
177
- } from "./chunk-V5TTXT4V.js";
177
+ } from "./chunk-OY2XHPUF.js";
178
178
 
179
179
  // src/hook.ts
180
180
  import { readFileSync, writeFileSync, existsSync, renameSync, statSync, readdirSync, unlinkSync, appendFileSync, openSync, readSync, closeSync } from "fs";
@@ -1793,8 +1793,18 @@ function extractGoal(params) {
1793
1793
  const topFiles = params.session_files.slice(-3).map((f) => f.split(/[/\\]/).pop() ?? f).join(", ");
1794
1794
  return `${params.cognitive_state.recent_discovery} (${topFiles})`;
1795
1795
  }
1796
+ if (params.session_files && params.session_files.length > 0) {
1797
+ const topFiles = params.session_files.slice(-5).map((f) => f.split(/[/\\]/).pop() ?? f).join(", ");
1798
+ return `modifying ${topFiles}`;
1799
+ }
1796
1800
  if (params.conversation.topic_history.length > 0) {
1797
- return params.conversation.topic_history[0].topic;
1801
+ const topic = params.conversation.topic_history[0].topic;
1802
+ const commaCount = (topic.match(/,/g) || []).length;
1803
+ const wordCount = topic.split(/\s+/).length;
1804
+ const isVagueKeywordList = commaCount >= 2 && wordCount <= commaCount + 2;
1805
+ if (!isVagueKeywordList) {
1806
+ return topic;
1807
+ }
1798
1808
  }
1799
1809
  return null;
1800
1810
  }
@@ -2820,7 +2830,9 @@ function extractSearchIntent(query) {
2820
2830
  function updateCognitiveState(current, signal, recentTools, recentErrors) {
2821
2831
  const updated = { ...current };
2822
2832
  updated.last_updated = (/* @__PURE__ */ new Date()).toISOString();
2823
- updated.session_phase = recentErrors.length > 0 ? "debugging" : inferSessionPhase(recentTools);
2833
+ const recentWindow = recentTools.slice(-3);
2834
+ const hasRecentErrorContext = recentErrors.length > 0 && recentWindow.some((t) => t === "Bash") && recentWindow.length >= 2;
2835
+ updated.session_phase = hasRecentErrorContext ? "debugging" : inferSessionPhase(recentTools);
2824
2836
  switch (signal.type) {
2825
2837
  case "tool_call":
2826
2838
  break;
@@ -3127,7 +3139,9 @@ function loadWatcherState() {
3127
3139
  offload_message_sent: raw.offload_message_sent ?? false,
3128
3140
  summary_injection_mode: raw.summary_injection_mode ?? false,
3129
3141
  recent_commands: raw.recent_commands ?? [],
3130
- procedural_encoded_count: raw.procedural_encoded_count ?? 0
3142
+ procedural_encoded_count: raw.procedural_encoded_count ?? 0,
3143
+ recent_actions: raw.recent_actions ?? [],
3144
+ continuation_brief: raw.continuation_brief ?? null
3131
3145
  };
3132
3146
  }
3133
3147
  } catch {
@@ -3191,7 +3205,9 @@ function loadWatcherState() {
3191
3205
  offload_message_sent: false,
3192
3206
  summary_injection_mode: false,
3193
3207
  recent_commands: [],
3194
- procedural_encoded_count: 0
3208
+ procedural_encoded_count: 0,
3209
+ recent_actions: [],
3210
+ continuation_brief: null
3195
3211
  };
3196
3212
  }
3197
3213
  function saveWatcherState(state) {
@@ -3628,6 +3644,14 @@ Output: ${truncate(toolOutput, 500)}`,
3628
3644
  if (state.recent_tool_names.length > 10) {
3629
3645
  state.recent_tool_names = state.recent_tool_names.slice(-10);
3630
3646
  }
3647
+ state.recent_actions.push({
3648
+ tool: "Bash",
3649
+ target: truncate(cmd, 120),
3650
+ time: (/* @__PURE__ */ new Date()).toISOString()
3651
+ });
3652
+ if (state.recent_actions.length > 15) {
3653
+ state.recent_actions = state.recent_actions.slice(-15);
3654
+ }
3631
3655
  stateChanged = true;
3632
3656
  let testRun = null;
3633
3657
  if (isTestCommand(cmd)) {
@@ -4111,6 +4135,14 @@ function handlePostWrite(toolInput, argFallback) {
4111
4135
  const filePath = input?.file_path ?? input?.path ?? "";
4112
4136
  if (!filePath) return;
4113
4137
  const state = loadWatcherState();
4138
+ state.recent_actions.push({
4139
+ tool: "Edit",
4140
+ target: filePath,
4141
+ time: (/* @__PURE__ */ new Date()).toISOString()
4142
+ });
4143
+ if (state.recent_actions.length > 15) {
4144
+ state.recent_actions = state.recent_actions.slice(-15);
4145
+ }
4114
4146
  if (typeof filePath === "string" && !state.session_files.includes(filePath)) {
4115
4147
  state.session_files.push(filePath);
4116
4148
  if (state.session_files.length > 50) {
@@ -4254,6 +4286,16 @@ function handlePostToolGeneric(stdinJson) {
4254
4286
  if (state.recent_tool_names.length > 10) {
4255
4287
  state.recent_tool_names = state.recent_tool_names.slice(-10);
4256
4288
  }
4289
+ if (toolName !== "Read" && toolName !== "Glob" && toolName !== "Grep") {
4290
+ state.recent_actions.push({
4291
+ tool: toolName,
4292
+ target: call.input_summary ?? "",
4293
+ time: (/* @__PURE__ */ new Date()).toISOString()
4294
+ });
4295
+ if (state.recent_actions.length > 15) {
4296
+ state.recent_actions = state.recent_actions.slice(-15);
4297
+ }
4298
+ }
4257
4299
  state.reasoning_buffer.push(call);
4258
4300
  if (state.reasoning_buffer.length > REASONING_TRACE.MAX_BUFFER_SIZE) {
4259
4301
  state.reasoning_buffer = state.reasoning_buffer.slice(-REASONING_TRACE.MAX_BUFFER_SIZE);
@@ -4940,7 +4982,9 @@ function handleSessionStart(stdinJson, argFallback) {
4940
4982
  offload_message_sent: false,
4941
4983
  summary_injection_mode: false,
4942
4984
  recent_commands: isPostCompact ? prevState?.recent_commands ?? [] : [],
4943
- procedural_encoded_count: isPostCompact ? prevState?.procedural_encoded_count ?? 0 : 0
4985
+ procedural_encoded_count: isPostCompact ? prevState?.procedural_encoded_count ?? 0 : 0,
4986
+ recent_actions: isPostCompact ? prevState?.recent_actions ?? [] : [],
4987
+ continuation_brief: isPostCompact ? prevState?.continuation_brief ?? null : null
4944
4988
  });
4945
4989
  const source = metadata.source;
4946
4990
  if (!source || source === "startup") {
@@ -5540,6 +5584,50 @@ function handleEngramUsed(stdinJson, argFallback) {
5540
5584
  }
5541
5585
  saveWatcherState(state);
5542
5586
  }
5587
+ function buildContinuationBrief(state) {
5588
+ const cog = state.cognitive_state;
5589
+ const task = state.active_task ?? cog.current_approach ?? (state.session_files.length > 0 ? `Working on ${state.session_files.slice(-3).map((f) => f.split(/[/\\]/).pop() ?? f).join(", ")}` : "unknown task");
5590
+ const lastActions = state.recent_actions.slice(-8).map((a) => {
5591
+ const fileName = a.target.includes("/") ? a.target.split(/[/\\]/).pop() ?? a.target : a.target;
5592
+ return `${a.tool}: ${truncate(fileName, 80)}`;
5593
+ });
5594
+ const nextSteps = [];
5595
+ if (cog.search_intent && !nextSteps.some((s) => s.includes(cog.search_intent))) {
5596
+ nextSteps.push(`Investigate: ${cog.search_intent}`);
5597
+ }
5598
+ const lastAction = state.recent_actions[state.recent_actions.length - 1];
5599
+ if (lastAction && nextSteps.length === 0) {
5600
+ if (lastAction.tool === "Edit" || lastAction.tool === "Write") {
5601
+ nextSteps.push("Run tests to verify changes");
5602
+ } else if (lastAction.tool === "Bash" && lastAction.target.includes("test")) {
5603
+ nextSteps.push("Review test results and commit if passing");
5604
+ }
5605
+ }
5606
+ if (state.recent_errors.length > 0 && nextSteps.length < 3) {
5607
+ nextSteps.push(`Fix: ${truncate(state.recent_errors[state.recent_errors.length - 1], 120)}`);
5608
+ }
5609
+ const decisions = [];
5610
+ for (const id of state.decision_memory_ids.slice(-5)) {
5611
+ try {
5612
+ const mem = getMemory(id);
5613
+ if (mem) decisions.push(truncate(mem.content, 150));
5614
+ } catch {
5615
+ }
5616
+ }
5617
+ const triedFailed = (state.session_outcomes ?? []).filter((o) => o.includes("\u2192 fail") || o.includes("\u2192 dead end") || o.includes("\u2192 blocked")).slice(-5).map((o) => truncate(o, 120));
5618
+ const editActions = state.recent_actions.filter((a) => a.tool === "Edit" || a.tool === "Write");
5619
+ const keyFiles = [...new Set(editActions.map((a) => a.target))].slice(-10);
5620
+ return {
5621
+ task: truncate(task, 300),
5622
+ phase: cog.session_phase ?? "unknown",
5623
+ last_actions: lastActions,
5624
+ next_steps: nextSteps.slice(0, 5),
5625
+ decisions,
5626
+ tried_failed: triedFailed,
5627
+ key_files: keyFiles.length > 0 ? keyFiles : state.session_files.slice(-10),
5628
+ blockers: state.recent_errors.slice(-3).map((e) => truncate(e, 150))
5629
+ };
5630
+ }
5543
5631
  function handlePreCompact() {
5544
5632
  const state = loadWatcherState();
5545
5633
  if (!state.active_project) {
@@ -5782,6 +5870,10 @@ ${summaryParts.join(". ")}` : `Pre-compaction session summary: ${summaryParts.jo
5782
5870
  });
5783
5871
  process.stdout.write(output + "\n");
5784
5872
  }
5873
+ try {
5874
+ state.continuation_brief = buildContinuationBrief(state);
5875
+ } catch {
5876
+ }
5785
5877
  state.recovery_context = recovery;
5786
5878
  state.understanding_snapshot = understanding;
5787
5879
  saveWatcherState(state);
@@ -6123,7 +6215,32 @@ ${distillLines}`
6123
6215
  }
6124
6216
  }
6125
6217
  }
6218
+ if (!state.active_task || state.active_task === "write a comprehensive report") {
6219
+ const editedFiles = state.recent_actions.filter((a) => a.tool === "Edit" || a.tool === "Write").map((a) => a.target.split(/[/\\]/).pop() ?? a.target);
6220
+ const uniqueFiles = [...new Set(editedFiles)].slice(-5);
6221
+ if (uniqueFiles.length > 0) {
6222
+ const cog = state.cognitive_state;
6223
+ if (cog.current_approach && cog.current_approach.length > 5 && cog.current_approach !== "X") {
6224
+ state.active_task = truncate(cog.current_approach, 150);
6225
+ } else {
6226
+ state.active_task = `Working on ${uniqueFiles.join(", ")}`;
6227
+ }
6228
+ }
6229
+ }
6126
6230
  try {
6231
+ const cog = state.cognitive_state;
6232
+ if (cog.current_approach === "X" || cog.current_approach === "X.") {
6233
+ state.cognitive_state.current_approach = null;
6234
+ }
6235
+ if (cog.active_hypothesis === "Y" || cog.active_hypothesis === "Y.") {
6236
+ state.cognitive_state.active_hypothesis = null;
6237
+ }
6238
+ if (cog.recent_discovery === "Z" || cog.recent_discovery === "Z.") {
6239
+ state.cognitive_state.recent_discovery = null;
6240
+ }
6241
+ if (cog.active_hypothesis && cog.active_hypothesis.startsWith("/")) {
6242
+ state.cognitive_state.active_hypothesis = null;
6243
+ }
6127
6244
  if (content.length >= 20 && !state.cognitive_state.current_approach) {
6128
6245
  const approach = extractApproachFromPrompt(content);
6129
6246
  if (approach) {
@@ -6173,6 +6290,7 @@ ${distillLines}`
6173
6290
  budget.append("antipatterns", warnings);
6174
6291
  state.pending_error_warnings = [];
6175
6292
  }
6293
+ const surfacedIds = /* @__PURE__ */ new Set();
6176
6294
  try {
6177
6295
  const surfaceDomain = state.active_domain;
6178
6296
  if (surfaceDomain && surfaceDomain.length >= MEMORY_SURFACE.MIN_DOMAIN_LENGTH) {
@@ -6205,6 +6323,7 @@ ${distillLines}`
6205
6323
  }
6206
6324
  state.surface_injection_turns[mem.id] = state.total_turns;
6207
6325
  state.proactive_injection_turns[mem.id] = state.total_turns;
6326
+ surfacedIds.add(mem.id);
6208
6327
  }
6209
6328
  budget.append("surface", surfaceLines.join("\n"));
6210
6329
  }
@@ -6272,6 +6391,7 @@ ${distillLines}`
6272
6391
  );
6273
6392
  for (const m of result.memories) {
6274
6393
  if (somaticIds.has(m.memory.id)) continue;
6394
+ if (surfacedIds.has(m.memory.id)) continue;
6275
6395
  if (isRecallNoise(m.memory.content, m.memory.type, m.memory.tags)) continue;
6276
6396
  if (m.memory.tags.includes("pre-compact")) continue;
6277
6397
  if (m.memory.tags.includes("session_narrative")) continue;
@@ -6919,38 +7039,54 @@ function handlePostCompact(stdinJson) {
6919
7039
  if (!recovery) return;
6920
7040
  const budget = new OutputBudget(OUTPUT_BUDGET.POST_COMPACT_MAX_BYTES);
6921
7041
  const lines = [];
6922
- const cog = state.cognitive_state;
6923
- const cogCtx = recovery.working_state?.cognitive_context;
6924
- const hasAnyCognitive = cog?.current_approach || cog?.active_hypothesis || cog?.recent_discovery || cogCtx?.planned_next_step || recovery.continuation_hint;
6925
- if (hasAnyCognitive) {
6926
- const mindLines = ["[Engram] Before compaction, you were:"];
6927
- if (state.active_task) mindLines.push(` Task: ${truncate(state.active_task, 200)}`);
6928
- if (cog?.session_phase) mindLines.push(` Phase: ${cog.session_phase}`);
6929
- if (cog?.current_approach) mindLines.push(` Approach: ${truncate(cog.current_approach, 300)}`);
6930
- if (cog?.active_hypothesis) mindLines.push(` Hypothesis: ${truncate(cog.active_hypothesis, 300)}`);
6931
- if (cog?.recent_discovery) mindLines.push(` Discovery: ${truncate(cog.recent_discovery, 300)}`);
6932
- if (cogCtx?.planned_next_step) mindLines.push(` Next step: ${truncate(cogCtx.planned_next_step, 200)}`);
6933
- if (cog?.search_intent) mindLines.push(` Investigating: ${truncate(cog.search_intent, 200)}`);
6934
- const ruledOut = (state.session_outcomes ?? []).filter((o) => o.includes("\u2192 fail") || o.includes("\u2192 dead end") || o.includes("\u2192 blocked"));
6935
- if (ruledOut.length > 0) {
7042
+ const brief = state.continuation_brief;
7043
+ if (brief) {
7044
+ const mindLines = ["[Engram] Continue from where you left off:"];
7045
+ mindLines.push(` Task: ${brief.task}`);
7046
+ mindLines.push(` Phase: ${brief.phase}`);
7047
+ if (brief.last_actions.length > 0) {
7048
+ mindLines.push(` Last actions:`);
7049
+ for (const a of brief.last_actions.slice(-5)) mindLines.push(` - ${a}`);
7050
+ }
7051
+ if (brief.next_steps.length > 0) {
7052
+ mindLines.push(` Next steps:`);
7053
+ for (const s of brief.next_steps) mindLines.push(` - ${s}`);
7054
+ }
7055
+ if (brief.decisions.length > 0) {
7056
+ mindLines.push(` Decisions made:`);
7057
+ for (const d of brief.decisions) mindLines.push(` - ${d}`);
7058
+ }
7059
+ if (brief.tried_failed.length > 0) {
6936
7060
  mindLines.push(` Already tried (didn't work):`);
6937
- for (const o of ruledOut.slice(-3)) mindLines.push(` - ${truncate(o, 150)}`);
7061
+ for (const t of brief.tried_failed) mindLines.push(` - ${t}`);
6938
7062
  }
6939
- const blockers = recovery.working_state?.active_blockers ?? [];
6940
- if (blockers.length > 0) {
6941
- mindLines.push(` Blockers: ${blockers.slice(0, 3).map((b) => truncate(b, 100)).join("; ")}`);
7063
+ if (brief.blockers.length > 0) {
7064
+ mindLines.push(` Blockers: ${brief.blockers.join("; ")}`);
6942
7065
  }
6943
- if (state.recent_errors.length > 0) {
6944
- mindLines.push(` Recent errors: ${state.recent_errors.slice(-2).map((e) => truncate(e, 100)).join("; ")}`);
6945
- }
6946
- if (state.session_files.length > 0) {
6947
- const files = state.session_files.slice(-8).map((f) => f.split(/[/\\]/).pop() ?? f);
7066
+ if (brief.key_files.length > 0) {
7067
+ const files = brief.key_files.map((f) => f.split(/[/\\]/).pop() ?? f);
6948
7068
  mindLines.push(` Files: ${files.join(", ")}`);
6949
7069
  }
6950
- if (recovery.continuation_hint) {
6951
- mindLines.push(` Continue: ${truncate(recovery.continuation_hint, 300)}`);
6952
- }
6953
7070
  lines.push(mindLines.join("\n"));
7071
+ } else {
7072
+ const cog = state.cognitive_state;
7073
+ const cogCtx = recovery.working_state?.cognitive_context;
7074
+ const hasAnyCognitive = cog?.current_approach || cog?.active_hypothesis || cog?.recent_discovery || cogCtx?.planned_next_step || recovery.continuation_hint;
7075
+ if (hasAnyCognitive) {
7076
+ const mindLines = ["[Engram] Before compaction, you were:"];
7077
+ if (state.active_task) mindLines.push(` Task: ${truncate(state.active_task, 200)}`);
7078
+ if (cog?.session_phase) mindLines.push(` Phase: ${cog.session_phase}`);
7079
+ if (cog?.current_approach) mindLines.push(` Approach: ${truncate(cog.current_approach, 300)}`);
7080
+ if (cog?.active_hypothesis) mindLines.push(` Hypothesis: ${truncate(cog.active_hypothesis, 300)}`);
7081
+ if (cog?.recent_discovery) mindLines.push(` Discovery: ${truncate(cog.recent_discovery, 300)}`);
7082
+ if (cogCtx?.planned_next_step) mindLines.push(` Next step: ${truncate(cogCtx.planned_next_step, 200)}`);
7083
+ if (recovery.continuation_hint) mindLines.push(` Continue: ${truncate(recovery.continuation_hint, 300)}`);
7084
+ if (state.session_files.length > 0) {
7085
+ const files = state.session_files.slice(-8).map((f) => f.split(/[/\\]/).pop() ?? f);
7086
+ mindLines.push(` Files: ${files.join(", ")}`);
7087
+ }
7088
+ lines.push(mindLines.join("\n"));
7089
+ }
6954
7090
  }
6955
7091
  try {
6956
7092
  const transcriptPath = stdinJson?.transcript_path ?? null;
package/dist/index.js CHANGED
@@ -154,7 +154,7 @@ import {
154
154
  vaccinate,
155
155
  vacuumDatabase,
156
156
  validateMultiPerspective
157
- } from "./chunk-V5TTXT4V.js";
157
+ } from "./chunk-OY2XHPUF.js";
158
158
 
159
159
  // src/index.ts
160
160
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vedtechsolutions/engram-mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Cognitive memory system for AI — persistent, cross-session learning via MCP",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",