.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= 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=] 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= 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" 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 `) 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= 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