memorix 0.9.13 → 0.9.15

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/CHANGELOG.md CHANGED
@@ -2,10 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.9.13] — 2026-02-26
5
+ ## [0.9.15] — 2026-02-26
6
6
 
7
7
  ### Fixed
8
- - **🔴 Critical: Hooks never auto-store during development** `extractContent()` had a fatal `parts.length === 0` guard that skipped rich `toolInput` data (file content, edit diffs, commands) whenever `toolResult` was present. Since Claude Code/Copilot/Cursor always send a short `toolResult` like `"File written successfully"` (28 chars), the extracted content was always < 100 chars and got rejected by the `MIN_STORE_LENGTH` threshold. Now always extracts `toolInput` fields (file_path, content, old_string/new_string, command, query) alongside `toolResult`, ensuring tool events produce enough content for pattern detection and auto-storage.
8
+ - **Feedback visibility** Hook auto-stores were silent. Now returns `systemMessage` to the agent after each save, e.g. `🟢 Memorix saved: Updated auth.ts [what-changed]`. Gives Codex-like visibility into what memorix is recording.
9
+ - **File-modifying tools always store** — Write/Edit/MultiEdit tool events were rejected when content lacked pattern keywords (e.g., writing utility functions with no "error"/"fix" keywords). Now file-modifying tools always store if content > 100 chars, classified as `what-changed` by default.
10
+ - **PreCompact low-quality spam** — PreCompact events stored empty/minimal observations with no meaningful content. Now requires `MIN_STORE_LENGTH` (100 chars) to store.
11
+ - **Normalizer prompt extraction** — `normalizeClaude` only extracted `prompt` for `user_prompt` events. Now extracts for all events (PreCompact, etc.), preserving context that would otherwise be lost.
12
+
13
+ ## [0.9.14] — 2026-02-26
14
+
15
+ ### Fixed
16
+ - **🔴 Critical: Hooks never auto-store during development** — Two root causes:
17
+ 1. `extractContent()` had a fatal `parts.length === 0` guard that skipped rich `toolInput` data (file content, edit diffs, commands) whenever `toolResult` was present. Since all agents send short `toolResult` like `"File written successfully"` (28 chars), the content was always < 100 chars and got rejected by `MIN_STORE_LENGTH`.
18
+ 2. Bash/shell tool events (npm install, npm test, git commands) also got rejected because their content (~90 chars) fell below the generic `post_tool` threshold of 200 chars, even though commands are inherently meaningful.
19
+ - **Fix**: Always extract `toolInput` fields alongside `toolResult`. Bash tools now use a dedicated low-threshold path (50 chars) with noise command filtering, matching the `post_command` logic.
20
+
21
+ ### Added
22
+ - **12 Claude Code E2E tests** — Validates the full hook pipeline (stdin JSON → normalize → handleHookEvent → observation) for Write, Edit, Bash, UserPromptSubmit, SessionStart, Stop, PreCompact, and edge cases (noise filtering, memorix recursion skip, short prompts).
9
23
 
10
24
  ## [0.9.12] — 2026-02-25
11
25
 
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://www.npmjs.com/package/memorix"><img src="https://img.shields.io/npm/dm/memorix.svg?style=flat-square&color=blue" alt="npm downloads"></a>
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green.svg?style=flat-square" alt="License"></a>
10
10
  <a href="https://github.com/AVIDS2/memorix"><img src="https://img.shields.io/github/stars/AVIDS2/memorix?style=flat-square&color=yellow" alt="GitHub stars"></a>
11
- <img src="https://img.shields.io/badge/tests-491%20passed-brightgreen?style=flat-square" alt="Tests">
11
+ <img src="https://img.shields.io/badge/tests-505%20passed-brightgreen?style=flat-square" alt="Tests">
12
12
  </p>
13
13
  <p align="center">
14
14
  <img src="https://img.shields.io/badge/Works%20with-Cursor-orange?style=flat-square" alt="Cursor">
package/README.zh-CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://www.npmjs.com/package/memorix"><img src="https://img.shields.io/npm/dm/memorix.svg?style=flat-square&color=blue" alt="npm downloads"></a>
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green.svg?style=flat-square" alt="License"></a>
10
10
  <a href="https://github.com/AVIDS2/memorix"><img src="https://img.shields.io/github/stars/AVIDS2/memorix?style=flat-square&color=yellow" alt="GitHub stars"></a>
11
- <img src="https://img.shields.io/badge/tests-491%20passed-brightgreen?style=flat-square" alt="Tests">
11
+ <img src="https://img.shields.io/badge/tests-505%20passed-brightgreen?style=flat-square" alt="Tests">
12
12
  </p>
13
13
  <p align="center">
14
14
  <img src="https://img.shields.io/badge/Works%20with-Cursor-orange?style=flat-square" alt="Cursor">
package/dist/cli/index.js CHANGED
@@ -43369,8 +43369,8 @@ function normalizeClaude(payload, event) {
43369
43369
  result.filePath = toolInput?.file_path ?? toolInput?.filePath;
43370
43370
  }
43371
43371
  }
43372
- if (event === "user_prompt") {
43373
- result.userPrompt = payload.prompt ?? "";
43372
+ if (payload.prompt) {
43373
+ result.userPrompt = payload.prompt;
43374
43374
  }
43375
43375
  return result;
43376
43376
  }
@@ -43747,6 +43747,7 @@ var init_pattern_detector = __esm({
43747
43747
  var handler_exports = {};
43748
43748
  __export(handler_exports, {
43749
43749
  handleHookEvent: () => handleHookEvent,
43750
+ resetCooldowns: () => resetCooldowns,
43750
43751
  runHook: () => runHook
43751
43752
  });
43752
43753
  function isInCooldown(eventKey) {
@@ -43757,6 +43758,9 @@ function isInCooldown(eventKey) {
43757
43758
  function markTriggered(eventKey) {
43758
43759
  cooldowns.set(eventKey, Date.now());
43759
43760
  }
43761
+ function resetCooldowns() {
43762
+ cooldowns.clear();
43763
+ }
43760
43764
  function extractContent(input) {
43761
43765
  const parts = [];
43762
43766
  if (input.userPrompt) parts.push(input.userPrompt);
@@ -43822,7 +43826,8 @@ function generateTitle(input, patternType) {
43822
43826
  }
43823
43827
  function buildObservation(input, content) {
43824
43828
  const pattern = detectBestPattern(content);
43825
- const obsType = pattern ? patternToObservationType(pattern.type) : "discovery";
43829
+ const fallbackType = input.filePath ? "what-changed" : "discovery";
43830
+ const obsType = pattern ? patternToObservationType(pattern.type) : fallbackType;
43826
43831
  return {
43827
43832
  entityName: deriveEntityName(input),
43828
43833
  type: obsType,
@@ -43922,11 +43927,16 @@ ${lines.join("\n")}`;
43922
43927
  }
43923
43928
  };
43924
43929
  }
43925
- case "pre_compact":
43930
+ case "pre_compact": {
43931
+ const compactContent = extractContent(input);
43932
+ if (compactContent.length < MIN_STORE_LENGTH) {
43933
+ return { observation: null, output: defaultOutput };
43934
+ }
43926
43935
  return {
43927
- observation: buildObservation(input, extractContent(input)),
43936
+ observation: buildObservation(input, compactContent),
43928
43937
  output: defaultOutput
43929
43938
  };
43939
+ }
43930
43940
  case "session_end":
43931
43941
  return {
43932
43942
  observation: buildObservation(input, extractContent(input)),
@@ -43976,9 +43986,32 @@ ${lines.join("\n")}`;
43976
43986
  return { observation: null, output: defaultOutput };
43977
43987
  }
43978
43988
  const toolContent = extractContent(input);
43989
+ if (input.command) {
43990
+ if (NOISE_COMMANDS.some((r4) => r4.test(input.command))) {
43991
+ return { observation: null, output: defaultOutput };
43992
+ }
43993
+ if (toolContent.length < 50) {
43994
+ return { observation: null, output: defaultOutput };
43995
+ }
43996
+ markTriggered(toolKey);
43997
+ return {
43998
+ observation: buildObservation(input, toolContent),
43999
+ output: defaultOutput
44000
+ };
44001
+ }
43979
44002
  if (toolContent.length < MIN_STORE_LENGTH) {
43980
44003
  return { observation: null, output: defaultOutput };
43981
44004
  }
44005
+ const isFileModifyingTool = /^(write|edit|multi_?edit|multiedittool|create|patch|insert)/i.test(
44006
+ input.toolName ?? ""
44007
+ );
44008
+ if (isFileModifyingTool) {
44009
+ markTriggered(toolKey);
44010
+ return {
44011
+ observation: buildObservation(input, toolContent),
44012
+ output: defaultOutput
44013
+ };
44014
+ }
43982
44015
  const toolPattern = detectBestPattern(toolContent);
43983
44016
  if (!toolPattern && toolContent.length < 200) {
43984
44017
  return { observation: null, output: defaultOutput };
@@ -44039,6 +44072,20 @@ async function runHook() {
44039
44072
  const dataDir = await getProjectDataDir2(project.id);
44040
44073
  await initObservations2(dataDir);
44041
44074
  await storeObservation2({ ...observation, projectId: project.id });
44075
+ const TYPE_EMOJI = {
44076
+ "gotcha": "\u{1F534}",
44077
+ "decision": "\u{1F7E4}",
44078
+ "problem-solution": "\u{1F7E1}",
44079
+ "trade-off": "\u2696\uFE0F",
44080
+ "discovery": "\u{1F7E3}",
44081
+ "how-it-works": "\u{1F535}",
44082
+ "what-changed": "\u{1F7E2}",
44083
+ "why-it-exists": "\u{1F7E0}",
44084
+ "session-request": "\u{1F3AF}"
44085
+ };
44086
+ const emoji2 = TYPE_EMOJI[observation.type] ?? "\u{1F4DD}";
44087
+ output.systemMessage = (output.systemMessage ?? "") + `
44088
+ ${emoji2} Memorix saved: ${observation.title} [${observation.type}]`;
44042
44089
  } catch {
44043
44090
  }
44044
44091
  }