kyp-mem 0.4.3 → 0.4.4

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/kyp_mem/cli.py CHANGED
@@ -45,6 +45,7 @@ def main():
45
45
 
46
46
  hook_parser = subparsers.add_parser("hook", help="Handle Claude Code hook events (internal)")
47
47
  hook_sub = hook_parser.add_subparsers(dest="hook_command")
48
+ hook_sub.add_parser("session-start", help="Inject project context at session start")
48
49
  hook_sub.add_parser("post-tool-use", help="Capture tool activity to session log")
49
50
  hook_sub.add_parser("user-prompt", help="Capture user prompt to session log")
50
51
  hook_sub.add_parser("stop", help="Compile session into vault note")
@@ -73,8 +74,10 @@ def main():
73
74
  elif args.command == "doctor":
74
75
  _run_doctor()
75
76
  elif args.command == "hook":
76
- from .hooks import handle_post_tool_use, handle_user_prompt, handle_stop
77
- if args.hook_command == "post-tool-use":
77
+ from .hooks import handle_session_start, handle_post_tool_use, handle_user_prompt, handle_stop
78
+ if args.hook_command == "session-start":
79
+ handle_session_start()
80
+ elif args.hook_command == "post-tool-use":
78
81
  handle_post_tool_use()
79
82
  elif args.hook_command == "user-prompt":
80
83
  handle_user_prompt()
@@ -286,7 +289,7 @@ def _run_install_hooks(global_config: bool = False, remove: bool = False):
286
289
 
287
290
  if remove:
288
291
  changed = False
289
- for event in ("PostToolUse", "UserPromptSubmit", "Stop"):
292
+ for event in ("SessionStart", "PostToolUse", "UserPromptSubmit", "Stop"):
290
293
  if event in hooks:
291
294
  hooks[event] = [h for h in hooks[event] if not _has_kyp_hook(h)]
292
295
  if not hooks[event]:
@@ -301,14 +304,19 @@ def _run_install_hooks(global_config: bool = False, remove: bool = False):
301
304
  print()
302
305
  return
303
306
 
307
+ session_start_hooks = hooks.setdefault("SessionStart", [])
304
308
  post_tool_hooks = hooks.setdefault("PostToolUse", [])
305
309
  prompt_hooks = hooks.setdefault("UserPromptSubmit", [])
306
310
  stop_hooks = hooks.setdefault("Stop", [])
307
311
 
312
+ session_start_hooks = [h for h in session_start_hooks if not _has_kyp_hook(h)]
308
313
  post_tool_hooks = [h for h in post_tool_hooks if not _has_kyp_hook(h)]
309
314
  prompt_hooks = [h for h in prompt_hooks if not _has_kyp_hook(h)]
310
315
  stop_hooks = [h for h in stop_hooks if not _has_kyp_hook(h)]
311
316
 
317
+ session_start_hooks.append({
318
+ "hooks": [{"type": "command", "command": f"{mcp_command} hook session-start"}],
319
+ })
312
320
  post_tool_hooks.append({
313
321
  "matcher": "Edit|Write|Read|Bash",
314
322
  "hooks": [{"type": "command", "command": f"{mcp_command} hook post-tool-use"}],
@@ -320,6 +328,7 @@ def _run_install_hooks(global_config: bool = False, remove: bool = False):
320
328
  "hooks": [{"type": "command", "command": f"{mcp_command} hook stop"}],
321
329
  })
322
330
 
331
+ hooks["SessionStart"] = session_start_hooks
323
332
  hooks["PostToolUse"] = post_tool_hooks
324
333
  hooks["UserPromptSubmit"] = prompt_hooks
325
334
  hooks["Stop"] = stop_hooks
package/kyp_mem/hooks.py CHANGED
@@ -12,6 +12,81 @@ CURRENT_SESSION = SESSION_DIR / "current.jsonl"
12
12
  MIN_ACTIONS = 3
13
13
 
14
14
 
15
+ def handle_session_start():
16
+ """Inject project context into the conversation at session start."""
17
+ sys.stdin.read()
18
+
19
+ cwd = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
20
+ project_name = Path(cwd).name
21
+
22
+ try:
23
+ from .config import get_vault_path
24
+ from .vault import Vault
25
+
26
+ vault = Vault(get_vault_path())
27
+
28
+ project_notes = [p for p in vault.index.notes if p.startswith(f"{project_name}/")]
29
+ if not project_notes:
30
+ return
31
+
32
+ parts = [f"# [kyp-mem] {project_name} — Project Context"]
33
+ parts.append(f"Vault: {get_vault_path()}")
34
+ parts.append("")
35
+
36
+ knowledge_path = f"{project_name}/Knowledge.md"
37
+ knowledge = vault.read(knowledge_path)
38
+ if knowledge:
39
+ parts.append("## Knowledge")
40
+ content = knowledge.content
41
+ timeline_idx = content.find("## Timeline")
42
+ if timeline_idx > 0:
43
+ content = content[:timeline_idx].strip()
44
+ if len(content) > 2000:
45
+ parts.append(content[:2000] + "\n...")
46
+ else:
47
+ parts.append(content)
48
+ parts.append("")
49
+
50
+ other_notes = sorted(
51
+ p for p in project_notes
52
+ if "/Sessions/" not in p and p != knowledge_path
53
+ )
54
+ if other_notes:
55
+ parts.append("## Project Notes")
56
+ for p in other_notes:
57
+ note = vault.index.notes.get(p)
58
+ title = note.title if note else p
59
+ tags = f" [{', '.join(note.tags)}]" if note and note.tags else ""
60
+ parts.append(f"- {title} ({p}){tags}")
61
+ parts.append("")
62
+
63
+ sessions = sorted(
64
+ (p for p in project_notes if "/Sessions/" in p),
65
+ reverse=True,
66
+ )[:3]
67
+ if sessions:
68
+ parts.append(f"## Recent Sessions (last {len(sessions)})")
69
+ for sp in sessions:
70
+ note = vault.read(sp)
71
+ if not note:
72
+ continue
73
+ parts.append(f"### {note.title}")
74
+ content = note.content
75
+ timeline_idx = content.find("## Timeline")
76
+ if timeline_idx > 0:
77
+ content = content[:timeline_idx].strip()
78
+ if len(content) > 300:
79
+ content = content[:300] + "..."
80
+ parts.append(content)
81
+ parts.append("")
82
+
83
+ parts.append("Use `kyp_project_context` for full details. Use `kyp_session_search` to search past sessions.")
84
+
85
+ print("\n".join(parts))
86
+ except Exception:
87
+ pass
88
+
89
+
15
90
  def handle_user_prompt():
16
91
  raw = sys.stdin.read().strip()
17
92
  if not raw:
@@ -1294,8 +1294,8 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1294
1294
 
1295
1295
  .session-item {
1296
1296
  display: flex;
1297
- align-items: center;
1298
- padding: 3px 10px;
1297
+ align-items: flex-start;
1298
+ padding: 5px 10px;
1299
1299
  border-radius: var(--radius-sm);
1300
1300
  cursor: pointer;
1301
1301
  font-size: 11px;
@@ -1328,6 +1328,14 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1328
1328
  color: var(--neon-green);
1329
1329
  opacity: 0.5;
1330
1330
  flex-shrink: 0;
1331
+ margin-top: 3px;
1332
+ }
1333
+
1334
+ .session-item .si-content {
1335
+ display: flex;
1336
+ flex-direction: column;
1337
+ min-width: 0;
1338
+ flex: 1;
1331
1339
  }
1332
1340
 
1333
1341
  .session-item .si-time {
@@ -1336,7 +1344,17 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1336
1344
  color: var(--text-secondary);
1337
1345
  }
1338
1346
 
1347
+ .session-item .si-summary {
1348
+ font-size: 10px;
1349
+ color: var(--text-muted);
1350
+ white-space: nowrap;
1351
+ overflow: hidden;
1352
+ text-overflow: ellipsis;
1353
+ line-height: 1.3;
1354
+ }
1355
+
1339
1356
  .session-item.active .si-time { color: var(--neon-green); }
1357
+ .session-item.active .si-summary { color: var(--text-secondary); }
1340
1358
 
1341
1359
  .session-badge {
1342
1360
  display: inline-flex;
package/kyp_mem/ui.py CHANGED
@@ -177,12 +177,22 @@ def create_app(vault_path: str = None) -> FastAPI:
177
177
  continue
178
178
  if proj not in sessions:
179
179
  sessions[proj] = []
180
+ summary = ""
181
+ lines = (note.content or "").split("\n")
182
+ for i, line in enumerate(lines):
183
+ if line.strip() == "## Summary":
184
+ for j in range(i + 1, len(lines)):
185
+ if lines[j].strip() and not lines[j].startswith("#"):
186
+ summary = lines[j].strip()
187
+ break
188
+ break
180
189
  sessions[proj].append({
181
190
  "path": path,
182
191
  "title": note.title,
183
192
  "tags": note.tags,
184
193
  "created": note.created,
185
194
  "updated": note.updated,
195
+ "summary": summary,
186
196
  })
187
197
  for proj in sessions:
188
198
  sessions[proj].sort(key=lambda s: s["path"], reverse=True)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyp-mem",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
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"