Cookbook: Multi-file Config with Includes
This recipe shows how to split a large qp.yaml into maintainable files using includes.
Scenario
Your single config has grown too large and different teams own different task areas.
Root qp.yaml
project: large-repo
default: check
includes:
- tasks/backend.yaml
- tasks/frontend.yaml
- tasks/ops.yaml
guards:
ci:
steps: [check-backend, check-frontend]Included File Example: tasks/backend.yaml
tasks:
lint-backend:
desc: Lint backend code
cmd: golangci-lint run ./internal/...
scope: backend
test-backend:
desc: Test backend
cmd: go test ./internal/...
scope: backend
check-backend:
desc: Backend quality gate
steps: [lint-backend, test-backend]
scope: backendIncluded File Example: tasks/frontend.yaml
tasks:
lint-frontend:
desc: Lint frontend
cmd: npm run lint --prefix apps/web
scope: frontend
test-frontend:
desc: Test frontend
cmd: npm test --prefix apps/web
scope: frontend
check-frontend:
desc: Frontend quality gate
steps: [lint-frontend, test-frontend]
scope: frontendValidation Rules To Know
Current include behavior:
- included files contribute
taskssections - duplicate task names across root/includes are rejected
- non-task sections in included files are not merged
So keep shared config (vars, profiles, scopes, guards) in root qp.yaml.
Team Ownership Pattern
Recommended layout:
qp.yaml(global policy, guards, profiles, scopes)tasks/backend.yaml(backend-owned task set)tasks/frontend.yaml(frontend-owned task set)tasks/ops.yaml(ops/release task set)
This keeps domain ownership clean while preserving one runtime contract.
Run and Validate
qp validate --suggest
qp list
qp guard ci --jsonIf include wiring breaks, qp validate fails fast with file/task collision details.