deja-cli 0.1.0__tar.gz
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.
- deja_cli-0.1.0/.gitignore +56 -0
- deja_cli-0.1.0/LICENSE +21 -0
- deja_cli-0.1.0/PKG-INFO +100 -0
- deja_cli-0.1.0/README.pypi.md +68 -0
- deja_cli-0.1.0/config/default.yaml +62 -0
- deja_cli-0.1.0/deja/__init__.py +0 -0
- deja_cli-0.1.0/deja/config.py +127 -0
- deja_cli-0.1.0/deja/core/__init__.py +0 -0
- deja_cli-0.1.0/deja/core/extractor.py +135 -0
- deja_cli-0.1.0/deja/core/reflection.py +364 -0
- deja_cli-0.1.0/deja/core/scheduler.py +65 -0
- deja_cli-0.1.0/deja/core/store.py +1413 -0
- deja_cli-0.1.0/deja/ingest/__init__.py +0 -0
- deja_cli-0.1.0/deja/ingest/watchers/__init__.py +0 -0
- deja_cli-0.1.0/deja/ingest/watchers/base.py +143 -0
- deja_cli-0.1.0/deja/ingest/watchers/claude_code.py +62 -0
- deja_cli-0.1.0/deja/ingest/watchers/codex_cli.py +95 -0
- deja_cli-0.1.0/deja/ingest/watchers/gemini_cli.py +96 -0
- deja_cli-0.1.0/deja/interfaces/__init__.py +0 -0
- deja_cli-0.1.0/deja/interfaces/cli.py +1967 -0
- deja_cli-0.1.0/deja/interfaces/mcp_server.py +96 -0
- deja_cli-0.1.0/deja/interfaces/web.py +104 -0
- deja_cli-0.1.0/deja/interfaces/web_ui/index.html +614 -0
- deja_cli-0.1.0/deja/llm/__init__.py +0 -0
- deja_cli-0.1.0/deja/llm/base.py +34 -0
- deja_cli-0.1.0/deja/llm/embedding.py +45 -0
- deja_cli-0.1.0/deja/llm/factory.py +90 -0
- deja_cli-0.1.0/deja/llm/providers/__init__.py +0 -0
- deja_cli-0.1.0/deja/llm/providers/anthropic.py +21 -0
- deja_cli-0.1.0/deja/llm/providers/ollama.py +30 -0
- deja_cli-0.1.0/deja/main.py +4 -0
- deja_cli-0.1.0/hooks/deja-post-fail.sh +88 -0
- deja_cli-0.1.0/hooks/deja-recall.sh +85 -0
- deja_cli-0.1.0/pyproject.toml +67 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python bytecode
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.pyc
|
|
7
|
+
|
|
8
|
+
# Distribution / packaging
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
*.egg
|
|
13
|
+
MANIFEST
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
.env
|
|
20
|
+
|
|
21
|
+
# uv
|
|
22
|
+
.uv/
|
|
23
|
+
|
|
24
|
+
# Environment variables
|
|
25
|
+
.env
|
|
26
|
+
.env.*
|
|
27
|
+
!.env.example
|
|
28
|
+
|
|
29
|
+
# Testing
|
|
30
|
+
.pytest_cache/
|
|
31
|
+
.coverage
|
|
32
|
+
coverage.xml
|
|
33
|
+
htmlcov/
|
|
34
|
+
.tox/
|
|
35
|
+
|
|
36
|
+
# Type checking
|
|
37
|
+
.mypy_cache/
|
|
38
|
+
.ruff_cache/
|
|
39
|
+
|
|
40
|
+
# Editors
|
|
41
|
+
.vscode/
|
|
42
|
+
.idea/
|
|
43
|
+
*.swp
|
|
44
|
+
*.swo
|
|
45
|
+
*~
|
|
46
|
+
|
|
47
|
+
# macOS
|
|
48
|
+
.DS_Store
|
|
49
|
+
|
|
50
|
+
# Logs
|
|
51
|
+
*.log
|
|
52
|
+
|
|
53
|
+
# Local dev artifacts
|
|
54
|
+
*.db
|
|
55
|
+
*.db-shm
|
|
56
|
+
*.db-wal
|
deja_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mike
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deja_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deja-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local-first persistent memory CLI for coding agents
|
|
5
|
+
Author-email: Mike <mike@bigtreeproduction.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: agent,claude,cli,developer-tools,llm,memory
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: aiosqlite>=0.20
|
|
19
|
+
Requires-Dist: anthropic>=0.34
|
|
20
|
+
Requires-Dist: apscheduler<4.0,>=3.10
|
|
21
|
+
Requires-Dist: fastapi>=0.115
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: mcp>=1.26.0
|
|
24
|
+
Requires-Dist: pydantic-settings>=2.4
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Requires-Dist: python-ulid>=2.0
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Requires-Dist: typer>=0.12
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
30
|
+
Requires-Dist: watchdog>=4.0
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# deja
|
|
34
|
+
|
|
35
|
+
Local-first persistent memory CLI for coding agents. Memories accumulate across sessions, deduplicate automatically, and stay on your machine.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv tool install deja-cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
deja init # first-time setup
|
|
47
|
+
deja setup claude-code # inject memory instructions into Claude Code
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Core Commands
|
|
51
|
+
|
|
52
|
+
**Save a memory:**
|
|
53
|
+
```bash
|
|
54
|
+
deja save "Always use explicit error handling over try/catch" --type preference
|
|
55
|
+
deja save "Auth tokens stored in localStorage, not cookies" --type decision --project myapp
|
|
56
|
+
deja save "Prisma migrations fail silently if DB_URL has wrong port" --type gotcha --project myapp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Load at session start:**
|
|
60
|
+
```bash
|
|
61
|
+
deja load --project myapp --context "what you're working on"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Search mid-session:**
|
|
65
|
+
```bash
|
|
66
|
+
deja search "authentication" --project myapp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Extract memories from a session:**
|
|
70
|
+
```bash
|
|
71
|
+
deja save-session --project myapp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Memory Types
|
|
75
|
+
|
|
76
|
+
| Type | When to use |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `preference` | How you like to code — style, tools, habits |
|
|
79
|
+
| `pattern` | Reusable solution that applies across contexts |
|
|
80
|
+
| `decision` | Non-obvious architectural choice with reasoning |
|
|
81
|
+
| `gotcha` | Bug, trap, or non-obvious issue to avoid |
|
|
82
|
+
| `progress` | Current state of in-progress work |
|
|
83
|
+
| `procedure` | Ordered steps for a recurring task |
|
|
84
|
+
|
|
85
|
+
## Setup for Coding Agents
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
deja setup claude-code # Claude Code — global config + recall hooks
|
|
89
|
+
deja setup gemini-cli # Gemini CLI
|
|
90
|
+
deja setup codex # Codex CLI
|
|
91
|
+
deja setup cursor # Cursor
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## MCP Server
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
claude mcp add --scope user deja -- ~/.local/bin/deja-mcp
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Registers `memory_load`, `memory_save`, and `memory_search` as native tools in every Claude Code session.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# deja
|
|
2
|
+
|
|
3
|
+
Local-first persistent memory CLI for coding agents. Memories accumulate across sessions, deduplicate automatically, and stay on your machine.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv tool install deja-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
deja init # first-time setup
|
|
15
|
+
deja setup claude-code # inject memory instructions into Claude Code
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Core Commands
|
|
19
|
+
|
|
20
|
+
**Save a memory:**
|
|
21
|
+
```bash
|
|
22
|
+
deja save "Always use explicit error handling over try/catch" --type preference
|
|
23
|
+
deja save "Auth tokens stored in localStorage, not cookies" --type decision --project myapp
|
|
24
|
+
deja save "Prisma migrations fail silently if DB_URL has wrong port" --type gotcha --project myapp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Load at session start:**
|
|
28
|
+
```bash
|
|
29
|
+
deja load --project myapp --context "what you're working on"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Search mid-session:**
|
|
33
|
+
```bash
|
|
34
|
+
deja search "authentication" --project myapp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Extract memories from a session:**
|
|
38
|
+
```bash
|
|
39
|
+
deja save-session --project myapp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Memory Types
|
|
43
|
+
|
|
44
|
+
| Type | When to use |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `preference` | How you like to code — style, tools, habits |
|
|
47
|
+
| `pattern` | Reusable solution that applies across contexts |
|
|
48
|
+
| `decision` | Non-obvious architectural choice with reasoning |
|
|
49
|
+
| `gotcha` | Bug, trap, or non-obvious issue to avoid |
|
|
50
|
+
| `progress` | Current state of in-progress work |
|
|
51
|
+
| `procedure` | Ordered steps for a recurring task |
|
|
52
|
+
|
|
53
|
+
## Setup for Coding Agents
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
deja setup claude-code # Claude Code — global config + recall hooks
|
|
57
|
+
deja setup gemini-cli # Gemini CLI
|
|
58
|
+
deja setup codex # Codex CLI
|
|
59
|
+
deja setup cursor # Cursor
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## MCP Server
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
claude mcp add --scope user deja -- ~/.local/bin/deja-mcp
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Registers `memory_load`, `memory_save`, and `memory_search` as native tools in every Claude Code session.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
llm:
|
|
2
|
+
# provider: none means the agent does extraction itself via deja save.
|
|
3
|
+
# No API key or local model required for the active (CLAUDE.md) workflow.
|
|
4
|
+
# Only change this if you want the passive watcher to auto-extract memories
|
|
5
|
+
# from session summaries without agent involvement.
|
|
6
|
+
extraction:
|
|
7
|
+
provider: none
|
|
8
|
+
|
|
9
|
+
reflection:
|
|
10
|
+
provider: none
|
|
11
|
+
|
|
12
|
+
# To enable LLM-powered extraction (passive watcher + deja save-session --transcript):
|
|
13
|
+
#
|
|
14
|
+
# Option A — Anthropic API (best quality, ~$0.50/day heavy use):
|
|
15
|
+
# extraction:
|
|
16
|
+
# provider: anthropic
|
|
17
|
+
# model: claude-haiku-4-5-20251001
|
|
18
|
+
# api_key: "${ANTHROPIC_API_KEY}"
|
|
19
|
+
# reflection:
|
|
20
|
+
# provider: anthropic
|
|
21
|
+
# model: claude-haiku-4-5-20251001
|
|
22
|
+
# api_key: "${ANTHROPIC_API_KEY}"
|
|
23
|
+
#
|
|
24
|
+
# Option B — Local model via Ollama (no API cost, needs Ollama running):
|
|
25
|
+
# extraction:
|
|
26
|
+
# provider: ollama
|
|
27
|
+
# model: qwen2.5:7b # best local quality for structured JSON
|
|
28
|
+
# base_url: http://localhost:11434
|
|
29
|
+
# reflection:
|
|
30
|
+
# provider: ollama
|
|
31
|
+
# model: llama3.1:8b-instruct-q4_K_M
|
|
32
|
+
# base_url: http://localhost:11434
|
|
33
|
+
|
|
34
|
+
embedding:
|
|
35
|
+
# Semantic (natural language) search. Set provider to 'ollama' and pull the model:
|
|
36
|
+
# ollama pull nomic-embed-text
|
|
37
|
+
# Then run 'deja embed' to backfill embeddings for existing memories.
|
|
38
|
+
provider: ollama # none | ollama
|
|
39
|
+
model: nomic-embed-text
|
|
40
|
+
base_url: http://localhost:11434
|
|
41
|
+
|
|
42
|
+
store:
|
|
43
|
+
path: ~/.deja/store/memories.db
|
|
44
|
+
vault_path: ~/.deja/store/vault/
|
|
45
|
+
|
|
46
|
+
reflection:
|
|
47
|
+
observer_trigger_tokens: 30000
|
|
48
|
+
reflector_trigger_tokens: 40000
|
|
49
|
+
kg_merge_schedule: "0 2 * * *"
|
|
50
|
+
confidence_archive_threshold: 0.3
|
|
51
|
+
# agent memories (gotcha, decision, progress, pattern) — operational knowledge goes stale
|
|
52
|
+
confidence_decay_per_week: 0.05
|
|
53
|
+
# user memories (preference) — personal habits are stable, decay ~5x slower
|
|
54
|
+
user_confidence_decay_per_week: 0.01
|
|
55
|
+
min_project_pattern_count: 2
|
|
56
|
+
|
|
57
|
+
watchers:
|
|
58
|
+
claude_code: true
|
|
59
|
+
gemini_cli: false
|
|
60
|
+
codex_cli: false
|
|
61
|
+
aider: false
|
|
62
|
+
debounce_seconds: 30
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from pydantic import BaseModel, field_validator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _substitute_env(value: str) -> str:
|
|
13
|
+
"""Substitute ${ENV_VAR} patterns with environment variable values."""
|
|
14
|
+
def replacer(match: re.Match) -> str:
|
|
15
|
+
var_name = match.group(1)
|
|
16
|
+
return os.environ.get(var_name, match.group(0))
|
|
17
|
+
|
|
18
|
+
return re.sub(r"\$\{([^}]+)\}", replacer, value)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _expand_paths(data: dict) -> dict:
|
|
22
|
+
"""Recursively expand ~ in string values."""
|
|
23
|
+
result = {}
|
|
24
|
+
for key, value in data.items():
|
|
25
|
+
if isinstance(value, dict):
|
|
26
|
+
result[key] = _expand_paths(value)
|
|
27
|
+
elif isinstance(value, str):
|
|
28
|
+
result[key] = _substitute_env(value)
|
|
29
|
+
else:
|
|
30
|
+
result[key] = value
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LLMProviderConfig(BaseModel):
|
|
35
|
+
provider: str = "ollama"
|
|
36
|
+
model: str = "qwen2.5:3b"
|
|
37
|
+
base_url: str = "http://localhost:11434"
|
|
38
|
+
api_key: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
@field_validator("api_key", mode="before")
|
|
41
|
+
@classmethod
|
|
42
|
+
def expand_env_vars(cls, v: Optional[str]) -> Optional[str]:
|
|
43
|
+
if v is None:
|
|
44
|
+
return v
|
|
45
|
+
return _substitute_env(v)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LLMConfig(BaseModel):
|
|
49
|
+
extraction: LLMProviderConfig = LLMProviderConfig(provider="none")
|
|
50
|
+
reflection: LLMProviderConfig = LLMProviderConfig(provider="none")
|
|
51
|
+
fallback: LLMProviderConfig = LLMProviderConfig(
|
|
52
|
+
provider="anthropic",
|
|
53
|
+
model="claude-haiku-4-5-20251001",
|
|
54
|
+
api_key="${ANTHROPIC_API_KEY}",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StoreConfig(BaseModel):
|
|
59
|
+
path: str = "~/.deja/store/memories.db"
|
|
60
|
+
vault_path: str = "~/.deja/store/vault/"
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def db_path(self) -> Path:
|
|
64
|
+
return Path(self.path).expanduser()
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def vault_dir(self) -> Path:
|
|
68
|
+
return Path(self.vault_path).expanduser()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ReflectionConfig(BaseModel):
|
|
72
|
+
observer_trigger_tokens: int = 30000
|
|
73
|
+
reflector_trigger_tokens: int = 40000
|
|
74
|
+
kg_merge_schedule: str = "0 2 * * *"
|
|
75
|
+
confidence_archive_threshold: float = 0.3
|
|
76
|
+
# agent memories (gotcha, decision, progress, pattern) decay at this rate.
|
|
77
|
+
# Operational knowledge goes stale; 0.05/week means a memory hits the 0.3
|
|
78
|
+
# archive threshold in ~14 weeks without re-confirmation.
|
|
79
|
+
confidence_decay_per_week: float = 0.05
|
|
80
|
+
# user memories (preferences, habits) decay ~5x slower. Personal style
|
|
81
|
+
# preferences don't go stale the way project-specific gotchas do.
|
|
82
|
+
user_confidence_decay_per_week: float = 0.01
|
|
83
|
+
min_project_pattern_count: int = 2
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class WatchersConfig(BaseModel):
|
|
87
|
+
claude_code: bool = True
|
|
88
|
+
gemini_cli: bool = False
|
|
89
|
+
codex_cli: bool = False
|
|
90
|
+
aider: bool = False
|
|
91
|
+
debounce_seconds: int = 30
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class EmbeddingConfig(BaseModel):
|
|
95
|
+
provider: str = "none" # none | ollama
|
|
96
|
+
model: str = "nomic-embed-text"
|
|
97
|
+
base_url: str = "http://localhost:11434"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Config(BaseModel):
|
|
101
|
+
llm: LLMConfig = LLMConfig()
|
|
102
|
+
store: StoreConfig = StoreConfig()
|
|
103
|
+
reflection: ReflectionConfig = ReflectionConfig()
|
|
104
|
+
watchers: WatchersConfig = WatchersConfig()
|
|
105
|
+
embedding: EmbeddingConfig = EmbeddingConfig()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def load_config(path: Optional[Path] = None) -> Config:
|
|
109
|
+
"""Load config from path, falling back to ~/.deja/config.yaml,
|
|
110
|
+
then to the bundled default."""
|
|
111
|
+
candidates = []
|
|
112
|
+
if path:
|
|
113
|
+
candidates.append(Path(path).expanduser())
|
|
114
|
+
candidates.append(Path("~/.deja/config.yaml").expanduser())
|
|
115
|
+
|
|
116
|
+
# Bundled default
|
|
117
|
+
default_path = Path(__file__).parent.parent / "config" / "default.yaml"
|
|
118
|
+
candidates.append(default_path)
|
|
119
|
+
|
|
120
|
+
for candidate in candidates:
|
|
121
|
+
if candidate.exists():
|
|
122
|
+
with open(candidate) as f:
|
|
123
|
+
raw = yaml.safe_load(f) or {}
|
|
124
|
+
raw = _expand_paths(raw)
|
|
125
|
+
return Config.model_validate(raw)
|
|
126
|
+
|
|
127
|
+
return Config()
|
|
File without changes
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from deja.llm.base import LLMAdapter
|
|
7
|
+
|
|
8
|
+
EXTRACTION_SYSTEM = """You are a memory extraction system for a software engineer.
|
|
9
|
+
Given a coding session transcript or summary, extract ONLY memories
|
|
10
|
+
that would be genuinely useful in a future session.
|
|
11
|
+
|
|
12
|
+
Be ruthless — most session content is NOT worth remembering.
|
|
13
|
+
Only extract things that are:
|
|
14
|
+
- Non-obvious (not derivable from reading the codebase)
|
|
15
|
+
- Reusable (would apply in future sessions)
|
|
16
|
+
- Important (would cause problems if forgotten)
|
|
17
|
+
|
|
18
|
+
Memory types:
|
|
19
|
+
- preference: how the user likes to code (style, tools, patterns)
|
|
20
|
+
- pattern: reusable solution / architectural approach that applies across contexts ("knowing what works")
|
|
21
|
+
- decision: non-obvious architectural choice with reasoning
|
|
22
|
+
- gotcha: bug, trap, or non-obvious issue to avoid
|
|
23
|
+
- progress: current state of in-progress work
|
|
24
|
+
- procedure: reusable ordered steps for a recurring class of work ("knowing how to execute"). Keep thin: numbered steps + tool hints + exit criteria only. Do NOT inline gotchas or decisions — save those as separate memory types.
|
|
25
|
+
|
|
26
|
+
Category:
|
|
27
|
+
- user: personal preferences and habits (applies across all projects)
|
|
28
|
+
- agent: operational knowledge discovered while doing work
|
|
29
|
+
|
|
30
|
+
Domain (optional, coarse routing tag — use for procedure type mainly):
|
|
31
|
+
- debug | build | test | deploy | research
|
|
32
|
+
|
|
33
|
+
Output ONLY valid JSON:
|
|
34
|
+
{
|
|
35
|
+
"memories": [
|
|
36
|
+
{
|
|
37
|
+
"type": "preference|pattern|decision|gotcha|progress|procedure",
|
|
38
|
+
"category": "user|agent",
|
|
39
|
+
"content": "concise, self-contained fact. 1-2 sentences max for most types. For procedure: one-line description followed by numbered steps, tool hints, and exit criteria.",
|
|
40
|
+
"scope": "global|project",
|
|
41
|
+
"project": "project_name or null if global",
|
|
42
|
+
"confidence": 0.0-1.0,
|
|
43
|
+
"domain": "debug|build|test|deploy|research|null"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
If nothing is worth remembering, return: {"memories": []}"""
|
|
49
|
+
|
|
50
|
+
EXTRACTION_SCHEMA = {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"memories": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {
|
|
58
|
+
"type": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"enum": ["preference", "pattern", "decision", "gotcha", "progress", "procedure"],
|
|
61
|
+
},
|
|
62
|
+
"category": {"type": "string", "enum": ["user", "agent"]},
|
|
63
|
+
"content": {"type": "string"},
|
|
64
|
+
"scope": {"type": "string", "enum": ["global", "project"]},
|
|
65
|
+
"project": {"type": ["string", "null"]},
|
|
66
|
+
"confidence": {"type": "number"},
|
|
67
|
+
"domain": {"type": ["string", "null"]},
|
|
68
|
+
},
|
|
69
|
+
"required": ["type", "category", "content", "scope", "confidence"],
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"required": ["memories"],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def extract_memories(
|
|
78
|
+
transcript: str,
|
|
79
|
+
project: str,
|
|
80
|
+
source: str,
|
|
81
|
+
adapter: LLMAdapter,
|
|
82
|
+
) -> list[dict]:
|
|
83
|
+
"""Extract memories from a session transcript or summary.
|
|
84
|
+
|
|
85
|
+
Returns list of memory dicts ready to pass to store.save().
|
|
86
|
+
"""
|
|
87
|
+
if not transcript.strip():
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
user_prompt = f"Session transcript/summary to extract memories from:\n\n{transcript}"
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
result = await adapter.complete_structured(
|
|
94
|
+
system=EXTRACTION_SYSTEM,
|
|
95
|
+
user=user_prompt,
|
|
96
|
+
schema=EXTRACTION_SCHEMA,
|
|
97
|
+
)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"[deja] Extraction LLM error: {e}", file=sys.stderr)
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
memories = result.get("memories", [])
|
|
103
|
+
if not isinstance(memories, list):
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
output = []
|
|
107
|
+
for mem in memories:
|
|
108
|
+
if not isinstance(mem, dict):
|
|
109
|
+
continue
|
|
110
|
+
if not mem.get("content") or not mem.get("type"):
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
# Normalize scope: if scope is "project" but no project given, use the provided project
|
|
114
|
+
scope = mem.get("scope", "global")
|
|
115
|
+
mem_project = mem.get("project") or (project if scope == "project" else None)
|
|
116
|
+
if scope == "project" and mem_project:
|
|
117
|
+
scope_value = f"project:{mem_project}"
|
|
118
|
+
else:
|
|
119
|
+
scope_value = "global"
|
|
120
|
+
mem_project = None
|
|
121
|
+
|
|
122
|
+
output.append(
|
|
123
|
+
{
|
|
124
|
+
"type": mem["type"],
|
|
125
|
+
"category": mem.get("category", "agent"),
|
|
126
|
+
"content": mem["content"],
|
|
127
|
+
"scope": scope_value,
|
|
128
|
+
"project": mem_project,
|
|
129
|
+
"source": source,
|
|
130
|
+
"confidence": float(mem.get("confidence", 0.8)),
|
|
131
|
+
"domain": mem.get("domain"),
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return output
|