refactor: remove all hardcoded paths, use env vars + config
All checks were successful
Tests / test (push) Successful in 2s
All checks were successful
Tests / test (push) Successful in 2s
All ~/clawd/ references replaced with configurable paths: - CORTEX_HOME (default: ~/.cortex) - CORTEX_MEMORY_DIR, CORTEX_CONFIG, CORTEX_GROWTH_LOG, CORTEX_ROADMAP - permanent_files configurable via config.json - Tests pass both with and without env vars set - 169/169 tests green
This commit is contained in:
parent
0972e81ec8
commit
734f96cfcf
9 changed files with 117 additions and 23 deletions
20
README.md
20
README.md
|
|
@ -21,6 +21,26 @@ Intelligence layer for [OpenClaw](https://github.com/moltbot/moltbot). The highe
|
|||
pip install -e .
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Cortex uses environment variables for path configuration. All paths have sensible defaults (`~/.cortex/`).
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CORTEX_HOME` | `~/.cortex` | Base directory |
|
||||
| `CORTEX_MEMORY_DIR` | `$CORTEX_HOME/memory` | Memory files |
|
||||
| `CORTEX_CONFIG` | `$CORTEX_HOME/config.json` | Config file |
|
||||
| `CORTEX_GROWTH_LOG` | `$CORTEX_MEMORY_DIR/growth-log.md` | Feedback loop output |
|
||||
| `CORTEX_ROADMAP` | `$CORTEX_MEMORY_DIR/roadmap.json` | Roadmap data |
|
||||
|
||||
Optional `config.json` for customization:
|
||||
```json
|
||||
{
|
||||
"permanent_files": ["MEMORY.md", "WORKING.md", "README.md"],
|
||||
"sessions_dir": "~/.openclaw/agents/main/sessions"
|
||||
}
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Usage:
|
|||
cortex hygiene stats|stale|duplicates|orphans|archive
|
||||
cortex roadmap list|add|update|overdue|upcoming|report
|
||||
cortex validate --transcript <path> --task "description"
|
||||
cortex search "query" [--memory-dir ~/clawd/memory]
|
||||
cortex search "query" [--memory-dir ~/.cortex/memory]
|
||||
cortex handoff --from <session> --to <agent> --task "description"
|
||||
cortex version
|
||||
"""
|
||||
|
|
|
|||
72
cortex/config.py
Normal file
72
cortex/config.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Cortex configuration — all paths and settings resolved here.
|
||||
|
||||
Priority: CLI args > environment variables > config file > defaults.
|
||||
|
||||
Environment variables:
|
||||
CORTEX_HOME Base directory (default: ~/.cortex)
|
||||
CORTEX_MEMORY_DIR Memory files directory
|
||||
CORTEX_CONFIG Path to config.json
|
||||
CORTEX_GROWTH_LOG Path to growth log file
|
||||
CORTEX_ROADMAP Path to roadmap.json
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def _env(key: str, default: str | None = None) -> str | None:
|
||||
return os.environ.get(key, default)
|
||||
|
||||
|
||||
def cortex_home() -> Path:
|
||||
"""Base directory for cortex data."""
|
||||
return Path(_env("CORTEX_HOME", str(Path.home() / ".cortex")))
|
||||
|
||||
|
||||
def memory_dir() -> Path:
|
||||
return Path(_env("CORTEX_MEMORY_DIR", str(cortex_home() / "memory")))
|
||||
|
||||
|
||||
def growth_log() -> Path:
|
||||
return Path(_env("CORTEX_GROWTH_LOG", str(memory_dir() / "growth-log.md")))
|
||||
|
||||
|
||||
def roadmap_file() -> Path:
|
||||
return Path(_env("CORTEX_ROADMAP", str(memory_dir() / "roadmap.json")))
|
||||
|
||||
|
||||
def archive_dir() -> Path:
|
||||
return memory_dir() / "archive"
|
||||
|
||||
|
||||
def logs_dir() -> Path:
|
||||
return cortex_home() / "logs"
|
||||
|
||||
|
||||
def config_path() -> Path:
|
||||
return Path(_env("CORTEX_CONFIG", str(cortex_home() / "config.json")))
|
||||
|
||||
|
||||
def load_config(path: Optional[Path] = None) -> dict[str, Any]:
|
||||
"""Load config.json. Returns empty dict if not found."""
|
||||
p = path or config_path()
|
||||
if p.exists():
|
||||
return json.loads(p.read_text())
|
||||
return {}
|
||||
|
||||
|
||||
# Default permanent files — overridable via config.json "permanent_files" key
|
||||
DEFAULT_PERMANENT_FILES = {
|
||||
"README.md",
|
||||
}
|
||||
|
||||
|
||||
def permanent_files(config: Optional[dict] = None) -> set[str]:
|
||||
"""Files that should never be archived or deleted."""
|
||||
cfg = config or load_config()
|
||||
custom = cfg.get("permanent_files", [])
|
||||
if custom:
|
||||
return set(custom)
|
||||
return DEFAULT_PERMANENT_FILES
|
||||
|
|
@ -30,14 +30,12 @@ from typing import Optional
|
|||
from cortex.composite_scorer import SearchResult, score_results, load_config as load_scorer_config
|
||||
from cortex.intent_classifier import classify, IntentResult
|
||||
|
||||
UNIFIED_MEMORY_SCRIPT = Path.home() / "clawd" / "scripts" / "unified-memory.py"
|
||||
UNIFIED_MEMORY_SCRIPT = Path(os.environ.get("CORTEX_UNIFIED_MEMORY_SCRIPT", str(Path.home() / ".cortex" / "scripts" / "unified-memory.py")))
|
||||
PYTHON = sys.executable or "/usr/bin/python3"
|
||||
|
||||
# Paths to search directly if unified-memory.py is unavailable
|
||||
SEARCH_PATHS = [
|
||||
Path.home() / "clawd" / "memory",
|
||||
Path.home() / "clawd" / "companies",
|
||||
Path.home() / "clawd" / "MEMORY.md",
|
||||
Path(os.environ.get("CORTEX_MEMORY_DIR", str(Path.home() / ".cortex" / "memory"))),
|
||||
Path.home() / "life" / "areas",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -323,7 +323,8 @@ def append_to_growth_log(findings: list[Finding], config: dict, dry_run: bool =
|
|||
text += finding_to_markdown(f, config) + "\n"
|
||||
if dry_run:
|
||||
return text
|
||||
log_path = expand(config.get("growth_log_path", "~/clawd/memory/growth-log.md"))
|
||||
from cortex.config import growth_log
|
||||
log_path = expand(config.get("growth_log_path", str(growth_log())))
|
||||
with open(log_path, "a") as fh:
|
||||
fh.write(text)
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ class HealthScanner:
|
|||
"""Check for large SQLite/DB files."""
|
||||
search_paths = [
|
||||
Path.home() / ".openclaw",
|
||||
Path.home() / "clawd",
|
||||
Path(os.environ.get("CORTEX_HOME", str(Path.home() / ".cortex"))),
|
||||
]
|
||||
for base in search_paths:
|
||||
if not base.exists():
|
||||
|
|
@ -250,7 +250,7 @@ class HealthScanner:
|
|||
def _check_log_sizes(self):
|
||||
log_dirs = [
|
||||
Path.home() / ".openclaw" / "logs",
|
||||
Path.home() / "clawd" / "logs",
|
||||
Path(os.environ.get("CORTEX_HOME", str(Path.home() / ".cortex"))) / "logs",
|
||||
Path("/tmp"),
|
||||
]
|
||||
for d in log_dirs:
|
||||
|
|
@ -320,16 +320,16 @@ class HealthScanner:
|
|||
|
||||
def _check_chromadb(self):
|
||||
rag_dirs = list(Path.home().glob("**/.rag-db"))[:3]
|
||||
clawd_rag = Path.home() / "clawd" / ".rag-db"
|
||||
if clawd_rag.exists():
|
||||
age_hours = (time.time() - clawd_rag.stat().st_mtime) / 3600
|
||||
rag_db = Path(os.environ.get("CORTEX_HOME", str(Path.home() / ".cortex"))) / ".rag-db"
|
||||
if rag_db.exists():
|
||||
age_hours = (time.time() - rag_db.stat().st_mtime) / 3600
|
||||
if age_hours > 48:
|
||||
self._add("deps", WARN,
|
||||
f"ChromaDB (.rag-db) last modified {age_hours:.0f}h ago")
|
||||
else:
|
||||
self._add("deps", INFO, f"ChromaDB (.rag-db) fresh ({age_hours:.0f}h)")
|
||||
else:
|
||||
self._add("deps", INFO, "No .rag-db found in clawd workspace")
|
||||
self._add("deps", INFO, "No .rag-db found in workspace")
|
||||
|
||||
def _check_ollama(self):
|
||||
try:
|
||||
|
|
@ -344,7 +344,7 @@ class HealthScanner:
|
|||
def _check_key_expiry(self):
|
||||
"""Scan env files for date-like patterns that might indicate key expiry."""
|
||||
env_files = list(Path.home().glob(".config/**/*.env"))
|
||||
env_files += list(Path.home().glob("clawd/**/.env"))
|
||||
env_files += list(Path(os.environ.get("CORTEX_HOME", str(Path.home() / ".cortex"))).glob("**/.env"))
|
||||
env_files += [Path.home() / ".env"]
|
||||
now = datetime.now()
|
||||
date_pattern = re.compile(r'(\d{4}-\d{2}-\d{2})')
|
||||
|
|
|
|||
|
|
@ -12,15 +12,12 @@ from collections import defaultdict
|
|||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
MEMORY_DIR = Path.home() / "clawd" / "memory"
|
||||
ARCHIVE_DIR = MEMORY_DIR / "archive"
|
||||
from cortex.config import memory_dir, archive_dir, permanent_files as get_permanent_files
|
||||
MEMORY_DIR = memory_dir()
|
||||
ARCHIVE_DIR = archive_dir()
|
||||
CONFIG_PATH = Path(__file__).parent / "config.json"
|
||||
|
||||
PERMANENT_FILES = {
|
||||
"MEMORY.md", "WORKING.md", "growth-log.md", "BOOT_CONTEXT.md",
|
||||
"README.md", "active-context.json", "network-map.md",
|
||||
"learned-context.md", "email-contacts.json",
|
||||
}
|
||||
PERMANENT_FILES = get_permanent_files()
|
||||
|
||||
DAILY_NOTE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}(?:-.+)?\.md$")
|
||||
DATE_RE = re.compile(r"\b(20\d{2})-(\d{2})-(\d{2})\b")
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ from datetime import datetime, timedelta
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROADMAP_FILE = Path.home() / "clawd" / "memory" / "roadmap.json"
|
||||
from cortex.config import roadmap_file
|
||||
ROADMAP_FILE = roadmap_file()
|
||||
VALID_STATUSES = {"open", "in_progress", "blocked", "done"}
|
||||
VALID_PRIORITIES = {"P0", "P1", "P2", "P3"}
|
||||
|
||||
|
|
|
|||
|
|
@ -272,8 +272,13 @@ class TestMemoryOrphans(unittest.TestCase):
|
|||
|
||||
def test_permanent_files_not_orphaned(self):
|
||||
(self.tmpdir / "WORKING.md").write_text("Current work")
|
||||
orig = mh.PERMANENT_FILES
|
||||
mh.PERMANENT_FILES = {"WORKING.md", "README.md"}
|
||||
try:
|
||||
orph = mh.find_orphans()
|
||||
self.assertNotIn("WORKING.md", orph["orphaned_files"])
|
||||
finally:
|
||||
mh.PERMANENT_FILES = orig
|
||||
|
||||
|
||||
class TestMemoryStats(unittest.TestCase):
|
||||
|
|
|
|||
Loading…
Reference in a new issue