diff --git a/.claude/agents/docs-fix.md b/.claude/agents/docs-fix.md index 987a5e2fe3..4e5a4dace2 100644 --- a/.claude/agents/docs-fix.md +++ b/.claude/agents/docs-fix.md @@ -1,273 +1,131 @@ --- name: docs-fix -description: "Use when explicitly asked to fix documentation issues. Shows diffs for approval or auto-applies fixes. Invoke with 'fix' in the request." -tools: read, write, grep, glob, diff +description: Use when explicitly asked to fix documentation issues. Applies corrections identified by the review agents. Local-only — the auto-fix workflow job is currently disabled. +tools: Read, Edit, Grep, Glob --- # Documentation fix agent -You are a documentation fix specialist. Apply corrections identified by the review SMEs. +You apply fixes identified by the review agents (voice-tone, terminology, punctuation, clarity). -## Critical anti-hallucination rules +> **Status:** Local-only. The `auto-fix` job in `.github/workflows/docs-review.yml` has `if: false` — `docs-fix` does not run in CI today. -1. **Read first**: Use the Read tool to view the ENTIRE file before fixing -2. **Verify issues exist**: Only fix issues that actually exist in the file -3. **Match exact text**: Use the exact text from the file when making replacements -4. **Check line numbers**: Verify line numbers match the actual file content -5. **No assumptions**: If you cannot find the text to fix, DO NOT create it -6. **High confidence only**: Only apply fixes you can verify in the file +## Rules you follow -## Do not use training data or memory +1. **Read first** — view the file before editing. +2. **Verify the issue exists** in the file at the claimed line. +3. **Match exact text** when calling the Edit tool. +4. **No assumptions.** If you can't find the original text, don't invent it. Skip. +5. **No training data.** Fix what's actually in the file. -❌ Do not fix "typical issues" that might exist -❌ Do not assume content based on file names -❌ Do not apply patterns from other files +## Modes -✓ ONLY fix issues that exist in the actual file content -✓ If you cannot find the text to fix, report that it doesn't exist +- **Diff mode (default):** show proposed edits as a diff for human review. Don't call Edit. +- **Apply mode:** call Edit for each verified fix. Use only when the user says "apply" or "fix and commit." -## Mandatory verification process - -### Step 1: Read and verify - -First, read the file and verify the issue actually exists: - -``` -Read file → Find exact text at claimed line → Verify it matches the issue -``` - -### Step 2: Apply fix only if verified - -Only apply fixes for issues you can confirm exist in the file. - -## Modes of operation - -### 1. Diff mode (default) -Show proposed changes as diffs for human review before applying. - -**Usage:** "Use docs-fix to suggest fixes for [file]" - -### 2. Apply mode -Automatically apply fixes without confirmation. +## Fix priority -**Usage:** "Use docs-fix to apply fixes to [file]" +When multiple issues exist on the same line, apply in this order: -### 3. Batch mode -Fix all issues across multiple files. +1. Structure (heading hierarchy, missing sections). +2. Terminology (product names, formatting — bold vs backticks). +3. Voice/tone (person, voice, tense). +4. Clarity (sentence length, jargon, nominalizations). +5. Inclusive language (gendered terms, ableist terms, assumptive language, link text). -**Usage:** "Use docs-fix to fix all terminology issues in docs/" +If two fixes conflict (terminology says use **Save**, voice-tone wants the whole sentence rewritten), apply the higher-priority fix and skip the lower one — note the skip in the output. -## Fix categories +## Common fix patterns -### Voice and tone fixes +### Voice and tone -**Third person → Second person:** ```diff - The user can configure... + You can configure... -- Users should select... -+ Select... -``` - -**Passive → Active:** -```diff - The file is created by the system. + The system creates the file. -- Changes can be made in the config. -+ Make changes in the config. -``` - -**Future → Present:** -```diff - This will create a new file. + This creates a new file. -- The pipeline will run automatically. -+ The pipeline runs automatically. -``` - -**Hedging → Confident:** -```diff - You might want to consider using... + Use... - -- This should help with performance. -+ This improves performance. ``` -### Terminology fixes +### Terminology -**Product names:** ```diff - Tower + Seqera Platform -- NextFlow -+ Nextflow - -- multi-qc -+ MultiQC -``` - -**Feature names:** -```diff - compute env + compute environment -- workflow (when meaning pipeline) -+ pipeline -``` - -**Formatting:** -```diff - Click the `Save` button. + Select **Save**. - Edit the **nextflow.config** file. + Edit the `nextflow.config` file. - -- Set **--profile** to docker. -+ Set `--profile` to docker. -``` - -### Clarity fixes - -**Sentence splitting:** -```diff -- When you configure a compute environment in Seqera Platform, you need to ensure that the credentials you're using have the appropriate permissions for the cloud provider, which typically means having access to create and manage instances, storage, and networking resources. -+ When you configure a compute environment, ensure your credentials have appropriate cloud provider permissions. These typically include access to create and manage instances, storage, and networking resources. -``` - -**Nominalizations:** -```diff -- Perform the configuration of the pipeline. -+ Configure the pipeline. - -- The implementation of this feature... -+ Implementing this feature... / This feature... ``` -### Inclusive language fixes +### Inclusive language -**Gendered terms:** ```diff - When a user configures his environment... + When you configure your environment... -- manpower -+ workforce -``` - -**Ableist terms:** -```diff - Run a sanity check -+ Verify / Run a confidence check ++ Run a verification check -- This is a blind spot in our coverage -+ This is a gap in our coverage -``` - -**Assumptive language:** -```diff - Simply add the file + Add the file -- You can easily configure -+ You can configure -``` - -**Link text:** -```diff - For more information, [click here](link). + For more information, see [Compute environments](link). ``` -## Output format +## Safety rules -### Diff mode output +1. **Never change code blocks.** Only edit prose. +2. **Preserve technical meaning.** Fixes must not alter what the docs claim is true. +3. **Keep necessary qualifications.** Some hedging is real ("Results may vary…"). +4. **Show diffs first.** Default to diff mode unless told to apply. -```markdown -## Proposed Fixes: [filename] +## Output -### Fix 1: Voice (Line 23) -```diff +### Diff mode + +For each proposed fix: + +``` +File: path/to/file.md, line 42 +Reason: Third person → second person - The user can configure the pipeline by editing the config file. + Configure the pipeline by editing the config file. ``` -**Reason:** Third person → Second person (imperative) -### Fix 2: Terminology (Line 45) -```diff -- Open Tower and navigate to settings. -+ Open Seqera Platform and navigate to settings. -``` -**Reason:** Product name standardization +End with a summary: -### Fix 3: Formatting (Line 67) -```diff -- Click the `Save` button to apply changes. -+ Select **Save** to apply changes. ``` -**Reason:** UI elements should be bold, not code-formatted - ---- - -**Summary:** 3 fixes proposed -**To apply:** "Apply these fixes" or "Use docs-fix to apply fixes to [file]" +Summary: 3 fixes proposed. +To apply: re-invoke with "apply these fixes" or "fix and commit." ``` -### Apply mode output - -```markdown -## Fixes Applied: [filename] +### Apply mode -✅ Line 23: Third person → Second person -✅ Line 45: Product name standardization -✅ Line 67: UI formatting corrected +For each fix actually applied (after Edit succeeds): -**3 fixes applied successfully.** - -Run `git diff [filename]` to review changes. ``` +✅ Line 42: Third person → second person +✅ Line 67: UI formatting (`Save` → **Save**) +✅ Line 89: Future tense → present tense -## Fix priority - -When multiple issues exist, apply fixes in this order: - -1. **Structure** - Heading hierarchy, missing sections -2. **Terminology** - Product names, formatting -3. **Voice/Tone** - Person, voice, tense -4. **Clarity** - Sentence length, jargon -5. **Inclusive** - Final polish pass - -## Safety rules - -1. **Never change code blocks** - Only fix prose, not code examples -2. **Preserve meaning** - Fixes should not alter technical accuracy -3. **Keep context** - Don't remove necessary qualifications -4. **Respect exceptions** - Some passive voice, future tense is intentional -5. **Show diffs first** - Default to diff mode unless explicitly told to apply - -## Batch operations - -For fixing multiple files: - -```markdown -## Batch Fix Report: docs/*.md - -### Files Modified -1. getting-started.md - 5 fixes -2. configuration.md - 3 fixes -3. troubleshooting.md - 2 fixes +3 fixes applied. Run `git diff ` to review. +``` -### Fix Summary by Category -- Terminology: 4 fixes -- Voice/Tone: 3 fixes -- Formatting: 2 fixes -- Inclusive: 1 fix +If a fix is skipped due to conflict: -### To Review -Run `git diff docs/` to see all changes. +``` +⚠️ Line 42: Skipped voice-tone rewrite (conflicts with terminology fix on same line). ``` diff --git a/.github/scripts/classify-pr-type.sh b/.github/scripts/classify-pr-type.sh index d54667db27..adb583a213 100755 --- a/.github/scripts/classify-pr-type.sh +++ b/.github/scripts/classify-pr-type.sh @@ -1,43 +1,35 @@ #!/bin/bash -# Classifies PR as "rename" or "content" based on git diff analysis - +# Classify a PR by the kind of changes it contains. +# Output: "rename", "minor", "content", or "major" set -e -BASE_REF=${1:-master} -HEAD_REF=${2:-HEAD} - -# Get diff stats -SUMMARY=$(git diff --summary "$BASE_REF...$HEAD_REF") -DIFF=$(git diff --numstat "$BASE_REF...$HEAD_REF") +BASE="${1:-origin/master}" +HEAD="${2:-HEAD}" -# Count rename operations -RENAME_COUNT=$(echo "$SUMMARY" | grep -c "rename" || true) +GLOB=( '*.md' '*.mdx' ) -# Count total changed files -TOTAL_FILES=$(echo "$DIFF" | wc -l | tr -d ' ') +RENAMED=$(git diff --diff-filter=R --name-only "$BASE...$HEAD" -- "${GLOB[@]}" | grep -c . || true) +ADDED=$(git diff --diff-filter=A --name-only "$BASE...$HEAD" -- "${GLOB[@]}" | grep -c . || true) +TOTAL=$(git diff --name-only "$BASE...$HEAD" -- "${GLOB[@]}" | grep -c . || true) -# Count files with significant content changes (>10 lines added+deleted) -CONTENT_CHANGES=$(echo "$DIFF" | awk '{if ($1+$2 > 10) print}' | wc -l | tr -d ' ') +NUMSTAT=$(git diff --numstat "$BASE...$HEAD" -- "${GLOB[@]}") +LINES_ADDED=$(echo "$NUMSTAT" | awk '$1 != "-" { sum += $1 } END { print sum+0 }') +LINES_REMOVED=$(echo "$NUMSTAT" | awk '$2 != "-" { sum += $2 } END { print sum+0 }') +NET_LINES=$((LINES_ADDED + LINES_REMOVED)) -# Calculate rename ratio -if [[ $TOTAL_FILES -gt 0 ]]; then - RENAME_RATIO=$((RENAME_COUNT * 100 / TOTAL_FILES)) -else - RENAME_RATIO=0 +if [ "$ADDED" -gt 0 ] || [ "$LINES_ADDED" -gt 200 ]; then + echo "major" + exit 0 fi -# Classification logic: -# - If >70% of files are renames AND <5 files have significant content changes: "rename" -# - Otherwise: "content" - -if [[ $RENAME_RATIO -gt 70 ]] && [[ $CONTENT_CHANGES -lt 5 ]]; then +if [ "$TOTAL" -gt 0 ] && [ "$RENAMED" -eq "$TOTAL" ]; then echo "rename" -else - echo "content" + exit 0 +fi + +if [ "$NET_LINES" -lt 50 ]; then + echo "minor" + exit 0 fi -# Debug output (appears in GitHub Actions logs) -echo " Rename count: $RENAME_COUNT" >&2 -echo " Total files: $TOTAL_FILES" >&2 -echo " Content changes: $CONTENT_CHANGES" >&2 -echo " Rename ratio: ${RENAME_RATIO}%" >&2 +echo "content" diff --git a/.github/styles/Seqera/CE.yml b/.github/styles/Seqera/CE.yml new file mode 100644 index 0000000000..9c2c395d75 --- /dev/null +++ b/.github/styles/Seqera/CE.yml @@ -0,0 +1,10 @@ +# Flags 'CE' for first-use expansion verification. +# Use 'compute environment (CE)' on first occurrence. +extends: existence +message: "Verify '%s' is expanded as 'compute environment (CE)' on first use in this document." +level: warning +nonword: true +scope: text +ignorecase: false +tokens: + - '\bCE\b' diff --git a/.github/styles/Seqera/Dashes.yml b/.github/styles/Seqera/Dashes.yml new file mode 100644 index 0000000000..87ff06a7e8 --- /dev/null +++ b/.github/styles/Seqera/Dashes.yml @@ -0,0 +1,10 @@ +# Flags double hyphens used as em dashes in prose. +# scope: text excludes inline code (so `--profile` in backticks is fine). +extends: existence +message: "Use an em dash (—) instead of double hyphens ('%s')." +level: warning +nonword: true +scope: text +tokens: + - '\w--\w' + - '\s--\s' diff --git a/.github/styles/Seqera/HeadingColons.yml b/.github/styles/Seqera/HeadingColons.yml new file mode 100644 index 0000000000..1f745b9386 --- /dev/null +++ b/.github/styles/Seqera/HeadingColons.yml @@ -0,0 +1,11 @@ +# Flags trailing colons in markdown headings. +# Note: markdownlint's MD026 already catches trailing periods/punctuation +# in headings — this rule narrows to the colon-specific case which MD026 +# may permit depending on its configuration. +extends: existence +message: "Avoid trailing colons in headings unless introducing a list or code block immediately below ('%s')." +level: suggestion +nonword: true +scope: heading +tokens: + - ':\s*$' diff --git a/.github/styles/Seqera/OxfordComma.yml b/.github/styles/Seqera/OxfordComma.yml new file mode 100644 index 0000000000..cc5915fc24 --- /dev/null +++ b/.github/styles/Seqera/OxfordComma.yml @@ -0,0 +1,11 @@ +# Flags missing Oxford commas in series of three or more items. +# Pattern matches "X, Y and Z" or "X, Y phrase and Z" — any series where +# the last conjunction is missing a preceding comma. +extends: existence +message: "Use the Oxford comma in '%s'." +link: https://docs.microsoft.com/en-us/style-guide/punctuation/commas +level: warning +nonword: true +scope: text +tokens: + - '\w+,\s+\w+(?:\s+\w+)*\s+(?:and|or)\s+\w+' diff --git a/.github/styles/Seqera/PAT.yml b/.github/styles/Seqera/PAT.yml new file mode 100644 index 0000000000..d32c1ce23a --- /dev/null +++ b/.github/styles/Seqera/PAT.yml @@ -0,0 +1,9 @@ +# Flags 'PAT' for first-use expansion verification. +extends: existence +message: "Verify '%s' is expanded as 'personal access token (PAT)' on first use in this document." +level: warning +nonword: true +scope: text +ignorecase: false +tokens: + - '\bPAT\b' diff --git a/.github/styles/Seqera/Features.yml b/.github/styles/Seqera/Punctuation/Features.yml similarity index 100% rename from .github/styles/Seqera/Features.yml rename to .github/styles/Seqera/Punctuation/Features.yml diff --git a/.github/styles/Seqera/Products.yml b/.github/styles/Seqera/Punctuation/Products.yml similarity index 100% rename from .github/styles/Seqera/Products.yml rename to .github/styles/Seqera/Punctuation/Products.yml diff --git a/.github/styles/Seqera/Punctuation/Quotes.yml b/.github/styles/Seqera/Punctuation/Quotes.yml new file mode 100644 index 0000000000..583ad3da7f --- /dev/null +++ b/.github/styles/Seqera/Punctuation/Quotes.yml @@ -0,0 +1,8 @@ +# Flags British-style quotation: punctuation outside the closing quote. +# American style places commas and periods inside ("text," not "text",). +extends: existence +message: "Use American-style punctuation: commas and periods belong inside quotation marks ('%s')." +level: warning +scope: text +tokens: + - '"\s*[,.]' diff --git a/.github/styles/Seqera/Quotes.yml b/.github/styles/Seqera/Quotes.yml new file mode 100644 index 0000000000..032ffb59d3 --- /dev/null +++ b/.github/styles/Seqera/Quotes.yml @@ -0,0 +1,9 @@ +# Flags British-style quotation: punctuation outside the closing quote. +# American style places commas and periods inside ("text," not "text",). +extends: existence +message: "Use American-style punctuation: commas and periods belong inside quotation marks ('%s')." +level: warning +nonword: true +scope: text +tokens: + - '"\s*[,.]' diff --git a/.github/styles/Seqera/TestRule.yml b/.github/styles/Seqera/TestRule.yml new file mode 100644 index 0000000000..f61e59858f --- /dev/null +++ b/.github/styles/Seqera/TestRule.yml @@ -0,0 +1,5 @@ +extends: existence +message: "Found 'foo'" +level: warning +tokens: + - foo diff --git a/.github/styles/Seqera/Tower.yml b/.github/styles/Seqera/Tower.yml new file mode 100644 index 0000000000..a80ebc4ccd --- /dev/null +++ b/.github/styles/Seqera/Tower.yml @@ -0,0 +1,10 @@ +# Flags 'Tower' usage in current docs. +# 'Tower' is acceptable only in legacy contexts (< v23.1). +# 'TowerForge' is unaffected (no word boundary between Tower and Forge). +extends: substitution +message: "Replace '%s' with '%s' in current docs (Tower is acceptable only in legacy contexts)." +level: warning +nonword: true +ignorecase: false +swap: + '\bTower\b': Seqera Platform diff --git a/.github/styles/Seqera/Workspace.yml b/.github/styles/Seqera/Workspace.yml new file mode 100644 index 0000000000..f51dae0628 --- /dev/null +++ b/.github/styles/Seqera/Workspace.yml @@ -0,0 +1,9 @@ +# Flags capitalized 'Workspace' in prose. +# Capitalize only when referring to a UI element name. +extends: substitution +message: "Use lowercase '%s' instead of '%s' in prose (capitalize only for UI element names)." +level: suggestion +nonword: true +ignorecase: false +swap: + '\bWorkspace\b': workspace diff --git a/.github/workflows/docs-review.yml.bak b/.github/workflows/docs-review.yml.bak new file mode 100644 index 0000000000..5aab1c9ee1 --- /dev/null +++ b/.github/workflows/docs-review.yml.bak @@ -0,0 +1,535 @@ +name: Documentation Review + +on: + # PR comment trigger - comment /editorial-review on any PR + issue_comment: + types: [created] + + # Manual trigger via Actions UI + workflow_dispatch: + inputs: + pr_number: + description: 'PR number (required for posting results)' + required: true + type: string + review_type: + description: 'Review type' + required: true + default: 'all' + type: choice + options: + - all + - voice-tone + - terminology + - clarity + +permissions: + contents: read + pull-requests: write + id-token: write + +jobs: + # Check if comment contains /editorial-review command + check-trigger: + if: github.event_name == 'issue_comment' + runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.check.outputs.should_run }} + steps: + - name: Check for /editorial-review command + id: check + run: | + COMMENT="${{ github.event.comment.body }}" + if [[ "$COMMENT" =~ ^/editorial-review ]]; then + echo "should_run=true" >> $GITHUB_OUTPUT + echo "✅ Command detected: /editorial-review" + else + echo "should_run=false" >> $GITHUB_OUTPUT + echo "⏭️ Skipping - comment does not contain /editorial-review" + fi + + - name: Check if comment is on a PR + if: steps.check.outputs.should_run == 'true' + run: | + if [[ "${{ github.event.issue.pull_request }}" == "" ]]; then + echo "❌ Comment is not on a pull request" + exit 1 + fi + echo "✅ Comment is on PR #${{ github.event.issue.number }}" + + - name: Acknowledge command + if: steps.check.outputs.should_run == 'true' + run: | + gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body "🔍 Editorial review started! [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Determine PR number and review type + setup: + needs: [check-trigger] + if: | + always() && ( + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issue_comment' && needs.check-trigger.outputs.should_run == 'true') + ) + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.get-pr.outputs.pr_number }} + review_type: ${{ steps.get-review-type.outputs.review_type }} + steps: + - name: Get PR number + id: get-pr + run: | + if [ "${{ github.event_name }}" = "issue_comment" ]; then + echo "pr_number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT + echo "📋 PR number from comment: ${{ github.event.issue.number }}" + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "pr_number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT + echo "📋 PR number from input: ${{ inputs.pr_number }}" + else + echo "❌ Unable to determine PR number" + exit 1 + fi + + - name: Get review type + id: get-review-type + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "review_type=${{ inputs.review_type }}" >> $GITHUB_OUTPUT + echo "📋 Review type: ${{ inputs.review_type }}" + else + # Default to 'all' for comment triggers + echo "review_type=all" >> $GITHUB_OUTPUT + echo "📋 Review type: all (comment trigger)" + fi + + # Check bash script syntax before running any reviews + syntax-check: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + + - name: Check bash script syntax + run: | + echo "🔍 Checking bash script syntax..." + for script in .github/scripts/*.sh; do + echo "Checking $script..." + bash -n "$script" + done + echo "✅ All bash scripts passed syntax check" + + # Get list of changed files and classify PR type + changes: + needs: syntax-check + runs-on: ubuntu-latest + outputs: + docs: ${{ steps.filter.outputs.docs }} + docs_files: ${{ steps.filter.outputs.docs_files }} + pr_type: ${{ steps.classify.outputs.pr_type }} + steps: + - name: Get PR details + if: github.event_name == 'issue_comment' + id: pr-details + run: | + PR_NUMBER="${{ github.event.issue.number }}" + PR_DATA=$(gh pr view ${PR_NUMBER} --json headRefName,headRepository) + HEAD_REF=$(echo "$PR_DATA" | jq -r '.headRefName') + echo "head_ref=${HEAD_REF}" >> $GITHUB_OUTPUT + echo "📋 PR head ref: ${HEAD_REF}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + with: + ref: ${{ github.event_name == 'issue_comment' && steps.pr-details.outputs.head_ref || '' }} + fetch-depth: 0 + + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # ratchet:dorny/paths-filter@v4.0.1 + id: filter + with: + list-files: json + filters: | + docs: + - 'platform-enterprise_docs/**/*.md' + - 'platform-enterprise_docs/**/*.mdx' + - 'platform-cloud/docs/**/*.md' + - 'platform-cloud/docs/**/*.mdx' + - 'platform-enterprise_versioned_docs/**/*.md' + - 'platform-enterprise_versioned_docs/**/*.mdx' + + - name: Classify PR type + id: classify + run: | + chmod +x .github/scripts/classify-pr-type.sh + BASE_REF="${{ github.base_ref }}" + + # For issue_comment events, fetch base ref from PR + if [ "${{ github.event_name }}" = "issue_comment" ]; then + PR_NUMBER="${{ github.event.issue.number }}" + BASE_REF=$(gh pr view ${PR_NUMBER} --json baseRefName --jq '.baseRefName') + echo "📋 Base ref from PR: ${BASE_REF}" + fi + + if [ -z "$BASE_REF" ]; then + # Fallback for events without a PR context + BASE_REF="master" + fi + + PR_TYPE=$(.github/scripts/classify-pr-type.sh "origin/$BASE_REF" HEAD) + echo "pr_type=$PR_TYPE" >> $GITHUB_OUTPUT + echo "📋 PR Type: $PR_TYPE" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ─────────────────────────────────────────────────────────────── + # Fast terminology check - catches obvious issues before AI review + # ─────────────────────────────────────────────────────────────── + vale-lint: + needs: changes + if: needs.changes.outputs.docs == 'true' + runs-on: ubuntu-latest + steps: + - name: Get PR head ref + if: github.event_name == 'issue_comment' + id: pr-ref + run: | + PR_NUMBER="${{ github.event.issue.number }}" + HEAD_REF=$(gh pr view ${PR_NUMBER} --json headRefName --jq '.headRefName') + echo "head_ref=${HEAD_REF}" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + with: + ref: ${{ github.event_name == 'issue_comment' && steps.pr-ref.outputs.head_ref || '' }} + + - name: Vale Terminology Check + uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1 + with: + files: | + platform-enterprise_docs + platform-cloud/docs + platform-enterprise_versioned_docs + reporter: github-pr-review + fail_on_error: false # Post suggestions without blocking + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + # Smart gate - prevents wasteful LLM runs + smart-gate: + needs: [setup, changes] + if: needs.changes.outputs.docs == 'true' && needs.changes.outputs.pr_type == 'content' + runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.decide.outputs.should_run }} + skip_reason: ${{ steps.decide.outputs.skip_reason }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + with: + fetch-depth: 0 + + - name: Check last review time + id: last-review + run: | + # Get timestamp of last /editorial-review comment + LAST_REVIEW=$(gh api repos/${{ github.repository }}/issues/${{ needs.setup.outputs.pr_number }}/comments --jq '[.[] | select(.body | startswith("/editorial-review")) | .created_at] | last' || echo "") + + if [ -n "$LAST_REVIEW" ]; then + MINUTES_AGO=$(( ($(date +%s) - $(date -d "$LAST_REVIEW" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_REVIEW" +%s)) / 60 )) + echo "minutes_since_last=$MINUTES_AGO" >> $GITHUB_OUTPUT + else + echo "minutes_since_last=999" >> $GITHUB_OUTPUT + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + + - name: Calculate meaningful changes + id: changes-size + run: | + # Count lines changed (excluding whitespace) + LINES_CHANGED=$(git diff --ignore-all-space --ignore-blank-lines origin/${{ github.base_ref }}...HEAD -- '*.md' '*.mdx' | wc -l | tr -d ' ') + echo "lines_changed=$LINES_CHANGED" >> $GITHUB_OUTPUT + echo "📊 Lines changed: $LINES_CHANGED" + + - name: Run static analysis + id: static-check + continue-on-error: true + run: | + # Check if npx is available + if command -v npx &> /dev/null; then + echo "Running markdownlint..." + npx --yes markdownlint-cli2@0.22.0 ${{ needs.changes.outputs.docs_files }} 2>&1 | tee /tmp/static-results.txt || true + ISSUES=$(grep -c ":" /tmp/static-results.txt 2>/dev/null || echo "0") + echo "static_issues=$ISSUES" >> $GITHUB_OUTPUT + echo "📋 Static issues found: $ISSUES" + else + echo "static_issues=0" >> $GITHUB_OUTPUT + echo "⚠️ npx not available, skipping static check" + fi + + - name: Decide if LLM review needed + id: decide + run: | + MINUTES=${{ steps.last-review.outputs.minutes_since_last || '999' }} + LINES=${{ steps.changes-size.outputs.lines_changed || '0' }} + STATIC_ISSUES=${{ steps.static-check.outputs.static_issues || '0' }} + + echo "⏱️ Minutes since last review: $MINUTES" + echo "📏 Lines changed: $LINES" + echo "🔍 Static issues: $STATIC_ISSUES" + + # Skip if reviewed <60 minutes ago + if [ "$MINUTES" -lt 60 ]; then + echo "should_run=false" >> $GITHUB_OUTPUT + echo "skip_reason=⏭️ Reviewed $MINUTES minutes ago. Wait at least 60 minutes between reviews." >> $GITHUB_OUTPUT + echo "⏭️ SKIPPING: Too soon since last review" + exit 0 + fi + + # Skip if <10 meaningful lines changed + if [ "$LINES" -lt 10 ]; then + echo "should_run=false" >> $GITHUB_OUTPUT + echo "skip_reason=⏭️ Only $LINES lines changed (minimum: 10). Changes too small for LLM review." >> $GITHUB_OUTPUT + echo "⏭️ SKIPPING: Changes too small" + exit 0 + fi + + # Skip if static analysis found issues (fix those first) + if [ "$STATIC_ISSUES" -gt 5 ]; then + echo "should_run=false" >> $GITHUB_OUTPUT + echo "skip_reason=⏭️ Found $STATIC_ISSUES formatting issues. Run \`npx markdownlint-cli2\` locally and fix those first." >> $GITHUB_OUTPUT + echo "⏭️ SKIPPING: Fix formatting issues first" + exit 0 + fi + + # Passed all gates + echo "should_run=true" >> $GITHUB_OUTPUT + echo "skip_reason=✅ Passed all pre-checks. Running LLM review." >> $GITHUB_OUTPUT + echo "✅ PROCEEDING: All gates passed" + + - name: Post skip message + if: steps.decide.outputs.should_run == 'false' + run: | + gh pr comment ${{ needs.setup.outputs.pr_number }} --body "${{ steps.decide.outputs.skip_reason }} + + **Why skip LLM review:** + - Saves ~50K tokens (~\$1.50) + - Saves ~0.15 kWh energy + - Avoids redundant processing + + **To proceed anyway:** Wait for cooldown period or accumulate more changes." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + # Editorial review using /editorial-review skill + editorial-review-skill: + needs: [setup, changes, smart-gate] + if: needs.smart-gate.outputs.should_run == 'true' + runs-on: ubuntu-latest + steps: + - name: Get PR head ref + if: github.event_name == 'issue_comment' + id: pr-ref + run: | + PR_NUMBER="${{ github.event.issue.number }}" + HEAD_REF=$(gh pr view ${PR_NUMBER} --json headRefName --jq '.headRefName') + echo "head_ref=${HEAD_REF}" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + with: + ref: ${{ github.event_name == 'issue_comment' && steps.pr-ref.outputs.head_ref || '' }} + fetch-depth: 0 + + - name: Run Editorial Review Skill + uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1.0.111 + with: + anthropic_api_key: ${{ secrets.ENG_ANTHROPIC_API_KEY }} + prompt: | + Run /editorial-review on the changed documentation files in this PR. + + Changed files: + ${{ needs.changes.outputs.docs_files }} + + Review type: ${{ needs.setup.outputs.review_type }} + + Output all findings to /tmp/editorial-review-suggestions.txt in this format: + + FILE: path/to/file.md + LINE: 42 + ISSUE: Brief description + ORIGINAL: | + exact original text + SUGGESTION: | + corrected text + --- + + The /editorial-review skill will orchestrate the appropriate agents + (voice-tone, terminology) based on the review type. + use_sticky_comment: true + claude_args: | + --allowedTools "Read,Grep,Glob,Write,Skill(editorial-review),Task" + + - name: Upload Editorial Review Results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v7.0.1 + with: + name: editorial-review-suggestions + path: /tmp/editorial-review-suggestions.txt + if-no-files-found: ignore + + # Consolidated Review - Posts ONE review with all suggestions + consolidated-review: + needs: [setup, changes, editorial-review-skill] + if: always() && needs.changes.outputs.docs == 'true' && needs.editorial-review-skill.result != 'skipped' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + + - name: Download Editorial Review Results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8.0.1 + with: + name: editorial-review-suggestions + path: ./artifacts + continue-on-error: true + + - name: Prepare Suggestions + run: | + mkdir -p ./artifacts + touch /tmp/all-suggestions.txt + + # Use editorial review results from the skill + if [[ -f ./artifacts/editorial-review-suggestions.txt ]]; then + cat ./artifacts/editorial-review-suggestions.txt >> /tmp/all-suggestions.txt + fi + + # Count total suggestions + TOTAL_SUGGESTIONS=$(grep -c "^FILE:" /tmp/all-suggestions.txt || echo 0) + echo "Total suggestions: $TOTAL_SUGGESTIONS" + + # Save full list before limiting + cp /tmp/all-suggestions.txt /tmp/all-suggestions-full.txt + + # Limit to 60 suggestions to prevent overwhelming output + # GitHub API also has limits on review comment size + if [[ $TOTAL_SUGGESTIONS -gt 60 ]]; then + echo "⚠️ Limiting to first 60 suggestions (found $TOTAL_SUGGESTIONS total)" + + # Extract first 60 suggestion blocks (each block ends with ---) + awk '/^FILE:/{c++} c<=60' /tmp/all-suggestions.txt > /tmp/all-suggestions-limited.txt + mv /tmp/all-suggestions-limited.txt /tmp/all-suggestions.txt + echo "$TOTAL_SUGGESTIONS" > /tmp/total-count.txt + fi + + # Check if we have any suggestions + if [[ ! -s /tmp/all-suggestions.txt ]]; then + echo "No suggestions found" + touch /tmp/no-suggestions.txt + fi + + - name: Upload Full Suggestions List + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v7.0.1 + with: + name: all-editorial-suggestions + path: /tmp/all-suggestions-full.txt + if-no-files-found: ignore + retention-days: 30 + + - name: Post Consolidated Review + run: | + chmod +x .github/scripts/post-inline-suggestions.sh + + PR_NUMBER="${{ needs.setup.outputs.pr_number }}" + PR_TYPE="${{ needs.changes.outputs.pr_type }}" + RUN_ID="${{ github.run_id }}" + REPO="${{ github.repository }}" + + if [[ -f /tmp/no-suggestions.txt ]]; then + gh pr comment ${PR_NUMBER} --body "✅ **Editorial Review Complete** (PR type: $PR_TYPE) - No issues found! Documentation looks good. *Review by Claude Code editorial agents*" + elif [[ -f /tmp/all-suggestions.txt ]] && [[ -s /tmp/all-suggestions.txt ]]; then + .github/scripts/post-inline-suggestions.sh /tmp/all-suggestions.txt ${PR_NUMBER} + + # Add note if suggestions were limited + if [[ -f /tmp/total-count.txt ]]; then + TOTAL=$(cat /tmp/total-count.txt) + ARTIFACT_URL="https://github.com/${REPO}/actions/runs/${RUN_ID}" + + cat > /tmp/limit-message.txt << EOF + ⚠️ **Note:** Found $TOTAL total suggestions, showing first 60 inline. + + **To see all $TOTAL suggestions:** + 1. Go to the [workflow run]($ARTIFACT_URL) + 2. Download the \`all-editorial-suggestions\` artifact + 3. Review the full list in \`all-suggestions-full.txt\` + + The inline suggestions focus on the most impactful changes. (PR type: $PR_TYPE) + EOF + + gh pr comment ${PR_NUMBER} --body-file /tmp/limit-message.txt + fi + else + echo "No review output to post" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# Summary report + summary: + needs: [setup, changes, vale-lint, editorial-review-skill, consolidated-review] + if: always() && needs.changes.outputs.docs == 'true' + runs-on: ubuntu-latest + steps: + - name: Post Summary + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # ratchet:actions/github-script@v9.0.0 + with: + script: | + const jobs = [ + { name: 'Terminology (Vale)', status: '${{ needs.vale-lint.result }}' }, + { name: 'Editorial review (AI)', status: '${{ needs.editorial-review-skill.result }}' }, + { name: 'Consolidated review', status: '${{ needs.consolidated-review.result }}' } + ]; + + # Auto-fix workflow (triggered by /fix-docs comment) + auto-fix: + # DISABLED: Auto-fix not needed with inline suggestions + # Users can apply suggestions individually or batch-commit multiple + # To re-enable: remove the "if: false" condition below + if: false + # if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '/fix-docs') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: Apply Fixes + uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1.0.111 + with: + anthropic_api_key: ${{ secrets.ENG_ANTHROPIC_API_KEY }} + prompt: | + Use the docs-fix agent to apply fixes to all changed documentation files in this PR. + + Apply fixes for: + - Terminology standardization + - Voice and tone consistency + - Formatting corrections + - Inclusive language updates + + Do NOT change code blocks or alter technical meaning. + claude_args: | + --allowedTools "Read,Write,Edit,Grep,Glob,Task(docs-fix)" + + - name: Commit Fixes + run: | + git config user.name "Claude Code Bot" + git config user.email "claude-bot@seqera.io" + git add -A + git diff --staged --quiet || git commit -m "docs: apply automated style fixes" + git push diff --git a/.vale.ini b/.vale.ini index 94321d9f0c..8045f16a43 100644 --- a/.vale.ini +++ b/.vale.ini @@ -1,47 +1,44 @@ -# Vale configuration for Seqera documentation -# Minimal setup - only catches terminology issues you care about +# Vale configuration for Seqera documentation. +# Catches terminology, punctuation, and prose-quality issues via static rules. StylesPath = .github/styles -# Download packages from Vale package hub +# Download packages from the Vale style hub Packages = write-good -# Only check markdown files +# Markdown files [*.md] -BasedOnStyles = Seqera, write-good +BasedOnStyles = Vale, Seqera, write-good -# Make context-dependent rules warnings (not blockers) -Seqera.Features.CE = warning # CE might match unrelated terms -Seqera.Features.Workspace = warning # Workspace capitalization is context-dependent -Seqera.Features.PAT = warning # PAT expansion depends on first use -Seqera.Products.Tower = warning # Tower is OK in legacy contexts - -# Disable noisy write-good rules -write-good.Weasel = NO # "very", "really" sometimes needed -write-good.ThereIs = NO # "There is" is fine in technical docs -write-good.So = NO # "So" is fine for transitions -write-good.Passive = suggestion # Passive voice is OK sometimes +# Ignore code blocks and frontmatter +BlockIgnores = (?s) *(`{3}.*?`{3}), (?s)^---.*?--- +TokenIgnores = (`[^`]+`) -# Also check MDX files -[*.mdx] -BasedOnStyles = Seqera, write-good +# Context-dependent terminology — warn, don't block +Seqera.CE = warning +Seqera.Workspace = suggestion +Seqera.PAT = warning +Seqera.Tower = warning -# Same overrides for MDX -Seqera.Features.CE = warning -Seqera.Features.Workspace = warning -Seqera.Features.PAT = warning -Seqera.Products.Tower = warning +# write-good overrides — keep the useful checks, mute the noisy ones write-good.Weasel = NO write-good.ThereIs = NO write-good.So = NO write-good.Passive = suggestion -# Ignore code blocks entirely -BlockIgnores = (?s) *(`{3}.*?`{3}) +# MDX files (same config) +[*.mdx] +BasedOnStyles = Vale, Seqera, write-good + +BlockIgnores = (?s) *(`{3}.*?`{3}), (?s)^---.*?--- TokenIgnores = (`[^`]+`) -# Ignore frontmatter -BlockIgnores = (?s)^---.*?--- +Seqera.CE = warning +Seqera.Workspace = suggestion +Seqera.PAT = warning +Seqera.Tower = warning -# Paths to check (relative patterns) -# Vale will check files matching these when run +write-good.Weasel = NO +write-good.ThereIs = NO +write-good.So = NO +write-good.Passive = suggestion diff --git a/.vale.ini.bak b/.vale.ini.bak new file mode 100644 index 0000000000..704f9d69a3 --- /dev/null +++ b/.vale.ini.bak @@ -0,0 +1,44 @@ +# Vale configuration for Seqera documentation. +# Catches terminology, punctuation, and prose-quality issues via static rules. + +StylesPath = .github/styles + +# Download packages from the Vale style hub +Packages = write-good + +# Markdown files +[*.md] +BasedOnStyles = Vale, Seqera, write-good + +# Ignore code blocks and frontmatter +BlockIgnores = (?s) *(`{3}.*?`{3}), (?s)^---.*?--- +TokenIgnores = (`[^`]+`) + +# Context-dependent terminology — warn, don't block +Seqera.Features.CE = warning +Seqera.Features.Workspace = warning +Seqera.Features.PAT = warning +Seqera.Products.Tower = warning + +# write-good overrides — keep the useful checks, mute the noisy ones +write-good.Weasel = NO +write-good.ThereIs = NO +write-good.So = NO +write-good.Passive = suggestion + +# MDX files (same config) +[*.mdx] +BasedOnStyles = Vale, Seqera, write-good + +BlockIgnores = (?s) *(`{3}.*?`{3}), (?s)^---.*?--- +TokenIgnores = (`[^`]+`) + +Seqera.Features.CE = warning +Seqera.Features.Workspace = warning +Seqera.Features.PAT = warning +Seqera.Products.Tower = warning + +write-good.Weasel = NO +write-good.ThereIs = NO +write-good.So = NO +write-good.Passive = suggestion