Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions claude-plugins/kampus-pipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ handoff is a durable GitHub artifact, you can stop after any stage and resume la
| `heal-ci` | Classify a red CI run into flake-vs-defect and emit one routed action — rerun a known transient once, or file a defect via `report`. |
| `adr` | Record an architecture decision (Context / Decision / Consequences) into `.decisions/NNNN-slug.md` and the index, following supersede rules. |
| `deslop-comments` | Ruthlessly cut comments that bury the code without earning their place — keeping load-bearing notes, collapsing duplicated "why" to ADR pointers. |
| `doctor` | Preflight a repo against the pipeline prerequisites (gh auth + scope, the 15 required labels, repo resolution, a CI signal, the two [repo-admin merge prerequisites](#repo-admin-prerequisites), npm deps) and print a tiered pass/fail checklist with the exact fix command for each gap. |
| `doctor` | Preflight a repo against the pipeline prerequisites (gh auth + scope, the required label vocabulary, repo resolution, a CI signal, the two [repo-admin merge prerequisites](#repo-admin-prerequisites), npm deps) and print a tiered pass/fail checklist with the exact fix command for each gap. |
| `wayfinder` | The ideation-layer front door, upstream of the pipeline: chart a fuzzy destination into a living `wayfinder:map` issue, then work its open frontier of investigation/decision tickets — recording answers, graduating cleared fog, surfacing founder-decision-forks — until a concrete plan is ready for `triage` / `plan-epic` (epic #2421). |

## Install
Expand Down Expand Up @@ -208,12 +208,14 @@ the superseded file's status edit), never a regenerated index.

**Start with `doctor`.** Before the first run in a freshly-adopted repo, run the `doctor`
skill (`claude-plugins/kampus-pipeline/skills/doctor/doctor.sh`) — it asserts the prerequisites in one pass (gh
auth + the `project` scope, the 15 required `status:*`/`type:*`/`p*` labels, repo
auth + the `project` scope, the required `status:*`/`type:*`/`p*` label vocabulary plus the
standing lanes and the platform discriminator `triage` homes into, repo
resolution, a CI signal, the two [repo-admin merge prerequisites](#repo-admin-prerequisites),
and the `@kampus/*` npm deps) and prints a tiered pass/fail
checklist with the exact `gh label create …` / `gh auth refresh …` fix command for each
gap. It turns "did I wire this up right?" into one checkable command instead of a first run
that fails deep inside a `gh api` call.
gap. It turns "did I wire this up right?" into one checkable command instead of a pipeline
that runs green over a repo whose label scoping matches nothing (a missing label does not
error the first write — `POST …/labels` auto-creates it; the damage lands on the read side).

To re-prove a skill's gate runs outside phoenix (the repeatable procedure
behind #368 / #432), exercise it in a throwaway non-phoenix git repo — **not** phoenix
Expand Down
42 changes: 28 additions & 14 deletions claude-plugins/kampus-pipeline/skills/doctor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ the repo — label creation, auth scopes — which is theirs to authorize).

The pipeline is repo-agnostic (ADR [0062](https://github.com/kamp-us/phoenix/blob/main/.decisions/0062-repo-as-config-plugin.md)): an adopter installs the plugin and it
operates on *their* issues. Several skills hard-depend on environment the host repo
must already provide — labels, `gh` auth, a CI signal — but nothing verifies that
up front, so a first run otherwise fails deep inside a `gh api` call (e.g. labeling
with a `status:needs-triage` that doesn't exist) instead of failing fast with a
clear "this repo isn't set up" message. Worst case it half-applies: an issue gets
created, then the label step errors, leaving an untracked issue outside the queue.
This skill closes that window.
must already provide — labels, `gh` auth, a CI signal, a home to file work into —
but nothing verifies that up front. This skill closes that window.

A missing label does **not** announce itself: `POST /repos/{owner}/{repo}/issues/{n}/labels`
**auto-creates** the label it is handed (measured 2026-07-26 — HTTP 200, the label
materializes repo-wide at GitHub's default grey with no description). So `report` succeeds
and quietly mints an off-taxonomy label, and the failure surfaces later on the **read**
side, where `?labels=status:triaged` and every guard's label scoping match nothing and the
pipeline runs protecting nothing (#4300; the silent no-op ADR [0092](https://github.com/kamp-us/phoenix/blob/main/.decisions/0092-gates-fail-closed-on-zero-scope.md) exists to kill).

## Running it

Expand All @@ -39,17 +42,19 @@ Do not run the fix commands yourself.
|---|---|---|
| **1 — load-bearing** | `gh` authenticated | every call is `gh api`; without auth nothing runs |
| | `gh` token has the `project` scope | the org's Projects-classic integration requires it (the reason the suite is REST-only, never GraphQL) |
| | the 15 required labels exist (`status:*` spine, `type:*` class, `p*` priority) | the intake skills key on them — `report` applies `status:needs-triage`, `write-code` picks `status:triaged`, etc. A missing one fails a run mid-`gh api`. |
| | the required labels exist (`status:*` spine, `type:*` class, `p*` priority, the two standing lanes, the platform discriminator) | the intake skills key on them — `report` applies `status:needs-triage`, `write-code` picks `status:triaged`, `triage` exempts to `wayfinder:backlog` / `axis:pipeline-hardening`. A missing one makes every scan match nothing. |
| | that set still covers the shared vocabulary | the create-commands table carries colours the source can't; this asserts it hasn't fallen behind the source (below) |
| **2 — gating** | target repo resolves | a skill can't target a repo it can't name |
| | at least one CI workflow exists | `ship-it` Step 3 gates on checks-green; with zero checks that gate passes vacuously |
| | repo-level `allow_auto_merge` is enabled | `ship-it`'s `gh pr merge --auto` cannot arm without it — the enqueue fails with `Auto merge is not allowed for this repository (enablePullRequestAutoMerge)` (ADR [0132](https://github.com/kamp-us/phoenix/blob/main/.decisions/0132-merge-queue-for-base-freshness.md) §Addendum §1) |
| | a `merge_queue` rule applies to the **resolved** default branch | `ship-it` deliberately passes no merge-method flag because the queue owns it (ADR 0132 §Consequences); with no queue there is no method and the merge never lands |
| **3 — optional** | `@kampus/pipeline-cli` resolves on npm | `adr` / `review-plan` reach for its `decisions-index` / `epic-ledger` tools via `pnpm dlx` as the published fallback (epic #994); absent → those stages degrade |
| | a `run-evidence` producer is defined | `ship-it` guard 2 runs strict when present, and **degrades to checks-green when absent** (ADR [0086](https://github.com/kamp-us/phoenix/blob/main/.decisions/0086-ship-it-foreign-repo-degradation.md)) — so this is informational, not a failure |
| | the ideation-layer labels exist (`wayfinder:map`) | `wayfinder` keys its map issue on it; it is an ideation front door, not a first-run prerequisite |
| | at least one **open milestone** exists | `triage` Step 6 homes an issue into an arc/campaign milestone and **never creates one** (ADR [0072](https://github.com/kamp-us/phoenix/blob/main/.decisions/0072-milestones-encode-strategic-sequencing.md) §3). With none, triage can still exempt to a standing lane but cannot home into an arc — a WARN, never a fail, because a freshly-adopted repo legitimately has none yet |

The load-bearing pair is **auth + labels** (Tier 1): get those wrong and the very
first `report` → `triage` round trips into a raw GitHub API error. The Tier-2 checks
keep `ship-it` honest; Tier-3 only ever downgrades a single stage.
The load-bearing pair is **auth + labels** (Tier 1). The Tier-2 checks keep `ship-it`
honest; Tier-3 only ever downgrades a single stage.

The last two Tier-2 checks are the **repo-admin governance pair**, and they are the one
prerequisite class this checklist can only *report*: `allow_auto_merge` and the
Expand All @@ -58,6 +63,12 @@ script can create** — not doctor, not any stand-up script. They also fail *las
enqueue, after an adopter has already built, reviewed, approved, and armed a PR. Reading
them up front is what turns that finish-line halt into a first-run checklist line.

Every check reports **three** states, never two: present, absent, and **UNDETERMINED** —
the read itself did not succeed. A failed label read is not "no labels", and no open
milestone found is not "the milestone read worked". Collapsing those is what lets a
checker pass while the thing it checks is absent, so doctor names the unknown as unknown
and still fails closed on it.

## Conventions

- **Read-only.** The helper never mutates the repo — it reads state and prints fix
Expand All @@ -70,7 +81,10 @@ them up front is what turns that finish-line halt into a first-run checklist lin
a different fact. `gh api` prints its error body to stdout **without** applying `--jq`, so
every read keeps gh's exit status and admits only the literal values it expects (§ZS /
ADR [0092](https://github.com/kamp-us/phoenix/blob/main/.decisions/0092-gates-fail-closed-on-zero-scope.md); the defect class of #4223).
- The required-label set in `doctor.sh` is the canonical one (name + color +
description). If the pipeline's label dimensions change (see
[`../gh-issue-intake-formats.md`](../gh-issue-intake-formats.md) §Pipeline labels), update the `REQUIRED_LABELS`
table there so a fresh adopter gets the current set.
- **`doctor.sh`'s `LABELS` table is a presentation mirror, not the source.** The required
set is single-sourced in `packages/pipeline-cli/src/tools/vocabulary-preflight/vocabulary.ts`,
which assembles it from the constants the guards themselves scope on; the table only adds the
colour + description a `gh label create` needs. Doctor reads the source through
`pipeline-cli vocabulary-preflight labels` and **reds if the table has fallen behind it**, so
the two cannot silently diverge — the drift that shipped as #4300. Change the vocabulary at
the source, then add the matching row here.
Loading
Loading