claude-recall 0.19.0 → 0.20.1

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.
@@ -8,40 +8,16 @@ source: claude-recall
8
8
 
9
9
  # Corrections
10
10
 
11
- Auto-generated from 30 memories. Last updated: 2026-04-02.
11
+ Auto-generated from 6 memories. Last updated: 2026-04-02.
12
12
 
13
13
  ## Rules
14
14
 
15
- - CORRECTION: Memory with complex metadata
16
- - CORRECTION: Memory with complex metadata
17
- - CORRECTION: Memory with complex metadata
18
- - CORRECTION: Memory with complex metadata
19
- - CORRECTION: Memory with complex metadata
20
- - CORRECTION: Memory with complex metadata
21
- - CORRECTION: Memory with complex metadata
22
- - CORRECTION: Memory with complex metadata
23
- - CORRECTION: Memory with complex metadata
24
- - CORRECTION: Memory with complex metadata
25
- - CORRECTION: Memory with complex metadata
26
- - CORRECTION: Memory with complex metadata
27
- - CORRECTION: Memory with complex metadata
28
- - CORRECTION: Memory with complex metadata
29
- - CORRECTION: Memory with complex metadata
30
- - CORRECTION: Memory with complex metadata
31
- - CORRECTION: Memory with complex metadata
32
15
  - CORRECTION: Memory with complex metadata
33
16
  - CORRECTION: Memory with complex metadata
34
17
  - CORRECTION: Memory with complex metadata
35
18
  - CORRECTION: Memory with complex metadata
36
19
  - CORRECTION: License copyright should include user's name instead of 'Claude Recall Contributors'
37
- - CORRECTION: Replace expired access token with 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
38
- - CORRECTION: use spaces not tabs for indentation
39
20
  - CORRECTION: License copyright should list your name instead of 'Claude Recall Contributors'
40
- - CORRECTION: cited (loaded 5+ times): 19
41
- - CORRECTION: cited (loaded 5+ times): 19
42
- - CORRECTION: cited (loaded 5+ times): 19
43
- - CORRECTION: cited (loaded 5+ times): 19
44
- - CORRECTION: cited (loaded 5+ times): 19
45
21
 
46
22
  ---
47
23
  *Auto-generated by Claude Recall. Regenerate: `npx claude-recall skills generate`*
@@ -1,38 +1,14 @@
1
1
  {
2
2
  "topicId": "corrections",
3
- "sourceHash": "e522ab0f108ce0a51b1dab005a2db51518edfdc431927275ba0c623a033d440e",
4
- "memoryCount": 30,
5
- "generatedAt": "2026-04-02T18:06:30.565Z",
3
+ "sourceHash": "b1b937b59846b125f1209888415c3c3ba1eb668bee822d20b08fd6a70783510f",
4
+ "memoryCount": 6,
5
+ "generatedAt": "2026-04-02T20:13:01.816Z",
6
6
  "memoryKeys": [
7
- "memory_1775153190549_s9iwgl8lp",
8
- "memory_1775152793092_g9wy80yom",
9
- "memory_1775152560585_6yo07v2f4",
10
- "memory_1775152548944_95j0vkg8u",
11
- "memory_1775152517032_jvetpszkr",
12
- "memory_1775152503075_3bncrxz1d",
13
- "memory_1775152493186_ecq01e1gx",
14
- "memory_1775152395533_qhhh4p5md",
15
- "memory_1775152294113_jgwg4rw1q",
16
- "memory_1775152031817_1vsksjun4",
17
- "memory_1774195109722_gij039r4m",
18
- "memory_1774191618092_vuxyvq3mw",
19
- "memory_1774106331272_kg5w7ztfj",
20
- "memory_1774104588311_65kg08e05",
21
- "memory_1773140547102_2wmy0cfga",
22
- "memory_1773065886725_rscm7v2qx",
23
- "memory_1773063478046_x0ryr9bk4",
24
- "memory_1773063445947_2r842dw2l",
25
- "memory_1772641994141_ddvzwdkd9",
26
- "memory_1772641570519_wmnb2b08w",
27
- "memory_1772641026962_tqm8ow04r",
7
+ "memory_1775160781806_vnqf355e9",
8
+ "memory_1775159399542_awfryrxp3",
9
+ "memory_1775157082825_8uvrul28i",
10
+ "memory_1775156381094_dr1nihes8",
28
11
  "hook_correction_1774180798682_9b5a2hadq",
29
- "hook_correction_1772101108419_le80cln1w",
30
- "hook_correction_1771112125882_99ihypf8x",
31
- "hook_correction_1774180805192_4ft4zhfsa",
32
- "hook_correction_1772638229134_otn9za2il",
33
- "hook_correction_1772636083381_th3eluwzv",
34
- "hook_correction_1772635851763_y91ugnjgy",
35
- "hook_correction_1772633504532_l6yo1jvun",
36
- "hook_correction_1772633492812_0iqsrgnl2"
12
+ "hook_correction_1774180805192_4ft4zhfsa"
37
13
  ]
38
14
  }
@@ -8,27 +8,19 @@ source: claude-recall
8
8
 
9
9
  # Failure Lessons
10
10
 
11
- Auto-generated from 24 memories. Last updated: 2026-03-22.
11
+ Auto-generated from 16 memories. Last updated: 2026-04-02.
12
12
 
13
13
  ## Rules
14
14
 
15
15
  - SQLite query syntax error: LIKE clause requires single quotes around string literal, not double quotes
16
16
  - Avoid: Command failed: claude-recall outcomes 2>&1 → Instead: Check command syntax, file paths, and prerequisites before running
17
17
  - Avoid: Command failed: npm whoami 2>&1 && npm config get registry 2>&1 → Instead: Check command syntax, file paths, and prerequisites before running
18
- - npm install -g claude-recall@0.15.36 failed with notarget error
19
- - claude-recall@0.15.36 does not exist on npm registry (ETARGET error)
20
- - npm install failed: claude-recall@0.15.36 version not found in registry
21
- - npm package claude-recall@0.15.36 does not exist - version not found in registry
22
- - npm package claude-recall@0.15.36 not found - version does not exist in registry
23
18
  - Claude-recall MCP Server failed to start with npx claude-recall@latest mcp start
24
19
  - Claude-recall MCP Server failed to start with command: npx -y claude-recall@latest mcp start
25
20
  - Avoid: Command failed: npm run build 2>&1 | tail -3 && npm test 2>&1 → Instead: Check command syntax, file paths, and prerequisites before running
26
21
  - Avoid: Command failed: npx jest tests/unit/failure-detectors.test.ts 2>&1 → Instead: Check command syntax, file paths, and prerequisites before running
27
- - Missing dependency: better-sqlite3 module not installed
28
- - Missing dependency: better-sqlite3 module not installed
29
22
  - SQLite query error: LIKE clause needs proper string literal syntax with single quotes in better-sqlite3
30
23
  - Node.js syntax error: multiline strings in -e flag not properly escaped; newlines break the command parsing
31
- - npm package claude-recall@0.15.14 not found in registry - 404 error during installation
32
24
  - Avoid: Test command reported failures: npx jest tests/unit/failure-detectors.test.ts 2>&1 → Instead: Read test output carefully — exit code 0 does not mean all tests passed
33
25
  - Node.js -e flag cannot parse multiline strings with unescaped newlines in single quotes
34
26
  - claude-recall reconnection failed after reinstall
@@ -1,26 +1,18 @@
1
1
  {
2
2
  "topicId": "failure-lessons",
3
- "sourceHash": "00deb7a6c6d99c26e0d29a6a707b4c303dcb19731d27a8ce41db09fd2ff3eb55",
4
- "memoryCount": 24,
5
- "generatedAt": "2026-03-22T15:00:18.049Z",
3
+ "sourceHash": "f3351c090380fb67413721e169ecee6882c991fff7f46e1bbd3b13b208e4c24b",
4
+ "memoryCount": 16,
5
+ "generatedAt": "2026-04-02T18:23:28.296Z",
6
6
  "memoryKeys": [
7
7
  "hook_failure_1772637584921_0tj4rrxnt",
8
8
  "hook_failure_non-zero-exit_1774020949485_6ubuoswae",
9
9
  "hook_failure_non-zero-exit_1773409269877_ful451241",
10
- "hook_failure_1773410916808_5k1r6zo4u",
11
- "hook_failure_1773410916789_xtrb8j9nw",
12
- "hook_failure_1773410910825_rd25wy0tf",
13
- "hook_failure_1773410885720_u05tuf2dk",
14
- "hook_failure_1773410874391_kbnfssg62",
15
10
  "hook_failure_1773409616313_gmsfcbuzh",
16
11
  "hook_failure_1773409607268_83ie5yunz",
17
12
  "hook_failure_non-zero-exit_1773068793859_0z0ah0743",
18
13
  "hook_failure_non-zero-exit_1772640279977_g3gwlfoqi",
19
- "hook_failure_1772637646276_ow6m7pr34",
20
- "hook_failure_1772637630961_6gsnac7cw",
21
14
  "hook_failure_1772637570984_yxs8zmurp",
22
15
  "hook_failure_1772637485532_djep8eysa",
23
- "hook_failure_1771937269919_jx3wvpyxq",
24
16
  "hook_failure_silent-test-failure_1772640279996_m8f3ks8fw",
25
17
  "hook_failure_1772637495154_i21dho3dv",
26
18
  "hook_failure_1773410663873_oegccxk83",
@@ -8,75 +8,31 @@ source: claude-recall
8
8
 
9
9
  # Preferences
10
10
 
11
- Auto-generated from 66 memories. Last updated: 2026-04-02.
11
+ Auto-generated from 22 memories. Last updated: 2026-04-02.
12
12
 
13
13
  ## Rules
14
14
 
15
- - Session test preference 1775153190803
16
- - Test preference 1775153190591-2
17
- - Test preference 1775153190591-1
18
- - Test preference 1775153190591-0
19
- - Test memory content
20
- - Session test preference 1775152793337
21
- - Test preference 1775152793133-2
22
- - Test preference 1775152793133-1
23
- - Test preference 1775152793133-0
24
- - Test memory content
25
- - Session test preference 1775152560667
26
- - Test preference 1775152560598-2
27
- - Test preference 1775152560598-1
28
- - Test preference 1775152560598-0
29
- - Test memory content
30
- - Session test preference 1775152549026
31
- - Test preference 1775152548959-2
32
- - Test preference 1775152548959-1
33
- - Test preference 1775152548959-0
34
- - Test memory content
35
- - Test preference 1775152517049-2
36
- - Test preference 1775152517049-1
37
- - Test preference 1775152517049-0
38
- - Test memory content
39
- - Test preference 1775152503089-2
40
- - Test preference 1775152503089-1
41
- - Test preference 1775152503089-0
42
- - Test memory content
43
- - Test preference 1775152493202-2
44
- - Test preference 1775152493202-1
45
- - Test preference 1775152493202-0
46
- - Test memory content
47
- - Test preference 1775152395578-2
48
- - Test preference 1775152395578-1
49
- - Test preference 1775152395578-0
50
- - Test memory content
51
- - Test preference 1775152294130-2
52
- - Test preference 1775152294130-1
53
- - Test preference 1775152294130-0
54
- - Test memory content
55
- - Test preference 1775152031832-2
56
- - Test preference 1775152031832-1
57
- - Test preference 1775152031832-0
15
+ - Session test preference 1775160781884
16
+ - Test preference 1775160781820-2
17
+ - Test preference 1775160781820-1
18
+ - Test preference 1775160781820-0
19
+ - Test memory content
20
+ - Session test preference 1775159399698
21
+ - Test preference 1775159399559-2
22
+ - Test preference 1775159399559-1
23
+ - Test preference 1775159399559-0
24
+ - Test memory content
25
+ - Session test preference 1775157082982
26
+ - Test preference 1775157082853-2
27
+ - Test preference 1775157082853-1
28
+ - Test preference 1775157082853-0
29
+ - Test memory content
30
+ - Session test preference 1775156381211
31
+ - Test preference 1775156381110-2
32
+ - Test preference 1775156381110-1
33
+ - Test preference 1775156381110-0
58
34
  - Test memory content
59
35
  - axios npm package was compromised in a supply chain attack (axios@1.14.1 pulled in malicious plain-crypto-js@4.2.1). Claude Recall is NOT affected — does not use axios. Verified 2026-04-01. If axios is ever added as a dependency, pin the version and audit lockfiles.
60
- - Test preference 1774195109742-2
61
- - Test preference 1774195109742-1
62
- - Test preference 1774195109742-0
63
- - Test memory content
64
- - Test preference 1774191618111-2
65
- - Test preference 1774191618111-1
66
- - Test preference 1774191618111-0
67
- - Test memory content
68
- - a normal preference
69
- - Session test preference 1774106331339
70
- - Test preference 1774106331283-2
71
- - Test preference 1774106331283-1
72
- - Test preference 1774106331283-0
73
- - Test memory content
74
- - Session test preference 1774104588383
75
- - Test preference 1774104588325-2
76
- - Test preference 1774104588325-1
77
- - Test preference 1774104588325-0
78
- - Test memory content
79
- - Session test preference 1774020789925
80
36
  - Upgrade all projects whenever a new version is pushed
81
37
 
82
38
  ---
@@ -1,74 +1,30 @@
1
1
  {
2
2
  "topicId": "preferences",
3
- "sourceHash": "72956f94499dd1ea22ae6348d86c5bb0199d3eec080cabc3960af1a57ea86dcb",
4
- "memoryCount": 66,
5
- "generatedAt": "2026-04-02T18:06:30.831Z",
3
+ "sourceHash": "d26df6886905013086073ba0031b5e052ec17ae91863e95938269e72d1d5a556",
4
+ "memoryCount": 22,
5
+ "generatedAt": "2026-04-02T20:13:01.897Z",
6
6
  "memoryKeys": [
7
- "memory_1775153190805_cvmwqk3fz",
8
- "memory_1775153190715_23rgmq122",
9
- "memory_1775153190682_arpcd1klk",
10
- "memory_1775153190594_6rf5m5mo4",
11
- "memory_1775153190422_owulv4b0l",
12
- "memory_1775152793339_z2gjzo21u",
13
- "memory_1775152793214_vz5vqxtqt",
14
- "memory_1775152793177_m1zrfowu2",
15
- "memory_1775152793135_0cxpxnzzt",
16
- "memory_1775152792982_kvzpym0nj",
17
- "memory_1775152560669_z96m74mo0",
18
- "memory_1775152560632_g1m6exf5d",
19
- "memory_1775152560615_bzsl0vevj",
20
- "memory_1775152560599_j4nxggnvj",
21
- "memory_1775152560543_2tlrz7uuw",
22
- "memory_1775152549028_vdh5d8l1z",
23
- "memory_1775152548991_s796whgbi",
24
- "memory_1775152548974_8wu7oxnht",
25
- "memory_1775152548960_6xn5qg1ed",
26
- "memory_1775152548897_i892zk4er",
27
- "memory_1775152517086_h1qtehxg5",
28
- "memory_1775152517069_7iyt7gtd2",
29
- "memory_1775152517050_jyndlwzm3",
30
- "memory_1775152516970_9jsac5vef",
31
- "memory_1775152503124_36fasrgv4",
32
- "memory_1775152503106_rmjhhcaqy",
33
- "memory_1775152503090_1vgaf4xvt",
34
- "memory_1775152503030_czylzojcv",
35
- "memory_1775152493236_kge25b20q",
36
- "memory_1775152493220_1xpmtploo",
37
- "memory_1775152493203_cck14tmil",
38
- "memory_1775152493142_fdxvwri6p",
39
- "memory_1775152395670_69if6leyw",
40
- "memory_1775152395636_iatgy1qym",
41
- "memory_1775152395579_00j66922k",
42
- "memory_1775152395397_c28bq57a0",
43
- "memory_1775152294164_6jc19cw4j",
44
- "memory_1775152294147_0b50nxbf8",
45
- "memory_1775152294131_isvbcko3z",
46
- "memory_1775152294061_93dm7kgkv",
47
- "memory_1775152031897_3owwqu9z2",
48
- "memory_1775152031871_p5fdk67r0",
49
- "memory_1775152031833_qtjc1ng2t",
50
- "memory_1775152031740_5lnk9fdzg",
51
- "memory_1775030829333_64gdk8kql",
52
- "memory_1774195109780_8ffmflge1",
53
- "memory_1774195109763_v6olh83ct",
54
- "memory_1774195109743_i79j9a9rl",
55
- "memory_1774195109624_nnh3wrwca",
56
- "memory_1774191618164_wefq7s9x6",
57
- "memory_1774191618139_k77izvxnq",
58
- "memory_1774191618112_hzi64751y",
59
- "memory_1774191617980_pzspevoct",
60
- "valid1",
61
- "memory_1774106331340_cwt88yhle",
62
- "memory_1774106331311_491hg2a21",
63
- "memory_1774106331297_0b6bvacd7",
64
- "memory_1774106331284_ai62szr9y",
65
- "memory_1774106331222_zjfz8zu59",
66
- "memory_1774104588384_hy5yej2pk",
67
- "memory_1774104588351_whsk5rvw3",
68
- "memory_1774104588338_80cx2o3jv",
69
- "memory_1774104588325_g4o5ksubl",
70
- "memory_1774104588255_fy0wv845g",
71
- "memory_1774020789927_g99mrubvu",
7
+ "memory_1775160781885_fkbnc0s85",
8
+ "memory_1775160781857_gj5y299dr",
9
+ "memory_1775160781842_0xmv146n5",
10
+ "memory_1775160781821_o9ipt8nsc",
11
+ "memory_1775160781747_70e5gbp7j",
12
+ "memory_1775159399702_yg6ynd05n",
13
+ "memory_1775159399631_fmy4s2vhr",
14
+ "memory_1775159399606_xymkvcjfv",
15
+ "memory_1775159399561_o6vpb3fto",
16
+ "memory_1775159399463_0pvhuixyr",
17
+ "memory_1775157082984_oiyt9u65w",
18
+ "memory_1775157082927_29d7mm59z",
19
+ "memory_1775157082892_o9i0jbw76",
20
+ "memory_1775157082855_zlbl2ib0j",
21
+ "memory_1775157082743_fu07oklto",
22
+ "memory_1775156381212_ptlo01224",
23
+ "memory_1775156381158_3p6goornd",
24
+ "memory_1775156381132_ikx95mk0n",
25
+ "memory_1775156381111_1x4fxfu9n",
26
+ "memory_1775156381012_3tw7we7cz",
27
+ "memory_1775154208266_arwo2fctx",
72
28
  "hook_preference_1774106575282_45sk4ep52"
73
29
  ]
74
30
  }
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # Claude Recall
2
2
 
3
- ### Persistent, local memory for Claude Code — learn from every session.
3
+ ### Persistent, local memory for coding agents — learn from every session.
4
4
 
5
- Claude Recall is a **local memory engine + MCP server** that gives Claude Code something it's missing by default:
5
+ Claude Recall is a **local memory engine** that gives coding agents something they're missing by default:
6
6
  **the ability to learn from you over time.**
7
7
 
8
+ Works with **Claude Code** (via MCP server + hooks) and **[Pi](https://github.com/mariozechner/pi)** (via native extension). Both share the same local database — a preference learned in one agent is available in the other.
9
+
8
10
  Your preferences, project structure, workflows, corrections, and coding style are captured automatically and applied in future sessions — **securely stored on your machine**.
9
11
 
10
12
  ---
@@ -12,8 +14,8 @@ Your preferences, project structure, workflows, corrections, and coding style ar
12
14
  ## Features
13
15
 
14
16
  - **Smart Memory Capture** — LLM-powered classification (via Claude Haiku) detects preferences and corrections from natural language, with silent regex fallback
15
- - **Project-Scoped Knowledge** — each project gets its own memory namespace; switch projects and Claude switches context automatically
16
- - **Failure Learning** — captures what failed, why, and what to do instead — so Claude doesn't repeat mistakes
17
+ - **Project-Scoped Knowledge** — each project gets its own memory namespace; switch projects and the agent switches context automatically
18
+ - **Failure Learning** — captures what failed, why, and what to do instead — so the agent doesn't repeat mistakes
17
19
  - **Outcome-Aware Learning** — tracks action outcomes (all tool results, test cycles, user corrections), synthesizes candidate lessons, and promotes validated patterns into active rules automatically
18
20
  - **Skill Crystallization** — auto-generates `.claude/skills/auto-*/` files from accumulated memories, using Anthropic's [Agent Skills](https://agentskills.io/) open standard
19
21
  - **Local-Only** — SQLite on your machine, no telemetry, no cloud, works fully offline
@@ -29,60 +31,46 @@ Your preferences, project structure, workflows, corrections, and coding style ar
29
31
  | Node.js | **20+** | required for better-sqlite3 |
30
32
  | OS | macOS / Linux / Windows | WSL supported |
31
33
 
32
- ### Install / Reinstall
33
-
34
- Run these from your project directory:
34
+ ### Install for Claude Code
35
35
 
36
36
  ```bash
37
- # 1. Remove MCP server registration (if exists)
38
- claude mcp remove claude-recall
39
-
40
- # 2. Clear npm cache
41
- npm cache clean --force
42
-
43
- # 3. Uninstall global claude-recall
44
- npm uninstall -g claude-recall
45
-
46
- # 4. Install global claude-recall
37
+ # Install globally
47
38
  npm install -g claude-recall
48
39
 
49
- # 5. Install in local project folder
40
+ # Set up hooks and skills in your project
50
41
  claude-recall setup --install
51
42
 
52
- # 6. Re-register MCP server
43
+ # Register MCP server
53
44
  claude mcp add claude-recall -- claude-recall mcp start
54
45
  ```
55
46
 
56
- Then restart your Claude Code session.
47
+ Then restart your Claude Code session. For additional projects, only the last two commands are needed.
57
48
 
58
- ### Adding to another project
49
+ **Verify:** Ask *"Load my rules"* — Claude should call `mcp__claude-recall__load_rules`.
59
50
 
60
- From the new project directory, only steps 5-6 are needed:
51
+ ### Install for Pi
61
52
 
62
53
  ```bash
63
- claude-recall setup --install
64
- claude mcp add claude-recall -- claude-recall mcp start
54
+ pi install npm:claude-recall
65
55
  ```
66
56
 
67
- Memories are automatically scoped per project in a shared database (`~/.claude-recall/claude-recall.db`).
57
+ That's it. The extension registers tools and loads a skill automatically. No further configuration needed.
68
58
 
69
- ### Verify
59
+ **Verify:** Start Pi and ask *"Load my rules"* — Pi should call `recall_load_rules`.
70
60
 
71
- In Claude Code, ask: *"Load my rules"*
61
+ ### Shared Database
72
62
 
73
- Claude should call `mcp__claude-recall__load_rules`. If it works, you're ready.
63
+ Both agents use the same database (`~/.claude-recall/claude-recall.db`). Memories are scoped per project by working directory. A correction learned in Claude Code is available in Pi and vice versa.
74
64
 
75
65
  ### Upgrading
76
66
 
77
- When a new version is published, update the global binary — no per-project reinstall needed:
78
-
79
67
  ```bash
80
- npm cache clean --force
81
- npm uninstall -g claude-recall
68
+ # Claude Code
82
69
  npm install -g claude-recall
83
- ```
84
70
 
85
- Then restart Claude Code sessions in each project to pick up the new version.
71
+ # Pi
72
+ pi update claude-recall
73
+ ```
86
74
 
87
75
  ---
88
76
 
@@ -90,21 +78,20 @@ Then restart Claude Code sessions in each project to pick up the new version.
90
78
 
91
79
  Once installed, Claude Recall works automatically in the background:
92
80
 
93
- 1. **First prompt** — the `search_enforcer` hook ensures Claude loads your stored rules before taking any action
94
- 2. **As you work** — the `correction-detector` hook classifies every prompt you type. Natural statements like *"we use tabs here"* or *"no, put tests in `__tests__/`"* are detected and stored automatically
95
- 3. **End of turn** — the `memory-stop` hook scans recent transcript entries for corrections, preferences, failures, and devops patterns. It also creates **episodes** summarizing the session outcome, generates **candidate lessons** from detected failures, and runs a **promotion cycle** to graduate validated patterns into active rules
96
- 4. **Tool outcomes** — the `tool-outcome-watcher` hook captures outcomes from all tools (Bash, Edit, Write, MCP tools) in real-time. Bash failures are paired with successful fixes. A separate `PostToolUseFailure` hook captures structured error details for any tool failure
97
- 5. **Reask detection** — the `correction-detector` hook detects user frustration signals ("still broken", "that didn't work") and records them as outcome events
98
- 6. **Before context compression** — the `precompact-preserve` hook sweeps up to 50 entries so nothing important is lost when the context window shrinks
99
- 7. **Rules sync to auto-memory** the `memory-sync` hook exports the top 30 rules as individual typed `.md` files with YAML frontmatter to `~/.claude/projects/{project}/memory/`, matching Claude Code's native memory format. Rules are ranked by citation count, load frequency, and recency
81
+ 1. **Session start** — active rules are loaded before the first action. In Claude Code, this happens via the `search_enforcer` hook; in Pi, rules are injected into the system prompt automatically
82
+ 2. **As you work** — every prompt is classified for corrections and preferences. Natural statements like *"we use tabs here"* or *"no, put tests in `__tests__/`"* are detected and stored
83
+ 3. **Tool outcomes** — results from all tools (Bash, Edit, Write, and more) are captured. Failures are stored as memories; Bash failures are paired with successful fixes
84
+ 4. **End of session** — session episodes are created, candidate lessons extracted from failures, and a promotion cycle graduates validated patterns into active rules
85
+ 5. **Reask detection** — frustration signals ("still broken", "that didn't work") are recorded as outcome events
86
+ 6. **Before context compression** — aggressive memory sweep captures important context before the window shrinks
87
+ 7. **Rules sync** (Claude Code only) top 30 rules are exported as typed `.md` files to Claude Code's native memory directory
100
88
 
101
- All classification uses Claude Haiku (via `ANTHROPIC_API_KEY` from your Claude Code session) with silent regex fallback. No configuration needed.
89
+ Classification uses Claude Haiku (via `ANTHROPIC_API_KEY`) with silent regex fallback. No configuration needed.
102
90
 
103
- **Next session:** `load_rules` returns everything captured previously — Claude applies your preferences without being told twice.
91
+ **Next session:** `load_rules` returns everything captured previously — the agent applies your preferences without being told twice.
104
92
 
105
93
  ```bash
106
94
  # Verify it's working
107
- cat ~/.claude-recall/hook-logs/correction-detector.log
108
95
  claude-recall stats
109
96
  claude-recall search "preference"
110
97
  ```
@@ -113,22 +100,28 @@ claude-recall search "preference"
113
100
 
114
101
  ## How It Works
115
102
 
116
- Claude Recall runs as an MCP server exposing four tools and seven prompts, backed by a local SQLite database with WAL mode, content-hash deduplication, and automatic compaction. The MCP prompts (including `load-rules` and `session-review`) are discoverable by Claude Code's skill system.
103
+ Claude Recall provides four memory tools backed by a local SQLite database with WAL mode, content-hash deduplication, and automatic compaction. The tools are exposed differently depending on the agent:
104
+
105
+ - **Claude Code** — MCP server with four tools and seven prompts, plus file-system hooks for automatic capture
106
+ - **Pi** — native extension with registered tools and event handlers, plus a skill file for behavioral guidance
107
+
108
+ | Tool | Claude Code | Pi |
109
+ | ---- | ----------- | --- |
110
+ | Load rules | `mcp__claude-recall__load_rules` | `recall_load_rules` |
111
+ | Store memory | `mcp__claude-recall__store_memory` | `recall_store_memory` |
112
+ | Search memory | `mcp__claude-recall__search_memory` | `recall_search_memory` |
113
+ | Delete memory | `mcp__claude-recall__delete_memory` | `recall_delete_memory` |
117
114
 
118
- ### Built on Agent Skills
115
+ ### Skills
119
116
 
120
- Claude Recall uses Anthropic's [Agent Skills](https://agentskills.io/) open standard to teach Claude when and how to use its memory tools. A core skill (`.claude/skills/memory-management/SKILL.md`) guides Claude's memory behavior using progressive disclosure — metadata loads at startup, full instructions load only when needed. When enough memories accumulate around a topic, Claude Recall auto-generates additional skills (`.claude/skills/auto-*/`) that load natively without MCP tool calls. See Anthropic's [blog post](https://claude.com/blog/equipping-agents-for-the-real-world-with-agent-skills) for more on the Agent Skills architecture.
117
+ Claude Recall uses skill files to teach agents when and how to use memory tools:
121
118
 
122
- | Tool | Purpose |
123
- | ---- | ------- |
124
- | `load_rules` | Load all active rules (preferences, corrections, failures, devops) at the start of a task |
125
- | `store_memory` | Save new knowledge — preferences, corrections, devops rules, failures |
126
- | `search_memory` | Search memories by keyword, ranked by relevance |
127
- | `delete_memory` | Delete a specific memory by ID |
119
+ - **Claude Code** uses Anthropic's [Agent Skills](https://agentskills.io/) open standard. A core skill (`.claude/skills/memory-management/SKILL.md`) guides memory behavior with progressive disclosure. Auto-generated skills (`.claude/skills/auto-*/`) crystallize from accumulated memories. See Anthropic's [blog post](https://claude.com/blog/equipping-agents-for-the-real-world-with-agent-skills) for more.
120
+ - **Pi** ships a `skills/memory-management.md` skill loaded via Pi's package manifest
128
121
 
129
- ### Outcome-Aware Learning (v0.18.0)
122
+ ### Outcome-Aware Learning
130
123
 
131
- Claude Recall tracks what happens *after* Claude acts — not just what was said. The outcome processing pipeline:
124
+ Claude Recall tracks what happens *after* the agent acts — not just what was said. The outcome processing pipeline:
132
125
 
133
126
  ```
134
127
  action → outcome event → episode → candidate lesson → promotion → active rule
@@ -216,7 +209,7 @@ claude-recall hook run memory-sync # Stop + PreCompact hook (syncs rul
216
209
 
217
210
  ## Project Scoping
218
211
 
219
- Each project gets isolated memory based on its working directory. **Project ID** is derived from the `cwd` that Claude Code passes to the MCP server. Universal memories (no project scope) are available everywhere. Switching projects switches memory automatically.
212
+ Each project gets isolated memory based on its working directory. **Project ID** is derived from the `cwd` passed by the agent. Universal memories (no project scope) are available everywhere. Switching projects switches memory automatically.
220
213
 
221
214
  Database location: `~/.claude-recall/claude-recall.db` (shared file, scoped by `project_id` column).
222
215
 
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Recall — Pi Extension
4
+ *
5
+ * Provides persistent memory tools for the Pi coding agent.
6
+ * Shares the same SQLite database as the Claude Code integration.
7
+ *
8
+ * Install: pi install npm:claude-recall
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.default = default_1;
12
+ const memory_1 = require("../services/memory");
13
+ const config_1 = require("../services/config");
14
+ const outcome_storage_1 = require("../services/outcome-storage");
15
+ const logging_1 = require("../services/logging");
16
+ const event_processors_1 = require("../shared/event-processors");
17
+ const LOAD_RULES_DIRECTIVE = 'INSTRUCTION: Before your FIRST edit or bash action, you MUST output an\n' +
18
+ '"Applying memories:" section listing which rules below apply to the current task.\n' +
19
+ 'If none apply, say so. Do NOT skip this step.\n' +
20
+ 'As you work, cite each applied rule inline: (applied from memory: <rule summary>)\n' +
21
+ 'If a rule conflicts with your plan, follow the rule — it reflects a user decision.';
22
+ /** Format a memory value for display. */
23
+ function extractVal(value) {
24
+ if (typeof value === 'string')
25
+ return value;
26
+ if (typeof value === 'object' && value !== null) {
27
+ return value.content || value.value || JSON.stringify(value);
28
+ }
29
+ return String(value ?? '');
30
+ }
31
+ /** Format active rules as markdown sections. */
32
+ function formatRules(rules) {
33
+ const sections = [];
34
+ if (rules.preferences.length > 0) {
35
+ sections.push('## Preferences\n' + rules.preferences.map(m => `- ${extractVal(m.value)}`).join('\n'));
36
+ }
37
+ if (rules.corrections.length > 0) {
38
+ sections.push('## Corrections\n' + rules.corrections.map(m => `- ${extractVal(m.value)}`).join('\n'));
39
+ }
40
+ if (rules.failures.length > 0) {
41
+ sections.push('## Failures\n' + rules.failures.map(m => `- ${extractVal(m.value)}`).join('\n'));
42
+ }
43
+ if (rules.devops.length > 0) {
44
+ sections.push('## DevOps Rules\n' + rules.devops.map(m => `- ${extractVal(m.value)}`).join('\n'));
45
+ }
46
+ return sections.join('\n\n');
47
+ }
48
+ function default_1(pi) {
49
+ let projectId = '';
50
+ let sessionId = `pi_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
51
+ let rulesLoaded = false;
52
+ const collectedUserTexts = [];
53
+ // Route logs through Pi's UI when available
54
+ (0, event_processors_1.setLogFunction)((source, msg) => {
55
+ try {
56
+ logging_1.LoggingService.getInstance().info(source, msg);
57
+ }
58
+ catch { /* silent */ }
59
+ });
60
+ // --- Session init: set project context from cwd ---
61
+ pi.on('session_start', (_event, ctx) => {
62
+ projectId = ctx.cwd.split('/').pop() || 'unknown';
63
+ rulesLoaded = false;
64
+ collectedUserTexts.length = 0;
65
+ (0, event_processors_1.resetPendingFailures)();
66
+ try {
67
+ config_1.ConfigService.getInstance().updateConfig({
68
+ project: { rootDir: ctx.cwd },
69
+ });
70
+ }
71
+ catch {
72
+ // Non-critical
73
+ }
74
+ });
75
+ // --- Event: inject rules before first agent turn ---
76
+ pi.on('before_agent_start', (_event, _ctx) => {
77
+ if (rulesLoaded)
78
+ return;
79
+ rulesLoaded = true;
80
+ try {
81
+ const ms = memory_1.MemoryService.getInstance();
82
+ const rules = ms.loadActiveRules(projectId || undefined);
83
+ const body = formatRules(rules);
84
+ if (body) {
85
+ return { systemPrompt: _event.systemPrompt + '\n\n' + LOAD_RULES_DIRECTIVE + '\n\n---\n\n' + body };
86
+ }
87
+ }
88
+ catch {
89
+ // Non-critical — tools still available as fallback
90
+ }
91
+ });
92
+ // --- Event: capture tool outcomes ---
93
+ pi.on('tool_result', (event, _ctx) => {
94
+ const output = event.content
95
+ .filter((c) => c.type === 'text')
96
+ .map(c => c.text)
97
+ .join('\n');
98
+ (0, event_processors_1.processToolOutcome)(event.toolName, event.input, output, event.isError, sessionId);
99
+ });
100
+ // --- Event: detect corrections from user input ---
101
+ pi.on('input', (event, _ctx) => {
102
+ collectedUserTexts.push(event.text);
103
+ (0, event_processors_1.processUserInput)(event.text, sessionId).catch(() => { });
104
+ return { action: 'continue' };
105
+ });
106
+ // --- Event: session end — episode + promotion ---
107
+ pi.on('session_shutdown', (_event, _ctx) => {
108
+ (0, event_processors_1.processSessionEnd)(collectedUserTexts, sessionId, projectId).catch(() => { });
109
+ });
110
+ // --- Event: pre-compaction — aggressive capture ---
111
+ pi.on('session_before_compact', (event, _ctx) => {
112
+ // Extract user texts from branch entries
113
+ const texts = [];
114
+ for (const entry of (event.branchEntries || [])) {
115
+ if (entry?.role === 'user' && typeof entry.content === 'string') {
116
+ texts.push(entry.content);
117
+ }
118
+ }
119
+ if (texts.length > 0) {
120
+ (0, event_processors_1.processPreCompact)(texts, sessionId).catch(() => { });
121
+ }
122
+ // Re-inject rules after compaction
123
+ rulesLoaded = false;
124
+ });
125
+ // --- Tool: recall_load_rules ---
126
+ pi.registerTool({
127
+ name: 'recall_load_rules',
128
+ label: 'Load Rules',
129
+ description: 'Load all stored rules (preferences, corrections, failures, devops). Call at the start of every task.',
130
+ promptSnippet: 'Load stored rules and preferences from memory',
131
+ parameters: {},
132
+ async execute(_id, _params, _signal, _onUpdate, ctx) {
133
+ try {
134
+ const ms = memory_1.MemoryService.getInstance();
135
+ const rules = ms.loadActiveRules(projectId || undefined);
136
+ const body = formatRules(rules);
137
+ const totalRules = rules.preferences.length + rules.corrections.length +
138
+ rules.failures.length + rules.devops.length;
139
+ // Track retrievals
140
+ try {
141
+ const os = outcome_storage_1.OutcomeStorage.getInstance();
142
+ const all = [...rules.preferences, ...rules.corrections, ...rules.failures, ...rules.devops];
143
+ for (const m of all)
144
+ os.recordRetrieval(m.key);
145
+ }
146
+ catch { /* non-critical */ }
147
+ const text = body
148
+ ? `${LOAD_RULES_DIRECTIVE}\n\n---\n\n${body}`
149
+ : 'No active rules found. This may be a new project.';
150
+ return {
151
+ content: [{ type: 'text', text }],
152
+ };
153
+ }
154
+ catch (err) {
155
+ return {
156
+ content: [{ type: 'text', text: `Failed to load rules: ${err.message}` }],
157
+ isError: true,
158
+ };
159
+ }
160
+ },
161
+ });
162
+ // --- Tool: recall_store_memory ---
163
+ pi.registerTool({
164
+ name: 'recall_store_memory',
165
+ label: 'Store Memory',
166
+ description: 'Store a rule or learning. Use for: corrections, preferences, devops rules, failures.',
167
+ promptSnippet: 'Store a rule, correction, or preference to memory',
168
+ parameters: {},
169
+ async execute(_id, params, _signal, _onUpdate, _ctx) {
170
+ try {
171
+ const content = params.content;
172
+ if (!content || typeof content !== 'string') {
173
+ return {
174
+ content: [{ type: 'text', text: 'Error: content is required and must be a string' }],
175
+ isError: true,
176
+ };
177
+ }
178
+ const validTypes = ['preference', 'correction', 'devops', 'failure', 'project-knowledge'];
179
+ const metadata = params.metadata || {};
180
+ const detectedType = (metadata.type && validTypes.includes(metadata.type))
181
+ ? metadata.type : 'preference';
182
+ const key = `memory_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
183
+ const ms = memory_1.MemoryService.getInstance();
184
+ ms.store({
185
+ key,
186
+ value: { content, ...metadata, sessionId, timestamp: Date.now() },
187
+ type: detectedType,
188
+ context: {
189
+ sessionId,
190
+ projectId: params.scope === 'project' ? projectId : undefined,
191
+ timestamp: Date.now(),
192
+ scope: params.scope || null,
193
+ },
194
+ });
195
+ return {
196
+ content: [{ type: 'text', text: JSON.stringify({
197
+ id: key,
198
+ success: true,
199
+ activeRule: `Stored as active rule:\n- ${content}`,
200
+ type: detectedType,
201
+ _directive: 'Apply this rule immediately.',
202
+ }) }],
203
+ };
204
+ }
205
+ catch (err) {
206
+ return {
207
+ content: [{ type: 'text', text: `Failed to store memory: ${err.message}` }],
208
+ isError: true,
209
+ };
210
+ }
211
+ },
212
+ });
213
+ // --- Tool: recall_search_memory ---
214
+ pi.registerTool({
215
+ name: 'recall_search_memory',
216
+ label: 'Search Memory',
217
+ description: 'Search memories by keyword. Returns matched memories ranked by relevance.',
218
+ promptSnippet: 'Search stored memories by keyword',
219
+ parameters: {},
220
+ async execute(_id, params, _signal, _onUpdate, _ctx) {
221
+ try {
222
+ const query = params.query;
223
+ if (!query || typeof query !== 'string') {
224
+ return {
225
+ content: [{ type: 'text', text: 'Error: query is required and must be a string' }],
226
+ isError: true,
227
+ };
228
+ }
229
+ const limit = Math.min(Math.max(params.limit || 10, 1), 25);
230
+ const ms = memory_1.MemoryService.getInstance();
231
+ const searchCtx = { query, projectId, timestamp: Date.now() };
232
+ if (params.type)
233
+ searchCtx.type = params.type;
234
+ const results = ms.findRelevant(searchCtx);
235
+ const top = results.slice(0, limit);
236
+ // Track retrievals
237
+ try {
238
+ const os = outcome_storage_1.OutcomeStorage.getInstance();
239
+ for (const r of top)
240
+ os.recordRetrieval(r.key);
241
+ }
242
+ catch { /* non-critical */ }
243
+ const formatted = top.map(r => {
244
+ const val = extractVal(r.value);
245
+ return `- [${r.type}] (id: ${r.key}) ${val}`;
246
+ });
247
+ return {
248
+ content: [{ type: 'text', text: JSON.stringify({
249
+ results: formatted.length > 0 ? formatted.join('\n') : `No memories found matching "${query}".`,
250
+ count: top.length,
251
+ query,
252
+ }) }],
253
+ };
254
+ }
255
+ catch (err) {
256
+ return {
257
+ content: [{ type: 'text', text: `Failed to search: ${err.message}` }],
258
+ isError: true,
259
+ };
260
+ }
261
+ },
262
+ });
263
+ // --- Tool: recall_delete_memory ---
264
+ pi.registerTool({
265
+ name: 'recall_delete_memory',
266
+ label: 'Delete Memory',
267
+ description: 'Delete a specific memory by its ID. Use recall_search_memory first to find the ID.',
268
+ parameters: {},
269
+ async execute(_id, params, _signal, _onUpdate, _ctx) {
270
+ try {
271
+ const id = params.id;
272
+ if (!id || typeof id !== 'string') {
273
+ return {
274
+ content: [{ type: 'text', text: 'Error: id is required and must be a string' }],
275
+ isError: true,
276
+ };
277
+ }
278
+ const ms = memory_1.MemoryService.getInstance();
279
+ const deleted = ms.delete(id);
280
+ return {
281
+ content: [{ type: 'text', text: JSON.stringify({
282
+ success: deleted,
283
+ id,
284
+ message: deleted ? `Memory "${id}" deleted.` : `Memory "${id}" not found.`,
285
+ }) }],
286
+ };
287
+ }
288
+ catch (err) {
289
+ return {
290
+ content: [{ type: 'text', text: `Failed to delete: ${err.message}` }],
291
+ isError: true,
292
+ };
293
+ }
294
+ },
295
+ });
296
+ }
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ /**
3
+ * Shared event processors — agent-agnostic functions that both
4
+ * Claude Code hooks and the Pi extension call.
5
+ *
6
+ * These operate on in-memory data (strings, arrays) rather than
7
+ * file paths or stdin, so they work in any runtime environment.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.setLogFunction = setLogFunction;
44
+ exports.resetPendingFailures = resetPendingFailures;
45
+ exports.processToolOutcome = processToolOutcome;
46
+ exports.processUserInput = processUserInput;
47
+ exports.processSessionEnd = processSessionEnd;
48
+ exports.processPreCompact = processPreCompact;
49
+ const shared_1 = require("../hooks/shared");
50
+ const memory_1 = require("../services/memory");
51
+ const outcome_storage_1 = require("../services/outcome-storage");
52
+ let logFn = () => { }; // silent by default
53
+ /** Set the log function (hookLog for CC hooks, console for Pi, etc.) */
54
+ function setLogFunction(fn) {
55
+ logFn = fn;
56
+ }
57
+ const MAX_PENDING = 10;
58
+ const FIX_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
59
+ const FIX_SIMILARITY_THRESHOLD = 0.3;
60
+ let pendingFailures = [];
61
+ /** Reset pending failures (e.g. on session start). */
62
+ function resetPendingFailures() {
63
+ pendingFailures = [];
64
+ }
65
+ /** Extract an identifier string from tool input for similarity comparison. */
66
+ function getToolIdentifier(toolName, toolInput) {
67
+ if ((toolName === 'Bash' || toolName === 'bash') && toolInput?.command) {
68
+ return toolInput.command;
69
+ }
70
+ if (toolInput?.file_path) {
71
+ return `${toolName}:${toolInput.file_path}`;
72
+ }
73
+ return toolName;
74
+ }
75
+ /**
76
+ * Check if a successful tool result matches a recent failure and pair the fix.
77
+ * Returns true if a fix was paired.
78
+ */
79
+ function tryPairFix(toolName, toolInput, output) {
80
+ if (pendingFailures.length === 0)
81
+ return false;
82
+ const now = Date.now();
83
+ const identifier = getToolIdentifier(toolName, toolInput);
84
+ let matched = false;
85
+ const remaining = [];
86
+ for (const pf of pendingFailures) {
87
+ if (now - pf.timestamp > FIX_WINDOW_MS)
88
+ continue; // expired
89
+ if (!matched && pf.toolName === toolName &&
90
+ (0, shared_1.jaccardSimilarity)(pf.identifier, identifier) >= FIX_SIMILARITY_THRESHOLD) {
91
+ try {
92
+ memory_1.MemoryService.getInstance().update(pf.memoryKey, {
93
+ value: { what_should_do: `Fix: ${truncate(identifier, 200)}` },
94
+ });
95
+ logFn('event-processor', `Paired fix: "${truncate(identifier, 60)}" → ${pf.memoryKey}`);
96
+ matched = true;
97
+ // Don't add to remaining — consumed
98
+ }
99
+ catch {
100
+ remaining.push(pf); // keep if update fails
101
+ }
102
+ }
103
+ else {
104
+ remaining.push(pf);
105
+ }
106
+ }
107
+ pendingFailures = remaining;
108
+ return matched;
109
+ }
110
+ // --- Tool Outcome Processing ---
111
+ /** Error patterns for Edit/Write tools */
112
+ const WRITE_ERROR_PATTERNS = [
113
+ /permission denied/i,
114
+ /EACCES/i,
115
+ /ENOENT/i,
116
+ /file not found/i,
117
+ /no such file/i,
118
+ /read-?only file/i,
119
+ /conflict/i,
120
+ /old_string.*not found/i,
121
+ /not unique in the file/i,
122
+ ];
123
+ /** Error patterns for MCP/custom tools */
124
+ const TOOL_ERROR_PATTERNS = [
125
+ /error/i,
126
+ /failed/i,
127
+ /exception/i,
128
+ /timeout/i,
129
+ ];
130
+ function truncate(s, maxLen) {
131
+ return s.length <= maxLen ? s : s.substring(0, maxLen - 3) + '...';
132
+ }
133
+ function firstLine(s) {
134
+ const idx = s.indexOf('\n');
135
+ return idx === -1 ? s : s.substring(0, idx);
136
+ }
137
+ /**
138
+ * Process a tool outcome — capture failures as memories, record outcome events.
139
+ * When a tool succeeds after a similar recent failure, pairs the fix.
140
+ * Works for any tool type (Bash, Edit, Write, MCP, generic).
141
+ */
142
+ function processToolOutcome(toolName, toolInput, toolOutput, isError, sessionId) {
143
+ try {
144
+ // Skip own tools
145
+ if (toolName.includes('claude-recall') || toolName.includes('claude_recall') ||
146
+ toolName.startsWith('recall_'))
147
+ return;
148
+ const isFail = isError || isToolFailureOutput(toolName, toolOutput);
149
+ if (isFail) {
150
+ storeToolFailure(toolName, toolInput, toolOutput, sessionId);
151
+ }
152
+ else {
153
+ // Success — check if this fixes a recent failure
154
+ tryPairFix(toolName, toolInput, toolOutput);
155
+ }
156
+ // Record outcome event for all tools
157
+ recordOutcomeEvent(toolName, toolInput, toolOutput);
158
+ }
159
+ catch (err) {
160
+ logFn('event-processor', `processToolOutcome error: ${(0, shared_1.safeErrorMessage)(err)}`);
161
+ }
162
+ }
163
+ function isToolFailureOutput(toolName, output) {
164
+ if (toolName === 'Bash' || toolName === 'bash') {
165
+ return /Exit code (\d+)/.test(output) && !/Exit code 0/.test(output);
166
+ }
167
+ if (toolName === 'Edit' || toolName === 'Write' || toolName === 'edit' || toolName === 'write') {
168
+ return WRITE_ERROR_PATTERNS.some(p => p.test(output));
169
+ }
170
+ // For other tools, only flag short error outputs (avoid false positives on long results)
171
+ if (output.length < 500) {
172
+ return TOOL_ERROR_PATTERNS.some(p => p.test(output));
173
+ }
174
+ return false;
175
+ }
176
+ function storeToolFailure(toolName, toolInput, output, sessionId) {
177
+ const filePath = toolInput?.file_path ?? '';
178
+ const command = toolInput?.command ?? '';
179
+ let whatFailed;
180
+ if ((toolName === 'Bash' || toolName === 'bash') && command) {
181
+ whatFailed = `Command failed: ${truncate(command, 100)}`;
182
+ }
183
+ else if (filePath) {
184
+ whatFailed = `${toolName} failed on ${truncate(filePath, 80)}`;
185
+ }
186
+ else {
187
+ whatFailed = `${toolName} failed`;
188
+ }
189
+ // Dedup
190
+ const existing = (0, shared_1.searchExisting)(whatFailed);
191
+ if ((0, shared_1.isDuplicate)(whatFailed, existing, 0.7))
192
+ return;
193
+ const failureContent = {
194
+ what_failed: whatFailed,
195
+ why_failed: truncate(firstLine(output), 200),
196
+ what_should_do: 'Check inputs and prerequisites before retrying',
197
+ context: `${toolName} error`,
198
+ preventative_checks: ['Verify inputs are correct', 'Check preconditions'],
199
+ };
200
+ const key = `hook_failure_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
201
+ memory_1.MemoryService.getInstance().store({
202
+ key,
203
+ value: failureContent,
204
+ type: 'failure',
205
+ context: { timestamp: Date.now() },
206
+ relevanceScore: 0.75,
207
+ });
208
+ // Track for fix pairing — when a similar tool succeeds, we update this memory
209
+ const identifier = getToolIdentifier(toolName, toolInput);
210
+ pendingFailures.push({ toolName, identifier, memoryKey: key, timestamp: Date.now() });
211
+ while (pendingFailures.length > MAX_PENDING)
212
+ pendingFailures.shift();
213
+ logFn('event-processor', `Stored failure: ${truncate(whatFailed, 60)}`);
214
+ }
215
+ function recordOutcomeEvent(toolName, toolInput, output) {
216
+ try {
217
+ const tags = [toolName.toLowerCase()];
218
+ if (toolInput?.file_path) {
219
+ const ext = toolInput.file_path.split('.').pop();
220
+ if (ext)
221
+ tags.push(ext);
222
+ }
223
+ outcome_storage_1.OutcomeStorage.getInstance().createOutcomeEvent({
224
+ event_type: 'tool_result',
225
+ actor: 'tool',
226
+ action_summary: `${toolName}: ${truncate(firstLine(output), 100)}`,
227
+ next_state_summary: truncate(firstLine(output), 200),
228
+ tags,
229
+ });
230
+ }
231
+ catch {
232
+ // Non-critical
233
+ }
234
+ }
235
+ // --- User Input Processing (Correction Detection) ---
236
+ const REASK_PATTERNS = [
237
+ /still broken/i,
238
+ /that'?s not what I (?:meant|asked|wanted)/i,
239
+ /wrong file/i,
240
+ /try again/i,
241
+ /that didn'?t (?:work|fix|help)/i,
242
+ /you (?:missed|forgot|ignored)/i,
243
+ ];
244
+ /**
245
+ * Process user input text — detect corrections, preferences, and reask signals.
246
+ * Returns a summary message if something was captured, or null.
247
+ */
248
+ async function processUserInput(text, sessionId) {
249
+ if (text.length < 20 || text.length > 2000)
250
+ return null;
251
+ if (text.startsWith('```') || text.startsWith('{'))
252
+ return null;
253
+ // Detect reask signals
254
+ try {
255
+ for (const pattern of REASK_PATTERNS) {
256
+ if (pattern.test(text)) {
257
+ outcome_storage_1.OutcomeStorage.getInstance().createOutcomeEvent({
258
+ event_type: 'reask_signal',
259
+ actor: 'user',
260
+ next_state_summary: `User reask detected: ${text.substring(0, 100)}`,
261
+ tags: ['reask'],
262
+ });
263
+ break;
264
+ }
265
+ }
266
+ }
267
+ catch {
268
+ // Non-critical
269
+ }
270
+ const result = await (0, shared_1.classifyContent)(text);
271
+ if (!result)
272
+ return null;
273
+ if (result.extract.length < 10 || result.extract.length > 200)
274
+ return null;
275
+ if ((result.type === 'correction' || result.type === 'preference') && result.confidence < 0.7)
276
+ return null;
277
+ if (result.confidence < 0.6)
278
+ return null;
279
+ // Dedup
280
+ const existing = (0, shared_1.searchExisting)(result.extract.substring(0, 100));
281
+ if ((0, shared_1.isDuplicate)(result.extract, existing))
282
+ return null;
283
+ (0, shared_1.storeMemory)(result.extract, result.type, undefined, result.confidence);
284
+ const summary = result.extract.length > 60 ? result.extract.substring(0, 60) + '...' : result.extract;
285
+ logFn('event-processor', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
286
+ return `Auto-captured ${result.type}: ${summary}`;
287
+ }
288
+ // --- Session End Processing ---
289
+ /**
290
+ * Process end-of-session: batch classify user texts, store memories,
291
+ * run promotion cycle.
292
+ *
293
+ * @param userTexts Array of user message texts from the session
294
+ * @param sessionId Current session ID
295
+ * @param projectId Current project ID
296
+ * @param maxStore Max memories to store (default 3)
297
+ */
298
+ async function processSessionEnd(userTexts, sessionId, projectId, maxStore = 3) {
299
+ let stored = 0;
300
+ let promoted = 0;
301
+ try {
302
+ // Create episode
303
+ const outcomeStorage = outcome_storage_1.OutcomeStorage.getInstance();
304
+ const episodeId = outcomeStorage.createEpisode({
305
+ project_id: projectId,
306
+ session_id: sessionId,
307
+ source: 'session-end',
308
+ });
309
+ // Filter to classifiable texts
310
+ const classifiable = userTexts
311
+ .filter(t => t.length >= 10 && t.length <= 2000)
312
+ .slice(-6); // last 6 entries
313
+ if (classifiable.length > 0) {
314
+ const results = await (0, shared_1.classifyBatch)(classifiable);
315
+ for (const result of results) {
316
+ if (stored >= maxStore)
317
+ break;
318
+ if (!result)
319
+ continue;
320
+ if (result.extract.length < 10 || result.extract.length > 200)
321
+ continue;
322
+ if ((result.type === 'correction' || result.type === 'preference') && result.confidence < 0.7)
323
+ continue;
324
+ if (result.confidence < 0.6)
325
+ continue;
326
+ const existing = (0, shared_1.searchExisting)(result.extract.substring(0, 100));
327
+ if ((0, shared_1.isDuplicate)(result.extract, existing))
328
+ continue;
329
+ (0, shared_1.storeMemory)(result.extract, result.type, projectId, result.confidence);
330
+ stored++;
331
+ }
332
+ }
333
+ // Update episode
334
+ outcomeStorage.updateEpisode(episodeId, {
335
+ outcome_type: stored > 0 ? 'success' : 'unclear',
336
+ severity: 'low',
337
+ outcome_summary: `Session end: ${stored} memories captured`,
338
+ });
339
+ // Run promotion cycle
340
+ try {
341
+ const { PromotionEngine } = await Promise.resolve().then(() => __importStar(require('../services/promotion-engine')));
342
+ const result = PromotionEngine.getInstance().runCycle(projectId);
343
+ promoted = result.promoted;
344
+ }
345
+ catch {
346
+ // Non-critical
347
+ }
348
+ // Prune old data
349
+ try {
350
+ outcomeStorage.pruneOldData();
351
+ }
352
+ catch {
353
+ // Non-critical
354
+ }
355
+ }
356
+ catch (err) {
357
+ logFn('event-processor', `processSessionEnd error: ${(0, shared_1.safeErrorMessage)(err)}`);
358
+ }
359
+ return { stored, promoted };
360
+ }
361
+ // --- Pre-Compact Processing ---
362
+ /**
363
+ * Aggressive memory capture before context compression.
364
+ * Batch classifies user texts with lower confidence threshold.
365
+ *
366
+ * @param userTexts Array of user message texts about to be compacted
367
+ * @param sessionId Current session ID
368
+ * @param maxStore Max memories to store (default 5)
369
+ */
370
+ async function processPreCompact(userTexts, sessionId, maxStore = 5) {
371
+ let stored = 0;
372
+ try {
373
+ const classifiable = userTexts
374
+ .filter(t => t.length >= 10 && t.length <= 2000);
375
+ if (classifiable.length === 0)
376
+ return 0;
377
+ const results = await (0, shared_1.classifyBatch)(classifiable);
378
+ for (const result of results) {
379
+ if (stored >= maxStore)
380
+ break;
381
+ if (!result)
382
+ continue;
383
+ if (result.extract.length < 10 || result.extract.length > 200)
384
+ continue;
385
+ if (result.confidence < 0.6)
386
+ continue;
387
+ const existing = (0, shared_1.searchExisting)(result.extract.substring(0, 100));
388
+ if ((0, shared_1.isDuplicate)(result.extract, existing))
389
+ continue;
390
+ const prefixed = `[PreCompact] ${result.extract}`;
391
+ (0, shared_1.storeMemory)(prefixed, result.type, undefined, result.confidence);
392
+ stored++;
393
+ logFn('event-processor', `PreCompact captured ${result.type}: ${result.extract.substring(0, 80)}`);
394
+ }
395
+ }
396
+ catch (err) {
397
+ logFn('event-processor', `processPreCompact error: ${(0, shared_1.safeErrorMessage)(err)}`);
398
+ }
399
+ return stored;
400
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.19.0",
4
- "description": "Persistent memory for Claude Code with native Skills integration, automatic capture, failure learning, and project scoping via MCP server",
3
+ "version": "0.20.1",
4
+ "description": "Persistent memory for Claude Code and Pi with native Skills integration, automatic capture, failure learning, and project scoping",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "claude-recall": "dist/cli/claude-recall-cli.js"
@@ -9,12 +9,17 @@
9
9
  "files": [
10
10
  "dist/",
11
11
  ".claude/",
12
+ "skills/",
12
13
  "scripts/postinstall.js",
13
14
  "scripts/uninstall.js",
14
15
  "README.md",
15
16
  "LICENSE",
16
17
  "docs/"
17
18
  ],
19
+ "pi": {
20
+ "extensions": ["./dist/pi/extension.js"],
21
+ "skills": ["./skills/*.md"]
22
+ },
18
23
  "directories": {
19
24
  "doc": "docs",
20
25
  "test": "tests"
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: memory-management
3
+ description: Persistent memory management — load rules at session start, store corrections and preferences automatically
4
+ ---
5
+
6
+ # Memory Management
7
+
8
+ You have access to persistent memory tools from Claude Recall. Use them to learn from the user over time.
9
+
10
+ ## When starting a task
11
+
12
+ Call `recall_load_rules` to load stored preferences, corrections, failure lessons, and devops rules. Apply relevant rules to your work and cite them inline: `(applied from memory: <rule summary>)`.
13
+
14
+ ## When the user corrects you
15
+
16
+ If the user says things like "no, always use X" or "don't do Y", call `recall_store_memory` with:
17
+ - `content`: the correction in clear, reusable language
18
+ - `metadata.type`: `"correction"`
19
+
20
+ ## When you learn a preference
21
+
22
+ If the user states a preference ("I prefer tabs", "use functional style"), call `recall_store_memory` with:
23
+ - `content`: the preference
24
+ - `metadata.type`: `"preference"`
25
+
26
+ ## When something fails
27
+
28
+ If a command fails or you need to backtrack, the failure is captured automatically. You don't need to store it manually.
29
+
30
+ ## Before making decisions
31
+
32
+ Call `recall_search_memory` with relevant keywords to check for existing project knowledge before choosing approaches, tools, or conventions.