bootstrap-project — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited bootstrap-project (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
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.
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.
confidence < 0.6, tell the user what you found and ask them to confirm the stack beforecontinuing. Show the top two or three reasoning entries so they can see why you chose what you chose.
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.
Three branches:
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.
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:
default — warn the userbecause default means "skip all stack-specific hooks".
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:
workspace_suggestions[].confidence >= 70, propose it directly.react-vite for apps/webinstead").
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.
doctor skill instead.retrofitskill instead — it handles audit + remediation for existing repos.
bin/recommend-branch.sh.needs_user_confirm is true, show the three top reasoning strings and ask the user toconfirm before writing the strategy into the plan.
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:
{
"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.
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:
{
"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.
bin/detect-mcp-docs.sh --project-path <target> to discover Obsidian / Notion connectorsand local vaults. Capture the JSON.
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.
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."
--obsidian-vault <vault_name> and proceed with MCP routing flow.
references/mcp-routing.md and walk the user throughlocal 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.
After routing, scan for existing documentation that doesn't follow nyann conventions:
bin/detect-doc-conformance.sh --target <repo> --archetype <archetype>.AskUserQuestion:{
"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.
Compose an ActionPlan JSON from the profile + StackDescriptor + DocumentationPlan + BranchingChoice. The shape:
{
"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 == trueCLAUDE.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 hooksdocs/architecture.md, docs/prd.md,docs/decisions/ADR-000-…md, docs/research/README.md) — when the profile's documentation.scaffold_types declares them
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.
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.
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:
git init if needed.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).
bin/scaffold-docs.sh with the DocumentationPlan.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.
After success, ask only these three — in order:
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.
/nyann:doctor now to audit?" → invoke the doctor skill. Pass thebare 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.
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.
End with a compact report:
preview.sh declined → stop, no cleanup needed (nothing was written).and exit. The user can re-run after fixing.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.