Skip to content

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:

  1. Project Type - Ask which type:
  2. angular-app - Angular SPA (Angular 21, PrimeNG, TypeScript, Vitest) ← default
  3. typescript-api - REST API service (Node.js, Express, TypeScript, Prisma)
  4. typescript-cli - Command-line tool (Node.js, TypeScript, Commander)
  5. rails-api - Rails API-only (Ruby, Rails, PostgreSQL)
  6. rails-full - Rails full-stack (Ruby, Rails, Hotwire, PostgreSQL)
  7. react-app - React SPA (React, TypeScript, Vite)
  8. nextjs-app - Next.js full-stack (Next.js, TypeScript, Tailwind)

  9. Project Name - The directory name (use current directory name if already in a project folder)

  10. Optional Features - Multi-select from:

  11. Docker support
  12. API documentation (OpenAPI)
  13. Authentication boilerplate
  14. Database migrations setup
  15. Error monitoring (Sentry)
  16. 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.json with zoneless change detection enabled ("zoneless": true in application builder options)
  • package.json with Angular 21, PrimeNG, PrimeIcons, PrimeFlex, Vitest, @angular-eslint
  • tsconfig.json with strict settings + strictTemplates: true
  • eslint.config.js with @angular-eslint
  • vitest.config.ts for Angular 21 zoneless testing
  • Feature-oriented directory structure: src/app/{core,shared,features}
  • PrimeNG preset file using definePreset() from @primeuix/themes based on Aura with VisiTrans design tokens
  • VisiTrans CSS variables file alongside PrimeNG tokens
  • app.config.ts with providePrimeNG() configuration

TypeScript Projects (typescript-api, typescript-cli, react-app, nextjs-app)

  • package.json with scripts and dependencies
  • tsconfig.json with strict settings
  • eslint.config.js or .eslintrc.js
  • prettier.config.js or .prettierrc
  • vitest.config.ts or jest.config.js

Rails Projects (rails-api, rails-full)

  • Gemfile with recommended gems
  • .rubocop.yml for Ruby style
  • Database configuration templates
  • strong_migrations initializer

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:

  1. Locate inbox: Check these paths in order: 00-inbox/, 01-docs/01-inbox/, docs/inbox/, docs/00-inbox/
  2. If inbox contains files (excluding README.md, .gitkeep):
  3. Read each document (up to 10 files, prioritizing .md then .txt then .pdf)
  4. Extract key themes, requirements, and stakeholder needs
  5. 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
  6. Display: "Found [N] inbox documents. Using as context for project setup."
  7. If inbox is empty or missing: Proceed normally (no change to current behavior)
  8. 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)

# Only if .git doesn't exist
git init

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:

~/.claude/hooks/vt-c-install-hooks.sh

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.js is retained as a polyfill fallback. The app uses zoneless change detection (see angular.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

// src/test-setup.ts
import '@analogjs/vitest-angular/setup-zone';

Optional Features

When selected, add:

Docker Support

  • Dockerfile
  • docker-compose.yml
  • .dockerignore
  • Update .gitignore

API Documentation (OpenAPI)

  • docs/openapi.yml template
  • 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.ts or config/initializers/sentry.rb
  • .env.example with SENTRY_DSN placeholder

Feature Flags Setup

  • TypeScript: Unleash or custom setup
  • Rails: Flipper gem
  • Basic toggle infrastructure

SpecKit Integration

If using SpecKit workflow:

  1. Create .specify/ directory and specs/ directory
  2. Move or create constitution.md in .specify/memory/
  3. 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 --force conceptually to overwrite (ask user first)

"Missing PRD"

  • Bootstrap works without PRD
  • Recommend creating PRD.md for better CLAUDE.md generation