Files
bootstrap/tools/bootstrap/init-project.sh
Jason Woltje 80c3680ccb feat: rename rails/ to tools/ and add service tool suites
Rename the `rails/` directory to `tools/` for agent discoverability —
agents frequently failed to locate helper scripts due to the non-intuitive
directory name. Add backward-compat symlink `rails/ → tools/`.

New tool suites:
- Authentik: auth-token, user-list, user-create, group-list, app-list,
  flow-list, admin-status (8 scripts)
- Coolify: team-list, project-list, service-list, service-status, deploy,
  env-set (7 scripts)
- Woodpecker: pipeline-list, pipeline-status, pipeline-trigger (3 stubs)
- GLPI: session-init, computer-list, ticket-list, ticket-create, user-list
  (6 scripts)
- Health: stack-health.sh — stack-wide connectivity check

Infrastructure:
- Shared credential loader at tools/_lib/credentials.sh
- install.sh creates symlink + chmod on tool scripts
- All ~253 rails/ path references updated across 68+ files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:51:39 -06:00

494 lines
17 KiB
Bash
Executable File

#!/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 <name> Project name (required)
-t, --type <type> Project type: nestjs-nextjs, django, generic (default: auto-detect)
-r, --repo <url> Git remote URL
-p, --prefix <prefix> Orchestrator task prefix (e.g., MS, UC)
-d, --description <desc> 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 <name:path> Service for Docker CI (repeatable, requires --cicd-docker)
--cicd-branches <list> 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)?$|\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