| name | altair |
| description | Drive a Univeros / Altair PHP project through `bin/altair` and `.agent/` manifests instead of reading source. Use when the user asks to scaffold an endpoint from a YAML spec, inspect routes / container / config / listeners / middleware, run doctor health checks, tail the events log, apply or roll back database migrations, manage Messenger workers, or undo a previous scaffold via the journal. |
Altair — driving the framework through bin/altair
This project is built on the Univeros / Altair PHP framework. The framework is designed to be agent-operable from the shell — the CLI is the primary surface, .agent/ manifests are the read-without-source surface, and almost every command supports --format=json so its output is consumable by an agent without parsing prose.
Use the CLI flows below before reading source. Read source only when a flow doesn't cover what you need.
When to skip this skill: if the only available channel is MCP (no shell, no local checkout), point the user at bin/altair mcp:serve and the docs/packages/mcp.md guide — that's the shell-less / remote bridge, and it surfaces the same capabilities as tool calls instead of subprocesses.
Read the project before mutating it
Two reads to do up front, every session, before the first edit:
AGENT.md at the repo root — the canonical project guide (architecture, package layout, conventions, the modernization roadmap).
.agent/MANIFEST.md — the auto-generated package index. Each .agent/packages/<slug>.md is a deterministic Markdown manifest for one package: classes, interfaces, public methods, configuration, "tests as documentation" list. It's a stable substitute for reading source when you only need the API shape.
If .agent/ is missing or stale: bin/altair manifest:generate. CI's determinism gate fails on drift, so don't hand-edit those files.
Adding an HTTP endpoint (the core flow)
The framework's most opinionated workflow. You do not hand-write the Action / Input / Responder / Domain stub / test / OpenAPI fragment / route entry — you write a YAML spec at api/<resource>/<action>.yaml and let spec:scaffold emit all seven artifacts.
$EDITOR api/users/create.yaml
bin/altair spec:scaffold api/users/create.yaml --dry-run
bin/altair spec:scaffold api/users/create.yaml
bin/altair spec:lint
bin/altair journal:list -n 5
bin/altair journal:show <id>
bin/altair journal:rewind
bin/altair journal:rewind --to=<id>
A persistence: block on the spec additionally emits a Cycle-annotated entity, a domain repository, and a Cycle migration. A queue: block additionally emits a readonly DTO message, a #[AsHandler]-decorated handler stub, and a handler test.
Never scaffold by hand-writing the four-class triple — the journal won't see the change and spec:lint will report drift. If you must edit a generated file, treat the YAML as the source of truth and prefer re-running spec:scaffold --force.
Inspecting a booted project (read-only X-ray)
When you'd reach for grep to figure out what's wired, reach for these first. Every command supports --format=json so you can branch on structured output.
bin/altair container:inspect --format=json
bin/altair routes:list --format=json
bin/altair routes:show GET /users/{id}
bin/altair listeners:list --format=json
bin/altair listeners:show user.created
bin/altair middleware:list --format=json
bin/altair config:dump --format=json
bin/altair spec:list
bin/altair spec:show api/users/create.yaml
bin/altair manifest:diff
Prefer the introspection JSON over a source grep when the question is "what does this app look like at boot time?" — it reflects the resolved configuration, not just the declared one.
Health checks before committing
bin/altair doctor
bin/altair doctor --format=json
bin/altair doctor --fix
doctor covers PHP / extension / composer state, the CS / PHPStan / test gates, container probes, database reachability, and the #74 determinism gate (regenerates .agent/, diffs, fails on drift). The JSON shape is stable, sorted, no timestamps — safe to compare across runs in CI.
If you're about to commit, run composer qa (cs + stan + test). If something fails, run bin/altair doctor to see whether it's environment, code, or determinism drift.
Database migrations
bin/altair db:migrate
bin/altair db:migrate --dry-run
bin/altair db:migrate:status
bin/altair db:migrate:rollback
bin/altair db:migration-plan --rename old:new
db:migration-plan runs read-only checks against the live DB (NOT NULL backfill, unique dupes, FK orphans, type-cast heuristic, large-table, drop-column) before emitting the Cycle migration + per-dialect preview SQL. Exits 1 on any error so it's CI-gate-safe.
Never write a migration by hand if the schema came from a spec — the entity + repository + migration drift otherwise.
Background workers
bin/altair worker
bin/altair worker --transports=default,high
bin/altair worker --time-limit=3600 --memory-limit=128M --limit=100
bin/altair worker:show-failed
bin/altair worker:retry-failed
When you add a queue dispatch, add a queue: block to the YAML spec and re-scaffold — don't hand-write the DTO + handler + test.
The events log (your mutation history)
Every mutating framework operation (scaffold, migration, rewind, replay, worker consume, …) records into .altair/events.jsonl. When the user asks "what just changed?", read this — don't git log and don't read source.
bin/altair events:tail -n 50
bin/altair events:show <id>
bin/altair events:since <ulid|timestamp>
bin/altair events:since-last-success
bin/altair events:filter --kind=scaffold,migration --status=fail
bin/altair events:stats
bin/altair events:checkpoint:create feat/x
bin/altair events:checkpoint:diff feat/x
When you complete a piece of work, an events:since-last-success is a tighter "what did I do this session?" than the entire shell history.
Code intelligence — index, suggest, eval
Reach for these when the question is "is this safe to change?" or "is this dead code?".
bin/altair index:build
bin/altair index:find-usages App\\User\\User
bin/altair index:callers-of 'App\\User\\CreateUser::__invoke'
bin/altair index:impact App\\User\\User,App\\User\\CreateUser
bin/altair index:unused --strict
bin/altair index:orphans
bin/altair suggest
bin/altair suggest --format=json --severity=warning
bin/altair eval 'return $container->get(Foo::class);'
bin/altair eval 'return iterator_to_array(...);' --timeout-ms=300
eval runs in a subprocess with disable_functions + open_basedir + memory/wall-clock kill. Use it for "let me check" probes; --unsafe lifts every guard and is audit-logged into events.jsonl.
Performance — profiling + observability
bin/altair profile:run path/to/script.php
bin/altair profile:list
bin/altair profile:show <id>
bin/altair profile:compare <baseline> <candidate>
bin/altair profile:flame <id> > flame.svg
bin/altair observability:tail -n 50 --format=json
bin/altair observability:stats
bin/altair observability:export --endpoint=https://otel/v1
Profiling needs ext-excimer to capture, but rendering (show, flame, compare) only needs PHP. Observability has no SDK dependency — speaks OTLP wire format directly.
REPL when nothing else works
bin/altair tinker
The REPL is a human tool — don't shell into it in autonomous loops. For one-shot probes use bin/altair eval '<snippet>' instead.
House rules for this skill
bin/altair is faster than reading source. Reach for the CLI first; open src/ when the CLI can't answer.
.agent/MANIFEST.md is faster than .agent/packages/*.md is faster than src/. Climb the ladder only as far as you need.
- Re-scaffold beats hand-editing. Treat YAML specs as the source of truth for endpoints, entities, and queue handlers.
composer qa before committing. Use bin/altair doctor to diagnose failures.
- Don't mutate
.agent/, .altair/, or vendor/ directly. They're outputs, not inputs.
- MCP is for shell-less clients. If you have a shell and a checkout, you have everything you need — leave MCP for hosted agents and remote / non-Claude clients.
When you actually do need to read source
The CLI doesn't cover ergonomic deep-dives into a single class's implementation. When you need that:
- Resolve the FQCN through
.agent/packages/<slug>.md first to find the file path.
- Then
Read only the file you need.
- Prefer the contracts (
src/Altair/<Pkg>/Contracts/) over the concretes — the framework's design enforces small, well-named interfaces.