Onboard a brownfield project into OpenSpec — analyze the existing codebase with parallel explorer agents, write an initial architecture document, then generate one spec per discovered feature describing what is already built. Use when adopting spec-driven development on a repo that has code but no specs.
Installs into .claude/skills of the current project.
Are you the author of Bootstrap?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/renzrollon-bootstrap)
---
name: bootstrap
description: Onboard a brownfield project into OpenSpec — analyze the existing codebase with parallel explorer agents, write an initial architecture document, then generate one spec per discovered feature describing what is already built. Use when adopting spec-driven development on a repo that has code but no specs.
license: MIT
compatibility: Requires the openspec CLI. Node.js >= 18 for the bundled interlock-graph CLI.
argument-hint: "[--quick] [--scope <path>]"
allowed-tools: Bash(openspec *) Bash(interlock-graph *) Bash(git check-ignore *) Agent Read Write Glob Grep
metadata:
type: discovery
outputs:
- openspec/initial-architecture.md
- openspec/specs/<feature>/spec.md
---
Document what a codebase **already is**, in OpenSpec form, so that every later change has something to change *from*.
This is archaeology, not design. Every spec you write describes behavior that exists in the code today. If you cannot point at the code that implements a requirement, it does not belong in the spec.
| Flag | Effect |
|------|--------|
| `--quick` | Sequential, single-context. For small repos (< ~50 source files). |
| `--scope <path>` | Restrict the whole run to a subdirectory. |
| *(none)* | Full parallel run. |
---
## 0. Pre-flight
```bash
# Initialize OpenSpec if this repo has never been initialized.
# `openspec init` creates openspec/config.yaml, so its absence is the real signal.
test -f openspec/config.yaml || openspec init --tools claude
# Build the structural graph once — it seeds every explorer below.
# Never swallow the outcome: stderr stays visible, and both failure modes
# (build errored / nothing indexable) get a banner.
graph_log=$(interlock-graph build .)
graph_status=$?
printf '%s\n' "$graph_log"
if [ "$graph_status" -ne 0 ]; then
echo "GRAPH UNAVAILABLE: build errored (exit $graph_status) — explorers fall back to grep and will be slower"
elif printf '%s' "$graph_log" | grep -q 'nodes=0'; then
echo "GRAPH UNAVAILABLE: nothing indexable found — explorers fall back to grep and will be slower"
fi
```
**A graph failure is not a stop.** Bootstrap works without it — docs and OpenSpec indexing still happen — so print the banner, carry it to the final report, and continue with degraded expectations: the explorers grep instead of following edges, and they are slower for it.
`nodes=0` is the ordinary case for a repo the graph doesn't structurally index (Go, Rust, Java, Ruby — structural indexing covers JavaScript/TypeScript, Python and shell). Say which of the two reasons applied rather than presenting either as a defect.
When the build did produce a graph, **read `.claude/graph/GRAPH_REPORT.md` before spawning anything**. Its hub nodes and module boundaries tell the explorers where to look, so they don't rediscover the tree from scratch.
Then check whether `openspec/specs/` already has content. If it does:
> "This project already has N specs. Bootstrap will ADD specs for undocumented features but will never overwrite an existing one. Continue?"
---
## 1. DISCOVER — parallel exploration
Spawn five explorer subagents via the Agent tool (`subagent_type: "Explore"`), or run them in sequence under `--quick`. Each returns structured JSON.
**Before writing the prompts, establish the stack.** Read the dependency manifest (`package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `pom.xml`, `Gemfile`, …) and the top-level layout. Then phrase each prompt in that stack's vocabulary. The categories below are universal; examples in your head are *illustrative only* — replace them with the frameworks this repo actually uses. Asking a Go service about React hooks wastes a whole agent.
Shared skeleton — fill `{role}`, `{task}`, `{schema}` from the table:
```
You are the {role} explorer for this repo. Phrase findings in this stack's vocabulary.
{task}
Prefer GRAPH_REPORT.md / interlock-graph when a graph exists; otherwise grep.
Return JSON only: {schema}
```
| Role | Task | JSON schema |
|------|------|-------------|
| Structure Mapper | Map layout. Prefer GRAPH_REPORT modules/hubs, then verify on disk. Report organization (feature/layer/hybrid), entry points and routes, naming conventions, key config files, monorepo yes/no. | `{ organization, entryPoints[], conventions[], configFiles[], monorepo }` |
| Dependency & Data Model Analyzer | Prefer `interlock-graph query` / `path`. Report third-party deps by role, internal layer imports, persistence schema/models, entity relationships, outbound integrations. | `{ dependencies[], internalLayers[], dataModels[], entityRelationships[], integrations[] }` |
| Pattern Extractor | Recurring composition (components/handlers/services), state/lifecycle, errors, auth, data fetch/mutate, testing, code style. | `{ compositionPatterns[], stateManagement, errorHandling, auth, dataFlow, testing, codeStyle }` |
| Interface Scanner | External surface: endpoints and protocol, mutations, schemas/validators, public exports/SDK, realtime/streaming, CLI. | `{ endpoints[], mutations[], schemas[], publicExports[], realtime[], cli[] }` |
| Infrastructure & Config Reader | Build/task-runner entry points, env vars referenced in code, CI/CD, deploy target, lint/format, typecheck/static analysis. | `{ buildSystem, tasks[], envVars[], cicd, deployment, linting, staticAnalysis }` |
Cap: 8 agents in this phase.
---
## 2. SYNTHESIZE — the architecture document
One synthesis pass reads all five outputs and writes `openspec/initial-architecture.md`:
```markdown
# Initial Architecture
> Auto-generated by /interlock:bootstrap on {date}.
> Point-in-time snapshot, not a living document.
## Stack & Infrastructure
## Project Organization
## Architecture Overview <- ASCII diagram, subsystem boundaries, data flow
## Data Model
## Subsystems & Features <- table: subsystem | location | responsibility | patterns
## Established Patterns <- composition, data flow, state, errors, auth, testing
## Interface Surface
## Design Decisions (Inferred)
- [EXTRACTED] stated outright in comments or docs
- [INFERRED] deduced from a consistent pattern
- [AMBIGUOUS] unclear — several patterns coexist
## Gaps & Risks <- incomplete work, conflicting patterns, hazards
```
Tag every design decision with its confidence. An `[INFERRED]` decision that turns out wrong is recoverable; one presented as fact is not.
---
## 3. IDENTIFY FEATURES — boundary detection
From the synthesis, pick the units that each deserve a spec:
- **Entry-point based** — each major route group, command, or consumer
- **Domain based** — each data model with a full lifecycle
- **Capability based** — cross-cutting concerns (auth, observability, error handling)
- **Module based** — feature directories and barrel exports
Present the list for confirmation before generating:
```
Discovered N features to spec:
1. authentication — login, register, session management
2. task-crud — create, read, update, delete tasks
3. app-shell — layout, navigation, route protection
Generate specs for all, or select specific ones?
```
---
## 4. SPEC GENERATION — one agent per feature
For each confirmed feature, spawn a subagent (cap: 12).
> You are writing an OpenSpec spec for an **existing** feature. Document what IS built, not what SHOULD be.
> Feature: `{name}`. Architecture context: `{relevant section}`. Source files: `{paths}`.
> Read the implementation. Write requirements in the repo's OpenSpec spec format, each one traceable to code. Where behavior is genuinely unclear, mark it rather than guessing.
Write to `openspec/specs/<feature>/spec.md`. **Never overwrite an existing spec file.** If one exists, skip it and report the skip.
Then validate:
```bash
openspec validate
```
---
## 5. UPDATE PROJECT CONTEXT
Fill in the project context in `openspec/config.yaml` with what was discovered — stack, architecture summary, conventions, domain — so later `openspec instructions` calls carry it. Merge into the existing file; never clobber unrelated keys a user has set.
---
## 6. REPORT
- Any `GRAPH UNAVAILABLE: <reason> — explorers fall back to grep and will be slower` banner from pre-flight, repeated verbatim. If the graph built, say so in one line instead — a report that mentions neither reads like a graph-backed run that never happened.
- Architecture doc path and what it covers
- Specs created, and specs skipped because they already existed
- Features deliberately not specced, with the reason
- `openspec validate` result
- Corpus-persistence posture (below)
- Suggested next step: `/interlock:docs-digest` to build the agent prose primer
### Corpus-persistence posture
This is the one moment the repository is looked at before any run has written to
it, so raise the question now: will its run corpora be kept?
Observe the current state with git's own matcher — do **not** read or parse
`.gitignore`. Its semantics include negation, directory-only trailing slashes,
nested files, `.git/info/exclude` and the user's global excludes file, and a
hand-rolled match gets some of those wrong:
```bash
git check-ignore -q .claude/ship/ ; echo "ship=$?"
git check-ignore -q .claude/learning/ ; echo "learning=$?"
git check-ignore -q .claude/metrics/ ; echo "metrics=$?"
```
Exit 0 means the path is excluded, 1 means it is tracked territory.
Report **each of the three separately** — never collapse them into one verdict.
A split state, where one is excluded and the others are not, is the case most
likely to be unintentional, and collapsing hides exactly that.
State the recommendation as **conditional**, never as a verdict about this
repository. Nothing you can observe tells you whether the repo will run
`/interlock:ship` against its own product, so say which posture fits which
intent and let the reader choose:
> `.claude/ship/` and `.claude/metrics/` are excluded, `.claude/learning/` is
> not. If this repository will run `/interlock:ship` on its own product, those
> exclusions drop the audit trail — remove them from `.gitignore`. If it is a
> harness whose ship runs are development exhaust, add `.claude/learning/` so
> all three match. Rationale and both `.gitignore` blocks: docs/11, "Whether to
> keep them".
**Never write to `.gitignore`.** Not to create it, not to append, not to "fix"
a split state. Name the entries a person could add and let them add them — this
skill was invoked to produce specs, and an edit to an unrelated file is a
surprise in someone's diff, not a service.
If any `git check-ignore` exits non-zero for a reason other than "not ignored"
— an unreadable `.gitignore`, or a directory that is not a git repository at all
— report the posture as **undetermined and say why**, then carry on. Bootstrap
completes and writes every artifact it would otherwise have written; a failed
check never fails the onboarding.
---
## Guardrails
- **Never overwrite an existing spec.** Add only.
- **Never modify source code.** This skill is read-only outside `openspec/` and `.claude/graph/`.
- Respect `.gitignore`; skip vendored and generated trees.
- Max 8 explorer agents in phase 1, 12 spec agents in phase 4.
- **Idempotent.** Re-running adds newly-discovered features and leaves everything else alone.
- If a feature's behavior can't be determined from the code, say so in the spec rather than inventing a requirement.
Follow `${CLAUDE_PLUGIN_ROOT}/shared/TOOL-ECONOMY.md`.