Ctrl K
ComunidadGuías…

Makefile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
VERSION  := $(shell ./scripts/version.sh)
GIT_SHA  := $(shell git rev-parse --short HEAD)
IMG_PREFIX := jentic-one
SERVICES := app registry admin control broker

BUILD_DIR := build

.PHONY: perf-help help install sync lock upgrade fmt format fix lint typecheck test test-unit test-fast test-integration test-integration-sqlite test-integration-all test-arch test-smoke cov cov-all check detect-secrets secrets-baseline score openapi openapi-parity config-schema config-reference endpoints cli-reference broker-reference skills hooks clean dev start-fixtures stop-fixtures destroy-fixtures start-quickstart-dev stop-quickstart-dev destroy-quickstart-dev start-app start-registry start-admin start-control start-broker start-local build-wheel build-base build-all save-all images check-image-lock release-image $(addprefix build-,$(SERVICES)) $(addprefix push-,$(SERVICES)) $(addprefix save-,$(SERVICES))

help: ## Show this help
	@awk 'BEGIN {FS = ":.*##"; printf "Usage: make <target>\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf "  \033[36m%-15s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
	@printf "\n"
	@$(MAKE) --no-print-directory -C tests/perf help

install: sync ui-setup hooks ## Full dev setup: sync deps, install UI deps, install lefthook hooks

sync: ## Install/sync project + dev dependencies (includes all extras)
	uv sync --dev --all-extras

lock: ## Refresh the lockfile
	uv lock

upgrade: ## Upgrade locked dependencies
	uv lock --upgrade
	uv sync --dev

fmt format: ## Format code with ruff
	uv run ruff format .
	uv run ruff check . --fix

fix: ## Auto-fix lint issues and reformat code
	uv run ruff check --fix .
	uv run ruff format .

lint: ## Lint (ruff check + format check + mypy)
	uv run ruff check .
	uv run ruff format --check .
	uv run mypy

typecheck: ## Run mypy
	uv run mypy

test: test-unit ## Run tests (unit only by default)

# Parallel test execution (pytest-xdist). `loadfile` keeps each test module on
# one worker, so module-scoped fixtures and in-module ordering hold; Postgres
# suites get one database per worker (tests/harness/postgres.py). Run serially
# with `make <target> PYTEST_XDIST=`. PYTEST_ARGS appends extra flags (CI sets
# `--durations=25`).
PYTEST_XDIST ?= -n auto --dist loadfile
PYTEST_ARGS ?=
COV_FLAGS := --cov=jentic_one --cov-report=term-missing

test-unit: ## Run unit tests (with coverage)
	uv run pytest tests/unit/ $(PYTEST_XDIST) $(COV_FLAGS) $(PYTEST_ARGS)

test-fast: ## Run unit + arch tests (no external services, no coverage)
	uv run pytest tests/unit/ tests/arch/ $(PYTEST_XDIST) $(PYTEST_ARGS)

test-integration: ## Run all integration tests against PostgreSQL (requires running fixtures)
	uv run pytest tests/ -m integration $(PYTEST_XDIST) $(PYTEST_ARGS)

test-integration-sqlite: ## Run backend-agnostic integration tests against SQLite (no external services)
	JENTIC_TEST_BACKEND=sqlite uv run pytest tests/integration/ -m integration $(PYTEST_XDIST) $(PYTEST_ARGS)

test-integration-all: test-integration test-integration-sqlite ## Run integration tests on both backends

test-arch: ## Run architecture enforcement tests
	uv run pytest tests/arch/ -m arch $(PYTEST_XDIST) $(PYTEST_ARGS)

test-smoke: ## Run smoke tests (requires running services)
	uv run pytest tests/smoke/ -m smoke

smoke-packaging: ## Build UI+wheel, install in a clean venv, verify the SPA is packaged & served (DB-free)
	./scripts/spa_packaging_smoke.sh

cov: ## Run unit + arch tests with coverage report
	uv run pytest tests/unit/ tests/arch/ $(PYTEST_XDIST) $(COV_FLAGS) --cov-report=html

cov-all: ## Run all tests with coverage (requires running fixtures/services)
	uv run pytest tests/ $(PYTEST_XDIST) $(COV_FLAGS) --cov-report=html

openapi: ## Regenerate the control-plane OpenAPI spec (+ UI client schema) from code
	uv run python -m tools.openapi_export
	uv run python -m tools.openapi_export --output ui/openapi.json
	@echo "Regenerated openapi/control/control.openapi.yaml and ui/openapi.json."
	@echo "If working on the UI, run 'cd ui && npm run codegen' to refresh the client."

openapi-parity: ## Print the reference-vs-generated OpenAPI coverage report
	uv run python -m tools.openapi_parity

config-schema: ## Regenerate the backend config JSON Schema (config/config-schema.json) from the AppConfig model
	uv run python -m tools.config_schema_export
	@echo "Regenerated config/config-schema.json."
	@echo "Run 'cd cli && make generate-config' to refresh the generated installer config struct."
	@echo "Run 'make config-reference' to refresh the configuration reference doc."

config-reference: ## Regenerate the configuration reference (docs/reference/config.md) from the AppConfig model
	uv run python -m tools.config_reference
	@echo "Regenerated docs/reference/config.md."
endpoints: ## Regenerate the endpoint + permission reference (docs/reference/endpoints.{md,json}) from code
	uv run python -m tools.endpoint_tree
	@echo "Regenerated docs/reference/endpoints.md and docs/reference/endpoints.json."

cli-reference: ## Regenerate the CLI command reference (ui/public/cli-reference.json + docs/reference/cli.md) from the cobra command tree
	cd cli && go run ./cmd/clidocs -o ../ui/public/cli-reference.json -md ../docs/reference/cli.md
	@echo "Regenerated ui/public/cli-reference.json and docs/reference/cli.md."

broker-reference: ## Regenerate the Broker OpenAPI artifact (ui/public/broker-openapi.json) from the hand-curated spec
	uv run python -m tools.broker_reference
	@echo "Regenerated ui/public/broker-openapi.json."

skills: ## Mirror skills/<name>/SKILL.md into both content/ dirs (CLI embed + wheel-served copies)
	uv run python -m tools.skills_sync
	@echo "Mirrored the served skill set; run 'go build ./...' in cli/ to re-embed."

SCORECARD_CLI := @jentic/api-scorecard-cli@1.12.0

score: ## Score the control + broker OpenAPI specs with the Jentic API Scorecard (needs JENTIC_SCORE_API_KEY; SCORECARD_MIN_SCORE gates)
	@# Opt-in: skips cleanly without a key so `make check` stays green offline.
	@# The CLI reads JENTIC_API_KEY; the key is handed to it per-invocation only.
	@# The registry override keeps a user-level `@jentic:` scope mapping (e.g. to
	@# GitHub Packages) from shadowing the public npm package.
	@if [ -z "$$JENTIC_SCORE_API_KEY" ]; then \
		echo "JENTIC_SCORE_API_KEY is not set; skipping OpenAPI scorecard."; \
		exit 0; \
	fi; \
	status=0; \
	for spec in control broker; do \
		file="openapi/$$spec/$$spec.openapi.yaml"; \
		echo "==> scoring $$file"; \
		if [ -z "$$SCORECARD_MIN_SCORE" ]; then \
			JENTIC_API_KEY="$$JENTIC_SCORE_API_KEY" npx --yes --@jentic:registry=https://registry.npmjs.org \
				$(SCORECARD_CLI) score "$$file" --quiet || status=1; \
		else \
			score=$$(JENTIC_API_KEY="$$JENTIC_SCORE_API_KEY" npx --yes --@jentic:registry=https://registry.npmjs.org \
				$(SCORECARD_CLI) score "$$file" --quiet --format json --detail summary | jq -r '.summary.score'); \
			case "$$score" in ''|null) echo "FAIL: could not read a score for $$spec"; status=1; continue;; esac; \
			if awk -v s="$$score" -v m="$$SCORECARD_MIN_SCORE" 'BEGIN { exit !(s < m) }'; then \
				echo "FAIL: $$spec scored $$score (< $$SCORECARD_MIN_SCORE)"; status=1; \
			else \
				echo "ok: $$spec scored $$score (>= $$SCORECARD_MIN_SCORE)"; \
			fi; \
		fi; \
	done; \
	exit $$status

detect-secrets: ## Fail if a fresh scan differs from .secrets.baseline (new secret, stale entry, or non-slim file)
	uv run python -m tools.secrets_baseline check

secrets-baseline: ## Re-scan into .secrets.baseline in slim form (no line numbers/timestamp)
	uv run python -m tools.secrets_baseline update

check: lint detect-secrets test-arch score ## Run lint, secrets audit, arch-tests, and score (score last: it needs npx/network and shouldn't block the offline gates)

hooks: ## Install lefthook git hooks (pre-commit + commit-msg)
	uv run lefthook install

clean: ## Remove caches and build artifacts
	rm -rf .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov coverage.xml build dist *.egg-info $(BUILD_DIR)
	find . -type d -name __pycache__ -prune -exec rm -rf {} +
	find deploy/helm -name "*.tgz" -delete 2>/dev/null || true
	find deploy/helm -name "Chart.lock" -delete 2>/dev/null || true

JENTIC_CONFIG_FILE ?= config/local.yaml
export JENTIC_CONFIG_FILE

dev: ## One-command local bring-up (idempotent): fixtures + migrations + UI, then start the app
	@./scripts/dev-up.sh
	@$(MAKE) --no-print-directory start-app

start-fixtures: ## Start Docker database fixtures and apply migrations
	@./scripts/setup.sh

stop-fixtures: ## Stop Docker database fixtures
	docker compose -f docker/local-setup/docker-compose.yaml stop

destroy-fixtures: ## Remove Docker database fixtures and volumes
	docker compose -f docker/local-setup/docker-compose.yaml down -v

# Docker quickstart plus a mock OIDC IdP (web SSO and MCP OAuth), for local development.
QUICKSTART_DEV := docker compose -p jentic-quickstart-dev \
	-f deploy/docker/quickstart/docker-compose.yaml \
	-f docker/local-setup/docker-compose.quickstart-dev-oauth-idp-overlay.yaml

# Extra `docker compose up` flags, e.g. ARGS="-d --wait" to run in the background.
ARGS ?=

start-quickstart-dev: ## Run the Docker quickstart with a mock IdP (SSO + MCP OAuth) on :8000/:8100/:8080, logs in the foreground (ARGS="-d --wait" for background)
	@# The quickstart's credential-encryption key, generated once (as in the README).
	@mkdir -p "$$HOME/.config/jentic-one"
	@[ -f "$$HOME/.config/jentic-one/.env" ] || (umask 077; echo "JENTIC_ENCRYPTION_KEY=$$(openssl rand -base64 32)" > "$$HOME/.config/jentic-one/.env")
	@echo "==> http://127.0.0.1:8000: create your admin, then sign in via SSO with that email"
	$(QUICKSTART_DEV) up $(ARGS)

stop-quickstart-dev: ## Remove the dev quickstart containers (keeps its data)
	$(QUICKSTART_DEV) down

destroy-quickstart-dev: ## Remove the dev quickstart stack and its data volume
	$(QUICKSTART_DEV) down -v

start-app: ## Start combined app (registry, admin, control, auth) on :8000; the broker is start-broker
	uv run python -m jentic_one

start-registry: ## Start registry surface standalone
	JENTIC__APPS=registry uv run python -m jentic_one

start-admin: ## Start admin surface standalone
	JENTIC__APPS=admin uv run python -m jentic_one

start-control: ## Start control surface standalone
	JENTIC__APPS=control uv run python -m jentic_one

# The standalone broker answers on :8100, but the admin API (jobs, approvals)
# and the web UI live on the combined app at :8000. Point the broker's
# absolute links (async-job and held-call `_links`, the approval `review_url`)
# there; its own request origin would name the broker instead.
# scripts/start-local.sh sets the same broker env for `start-local`.
LOCAL_BROKER_ENV := JENTIC__APPS=broker JENTIC__SERVER__PORT=8100 \
	JENTIC__BROKER__JOBS_API_BASE_URL=http://127.0.0.1:8000

start-broker: ## Start broker surface standalone on :8100 (runs alongside start-app on :8000)
	$(LOCAL_BROKER_ENV) uv run python -m jentic_one

start-local: ## Start the combined app (:8000) and the broker (:8100) together for local testing
	@echo "==> combined app → http://127.0.0.1:8000   broker → http://127.0.0.1:8100 (Ctrl-C stops both)"
	@scripts/start-local.sh

migrate-sqlite: ## Apply all migrations to the local SQLite databases (config/local-sqlite.yaml)
	@mkdir -p .data
	JENTIC_CONFIG_FILE=config/local-sqlite.yaml uv run python -m jentic_one.migrations.run

start-app-sqlite: migrate-sqlite ## Start combined app on local SQLite (lexical search; no Docker)
	JENTIC_CONFIG_FILE=config/local-sqlite.yaml uv run python -m jentic_one

build-wheel: ## Build Python wheel (packaging TEST ONLY — never published; the container image is the canonical backend distribution)
	uv build --wheel

# ─── UI (frontend) ───────────────────────────────────────────────────────
UI_DIR := ui

ui-setup: ## Install UI deps for local dev (no-op when node is unavailable)
	@if command -v node >/dev/null 2>&1; then \
		echo "Installing UI dependencies…"; \
		cd $(UI_DIR) && npm ci; \
	else \
		echo "node not found — skipping UI deps (backend-only setup)."; \
		echo "Install Node.js if you intend to work on the UI."; \
	fi

ui-install: ## Install UI dependencies
	cd $(UI_DIR) && npm ci

ui-build: ## Build the UI bundle into ui/dist
	cd $(UI_DIR) && npm ci && npm run build

ui-lint: ## Lint the UI
	cd $(UI_DIR) && npm ci && npm run lint

ui-test: ## Run UI unit/component tests
	cd $(UI_DIR) && npm ci && npm run test:run

# Optional extras to bake into the images, space-separated (e.g. EXTRAS=redis).
# They go through the builder's lock export: the wheel installs --no-deps, so
# `[extra]` on the wheel itself installs nothing.
EXTRAS ?=
EXTRAS_BUILD_ARG = $(if $(strip $(EXTRAS)),--build-arg EXTRAS="$(strip $(EXTRAS))")

build-base: ## Build the Python base Docker image (builder + runtime stages; optional EXTRAS=redis)
	docker build -f deploy/docker/python-base.Dockerfile --target builder $(EXTRAS_BUILD_ARG) -t python-base:builder .
	docker build -f deploy/docker/python-base.Dockerfile --target runtime -t python-base:runtime .

# Per-service build / push / save rules generated explicitly (no pattern rules,
# so this works on GNU Make 3.81 — Apple's bundled version).
# NOTE: push-<svc> pushes the local per-service names ($(IMG_PREFIX)/<svc>) for
# dev/fork registries; the published-release path is `release-image` below,
# which pushes the differently named <REGISTRY>/jentic-one-app.
define SERVICE_RULES
build-$(1): build-base ## Build Docker image for $(1)
	docker build -f deploy/docker/$(1).Dockerfile -t $(IMG_PREFIX)/$(1):$(VERSION) -t $(IMG_PREFIX)/$(1):$(GIT_SHA) .

push-$(1): ## Push $(1) image to the registry
	docker push $(IMG_PREFIX)/$(1):$(VERSION)
	docker push $(IMG_PREFIX)/$(1):$(GIT_SHA)

save-$(1): ## Save $(1) image to build/jentic-$(1)-$(VERSION).tar
	@mkdir -p $(BUILD_DIR)
	docker save $(IMG_PREFIX)/$(1):$(VERSION) -o $(BUILD_DIR)/jentic-$(1)-$(VERSION).tar
	@echo "Wrote $(BUILD_DIR)/jentic-$(1)-$(VERSION).tar"
endef

$(foreach svc,$(SERVICES),$(eval $(call SERVICE_RULES,$(svc))))

build-all: build-base $(addprefix build-,$(SERVICES)) ## Build all Docker images

save-all: $(addprefix save-,$(SERVICES)) ## Save all built images as tarballs under build/

images: ## List locally built jentic-one images
	@docker images "$(IMG_PREFIX)/*"

check-image-lock: ## Fail if IMAGE's Python packages differ from uv.lock (IMAGE=<tag>, optional PLATFORM=linux/arm64, EXTRAS=redis)
	@test -n "$(IMAGE)" || { echo "usage: make check-image-lock IMAGE=<tag> [PLATFORM=linux/<arch>] [EXTRAS=<extra> ...]"; exit 1; }
	./scripts/check-image-lock.sh $(foreach e,$(EXTRAS),--extra "$(e)") "$(IMAGE)" $(if $(PLATFORM),--platform "$(PLATFORM)")

# ─── Release (publish the app image to a real registry) ───────────────────
# One `app` image serves every surface — the surface set is chosen at runtime
# via JENTIC__APPS (see deploy/README.md "One image, runtime surfaces"). So
# publishing the single `app` image is enough for a self-hosted app + broker
# deployment.
#
#   make release-image REGISTRY=ghcr.io/jentic
#
# builds deploy/docker/app.multiarch.Dockerfile for linux/amd64 + linux/arm64
# with `docker buildx` and pushes the OCI index to $(REGISTRY)/jentic-one-app
# tagged with the pyproject version and the short git SHA. `latest` only moves
# when the version is a stable X.Y.Z (no prerelease suffix), mirroring CI's
# guard so a dev/rc push can never hijack the tag self-hosters float on.
# Requires `docker login <registry>` first AND a buildx builder that can build
# both arches (`docker buildx create --use` + QEMU for the non-native leg). CI
# does this on a vX.Y.Z tag (see .github/workflows/release.yml) with its own
# inline script rather than this target: CI must scan each arch, interleave
# cosign signing between the version and :latest tags, and lower-case the GHCR
# owner — which this all-in-one target can't express.
REGISTRY ?=
RELEASE_IMAGE := $(REGISTRY)/jentic-one-app
RELEASE_PLATFORMS ?= linux/amd64,linux/arm64

release-image: ## Build + push the multi-arch app image to REGISTRY (e.g. REGISTRY=ghcr.io/jentic)
	@if [ -z "$(REGISTRY)" ]; then \
		echo "ERROR: set REGISTRY, e.g. make release-image REGISTRY=ghcr.io/jentic"; exit 1; \
	fi
	docker buildx build -f deploy/docker/app.multiarch.Dockerfile \
		--platform $(RELEASE_PLATFORMS) $(EXTRAS_BUILD_ARG) \
		-t $(RELEASE_IMAGE):$(VERSION) \
		-t $(RELEASE_IMAGE):$(GIT_SHA) \
		--push .
	@if echo "$(VERSION)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$$'; then \
		docker buildx imagetools create -t $(RELEASE_IMAGE):latest $(RELEASE_IMAGE):$(VERSION); \
		echo "Pushed $(RELEASE_IMAGE) ($(VERSION), $(GIT_SHA), latest) [$(RELEASE_PLATFORMS)]"; \
	else \
		echo "Prerelease version — not moving :latest"; \
		echo "Pushed $(RELEASE_IMAGE) ($(VERSION), $(GIT_SHA)) [$(RELEASE_PLATFORMS)]"; \
	fi

# ─── Broker perf / load suite (tests/perf/Makefile, see tests/perf/README.md) ─
# Local-only, never run in CI. Targets live in tests/perf/Makefile; these are
# explicit pass-throughs (no pattern rule, see SERVICE_RULES above), so
# `make perf-<TAB>` completes and variables pass through (make perf-agent-sql CACHE=cold).
PERF_TARGETS := build up provision overhead dbcost capacity monitoring profile ui load obs \
	report down \
	agent-doctor agent-sql agent-stages agent-cpu agent-resilience agent-compare

# .PHONY is registered per rule here: PERF_TARGETS is defined after the .PHONY
# list at the top of the file, which make expands at parse time.
define PERF_RULE
.PHONY: perf-$(1)
perf-$(1):
	@$$(MAKE) --no-print-directory -C tests/perf $(1)
endef
$(foreach t,$(PERF_TARGETS),$(eval $(call PERF_RULE,$(t))))

perf-help: ## Perf: list the perf suite + agent probe targets (tests/perf/Makefile)
	@$(MAKE) --no-print-directory -C tests/perf help