.PHONY: help build test fmt fmt-check clippy check clean example-clean coverage run-plan logo example example-cancel time check-agent check-round-trip check-gh check-service check-mock cli book book-serve book-deploy copilot-review release
help:
@echo "Available targets:"
@echo " build - Build the project"
@echo " test - Run all tests"
@echo " fmt - Format code with rustfmt"
@echo " fmt-check - Check code formatting"
@echo " clippy - Run clippy lints"
@echo " check - Quick check (fmt + clippy + test)"
@echo " coverage - Generate coverage report (requires cargo-llvm-cov)"
@echo " clean - Clean build artifacts (cargo clean)"
@echo " example-clean - Remove auto-generated files from examples"
@echo " logo - Compile logo (requires typst)"
@echo " run-plan - Execute a plan with Claude headless autorun"
@echo " example - Run an example (DIR=examples/mr-lazy or DIR=examples/chess-by-mail)"
@echo " example-cancel - Stop a running example (DIR=examples/...)"
@echo " time - Show current time or compute offset (OFFSET=\"+1 day\")"
@echo " check-agent - Quick agent smoke test (runs agent once)"
@echo " check-round-trip - Full round-trip test with mr-lazy (daemon, Ctrl-C to stop)"
@echo " check-gh - Verify GitHub Discussion sync (requires gh auth)"
@echo " check-service - Verify OS service install/uninstall (launchd/systemd)"
@echo " check-mock - Run mock agent integration tests"
@echo " cli - Install the cryo CLI locally"
@echo " book - Build mdbook documentation"
@echo " book-serve - Serve mdbook locally with live reload"
@echo " book-deploy - Deploy mdbook to GitHub Pages (gh-pages branch)"
@echo " copilot-review - Request Copilot code review on current PR"
@echo " release V=x.y.z - Tag and push a release (triggers CI publish)"
build:
cargo build
test:
cargo test
fmt:
cargo fmt --all
fmt-check:
cargo fmt --all -- --check
clippy:
cargo clippy --all-targets -- -D warnings
check: fmt-check clippy test
@echo "All checks passed!"
coverage:
@command -v cargo-llvm-cov >/dev/null 2>&1 || { echo "Installing cargo-llvm-cov..."; cargo install cargo-llvm-cov; }
cargo llvm-cov --workspace --html --open
logo:
typst compile docs/logo/logo.typ docs/logo/logo.svg
typst compile docs/logo/logo.typ docs/logo/logo.png --ppi 300
clean:
cargo clean
example-clean:
@for dir in examples/*/; do \
if [ -f "$(CURDIR)/$$dir/timer.json" ]; then \
cd "$(CURDIR)/$$dir" && $(CURDIR)/target/debug/cryo cancel 2>/dev/null; \
fi; \
done; true
rm -f examples/*/CLAUDE.md examples/*/AGENTS.md
rm -f examples/*/*.log examples/*/*.json
rm -rf examples/*/messages examples/*/.cryo
INSTRUCTIONS ?=
OUTPUT ?= claude-output.log
AGENT_TYPE ?= claude
PLAN_FILE ?= $(shell ls -t docs/plans/*.md 2>/dev/null | head -1)
run-plan:
@NL=$$'\n'; \
BRANCH=$$(git branch --show-current); \
if [ "$(AGENT_TYPE)" = "claude" ]; then \
PROCESS="1. Read the plan file$${NL}2. Use /subagent-driven-development to execute tasks$${NL}3. Push: git push origin $$BRANCH$${NL}4. Create a pull request"; \
else \
PROCESS="1. Read the plan file$${NL}2. Execute the tasks step by step. For each task, implement and test before moving on.$${NL}3. Push: git push origin $$BRANCH$${NL}4. Create a pull request"; \
fi; \
PROMPT="Execute the plan in '$(PLAN_FILE)'."; \
if [ -n "$(INSTRUCTIONS)" ]; then \
PROMPT="$${PROMPT}$${NL}$${NL}## Additional Instructions$${NL}$(INSTRUCTIONS)"; \
fi; \
PROMPT="$${PROMPT}$${NL}$${NL}## Process$${NL}$${PROCESS}$${NL}$${NL}## Rules$${NL}- Tests should be strong enough to catch regressions.$${NL}- Do not modify tests to make them pass.$${NL}- Test failure must be reported."; \
echo "=== Prompt ===" && echo "$$PROMPT" && echo "===" ; \
claude --dangerously-skip-permissions \
--model opus \
--verbose \
--max-turns 500 \
-p "$$PROMPT" 2>&1 | tee "$(OUTPUT)"
cli:
cargo install --path .
example: build
@if [ -z "$(DIR)" ]; then echo "Usage: make example DIR=examples/mr-lazy"; exit 1; fi
@if [ -f "$(DIR)/timer.json" ]; then (cd "$(DIR)" && $(CURDIR)/target/debug/cryo cancel 2>/dev/null); fi; \
cd "$(DIR)" && rm -rf .cryo timer.json cryo.log cryo-agent.log messages AGENTS.md CLAUDE.md && \
$(CURDIR)/target/debug/cryo init --agent "$(AGENT)" && $(CURDIR)/target/debug/cryo start --agent "$(AGENT)" && \
$(CURDIR)/target/debug/cryo web
example-cancel:
cd "$(DIR)" && $(CURDIR)/target/debug/cryo cancel
AGENT ?= opencode
CHECK_TIMEOUT ?= 3000
check-agent: build
@TMPDIR=$$(mktemp -d /tmp/cryo-check-XXXXXX); \
cp examples/mr-lazy/plan.md "$$TMPDIR/plan.md"; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo init --agent "$(AGENT)"; \
echo "=== Agent Health Check ==="; \
echo "Agent: $(AGENT)"; \
echo ""; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo start \
--agent "$(AGENT)" \
--max-session-duration $(CHECK_TIMEOUT) 2>&1; \
RC=$$?; \
if [ $$RC -ne 0 ]; then \
echo "FAIL: cryo start failed (exit code $$RC)"; \
rm -rf "$$TMPDIR"; \
exit 1; \
fi; \
echo ""; \
echo "=== Session Log (Ctrl-C to stop) ==="; \
trap 'cd "'"$$TMPDIR"'" && '"$(CURDIR)"'/target/debug/cryo cancel 2>/dev/null; rm -rf "'"$$TMPDIR"'"; exit 0' INT TERM; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo watch --all; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo cancel 2>/dev/null; \
rm -rf "$$TMPDIR"
check-round-trip: build
@echo "=== Round-Trip Test (mr-lazy) ==="
@PROG=$$(echo "$(AGENT)" | awk '{print $$1}'); \
echo "Agent: $(AGENT)"; \
echo "Timeout: $(CHECK_TIMEOUT)s"; \
echo ""; \
echo "1. Checking if $$PROG is in PATH..."; \
if command -v "$$PROG" >/dev/null 2>&1; then \
echo " OK: $$(command -v $$PROG)"; \
else \
echo " FAIL: '$$PROG' not found in PATH"; exit 1; \
fi; \
echo ""; \
echo "2. Starting mr-lazy daemon..."; \
TMPDIR=$$(mktemp -d /tmp/cryo-check-XXXXXX); \
cp examples/mr-lazy/plan.md "$$TMPDIR/plan.md"; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo init --agent "$(AGENT)"; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo start \
--agent "$(AGENT)" \
--max-session-duration $(CHECK_TIMEOUT) 2>&1; \
RC=$$?; \
echo ""; \
if [ $$RC -ne 0 ]; then \
echo " FAIL: cryo daemon failed to start (exit code $$RC)"; \
echo " Last 10 lines of log:"; \
tail -10 "$$TMPDIR/cryo.log" 2>/dev/null | sed 's/^/ | /' || echo " (no log)"; \
rm -rf "$$TMPDIR"; \
exit 1; \
fi; \
echo " OK: Daemon started. Watching log (Ctrl-C to stop)..."; \
echo ""; \
trap 'echo ""; echo "Stopping daemon..."; cd "'"$$TMPDIR"'" && '"$(CURDIR)"'/target/debug/cryo cancel 2>/dev/null; rm -rf "'"$$TMPDIR"'"; echo "=== Done ==="; exit 0' INT TERM; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo watch --all; \
echo ""; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo cancel 2>/dev/null; \
rm -rf "$$TMPDIR"; \
echo "=== Round-trip test done ==="
REPO ?= GiggleLiu/cryochamber
check-gh: build
@echo "=== GitHub Sync Check ==="
@echo "1. Checking gh CLI..."; \
if command -v gh >/dev/null 2>&1; then \
echo " OK: $$(command -v gh)"; \
else \
echo " FAIL: 'gh' not found. Install: https://cli.github.com"; exit 1; \
fi; \
echo ""; \
echo "2. Checking gh authentication..."; \
if gh auth status >/dev/null 2>&1; then \
echo " OK: authenticated as $$(gh api user -q .login)"; \
else \
echo " FAIL: not authenticated. Run: gh auth login"; exit 1; \
fi; \
echo ""; \
echo "3. Creating test Discussion in $(REPO)..."; \
TMPDIR=$$(mktemp -d /tmp/cryo-check-gh-XXXXXX); \
printf '# Health Check\n\nThis is an automated test.\n' > "$$TMPDIR/plan.md"; \
cd "$$TMPDIR" && \
$(CURDIR)/target/debug/cryo-gh init --repo "$(REPO)" --title "[Cryo] Health Check $$(date +%Y%m%d-%H%M%S)"; \
RC=$$?; \
if [ $$RC -ne 0 ]; then \
echo " FAIL: could not create Discussion"; \
rm -rf "$$TMPDIR"; \
exit 1; \
fi; \
echo " OK: Discussion created"; \
echo ""; \
echo "4. Posting test comment..."; \
mkdir -p "$$TMPDIR/messages/inbox"; \
printf '--- CRYO SESSION 1 ---\ntask: health check\nagent: gh-check\ninbox: 0 messages\n[00:00:01] agent started (pid 1)\n[00:00:02] hibernate: complete, exit=0, summary="Health check passed"\n[00:00:02] agent exited (code 0)\n--- CRYO END ---\n' > "$$TMPDIR/cryo.log"; \
printf '{"plan_path":"plan.md","session_number":1,"last_command":null,"pid":null,"max_retries":1,"retry_count":0,"max_session_duration":300,"watch_inbox":false,"daemon_mode":false}' > "$$TMPDIR/timer.json"; \
$(CURDIR)/target/debug/cryo-gh push; \
RC=$$?; \
if [ $$RC -ne 0 ]; then \
echo " FAIL: could not post comment"; \
rm -rf "$$TMPDIR"; \
exit 1; \
fi; \
echo " OK: comment posted"; \
rm -rf "$$TMPDIR"; \
echo ""; \
echo "=== GitHub sync check passed ==="
check-service: build
@echo "=== Service Lifecycle Check ==="
@echo "Platform: $$(uname -s)"
@echo ""
@echo "1. Setting up test project..."
@TMPDIR=$$(mktemp -d /tmp/cryo-check-svc-XXXXXX); \
cp examples/mr-lazy/plan.md "$$TMPDIR/plan.md"; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo init --agent "$(AGENT)"; \
echo " OK: $$TMPDIR"; \
echo ""; \
echo "2. Installing daemon service (cryo start)..."; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo start \
--agent "/bin/sh -c 'sleep 600'" \
--max-session-duration 600 2>&1; \
RC=$$?; \
if [ $$RC -ne 0 ]; then \
echo " FAIL: cryo start failed (exit $$RC)"; \
rm -rf "$$TMPDIR"; exit 1; \
fi; \
echo " OK: service installed"; \
echo ""; \
echo "3. Verifying service is running..."; \
sleep 2; \
if [ "$$(uname -s)" = "Darwin" ]; then \
SVC_FILE=$$(ls -t ~/Library/LaunchAgents/com.cryo.daemon.*.plist 2>/dev/null | head -1); \
if [ -n "$$SVC_FILE" ]; then \
echo " OK: plist found: $$(basename $$SVC_FILE)"; \
else \
echo " FAIL: no launchd plist found"; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo cancel 2>/dev/null; \
rm -rf "$$TMPDIR"; exit 1; \
fi; \
else \
SVC_FILE=$$(ls -t ~/.config/systemd/user/com.cryo.daemon.*.service 2>/dev/null | head -1); \
if [ -n "$$SVC_FILE" ]; then \
echo " OK: unit found: $$(basename $$SVC_FILE)"; \
else \
echo " FAIL: no systemd unit found"; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo cancel 2>/dev/null; \
rm -rf "$$TMPDIR"; exit 1; \
fi; \
fi; \
PID=$$(cd "$$TMPDIR" && cat timer.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('pid',''))" 2>/dev/null); \
if [ -n "$$PID" ] && kill -0 "$$PID" 2>/dev/null; then \
echo " OK: daemon process alive (PID $$PID)"; \
else \
echo " WARN: daemon PID not found in timer.json (may still be starting)"; \
fi; \
echo ""; \
echo "4. Cancelling (cryo cancel)..."; \
cd "$$TMPDIR" && $(CURDIR)/target/debug/cryo cancel 2>&1; \
RC=$$?; \
if [ $$RC -ne 0 ]; then \
echo " FAIL: cryo cancel failed (exit $$RC)"; \
rm -rf "$$TMPDIR"; exit 1; \
fi; \
echo " OK: cancelled"; \
echo ""; \
echo "5. Verifying service removed..."; \
if [ -e "$$SVC_FILE" ]; then \
echo " FAIL: service file still exists: $$SVC_FILE"; \
rm -rf "$$TMPDIR"; exit 1; \
else \
echo " OK: service file removed ($$SVC_FILE)"; \
fi; \
rm -rf "$$TMPDIR"; \
echo ""; \
echo "=== Service lifecycle check passed ==="; \
echo ""; \
echo "To test reboot persistence, run manually:"; \
echo " cd /tmp/cryo-reboot-test && cryo init && cryo start --agent '/bin/sh -c sleep 999'"; \
echo " echo " echo " echo "
check-mock:
cargo test --test mock_agent_tests -- --nocapture --test-threads=1
book:
@command -v mdbook >/dev/null 2>&1 || { echo "Installing mdbook..."; cargo install mdbook; }
mdbook build
book-serve:
@command -v mdbook >/dev/null 2>&1 || { echo "Installing mdbook..."; cargo install mdbook; }
mdbook serve --open
book-deploy: book
@echo "=== Deploying to gh-pages ==="
@TMPDIR=$$(mktemp -d); \
cp -r book/* "$$TMPDIR/"; \
cd "$$TMPDIR" && \
git init && \
git checkout -b gh-pages && \
git add -A && \
git commit -m "Deploy mdbook" && \
git remote add origin "$$(cd "$(CURDIR)" && git remote get-url origin)" && \
git push --force origin gh-pages; \
rm -rf "$$TMPDIR"; \
echo "=== Deployed to gh-pages ==="
release:
ifndef V
$(error Usage: make release V=x.y.z)
endif
@echo "Releasing v$(V)..."
sed -i 's/^version = ".*"/version = "$(V)"/' Cargo.toml
cargo check
git add Cargo.toml
git commit -m "release: v$(V)"
git tag -a "v$(V)" -m "Release v$(V)"
git push origin main --tags
@echo "v$(V) pushed — CI will publish to crates.io"
copilot-review:
@PR=$$(gh pr view --json number --jq .number 2>/dev/null) || { echo "No PR found for current branch"; exit 1; }; \
echo "Requesting Copilot review on PR gh copilot-review $$PR