skills/bootstrap-project/SKILL.md
Generate a portable, Nazgul-free project bundle (docs + Claude subagents) without installing Nazgul. Runs the full pre-planning pipeline (discovery, doc-generator, reviewer-instantiation, optional designer) and emits output into standard paths (./docs/, ./docs/context/, ./.claude/agents/, ./.claude/).
npx skillsauth add OrodruinLabs/nazgul nazgul:bootstrap-projectInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
/nazgul:bootstrap-project — Interactive: ask for objective, then run full pipeline/nazgul:bootstrap-project "Add Stripe billing to existing app" — Use given objective/nazgul:bootstrap-project --dry-run — Run pipeline and transform into scratch without relocating/nazgul:bootstrap-project --yes --overwrite — Non-interactive, overwrite existing ./docs or ./.claude/agents$ARGUMENTS
--yes — Non-interactive; abort on ambiguous prompts.--overwrite — Force overwrite of non-empty ./docs/ or ./.claude/agents/.--dry-run — Run pipeline and transform into scratch; skip relocation and cleanup.--wipe-scratch — Delete any existing ./.bootstrap-scratch/ before starting.--resume-scratch — Keep any existing ./.bootstrap-scratch/ and proceed with the run. Pipeline agents are still invoked; each may reuse pre-existing files in scratch at its own discretion, but the skill does not explicitly skip phases based on what's already there.Pre-load: Run ToolSearch with query select:AskUserQuestion to load the interactive prompt tool (deferred by default). Do this BEFORE any step that uses AskUserQuestion.
Source the preflight helpers:
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/bootstrap-preflight.sh"
Parse $ARGUMENTS structurally: split on whitespace, pull out known flags, and leave the rest as the free-form objective. This prevents false positives where an objective string contains a flag-like substring (e.g. "document the --dry-run workflow").
BOOTSTRAP_YES=false
BOOTSTRAP_OVERWRITE=false
BOOTSTRAP_DRY_RUN=false
BOOTSTRAP_WIPE_SCRATCH=false
BOOTSTRAP_RESUME_SCRATCH=false
BOOTSTRAP_OBJECTIVE=""
# Disable pathname expansion during tokenization so an objective containing
# glob metachars (*, ?, [) doesn't expand to matching filenames. Re-enable
# afterwards so downstream code behaves normally.
set -f
# Intentionally NOT quoted: we want word-splitting so each token is processed.
# shellcheck disable=SC2086
set -- $ARGUMENTS
for tok in "$@"; do
case "$tok" in
--yes) BOOTSTRAP_YES=true ;;
--overwrite) BOOTSTRAP_OVERWRITE=true ;;
--dry-run) BOOTSTRAP_DRY_RUN=true ;;
--wipe-scratch) BOOTSTRAP_WIPE_SCRATCH=true ;;
--resume-scratch) BOOTSTRAP_RESUME_SCRATCH=true ;;
--*) echo "warning: unknown flag: $tok" >&2 ;;
*) BOOTSTRAP_OBJECTIVE="${BOOTSTRAP_OBJECTIVE:+$BOOTSTRAP_OBJECTIVE }$tok" ;;
esac
done
set +f
Run checks in order. If any returns non-zero, respect the contract (hard abort or prompt):
check_no_nazgul_dir || exit $?
check_scratch_state; scratch_rc=$?
case $scratch_rc in
0) ;;
12)
if [ "$BOOTSTRAP_RESUME_SCRATCH" = "true" ]; then
# Keep the existing scratch tree and continue into Phase 2.
# Downstream steps detect and reuse pre-existing outputs where present.
true
elif [ "$BOOTSTRAP_WIPE_SCRATCH" = "true" ]; then
rm -rf ./.bootstrap-scratch
elif [ "$BOOTSTRAP_YES" = "true" ]; then
echo "error: scratch exists, --yes in effect, cannot prompt; pass --wipe-scratch or --resume-scratch" >&2
exit 12
else
# Interactive: prompt the user and RE-INVOKE the skill with the matching
# flag. The bash fragment exits here so the LLM can ask the question and
# restart, instead of the resume path being silently unreachable.
#
# LLM: use AskUserQuestion with these options:
# header: "Scratch"
# question: "A previous bootstrap run left ./.bootstrap-scratch/. How would you like to proceed?"
# options:
# - "Resume" — "Continue from where the last run stopped"
# - "Wipe and restart" — "Delete scratch and start fresh"
# - "Abort" — "Stop and keep everything as-is"
# Then re-invoke:
# - Resume → /nazgul:bootstrap-project --resume-scratch ...
# - Wipe/restart → /nazgul:bootstrap-project --wipe-scratch ...
# - Abort → stop
echo "./.bootstrap-scratch/ exists from a prior run." >&2
exit 12
fi
;;
*) exit "$scratch_rc" ;;
esac
check_docs_agents_empty; docs_rc=$?
case $docs_rc in
0) ;;
11)
if [ "$BOOTSTRAP_OVERWRITE" = "true" ]; then
# Actually clear the managed targets so relocation produces a clean
# bundle instead of silently merging with stale files. Only remove
# paths this skill owns — never the whole ./.claude/ tree (which may
# hold unrelated user config).
rm -rf ./docs ./.claude/agents
rm -f ./.claude/design-tokens.json ./.claude/design-system.md
elif [ "$BOOTSTRAP_YES" = "true" ]; then
exit 11
else
# LLM: use AskUserQuestion with these options:
# header: "Overwrite"
# question: "Non-empty ./docs/ or ./.claude/agents/ detected. What would you like to do?"
# options:
# - "Overwrite" — "Clear managed targets (./docs/, ./.claude/agents/) and proceed"
# - "Abort" — "Stop and keep everything as-is"
# If Overwrite: re-invoke with --overwrite flag
# If Abort: stop
echo "Non-empty ./docs/ or ./.claude/agents/ detected." >&2
exit 11
fi
;;
*) exit "$docs_rc" ;;
esac
check_git_clean
if [ -n "${BOOTSTRAP_GIT_WARNING:-}" ]; then
echo "$BOOTSTRAP_GIT_WARNING" >&2
fi
Create the scratch tree:
mkdir -p ./.bootstrap-scratch/context ./.bootstrap-scratch/docs ./.bootstrap-scratch/agents ./.bootstrap-scratch/.claude
Detect whether this is an existing codebase or an empty project:
detect_project_type
echo "Detected: $BOOTSTRAP_PROJECT_TYPE ($BOOTSTRAP_SOURCE_COUNT source files)"
Determine the objective source using a three-tier priority:
If $BOOTSTRAP_OBJECTIVE (parsed in Phase 1) is non-empty, use it regardless of project type. Write a minimal project-spec:
if [ -n "$BOOTSTRAP_OBJECTIVE" ]; then
cat > ./.bootstrap-scratch/context/project-spec.md <<SPEC
# Project Specification
## Source
- Method: argument
- Created at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
## Vision
$BOOTSTRAP_OBJECTIVE
SPEC
fi
If brownfield ($BOOTSTRAP_PROJECT_TYPE = "brownfield") and no explicit objective, skip interactive questions entirely. The codebase IS the spec — Discovery will scan it and derive everything. Write a codebase-derived project-spec:
if [ -z "$BOOTSTRAP_OBJECTIVE" ] && [ "$BOOTSTRAP_PROJECT_TYPE" = "brownfield" ]; then
PROJECT_NAME=$(basename "$(pwd)")
cat > ./.bootstrap-scratch/context/project-spec.md <<SPEC
# Project Specification
## Source
- Method: brownfield-auto (derived from existing codebase)
- Created at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
- Source files detected: $BOOTSTRAP_SOURCE_COUNT
## Vision
Document and analyze the existing $PROJECT_NAME codebase.
## Note
This is a brownfield project. The objective, architecture, features, and
constraints will be derived from scanning the existing source code during
the Discovery phase. No interactive input was required.
SPEC
fi
If greenfield ($BOOTSTRAP_PROJECT_TYPE = "greenfield") and no explicit objective:
First, enforce the --yes non-interactive contract — we cannot ask questions in that mode:
if [ -z "$BOOTSTRAP_OBJECTIVE" ] && [ "$BOOTSTRAP_PROJECT_TYPE" = "greenfield" ] && [ "$BOOTSTRAP_YES" = "true" ]; then
echo "error: greenfield project with no objective; --yes is set so we cannot prompt." >&2
echo " Re-run with an explicit objective argument, e.g.:" >&2
echo " /nazgul:bootstrap-project --yes \"your product idea here\"" >&2
exit 13
fi
Otherwise, run the condensed Tier 1 interactive flow. Ask these 5 questions one at a time, phrased naturally, and wait for each answer:
After collecting answers, write ./.bootstrap-scratch/context/project-spec.md with the standard sections (## Vision, ## Target Users, ## Core Features, ## Problem Statement, ## Constraints).
After Tier 1, offer Tier 2: "Got the basics. Want to go deeper on user stories and success metrics? (~5 more minutes) (y/n)"
Verify the file exists before proceeding:
test -f ./.bootstrap-scratch/context/project-spec.md || { echo "error: project-spec.md not written" >&2; exit 30; }
Export STATE_ROOT and BUNDLE_MODE for all downstream renders:
export STATE_ROOT="./.bootstrap-scratch"
export BUNDLE_MODE="true"
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/bootstrap-render.sh"
Render and invoke each pipeline agent. For each agent, the renderer produces a Nazgul-free prompt pointed at the scratch tree; the LLM then executes the agent's instructions.
Render the discovery prompt and invoke it:
render_agent_prompt "$CLAUDE_PLUGIN_ROOT/agents/discovery.md" "$STATE_ROOT" > "$STATE_ROOT/.discovery-prompt.md"
Then invoke the discovery agent via the Agent tool, passing the rendered prompt as the system/initial message. The agent writes to $STATE_ROOT/context/{project-profile,project-classification,architecture-map,existing-docs}.md.
After the agent returns, verify expected outputs exist:
for f in project-profile.md project-classification.md architecture-map.md; do
test -f "$STATE_ROOT/context/$f" || {
echo "error: discovery did not produce $f; preserving scratch for debugging" >&2
exit 40
}
done
render_agent_prompt "$CLAUDE_PLUGIN_ROOT/agents/doc-generator.md" "$STATE_ROOT" > "$STATE_ROOT/.docgen-prompt.md"
Invoke the doc-generator agent. It reads $STATE_ROOT/context/ and writes to $STATE_ROOT/docs/{PRD,TRD,ADR-*,test-plan,manifest}.md (and migration-plan.md if classification=migration).
Verify:
for f in PRD.md TRD.md test-plan.md; do
test -f "$STATE_ROOT/docs/$f" || {
echo "error: doc-generator did not produce $f; preserving scratch" >&2
exit 41
}
done
The reviewer template at agents/templates/reviewer-base.md is rendered once per reviewer domain determined from project-profile.md. Available domain definitions live in agents/templates/reviewer-domains.json (existing structure: keys like qa-reviewer, api-reviewer, with per-domain checklist and review_steps arrays, plus description, title, context_items, category, approved_criteria, rejected_criteria). The JSON does NOT have a keywords field today, so domain selection goes via a two-step rule:
qa-reviewer and code-reviewer (baseline; assumed present in the JSON — if a key is missing, skip it with a warning and continue).project-profile.md:
api-reviewer — if profile mentions api, rest, graphql, endpointfrontend-reviewer — if profile mentions react, vue, svelte, angular, next.js, nuxt, nextjssecurity-reviewer — always include when the profile mentions auth, login, password, token, jwt, oauthperformance-reviewer — if profile mentions database, caching, redis, perfThis mapping is hard-coded in select_reviewer_domains (see Step 2 below). Domains not defined in reviewer-domains.json are skipped.
For each selected domain, render the template with BUNDLE_MODE=true:
DOMAINS=$(select_reviewer_domains "$STATE_ROOT/context/project-profile.md" "$CLAUDE_PLUGIN_ROOT/agents/templates/reviewer-domains.json")
for domain in $DOMAINS; do
out="$STATE_ROOT/agents/${domain}.md"
BUNDLE_MODE=true render_template "$CLAUDE_PLUGIN_ROOT/agents/templates/reviewer-base.md" \
| substitute_domain_vars "$domain" "$CLAUDE_PLUGIN_ROOT/agents/templates/reviewer-domains.json" \
> "$out"
test -s "$out" || { echo "error: reviewer $domain rendered empty" >&2; exit 42; }
done
if grep -qiE 'react|vue|svelte|angular|swiftui|flutter' "$STATE_ROOT/context/project-profile.md"; then
render_agent_prompt "$CLAUDE_PLUGIN_ROOT/agents/designer.md" "$STATE_ROOT" > "$STATE_ROOT/.designer-prompt.md"
# Invoke designer agent. Writes to $STATE_ROOT/.claude/{design-tokens.json,design-system.md}.
fi
Source the relocate helpers:
source "$CLAUDE_PLUGIN_ROOT/scripts/lib/bootstrap-relocate.sh"
Run the transform:
bash "$CLAUDE_PLUGIN_ROOT/scripts/bootstrap-transform.sh" "$STATE_ROOT"
transform_rc=$?
case $transform_rc in
0) ;;
2) echo "error: transform usage error" >&2; exit 50 ;;
3)
echo "error: transform final assertion failed — see output above." >&2
echo " scratch preserved at $STATE_ROOT for debugging." >&2
exit 51
;;
*) echo "error: transform failed (exit $transform_rc)" >&2; exit 52 ;;
esac
If --dry-run was set (parsed in Phase 1), stop here. Report the scratch location to the user:
if [ "$BOOTSTRAP_DRY_RUN" = "true" ]; then
echo "Dry-run complete. Review the bundle at $STATE_ROOT/ before re-running without --dry-run."
exit 0
fi
Relocate atomically:
relocate_bundle "$STATE_ROOT" "." || exit $?
Append to .gitignore and clean up:
append_gitignore "."
cleanup_scratch "$STATE_ROOT"
count_md() { find "$1" -maxdepth 1 -type f -name '*.md' 2>/dev/null | wc -l | tr -d ' '; }
count_dir() { find "$1" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' '; }
DOCS=$(count_md ./docs)
CTX=$(count_md ./docs/context)
AGENTS=$(count_md ./.claude/agents)
DESIGN=$(count_dir ./.claude)
cat <<SUMMARY
Bootstrap complete.
Generated:
./docs/ $DOCS documents (PRD, TRD, ADRs, test plan)
./docs/context/ $CTX context files
./.claude/agents/ $AGENTS reviewer agents
./.claude/ $DESIGN design-system files (if UI surface detected)
Next steps:
- Review ./docs/PRD.md and ./docs/TRD.md
- Commit the bundle: git add docs/ .claude/ && git commit
- Use the reviewers: invoke them from Claude Code in this repo
SUMMARY
testing
Human acceptance testing — structured verification that work actually works. Run standalone or integrated in HITL review cycle.
devops
Task lifecycle management — skip, unblock, add, prioritize, info, and list tasks. Use when you need to manage individual tasks in the Nazgul pipeline.
development
Check the current state of a Nazgul autonomous loop. Use when asked about loop progress, task status, iteration count, review board status, or how the Nazgul loop is going.
development
Start or resume a Nazgul autonomous development loop. Use when user says "start nazgul", "run nazgul", "begin development", "resume the loop", or passes an objective for new work. Auto-detects project state — no arguments needed.