.PHONY: install install-cli db db-tools migrate seed api dashboard check test clean register-project validate-adr add-domain rename-domain add-repo list-repos register-path cleanup-stale tunnels-up tunnels-status tunnels-check install-hooks install-hooks-all gitea-inventory COMPOSE = docker compose -f infra/docker-compose.yml --env-file .env install: uv sync ## 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 @echo "Installed: custodian → $$(readlink -f ~/.local/bin/custodian)" @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 dashboard: @fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true cd dashboard && npm run dev check: curl -sf http://127.0.0.1:8000/state/health | python3 -m json.tool test: TEST_DATABASE_URL=postgresql+asyncpg://custodian:changeme@127.0.0.1:5432/custodian_test \ uv run pytest -x -q ## ops-bridge managed tunnels ## Requires ops-bridge: bridge is at /home/worsch/.local/bin/bridge tunnels-up: 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 ## Start (or restart) the full backend — db + migrate + uvicorn. ## Stops uvicorn on :8000 if already running, then starts fresh. api: db @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 --host 127.0.0.1 --port 8000 ## Register a project: 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)" ## 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)",) ## Check a single repo for ADR-001 consistency: make check-consistency REPO=the-custodian [REPO_PATH=/override] ## Exit 0 = clean, exit 2 = warnings only (treated as success), 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, exit 2 = warnings only (treated as success), 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 ## 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 ## 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 ~/the-custodian/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) clean: $(COMPOSE) down -v