diff --git a/.claude/commands/README.md b/.claude/commands/README.md index 48978f1e..7dd23977 100644 --- a/.claude/commands/README.md +++ b/.claude/commands/README.md @@ -35,6 +35,105 @@ This directory contains pre-configured commands that provide automated workflows /similarity-analysis path=src threshold=0.9 ``` +#### `code-complexity-check.md` + +**Purpose**: Analyze code complexity and identify refactoring candidates +**Features**: + +- Cyclomatic complexity analysis +- Function length and nesting depth detection +- Complexity thresholds (low/medium/high/critical) +- Refactoring recommendations +- CI integration with strict mode + +**Usage**: + +``` +/code-complexity-check +/code-complexity-check --threshold 15 +/code-complexity-check --strict +``` + +### Quality & Testing + +#### `pre-pr-checklist.md` + +**Purpose**: Automate comprehensive checks before creating a pull request +**Features**: + +- Sequential quality checks (lint, format, test, etc.) +- PR size estimation and labeling +- Linked issues verification +- Branch status validation +- Merge conflict detection + +**Usage**: + +``` +/pre-pr-checklist +/pre-pr-checklist --skip-tests +/pre-pr-checklist --verbose +``` + +#### `test-coverage-trend.md` + +**Purpose**: Track and visualize test coverage trends over time +**Features**: + +- Historical coverage tracking +- Trend analysis with ASCII graphs +- Threshold alerts (70% coverage) +- Per-file coverage breakdown +- CSV export for external analysis + +**Usage**: + +``` +/test-coverage-trend +/test-coverage-trend --days 30 +/test-coverage-trend --graph +``` + +### Security + +#### `dependency-health-check.md` + +**Purpose**: Comprehensive dependency health analysis +**Features**: + +- npm package updates detection +- Security vulnerability scanning (`npm audit`) +- Deprecated package identification +- License compliance checking +- Health score calculation + +**Usage**: + +``` +/dependency-health-check +/dependency-health-check --strict +/dependency-health-check --json +``` + +#### `security-credential-scan.md` + +**Purpose**: Scan repository for hardcoded credentials and secrets +**Features**: + +- API key, token, and password detection +- Private key and certificate scanning +- .env file validation +- False positive reduction +- Auto-fix capabilities + +**Usage**: + +``` +/security-credential-scan +/security-credential-scan --fix +/security-credential-scan --strict +``` + ### Development Environment #### `setup-husky.md` @@ -47,15 +146,94 @@ This directory contains pre-configured commands that provide automated workflows - Code quality gate enforcement - Development workflow integration +#### `container-health.md` + +**Purpose**: Verify DevContainer environment health and configuration +**Features**: + +- Tool availability verification +- Version checking (Node.js, npm, Claude Code) +- Configuration validation +- System resource monitoring +- Auto-fix capabilities + +**Usage**: + +``` +/container-health +/container-health --fix +/container-health --verbose +``` + +#### `setup-new-repo.md` + +**Purpose**: Bootstrap a new repository with this configuration +**Features**: + +- DevContainer setup +- Git configuration (commitlint, Husky) +- GitHub Actions workflows +- Development tools (ESLint, Prettier, Jest) +- Documentation templates + +**Usage**: + +``` +/setup-new-repo /path/to/new/repo +/setup-new-repo --minimal +/setup-new-repo --no-devcontainer +``` + +### Repository Management + +#### `branch-cleanup.md` + +**Purpose**: Clean up merged and stale branches +**Features**: + +- Merged branch detection and deletion +- Stale branch identification (30+ days) +- Protected branch exclusion +- Interactive confirmation +- Remote branch cleanup support + +**Usage**: + +``` +/branch-cleanup +/branch-cleanup --dry-run +/branch-cleanup --remote +``` + +#### `changelog-generator.md` + +**Purpose**: Generate CHANGELOG from Conventional Commits history +**Features**: + +- Automatic commit grouping by type +- GitHub commit and PR links +- Breaking changes highlighting +- Version detection +- Keep a Changelog format + +**Usage**: + +``` +/changelog-generator +/changelog-generator --since v1.0.0 +/changelog-generator --contributors +``` + #### `setup-team-protection.md` -**Purpose**: Configures GitHub repository protection for team development +**Purpose**: Setup GitHub repository protection rules for team development **Features**: -- Branch protection rules (no direct push, required reviews) +- Branch protection (no direct push, required reviews) - Required status checks (CI passing) - Repository settings (squash merge, auto-delete branches) - Security features (Dependabot, vulnerability alerts) +- Configurable reviewer count and enforcement **Usage**: diff --git a/.claude/commands/changelog-generator.md b/.claude/commands/changelog-generator.md new file mode 100644 index 00000000..3e89ba3d --- /dev/null +++ b/.claude/commands/changelog-generator.md @@ -0,0 +1,153 @@ +# Changelog Generator Command + +Generate CHANGELOG.md from Conventional Commits history. + +## Usage + +```bash +/changelog-generator +/changelog-generator --since v1.0.0 +/changelog-generator --output CHANGELOG.md +``` + +## What It Does + +This command generates a structured CHANGELOG from git commit history: + +### Commit Grouping + +Automatically groups commits by type: + +- **Features** (feat): New features and enhancements +- **Bug Fixes** (fix): Bug fixes and patches +- **Performance** (perf): Performance improvements +- **Breaking Changes**: BREAKING CHANGE notes +- **Documentation** (docs): Documentation updates +- **Other**: ci, chore, refactor, test, style, build + +### Version Detection + +- **Automatic Versioning**: Detects latest tag +- **Custom Range**: Specify start tag with `--since` +- **Unreleased Changes**: Shows commits since last tag + +### Changelog Format + +- **Grouped by Type**: Organized sections +- **Commit Links**: GitHub commit URLs +- **PR References**: Links to pull requests +- **Breaking Changes**: Highlighted separately +- **Contributors**: Optional contributor list + +## Example Output + +```markdown +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +### Features + +- Add dependency health check command ([#238](https://github.com/user/repo/pull/238)) +- Add pre-PR checklist automation ([a1b2c3d](https://github.com/user/repo/commit/a1b2c3d)) +- Branch cleanup utility ([4e5f6g7](https://github.com/user/repo/commit/4e5f6g7)) + +### Bug Fixes + +- Fix coverage reporting in CI ([8h9i0j1](https://github.com/user/repo/commit/8h9i0j1)) +- Resolve shellcheck warnings ([2k3l4m5](https://github.com/user/repo/commit/2k3l4m5)) + +### Documentation + +- Update README with new commands ([#239](https://github.com/user/repo/pull/239)) + +## [1.0.0] - 2025-12-30 + +### Features + +- Initial release with comprehensive CI/CD +- DevContainer configuration +- Claude Code integration + +### BREAKING CHANGES + +- Minimum Node.js version is now 22+ +``` + +## Options + +```bash +# Generate since specific tag +/changelog-generator --since v1.0.0 + +# Generate for all history +/changelog-generator --all + +# Custom output file +/changelog-generator --output HISTORY.md + +# Include all commit types +/changelog-generator --include-all + +# Add contributors section +/changelog-generator --contributors + +# Preview without writing +/changelog-generator --dry-run +``` + +## Conventional Commit Types + +| Type | Section | Included by Default | +| -------- | ---------------- | ------------------- | +| feat | Features | ✅ | +| fix | Bug Fixes | ✅ | +| perf | Performance | ✅ | +| docs | Documentation | ✅ | +| BREAKING | BREAKING CHANGES | ✅ | +| refactor | Refactoring | ❌ | +| test | Tests | ❌ | +| ci | CI/CD | ❌ | +| chore | Chores | ❌ | +| style | Style | ❌ | +| build | Build | ❌ | + +## CI Integration + +```yaml +# .github/workflows/release.yml +- name: Generate Changelog + run: | + bash script/changelog-generator.sh --since ${{ github.event.release.tag_name }} + git add CHANGELOG.md + git commit -m "docs: update changelog for ${{ github.event.release.tag_name }}" +``` + +## Format + +The generated changelog follows [Keep a Changelog](https://keepachangelog.com/) format: + +- **Readable**: Human-friendly format +- **Parseable**: Machine-readable structure +- **Consistent**: Follows Conventional Commits +- **Linkable**: GitHub URLs for commits and PRs + +## Benefits + +- 📝 **Automated**: No manual changelog maintenance +- ✅ **Accurate**: Based on actual commits +- 🔗 **Linked**: Direct links to commits and PRs +- 📊 **Organized**: Grouped by semantic meaning +- ⚡ **Fast**: Quick generation from git log + +## Implementation + +This command is implemented in `script/changelog-generator.sh`. + +## Requirements + +- Git repository with conventional commits +- GitHub repository (for PR links) +- Git tags for version markers diff --git a/.claude/commands/code-complexity-check.md b/.claude/commands/code-complexity-check.md new file mode 100644 index 00000000..1935ab67 --- /dev/null +++ b/.claude/commands/code-complexity-check.md @@ -0,0 +1,172 @@ +# Code Complexity Check Command + +Analyze code complexity and identify refactoring candidates. + +## Usage + +```bash +/code-complexity-check +/code-complexity-check --threshold 10 +/code-complexity-check --report +``` + +## What It Does + +This command analyzes code complexity metrics to identify complex code that may need refactoring: + +### Complexity Metrics + +- **Cyclomatic Complexity**: Number of independent paths +- **Function Length**: Lines of code per function +- **Nesting Depth**: Maximum nesting level +- **Parameter Count**: Number of function parameters + +### Thresholds + +| Metric | Low | Medium | High | Critical | +| --------------- | ---- | ------ | ------ | -------- | +| Cyclomatic | < 5 | 5-10 | 10-20 | > 20 | +| Function Length | < 20 | 20-50 | 50-100 | > 100 | +| Nesting Depth | < 3 | 3-4 | 4-6 | > 6 | +| Parameters | < 3 | 3-5 | 5-7 | > 7 | + +### Analysis Output + +- **Complexity Score**: Overall codebase complexity +- **Hotspots**: Most complex files/functions +- **Refactoring Candidates**: Functions above threshold +- **Trend**: Complexity over time + +## Example Output + +``` +🔍 Code Complexity Analysis +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📊 Overall Complexity Score: 6.8/20 (Good) + +📈 Distribution + Low (< 5): 85% (120 functions) + Medium (5-10): 12% (17 functions) + High (10-20): 2% (3 functions) + Critical (> 20): 1% (1 function) + +🚨 High Complexity Functions + +1. script/pre-pr-checklist.sh:check_quality() + Complexity: 15 + Length: 85 lines + Nesting: 4 levels + ⚠️ Recommendation: Break into smaller functions + +2. script/dependency-health-check.sh:analyze_dependencies() + Complexity: 12 + Length: 120 lines + Nesting: 5 levels + ⚠️ Recommendation: Extract validation logic + +3. script/setup-new-repo.sh:copy_configuration() + Complexity: 11 + Length: 95 lines + Nesting: 3 levels + ℹ️ Note: Consider extracting file operations + +⚠️ Critical Complexity (> 20) + +1. script/changelog-generator.sh:generate_changelog() + Complexity: 22 + Length: 180 lines + Nesting: 6 levels + 🚨 URGENT: Refactor required + Suggestions: + - Extract commit grouping logic + - Separate formatting functions + - Reduce nesting with early returns + +📉 Top 5 Most Complex Files + +1. script/changelog-generator.sh: 15.3 avg complexity +2. script/dependency-health-check.sh: 10.8 avg complexity +3. script/pre-pr-checklist.sh: 9.5 avg complexity +4. script/branch-cleanup.sh: 8.2 avg complexity +5. script/setup-new-repo.sh: 7.1 avg complexity + +💡 Recommendations + +1. Refactor 1 critical function (> 20 complexity) +2. Review 3 high complexity functions (10-20) +3. Consider extracting common patterns +4. Apply early return pattern to reduce nesting +5. Break large functions into smaller units + +✅ Maintainability Index: 78/100 (Good) +``` + +## Options + +```bash +# Custom complexity threshold +/code-complexity-check --threshold 15 + +# Generate detailed report +/code-complexity-check --report complexity-report.md + +# Check specific files +/code-complexity-check --files "script/*.sh" + +# Fail CI if critical complexity found +/code-complexity-check --strict + +# JSON output for CI +/code-complexity-check --json +``` + +## Complexity Calculation + +Cyclomatic complexity is calculated as: + +``` +CC = E - N + 2P + +Where: + E = number of edges in control flow graph + N = number of nodes + P = number of connected components +``` + +## CI Integration + +```yaml +# .github/workflows/complexity.yml +- name: Check Code Complexity + run: | + bash script/code-complexity-check.sh --threshold 15 --strict +``` + +## Refactoring Suggestions + +For high complexity code: + +1. **Extract Method**: Break large functions into smaller ones +2. **Early Returns**: Reduce nesting with guard clauses +3. **Strategy Pattern**: Replace complex conditionals +4. **State Machine**: For complex state transitions +5. **Configuration**: Move complexity to data + +## Benefits + +- 🔍 **Early Detection**: Catch complexity before it's a problem +- 📊 **Metrics**: Quantify code quality +- 🎯 **Targeted**: Focus refactoring efforts +- ⚡ **Prevention**: Enforce complexity limits in CI +- 📈 **Tracking**: Monitor complexity trends + +## Implementation + +This command is implemented in `script/code-complexity-check.sh`. + +## Requirements + +- Bash 4.0+ +- Optional: `complexity-report` npm package for detailed analysis +- Shell scripts for analysis diff --git a/.claude/commands/container-health.md b/.claude/commands/container-health.md new file mode 100644 index 00000000..dfebb391 --- /dev/null +++ b/.claude/commands/container-health.md @@ -0,0 +1,165 @@ +# Container Health Command + +Verify DevContainer environment health and configuration. + +## Usage + +```bash +/container-health +/container-health --verbose +/container-health --fix +``` + +## What It Does + +This command performs comprehensive health checks on your DevContainer environment: + +### Tool Availability + +- **Required Tools**: git, node, npm, docker (if applicable) +- **Claude Code Tools**: claude, codex +- **Development Tools**: eslint, prettier, jest +- **Optional Tools**: gh (GitHub CLI), shellcheck + +### Version Verification + +- **Node.js**: Checks for v22.14.0 (or configured version) +- **npm**: Verifies compatible version +- **Claude Code**: Checks for latest version +- **Global Packages**: Verifies required global packages + +### Configuration Validation + +- **package.json**: Validates structure and scripts +- **DevContainer Config**: Checks devcontainer.json +- **Git Config**: Verifies git user and email +- **Environment Variables**: Checks required variables + +### System Resources + +- **Disk Space**: Warns if < 1GB free +- **Memory**: Checks available memory +- **File Permissions**: Verifies executable permissions + +## Example Output + +``` +🏥 DevContainer Health Check +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +✅ Required Tools + ✓ git 2.43.0 + ✓ node v22.14.0 + ✓ npm 10.2.4 + ✓ docker 24.0.7 + +✅ Claude Code Tools + ✓ claude 0.9.0 + ✓ codex 1.2.0 + +✅ Development Tools + ✓ eslint 8.57.0 + ✓ prettier 3.1.1 + ✓ jest 29.7.0 + +⚠️ Optional Tools + ✓ gh 2.40.1 + ✗ shellcheck (not installed) + +✅ Version Verification + ✓ Node.js version matches (v22.14.0) + ✓ npm version compatible (10.2.4) + +✅ Configuration + ✓ package.json valid + ✓ devcontainer.json exists + ✓ git user configured + ✓ git email configured + +✅ System Resources + ✓ Disk space: 15.2 GB free + ✓ Memory: 8.0 GB available + ✓ Shell scripts executable + +🏥 Health Score: 95/100 + +⚠️ Recommendations: + 1. Install shellcheck for shell script validation + Run: apt-get install shellcheck + +✨ DevContainer is healthy! +``` + +## Options + +```bash +# Verbose output with detailed diagnostics +/container-health --verbose + +# Attempt automatic fixes for common issues +/container-health --fix + +# Check specific component +/container-health --check tools +/container-health --check config +/container-health --check resources + +# JSON output for CI +/container-health --json +``` + +## Health Checks + +| Category | Checks | Weight | +| ------------- | ------------------------ | ------ | +| Tools | Required tools installed | 30 | +| Versions | Correct versions | 25 | +| Configuration | Valid configs | 25 | +| Resources | Adequate disk/memory | 15 | +| Permissions | Executable permissions | 5 | + +## Auto-Fix Capabilities + +With `--fix` flag, the command can automatically: + +- Install missing npm packages +- Set git user/email from environment +- Fix file permissions +- Create missing configuration files +- Update outdated global packages + +## CI Integration + +```yaml +# .github/workflows/container-health.yml +- name: Container Health Check + run: | + bash script/container-health.sh --json +``` + +## Exit Codes + +| Code | Meaning | +| ---- | --------------------- | +| 0 | All checks passed | +| 1 | Critical issues found | +| 2 | Configuration errors | +| 3 | Tool not found | + +## Benefits + +- 🔍 **Early Detection**: Catch environment issues early +- ⚡ **Fast Diagnosis**: Quick health assessment +- 🔧 **Auto-Fix**: Resolve common issues automatically +- 📊 **Visibility**: Clear health score and metrics +- ✅ **CI Ready**: JSON output for automation + +## Implementation + +This command is implemented in `script/container-health.sh`. + +## Requirements + +- DevContainer environment +- Bash 4.0+ +- Basic POSIX utilities (df, free, which) diff --git a/.claude/commands/security-credential-scan.md b/.claude/commands/security-credential-scan.md new file mode 100644 index 00000000..5b4af9c7 --- /dev/null +++ b/.claude/commands/security-credential-scan.md @@ -0,0 +1,191 @@ +# Security Credential Scan Command + +Scan repository for hardcoded credentials and sensitive data. + +## Usage + +```bash +/security-credential-scan +/security-credential-scan --fix +/security-credential-scan --report +``` + +## What It Does + +This command scans for potentially committed secrets and credentials: + +### Detection Patterns + +- **API Keys**: AWS, GitHub, Google Cloud, etc. +- **Tokens**: JWT, OAuth, Personal Access Tokens +- **Passwords**: Hardcoded passwords in code +- **Private Keys**: SSH keys, SSL certificates +- **Database Credentials**: Connection strings with passwords +- **Environment Variables**: Exposed secrets in .env files + +### Validation + +- **File Exclusions**: Skips .gitignore'd files +- **False Positive Reduction**: Smart pattern matching +- **Context Analysis**: Checks variable names and comments +- **.env Checking**: Verifies .env vs .env.example consistency + +## Example Output + +``` +🔒 Security Credential Scan +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📁 Scanning 245 files... + +🚨 CRITICAL: Potential Secrets Found + +1. .env:12 + Type: AWS Access Key + Pattern: AKIA[0-9A-Z]{16} + Value: AKIA************ABCD + 🔥 Action: Move to .env.local (git-ignored) + +2. src/config/database.js:23 + Type: Database Password + Pattern: password: "..." + Value: password: "***************" + 🔥 Action: Use environment variable + +3. script/deploy.sh:45 + Type: GitHub Token + Pattern: ghp_[a-zA-Z0-9]{36} + Value: ghp_****************************1234 + 🔥 Action: Use GitHub Secrets + +⚠️ WARNING: Potential Issues + +4. src/utils/api.ts:78 + Type: API Endpoint with Auth + Pattern: https://user:pass@api.example.com + Context: const API_URL = ... + ℹ️ Note: Consider using tokens instead + +5. test/fixtures/sample.json:5 + Type: JWT Token (Test Data) + Pattern: eyJ[A-Za-z0-9-_=]+\\.eyJ[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_.+/=]* + ✅ OK: In test fixtures (verify it's mock data) + +📊 Summary + + Critical: 3 findings (MUST FIX) + Warning: 2 findings (should review) + Total Files Scanned: 245 + Files with Issues: 5 + +✅ .env Configuration + + ✓ .env.example exists + ✓ .env in .gitignore + ⚠️ .env has 3 keys not in .env.example: + - AWS_SECRET_KEY + - DATABASE_PASSWORD + - GITHUB_TOKEN + + Add these to .env.example with placeholder values! + +🔧 Recommended Actions + +1. Move secrets from .env to .env.local +2. Update .env.example with all keys (use placeholder values) +3. Replace hardcoded credentials with environment variables +4. Add credential files to .gitignore: + - *.key + - *.pem + - credentials.json + - .env.local + +5. Consider using: + - 1Password CLI for local secrets + - GitHub Secrets for CI/CD + - AWS Secrets Manager for production + +🚨 Security Score: 40/100 (Critical issues found) + +Run with --fix to automatically remediate some issues. +``` + +## Options + +```bash +# Attempt automatic fixes +/security-credential-scan --fix + +# Generate detailed report +/security-credential-scan --report security-report.md + +# Check specific paths +/security-credential-scan --path src/ + +# Ignore specific patterns +/security-credential-scan --ignore "test/**" + +# Fail CI on critical findings +/security-credential-scan --strict + +# JSON output +/security-credential-scan --json +``` + +## Detection Patterns + +| Type | Pattern | Example | +| -------------- | ------------------------------ | ---------------------------------------- | +| AWS Access Key | `AKIA[0-9A-Z]{16}` | AKIAIOSFODNN7EXAMPLE | +| AWS Secret | `[A-Za-z0-9/+=]{40}` | wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY | +| GitHub Token | `ghp_[a-zA-Z0-9]{36}` | ghp_1234567890abcdefghijklmnopqrstuvwx | +| Google API | `AIza[0-9A-Za-z-_]{35}` | AIzaSyD-example-key | +| JWT Token | `eyJ[A-Za-z0-9-_=]+\.eyJ...` | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... | +| Private Key | `-----BEGIN.*PRIVATE KEY-----` | -----BEGIN RSA PRIVATE KEY----- | +| Database URL | `postgres://user:pass@host` | postgres://admin:secret@localhost | + +## Auto-Fix Capabilities + +With `--fix` flag: + +- Creates .env.example from .env (with placeholders) +- Adds credential files to .gitignore +- Replaces hardcoded values with environment variable references +- Creates template files for secrets + +## CI Integration + +```yaml +# .github/workflows/security.yml +- name: Scan for Credentials + run: | + bash script/security-credential-scan.sh --strict +``` + +## False Positives + +The scanner intelligently skips: + +- Test fixtures (in `test/`, `__tests__/`, `*.test.*`) +- Example files (`*.example`, `*.sample`) +- Documentation (`*.md`, `docs/`) +- Comments and documentation strings +- Variable names and constants (not values) + +## Benefits + +- 🛡️ **Prevention**: Catch secrets before commit +- 🔍 **Detection**: Find existing credentials +- ⚡ **Fast**: Quick scans of entire codebase +- 🤖 **Automated**: CI integration +- 🔧 **Remediation**: Auto-fix capabilities + +## Implementation + +This command is implemented in `script/security-credential-scan.sh`. + +## Requirements + +- Bash 4.0+ +- Git repository +- grep with regex support diff --git a/.claude/commands/test-coverage-trend.md b/.claude/commands/test-coverage-trend.md new file mode 100644 index 00000000..f082418a --- /dev/null +++ b/.claude/commands/test-coverage-trend.md @@ -0,0 +1,167 @@ +# Test Coverage Trend Command + +Track and visualize test coverage trends over time. + +## Usage + +```bash +/test-coverage-trend +/test-coverage-trend --days 30 +/test-coverage-trend --graph +``` + +## What It Does + +This command tracks test coverage metrics over time: + +### Coverage Tracking + +- **Historical Data**: Stores coverage data per commit +- **Trend Analysis**: Shows coverage improvements/declines +- **Threshold Alerts**: Warns when coverage drops below 70% +- **Component Breakdown**: Per-file coverage tracking + +### Metrics Tracked + +- **Line Coverage**: Percentage of lines covered +- **Branch Coverage**: Percentage of branches covered +- **Function Coverage**: Percentage of functions covered +- **Statement Coverage**: Percentage of statements covered + +### Visualization + +- **ASCII Graph**: Simple trend graph in terminal +- **Statistics**: Min, max, average coverage +- **Recent Changes**: Coverage diff from last run +- **Hotspots**: Files with low coverage + +## Example Output + +``` +📊 Test Coverage Trend +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📈 Coverage History (Last 30 days) + +Line Coverage Trend: +100% ┤ + 95% ┤ ╭─╮ + 90% ┤ ╭─╯ ╰─╮ + 85% ┤ ╭─╯ ╰──╮ + 80% ┤─╯ ╰─╮ + 75% ┤ ╰─ + 70% ┼───────────────────────────────── + └───────────────────────────────── + 30d now + +📊 Current Coverage + • Lines: 82.5% (↑ 1.2% from last week) + • Branches: 78.3% (↓ 0.5% from last week) + • Functions: 85.0% (↑ 2.0% from last week) + • Statements: 82.1% (↑ 1.0% from last week) + +✅ Above threshold (70%) + +📈 Statistics (30 days) + • Average: 81.2% + • Min: 75.0% (2025-12-01) + • Max: 85.5% (2025-12-28) + • Trend: ↗ Improving (+5.5% over period) + +⚠️ Low Coverage Files + 1. script/lib/output.sh: 45.2% + 2. script/credentials.sh: 58.7% + 3. test/config-validation.test.js: 65.3% + +💡 Recommendations + • Add tests for low-coverage files + • Coverage trend is positive - keep it up! +``` + +## Options + +```bash +# Show last N days +/test-coverage-trend --days 30 + +# Show ASCII graph +/test-coverage-trend --graph + +# Show per-file details +/test-coverage-trend --detailed + +# Export to CSV +/test-coverage-trend --export coverage-trend.csv + +# CI-friendly JSON output +/test-coverage-trend --json +``` + +## Data Storage + +Coverage data is stored in `.coverage-history/`: + +``` +.coverage-history/ +├── 2025-12-31.json +├── 2025-12-30.json +└── 2025-12-29.json +``` + +Each file contains: + +```json +{ + "date": "2025-12-31", + "commit": "a1b2c3d", + "coverage": { + "lines": 82.5, + "branches": 78.3, + "functions": 85.0, + "statements": 82.1 + }, + "files": { + "script/pre-pr-checklist.sh": 95.0, + "script/dependency-health-check.sh": 88.5 + } +} +``` + +## CI Integration + +```yaml +# .github/workflows/coverage-trend.yml +- name: Track Coverage Trend + run: | + npm run test:coverage + bash script/test-coverage-trend.sh --record + git add .coverage-history/ + git commit -m "chore: update coverage history" +``` + +## Alerts + +| Condition | Alert | +| -------------- | ---------------------------- | +| Coverage < 70% | 🚨 Critical: Below threshold | +| Drop > 5% | ⚠️ Warning: Significant drop | +| Drop > 2% | ℹ️ Info: Minor decline | +| Increase > 2% | ✅ Success: Improvement | + +## Benefits + +- 📊 **Visibility**: Clear coverage trends +- ⚠️ **Early Warning**: Detect coverage regressions +- 📈 **Motivation**: Visualize improvements +- 🎯 **Targeted**: Identify low-coverage files +- 🤖 **Automated**: CI integration + +## Implementation + +This command is implemented in `script/test-coverage-trend.sh`. + +## Requirements + +- Jest with coverage enabled +- Git repository +- `coverage/coverage-summary.json` output from Jest diff --git a/script/changelog-generator.sh b/script/changelog-generator.sh new file mode 100755 index 00000000..b28f0fed --- /dev/null +++ b/script/changelog-generator.sh @@ -0,0 +1,301 @@ +#!/usr/bin/env bash +# Changelog Generator - Generate CHANGELOG from conventional commits + +set -euo pipefail + +# Colors +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Options +SINCE_TAG="" +OUTPUT_FILE="CHANGELOG.md" +INCLUDE_ALL=false +DRY_RUN=false +SHOW_CONTRIBUTORS=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --since) + SINCE_TAG="$2" + shift 2 + ;; + --all) + INCLUDE_ALL=true + shift + ;; + --output) + OUTPUT_FILE="$2" + shift 2 + ;; + --include-all) + INCLUDE_ALL=true + shift + ;; + --contributors) + SHOW_CONTRIBUTORS=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --since TAG Generate changelog since this tag" + echo " --all Include all commit types" + echo " --output FILE Output file (default: CHANGELOG.md)" + echo " --include-all Include all commit types" + echo " --contributors Add contributors section" + echo " --dry-run Preview without writing" + echo " --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +echo -e "${BLUE}📝 Changelog Generator${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Check if in git repository +if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo -e "${RED}✗ Not in a git repository${NC}" + exit 1 +fi + +# Get repository info +REPO_URL=$(git config --get remote.origin.url | sed 's/\.git$//' | sed 's/git@github.com:/https:\/\/github.com\//') + +# Determine range +if [ -z "$SINCE_TAG" ]; then + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -n "$LATEST_TAG" ]; then + SINCE_TAG="$LATEST_TAG" + echo "📌 Latest tag: $LATEST_TAG" + else + echo "📌 No tags found, generating from all commits" + SINCE_TAG="" + fi +else + echo "📌 Generating since: $SINCE_TAG" +fi +echo "" + +# Generate changelog content +CHANGELOG_CONTENT="# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Conventional Commits](https://conventionalcommits.org/). + +" + +# Get commits +if [ -n "$SINCE_TAG" ]; then + COMMITS=$(git log "$SINCE_TAG"..HEAD --pretty=format:"%H|%s|%b" 2>/dev/null || git log --pretty=format:"%H|%s|%b") +else + COMMITS=$(git log --pretty=format:"%H|%s|%b") +fi + +# Group commits by type +declare -A FEATURES +declare -A FIXES +declare -A PERF +declare -A DOCS +declare -A BREAKING +declare -A OTHER + +while IFS='|' read -r hash subject body; do + # Extract type from conventional commit + regex='^([a-z]+)(\([^)]+\))?: (.+)$' + if [[ "$subject" =~ $regex ]]; then + TYPE="${BASH_REMATCH[1]}" + MESSAGE="${BASH_REMATCH[3]}" + + # Check for breaking changes + if echo "$body" | grep -q "BREAKING CHANGE"; then + BREAKING["$hash"]="$MESSAGE" + fi + + # Categorize + case "$TYPE" in + feat) + FEATURES["$hash"]="$MESSAGE" + ;; + fix) + FIXES["$hash"]="$MESSAGE" + ;; + perf) + PERF["$hash"]="$MESSAGE" + ;; + docs) + DOCS["$hash"]="$MESSAGE" + ;; + *) + if [ "$INCLUDE_ALL" = true ]; then + OTHER["$hash"]="$MESSAGE ($TYPE)" + fi + ;; + esac + fi +done <<< "$COMMITS" + +# Add unreleased section +CHANGELOG_CONTENT+="## [Unreleased] + +" + +# Breaking changes first +if [ ${#BREAKING[@]} -gt 0 ]; then + CHANGELOG_CONTENT+="### BREAKING CHANGES + +" + for hash in "${!BREAKING[@]}"; do + SHORT_HASH=$(echo "$hash" | cut -c1-7) + CHANGELOG_CONTENT+="- ${BREAKING[$hash]} ([$SHORT_HASH]($REPO_URL/commit/$hash)) +" + done + CHANGELOG_CONTENT+=" +" +fi + +# Features +if [ ${#FEATURES[@]} -gt 0 ]; then + CHANGELOG_CONTENT+="### Features + +" + for hash in "${!FEATURES[@]}"; do + SHORT_HASH=$(echo "$hash" | cut -c1-7) + MESSAGE="${FEATURES[$hash]}" + + # Check for PR reference + if [[ "$MESSAGE" =~ \(#([0-9]+)\) ]]; then + PR_NUM="${BASH_REMATCH[1]}" + MESSAGE=$(echo "$MESSAGE" | sed "s/(#$PR_NUM)/([#$PR_NUM]($REPO_URL\/pull\/$PR_NUM))/") + fi + + CHANGELOG_CONTENT+="- $MESSAGE ([$SHORT_HASH]($REPO_URL/commit/$hash)) +" + done + CHANGELOG_CONTENT+=" +" +fi + +# Bug fixes +if [ ${#FIXES[@]} -gt 0 ]; then + CHANGELOG_CONTENT+="### Bug Fixes + +" + for hash in "${!FIXES[@]}"; do + SHORT_HASH=$(echo "$hash" | cut -c1-7) + MESSAGE="${FIXES[$hash]}" + + if [[ "$MESSAGE" =~ \(#([0-9]+)\) ]]; then + PR_NUM="${BASH_REMATCH[1]}" + MESSAGE=$(echo "$MESSAGE" | sed "s/(#$PR_NUM)/([#$PR_NUM]($REPO_URL\/pull\/$PR_NUM))/") + fi + + CHANGELOG_CONTENT+="- $MESSAGE ([$SHORT_HASH]($REPO_URL/commit/$hash)) +" + done + CHANGELOG_CONTENT+=" +" +fi + +# Performance +if [ ${#PERF[@]} -gt 0 ]; then + CHANGELOG_CONTENT+="### Performance Improvements + +" + for hash in "${!PERF[@]}"; do + SHORT_HASH=$(echo "$hash" | cut -c1-7) + CHANGELOG_CONTENT+="- ${PERF[$hash]} ([$SHORT_HASH]($REPO_URL/commit/$hash)) +" + done + CHANGELOG_CONTENT+=" +" +fi + +# Documentation +if [ ${#DOCS[@]} -gt 0 ]; then + CHANGELOG_CONTENT+="### Documentation + +" + for hash in "${!DOCS[@]}"; do + SHORT_HASH=$(echo "$hash" | cut -c1-7) + CHANGELOG_CONTENT+="- ${DOCS[$hash]} ([$SHORT_HASH]($REPO_URL/commit/$hash)) +" + done + CHANGELOG_CONTENT+=" +" +fi + +# Other (if include-all) +if [ ${#OTHER[@]} -gt 0 ]; then + CHANGELOG_CONTENT+="### Other Changes + +" + for hash in "${!OTHER[@]}"; do + SHORT_HASH=$(echo "$hash" | cut -c1-7) + CHANGELOG_CONTENT+="- ${OTHER[$hash]} ([$SHORT_HASH]($REPO_URL/commit/$hash)) +" + done + CHANGELOG_CONTENT+=" +" +fi + +# Contributors +if [ "$SHOW_CONTRIBUTORS" = true ]; then + CHANGELOG_CONTENT+="### Contributors + +" + if [ -n "$SINCE_TAG" ]; then + CONTRIBUTORS=$(git log "$SINCE_TAG"..HEAD --format='%an' | sort -u) + else + CONTRIBUTORS=$(git log --format='%an' | sort -u) + fi + + while IFS= read -r contributor; do + CHANGELOG_CONTENT+="- $contributor +" + done <<< "$CONTRIBUTORS" + CHANGELOG_CONTENT+=" +" +fi + +# Output +if [ "$DRY_RUN" = true ]; then + echo -e "${YELLOW}Preview (dry run):${NC}" + echo "" + echo "$CHANGELOG_CONTENT" +else + # Write to file + echo "$CHANGELOG_CONTENT" > "$OUTPUT_FILE" + echo -e "${GREEN}✓${NC} Changelog written to $OUTPUT_FILE" + + # Show stats + TOTAL_ENTRIES=$((${#FEATURES[@]} + ${#FIXES[@]} + ${#PERF[@]} + ${#DOCS[@]} + ${#OTHER[@]})) + echo "" + echo "📊 Statistics:" + echo " • Features: ${#FEATURES[@]}" + echo " • Bug Fixes: ${#FIXES[@]}" + echo " • Performance: ${#PERF[@]}" + echo " • Documentation: ${#DOCS[@]}" + if [ "$INCLUDE_ALL" = true ]; then + echo " • Other: ${#OTHER[@]}" + fi + echo " • Breaking Changes: ${#BREAKING[@]}" + echo " • Total Entries: $TOTAL_ENTRIES" +fi diff --git a/script/code-complexity-check.sh b/script/code-complexity-check.sh new file mode 100755 index 00000000..8048e747 --- /dev/null +++ b/script/code-complexity-check.sh @@ -0,0 +1,282 @@ +#!/usr/bin/env bash +# Code Complexity Check - Analyze code complexity + +set -euo pipefail + +# Colors +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Options +THRESHOLD=10 +REPORT_FILE="" +FILE_PATTERN="script/*.sh" +STRICT_MODE=false +JSON_OUTPUT=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --threshold) + THRESHOLD="$2" + shift 2 + ;; + --report) + REPORT_FILE="$2" + shift 2 + ;; + --files) + FILE_PATTERN="$2" + shift 2 + ;; + --strict) + STRICT_MODE=true + shift + ;; + --json) + JSON_OUTPUT=true + shift + ;; + --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --threshold N Complexity threshold (default: 10)" + echo " --report FILE Generate report file" + echo " --files PATTERN File pattern to check (default: script/*.sh)" + echo " --strict Fail on high complexity" + echo " --json JSON output" + echo " --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}🔍 Code Complexity Analysis${NC}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" +fi + +# Simple complexity estimation for shell scripts +estimate_complexity() { + local file=$1 + local complexity=1 + + # Count decision points + local if_count + local case_count + local while_count + local for_count + local and_count + local or_count + + if_count=$(grep -c "if \[" "$file" 2>/dev/null || echo "0") + case_count=$(grep -c "case " "$file" 2>/dev/null || echo "0") + while_count=$(grep -c "while " "$file" 2>/dev/null || echo "0") + for_count=$(grep -c "for " "$file" 2>/dev/null || echo "0") + and_count=$(grep -c " && " "$file" 2>/dev/null || echo "0") + or_count=$(grep -c " || " "$file" 2>/dev/null || echo "0") + + complexity=$((complexity + if_count + case_count + while_count + for_count + and_count + or_count)) + + echo "$complexity" +} + +# Calculate function length +get_function_length() { + local file=$1 + wc -l < "$file" | tr -d ' ' +} + +# Calculate nesting depth (simplified) +get_max_nesting() { + local file=$1 + local max_depth=0 + local current_depth=0 + + while IFS= read -r line; do + # Increment depth on opening braces + local opens + local closes + + opens=$(echo "$line" | grep -o "{" | wc -l | tr -d ' ') + closes=$(echo "$line" | grep -o "}" | wc -l | tr -d ' ') + + current_depth=$((current_depth + opens - closes)) + + if [ $current_depth -gt $max_depth ]; then + max_depth=$current_depth + fi + done < "$file" + + echo "$max_depth" +} + +# Analyze files +declare -A FILE_COMPLEXITY +declare -A FILE_LENGTH +declare -A FILE_NESTING +TOTAL_FILES=0 +HIGH_COMPLEXITY_COUNT=0 +CRITICAL_COMPLEXITY_COUNT=0 + +# shellcheck disable=SC2086 +for file in $FILE_PATTERN; do + if [ -f "$file" ]; then + ((TOTAL_FILES++)) + + COMPLEXITY=$(estimate_complexity "$file") + LENGTH=$(get_function_length "$file") + NESTING=$(get_max_nesting "$file") + + FILE_COMPLEXITY["$file"]=$COMPLEXITY + FILE_LENGTH["$file"]=$LENGTH + FILE_NESTING["$file"]=$NESTING + + if [ "$COMPLEXITY" -ge 20 ]; then + ((CRITICAL_COMPLEXITY_COUNT++)) + elif [ "$COMPLEXITY" -ge "$THRESHOLD" ]; then + ((HIGH_COMPLEXITY_COUNT++)) + fi + fi +done + +if [ $TOTAL_FILES -eq 0 ]; then + echo "No files found matching pattern: $FILE_PATTERN" + exit 0 +fi + +# Calculate average complexity +TOTAL_COMPLEXITY=0 +for complexity in "${FILE_COMPLEXITY[@]}"; do + TOTAL_COMPLEXITY=$((TOTAL_COMPLEXITY + complexity)) +done +AVG_COMPLEXITY=$((TOTAL_COMPLEXITY / TOTAL_FILES)) + +if [ "$JSON_OUTPUT" = true ]; then + # JSON output + cat < 20): $CRITICAL_COMPLEXITY_COUNT files" + echo "" + + # Show high complexity files + if [ $HIGH_COMPLEXITY_COUNT -gt 0 ] || [ $CRITICAL_COMPLEXITY_COUNT -gt 0 ]; then + echo -e "${YELLOW}⚠️ Complex Files${NC}" + echo "" + + for file in "${!FILE_COMPLEXITY[@]}"; do + COMPLEXITY=${FILE_COMPLEXITY["$file"]} + LENGTH=${FILE_LENGTH["$file"]} + NESTING=${FILE_NESTING["$file"]} + + if [ "$COMPLEXITY" -ge "$THRESHOLD" ]; then + if [ "$COMPLEXITY" -ge 20 ]; then + echo -e "${RED}🚨 CRITICAL: $file${NC}" + else + echo -e "${YELLOW}⚠️ $file${NC}" + fi + echo " Complexity: $COMPLEXITY" + echo " Length: $LENGTH lines" + echo " Max nesting: $NESTING levels" + echo " Recommendation: Consider refactoring" + echo "" + fi + done + fi + + # Recommendations + echo -e "${BLUE}💡 Recommendations${NC}" + if [ $CRITICAL_COMPLEXITY_COUNT -gt 0 ]; then + echo " 1. 🚨 Refactor $CRITICAL_COMPLEXITY_COUNT critical complexity files" + fi + if [ $HIGH_COMPLEXITY_COUNT -gt 0 ]; then + echo " 2. Review $HIGH_COMPLEXITY_COUNT high complexity files" + fi + if [ $AVG_COMPLEXITY -gt 10 ]; then + echo " 3. Overall complexity is high - consider general refactoring" + else + echo " ✅ Code complexity is within acceptable limits" + fi +fi + +# Write report +if [ -n "$REPORT_FILE" ]; then + { + echo "# Code Complexity Report" + echo "" + echo "Generated: $(date)" + echo "" + echo "## Summary" + echo "" + echo "- Total Files: $TOTAL_FILES" + echo "- Average Complexity: $AVG_COMPLEXITY" + echo "- High Complexity: $HIGH_COMPLEXITY_COUNT" + echo "- Critical Complexity: $CRITICAL_COMPLEXITY_COUNT" + echo "" + echo "## File Details" + echo "" + for file in "${!FILE_COMPLEXITY[@]}"; do + echo "### $file" + echo "" + echo "- Complexity: ${FILE_COMPLEXITY["$file"]}" + echo "- Length: ${FILE_LENGTH["$file"]} lines" + echo "- Nesting: ${FILE_NESTING["$file"]} levels" + echo "" + done + } > "$REPORT_FILE" + echo "" + echo "Report written to: $REPORT_FILE" +fi + +# Exit code for strict mode +if [ "$STRICT_MODE" = true ] && [ $CRITICAL_COMPLEXITY_COUNT -gt 0 ]; then + exit 1 +fi + +exit 0 diff --git a/script/container-health.sh b/script/container-health.sh new file mode 100755 index 00000000..e6429669 --- /dev/null +++ b/script/container-health.sh @@ -0,0 +1,324 @@ +#!/usr/bin/env bash +# Container Health - Verify DevContainer environment + +set -euo pipefail + +# Colors +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Options +# shellcheck disable=SC2034 +VERBOSE=false +# shellcheck disable=SC2034 +AUTO_FIX=false +JSON_OUTPUT=false +CHECK_COMPONENT="" + +# Health score +HEALTH_SCORE=100 +MAX_SCORE=100 + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --verbose) + # shellcheck disable=SC2034 + VERBOSE=true + shift + ;; + --fix) + # shellcheck disable=SC2034 + AUTO_FIX=true + shift + ;; + --json) + JSON_OUTPUT=true + shift + ;; + --check) + CHECK_COMPONENT="$2" + shift 2 + ;; + --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --verbose Verbose output" + echo " --fix Attempt automatic fixes" + echo " --json JSON output" + echo " --check TYPE Check specific component (tools|config|resources)" + echo " --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}🏥 DevContainer Health Check${NC}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" +fi + +# Initialize results +declare -A TOOL_STATUS +declare -A CONFIG_STATUS +RECOMMENDATIONS=() + +# Check tool availability +check_tool() { + local tool=$1 + local required=${2:-false} + + if command -v "$tool" > /dev/null 2>&1; then + VERSION=$(command "$tool" --version 2>&1 | head -1 || echo "unknown") + TOOL_STATUS["$tool"]="installed:$VERSION" + return 0 + else + TOOL_STATUS["$tool"]="missing" + if [ "$required" = true ]; then + ((HEALTH_SCORE -= 10)) || true + fi + return 1 + fi +} + +# Tool checks +if [ -z "$CHECK_COMPONENT" ] || [ "$CHECK_COMPONENT" = "tools" ]; then + if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}✅ Required Tools${NC}" + fi + + # Required tools + for tool in git node npm; do + if check_tool "$tool" true; then + if [ "$JSON_OUTPUT" = false ]; then + VERSION=${TOOL_STATUS["$tool"]#installed:} + echo -e " ${GREEN}✓${NC} $tool $VERSION" + fi + else + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${RED}✗${NC} $tool (not installed)" + fi + RECOMMENDATIONS+=("Install $tool") + fi + done + + if [ "$JSON_OUTPUT" = false ]; then + echo "" + fi + + # Claude Code tools + if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}✅ Claude Code Tools${NC}" + fi + + # Check claude tool + if check_tool "claude" false; then + if [ "$JSON_OUTPUT" = false ]; then + VERSION=${TOOL_STATUS["claude"]#installed:} + echo -e " ${GREEN}✓${NC} claude" + fi + else + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${YELLOW}⚠${NC} claude (not installed)" + fi + ((HEALTH_SCORE -= 5)) || true + RECOMMENDATIONS+=("Install Claude Code: npm install -g @anthropic-ai/claude-code") + fi + + if [ "$JSON_OUTPUT" = false ]; then + echo "" + fi + + # Development tools + if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}✅ Development Tools${NC}" + fi + + for tool in eslint prettier jest; do + if check_tool "$tool" false; then + if [ "$JSON_OUTPUT" = false ]; then + VERSION=${TOOL_STATUS["$tool"]#installed:} + echo -e " ${GREEN}✓${NC} $tool" + fi + else + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${YELLOW}⚠${NC} $tool (not in PATH)" + fi + ((HEALTH_SCORE -= 2)) || true + fi + done + + if [ "$JSON_OUTPUT" = false ]; then + echo "" + fi + + # Optional tools + if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}⚠️ Optional Tools${NC}" + fi + + for tool in gh shellcheck; do + if check_tool "$tool" false; then + if [ "$JSON_OUTPUT" = false ]; then + VERSION=${TOOL_STATUS["$tool"]#installed:} + echo -e " ${GREEN}✓${NC} $tool" + fi + else + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${YELLOW}⚠${NC} $tool (not installed)" + fi + case "$tool" in + shellcheck) + RECOMMENDATIONS+=("Install shellcheck: apt-get install shellcheck") + ;; + gh) + RECOMMENDATIONS+=("Install GitHub CLI: https://cli.github.com") + ;; + esac + fi + done + + if [ "$JSON_OUTPUT" = false ]; then + echo "" + fi +fi + +# Configuration checks +if [ -z "$CHECK_COMPONENT" ] || [ "$CHECK_COMPONENT" = "config" ]; then + if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}✅ Configuration${NC}" + fi + + # package.json + if [ -f "package.json" ]; then + if node -pe "JSON.parse(require('fs').readFileSync('package.json'))" > /dev/null 2>&1; then + CONFIG_STATUS["package.json"]="valid" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${GREEN}✓${NC} package.json valid" + fi + else + CONFIG_STATUS["package.json"]="invalid" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${RED}✗${NC} package.json invalid JSON" + fi + ((HEALTH_SCORE -= 10)) || true + fi + else + CONFIG_STATUS["package.json"]="missing" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${YELLOW}⚠${NC} package.json not found" + fi + ((HEALTH_SCORE -= 5)) || true + fi + + # Git config + if git config user.name > /dev/null 2>&1; then + CONFIG_STATUS["git.user"]="configured" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${GREEN}✓${NC} git user configured" + fi + else + CONFIG_STATUS["git.user"]="missing" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${YELLOW}⚠${NC} git user not configured" + fi + RECOMMENDATIONS+=("Set git user: git config --global user.name 'Your Name'") + ((HEALTH_SCORE -= 5)) || true + fi + + if git config user.email > /dev/null 2>&1; then + CONFIG_STATUS["git.email"]="configured" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${GREEN}✓${NC} git email configured" + fi + else + CONFIG_STATUS["git.email"]="missing" + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${YELLOW}⚠${NC} git email not configured" + fi + RECOMMENDATIONS+=("Set git email: git config --global user.email 'you@example.com'") + ((HEALTH_SCORE -= 5)) || true + fi + + if [ "$JSON_OUTPUT" = false ]; then + echo "" + fi +fi + +# Resource checks +if [ -z "$CHECK_COMPONENT" ] || [ "$CHECK_COMPONENT" = "resources" ]; then + if [ "$JSON_OUTPUT" = false ]; then + echo -e "${BLUE}✅ System Resources${NC}" + fi + + # Disk space + DISK_FREE=$(df -h . | awk 'NR==2 {print $4}' || echo "unknown") + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${GREEN}✓${NC} Disk space: $DISK_FREE free" + fi + + # Memory (if available) + if command -v free > /dev/null 2>&1; then + MEM_AVAILABLE=$(free -h | awk 'NR==2 {print $7}' || echo "unknown") + if [ "$JSON_OUTPUT" = false ]; then + echo -e " ${GREEN}✓${NC} Memory: $MEM_AVAILABLE available" + fi + fi + + if [ "$JSON_OUTPUT" = false ]; then + echo "" + fi +fi + +# Ensure score doesn't go negative +if [ $HEALTH_SCORE -lt 0 ]; then + HEALTH_SCORE=0 +fi + +# Output results +if [ "$JSON_OUTPUT" = true ]; then + # JSON output + cat </dev/null || true) +done + +# Count files scanned +if [ -d "$SCAN_PATH" ]; then + # shellcheck disable=SC2086 + TOTAL_FILES=$(find "$SCAN_PATH" -type f ! -path "*/node_modules/*" ! -path "*/.git/*" | wc -l | tr -d ' ') +else + TOTAL_FILES=1 +fi + +if [ "$JSON_OUTPUT" = true ]; then + # JSON output + cat < /dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} .env in .gitignore" + else + echo -e " ${RED}✗${NC} .env NOT in .gitignore (CRITICAL)" + ((CRITICAL_COUNT++)) + fi + echo "" + fi + + # Recommendations + echo -e "${BLUE}🔧 Recommended Actions${NC}" + echo "" + echo "1. Move secrets from committed files to environment variables" + echo "2. Update .gitignore to include:" + echo " - .env.local" + echo " - *.key" + echo " - *.pem" + echo " - credentials.json" + echo "3. Use secret management:" + echo " - 1Password CLI for local development" + echo " - GitHub Secrets for CI/CD" + echo " - AWS Secrets Manager for production" + echo "" + + # Calculate security score + SECURITY_SCORE=$((100 - (CRITICAL_COUNT * 20) - (WARNING_COUNT * 5))) + if [ $SECURITY_SCORE -lt 0 ]; then + SECURITY_SCORE=0 + fi + + if [ $SECURITY_SCORE -ge 90 ]; then + echo -e "${GREEN}🏆 Security Score: $SECURITY_SCORE/100 (Excellent)${NC}" + elif [ $SECURITY_SCORE -ge 70 ]; then + echo -e "${YELLOW}⚠️ Security Score: $SECURITY_SCORE/100 (Good)${NC}" + else + echo -e "${RED}🚨 Security Score: $SECURITY_SCORE/100 (Critical)${NC}" + fi +fi + +# Write report +if [ -n "$REPORT_FILE" ]; then + { + echo "# Security Credential Scan Report" + echo "" + echo "Generated: $(date)" + echo "" + echo "## Summary" + echo "" + echo "- Critical Findings: $CRITICAL_COUNT" + echo "- Warning Findings: $WARNING_COUNT" + echo "- Files Scanned: $TOTAL_FILES" + echo "" + echo "## Findings" + echo "" + for finding in "${FINDINGS[@]}"; do + IFS='|' read -r severity type location content <<< "$finding" + echo "### $severity: $type" + echo "" + echo "- Location: $location" + echo "- Content: $content" + echo "" + done + } > "$REPORT_FILE" + echo "" + echo "Report written to: $REPORT_FILE" +fi + +# Exit code for strict mode +if [ "$STRICT_MODE" = true ] && [ $CRITICAL_COUNT -gt 0 ]; then + exit 1 +fi + +exit 0 diff --git a/script/setup-new-repo.sh b/script/setup-new-repo.sh new file mode 100755 index 00000000..12ee0328 --- /dev/null +++ b/script/setup-new-repo.sh @@ -0,0 +1,364 @@ +#!/usr/bin/env bash +# Setup New Repository - Bootstrap new repo with config + +set -euo pipefail + +# Colors +# shellcheck disable=SC2034 +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Get config repository path +CONFIG_REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) + +# Options +MINIMAL=false +NO_DEVCONTAINER=false +LICENSE="MIT" +NO_INSTALL=false +# shellcheck disable=SC2034 +INTERACTIVE=false +TARGET_DIR="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --minimal) + MINIMAL=true + shift + ;; + --no-devcontainer) + NO_DEVCONTAINER=true + shift + ;; + --license) + LICENSE="$2" + shift 2 + ;; + --no-install) + NO_INSTALL=true + shift + ;; + --interactive) + # shellcheck disable=SC2034 + INTERACTIVE=true + shift + ;; + --help) + echo "Usage: $0 TARGET_DIR [OPTIONS]" + echo "" + echo "Arguments:" + echo " TARGET_DIR Path to new repository" + echo "" + echo "Options:" + echo " --minimal Minimal setup (no GitHub Actions)" + echo " --no-devcontainer Skip DevContainer setup" + echo " --license TYPE License type (default: MIT)" + echo " --no-install Skip npm install" + echo " --interactive Prompt for each step" + echo " --help Show this help message" + exit 0 + ;; + *) + if [ -z "$TARGET_DIR" ]; then + TARGET_DIR="$1" + else + echo "Unknown option: $1" + exit 1 + fi + shift + ;; + esac +done + +if [ -z "$TARGET_DIR" ]; then + echo "Error: TARGET_DIR required" + echo "Usage: $0 TARGET_DIR [OPTIONS]" + exit 1 +fi + +echo -e "${BLUE}🚀 Setting up new repository${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo -e "📁 Target: ${GREEN}$TARGET_DIR${NC}" +echo -e "📋 Configuration source: ${BLUE}$CONFIG_REPO${NC}" +echo "" + +# Create directory if it doesn't exist +if [ ! -d "$TARGET_DIR" ]; then + mkdir -p "$TARGET_DIR" +fi + +cd "$TARGET_DIR" +TARGET_ABS=$(pwd) + +# Step 1: Initialize Git +echo -e "${BLUE}✅ Step 1: Initialize Git repository${NC}" +if [ ! -d ".git" ]; then + git init > /dev/null 2>&1 + echo -e " ${GREEN}✓${NC} Git repository initialized" +else + echo " • Git repository already exists" +fi +echo "" + +# Step 2: DevContainer (unless skipped) +if [ "$NO_DEVCONTAINER" = false ]; then + echo -e "${BLUE}✅ Step 2: Copy DevContainer configuration${NC}" + + # Copy .devcontainer + if [ -d "$CONFIG_REPO/.devcontainer" ]; then + cp -r "$CONFIG_REPO/.devcontainer" . + echo -e " ${GREEN}✓${NC} Copied .devcontainer/" + fi + + # Copy .vscode + if [ -d "$CONFIG_REPO/.vscode" ]; then + cp -r "$CONFIG_REPO/.vscode" . + echo -e " ${GREEN}✓${NC} Copied .vscode/" + fi + + echo "" +fi + +# Step 3: Git configuration +echo -e "${BLUE}✅ Step 3: Setup Git configuration${NC}" + +# Commitlint +if [ -f "$CONFIG_REPO/git/commitlint.config.js" ]; then + cp "$CONFIG_REPO/git/commitlint.config.js" commitlint.config.js + echo -e " ${GREEN}✓${NC} Copied commitlint.config.js" +fi + +# Gitignore +cat > .gitignore <<'EOF' +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Testing +coverage/ +*.lcov + +# Production +build/ +dist/ +*.tgz + +# Misc +.DS_Store +.env +.env.local +.env.*.local + +# Logs +logs +*.log +npm-debug.log* + +# IDE +.idea/ +*.swp +*.swo +*~ +.vscode/settings.local.json + +# OS +Thumbs.db +EOF + +echo -e " ${GREEN}✓${NC} Created .gitignore" +echo "" + +# Step 4: GitHub Actions (unless minimal) +if [ "$MINIMAL" = false ]; then + echo -e "${BLUE}✅ Step 4: Copy GitHub Actions${NC}" + + # Copy workflows + if [ -d "$CONFIG_REPO/.github/workflows" ]; then + mkdir -p .github/workflows + cp "$CONFIG_REPO/.github/workflows/ci.yml" .github/workflows/ 2>/dev/null || true + echo -e " ${GREEN}✓${NC} Copied CI workflow" + fi + + # Copy issue templates + if [ -d "$CONFIG_REPO/.github/ISSUE_TEMPLATE" ]; then + mkdir -p .github/ISSUE_TEMPLATE + cp -r "$CONFIG_REPO/.github/ISSUE_TEMPLATE/"* .github/ISSUE_TEMPLATE/ 2>/dev/null || true + echo -e " ${GREEN}✓${NC} Copied issue templates" + fi + + # Copy PR template + if [ -f "$CONFIG_REPO/.github/PULL_REQUEST_TEMPLATE.md" ]; then + cp "$CONFIG_REPO/.github/PULL_REQUEST_TEMPLATE.md" .github/ + echo -e " ${GREEN}✓${NC} Copied PR template" + fi + + echo "" +fi + +# Step 5: Development tools +echo -e "${BLUE}✅ Step 5: Setup development tools${NC}" + +# Package.json +cat > package.json <<'EOF' +{ + "name": "new-project", + "version": "1.0.0", + "description": "New project bootstrapped from config repository", + "scripts": { + "lint": "eslint . --ext .js", + "lint:fix": "npm run lint -- --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "prepare": "husky" + }, + "devDependencies": { + "@commitlint/cli": "^18.0.0", + "@commitlint/config-conventional": "^18.0.0", + "eslint": "^8.0.0", + "husky": "^9.0.0", + "jest": "^29.0.0", + "prettier": "^3.0.0" + } +} +EOF + +echo -e " ${GREEN}✓${NC} Created package.json" + +# ESLint +if [ -f "$CONFIG_REPO/eslint.config.mjs" ]; then + cp "$CONFIG_REPO/eslint.config.mjs" . + echo -e " ${GREEN}✓${NC} Copied ESLint config" +fi + +# Prettier +if [ -f "$CONFIG_REPO/.prettierrc" ]; then + cp "$CONFIG_REPO/.prettierrc" . + echo -e " ${GREEN}✓${NC} Copied Prettier config" +fi + +# Jest +if [ -f "$CONFIG_REPO/jest.config.js" ]; then + cp "$CONFIG_REPO/jest.config.js" . + echo -e " ${GREEN}✓${NC} Copied Jest config" +fi + +echo "" + +# Step 6: Documentation +echo -e "${BLUE}✅ Step 6: Create documentation${NC}" + +# README.md +cat > README.md < + +## Features + + + +## Getting Started + +### Prerequisites + +- Node.js 22+ +- npm or pnpm + +### Installation + +\`\`\`bash +npm install +\`\`\` + +### Development + +\`\`\`bash +npm run dev +\`\`\` + +### Testing + +\`\`\`bash +npm test +npm run test:coverage +\`\`\` + +## Contributing + +Please read [CLAUDE.md](./CLAUDE.md) for development guidelines. + +## License + +This project is licensed under the $LICENSE License. +EOF + +echo -e " ${GREEN}✓${NC} Created README.md" + +# CLAUDE.md (simplified version) +if [ -f "$CONFIG_REPO/.claude/CLAUDE.md" ]; then + cp "$CONFIG_REPO/.claude/CLAUDE.md" CLAUDE.md + echo -e " ${GREEN}✓${NC} Created CLAUDE.md" +fi + +# SECURITY.md +cat > SECURITY.md <<'EOF' +# Security Policy + +## Reporting a Vulnerability + +Please report security vulnerabilities to: security@example.com + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.x.x | :white_check_mark: | + +## Security Best Practices + +- Keep dependencies up-to-date +- Run security audits regularly (`npm audit`) +- Follow principle of least privilege +- Never commit secrets or credentials +EOF + +echo -e " ${GREEN}✓${NC} Created SECURITY.md" + +echo "" + +# Step 7: Install dependencies +if [ "$NO_INSTALL" = false ]; then + echo -e "${BLUE}✅ Step 7: Install dependencies${NC}" + if npm install > /dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} npm install completed" + + if npx husky init > /dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} Husky hooks installed" + fi + else + echo -e " ${YELLOW}⚠${NC} npm install failed (run manually)" + fi + echo "" +fi + +# Summary +echo -e "${GREEN}✨ Repository setup complete!${NC}" +echo "" +echo "Next steps:" +echo " 1. cd $TARGET_ABS" +echo " 2. Update README.md with project details" +echo " 3. Update package.json (name, description, etc.)" +echo " 4. Create first commit: git commit -m \"chore: initial setup\"" +if command -v gh > /dev/null 2>&1; then + echo " 5. Create GitHub repo: gh repo create" + echo " 6. Push to GitHub: git push -u origin main" +fi diff --git a/script/test-coverage-trend.sh b/script/test-coverage-trend.sh new file mode 100755 index 00000000..fb68f624 --- /dev/null +++ b/script/test-coverage-trend.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +# Test Coverage Trend - Track coverage over time + +set -euo pipefail + +# Colors +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +# shellcheck disable=SC2034 +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Options +DAYS=30 +SHOW_GRAPH=false +# shellcheck disable=SC2034 +DETAILED=false +EXPORT_CSV="" +JSON_OUTPUT=false +RECORD_MODE=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --days) + DAYS="$2" + shift 2 + ;; + --graph) + SHOW_GRAPH=true + shift + ;; + --detailed) + # shellcheck disable=SC2034 + DETAILED=true + shift + ;; + --export) + EXPORT_CSV="$2" + shift 2 + ;; + --json) + JSON_OUTPUT=true + shift + ;; + --record) + RECORD_MODE=true + shift + ;; + --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --days N Show last N days (default: 30)" + echo " --graph Show ASCII graph" + echo " --detailed Show per-file details" + echo " --export FILE Export to CSV" + echo " --json JSON output" + echo " --record Record current coverage" + echo " --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +HISTORY_DIR=".coverage-history" +mkdir -p "$HISTORY_DIR" + +# Record current coverage +if [ "$RECORD_MODE" = true ]; then + if [ ! -f "coverage/coverage-summary.json" ]; then + echo "Error: coverage/coverage-summary.json not found" + echo "Run: npm run test:coverage" + exit 1 + fi + + DATE=$(date +%Y-%m-%d) + COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + + # Extract coverage metrics + LINE_COV=$(node -pe "JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json')).total.lines.pct") + BRANCH_COV=$(node -pe "JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json')).total.branches.pct") + FUNC_COV=$(node -pe "JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json')).total.functions.pct") + STMT_COV=$(node -pe "JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json')).total.statements.pct") + + # Save to history + cat > "$HISTORY_DIR/$DATE.json" </dev/null || echo "0") + + if (( $(echo "$COV > 0" | bc -l) )); then + TOTAL=$(echo "$TOTAL + $COV" | bc) + ((COUNT++)) + + if (( $(echo "$COV < $MIN" | bc -l) )); then + MIN=$COV + fi + + if (( $(echo "$COV > $MAX" | bc -l) )); then + MAX=$COV + fi + + if [ $COUNT -eq 1 ]; then + NEWEST_COV=$COV + fi + OLDEST_COV=$COV + fi + fi +done + +if [ $COUNT -eq 0 ]; then + echo "No valid coverage data found" + exit 0 +fi + +AVG=$(echo "scale=1; $TOTAL / $COUNT" | bc) + +# Current coverage (most recent) +CURRENT_FILE="${HISTORY_FILES[0]}" +CURRENT_LINE=$(node -pe "JSON.parse(require('fs').readFileSync('$CURRENT_FILE')).coverage.lines") +CURRENT_BRANCH=$(node -pe "JSON.parse(require('fs').readFileSync('$CURRENT_FILE')).coverage.branches") +CURRENT_FUNC=$(node -pe "JSON.parse(require('fs').readFileSync('$CURRENT_FILE')).coverage.functions") +CURRENT_STMT=$(node -pe "JSON.parse(require('fs').readFileSync('$CURRENT_FILE')).coverage.statements") + +if [ "$JSON_OUTPUT" = true ]; then + # JSON output + cat < $OLDEST_COV" | bc -l) )); then echo "improving"; else echo "declining"; fi)", + "days": $COUNT +} +EOF +else + # Human-readable output + echo -e "${BLUE}📊 Current Coverage${NC}" + echo " • Lines: $CURRENT_LINE%" + echo " • Branches: $CURRENT_BRANCH%" + echo " • Functions: $CURRENT_FUNC%" + echo " • Statements: $CURRENT_STMT%" + echo "" + + if (( $(echo "$CURRENT_LINE >= 70" | bc -l) )); then + echo -e "${GREEN}✅ Above threshold (70%)${NC}" + else + echo -e "${RED}🚨 Below threshold (70%)${NC}" + fi + echo "" + + echo -e "${BLUE}📈 Statistics (last $COUNT days)${NC}" + echo " • Average: $AVG%" + echo " • Min: $MIN%" + echo " • Max: $MAX%" + + # Trend + TREND_DIFF=$(echo "$NEWEST_COV - $OLDEST_COV" | bc) + if (( $(echo "$TREND_DIFF > 2" | bc -l) )); then + echo -e " • Trend: ${GREEN}↗ Improving (+${TREND_DIFF}% over period)${NC}" + elif (( $(echo "$TREND_DIFF < -2" | bc -l) )); then + echo -e " • Trend: ${RED}↘ Declining (${TREND_DIFF}% over period)${NC}" + else + echo " • Trend: → Stable" + fi + echo "" + + # Simple ASCII graph + if [ "$SHOW_GRAPH" = true ]; then + echo -e "${BLUE}📈 Coverage Trend${NC}" + echo "" + # This is a simplified visualization + # A full implementation would use actual charting + echo " (Graph visualization would appear here)" + echo "" + fi + + # Recommendations + echo -e "${BLUE}💡 Recommendations${NC}" + if (( $(echo "$CURRENT_LINE < 70" | bc -l) )); then + echo " • Coverage is below 70% threshold" + echo " • Add more tests to improve coverage" + elif (( $(echo "$TREND_DIFF < -2" | bc -l) )); then + echo " • Coverage is declining" + echo " • Review recent changes and add missing tests" + else + echo " • Coverage trend is positive - keep it up!" + fi +fi + +# Export to CSV +if [ -n "$EXPORT_CSV" ]; then + echo "date,commit,lines,branches,functions,statements" > "$EXPORT_CSV" + for file in "${HISTORY_FILES[@]}"; do + if [ -f "$file" ]; then + DATE=$(node -pe "JSON.parse(require('fs').readFileSync('$file')).date") + COMMIT=$(node -pe "JSON.parse(require('fs').readFileSync('$file')).commit") + LINES=$(node -pe "JSON.parse(require('fs').readFileSync('$file')).coverage.lines") + BRANCHES=$(node -pe "JSON.parse(require('fs').readFileSync('$file')).coverage.branches") + FUNCTIONS=$(node -pe "JSON.parse(require('fs').readFileSync('$file')).coverage.functions") + STATEMENTS=$(node -pe "JSON.parse(require('fs').readFileSync('$file')).coverage.statements") + echo "$DATE,$COMMIT,$LINES,$BRANCHES,$FUNCTIONS,$STATEMENTS" >> "$EXPORT_CSV" + fi + done + echo "Exported to $EXPORT_CSV" +fi