generated from coulomb/repo-seed
Replace the fixed 15s TTL on GET /state/summary with per-table revision watermarks, stale-while-revalidate background refresh, and a progress-tail section split. SQLAlchemy write hooks invalidate core or progress sections on mutation. Adds tests, benchmark script, and operator docs.
346 lines
15 KiB
Makefile
346 lines
15 KiB
Makefile
.PHONY: install install-cli dashboard-install dashboard-check db db-tools migrate seed api dashboard check test test-python clean register-project register-codex-project register-mcp bootstrap-env 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
|
|
|
|
COMPOSE = docker compose -f infra/docker-compose.yml --env-file .env
|
|
PYTHON ?= python3
|
|
# 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)
|
|
|
|
start:
|
|
@echo "# run in different terminals"
|
|
@echo "make db # docker compose up postgres"
|
|
@echo "make api # start backend api"
|
|
@echo "make mcp-http # start state-hub mcp service"
|
|
@echo "make dashboard # Observable dev server on :3000"
|
|
@echo "make bridges # Set up ssh bridges for cross machines access"
|
|
|
|
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
|
|
|
|
dashboard:
|
|
@fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true
|
|
$(MAKE) dashboard-install
|
|
cd dashboard && npm run dev
|
|
|
|
check:
|
|
curl -sf http://127.0.0.1:8000/state/health | python3 -m json.tool
|
|
|
|
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:
|
|
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:
|
|
@echo "==> Bringing up all tunnels..."
|
|
bridge up
|
|
@echo ""
|
|
@echo "==> Tunnel status:"
|
|
bridge status
|
|
@echo ""
|
|
@echo "==> Checking tunnel health..."
|
|
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 --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=<domain> PROJECT_PATH=<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=<domain> PROJECT_PATH=<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,)
|
|
|
|
## Bootstrap a new operator/collaborator environment. Optional: make bootstrap-env ARGS="--install-missing"
|
|
bootstrap-env:
|
|
scripts/bootstrap-env.sh $(ARGS)
|
|
|
|
## 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=<slug> PATH=<path>"; exit 1)
|
|
@test -n "$(PATH)" || (echo "ERROR: PATH is required. Usage: make register-path REPO=<slug> PATH=<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=<path> [DOMAIN=<slug>]"; 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=<slug>"; 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=<slug>"; 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
|
|
|
|
## 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=<slug>"; 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=<slug>"; 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=<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
|