Skip to content
Open
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
11 changes: 11 additions & 0 deletions .claude/commands/kickoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@

Run the full sandwich workflow while preserving the original repo shell.

## Multi-repo support

ubtstack supports multiple target repos. If your `.env` has `REPO_<ALIAS>_URL` vars, the planning pipeline is multi-repo aware:

- Each ticket's `repo` field must match a registered alias
- Per-repo config (CI commands, approval, docs paths) is resolved via `REPO_<ALIAS>_<VAR>`
- Symphony hooks resolve the correct repo URL and directory name per ticket
- `/ship` uses per-repo CI commands and approval config

If only `TARGET_REPO_URL` is set (no `REPO_*_URL` vars), everything works as before in single-repo mode.

## Phase 0 — Prerequisites

Before anything else, check for the two required anchor documents and the docs directory:
Expand Down
2 changes: 1 addition & 1 deletion .claude/commands/plan-to-linear.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Create the directory first if it doesn't exist: `mkdir -p .claude/state`
- One feature = one manifest
- One ticket = one TV (one behavior, one end-to-end path)
- Tickets must include:
- id, title, repo, type (feature/bugfix/refactor/infrastructure/migration), priority
- id, title, repo (must match a registered alias in `.env` — see `REPO_<ALIAS>_URL`), type (feature/bugfix/refactor/infrastructure/migration), priority
- depends_on (other ticket IDs in this manifest)
- labels (format: `team:eng`, `service:PROC-xxx`, `domain:xxx`)
- thin_vertical (TV-xxx reference from product brief)
Expand Down
14 changes: 10 additions & 4 deletions .claude/skills/plan-ceo-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,16 @@ This command may start from any combination of:

## Auto-discovery

Before starting, check `.env` for codebase context paths and scan them for relevant docs:
- `PRD_DOCS_PATH` — PRDs, feature briefs, product specs
- `ARCHITECTURE_DOCS_PATH` — architecture notes, system design memos
- `EXISTING_SPECS_PATH` — existing specs that may constrain this work
Before starting, check `.env` for codebase context paths and scan them for relevant docs. If working in a multi-repo setup, resolve per-repo paths first:

1. Determine the repo alias (from the current working directory name, `--repo` flag, or `DEFAULT_REPO`)
2. Read `REPO_<ALIAS>_PRD_DOCS_PATH`, `REPO_<ALIAS>_ARCHITECTURE_DOCS_PATH`, `REPO_<ALIAS>_EXISTING_SPECS_PATH`
3. Fall back to global `PRD_DOCS_PATH`, `ARCHITECTURE_DOCS_PATH`, `EXISTING_SPECS_PATH`

Scan the resolved paths:
- PRD docs path — PRDs, feature briefs, product specs
- Architecture docs path — architecture notes, system design memos
- Existing specs path — existing specs that may constrain this work

List discovered docs and ask the user which are relevant to this review. This replaces the need to manually specify every input.

Expand Down
14 changes: 10 additions & 4 deletions .claude/skills/plan-eng-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ Review this plan thoroughly before making any code changes. For every issue or r

## Auto-discovery

Before starting, check `.env` for codebase context paths and scan them:
- `ARCHITECTURE_DOCS_PATH` — architecture notes, system design docs relevant to this change
- `EXISTING_SPECS_PATH` — existing specs that may constrain implementation
- `TARGET_REPO_URL` — the repo being modified (clone or inspect for existing patterns)
Before starting, check `.env` for codebase context paths and scan them. If working in a multi-repo setup, resolve per-repo paths first:

1. Determine the repo alias (from the current working directory name, `--repo` flag, or `DEFAULT_REPO`)
2. Read `REPO_<ALIAS>_ARCHITECTURE_DOCS_PATH`, `REPO_<ALIAS>_EXISTING_SPECS_PATH`, `REPO_<ALIAS>_URL`
3. Fall back to global `ARCHITECTURE_DOCS_PATH`, `EXISTING_SPECS_PATH`, `TARGET_REPO_URL`

Scan the resolved paths:
- Architecture docs path — architecture notes, system design docs relevant to this change
- Existing specs path — existing specs that may constrain implementation
- Repo URL — the repo being modified (clone or inspect for existing patterns)

Cross-reference discovered docs with the CEO review output. Flag any architectural constraints or existing specs that the plan must comply with.

Expand Down
16 changes: 13 additions & 3 deletions .claude/skills/ship/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,18 @@ If there are merge conflicts that can't be auto-resolved, **STOP** and show them

## Step 3: Run CI tests

Run the CI-stage test commands (fast, deterministic):
Run the CI-stage test commands (fast, deterministic).

**CI command resolution (in priority order):**
1. Read `REPO_<ALIAS>_CI_COMMANDS` from `.env` (where `<ALIAS>` is the repo alias for this target repo — check the directory name or `DEFAULT_REPO`)
2. Fall back to the test commands in the ticket's traceability matrix (from the planning manifest)
3. Fall back to `cargo build && cargo test && cargo clippy -- -D warnings`

To resolve the repo alias: derive it from the current working directory name, or read `DEFAULT_REPO` from `.env`.

```bash
# Example: if REPO_BACKEND_CI_COMMANDS="cargo build && cargo test && cargo clippy -- -D warnings"
# run that. Otherwise fall back to defaults:
cargo build 2>&1
cargo test 2>&1
cargo clippy -- -D warnings 2>&1
Expand Down Expand Up @@ -152,7 +161,7 @@ ATTESTATION=".claude/state/attestation-${TICKET_ID}-CI.json"

If CI attestation exists:
- Verify GPG signature is valid
- Verify operator matches `APPROVAL_REQUIRED_FROM`
- Verify operator matches `APPROVAL_REQUIRED_FROM` (resolve per-repo: check `REPO_<ALIAS>_APPROVAL_REQUIRED_FROM` first, fall back to global `APPROVAL_REQUIRED_FROM`)
- Verify `git_ref` matches the current branch HEAD
- Report status in review output

Expand Down Expand Up @@ -250,6 +259,7 @@ gh pr create --title "<type>: <summary>" --body "$(cat <<'EOF'
## Planning Manifest
- Feature: <feature_id>
- Ticket: <ticket_id>
- Repo: <repo alias from ticket, if multi-repo>

## Structural Review Summary
<findings from Step 4, or "No issues found.">
Expand Down Expand Up @@ -313,7 +323,7 @@ Ticket <ID> is ready for Human Review.
Recommended Linear transition: In Progress -> Human Review
```

**Agents do not merge. Only the approver designated by `APPROVAL_REQUIRED_FROM` in `.env` can approve and merge.**
**Agents do not merge. Only the approver designated by `APPROVAL_REQUIRED_FROM` (or `REPO_<ALIAS>_APPROVAL_REQUIRED_FROM`) in `.env` can approve and merge.**

---

Expand Down
11 changes: 11 additions & 0 deletions .claude/templates/planning-manifest-v2.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
"title": "Example feature",
"summary": "One-paragraph summary of the feature, grounded in the product brief context.",

"target_repos": {
"_comment": "Repos involved in this feature. Each alias must match a registered repo in .env (REPO_<ALIAS>_URL). For single-repo setups, this may contain one entry matching TARGET_REPO_URL.",
"repos": [
{
"alias": "repo-name",
"url": "https://github.com/your-org/your-repo.git",
"ci_commands": "cargo build && cargo test && cargo clippy -- -D warnings"
}
]
},

"source": {
"origin_commands": ["/plan-ceo-review", "/plan-eng-review"],
"requested_by": "user",
Expand Down
52 changes: 45 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,56 @@ LINEAR_ENG_TEAM_KEY=ENG
LINEAR_BIZ_TEAM_KEY=BIZ

# -----------------------------------------------------------------------------
# Target repository
# Target repository (multi-repo)
# -----------------------------------------------------------------------------
# The repo that agents will clone and work on
# Used by Symphony workspace hooks to set up isolated worktrees
# ubtstack supports multiple target repos. Each repo gets a short alias and
# uses the pattern REPO_<ALIAS>_<VAR> for per-repo configuration.
#
# Example multi-repo setup:
# REPO_BACKEND_URL=https://github.com/org/backend.git
# REPO_BACKEND_APPROVAL_REQUIRED_FROM=kyle
# REPO_BACKEND_CI_COMMANDS=cargo build && cargo test && cargo clippy -- -D warnings
# REPO_BACKEND_ARCHITECTURE_DOCS_PATH=docs/technical
# REPO_BACKEND_PRD_DOCS_PATH=docs/prd
# REPO_BACKEND_EXISTING_SPECS_PATH=docs/specs
# REPO_BACKEND_STAGING_URL=https://staging.backend.example.com
#
# REPO_FRONTEND_URL=https://github.com/org/frontend.git
# REPO_FRONTEND_APPROVAL_REQUIRED_FROM=kyle
# REPO_FRONTEND_CI_COMMANDS=npm run build && npm test && npm run lint
#
# DEFAULT_REPO=backend
#
# Per-repo vars fall back to global vars if not set.
# Run: npx tsx scripts/repo-registry.ts to inspect the resolved registry.

# Which repo is the default when no --repo flag is provided
# DEFAULT_REPO=backend

# --- Per-repo config (uncomment and duplicate for each repo) ---
# REPO_BACKEND_URL=https://github.com/your-org/your-backend.git
# REPO_BACKEND_APPROVAL_REQUIRED_FROM=your-github-handle
# REPO_BACKEND_CI_COMMANDS=cargo build && cargo test && cargo clippy -- -D warnings
# REPO_BACKEND_ARCHITECTURE_DOCS_PATH=docs/technical,docs/architecture
# REPO_BACKEND_PRD_DOCS_PATH=docs/functional,docs/prd
# REPO_BACKEND_EXISTING_SPECS_PATH=docs/specs
# REPO_BACKEND_STAGING_URL=

# -----------------------------------------------------------------------------
# Target repository (legacy single-repo — backward compatible)
# -----------------------------------------------------------------------------
# If no REPO_*_URL vars are set, TARGET_REPO_URL is auto-registered as
# a single-entry registry. The alias is derived from the URL or DEFAULT_REPO.
TARGET_REPO_URL=https://github.com/your-org/your-repo.git

# Local path to the ubtstack tooling repo (sibling directory)
# Skills and scripts reference this to find validators, templates, etc.
UBTSTACK_PATH=../ubtstack

# -----------------------------------------------------------------------------
# Codebase context paths (relative to target repo root)
# Codebase context paths (global fallbacks, relative to target repo root)
# -----------------------------------------------------------------------------
# These are global fallbacks. Per-repo overrides use REPO_<ALIAS>_<VAR>.
# Planning skills (/kickoff, /plan-ceo-review, /plan-eng-review) scan these
# directories to auto-discover relevant docs before starting a review.
# Comma-separated if multiple directories.
Expand All @@ -68,17 +105,18 @@ PRD_DOCS_PATH=docs/functional,docs/prd
EXISTING_SPECS_PATH=docs/specs

# -----------------------------------------------------------------------------
# Approval gate
# Approval gate (global fallback)
# -----------------------------------------------------------------------------
# GitHub handle that must approve and merge all PRs
# Used by /ship skill to tag the reviewer in PR descriptions
# Per-repo override: REPO_<ALIAS>_APPROVAL_REQUIRED_FROM
# Agents never merge — only this person can
APPROVAL_REQUIRED_FROM=your-github-handle

# -----------------------------------------------------------------------------
# Staging / CD
# Staging / CD (global fallback)
# -----------------------------------------------------------------------------
# URL of the staging environment for CD test verification
# Used by /ship to check if CD tests have been run
# Per-repo override: REPO_<ALIAS>_STAGING_URL
# Leave empty if using ephemeral staging (cd-staging.yml workflow)
STAGING_URL=
14 changes: 14 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ The engineering planning step remains responsible for:
Architecture guidance can shape the first step, but it does **not** collapse CEO review and engineering review into a single step.


## Multi-repo configuration

ubtstack supports multiple target repos from a single installation. Configuration uses `REPO_<ALIAS>_<VAR>` patterns in `.env`:

- `REPO_<ALIAS>_URL` — Git clone URL
- `REPO_<ALIAS>_CI_COMMANDS` — CI commands for `/ship`
- `REPO_<ALIAS>_APPROVAL_REQUIRED_FROM` — Per-repo PR approver
- `REPO_<ALIAS>_ARCHITECTURE_DOCS_PATH`, `_PRD_DOCS_PATH`, `_EXISTING_SPECS_PATH` — Per-repo doc paths
- `DEFAULT_REPO` — Default alias when no `--repo` flag is given

Each ticket's `repo` field must match a registered alias. `ticket-repo-map.json` maps ticket IDs to repo aliases for Symphony hooks.

**Backward compat:** If only `TARGET_REPO_URL` is set (no `REPO_*_URL` vars), it is auto-registered as a single-entry registry.

## Existing code context

This workflow is intended to work on both greenfield and pre-existing codebases.
Expand Down
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,13 @@ Agent workspaces (created by Symphony at runtime) follow the same layout:
```
~/code/workspaces/<ticket-id>/
├── ubtstack/ # Cloned automatically by after_create hook
└── your-target-repo/ # Cloned automatically, checked out to symphony/<ticket-id>
├── backend/ # Multi-repo: directory name = repo alias
├── frontend/ # Each ticket clones only its assigned repo
└── .target-repo-dir # Sentinel file containing the directory name
```

In single-repo (legacy) mode, the directory is `your-target-repo/`. In multi-repo mode, the `after_create` hook reads `ticket-repo-map.json` to resolve the correct repo alias and clone URL.

Agents only modify the target repo. ubtstack is read-only tooling.

## Running
Expand Down Expand Up @@ -206,11 +210,42 @@ See `.env.example`. The essentials:
| Variable | Required |
|----------|----------|
| `LINEAR_API_KEY` | Yes |
| `APPROVAL_REQUIRED_FROM` | Yes — GitHub handle for PR approval |
| `TARGET_REPO_URL` | Yes — repo clone URL for Symphony |
| `APPROVAL_REQUIRED_FROM` | Yes — GitHub handle for PR approval (global fallback) |
| `TARGET_REPO_URL` | Yes (single-repo) — repo clone URL for Symphony |
| `UBTSTACK_PATH` | Yes — local path to ubtstack tooling repo (default: `../ubtstack`) |
| `LINEAR_PROJECT_SLUG` | Yes — Symphony project targeting |

### Multi-repo configuration

To serve multiple target repos from a single ubtstack installation, use `REPO_<ALIAS>_<VAR>` instead of the global vars:

```bash
REPO_BACKEND_URL=https://github.com/org/backend.git
REPO_BACKEND_APPROVAL_REQUIRED_FROM=kyle
REPO_BACKEND_CI_COMMANDS=cargo build && cargo test && cargo clippy -- -D warnings
REPO_BACKEND_ARCHITECTURE_DOCS_PATH=docs/technical

REPO_FRONTEND_URL=https://github.com/org/frontend.git
REPO_FRONTEND_CI_COMMANDS=npm run build && npm test && npm run lint

DEFAULT_REPO=backend
```

| Variable pattern | Purpose |
|------------------|---------|
| `REPO_<ALIAS>_URL` | Git clone URL for this repo |
| `REPO_<ALIAS>_APPROVAL_REQUIRED_FROM` | GitHub handle for PR approval (overrides global) |
| `REPO_<ALIAS>_CI_COMMANDS` | CI commands for `/ship` (overrides hardcoded defaults) |
| `REPO_<ALIAS>_ARCHITECTURE_DOCS_PATH` | Architecture docs path (overrides global) |
| `REPO_<ALIAS>_PRD_DOCS_PATH` | PRD docs path (overrides global) |
| `REPO_<ALIAS>_EXISTING_SPECS_PATH` | Existing specs path (overrides global) |
| `REPO_<ALIAS>_STAGING_URL` | Staging URL (overrides global) |
| `DEFAULT_REPO` | Default alias when no `--repo` flag is provided |

Per-repo vars fall back to global vars if not set. Run `npx tsx scripts/repo-registry.ts` to inspect the resolved registry.

**Backward compat:** If only `TARGET_REPO_URL` is set (no `REPO_*_URL` vars), it is auto-registered as a single-entry registry.

## Contributing

ubtstack is designed to be forked and customized for specific domains. The core pipeline is stack-agnostic — adapt the skills, test policies, and staging templates to fit your team's workflow.
Expand Down
Loading