Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
599 lines (529 loc) · 22.2 KB
/
Copy pathMakefile
File metadata and controls
599 lines (529 loc) · 22.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
# Supported Python versions
PYTHON_VERSIONS = 3.10 3.11 3.12 3.13 3.14
DEFAULT_PYTHON = 3.10
# Every `uv run` implicitly re-syncs the venv (rebuilding the roboco package
# after any source edit). Two uv processes doing that concurrently — a
# background `make quality` plus any foreground `uv run` — race on one
# .venv and tear site-packages apart (recurring rich/pip/bandit ImportError
# corruption). Recipes therefore never sync implicitly; targets that need a
# fresh env depend on the explicit `sync` below, which runs once, up front.
export UV_NO_SYNC := 1
# Private per-repo uv cache. The user-level ~/.cache/uv is SHARED with every
# `uvx` tool server on the machine (e.g. Claude Code's mcp-server-fetch runs
# for days holding/contending the cache lock); concurrent cache writes from
# those processes poisoned package entries (rich/pip/bandit rot that survived
# venv rebuilds — the cache, not the venv, was the carrier). One-time cost:
# the first sync re-downloads; after that, total isolation.
export UV_CACHE_DIR := $(CURDIR)/.uv-cache
.PHONY: sync
sync:
@echo "==> uv sync --extra dev"
@uv sync --extra dev
# Install dependencies
.PHONY: install
install:
@uv sync
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Install dev dependencies
.PHONY: install-dev
install-dev:
@uv sync --extra dev
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Update dependencies
.PHONY: lock
lock:
@uv lock
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Upgrade dependencies
.PHONY: upgrade
upgrade:
@uv lock --upgrade
@uv sync --all-extras
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# =============================================================================
# INFRASTRUCTURE
# =============================================================================
# Default agents to spawn
AGENTS ?= main-pm be-dev-1 be-qa
# Start infrastructure (PostgreSQL + Redis)
.PHONY: infra
infra:
@echo "Starting infrastructure..."
@docker compose up -d postgres redis
@echo "Waiting for services to be healthy..."
@sleep 3
@docker compose ps
# Stop infrastructure
.PHONY: infra-down
infra-down:
@echo "Stopping infrastructure..."
@docker compose down
# One-command bring-up for the registry (pull-and-run) deploy: scaffolds .env
# with freshly generated secrets on first run (never touches an existing one),
# pulls + starts docker-compose.registry.yml, then polls until the stack is
# genuinely ready and prints a doctor-style summary. See scripts/bootstrap.sh
# for the grounded health/readiness probes.
.PHONY: quickstart
quickstart:
@./scripts/bootstrap.sh
# Run database migrations
.PHONY: migrate
migrate:
@echo "Running database migrations..."
@uv run alembic upgrade head
# Create new migration
.PHONY: migration
migration:
@read -p "Migration message: " msg; \
uv run alembic revision --autogenerate -m "$$msg"
# =============================================================================
# RUNNING THE APPLICATION
# =============================================================================
# Start API server only (development mode with reload)
.PHONY: api
api:
@echo "Starting RoboCo API (development mode)..."
@uv run uvicorn roboco.api.app:app --host 0.0.0.0 --port 8000 --reload
# Start API server (production mode, no reload)
.PHONY: run
run:
@echo "Starting RoboCo API (production mode)..."
@uv run uvicorn roboco.api.app:app --host 0.0.0.0 --port 8000
# Start orchestrator only (spawns agents)
.PHONY: orchestrator
orchestrator:
@echo "Starting orchestrator with agents: $(AGENTS)..."
@uv run python -m roboco.cli --spawn $(AGENTS)
# Start API + Orchestrator (full development mode)
.PHONY: dev
dev:
@echo "Starting RoboCo in development mode..."
@echo "Agents to spawn: $(AGENTS)"
@echo ""
@echo "Starting API in background..."
@uv run uvicorn roboco.api.app:app --host 0.0.0.0 --port 8000 &
@sleep 2
@echo "Starting orchestrator..."
@uv run python -m roboco.cli --spawn $(AGENTS)
# Initialize database only (seed data)
.PHONY: db-init
db-init:
@echo "Initializing database..."
@uv run python -m roboco.cli --db-only
# =============================================================================
# MONITORING & STATUS
# =============================================================================
# Show system status
.PHONY: status
status:
@echo "=== Infrastructure ==="
@docker compose ps
@echo ""
@echo "=== API Health ==="
@curl -s http://localhost:8000/health 2>/dev/null | jq . || echo "API not running"
@echo ""
@echo "=== Orchestrator Status ==="
@curl -s http://localhost:8000/api/v1/orchestrator/status 2>/dev/null | jq . || echo "Orchestrator not available"
# Tail all logs
.PHONY: logs
logs:
@docker compose logs -f
# =============================================================================
# TMUX SESSION
# =============================================================================
# Create tmux session with all components
.PHONY: tmux
tmux:
@echo "Creating tmux session 'roboco'..."
@tmux kill-session -t roboco 2>/dev/null || true
@tmux new-session -d -s roboco -n infra
@tmux send-keys -t roboco:infra "cd $(PWD) && docker compose logs -f" Enter
@tmux new-window -t roboco -n api
@tmux send-keys -t roboco:api "cd $(PWD) && make api" Enter
@tmux new-window -t roboco -n orch
@tmux send-keys -t roboco:orch "cd $(PWD) && sleep 3 && make orchestrator AGENTS='$(AGENTS)'" Enter
@tmux select-window -t roboco:api
@echo "tmux session 'roboco' created. Attach with: tmux attach -t roboco"
# Stop
.PHONY: stop
stop:
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Restart
.PHONY: restart
restart: stop start-example
# Lint code
.PHONY: lint
lint:
@echo 'Formatting w/ Ruff...' ; echo '' ; uv run ruff format .
@echo '' ; echo '' ; echo 'Linting w/ Ruff...' ; echo '' ; uv run ruff check .
@echo '' ; echo '' ; echo 'Type checking w/ Mypy...' ; echo '' ; uv run mypy roboco/ tests/
@echo '' ; echo '' ; echo 'Finding dead code w/ Vulture...' ; echo '' ; uv run vulture vulture_whitelist.py
# Fix code
.PHONY: fix
fix:
@echo "Fixing formatting w/ Ruff..."
@echo ''
@uv run ruff check --fix .
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Reflow hard-wrapped markdown prose to one line per paragraph (docs style).
.PHONY: reflow-docs
reflow-docs:
uv run python scripts/reflow_md.py --apply
# CI guard: fail if any in-scope doc has hard-wrapped prose. Wired into `quality`.
.PHONY: reflow-check
reflow-check:
uv run python scripts/reflow_md.py --check
# Find dead code with Vulture
.PHONY: vulture
vulture:
@echo "Finding dead code with Vulture..."
@echo ''
# @uv run vulture
# @uv run vulture --verbose
@uv run vulture vulture_whitelist.py
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Security scan with Bandit
.PHONY: bandit
bandit:
@echo "Running Bandit security scan..."
@echo ''
@uv run bandit -r roboco -ll
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Audit dependencies with pip-audit
.PHONY: pip-audit
pip-audit:
@echo "Auditing dependencies with pip-audit..."
@echo ''
@uv run pip-audit
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Analyze code complexity with Radon
.PHONY: radon
radon:
@echo "Analyzing code complexity with Radon..."
@echo ''
@echo "Cyclomatic Complexity:"
@uv run radon cc roboco -nc
@echo ''
@echo "Maintainability Index:"
@uv run radon mi roboco -nc
@echo ''
@echo "Raw Metrics:"
@uv run radon raw roboco
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Check complexity thresholds with Xenon
.PHONY: xenon
xenon:
@echo "Checking complexity thresholds with Xenon..."
@echo ''
@uv run xenon roboco --max-absolute B --max-modules A --max-average A
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Analyze dependencies with Deptry
.PHONY: deptry
deptry:
@echo "Analyzing dependencies with Deptry..."
@echo ''
@uv run deptry .
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache)" | xargs rm -rf
# Run all security checks
.PHONY: security
security: bandit pip-audit
@echo "All security checks completed."
# =============================================================================
# QUALITY GATES
# =============================================================================
# docker-compose.yaml and docker-compose.yml must stay byte-identical: Compose's
# default file lookup prefers .yaml, so a bare `docker compose up` silently runs
# whichever twin is stale. This has drifted before (see CHANGELOG's vault-mount
# divergence) — this is the guard that catches it before it ships again.
.PHONY: compose-sync
compose-sync:
@cmp -s docker-compose.yml docker-compose.yaml || (echo "docker-compose.yaml has drifted from docker-compose.yml — copy .yml over .yaml" && exit 1)
# Run every quality gate. Fails on any red. Use this as the merge gate.
.PHONY: quality
quality: sync
@echo "==> compose files in sync (.yaml == .yml)"
@$(MAKE) compose-sync
@echo "==> ruff format --check"
@uv run ruff format --check .
@echo "==> ruff check"
@uv run ruff check .
@echo "==> markdown prose (no hard-wrapping)"
@uv run python scripts/reflow_md.py --check
@echo "==> mypy"
@uv run mypy roboco/ tests/
@echo "==> pytest with coverage"
@uv run pytest -q --cov=roboco --cov-report=term-missing --cov-fail-under=75
@echo "==> xenon (cyclomatic complexity)"
@uv run xenon --max-absolute B --max-modules A --max-average A roboco/
@echo "==> radon mi (maintainability index)"
@uv run radon mi roboco/ -nc -s
@echo "==> vulture (dead code)"
@uv run vulture roboco/ tests/ vulture_whitelist.py --min-confidence 100
@echo "==> bandit (security)"
@uv run bandit -r roboco/ -ll
@echo "==> pip-audit (deps vulnerabilities)"
# CVE-2025-3000: memory corruption in torch.jit.script (MEDIUM, local-only,
# no fix published). torch is a transitive dep (piragi / sentence-transformers)
# pinned to the CPU wheel and NEVER loaded at runtime — the stack uses Ollama
# over HTTP for all embeddings/LLM, so the vulnerable JIT path is unreachable.
# Documented waiver; revisit when a fixed torch ships.
@uv run pip-audit --ignore-vuln CVE-2025-3000
@echo "==> deptry (dependency hygiene)"
@uv run deptry roboco/
@echo "==> alembic upgrade --sql (migrations parse)"
@uv run alembic upgrade head --sql > /dev/null
@echo "==> import-linter (architectural boundaries)"
@uv run lint-imports
@echo "==> foundation drift checks (includes lifecycle artifacts)"
@$(MAKE) foundation-check
@echo ""
@echo "All quality gates passed."
# Scripted-agent lifecycle smoke: the REAL MCP flow/do tools driven through
# the REAL gateway/gates against an in-process API + ephemeral test Postgres +
# local git origin (gh shimmed). No LLM. Excluded from `quality` (env-gated);
# CI runs it as its own job.
.PHONY: e2e-smoke
e2e-smoke: sync
@echo "==> e2e lifecycle smoke (scripted agents, real gates)"
@ROBOCO_E2E_SMOKE=1 uv run pytest tests/e2e_smoke -q --no-cov
.PHONY: quality-fast
quality-fast: sync
@uv run ruff format --check .
@uv run ruff check .
@uv run mypy roboco/ tests/
@uv run pytest -q -x --no-cov
# Fast pre-submit gate: format-check + lint + types + complexity + import boundaries, NO tests.
# This is the command a project points `quality_command` at, so the agent
# pre-submit gate (run at i_am_done) executes it in the dev's workspace and
# catches lint/type/complexity at the desk. The test suite stays on CI.
.PHONY: gate
gate: sync
@uv run ruff format --check .
@uv run ruff check .
@uv run mypy roboco/ tests/
@uv run xenon --max-absolute B --max-modules A --max-average A roboco/
@uv run lint-imports
# Panel (Next.js) fast gate: lint + type-check + vitest.
# Run locally before submitting panel changes; mirrors the CI panel job exactly.
.PHONY: panel-gate
panel-gate:
@cd panel && pnpm lint
@cd panel && pnpm exec tsc --noEmit
@cd panel && pnpm test
# Full CI-equivalent panel gate (alias for panel-gate).
.PHONY: panel-quality
panel-quality: panel-gate
# Run all analysis tools
.PHONY: analysis
analysis: deptry
@echo "All analysis tools completed."
# Run all checks (linting, security, quality, and analysis)
.PHONY: check-all
check-all: lint security quality analysis
@echo "All checks completed."
# Run tests (default Python version)
.PHONY: test
test:
@COMPOSE_BAKE=true PYTHON_VERSION=$(DEFAULT_PYTHON) docker compose run --rm --build roboco pytest -v --cov=.
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Run All Python versions
.PHONY: test-all
test-all: test-3.10 test-3.11 test-3.12 test-3.13 test-3.14
# Python 3.10
.PHONY: test-3.10
test-3.10:
@docker compose down -v roboco
@COMPOSE_BAKE=true PYTHON_VERSION=3.10 docker compose build roboco
@PYTHON_VERSION=3.10 docker compose run --rm roboco pytest -v --cov=.
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Python 3.11
.PHONY: test-3.11
test-3.11:
@docker compose down -v roboco
@COMPOSE_BAKE=true PYTHON_VERSION=3.11 docker compose build roboco
@PYTHON_VERSION=3.11 docker compose run --rm roboco pytest -v --cov=.
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Python 3.12
.PHONY: test-3.12
test-3.12:
@docker compose down -v roboco
@COMPOSE_BAKE=true PYTHON_VERSION=3.12 docker compose build roboco
@PYTHON_VERSION=3.12 docker compose run --rm roboco pytest -v --cov=.
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Python 3.13
.PHONY: test-3.13
test-3.13:
@docker compose down -v roboco
@COMPOSE_BAKE=true PYTHON_VERSION=3.13 docker compose build roboco
@PYTHON_VERSION=3.13 docker compose run --rm roboco pytest -v --cov=.
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Python 3.14
.PHONY: test-3.14
test-3.14:
@docker compose down -v roboco
@COMPOSE_BAKE=true PYTHON_VERSION=3.14 docker compose build roboco
@PYTHON_VERSION=3.14 docker compose run --rm roboco pytest -v --cov=.
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Stress Test
.PHONY: stress-test
stress-test:
@COMPOSE_BAKE=true docker compose up --build -d roboco-example redis
@echo "Waiting for services to start up..."
@sleep 5
@docker compose run --rm roboco-example uv run python examples/testing/stress_test.py --url http://roboco-example:8000 --duration 120 --concurrency 50 --ramp-up 10 --delay 0.02 --test-type standard -v
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# High-load stress test
.PHONY: high-load-stress-test
high-load-stress-test:
@COMPOSE_BAKE=true docker compose up --build -d roboco-example redis
@echo "Waiting for services to start up..."
@sleep 5
@docker compose run --rm roboco-example uv run python examples/testing/stress_test.py --url http://roboco-example:8000 --duration 180 --concurrency 100 --ramp-up 15 --delay 0.01 --test-type high_load -v
@docker compose down --rmi all --remove-orphans -v
@docker system prune -f
# Regenerate the GitHub Pages redirect stubs (docs-redirects/) that replaced
# the old MkDocs-built site — docs.roboco.tech is canonical now. Only needed
# if a legacy URL is missing a stub; requires a restored copy of the old
# nav-bearing mkdocs.yml (see git history) since the live one is gone.
.PHONY: regen-docs-redirects
regen-docs-redirects:
@uv run python scripts/gen_docs_redirects.py
# Prune
.PHONY: prune
prune:
@docker system prune -f
# Clean Cache Files
.PHONY: clean
clean:
@find . | grep -E "(__pycache__|\.pyc|\.pyo|\.pytest_cache|\.ruff_cache|\.mypy_cache|data/|site/)" | xargs rm -rf
@cd panel && rm -rf node_modules/ && rm -rf .next/ && rm -rf logs/ && rm -rf coverage/
@cd ..
@cd motion && rm -rf node_modules/
@cd ..
@cd video-renderer && rm -rf node_modules/
@cd ..
# Security
.PHONY: panel-token
panel-token:
@# Strip surrounding quotes from the .env value: docker-compose/pydantic
@# unquote it, so the token must be signed with the UNQUOTED secret or it
@# won't verify against the orchestrator (a quoted .env value silently
@# produced a mismatched token before this).
@SECRET="$$(grep -E '^ROBOCO_AGENT_AUTH_SECRET=' .env 2>/dev/null | head -1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$$//' -e "s/^'//" -e "s/'$$//")"; \
ROBOCO_AGENT_AUTH_SECRET="$${SECRET:-$$ROBOCO_AGENT_AUTH_SECRET}" \
uv run python -c "import sys; from roboco.agents_config import issue_panel_token; tok = issue_panel_token(); print(tok) if tok != 'UNSIGNED' else sys.exit('ERROR: ROBOCO_AGENT_AUTH_SECRET not set (in .env or environment) - the panel token would be unsigned')"
# Help
.PHONY: help
help:
@echo "RoboCo - AI Agents Company"
@echo ""
@echo "Infrastructure:"
@echo " make quickstart - One-command bring-up (registry pull-and-run)"
@echo " make infra - Start PostgreSQL + Redis"
@echo " make infra-down - Stop infrastructure"
@echo " make migrate - Run database migrations"
@echo " make migration - Create new migration"
@echo " make db-init - Initialize/seed database"
@echo ""
@echo "Running:"
@echo " make dev - Start API + Orchestrator (development)"
@echo " make dev AGENTS='a b c' - Start with specific agents"
@echo " make api - Start API only (with reload)"
@echo " make run - Start API only (production)"
@echo " make orchestrator - Start orchestrator only"
@echo " make tmux - Create tmux session with all components"
@echo ""
@echo "Monitoring:"
@echo " make status - Show system status"
@echo " make logs - Tail infrastructure logs"
@echo ""
@echo "Dependencies:"
@echo " make install - Install dependencies"
@echo " make install-dev - Install dev dependencies"
@echo " make lock - Update lock file"
@echo " make upgrade - Upgrade all dependencies"
@echo ""
@echo "Code Quality:"
@echo " make lint - Run linting (ruff, mypy, vulture)"
@echo " make fix - Auto-fix linting issues"
@echo " make quality - Run all quality checks"
@echo " make security - Run security checks (bandit, safety, pip-audit)"
@echo " make check-all - Run ALL checks"
@echo " make panel-token - Print the panel's CEO token for secure mode"
@echo ""
@echo "Testing:"
@echo " make test - Run tests (Python $(DEFAULT_PYTHON))"
@echo " make test-all - Run tests (all Python versions)"
@echo " make stress-test - Run stress test"
@echo ""
@echo "Cleanup:"
@echo " make stop - Stop all containers"
@echo " make clean - Clean cache files"
@echo " make prune - Prune docker resources"
@echo ""
@echo "Full docs: https://docs.roboco.tech"
# Python versions list
.PHONY: show-python-versions
show-python-versions:
@echo "Supported Python versions: $(PYTHON_VERSIONS)"
@echo "Default Python version: $(DEFAULT_PYTHON)"
# =============================================================================
# LIFECYCLE ARTIFACTS
# =============================================================================
# Regenerate canonical lifecycle artifacts (markdown / JSON / prompt fragments)
# from roboco/lifecycle/spec.py. Output is deterministic; CI gates on
# `make lifecycle && git diff --exit-code`.
.PHONY: lifecycle
lifecycle:
uv run python scripts/build_lifecycle_artifacts.py
# =============================================================================
# FOUNDATION DRIFT GATE
# =============================================================================
# Canonical drift gate: validates identity tables, runs foundation self-tests,
# regenerates lifecycle artifacts and fails on any uncommitted diff, and
# (when reachable) checks postgres enum parity. Run on every PR — drift
# between foundation tables / lifecycle spec and the committed artifacts
# cannot land on master.
.PHONY: foundation-check
foundation-check:
@echo "==> foundation/identity validators"
uv run python -c "from roboco.foundation import _validate; _validate.run_all(); print(' identity validators: OK')"
@echo "==> foundation/tracing verb parity"
uv run pytest tests/foundation/test_tracing_verb_parity.py --no-cov -q
@echo "==> foundation/journaling consumers"
uv run pytest tests/foundation/test_journaling_consumers.py --no-cov -q
@echo "==> foundation/communications consumers"
uv run pytest tests/foundation/test_communications_consumers.py --no-cov -q
@echo "==> foundation tests (full)"
uv run pytest tests/foundation/ --no-cov -q
@echo "==> lifecycle artifacts up-to-date (renders + git diff)"
@$(MAKE) lifecycle
@git diff --exit-code -- docs/rag/lifecycle panel/lib/lifecycle.json agents/prompts/_generated/lifecycle-*.md \
|| (echo "Lifecycle artifacts are out of date. Run 'make lifecycle' and commit the diff." && exit 1)
@echo "==> verb tables up-to-date (renders + git diff)"
@uv run python scripts/regenerate_verb_tables.py
@git diff --exit-code -- agents/prompts/_generated/ ':!agents/prompts/_generated/lifecycle-*.md' \
|| (echo "Verb tables are out of date. Run 'make foundation-check' and commit the diff." && exit 1)
@echo "==> postgres enum parity (skip if no migrated DB)"
uv run python scripts/verify_postgres_enums.py
@echo "All foundation drift checks passed."
# Backwards-compatible alias — prior CI / scripts called `ci-lifecycle-check`.
# `foundation-check` is now the canonical drift gate; this alias just forwards.
.PHONY: ci-lifecycle-check
ci-lifecycle-check: foundation-check
# Write counterpart to foundation-check's read: regenerates the same checked-in
# artifacts IN PLACE (no diff/exit-code guard) so a project can point its
# `codegen_command` at this and have drift committed before a push, instead of
# only ever discovering it at CI's foundation-check hard-fail.
.PHONY: codegen
codegen:
@$(MAKE) lifecycle
@uv run python scripts/regenerate_verb_tables.py