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 +79 -230
- package/bin/cli.mjs +17 -0
- package/bin/install.mjs +61 -10
- package/kyp_mem/cli.py +44 -1
- package/kyp_mem/config.py +1 -1
- package/kyp_mem/hooks.py +133 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,288 +1,137 @@
|
|
|
1
|
-
# KYP-MEM
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
34
|
+
### 1. Episodic Memory (Sessions)
|
|
71
35
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
KYP-MEM maintains structured project knowledge as Markdown files with `[[wikilinks]]`:
|
|
86
48
|
|
|
87
|
-
|
|
49
|
+
- Architecture docs, API references, setup guides
|
|
50
|
+
- Known issues, decision history, linked concepts
|
|
88
51
|
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
61
|
+
## Installation
|
|
100
62
|
|
|
101
63
|
```bash
|
|
102
|
-
kyp-mem
|
|
64
|
+
npm install -g kyp-mem
|
|
103
65
|
```
|
|
104
66
|
|
|
105
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
127
|
-
> also add retry logic for expired tokens
|
|
76
|
+
### Requirements
|
|
128
77
|
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
78
|
+
- Node.js 18+
|
|
79
|
+
- Python 3.10+
|
|
80
|
+
- Claude Code CLI
|
|
81
|
+
- Anthropic API key (for session summarization with Sonnet)
|
|
132
82
|
|
|
133
|
-
|
|
134
|
-
- Refresh tokens expire after 30 days, not 90
|
|
83
|
+
### Custom Vault Path
|
|
135
84
|
|
|
136
|
-
|
|
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
|
-
|
|
141
|
-
-
|
|
87
|
+
```bash
|
|
88
|
+
kyp-mem init # Interactive prompt to choose vault location
|
|
142
89
|
```
|
|
143
90
|
|
|
144
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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 |
|
|
110
|
+
| Command | Description |
|
|
273
111
|
|---------|-------------|
|
|
274
|
-
| `kyp-mem init` |
|
|
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
|
|
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
|
|
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(
|
|
57
|
-
console.log(
|
|
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
|
-
|
|
71
|
+
// Step 1: Install Python package
|
|
72
|
+
console.log(` Installing kyp-mem Python package...`);
|
|
64
73
|
|
|
65
|
-
const
|
|
74
|
+
const pipResult = run(
|
|
66
75
|
pythonCommand,
|
|
67
76
|
[...pythonPrefixArgs, "-m", "pip", "install", "--user", "."],
|
|
68
77
|
{ stdio: "inherit" },
|
|
69
78
|
);
|
|
70
79
|
|
|
71
|
-
if (
|
|
72
|
-
console.log(
|
|
73
|
-
}
|
|
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
|
|
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
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
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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("
|
|
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
|
-
|
|
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"""
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
438
|
+
<1-2 sentences>
|
|
446
439
|
|
|
447
440
|
## INVESTIGATED
|
|
448
|
-
|
|
441
|
+
<one paragraph>
|
|
449
442
|
|
|
450
443
|
## LEARNED
|
|
451
|
-
|
|
444
|
+
<one paragraph>
|
|
452
445
|
|
|
453
446
|
## COMPLETED
|
|
454
|
-
|
|
447
|
+
<one paragraph>
|
|
455
448
|
|
|
456
449
|
## NEXT STEPS
|
|
457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
537
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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("##
|
|
559
|
-
|
|
560
|
-
raw_parts.
|
|
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("##
|
|
563
|
-
|
|
564
|
-
raw_parts.
|
|
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("##
|
|
567
|
-
|
|
568
|
-
raw_parts.
|
|
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
|
-
#
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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.
|
|
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"
|