Confirm the directory
Ask plainly which directory to set up in — never assume the working directory is right just because it's where the conversation happens to be running.
Step Zero / Install
A stranger just cloned the repo and has never run any of these agents before. /install is the one command that has to work before anything else can: it detects the repo, gets git/gh/sqlite3 in place, builds the docs/ skeleton, and confirms a GitHub Project board — asking a human for anything it can't safely decide on its own, and re-verifying live state on every re-run rather than trusting what it found last time.
01 / The checklist
The installer agent thinks of itself as a building inspector working a new construction site before anyone moves in: check each item with a real test, in order, and don't let the next step start on a foundation that isn't actually confirmed. Most of the checking is a script call; a few steps are a plain question to the human running it.
Ask plainly which directory to set up in — never assume the working directory is right just because it's where the conversation happens to be running.
Every stage this team runs depends on git — worktrees, branches, per-stage commits — more than it depends on anything else.
gh auth login is a human's browser or device-code flow. The script opens the terminal; it never completes the login itself.
bin/agent-log shells out to this binary directly for every read and write — without it, no agent in the team can log anything.
Detects the git remote, writes team.yml, creates the feature/bug/tech-debt labels, migrates db/agent_log.sqlite3, builds the docs/ skeleton.
Asks first. A GitHub Project (v2) board is owner-scoped, not repo-scoped — confirming or creating one still leaves it unlinked from the repo's own Projects tab until gh project link runs too.
Makes sure team.yml's default_status actually exists as an option on the board's Status field — the centerpiece story below.
The repo's own Issues feature, not the Project board — a simpler, separate thing to verify.
Fills in github.project.owner/number in team.yml, then reports one line per item: present, created, or failed.
02 / Automated vs. permanently manual
This split isn't a backlog of things still to build — it's drawn from what a human's own hands are for: a sudo password, a browser login, a yes before something appears on their GitHub account.
Automated
team.yml — a comment-preserving targeted edit, never a full rewritefeature/bug/tech-debt repo labelsdb/agent_log.sqlite3 and building the docs/ skeletonPermanently manual
gh auth login — unavoidably a human's browser or device-code flowruby being on PATH — never auto-installed by anything hereOne thing genuinely isn't built yet, and the source calls it out by that name: copying bin/agent-log itself into a target repo that doesn't already have it. That's a future "not yet built," distinct from the permanently-manual list above — those stay manual no matter how much tooling gets added later.
03 / The centerpiece
gh command for this. So the script talks to the API directly.Code owns the mechanics here because nothing else can reach them — the judgment (which option, which board, never delete a card's existing status) stays exactly where it was.
What's missing
$ gh project field-create
→ makes a brand-new field
$ gh project field-list
→ id, name only —
no color, no description
The CLI can create a whole new field. Nothing in it edits one option onto a Status field that already exists.
What the script does instead
updateProjectV2Field(
fieldId: $id,
singleSelectField: {
options: $fullList # every existing
# option, plus the
# one being added
}
)
The mutation demands the entire option list on every call. Drop one, and it's deleted — with any card already set to it.
Adding a new option to a GitHub Project board's existing Status field (e.g. "Ready" on a board that only ships with Todo/In Progress/Done) has no dedicated gh command — gh project field-create can make a brand-new field, but nothing edits an option onto one that already exists. bin/team-setup-project-status does it through the underlying GraphQL API instead: updateProjectV2Field requires resending the field's entire option list on every call, so the script always reads every existing option's id, name, color, and description first (gh project field-list doesn't expose color/description, so this is a separate query), then resends that full list plus the one new option. Leaving any existing option out of that list would silently delete it — and any card already set to it — so the script never constructs the list from anything but a fresh read. This was verified against a real, item-free project board before being trusted in the installer.
04 / Idempotent, and what it never does alone
A second run isn't a no-op — it re-verifies everything live (GitHub state can drift even when team.yml hasn't) and reports it all as already present rather than re-asking the same questions. This is why the scripts print structured status instead of just doing work silently: "already correct" and "just fixed" both look like status: "ok" to the agent, but the detail string says which.
What it does
team.ymldocs/What it cannot do
gh auth login, or a system package install, on the user's behalf