| name | setup |
| description | Set up a project for the orbit workflow — creates .orbit/ directory with artefact subdirs on greenfield, or interactively migrates bare-layout or wrapped-undotted repos to the .orbit/ folder on brownfield |
| argument-hint | [project-root] |
| allowed-tools | Bash Read Edit Write AskUserQuestion |
/orb:setup
Set up a project for the orbit specification-driven workflow.
Workflow artefacts live under a single top-level orbit/ folder — .orbit/cards/, .orbit/specs/, .orbit/choices/, and (when created ad-hoc) .orbit/discovery/. This keeps workflow state separated from source code and standard repo metadata.
Usage
/orb:setup
Instructions
0. Preconditions
Before classifying the repo state, run two refuse-or-proceed checks. Both exit non-zero without mutating the working tree (no mkdir, no git mv, no file copy).
0a. orbit-state binary present. Run command -v orbit-state. If it returns nothing:
orbit: orbit-state binary not found on $PATH.
orbit IS the orbit-state-native workflow — there is no useful degradation without it.
Install: brew tap hughcameron/orbit && brew install orbit-state
Exit non-zero. No fallback. The setup pathway depends on orbit-state primitives (classifier helper, conformance audit, topology setup) — without the binary every subsequent step would fail downstream, so refuse up front.
0b. orbit-state version sanity. This skill's new pathway depends on primitives that landed in orbit-state 0.4.33 (substrate-layout classifier, plugin_repo config field, decisions-md-unmigrated conformance finding). Run orbit --version and parse the leading semver. If the installed version is below the minimum, refuse:
orbit: orbit-state X.Y.Z is below the minimum required for /orb:setup (0.4.33).
This setup pathway depends on the substrate-layout classifier and plugin_repo gating
shipped in 0.4.33. Upgrade: brew upgrade orbit-state
Exit non-zero. No partial-run; the missing primitives would break §1's classifier call and §6d's topology gating.
Both checks are read-only — they touch no filesystem state. A repo with a clean working tree pre-invocation has the same git status --porcelain output post-refusal.
1. Detect the Repo State
Before creating or moving anything, classify the repo into one of six mutually exclusive states by inspecting the working tree at the project root:
| State | Condition | Action |
|---|
| greenfield | none of .orbit/, orbit/, or bare cards//specs//decisions//discovery/ present | Create .orbit/ fresh → §2 |
| idempotent | .orbit/ present AND neither orbit/ nor any bare artefact dir present | No-op → §5 |
| brownfield-bare | bare cards//specs//decisions//discovery/ present AND neither .orbit/ nor orbit/ present | Prompt → migrate or abort → §3 |
| wrapped-undotted | orbit/ present (wrapped substrate from a pre-.orbit/ plugin version) AND .orbit/ absent AND no bare dirs | Prompt → single-rename git mv orbit .orbit → §3 |
| mixed-bare | .orbit/ present AND any bare artefact dir present at root | Refuse → §4 |
| mixed-undotted | .orbit/ present AND orbit/ present | Refuse → §4 |
These six states cover all reachable combinations of the three independent axes (.orbit/ present?, orbit/ present?, any bare artefact dir present?). Conditions are non-overlapping; the union is exhaustive. There is no other state.
The classifier helper lives in orbit-state as a shared Rust function (see orbit-state/crates/core/src/verbs.rs::classify_substrate_layout); the setup skill and the conformance audit's undotted_substrate_finding (per spec 2026-05-24-workflow-conformance) call the same predicate.
2. Greenfield: Create Fresh .orbit/
Create the following directories (skip any that already exist within .orbit/):
.orbit/
cards/ # Feature cards — who needs what and why
specs/ # Specifications, interviews, reviews, progress
choices/ # MADR decision records (YAML — was `decisions/` pre-canonical)
memos/ # Rough ideas awaiting distillation
Do not create .orbit/discovery/ at setup time. It is created ad-hoc the first time /orb:discovery runs. Setup detects it during brownfield migration but never creates it eagerly.
Then proceed to §6 (canonical files + topology) and §7 (first card tutorial). Topology scaffolding follows the plugin_repo gate (§6d).
3. Brownfield: Interactive All-or-Nothing Migration
The repo has one or more bare artefact directories at the root from a pre-orbit/ version of orb. Migrate them under orbit/ in a single atomic transaction.
3a. Enumerate detected bare dirs. Collect the subset of {cards, specs, decisions, discovery} that exist as directories at the repo root.
3b. Scan for untracked residue. Run git status --porcelain -- <detected-bare-dirs> and collect any untracked paths inside them. Untracked files will be left behind by git mv — they need to be reported to the author so they know about the residue.
3c. Present a single all-or-nothing prompt. Example:
orbit: detected legacy layout. Ready to migrate:
cards/ → .orbit/cards/
specs/ → .orbit/specs/
decisions/ → .orbit/decisions/ (MADR files; manual MD→YAML conversion needed)
discovery/ → .orbit/discovery/
Untracked files inside these dirs (will remain at the old path after git mv):
cards/scratch.md
Migrate now? (y/N)
The decisions/ → .orbit/decisions/ line is a folder rename only. MADR markdown content is preserved verbatim under the new path; .orbit/decisions/<slug>.md does NOT become .orbit/choices/<slug>.yaml automatically. Once migrated, orbit audit conformance surfaces a decisions_md_unmigrated finding per file, pointing the operator at the documented manual MD→YAML conversion task. Per rally lockdown 2026-05-24-brownfield-migration-hardening (no auto-conversion).
If no untracked files are present, omit that section. One prompt covers all detected dirs — no per-directory confirmation. A single "y" migrates everything in one transaction; anything else aborts with no filesystem changes.
Dirty-tree handling is deliberate: setup does NOT refuse on a dirty working tree. git mv preserves tracked-but-modified files' modifications, so there is no correctness risk. The author may have reasons for mid-work migrations; respect that. If git status --porcelain reports uncommitted changes outside the migration scope, proceed regardless.
3d. On confirm — run git mv in one transaction.
mkdir -p .orbit
git mv cards .orbit/cards
git mv specs .orbit/specs
git mv decisions .orbit/decisions
git mv discovery .orbit/discovery
Run only the git mv lines for directories that were actually detected in 3a. If any git mv fails (e.g. a target already exists from a half-completed prior migration), abort and surface the error. This state should have been caught as "mixed" in §1, but defence-in-depth applies.
3e. On decline — abort cleanly. Do nothing. Assert no filesystem changes occurred (git status --porcelain compares equal to pre-invocation). Tell the author how to re-run setup when ready.
3f. After migration — report residue. If any untracked files were detected in 3b, surface them explicitly in the completion message:
orbit: migration complete.
Moved: 4 directories under .orbit/
Untracked residue: cards/scratch.md (file remains at old path)
Consider: git add .orbit/cards/ or move manually
When no residue exists, the completion message is quiet about it.
3f.1 If decisions/ was migrated — print the MADR conversion warning. Emit a single paragraph naming the unmigrated content:
orbit: .orbit/decisions/ contains MADR markdown (renamed verbatim from decisions/).
The canonical surface for architectural choices is .orbit/choices/<slug>.yaml.
Each .md file needs a manual MD→YAML pass — see `orbit choice` and the existing
.orbit/choices/ examples for the target shape. `orbit audit conformance` will
surface a decisions_md_unmigrated finding for each unconverted file.
This warning fires once per migration, regardless of how many .md files are present. No auto-conversion is performed — the format gap requires human judgment on each file. Per rally lockdown 2026-05-24-brownfield-migration-hardening.
3g. Reconcile legacy field shapes (after the layout migration). With the substrate now under .orbit/, run orbit audit drift to detect spec/card/choice/memory files whose top-level or inner field shape pre-dates the canonical schema. If the drift report is empty, skip the rest of §3g.
When drift is non-empty, offer the reconcile path:
orbit: audit drift surfaced N unknown field(s) across M file(s).
Preview the reconcile pass (renames / drops / quarantines) before any rewrite? (y/N)
-
y: run orbit canonicalise --reconcile --dry-run. The output lists every disposition. Then prompt:
Apply these dispositions? (y/N)
y: run orbit canonicalise --reconcile. Each quarantined field lands in a sibling <name>.legacy.yaml sidecar so semantic content is preserved for a human to re-anchor.
- anything else: abort the reconcile step. The substrate still parses against the canonical schema only after the author either applies reconcile, hand-edits the legacy fields, or removes them. Surface this in the completion message.
-
anything else: skip reconcile. The author can invoke orbit canonicalise --reconcile --dry-run later when ready.
Reconcile is only offered from this brownfield path. It is never invoked by the greenfield path (§2), orbit verify, pre-commit hooks, or any other routine surface.
Then proceed to §6 (CLAUDE.md snippet) and §7 (first card tutorial).
3.W. Wrapped-Undotted: Single git mv orbit .orbit
When the classifier (§1) returns wrapped-undotted — orbit/ exists, .orbit/ is absent, no bare artefact dirs — the migration is a single rename. The pre-.orbit/ plugin versions placed substrate under orbit/; the canonical layout is .orbit/. git mv orbit .orbit preserves history (git log --follow traces any moved file to its orbit/ ancestor) and moves the entire wrapped tree (including orbit/decisions/ → .orbit/decisions/) in one shot.
3W-a. Prompt before mutating. The classifier surfaces both the rename and any orbit/decisions/ content the operator should know about:
orbit: detected wrapped-undotted layout (orbit/ present, .orbit/ absent).
Migration is a single rename:
orbit/ → .orbit/ (full history preserved via `git mv`)
orbit/decisions/ → .orbit/decisions/ (MADR files; manual MD→YAML conversion needed)
Migrate now? (y/N)
If orbit/decisions/ is absent, the second line is omitted.
3W-b. On confirm — single transaction.
git mv orbit .orbit
git mv on the parent folder moves every tracked file, preserving history at the per-file level. Untracked files inside orbit/ are left behind (same residue mechanics as §3); report any to the operator post-migration.
3W-c. On decline — abort cleanly. No filesystem changes. git status --porcelain compares equal to pre-invocation.
3W-d. After migration — print MADR warning if applicable. If .orbit/decisions/ exists post-rename, emit the same one-paragraph warning as §3f.1.
Then proceed to §6 and §7. Reconcile (§3g) applies to wrapped-undotted as well — the substrate is now under .orbit/ and orbit audit drift can run.
4. Mixed State: Refuse With Clear Error
If both .orbit/ and any bare artefact dir exist (mixed-bare), OR if both .orbit/ and orbit/ exist (mixed-undotted), the repo is in a transitional state setup cannot safely resolve automatically. Do not attempt silent reconciliation — the all-or-nothing migration model depends on clean pre- and post-states. The mixed-undotted case is particularly load-bearing: git mv orbit .orbit would otherwise blow up with an opaque "destination exists" error part-way through, leaving the working tree in a half-migrated state.
Snapshot the working tree first so the post-refusal no-mutation invariant is verifiable:
git status --porcelain > /tmp/orbit-setup-pre.txt
Refuse with a message naming each collision by path:
orbit: cannot migrate — inconsistent layout detected.
mixed-bare:
.orbit/cards/ exists AND bare cards/ also exists at root
.orbit/specs/ exists AND bare specs/ also exists at root
mixed-undotted:
.orbit/ exists AND orbit/ also exists at root (substrate wrapped twice)
Resolve manually before re-running /orb:setup. Typical causes: an aborted prior migration,
a manually-created orbit/ directory, or a partial downstream pull.
The message names only the collisions actually present (mixed-bare lists colliding bare dirs; mixed-undotted lists the .orbit/ + orbit/ pair). Exit with a non-zero status so the author sees it as a refusal, not a completion.
No-mutation invariant. Re-run git status --porcelain post-refusal. It must compare byte-equal to the pre-snapshot. If a difference appears, the refusal path has a bug — surface the diff and halt. The fixtures in orbit-state/crates/cli/tests/parity.rs assert this equality on every refusal-shape fixture.
5. Idempotent State: No-Op on Filesystem
The .orbit/ layout is already in place. The filesystem needs no changes:
- Do not recreate
.orbit/ or any subdir
- Do not run the first-card tutorial unless the author explicitly asks
Still run §6's CLAUDE.md check — an author on a newer plugin version may have an older snippet that lacks the vocabulary glossary. §6 detects this and offers a targeted migration. If no migration is needed (or the author declines), tell the author setup is already complete and offer /orb:card.
Preserve plugin_repo on idempotent re-run. If .orbit/config.yaml already contains a plugin_repo: true line, setup does NOT modify it (no read-rewrite-write cycle, no flag flip). The flag is load-bearing for §6d's topology seed choice: flipping it to false on idempotent re-run would silently drop the substrate-typed seeds on the next orbit topology setup invocation. Grep-verifiable invariant: grep -F 'plugin_repo: true' .orbit/config.yaml returns the same line pre- and post-setup on an idempotent run.
6. Canonical files (METHOD.md + STYLE.md): Copy and Import
The canonical orbit substrate files live in the plugin source: plugins/orb/skills/setup/METHOD.md (workflow overview — vocabulary, pipeline, substrate rules, four pillars) and plugins/orb/skills/setup/STYLE.md (agent prose discipline — the contract loaded into every author-facing response). Card 0026 (.orbit/cards/0026-agent-prose-discipline.yaml) owns the prose discipline; STYLE.md is its canonical surface. Setup copies both into the project and ensures CLAUDE.md @-imports them. Never inline either file's content into CLAUDE.md.
The operations in 6a/6b/6c below are also implemented as a single native verb for one-step execution and testing:
orbit setup files --project-root <project>
The verb performs the same steps in the same order with the same atomic semantics. Use it directly for non-interactive runs (the verb supports --answer-legacy y|n, --answer-method-drift y|n, and --answer-style-drift y|n for scripted contexts). Interactive prompts at the CLI layer only — MCP callers pass typed legacy_action / method_drift_action / style_drift_action enums on the args struct.
Run the steps below in order. Legacy detection runs before any file is written so a refused migration leaves no orphan canonical files.
6a. Legacy-CLAUDE.md detection (atomic). Scan CLAUDE.md for any of these legacy markers anywhere in the file:
## Workflow (orbit)
## Orbit vocabulary
## Current Sprint
If any are present, prompt:
orbit: CLAUDE.md contains legacy workflow blocks (## Workflow (orbit) / ## Orbit vocabulary / ## Current Sprint).
Migration removes them and adds @.orbit/METHOD.md + @.orbit/STYLE.md as the single sources of truth.
Migrate now? (y/N)
If no legacy markers are present, fall through to 6b.
6b. Copy canonical files. Copy plugins/orb/skills/setup/METHOD.md to .orbit/METHOD.md and plugins/orb/skills/setup/STYLE.md to .orbit/STYLE.md in the project. The two files follow the same seed semantics — handled independently.
For each canonical file, if the target already exists, compare it to the canonical via byte-for-byte equality of the entire file (no whitespace tolerance, no hash-only comparison). If they match, no-op. If they differ, prompt:
orbit: <target path> differs from the canonical (the plugin has updated, or the file has been edited locally).
Overwrite with canonical? (y/N)
y: overwrite.
- anything else: keep the local copy (the prompt is informational; setup does not refuse on decline).
The prompt fires per-file — operators can accept the canonical METHOD.md while keeping a customised STYLE.md, or vice versa.
6c. Ensure CLAUDE.md @-imports. If CLAUDE.md does not contain the line @.orbit/METHOD.md anywhere, append it on its own line at end-of-file with a single blank line above. Same for @.orbit/STYLE.md: append @.orbit/STYLE.md on its own line if absent. No marker heading on either.
Without these imports, the seeded .orbit/METHOD.md and .orbit/STYLE.md sit on disk but do not load into Claude Code sessions — the agent reads them only through the @-import. This is the load-bearing wire.
If 6a fired the migration prompt and the author accepted, this step also removes the legacy blocks (## Workflow (orbit), ## Orbit vocabulary, ## Current Sprint and their bodies) in the same transaction as adding both @-imports. Either all edits land or none do.
If @.orbit/METHOD.md and @.orbit/STYLE.md are already present, no-op (idempotent per-file).
Personalising prose discipline. Operators who want to tailor agent voice for their project should not edit .orbit/STYLE.md directly (which causes conformance drift). Instead, add a ## Persona section to the project's CLAUDE.md describing the agent's stance and disposition (project goals, default lean, when to halt, research posture). STYLE.md stays canonical across projects; persona is the per-project surface. See the orbit repo's own CLAUDE.md for the pattern.
6d. Topology capability scaffolding. Scaffold the .orbit/topology/ substrate folder and write the self-describing seed entries. The byte-compare-and-prompt voice of §6b applies — the prompt fires when the substrate is absent or empty; idempotent on a populated repo. Per choice 0025 (topology-substrate-folder).
Plugin-repo gate. The seed-content branch is gated on the plugin_repo field of .orbit/config.yaml:
plugin_repo: true (the orbit-plugin source repo itself) — orbit topology setup seeds the 5 substrate-typed entries (cards, choices, memories, specs-substrate, topology) that describe orbit's own substrate types. These pointers (e.g. orbit-state/crates/core/src/schema.rs) are load-bearing for this repo and would be a category error in any downstream project that doesn't contain that tree.
- Unset /
plugin_repo: false (every other repo) — orbit topology setup creates an empty .orbit/topology/ directory plus a one-line .orbit/topology/README.md pointing the operator at /orb:topology to author the first entry. No substrate-typed seeds are written.
This gate is the fix for the "topology seeds point at orbit-plugin paths" failure mode observed in the brownfield-migration-hardening rally: a previous setup pass seeded substrate-typed entries into a downstream project, generating 21 stale-pointer drift entries on the immediate next audit pass. Per spec 2026-05-24-setup-is-orbit-state-aware ac-12.
Canonical_code path validation. When seeding fires (plugin_repo: true branch), every seed entry's canonical_code paths are verified to exist in the working tree before the seed is written. If any path is missing, orbit topology setup errors out cleanly naming the offending entry+path — better than silently writing a seed that orbit audit topology will then flag as drift on the next audit. Per spec 2026-05-24-setup-is-orbit-state-aware ac-13.
The operation is implemented as a Rust verb (per choice 0020 — shell-scripts-to-rust-verbs):
orbit topology setup
Steps the verb performs:
-
Brownfield config cleanup. If .orbit/config.yaml exists and carries a legacy docs.topology key, transactionally rewrite the file with that key removed (and the now-empty docs: block elided if it has no other keys). The DocsConfig::topology field is retained as parse-only for compatibility — this step removes it from on-disk config.
-
Idempotent directory creation. Create .orbit/topology/ if absent (no-op when present).
-
Self-describing seed. Write one TopologyEntry per .orbit/ entity type (cards, choices, specs, memories, topology itself) at .orbit/topology/<entity>.yaml. Each seed entry points at the orbit-state schema struct (canonical_code), the relevant choice (decision_record), the writing SKILL.md (operational_doc), and the schema tests (test_surface). Existing entries are skipped (operator edits preserved — no overwrite).
-
Wire-or-decline prompt. When .orbit/topology/ is absent or empty (substrate unconfigured), the verb prompts:
orbit: topology capability not wired (.orbit/topology/ absent or empty).
orbit: wiring creates .orbit/topology/ and writes a self-describing seed (one entry per .orbit/ entity type).
Wire topology now? (y/N)
y: runs the setup steps above.
- anything else: leave the topology capability unconfigured. The rest of orbit still works.
-
Test affordance. --answer-wire y|n scripts the prompt for non-interactive runs.
Topology scaffolding is independent of §6a-§6c (it neither reads nor writes CLAUDE.md / METHOD.md). It runs after them in the §6 sequence but can be invoked standalone via orbit topology setup.
6e. Workflow conformance audit — orbit audit conformance. After setup completes, agents can ask whether the operator's repo is operating against the current plugin contract by invoking the workflow conformance verb:
orbit audit conformance --json
The verb returns a structured findings envelope ({severity, subsystem, subject, state, evidence, remediation} per finding) covering three v1 finding families plus aggregated audit.drift + audit.topology results:
- plugin-canonical-file drift —
.orbit/METHOD.md and .orbit/STYLE.md byte-compared against the canonical bytes embedded in the orbit-state binary; firing remediation = orbit setup.
- card-state — cards at
maturity: planned with empty specs (ready for tabletop); firing remediation = /orb:tabletop <id>.
- memo staleness — memos undistilled > 7 days; firing remediation =
/orb:distill <memo-path>.
- plugin-version pin — per-repo pin in
.orbit/config.yaml (plugin_version: "0.4.20"); pin_behind / pin_ahead each fire a single dominant finding and suppress per-file findings.
Parked-card carve-out. Within the card-state family, cards carrying a park: block on the Card schema (with reason: and until: subfields) are excluded from the ready_for_tabletop finding silently — no envelope trace, no remediation surface. This lets the author declare a card deliberately on hold (awaiting evidence, cluster synthesis, a date) without the audit nagging on every pass. The carve-out is scoped to the card-state family only — aggregated drift, topology drift, memo staleness, and plugin-canonical-file findings continue to fire normally for the same card. See /orb:card's Parking a card section for the authoring side. Per spec 2026-05-20-conformance-park-signal.
Each finding carries an explicit remediation.verb the agent runs without translation. The verb is agent-first: zero-finding case is silent; the operator only sees output on agent escalation. Read-only — invoking it produces no on-disk changes. Per spec 2026-05-19-workflow-conformance.
Pin remediation. When pin_behind or pin_ahead fires, the finding's remediation.verb is orbit setup --bump-pin. As of orbit 0.4.20 the pin is operator-managed — the operator hand-edits .orbit/config.yaml to set plugin_version: "<vN.N.N>". The --bump-pin invocation is the documented handle for a follow-on spec that automates the rewrite. Agents should surface the finding to the operator (per the agent-first / escalation contract) rather than attempt to bump the pin programmatically until the flag ships.
7. First Card Tutorial
Walk the author through writing their first feature card using /orb:card. Explain:
- A card captures who needs something, why it matters, and what they'd expect to see
- Scenarios are written in user language, not engineering language
- Cards are the intake layer — they survive context loss and ground future interviews
Then invoke /orb:card to interactively write the first card.
Setup does NOT auto-run a drive. Validation is a separate operator concern — once setup completes, the author chooses when to invoke /orb:drive against a real card. A future /orb:smoke-test skill may earn its keep if a hands-off smoke pass becomes a recurring need, but setup itself stays focused on layout + canonical-file shaping. Per spec 2026-05-24-setup-is-orbit-state-aware ac-09.
Idempotency
This skill is idempotent. Running it again on an already-initialised project:
- Does NOT recreate existing directories
- Does NOT duplicate the CLAUDE.md snippet
- On brownfield-then-idempotent (migrate, then re-run immediately): the second run enters §5 (idempotent no-op), not §3 (brownfield) — because there are no bare dirs left to detect
Why orbit/?
One folder name, one convention, not configurable. See spec .orbit/specs/2026-04-20-orbit-artefact-folder/spec.yaml (constraint #4) and card .orbit/cards/0008-consolidated-orbit-artefact-folder.yaml for the decision record.
Next step: Write feature cards with /orb:card, then refine them with /orb:tabletop.