Point an agent at a repo + a task. Get a PR-ready branch back.
shipyard runs a per-task pipeline against any repo you've configured, so you stop doing the manual loop by hand:
resolve task → branch (your convention) → implement → gates (lint/type/test)
→ security review → adversarial code review → PR description → optional smoke
→ stop PR-ready | open a PR (per the repo's config)
The pipeline logic is an installable skill; this binary is the launcher + config layer around it. One config file per repo describes what differs (task source, branch/commit convention, gates, push policy). Add a repo = add a YAML, not new code.
# Homebrew (tap)
brew install edihasaj/tap/shipyard
# or from source
go install github.com/edihasaj/shipyard@latest
# install the pipeline skill for your agent (Claude Code by default)
shipyard install-skillshipyard shells out to an agent CLI (default claude; override with
$SHIPYARD_AGENT or --agent).
shipyard contains no model API calls and no API key. It resolves your repo
config, builds the task prompt, and execs an agent CLI — the agent does the
thinking. The pipeline itself is the bundled ship-task skill (SKILL.md).
This keeps shipyard thin and lets it ride your agent's existing tools (gh,
git, MCP, /security-review, /code-review), repo context, and permission
model instead of reimplementing them.
shipyard can report its own errors and panics to Sentry. It's errors-only (no tracing, no profiling, no PII) and off by default — nothing is sent unless you set a DSN:
# project applifyer/shipyard
export SENTRY_DSN="https://85dddff1a7a494446cd5788d60e9bafe@o4510266463813632.ingest.de.sentry.io/4511711418777680"
export ENVIRONMENT=production # optional; tags eventsUnset SENTRY_DSN and reporting is a no-op. The DSN is a backend secret — keep
it in your environment, never commit it.
Each agent has an invocation profile (how shipyard renders the prompt and
builds argv). Pick it with --agent-profile or $SHIPYARD_AGENT_PROFILE;
otherwise it's inferred from the agent binary name.
| Profile | Agent | How the task is delivered |
|---|---|---|
claude (default) |
Claude Code | /ship-task slash command (runs the installed skill) |
codex |
OpenAI Codex CLI | pipeline inlined in the prompt; codex exec when headless |
generic |
any other CLI | pipeline inlined in the prompt; passed as one positional arg |
Skill-aware agents (Claude Code) use the installed skill; others receive the
full pipeline inlined in the prompt, so they don't need a separate install
step. Add an agent by adding a profile in internal/agent — no launch-path
changes.
shipyard init my-app # scaffolds ~/.config/shipyard/repos/my-app.yml
$EDITOR ~/.config/shipyard/repos/my-app.ymlConfig home resolves in order: $SHIPYARD_HOME → ./.shipyard →
$XDG_CONFIG_HOME/shipyard (default ~/.config/shipyard). Run shipyard where
to see the resolved paths. Configs are yours — keep them private; shipyard
itself ships only the schema + examples.
A minimal config:
key: my-app
path: ~/code/my-app
task_source: github
github: { repo: acme/my-app }
base_branch: main
branch_format: "{type}/{slug}"
branch_types: { feature: feat, bug: fix, chore: chore }
commit_convention: conventional
gates:
install: "pnpm install"
lint: "pnpm lint"
typecheck: "tsc --noEmit"
test: "pnpm test"
worktree:
enabled: true
root: ~/Projects/farm/worktrees
vmlab:
enabled: true
target: marketing-site
web: "goto http://localhost:3000 && screenshot /tmp/site.png"
review: { security: true, level: high }
pr: { base: main, draft: false }
push: ask # manual | pr | askSee internal/assets/schema/_schema.yml
for every field, documented.
pr.template controls the PR body skeleton. Leave it "" for the built-in
default (Summary / Changes / Testing / Risk / Linked task), or point it at your
team's template so every PR matches house style:
pr:
template: ~/templates/pr.md # a file path
# template: "obsidian://open?vault=main&file=Team/pr-template" # an Obsidian noteWhen set, the pipeline reads that template and fills in only the prose
placeholders and obvious tokens (e.g. <TICKET-ID>) — headers, comments, and
review checklists are preserved verbatim. Obsidian URLs are resolved with the
obsidian CLI (obsidian read path="<note>.md" vault=<v>), so you can keep a
PR template in your vault and reference it by URL.
shipyard list # configured repos
shipyard my-app "add CSV export to reports" # free-text task
shipyard my-app ABC-123 # Jira key
shipyard my-app "#86 fix null totals" # GitHub issue
shipyard https://github.com/acme/my-app/issues/86 # paste a URL — repo inferredOr, inside an agent session already in the repo: /ship-task my-app ABC-123.
push: manual— never pushes. Stops PR-ready and hands back. Use for enterprise repos where you push by hand.push: ask— summarizes, then asks.push: pr— pushes and opens the PR automatically.
For farm-style concurrency, enable worktree.enabled. shipyard creates a
detached git worktree from base_branch, runs the agent inside it, and passes
SHIPYARD_WORKTREE_PATH=<path> in the task notes so the bundled skill keeps all
edits in that worktree. Worktrees are left in place for inspection/cleanup.
For real smoke tests, set vmlab.enabled and one of flow, command, web,
or gui. The skill runs the matching vmlab command after the normal gates and
reports the evidence bundle/path when available.
| Command | What |
|---|---|
shipyard <repo> <task> |
run the pipeline (-p/--print for headless; --agent/--agent-profile to pick the agent) |
shipyard list |
list configured repos |
shipyard init <repo> |
scaffold a config from the schema |
shipyard install-skill |
install the ship-task skill for the agent |
shipyard where |
print the resolved config home |
MIT © Edi Hasaj
