vt-d-bootstrap¶
Scaffold a new project with best-practice structure, configurations, and documentation. Use when starting a new project or adding toolkit structure to an existing folder.
Plugin: vt-product-dev
Category: Project Setup
Command: /vt-d-bootstrap
Bootstrap Skill¶
Create a new project with industry best-practice structure, tooling, and documentation.
Invocation¶
# Interactive mode - prompts for all options
/vt-d-bootstrap
# With project type and name (angular-app is default)
/vt-d-bootstrap angular-app my-frontend
/vt-d-bootstrap typescript-api my-service
/vt-d-bootstrap rails-api my-app
Execution Instructions¶
When this skill is invoked, follow these steps:
Step 1: Gather Requirements¶
If arguments were not provided, use AskUserQuestion to gather:
- Project Type - Ask which type:
angular-app- Angular SPA (Angular 21, PrimeNG, TypeScript, Vitest) ← defaulttypescript-api- REST API service (Node.js, Express, TypeScript, Prisma)typescript-cli- Command-line tool (Node.js, TypeScript, Commander)rails-api- Rails API-only (Ruby, Rails, PostgreSQL)rails-full- Rails full-stack (Ruby, Rails, Hotwire, PostgreSQL)react-app- React SPA (React, TypeScript, Vite)-
nextjs-app- Next.js full-stack (Next.js, TypeScript, Tailwind) -
Project Name - The directory name (use current directory name if already in a project folder)
-
Optional Features - Multi-select from:
- Docker support
- API documentation (OpenAPI)
- Authentication boilerplate
- Database migrations setup
- Error monitoring (Sentry)
- Feature flags setup
Step 2: Check Current Directory¶
Before creating structure:
- If current directory is empty OR only contains PRD.md/Constitution files → use current directory
- If project-name argument provided AND current directory is different → create new subdirectory
- If .specify/ exists → preserve existing spec files
Step 3: Create Directory Structure¶
Create the following structure (adapt paths based on Step 2):
project-root/
├── .claude/
│ └── skills/
│ └── .gitkeep # Repo-local skill slot (SPEC-123 US-2)
├── 00-inbox/ # Drop zone for raw input (created if not present)
│ └── README.md
├── .github/
│ ├── workflows/
│ │ ├── ci.yml
│ │ └── deploy.yml
│ └── PULL_REQUEST_TEMPLATE.md
├── 01-docs/
│ ├── solutions/
│ │ └── patterns/
│ │ └── critical-patterns.md
│ ├── journal/
│ │ └── .gitkeep
│ ├── adr/
│ │ └── .gitkeep
│ └── runbooks/
│ └── .gitkeep
├── src/ # Or app/ for Rails
├── tests/ # Or spec/ for Rails
├── scripts/
├── .env.example
├── .gitignore
├── CLAUDE.md
└── README.md
Step 4: Create Configuration Files¶
Based on project type, create appropriate configs:
All Projects¶
.gitignore - Comprehensive ignore file for the stack (includes .env, .env.local, .env.*.local — SPEC-114 / FR-12)
.env.example - Environment variables template with comments
.claudeignore - Claude Code context exclusions (from templates/scaffold/base/.claudeignore). Enforced by the claudeignore-guard.sh PreToolUse hook — SPEC-114 / FR-17. Hides credentials, secret directories, and context-polluting artifacts from Claude's tool operations.
CLAUDE.md - Project-specific instructions (see template below)
README.md - Getting started documentation
Angular Projects (angular-app)¶
angular.jsonwith zoneless change detection enabled ("zoneless": truein application builder options)package.jsonwith Angular 21, PrimeNG, PrimeIcons, PrimeFlex, Vitest, @angular-eslinttsconfig.jsonwith strict settings +strictTemplates: trueeslint.config.jswith @angular-eslintvitest.config.tsfor Angular 21 zoneless testing- Feature-oriented directory structure:
src/app/{core,shared,features} - PrimeNG preset file using
definePreset()from@primeuix/themesbased on Aura with VisiTrans design tokens - VisiTrans CSS variables file alongside PrimeNG tokens
app.config.tswithprovidePrimeNG()configuration
TypeScript Projects (typescript-api, typescript-cli, react-app, nextjs-app)¶
package.jsonwith scripts and dependenciestsconfig.jsonwith strict settingseslint.config.jsor.eslintrc.jsprettier.config.jsor.prettierrcvitest.config.tsorjest.config.js
Rails Projects (rails-api, rails-full)¶
Gemfilewith recommended gems.rubocop.ymlfor Ruby style- Database configuration templates
strong_migrationsinitializer
Step 5: Create GitHub Workflows¶
.github/workflows/ci.yml:
name: vt-d-bootstrap
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup [Language]
uses: [setup-action]
- name: Install dependencies
run: [install-command]
- name: Lint
run: [lint-command]
- name: Test
run: [test-command]
Step 5.5: Scan Inbox for Context¶
Before generating CLAUDE.md and README.md, check for existing inbox contents that can inform the generated files:
- Locate inbox: Check these paths in order:
00-inbox/,01-docs/01-inbox/,docs/inbox/,docs/00-inbox/ - If inbox contains files (excluding README.md, .gitkeep):
- Read each document (up to 10 files, prioritizing .md then .txt then .pdf)
- Extract key themes, requirements, and stakeholder needs
- Use these as additional context when generating:
- PRD.md: Incorporate project goals and requirements from inbox docs
- CLAUDE.md (Step 6): Add project-specific context from inbox materials
- README.md: Reflect the project's purpose as described in inbox documents
- Display: "Found [N] inbox documents. Using as context for project setup."
- If inbox is empty or missing: Proceed normally (no change to current behavior)
- NEVER move, delete, or modify inbox documents during bootstrap
Step 6: Create CLAUDE.md¶
Use this template, filled with project-specific values:
# CLAUDE.md
## Project Overview
[Brief description based on PRD if available]
## Tech Stack
- [Framework from project type]
- [Database if applicable]
- [Key libraries]
## Development Commands
\`\`\`bash
# Start development server
[npm run dev / rails s / etc.]
# Run tests
[npm test / rspec / etc.]
# Run linter
[npm run lint / rubocop / etc.]
\`\`\`
## Architecture
[Based on project type - API routes, components, etc.]
## Key Files
- `src/index.ts` - Application entry point
- `src/routes/` - API endpoints
- `src/models/` - Data models
## Standards
Global standards from ~/.claude/CLAUDE.md apply automatically.
Project-specific conventions are documented below.
## Conventions
- Follow [TypeScript/Ruby] naming conventions
- Tests required for new features
- PR reviews required before merge
## What NOT to Change
<!--
This section is read by /simplify to prevent false positives.
List code patterns that look like simplification candidates but must be preserved.
Be specific: /simplify cannot distinguish intentional complexity from accidental complexity.
Categories to consider:
- Defensive code that looks redundant (null checks for external API responses)
- Code separated for future divergence (intentionally duplicated logic)
- Performance-critical code written imperatively for speed
- Debugging artifacts (named variables that exist for debugger breakpoints)
- Domain-specific patterns that conflict with generic best practices
-->
- Intentional defensive null checks (e.g., API response validation, external webhook handling)
- Error boundary components and fallback UI
- JSDoc/TSDoc comments on public APIs
- Security-related code patterns (input sanitization, auth guards)
- Regulatory compliance checks and data validation patterns
- Named function expressions kept for stack trace readability
- [Add project-specific items here]
Step 7: Create Toolkit Integration Files¶
01-docs/solutions/patterns/critical-patterns.md:
# Critical Patterns
This file contains patterns that must be checked during code review and bug investigation.
## Patterns
*No patterns recorded yet. Patterns will be added as bugs are fixed and solutions documented.*
## How This File Grows
1. Fix a bug using `/vt-d-investigate-bug`
2. Document the solution with `/vt-c-compound`
3. If the bug represents a recurring pattern, it gets added here
4. All orchestrators check this file before starting work
Step 8: Handle Existing Files¶
If the directory already contains:
- PRD.md → Keep it, reference in CLAUDE.md
- Constitution → Move to .specify/memory/constitution.md if not already there
- spec.md → Move to active spec's specs/[N]-feature/spec.md if not already there
- 00-inbox/ → Keep it and all contents, do not overwrite README.md if present
- Legacy inbox (01-docs/01-inbox/, docs/inbox/, docs/00-inbox/) → Keep, do not migrate. Note: "Standard inbox location is 00-inbox/ at project root."
Step 9: Initialize Git (if not already)¶
Step 10: Initialize Git LFS (if governed)¶
If .repo-manifest.yaml exists and defines binary_rules:
# Check if git lfs is available
command -v git-lfs &>/dev/null
# If available and not already initialized:
git lfs install
# Ensure .gitattributes has LFS rules for declared extensions
# (Only add rules not already present)
This catches the case where governance was set up during /vt-d-scaffold but /vt-d-1-bootstrap adds a tech stack later that may produce binary artifacts.
Step 11: Install Git Hooks¶
Install the pre-commit hook to enforce branch-based workflow:
This installs the pre-commit hook that:
- Blocks direct commits to main and master
- Enforces feature branch workflow
- Provides helpful error messages with branch naming suggestions
Step 12: Provide Next Steps¶
Output to user:
Project scaffolded successfully!
Next steps:
1. cd [project-name] (if new directory created)
2. [npm ci --ignore-scripts / bundle install / etc.]
3. npm audit --audit-level=high (for npm projects)
4. cp .env.example .env
5. [npm run dev / rails s / etc.]
Toolkit integration ready:
- /vt-d-wf-plan - Create implementation plan
- /vt-d-wf-review - Run code review
- /vt-c-journal - Capture learnings
- /vt-d-finalize-check - Pre-merge verification
Your project is ready!
File Templates¶
TypeScript API - package.json¶
{
"name": "{{PROJECT_NAME}}",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "vitest",
"lint": "eslint src/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"express": "^4.18.2",
"zod": "^3.22.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.10.0",
"eslint": "^8.55.0",
"prettier": "^3.1.0",
"tsx": "^4.6.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0"
}
}
TypeScript API - tsconfig.json¶
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Rails API - Gemfile additions¶
# Add to Gemfile
gem 'strong_migrations'
gem 'rack-cors'
gem 'oj' # Fast JSON
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'rubocop', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
end
Angular App - package.json¶
{
"name": "{{PROJECT_NAME}}",
"version": "0.1.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "vitest",
"test:watch": "vitest --watch",
"lint": "ng lint",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@angular/animations": "^21.0.0",
"@angular/common": "^21.0.0",
"@angular/compiler": "^21.0.0",
"@angular/core": "^21.0.0",
"@angular/forms": "^21.0.0",
"@angular/platform-browser": "^21.0.0",
"@angular/router": "^21.0.0",
"primeng": "^20.0.0",
"primeicons": "^7.0.0",
"primeflex": "^4.0.0",
"@primeuix/themes": "^2.0.0",
"rxjs": "^7.8.0",
"zone.js": "^0.15.0"
},
"devDependencies": {
"@angular/build": "^21.0.0",
"@angular/cli": "^21.0.0",
"@angular/compiler-cli": "^21.0.0",
"@angular-eslint/builder": "^19.0.0",
"@angular-eslint/eslint-plugin": "^19.0.0",
"@angular-eslint/eslint-plugin-template": "^19.0.0",
"@angular-eslint/template-parser": "^19.0.0",
"eslint": "^9.0.0",
"typescript": "^5.7.0",
"vitest": "^3.0.0",
"@analogjs/vitest-angular": "^1.0.0"
}
}
Note:
zone.jsis retained as a polyfill fallback. The app uses zoneless change detection (seeangular.json"zoneless": true).
Angular App - tsconfig.json¶
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "dom"],
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
Angular App - angular.json (key sections)¶
{
"projects": {
"{{PROJECT_NAME}}": {
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"zoneless": true,
"outputPath": "dist/{{PROJECT_NAME}}",
"index": "src/index.html",
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"styles": ["src/styles.css"]
}
}
}
}
}
}
Angular App - app.config.ts¶
// src/app/app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import { vtPreset } from './core/theme/vt-preset';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
provideRouter(routes),
provideHttpClient(withInterceptorsFromDi()),
provideAnimationsAsync(),
providePrimeNG({
theme: {
preset: vtPreset,
options: {
// Dark is the DEFAULT (SPEC-157: theme polarity is a property of the
// surface, not the brand). The :not() form is required because our
// polarity is inverted relative to PrimeNG's: PrimeNG assumes light is
// the default and dark the opt-in, while we ship dark as :root and
// light as the [data-theme="light"] opt-in. Expressed positively
// (darkModeSelector: '[data-theme="dark"]') the app renders LIGHT
// until something sets that attribute — and nothing does, because
// dark needs no attribute.
//
// Never set this to false — that hard-disables dark mode, which on a
// dark-by-default token layer produces light PrimeNG components on a
// dark page. See the VERIFY steps in the generated preset header.
darkModeSelector: ':root:not([data-theme="light"])',
},
},
}),
],
};
Angular App - Design System Setup (Generated)¶
Generate VisiTrans design system files from the canonical token source instead of copy-pasting templates:
# PrimeNG preset
python3 TOOLKIT_ROOT/scripts/design_system/generate_primeng_preset.py --product visitrans --output src/app/core/theme/vt-preset.ts
# CSS variables
python3 TOOLKIT_ROOT/scripts/design_system/generate_css_variables.py --product visitrans --output src/styles/vt-variables.css
The generators read from TOOLKIT_ROOT/configs/visitrans-cd-tokens.yaml (single source of truth). Use --product visimatch|visifair|visiarea for product-specific themes.
The generated vt-preset.ts ships both colorScheme.light and colorScheme.dark (each with its own surface ramp and formField block) plus the inverted darkModeSelector wiring shown in app.config.ts above. There is deliberately no inline preset here to copy: a second copy drifts from the generator silently — an earlier revision of this file carried one whose orange ramp and dark form fields had already diverged. Read the generated file, or run the generator with no --output to print it to stdout.
Angular App - CSS Variables¶
generate_css_variables.py (run above) is the canonical source for this file — it
emits the full --vt-* set from the token YAML. The excerpt below shows only the
handful of variables you are most likely to reference by hand in non-PrimeNG CSS;
regenerate rather than hand-maintaining the whole set.
/* src/styles.css */
/* Google Fonts: convenient for prototyping. For production, self-host Inter from
public/fonts/Inter/ to avoid third-party tracking (GDPR/privacy). */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
:root {
/* VisiTrans Primary Colors (for non-PrimeNG contexts) */
/* --vt-black is the LOGO base and stays pure #000000. Dark *surfaces* are
anthracite (--vt-anthracite: #101216); dark *logos* are black. Never
substitute one for the other — a logo drawn on #101216 loses its edge, and
an anthracite surface named "black" invites the next editor to swap in
#000000 and flatten the whole ramp. See the design-system skill's
colors_and_type.css for the full ramp. */
--vt-black: #000000;
--vt-anthracite: #101216;
/* SCREEN orange is #FF9E00, not the print/CD #FC9E00 — the CMYK-derived print
value reads muddy on an emissive display. This is the value the generators
emit and the design system locks. Do not "correct" it to #FC9E00; that is the
print path's value only. See docs/decisions/2026-07-21-brand-orange-screen-vs-print.md. */
--vt-orange: #FF9E00;
--vt-white: #FFFFFF;
/* VisiTrans Gray Scale */
--vt-gray-90: #2D2D2D;
--vt-gray-70: #5C5C5C;
--vt-gray-50: #8A8A8A;
--vt-gray-30: #B8B8B8;
--vt-gray-10: #E6E6E6;
/* VisiTrans Orange Scale */
--vt-orange-dark: #CC5200;
--vt-orange-light: #FF944D;
/* VisiTrans Action Color — SCREEN magenta is pure #FF00FF (print/CD is #E6007E). */
--vt-magenta: #FF00FF;
/* Typography */
--vt-font-family: 'Inter', sans-serif;
/* Spacing */
--vt-space-xs: 4px;
--vt-space-sm: 8px;
--vt-space-md: 16px;
--vt-space-lg: 24px;
--vt-space-xl: 32px;
--vt-space-2xl: 48px;
}
/* Dark is the default surface (SPEC-157: theme polarity is a property of the
surface, not the brand). Marketing sites and logged-out product surfaces stay
on this branch; only a logged-in product UI opts into light, by setting
data-theme="light" on <html>. This must agree with darkModeSelector in
app.config.ts above — if one says dark and the other light, PrimeNG's
components and the page they sit on disagree. */
body {
font-family: var(--vt-font-family);
color: var(--vt-white);
background-color: var(--vt-anthracite);
}
/* Specificity 0,2,1 beats the bare `body` above on specificity rather than on
source order, so it survives a re-ordering of this stylesheet. */
:root[data-theme="light"] body {
color: var(--vt-black);
background-color: var(--vt-white);
}
Angular App - PrimeNG Import Best Practice¶
Import individual PrimeNG components rather than full modules to enable tree-shaking in PrimeNG v20+:
// Preferred: individual component imports (smaller bundles)
import { Button } from 'primeng/button';
import { Card } from 'primeng/card';
// Avoid in production: full module imports increase bundle size
// import { ButtonModule } from 'primeng/button';
Angular App - Directory Structure¶
src/
├── app/
│ ├── core/ # Singleton services, guards, interceptors
│ │ ├── theme/
│ │ │ └── vt-preset.ts # PrimeNG VisiTrans preset
│ │ ├── interceptors/
│ │ └── guards/
│ ├── shared/ # Reusable components, pipes, directives
│ │ ├── components/
│ │ ├── pipes/
│ │ └── directives/
│ ├── features/ # Feature modules (lazy-loaded routes)
│ │ └── dashboard/
│ │ ├── dashboard.component.ts
│ │ ├── dashboard.component.html
│ │ └── dashboard.routes.ts
│ ├── app.component.ts
│ ├── app.config.ts
│ └── app.routes.ts
├── styles.css
├── main.ts
└── index.html
Angular App - vitest.config.ts¶
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['src/test-setup.ts'],
include: ['src/**/*.spec.ts'],
},
});
Angular App - test-setup.ts¶
Optional Features¶
When selected, add:
Docker Support¶
Dockerfiledocker-compose.yml.dockerignore- Update
.gitignore
API Documentation (OpenAPI)¶
docs/openapi.ymltemplate- Add swagger-ui or redoc dependency
Authentication Boilerplate¶
- TypeScript: passport.js or jose JWT setup
- Rails: devise or has_secure_password setup
Database Migrations Setup¶
- TypeScript: Prisma schema and initial migration
- Rails: Database config and example migration
Error Monitoring (Sentry)¶
- Sentry SDK dependency
src/lib/sentry.tsorconfig/initializers/sentry.rb.env.examplewith SENTRY_DSN placeholder
Feature Flags Setup¶
- TypeScript: Unleash or custom setup
- Rails: Flipper gem
- Basic toggle infrastructure
SpecKit Integration¶
If using SpecKit workflow:
- Create
.specify/directory andspecs/directory - Move or create
constitution.mdin.specify/memory/ - Scaffold will be validated against spec during
/vt-d-wf-review
Troubleshooting¶
"Directory not empty"¶
- Bootstrap will preserve existing files
- Only creates files that don't exist
- Use
--forceconceptually to overwrite (ask user first)
"Missing PRD"¶
- Bootstrap works without PRD
- Recommend creating PRD.md for better CLAUDE.md generation