Merge pull request #1 from StrangeDaysTech/feat/rebranding-and-cli-sc… #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================= | |
| # DevTrail - Documentation Validation Workflow | |
| # ============================================================================= | |
| # | |
| # This workflow validates documentation on each Pull Request and push to main. | |
| # https://strangedays.tech | |
| # | |
| # Executes: | |
| # 1. File naming convention validation | |
| # 2. Metadata (front-matter) validation | |
| # 3. Sensitive information detection | |
| # 4. Markdown linting | |
| # 5. Internal link verification | |
| # | |
| # ============================================================================= | |
| name: Documentation Validation | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths: | |
| - '.devtrail/**' | |
| - '.github/workflows/docs-validation.yml' | |
| pull_request: | |
| branches: [main, develop] | |
| paths: | |
| - '.devtrail/**' | |
| jobs: | |
| validate-docs: | |
| name: Validate Documentation | |
| runs-on: ubuntu-latest | |
| steps: | |
| # ========================================================================= | |
| # Checkout | |
| # ========================================================================= | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required to compare with base branch | |
| # ========================================================================= | |
| # Setup Node.js (for markdownlint) | |
| # ========================================================================= | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install markdownlint-cli | |
| run: npm install -g markdownlint-cli | |
| # ========================================================================= | |
| # Get changed files | |
| # ========================================================================= | |
| - name: Get changed files | |
| id: changed-files | |
| uses: tj-actions/changed-files@v44 | |
| with: | |
| files: | | |
| .devtrail/**/*.md | |
| # ========================================================================= | |
| # Validate file naming convention | |
| # ========================================================================= | |
| - name: Validate file naming convention | |
| if: steps.changed-files.outputs.any_changed == 'true' | |
| run: | | |
| echo "📋 Validating file naming convention..." | |
| ERRORS=0 | |
| VALID_PATTERN="^(ADR|REQ|TES|OPS|INC|TDE|AILOG|AIDEC|ETH|DOC)-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{3}-[a-z0-9-]+\.md$" | |
| EXCLUDED="PRINCIPLES.md|DOCUMENTATION-POLICY.md|AGENT-RULES.md|TEMPLATE-.*\.md|README.md|QUICK-REFERENCE.md|INDEX.md|GIT-BRANCHING-STRATEGY.md" | |
| for file in ${{ steps.changed-files.outputs.all_changed_files }}; do | |
| filename=$(basename "$file") | |
| # Skip excluded files | |
| if echo "$filename" | grep -qE "$EXCLUDED"; then | |
| echo " ⊘ Excluded: $filename" | |
| continue | |
| fi | |
| # Validate naming convention | |
| if ! echo "$filename" | grep -qE "$VALID_PATTERN"; then | |
| echo " ✗ Invalid naming: $filename" | |
| echo " Expected: [TYPE]-[YYYY-MM-DD]-[NNN]-[description].md" | |
| ERRORS=$((ERRORS + 1)) | |
| else | |
| echo " ✓ $filename" | |
| fi | |
| done | |
| if [ $ERRORS -gt 0 ]; then | |
| echo "::error::Found $ERRORS naming convention errors" | |
| exit 1 | |
| fi | |
| echo "✅ Naming convention valid" | |
| # ========================================================================= | |
| # Validate front-matter | |
| # ========================================================================= | |
| - name: Validate front-matter metadata | |
| if: steps.changed-files.outputs.any_changed == 'true' | |
| run: | | |
| echo "📋 Validating metadata..." | |
| ERRORS=0 | |
| EXCLUDED="PRINCIPLES.md|DOCUMENTATION-POLICY.md|AGENT-RULES.md|TEMPLATE-.*\.md|README.md|QUICK-REFERENCE.md|INDEX.md|GIT-BRANCHING-STRATEGY.md" | |
| REQUIRED_FIELDS="id title status created" | |
| for file in ${{ steps.changed-files.outputs.all_changed_files }}; do | |
| filename=$(basename "$file") | |
| # Skip excluded files | |
| if echo "$filename" | grep -qE "$EXCLUDED"; then | |
| continue | |
| fi | |
| # Verify front-matter exists | |
| if ! head -1 "$file" | grep -q "^---"; then | |
| echo " ✗ Missing front-matter: $filename" | |
| ERRORS=$((ERRORS + 1)) | |
| continue | |
| fi | |
| # Verify required fields | |
| for field in $REQUIRED_FIELDS; do | |
| if ! grep -q "^$field:" "$file"; then | |
| echo " ✗ Missing field '$field' in: $filename" | |
| ERRORS=$((ERRORS + 1)) | |
| fi | |
| done | |
| done | |
| if [ $ERRORS -gt 0 ]; then | |
| echo "::error::Found $ERRORS metadata errors" | |
| exit 1 | |
| fi | |
| echo "✅ Metadata valid" | |
| # ========================================================================= | |
| # Detect sensitive information | |
| # ========================================================================= | |
| - name: Check for sensitive information | |
| if: steps.changed-files.outputs.any_changed == 'true' | |
| run: | | |
| echo "🔒 Checking for sensitive information..." | |
| WARNINGS=0 | |
| PATTERNS="password|api_key|apikey|secret|token|private_key|credentials" | |
| for file in ${{ steps.changed-files.outputs.all_changed_files }}; do | |
| MATCHES=$(grep -inE "$PATTERNS" "$file" 2>/dev/null | head -5 || true) | |
| if [ -n "$MATCHES" ]; then | |
| echo "::warning file=$file::Possible sensitive information detected" | |
| echo "$MATCHES" | |
| WARNINGS=$((WARNINGS + 1)) | |
| fi | |
| done | |
| if [ $WARNINGS -gt 0 ]; then | |
| echo "⚠️ Detected $WARNINGS files with possible sensitive information" | |
| else | |
| echo "✅ No sensitive information detected" | |
| fi | |
| # ========================================================================= | |
| # Markdown Lint | |
| # ========================================================================= | |
| - name: Run markdownlint | |
| if: steps.changed-files.outputs.any_changed == 'true' | |
| run: | | |
| echo "📝 Running markdownlint..." | |
| # Create temporary configuration | |
| cat > .markdownlint.json << 'EOF' | |
| { | |
| "default": true, | |
| "MD013": false, | |
| "MD033": false, | |
| "MD041": false, | |
| "MD024": { "siblings_only": true } | |
| } | |
| EOF | |
| markdownlint ${{ steps.changed-files.outputs.all_changed_files }} || { | |
| echo "::warning::markdownlint found formatting issues" | |
| } | |
| echo "✅ Linting completed" | |
| # ========================================================================= | |
| # Verify internal links | |
| # ========================================================================= | |
| - name: Check internal links | |
| if: steps.changed-files.outputs.any_changed == 'true' | |
| run: | | |
| echo "🔗 Verifying internal links..." | |
| ERRORS=0 | |
| for file in ${{ steps.changed-files.outputs.all_changed_files }}; do | |
| # Extract internal markdown links: [text](path) | |
| LINKS=$(grep -oE '\[.+\]\([^http][^)]+\)' "$file" 2>/dev/null || true) | |
| for link in $LINKS; do | |
| # Extract only the path | |
| path=$(echo "$link" | sed 's/.*](//' | sed 's/)//' | sed 's/#.*//') | |
| if [ -n "$path" ]; then | |
| # Resolve relative path | |
| dir=$(dirname "$file") | |
| fullpath="$dir/$path" | |
| if [ ! -f "$fullpath" ] && [ ! -d "$fullpath" ]; then | |
| echo " ✗ Broken link in $file: $path" | |
| ERRORS=$((ERRORS + 1)) | |
| fi | |
| fi | |
| done | |
| done | |
| if [ $ERRORS -gt 0 ]; then | |
| echo "::warning::Found $ERRORS broken links" | |
| else | |
| echo "✅ All internal links are valid" | |
| fi | |
| # ========================================================================= | |
| # Summary | |
| # ========================================================================= | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "═══════════════════════════════════════════════════════════════" | |
| echo "📊 Documentation validation completed" | |
| echo "═══════════════════════════════════════════════════════════════" | |
| echo "" | |
| echo "Files validated: ${{ steps.changed-files.outputs.all_changed_files_count }}" | |
| # =========================================================================== | |
| # Job: Generate documentation index (only on main) | |
| # =========================================================================== | |
| generate-index: | |
| name: Generate Documentation Index | |
| runs-on: ubuntu-latest | |
| needs: validate-docs | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Generate documentation index | |
| run: | | |
| echo "📚 Generating documentation index..." | |
| cat > .devtrail/INDEX.md << 'EOF' | |
| # Documentation Index | |
| *Automatically generated on $(date -u +"%Y-%m-%d %H:%M UTC")* | |
| ## Governance | |
| EOF | |
| # List documents by folder | |
| for folder in .devtrail/*/; do | |
| folder_name=$(basename "$folder") | |
| echo "" >> .devtrail/INDEX.md | |
| echo "## ${folder_name}" >> .devtrail/INDEX.md | |
| echo "" >> .devtrail/INDEX.md | |
| find "$folder" -name "*.md" -type f | sort | while read file; do | |
| filename=$(basename "$file") | |
| # Extract title from front-matter or use filename | |
| title=$(grep "^title:" "$file" 2>/dev/null | sed 's/title: *//' | head -1 || echo "$filename") | |
| echo "- [$title]($file)" >> .devtrail/INDEX.md | |
| done | |
| done | |
| echo "✅ Index generated: .devtrail/INDEX.md" | |
| - name: Commit index if changed | |
| run: | | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| if git diff --quiet .devtrail/INDEX.md; then | |
| echo "No changes to index" | |
| else | |
| git add .devtrail/INDEX.md | |
| git commit -m "docs: update documentation index [skip ci]" | |
| git push | |
| fi |