# Bootstrap Project

> Bootstrap a fresh or existing repo with nyann. TRIGGER when the user says "set up this project", "initialize git workflow", "bootstrap this repo", "scaffold this project", "ngyamm this repo", "use my <name> profile" / "apply the nextjs-prototype profile" (profile mode). ALSO trigger on "standard setup" / "usual stack" / "the usual setup" / "install all the standard hooks" / "give this repo the usual setup" / "make this repo standard" / "install nyann" — these read as opinionated bulk setup, not narrow edits, even though they sound small. Also trigger on any phrasing that mentions wiring up git hooks + branching + conventions + docs as a single opinionated setup. DO NOT trigger on narrow requests like "add a lint hook" or "update CLAUDE.md" — those are edits, not bootstraps. DO NOT trigger on "audit this project" / "fix what's drifted" / "bring into compliance" — those are retrofit. DO NOT trigger on "check this project's health" / "is this healthy" — those are doctor. When in doubt, run the detection step and

- Skill: `thettwe/bootstrap-project` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add thettwe/bootstrap-project`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thettwe/bootstrap-project/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: thettwe (https://skillmd.com/u/thettwe)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/thettwe/bootstrap-project

---


# bootstrap-project

You are executing nyann's main bootstrap flow. The goal is to take an empty directory, a fresh
repo, or an existing messy repo and bring it up to the conventions in a nyann profile — without
silently mutating anything.

**Work in phases.** Each phase is one or more bash scripts in `bin/`. Never run a destructive
action outside a preview-confirmed plan. If a phase emits a structured skip record
(`{"skipped": "...", "reason": "..."}`), log it in your final summary and continue with the
remaining phases.

## 0. Team profile freshness check (quick, non-blocking)

Run `bin/check-team-staleness.sh` before starting. If it produces
output, show the notification to the user (e.g. "Your team profiles
have upstream changes. Run `/nyann:sync-team-profiles` to update
before bootstrapping, or continue with the current versions.").
Do not block — let the user decide whether to sync first.

## 1. Detect

1. Run `bin/detect-stack.sh --path <target>` and capture the JSON to a temp file
   (e.g. `bin/detect-stack.sh --path <target> > "${TMPDIR:-/tmp}/nyann-stack.json"`)
   so step 2 can pass it via `--stack`. Never proceed without it.
2. If `confidence < 0.6`, tell the user what you found and ask them to confirm the stack before
   continuing. Show the top two or three reasoning entries so they can see *why* you chose what
   you chose.
3. If `is_monorepo` is true, note it — bootstrap will auto-resolve per-workspace configs
   (hooks, lint-staged entries, commit scopes) using `bin/resolve-workspace-configs.sh`.

See `references/python.md` and `references/javascript.md` for stack-specific notes Claude should
fold into its follow-ups. Only load them when the detected stack matches.

## 2. Pick a profile

Three branches:

- **User named a profile.** Load it with `bin/load-profile.sh <name>`. If the loader exits 2
  (profile not found), list the available profiles it reported and ask the user to pick one.
- **No profile named.** Run `bin/suggest-profile.sh --target <repo> --stack <stack-json-file>`
  to get a ranked list of matching profiles with confidence scores. Pass the StackDescriptor
  JSON captured in step 1 via `--stack` so the script reuses it instead of re-running
  detect-stack.sh (which would duplicate the directory walk).

  Show the user the top suggestion (or top 2-3 if scores are close):
  > "Detected: TypeScript + Next.js. Suggested profile: `nextjs-prototype` (confidence: 90).
  > Use this profile?"

  Rules:
  - If the top suggestion has confidence ≥ 70, propose it directly.
  - If the top suggestion has confidence 40-69, propose it but ask the user to confirm.
  - If no suggestion has confidence ≥ 40, fall back to `default` — **warn the user**
    because `default` means "skip all stack-specific hooks".
  - If suggestions is empty (no matches at all), use `default` with the same warning.

  **Multi-stack repos (monorepo with workspace_suggestions):** If the
  `workspace_suggestions[]` array is non-empty, each workspace has its own
  best-matching profile. Present per-workspace profile assignments to the user:

  > "This monorepo has multiple stacks. Here are the suggested profiles:
  >
  > | Workspace | Stack | Suggested profile | Confidence |
  > |-----------|-------|-------------------|------------|
  > | apps/web | TypeScript + Next.js | `nextjs-prototype` | 90 |
  > | apps/api | TypeScript + Express | `node-api` | 85 |
  > | apps/mobile | Dart + Flutter | `flutter-app` | 92 |
  >
  > Accept these assignments?"

  Rules for workspace profiles:
  - If `workspace_suggestions[].confidence >= 70`, propose it directly.
  - If confidence 40-69, include but flag for confirmation.
  - If confidence < 40 or suggestion is null, workspace inherits the root profile.
  - User can override any workspace assignment (e.g., "use `react-vite` for apps/web
    instead").
  - Confirmed workspace→profile mappings are written into the root profile's
    `workspaces` field with `"profile": "<name>"` entries. This persists the choice
    for future bootstraps.

  **Legacy secondary_suggestions:** If `workspace_suggestions` is empty but
  `secondary_suggestions` is non-empty, fall back to the legacy behaviour:
  mention the secondary stacks as informational.

- **Audit mode** ("check hygiene", "is this healthy"). Invoke the `doctor` skill instead.
- **Retrofit mode** ("fix what's drifted", "bring into compliance"). Invoke the `retrofit`
  skill instead — it handles audit + remediation for existing repos.

## 3. Recommend branching (unless the profile already pins one)

1. Pipe the StackDescriptor into `bin/recommend-branch.sh`.
2. If `needs_user_confirm` is true, show the three top reasoning strings and ask the user to
   confirm before writing the strategy into the plan.
3. Otherwise, feed the recommendation straight into the action plan.

## 4. Route docs

### 4a. Archetype prompt (v1.6.0+)

The StackDescriptor from step 1 carries an `archetype` field
(`api-service` / `cli-tool` / `library` / `web-app` / `mobile-app` /
`plugin` / `unknown`). When it's anything other than `unknown` AND
the resolved profile does not already declare
`documentation.use_archetype_scaffolds: true`, prompt the user via
`AskUserQuestion`:

```json
{
  "questions": [{
    "question": "Detected archetype: <archetype>. Enable archetype-aware Project Memory scaffolding?",
    "header": "Project Memory",
    "options": [
      {"label": "Yes (recommended)", "description": "Scaffold api-reference, runbook, deployment, glossary as appropriate for this system."},
      {"label": "No", "description": "Use the profile's flat scaffold list (pre-v1.6.0 behaviour)."}
    ],
    "multiSelect": false
  }]
}
```

If the user picks **Yes**, pass `--archetype <archetype>
--use-archetype-scaffolds` to `bin/route-docs.sh` in the next step.
If **No**, pass nothing extra (current behaviour). Skip the prompt
entirely when archetype is `unknown` or when the profile already
sets `use_archetype_scaffolds: true`.

### 4a-2. Glossary auto-population (v1.7.0+)

When the resolved scaffold set includes `glossary` (either via the
profile's `documentation.scaffold_types` OR via an enabled archetype
map for `library` / `api-service` / others), prompt the user via
`AskUserQuestion`:

```json
{
  "questions": [{
    "question": "Auto-populate `docs/glossary.md` with detected exported types from your codebase?",
    "header": "Glossary",
    "options": [
      {"label": "Yes (recommended)", "description": "Seed entries for top-level exported structs/interfaces/classes/types. Idempotent, preserves user content outside markers."},
      {"label": "No",  "description": "Keep the empty template. You can opt in later via the profile."}
    ],
    "multiSelect": false
  }]
}
```

If **Yes**, set `documentation.glossary.auto_populate: true` in the
resolved profile snapshot before passing it to `bootstrap.sh`. The
bootstrap orchestrator reads the flag and forwards
`--auto-glossary` (plus `--glossary-max-terms` /
`--glossary-languages`) to `bin/scaffold-docs.sh`. Default Yes for
the `library` and `api-service` archetypes; default No otherwise.

### 4b. Run route-docs

1. Run `bin/detect-mcp-docs.sh --project-path <target>` to discover Obsidian / Notion connectors
   and local vaults. Capture the JSON.
2. If `available[]` is empty AND `discoverable_vaults[]` is empty, run `bin/route-docs.sh
   --profile <path>` with no MCP flags — plan is local-only. Pass the archetype flags from 4a
   if the user opted in.
3. If `available[]` is empty but `discoverable_vaults[]` is non-empty, offer the user:
   > "Found Obsidian vault(s) on disk: `<vault_name>` (`<vault_path>`).
   > Would you like to route docs to Obsidian? I can connect directly to
   > the vault, or you can add the Obsidian MCP server for full integration."
   - If user says yes to direct connection: use the vault path directly with
     `--obsidian-vault <vault_name>` and proceed with MCP routing flow.
   - If user declines: fall back to local-only routing.
4. If any MCP connector is available, load `references/mcp-routing.md` and walk the user through
   local vs MCP vs split. It covers the questions to ask, how to compose the `--routing` string,
   the connector-target inputs (`--obsidian-vault`, `--obsidian-folder`, `--notion-parent`),
   and the post-route creation flow (MCP tool calls per non-local target + plan update with
   returned identifiers). Archetype flags from 4a propagate the same way.

Capture the resulting `DocumentationPlan`. `memory/` stays local by invariant regardless of
any routing choice.

### 4c. Doc conformance check (v1.9.0+)

After routing, scan for existing documentation that doesn't follow nyann conventions:

1. Run `bin/detect-doc-conformance.sh --target <repo> --archetype <archetype>`.
2. If the output array is non-empty, present the proposals to the user via `AskUserQuestion`:

```json
{
  "questions": [{
    "question": "Found existing docs that don't follow nyann conventions. Reorganize them?",
    "header": "Doc layout",
    "options": [
      {"label": "Yes, reorganize all", "description": "Move/rename all detected docs to their canonical paths (uses git mv when tracked)."},
      {"label": "Let me pick", "description": "Show the list so I can approve each move individually."},
      {"label": "No, leave as-is", "description": "Keep current paths. Scaffolding will skip those categories since files already exist."}
    ],
    "multiSelect": false
  }]
}
```

If **Yes, reorganize all**: write the full proposals JSON to a temp file and run
`bin/reorganize-docs.sh --target <repo> --moves <proposals.json> --apply`. (Without
`--apply`, the script previews and exits without mutating; pass `--apply` once the
user has approved the moves.)

If **Let me pick**: show a table of proposals (source → target, confidence, reason) and
ask the user which to approve. Filter the array to approved-only, then run reorganize-docs.

If **No**: skip. The scaffolder's idempotency means it won't create docs where
non-conforming versions already exist at other paths — but the user keeps their layout.

After reorganization, any moved files now occupy canonical paths. The subsequent scaffold
step will detect them as "exists" and skip creation — preserving user content that was moved
into place.

## 5. Build and preview the plan

Compose an ActionPlan JSON from the profile + StackDescriptor + DocumentationPlan + BranchingChoice.
The shape:

```json
{
  "writes":   [{ "path": "...", "action": "create|merge|overwrite", "bytes": 123 }],
  "commands": [{ "cmd": "git init", "cwd": "." }],
  "remote":   []
}
```

**Required writes[] entries** — include each one the profile/plan opts into; bootstrap.sh will
refuse to materialise a file that isn't in this list (preview-before-mutate):

- `.gitignore` — when gitignore templates apply
- `.editorconfig` — when `profile.extras.editorconfig == true`
- `CLAUDE.md` — when `profile.extras.claude_md == true`
- `.husky/pre-commit`, `.husky/commit-msg`, `commitlint.config.js` — for JS/TS hook phase
- `.pre-commit-config.yaml` — for Python hook phase
- `.git/hooks/commit-msg`, `.git/hooks/pre-commit` — always for core hooks
- Doc files per DocumentationPlan (`docs/architecture.md`, `docs/prd.md`,
  `docs/decisions/ADR-000-…md`, `docs/research/README.md`) — when the profile's
  `documentation.scaffold_types` declares them

### 5a. Pre-render merge previews (v1.7.0+)

Before invoking `preview.sh`, run `bin/render-plan.sh` so merge actions
on `.gitignore` and `CLAUDE.md` carry a `preview_blob` field. Preview
diffs against the current file and shows the operator the actual lines
about to be added — no more "234 B merged" surprises.

```
bin/render-plan.sh \
  --plan <plan.json> \
  --target <repo> \
  --profile <profile.json> \
  --doc-plan <doc-plan.json> \
  --stack <stack.json> \
  --templates-csv "<inferred-from-stack>" \
  --output <plan.rendered.json>
```

Determine `--templates-csv` from the StackDescriptor exactly the way
`bootstrap.sh` does (jsts / python / go / rust / generic, plus
secondary languages). Skip render-plan only when the plan has zero
merge actions for the two covered paths — render-plan is a no-op in
that case so it's safe to always call.

The rendered plan supersedes the original for both preview and execute.
Pass it as `--plan <plan.rendered.json>` from this point on.

### 5b. Preview

Pipe the rendered plan through `bin/preview.sh --plan <file> --target <repo>`.
Pass `--target` so the merge-diff renderer resolves `.path` entries
against the actual repo (not the caller's cwd). Show the stderr
preview to the user. If they respond with `skip <path>`, re-invoke
with `--skip <path>` and reshow. If `no`, stop and exit.

## 6. Execute

Capture the plan SHA-256 first — `bin/preview.sh --plan <file> --emit-sha256` prints just the
hex on stdout. Pass it as `--plan-sha256` so bootstrap can recompute and verify the bytes
haven't changed between the user's "yes" and execution. The SHA binding is required;
bootstrap refuses to run without it.

```
sha=$(bin/preview.sh --plan <confirmed-plan.json> --emit-sha256)
bin/bootstrap.sh --plan <confirmed-plan.json> --plan-sha256 "$sha" \
  --target <repo> --profile <path> --doc-plan <path> --stack <path>
```

bootstrap.sh handles:

1. `git init` if needed.
2. Creating base branches per the strategy.
3. Writing files in the plan.
4. Running install commands (declared in the plan, never inferred).
5. Calling `bin/install-hooks.sh` with the matching phase flags (`--core`, `--jsts`, `--python`).
   For monorepos, also passes `--workspace-configs` (per-workspace lint-staged entries) and
   `--commit-scopes` (workspace-derived scopes for commitlint).
6. Calling `bin/scaffold-docs.sh` with the DocumentationPlan.
7. Calling `bin/gen-claudemd.sh` with profile + plan + stack.
   For monorepos, also passes `--workspace-configs` (renders Workspaces table) and
   `--extra-scopes` (merges workspace scopes into conventions table).

Every step is idempotent. On any failure, bootstrap.sh aborts cleanly with which step failed in
its summary; surface that verbatim. Do not retry automatically.

## 7. Offer the post-bootstrap nudges

After success, ask *only these three* — in order:

1. "Save this as a profile for reuse?" → if yes, ask for a profile name,
   then call `bin/learn-profile.sh --target <repo> --name <slug>`.
   Both `--target` and `--name` are required flags — never pass the name
   as a positional argument.
2. "Run `/nyann:doctor` now to audit?" → invoke the doctor skill. Pass the
   **bare profile name** (e.g. `python-cli`), not a filesystem path, as
   `--profile`. Doctor's `GITHUB PROTECTION` section will surface whether
   branch / tag protection is in place; tell the user they can re-run
   doctor anytime to verify protection state.
3. "Set up GitHub branch protection?" → invoke `bin/gh-integration.sh`
   (apply mode). For audit-only ("is protection drift on this repo?"),
   doctor already covers it via `bin/gh-integration.sh --check` —
   no separate skill needed.

## Output summary

End with a compact report:

- Detected stack + confidence.
- Profile applied (source: user / starter).
- Files created / merged (count only; the plan has the detail).
- Hook phases run, with any skip records.
- Whether CLAUDE.md was under the soft-cap budget.
- Next steps the user can take.

## When something goes wrong

- Detection confidence low → ask before writing.
- `preview.sh` declined → stop, no cleanup needed (nothing was written).
- Any bin script exits non-zero during execute → do not proceed; show the failing step's stderr
  and exit. The user can re-run after fixing.
- A structured skip record is *not* a failure; include it in the summary but keep going.

