kyp-mem 0.5.0 → 0.6.0

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/README.md CHANGED
@@ -1,288 +1,137 @@
1
- # KYP-MEM — Know Your Project Memory
1
+ # KYP-MEM
2
2
 
3
- **Persistent memory for AI coding agents.** Your AI agent forgets everything between sessions. KYP-MEM fixes that.
4
3
 
5
- KYP-MEM is an MCP server that gives your AI agent a knowledge base and session memory. It remembers your architecture, past bugs, decisions, and what happened last session — so you never repeat yourself.
4
+ > Claude that remembers your conversations AND understands your project.
6
5
 
7
- ---
6
+ KYP-MEM gives AI coding agents two-layer memory:
8
7
 
9
- ## How It Works
10
-
11
- KYP-MEM gives your agent two types of memory:
12
-
13
- ### Knowledge Base — long-term project memory
14
-
15
- Structured markdown notes organized by project. Architecture docs, API references, known bugs, key decisions, setup guides. The agent reads these before doing any work and updates them as it learns.
16
-
17
- ```
18
- MyProject/
19
- ├── Knowledge.md # Architecture, bugs, decisions, notes
20
- ├── API.md # Endpoints and contracts
21
- ├── Setup.md # Environment setup guide
22
- └── Sessions/
23
- ```
24
-
25
- ### Session Memory — what happened each session
26
-
27
- Every coding session is automatically captured — files changed, commands run, prompts used. These notes are embedded into a vector database for semantic search, so the agent can find past work by meaning, not just keywords.
8
+ - **Session Memory (Episodic)** → remembers what happened across coding sessions
28
9
 
29
- ```
30
- MyProject/Sessions/
31
- ├── 2026-05-12_143022.md # "Fixed auth bug, found rate limiter issue"
32
- ├── 2026-05-12_091544.md # "Investigated flaky tests — race condition"
33
- └── 2026-05-11_162301.md # "Set up CI, decided on GitHub Actions"
34
- ```
10
+ - **Project Intelligence** → understands architecture, decisions, docs, and relationships
35
11
 
36
- ### The Loop
12
+ Your AI agent stops starting from zero every day.
37
13
 
38
- ```
39
- Session Start
40
- → Agent loads project knowledge + recent sessions
41
- → Agent is grounded: knows architecture, past bugs, last session's next steps
14
+ ## Example
42
15
 
43
- During Work
44
- → Agent hits a bug → searches session memory → finds it was already investigated
45
- → Agent makes a decision → updates Knowledge.md so future sessions know
16
+ User:
46
17
 
47
- Session End
48
- → Hooks auto-capture everything into a structured session note
49
- → Note is embedded into the vector store for future semantic search
50
- ```
18
+ > "Why did we move from REST to Kafka?"
51
19
 
52
- ---
20
+ Claude:
53
21
 
54
- ## Install
22
+ > "In the May 12 session, we found the REST pipeline couldn't handle peak trading volume.
23
+ > We decided to migrate to Kafka for async processing.
24
+ > See [[Architecture Decisions]] and [[Event Pipeline]]."
55
25
 
56
- ```bash
57
- pip install kyp-mem
58
- ```
59
-
60
- ## Setup
26
+ By intercepting the prompt, KYP-MEM automatically provided the agent with:
27
+ - The vectorized semantic search results of past session logs.
28
+ - The relevant markdown files from the project knowledge base.
61
29
 
62
- ```bash
63
- kyp-mem init # Choose where to store your vault
64
- kyp-mem setup-claude # Register MCP server with Claude Code
65
- kyp-mem install-hooks # Enable automatic session capture
66
- ```
30
+ ## How It Works
67
31
 
68
- Restart Claude Code. KYP-MEM runs automatically with 14 tools available to the agent.
32
+ KYP-MEM operates as a Model Context Protocol (MCP) server that runs silently in the background, integrating directly with Claude Code.
69
33
 
70
- ### Quick Setup (one command)
34
+ ### 1. Episodic Memory (Sessions)
71
35
 
72
- ```bash
73
- claude mcp add -s user -e KYP_VAULT="$HOME/.kyp-mem/vault" kyp-mem -- kyp-mem serve
74
- ```
36
+ Every coding session is automatically captured with full context:
75
37
 
76
- ### Enable for All Projects
38
+ - User prompts (what was asked)
39
+ - File reads with content (what was found)
40
+ - File edits with diffs (what changed and why)
41
+ - Command outputs (what happened)
77
42
 
78
- ```bash
79
- kyp-mem setup-claude --global
80
- kyp-mem install-hooks --global
81
- ```
43
+ At session end, Claude Sonnet synthesizes raw activity into a structured summary with **Summary**, **Investigated**, **Learned**, **Completed**, and **Next Steps** sections. Sessions are semantically searchable via ChromaDB vector embeddings.
82
44
 
83
- ---
45
+ ### 2. Project Intelligence (Vault)
84
46
 
85
- ## What the Agent Does Automatically
47
+ KYP-MEM maintains structured project knowledge as Markdown files with `[[wikilinks]]`:
86
48
 
87
- KYP-MEM embeds behavioral instructions directly into tool descriptions. The agent follows these rules without any user action:
49
+ - Architecture docs, API references, setup guides
50
+ - Known issues, decision history, linked concepts
88
51
 
89
- 1. **Session start** loads project knowledge + recent session history
90
- 2. **Before investigating bugs** — searches session memory first to avoid duplicate work
91
- 3. **Before making decisions** — checks if a prior session already decided this
92
- 4. **After fixing or learning something** — updates Knowledge.md for future sessions
93
- 5. **Never hallucinates** — if it's not in the knowledge base, it says so
52
+ The agent searches this on-demand via `kyp_search` when it needs project context.
94
53
 
95
- No prompting needed. The agent reads the tool descriptions and follows the protocol.
54
+ ### How It All Connects
96
55
 
97
- ---
56
+ 1. **Session Start:** Recent session summaries are injected automatically — the agent knows what happened last time.
57
+ 2. **During Work:** Hooks capture tool activity (reads, edits, commands) with actual content, not just file names.
58
+ 3. **Session End:** Sonnet synthesizes a rich, semantic summary and saves it to the vault + vector DB.
59
+ 4. **Future Sessions:** The agent can search past sessions semantically or look up project knowledge on demand.
98
60
 
99
- ## Automatic Session Capture
61
+ ## Installation
100
62
 
101
63
  ```bash
102
- kyp-mem install-hooks
64
+ npm install -g kyp-mem
103
65
  ```
104
66
 
105
- Installs three Claude Code hooks:
106
-
107
- - **UserPromptSubmit** — captures every prompt you send to the agent
108
- - **PostToolUse** — captures file edits, writes, and shell commands in real-time
109
- - **Stop** — when the session ends, compiles all activity into a structured note
110
-
111
- Each session note looks like this:
112
-
113
- ```markdown
114
- # Session 2026-05-12_143022
115
-
116
- **Project:** MyProject
117
- **Actions:** 15 total, 12 substantive
67
+ That's it. The postinstall script automatically:
118
68
 
119
- ## Summary
120
- Fixed auth bug, refactored token refresh logic.
69
+ 1. Installs the Python package
70
+ 2. Creates the default vault at `~/.kyp-mem/vault`
71
+ 3. Registers the MCP server with Claude Code
72
+ 4. Installs session capture hooks
121
73
 
122
- ## PROMPTS
123
- ### 1. [14:25:01]
124
- > fix the token refresh bug in auth.py
74
+ Restart Claude Code and you're ready to go.
125
75
 
126
- ### 2. [14:30:22]
127
- > also add retry logic for expired tokens
76
+ ### Requirements
128
77
 
129
- ## INVESTIGATED
130
- - grep for "token expired" across auth module
131
- - read OAuth provider docs
78
+ - Node.js 18+
79
+ - Python 3.10+
80
+ - Claude Code CLI
81
+ - Anthropic API key (for session summarization with Sonnet)
132
82
 
133
- ## LEARNED
134
- - Refresh tokens expire after 30 days, not 90
83
+ ### Custom Vault Path
135
84
 
136
- ## COMPLETED
137
- - Fixed token refresh in auth.py
138
- - Added retry logic for expired tokens
85
+ If you want to store your vault somewhere other than `~/.kyp-mem/vault`:
139
86
 
140
- ## NEXT STEPS
141
- - Add integration test for token refresh flow
87
+ ```bash
88
+ kyp-mem init # Interactive prompt to choose vault location
142
89
  ```
143
90
 
144
- Sessions with fewer than 3 substantive actions are automatically skipped.
145
-
146
- ---
147
-
148
- ## Semantic Search
149
-
150
- Session notes are embedded into a vector database (ChromaDB). This enables search by meaning:
91
+ ## The Agent's Workflow
151
92
 
152
- - Searching **"authentication failing"** finds sessions about "login bug" and "OAuth token expiry"
153
- - Searching **"deploy process"** finds sessions about "CI pipeline setup" and "release workflow"
93
+ KYP-MEM embeds behavioral instructions directly into its tools. Without any prompting from you, the agent will automatically:
154
94
 
155
- The agent doesn't need exact keywords it finds semantically related past work.
156
-
157
- ---
95
+ 1. **Load Context:** On session start, it loads recent session summaries so it knows what happened last time.
96
+ 2. **Search Before Acting:** Before investigating bugs or making decisions, it searches past sessions to avoid repeating work.
97
+ 3. **Persist Knowledge:** After fixing a bug or making a decision, it updates the project's knowledge base for future sessions.
158
98
 
159
99
  ## Web UI
160
100
 
101
+ Browse your knowledge graph, view session timelines, and see semantic relationships visually.
102
+
161
103
  ```bash
162
104
  kyp-mem ui
163
105
  ```
164
-
165
- Opens at `localhost:3333` with two panels:
166
-
167
- **Session Memory** — semantic search across all sessions, grouped by project with timestamps
168
-
169
- **Knowledge Base** — file tree with folders, notes, tags. Full-text search, tag filtering, quick switcher (`Cmd+O`)
170
-
171
- **Editor** — rendered markdown with `[[wikilink]]` navigation, inline editing
172
-
173
- **Graph** — knowledge graph showing connections between notes, backlinks, and related content
174
-
175
- ---
176
-
177
- ## MCP Tools (14 total)
178
-
179
- ### Agent Behavior
180
-
181
- | Tool | Purpose |
182
- |------|---------|
183
- | `____kyp_instructions` | Embeds behavioral rules the agent follows automatically |
184
- | `kyp_project_context` | Loads project knowledge + recent sessions at session start |
185
-
186
- ### Knowledge Base
187
-
188
- | Tool | Purpose |
189
- |------|---------|
190
- | `kyp_list` | Browse folders and notes |
191
- | `kyp_read` | Read a note (summary by default, `full=True` for complete) |
192
- | `kyp_write` | Create or update a note with tags and `[[wikilinks]]` |
193
- | `kyp_delete` | Delete a note |
194
- | `kyp_search` | Full-text search with optional tag filter |
195
- | `kyp_tags` | List all tags or filter notes by tag |
196
- | `kyp_related` | Find related notes by links, tags, proximity |
197
- | `kyp_recent` | Recently modified notes |
198
- | `kyp_stats` | Vault statistics |
199
-
200
- ### Session Memory
201
-
202
- | Tool | Purpose |
203
- |------|---------|
204
- | `kyp_session_search` | Semantic search across all session logs |
205
- | `kyp_session_create` | Manually create a session note |
206
- | `kyp_sessions` | List sessions by project |
207
-
208
- ---
209
-
210
- ## Adding to Your Project
211
-
212
- Add a `CLAUDE.md` to any project root:
213
-
214
- ```markdown
215
- # Project Memory
216
-
217
- ## Session Start (MANDATORY)
218
- Call `kyp_project_context("PROJECT_NAME")` at the start of every session to load:
219
- - Project knowledge base (architecture, bugs, decisions)
220
- - Recent session history (what was done, what's next)
221
-
222
- ## During Work
223
- - Before investigating bugs: `kyp_session_search("error or symptom")`
224
- - Before making decisions: `kyp_session_search("topic")`
225
- - After fixing bugs: update Knowledge.md via `kyp_write`
226
- - After decisions: add to Key Decisions in Knowledge.md
227
-
228
- ## Rules
229
- - Never hallucinate project details — check the knowledge base first.
230
- - Use [[wikilinks]] to connect related notes.
231
- - Sessions are captured automatically — no manual logging needed.
232
- ```
233
-
234
- A template is available at `templates/CLAUDE.md.template`.
235
-
236
- ---
237
-
238
- ## Vault Structure
239
-
240
- ```
241
- ~/.kyp-mem/
242
- ├── config.json # Vault path configuration
243
- ├── chroma/ # Vector database for semantic search
244
- └── vault/
245
- ├── ProjectA/
246
- │ ├── Knowledge.md # Ground truth: architecture, bugs, decisions
247
- │ ├── API.md
248
- │ └── Sessions/
249
- │ ├── 2026-05-12_143022.md
250
- │ └── 2026-05-11_091544.md
251
- ├── ProjectB/
252
- │ ├── Knowledge.md
253
- │ └── Sessions/
254
- └── ...
255
- ```
256
-
257
- Notes use YAML frontmatter for tags and `[[wikilinks]]` for cross-references:
258
-
259
- ```markdown
260
- ---
261
- tags: [trading, config]
262
- created: 2026-05-12
263
- ---
264
- # Configuration
265
- Settings are in `HedgeConfig`. See [[Risk Management]] for limits.
266
- ```
267
-
268
- ---
106
+ *Opens at `localhost:3333`.*
269
107
 
270
108
  ## CLI Commands
271
109
 
272
- | Command | What it does |
110
+ | Command | Description |
273
111
  |---------|-------------|
274
- | `kyp-mem init` | First-time setup choose vault location |
112
+ | `kyp-mem init` | Choose vault location (default: `~/.kyp-mem/vault`) |
275
113
  | `kyp-mem setup-claude` | Register MCP server with Claude Code |
276
- | `kyp-mem setup-claude --global` | Register globally (all projects) |
277
114
  | `kyp-mem install-hooks` | Enable automatic session capture |
278
- | `kyp-mem install-hooks --remove` | Remove session capture hooks |
279
115
  | `kyp-mem serve` | Start MCP server (stdio, used by the agent) |
280
- | `kyp-mem ui` | Open web UI at localhost:3333 |
116
+ | `kyp-mem ui` | Open the local web UI |
281
117
  | `kyp-mem stats` | Print vault statistics |
282
- | `kyp-mem tree` | Print vault tree |
283
- | `kyp-mem doctor` | Check installation health |
118
+ | `kyp-mem tree` | Print vault file tree |
119
+ | `kyp-mem config` | View or set configuration (e.g. `kyp-mem config session_model`) |
120
+ | `kyp-mem doctor` | Check installation and configuration health |
121
+ | `kyp-mem uninstall` | Remove hooks and MCP server from Claude Code |
122
+
123
+ ## Uninstall
284
124
 
285
- ---
125
+ ```bash
126
+ # Remove from Claude Code (keeps your vault data)
127
+ kyp-mem uninstall
128
+
129
+ # Remove from Claude Code AND delete all data
130
+ kyp-mem uninstall --purge
131
+
132
+ # Remove the npm package
133
+ npm uninstall -g kyp-mem
134
+ ```
286
135
 
287
136
  ## License
288
137
 
package/bin/cli.mjs CHANGED
@@ -84,22 +84,29 @@ if (args[0] === "hook") {
84
84
  if (tool.includes("kyp-mem") || tool.includes("kyp_mem")) process.exit(0);
85
85
 
86
86
  const input = data.tool_input || {};
87
+ const rawResp = data.tool_response || "";
88
+ const resp = (typeof rawResp === "string" ? rawResp : JSON.stringify(rawResp)).slice(0, 2000);
87
89
  const entry = { ts: new Date().toISOString(), tool, cwd: process.cwd() };
88
90
 
89
91
  if (tool === "Edit" || tool === "Write") {
90
92
  entry.file = input.file_path || "";
91
93
  entry.action = tool === "Edit" ? "edit" : "create";
94
+ if (input.old_string) entry.old_string = input.old_string.slice(0, 500);
95
+ if (input.new_string) entry.new_string = input.new_string.slice(0, 500);
92
96
  } else if (tool === "Read") {
93
97
  entry.file = input.file_path || "";
94
98
  entry.action = "read";
99
+ entry.content = resp;
95
100
  } else if (tool === "Bash") {
96
101
  entry.command = (input.command || "").slice(0, 300);
97
102
  entry.action = "command";
103
+ entry.output = resp;
98
104
  } else {
99
105
  entry.action = "other";
100
106
  entry.detail = tool;
101
107
  }
102
108
 
109
+ entry.response_chars = (typeof rawResp === "string" ? rawResp : JSON.stringify(rawResp)).length;
103
110
  mkdirSync(sessionDir, { recursive: true });
104
111
  appendFileSync(sessionFile, JSON.stringify(entry) + "\n");
105
112
  } catch (_) {
@@ -118,6 +125,16 @@ if (args[0] === "hook") {
118
125
  process.exit(0);
119
126
  }
120
127
 
128
+ if (hookType === "session-start") {
129
+ const py = findPython();
130
+ if (py) {
131
+ const [cmd, pre] = py;
132
+ const r = run(cmd, [...pre, "-m", "kyp_mem.cli", "hook", "session-start"], "inherit");
133
+ process.exit(r.status ?? 0);
134
+ }
135
+ process.exit(0);
136
+ }
137
+
121
138
  console.error("Unknown hook type:", hookType);
122
139
  process.exit(1);
123
140
  }
package/bin/install.mjs CHANGED
@@ -1,12 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "child_process";
4
+ import { mkdirSync } from "fs";
5
+ import { homedir } from "os";
4
6
  import { fileURLToPath } from "url";
5
- import { dirname, resolve } from "path";
7
+ import { dirname, join, resolve } from "path";
6
8
 
7
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
10
  const root = resolve(__dirname, "..");
9
11
 
12
+ const G = "\x1b[32m";
13
+ const Y = "\x1b[33m";
14
+ const C = "\x1b[36m";
15
+ const D = "\x1b[90m";
16
+ const R = "\x1b[0m";
17
+
10
18
  function run(command, args, options = {}) {
11
19
  return spawnSync(command, args, {
12
20
  cwd: root,
@@ -53,25 +61,68 @@ if (process.env.KYP_MEM_SKIP_PYTHON_INSTALL === "1") {
53
61
  const python = findPython();
54
62
 
55
63
  if (!python) {
56
- console.log(" \x1b[33m!\x1b[0m Python 3 was not found.");
57
- console.log(" \x1b[33m!\x1b[0m Install Python 3.10+ and run: python3 -m pip install --user .");
64
+ console.log(` ${Y}!${R} Python 3 was not found.`);
65
+ console.log(` ${Y}!${R} Install Python 3.10+ and run: python3 -m pip install --user .`);
58
66
  process.exit(0);
59
67
  }
60
68
 
61
69
  const [pythonCommand, pythonPrefixArgs] = python;
62
70
 
63
- console.log(" Installing kyp-mem Python package...");
71
+ // Step 1: Install Python package
72
+ console.log(` Installing kyp-mem Python package...`);
64
73
 
65
- const result = run(
74
+ const pipResult = run(
66
75
  pythonCommand,
67
76
  [...pythonPrefixArgs, "-m", "pip", "install", "--user", "."],
68
77
  { stdio: "inherit" },
69
78
  );
70
79
 
71
- if (result.status === 0) {
72
- console.log(" \x1b[32m✓\x1b[0m kyp-mem installed successfully");
73
- } else {
74
- console.log(" \x1b[33m!\x1b[0m Could not auto-install the Python package.");
75
- console.log(` \x1b[33m!\x1b[0m Run manually from ${root}:`);
80
+ if (pipResult.status !== 0) {
81
+ console.log(` ${Y}!${R} Could not auto-install the Python package.`);
82
+ console.log(` ${Y}!${R} Run manually from ${root}:`);
76
83
  console.log(" python3 -m pip install --user .");
84
+ process.exit(0);
85
+ }
86
+
87
+ console.log(` ${G}✓${R} Python package installed`);
88
+
89
+ // Step 2: Create default vault directory
90
+ const vaultDir = join(homedir(), ".kyp-mem", "vault");
91
+ try {
92
+ mkdirSync(vaultDir, { recursive: true });
93
+ console.log(` ${G}✓${R} Vault ready at ${D}${vaultDir}${R}`);
94
+ } catch (_) {
95
+ console.log(` ${Y}!${R} Could not create vault at ${vaultDir}`);
77
96
  }
97
+
98
+ // Step 3: Register MCP server with Claude Code (global)
99
+ const setupResult = run(
100
+ pythonCommand,
101
+ [...pythonPrefixArgs, "-m", "kyp_mem.cli", "setup-claude", "--global"],
102
+ { stdio: "inherit" },
103
+ );
104
+
105
+ if (setupResult.status === 0) {
106
+ console.log(` ${G}✓${R} MCP server registered with Claude Code`);
107
+ } else {
108
+ console.log(` ${Y}!${R} Could not register MCP server — run manually: ${C}kyp-mem setup-claude --global${R}`);
109
+ }
110
+
111
+ // Step 4: Install hooks (global)
112
+ const hooksResult = run(
113
+ pythonCommand,
114
+ [...pythonPrefixArgs, "-m", "kyp_mem.cli", "install-hooks", "--global"],
115
+ { stdio: "inherit" },
116
+ );
117
+
118
+ if (hooksResult.status === 0) {
119
+ console.log(` ${G}✓${R} Session capture hooks installed`);
120
+ } else {
121
+ console.log(` ${Y}!${R} Could not install hooks — run manually: ${C}kyp-mem install-hooks --global${R}`);
122
+ }
123
+
124
+ console.log();
125
+ console.log(` ${C}KYP-MEM${R} is ready! Restart Claude Code to activate.`);
126
+ console.log(` ${D}Vault: ${vaultDir}${R}`);
127
+ console.log(` ${D}To customize vault path: kyp-mem init${R}`);
128
+ console.log();
package/kyp_mem/cli.py CHANGED
@@ -41,6 +41,8 @@ def main():
41
41
  help="Add hooks to global ~/.claude/settings.json (default: project)")
42
42
  ih.add_argument("--remove", action="store_true", help="Remove KYP-MEM hooks")
43
43
 
44
+ un = subparsers.add_parser("uninstall", help="Remove KYP-MEM from Claude Code (hooks + MCP server)")
45
+ un.add_argument("--purge", action="store_true", help="Also delete vault data and config at ~/.kyp-mem")
44
46
  subparsers.add_parser("doctor", help="Check installation and config health")
45
47
 
46
48
  cfg_parser = subparsers.add_parser("config", help="Get or set configuration values")
@@ -77,6 +79,8 @@ def main():
77
79
  _run_install_hooks(global_config=args.global_config, remove=args.remove)
78
80
  elif args.command == "config":
79
81
  _run_config(args.key, args.value)
82
+ elif args.command == "uninstall":
83
+ _run_uninstall(purge=args.purge)
80
84
  elif args.command == "doctor":
81
85
  _run_doctor()
82
86
  elif args.command == "hook":
@@ -267,7 +271,46 @@ def _write_legacy_claude_settings(
267
271
  return settings_path
268
272
 
269
273
 
270
- def _run_install_hooks(global_config: bool = False, remove: bool = False):
274
+ def _run_uninstall(purge: bool = False):
275
+ from .config import CONFIG_DIR
276
+
277
+ print()
278
+ print(f" {C}KYP-MEM{R} — Uninstall")
279
+ print()
280
+
281
+ # Remove hooks from global settings
282
+ _run_install_hooks(global_config=True, remove=True)
283
+
284
+ # Remove MCP server from Claude Code
285
+ claude_bin = shutil.which("claude")
286
+ if claude_bin:
287
+ subprocess.run(
288
+ [claude_bin, "mcp", "remove", "-s", "user", "kyp-mem"],
289
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True,
290
+ )
291
+ print(f" {G}✓{R} MCP server removed from Claude Code")
292
+ else:
293
+ print(f" {Y}!{R} 'claude' CLI not found — remove kyp-mem MCP server manually")
294
+
295
+ if purge:
296
+ import shutil as sh
297
+ if CONFIG_DIR.exists():
298
+ sh.rmtree(CONFIG_DIR)
299
+ print(f" {G}✓{R} Deleted {CONFIG_DIR} (vault, config, sessions)")
300
+ else:
301
+ print(f" {D} {CONFIG_DIR} does not exist{R}")
302
+
303
+ print()
304
+ print(f" To finish, remove the npm package:")
305
+ print(f" {Y}npm uninstall -g kyp-mem{R}")
306
+ print()
307
+ if not purge:
308
+ print(f" {D}Your vault data at {CONFIG_DIR} was kept.{R}")
309
+ print(f" {D}To delete it too: kyp-mem uninstall --purge{R}")
310
+ print()
311
+
312
+
313
+
271
314
  mcp_command, _ = _get_mcp_command()
272
315
 
273
316
  if global_config:
package/kyp_mem/config.py CHANGED
@@ -34,4 +34,4 @@ def get_vault_path() -> str:
34
34
 
35
35
  def get_session_model() -> str:
36
36
  config = load_config()
37
- return config.get("session_model", "claude-haiku-4-5-20251001")
37
+ return config.get("session_model", "claude-sonnet-4-6")
package/kyp_mem/hooks.py CHANGED
@@ -74,7 +74,7 @@ def _record_injection(project, chars):
74
74
 
75
75
 
76
76
  def handle_session_start():
77
- """Inject project context into the conversation at session start."""
77
+ """Inject recent session memory into the conversation at session start."""
78
78
  sys.stdin.read()
79
79
 
80
80
  cwd = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
@@ -90,58 +90,31 @@ def handle_session_start():
90
90
  if not project_notes:
91
91
  return
92
92
 
93
- parts = [f"# [kyp-mem] {project_name} — Project Context"]
94
- parts.append(f"Vault: {get_vault_path()}")
95
- parts.append("")
96
-
97
- knowledge_path = f"{project_name}/Knowledge.md"
98
- knowledge = vault.read(knowledge_path)
99
- if knowledge:
100
- parts.append("## Knowledge")
101
- content = knowledge.content
102
- timeline_idx = content.find("## Timeline")
103
- if timeline_idx > 0:
104
- content = content[:timeline_idx].strip()
105
- if len(content) > 2000:
106
- parts.append(content[:2000] + "\n...")
107
- else:
108
- parts.append(content)
109
- parts.append("")
110
-
111
- other_notes = sorted(
112
- p for p in project_notes
113
- if "/Sessions/" not in p and p != knowledge_path
114
- )
115
- if other_notes:
116
- parts.append("## Project Notes")
117
- for p in other_notes:
118
- note = vault.index.notes.get(p)
119
- title = note.title if note else p
120
- tags = f" [{', '.join(note.tags)}]" if note and note.tags else ""
121
- parts.append(f"- {title} ({p}){tags}")
122
- parts.append("")
123
-
124
93
  sessions = sorted(
125
94
  (p for p in project_notes if "/Sessions/" in p),
126
95
  reverse=True,
127
96
  )[:3]
128
- if sessions:
129
- parts.append(f"## Recent Sessions (last {len(sessions)})")
130
- for sp in sessions:
131
- note = vault.read(sp)
132
- if not note:
133
- continue
134
- parts.append(f"### {note.title}")
135
- content = note.content
136
- timeline_idx = content.find("## Timeline")
137
- if timeline_idx > 0:
138
- content = content[:timeline_idx].strip()
139
- if len(content) > 300:
140
- content = content[:300] + "..."
141
- parts.append(content)
142
- parts.append("")
97
+ if not sessions:
98
+ return
99
+
100
+ parts = [f"# [kyp-mem] {project_name} — Recent Sessions"]
101
+ parts.append(f"Use `kyp_search` or `kyp_project_context` for architecture/project knowledge on demand.")
102
+ parts.append("")
143
103
 
144
- parts.append("Use `kyp_project_context` for full details. Use `kyp_session_search` to search past sessions.")
104
+ parts.append(f"## Last {len(sessions)} Sessions")
105
+ for sp in sessions:
106
+ note = vault.read(sp)
107
+ if not note:
108
+ continue
109
+ parts.append(f"### {note.title}")
110
+ content = note.content
111
+ timeline_idx = content.find("## TIMELINE")
112
+ if timeline_idx < 0:
113
+ timeline_idx = content.find("## Timeline")
114
+ if timeline_idx > 0:
115
+ content = content[:timeline_idx].strip()
116
+ parts.append(content)
117
+ parts.append("")
145
118
 
146
119
  output = "\n".join(parts)
147
120
  try:
@@ -195,13 +168,25 @@ def handle_post_tool_use():
195
168
  cwd = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
196
169
  entry["cwd"] = cwd
197
170
 
198
- # Measure response size for token economics
199
171
  tool_response = data.get("tool_response", "")
200
- response_chars = len(str(tool_response)) if tool_response else 0
172
+ if isinstance(tool_response, str):
173
+ resp_str = tool_response
174
+ elif tool_response:
175
+ resp_str = json.dumps(tool_response)
176
+ else:
177
+ resp_str = ""
178
+ response_chars = len(resp_str)
179
+ resp_truncated = resp_str[:2000]
201
180
 
202
181
  if tool_name == "Edit":
203
182
  entry["action"] = "edit"
204
183
  entry["file"] = tool_input.get("file_path", "")
184
+ old_s = tool_input.get("old_string", "")
185
+ new_s = tool_input.get("new_string", "")
186
+ if old_s:
187
+ entry["old_string"] = old_s[:500]
188
+ if new_s:
189
+ entry["new_string"] = new_s[:500]
205
190
  elif tool_name == "Write":
206
191
  entry["action"] = "create"
207
192
  entry["file"] = tool_input.get("file_path", "")
@@ -214,10 +199,12 @@ def handle_post_tool_use():
214
199
  except OSError:
215
200
  pass
216
201
  entry["response_chars"] = response_chars
202
+ entry["content"] = resp_truncated
217
203
  elif tool_name == "Bash":
218
204
  entry["action"] = "command"
219
205
  entry["command"] = tool_input.get("command", "")
220
206
  entry["response_chars"] = response_chars
207
+ entry["output"] = resp_truncated
221
208
  else:
222
209
  return
223
210
 
@@ -426,35 +413,41 @@ def _summarize_with_claude(raw_note, project_name):
426
413
  model = get_session_model()
427
414
  client = anthropic.Anthropic()
428
415
 
429
- prompt = f"""Summarize this coding session for "{project_name}" in plain English. A future AI agent will read this to understand what happenedwrite for that audience.
416
+ prompt = f"""Rewrite this raw coding session into a structured summary. A future AI agent reads this to pick up where you left off be precise and technical.
430
417
 
431
- You have: user prompts (what was asked), a timeline of file edits/reads/commands (what happened), and raw section data. Synthesize these into a coherent narrative.
418
+ You have: user prompts (the objectives), a timeline of file edits/reads/commands, and raw section data. Synthesize into a dense, specific narrative.
432
419
 
433
- Rules:
434
- - Summary: 2-3 sentences. State the objective (from prompts), what was done, and the outcome. Be specific: "Fixed navigation bug where clicking sessions broke the back button" not "Modified files and ran commands."
435
- - INVESTIGATED: What was explored and WHY. "Examined the session hook pipeline to understand why summaries were empty" not "Searched for `session-view`". Max 4 bullets.
436
- - LEARNED: Insights or discoveries. "The config CLI command was defined but never wired to the dispatcher" not "Investigated and modified: `cli.py`". Max 4 bullets.
437
- - COMPLETED: Concrete deliverables. "Added AI-powered session summarization using Claude Haiku" not "Modified `hooks.py`". Max 5 bullets.
438
- - NEXT STEPS: What should happen next session. Infer from context — unfinished work, unfixed bugs, natural follow-ups. Max 3 bullets.
420
+ ## Format rules
439
421
 
440
- NEVER include raw grep patterns, CSS class names, file paths, or command output. Write like you're telling a teammate what you did today.
422
+ - **Summary**: 1-2 sentences. State what was done and the outcome. Include error messages, feature names, or bug descriptions verbatim. Example: 'Debugged and fixed "Unknown hook type: session-start" error in kyp-mem; cleaned repository of session-specific files and prepared for release'
423
+ - **INVESTIGATED**: One dense paragraph (not bullets). List specific files, paths, and systems examined with semicolons. Include full relative paths and module names. Example: 'Global and project-level Claude Code settings.json; kyp-mem Python CLI source (cli.py, hooks.py); installed Node.js wrapper at /opt/homebrew/lib/node_modules/kyp-mem/bin/cli.mjs; hook dispatcher implementation; git commit history'
424
+ - **LEARNED**: One dense paragraph (not bullets). State technical insights with specifics — what was discovered, why it matters, root causes. Include version numbers, commit hashes, config values, error messages. Example: 'kyp-mem uses a Node.js wrapper with a "hook fast path" dispatcher that only handled 3 hook types (user-prompt, post-tool-use, stop); session-start was missing despite being implemented in Python backend'
425
+ - **COMPLETED**: One dense paragraph (not bullets). List concrete deliverables with specifics — file names modified, features added, tests passed, counts, commit hashes. Use semicolons to separate items. Example: 'Fixed .gitignore to exclude session-specific files (CLAUDE.md, PLAN-ui-rewrite.md, templates/); removed 3 tracked files from git history; committed cleanup to main (commit f0b114e: 4 files changed, 626 deletions)'
426
+ - **NEXT STEPS**: One dense paragraph (not bullets). Concrete actionable items for the next session. Example: 'Push commit f0b114e to GitHub; publish 0.5.1 release to npm with session-start hook support'
427
+
428
+ ## Critical rules
429
+ - ALWAYS include specific file names, paths, commit hashes, error messages, and counts
430
+ - Write dense paragraphs with semicolons, NOT bullet lists
431
+ - Never be vague: "Fixed 3 files" is bad, "Fixed .gitignore, cli.mjs, and hooks.py" is good
432
+ - If a commit hash appears in the timeline, include it
433
+ - Keep each section to one paragraph max
441
434
 
442
435
  Return ONLY this format (no preamble):
443
436
 
444
437
  ## Summary
445
- <text>
438
+ <1-2 sentences>
446
439
 
447
440
  ## INVESTIGATED
448
- - <item>
441
+ <one paragraph>
449
442
 
450
443
  ## LEARNED
451
- - <item>
444
+ <one paragraph>
452
445
 
453
446
  ## COMPLETED
454
- - <item>
447
+ <one paragraph>
455
448
 
456
449
  ## NEXT STEPS
457
- - <item>
450
+ <one paragraph>
458
451
 
459
452
  Raw session data:
460
453
  {raw_note}"""
@@ -503,7 +496,7 @@ def handle_stop():
503
496
  files_created = set()
504
497
  commands = []
505
498
  prompts = []
506
- timeline = []
499
+ events = []
507
500
 
508
501
  for e in entries:
509
502
  ts_raw = e.get("ts", "")
@@ -512,69 +505,96 @@ def handle_stop():
512
505
 
513
506
  if action == "prompt":
514
507
  prompts.append({"ts": ts, "text": e.get("prompt", "")})
515
- timeline.append(f" {ts} Prompt: {e.get('prompt', '')[:60]}...")
508
+ events.append({"ts": ts, "type": "prompt", "text": e.get("prompt", "")[:500]})
516
509
  elif action == "read":
517
510
  fp = e.get("file", "")
518
511
  files_read.add(fp)
519
- timeline.append(f" {ts} — Read `{Path(fp).name}`")
512
+ content = e.get("content", "")
513
+ events.append({"ts": ts, "type": "read", "file": fp, "content": content[:1000] if content else ""})
520
514
  elif action == "edit":
521
515
  fp = e.get("file", "")
522
516
  files_edited.add(fp)
523
- timeline.append(f" {ts} — Edit `{Path(fp).name}`")
517
+ events.append({
518
+ "ts": ts, "type": "edit", "file": fp,
519
+ "old": e.get("old_string", "")[:300],
520
+ "new": e.get("new_string", "")[:300],
521
+ })
524
522
  elif action == "create":
525
523
  fp = e.get("file", "")
526
524
  files_created.add(fp)
527
- timeline.append(f" {ts} Write `{Path(fp).name}`")
525
+ events.append({"ts": ts, "type": "create", "file": fp})
528
526
  elif action == "command":
529
527
  cmd = e.get("command", "")
528
+ output = e.get("output", "")
530
529
  commands.append(cmd)
531
- short = cmd[:80] + "..." if len(cmd) > 80 else cmd
532
- timeline.append(f" {ts} — `{short}`")
530
+ events.append({"ts": ts, "type": "command", "cmd": cmd[:300], "output": output[:1000] if output else ""})
533
531
 
534
532
  commands_classified = [_classify_command(cmd) for cmd in commands]
535
533
 
536
- summary_items = []
537
- if files_edited:
538
- summary_items.append(f"Modified {len(files_edited)} file{'s' if len(files_edited) != 1 else ''}")
539
- if files_created:
540
- summary_items.append(f"Created {len(files_created)} file{'s' if len(files_created) != 1 else ''}")
541
- if commands:
542
- summary_items.append(f"Ran {len(commands)} command{'s' if len(commands) != 1 else ''}")
534
+ # Build rich context for Sonnet — actual content, not just filenames
535
+ raw_parts = []
543
536
 
544
- investigated = _build_investigated(files_read, commands_classified, project_dir)
545
- learned = _build_learned(files_read, files_edited, files_created, commands_classified, project_dir)
546
- completed = _build_completed(files_edited, files_created, commands_classified, project_dir)
547
- next_steps = _build_next_steps(files_edited, files_created, commands_classified)
537
+ if prompts:
538
+ raw_parts.append("## USER PROMPTS (the objectives)")
539
+ for p in prompts:
540
+ raw_parts.append(f"[{p['ts']}] {p['text'][:500]}")
541
+ raw_parts.append("")
542
+
543
+ raw_parts.append("## SESSION EVENTS (chronological, with content)")
544
+ for ev in events:
545
+ if ev["type"] == "prompt":
546
+ raw_parts.append(f"\n### [{ev['ts']}] User asked:")
547
+ raw_parts.append(ev["text"])
548
+ elif ev["type"] == "read":
549
+ raw_parts.append(f"\n### [{ev['ts']}] Read `{ev['file']}`")
550
+ if ev.get("content"):
551
+ raw_parts.append(f"```\n{ev['content']}\n```")
552
+ elif ev["type"] == "edit":
553
+ raw_parts.append(f"\n### [{ev['ts']}] Edited `{ev['file']}`")
554
+ if ev.get("old"):
555
+ raw_parts.append(f"Replaced:\n```\n{ev['old']}\n```")
556
+ if ev.get("new"):
557
+ raw_parts.append(f"With:\n```\n{ev['new']}\n```")
558
+ elif ev["type"] == "create":
559
+ raw_parts.append(f"\n### [{ev['ts']}] Created `{ev['file']}`")
560
+ elif ev["type"] == "command":
561
+ raw_parts.append(f"\n### [{ev['ts']}] Ran: `{ev['cmd']}`")
562
+ if ev.get("output"):
563
+ raw_parts.append(f"Output:\n```\n{ev['output']}\n```")
548
564
 
549
- # Build raw note for Claude summarization
550
- raw_parts = []
551
- raw_parts.append("## Summary")
552
- raw_parts.append(", ".join(summary_items) + f" in `{project_name}`." if summary_items else "")
553
- raw_parts.append("")
554
- raw_parts.append("## INVESTIGATED")
555
- if investigated:
556
- raw_parts.extend(investigated)
557
565
  raw_parts.append("")
558
- raw_parts.append("## LEARNED")
559
- if learned:
560
- raw_parts.extend(learned)
566
+ raw_parts.append("## FILES MODIFIED")
567
+ for fp in sorted(files_edited):
568
+ raw_parts.append(f"- {_relative_path(fp, project_dir)}")
561
569
  raw_parts.append("")
562
- raw_parts.append("## COMPLETED")
563
- if completed:
564
- raw_parts.extend(completed)
570
+ raw_parts.append("## FILES CREATED")
571
+ for fp in sorted(files_created):
572
+ raw_parts.append(f"- {_relative_path(fp, project_dir)}")
565
573
  raw_parts.append("")
566
- raw_parts.append("## NEXT STEPS")
567
- if next_steps:
568
- raw_parts.extend(next_steps)
574
+ raw_parts.append("## FILES READ")
575
+ for fp in sorted(files_read):
576
+ raw_parts.append(f"- {_relative_path(fp, project_dir)}")
569
577
 
570
- # Prompts go FIRST they define the session's objective
571
- if prompts:
572
- raw_parts.insert(0, "## USER PROMPTS (what was asked)")
573
- for i, p in enumerate(prompts):
574
- raw_parts.insert(i + 1, f"- [{p['ts']}] {p['text'][:300]}")
575
- raw_parts.insert(len(prompts) + 1, "")
578
+ # Keep timeline for backward compat in case summarization fails
579
+ timeline = []
580
+ for ev in events:
581
+ if ev["type"] == "prompt":
582
+ timeline.append(f" {ev['ts']} — Prompt: {ev['text'][:60]}...")
583
+ elif ev["type"] == "read":
584
+ timeline.append(f" {ev['ts']} — Read `{Path(ev['file']).name}`")
585
+ elif ev["type"] == "edit":
586
+ timeline.append(f" {ev['ts']} — Edit `{Path(ev['file']).name}`")
587
+ elif ev["type"] == "create":
588
+ timeline.append(f" {ev['ts']} — Write `{Path(ev['file']).name}`")
589
+ elif ev["type"] == "command":
590
+ short = ev["cmd"][:80] + "..." if len(ev["cmd"]) > 80 else ev["cmd"]
591
+ timeline.append(f" {ev['ts']} — `{short}`")
592
+
593
+ investigated = _build_investigated(files_read, commands_classified, project_dir)
594
+ learned = _build_learned(files_read, files_edited, files_created, commands_classified, project_dir)
595
+ completed = _build_completed(files_edited, files_created, commands_classified, project_dir)
596
+ next_steps = _build_next_steps(files_edited, files_created, commands_classified)
576
597
 
577
- # Full timeline gives Claude the narrative arc
578
598
  if timeline:
579
599
  raw_parts.append("")
580
600
  raw_parts.append("## TIMELINE (what happened, chronological)")
@@ -639,6 +659,13 @@ def handle_stop():
639
659
  parts.append("")
640
660
  parts.append(summarized)
641
661
  else:
662
+ summary_items = []
663
+ if files_edited:
664
+ summary_items.append(f"Modified {len(files_edited)} file{'s' if len(files_edited) != 1 else ''}")
665
+ if files_created:
666
+ summary_items.append(f"Created {len(files_created)} file{'s' if len(files_created) != 1 else ''}")
667
+ if commands:
668
+ summary_items.append(f"Ran {len(commands)} command{'s' if len(commands) != 1 else ''}")
642
669
  parts.append("## Summary")
643
670
  parts.append(", ".join(summary_items) + f" in `{project_name}`." if summary_items else "")
644
671
  parts.append("")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyp-mem",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Know Your Project — Persistent & Session level knowledge base for AI agents. MCP-powered with wikilinks, backlinks, auto-learning, and neon web UI.",
5
5
  "bin": {
6
6
  "kyp-mem": "bin/cli.mjs"