personal memory agent
at main 442 lines 15 kB view raw
1# solstone Makefile 2# Python-based AI-driven desktop journaling toolkit 3 4.PHONY: install uninstall test test-apps test-app test-only test-integration test-integration-only test-all format ci clean clean-install coverage watch versions update update-prices pre-commit skills dev all sail sandbox sandbox-stop install-pinchtab verify-browser update-browser-baselines review verify-api update-api-baselines install-service uninstall-service 5 6# Default target - install package in editable mode 7all: install 8 9# Virtual environment directory 10VENV := .venv 11VENV_BIN := $(VENV)/bin 12PYTHON := $(VENV_BIN)/python 13 14# Require uv 15UV := $(shell command -v uv 2>/dev/null) 16ifndef UV 17$(error uv is not installed. Install it: curl -LsSf https://astral.sh/uv/install.sh | sh) 18endif 19 20# User bin directory for symlink (standard location, usually already in PATH) 21USER_BIN := $(HOME)/.local/bin 22 23# Marker file to track installation 24.installed: pyproject.toml uv.lock 25 @echo "Installing package with uv..." 26 $(UV) sync 27 @# Python 3.14+ needs onnxruntime from nightly (not yet on PyPI) 28 @PY_MINOR=$$($(PYTHON) -c "import sys; print(sys.version_info.minor)"); \ 29 if [ "$$PY_MINOR" -ge 14 ]; then \ 30 echo "Python 3.14+ detected - installing onnxruntime from nightly feed..."; \ 31 $(UV) pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ORT-Nightly/pypi/simple/ onnxruntime; \ 32 fi 33 @echo "Installing Playwright browser for sol screenshot..." 34 $(VENV_BIN)/playwright install chromium 35 @if [ -d .git ]; then \ 36 mkdir -p $(USER_BIN); \ 37 ln -sf $(CURDIR)/$(VENV_BIN)/sol $(USER_BIN)/sol; \ 38 echo ""; \ 39 echo "Done! 'sol' command installed to $(USER_BIN)/sol"; \ 40 if ! echo "$$PATH" | grep -q "$(USER_BIN)"; then \ 41 echo ""; \ 42 echo "NOTE: $(USER_BIN) is not in your PATH."; \ 43 echo "Add this to your shell profile (~/.bashrc, ~/.zshrc, etc.):"; \ 44 echo " export PATH=\"\$$HOME/.local/bin:\$$PATH\""; \ 45 echo ""; \ 46 echo "Or run sol directly: $(CURDIR)/$(VENV_BIN)/sol"; \ 47 fi; \ 48 else \ 49 echo ""; \ 50 echo "Done! (worktree detected, skipping ~/.local/bin/sol symlink)"; \ 51 fi 52 @$(MAKE) --no-print-directory skills 53 @if [ -d .git ] && [ -f skills/solstone/SKILL.md ]; then \ 54 echo "Installing solstone skill user-wide..."; \ 55 npx skills add ./skills/solstone -g -a claude-code -y; \ 56 fi 57 @touch .installed 58 59# Generate lock file if missing 60uv.lock: pyproject.toml 61 $(UV) lock 62 63# Install package in editable mode with isolated venv 64install: .installed 65 66# Directories where AI coding agents look for skills 67SKILL_DIRS := .agents/skills .claude/skills 68 69# Discover SKILL.md files in talent/ and apps/*/talent/, symlink into agent skill dirs 70skills: 71 @# Collect all skill directories (containing SKILL.md) 72 @SKILLS=""; \ 73 for skill_md in talent/*/SKILL.md apps/*/talent/*/SKILL.md; do \ 74 [ -f "$$skill_md" ] || continue; \ 75 skill_dir=$$(dirname "$$skill_md"); \ 76 skill_name=$$(basename "$$skill_dir"); \ 77 if echo "$$SKILLS" | grep -qw "$$skill_name"; then \ 78 echo "Error: duplicate skill name '$$skill_name' found in $$skill_dir" >&2; \ 79 echo "Each skill directory name must be unique across talent/ and apps/*/talent/." >&2; \ 80 exit 1; \ 81 fi; \ 82 SKILLS="$$SKILLS $$skill_name"; \ 83 done; \ 84 for dir in $(SKILL_DIRS); do \ 85 mkdir -p "$$dir"; \ 86 for link in "$$dir"/*; do \ 87 [ -L "$$link" ] && rm -f "$$link"; \ 88 done; \ 89 done; \ 90 count=0; \ 91 for skill_md in talent/*/SKILL.md apps/*/talent/*/SKILL.md; do \ 92 [ -f "$$skill_md" ] || continue; \ 93 skill_dir=$$(dirname "$$skill_md"); \ 94 skill_name=$$(basename "$$skill_dir"); \ 95 for dir in $(SKILL_DIRS); do \ 96 ln -sf "../../$$skill_dir" "$$dir/$$skill_name"; \ 97 done; \ 98 count=$$((count + 1)); \ 99 done; \ 100 if [ "$$count" -gt 0 ]; then \ 101 echo "Linked $$count skill(s) into $(SKILL_DIRS)"; \ 102 fi 103 @$(PYTHON) scripts/generate_agents_md.py 104 105# Start local dev stack against fixture journal (no observers, no daily processing) 106dev: .installed 107 $(TEST_ENV) PATH=$(CURDIR)/$(VENV_BIN):$$PATH $(VENV_BIN)/sol supervisor 0 --no-observers --no-daily 108 109# Restart solstone service (noop in dev mode) 110sail: .installed 111 $(VENV_BIN)/sol service restart --if-installed 112 113# Start sandbox stack: fixture copy + background supervisor + readiness wait 114sandbox: .installed 115 @# Fail if sandbox already running 116 @if [ -f .sandbox.pid ] && kill -0 $$(cat .sandbox.pid) 2>/dev/null; then \ 117 echo "Sandbox already running (PID $$(cat .sandbox.pid))"; \ 118 echo "Run 'make sandbox-stop' first."; \ 119 exit 1; \ 120 fi 121 @# Clean up stale state from a previous crashed sandbox 122 @if [ -f .sandbox.journal ]; then \ 123 rm -rf "$$(cat .sandbox.journal)" 2>/dev/null; \ 124 rm -f .sandbox.pid .sandbox.journal; \ 125 fi 126 @# Copy fixtures to temp dir 127 @SANDBOX_JOURNAL=$$(mktemp -d /tmp/solstone-sandbox-XXXXXX); \ 128 cp -r tests/fixtures/journal/* "$$SANDBOX_JOURNAL/"; \ 129 echo "$$SANDBOX_JOURNAL" > .sandbox.journal; \ 130 echo "Sandbox journal: $$SANDBOX_JOURNAL"; \ 131 # Boot supervisor in background \ 132 _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" PATH=$(CURDIR)/$(VENV_BIN):$$PATH \ 133 $(VENV_BIN)/sol supervisor 0 --no-observers --no-daily \ 134 > "$$SANDBOX_JOURNAL/health/supervisor.log" 2>&1 & \ 135 echo $$! > .sandbox.pid; \ 136 echo "Supervisor PID: $$(cat .sandbox.pid)"; \ 137 # Poll for readiness \ 138 echo "Waiting for services..."; \ 139 READY=false; \ 140 for i in $$(seq 1 20); do \ 141 if _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/sol health > /dev/null 2>&1; then \ 142 READY=true; \ 143 break; \ 144 fi; \ 145 sleep 1; \ 146 done; \ 147 if [ "$$READY" = "false" ]; then \ 148 echo "Readiness timeout - killing supervisor"; \ 149 kill $$(cat .sandbox.pid) 2>/dev/null || true; \ 150 rm -rf "$$SANDBOX_JOURNAL" .sandbox.pid .sandbox.journal; \ 151 exit 1; \ 152 fi; \ 153 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port" 2>/dev/null); \ 154 echo ""; \ 155 echo "Sandbox is ready!"; \ 156 echo " Convey: http://localhost:$$CONVEY_PORT/"; \ 157 echo " Journal: $$SANDBOX_JOURNAL"; \ 158 echo " Stop: make sandbox-stop" 159 160# Stop sandbox: terminate supervisor, clean up temp dir and state files 161sandbox-stop: 162 @if [ ! -f .sandbox.pid ]; then \ 163 echo "No sandbox running."; \ 164 exit 0; \ 165 fi; \ 166 PID=$$(cat .sandbox.pid); \ 167 echo "Stopping supervisor (PID $$PID)..."; \ 168 kill "$$PID" 2>/dev/null || true; \ 169 # Wait up to 5s for clean shutdown \ 170 for i in $$(seq 1 10); do \ 171 kill -0 "$$PID" 2>/dev/null || break; \ 172 sleep 0.5; \ 173 done; \ 174 kill -9 "$$PID" 2>/dev/null || true; \ 175 if [ -f .sandbox.journal ]; then \ 176 SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 177 rm -rf "$$SANDBOX_JOURNAL"; \ 178 echo "Removed $$SANDBOX_JOURNAL"; \ 179 fi; \ 180 rm -f .sandbox.pid .sandbox.journal; \ 181 echo "Sandbox stopped." 182 183# Verify API baselines against running sandbox 184verify-api: .installed 185 @echo "Verifying API baselines (sandbox)..." 186 @$(MAKE) sandbox 187 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 188 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 189 RESULT=0; \ 190 _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 191 $(MAKE) sandbox-stop; \ 192 exit $$RESULT 193 194# Regenerate all API baseline files from current responses (uses sandbox for consistency) 195update-api-baselines: .installed 196 @echo "Updating API baselines (sandbox)..." 197 @$(MAKE) sandbox 198 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 199 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 200 RESULT=0; \ 201 _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py update --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 202 $(MAKE) sandbox-stop; \ 203 exit $$RESULT 204 205 206# Install pinchtab browser automation tool 207install-pinchtab: 208 @if command -v pinchtab >/dev/null 2>&1; then \ 209 echo "pinchtab already installed: $$(pinchtab --version 2>/dev/null || echo 'unknown')"; \ 210 else \ 211 echo "Installing pinchtab..."; \ 212 curl -fsSL https://pinchtab.com/install.sh | bash; \ 213 fi 214 215# Run browser scenarios against sandbox 216verify-browser: .installed 217 @echo "Running browser scenarios (sandbox)..." 218 @$(MAKE) sandbox 219 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 220 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 221 RESULT=0; \ 222 $(VENV_BIN)/python tests/verify_browser.py verify --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 223 $(MAKE) sandbox-stop; \ 224 exit $$RESULT 225 226# Re-capture all browser baseline screenshots 227update-browser-baselines: .installed 228 @echo "Updating browser baselines (sandbox)..." 229 @$(MAKE) sandbox 230 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 231 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 232 RESULT=0; \ 233 $(VENV_BIN)/python tests/verify_browser.py update --base-url "http://localhost:$$CONVEY_PORT" || RESULT=$$?; \ 234 $(MAKE) sandbox-stop; \ 235 exit $$RESULT 236 237# Full product verification: API baselines + browser scenarios 238review: .installed 239 @command -v pinchtab >/dev/null 2>&1 || { \ 240 echo "pinchtab is required for browser verification."; \ 241 echo "Run 'make install-pinchtab' to install it."; \ 242 exit 1; \ 243 } 244 @echo "=== Starting review ===" 245 @$(MAKE) sandbox 246 @SANDBOX_JOURNAL=$$(cat .sandbox.journal); \ 247 CONVEY_PORT=$$(cat "$$SANDBOX_JOURNAL/health/convey.port"); \ 248 BASE_URL="http://localhost:$$CONVEY_PORT"; \ 249 RESULT_API=0; \ 250 RESULT_BROWSER=0; \ 251 echo ""; \ 252 echo "=== API baseline verification ==="; \ 253 _SOLSTONE_JOURNAL_OVERRIDE="$$SANDBOX_JOURNAL" $(VENV_BIN)/python tests/verify_api.py verify --base-url "$$BASE_URL" || RESULT_API=$$?; \ 254 echo ""; \ 255 echo "=== Browser scenario verification ==="; \ 256 $(VENV_BIN)/python tests/verify_browser.py verify --base-url "$$BASE_URL" || RESULT_BROWSER=$$?; \ 257 echo ""; \ 258 echo "=== Stopping sandbox ==="; \ 259 $(MAKE) sandbox-stop; \ 260 echo ""; \ 261 echo "=== Review Summary ==="; \ 262 if [ $$RESULT_API -eq 0 ]; then \ 263 echo " API: PASS"; \ 264 else \ 265 echo " API: FAIL"; \ 266 fi; \ 267 if [ $$RESULT_BROWSER -eq 0 ]; then \ 268 echo " Browser: PASS"; \ 269 else \ 270 echo " Browser: FAIL"; \ 271 fi; \ 272 echo ""; \ 273 if [ $$RESULT_API -eq 0 ] && [ $$RESULT_BROWSER -eq 0 ]; then \ 274 echo "Review: ALL PASS"; \ 275 else \ 276 echo "Review: FAIL"; \ 277 exit 1; \ 278 fi 279 280# Test environment - use fixtures journal for all tests 281TEST_ENV = _SOLSTONE_JOURNAL_OVERRIDE=tests/fixtures/journal 282 283# Venv tool shortcuts 284PYTEST := $(VENV_BIN)/pytest 285RUFF := $(VENV_BIN)/ruff 286MYPY := $(VENV_BIN)/mypy 287 288# Run core tests (excluding integration and app tests) 289test: .installed 290 @echo "Running core tests..." 291 $(TEST_ENV) $(PYTEST) tests/ -q --cov=. --ignore=tests/integration 292 293# Run app tests 294test-apps: .installed 295 @echo "Running app tests..." 296 $(TEST_ENV) $(PYTEST) apps/ -q 297 298# Run specific app tests 299test-app: .installed 300 @if [ -z "$(APP)" ]; then \ 301 echo "Usage: make test-app APP=<app_name>"; \ 302 echo "Example: make test-app APP=todos"; \ 303 exit 1; \ 304 fi 305 $(TEST_ENV) $(PYTEST) apps/$(APP)/tests/ -v 306 307# Run specific test file or pattern 308test-only: .installed 309 @if [ -z "$(TEST)" ]; then \ 310 echo "Usage: make test-only TEST=<test_file_or_pattern>"; \ 311 echo "Example: make test-only TEST=tests/test_utils.py"; \ 312 echo "Example: make test-only TEST=\"-k test_function_name\""; \ 313 exit 1; \ 314 fi 315 $(TEST_ENV) $(PYTEST) $(TEST) 316 317# Run integration tests 318test-integration: .installed 319 @echo "Running integration tests..." 320 $(TEST_ENV) $(PYTEST) tests/integration/ -v --tb=short --timeout=20 321 322# Run specific integration test 323test-integration-only: .installed 324 @if [ -z "$(TEST)" ]; then \ 325 echo "Usage: make test-integration-only TEST=<test_file_or_pattern>"; \ 326 echo "Example: make test-integration-only TEST=test_api.py"; \ 327 exit 1; \ 328 fi 329 $(TEST_ENV) $(PYTEST) tests/integration/$(TEST) --timeout=20 330 331# Run all tests (core + apps + integration) 332test-all: .installed 333 @echo "Running all tests (core + apps + integration)..." 334 $(TEST_ENV) $(PYTEST) tests/ -v --cov=. && $(TEST_ENV) $(PYTEST) apps/ -v --cov=. --cov-append 335 336# Auto-format and fix code, then report any remaining issues 337format: .installed 338 @echo "Formatting and fixing code with ruff..." 339 @$(RUFF) format . 340 @$(RUFF) check --fix . 341 @echo "" 342 @echo "Checking for remaining issues..." 343 @RUFF_OK=true; MYPY_OK=true; \ 344 $(RUFF) check . || RUFF_OK=false; \ 345 $(MYPY) . || MYPY_OK=false; \ 346 if $$RUFF_OK && $$MYPY_OK; then \ 347 echo ""; \ 348 echo "All clean!"; \ 349 else \ 350 echo ""; \ 351 echo "Issues above need manual fixes."; \ 352 fi 353 354# Clean build artifacts and cache files 355clean: 356 @echo "Cleaning build artifacts and cache files..." 357 rm -rf build/ dist/ *.egg-info/ 358 rm -rf .pytest_cache/ .coverage .mypy_cache/ 359 rm -rf .agents/ .claude/ 360 find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true 361 find . -type f -name "*.pyc" -delete 362 find . -type f -name "*.pyo" -delete 363 find . -type f -name ".DS_Store" -delete 364 rm -f .installed 365 366# Service management (override port: make install-service PORT=8000) 367install-service: .installed 368 $(VENV_BIN)/sol service install --port $(or $(PORT),5015) 369 $(VENV_BIN)/sol service start 370 $(VENV_BIN)/sol service status 371 372uninstall-service: 373 -$(VENV_BIN)/sol service uninstall 374 375# Uninstall - remove venv and sol symlink 376uninstall: uninstall-service clean 377 @echo "Removing virtual environment..." 378 rm -rf $(VENV) 379 @if [ -L $(USER_BIN)/sol ]; then \ 380 echo "Removing sol symlink from $(USER_BIN)..."; \ 381 rm -f $(USER_BIN)/sol; \ 382 fi 383 384# Clean everything and reinstall 385clean-install: uninstall install 386 387# Run continuous integration checks (what CI would run) 388ci: .installed 389 @echo "Running CI checks..." 390 @echo "=== Checking formatting ===" 391 @$(RUFF) format --check . || { echo "Run 'make format' to fix formatting"; exit 1; } 392 @echo "" 393 @echo "=== Running ruff ===" 394 @$(RUFF) check . || { echo "Run 'make format' to auto-fix"; exit 1; } 395 @echo "" 396 @echo "=== Running mypy ===" 397 @$(MYPY) . || true 398 @echo "" 399 @echo "=== Running tests ===" 400 @$(MAKE) test 401 @echo "" 402 @echo "All CI checks passed!" 403 404# Watch for changes and run tests (requires pytest-watch) 405watch: .installed 406 @$(UV) pip show pytest-watch >/dev/null 2>&1 || { echo "Installing pytest-watch..."; $(UV) pip install pytest-watch; } 407 $(VENV_BIN)/ptw -- -q 408 409# Generate coverage report (core + apps, excluding core integration tests) 410coverage: .installed 411 $(TEST_ENV) $(PYTEST) tests/ --cov=. --cov-report=html --cov-report=term --ignore=tests/integration 412 $(TEST_ENV) $(PYTEST) apps/ --cov=. --cov-report=html --cov-report=term --cov-append 413 @echo "Coverage report generated in htmlcov/index.html" 414 415# Update all dependencies to latest versions and refresh genai-prices 416update: .installed 417 @echo "Updating all dependencies to latest versions..." 418 $(UV) lock --upgrade 419 $(UV) sync 420 @echo "Done. All packages updated to latest." 421 422# Update genai-prices to get latest model pricing data 423# Run this when adding new models or if pricing tests fail 424update-prices: .installed 425 @echo "Updating genai-prices to latest version..." 426 $(UV) lock --upgrade-package genai-prices 427 $(UV) sync 428 @echo "Done. Re-run tests to verify model pricing support." 429 430# Show installed package versions 431versions: .installed 432 @echo "=== Python version ===" 433 $(PYTHON) --version 434 @echo "" 435 @echo "=== Key package versions ===" 436 @$(UV) pip list | grep -E "^(pytest|ruff|mypy|Flask|numpy|Pillow|openai|anthropic|google-genai)" || true 437 438# Install pre-commit hooks (if using pre-commit) 439pre-commit: .installed 440 @$(UV) pip show pre-commit >/dev/null 2>&1 || { echo "Installing pre-commit..."; $(UV) pip install pre-commit; } 441 $(VENV_BIN)/pre-commit install 442 @echo "Pre-commit hooks installed!"