memorix 0.9.14 → 0.9.16

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,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.9.16] — 2026-02-26
6
+
7
+ ### Architecture
8
+ - **Classify → Policy → Store pipeline** — Replaced the monolithic `switch/case` handler (527 lines) with a clean declarative pipeline (432 lines). Inspired by claude-mem's store-first philosophy and mcp-memory-service's configurable scoring.
9
+ - **Tool Taxonomy** — `classifyTool()` categorizes tools into `file_modify | file_read | command | search | memorix_internal | unknown`. Each category has a declarative `StoragePolicy` (store mode, minLength, defaultType).
10
+ - **Pattern detection = classification only** — Pattern detection now only determines observation *type* (decision, error, etc.), not whether to store. Storage decisions are made by policy.
11
+ - **Unified `TYPE_EMOJI`** — Single exported constant, eliminating 3 duplicated copies across handler and session_start.
12
+
13
+ ### Fixed
14
+ - **🔴 Critical: Bash commands with `cd` prefix silently dropped** — Claude Code sends Bash commands as `cd /project && npm test 2>&1`. The noise filter `/^cd\b/` matched the `cd` prefix and silently discarded the entire command. This caused `npm test`, `npm install express`, `node -e "..."`, and all other project-scoped commands to never be stored. Fix: `extractRealCommand()` strips `cd path && ` prefix before noise checking, so `cd /path && npm test` is correctly evaluated as `npm test`.
15
+ - **Cooldown key too broad** — Old key `post_tool:Bash` meant ALL Bash commands shared one 30-second cooldown. New key uses `event:filePath|command|toolName`, so `npm test` and `npm install` have independent cooldowns.
16
+ - **Store-first for commands** — Command-category tools now use `store: 'always'` policy with minLength 30 (down from 50-200), capturing more meaningful development activity.
17
+
18
+ ## [0.9.15] — 2026-02-26
19
+
20
+ ### Fixed
21
+ - **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.
22
+ - **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.
23
+ - **PreCompact low-quality spam** — PreCompact events stored empty/minimal observations with no meaningful content. Now requires `MIN_STORE_LENGTH` (100 chars) to store.
24
+ - **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.
25
+
5
26
  ## [0.9.14] — 2026-02-26
6
27
 
7
28
  ### Fixed
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-503%20passed-brightgreen?style=flat-square" alt="Tests">
11
+ <img src="https://img.shields.io/badge/tests-507%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-503%20passed-brightgreen?style=flat-square" alt="Tests">
11
+ <img src="https://img.shields.io/badge/tests-507%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
@@ -39920,7 +39920,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
39920
39920
  lines.push("");
39921
39921
  }
39922
39922
  const PRIORITY_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "problem-solution", "trade-off", "discovery"]);
39923
- const TYPE_EMOJI = {
39923
+ const TYPE_EMOJI2 = {
39924
39924
  "gotcha": "\u{1F534}",
39925
39925
  "decision": "\u{1F7E4}",
39926
39926
  "problem-solution": "\u{1F7E1}",
@@ -39935,7 +39935,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
39935
39935
  if (priorityObs.length > 0) {
39936
39936
  lines.push(`## Key Memories`);
39937
39937
  for (const obs of priorityObs) {
39938
- const emoji2 = TYPE_EMOJI[obs.type] ?? "\u{1F4CC}";
39938
+ const emoji2 = TYPE_EMOJI2[obs.type] ?? "\u{1F4CC}";
39939
39939
  const fact = obs.facts?.[0] ? ` \u2014 ${obs.facts[0]}` : "";
39940
39940
  lines.push(`${emoji2} ${obs.title}${fact}`);
39941
39941
  }
@@ -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
  }
@@ -43746,10 +43746,37 @@ var init_pattern_detector = __esm({
43746
43746
  // src/hooks/handler.ts
43747
43747
  var handler_exports = {};
43748
43748
  __export(handler_exports, {
43749
+ TYPE_EMOJI: () => TYPE_EMOJI,
43749
43750
  handleHookEvent: () => handleHookEvent,
43750
43751
  resetCooldowns: () => resetCooldowns,
43751
43752
  runHook: () => runHook
43752
43753
  });
43754
+ function classifyTool(input) {
43755
+ if (input.event === "post_edit") return "file_modify";
43756
+ if (input.event === "post_command") return "command";
43757
+ const name = (input.toolName ?? "").toLowerCase();
43758
+ if (name.startsWith("memorix_")) return "memorix_internal";
43759
+ if (/^(write|edit|multi_?edit|multiedittool|create|patch|insert|notebook_?edit)$/i.test(name)) {
43760
+ return "file_modify";
43761
+ }
43762
+ if (/^(read|read_?file|view|list_?dir)$/i.test(name)) {
43763
+ return "file_read";
43764
+ }
43765
+ if (/^(bash|shell|terminal|command|run)$/i.test(name) || input.command) {
43766
+ return "command";
43767
+ }
43768
+ if (/^(search|grep|ripgrep|find_?by_?name|glob)$/i.test(name)) {
43769
+ return "search";
43770
+ }
43771
+ return "unknown";
43772
+ }
43773
+ function extractRealCommand(command) {
43774
+ return command.replace(/^cd\s+\S+\s*&&\s*/i, "").trim();
43775
+ }
43776
+ function isNoiseCommand(command) {
43777
+ const real = extractRealCommand(command);
43778
+ return NOISE_COMMANDS.some((r4) => r4.test(real));
43779
+ }
43753
43780
  function isInCooldown(eventKey) {
43754
43781
  const last = cooldowns.get(eventKey);
43755
43782
  if (!last) return false;
@@ -43766,7 +43793,7 @@ function extractContent(input) {
43766
43793
  if (input.userPrompt) parts.push(input.userPrompt);
43767
43794
  if (input.aiResponse) parts.push(input.aiResponse);
43768
43795
  if (input.commandOutput) parts.push(input.commandOutput);
43769
- if (input.command) parts.push(`Command: ${input.command}`);
43796
+ if (input.command) parts.push(`Command: ${extractRealCommand(input.command)}`);
43770
43797
  if (input.filePath) parts.push(`File: ${input.filePath}`);
43771
43798
  if (input.edits) {
43772
43799
  for (const edit of input.edits) {
@@ -43782,8 +43809,7 @@ function extractContent(input) {
43782
43809
  parts.push(`File: ${input.toolInput.file_path}`);
43783
43810
  }
43784
43811
  if (input.toolInput.content) {
43785
- const content = input.toolInput.content;
43786
- parts.push(content.slice(0, 1e3));
43812
+ parts.push(input.toolInput.content.slice(0, 1e3));
43787
43813
  }
43788
43814
  if (input.toolInput.old_string || input.toolInput.new_string) {
43789
43815
  const oldStr = input.toolInput.old_string ?? "";
@@ -43804,7 +43830,7 @@ function deriveEntityName(input) {
43804
43830
  }
43805
43831
  if (input.toolName) return input.toolName;
43806
43832
  if (input.command) {
43807
- const firstWord = input.command.split(/\s+/)[0];
43833
+ const firstWord = extractRealCommand(input.command).split(/\s+/)[0];
43808
43834
  return firstWord.replace(/[^a-zA-Z0-9-_]/g, "");
43809
43835
  }
43810
43836
  return "session";
@@ -43817,16 +43843,18 @@ function generateTitle(input, patternType) {
43817
43843
  return `${verb} ${filename}`.slice(0, maxLen);
43818
43844
  }
43819
43845
  if (input.command) {
43820
- return `Ran: ${input.command}`.slice(0, maxLen);
43846
+ return `Ran: ${extractRealCommand(input.command)}`.slice(0, maxLen);
43821
43847
  }
43822
43848
  if (input.userPrompt) {
43823
43849
  return input.userPrompt.slice(0, maxLen);
43824
43850
  }
43825
43851
  return `Session activity (${patternType})`;
43826
43852
  }
43827
- function buildObservation(input, content) {
43853
+ function buildObservation(input, content, category) {
43828
43854
  const pattern = detectBestPattern(content);
43829
- const obsType = pattern ? patternToObservationType(pattern.type) : "discovery";
43855
+ const policy = STORAGE_POLICY[category] ?? STORAGE_POLICY.unknown;
43856
+ const fallbackType = input.filePath ? "what-changed" : policy.defaultType;
43857
+ const obsType = pattern ? patternToObservationType(pattern.type) : fallbackType;
43830
43858
  return {
43831
43859
  entityName: deriveEntityName(input),
43832
43860
  type: obsType,
@@ -43836,197 +43864,111 @@ function buildObservation(input, content) {
43836
43864
  `Agent: ${input.agent}`,
43837
43865
  `Session: ${input.sessionId}`,
43838
43866
  ...input.filePath ? [`File: ${input.filePath}`] : [],
43839
- ...input.command ? [`Command: ${input.command}`] : []
43867
+ ...input.command ? [`Command: ${extractRealCommand(input.command)}`] : []
43840
43868
  ],
43841
43869
  concepts: pattern?.matchedKeywords ?? [],
43842
43870
  filesModified: input.filePath ? [input.filePath] : []
43843
43871
  };
43844
43872
  }
43845
- async function handleHookEvent(input) {
43846
- const defaultOutput = { continue: true };
43847
- if (input.toolName === "memorix_store" || input.toolName === "memorix_search") {
43848
- return { observation: null, output: defaultOutput };
43849
- }
43850
- switch (input.event) {
43851
- case "session_start": {
43852
- let contextSummary = "";
43853
- try {
43854
- const { detectProject: detectProject2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
43855
- const { getProjectDataDir: getProjectDataDir2, loadObservationsJson: loadObservationsJson2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
43856
- const project = await detectProject2(input.cwd || process.cwd());
43857
- const dataDir = await getProjectDataDir2(project.id);
43858
- const allObs = await loadObservationsJson2(dataDir);
43859
- if (allObs.length > 0) {
43860
- const PRIORITY_ORDER = {
43861
- "gotcha": 6,
43862
- "decision": 5,
43863
- "problem-solution": 4,
43864
- "trade-off": 3,
43865
- "discovery": 2,
43866
- "how-it-works": 1
43867
- };
43868
- const LOW_QUALITY_PATTERNS2 = [
43869
- /^Session activity/i,
43870
- /^Updated \S+\.\w+$/i,
43871
- // "Updated foo.ts" too generic
43872
- /^Created \S+\.\w+$/i,
43873
- // "Created bar.js"
43874
- /^Deleted \S+\.\w+$/i,
43875
- /^Modified \S+\.\w+$/i
43876
- ];
43877
- const isLowQuality2 = (title) => LOW_QUALITY_PATTERNS2.some((p2) => p2.test(title));
43878
- const scored = allObs.map((obs, i2) => {
43879
- const title = obs.title ?? "";
43880
- const hasFacts = (obs.facts?.length ?? 0) > 0;
43881
- const hasSubstance = title.length > 20 || hasFacts;
43882
- const quality = isLowQuality2(title) ? 0.1 : hasSubstance ? 1 : 0.5;
43883
- return {
43884
- obs,
43885
- priority: PRIORITY_ORDER[obs.type ?? ""] ?? 0,
43886
- quality,
43887
- recency: i2
43888
- // higher index = more recent
43889
- };
43890
- }).sort((a3, b3) => {
43891
- const scoreA = a3.priority * a3.quality;
43892
- const scoreB = b3.priority * b3.quality;
43893
- if (scoreB !== scoreA) return scoreB - scoreA;
43894
- return b3.recency - a3.recency;
43895
- });
43896
- const top = scored.slice(0, 5);
43897
- const TYPE_EMOJI = {
43898
- "gotcha": "\u{1F534}",
43899
- "decision": "\u{1F7E4}",
43900
- "problem-solution": "\u{1F7E1}",
43901
- "trade-off": "\u2696\uFE0F",
43902
- "discovery": "\u{1F7E3}",
43903
- "how-it-works": "\u{1F535}",
43904
- "what-changed": "\u{1F7E2}",
43905
- "why-it-exists": "\u{1F7E0}",
43906
- "session-request": "\u{1F3AF}"
43907
- };
43908
- const lines = top.map(({ obs }) => {
43909
- const emoji2 = TYPE_EMOJI[obs.type ?? ""] ?? "\u{1F4CC}";
43910
- const title = obs.title ?? "(untitled)";
43911
- const fact = obs.facts?.[0] ? ` \u2014 ${obs.facts[0]}` : "";
43912
- return `${emoji2} ${title}${fact}`;
43913
- });
43914
- contextSummary = `
43873
+ async function handleSessionStart(input) {
43874
+ let contextSummary = "";
43875
+ try {
43876
+ const { detectProject: detectProject2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
43877
+ const { getProjectDataDir: getProjectDataDir2, loadObservationsJson: loadObservationsJson2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
43878
+ const project = await detectProject2(input.cwd || process.cwd());
43879
+ const dataDir = await getProjectDataDir2(project.id);
43880
+ const allObs = await loadObservationsJson2(dataDir);
43881
+ if (allObs.length > 0) {
43882
+ const PRIORITY_ORDER = {
43883
+ "gotcha": 6,
43884
+ "decision": 5,
43885
+ "problem-solution": 4,
43886
+ "trade-off": 3,
43887
+ "discovery": 2,
43888
+ "how-it-works": 1
43889
+ };
43890
+ const LOW_QUALITY_PATTERNS2 = [
43891
+ /^Session activity/i,
43892
+ /^Updated \S+\.\w+$/i,
43893
+ /^Created \S+\.\w+$/i,
43894
+ /^Deleted \S+\.\w+$/i,
43895
+ /^Modified \S+\.\w+$/i
43896
+ ];
43897
+ const isLowQuality2 = (title) => LOW_QUALITY_PATTERNS2.some((p2) => p2.test(title));
43898
+ const scored = allObs.map((obs, i2) => {
43899
+ const title = obs.title ?? "";
43900
+ const hasFacts = (obs.facts?.length ?? 0) > 0;
43901
+ const hasSubstance = title.length > 20 || hasFacts;
43902
+ const quality = isLowQuality2(title) ? 0.1 : hasSubstance ? 1 : 0.5;
43903
+ return { obs, priority: PRIORITY_ORDER[obs.type ?? ""] ?? 0, quality, recency: i2 };
43904
+ }).sort((a3, b3) => {
43905
+ const scoreA = a3.priority * a3.quality;
43906
+ const scoreB = b3.priority * b3.quality;
43907
+ if (scoreB !== scoreA) return scoreB - scoreA;
43908
+ return b3.recency - a3.recency;
43909
+ });
43910
+ const top = scored.slice(0, 5);
43911
+ const lines = top.map(({ obs }) => {
43912
+ const emoji2 = TYPE_EMOJI[obs.type ?? ""] ?? "\u{1F4CC}";
43913
+ const title = obs.title ?? "(untitled)";
43914
+ const fact = obs.facts?.[0] ? ` \u2014 ${obs.facts[0]}` : "";
43915
+ return `${emoji2} ${title}${fact}`;
43916
+ });
43917
+ contextSummary = `
43915
43918
 
43916
43919
  Recent project memories (${project.name}):
43917
43920
  ${lines.join("\n")}`;
43918
- }
43919
- } catch {
43920
- }
43921
- return {
43922
- observation: null,
43923
- output: {
43924
- continue: true,
43925
- systemMessage: `Memorix is active. Your memories from previous sessions are available via memorix_search.${contextSummary}`
43926
- }
43927
- };
43928
43921
  }
43929
- case "pre_compact":
43930
- return {
43931
- observation: buildObservation(input, extractContent(input)),
43932
- output: defaultOutput
43933
- };
43934
- case "session_end":
43935
- return {
43936
- observation: buildObservation(input, extractContent(input)),
43937
- output: defaultOutput
43938
- };
43939
- case "post_edit": {
43940
- const editKey = `post_edit:${input.filePath ?? "general"}`;
43941
- if (isInCooldown(editKey)) {
43942
- return { observation: null, output: defaultOutput };
43943
- }
43944
- const editContent = extractContent(input);
43945
- if (editContent.length < MIN_EDIT_LENGTH) {
43946
- return { observation: null, output: defaultOutput };
43947
- }
43948
- const editPattern = detectBestPattern(editContent, 0.6);
43949
- if (!editPattern) {
43950
- return { observation: null, output: defaultOutput };
43951
- }
43952
- markTriggered(editKey);
43953
- return {
43954
- observation: buildObservation(input, editContent),
43955
- output: defaultOutput
43956
- };
43957
- }
43958
- case "post_command": {
43959
- if (input.command && NOISE_COMMANDS.some((r4) => r4.test(input.command))) {
43960
- return { observation: null, output: defaultOutput };
43961
- }
43962
- const cmdKey = `post_command:${input.command ?? "general"}`;
43963
- if (isInCooldown(cmdKey)) {
43964
- return { observation: null, output: defaultOutput };
43965
- }
43966
- const cmdContent = input.commandOutput || extractContent(input);
43967
- if (cmdContent.length < MIN_STORE_LENGTH) {
43968
- return { observation: null, output: defaultOutput };
43969
- }
43970
- detectBestPattern(cmdContent);
43971
- markTriggered(cmdKey);
43972
- return {
43973
- observation: buildObservation(input, cmdContent),
43974
- output: defaultOutput
43975
- };
43976
- }
43977
- case "post_tool": {
43978
- const toolKey = `post_tool:${input.toolName ?? "general"}`;
43979
- if (isInCooldown(toolKey)) {
43980
- return { observation: null, output: defaultOutput };
43981
- }
43982
- const toolContent = extractContent(input);
43983
- if (input.command) {
43984
- if (NOISE_COMMANDS.some((r4) => r4.test(input.command))) {
43985
- return { observation: null, output: defaultOutput };
43986
- }
43987
- if (toolContent.length < 50) {
43988
- return { observation: null, output: defaultOutput };
43989
- }
43990
- markTriggered(toolKey);
43991
- return {
43992
- observation: buildObservation(input, toolContent),
43993
- output: defaultOutput
43994
- };
43995
- }
43996
- if (toolContent.length < MIN_STORE_LENGTH) {
43997
- return { observation: null, output: defaultOutput };
43998
- }
43999
- const toolPattern = detectBestPattern(toolContent);
44000
- if (!toolPattern && toolContent.length < 200) {
44001
- return { observation: null, output: defaultOutput };
44002
- }
44003
- markTriggered(toolKey);
44004
- return {
44005
- observation: buildObservation(input, toolContent),
44006
- output: defaultOutput
44007
- };
44008
- }
44009
- case "post_response":
44010
- case "user_prompt": {
44011
- const promptKey = `${input.event}:${input.sessionId ?? "general"}`;
44012
- if (isInCooldown(promptKey)) {
44013
- return { observation: null, output: defaultOutput };
44014
- }
44015
- const content = extractContent(input);
44016
- const minLen = input.event === "user_prompt" ? MIN_PROMPT_LENGTH : MIN_STORE_LENGTH;
44017
- if (content.length < minLen) {
44018
- return { observation: null, output: defaultOutput };
44019
- }
44020
- detectBestPattern(content);
44021
- markTriggered(promptKey);
44022
- return {
44023
- observation: buildObservation(input, content),
44024
- output: defaultOutput
44025
- };
43922
+ } catch {
43923
+ }
43924
+ return {
43925
+ observation: null,
43926
+ output: {
43927
+ continue: true,
43928
+ systemMessage: `Memorix is active. Your memories from previous sessions are available via memorix_search.${contextSummary}`
44026
43929
  }
44027
- default:
43930
+ };
43931
+ }
43932
+ async function handleHookEvent(input) {
43933
+ const defaultOutput = { continue: true };
43934
+ if (input.event === "session_start") {
43935
+ return handleSessionStart(input);
43936
+ }
43937
+ if (input.event === "session_end") {
43938
+ return {
43939
+ observation: buildObservation(input, extractContent(input), "unknown"),
43940
+ output: defaultOutput
43941
+ };
43942
+ }
43943
+ const category = classifyTool(input);
43944
+ const policy = STORAGE_POLICY[category] ?? STORAGE_POLICY.unknown;
43945
+ const content = extractContent(input);
43946
+ if (policy.store === "never") {
43947
+ return { observation: null, output: defaultOutput };
43948
+ }
43949
+ if (category === "command" && input.command && isNoiseCommand(input.command)) {
43950
+ return { observation: null, output: defaultOutput };
43951
+ }
43952
+ const minLen = input.event === "user_prompt" ? MIN_PROMPT_LENGTH : policy.minLength;
43953
+ if (content.length < minLen) {
43954
+ return { observation: null, output: defaultOutput };
43955
+ }
43956
+ const effectiveStore = input.event === "user_prompt" || input.event === "post_response" ? "always" : policy.store;
43957
+ if (effectiveStore === "if_substantial") {
43958
+ const pattern = detectBestPattern(content);
43959
+ if (!pattern && content.length < 200) {
44028
43960
  return { observation: null, output: defaultOutput };
43961
+ }
44029
43962
  }
43963
+ const cooldownKey = `${input.event}:${input.filePath ?? input.command ?? input.toolName ?? "general"}`;
43964
+ if (isInCooldown(cooldownKey)) {
43965
+ return { observation: null, output: defaultOutput };
43966
+ }
43967
+ markTriggered(cooldownKey);
43968
+ return {
43969
+ observation: buildObservation(input, content, category),
43970
+ output: defaultOutput
43971
+ };
44030
43972
  }
44031
43973
  async function runHook() {
44032
43974
  const chunks = [];
@@ -44056,31 +43998,49 @@ async function runHook() {
44056
43998
  const dataDir = await getProjectDataDir2(project.id);
44057
43999
  await initObservations2(dataDir);
44058
44000
  await storeObservation2({ ...observation, projectId: project.id });
44001
+ const emoji2 = TYPE_EMOJI[observation.type] ?? "\u{1F4DD}";
44002
+ output.systemMessage = (output.systemMessage ?? "") + `
44003
+ ${emoji2} Memorix saved: ${observation.title} [${observation.type}]`;
44059
44004
  } catch {
44060
44005
  }
44061
44006
  }
44062
44007
  process.stdout.write(JSON.stringify(output));
44063
44008
  }
44064
- var cooldowns, COOLDOWN_MS, MIN_STORE_LENGTH, MIN_PROMPT_LENGTH, MIN_EDIT_LENGTH, NOISE_COMMANDS, MAX_CONTENT_LENGTH;
44009
+ var TYPE_EMOJI, cooldowns, COOLDOWN_MS, MIN_PROMPT_LENGTH, MAX_CONTENT_LENGTH, NOISE_COMMANDS, STORAGE_POLICY;
44065
44010
  var init_handler = __esm({
44066
44011
  "src/hooks/handler.ts"() {
44067
44012
  "use strict";
44068
44013
  init_esm_shims();
44069
44014
  init_normalizer();
44070
44015
  init_pattern_detector();
44016
+ TYPE_EMOJI = {
44017
+ "gotcha": "\u{1F534}",
44018
+ "decision": "\u{1F7E4}",
44019
+ "problem-solution": "\u{1F7E1}",
44020
+ "trade-off": "\u2696\uFE0F",
44021
+ "discovery": "\u{1F7E3}",
44022
+ "how-it-works": "\u{1F535}",
44023
+ "what-changed": "\u{1F7E2}",
44024
+ "why-it-exists": "\u{1F7E0}",
44025
+ "session-request": "\u{1F3AF}"
44026
+ };
44071
44027
  cooldowns = /* @__PURE__ */ new Map();
44072
44028
  COOLDOWN_MS = 3e4;
44073
- MIN_STORE_LENGTH = 100;
44074
44029
  MIN_PROMPT_LENGTH = 20;
44075
- MIN_EDIT_LENGTH = 30;
44030
+ MAX_CONTENT_LENGTH = 4e3;
44076
44031
  NOISE_COMMANDS = [
44077
- /^(ls|dir|cd|pwd|echo|cat|type|head|tail|wc|find|which|where|whoami)\b/i,
44078
- /^(Get-Content|Test-Path|Get-Item|Get-ChildItem|Set-Location|Write-Host)\b/i,
44079
- /^(Start-Sleep|Select-String|Select-Object|Format-Table|Measure-Object)\b/i,
44080
- /^(mkdir|rm|cp|mv|touch|chmod|chown)\b/i,
44081
- /^(node -[ep]|python -c)\b/i
44032
+ /^(ls|dir|cd|pwd|echo|cat|type|head|tail|wc|which|where|whoami)(\s|$)/i,
44033
+ /^(Get-Content|Test-Path|Get-Item|Get-ChildItem|Set-Location|Write-Host)(\s|$)/i,
44034
+ /^(Start-Sleep|Select-String|Select-Object|Format-Table|Measure-Object)(\s|$)/i
44082
44035
  ];
44083
- MAX_CONTENT_LENGTH = 4e3;
44036
+ STORAGE_POLICY = {
44037
+ file_modify: { store: "always", minLength: 50, defaultType: "what-changed" },
44038
+ command: { store: "always", minLength: 30, defaultType: "discovery" },
44039
+ file_read: { store: "if_substantial", minLength: 200, defaultType: "discovery" },
44040
+ search: { store: "if_substantial", minLength: 200, defaultType: "discovery" },
44041
+ memorix_internal: { store: "never", minLength: 0, defaultType: "discovery" },
44042
+ unknown: { store: "if_substantial", minLength: 100, defaultType: "discovery" }
44043
+ };
44084
44044
  }
44085
44045
  });
44086
44046