.PHONY: start install install-cli dashboard-install dashboard-check db db-tools migrate seed api dashboard dashboard-local sync check-primary primary-port-clear check check-local test test-python clean register-project register-codex-project register-mcp configure-codex bootstrap-env dev-hub edge-relay mcp-profile validate-adr add-domain rename-domain add-repo list-repos register-path register-from-classification register-from-classification-all cleanup-stale tunnels-up tunnels-status tunnels-check bridges install-hooks install-hooks-all gitea-inventory token-reconcile railiance-state-hub-render railiance-state-hub-client-dry-run railiance-state-hub-server-dry-run COMPOSE = docker compose -f infra/docker-compose.yml --env-file .env PYTHON ?= python3 HELM ?= $(shell command -v helm 2>/dev/null || if [ -x "$$HOME/.local/bin/helm" ]; then printf "%s" "$$HOME/.local/bin/helm"; else printf "%s" "helm"; fi) KUBECTL ?= $(shell command -v kubectl 2>/dev/null || if [ -x "$$HOME/.local/bin/kubectl" ]; then printf "%s" "$$HOME/.local/bin/kubectl"; else printf "%s" "kubectl"; fi) RAILIANCE_STATE_HUB_RELEASE ?= state-hub RAILIANCE_STATE_HUB_NAMESPACE ?= state-hub RAILIANCE_STATE_HUB_CHART ?= deploy/railiance/apps/charts/state-hub RAILIANCE_STATE_HUB_VALUES ?= deploy/railiance/apps/helm/state-hub-values.yaml RAILIANCE_STATE_HUB_IMAGE_TAG ?= f2e042a RAILIANCE_STATE_HUB_PLATFORM_DIR ?= deploy/railiance/platform RAILIANCE_STATE_HUB_APP_MANIFESTS ?= deploy/railiance/apps/manifests # Codex/WSL non-login shells may not source ~/.profile; keep uv discoverable. UV ?= $(shell command -v uv 2>/dev/null || if [ -x "$$HOME/.local/bin/uv" ]; then printf "%s" "$$HOME/.local/bin/uv"; else printf "%s" "uv"; fi) RMGR ?= $(shell command -v rmgr 2>/dev/null || if [ -x "$$HOME/repo-manager/.venv/bin/rmgr" ]; then printf "%s" "$$HOME/repo-manager/.venv/bin/rmgr"; else printf "%s" "rmgr"; fi) STATE_HUB_API_BASE ?= http://127.0.0.1:8000 SYNC_PATH ?= . SYNC_PUSH ?= 1 start: @echo "# Normal production UI access (run in order)" @echo "make bridges # connect local :8000 to the railiance01 primary" @echo "make dashboard # verify the primary, then serve the UI on :3000" @echo "make sync # push file-backed records and reconcile the exact forge commit" @echo "" @echo "# Optional local MCP adapter" @echo "make mcp-http # local SSE adapter on :8001; not needed by the UI" @echo "" @echo "# Local fallback/development only" @echo "make api # local Postgres + API; conflicts with the production :8000 tunnel" @echo "make dashboard-local # dashboard against the deliberate local API" install: $(UV) sync dashboard/node_modules/.bin/observable: dashboard/package.json dashboard/package-lock.json cd dashboard && npm ci dashboard-install: dashboard/node_modules/.bin/observable dashboard-check: dashboard-install cd dashboard && npm run build ## Symlink the custodian CLI into ~/.local/bin so it's on PATH system-wide install-cli: install mkdir -p ~/.local/bin ln -sf "$(shell pwd)/.venv/bin/custodian" ~/.local/bin/custodian ln -sf "$(shell pwd)/.venv/bin/statehub" ~/.local/bin/statehub @echo "Installed: custodian → $$(readlink -f ~/.local/bin/custodian)" @echo "Installed: statehub → $$(readlink -f ~/.local/bin/statehub)" @echo "Make sure ~/.local/bin is on your PATH:" @echo " echo 'export PATH=\"\$$HOME/.local/bin:\$$PATH\"' >> ~/.bashrc && source ~/.bashrc" db: $(COMPOSE) up -d postgres db-tools: $(COMPOSE) --profile tools up -d migrate: $(UV) run alembic upgrade head seed: $(UV) run python scripts/seed.py ## Start (or restart) the MCP SSE server on :8001 — primary transport for Claude Code. ## Remote clients (e.g. COULOMBCORE) connect via the ops-bridge tunnel (port 18001). ## Registration: claude mcp add-json -s user state-hub '{"type":"sse","url":"http://127.0.0.1:8001/sse"}' mcp-http: @fuser -k 8001/tcp 2>/dev/null && echo "Stopped running MCP server" || true MCP_TRANSPORT=sse MCP_PORT=8001 $(UV) run python mcp_server/server.py ## Require the production identity before serving the normal dashboard. This ## prevents an accidental local `make api` from presenting an empty fallback DB ## as the live State Hub. check-primary: @health="$$(curl -fsS --max-time 5 http://127.0.0.1:8000/state/health 2>/dev/null)" || { \ echo "ERROR: State Hub primary is not reachable on 127.0.0.1:8000." >&2; \ echo "Run 'make bridges' first." >&2; \ exit 1; \ }; \ identity="$$(printf '%s' "$$health" | $(PYTHON) -c 'import json, sys; d=json.load(sys.stdin); print("{}/{}".format(d.get("instance_role", ""), d.get("instance_label", "")))')"; \ if [ "$$identity" != "primary/railiance01" ]; then \ echo "ERROR: 127.0.0.1:8000 is '$$identity', not the primary/railiance01 State Hub." >&2; \ echo "Stop the local API, then run 'make bridges'. Use 'make dashboard-local' only for intentional local development." >&2; \ exit 1; \ fi ## Refuse to start the production tunnel when a non-primary API already owns ## port 8000. With no listener, bridge is free to establish the tunnel. primary-port-clear: @if health="$$(curl -fsS --max-time 2 http://127.0.0.1:8000/state/health 2>/dev/null)"; then \ identity="$$(printf '%s' "$$health" | $(PYTHON) -c 'import json, sys; d=json.load(sys.stdin); print("{}/{}".format(d.get("instance_role", ""), d.get("instance_label", "")))')"; \ if [ "$$identity" != "primary/railiance01" ]; then \ echo "ERROR: port 8000 is occupied by '$$identity'." >&2; \ echo "Stop the local 'make api' process before starting the production tunnel." >&2; \ exit 1; \ fi; \ fi dashboard: check-primary @fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true $(MAKE) dashboard-install cd dashboard && npm run dev ## Deliberate local-development dashboard; bypasses the production identity gate. dashboard-local: @echo "WARNING: serving the dashboard against the local/fallback API on :8000." @fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true $(MAKE) dashboard-install cd dashboard && npm run dev ## Fast work-record path. Repo Manager assigns only missing deterministic IDs, ## verifies the primary/railliance01 bridge, and asks central to derive the ## exact pushed Forgejo commit in one transactional request. sync: $(RMGR) sync --path "$(SYNC_PATH)" --api-base "$(STATE_HUB_API_BASE)" $(if $(filter 1 true yes,$(SYNC_PUSH)),--push,) check: check-primary @echo "State Hub primary/railiance01 is healthy." check-local: curl -sf http://127.0.0.1:8000/state/health | python3 -m json.tool # CUST-WP-0067-T09. The chart ships a copy of the-custodian canon allowed-values # because a container has no checkout to read it from. Regenerate after any # canon vocabulary change; the -check target fails on drift so a release cannot # validate classification against a stale vocabulary. sync-classification-allowed: python3 scripts/sync_classification_allowed.py check-classification-allowed: python3 scripts/sync_classification_allowed.py --check railiance-state-hub-render: check-classification-allowed $(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \ --namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \ -f $(RAILIANCE_STATE_HUB_VALUES) \ --set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) railiance-state-hub-client-dry-run: @set -e; \ tmpdir="$$(mktemp -d)"; \ trap 'rm -rf "$$tmpdir"' EXIT; \ $(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \ --namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \ -f $(RAILIANCE_STATE_HUB_VALUES) \ --set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \ $(KUBECTL) apply --dry-run=client -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml" railiance-state-hub-server-dry-run: @set -e; \ tmpdir="$$(mktemp -d)"; \ trap 'rm -rf "$$tmpdir"' EXIT; \ $(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \ --namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \ -f $(RAILIANCE_STATE_HUB_VALUES) \ --set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \ $(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \ $(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \ $(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \ $(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \ if $(KUBECTL) get namespace $(RAILIANCE_STATE_HUB_NAMESPACE) >/dev/null 2>&1; then \ $(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \ $(KUBECTL) apply --dry-run=server -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"; \ else \ echo "Namespace $(RAILIANCE_STATE_HUB_NAMESPACE) does not exist; validating namespaced app manifests with client dry-run."; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \ $(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \ $(KUBECTL) apply --dry-run=client -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"; \ fi test: test-python dashboard-check test-python: TEST_DATABASE_URL=postgresql+asyncpg://custodian:changeme@127.0.0.1:5432/custodian_test \ $(UV) run pytest -x -q ## Benchmark /state/summary revision cache (API must be running on :8000) benchmark-summary-cache: $(UV) run python scripts/benchmark_summary_cache.py ## ops-bridge managed tunnels ## Requires ops-bridge: bridge is at /home/worsch/.local/bin/bridge tunnels-up: primary-port-clear bridge up tunnels-status: bridge status ## End-to-end check: verifies SSH process alive + remote port listening on COULOMBCORE. ## Exits non-zero if any tunnel is not fully operational. tunnels-check: bridge check ## Ensure all ops-bridge tunnels are up and healthy. ## Brings up any stopped/stale tunnels, shows final status, exits non-zero if anything is still down. bridges: primary-port-clear @echo "==> Bringing up all tunnels..." bridge up @echo "" @echo "==> Tunnel status:" bridge status @echo "" @echo "==> Checking tunnel health..." bridge check ## Start (or restart) the LOCAL FALLBACK backend — db + migrate + uvicorn. ## This replaces anything on :8000, including the production State Hub tunnel. api: db @echo "WARNING: starting the local fallback API; this is not the railiance01 primary." @echo "Waiting for postgres..."; \ for i in 1 2 3 4 5 6 7 8 9 10; do \ nc -z 127.0.0.1 5432 2>/dev/null && break; \ sleep 1; \ done $(MAKE) migrate @fuser -k 8000/tcp 2>/dev/null && echo "Stopped running API" || true $(UV) run uvicorn api.main:app --reload --reload-dir api --reload-dir mcp_server --reload-dir task_flow_engine --host 127.0.0.1 --port 8000 ## Register a project (Claude Code): make register-project DOMAIN=railiance PROJECT_PATH=/home/worsch/railiance register-project: @test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required. Usage: make register-project DOMAIN= PROJECT_PATH="; exit 1) @test -n "$(PROJECT_PATH)" || (echo "ERROR: PROJECT_PATH is required."; exit 1) scripts/register_project.sh "$(DOMAIN)" "$(PROJECT_PATH)" ## Register a Codex project (AGENTS.md + HTTP API): make register-codex-project DOMAIN=capabilities PROJECT_PATH=/home/worsch/my-repo register-codex-project: @test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required. Usage: make register-codex-project DOMAIN= PROJECT_PATH="; exit 1) @test -n "$(PROJECT_PATH)" || (echo "ERROR: PROJECT_PATH is required."; exit 1) scripts/register_project.sh "$(DOMAIN)" "$(PROJECT_PATH)" --codex ## Register State Hub MCP for Claude Code. Optional: make register-mcp MCP_URL=http://127.0.0.1:18001/sse register-mcp: scripts/register-mcp.sh \ $(if $(MCP_URL),--url "$(MCP_URL)",) \ $(if $(API_BASE),--api-base "$(API_BASE)",) \ $(if $(DRY_RUN),--dry-run,) ## Configure Codex workspace-write networking for local State Hub access. configure-codex: scripts/configure-codex.sh \ $(if $(CODEX_HOME),--codex-home "$(CODEX_HOME)",) \ $(if $(API_BASE),--api-base "$(API_BASE)",) \ $(if $(WITH_MCP),--with-mcp,) \ $(if $(DRY_RUN),--dry-run,) ## Bootstrap a new operator/collaborator environment. Optional: make bootstrap-env ARGS="--install-missing" bootstrap-env: scripts/bootstrap-env.sh $(ARGS) ## Files-first local dev hub (CUST-WP-0054-T07): postgres + migrate + seed + API + repo registration. ## Optional: WITH_EDGE=1 WITH_MCP=1 make dev-hub ## Fleet tunnel when reachable: make dev-hub PROFILE=fleet dev-hub: bash scripts/dev_hub_up.sh $(if $(PROFILE),$(PROFILE),dev) ## Local edge relay for offline write buffering (STATE-WP-0068). edge-relay: @fuser -k 18080/tcp 2>/dev/null && echo "Stopped running edge relay" || true STATEHUB_UPSTREAM_URL=$${STATEHUB_UPSTREAM_URL:-http://127.0.0.1:8000} \ $(UV) run uvicorn api.edge.relay:app --host 127.0.0.1 --port 18080 ## Switch MCP/API profile: make mcp-profile PROFILE=dev|fleet|status mcp-profile: @test -n "$(PROFILE)" || (echo "ERROR: PROFILE is required (dev|fleet|status)."; exit 1) bash scripts/mcp_hub_profile.sh "$(PROFILE)" ## Add a second repo to an existing domain: make add-repo DOMAIN=railiance REPO_PATH=/home/worsch/railiance-infra add-repo: @test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required."; exit 1) @test -n "$(REPO_PATH)" || (echo "ERROR: REPO_PATH is required."; exit 1) scripts/register_project.sh "$(DOMAIN)" "$(REPO_PATH)" --additional ## Create a new domain: make add-domain DOMAIN=my_domain NAME="My Domain" add-domain: @test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required (slug)."; exit 1) @test -n "$(NAME)" || (echo "ERROR: NAME is required (display name)."; exit 1) curl -sf -X POST http://127.0.0.1:8000/domains/ \ -H "Content-Type: application/json" \ -d "{\"slug\": \"$(DOMAIN)\", \"name\": \"$(NAME)\"}" | python3 -m json.tool ## Rename a domain: make rename-domain DOMAIN=old_slug NEW_SLUG=new_slug NEW_NAME="New Name" rename-domain: @test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN (old slug) is required."; exit 1) @test -n "$(NEW_SLUG)" || (echo "ERROR: NEW_SLUG is required."; exit 1) @test -n "$(NEW_NAME)" || (echo "ERROR: NEW_NAME is required."; exit 1) curl -sf -X PATCH http://127.0.0.1:8000/domains/$(DOMAIN)/rename \ -H "Content-Type: application/json" \ -d "{\"new_slug\": \"$(NEW_SLUG)\", \"new_name\": \"$(NEW_NAME)\"}" | python3 -m json.tool ## Register this machine's local path for a repo: make register-path REPO=marki-docx PATH=/home/tegwick/marki-docx register-path: @test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make register-path REPO= PATH="; exit 1) @test -n "$(PATH)" || (echo "ERROR: PATH is required. Usage: make register-path REPO= PATH="; exit 1) curl -sf -X POST "http://127.0.0.1:8000/repos/$(REPO)/paths" \ -H "Content-Type: application/json" \ -d "{\"host\": \"$$(hostname)\", \"path\": \"$(PATH)\"}" | python3 -m json.tool ## List repos for a domain: make list-repos DOMAIN=railiance list-repos: @test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required."; exit 1) curl -sf "http://127.0.0.1:8000/repos/?domain=$(DOMAIN)" | python3 -m json.tool ## Ingest SBOM data for a repo (all mechanisms: lockfiles + ansible + sbom-tools.yaml). ## Auto-detect all sources: make ingest-sbom REPO=the-custodian REPO_PATH=/home/worsch/the-custodian ## Single lockfile (explicit): make ingest-sbom REPO=the-custodian LOCKFILE=/path/to/uv.lock ## Dry-run (no submit): make ingest-sbom REPO=the-custodian REPO_PATH=... DRY_RUN=1 ## Tip: run capture-tools first for repos with system-level tool dependencies. ingest-sbom: @test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1) $(UV) run python scripts/ingest_sbom.py --repo "$(REPO)" \ $(if $(LOCKFILE),--lockfile "$(LOCKFILE)") \ $(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \ $(if $(DRY_RUN),--dry-run) ## Ingest capability declarations from SCOPE.md into the catalog. ## Usage: make ingest-capabilities REPO=the-custodian [REPO_PATH=/home/worsch/the-custodian] ## Or: make ingest-capabilities-all ## Add DRY_RUN=1 to preview without writing. ingest-capabilities: @test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1) $(UV) run python scripts/ingest_capabilities.py --repo "$(REPO)" \ $(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \ $(if $(DRY_RUN),--dry-run) ingest-capabilities-all: $(UV) run python scripts/ingest_capabilities.py --all \ $(if $(DRY_RUN),--dry-run) ## Check Repository Definition of Integrated (DoI) criteria for a repo. ## Usage: make check-doi REPO=llm-connect ## Or: make check-doi-all ## Add JSON=1 for machine-readable output. check-doi: @test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1) $(UV) run python scripts/check_doi.py --repo "$(REPO)" $(if $(JSON),--json) check-doi-all: $(UV) run python scripts/check_doi.py --all $(if $(JSON),--json) ## Ingest tpsc.yaml service declarations from a repo into the TPSC catalog. ## Usage: make ingest-tpsc REPO=llm-connect ## Or: make ingest-tpsc-all ## Add DRY_RUN=1 to preview without writing. ingest-tpsc: @test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1) $(UV) run python scripts/ingest_tpsc.py --repo "$(REPO)" \ $(if $(DRY_RUN),--dry-run) ingest-tpsc-all: $(UV) run python scripts/ingest_tpsc.py --all \ $(if $(DRY_RUN),--dry-run) ## Run SBOM capture agent for a repo — generates/updates sbom-tools.yaml. ## Usage: make capture-tools REPO=railiance-infra [REPO_PATH=/home/worsch/railiance-infra] ## Add DRY_RUN=1 to preview without writing. capture-tools: @test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1) $(UV) run python scripts/capture_sbom_tools.py --repo "$(REPO)" \ $(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \ $(if $(DRY_RUN),--dry-run) ## Check a repo for ADR-001 compliance: make validate-adr REPO=/path/to/repo [DOMAIN=custodian] validate-adr: @test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make validate-adr REPO= [DOMAIN=]"; exit 1) $(UV) run python scripts/validate_repo_adr.py "$(REPO)" $(if $(DOMAIN),--domain "$(DOMAIN)",) ## Consistency exit contract: ## - Direct scripts/consistency_check.py: 0 clean, 2 warnings-only, 1 failures. ## - Agent/operator Make wrappers below normalize warning-only 2 to shell success ## while preserving visible WARN output and keeping real failures non-zero. ## Check a single repo for ADR-001 consistency: make check-consistency REPO=the-custodian [REPO_PATH=/override] ## Exit 0 = clean or warnings-only (warnings stay visible), exit 1 = failures check-consistency: @test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make check-consistency REPO="; exit 1) $(UV) run python scripts/consistency_check.py --repo "$(REPO)" \ $(if $(API_BASE),--api-base "$(API_BASE)",) \ $(if $(REPO_PATH),--repo-path "$(REPO_PATH)",); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Check and auto-fix a single repo: make fix-consistency REPO=the-custodian [REPO_PATH=/override] ## Exit 0 = clean or warnings-only (warnings stay visible), exit 1 = failures fix-consistency: @test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make fix-consistency REPO="; exit 1) $(UV) run python scripts/consistency_check.py --repo "$(REPO)" --fix \ $(if $(API_BASE),--api-base "$(API_BASE)",) \ $(if $(REPO_PATH),--repo-path "$(REPO_PATH)",); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Normalize workplan frontmatter and task status literals in attached repos. ## Usage: make normalize-attached-workplans REPO=artifact-store ## make normalize-attached-workplans DIRTY=1 normalize-attached-workplans: $(UV) run python scripts/normalize_attached_repo_workplans.py \ $(if $(REPO),--repo "$(REPO)",) \ $(if $(DIRTY),--dirty,) \ $(if $(DRY_RUN),--dry-run,) @test -n "$(REPO)$(DIRTY)" || (echo "ERROR: set REPO= or DIRTY=1"; exit 1) ## Regenerate AGENTS.md / CLAUDE.md / .claude/rules from templates. ## Usage: make update-agent-instructions REPO=artifact-store ## make update-agent-instructions DIRTY=1 update-agent-instructions: $(UV) run python scripts/update_agent_instruction_files.py \ $(if $(REPO),--repo "$(REPO)",) \ $(if $(DIRTY),--dirty,) @test -n "$(REPO)$(DIRTY)" || (echo "ERROR: set REPO= or DIRTY=1"; exit 1) ## Reconcile measured token sources against State Hub. ## Usage: make token-reconcile [SINCE=2026-05-19] [APPLY=1] [ZERO_FALLBACKS=1] token-reconcile: $(PYTHON) scripts/token_reconcile.py \ $(if $(SINCE),--since "$(SINCE)",) \ $(if $(API_BASE),--api-base "$(API_BASE)",) \ $(if $(CODEX_HOME),--codex-home "$(CODEX_HOME)",) \ $(if $(CLAUDE_HOME),--claude-home "$(CLAUDE_HOME)",) \ $(if $(APPLY),--apply,) \ $(if $(ZERO_FALLBACKS),--zero-superseded-fallbacks,) ## Pull then fix: single repo or all repos if REPO omitted ## make fix-consistency-remote — smart pull+fix all repos that need it ## make fix-consistency-remote REPO=slug — pull+fix one repo fix-consistency-remote: $(UV) run python scripts/consistency_check.py \ $(if $(REPO),--repo "$(REPO)",--all) \ --remote \ $(if $(API_BASE),--api-base "$(API_BASE)",) \ $(if $(NO_WRITEBACK),--no-writeback,); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Infer repo slug from git remote URL and check: make check-consistency-here [REPO_PATH=/path/to/repo] ## Omit REPO_PATH to use the Python script's CWD (i.e. pass an empty --here flag). check-consistency-here: $(UV) run python scripts/consistency_check.py \ --here $(if $(REPO_PATH),"$(REPO_PATH)",) \ $(if $(API_BASE),--api-base "$(API_BASE)",); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Infer repo slug from git remote URL and fix: make fix-consistency-here [REPO_PATH=/path/to/repo] fix-consistency-here: $(UV) run python scripts/consistency_check.py \ --here $(if $(REPO_PATH),"$(REPO_PATH)",) \ --fix \ $(if $(API_BASE),--api-base "$(API_BASE)",); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Check all registered repos for ADR-001 consistency check-consistency-all: $(UV) run python scripts/consistency_check.py --all $(if $(API_BASE),--api-base "$(API_BASE)",); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Check and auto-fix all registered repos fix-consistency-all: $(UV) run python scripts/consistency_check.py --all --fix $(if $(API_BASE),--api-base "$(API_BASE)",); \ e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e ## Cancel open tasks belonging to completed/archived workstreams. ## Safe to run at any time; also suitable for a daily cron job. ## Cron example: 0 3 * * * cd ~/state-hub && make cleanup-stale cleanup-stale: $(UV) run python scripts/cleanup_stale_tasks.py ## Install custodian post-commit sync hook into one repo: make install-hooks REPO=marki-docx install-hooks: @test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make install-hooks REPO="; exit 1) bash scripts/install_hooks.sh --repo "$(REPO)" ## Install custodian post-commit sync hook into all active registered repos install-hooks-all: bash scripts/install_hooks.sh --all ## Remove custodian post-commit sync hook from one repo: make remove-hooks REPO=marki-docx remove-hooks: @test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make remove-hooks REPO="; exit 1) bash scripts/install_hooks.sh --repo "$(REPO)" --remove ## Compare Gitea coulomb org repos against state-hub registered repos ## Requires GITEA_TOKEN in env or .env: make gitea-inventory GITEA_TOKEN= gitea-inventory: $(UV) run python scripts/gitea_inventory.py $(if $(JSON),--json) ## Register/update one repo from .repo-classification.yaml: ## make register-from-classification REPO=state-hub ## make register-from-classification PATH=/path/to/repo ## Optional: DRY_RUN=1 register-from-classification: @test -n "$(REPO)" -o -n "$(PATH)" || (echo "ERROR: REPO or PATH is required."; exit 1) $(UV) run python scripts/register_from_classification.py \ $(if $(PATH),--repo-path "$(PATH)",--slug "$(REPO)") \ $(if $(DRY_RUN),--dry-run,) ## Bulk register/update all active repos with accessible local paths register-from-classification-all: $(UV) run python scripts/register_from_classification.py --bulk \ $(if $(DRY_RUN),--dry-run,) clean: $(COMPOSE) down -v