Skip to content
Back to skills

Adopt Pipeline

ASecurity

Work on the adopt module — the ordered pipeline that adopts Claude Code into a GitHub repo (toolchain, clone, branch, trust, init, conform, enforcer, verify, push, PR), its step contract, CLI flags and credential masking. Use when adding or changing an adoption step, debugging a run, or when the user says "adopt", "adoption pipeline", "adopt_repo", or "dry run".

  • 11 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added August 31, 2026
ai-agentsrustgojavakotlinshellspringtestingdebugginggit

Works with

  • claude code
  • cli
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 28, 2026

npx -y skills add adamw7/tools --skill adopt-pipeline --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Adopt Pipeline?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Adopt Pipeline
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adamw7-adopt-pipeline/badge)](https://www.skillsdirectory.com/skills/adamw7-adopt-pipeline)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: adopt-pipeline
description: Work on the adopt module — the ordered pipeline that adopts Claude Code into a GitHub repo (toolchain, clone, branch, trust, init, conform, enforcer, verify, push, PR), its step contract, CLI flags and credential masking. Use when adding or changing an adoption step, debugging a run, or when the user says "adopt", "adoption pipeline", "adopt_repo", or "dry run".
---

# Adopt Pipeline Skill

Change the `adopt` module — a pipeline of small, ordered steps that clones a
GitHub repository, wires Claude Code into it, and opens a pull request. The
module's rules are unusually strict because it shells out to `git`, `gh` and
`claude` against real repositories.

## The pipeline, in order
`GitHubRepoAdopter.defaultSteps(options)` assembles:

1. `ToolchainStep` — the pipeline's own tools: `git`, `claude`, `gh` (and `gh`
   is logged in). Fails before any expensive work. A dry run is checked with
   `ToolchainStep.forDryRun()` — `git` and `claude` only, since `gh` is used by
   the pull request the run leaves out.
2. `CloneStep` — clones into the workspace, then rewrites the checkout's `origin`
   to `checkoutUrl()`, the same URL without its credentials, so the token git
   wrote into `.git/config` does not outlive the run.
3. `BuildToolchainStep` — the *cloned project's* build tool, checked as soon as
   the clone reveals which one it is.
4. `BranchStep` — feature branch (`claude/adopt-claude-code` by default). The
   default branch is never written to.
5. `TrustStep` — marks the checkout trusted for Claude Code.
6. `ClaudeInitStep` → `ClaudeMdConformanceStep` → `CommitStep` — generate
   `CLAUDE.md`, conform it (plus a companion `AGENTS.md`) as far as the guard
   about to be wired in demands — `BuildSystem.requiredClaudeMdSections()`, so
   only the Maven path adds the format rule's Java/Maven headings — commit.
7. `EnforcerStep` → `CommitStep` — wire the build-tool-aware guard in and commit.
8. *(optional)* `AssetsStep` → `SkillsStep` → `CommitStep` — starter config assets
   and the starter skills, on `--assets`, in one commit.
9. `VerifyStep` — proves the guard passes on the generated file.
10. `PushStep` → `PullRequestStep` — **omitted entirely on a dry run**, so the
    report says what a dry run really did rather than listing steps that
    pretended to run.

The build-system-aware steps (`BuildToolchainStep`, `ClaudeMdConformanceStep`,
`EnforcerStep`, `SkillsStep`, `VerifyStep`) share one `BuildSystems.defaults(...)`
list, so the guard that is wired in is the guard that is verified with the tool
that was probed, and the one the starter skills describe (`MavenBuildSystem`,
`GradleBuildSystem`, `FallbackBuildSystem`).

## The starter skills
`StarterSkills` writes `.claude/skills/build-and-test/SKILL.md` and
`.claude/skills/claude-md/SKILL.md` from what the run established, never from
anything guessed: `BuildSystem.buildDescription()` (overridden by the catch-all,
which is not a build tool and must not claim to be), `requiredClaudeMdSections()`,
and `verifyCommand(...)` — relativized, because that answers a checkout's wrapper
by absolute path and committing it would put the adoption host's workspace path
into somebody else's repository. What the adoption does not know is left as
headings for the project to fill in. A skill whose name the checkout's own
commands, sub-agents or skills already claim is not installed: `uniqueNames`
fails a repository where two definitions answer to one name. Add a skill by
naming it in `StarterSkills.NAMES` — `AdoptionAssets.WRITTEN_PATHS` derives its
path from the name, so nothing else has to be told about it. Weigh that name
against what repositories *ignore*, not only against what they name: the build
skill is `build-and-test` because `build` is one of the most widely ignored words
in a JVM `.gitignore`, and four of the seven foreign repositories excluded
`.claude/skills/build/SKILL.md` on that rule alone.

## The step contract
```java
public interface AdoptionStep {
    String name();
    void execute(AdoptionContext context, CommandRunner runner);
    default void execute(AdoptionContext context, CommandRunner runner, AdoptionReport report) {
        execute(context, runner);   // most steps have nothing to report
    }
}
```
Implement the three-argument variant only when the step contributes a fact to
the report: `CloneStep` records the checkout directory, `PullRequestStep` the
pull request's URL.

### Adding a step — the checklist ArchUnit enforces
- Class name **ends with `Step`** and lives in `…adopt.step`.
- **Never spawn a process yourself.** `ProcessBuilder`/`Runtime` are confined to
  the `command` package; a step shells out only through `CommandRunner`.
  `AbstractCommandStep` is the usual base.
- Fields are **immutable** (step state is final).
- A step must not depend on `GitHubRepoAdopter` — the pipeline knows its steps,
  not the other way round.
- Only `CloneStep` and `PushStep` may touch `AdoptionContext#repositoryUrl()` —
  the classes holding the commands that authenticate to the remote. Everything
  else logs `displayUrl()` or, where a working URL is needed, `checkoutUrl()`.
  The two commands that still authenticate after the clone strips the token pass
  it per invocation and write it nowhere: `PushStep` as a
  `-c remote.origin.pushurl` override, and `CloneStep`'s resuming fetch as a
  `-c url.<credentialled>.insteadOf=<origin>` rewrite with
  `--no-write-fetch-head`. Do not reach for `-c remote.origin.url` or a
  positional URL — git treats the first as another value of a multi-valued key
  and resolves the configured one instead, and writes the second into the reflog
  of every ref the fetch updates. `Redaction` (in `mcp-common`, package
  `io.github.adamw7.tools.secret`, so the MCP failure path masks with the same
  rule) masks credentials in every log, message and report — keep it that way.
- Register the step in `GitHubRepoAdopter.defaultSteps` (or the optional list it
  belongs to), and unit-test it with a fake `CommandRunner`.

## Batch runs and failure isolation
One run adopts a list of repositories: repeatable `--repo <url>`, `--repos
<file>`, or `repository_urls` on the MCP tool. Each gets its own checkout
(claimed inside its own adoption via `Checkouts`) and its own `AdoptionReport`,
and **a repository that fails does not stop the rest** — a malformed URL
included. Preserve that when changing `BatchAdoption` or `Failures`.

## CLI flags
`--repo`, `--repos`, `--workspace`, `--branch`, `--title`, `--body`,
`--reviewer`, `--label`, `--assignee`, `--draft`, `--assets`, `--dry-run`,
`--verify-only`, `--keep-workspace`, `--parallel <count>`, `--rules
<project|minimal>`, `--section <heading>`, `--rule-version`, `--timeout
<minutes>`, `--retries <count>`, `--report <file>`, `--help`. They build an `AdoptionOptions` (wrapping `PullRequestOptions`) that
both entry points — `Main` and the MCP `AdoptTool` — hand to the pipeline factory.

## Retrying what the network refused
Both entry points assemble their toolchain with `CommandRunners.forRun(options)`,
which wraps `ProcessCommandRunner` in a `RetryingCommandRunner`, so a
`git` or `gh` the network refused is run again — twice by default, after 2s and
4s (doubling, capped at 30s), and `--retries 0` turns it off. An unattended batch
otherwise lost a whole repository, `claude init` included, to one connection
reset.

`TransientFailures` is the single place that decides, and it is deliberately
narrow on two axes: the program must be `git` or `gh` — re-runnable, and unlike
`claude` or a build tool not liable to *discuss* a connection reset in a
transcript — and the wording must be a transport-level refusal. Everything else
still fails on the first attempt: a 403 or 404, a rejected non-fast-forward, a
rate limit that wants minutes rather than seconds, and the git queries whose
answer *is* a non-zero exit (`rev-parse --verify`, `diff --cached --quiet`).
Nothing that throws is retried — an unstartable program and a timeout both buy
only another wait. Add a wording to that list rather than teaching a step to
retry for itself.

## Debugging a run: where the logging goes
Two channels, from `adopt/src/main/resources/log4j2.properties`. The **console**
(standard error, threshold-filtered to `info`) is progress: `Repository 2 of 5`,
`Step 4/12: branch`, what each step cost, and the closing count of repositories
that landed. **`logs/adopt.log`** adds the adoption's own `debug` —
`io.github.adamw7.tools.adopt` is set to it while the root stays at `info`, so the
embedded Spring Boot MCP server does not bury the trace — carrying every
`git`/`claude`/`gh` invocation with its working directory, exit code and duration,
plus the redacted transcript of the ones that failed.

Reach for that file first when a run "did nothing": `runTolerating` swallows a
non-zero exit by design, so the command trace is the only place a tolerated
refusal is recorded. Durations come from `Elapsed`; log through it rather than
formatting a `Duration` yourself. Anything reporting a command or its output goes
through `CommandResult.describe()` / `redactedOutput()`, never the raw values — a
clone URL's credentials come back in the tool's own error text.

## Testing
- Step tests use a fake `CommandRunner`: **a test must never spawn a real
  process** (pinned by `TestConventionsArchitectureTest`).
- `MultiRepoAdoptionIT` clones real sample repositories to prove each gets its
  own checkout, and **stops at the branch step** — the `*IT`s never push and
  never open a pull request. Keep that invariant when extending them.
- `ForeignRepositoryAdoptionIT` adopts seven real repositories nobody prepared —
  `google/gson` (multi-module Maven), `square/okhttp` (Gradle, Kotlin DSL),
  `JakeWharton/timber` (Gradle, Groovy DSL, developed on `trunk`),
  `anthropics/anthropic-quickstarts` (a real `CLAUDE.md`),
  `anthropics/claude-code` (a real `.claude`), `modelcontextprotocol/servers`
  (its own files where two starter assets go) and `github/gitignore` (a very
  large flat tree). A step that reads or writes the checkout has to survive them:
  the guard is only ever an *addition* to a build file somebody else wrote and is
  written in that script's own DSL, the commits carry only
  `AdoptionAssets.WRITTEN_PATHS`, the working tree is left clean, the default
  branch — read from the remote, never guessed — is untouched, and the repository
  on GitHub is asked with `ls-remote` whether the adoption branch reached it (a
  local tracking ref is evidence about the clone; the claim is about somebody
  else's repository). A second adoption changes nothing. It installs the starter
  assets and skills too, so
  `AssetInstaller`'s "the project's own version wins" is checked against files
  somebody else wrote — `claude-code`'s own `.github/workflows/claude.yml` and
  `servers`' own `.mcp.json`, which no fixture can imitate. Its Maven guard is pinned to a released `--rule-version`,
  because the installer refuses to wire a `-SNAPSHOT` into another project's POM.
- `McpStreamableHttpIT` and `McpStatelessHttpIT` (over `AbstractAdoptMcpIT`) boot
  the adoption MCP server on each HTTP transport and drive `adopt_repo` across it
  with a real client. The payload is a `verify_only` call — it clones
  `octocat/Hello-World`, finds it was never adopted, and answers with the failed
  run's JSON report — because a `dry_run` would reach `claude init` and a
  verification shells out to `git` alone, writing nothing anywhere. Keep any new
  transport test to that: no test may need a logged-in `claude` or `gh`.
- Run them with `mvn -P integration-tests verify` (the profile is declared in
  `adopt`'s own pom).

## References
- `CLAUDE.md` / `AGENTS.md` — *Claude Code adoption* (source of truth)
- `adopt/.../GitHubRepoAdopter.java` — the assembled pipeline
- `adopt/.../architecture/AdoptArchitectureTest.java` — the rules above, as tests
- `adopt/.../ForeignRepositoryAdoptionIT.java` — the pipeline against seven real
  repositories that never adopted it
- `adopt/.../mcp/MCP_USAGE.md` — the `adopt_repo` tool

Files in this skill

  • README.md1.2 KB
  • SKILL.md10.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.