Makefile
Rama main · réplica de lectura del repositorio original.
.PHONY: install start start-build start-prod stop logs clean build dev-backend run-backend dev-frontend setup check check-diff docs-reanchor test-backend test-backend-cov test-backend-lib install-dev-tools require-nextest test-shell lint-backend .env kiro-login bump check-version desktop desktop-dev desktop-target
# Doc-skippers type `make` bare (or `make install`) before reading anything.
# Bare `make` used to run the FIRST target — `_gen-override`, an internal Docker
# helper — a baffling near-no-op. Default to help instead; it points at the
# guided entry (`./kronn start`).
.DEFAULT_GOAL := help
# ─── Configuration ───────────────────────────────────────────────────────────
APP_NAME := kronn
VERSION := $(shell cat VERSION 2>/dev/null || echo "0.0.0")
PORT := 3140
DOCKER_COMP := docker compose
# In-place sed differs between GNU (`sed -i`) and BSD/macOS (`sed -i ''`).
# Detected once so `make bump` works on both — the GNU-only syntax used to
# abort mid-bump on macOS (observed on the 0.8.10 bump).
SEDI := $(shell sed --version >/dev/null 2>&1 && echo "-i" || echo "-i ''")
# ─── Colors ──────────────────────────────────────────────────────────────────
RED := \033[0;31m
GREEN := \033[0;32m
YELLOW := \033[0;33m
CYAN := \033[0;36m
RESET := \033[0m
# ─── Main Commands ───────────────────────────────────────────────────────────
## Auto-detect host environment and write .env for docker compose
.env:
@echo "# Auto-generated by Kronn — do not edit" > .env
@uname_s=$$(uname -s 2>/dev/null); \
if [ "$$uname_s" = "Darwin" ]; then \
echo "KRONN_HOST_OS=macOS" >> .env; \
echo "KRONN_MACOS_SSH_SOCK=/run/host-services/ssh-auth.sock" >> .env; \
if [ -d /opt/homebrew/bin ]; then \
echo "KRONN_GLOBAL_BIN=/opt/homebrew/bin" >> .env; \
echo "$(CYAN) Detected macOS (Apple Silicon)$(RESET)"; \
elif [ -d /usr/local/bin ]; then \
echo "KRONN_GLOBAL_BIN=/usr/local/bin" >> .env; \
echo "$(CYAN) Detected macOS (Intel)$(RESET)"; \
fi; \
elif grep -qiE "microsoft|wsl" /proc/version 2>/dev/null; then \
echo "KRONN_HOST_OS=WSL" >> .env; \
echo "$(CYAN) Detected WSL2$(RESET)"; \
else \
echo "KRONN_HOST_OS=Linux" >> .env; \
echo "$(CYAN) Detected Linux$(RESET)"; \
fi
@# Detect npm global bin directory (where `npm install -g` puts binaries)
@npm_bin=$$(npm bin -g 2>/dev/null || npm config get prefix 2>/dev/null | xargs -I{} echo "{}/bin"); \
if [ -d "$$npm_bin" ]; then \
echo "KRONN_NPM_BIN=$$npm_bin" >> .env; \
echo "$(CYAN) npm global bin: $$npm_bin$(RESET)"; \
fi
@echo "KRONN_HOST_UID=$$(id -u)" >> .env
@echo "KRONN_HOST_GID=$$(id -g)" >> .env
@echo "UID=$$(id -u)" >> .env
@echo "GID=$$(id -g)" >> .env
@# Confirm repos dir on first run; inference remains the default answer.
@# Edge case (TD-20260507-makefile-repos-rel-home-edge): when Kronn lives
@# directly under $$HOME, the parent IS $$HOME, which collides with the
@# read-only `${HOME}:/host-home:ro` mount in docker-compose.yml. Refuse
@# rather than silently produce a broken mount layout — the operator
@# either moves Kronn under a sub-dir OR sets KRONN_REPOS_DIR explicitly.
@home_dir=$$(cd "$$HOME" && pwd -P); \
repos_dir=$$(cd "$(CURDIR)/.." && pwd -P); \
if [ -n "$$KRONN_REPOS_DIR" ]; then repos_dir="$$KRONN_REPOS_DIR"; \
elif [ -t 0 ]; then \
echo "$(CYAN)Kronn must know where it may write project files (.mcp.json, agent instructions, worktrees).$(RESET)"; \
printf "$(CYAN)Primary repositories directory [$$repos_dir]: $(RESET)"; \
read -r confirmed_repos; \
if [ -n "$$confirmed_repos" ]; then repos_dir="$$confirmed_repos"; fi; \
fi; \
if [ -d "$$repos_dir" ]; then repos_dir=$$(cd "$$repos_dir" && pwd -P); fi; \
if [ -z "$$KRONN_REPOS_DIR" ] && [ "$$repos_dir" = "$$home_dir" ]; then \
echo "$(RED)KRONN_REPOS_DIR auto-detect picked $$HOME, which collides with the read-only /host-home mount.$(RESET)"; \
echo "$(YELLOW)The parent of Kronn becomes the rw repositories directory: move Kronn into the directory that already contains your repos.$(RESET)"; \
echo "$(YELLOW)Or set KRONN_REPOS_DIR for the primary directory; use KRONN_EXTRA_REPOS for additional directories.$(RESET)"; \
exit 1; \
fi; \
case "$$repos_dir" in \
"$$home_dir"/*) repos_rel=$${repos_dir#$$home_dir/} ;; \
*) repos_rel=.kronn-external-repos ;; \
esac; \
echo "KRONN_REPOS_DIR=$$repos_dir" >> .env; \
echo "KRONN_REPOS_REL=$$repos_rel" >> .env; \
echo "$(CYAN) Repos dir confirmed: $$repos_dir (rw mount)$(RESET)"; \
case "$$repos_dir" in /mnt/[a-zA-Z]/*) \
echo "$(YELLOW) WSL warning: /mnt/c-style repositories are much slower for Docker/agents. Prefer the Linux filesystem under $$HOME.$(RESET)" ;; \
esac; \
extra_repos="$${KRONN_EXTRA_REPOS:-}"; \
if [ -z "$$extra_repos" ] && [ -t 0 ]; then \
printf "$(CYAN)Additional repositories directories (colon-separated, optional): $(RESET)"; \
read -r extra_repos; \
fi; \
if [ -n "$$extra_repos" ]; then \
echo "KRONN_EXTRA_REPOS=$$extra_repos" >> .env; \
echo "$(CYAN) Additional repos: $$extra_repos (rw mounts)$(RESET)"; \
fi
@# Docker socket GID (for docker compose access from container)
@if [ -S /var/run/docker.sock ]; then \
docker_gid=$$(stat -c '%g' /var/run/docker.sock 2>/dev/null || stat -f '%g' /var/run/docker.sock 2>/dev/null || echo ""); \
if [ -n "$$docker_gid" ]; then \
echo "KRONN_DOCKER_GID=$$docker_gid" >> .env; \
echo "$(CYAN) Docker socket GID: $$docker_gid$(RESET)"; \
fi; \
fi
@# 0.8.2 — Auto-detect version-manager bin paths so workflow Exec
@# steps can call `cargo`, `bun`, etc. without manual config. Each
@# path is written ONLY if the directory exists; missing → silent skip
@# so users without rustup/Bun aren't penalized.
@if [ -d "$$HOME/.cargo/bin" ]; then \
echo "KRONN_CARGO_BIN=$$HOME/.cargo/bin" >> .env; \
echo "$(CYAN) Cargo bin: $$HOME/.cargo/bin$(RESET)"; \
fi
@if [ -d "$$HOME/.rustup" ]; then \
echo "KRONN_RUSTUP_HOME=$$HOME/.rustup" >> .env; \
echo "$(CYAN) Rustup home: $$HOME/.rustup$(RESET)"; \
fi
@if [ -d "$$HOME/.bun/bin" ]; then \
echo "KRONN_BUN_BIN=$$HOME/.bun/bin" >> .env; \
echo "$(CYAN) Bun bin: $$HOME/.bun/bin$(RESET)"; \
fi
@# Auto-detect host system binary paths for terminal access
@sysbin=""; \
for dir in /usr/bin /usr/sbin /snap/bin; do \
[ -d "$$dir" ] && sysbin="$${sysbin:+$$sysbin:}$$dir"; \
done; \
if [ -n "$$sysbin" ]; then \
echo "KRONN_HOST_SYSBIN=$$sysbin" >> .env; \
echo "$(CYAN) Host system binaries: $$sysbin$(RESET)"; \
fi
@# Auto-detect host network IPs for multi-user identity display
@host_ips=$$(ip -4 addr show 2>/dev/null | awk '/^[0-9]/{split($$2,a,/[:@]/); iface=a[1]} /inet /{split($$2,a,"/"); ip=a[1]; if(ip~/^127\./)next; if(iface~/^(lo|docker|br-|veth)/)next; printf "%s%s:%s",sep,iface,ip; sep=","}'); \
if [ -n "$$host_ips" ]; then \
echo "KRONN_HOST_IPS=$$host_ips" >> .env; \
echo "$(CYAN) Host IPs: $$host_ips$(RESET)"; \
fi
@echo "# Add extra repo dirs (colon-separated) for rw access:" >> .env
@echo "# KRONN_EXTRA_REPOS=$$HOME/Desktop/projects:$$HOME/Work" >> .env
@# Ensure .claude.json exists as a file (Docker creates a directory if missing)
@if [ ! -e "$$HOME/.claude.json" ]; then \
echo '{}' > "$$HOME/.claude.json"; \
echo "$(CYAN) Created ~/.claude.json (empty file)$(RESET)"; \
elif [ -d "$$HOME/.claude.json" ]; then \
echo "$(YELLOW) WARNING: ~/.claude.json is a directory — fixing$(RESET)"; \
rmdir "$$HOME/.claude.json" 2>/dev/null && echo '{}' > "$$HOME/.claude.json" || \
echo "$(RED) ERROR: Cannot fix ~/.claude.json — remove it manually$(RESET)"; \
fi
## Create the ${HOME} bind-mount sources as the user before Docker does it as
## root (scripts/docker-bind-sources.sh).
.PHONY: _ensure-bind-sources
_ensure-bind-sources:
@./scripts/docker-bind-sources.sh docker-compose.yml "$$HOME"
## Generate docker-compose.override.yml: empty read-only mounts over the host's
## secrets (scripts/docker-secret-masks.sh), extra repo dirs (KRONN_EXTRA_REPOS)
## and, only when KRONN_DOCKER_SOCKET=1, the host's Docker socket and group.
.PHONY: _gen-override
_gen-override:
@extra_repos=$$(sed -n 's/^KRONN_EXTRA_REPOS=//p' .env | tail -n 1); \
docker_socket=$$(sed -n 's/^KRONN_DOCKER_SOCKET=//p' .env | tail -n 1); \
docker_gid=$$(sed -n 's/^KRONN_DOCKER_GID=//p' .env | tail -n 1); \
masks=$$(./scripts/docker-secret-masks.sh "$$HOME" ./.docker/empty ./.docker/empty-file); \
if [ -z "$$masks" ] && [ -z "$$extra_repos" ] && [ "$$docker_socket" != "1" ]; then \
rm -f docker-compose.override.yml; \
else \
echo "# Auto-generated — host secret masks, KRONN_EXTRA_REPOS rw mounts, opt-in Docker socket" > docker-compose.override.yml; \
echo "services:" >> docker-compose.override.yml; \
echo " backend:" >> docker-compose.override.yml; \
if [ "$$docker_socket" = "1" ]; then \
echo " group_add:" >> docker-compose.override.yml; \
echo " - \"$${docker_gid:-989}\"" >> docker-compose.override.yml; \
echo "$(YELLOW) Docker socket enabled (KRONN_DOCKER_SOCKET=1): agents can control Docker, and so this machine.$(RESET)"; \
fi; \
echo " volumes:" >> docker-compose.override.yml; \
if [ "$$docker_socket" = "1" ]; then echo " - /var/run/docker.sock:/var/run/docker.sock" >> docker-compose.override.yml; fi; \
if [ -n "$$masks" ]; then printf '%s\n' "$$masks" >> docker-compose.override.yml; fi; \
if [ -n "$$extra_repos" ]; then \
IFS=':'; for dir in $$extra_repos; do \
case "$$dir" in \
"$$HOME"/*) rel=$${dir#$$HOME/}; target="/host-home/$$rel" ;; \
*) target="$$dir" ;; \
esac; \
echo " - $$dir:$$target:rw" >> docker-compose.override.yml; \
echo "$(CYAN) Extra rw mount: $$dir$(RESET)"; \
done; \
fi; \
fi
## Write/remove KRONN_RUST_LOG in .env based on DEBUG=1.
## docker-compose.yml reads `RUST_LOG=${KRONN_RUST_LOG:-}`, so setting it
## here enables verbose backend logs for the next `up -d` without touching
## the persisted `config.server.debug_mode`.
_apply-debug-flag:
@sed -i.bak '/^KRONN_RUST_LOG=/d' .env 2>/dev/null || true
@rm -f .env.bak
@if [ "$(DEBUG)" = "1" ]; then \
echo "KRONN_RUST_LOG=kronn=debug,tower_http=debug" >> .env; \
echo "$(CYAN) Debug mode: verbose logs enabled (DEBUG=1)$(RESET)"; \
fi
## Snapshot the machine hosts file for the backend container. A direct bind of
## `/etc/hosts` is unreliable on Docker Desktop and OrbStack: it resolves to the
## Linux VM's file instead of the macOS host file.
.PHONY: _snapshot-host-hosts
_snapshot-host-hosts: .env
@mkdir -p .docker/runtime
@hosts_source="$${KRONN_HOSTS_FILE:-/etc/hosts}"; \
snapshot_tmp=".docker/runtime/hosts.tmp"; \
env_tmp=".env.hosts.tmp"; \
snapshot="./.docker/host-hosts-unavailable"; \
if [ -f "$$hosts_source" ] && cp "$$hosts_source" "$$snapshot_tmp"; then \
mv "$$snapshot_tmp" .docker/runtime/hosts; \
snapshot="./.docker/runtime/hosts"; \
else \
echo "$(YELLOW) Host mappings unavailable: $$hosts_source$(RESET)"; \
fi; \
grep -v '^KRONN_HOSTS_SNAPSHOT=' .env > "$$env_tmp" || true; \
echo "KRONN_HOSTS_SNAPSHOT=$$snapshot" >> "$$env_tmp"; \
mv "$$env_tmp" .env
# The universal "I didn't read the docs" entry point. Delegates to the guided
# CLI (`./kronn start`), which handles the macOS Docker warning, prerequisites,
# and points at `./kronn start-dev` (native) where appropriate. NOTE: `start`
# below must NOT delegate to the CLI — kronn's cmd_web calls `make start`
# internally, so that would recurse.
install:
@echo "$(CYAN)Launching the guided Kronn setup (./kronn start)...$(RESET)"
@./kronn start
## Start everything with Docker. Pulls the published images of VERSION and starts
## them without compiling (KT-1468: the backend crate needs ~19 GB of RAM to
## build). On main, VERSION is the last release: this runs its image, not local edits.
## A pull failure (VERSION not published, offline) falls back to a source build behind
## a memory check (scripts/docker-up.sh). `make start-build` forces the source build.
## Pass DEBUG=1 to force verbose logs (kronn=debug,tower_http=debug) for this run.
## Without DEBUG=1, the backend picks the level from config.server.debug_mode.
start: .env _snapshot-host-hosts _ensure-bind-sources _gen-override _apply-debug-flag
@if [ "$$(uname -s)" = "Darwin" ]; then \
echo "$(YELLOW) macOS note: Docker can't run your host agents (Claude, Codex, …) or read the Keychain.$(RESET)"; \
echo "$(YELLOW) For native agent execution prefer: ./kronn start-dev$(RESET)"; \
fi
@./scripts/docker-up.sh
@$(MAKE) --no-print-directory _own-claude-volumes
@echo ""
@echo " $(CYAN)╭──╮$(RESET)"
@echo " $(CYAN)│$(GREEN)⚡$(CYAN)│$(RESET) $(GREEN)Kronn v$(VERSION)$(RESET)"
@echo " $(CYAN)╰──╯$(RESET) Services running."
@echo ""
@echo " → $(GREEN)http://localhost:$(PORT)$(RESET)"
@echo ""
@echo " $(CYAN)kronn stop$(RESET) Stop"
@echo " $(CYAN)kronn logs$(RESET) Logs"
@echo " $(CYAN)kronn restart$(RESET) Restart"
@echo ""
## Same as `make start`, but always builds the images from source (skips the
## pull). Needs ~20 GB of memory for Docker: the memory check stops a smaller
## machine before it compiles; KRONN_FORCE_BUILD=1 overrides it.
start-build: export KRONN_BUILD_FROM_SOURCE := 1
start-build: start
## Production build (Docker, release with LTO — slow but optimized binary)
start-prod: .env _snapshot-host-hosts _ensure-bind-sources _gen-override
@echo "$(GREEN)▸ Building $(APP_NAME) (production release)...$(RESET)"
@KRONN_BUILD_FROM_SOURCE=1 ./scripts/docker-up.sh
@$(MAKE) --no-print-directory _own-claude-volumes
.PHONY: _own-claude-volumes
# Volumes created before the image pre-created their mount points are owned by
# root, so Claude Code cannot save a session and every resumed turn fails.
_own-claude-volumes:
@$(DOCKER_COMP) exec -T -u 0 backend chown kronn:kronn /home/kronn/.claude/projects /home/kronn/.claude/sessions
## Stop all services
stop:
@echo "$(YELLOW)▸ Stopping $(APP_NAME)...$(RESET)"
@$(DOCKER_COMP) down
@echo ""
@echo " $(CYAN)╭──╮$(RESET)"
@echo " $(CYAN)│$(RED)⚡$(CYAN)│$(RESET) $(RED)Kronn v$(VERSION)$(RESET)"
@echo " $(CYAN)╰──╯$(RESET) Services stopped."
@echo ""
## Tail logs
logs:
@$(DOCKER_COMP) logs -f
## Login to Kiro from the backend container (headless-safe OAuth device flow)
kiro-login: .env _gen-override
@echo "$(CYAN)▸ Kiro login (device flow)...$(RESET)"
@$(DOCKER_COMP) exec -u kronn backend /bin/sh -lc 'set -e; PATH="$$HOME/.local/bin:$$PATH"; command -v kiro-cli >/dev/null 2>&1 || (command -v unzip >/dev/null 2>&1 || { echo "Missing unzip in backend image. Rebuild with make start."; exit 1; } ; echo "Installing kiro-cli in container..." ; curl -fsSL https://cli.kiro.dev/install | bash); kiro-cli login --use-device-flow'
## Clean everything
clean:
@echo "$(YELLOW)▸ Cleaning up...$(RESET)"
@$(DOCKER_COMP) down -v --remove-orphans
@rm -rf target backend/target frontend/node_modules frontend/dist
@rm -f docker-compose.override.yml
## Production build (no Docker)
build:
@echo "$(GREEN)▸ Building backend...$(RESET)"
cd backend && cargo build --release
@echo "$(GREEN)▸ Building frontend...$(RESET)"
cd frontend && pnpm install && pnpm build
@echo "$(GREEN)▸ Done. Binary at target/release/$(APP_NAME)$(RESET)"
# ─── Development ─────────────────────────────────────────────────────────────
## Backend dev with hot reload
dev-backend:
@echo "$(GREEN)▸ Starting backend (watch mode)...$(RESET)"
./scripts/dev-backend-supervisor.sh
# Exploitation mode: NO watcher. Dev mode now keeps the old backend serving
# while it compiles, but a successful build still swaps the process and would
# interrupt live agents/audits. Use this whenever runs must survive repo edits.
# NB: .cargo/config.toml shares target-dir at the repo root — the binary
# lands HERE, not in backend/target/.
run-backend:
@echo "$(GREEN)▸ Building backend...$(RESET)"
cd backend && cargo build
@echo "$(GREEN)▸ Starting backend (bare binary — no watcher)...$(RESET)"
./target/debug/kronn
## Frontend dev server
dev-frontend:
@echo "$(GREEN)▸ Starting frontend dev server...$(RESET)"
cd frontend && pnpm install && pnpm dev
## Run both in dev mode (requires tmux or two terminals)
dev:
@echo "$(CYAN)One command runs both (backend hot-reload + Vite):$(RESET)"
@echo " ./kronn start-dev"
@echo "$(YELLOW)Or manually, in two terminals:$(RESET)"
@echo " make dev-backend"
@echo " make dev-frontend"
# ─── Utilities ───────────────────────────────────────────────────────────────
## Check prerequisites
check:
@echo "$(CYAN)Checking prerequisites...$(RESET)"
@command -v docker >/dev/null 2>&1 && echo " ✓ Docker" || echo " ✗ Docker (required)"
@command -v cargo >/dev/null 2>&1 && echo " ✓ Rust/Cargo" || echo " ✗ Rust/Cargo (optional, for dev)"
@command -v node >/dev/null 2>&1 && echo " ✓ Node.js" || echo " ✗ Node.js (optional, for dev)"
@command -v pnpm >/dev/null 2>&1 && echo " ✓ pnpm" || echo " ✗ pnpm (optional, for dev)"
## Generate TypeScript types from Rust models
typegen:
@echo "$(GREEN)▸ Generating ts-rs bindings from Rust models...$(RESET)"
cd backend && cargo test --lib export_bindings -- --nocapture
@echo "$(GREEN)▸ Assembling frontend/src/types/generated.ts from the bindings...$(RESET)"
@node frontend/scripts/assemble-generated-types.mjs
@echo "$(GREEN)▸ generated.ts regenerated — commit it with your Rust model change.$(RESET)"
## Run the backend suite CI runs, with the runner CI uses (cargo-nextest,
## backend/.config/nextest.toml): every test in its own process, then the
## doctests nextest does not run. The ts-rs exports are filtered out by the
## default profile so bindings stay put. No `cargo test` fallback: the
## integration tests share one binary and assume a process per test.
test-backend: require-nextest
@echo "$(CYAN)▸ Running backend tests with cargo-nextest (as CI)...$(RESET)"
cd backend && cargo nextest run --workspace $(NEXTEST_ARGS)
cd backend && cargo test --doc
require-nextest:
@cargo nextest --version >/dev/null 2>&1 \
|| { echo "$(YELLOW)▸ cargo-nextest is required (the runner CI uses): run 'make install-dev-tools'.$(RESET)"; exit 1; }
## The CI coverage gate, locally: the instrumented nextest run with the same
## floors, then the key-management per-file floors from the same data. The
## profile pool keeps one process per test from writing ~50 GB of profraw.
test-backend-cov:
@cargo nextest --version >/dev/null 2>&1 && cargo llvm-cov --version >/dev/null 2>&1 \
|| { echo "$(YELLOW)▸ cargo-nextest and cargo-llvm-cov are required: run 'make install-dev-tools'.$(RESET)"; exit 1; }
@echo "$(CYAN)▸ Running the backend coverage gate (as CI)...$(RESET)"
cd backend && NEXTEST_PROFILE=ci LLVM_PROFILE_FILE_NAME='kronn-%8m.profraw' cargo llvm-cov nextest --workspace --summary-only \
--fail-under-lines 83 --fail-under-functions 83 --fail-under-regions 83
scripts/check-keymgmt-coverage.sh
## Library unit tests only: a quick loop, not the gate.
test-backend-lib: require-nextest
@echo "$(CYAN)▸ Running backend library tests...$(RESET)"
cd backend && cargo nextest run --lib $(NEXTEST_ARGS)
## Install the backend test tools CI uses: cargo-nextest and cargo-llvm-cov
## (prebuilt through cargo-binstall when present, built from source otherwise).
install-dev-tools:
@if command -v cargo-binstall >/dev/null 2>&1; then \
cargo binstall --no-confirm cargo-nextest cargo-llvm-cov; \
else \
cargo install --locked cargo-nextest cargo-llvm-cov; \
fi
rustup component add llvm-tools-preview
## Run Python helper tests (MCP bridge auto-inheritance contract).
## Stdlib `unittest` only — no extra dev deps. The MCP script
## (`disc-introspection-mcp.py`) drives the kronn-internal MCP server
## that 5+ agent CLIs talk to, so its helpers MUST stay green.
test-python:
@echo "$(CYAN)▸ Running Python helper tests...$(RESET)"
python3 -m unittest discover -s backend/scripts -p 'test_*.py' -v
cd backend/sidecars/docs && python3 -m unittest test_build_bundle.py -v
## Run frontend unit tests (vitest)
test-frontend:
@echo "$(CYAN)▸ Running frontend unit tests (vitest)...$(RESET)"
cd frontend && pnpm test
## Orchestration validation compatibility: keep these stable target names for
## persisted TaskExecution gates, even though the frontend now uses pnpm.
typecheck:
@echo "$(CYAN)▸ Running frontend TypeScript parity checks...$(RESET)"
cd frontend && pnpm typecheck:native
cd frontend && pnpm typecheck:legacy
yarn-test-changed:
@echo "$(CYAN)▸ Running frontend unit tests for persisted validation gates...$(RESET)"
cd frontend && pnpm test
## Run frontend E2E tests (Playwright). Pre-req: `make dev` running (Vite + backend).
test-e2e:
@echo "$(CYAN)▸ Running E2E tests (Playwright)...$(RESET)"
cd frontend && pnpm test:e2e
## Run frontend E2E tests with the Playwright UI (visual debug).
test-e2e-ui:
@echo "$(CYAN)▸ Launching Playwright UI...$(RESET)"
cd frontend && pnpm test:e2e:ui
## Run clippy lints in Docker (same toolchain as CI)
lint-backend:
@echo "$(CYAN)▸ Running cargo clippy in Docker...$(RESET)"
@docker build --target linter -f backend/Dockerfile backend/
## Run clippy locally with the ts-rs 9.x noise filtered out.
## ts-rs's proc macro emits "failed to parse serde attribute" for any
## #[serde] attribute combination it doesn't recognize (skip_serializing_if,
## deserialize_with, ...). These don't affect the generated TypeScript and
## aren't actionable on our side — wait for the ts-rs 10 release. Until
## then, this target makes `cargo clippy` actually scannable for real
## warnings.
lint-backend-local:
@echo "$(CYAN)▸ Running cargo clippy locally (ts-rs noise filtered)...$(RESET)"
@cd backend && cargo clippy --all-targets 2>&1 \
| awk 'BEGIN { skip=0 } \
/^warning: failed to parse serde attribute/ { skip=6; next } \
skip>0 { skip--; next } \
{ print }' \
| tee /dev/stderr | grep -qE "^(warning|error):" && exit 1 || exit 0
## Run shell script tests (bats)
test-shell:
@echo "$(CYAN)▸ Running shell tests...$(RESET)"
@./tests/bats/run.sh
## ─── Sidecars (Python helpers) ──────────────────────────────────────────────
## Install the kronn-docs sidecar (document generation: PDF/DOCX/XLSX/CSV/PPTX).
## Creates a dedicated venv at ~/.kronn/venv/docs and installs the pinned deps.
## Idempotent — reruns just upgrade deps in place.
docs-setup:
@echo "$(CYAN)▸ Setting up kronn-docs sidecar...$(RESET)"
@command -v python3 >/dev/null 2>&1 || { echo "$(RED)python3 is required$(RESET)"; exit 1; }
@mkdir -p $$HOME/.kronn/venv
@if [ ! -d $$HOME/.kronn/venv/docs ]; then \
echo " · Creating venv at ~/.kronn/venv/docs"; \
python3 -m venv $$HOME/.kronn/venv/docs; \
fi
@echo " · Installing kronn-docs + deps (this may take ~1-2 min on first run)"
@$$HOME/.kronn/venv/docs/bin/pip install --quiet --upgrade pip
@$$HOME/.kronn/venv/docs/bin/pip install --quiet -e backend/sidecars/docs
@echo "$(GREEN)▸ kronn-docs sidecar ready.$(RESET)"
@echo " WeasyPrint needs cairo/pango/gdk-pixbuf system libs on your OS —"
@echo " if PDF generation fails with a 'library not found' error, install them:"
@echo " apt: sudo apt install libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libgdk-pixbuf-2.0-0"
@echo " brew: brew install cairo pango gdk-pixbuf libffi"
@echo " win: use the GTK+ runtime (see weasyprint.readthedocs.io)"
## ─── Desktop app (Tauri) ────────────────────────────────────────────────────
## Build the desktop app for the current platform
desktop:
@echo "$(CYAN)▸ Building desktop app...$(RESET)"
@cd desktop && cargo tauri build
@echo "$(GREEN)✓ Desktop app built$(RESET)"
@echo " Output: desktop/src-tauri/target/release/bundle/"
## Build only the standalone document exporter embedded in desktop installers.
docs-bundle:
@echo "$(CYAN)▸ Building bundled kronn-docs exporter...$(RESET)"
@cd desktop && node scripts/build-docs-sidecar.mjs
## Run the desktop app in dev mode (hot reload)
desktop-dev:
@echo "$(CYAN)▸ Starting desktop app (dev)...$(RESET)"
@cd desktop && cargo tauri dev
## Build desktop app for a specific target (cross-compilation)
## Usage: make desktop-target T=x86_64-pc-windows-msvc
desktop-target:
ifndef T
$(error Usage: make desktop-target T=<rust-target-triple> Ex: x86_64-pc-windows-msvc, aarch64-apple-darwin)
endif
@echo "$(CYAN)▸ Building desktop app for $(T)...$(RESET)"
@cd frontend && pnpm build
@cd desktop/src-tauri && cargo build --release --target $(T)
@echo "$(GREEN)✓ Desktop app built for $(T)$(RESET)"
help:
@echo ""
@echo "$(CYAN)$(APP_NAME) — Enter the grid. Command your agents.$(RESET)"
@echo ""
@echo "$(GREEN)New here? One command:$(RESET)"
@echo " ./kronn start Guided setup & launch (recommended)"
@echo " ./kronn start-dev Native mode — REQUIRED on macOS for host agents"
@echo ""
@echo "$(GREEN)Usage:$(RESET)"
@echo " make install Same as ./kronn start (guided)"
@echo " make start Pull the published image & launch (Docker); builds only if the pull fails"
@echo " make start-build Build from source & launch (Docker; needs ~20 GB for Docker, checked first)"
@echo " make start-prod Build & launch (Docker, release + LTO)"
@echo " make stop Stop services"
@echo " make logs Tail logs"
@echo " make clean Remove containers & data"
@echo " make build Production build (native)"
@echo " make dev-backend Rust hot reload"
@echo " make run-backend Bare binary, no watcher (audits/agents survive edits)"
@echo " make dev-frontend Vite dev server"
@echo " make check Verify prerequisites"
@echo " make kiro-login Kiro OAuth login (device flow in container)"
@echo " make typegen Sync Rust → TS types"
@echo " make test-backend Backend tests (cargo-nextest, as CI)"
@echo " make test-backend-cov Backend coverage gate (as CI)"
@echo " make install-dev-tools Install cargo-nextest and cargo-llvm-cov"
@echo " make test-shell Run shell tests (bats)"
@echo " make bump V=x.y.z Bump version everywhere"
@echo " make check-version Verify release version consistency"
@echo " make check-diff Whitespace/conflict markers in the branch diff"
@echo " make docs-reanchor [BASE=<rev>] Re-anchor moved doc citations, then check them all"
@echo " make desktop Build desktop app (current platform)"
@echo " make desktop-dev Run desktop app (dev mode)"
@echo " make desktop-target T=<triple> Cross-compile desktop app"
@echo ""
## ─── Version bump ────────────────────────────────────────────────────────────
## Usage: make bump V=0.2.0
bump:
ifndef V
$(error Usage: make bump V=x.y.z)
endif
@echo "$(YELLOW)▸ Bumping version to $(V)...$(RESET)"
@echo "$(V)" > VERSION
@sed $(SEDI) 's/^version = ".*"/version = "$(V)"/' backend/Cargo.toml
@sed $(SEDI) 's/^version = ".*"/version = "$(V)"/' desktop/src-tauri/Cargo.toml
@sed $(SEDI) 's/"version": ".*"/"version": "$(V)"/' frontend/package.json
@sed $(SEDI) 's/"version": ".*"/"version": "$(V)"/' desktop/package.json
@sed $(SEDI) 's/"version": ".*"/"version": "$(V)"/' desktop/src-tauri/tauri.conf.json
@sed $(SEDI) -E \
-e 's|^> \*\*Status:.*|> **Status: $(V) (current release).** Functional but pre-1.0. Breaking changes happen between minor versions; patch versions are safe.|' \
-e 's|(git clone --branch )[0-9]+\.[0-9]+\.[0-9]+|\1$(V)|g' \
-e 's|Kronn v[0-9]+\.[0-9]+\.[0-9]+|Kronn v$(V)|g' README.md
@sed $(SEDI) -E \
-e 's|^> \*\*Statut :.*|> **Statut : $(V) (version actuelle).** Fonctionnel mais pré-1.0. Les versions mineures peuvent introduire des breaking changes ; les patch versions sont safe.|' \
-e 's|(git clone --branch )[0-9]+\.[0-9]+\.[0-9]+|\1$(V)|g' \
-e 's|Kronn v[0-9]+\.[0-9]+\.[0-9]+|Kronn v$(V)|g' README.fr.md
@sed $(SEDI) -E 's|^## Current release: [0-9]+\.[0-9]+\.[0-9]+|## Current release: $(V)|' docs/index.md
@sed $(SEDI) -E 's|(git clone --branch )[0-9]+\.[0-9]+\.[0-9]+|\1$(V)|g' docs/install.md
@# The published images `make start` pulls (KT-1468) are tagged with VERSION.
@sed $(SEDI) -E 's|(\$$\{KRONN_IMAGE_TAG:-)[0-9]+\.[0-9]+\.[0-9]+(\})|\1$(V)\2|g' docker-compose.yml
@# 0.8.6 — also bump the hardcoded version in the public site (FR/EN/ES).
@# Pre-fix `make bump` skipped these and we shipped 0.8.6 with the site
@# still claiming v0.8.5 on the early-access disclaimer + credits line.
@# Pattern: any "v<semver>" occurrence in site/*.html is the Kronn version
@# (no other tokens use that prefix today).
@sed $(SEDI) -E 's/v[0-9]+\.[0-9]+\.[0-9]+/v$(V)/g' site/index.html site/en.html site/es.html
@# Sync Cargo.lock workspace entries so `cargo check --locked` stays green in CI.
@# `cargo update --workspace --offline` only touches local package versions in the
@# lock file — no network, no dep bumps. Required after editing a workspace
@# member's own `version` in Cargo.toml.
@echo "$(YELLOW)▸ Syncing Cargo.lock files...$(RESET)"
@cd backend && cargo update --workspace --offline >/dev/null 2>&1 || \
{ echo "$(YELLOW) backend: offline failed, retrying online$(RESET)"; cd ../backend && cargo update --workspace >/dev/null; }
@cd desktop/src-tauri && cargo update --workspace --offline >/dev/null 2>&1 || \
{ echo "$(YELLOW) desktop: offline failed, retrying online$(RESET)"; cd ../../desktop/src-tauri && cargo update --workspace >/dev/null; }
@echo "$(GREEN)✓ Version bumped to $(V) in all files$(RESET)"
@echo " Files updated: VERSION, backend/Cargo.toml, desktop/src-tauri/Cargo.toml,"
@echo " frontend/package.json, desktop/package.json, desktop/src-tauri/tauri.conf.json,"
@echo " README.md, README.fr.md,"
@echo " docs/index.md, docs/install.md, docker-compose.yml (published image tags),"
@echo " site/index.html, site/en.html, site/es.html (v-prefixed mentions only — historical (0.8.x) refs preserved)"
@echo " + Cargo.lock (backend, desktop/src-tauri) synced via cargo update --workspace"
@$(MAKE) --no-print-directory check-version
check-version:
@./scripts/check-version-sync.sh
## Whitespace errors and conflict markers in what the branch changes
## (BASE defaults to the merge base with origin/main). CI runs the same script.
check-diff:
@./scripts/check-diff.sh $(BASE)
## Re-points a doc citation whose #symbol left its range: unique symbol first,
## then a safe line map through `git diff -U0 BASE HEAD`. In a pull-request CI
## checkout BASE is its base parent; locally it defaults to the merge-base with
## origin/HEAD (then origin/main or origin/master, then a local main or master).
## During an unfinished merge (`git merge --no-commit <target>`) both sides of
## the merge are the evidence and no BASE is needed. With no base at all it still
## re-points the unique-symbol citations instead of refusing to run (a worker
## worktree has no origin refs to find one). DOCS="docs/a.md docs/b.md" limits
## both the rewrite and the strict citation check to those docs.
docs-reanchor:
@base="$(BASE)"; \
if [ -z "$$base" ] && git rev-parse --verify --quiet MERGE_HEAD >/dev/null; then \
base=merge; \
fi; \
if [ -z "$$base" ]; then \
if [ "$${GITHUB_EVENT_NAME:-}" = pull_request ] && git rev-parse --verify --quiet HEAD^2 >/dev/null; then \
base=HEAD^1; \
else \
target="$$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || true)"; \
for candidate in origin/main origin/master main master; do \
if [ -z "$$target" ] && git rev-parse --verify --quiet "$$candidate" >/dev/null; then target="$$candidate"; fi; \
done; \
if [ -n "$$target" ]; then base="$$(git merge-base "$$target" HEAD 2>/dev/null || true)"; fi; \
fi; \
fi; \
if [ -z "$$base" ]; then echo "docs-reanchor: no target-branch merge-base found; re-anchoring unique #symbol moves only (pass BASE=<rev> for the Git line map)" >&2; fi; \
cd backend && KRONN_DOCS_REANCHOR="$(or $(DOCS),all)" KRONN_DOCS_REANCHOR_BASE="$$base" cargo test --lib core::docs_citations::tests::docs_citations_resolve_in_the_repo -- --nocapture