#!/bin/bash # init-project.sh - Bootstrap a project for AI-assisted development # Usage: init-project.sh [OPTIONS] # # Creates CLAUDE.md, AGENTS.md, and standard directories using templates. # Optionally initializes git labels and milestones. set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TEMPLATE_DIR="$HOME/.config/mosaic/templates/agent" GIT_SCRIPT_DIR="$HOME/.config/mosaic/tools/git" SEQUENTIAL_MCP_SCRIPT="$HOME/.config/mosaic/bin/mosaic-ensure-sequential-thinking" # Defaults PROJECT_NAME="" PROJECT_TYPE="" REPO_URL="" TASK_PREFIX="" PROJECT_DESCRIPTION="" SKIP_LABELS=false SKIP_CI=false CICD_DOCKER=false DRY_RUN=false declare -a CICD_SERVICES=() CICD_BRANCHES="main,develop" show_help() { cat <<'EOF' Usage: init-project.sh [OPTIONS] Bootstrap a project for AI-assisted development. Options: -n, --name Project name (required) -t, --type Project type: nestjs-nextjs, django, generic (default: auto-detect) -r, --repo Git remote URL -p, --prefix Orchestrator task prefix (e.g., MS, UC) -d, --description One-line project description --skip-labels Skip creating git labels and milestones --skip-ci Skip copying CI pipeline files --cicd-docker Generate Docker build/push/link pipeline steps --cicd-service Service for Docker CI (repeatable, requires --cicd-docker) --cicd-branches Branches for Docker builds (default: main,develop) --dry-run Show what would be created without creating anything -h, --help Show this help Examples: # Full bootstrap with auto-detection init-project.sh --name "My App" --description "A web application" # Specific type init-project.sh --name "My API" --type django --prefix MA # Dry run init-project.sh --name "Test" --type generic --dry-run # With Docker CI/CD pipeline init-project.sh --name "My App" --cicd-docker \ --cicd-service "my-api:src/api/Dockerfile" \ --cicd-service "my-web:src/web/Dockerfile" Project Types: nestjs-nextjs NestJS + Next.js monorepo (pnpm + TurboRepo) django Django project (pytest + ruff + mypy) typescript Standalone TypeScript/Next.js project python-fastapi Python FastAPI project (pytest + ruff + mypy + uv) python-library Python library/SDK (pytest + ruff + mypy + uv) generic Generic project (uses base templates) auto Auto-detect from project files (default) EOF exit 0 } # Parse arguments while [[ $# -gt 0 ]]; do case $1 in -n|--name) PROJECT_NAME="$2" shift 2 ;; -t|--type) PROJECT_TYPE="$2" shift 2 ;; -r|--repo) REPO_URL="$2" shift 2 ;; -p|--prefix) TASK_PREFIX="$2" shift 2 ;; -d|--description) PROJECT_DESCRIPTION="$2" shift 2 ;; --skip-labels) SKIP_LABELS=true shift ;; --skip-ci) SKIP_CI=true shift ;; --cicd-docker) CICD_DOCKER=true shift ;; --cicd-service) CICD_SERVICES+=("$2") shift 2 ;; --cicd-branches) CICD_BRANCHES="$2" shift 2 ;; --dry-run) DRY_RUN=true shift ;; -h|--help) show_help ;; *) echo "Unknown option: $1" >&2 echo "Run with --help for usage" >&2 exit 1 ;; esac done # Validate required args if [[ -z "$PROJECT_NAME" ]]; then echo "Error: --name is required" >&2 exit 1 fi # Auto-detect project type if not specified detect_project_type() { # Monorepo (pnpm + turbo or npm workspaces with NestJS) if [[ -f "pnpm-workspace.yaml" ]] || [[ -f "turbo.json" ]]; then echo "nestjs-nextjs" return fi if [[ -f "package.json" ]] && grep -q '"workspaces"' package.json 2>/dev/null; then echo "nestjs-nextjs" return fi # Django if [[ -f "manage.py" ]] && [[ -f "pyproject.toml" ]]; then echo "django" return fi # FastAPI if [[ -f "pyproject.toml" ]] && grep -q "fastapi" pyproject.toml 2>/dev/null; then echo "python-fastapi" return fi # Standalone TypeScript if [[ -f "tsconfig.json" ]] && [[ -f "package.json" ]]; then echo "typescript" return fi # Python library/tool if [[ -f "pyproject.toml" ]]; then echo "python-library" return fi echo "generic" } if [[ -z "$PROJECT_TYPE" || "$PROJECT_TYPE" == "auto" ]]; then PROJECT_TYPE=$(detect_project_type) echo "Auto-detected project type: $PROJECT_TYPE" fi # Derive defaults if [[ -z "$REPO_URL" ]]; then REPO_URL=$(git remote get-url origin 2>/dev/null || echo "") fi if [[ -z "$TASK_PREFIX" ]]; then # Generate prefix from project name initials TASK_PREFIX=$(echo "$PROJECT_NAME" | sed 's/[^A-Za-z ]//g' | awk '{for(i=1;i<=NF;i++) printf toupper(substr($i,1,1))}') if [[ -z "$TASK_PREFIX" ]]; then TASK_PREFIX="PRJ" fi fi if [[ -z "$PROJECT_DESCRIPTION" ]]; then PROJECT_DESCRIPTION="$PROJECT_NAME" fi PROJECT_DIR=$(basename "$(pwd)") # Detect quality gates, source dir, and stack info based on type case "$PROJECT_TYPE" in nestjs-nextjs) export QUALITY_GATES="pnpm typecheck && pnpm lint && pnpm test" export SOURCE_DIR="apps" export BUILD_COMMAND="pnpm build" export TEST_COMMAND="pnpm test" export LINT_COMMAND="pnpm lint" export TYPECHECK_COMMAND="pnpm typecheck" export FRONTEND_STACK="Next.js + React + TailwindCSS + Shadcn/ui" export BACKEND_STACK="NestJS + Prisma ORM" export DATABASE_STACK="PostgreSQL" export TESTING_STACK="Vitest + Playwright" export DEPLOYMENT_STACK="Docker + docker-compose" export CONFIG_FILES="turbo.json, pnpm-workspace.yaml, tsconfig.json" ;; django) export QUALITY_GATES="ruff check . && mypy . && pytest tests/" export SOURCE_DIR="src" export BUILD_COMMAND="pip install -e ." export TEST_COMMAND="pytest tests/" export LINT_COMMAND="ruff check ." export TYPECHECK_COMMAND="mypy ." export FRONTEND_STACK="N/A" export BACKEND_STACK="Django / Django REST Framework" export DATABASE_STACK="PostgreSQL" export TESTING_STACK="pytest + pytest-django" export DEPLOYMENT_STACK="Docker + docker-compose" export CONFIG_FILES="pyproject.toml" export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g') ;; typescript) PKG_MGR="npm" [[ -f "pnpm-lock.yaml" ]] && PKG_MGR="pnpm" [[ -f "yarn.lock" ]] && PKG_MGR="yarn" export QUALITY_GATES="$PKG_MGR run lint && $PKG_MGR run typecheck && $PKG_MGR test" export SOURCE_DIR="src" export BUILD_COMMAND="$PKG_MGR run build" export TEST_COMMAND="$PKG_MGR test" export LINT_COMMAND="$PKG_MGR run lint" export TYPECHECK_COMMAND="npx tsc --noEmit" export FRAMEWORK="TypeScript" export PACKAGE_MANAGER="$PKG_MGR" export FRONTEND_STACK="N/A" export BACKEND_STACK="N/A" export DATABASE_STACK="N/A" export TESTING_STACK="Vitest or Jest" export DEPLOYMENT_STACK="TBD" export CONFIG_FILES="tsconfig.json, package.json" # Detect Next.js if grep -q '"next"' package.json 2>/dev/null; then export FRAMEWORK="Next.js" export FRONTEND_STACK="Next.js + React" fi ;; python-fastapi) export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g') export QUALITY_GATES="uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy src/ && uv run pytest --cov" export SOURCE_DIR="src" export BUILD_COMMAND="uv sync --all-extras" export TEST_COMMAND="uv run pytest --cov" export LINT_COMMAND="uv run ruff check src/ tests/" export TYPECHECK_COMMAND="uv run mypy src/" export FRONTEND_STACK="N/A" export BACKEND_STACK="FastAPI" export DATABASE_STACK="TBD" export TESTING_STACK="pytest + httpx" export DEPLOYMENT_STACK="Docker" export CONFIG_FILES="pyproject.toml" ;; python-library) export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g') export QUALITY_GATES="uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy src/ && uv run pytest --cov" export SOURCE_DIR="src" export BUILD_COMMAND="uv sync --all-extras" export TEST_COMMAND="uv run pytest --cov" export LINT_COMMAND="uv run ruff check src/ tests/" export TYPECHECK_COMMAND="uv run mypy src/" export BUILD_SYSTEM="hatchling" export FRONTEND_STACK="N/A" export BACKEND_STACK="N/A" export DATABASE_STACK="N/A" export TESTING_STACK="pytest" export DEPLOYMENT_STACK="PyPI / Gitea Packages" export CONFIG_FILES="pyproject.toml" ;; *) export QUALITY_GATES="echo 'No quality gates configured — update CLAUDE.md'" export SOURCE_DIR="src" export BUILD_COMMAND="echo 'No build command configured'" export TEST_COMMAND="echo 'No test command configured'" export LINT_COMMAND="echo 'No lint command configured'" export TYPECHECK_COMMAND="echo 'No typecheck command configured'" export FRONTEND_STACK="TBD" export BACKEND_STACK="TBD" export DATABASE_STACK="TBD" export TESTING_STACK="TBD" export DEPLOYMENT_STACK="TBD" export CONFIG_FILES="TBD" ;; esac # Export common variables export PROJECT_NAME export PROJECT_DESCRIPTION export PROJECT_DIR export REPO_URL export TASK_PREFIX echo "=== Project Bootstrap ===" echo " Name: $PROJECT_NAME" echo " Type: $PROJECT_TYPE" echo " Prefix: $TASK_PREFIX" echo " Description: $PROJECT_DESCRIPTION" echo " Repo: ${REPO_URL:-'(not set)'}" echo " Directory: $(pwd)" echo "" # Select template directory STACK_TEMPLATE_DIR="$TEMPLATE_DIR/projects/$PROJECT_TYPE" if [[ ! -d "$STACK_TEMPLATE_DIR" ]]; then STACK_TEMPLATE_DIR="$TEMPLATE_DIR" echo "No stack-specific templates found for '$PROJECT_TYPE', using generic templates." fi if [[ "$DRY_RUN" == true ]]; then echo "[DRY RUN] Would create:" echo " - Validate sequential-thinking MCP hard requirement" echo " - CLAUDE.md (from $STACK_TEMPLATE_DIR/CLAUDE.md.template)" echo " - AGENTS.md (from $STACK_TEMPLATE_DIR/AGENTS.md.template)" echo " - docs/scratchpads/" echo " - docs/reports/qa-automation/{pending,in-progress,done,escalated}" echo " - docs/reports/deferred/" echo " - docs/tasks/" echo " - docs/releases/" echo " - docs/templates/" if [[ "$SKIP_CI" != true ]]; then echo " - .woodpecker/codex-review.yml" echo " - .woodpecker/schemas/*.json" fi if [[ "$SKIP_LABELS" != true ]]; then echo " - Standard git labels (epic, feature, bug, task, documentation, security, breaking)" echo " - Milestone: 0.0.1 - Pre-MVP Foundation" echo " - Milestone policy: 0.0.x pre-MVP, 0.1.0 for MVP release" fi if [[ "$CICD_DOCKER" == true ]]; then echo " - Docker build/push/link steps appended to .woodpecker.yml" for svc in "${CICD_SERVICES[@]}"; do echo " - docker-build-${svc%%:*}" done echo " - link-packages" fi exit 0 fi # Enforce sequential-thinking MCP hard requirement. if [[ ! -x "$SEQUENTIAL_MCP_SCRIPT" ]]; then echo "Error: Missing sequential-thinking setup helper: $SEQUENTIAL_MCP_SCRIPT" >&2 echo "Install/repair Mosaic at ~/.config/mosaic before bootstrapping projects." >&2 exit 1 fi if "$SEQUENTIAL_MCP_SCRIPT" >/dev/null 2>&1; then echo "Verified sequential-thinking MCP configuration" else echo "Error: sequential-thinking MCP setup failed (hard requirement)." >&2 echo "Run: $SEQUENTIAL_MCP_SCRIPT" >&2 exit 1 fi # Create CLAUDE.md if [[ -f "CLAUDE.md" ]]; then echo "CLAUDE.md already exists — skipping (rename or delete to recreate)" else if [[ -f "$STACK_TEMPLATE_DIR/CLAUDE.md.template" ]]; then envsubst < "$STACK_TEMPLATE_DIR/CLAUDE.md.template" > CLAUDE.md echo "Created CLAUDE.md" else echo "Warning: No CLAUDE.md template found at $STACK_TEMPLATE_DIR" >&2 fi fi # Create AGENTS.md if [[ -f "AGENTS.md" ]]; then echo "AGENTS.md already exists — skipping (rename or delete to recreate)" else if [[ -f "$STACK_TEMPLATE_DIR/AGENTS.md.template" ]]; then envsubst < "$STACK_TEMPLATE_DIR/AGENTS.md.template" > AGENTS.md echo "Created AGENTS.md" else echo "Warning: No AGENTS.md template found at $STACK_TEMPLATE_DIR" >&2 fi fi # Create directories mkdir -p \ docs/scratchpads \ docs/reports/qa-automation/pending \ docs/reports/qa-automation/in-progress \ docs/reports/qa-automation/done \ docs/reports/qa-automation/escalated \ docs/reports/deferred \ docs/tasks \ docs/releases \ docs/templates echo "Created docs/scratchpads/, docs/reports/*, docs/tasks/, docs/releases/, docs/templates/" # Set up CI/CD pipeline if [[ "$SKIP_CI" != true ]]; then CODEX_DIR="$HOME/.config/mosaic/tools/codex" if [[ -d "$CODEX_DIR/woodpecker" ]]; then mkdir -p .woodpecker/schemas cp "$CODEX_DIR/woodpecker/codex-review.yml" .woodpecker/ cp "$CODEX_DIR/schemas/"*.json .woodpecker/schemas/ echo "Created .woodpecker/ with Codex review pipeline" else echo "Codex pipeline templates not found — skipping CI setup" fi fi # Generate Docker build/push/link pipeline steps if [[ "$CICD_DOCKER" == true ]]; then CICD_SCRIPT="$HOME/.config/mosaic/tools/cicd/generate-docker-steps.sh" if [[ -x "$CICD_SCRIPT" ]]; then # Parse org and repo from git remote CICD_REGISTRY="" CICD_ORG="" CICD_REPO_NAME="" if [[ -n "$REPO_URL" ]]; then # Extract host from https://host/org/repo.git or git@host:org/repo.git CICD_REGISTRY=$(echo "$REPO_URL" | sed -E 's|https?://([^/]+)/.*|\1|; s|git@([^:]+):.*|\1|') CICD_ORG=$(echo "$REPO_URL" | sed -E 's|https?://[^/]+/([^/]+)/.*|\1|; s|git@[^:]+:([^/]+)/.*|\1|') CICD_REPO_NAME=$(echo "$REPO_URL" | sed -E 's|\.git$||' | sed -E 's|.*/([^/]+)$|\1|') fi if [[ -n "$CICD_REGISTRY" && -n "$CICD_ORG" && -n "$CICD_REPO_NAME" && ${#CICD_SERVICES[@]} -gt 0 ]]; then # Build service args SVC_ARGS="" for svc in "${CICD_SERVICES[@]}"; do SVC_ARGS="$SVC_ARGS --service $svc" done echo "" echo "Generating Docker CI/CD pipeline steps..." # Add kaniko_setup anchor to variables section if .woodpecker.yml exists if [[ -f ".woodpecker.yml" ]]; then # Append Docker steps to existing pipeline "$CICD_SCRIPT" \ --registry "$CICD_REGISTRY" \ --org "$CICD_ORG" \ --repo "$CICD_REPO_NAME" \ $SVC_ARGS \ --branches "$CICD_BRANCHES" >> .woodpecker.yml echo "Appended Docker build/push/link steps to .woodpecker.yml" else echo "Warning: No .woodpecker.yml found — generate quality gates first, then re-run with --cicd-docker" >&2 fi else if [[ ${#CICD_SERVICES[@]} -eq 0 ]]; then echo "Warning: --cicd-docker requires at least one --cicd-service" >&2 else echo "Warning: Could not parse registry/org/repo from git remote — specify --repo" >&2 fi fi else echo "Docker CI/CD generator not found at $CICD_SCRIPT — skipping" >&2 fi fi # Initialize labels and milestones if [[ "$SKIP_LABELS" != true ]]; then LABEL_SCRIPT="$SCRIPT_DIR/init-repo-labels.sh" if [[ -x "$LABEL_SCRIPT" ]]; then echo "" echo "Initializing git labels and milestones..." "$LABEL_SCRIPT" else echo "Label init script not found — skipping label setup" fi fi echo "" echo "=== Bootstrap Complete ===" echo "" echo "Next steps:" echo " 1. Review and customize CLAUDE.md" echo " 2. Review and customize AGENTS.md" echo " 3. Update quality gate commands if needed" echo " 4. Commit: git add CLAUDE.md AGENTS.md docs/ .woodpecker/ && git commit -m 'feat: Bootstrap project for AI development'" if [[ "$SKIP_CI" != true ]]; then echo " 5. Add 'codex_api_key' secret to Woodpecker CI" fi if [[ "$CICD_DOCKER" == true ]]; then echo " 6. Add 'gitea_username' and 'gitea_token' secrets to Woodpecker CI" echo " (token needs package:write scope)" fi