Setup
Get a repo from nothing to the full workflow, or tell an already-configured repo what it is missing.
Two modes, chosen from the user's wording:
- Bootstrap (default, or "set up", "onboard") — probe, write, create, report.
- Check ("check", "doctor", "why isn't X working", "what's missing") — probe and
report only. Changes nothing. Run this first on a repo that already half-works.
When
reposnames siblings, check also reads each sibling's file — its checkout, else the GitHub read inshared/config.md§ Repo scope — diffs the two shared keys,boardandpriorityCaps, across the files and reports any disagreement as a Missing row: two files feeding one board with two boards named split the sweep. The area maps are per repo and are not diffed. Check instead lists this repo'sarea:*labels (gh label list --limit 200 --json name --jq '.[].name') and reports every label with nodrientry in this file as a Missing row, and everydrientry with no such label here as cleanup — that second list is what a pre-0.12.0 union file shows, and it is not a failure. - Upgrade ("upgrade", "migrate the config", "bring workflow.json up to date", or the drift line any skill prints) — an existing file, a newer plugin: add only the keys the schema gained since the file was written. § 3 Upgrade mode.
Never overwrite without asking. If .claude/workflow.json already exists, show the
diff between it and what you would write, and let the user choose.
Workflow
- [ ] 1. Preconditions — gh auth, repo, signing
- [ ] 2. Probe the repo (branch, gate, spec flow, merge method, protection)
- [ ] 3. Write .claude/workflow.json (bootstrap only) — or add its missing keys (upgrade only)
- [ ] 4. Labels — create the ones the skills read (bootstrap only)
- [ ] 5. Board — verify the fields exist; create what gh can (bootstrap only)
- [ ] 5b. Stacks — name the stack(s), generate .claude/workflow/deploy-targets/<name>.md from
what § 2 probed, list every UNVERIFIED section (bootstrap + upgrade)
- [ ] 6. Confirm the two agents loaded, and say what they are for
- [ ] 7. Report: what is configured, what is missing, what only a human can do
1. Preconditions
gh auth status # must be logged in, with project scope for board steps
git rev-parse --show-toplevel # must be a git repo
git config --get commit.gpgsign # the workflow signs every commit
If gh auth status lacks the project scope, board steps will fail with a
permissions error rather than an empty result:
gh auth refresh -s project,read:project
If commit.gpgsign is unset, say so — every skill here signs commits and stops on a
signing failure. That is a user decision, not something to configure for them.
2. Probe
Never assume. Read each of these:
gh repo view --json nameWithOwner,defaultBranchRef,squashMergeAllowed,rebaseMergeAllowed,mergeCommitAllowed,isEmpty
git branch -r --list 'origin/*'
🚨 Handle the empty repo first. A GitHub repo created but never pushed to reports
isEmpty: true and defaultBranchRef.name as an empty string — measured, not null,
so a truthiness check on the object still passes and you compose origin/ + "" =
origin/, a branch name that silently matches nothing downstream.
gh repo view --json isEmpty --jq '.isEmpty' # true -> stop probing the branch
On an empty repo: say so, write no integrationBranch, and tell the user to push first
and re-run. Everything else in this skill still applies — labels and the board can be set
up before the first commit.
⚠️ Glob defensively — the agent's shell is zsh, where an unmatched glob is an ERROR,
not an empty list. ls .github/workflows/*.yml exits 1 with no matches found and
aborts a chained command, where bash would have passed the pattern through. Measured on a
repo with no CI. Test the directory first, or use find:
[ -d .github/workflows ] && find .github/workflows -name '*.yml' -o -name '*.yaml'
| Fact | How |
|---|---|
board |
Ask which board THIS repo feeds, and write {"number": N, "owner": "<owner>"} whenever it is not the user's machine default. This is what lets one machine work several workspaces against different boards — see shared/board.md § Resolution for the resolution order. Omit the key when the repo uses the default; do not write a copy of it. 🚨 Both sub-keys or neither — a board with number and no owner does not fall back, it stops every board step in a repo you just green-lit. If the owner is unknown, ask; if you cannot get it, write no board key. |
repos |
The repo you are in (gh repo view --json nameWithOwner). Ask whether other repos feed the same board — if so, list them all, full owner/repo. One repo is the common answer and a perfectly good one; write the key anyway so the skills never have to guess. Listing a sibling widens the issue sweep only: its branch, gate and forbidden paths still come from its own file (shared/config.md § Repo scope), so every repo keeps a file; board and priorityCaps must agree across them, and each file's areaLabels, dri and trackForArea name only that repo's areas. |
integrationBranch |
origin/ + the default branch — after the empty-repo check above. ⚠️ Not always main — if a dev/develop remote branch exists and is ahead of the default, the repo probably integrates there and releases from the default. Ask; do not guess. |
validate |
Read the CI workflow first — .github/workflows/*.yml, the job that runs on PRs into the integration branch. Copy its step commands in order. Fall back to the toolchain only if there is no CI: pyproject.toml/requirements.txt → ruff/pytest; package.json → the lint/typecheck/test/build scripts that actually exist; Cargo.toml → cargo clippy/cargo test; go.mod → go vet/go test ./.... |
preflight |
Anything the gate shells out to that no lockfile installs. |
specFlow |
An openspec/ directory at the repo root → "openspec". |
mergeMethod |
From the *MergeAllowed flags. |
deployOnMerge |
Grep .github/workflows/ for a workflow triggering on push to the integration branch that deploys. Do not record "nothing happens" unless you looked. Note that some hosts (Vercel, Netlify, Fly) deploy from the repo with no workflow at all — check for their config files too. |
requiredChecks, protection |
gh api repos/<owner>/<repo>/branches/<b>/protection — this 404s if the branch is unprotected, which is itself the answer. |
workstreams |
For a monorepo: the actual directories under apps/, packages/, crates/, etc. Read the tree; never trust a README. |
validateWhenChanged |
A CI job gated by a paths: filter — its command keyed by that glob. Omit when CI has no such job. |
ciOnly |
A required check that needs a service, secret or multi-GB download you cannot reproduce locally — its name, with the reason. Read the job's services: and secrets. uses. |
deployWorkflow |
The workflow file deployOnMerge was read from, so the next reader can re-derive it. While it is open, copy its paths / paths-ignore block verbatim — § 5b writes it into the deploy-target doc § Deploy. |
trackForArea |
Each area:* label → the board's Track option of the same name, from field-list (§ 5). Only when the board has a Track field. |
agentReadyForbiddenPaths |
The infra, migration and workflow directories the deploy and protection probes found — the paths an unattended run must never touch. Record the migration directory and its apply command from the same read; § 5b writes those under the deploy-target doc § Infra and migrations. |
priorityCaps |
Never probed. Written as the default {"legal": "P1"} with a $comment_priorityCaps saying what it does and that {} turns it off — so the rule triage applies is visible in the file rather than implied by its absence. |
schemaVersion |
Always the Current schema from shared/config.md § Layer 2 → Schema. Never probed, never omitted. |
Verify each probed command actually runs before writing it into the config. A gate entry that errors on first use is worse than an absent one — the next session reads its failure as a broken repo. Run them; report any that fail.
3. Write .claude/workflow.json
Schema and key meanings: shared/config.md.
Write only what you probed. Leave a key out rather than guessing it — an absent key
falls through to Layer-3 probing, a wrong key is believed. ⚠️ board is the exception
to "leave a key out": it is both sub-keys or no key at all, never a half. See § 2.
Add $comment keys recording where each value came from and when. Every list in that
file is a snapshot of something that moves; the comment is what tells the next reader to
re-derive rather than trust.
Always write schemaVersion — the current schema from
shared/config.md § Layer 2 → Schema. It is how every later
run knows whether this file has kept up with the plugin.
Upgrade mode — an existing file, a newer plugin
claude plugin update refreshes the plugin's code and tells no repo that its
workflow.json is behind. This mode closes that gap, and the drift line every skill
prints (shared/config.md § Resolving workflow.json, step 2) is what sends
you here.
- [ ] 1. Read the file. `.schemaVersion // 0` is where it stands; Current schema is
where it should be. Equal → the keys are current: say so, skip to step 8.
- [ ] 2. From the schema table, take every key whose Since is greater than the file's
version — PLUS any other key the file lacks that § 2 knows how to probe.
- [ ] 3. Probe each exactly as § 2 does. Show the proposed keys with their sources and
ASK. A probe is a proposal, not a decision. `deployTargets` is the exception: § 5b
both probes it and writes its files, so hand that key to § 5b.
- [ ] 4. Write ONLY the missing keys, each with a `$comment_<key>` naming the plugin
version, the source and the date. Set `schemaVersion` to the current schema.
- [ ] 5. Re-run any formatter the repo applies to the file — a `$comment` usually says
which — then show the diff. In `workflow.json` it must touch nothing but the
added keys; the deploy-target docs § 5b writes are separate files.
- [ ] 6. The file is usually tracked on a protected branch: branch, commit, open a PR.
Never merge it.
- [ ] 7. Read it back: `jq .schemaVersion` equals the current schema and every proposed
key is present.
- [ ] 8. Run § 4 and § 5b — always, even when step 1 found the keys current. § 4 creates
any label the skills read that the repo lacks (idempotent: `gh label create`
exits 1 on an existing name, tolerate it). MEASURED: a repo upgraded from a
pre-0.5.2 setup was missing `legal`, `compliance` and `security`, which triage
applies and reads, because upgrade ran only the key steps. § 5b adds a header
the skeleton gained, or a doc for evidence that appeared since, and says
"nothing to add" otherwise. Neither depends on a schema bump.
🚨 Never touch an existing key — not its value, not its formatting, not its comment. A human wrote it, possibly to override exactly what the probe would have found. If a probe disagrees with an existing value, report the disagreement in § 7; do not resolve it.
Check mode reports the same delta and writes nothing: the drift line, then the keys upgrade would add and what each probe found.
⚠️ Check whether .claude/ is gitignored before declaring the file shared:
git check-ignore -v .claude/workflow.json .claude/workflow/deploy-targets/probe.md # the second path need not exist
Both paths, because the fix for one is not the fix for the other: the common
.claude/* + !/.claude/workflow.json pattern shares the config and still hides every
deploy-target doc § 5b writes — the read-back passes, the file never reaches the PR, and every
teammate reads "no deploy-target docs" as a normal state.
Read the -v output, not the exit code — git check-ignore exits 0 on any pattern
match, a negation included. Many repos blanket-ignore .claude/* with per-file
negations. If the new file is ignored, it is local-only and teammates get nothing:
tell the user the exact negation line to add, and that .gitignore is a tracked file
whose change may need a PR.
4. Labels
These are the labels the skills read. Create the missing ones; never modify an existing label's colour or description — a repo's palette is a human's choice.
gh label create improvement -d "Refactor, perf, DX, cleanup of something that works" -c 0E8A16
gh label create effort:easy -d "One repo, obvious files, a pattern to mirror" -c C2E0C6
gh label create effort:medium -d "Multiple modules or a new pattern" -c FBCA04
gh label create effort:hard -d "Cross-repo, migration, infra, or an open question" -c D93F0B
gh label create triaged -d "Deep-triage idempotency key" -c EDEDED
gh label create blocked -d "Cannot proceed — see 'Blocked by: #n'" -c B60205
gh label create epic -d "Tracking issue with sub-issues" -c 5319E7
gh label create agent-ready -d "Gated safe for unattended work" -c 1D76DB
gh label create agent-wip -d "An unattended run has claimed this" -c 0052CC
gh label create agent-blocked -d "Unattended run handed it back — a human decides" -c B60205
gh label create agent-authored -d "PR opened unattended" -c 1D76DB
gh label create legal -d "Legal / policy / contractual — priority caps at P1" -c 5319E7
gh label create compliance -d "Needs a human owner — never agent-ready" -c B60205
gh label create security -d "Needs a human owner — never agent-ready" -c B60205
A new GitHub repo ships with bug, documentation, duplicate, enhancement,
good first issue, help wanted, invalid, question and wontfix (verified) — so
four of the category labels already exist. Check before creating.
gh label create exits 1 on an existing name (measured) with
label with name "x" already exists. Tolerate that failure rather than passing
--force, which would overwrite a description someone wrote.
⚠️ Do not pipe the loop into head/tail. The pipeline's status becomes the pager's,
so every failure reads as success — and a label loop is exactly where that bites. Capture
the status separately, then read the labels back and report what actually exists:
gh label list --limit 200 --json name --jq '.[].name'
🚨 Pass each label as its own argument, never a split shell variable. The labels API
auto-creates any name it is handed, and under a shell that does not word-split, a
variable holding two names becomes one junk label created repo-wide. After any label
loop, assert that no label this run created contains a space. Not every label:
MEASURED, GitHub's own defaults good first issue and help wanted contain spaces, so a
blanket check false-alarms on every fresh repo and trains you to ignore it.
Area labels are the user's taxonomy, not ours. Ask what areas this repo has, create
area:<name> for each, and record them in workflow.json → areaLabels with a
one-line meaning, plus dri mapping each area to the GitHub login that owns it.
Triage routes assignees off that map; without it, the integrity pass cannot guarantee
"0 unassigned" and can only report the gap.
Only this repo's areas, described from this repo's point of view. When a sibling in
repos feeds the same board, its areas belong in its file, and every skill reads a
repo's map from that repo's file (shared/config.md § Repo
scope). Do not copy a sibling's areas here, and do not word a meaning as "in this repo
only" to fence off a sibling — the file's location already does that.
On a solo repo, dri is every area mapped to the one person — write it out rather
than leaving the key off. "There is only me" is a fact worth recording; an absent key
reads as "not configured yet" to every later run. Both keys are in
shared/config.md.
5. Board
The board is optional. With no board, triage still does the label half and
next-issue still selects from gh issue list — say so and move on.
gh project list --owner <owner>
gh project field-list <number> --owner <owner> --format json
| Field | Needed for | If missing |
|---|---|---|
Status |
every skill | Ships with a new board: Todo / In Progress / Done |
Priority |
triage §3c, autopilot ordering | gh project field-create (below) |
Track |
triage integrity pass | gh project field-create, options = the user's areas |
gh project field-create <number> --owner <owner> --name Priority \
--data-type SINGLE_SELECT --single-select-options P0,P1,P2,P3
gh project field-create <number> --owner <owner> --name Track \
--data-type SINGLE_SELECT --single-select-options "<their areas, comma-separated>"
To create a board from scratch: gh project create --owner <owner> --title "<name>",
then gh project link <number> --owner <owner> --repo <owner>/<repo>.
🚨 The one thing you must NOT automate: adding a Status option
The workflow uses a Hold Status — "a human parked this by choice" — which a new
board does not have. gh project has no field-edit, and the GraphQL alternative
(updateProjectV2Field with singleSelectOptions) takes the whole option list and
replaces it. Running it would mint new option ids for Todo / In Progress / Done and
unset the Status of every existing card.
Tell the user to add Hold in the board UI (Settings → Status → add option). It is
one click and it is not worth the blast radius.
If Hold does not exist, the skills still work — triage simply has no parked state to
protect, and every card it sees is fair game for next-issue. Say that plainly rather
than implying the board is broken.
⚠️ Never hardcode a field or option id into workflow.json or anywhere else.
Resolve them from field-list in the same run that uses them.
5b. Deploy targets and repo.md — the repo's own operational knowledge
Stack-specific knowledge lives in the repo, not the plugin, in two kinds of file:
.claude/workflow/deploy-targets/<name>.md, one per place code gets deployed (not per
technology), named in workflow.json → deployTargets; and .claude/workflow/repo.md,
exactly one, for the review bot, the reviewer invariants and the traps, which do not
vary by target. What each section is for and which skill reads it:
shared/config.md § Deploy-target docs.
Runs in bootstrap and upgrade. Check reports what it would generate and writes nothing.
- [ ] 1. Name the deploy target(s) from EVIDENCE — the table below. Never from a README.
- [ ] 2. Copy the skeletons: cat "${CLAUDE_PLUGIN_ROOT}/skills/setup/deploy-target-template.md"
and cat "${CLAUDE_PLUGIN_ROOT}/skills/setup/repo-template.md" (one repo.md per repo)
Their headers need the plugin version: jq -r .version "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json"
- [ ] 3. Fill each section from what § 2 already probed, plus the two probes this step
owns (below). Every filled line carries its source path and today's date.
Anything no probe answered stays `UNVERIFIED — fill in`. Nothing is invented.
- [ ] 4. Show both files and ASK. A target name is a proposal; the user may rename it,
split it in two, or drop it.
- [ ] 5. Write them, and write the target names into `workflow.json` → `deployTargets`
with a `$comment_deployTargets` — `[]` when the evidence named nothing (repo.md is
written regardless). Read back: every `##` header from each skeleton is present,
exactly, and every name has a file.
- [ ] 6. List every UNVERIFIED section in § 7 Missing, with who can fill it.
Naming. A lowercase slug for the deploy target plus the infra tool, from what the repo contains — several may apply, and each gets its own file:
| Evidence | Name |
|---|---|
vercel.json or .vercel/project.json |
vercel |
fly.toml |
fly |
netlify.toml |
netlify |
gcloud run deploy or google-github-actions/* in a workflow |
gcp-cloud-run |
aws ecs / aws-actions/* in a workflow, or serverless.yml |
aws |
kubectl / helm in a workflow, or a k8s/ dir |
kubernetes |
*.tf or terraform/ |
terraform, prefixed by the provider the .tf names: gcp-terraform, aws-terraform |
a Dockerfile alone |
not a stack — it says nothing about where it runs |
Read the workflow lines, not just the file names: a Dockerfile plus a workflow that
runs gcloud run deploy is gcp-cloud-run. When the evidence names nothing, write no
deploy-target doc and say so — the generic reference docs still apply, and an invented stack
is worse than none.
Filling from § 2, and the two probes this step owns. deployOnMerge and
deployWorkflow go under Deploy, with the paths / paths-ignore block § 2 copied
from that workflow; the migration directory and apply command § 2 recorded beside
agentReadyForbiddenPaths go under Infra. The forbidden paths themselves are not
copied — workflow.json owns that list and the deploy-target doc points at it, so there is
one list to rot. Two things § 2 does not probe, so this step does:
grep -rn 'secrets\.\|env add\|secrets versions add\|--data-file=-' .github/workflows scripts 2>/dev/null # Secrets and env
grep -rln 'terraform apply\|alembic\|migrate\|prisma\|drizzle-kit' .github/workflows scripts Makefile 2>/dev/null # Infra apply commands
What those find goes in as PROBED lines with the path; the read-back that proves a
value landed stays UNVERIFIED, because no probe can know it.
Review bot. Detect, do not assume — and read both places a bot writes, because
they differ per bot. MEASURED on a public repo: the comment endpoints returned only two
CI bots while pulls/<n>/reviews returned the review bot on every recent PR; a scan of
comments alone would have written "no review bot" for a repo that has one.
# Who actually reviews. Seven REST calls; the check names are already in workflow.json -> requiredChecks (§ 2), do not re-fetch protection.
{ gh api "repos/<owner>/<repo>/issues/comments?sort=created&direction=desc&per_page=100" --jq '.[].user.login'
gh pr list --repo <owner>/<repo> --state merged --limit 5 --json number --jq '.[].number' \
| while read n; do gh api "repos/<owner>/<repo>/pulls/$n/reviews" --jq '.[].user.login'; done
} | grep '\[bot\]$' | sort | uniq -c | grep . \
|| echo "NO bot in the last 100 comments or the last 5 merged PRs' reviews -> Bot: none"
# MEASURED on a repo with no bot: without the final `grep . || echo`, this printed nothing at
# all — the same silent-empty result the rest of this plugin exists to prevent.
# A config file only ANNOTATES. If it names a bot the scan did not see, the file is stale: report it, do not write it as the bot.
ls .coderabbit.yaml .coderabbit.yml 2>/dev/null
Write the login and where it came from. Then, in this same run, derive the two
patterns from the bot's own comments: open the newest review it posted and the newest
acknowledgement (the short one with no findings), quote a phrase from each that the
other does not contain, and write both lines as PROBED with the two comment URLs as the
source. Show the user the two comments beside the two patterns — a pattern is a
proposal. Only when the bot has fewer than two comments to read do the lines stay
UNVERIFIED, and that is a Missing row: the babysit loop cannot tell an ack from a
review without them.
Upgrade on a repo that already has these docs: add any ## header a skeleton has and
the file lacks, with its body UNVERIFIED; generate a target doc for evidence that has
appeared since (a new fly.toml, say) and add its name to deployTargets; say "nothing to
add" when neither applies. Never touch a filled section, and never rename a target
file — a human chose that name.
Upgrade from schema 3, where the key was stacks and the docs carried the per-repo
sections themselves:
- [ ] a. `git mv .claude/workflow/stacks .claude/workflow/deploy-targets`; rename the key
`stacks` → `deployTargets` and `$comment_stacks` → `$comment_deployTargets` in
place, values untouched.
- [ ] b. Create `repo.md` from the skeleton. Move each target doc's `## Review bot`,
`## Reviewer invariants` and `## Traps` sections into it: sections that are
byte-identical across targets become one; sections that differ are both kept,
each line suffixed with the target it came from, and listed in § 7 Missing for
a human to reconcile. Remove the moved sections from the target docs.
- [ ] c. Read back: target docs have exactly the four skeleton headers, `repo.md` has the
three, and the resolution block prints `repo=` naming this checkout, `targets=`,
one `targetdoc=` per file and `repodoc=`.
6. Confirm the agents
Two subagents carry the workflow's quality, and a user who does not know they exist never notices when they silently are not running.
| Agent | Runs at | Used by | Returns |
|---|---|---|---|
issue-planner |
effort: max, read-only |
next-issue, autopilot |
The scoping plan — and REVIEW LENSES, which decides the next step |
diff-reviewer |
effort: max, read-only |
next-issue, autopilot |
Findings through one lens: correctness, contract, scoping, safety, tests, deploy |
Check, do not warn. A same-named agent in ~/.claude/agents/ or the project's
.claude/agents/ wins over the plugin's copy, with no error and a plausible result.
MEASURED: five runs were spent tuning a plugin file nothing read; the story is in
CONTRIBUTING.md § Testing a change to an agent, and the reason it is a check here is that
a prose warning in this skill was ignored.
claude plugin list # "No plugins installed" -> every skill here is inert
ls ~/.claude/agents/ .claude/agents/ 2>/dev/null
For each of issue-planner and diff-reviewer, report explicitly:
| Finding | What it means |
|---|---|
| plugin not installed | Stop. Every skill here is inert. Install it, or run claude --plugin-dir <path>. |
same-named file in ~/.claude/agents/ |
That file runs. The plugin's copy is dead. |
same-named file in .claude/agents/ |
Same, and it also shadows the user-level one. |
| neither | The plugin's agents are live. |
If a shadow exists, name the file that will execute, by absolute path, and change
nothing — it may be deliberate; the user decides. Every skill here spawns the namespaced
gh-issue-flow:<agent>, which always resolves to the plugin's copy; a bare name resolves
to whichever wins.
🚨 Agent types resolve at session start. Measured: an edit or an install changes
nothing for the running session, and the spawn fails with Agent type '<name>' not found. Restart to test an agent change; /reload-plugins refreshes skills only.
Tell the user once: the agents pin effort: max regardless of the session's setting
(implementation does not — a skill cannot pin the main loop), and a subagent cannot fan
out, so gh-issue-flow:diff-reviewer is spawned N times from the parent in one message,
one per lens.
7. Report
Print three blocks, in this order:
Configured — what was probed and written, each with where it came from:
| Setting | Value | Source |
|---|---|---|
| integrationBranch | origin/dev |
gh repo view default branch |
| validate | 3 commands | .github/workflows/ci.yml job test |
| schemaVersion | 2 | current schema, shared/config.md § Layer 2 |
| deployTargets | gcp-cloud-run, gcp-terraform |
.github/workflows/deploy.yml, terraform/ (provider google) |
| repo.md | written | review bot from the scan; invariants and traps UNVERIFIED |
Created — labels and board fields, with anything skipped because it already existed.
Missing — and who can fix it. The honest half. Separate what a human must do from what is merely absent.
🚨 An unset board is a narrowing, not a Missing row. Resolve both layers first
(shared/board.md § Resolution): workflow.json → board wins,
the machine default is second, and only when both are empty is the repo label-only. Say
which layer answered, every run — pointing a repo's triage at the previous project's
board is silent and expensive to undo. When neither is set, say "no board is configured
— if that is deliberate, nothing is wrong; if you expected one, here is how to restore
it" and give the way back from board.md § Resolution: the --config command and the
/reload-plugins after it, without which the next skill still resolves empty. You cannot
tell a deliberate blank from one claude plugin uninstall wiped, so never assert it was
a choice. The install's "N options not yet set" count is the same shape: not a gap you
can close.
| Gap | Effect | Fix |
|---|---|---|
No Hold Status option |
No parked state; every Todo card is pickable | Board UI, one click |
workflow.json behind the schema |
Keys the plugin gained are unknown here; skills run on defaults | /gh-issue-flow:setup upgrade |
repo.md § Review bot patterns UNVERIFIED |
The babysit loop cannot tell an ack from a review | The bot had fewer than two comments to read; re-run § 5b after its next review, or a human writes the two patterns |
repo.md § Reviewer invariants UNVERIFIED |
The safety lens has nothing stack-specific to check |
The person who owns the data model writes one line per invariant |
.claude/ is gitignored |
Config and deploy-target docs are local-only; teammates get nothing | Add !/.claude/workflow.json and !/.claude/workflow/, needs a PR |
| No area labels yet | Triage cannot route assignees | Tell me your areas and I will create them |
| Branch unprotected | Nothing blocks a red merge | Repo settings — a deliberate choice |
commit.gpgsign unset |
Skills stop on a signing failure | git config commit.gpgsign true |
Close with the one next command the user should run — usually
/gh-issue-flow:triage dry run — and a one-line map of what follows it:
triage gates issues → next-issue or autopilot works them → work-summary reports.
⚠️ Read back anything you created before claiming it. gh exits 0 on writes the
server rejected, so a report listing labels or fields you never actually made is the
exact failure this skill exists to prevent. Re-list and count.
🚨 But board writes are eventually consistent — labels are not. A label read-back is
immediate and trustworthy. A Projects v2 read-back is not: measured, an item-list
immediately after adding items reported 0 while every add had in fact succeeded, settling
~30s later. Poll with backoff before concluding a board write failed, and prefer
resolving a returned item id over counting. See
../../reference/verification.md.
What this cannot give you
Say these plainly rather than letting the user discover them:
- Issue hygiene. Triage's guarantees are about routing, not content. Issues with
no acceptance criteria stay un-
agent-readyforever, and that is correct. - A green gate. If the repo's suite is red on the integration branch, "any red is yours" stops being true and every skill's validation step degrades. Fix the base first.
- Review bots. The babysit loop watches whatever checks and review threads exist. It does not install a bot for you, and with none configured the review half of the loop has nothing to watch.