diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000000..1c18731a3ee --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,45 @@ +# Red Hat Developer Hub Documentation + +## CQA Compliance + +When editing `.adoc` files, ALWAYS run the relevant CQA scripts to validate and fix changes before considering the task done. Run all 19 checks at once with `build/scripts/cqa.sh`, or run individual scripts. All share a common interface via `build/scripts/cqa-lib.sh`: + +```bash +# Report issues for a title +./build/scripts/cqa.sh titles//master.adoc + +# Auto-fix issues +./build/scripts/cqa.sh --fix titles/<title>/master.adoc + +# Run across all titles +./build/scripts/cqa.sh --all + +# Auto-fix across all titles +./build/scripts/cqa.sh --fix --all +``` + +Output markers: `[AUTOFIX]` (auto-fixable), `[FIXED]` (applied), `[MANUAL]` (needs human), `[-> CQA #NN AUTOFIX]` / `[-> CQA #NN MANUAL]` (delegated). + +For full CQA workflow, load `.claude/skills/cqa-main-workflow.md`. Individual skill files are in `.claude/skills/cqa-*.md`. + +## Pull Requests + +When creating PRs, follow `.github/pull_request_template.md`: + +- **Title format:** `[RHIDP#<jira-id>]: <short description>` (no `GH#` or `BZ#` prefix needed unless applicable) +- **Body:** Must include the `IMPORTANT: Do Not Merge` banner, `Version(s):`, `Issue:` (Jira link), and `Preview:` (preview URL or N/A) +- **Target branch:** Open PRs against `main` and cherrypick to released branches as needed +- **Never use `#N` in PR title or body** — GitHub auto-links it to issues/PRs. Use dash notation (e.g., `CQA-05`) instead. + +## GitHub Workflows (`.github/workflows/`) + +| Workflow | Trigger | Purpose | +|---|---|---| +| `content-quality-assessment.yml` | PR (`.adoc`, `build/scripts/`) | Runs `cqa.sh --all` on entire repo. Posts checklist as PR comment, uploads SARIF to Code Scanning. Scripts sourced from `main` branch (not base) for backport compatibility. | +| `build-asciidoc.yml` | Push to main/release | Builds AsciiDoc docs and deploys to GitHub Pages. Cleans up merged PR preview branches. | +| `pr.yml` | PR | Builds HTML preview, deploys to `gh-pages`, posts preview URL as PR comment. | +| `style-guide.yml` | PR | Runs Vale linter on `assemblies/` for style guide compliance. | +| `shellcheck.yml` | PR (`*.sh`) | Runs shellcheck on changed shell scripts via reviewdog. | +| `generate-supported-plugins-pr.yml` | Manual dispatch | Updates Dynamic Plugins tables and creates a PR. | + +**Security:** `content-quality-assessment`, `pr`, and `shellcheck` use `pull_request_target` with an authorization gate — fork PRs from non-team members require manual approval via the `external` environment. diff --git a/.claude/MEMORY.md b/.claude/MEMORY.md index 14e7b537f72..7fc165f4a21 100644 --- a/.claude/MEMORY.md +++ b/.claude/MEMORY.md @@ -4,205 +4,53 @@ This file contains persistent knowledge about working with the RHDH documentatio ## CQA 2.1 Compliance Process -### Critical Workflow Rule - **ALWAYS use the checklist for CQA work:** -When starting CQA 2.1 compliance for any title: - 1. **FIRST:** Load the workflow files ``` - Read .claude/skills/cqa-master-workflow.md, .claude/cqa-checklist.md, .claude/MEMORY.md + Read .claude/skills/cqa-main-workflow.md, .claude/cqa-checklist.md, .claude/MEMORY.md ``` - - The master workflow orchestrates all 17 CQA requirements in optimal order - - The checklist follows the same 5-phase structure (updated 2026-03-13) + - The main workflow orchestrates all 17 CQA requirements in optimal order + - The checklist follows the same 5-phase structure - **Idempotency requirement:** Re-execute each requirement until no changes, then re-run entire workflow until stable 2. **VERIFY required information** - If any are missing, ASK the user: - JIRA ticket number (e.g., RHIDP-12345) - - Title name (e.g., "Installing Red Hat Developer Hub on OpenShift Container Platform") OR - - Path to master.adoc file (e.g., `titles/installing-rhdh-ocp/master.adoc`) + - Title name or path to master.adoc file 3. **THEN:** Use the master workflow as the guide - - Create a TodoWrite with items from cqa-master-workflow.md following the sequence defined in cqa-master-workflow.md + - Create a TodoWrite following the sequence in cqa-main-workflow.md - Read individual CQA skills (.claude/skills/cqa-##-*.md) for detailed assessment - - Run each step sequentially: always await for the previous task completion before running the next task - - Respect the sequence order. + - Run each step sequentially; respect the sequence order -4. **IMPORTANT:** Fill in the checklist header: - - Title name - - JIRA number (RHIDP-XXXXX) - - Target file path - -5. **NEVER claim completion unless:** - - ALL checkbox items are marked ✓ +4. **NEVER claim completion unless:** + - ALL checkbox items are marked with a checkmark - Idempotency verified (re-running workflow produces no changes) - TodoWrite shows all tasks complete - - User has explicitly verified completion - -### Why This Matters - -**The problem:** CQA has 17 main steps with multiple sub-steps. Without a checklist, steps get forgotten and completion is claimed prematurely. - -**The solution:** The checklist is the single source of truth. If it's not checked, it's not done. - -### Automation Scripts - -Use these scripts for systematic tasks: - -1. **Content Type Detection/Fixing:** - ```bash - ./build/scripts/cqa-03-content-is-modularized.sh [--fix] titles/<title>/master.adoc - ``` - - Auto-detects and fixes content type metadata - - Normalizes .Procedure and .Verification list formatting - - Validates PROCEDURE structure - - Shows violation breakdown - -2. **Title/ID/Filename Alignment:** - ```bash - ./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] titles/<title>/master.adoc - ``` - - Ensures content type metadata exists (calls cqa-03-content-is-modularized.sh if needed) - - Fixes title forms (gerund → imperative) - - Aligns IDs to match titles - - Renames files with git mv - - Updates xrefs and includes automatically - -3. **Orphaned Module Detection:** - ```bash - ./build/scripts/fix-orphaned-modules.sh # Dry-run (lists only) - ./build/scripts/fix-orphaned-modules.sh --execute # Actually deletes - ``` - - Finds files not referenced by any include statement - - **Dynamic attribute detection** (updated 2026-03-13): - - Automatically detects ANY `{...}` attribute substitution - - No hardcoded patterns - works with {platform-id}, {context}, {product}, future attributes - - Converts include patterns to regex (e.g., `proc-install-on-{platform-id}.adoc` → `proc-install-on-.*[.]adoc`) - - Excludes template files (*.template.adoc) - these are code generation sources - - Uses `cut -d: -f2-` to extract only include paths (not grep filename prefixes) - -4. **Short Description Verification:** - ```bash - ./build/scripts/cqa-09-short-description-format.sh [--fix] titles/<title>/master.adoc - ``` - - Verifies [role="_abstract"] presence - - Checks 50-300 character requirement - - Validates no empty line after marker - -5. **Build and Preview Generation:** - ```bash - ./build/scripts/build-ccutil.sh # ALWAYS run without arguments - ``` - - **IMPORTANT:** Always run without arguments (builds all titles for current branch) - - Uses Podman to run ccutil container (quay.io/ivanhorvath/ccutil:amazing) - - Processes all titles/*/master.adoc files (excludes rhdh-plugins-reference) - - Generates HTML single-page output in titles-generated/${BRANCH}/ - - Copies referenced images to output directory - - Creates navigation index.html - - Runs htmltest for link validation - - Supports branch-specific builds with `-b <branch>` flag (rarely needed) ### Reference Materials -**CQA reference materials are updated using:** -```bash -./build/scripts/update-cqa-resources.sh -``` +Updated using `./build/scripts/update-cqa-resources.sh` (weekly minimum, daily maximum). See `.claude/skills/update-all-resources.md` for details. -**Update frequency:** Weekly minimum (7 days), daily maximum (1 day) - -**Resources maintained:** +- **SSG:** `.claude/resources/red-hat-ssg.md` — grammar, style, formatting (CQA 8, 9, 10, 12, 16) +- **Peer Review:** `.claude/resources/red-hat-peer-review.md` — titles, grammar, editorial quality (CQA 10, 12) +- **Modular Docs:** `.claude/resources/red-hat-modular-docs.md` — content types, module templates (CQA 2-6, 10, 13) +- **Vale:** `.vale-dita-only.ini` (CQA 1), `.vale.ini` (CQA 12). Sync with `vale sync`. `attributes.adoc` excluded from Vale. -1. **Red Hat Supplementary Style Guide (SSG):** - - **Location:** `.claude/resources/red-hat-ssg.md` - - **When to reference:** CQA #8, #9, #10, #12, #16 (grammar, style, formatting) - - **Key topics:** Conscious language, short descriptions, titles, user-replaced values, Technology Preview - -2. **Red Hat Peer Review Guide:** - - **Location:** `.claude/resources/red-hat-peer-review.md` - - **When to reference:** CQA #10 (titles), CQA #12 (grammar), editorial quality - - **Key topics:** Style checklist, grammar rules, formatting standards, review workflow - -3. **Red Hat Modular Documentation Guide:** - - **Location:** `.claude/resources/red-hat-modular-docs.md` - - **When to reference:** CQA #2, #3, #4, #5, #6, #10, #13 (modularization, content types) - - **Key topics:** Content types (ASSEMBLY, CONCEPT, PROCEDURE, REFERENCE), module templates, file naming - -4. **Vale Linting Rules:** - - **Timestamp:** `.claude/.vale-sync-timestamp` - - **Sync command:** `vale sync` (automated by update script) - - **When to sync:** Before CQA #1 (DITA validation) or CQA #12 (grammar/style validation) - - **Configurations:** `.vale-dita-only.ini` (CQA #1), `.vale.ini` (CQA #12) - - **Excluded:** `attributes.adoc` is excluded from Vale validation (defines attribute values using literal product names, which triggers false positives for `DeveloperHub.Attributes` rules) - - **All DITA warnings must be fixed** — no acceptable warnings. See `.claude/skills/cqa-01-asciidoctor-dita-vale.md` for fix guidance - -### Best Practices Learned +### Best Practices 1. **Scripts first, manual second:** Run all automated scripts before manual fixes 2. **One step at a time:** Don't batch steps, even if they seem related 3. **Verify after fixes:** Re-run scripts after manual changes to confirm alignment 4. **Git mv for renames:** Always use git mv to preserve history -5. **Update includes last:** Fix filenames before updating include statements -6. **Check SSG currency:** Before style-related CQA work, ensure SSG is current (within 7 days) -7. **Sync Vale styles:** Before Vale validation (CQA #1, #12), ensure Vale is synced (within 7 days) -8. **Idempotency:** Re-run each CQA requirement until no changes, then re-run full workflow until stable -9. **Settings.json hygiene:** - - Use wildcard patterns (e.g., `Bash(find *)`) not specific commands - - Never commit sensitive information (usernames, API keys, absolute paths with usernames) - - Use relative paths (.claude, .claude/resources) not absolute paths - - Alphabetize permissions for maintainability - - Verify with `jq . .claude/settings.json` before committing - -### Common Pitfalls to Avoid - -❌ **Don't:** Skip the checklist and try to remember all steps -✅ **Do:** Load checklist first, mark items as done - -❌ **Don't:** Claim "CQA complete" without showing all ✓ items -✅ **Do:** Show checklist with all items marked before claiming completion - -❌ **Don't:** Run steps out of order -✅ **Do:** Follow exact sequence in checklist - -❌ **Don't:** Batch steps together ("I'll do 2-4 at once") -✅ **Do:** Complete each step fully, mark it ✓, then proceed +5. **Idempotency:** Re-run each CQA requirement until no changes, then re-run full workflow until stable ### Pull Request Guidelines -**CRITICAL:** When creating PR descriptions, **never use `#` notation for CQA numbers** (e.g., avoid "CQA #1", "CQA #2"). - -❌ **Don't write:** "CQA #1", "CQA #2", "CQA #17" -✅ **Do write:** "CQA 1", "CQA 2", "CQA 17" - -**Reason:** GitHub automatically creates links for `#number` patterns, linking to unrelated issues/PRs and creating noise. - -**PR Description Format for CQA Work:** -```markdown -## Summary - -Completed CQA 2.1 validation for `titles/<title-name>/master.adoc`: - -### Validation Results -- ✅ **CQA 1**: Vale DITA validation (0 errors, acceptable warnings) -- ✅ **CQA 2**: Assembly structure compliance -- ✅ **CQA 3**: Content modularization verified -... -- ✅ **CQA 17**: Disclaimers not applicable (no preview features) - -### Key Changes -- List of major changes made -- File renames, content updates, etc. - -### Files Changed -- List of modified files +**Never use `#` notation for CQA numbers** in PR descriptions (e.g., write "CQA 1" not "CQA #1"). GitHub auto-links `#number` to unrelated issues/PRs. -## Test Plan -- [x] All CQA automation scripts pass -- [x] Vale validations pass -- [x] Build successful -``` +PR description format is in `.claude/cqa-checklist.md`. ## Repository Structure @@ -212,13 +60,9 @@ Completed CQA 2.1 validation for `titles/<title-name>/master.adoc`: - `artifacts/` - Reusable snippets and fragments - `build/scripts/` - Automation scripts for CQA and validation - `.claude/` - Claude Code configuration and documentation - - `settings.json` - Repository-specific permissions and settings - - `MEMORY.md` - This file - persistent knowledge for Claude - - `cqa-checklist.md` - CQA 2.1 compliance checklist template ## User Preferences - Uses wildcard patterns in .claude/settings.json (not individual files) - Prefers focused, single-purpose tasks over large multi-step processes - Values explicit tracking with TodoWrite for complex workflows -- Commits frequently with descriptive messages including "Co-Authored-By: Claude Sonnet 4.5" diff --git a/.claude/README.md b/.claude/README.md index fd30839c068..4580d6f9ebf 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -35,8 +35,8 @@ Comprehensive checklist template for CQA 2.1 compliance work. Contains: ### `skills/` CQA 2.1 compliance assessment skills: -**Master Workflow:** -- `cqa-master-workflow.md` - Complete CQA process orchestrating all 17 requirements in optimal order +**Main Workflow:** +- `cqa-main-workflow.md` - Complete CQA process orchestrating all 17 requirements in optimal order **Individual Skills (17 total):** - `cqa-01-*.md` through `cqa-17-*.md` - One skill per Pre-migration requirement diff --git a/.claude/cqa-checklist.md b/.claude/cqa-checklist.md index 8d96f43b84a..c6588b1a1eb 100644 --- a/.claude/cqa-checklist.md +++ b/.claude/cqa-checklist.md @@ -1,9 +1,9 @@ # CQA 2.1 Compliance Checklist -**IMPORTANT:** This checklist implements the workflow defined in [cqa-master-workflow.md](skills/cqa-master-workflow.md). +**IMPORTANT:** This checklist implements the workflow defined in [cqa-main-workflow.md](skills/cqa-main-workflow.md). **Before starting:** -1. Read [cqa-master-workflow.md](skills/cqa-master-workflow.md) for execution rules and skill order +1. Read [cqa-main-workflow.md](skills/cqa-main-workflow.md) for execution rules and skill order 2. Follow the CRITICAL EXECUTION RULES defined in the workflow 3. Execute all requirements in the exact order listed below 4. Re-run each requirement until idempotent (no changes) @@ -19,7 +19,36 @@ ## Execution Workflow -**IMPORTANT:** Execute in this exact order (follows [cqa-master-workflow.md](skills/cqa-master-workflow.md) Process section). +**IMPORTANT:** Execute in this exact order (follows [cqa-main-workflow.md](skills/cqa-main-workflow.md) Process section). + +**Unified script interface:** +```bash +# Report issues for a title +./build/scripts/cqa-XX-*.sh titles/<your-title>/master.adoc + +# Auto-fix issues for a title +./build/scripts/cqa-XX-*.sh --fix titles/<your-title>/master.adoc + +# Report issues across all titles +./build/scripts/cqa-XX-*.sh --all +``` + +Output markers: `[AUTOFIX]` (auto-fixable), `[MANUAL]` (needs human), `[FIXED]` (applied), `[-> CQA #NN AUTOFIX]` / `[-> CQA #NN MANUAL]` (delegated). + +- [ ] **CQA #0: Orphaned modules** + ```bash + ./build/scripts/cqa-00-orphaned-modules.sh [--fix] + ``` + - [ ] No orphaned .adoc files + - [ ] No orphaned images + - [ ] Delete orphans: `./build/scripts/cqa-00-orphaned-modules.sh --fix` + +- [ ] **CQA #0: Directory structure** + ```bash + ./build/scripts/cqa-00-directory-structure.sh [--fix] + ``` + - [ ] All directories follow `<category>_<context>` naming + - [ ] Fix misnamed dirs: `./build/scripts/cqa-00-directory-structure.sh --fix` - [ ] **Resources current** - [Update all resources](skills/update-all-resources.md) ```bash @@ -29,19 +58,37 @@ - [ ] Red Hat style guides current - [ ] **CQA #3: Content is modularized** - [Skill](skills/cqa-03-content-is-modularized.md) + ```bash + ./build/scripts/cqa-03-content-is-modularized.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Modular structure (assemblies include modules) - [ ] Correct metadata (`:_mod-docs-content-type:`) - [ ] Correct filename prefixes (proc-, con-, ref-, assembly-) - [ ] **CQA #13: Correct content type** - [Skill](skills/cqa-13-information-is-conveyed-using-the-correct-content.md) ```bash - ./build/scripts/cqa-03-content-is-modularized.sh [--fix] titles/<your-title>/master.adoc + ./build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh [--fix] titles/<your-title>/master.adoc ``` - [ ] Content matches declared type (PROCEDURE/CONCEPT/REFERENCE/ASSEMBLY) - [ ] Procedures have `.Procedure` sections - [ ] No violations found +- [ ] **CQA #10: Titles are brief, complete, and descriptive** - [Skill](skills/cqa-10-titles-are-brief-complete-and-descriptive.md) + ```bash + ./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] titles/<your-title>/master.adoc + ``` + - [ ] Procedures: imperative form ("Install" not "Installing") + - [ ] Concepts: noun phrases ("Configuration options") + - [ ] References: noun phrases ("API reference") + - [ ] IDs match titles (lowercase-with-hyphens_{context}) + - [ ] Filenames match titles (with prefix) + - [ ] All xrefs updated for changed IDs + - [ ] All includes updated for renamed files + - [ ] **CQA #8: Short description content** - [Skill](skills/cqa-08-short-description-content.md) + ```bash + ./build/scripts/cqa-08-short-description-content.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] WHY user should read (benefit-focused) - [ ] Not self-referential ("This section describes...") - [ ] Explains value/purpose @@ -54,60 +101,68 @@ - [ ] 50-300 characters - [ ] No empty line after marker -- [ ] **CQA #10: Titles are brief, complete, and descriptive** - [Skill](skills/cqa-10-titles-are-brief-complete-and-descriptive.md) +- [ ] **CQA #11: Procedures have prerequisites** - [Skill](skills/cqa-11-procedures-prerequisites.md) ```bash - ./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] titles/<your-title>/master.adoc + ./build/scripts/cqa-11-procedures-prerequisites.sh [--fix] titles/<your-title>/master.adoc ``` - - [ ] Procedures: imperative form ("Install" not "Installing") - - [ ] Concepts: noun phrases ("Configuration options") - - [ ] References: noun phrases ("API reference") - - [ ] IDs match titles (lowercase-with-hyphens_{context}) - - [ ] Filenames match titles (with prefix) - - [ ] All xrefs updated for changed IDs - - [ ] All includes updated for renamed files - -- [ ] **CQA #11: Procedures have prerequisites** - [Skill](skills/cqa-11-procedures-prerequisites.md) - [ ] `.Prerequisites` label used (plural) - [ ] Max 10 items - [ ] Use completed states ("You have installed...") - [ ] Link to procedures when possible - [ ] **CQA #2: Assembly structure** - [Skill](skills/cqa-02-assembly-structure.md) + ```bash + ./build/scripts/cqa-02-assembly-structure.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Introduction paragraph (context) - [ ] Include statements only (no inline content) - [ ] Context restoration at end (`:context: {parent-context}`) - [ ] Proper leveloffset on includes - [ ] **CQA #5: Required elements** - [Skill](skills/cqa-05-modular-elements-checklist.md) + ```bash + ./build/scripts/cqa-05-modular-elements-checklist.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] All mandatory elements present per type - [ ] Procedures: title, abstract, prerequisites, procedure, verification - [ ] Concepts: title, abstract, content - [ ] References: title, abstract, data - [ ] **CQA #4: Official templates** - [Skill](skills/cqa-04-modules-use-official-templates.md) + ```bash + ./build/scripts/cqa-04-modules-use-official-templates.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] CONCEPT template structure followed - [ ] PROCEDURE template structure followed - [ ] REFERENCE template structure followed - [ ] **CQA #6: Assembly tells one story** - [Skill](skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md) + ```bash + ./build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Single user story per assembly - [ ] No overlapping content with other assemblies - [ ] Clear purpose statement - [ ] **CQA #7: TOC depth max 3 levels** - [Skill](skills/cqa-07-toc-max-3-levels.md) + ```bash + ./build/scripts/cqa-07-toc-max-3-levels.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Maximum 3 heading levels - [ ] No excessive nesting - [ ] Proper leveloffset usage - [ ] **CQA #16: Official product names** - [Skill](skills/cqa-16-official-product-names-are-used.md) + ```bash + ./build/scripts/cqa-16-official-product-names-are-used.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Use attributes ({product}, {ocp-short}, etc.) - [ ] Follow Red Hat OPL (Official Product List) - [ ] No hardcoded product names - [ ] **CQA #1: Vale DITA** - [Skill](skills/cqa-01-asciidoctor-dita-vale.md) ```bash - vale --config .vale-dita-only.ini \ - $(./build/scripts/list-all-included-files-starting-from.sh titles/<your-title>/master.adoc) + ./build/scripts/cqa-01-asciidoctor-dita-vale.sh [--fix] titles/<your-title>/master.adoc ``` - [ ] 0 errors - [ ] Only acceptable warnings (callouts, false positives) @@ -117,8 +172,7 @@ - [ ] **CQA #12: Grammar** - [Skill](skills/cqa-12-content-is-grammatically-correct-and-follows-rules.md) ```bash - vale --config .vale.ini \ - $(./build/scripts/list-all-included-files-starting-from.sh titles/<your-title>/master.adoc) + ./build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh [--fix] titles/<your-title>/master.adoc ``` - [ ] 0 errors - [ ] American English @@ -128,20 +182,26 @@ - [ ] Final warning count: 0 - [ ] **CQA #17: Disclaimers** - [Skill](skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md) + ```bash + ./build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Tech Preview disclaimer if applicable - [ ] Developer Preview disclaimer if applicable - [ ] Legal-approved text used - [ ] **CQA #14: No broken links** - [Skill](skills/cqa-14-no-broken-links.md) ```bash - ./build/scripts/build-ccutil.sh + ./build/scripts/cqa-14-no-broken-links.sh [--fix] titles/<your-title>/master.adoc ``` - [ ] All xrefs resolve - [ ] All external links valid - [ ] Build completes successfully - [ ] No xref errors in output -- [ ] **CQA #15: Redirects** - [Skill](skills/cqa-15-redirects-if-needed-are-in-place-and-work-correc.md) +- [ ] **CQA #15: Redirects** - [Skill](skills/cqa-15-redirects.sh) + ```bash + ./build/scripts/cqa-15-redirects.sh [--fix] titles/<your-title>/master.adoc + ``` - [ ] Redirects in place if files moved - [ ] Old URLs redirect to new locations - [ ] Redirect file syntax correct @@ -150,14 +210,6 @@ ### Cleanup & Verification -- [ ] **Remove orphaned modules** - ```bash - ./build/scripts/fix-orphaned-modules.sh - ``` - - [ ] Review list: _____ files found - - [ ] Verify truly orphaned - - [ ] Delete: `./build/scripts/fix-orphaned-modules.sh --execute` - - [ ] **Verify .claude/settings.json** (if updated) - [ ] Permissions alphabetically sorted - [ ] Uses wildcard patterns (not individual files) @@ -169,7 +221,8 @@ ## Completion Checklist -**All 17 Requirements:** +**All 18 Requirements:** +- [ ] CQA #0: Orphaned modules ✓ - [ ] CQA #1: Vale DITA ✓ - [ ] CQA #2: Assembly structure ✓ - [ ] CQA #3: Modularized ✓ @@ -191,7 +244,6 @@ **Final Steps:** - [ ] All automation scripts run - [ ] All manual assessments complete -- [ ] Orphaned modules removed - [ ] .claude/settings.json verified (if updated) --- @@ -211,7 +263,7 @@ - Removed X orphaned modules [Add .claude/settings.json note if applicable] - Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>" + Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` - [ ] **Create pull request** @@ -227,11 +279,11 @@ [Brief summary of changes] ## CQA 2.1 Results - - ✅ Vale DITA: 0 errors, X acceptable warnings - - ✅ Vale style: 0 errors, 0 warnings - - ✅ Build: successful - - ✅ All 17 CQA requirements met - - ✅ Idempotency verified + - Vale DITA: 0 errors, X acceptable warnings + - Vale style: 0 errors, 0 warnings + - Build: successful + - All 19 CQA requirements met + - Idempotency verified ## Changes Made - Content type compliance: X files @@ -239,7 +291,7 @@ - Short descriptions: X files - Orphaned modules removed: X files - 🤖 Generated with Claude Code + Generated with Claude Code EOF )" ``` @@ -251,7 +303,7 @@ ## Assessment -**Title:** _____ | **JIRA:** RHIDP-_____ | **Status:** ⬜ In Progress | ⬜ Complete | ⬜ Blocked +**Title:** _____ | **JIRA:** RHIDP-_____ | **Status:** In Progress | Complete | Blocked **Results:** - Vale DITA: _____ errors, _____ warnings @@ -263,4 +315,4 @@ --- -**CRITICAL:** Do not claim completion unless ALL checkboxes marked ✓ +**CRITICAL:** Do not claim completion unless ALL checkboxes marked diff --git a/.claude/resources/assembly-template.adoc b/.claude/resources/assembly-template.adoc new file mode 100644 index 00000000000..3b27f82e3d9 --- /dev/null +++ b/.claude/resources/assembly-template.adoc @@ -0,0 +1,46 @@ +:_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] + +[id="my-user-story_{context}"] += My user story + +:context: my-user-story + +[role="_abstract"] +This paragraph is the assembly introduction. It explains what the user will accomplish by working through the modules in the assembly and sets the context for the user story the assembly is based on. + +== Prerequisites + +* A bulleted list of conditions that must be satisfied before the user starts following this assembly. +* You can also link to other modules or assemblies the user must follow before starting this assembly. +* Delete the section title and bullets if the assembly has no prerequisites. +* X is installed. For information about installing X, see <link>. +* You can log in to X with administrator privileges. + +//// +The following include statements pull in the module files that comprise the assembly. Include any combination of concept, procedure, or reference modules required to cover the user story. You can also include other assemblies. + +[leveloffset=+1] ensures that when a module title is a level 1 heading (= Title), the heading will be interpreted as a level-2 heading (== Title) in the assembly. Use [leveloffset=+2] and [leveloffset=+3] to nest modules in an assembly. + + Add a blank line after each 'include::' statement. +//// + +include::TEMPLATE_CONCEPT_concept-explanation.adoc[leveloffset=+1] + +include::TEMPLATE_PROCEDURE_doing-one-procedure.adoc[leveloffset=+2] + +include::TEMPLATE_REFERENCE_reference-material.adoc[leveloffset=+2] + +// If the last module included in your assembly contains an Additional resources section as well, check the appearance of the rendered assembly. If the two Additional resources sections might be confusing for a reader, consider consolidating their content and removing one of them. + +[role="_additional-resources"] +== Additional resources +//// +Optional. Delete if not used. +Provide a bulleted list of links and display text relevant to the assembly. These links can include `link:` and `xref:` macros. Do not include additional text. +//// +* link:https://github.com/redhat-documentation/modular-docs#modular-documentation-reference-guide[Modular Documentation Reference Guide] +* xref:some-module_{context}[] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/.claude/resources/concept-template.adoc b/.claude/resources/concept-template.adoc new file mode 100644 index 00000000000..ea22c0accba --- /dev/null +++ b/.claude/resources/concept-template.adoc @@ -0,0 +1,52 @@ +//// +Metadata attribute that will help enable correct parsing and conversion to the appropriate DITA topic type. +//// + +:_mod-docs-content-type: CONCEPT + +//// +Base the file name and the ID on the module title. For example: +* file name: con_my-concept-module-a.adoc +* ID: [id="my-concept-module-a_{context}"] +* Title: = My concept module A + + ID is a unique identifier that can be used to reference this module. Avoid changing it after the module has been published to ensure existing links are not broken. + +The `context` attribute enables module reuse. Every module ID includes {context}, which ensures that the module has a unique ID so you can include it multiple times in the same guide. + +Be sure to include a line break between the title and the module introduction. +//// + +[id="my-concept-module-a_{context}"] += My concept module A +//// +In the title of concept modules, include nouns or noun phrases that are used in the body text. This helps readers and search engines find the information quickly. Do not start the title of concept modules with a verb or gerund. See also _Wording of headings_ in _IBM Style_. +//// + +[role="_abstract"] +Write a short introductory paragraph that provides an overview of the module. + +The contents of a concept module give the user descriptions and explanations needed to understand and use a product. + +* Look at nouns and noun phrases in related procedure modules and assemblies to find the concepts to explain to users. +* Explain only things that are visible to users. Even if a concept is interesting, it probably does not require explanation if it is not visible to users. +* Avoid including action items. Action items belong in procedure modules. However, in some cases a concept or reference can include suggested actions when those actions are simple, are highly dependent on the context of the module, and have no place in any procedure module. In such cases, ensure that the heading of the concept or reference remains a noun phrase and not a gerund. + + +//// +Do not include third-level headings (===). +Include titles and alternative text descriptions for images and enclose the descriptions in straight quotation marks (""). Alternative text should provide a textual, complete description of the image as a full sentence. +Images should never be the sole means of conveying information and should only supplement the text. +Avoid screenshots or other images that might quickly go out of date and that create a maintenance burden on documentation. Provide text equivalents for every diagram, image, or other non-text element. Avoid using images of text instead of actual text. +//// +//.Image title +//image::image-file.png["A textual representation of the essential information conveyed by the image."] + +[role="_additional-resources"] +.Additional resources +//// +Optional. Delete if not used. +Provide a bulleted list of links and display text relevant to the assembly. These links can include `link:` and `xref:` macros. Do not include additional text. +//// +* link:https://github.com/redhat-documentation/modular-docs#modular-documentation-reference-guide[Modular Documentation Reference Guide] +* xref:some-module_{context}[] diff --git a/.claude/resources/content-types-for-cqa.md b/.claude/resources/content-types-for-cqa.md new file mode 100644 index 00000000000..5eec887b335 --- /dev/null +++ b/.claude/resources/content-types-for-cqa.md @@ -0,0 +1,38 @@ +# Product Documentation Content Types (CQA Extract) + +> **This is a condensed CQA-focused extract.** Full version: [content-types.md](content-types.md) + +Modular, topic-based content that supports the use of a product, service, or technology. Product documentation is a release requirement and is carefully vetted by QE, SMEs, and other stakeholders. + +Modular documentation is based on units of content called **modules**, which authors combine into organizational units called **assemblies**. A module is a self-contained unit of content that is based on one of three officially supported content types: **concept**, **task**, or **reference**. An assembly is a collection of several modules that the author creates to document a user story. + +## Purpose + +- Sets expectations of what the product, service, or technology can do +- Guides users through tasks: What do I need to do, why do I need to do it, and how do I do it? +- Enables informed decision making when evaluating the offering +- Helps users troubleshoot and recover from mistakes +- Provides a comprehensive, trustworthy reference source for information that users look up on demand + +## Module Types + +| Module Type | File prefix | Purpose | Title form | +|-------------|-------------|---------|------------| +| **Concept** | con-*.adoc | Explain what and why | Noun phrase | +| **Procedure** | proc-*.adoc | Step-by-step how-to instructions | Imperative verb | +| **Reference** | ref-*.adoc | Lookup data (commands, configurations, options) | Noun phrase | +| **Assembly** | assembly-*.adoc | Combine modules to address user stories | Imperative or noun phrase | +| **Snippet** | snip-*.adoc | Reusable content fragments (no structural elements) | N/A | + +## Choosing the Right Module Type + +| User Need | Module Type | +|-----------|-------------| +| Understand a concept | Concept module | +| Complete a task | Procedure module | +| Look up reference data | Reference module | +| Address a user story end-to-end | Assembly (combining modules) | + +## Style Guide + +IBM Style guide, Red Hat Supplementary Style guide diff --git a/.claude/resources/cqa-common-issues.md b/.claude/resources/cqa-common-issues.md deleted file mode 100644 index b4404f4068a..00000000000 --- a/.claude/resources/cqa-common-issues.md +++ /dev/null @@ -1,227 +0,0 @@ -# CQA Common Issues and Fixes - -Reference guide for troubleshooting common CQA 2.1 validation issues. - -## Modules nested within modules - -- **Symptom**: Include statements for modules within other modules (not in assemblies) -- **Root cause**: Violates Red Hat modular documentation rule: "A module should not contain another module" -- **Fix**: - 1. Create an assembly to contain the modules - 2. Move all module includes to the assembly - 3. Update parent assembly to include the new assembly instead of individual modules -- **Example**: If `proc-main.adoc` includes `proc-substep.adoc`, create `assembly-main-process.adoc` to include both procedures - -## Custom subheadings in procedure modules - -- **Symptom**: Procedure modules contain section headings (`== Custom Section`) beyond standard ones -- **Root cause**: Red Hat modular docs restrict procedure subheadings to predefined types only -- **Fix**: Remove custom subheadings; use only: `.Prerequisites`, `.Procedure`, `.Verification`, `.Troubleshooting`, `.Next steps`, `.Additional resources` -- **Alternative**: Move content requiring subheadings to concept or reference modules - -## Block titles incompatible with DITA - -- **Symptom**: Vale warning about block titles (`.Title` format) in unexpected contexts -- **Fix**: Convert to section headings (`== Title` format) or remove if in snippets - -## Short description missing or wrong length - -- **Symptom**: Vale error about missing shortdesc or character count -- **Fix**: Add `[role="_abstract"]` before intro paragraph, ensure 50-300 chars -- **Important**: Don't duplicate content—mark existing paragraph when appropriate - -## Incorrect title pattern - Concept module - -- **Symptom**: Concept module using imperative verb (e.g., "Achieve high availability") -- **Fix**: Change to noun phrase (e.g., "High availability with database layers") - -## Incorrect title pattern - Procedure module - -- **Symptom**: Procedure module using gerund form (e.g., "Achieving high availability") -- **Fix**: Change to imperative form (e.g., "Achieve high availability") - -## Grammar/parallel structure - -- **Symptom**: Verb agreement issues in compound phrases -- **Fix**: Ensure parallel structure (e.g., "helps simplify and accelerate" not "helps simplify and accelerates") - -## Missing context restoration in assembly - -- **Symptom**: Assembly doesn't restore parent context at end -- **Fix**: Add at end of assembly: - ```asciidoc - ifdef::parent-context-of-[context-name][:context: {parent-context-of-[context-name]}] - ifndef::parent-context-of-[context-name][:!context:] - ``` - -## Content other than a single list in .Procedure section - -- **Symptom**: DITA task mapping error: "Content other than a single list cannot be mapped to DITA tasks" -- **Fix**: Move descriptive content (bullets, explanations) before the `.Procedure` section -- **Example**: If you have bullets describing what data is displayed, put them in the introductory content, not after `.Procedure` - -## Red Hat style violations - -- **Symptom**: Vale warning about using "like" instead of "such as" -- **Fix**: Use "such as" for examples (e.g., "catalog entities (such as components, APIs)") -- **Note**: Always run Vale with default config after DITA validation to catch style issues - -## Snippet files with structural elements - -- **Symptom**: Vale warning about missing content type, document title, or block titles in snippet files -- **Root cause**: Snippet files should only contain content (lists, paragraphs, code blocks), not structural elements -- **Fix**: - 1. Add `:_mod-docs-content-type: SNIPPET` to the beginning of all snippet files - 2. Remove structural block titles (`.Prerequisites`, `.Procedure`, `.Verification`, `.Next steps`) from snippet files - 3. Add those block titles to the parent files before the include statements -- **Example**: - - ✗ Wrong (in snippet): - ```asciidoc - .Prerequisites - * You have installed the Operator. - ``` - - ✓ Correct (in parent file): - ```asciidoc - .Prerequisites - include::snip-prerequisites.adoc[] - * Additional prerequisite in parent. - ``` - - ✓ Correct (in snippet): - ```asciidoc - :_mod-docs-content-type: SNIPPET - - * You have installed the Operator. - ``` - -## Inline admonitions in procedures - -- **Symptom**: Inconsistent admonition formatting, or `AsciiDocDITA.TaskStep` warnings -- **Root cause**: Inline format (`TIP:`, `NOTE:`, etc.) is less consistent than block format -- **Fix**: Convert all inline admonitions to block format with delimiters -- **Example**: - - ✗ Wrong: - ```asciidoc - TIP: Optionally, enable the cache for unsupported plugins. - ``` - - ✓ Correct: - ```asciidoc - [TIP] - ==== - Optionally, enable the cache for unsupported plugins. - ==== - ``` - -## Admonitions in procedure steps without continuation marks - -- **Symptom**: Vale warning `AsciiDocDITA.TaskStep`: "Content other than a single list cannot be mapped to DITA tasks" -- **Root cause**: Admonitions, source blocks, or other content after a procedure step need to be attached to that step using a continuation mark -- **Fix**: Add a line with a single `+` (continuation mark) before the admonition or content block -- **Example**: - - ✗ Wrong: - ```asciidoc - . Enable the cache in your configuration file. - - [TIP] - ==== - You can also enable caching for other plugins. - ==== - ``` - - ✓ Correct: - ```asciidoc - . Enable the cache in your configuration file. - + - [TIP] - ==== - You can also enable caching for other plugins. - ==== - ``` - -## Example blocks nested in other blocks - -- **Symptom**: Vale error `AsciiDocDITA.ExampleBlock`: "Examples can not be inside of other blocks in DITA" -- **Root cause**: DITA does not support nested example blocks (`.Example` with `====` delimiters) -- **Fix**: Convert the example block to regular text with a source code block, or move it outside the parent block -- **Example**: - - ✗ Wrong: - ```asciidoc - ** Configure the base URL: - + - .Configuring the baseUrl - ==== - [source,yaml] - ---- - app: - baseUrl: https://example.com - ---- - ==== - ``` - - ✓ Correct: - ```asciidoc - ** Configure the base URL: - + - Configuring the baseUrl: - + - [source,yaml] - ---- - app: - baseUrl: https://example.com - ---- - ``` - -## Modules in wrong directories - -- **Symptom**: Modules in `modules/configuring/` but not actually used in the Configuring title -- **Root cause**: Modules should be organized by where they're actually used, not by their topic -- **Fix**: - 1. Use `git mv` to relocate modules to directories matching their actual usage - 2. Update include paths in all titles/assemblies that reference the moved modules - 3. Run build validation to ensure everything still works -- **Example**: A module `con-dynamic-plugins-dependencies.adoc` in `modules/configuring/` but only included in the "Installing plugins" title should be moved to `modules/dynamic-plugins/` - -## Usage of "respective" and "respectively" - -- **Symptom**: Not a Vale error, but poor writing style that makes content harder to understand -- **Fix**: Rewrite sentences to be explicit about which items correspond to which -- **Examples**: - - ✗ Wrong: "certificates and keys respectively in the `ldap_certs.pem` and `ldap_keys.pem` files" - - ✓ Correct: "certificates in the `ldap_certs.pem` file and keys in the `ldap_keys.pem` file" - - ✗ Wrong: "their respective environment variable names" - - ✓ Correct: "the corresponding environment variable name for each secret" - - ✗ Wrong: "Files with _.k8s_ or _.ocp_ extensions provide overrides for Kubernetes and OpenShift, respectively" - - ✓ Correct: "Files with _.k8s_ extension provide overrides for Kubernetes, and files with _.ocp_ extension provide overrides for OpenShift" - -## Reference modules with nested sections (level 2+) - -- **Symptom**: Vale DITA error: "Level 2, 3, 4, and 5 sections are not supported in DITA" in reference modules -- **Root cause**: Reference modules in DITA cannot have nested sections (`==`, `===`, etc.). They can only have: - - Level 1 heading (module title) - - Tables - - Description lists - - Paragraphs - - Code blocks -- **Wrong approach**: Converting `==` subheadings to description lists maintains structure but may not be semantic -- **Correct approach**: Split the monolithic reference module into multiple focused reference modules: - 1. Create individual reference modules for each major section (each `==` becomes its own module) - 2. Create an assembly to organize and include all the new reference modules - 3. Update parent assembly to include the new assembly instead of the old monolithic module - 4. Update all cross-references (xrefs) throughout the documentation to point to the new assembly ID -- **Example**: A `ref-permissions.adoc` with 11 `==` subsections becomes: - - 11 individual reference modules (one per subsection) - - 1 assembly that includes all 11 modules - - Parent assembly updated to include the new assembly - - All xrefs updated from `ref-permissions_{context}` to `assembly-permissions-reference_{context}` - -## Broken cross-references after module splitting - -- **Symptom**: Build errors showing "Unknown ID" when building with ccutil -- **Root cause**: When you split a reference module into an assembly + multiple modules, old xrefs still point to the old module ID -- **Fix**: Search for all xrefs to the old module ID and update them to point to the new assembly ID: - ```bash - # Find all xrefs to old module - grep -r "xref:old-module-id" modules/ assemblies/ - - # Update them to new assembly ID - xref:old-module-id_{context} → xref:new-assembly-id_{context} - xref:old-module-id_title-name → xref:new-assembly-id_title-name - ``` -- **Verification**: Run `build/scripts/build-ccutil.sh` to verify all xrefs resolve correctly diff --git a/.claude/resources/red-hat-peer-review-for-cqa.md b/.claude/resources/red-hat-peer-review-for-cqa.md new file mode 100644 index 00000000000..8d71179a0d0 --- /dev/null +++ b/.claude/resources/red-hat-peer-review-for-cqa.md @@ -0,0 +1,149 @@ +# Red Hat Peer Review Checklists for CQA + +**Note:** This is a condensed extract of the complete [Red Hat peer review guide for technical documentation](./.claude/resources/red-hat-peer-review.md), focused on the peer review checklists most relevant for CQA work. For the full guide including process guidelines and detailed explanations, refer to the complete document. + +## About these checklists + +Writers and peer reviewers can use the peer review checklists as a quick reference to the Red Hat technical documentation style guidelines. Use the checklists to help structure your peer reviews, and adapt the checklists to meet the needs of your team. + +For guidance on each topic outlined in the checklists, see the following resources: + +- IBM Style +- [Red Hat supplementary style guide for product documentation](https://redhat-documentation.github.io/supplementary-style-guide/) +- [Merriam-Webster Dictionary](https://www.merriam-webster.com/) + +--- + +## Language + +### Spelling errors and typos + +- [ ] American English spelling is used consistently in the text. +- [ ] Correct punctuation is used in the text. + +### Grammar + +- [ ] American English grammar is used consistently in the text. +- [ ] Slang or non-English words are not used in the text. + +### Correct word usage and entity naming + +- [ ] Precise wording is used. Words are used in accordance with their dictionary definitions. + - [ ] The writer has also considered the context of the words, so that the meaning, tone, and implications are appropriate. +- [ ] Named entities are classified on first use. +- [ ] Contractions are avoided, unless they are used intentionally for conversational style, such as in quick starts. +- [ ] Proper nouns are capitalized. +- [ ] Conscious language guidelines are followed. The terms _blacklist_, _whitelist_, _master_, and _slave_ are used only when absolutely necessary. + +### Correct use of acronyms and abbreviations + +- [ ] Acronyms are expanded on first use. +- [ ] Abbreviations are used and applied correctly. + +### Terms and constructions + +- [ ] Phrasal verbs are avoided. +- [ ] Use of problematic terms such as _should_ or _may_ are avoided. +- [ ] Use of anthropomorphism is avoided. + +--- + +## Style + +### Passive voice + +- [ ] Unnecessary use of passive voice is avoided. + +### Tense + +- [ ] Future tense is used only when necessary. + +### Titles + +- [ ] Titles use sentence case. +- [ ] Titles and headings have consistent styling. +- [ ] Titles are effective and descriptive. +- [ ] Titles focus on customer tasks instead of the product. +- [ ] Titles are 3-11 words long and have 50-80 characters. +- [ ] Titles of procedure modules begin with a gerund, for example, "Configuring", "Using", or "Installing". + +### Number + +- [ ] Number conventions are followed. + +### Formatting + +- [ ] Content follows style and consistency guidelines for formatting, for example, user-replaceable values. +- [ ] Content uses correct AsciiDoc markup. + +--- + +## Minimalism + +### Customer focus and action orientation + +- [ ] Content focuses on actions and customer tasks. + +### Scannability/Findability + +- [ ] Content is easy to scan. +- [ ] Information is easy to find. +- [ ] Content uses bulleted lists and tables to make information easier to digest. + +### Sentences + +- [ ] Sentences are not unnecessarily long and only use the required number of words. Ensure that any long sentences cannot be shortened. +- [ ] Sentences are concise and informative. + +### Conciseness (no fluff) + +- [ ] The text does not include unnecessary information. +- [ ] Admonitions are used only when necessary. +- [ ] Screenshots and diagrams are used only when necessary. +- [ ] Content is clear, concise, precise, and unambiguous. + +--- + +## Structure + +### Structure meets modular guidelines + +- [ ] Module types are not mixed, for example, concept and procedure information is separate. +- [ ] Module types are used correctly. +- [ ] Tags and entities are used correctly. +- [ ] Modules are as self-contained as possible to facilitate reuse in other locations. + +### A logical flow of information + +- [ ] Information is provided at the right pace. +- [ ] Information is presented in the most logical order and location. +- [ ] Cross-references are used appropriately and only when useful. + +### User stories + +- [ ] The user goal is clear. +- [ ] Tasks reflect the intended goal of the user. +- [ ] Troubleshooting and error recognition steps are included where appropriate. + +--- + +## Usability + +### Content + +- [ ] The content is appropriate for the intended audience. + +### Accessibility + +- [ ] Tables and diagrams have alternative (alt) text and are clearly labeled and explained in surrounding text. + +### Links + +- [ ] Use of inline links is minimized. +- [ ] All the links in the document work. +- [ ] All links are current. + +### Visual continuity + +- [ ] The content renders correctly in preview, including correct spacing, bulleted lists, and numbering. +- [ ] Product versioning and release dates are accurate. diff --git a/.claude/resources/red-hat-peer-review.md b/.claude/resources/red-hat-peer-review.md index 8560427350e..1742f4426f3 100644 --- a/.claude/resources/red-hat-peer-review.md +++ b/.claude/resources/red-hat-peer-review.md @@ -1,4616 +1,527 @@ -<!DOCTYPE html> -<html lang="en"> -<head> -<meta charset="UTF-8"> -<meta http-equiv="X-UA-Compatible" content="IE=edge"> -<meta name="viewport" content="width=device-width, initial-scale=1.0"> -<meta name="generator" content="Asciidoctor 2.0.20"> -<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico"> -<title>Red Hat peer review guide for technical documentation - - - - - - - - - -
-
-

Introduction

-
-
-

About this guide

-
-

This guide provides information about best practices for peer reviewing Red Hat technical documentation.

-
-
-

The Red Hat Customer Content Services (CCS) team created this guide for customer-facing documentation, but upstream communities that want to align more closely with the standards used by Red Hat documentation can also use this guide.

-
-
-
-

Purpose of peer reviews

-
-

It is recommended to perform a peer review on all updates to Red Hat documentation. Peer review provides the following benefits:

-
-
-
    -
  • -

    Ensuring higher quality content, which helps our users

    -
  • -
  • -

    Giving writers and reviewers a chance to see more content, find new ways to approach changes, and share expertise

    -
  • -
-
-
-

For peer reviews to achieve these goals, reviewers should present their comments positively and avoid negative wording. At the same time, writers must be open to reviewers' feedback. Peer reviews can catch issues that writers might miss.

-
-
-
-
-
-

Peer review checklists

-
-
-

Writers and peer reviewers can use the peer review checklists as a quick reference to the Red Hat technical documentation style guidelines. Use the checklists to help structure your peer reviews, and adapt the checklists to meet the needs of your team.

-
-
-

For guidance on each topic outlined in the checklists, see the following resources:

-
- -
-

Language

- - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 1. Language checklist
Check forChecked
-

Spelling errors and typos

-
-
-
    -
  • -

    American English spelling is used consistently in the text.

    -
  • -
  • -

    Correct punctuation is used in the text.

    -
  • -
-

-

Grammar

-
-
-
    -
  • -

    American English grammar is used consistently in the text.

    -
  • -
  • -

    Slang or non-English words are not used in the text.

    -
  • -
-

-

Correct word usage and entity naming

-
-
-
    -
  • -

    Precise wording is used. Words are used in accordance with their dictionary definitions.

    -
    -
      -
    • -

      The writer has also considered the context of the words, so that the meaning, tone, and implications are appropriate.

      -
    • -
    -
    -
  • -
  • -

    Named entities are classified on first use.

    -
  • -
  • -

    Contractions are avoided, unless they are used intentionally for conversational style, such as in quick starts.

    -
  • -
  • -

    Proper nouns are capitalized.

    -
  • -
  • -

    Conscious language guidelines are followed. The terms blacklist, whitelist, master, and slave are used only when absolutely necessary.

    -
  • -
-

-

Correct use of acronyms and abbreviations

-
-
-
    -
  • -

    Acronyms are expanded on first use.

    -
  • -
  • -

    Abbreviations are used and applied correctly.

    -
  • -
-

-

Terms and constructions

-
-
-
    -
  • -

    Phrasal verbs are avoided.

    -
  • -
  • -

    Use of problematic terms such as should or may are avoided.

    -
  • -
  • -

    Use of anthropomorphism is avoided.

    -
  • -
-

-
-
-

Style

- - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 2. Style checklist
Check forChecked
-

Passive voice

-
-
-
    -
  • -

    Unnecessary use of passive voice is avoided.

    -
  • -
-

-

Tense

-
-
-
    -
  • -

    Future tense is used only when necessary.

    -
  • -
-

-

Titles

-
-
-
    -
  • -

    Titles use sentence case.

    -
  • -
  • -

    Titles and headings have consistent styling.

    -
  • -
  • -

    Titles are effective and descriptive.

    -
  • -
  • -

    Titles focus on customer tasks instead of the product.

    -
  • -
  • -

    Titles are 3-11 words long and have 50-80 characters.

    -
  • -
  • -

    Titles of procedure modules begin with a gerund, for example, "Configuring", "Using", or "Installing".

    -
  • -
-

-

Number

-
-
-
    -
  • -

    Number conventions are followed.

    -
  • -
-

-

Formatting

-
-
-
    -
  • -

    Content follows style and consistency guidelines for formatting, for example, user-replaceable values.

    -
  • -
  • -

    Content uses correct AsciiDoc markup.

    -
  • -
-

-
-
-

Minimalism

- - ---- - - - - - - - - - - - - - - - - - - - - - - - - -
Table 3. Minimalism checklist
Check forChecked
-

Customer focus and action orientation

-
-
-
    -
  • -

    Content focuses on actions and customer tasks.

    -
  • -
-

-

Scannability/Findability

-
-
-
    -
  • -

    Content is easy to scan.

    -
  • -
  • -

    Information is easy to find.

    -
  • -
  • -

    Content uses bulleted lists and tables to make information easier to digest.

    -
  • -
-

-

Sentences

-
-
-
    -
  • -

    Sentences are not unnecessarily long and only use the required number of words. Ensure that any long sentences cannot be shortened.

    -
  • -
  • -

    Sentences are concise and informative.

    -
  • -
-

-

Conciseness (no fluff)

-
-
-
    -
  • -

    The text does not include unnecessary information.

    -
  • -
  • -

    Admonitions are used only when necessary.

    -
  • -
  • -

    Screenshots and diagrams are used only when necessary.

    -
  • -
  • -

    Content is clear, concise, precise, and unambiguous.

    -
  • -
-

-
-
-

Structure

- - ---- - - - - - - - - - - - - - - - - - - - - -
Table 4. Structure checklist
Check forChecked
-

Structure meets modular guidelines

-
-
-
    -
  • -

    Module types are not mixed, for example, concept and procedure information is separate.

    -
  • -
  • -

    Module types are used correctly.

    -
  • -
  • -

    Tags and entities are used correctly.

    -
  • -
  • -

    Modules are as self-contained as possible to facilitate reuse in other locations.

    -
  • -
-

-

A logical flow of information

-
-
-
    -
  • -

    Information is provided at the right pace.

    -
  • -
  • -

    Information is presented in the most logical order and location.

    -
  • -
  • -

    Cross-references are used appropriately and only when useful.

    -
  • -
-

-

User stories

-
-
-
    -
  • -

    The user goal is clear.

    -
  • -
  • -

    Tasks reflect the intended goal of the user.

    -
  • -
  • -

    Troubleshooting and error recognition steps are included where appropriate.

    -
  • -
-

-
-
-

Usability

- - ---- - - - - - - - - - - - - - - - - - - - - - - - - -
Table 5. Usability checklist
Check forChecked
-

Content

-
-
-
    -
  • -

    The content is appropriate for the intended audience.

    -
  • -
-

-

Accessibility

-
-
-
    -
  • -

    Tables and diagrams have alternative (alt) text and are clearly labeled and explained in surrounding text.

    -
  • -
-

-

Links

-
-
-
    -
  • -

    Use of inline links is minimized.

    -
  • -
  • -

    All the links in the document work.

    -
  • -
  • -

    All links are current.

    -
  • -
-

-

Visual continuity

-
-
-
    -
  • -

    The content renders correctly in preview, including correct spacing, bulleted lists, and numbering.

    -
  • -
  • -

    Product versioning and release dates are accurate.

    -
  • -
-

-
-
-
-
-

Providing peer review feedback

-
-
-

Peer reviews must be kind, helpful, and consistent among peer reviewers.

-
-
-
    -
  • -

    Support your comments.

    -
    -
      -
    • -

      Use documented resources, such as style guides or Red Hat writing conventions.

      -
    • -
    • -

      Explain the impact of the issue on the audience.

      -
    • -
    • -

      If you cannot find documented support, rethink the need for the comment.

      -
    • -
    -
    -
  • -
  • -

    Use a respectful tone.

    -
    -
      -
    • -

      Pose comments as questions when you are unsure.

      -
    • -
    • -

      Choose your wording carefully and do not be harsh. Be concise for easy content updates. If you have a suggestion, ask the writer to "consider" your comment or state that you "suggest" something.

      -
    • -
    -
    -
  • -
  • -

    Stay within scope. Review only the new content, changed content, and content that provides necessary context.

    -
    -
      -
    • -

      Review content that was changed in the pull request (PR) or merge request (MR).

      -
    • -
    • -

      Review the preexisting section to ensure that the new or updated content fits.

      -
    • -
    • -

      Do not request enhancements to the content unless the content is unclear without it.

      -
    • -
    • -

      If you notice an issue in related content that you are not explicitly reviewing, use friendly wording to suggest changes. Some examples of appropriate language include:

      -
      -
      -
      -
        -
      • -

        "I know this was existing content, but would you mind fixing this typo while you’re in there?"

        -
      • -
      • -

        "I know this is out of scope for this PR, but consider looking into this in a future update."

        -
      • -
      -
      -
      -
      -
      -

      The writer might either address the issue now, track it as a future request, or let the peer reviewer know that they cannot apply the change.

      -
      -
    • -
    • -

      For more information about scope, see Scope examples.

      -
    • -
    -
    -
  • -
  • -

    Understand that peer reviewers do not review for technical accuracy.

    -
    -
      -
    • -

      Subject matter experts (SMEs) and quality engineering (QE) associates are responsible for testing and technical accuracy.

      -
    • -
    • -

      Peer reviewers check for issues like usability problems, style guide compliance, and unclear or missing steps in a procedure.

      -
    • -
    • -

      Peer reviewers do not need to understand all the technical details. The audience might be users who are already familiar with the technology. Request additional technical information as a followup and not as a requirement for the current PR or MR.

      -
    • -
    • -

      Some peer reviewers might be more familiar with a particular subject or know that an update can affect another area of the documentation. In these cases, provide this feedback to the writer.

      -
    • -
    • -

      If you are certain that information is wrong or that a command will fail, ask the contributor to check with their SME or QE. Avoid tagging their SMEs or QEs directly to ask.

      -
    • -
    -
    -
  • -
  • -

    Recognize that writers do not have to accept all your suggestions.

    -
    -
      -
    • -

      Writers must implement mandatory peer review feedback that relates to style guides or typographical errors, but they can implement optional feedback at their discretion. If the issue does not break any rules or is not an actual typographical error or issue, let writers keep it as it is.

      -
    • -
    • -

      If you are merging a PR or MR and feel strongly that the writer must make a change but they disagree, speak to the writer in private. Cite style guides or vetted documentation so that they know your reasoning. Listen to their perspective. If the topic of the disagreement is not in any of the guides, consider bringing it to the team for discussion. In some cases, the guidelines might need to be updated.

      -
    • -
    -
    -
  • -
  • -

    Differentiate between required and optional changes.

    -
    -
      -
    • -

      Required changes must be fixed before the writer can merge the PR or MR. Support your change with a reference to the relevant style guide or principle. Examples include modular docs template adherence, typographical error fixes, or product-specific guidelines.

      -
    • -
    • -

      Optional changes do not have to be addressed before the writer can merge the PR or MR. Use softer language, for example, "Here, it might be clearer to…​" or use a [SUGGESTION] tag to clearly indicate it to the writer. Examples: wording improvements, content relocation, and stylistic preference.

      -
    • -
    • -

      For more information about required versus suggested changes, see Scope examples.

      -
    • -
    -
    -
  • -
  • -

    Add your own suggestions for improvements for a problematic area. Do not provide vague or generic comments, such as "this doesn’t make sense."

    -
    -
      -
    • -

      Offer rewrites as suggestions, not something that the writer has to take word-for-word. For example, "I don’t understand this description. Did you mean…​?"

      -
    • -
    • -

      Avoid rewriting entire paragraphs of the writer’s content. If you find yourself doing this because multiple items in a paragraph need attention, break out your suggestions. If providing an alternative paragraph wording is necessary, ensure that you make it clear that the writer does not need to use your suggestion exactly as written.

      -
    • -
    • -

      If you notice a recurring issue, leave a global comment for the writer so that they know to address every instance of the issue. For example, "[GLOBAL] This typo occurs in other locations within the doc. I won’t comment on the other examples after this point, but please address all instances."

      -
    • -
    -
    -
  • -
  • -

    Provide positive feedback as well as negative

    -
    -
      -
    • -

      If during your review you find a portion of content that you think is exceptionally well done, point that out in your feedback. For example, "This part is pretty much perfect, nicely done!"

      -
    • -
    • -

      This reinforces good writing habits and also makes getting reviews less daunting.

      -
    • -
    -
    -
  • -
  • -

    If the review requires a significant amount of editing or rework, pause the review and contact the writer directly to discuss.

    -
    -
      -
    • -

      This avoids overwhelming the writer with too many comments and saves the peer reviewer’s time.

      -
    • -
    • -

      If the content is not ready for peer review, tell the writer and continue after it is ready.

      -
    • -
    • -

      Examples of when to pause a review include if the build is broken, if the content is not rendering properly, or if the content is not modularized correctly.

      -
    • -
    • -

      Contact the writer privately, for example, by chat, to express your concerns and provide advice on how to move forward.

      -
    • -
    • -

      Decide whether you have the time to work with the writer or if you need to request that they contact someone else, for example, an onboarding buddy or a senior writer.

      -
    • -
    -
    -
  • -
  • -

    Notify the writer when the review is complete.

    -
    -
      -
    • -

      After you finish the review, notify the writer that the review is complete, so that they can start reviewing and implementing your feedback.

      -
    • -
    -
    -
  • -
-
-
-
Scope examples
-

Some suggested changes might improve the content but are not relevant or in the scope of the updates. The following table includes examples of changes that are in scope and required, in scope but suggested, and out of scope.

-
- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 6. Examples of in scope and out of scope feedback
In scope - requiredIn scope - suggestedOut of scope

Typographical errors, grammatical issues, formatting issues

-

Rearranging:

-
-
-
    -
  • -

    Moving something to the prerequisites section

    -
  • -
  • -

    Moving verification steps out of the ".Procedure" and into a specific ".Verification" section in the procedure module, if applicable

    -
  • -
-

Comments on content that was not changed in the PR or MR

-

Modular docs guidelines, for example:

-
-
-
    -
  • -

    Adhering to the templates

    -
  • -
  • -

    Correct anchor ID format

    -
  • -
-

Reviewing wording that does not sound right to the reviewer to see if it can be improved

Requesting additional details, like default values or units

-

IBM Style Guide - and CCS supplementary style guide guidelines, for example:

-
-
-
    -
  • -

    "may" to "might"

    -
  • -
  • -

    "Click the Save button." to "Click Save."

    -
  • -
-

Avoiding sequences of admonitions, for example, a [NOTE] followed by an [IMPORTANT] block, especially if they are the same type of admonition

Technical accuracy, unless you know for certain something is wrong or that a command will fail

-

Product-specific guidelines, for example:

-
-
-
    -
  • -

    Prompts on terminal commands

    -
  • -
  • -

    Separating commands into individual code blocks

    -
  • -
  • -

    Sentence case in titles

    -
  • -
-
-
-
-
-

Creating a peer review process

-
-
-

Red Hat Customer Content Services (CCS) does not follow one definitive peer review process. Each team within CCS is different, with unique workflows, preferred tools, release cycles, and engineering team preferences that are customized to meet their product and customer requirements. Each team determines a peer review process that works for them.

-
-
-

Define a process so that peer reviews are used consistently throughout your team.

-
-
-

Considerations when creating a peer review process

-
-

Before you establish a peer review process that works for your team, review the following factors:

-
- -

Is a peer review required or optional?

-
-

A technical writing manager, documentation program manager (DPM), or content strategist (CS) determines whether requesting a peer review is required or optional and communicates this expectation to the team.

-
-
-
Example options
-
    -
  • -

    Require a peer review on each GitHub PR (or GitLab MR) prior to accepting the request.

    -
  • -
  • -

    Require a peer review in certain, defined situations.

    -
  • -
  • -

    Request a peer review at the writer’s discretion.

    -
  • -
-
-

Who are the peer reviewers?

-
-

Determine who conducts a peer review. A manager, DPM, or CS communicates this expectation to the team.

-
-
-
Example options
-
    -
  • -

    Individuals can volunteer as peer reviewers.

    -
  • -
  • -

    Everyone on the team is expected to be available to review at any time.

    -
  • -
  • -

    Everyone participates in peer reviews and rotates being available or follows a roster.

    -
  • -
-
-

How does a writer request a peer review?

-
-

Determine how writers request a peer review.

-
-
-
Example options
-
    -
  • -

    Add the request details to a tracking spreadsheet.

    -
  • -
  • -

    Communicate with a reviewer in a Google Chat or a Slack channel.

    -
  • -
  • -

    Request a review through email.

    -
  • -
  • -

    Use GitHub or GitLab labels to mark when content is ready for review.

    -
  • -
  • -

    Open a Jira ticket or Bugzilla ticket with the request.

    -
  • -
  • -

    Contact a reviewer in the original documentation ticket.

    -
  • -
-
-

How is the peer reviewer assigned?

-
-

Some assignment methods might work better if the reviewers are on the same product team; others might work better for cross-product reviews. Establish a method that suits the structure and dynamic of the group of writers and reviewers that the process targets.

-
-
-

Writers must ensure that reviewers can access the tools needed to complete the review.

-
-
-
Example options
-
    -
  • -

    Reviewers check a tracking spreadsheet and assign themselves.

    -
  • -
  • -

    Reviewers are notified for all peer review requests and assign themselves.

    -
  • -
  • -

    Reviewers regularly check a GitHub PR or a GitLab MR queue and assign themselves.

    -
  • -
  • -

    A writer contacts the reviewer.

    -
  • -
-
-

What is the level or scope of the peer review?

-
-

Determine the level or scope of the peer review, so that the writer and reviewer have the same expectations.

-
-
- - - - - -
- - -
-

The writer is responsible for informing the peer reviewer of any essential information related to the content.

-
-
-
-
-
Example options
-
    -
  • -

    Perform a general review that checks for typographical errors, style guide compliance, and link checking.

    -
  • -
  • -

    Perform a deeper review of the content that includes checks on typographical errors and grammar, content placement or flow, structure, style guide compliance, and consistency.

    -
  • -
-
-

Is there a checklist for the peer reviewer to follow?

-
-

Determine which checklists and other resources the reviewer should follow.

-
-
- - - - - -
- - -
-

The writer must inform the peer reviewer of any essential information related to the content.

-
-
-
-
-
Example options
-
    -
  • -

    Follow the CCS peer review checklist.

    -
  • -
  • -

    Follow the CCS peer review checklist and a team-specific checklist.

    -
  • -
-
-

What platform and tools are used to perform the review and give feedback?

-
-

Determine how to share content and provide feedback.

-
-
-
Example options
-
    -
  • -

    Draft content in a Google Doc and use the document for comments and suggestions.

    -
  • -
  • -

    Share a GitHub PR or GitLab MR. Reviewers can comment directly inline for each change.

    -
  • -
  • -

    Provide small snippets of content by email, instant messaging, or a ticket.

    -
  • -
-
- -

What is the expected turnaround time?

-
-

Determine the expected turnaround time for completing a peer review. Writers should communicate if there is any urgency or deadlines for the review.

-
-
-
Example options
-
    -
  • -

    Reviewers check the GitHub or GitLab queue daily or twice daily.

    -
  • -
  • -

    Reviewers respond to a Slack or a Google Chat ping within a few hours.

    -
  • -
  • -

    Reviewers check a tracking spreadsheet daily.

    -
  • -
  • -

    Writers communicate the requested turnaround time after requesting the peer review.

    -
  • -
-
-

How are urgent peer reviews escalated?

-
-

Determine how an unassigned peer review request is escalated if it can affect product release schedules.

-
-
-
Example options
-
    -
  • -

    Inform a manager, DPM, or CS of the unassigned time-critical peer review so that they can escalate the peer review request or negotiate a new timeline for reviewing the content.

    -
  • -
  • -

    Use your peer review request channel to request an urgent peer review. Ensure you detail the tight timelines in the channel.

    -
  • -
-
-

How is peer review feedback incorporated?

-
-

Determine the expectations for addressing or incorporating feedback. Expectations become important if the writer and peer reviewer disagree on a review item.

-
-
-

Incorporate an escalation process into your peer review process, such as communicating in a guidelines group or requesting manager, DPM, or CS input. This way, the writer and peer reviewer can resolve any disagreement.

-
-
-
Example options
-
    -
  • -

    Incorporate feedback at the writer’s discretion.

    -
  • -
  • -

    Establish a communication channel for informing the peer reviewer of the next steps.

    -
  • -
  • -

    Address peer review feedback and request the peer reviewer to perform a review of the revised content.

    -
  • -
-
-
-
-

Finalizing your team’s peer review process

-
-

Writers and peer reviewers must agree on the expectations for the peer review process.

-
-
-

Complete the following steps to finalize the peer review process:

-
-
-
    -
  1. -

    Draft a proposal for the peer review process.

    -
  2. -
  3. -

    Share the proposal with the team and set a time for the team to provide feedback.

    -
  4. -
  5. -

    Test the process to ensure that it works well for your team.

    -
  6. -
  7. -

    Document the final process wherever your team stores its resources.

    -
  8. -
  9. -

    Communicate the final process to the team and any other contributors or stakeholders.

    -
  10. -
-
-
-
-

Example peer review process 1

-
-

The first example peer review process demonstrates how a cross-product team uses Jira tickets for communication and GitLab to perform peer reviews.

-
-
-

This team has a peer review squad of at least two members at any specific time. Membership of the squad rotates every week. The team maintains a peer review assignment roster in a Confluence page that lists the assigned reviewers for each week. The assignment roster is published in the Jira product dashboards, so that writers can see the assigned reviewers for the current week.

-
-
-
-A flowchart that is a visual representation of the first example peer review process described in the following procedure -
-
Figure 1. Example 1 of a peer review process conducted through Jira and GitLab
-
-
-
Prerequisites
-
    -
  • -

    A subject matter expert (SME) has completed a technical review.

    -
    -

    To request and mark a technical review as complete, the writer performs the following tasks:

    -
    -
    -
      -
    1. -

      Put a link to the MR in the Git Pull Request field in the Jira doc ticket.

      -
    2. -
    3. -

      Submit the MR for SME review.

      -
    4. -
    5. -

      Apply the SME reviewer’s feedback.

      -
    6. -
    7. -

      Update the MR in GitLab.

      -
    8. -
    -
    -
  • -
-
-
-
Procedure
-
    -
  1. -

    To request a peer review, the writer performs the following tasks:

    -
    -
      -
    1. -

      Check the peer review assignment roster in the Jira product dashboard.

      -
    2. -
    3. -

      Add a comment in the Jira doc ticket to contact the assigned reviewers.

      -
      - - - - - -
      - - -
      -

      The writer needs to contact the reviewers who are currently on duty according to the roster.

      -
      -
      -
      -
    4. -
    5. -

      Add the assigned reviewers to the Includes field in the Jira doc ticket.

      -
    6. -
    7. -

      Optional: Contact the assigned reviewers in the MR or chat.

      -
    8. -
    -
    -
  2. -
  3. -

    If a peer review does not start within the expected timeframe and the review deadline is jeopardized, the writer performs the following task:

    -
    -
      -
    • -

      Contact the assigned reviewers again to communicate the urgency of the request.

      -
      - - - - - -
      - - -
      -

      If the review deadline is not jeopardized, the writer does not need to take any action at this point.

      -
      -
      -
      -
    • -
    -
    -
  4. -
  5. -

    To complete a review, the peer reviewer performs the following tasks:

    -
    -
      -
    1. -

      Notify the other assigned reviewer that you will do the review.

      -
    2. -
    3. -

      Remove the other assigned reviewer from the Includes field in the Jira doc ticket.

      -
    4. -
    5. -

      Add review comments in the MR.

      -
    6. -
    7. -

      Notify the writer when you complete the review.

      -
    8. -
    -
    -
  6. -
  7. -

    To apply feedback and complete the process, the writer performs the following tasks:

    -
    -
      -
    1. -

      Apply the peer reviewer’s feedback.

      -
    2. -
    3. -

      Update the MR in GitLab.

      -
    4. -
    -
    -
  8. -
-
-
-
-

Example peer review process 2

-
-

The second example peer review process demonstrates how a team uses a Slack channel for communication and GitHub to perform peer reviews. The peer review team consists of five team members at a given time. Membership of the peer review team rotates every sprint.

-
-
-
-A flowchart that is a visual representation of the second example peer review process described in the following procedure. -
-
Figure 2. Example 2 of a peer review process conducted through Slack and GitHub
-
-
-
Procedure
-
    -
  1. -

    To request a peer review, the writer performs the following tasks:

    -
    -
      -
    1. -

      Notify the peer review squad using the Slack channel.

      -
    2. -
    3. -

      Include a link to the PR in the Slack notification.

      -
    4. -
    5. -

      Specify any deadline or other special considerations in the Slack notification.

      -
    6. -
    -
    -
  2. -
  3. -

    If a peer review does not start within the expected timeframe and the review deadline is jeopardized, the writer performs the following task:

    -
    -
      -
    • -

      Contact the assigned reviewer or the peer review squad again to communicate the urgency of the request.

      -
      - - - - - -
      - - -
      -

      If the review deadline is not jeopardized, the writer does not need to take any action at this point.

      -
      -
      -
      -
    • -
    -
    -
  4. -
  5. -

    To complete a review, the peer reviewer performs the following tasks:

    -
    -
      -
    1. -

      Mark the request in Slack to indicate that you will perform the review.

      -
    2. -
    3. -

      Add review comments in the PR.

      -
    4. -
    5. -

      Notify the writer when you complete the review.

      -
    6. -
    -
    -
  6. -
  7. -

    To apply feedback and complete the process, the writer performs the following tasks:

    -
    -
      -
    1. -

      Apply the peer reviewer’s feedback.

      -
    2. -
    3. -

      Update the PR in GitHub.

      -
    4. -
    -
    -
  8. -
-
-
-
-
-
-

Appendix A: Pros and cons of the different peer review platforms

-
-
-

Review the following pros and cons for each platform to choose the right peer review method for your team.

-
-

GitHub or GitLab

-
-
Pros
-
    -
  • -

    Provides a convenient method for commenting on specific lines of content on a GitHub PR or GitLab MR

    -
  • -
  • -

    Includes functionality for easily adding additional reviewers

    -
  • -
  • -

    Includes a mechanism for multiple people to collaborate on the same PR or MR

    -
  • -
  • -

    Provides an easy linking functionality

    -
  • -
  • -

    Offers the capability for writers to incorporate feedback before the PR or MR is approved

    -
  • -
-
-
-
Cons
-
    -
  • -

    Requires that you are familiar with the GitHub or GitLab UI

    -
  • -
  • -

    Requires that you have login credentials to comment on a PR or MR

    -
  • -
-
-

Google Docs

-
-
Pros
-
    -
  • -

    Includes a convenient method for commenting on specific text

    -
  • -
  • -

    Includes functionality for easily adding additional reviewers

    -
  • -
  • -

    Includes a mechanism for multiple people to collaborate on the same Google Doc

    -
  • -
  • -

    Provides an easy linking functionality

    -
  • -
  • -

    Supports copying and pasting of AsciiDoc syntax

    -
  • -
-
-
-
Cons
-
    -
  • -

    Can produce unreliable formatting when copying and pasting HTML, PDF, or markup syntax content

    -
  • -
  • -

    Can be time consuming to copy and paste AsciiDoc content

    -
  • -
-
-

Email

-
-
Pros
-
    -
  • -

    An easy tool for anyone to use

    -
  • -
  • -

    A historical record of the discussion

    -
  • -
-
-
-
Cons
-
    -
  • -

    Can be difficult to link specific email comments to other communication channels

    -
  • -
  • -

    Can be slow and time consuming

    -
  • -
  • -

    Can be difficult to understand feedback if the content is not well structured

    -
  • -
-
-

IRC, Google Chat, or Slack

-
-
Pros
-
    -
  • -

    Provides fast communication

    -
  • -
  • -

    Can send instant notifications to online participants

    -
  • -
  • -

    Provides an opportunity for immediate discussion

    -
  • -
-
-
-
Cons
-
    -
  • -

    Requires online access

    -
  • -
  • -

    Limits message length

    -
  • -
-
-

Jira or Bugzilla ticket

-
-
Pros
-
    -
  • -

    Supports collaboration and approval among multiple reviewers before any change is made

    -
  • -
  • -

    Sends comments to all followers of the ticket

    -
  • -
-
-
-
Cons
-
    -
  • -

    Difficulty editing submitted comments

    -
  • -
  • -

    Not easy to provide inline comments on the ticket

    -
  • -
  • -

    Unwanted notification emails when there are multiple followers

    -
  • -
  • -

    Tedious to discuss lengthy content on a ticket

    -
  • -
  • -

    Limited space to add comments

    -
  • -
-
-
-
-
-

Appendix B: Peer review resources

-
-
-

This section lists additional tools and resources available for peer reviewing documentation.

-
- - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 7. Validation tools
ResourceDescription

newdoc

A script for creating new files for a modular documentation repository. You can also use the script to validate whether a piece of content adheres to Red Hat documentation markup and structure standards.

Vale for Red Hat documentation writers

A linting system that validates whether your text is compatible with Red Hat writing style.

IBM Equal Access Accessibility Checker

A toolkit of instructions and browser extensions to generate automated accessibility reports.

htmltest

A test that validates whether the links in your HTML work.

Grammarly

A browser plug-in that checks your English spelling and grammar, but also helps improve your writing style.

- - ---- - - - - - - - - - - - - - - - - - - - - -
Table 8. Style resources
ResourceDescription

IBM Style Guide

The governing guide for IBM writing style, which most Red Hat documentation follows.

Red Hat supplementary style guide for product documentation

A guide for writing documentation the Red Hat way, including style guidelines, formatting, and a glossary of terms and conventions. -Complementary to the IBM Style Guide.

The Wisdom of Crowds slides

A slide deck on Red Hat community outreach, including the principles of minimalism in writing documentation.

- - ---- - - - - - - - - - - - - -
Table 9. Markup resources
ResourceDescription

AsciiDoc Mark-up Quick Reference for Red Hat Documentation

Guidelines on using the AsciiDoc markup language in Red Hat documentation projects.

- - ---- - - - - - - - - - - - - - - - - - - - - -
Table 10. Structure resources
ResourceDescription

Modular Documentation Reference Guide

Instructions for creating Red Hat documentation in a modular way, with templates and examples.

How Chunking Helps Content Processing

Tips for structuring your docs content in a visually comprehensible way.

Starting a modular documentation Project in Antora

How to use the Antora toolchain to create a community documentation project.

- - ---- - - - - - - - - - - - - - - - - -
Table 11. Methodology resources
ResourceDescription

Red Hat Community Collaboration Guide

Tips and best practices for Red Hat and the upstream community joining forces on documentation projects.

How to edit other people’s content without pissing them off

Ingrid Towey’s talk on conducting peer reviews that inform and inspire but do not infuriate.

-
-
-
- - - - - - - - - - - \ No newline at end of file +# Red Hat peer review guide for technical documentation + +## Table of Contents + +- [Introduction](#introduction) + - [About this guide](#about-this-guide) + - [Purpose of peer reviews](#purpose-of-peer-reviews) +- [Peer review checklists](#peer-review-checklists) + - [Language](#language) + - [Style](#style) + - [Minimalism](#minimalism) + - [Structure](#structure) + - [Usability](#usability) +- [Providing peer review feedback](#providing-peer-review-feedback) +- [Creating a peer review process](#creating-a-peer-review-process) + - [Considerations when creating a peer review process](#considerations-when-creating-a-peer-review-process) + - [Finalizing your team's peer review process](#finalizing-your-teams-peer-review-process) + - [Example peer review process 1](#example-peer-review-process-1) + - [Example peer review process 2](#example-peer-review-process-2) +- [Appendix A: Pros and cons of the different peer review platforms](#appendix-a-pros-and-cons-of-the-different-peer-review-platforms) +- [Appendix B: Peer review resources](#appendix-b-peer-review-resources) + +## Introduction + +### About this guide + +This guide provides information about best practices for peer reviewing Red Hat technical documentation. + +The Red Hat Customer Content Services (CCS) team created this guide for customer-facing documentation, but upstream communities that want to align more closely with the standards used by Red Hat documentation can also use this guide. + +### Purpose of peer reviews + +It is recommended to perform a peer review on all updates to Red Hat documentation. Peer review provides the following benefits: + +- Ensuring higher quality content, which helps our users +- Giving writers and reviewers a chance to see more content, find new ways to approach changes, and share expertise + +For peer reviews to achieve these goals, reviewers should present their comments positively and avoid negative wording. At the same time, writers must be open to reviewers' feedback. Peer reviews can catch issues that writers might miss. + +## Peer review checklists + +Writers and peer reviewers can use the peer review checklists as a quick reference to the Red Hat technical documentation style guidelines. Use the checklists to help structure your peer reviews, and adapt the checklists to meet the needs of your team. + +For guidance on each topic outlined in the checklists, see the following resources: + +- IBM Style +- [Red Hat supplementary style guide for product documentation](https://redhat-documentation.github.io/supplementary-style-guide/) +- [Merriam-Webster Dictionary](https://www.merriam-webster.com/) + +### Language + +**Table 1. Language checklist** + +| Check for | Checked | +|-----------|---------| +| **Spelling errors and typos**
- American English spelling is used consistently in the text.
- Correct punctuation is used in the text. | ☐ | +| **Grammar**
- American English grammar is used consistently in the text.
- Slang or non-English words are not used in the text. | ☐ | +| **Correct word usage and entity naming**
- Precise wording is used. Words are used in accordance with their dictionary definitions.
- The writer has also considered the context of the words, so that the meaning, tone, and implications are appropriate.
- Named entities are classified on first use.
- Contractions are avoided, unless they are used intentionally for conversational style, such as in quick starts.
- Proper nouns are capitalized.
- Conscious language guidelines are followed. The terms *blacklist*, *whitelist*, *master*, and *slave* are used only when absolutely necessary. | ☐ | +| **Correct use of acronyms and abbreviations**
- Acronyms are expanded on first use.
- Abbreviations are used and applied correctly. | ☐ | +| **Terms and constructions**
- Phrasal verbs are avoided.
- Use of problematic terms such as *should* or *may* are avoided.
- Use of anthropomorphism is avoided. | ☐ | + +### Style + +**Table 2. Style checklist** + +| Check for | Checked | +|-----------|---------| +| **Passive voice**
- Unnecessary use of passive voice is avoided. | ☐ | +| **Tense**
- Future tense is used only when necessary. | ☐ | +| **Titles**
- Titles use sentence case.
- Titles and headings have consistent styling.
- Titles are effective and descriptive.
- Titles focus on customer tasks instead of the product.
- Titles are 3-11 words long and have 50-80 characters.
- Titles of procedure modules begin with a gerund, for example, "Configuring", "Using", or "Installing". | ☐ | +| **Number**
- Number conventions are followed. | ☐ | +| **Formatting**
- Content follows style and consistency guidelines for formatting, for example, user-replaceable values.
- Content uses correct AsciiDoc markup. | ☐ | + +### Minimalism + +**Table 3. Minimalism checklist** + +| Check for | Checked | +|-----------|---------| +| **Customer focus and action orientation**
- Content focuses on actions and customer tasks. | ☐ | +| **Scannability/Findability**
- Content is easy to scan.
- Information is easy to find.
- Content uses bulleted lists and tables to make information easier to digest. | ☐ | +| **Sentences**
- Sentences are not unnecessarily long and only use the required number of words. Ensure that any long sentences cannot be shortened.
- Sentences are concise and informative. | ☐ | +| **Conciseness (no fluff)**
- The text does not include unnecessary information.
- Admonitions are used only when necessary.
- Screenshots and diagrams are used only when necessary.
- Content is clear, concise, precise, and unambiguous. | ☐ | + +### Structure + +**Table 4. Structure checklist** + +| Check for | Checked | +|-----------|---------| +| **Structure meets modular guidelines**
- Module types are not mixed, for example, concept and procedure information is separate.
- Module types are used correctly.
- Tags and entities are used correctly.
- Modules are as self-contained as possible to facilitate reuse in other locations. | ☐ | +| **A logical flow of information**
- Information is provided at the right pace.
- Information is presented in the most logical order and location.
- Cross-references are used appropriately and only when useful. | ☐ | +| **User stories**
- The user goal is clear.
- Tasks reflect the intended goal of the user.
- Troubleshooting and error recognition steps are included where appropriate. | ☐ | + +### Usability + +**Table 5. Usability checklist** + +| Check for | Checked | +|-----------|---------| +| **Content**
- The content is appropriate for the intended audience. | ☐ | +| **Accessibility**
- Tables and diagrams have alternative (alt) text and are clearly labeled and explained in surrounding text. | ☐ | +| **Links**
- Use of inline links is minimized.
- All the links in the document work.
- All links are current. | ☐ | +| **Visual continuity**
- The content renders correctly in preview, including correct spacing, bulleted lists, and numbering.
- Product versioning and release dates are accurate. | ☐ | + +## Providing peer review feedback + +Peer reviews must be kind, helpful, and consistent among peer reviewers. + +- **Support your comments.** + - Use documented resources, such as style guides or Red Hat writing conventions. + - Explain the impact of the issue on the audience. + - If you cannot find documented support, rethink the need for the comment. + +- **Use a respectful tone.** + - Pose comments as questions when you are unsure. + - Choose your wording carefully and do not be harsh. Be concise for easy content updates. If you have a suggestion, ask the writer to "consider" your comment or state that you "suggest" something. + +- **Stay within scope**. Review only the new content, changed content, and content that provides necessary context. + - Review content that was changed in the pull request (PR) or merge request (MR). + - Review the preexisting section to ensure that the new or updated content fits. + - Do not request enhancements to the content unless the content is unclear without it. + - If you notice an issue in related content that you are not explicitly reviewing, use friendly wording to suggest changes. Some examples of appropriate language include: + - "I know this was existing content, but would you mind fixing this typo while you're in there?" + - "I know this is out of scope for this PR, but consider looking into this in a future update." + + The writer might either address the issue now, track it as a future request, or let the peer reviewer know that they cannot apply the change. + - For more information about scope, see [Scope examples](#scope-examples). + +- **Understand that peer reviewers do not review for technical accuracy.** + - Subject matter experts (SMEs) and quality engineering (QE) associates are responsible for testing and technical accuracy. + - Peer reviewers check for issues like usability problems, style guide compliance, and unclear or missing steps in a procedure. + - Peer reviewers do not need to understand all the technical details. The audience might be users who are already familiar with the technology. Request additional technical information as a followup and not as a requirement for the current PR or MR. + - Some peer reviewers might be more familiar with a particular subject or know that an update can affect another area of the documentation. In these cases, provide this feedback to the writer. + - If you are certain that information is wrong or that a command will fail, ask the contributor to check with their SME or QE. Avoid tagging their SMEs or QEs directly to ask. + +- **Recognize that writers do not have to accept all your suggestions.** + - Writers must implement mandatory peer review feedback that relates to style guides or typographical errors, but they can implement optional feedback at their discretion. If the issue does not break any rules or is not an actual typographical error or issue, let writers keep it as it is. + - If you are merging a PR or MR and feel strongly that the writer must make a change but they disagree, speak to the writer in private. Cite style guides or vetted documentation so that they know your reasoning. Listen to their perspective. If the topic of the disagreement is not in any of the guides, consider bringing it to the team for discussion. In some cases, the guidelines might need to be updated. + +- **Differentiate between required and optional changes.** + - Required changes must be fixed before the writer can merge the PR or MR. Support your change with a reference to the relevant style guide or principle. Examples include modular docs template adherence, typographical error fixes, or product-specific guidelines. + - Optional changes do not have to be addressed before the writer can merge the PR or MR. Use softer language, for example, "Here, it might be clearer to…" or use a [SUGGESTION] tag to clearly indicate it to the writer. Examples: wording improvements, content relocation, and stylistic preference. + - For more information about required versus suggested changes, see [Scope examples](#scope-examples). + +- **Add your own suggestions for improvements** for a problematic area. Do not provide vague or generic comments, such as "this doesn't make sense." + - Offer rewrites as suggestions, not something that the writer has to take word-for-word. For example, "I don't understand this description. Did you mean…?" + - Avoid rewriting entire paragraphs of the writer's content. If you find yourself doing this because multiple items in a paragraph need attention, break out your suggestions. If providing an alternative paragraph wording is necessary, ensure that you make it clear that the writer does not need to use your suggestion exactly as written. + - If you notice a recurring issue, leave a global comment for the writer so that they know to address every instance of the issue. For example, "[GLOBAL] This typo occurs in other locations within the doc. I won't comment on the other examples after this point, but please address all instances." + +- **Provide positive feedback as well as negative** + - If during your review you find a portion of content that you think is exceptionally well done, point that out in your feedback. For example, "This part is pretty much perfect, nicely done!" + - This reinforces good writing habits and also makes getting reviews less daunting. + +- **If the review requires a significant amount of editing or rework, pause the review and contact the writer directly to discuss.** + - This avoids overwhelming the writer with too many comments and saves the peer reviewer's time. + - If the content is not ready for peer review, tell the writer and continue after it is ready. + - Examples of when to pause a review include if the build is broken, if the content is not rendering properly, or if the content is not modularized correctly. + - Contact the writer privately, for example, by chat, to express your concerns and provide advice on how to move forward. + - Decide whether you have the time to work with the writer or if you need to request that they contact someone else, for example, an onboarding buddy or a senior writer. + +- **Notify the writer when the review is complete.** + - After you finish the review, notify the writer that the review is complete, so that they can start reviewing and implementing your feedback. + +### Scope examples + +Some suggested changes might improve the content but are not relevant or in the scope of the updates. The following table includes examples of changes that are in scope and required, in scope but suggested, and out of scope. + +**Table 6. Examples of in scope and out of scope feedback** + +| In scope - required | In scope - suggested | Out of scope | +|---------------------|----------------------|--------------| +| Typographical errors, grammatical issues, formatting issues | Rearranging:
- Moving something to the prerequisites section
- Moving verification steps out of the ".Procedure" and into a specific ".Verification" section in the procedure module, if applicable | Comments on content that was not changed in the PR or MR | +| [Modular docs guidelines](https://redhat-documentation.github.io/modular-docs/), for example:
- Adhering to the templates
- Correct anchor ID format | Reviewing wording that does not sound right to the reviewer to see if it can be improved | Requesting additional details, like default values or units | +| IBM Style Guide and [CCS supplementary style guide](https://redhat-documentation.github.io/supplementary-style-guide/) guidelines, for example:
- "may" to "might"
- "Click the **Save** button." to "Click **Save**." | Avoiding sequences of admonitions, for example, a [NOTE] followed by an [IMPORTANT] block, especially if they are the same type of admonition | Technical accuracy, unless you know for certain something is wrong or that a command will fail | +| Product-specific guidelines, for example:
- Prompts on terminal commands
- Separating commands into individual code blocks
- Sentence case in titles | | | + +## Creating a peer review process + +Red Hat Customer Content Services (CCS) does not follow one definitive peer review process. Each team within CCS is different, with unique workflows, preferred tools, release cycles, and engineering team preferences that are customized to meet their product and customer requirements. Each team determines a peer review process that works for them. + +Define a process so that peer reviews are used consistently throughout your team. + +### Considerations when creating a peer review process + +Before you establish a peer review process that works for your team, review the following factors: + +- [Is a peer review required or optional?](#is-a-peer-review-required-or-optional) +- [Who are the peer reviewers?](#who-are-the-peer-reviewers) +- [How does a writer request a peer review?](#how-does-a-writer-request-a-peer-review) +- [How is the peer reviewer assigned?](#how-is-the-peer-reviewer-assigned) +- [What is the level or scope of the peer review?](#what-is-the-level-or-scope-of-the-peer-review) +- [Is there a checklist for the peer reviewer to follow?](#is-there-a-checklist-for-the-peer-reviewer-to-follow) +- [What platform and tools are used to perform the review and give feedback?](#what-platform-and-tools-are-used-to-perform-the-review-and-give-feedback) +- [What is the expected turnaround time?](#what-is-the-expected-turnaround-time) +- [How are urgent peer reviews escalated?](#how-are-urgent-peer-reviews-escalated) +- [How is peer review feedback incorporated?](#how-is-peer-review-feedback-incorporated) + +#### Is a peer review required or optional? + +A technical writing manager, documentation program manager (DPM), or content strategist (CS) determines whether requesting a peer review is required or optional and communicates this expectation to the team. + +**Example options** + +- Require a peer review on each GitHub PR (or GitLab MR) prior to accepting the request. +- Require a peer review in certain, defined situations. +- Request a peer review at the writer's discretion. + +#### Who are the peer reviewers? + +Determine who conducts a peer review. A manager, DPM, or CS communicates this expectation to the team. + +**Example options** + +- Individuals can volunteer as peer reviewers. +- Everyone on the team is expected to be available to review at any time. +- Everyone participates in peer reviews and rotates being available or follows a roster. + +#### How does a writer request a peer review? + +Determine how writers request a peer review. + +**Example options** + +- Add the request details to a tracking spreadsheet. +- Communicate with a reviewer in a Google Chat or a Slack channel. +- Request a review through email. +- Use GitHub or GitLab labels to mark when content is ready for review. +- Open a Jira ticket or Bugzilla ticket with the request. +- Contact a reviewer in the original documentation ticket. + +#### How is the peer reviewer assigned? + +Some assignment methods might work better if the reviewers are on the same product team; others might work better for cross-product reviews. Establish a method that suits the structure and dynamic of the group of writers and reviewers that the process targets. + +Writers must ensure that reviewers can access the tools needed to complete the review. + +**Example options** + +- Reviewers check a tracking spreadsheet and assign themselves. +- Reviewers are notified for all peer review requests and assign themselves. +- Reviewers regularly check a GitHub PR or a GitLab MR queue and assign themselves. +- A writer contacts the reviewer. + +#### What is the level or scope of the peer review? + +Determine the level or scope of the peer review, so that the writer and reviewer have the same expectations. + +> **Note:** The writer is responsible for informing the peer reviewer of any essential information related to the content. + +**Example options** + +- Perform a general review that checks for typographical errors, style guide compliance, and link checking. +- Perform a deeper review of the content that includes checks on typographical errors and grammar, content placement or flow, structure, style guide compliance, and consistency. + +#### Is there a checklist for the peer reviewer to follow? + +Determine which checklists and other resources the reviewer should follow. + +> **Note:** The writer must inform the peer reviewer of any essential information related to the content. + +**Example options** + +- Follow the CCS peer review checklist. +- Follow the CCS peer review checklist and a team-specific checklist. + +#### What platform and tools are used to perform the review and give feedback? + +Determine how to share content and provide feedback. + +**Example options** + +- Draft content in a Google Doc and use the document for comments and suggestions. +- Share a GitHub PR or GitLab MR. Reviewers can comment directly inline for each change. +- Provide small snippets of content by email, instant messaging, or a ticket. + +For more information, see [Appendix A: Pros and cons of the different peer review platforms](#appendix-a-pros-and-cons-of-the-different-peer-review-platforms). + +#### What is the expected turnaround time? + +Determine the expected turnaround time for completing a peer review. Writers should communicate if there is any urgency or deadlines for the review. + +**Example options** + +- Reviewers check the GitHub or GitLab queue daily or twice daily. +- Reviewers respond to a Slack or a Google Chat ping within a few hours. +- Reviewers check a tracking spreadsheet daily. +- Writers communicate the requested turnaround time after requesting the peer review. + +#### How are urgent peer reviews escalated? + +Determine how an unassigned peer review request is escalated if it can affect product release schedules. + +**Example options** + +- Inform a manager, DPM, or CS of the unassigned time-critical peer review so that they can escalate the peer review request or negotiate a new timeline for reviewing the content. +- Use your peer review request channel to request an urgent peer review. Ensure you detail the tight timelines in the channel. + +#### How is peer review feedback incorporated? + +Determine the expectations for addressing or incorporating feedback. Expectations become important if the writer and peer reviewer disagree on a review item. + +Incorporate an escalation process into your peer review process, such as communicating in a guidelines group or requesting manager, DPM, or CS input. This way, the writer and peer reviewer can resolve any disagreement. + +**Example options** + +- Incorporate feedback at the writer's discretion. +- Establish a communication channel for informing the peer reviewer of the next steps. +- Address peer review feedback and request the peer reviewer to perform a review of the revised content. + +### Finalizing your team's peer review process + +Writers and peer reviewers must agree on the expectations for the peer review process. + +Complete the following steps to finalize the peer review process: + +1. Draft a proposal for the peer review process. +2. Share the proposal with the team and set a time for the team to provide feedback. +3. Test the process to ensure that it works well for your team. +4. Document the final process wherever your team stores its resources. +5. Communicate the final process to the team and any other contributors or stakeholders. + +### Example peer review process 1 + +The first example peer review process demonstrates how a cross-product team uses Jira tickets for communication and GitLab to perform peer reviews. + +This team has a peer review squad of at least two members at any specific time. Membership of the squad rotates every week. The team maintains a peer review assignment roster in a Confluence page that lists the assigned reviewers for each week. The assignment roster is published in the Jira product dashboards, so that writers can see the assigned reviewers for the current week. + +![A flowchart that is a visual representation of the first example peer review process described in the following procedure](images/example_peer_review_process1_image.png) + +**Figure 1. Example 1 of a peer review process conducted through Jira and GitLab** + +**Prerequisites** + +- A subject matter expert (SME) has completed a technical review. + + To request and mark a technical review as complete, the writer performs the following tasks: + + a. Put a link to the MR in the **Git Pull Request** field in the Jira doc ticket. + b. Submit the MR for SME review. + c. Apply the SME reviewer's feedback. + d. Update the MR in GitLab. + +**Procedure** + +1. To request a peer review, the writer performs the following tasks: + + a. Check the peer review assignment roster in the Jira product dashboard. + b. Add a comment in the Jira doc ticket to contact the assigned reviewers. + + > **Note:** The writer needs to contact the reviewers who are currently on duty according to the roster. + + c. Add the assigned reviewers to the **Includes** field in the Jira doc ticket. + d. Optional: Contact the assigned reviewers in the MR or chat. + +2. If a peer review does not start within the expected timeframe and the review deadline is jeopardized, the writer performs the following task: + + - Contact the assigned reviewers again to communicate the urgency of the request. + + > **Note:** If the review deadline is not jeopardized, the writer does not need to take any action at this point. + +3. To complete a review, the peer reviewer performs the following tasks: + + a. Notify the other assigned reviewer that you will do the review. + b. Remove the other assigned reviewer from the **Includes** field in the Jira doc ticket. + c. Add review comments in the MR. + d. Notify the writer when you complete the review. + +4. To apply feedback and complete the process, the writer performs the following tasks: + + a. Apply the peer reviewer's feedback. + b. Update the MR in GitLab. + +### Example peer review process 2 + +The second example peer review process demonstrates how a team uses a Slack channel for communication and GitHub to perform peer reviews. The peer review team consists of five team members at a given time. Membership of the peer review team rotates every sprint. + +![A flowchart that is a visual representation of the second example peer review process described in the following procedure.](images/example_peer_review_process2_image.png) + +**Figure 2. Example 2 of a peer review process conducted through Slack and GitHub** + +**Procedure** + +1. To request a peer review, the writer performs the following tasks: + + a. Notify the peer review squad using the Slack channel. + b. Include a link to the PR in the Slack notification. + c. Specify any deadline or other special considerations in the Slack notification. + +2. If a peer review does not start within the expected timeframe and the review deadline is jeopardized, the writer performs the following task: + + - Contact the assigned reviewer or the peer review squad again to communicate the urgency of the request. + + > **Note:** If the review deadline is not jeopardized, the writer does not need to take any action at this point. + +3. To complete a review, the peer reviewer performs the following tasks: + + a. Mark the request in Slack to indicate that you will perform the review. + b. Add review comments in the PR. + c. Notify the writer when you complete the review. + +4. To apply feedback and complete the process, the writer performs the following tasks: + + a. Apply the peer reviewer's feedback. + b. Update the PR in GitHub. + +## Appendix A: Pros and cons of the different peer review platforms + +Review the following pros and cons for each platform to choose the right peer review method for your team. + +### GitHub or GitLab + +**Pros** + +- Provides a convenient method for commenting on specific lines of content on a GitHub PR or GitLab MR +- Includes functionality for easily adding additional reviewers +- Includes a mechanism for multiple people to collaborate on the same PR or MR +- Provides an easy linking functionality +- Offers the capability for writers to incorporate feedback before the PR or MR is approved + +**Cons** + +- Requires that you are familiar with the GitHub or GitLab UI +- Requires that you have login credentials to comment on a PR or MR + +### Google Docs + +**Pros** + +- Includes a convenient method for commenting on specific text +- Includes functionality for easily adding additional reviewers +- Includes a mechanism for multiple people to collaborate on the same Google Doc +- Provides an easy linking functionality +- Supports copying and pasting of AsciiDoc syntax + +**Cons** + +- Can produce unreliable formatting when copying and pasting HTML, PDF, or markup syntax content +- Can be time consuming to copy and paste AsciiDoc content + +### Email + +**Pros** + +- An easy tool for anyone to use +- A historical record of the discussion + +**Cons** + +- Can be difficult to link specific email comments to other communication channels +- Can be slow and time consuming +- Can be difficult to understand feedback if the content is not well structured + +### IRC, Google Chat, or Slack + +**Pros** + +- Provides fast communication +- Can send instant notifications to online participants +- Provides an opportunity for immediate discussion + +**Cons** + +- Requires online access +- Limits message length + +### Jira or Bugzilla ticket + +**Pros** + +- Supports collaboration and approval among multiple reviewers before any change is made +- Sends comments to all followers of the ticket + +**Cons** + +- Difficulty editing submitted comments +- Not easy to provide inline comments on the ticket +- Unwanted notification emails when there are multiple followers +- Tedious to discuss lengthy content on a ticket +- Limited space to add comments + +## Appendix B: Peer review resources + +This section lists additional tools and resources available for peer reviewing documentation. + +**Table 7. Validation tools** + +| Resource | Description | +|----------|-------------| +| [newdoc](https://github.com/redhat-documentation/newdoc) | A script for creating new files for a modular documentation repository. You can also use the script to [validate](https://github.com/redhat-documentation/newdoc#validating-a-file-for-red-hat-requirements) whether a piece of content adheres to Red Hat documentation markup and structure standards. | +| [Vale for Red Hat documentation writers](https://redhat-documentation.github.io/vale-at-red-hat/docs/main/user-guide/introduction/) | A linting system that validates whether your text is compatible with Red Hat writing style. | +| [IBM Equal Access Accessibility Checker](https://www.ibm.com/able/toolkit/verify/) | A toolkit of instructions and [browser extensions](https://www.ibm.com/able/toolkit/verify/automated) to generate automated accessibility reports. | +| [htmltest](https://github.com/wjdp/htmltest) | A test that validates whether the links in your HTML work. | +| [Grammarly](https://www.grammarly.com/) | A browser plug-in that checks your English spelling and grammar, but also helps improve your writing style. | + +**Table 8. Style resources** + +| Resource | Description | +|----------|-------------| +| IBM Style Guide | The governing guide for IBM writing style, which most Red Hat documentation follows. | +| [Red Hat supplementary style guide for product documentation](https://redhat-documentation.github.io/supplementary-style-guide/) | A guide for writing documentation the Red Hat way, including style guidelines, formatting, and a glossary of terms and conventions. Complementary to the IBM Style Guide. | +| [The Wisdom of Crowds slides](https://docs.google.com/presentation/d/1Yeql9FrRBgKU-QlRU-nblPJ9pfZKgoKcU8SW6SQ_UqI/edit#slide=id.g1f4790d380_2_176) | A slide deck on Red Hat community outreach, including the principles of minimalism in writing documentation. | + +**Table 9. Markup resources** + +| Resource | Description | +|----------|-------------| +| [AsciiDoc Mark-up Quick Reference for Red Hat Documentation](https://redhat-documentation.github.io/asciidoc-markup-conventions/) | Guidelines on using the AsciiDoc markup language in Red Hat documentation projects. | + +**Table 10. Structure resources** + +| Resource | Description | +|----------|-------------| +| [Modular Documentation Reference Guide](https://redhat-documentation.github.io/modular-docs/) | Instructions for creating Red Hat documentation in a modular way, with templates and examples. | +| [How Chunking Helps Content Processing](https://www.nngroup.com/articles/chunking/) | Tips for structuring your docs content in a visually comprehensible way. | +| [Starting a modular documentation Project in Antora](https://antora-for-modular-docs.github.io/antora-for-modular-docs/docs/user-guide/introduction/) | How to use the Antora toolchain to create a community documentation project. | + +**Table 11. Methodology resources** + +| Resource | Description | +|----------|-------------| +| [Red Hat Community Collaboration Guide](https://redhat-documentation.github.io/community-collaboration-guide/) | Tips and best practices for Red Hat and the upstream community joining forces on documentation projects. | +| [How to edit other people's content without pissing them off](https://www.youtube.com/watch?v=7iWUSetbaos) | Ingrid Towey's talk on conducting peer reviews that inform and inspire but do not infuriate. | + +--- + +*Last updated 2023-06-19 13:04:47 UTC* diff --git a/.claude/resources/red-hat-ssg-for-cqa.md b/.claude/resources/red-hat-ssg-for-cqa.md new file mode 100644 index 00000000000..e53a9106892 --- /dev/null +++ b/.claude/resources/red-hat-ssg-for-cqa.md @@ -0,0 +1,557 @@ +# Red Hat Supplementary Style Guide - CQA Extract + +**Purpose:** This file contains selected sections from the full Red Hat Supplementary Style Guide that are most relevant for CQA (Content Quality Assessment) documentation reviews. + +**Full version:** For the complete guide, see `.claude/resources/red-hat-ssg.md` + +**Contents:** +- Conscious language +- Contractions +- Conversational style +- Minimalism +- Users +- Product names and version references +- Single-step procedures +- Titles and headings +- User-replaced values +- Admonitions +- Lead-in sentences +- Prerequisites +- Short descriptions +- Developer Preview +- Technology Preview + +--- + +## Conscious language + +The Conscious Language Group supports the Red Hat commitment to remove problematic language from our code, documentation, websites, and open source projects with which Red Hat is involved. +For more information about the Conscious Language Group, see https://github.com/conscious-lang/conscious-lang-docs. + +> **IMPORTANT:** + +To ensure consistency and success, it is imperative for product team stakeholders to align internally. For example, documentation teams should engage in discussions with their engineering leadership to reach an agreement on replacement terms. This ensures that the product documentation matches the code. + + +### Blacklist and whitelist + +When possible, rewrite documentation to avoid these terms. +When it is not possible to remove the terms _blacklist_ and _whitelist_, replace them with one of the following alternatives: + +* Blocklist / allowlist: This combination is recommended by the _IBM Style_ guide. Use this combination unless your product area has another specific replacement that is agreed between engineering leadership and your documentation team. +* Denylist / allowlist +* Blocklist / passlist +* You can also use a term that has been agreed by your product team stakeholders. + +**Examples** + +* Removing blacklist + + ![no](images/no.png) Heat _blacklists_ any servers in the list from receiving updated heat deployments. After the stack operation completes, any blacklisted servers remain unchanged. You can also power off or stop the `os-collect-config` agents during the operation. + + ![yes](images/yes.png) Heat _excludes_ any servers in the list from receiving updated heat deployments. After the stack operation completes, any excluded servers remain unchanged. You can also power off or stop the `os-collect-config` agents during the operation. +* Removing whitelist + + ![no](images/no.png) The following steps demonstrate adding a new rule to _whitelist_ a custom binary. + + ![yes](images/yes.png) The following steps demonstrate adding a new rule to _allow_ a custom binary. + +### Master and slave + +When possible, rewrite documentation to avoid these terms. When it is not possible to rewrite, you can use the following alternatives for _master_ / _slave_: + +* Primary / secondary +* Source / replica +* Initiator, requester / responder +* Controller, host / device, worker, proxy +* Director / performer +* Controller / port interface (in networking) +* You can also use a term that has been agreed by your product team stakeholders. + +**Examples** + +* Removing _master_ + + ![no](images/no.png) A Ceph Monitor maintains the _master_ copy of the Red Hat Ceph Storage cluster map with the current state of the Red Hat Ceph Storage cluster. + + ![yes](images/yes.png) A Ceph Monitor maintains the _primary_ copy of the Red Hat Ceph Storage cluster map with the current state of the Red Hat Ceph Storage cluster. + + ![yes](images/yes.png) A Ceph Monitor maintains the _main_ copy of the Red Hat Ceph Storage cluster map with the current state of the Red Hat Ceph Storage cluster. +* Removing _slave_ + + ![no](images/no.png) Use the following command to copy the public key to the _slave_ node. + + ![yes](images/yes.png) Use the following command to copy the public key to the _secondary_ node. + +## Contractions + +Avoid contractions in product documentation to leave no ambiguity and to make it easier for translation and international audiences. + +If you are writing quick start or other content that uses a more informal [conversational style](#conversational-style) (_fairly conversational_ or _more conversational_), you may use contractions. In this case, follow the guidance in the _IBM Style_ guide on using contractions. + +## Conversational style + +Follow the _IBM Style_ guide advice of _less conversational_ style in most cases. + +Red Hat Enterprise Linux 8 delivers a stable, secure, and consistent foundation across hybrid cloud deployments with the tools needed to deliver workloads faster with less effort. + +As needed, adjust the conversational to _fairly conversational_ for an audience of new users or _least conversational_ for API documentation and other very experienced audiences. + +> **NOTE:** + +Documentation for cloud services follows the _IBM Style_ guide for _fairly conversational_ tone. When using _fairly conversational_ tone, use contractions where appropriate. + +## Minimalism +Minimalism is a methodology for creating targeted documentation focused on your readers' needs. If you understand your customers' needs, you can write shorter and simpler documentation specific to what customers want to do. + +Minimalism has five principles: + +### Principle 1: Customer focus and action orientation +Know what your users do, what their goals are, and why they perform these actions. Minimize how much content customers must wade through to get to something they recognize as real work. Separate conceptual and background information from procedural tasks. + +### Principle 2: Findability +Findability covers two areas: + +* Ensure your content is findable through Google search and access.redhat.com site searches. +* Ensure your content is scannable. Use short paragraphs and sentences and bulleted lists where appropriate. + +### Principle 3: Titles and headings +Use clear titles with familiar keywords for customers. Keep titles and headings between 3 to 11 words. Headings that are too short lack clarity and don't help customers know what's in a section. Headings that are too long are less visible in Google searches and harder for customers to understand. + +### Principle 4: Elimination of fluff +Avoid long introductions and unnecessary context. Shorten unnecessarily long sentences. + +### Principle 5: Error recovery, verification, and troubleshooting +Recognize that people make mistakes and need to verify that they have completed a task. Be sure to include troubleshooting, error recovery, and verification steps. + +## Users +In most cases, the word "user" refers to a person or a person's user account, and therefore would be considered animate. In these cases, use animate personal pronouns such as "who". + +In certain technical cases, these users are not persons but instead system accounts or more abstract concepts (inanimate). For example, Linux `root` and `guest` users do not relate to any person. Applications and services might run as specific Linux users with no person controlling them. SELinux users such as `user_u` or `sysadm_u` are identifiers of one or multiple Linux users for access control purposes. In these specific cases, refer to these inanimate users with inanimate personal pronouns such as "that". + +In these specific cases, and only if you cannot write around it, you can refer to these inanimate users with inanimate personal pronouns such as "that". + +**Examples** + +* Animate user + + ![no](images/no.png) Experienced _users that_ can configure their own systems... + + ![yes](images/yes.png) _Users who_ want to install their own packages... +* Inanimate user + + ![no](images/no.png) A Linux user has the restrictions of the _SELinux user who_ it is assigned to. + + ![no](images/no.png) A Linux user has the restrictions of the _SELinux user_ to _whom_ it is assigned. + + ![yes](images/yes.png) Specify a _user that_ is allowed to perform the requested action. + + ![yes](images/yes.png) A Linux user has the restrictions of the _SELinux user that_ it is assigned to. + +## Product names and version references + +Use attributes instead of hard-coded references when you refer to the name of your product in full, to its abbreviated form, or to its major or minor version. +Only use hard-coded version references if the version that you are referring to in a particular case never changes. + +### Attribute file + +Define attributes for product name and product version and store them in a dedicated attributes file for each set of product documentation. +For examples of where you can store the shared attributes file inside your documentation repository, see the [Example modular documentation repository](https://github.com/redhat-documentation/modular-docs/blob/mod-doc-repo-example/_artifacts/document-attributes.adoc). +Include the attributes file at the beginning of the `master.adoc` files of all titles in your documentation set: + +**Example AsciiDoc: Attribute file included in a master.adoc file** + +``` +include::____/attributes.adoc[] +``` + +### Minimum required attributes + +Define attributes for the following values in each documentation set. +Note that the attribute names used in this section are only meant as examples. +You can use different attribute names: + +* **The name of the product**\ +Use the product name attribute for all instances of the product name where possible. +Avoid using hard-coded product names. +For example: + + **Example AsciiDoc: Product name attribute** + + ``` + :name-product: Red Hat JBoss Enterprise Application Platform + ``` +* **The abbreviated form of the product name**\ +If it is necessary for your product, you can use an attribute to store a shortened version of the name of your product, for example: + + **Example AsciiDoc: Abbreviated product name attribute** + + ``` + :name-product-abbreviated: JBoss EAP + ``` +* **The major and minor version of the product**\ +Use an attribute for the product version in cases where the product version can change with each release and the content is still correct. +For example: + + **Example AsciiDoc: Product version attributes** + + ``` + :version-product-minor: 1.11 + :version-product-patch: 1.11.6 + ``` + + > **NOTE:** + + Do not use the product version attribute if the version should not change. + For example, if a feature was introduced in a certain version, the version should be hard-coded. + + +You might create additional attributes according to what your documentation requires. +For example, you might combine existing product name attributes to create compound names of products or components: + +**Example attributes for compound names of product components** + +``` +:name-runtime-spring-boot: Spring Boot +:name-runtime-vertx: Eclipse Vert.x +:name-spring-reactive: {name-runtime-spring-boot} with {name-runtime-vertx} reactive components +``` + +## Single-step procedures + +When a procedure contains only one step, use an unnumbered bullet. + +For example: +* Install the `dnf-automatic` package. + +## Titles and headings + +Write all titles and headings, including the titles of product documentation guides and Knowledgebase articles, in sentence-style capitalization. Do not use headline-style capitalization. + +**Examples** + +* _Composing a customized RHEL system image_ +* _Configuring the node port service range_ +* _How to perform an unsupported conversion from a RHEL-derived Linux distribution to RHEL_ + +## User-replaced values + +A _user-replaced value_, also known as a replaceable or variable value, is a placeholder that the user replaces with a value that is relevant for their situation. User-replaced values are often found in places such as code blocks, file paths, and commands. + +Use descriptive names for user-replaced values and follow this general format: _<value_name>_. + +> **NOTE:** + +For XML code blocks, see the guidance on [user-replaced values for XML](#user-replaced-values-for-xml). + + +Ensure that user-replaced values have the following characteristics: + +* Surrounded by angle brackets (`< >`) +* Separated by underscores (`_`) for multi-word values +* Lowercase, unless the rest of the related text is uppercase or another capitalization scheme +* Italicized +* If the user-replaced value is referencing a value in code or in a command that is normally monospace, also use monospace for the user-replaced value +* If you want to use a user-replaced value in example output, format the replaceable value with italics and in angle brackets. Alternatively, if you choose to use an example value instead, do not italicize the example value and do not place it in angle brackets. + +``` +Create an Ansible inventory file that is named `/__/inventory/hosts`. +``` + +This example renders as follows in HTML: + +Create an Ansible inventory file that is named `/__/inventory/hosts`. + +To italicize a user-replaced value in a code block, you must add an attribute to apply text formatting, such as `subs="+quotes"` or `subs="normal"`, to the attribute list of the code block. + + [subs="+quotes"] + ---- + $ *oc describe node ____* + ---- + +This example renders as follows in HTML: + +``` +$ *oc describe node ____* +``` + + [subs="+quotes"] + ---- + connection.id: ____ + connection.uuid: b6cdfa1c-e4ad-46e5-af8b-a75f06b79f76 + connection.type: 802-3-ethernet + connection.interface-name: enp7s0 + ---- + +This example renders as follows in HTML: + +``` +connection.id: ____ +connection.uuid: b6cdfa1c-e4ad-46e5-af8b-a75f06b79f76 +connection.type: 802-3-ethernet +connection.interface-name: enp7s0 +``` + +To explain user-replaced values used in a code block, you must use a definition list following the code block. See [Explanation of commands and variables used in code blocks](#explanation-of-commands-and-variables-used-in-code-blocks) for details. + +### User-replaced values for XML + +Because XML uses angle brackets (`< >`), the [default guidance](#user-replaced-values) for user-replaced values does not work well for it. If you are using user-replaced values in an XML code block, use the following format: _${value_name}_. + +Ensure that user-replaced values in XML have the following characteristics: + +* Surrounded by curly braces and preceded by a dollar sign (`${ }`) +* Separated by underscores (`_`) for multi-word values +* Lowercase, unless the rest of the related text is uppercase or another capitalization scheme +* Italicized +* If the user-replaced value is referencing a value in code or in a command that is normally monospace, also use monospace for the user-replaced value + + [source,xml,subs="+quotes"] + ---- + __${ip_address}__ + ---- + +This example renders as follows in HTML: + +```xml +__${ip_address}__ +``` + + [source,xml,subs="+quotes"] + ---- + + ---- + +This example renders as follows in HTML: + +```xml + +``` + +To explain user-replaced values used in a code block, you must use a definition list following the code block. See [Explanation of commands and variables used in code blocks](#explanation-of-commands-and-variables-used-in-code-blocks) for details. + +## Admonitions + +Admonitions should draw the reader's attention to certain information. Keep admonitions to a minimum, and avoid placing multiple admonitions close to one another. If multiple admonitions are necessary, restructure the information by moving the less-important statements into the flow of the main content. + +Valid admonition types: + +* **NOTE**\ +Additional guidance or advice that improves product configuration, performance, or supportability. +* **IMPORTANT**\ +Advisory information essential to the completion of a task. Users must not disregard this information. +* **WARNING**\ +Information about potential system damage, data loss, or a support-related issue if the user disregards this admonition. Explain the problem, cause, and offer a solution that works. If available, offer information to avoid the problem in the future or state where to find more information. +* **TIP**\ +Alternative methods that might not be obvious. Makes applying the techniques and procedures described in the text easier or targets specific needs. Helps users understand the benefits and capabilities of the product. Not essential to using the product. + +> **IMPORTANT:** + +CAUTION, which is another type of AsciiDoc admonition, is not fully supported by the Red Hat Customer Portal. Do not use this admonition type. + + +Admonitions should be short and concise. Do not include procedures in an admonition. + +Only individual admonitions are allowed, for example, you cannot have a plural **NOTES** heading. + +**Example AsciiDoc** + +``` +[NOTE] +==== +Text for note. +==== +``` + +## Lead-in sentences + +A lead-in sentence in this context is the text that directly follows a `Prerequisites` or `Procedure` heading in a task-based module. It is distinct from the module abstract, which describes the goals of the user for the module. + +Do not use a lead-in sentence in the `Prerequisites` or `Procedure` sections of a module unless it is necessary to aid navigation or add clarity. + +The following examples demonstrate when a lead-in sentence might add value. + +* Your module has a long list of prerequisites, and you want to group the prerequisites in sections to make it easier for users to understand what tasks must be performed to complete a procedure. +* Your module has a complex procedure or set of prerequisites, and you want to emphasize that all steps or prerequisites must be completed. + +Use a complete sentence for the lead-in sentence to reduce ambiguity and support translation. + +## Prerequisites + +When writing prerequisites, be as clear and concise as possible. You can use the passive voice, _if necessary_, to achieve that end. + +Write prerequisites as checks that are true or that the user must have completed before they begin a procedure. They can be actions that the user, another person, or piece of technology has completed. Prerequisites can also include items that the user must have ready before beginning the procedure. + +* The passive voice might be appropriate for a prerequisite that is not completed by the current user. For example, having a configuration enabled by a system admin. +* Avoid using imperative formations. +* Use parallel language when you write prerequisites. For example, if one bullet is a complete sentence, write the other bullets as complete sentences. But one bullet can be passive voice and another active voice. + +* JDK 11 or later is installed. + + Passive voice: the agent is unknown or unimportant. +* A running Kafka instance in {product}. + + Not a complete sentence: This prerequisite is acceptable if all the other prerequisites in your list are also not complete sentences. +* You are logged in to the Administration Portal. +* You have validated Thing 1. + +* [_Procedure Prerequisites_ in the _Modular Documentation Reference Guide_](https://redhat-documentation.github.io/modular-docs/#creating-procedure-modules) + +## Short descriptions + +Every module and assembly must include a _short description_, formerly called an _abstract_. A short description provides a high-quality summary for both readers and AI-powered search tools. + +* Short descriptions ***must*** be at least 50 characters and no more than 300 characters long. +* Place any information that exceeds 300 characters in a new paragraph or paragraphs. It cannot be part of the short description. + +The short description must have the correct formatting and tagging: + +* In AsciiDoc, label the short description with `[role="_abstract"]`. +* In DITA, tag the short description with ``. + +> **IMPORTANT:** + +Do not start a module or assembly with an admonition, even when adding the Technology Preview admonition. Always provide a short description first. + + +### Placement of the short description in procedures + +In procedures, the short description is displayed between the module title and the prerequisites section. Put additional information in a new paragraph or paragraphs. + +In AsciiDoc, additional information is displayed ***before*** the prerequisites. The following image illustrates how to place this information: + +![Place additional information before prerequisites](images/Structure-of-an-AsciiDoc-procedure.png) + +In DITA, include the additional information ***after*** the prerequisites, as the following image illustrates. Be careful that this added text does not rely on the short description for context: + +![Place additional information after prerequisites](images/Structure-of-a-DITA-procedure.png) + +### Core principles for writing a helpful short description + +Short descriptions help readers find the information that they need and confirm that they are in the right place. The following principles ensure that you are writing the best short description that you can: + +* Include user intent. Explain **what** the user must do and **why** they must complete that action. Build upon the title--do not repeat it. +* Write for AI and search. High-quality short descriptions are a primary source of metadata for large language models (LLMs) and search engine link previews. A high quality, human-verified summary reduces the risk of AI misinterpretation and saves processing time. +* Do not use DITA-incompatible structures, such as bulleted lists or multiple paragraphs. + +### Style guidelines + +Be sure to follow Red Hat style guidelines. Pay particular attention to the following rules, because short descriptions frequently violate these standards: + +* Use active voice and present tense. Write in plain English using simple, direct sentences. +* Use customer-centric language. Use phrases like "You can... by..." or "To..., configure...". +* Do not use self-referential language, for example, "This topic covers..." or "Use this procedure to...". +* Do not use feature-focused language. Focus on what users can accomplish rather than what the product does. Do not use "This product allows you to...". +* Make modules findable and reusable. Include the product name in either the title or the short description to make the module reusable. + +### Short descriptions for complex procedures + +If you are documenting two or more ways of completing the same procedure, use the short description to explain why users would want to choose one or the other. For complex procedures that have multiple sub-procedures, include some of the key tasks that a customer must complete. + +### Example: Assembly + +The original short description for this assembly example is self-referential and does not explain the **why**, although it does explain some of the **what**. The rewrite fixes both issues. + +``` +*Original:* Use one the following procedures to configure Satellite for the method that you have selected to deploy compliance policies. You will select one of these methods when you later create a compliance policy. +``` + +``` +*Rewrite:* To choose the appropriate method for your infrastructure [*why*], review the compliance policy deployment options in Red Hat Satellite [*what*]. Understanding the Ansible method, Puppet method, and manual method helps you plan your deployment effectively [*why*]. +``` + +### Example: Procedure (Task) topic + +The original short description in this example is self-referential and does not contain much information. The rewrite leads with the benefit and explains what you can do after performing the task. + +``` +*Original:* Use this procedure to create an organization. To use the CLI instead of the Satellite web UI, see the CLI procedure. +``` + +``` +*Rewrite:* Create organizations to divide resources among multiple teams [*why*]. Assign content and subscriptions to each organization or team, based on ownership, purpose, or security level [*what*]. +``` + +### Example: Concept topic + +A short description for a concept module briefly explains the **what** or **why** of a concept and helps readers decide if the topic is relevant to them. The following example does this by explaining the benefit of each migration method. + +``` +You can minimize virtual machine downtime by choosing an appropriate migration path for your workload. Warm migration runs in the background to keep applications active, but cold migration requires a full shutdown and is safer. Both methods provide similar transfer speeds. +``` + +### Example: Reference topic + +A short description for a reference topic should provide a brief direct answer to the question, "What is this?" The following example does this by describing the repository contents, which are listed in a table below the short description. + +## Developer Preview + +Developer Preview software provides early access to a technology, component, or feature in advance of its possible inclusion in a Red Hat product offering. Customers can use Developer Preview software to test functionality and provide feedback during the development process. Documentation is not required for Developer Preview software, but if documentation is provided, it is subject to change or removal at any time. Also, testing is limited for Developer Preview software. Red Hat might provide ways to submit feedback on Developer Preview software without an associated SLA. + +> **WARNING:** + +Some products, such as Red Hat Openshift Container Platform, do not include Developer Preview content in the documentation. Check with your Content Strategist or Support contact to confirm whether you can publish Developer Preview documentation for your product. + + +When documenting a Developer Preview software, follow these guidelines: + +* Add an admonition labeled ***IMPORTANT*** at the beginning of the Developer Preview content and include the template text. +* Use initial uppercase capitalization, that is, Developer Preview. +* Never use the phrase "supported as a Developer Preview", and avoid using "support" in Developer Preview descriptions. Instead, use neutral words like "available", "provide", "capability", and so on. +* When the Developer Preview software becomes generally available, remove the IMPORTANT admonition from any document that includes content about the feature. + + > **NOTE:** + + You might need to replace the Developer Preview admonition with a Technology Preview admonition. For more information, see [Technology Preview](#technology-preview). + + +Use the following template. Replace _<software_name>_ with the software name: + +**Example AsciiDoc: Developer Preview admonition template** + +```text +[IMPORTANT] +==== +__ is Developer Preview software only. Developer Preview software is not supported by Red Hat in any way and is not functionally complete or production-ready. Do not use Developer Preview software for production or business-critical workloads. Developer Preview software provides early access to upcoming product software in advance of its possible inclusion in a Red Hat product offering. Customers can use this software to test functionality and provide feedback during the development process. This software might not have any documentation, is subject to change or removal at any time, and has received limited testing. Red Hat might provide ways to submit feedback on Developer Preview software without an associated SLA. + +For more information about the support scope of Red Hat Developer Preview software, see link:https://access.redhat.com/support/offerings/devpreview/[Developer Preview Support Scope]. +==== +``` + +* NUMA-aware scheduling +* Node Health Check Operator +* CSI inline ephemeral volumes + +For more information about the support scope of Red Hat Developer Preview features, see [Developer Preview Support Scope](https://access.redhat.com/support/offerings/devpreview/). For a comparison of Developer Preview and Technology Preview features, see [Developer and Technology Previews: How they compare](https://access.redhat.com/articles/6966848). + +## Technology Preview + +Technology Preview features provide early access to upcoming product innovations, enabling customers to test functionality and provide feedback during the development process. However, these features are not fully supported. Documentation for a Technology Preview feature might be incomplete or include only basic installation and configuration information. + +When documenting a Technology Preview feature, follow these guidelines: + +* Add an admonition labeled IMPORTANT at the beginning of the Technology Preview content and include the template text. +* Use initial uppercase capitalization, that is, Technology Preview. +* Include a brief description of the Technology Preview feature in the release notes. +* Maintain a list of features that are currently in Technology Preview status in the release notes. +* Never use the phrase "supported as a Technology Preview", and avoid using "support" in Technology Preview descriptions. Instead, use neutral words like "available", "provide", "capability", and so on. +* When the Technology Preview feature becomes generally available, remove the IMPORTANT admonition from the release notes and any other document that includes content about the feature. + +Use the following template text verbatim, where _<feature_name>_ is your feature name. If you are not referring to a specific feature, you can omit the first sentence of the template text: + +**Example AsciiDoc: Technology Preview admonition template** + +```text +[IMPORTANT] +==== +__ is a Technology Preview feature only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs) and might not be functionally complete. Red Hat does not recommend using them in production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process. + +For more information about the support scope of Red Hat Technology Preview features, see link:https://access.redhat.com/support/offerings/techpreview/[Technology Preview Features Support Scope]. +==== +``` + +* The Driver Toolkit +* SSPI connection support on Microsoft Windows +* Hot-plugging virtual disks + +For more information about the support scope of Red Hat Technology Preview features, see [Technology Preview Features Support Scope](https://access.redhat.com/support/offerings/techpreview/). For a comparison of Developer Preview and Technology Preview features, see [Developer and Technology Previews: How they compare](https://access.redhat.com/articles/6966848). diff --git a/.claude/resources/reference-template.adoc b/.claude/resources/reference-template.adoc new file mode 100644 index 00000000000..ea22c0accba --- /dev/null +++ b/.claude/resources/reference-template.adoc @@ -0,0 +1,52 @@ +//// +Metadata attribute that will help enable correct parsing and conversion to the appropriate DITA topic type. +//// + +:_mod-docs-content-type: CONCEPT + +//// +Base the file name and the ID on the module title. For example: +* file name: con_my-concept-module-a.adoc +* ID: [id="my-concept-module-a_{context}"] +* Title: = My concept module A + + ID is a unique identifier that can be used to reference this module. Avoid changing it after the module has been published to ensure existing links are not broken. + +The `context` attribute enables module reuse. Every module ID includes {context}, which ensures that the module has a unique ID so you can include it multiple times in the same guide. + +Be sure to include a line break between the title and the module introduction. +//// + +[id="my-concept-module-a_{context}"] += My concept module A +//// +In the title of concept modules, include nouns or noun phrases that are used in the body text. This helps readers and search engines find the information quickly. Do not start the title of concept modules with a verb or gerund. See also _Wording of headings_ in _IBM Style_. +//// + +[role="_abstract"] +Write a short introductory paragraph that provides an overview of the module. + +The contents of a concept module give the user descriptions and explanations needed to understand and use a product. + +* Look at nouns and noun phrases in related procedure modules and assemblies to find the concepts to explain to users. +* Explain only things that are visible to users. Even if a concept is interesting, it probably does not require explanation if it is not visible to users. +* Avoid including action items. Action items belong in procedure modules. However, in some cases a concept or reference can include suggested actions when those actions are simple, are highly dependent on the context of the module, and have no place in any procedure module. In such cases, ensure that the heading of the concept or reference remains a noun phrase and not a gerund. + + +//// +Do not include third-level headings (===). +Include titles and alternative text descriptions for images and enclose the descriptions in straight quotation marks (""). Alternative text should provide a textual, complete description of the image as a full sentence. +Images should never be the sole means of conveying information and should only supplement the text. +Avoid screenshots or other images that might quickly go out of date and that create a maintenance burden on documentation. Provide text equivalents for every diagram, image, or other non-text element. Avoid using images of text instead of actual text. +//// +//.Image title +//image::image-file.png["A textual representation of the essential information conveyed by the image."] + +[role="_additional-resources"] +.Additional resources +//// +Optional. Delete if not used. +Provide a bulleted list of links and display text relevant to the assembly. These links can include `link:` and `xref:` macros. Do not include additional text. +//// +* link:https://github.com/redhat-documentation/modular-docs#modular-documentation-reference-guide[Modular Documentation Reference Guide] +* xref:some-module_{context}[] diff --git a/.claude/resources/vale-acceptable-warnings.md b/.claude/resources/vale-acceptable-warnings.md deleted file mode 100644 index 64eb6528c3f..00000000000 --- a/.claude/resources/vale-acceptable-warnings.md +++ /dev/null @@ -1,78 +0,0 @@ -# Vale Acceptable Warnings - -Some Vale DITA warnings are acceptable and do not block CQA 2.1 compliance. - -## Callout Warnings - -- **Warning**: `AsciiDocDITA.CalloutList`: "Callouts are not supported in DITA" -- **Context**: Callouts (`<1>`, `<2>`, etc.) in code blocks with corresponding explanations -- **Acceptable**: Yes - this is a known DITA limitation, but callouts are valuable for technical documentation -- **Note**: Track these warnings but do not remove callouts - -**Example**: -```asciidoc -[source,yaml] ----- -app: - baseUrl: https://example.com # <1> - title: My App # <2> ----- -<1> The base URL for your application -<2> The display title -``` - -## Concept Link False Positives - -- **Warning**: `AsciiDocDITA.ConceptLink`: "Move all links and cross references to Additional resources" -- **Context**: Vale sometimes detects abbreviations like "CR" (Custom Resource) as cross-references -- **Acceptable**: Yes - if the warning is on plain text abbreviations, not actual links -- **Fix**: You can ignore these false positives, or spell out the abbreviation if desired - -**Example** (false positive): -```asciidoc -Create a CR (Custom Resource) for the configuration. -``` - -## Task Step Warnings in Introductory Content - -- **Warning**: `AsciiDocDITA.TaskStep`: "Content other than a single list cannot be mapped to DITA tasks" -- **Context**: Descriptive content before `.Procedure` section -- **Acceptable**: Sometimes - if the content is legitimately before the procedure section (like field definitions) -- **Fix**: If the warning appears AFTER `.Procedure`, add a continuation mark (+). If before, it may be acceptable. - -**Example** (acceptable - before .Procedure): -```asciidoc -[role="_abstract"] -Configure the plugin settings. - -The following fields are available: - -Name:: -The display name for the plugin - -Type:: -The plugin type (frontend or backend) - -.Procedure - -. Edit the configuration file. -``` - -**Example** (NOT acceptable - after .Procedure): -```asciidoc -.Procedure - -. Edit the configuration file. - -The file contains the following settings: -* Setting 1 -* Setting 2 -``` - -## Success Criteria - -CQA 2.1 compliance is achieved when: -- Vale DITA validation shows: `0 errors, 0-15 acceptable warnings, 0 suggestions` -- Acceptable warnings are documented and verified as false positives or known limitations -- Vale Red Hat style validation shows: `0 errors, 0 warnings` -- Build validation (`build/scripts/build-ccutil.sh`) completes successfully with all titles built and no xref errors diff --git a/.claude/settings.json b/.claude/settings.json index a07a83b8e44..64a4f47fb3f 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -49,20 +49,11 @@ "Bash(xxd *)", "Read(//**)", "WebFetch(domain:*)", - "WebSearch", - "Bash(/dev/null jq:*)", - "Bash(bash -x ./build/scripts/align-title-directories.sh --all)", - "Bash(do ./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh --fix \"$master\")" + "WebSearch" ], "additionalDirectories": [ ".claude", - ".claude/resources", - ".claude/skills", - "/tmp", - "titles/*", - "/home/ffloreth/src/gh/themr0c/red-hat-developers-documentation-rhdh", - "/dev", - "/home/ffloreth/src/gh/themr0c/red-hat-developers-documentation-rhdh/assemblies/authorization-in-rhdh" + "/tmp" ] } } diff --git a/.claude/skills/README.md b/.claude/skills/README.md index 6eae9c03e3a..84cb48afa93 100644 --- a/.claude/skills/README.md +++ b/.claude/skills/README.md @@ -2,11 +2,11 @@ Individual assessment skills for each Pre-migration requirement. -## Master Workflow +## Main Workflow -**START HERE:** [CQA Master Workflow](cqa-master-workflow.md) - Execute all 17 requirements in optimal order +**START HERE:** [CQA Main Workflow](cqa-main-workflow.md) - Execute all 17 requirements in optimal order -This master workflow orchestrates the individual skills below in the correct sequence aligned with the CQA 2.1 checklist. Use this for complete CQA compliance assessment. +This main workflow orchestrates the individual skills below in the correct sequence aligned with the CQA 2.1 checklist. Use this for complete CQA compliance assessment. --- @@ -55,7 +55,7 @@ This master workflow orchestrates the individual skills below in the correct seq 14. [No broken links](cqa-14-no-broken-links.md) -15. [Redirects (if needed) are in place and work correctly](cqa-15-redirects-if-needed-are-in-place-and-work-correc.md) +15. [Redirects (if needed) are in place and work correctly](cqa-15-redirects.md) ## Legal and Branding diff --git a/.claude/skills/align-title-directories.md b/.claude/skills/align-title-directories.md index c89d532be84..4ed76bc6df3 100644 --- a/.claude/skills/align-title-directories.md +++ b/.claude/skills/align-title-directories.md @@ -9,8 +9,10 @@ All directories follow `_` naming: ``` titles/_/master.adoc assemblies/_/ # owned by one title +assemblies/_shared/ # shared within a category assemblies/shared/ # shared across categories modules/_/ # owned by one title +modules/_shared/ # shared within a category modules/shared/ # shared across categories images/_/ # owned by one title images/shared/ # shared across categories @@ -29,12 +31,12 @@ The `:context:` value is derived automatically from the `:title:` attribute in m ## Script Usage ```bash -./build/scripts/align-title-directories.sh --list # show all titles -./build/scripts/align-title-directories.sh --all # dry-run all titles -./build/scripts/align-title-directories.sh --all --exec # execute all titles -./build/scripts/align-title-directories.sh # dry-run single title -./build/scripts/align-title-directories.sh --exec # execute single title -./build/scripts/align-title-directories.sh --exec # explicit context +./build/scripts/cqa-00-directory-structure.sh # report misnamed dirs (full repo) +./build/scripts/cqa-00-directory-structure.sh --fix # execute renames (full repo) +./build/scripts/cqa-00-directory-structure.sh # report single title +./build/scripts/cqa-00-directory-structure.sh --fix # execute single title +./build/scripts/cqa-00-directory-structure.sh --fix # explicit context +./build/scripts/cqa-00-directory-structure.sh --format json # SARIF output ``` ## Script Phases @@ -64,8 +66,8 @@ Resources used by multiple titles stay in `*/shared/`. The script determines thi ## Companion Scripts -- `./build/scripts/fix-orphaned-modules.sh` - Find orphaned .adoc files and images (dry-run) -- `./build/scripts/fix-orphaned-modules.sh --execute` - Delete orphaned files +- `./build/scripts/cqa-00-orphaned-modules.sh` - Find orphaned .adoc files and images +- `./build/scripts/cqa-00-orphaned-modules.sh --fix` - Delete orphaned files ## Per-Title Checklist @@ -73,11 +75,11 @@ For each title, follow this checklist in order: 1. **Preview changes** (dry-run): ```bash - ./build/scripts/align-title-directories.sh titles/ + ./build/scripts/cqa-00-directory-structure.sh titles/ ``` 2. **Execute**: ```bash - ./build/scripts/align-title-directories.sh --exec titles/ + ./build/scripts/cqa-00-directory-structure.sh --fix titles/ ``` 3. **If single assembly**: Inline assembly content into master.adoc without adding a second level heading, then delete the assembly file 4. **Verify** `[id="{context}"]` and `= {title}` are present in master.adoc @@ -102,4 +104,4 @@ For each title, follow this checklist in order: - Do NOT commit this skill file or memory changes - Build using `./build/scripts/build-ccutil.sh` and fix errors before committing -- The `--all` mode is idempotent: running it again on an already-aligned repo produces no changes +- The full repo mode is idempotent: running it again on an already-aligned repo produces no changes diff --git a/.claude/skills/cqa-01-asciidoctor-dita-vale.md b/.claude/skills/cqa-01-asciidoctor-dita-vale.md index ac65dd01415..0c6c4dfd996 100644 --- a/.claude/skills/cqa-01-asciidoctor-dita-vale.md +++ b/.claude/skills/cqa-01-asciidoctor-dita-vale.md @@ -6,65 +6,73 @@ **Quality Level:** Required/non-negotiable -## Automated Validation +## Automated Validation and Fixing -### Run Complete Validation Script +**IMPORTANT:** ALWAYS use the script below. NEVER run `vale` directly — the script handles file discovery, attributes.adoc exclusion, and correct config selection. Running `vale` directly also requires separate authorization. ```bash -./build/scripts/cqa-01-asciidoctor-dita-vale.sh [--fix] titles//master.adoc -``` +# 1. Report issues +./build/scripts/cqa-01-asciidoctor-dita-vale.sh titles//master.adoc -**What the script validates:** -- Runs Vale with `.vale-dita-only.ini` configuration -- Validates all included files (master.adoc + assemblies + modules) -- Reports errors, warnings, and suggestions -- Provides clear pass/fail status +# 2. Auto-fix what can be fixed +./build/scripts/cqa-01-asciidoctor-dita-vale.sh --fix titles//master.adoc -**Target Results:** -- ✅ 0 errors -- ✅ 0 warnings (all warnings must be fixed) -- ✅ 0 suggestions +# 3. Re-run to verify remaining issues +./build/scripts/cqa-01-asciidoctor-dita-vale.sh titles//master.adoc -**Example output:** -``` -✓ All files pass AsciiDoc DITA validation -✔ 0 errors, 0 warnings and 0 suggestions in 13 files. -``` +# 4. Attempt manual fixes for remaining issues -### Manual Validation +# 5. Re-run to verify remaining issues -**Run Vale directly on specific title:** +# 6. If issues remain, report as failed and list the remaining issues -```bash -# For a specific title -vale --config .vale-dita-only.ini \ - $(./build/scripts/list-all-included-files-starting-from.sh titles//master.adoc) +# JSON output for programmatic parsing (pipe to jq) +./build/scripts/cqa-01-asciidoctor-dita-vale.sh --output JSON titles//master.adoc ``` -**See also:** [get-title-files.md](get-title-files.md) for detailed explanation of the file list extraction script. - -**IMPORTANT:** Do NOT run `vale titles//` on the entire directory - this validates files NOT part of the title and produces misleading results. Always specify master.adoc and its includes. - -## Notes +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. -The AsciiDocDITA tool identifies markup that does not have a direct equivalent in DITA 1.3. See the readme for details about specific issues that it finds. Note: The AsciiDocDITA tool is updated frequently. - -**Reference:** https://github.com/jhradilek/asciidoctor-dita-vale - -**NOT acceptable warnings (must be fixed):** -- `AsciiDocDITA.BlockTitle`: Block titles (e.g. `.Prerequisites`) — in reference modules, convert to second-level headings (`==`); in procedures/assemblies, restructure to avoid block titles -- `AsciiDocDITA.CalloutList`: Callouts in code blocks — replace with inline comments or numbered annotations -- `AsciiDocDITA.ConceptLink`: Inline links in prerequisites/concept content — move links to `.Additional resources` section -- `AsciiDocDITA.RelatedLinks`: `.Additional resources` items contain explanatory text — must be link-only (no surrounding prose) -- `AsciiDocDITA.TaskStep`: Content other than a single list in `.Procedure` — split description lists into separate procedure modules - -**All DITA warnings must be fixed.** There are no acceptable warnings. - -**Fix for `AsciiDocDITA.DocumentId`:** In master.adoc files, add `[id="{context}"]` before the level 0 heading. - -**Common Errors to Fix:** -- `AsciiDocDITA.ExampleBlock`: Nested example blocks - convert to regular text with source blocks -- `AsciiDocDITA.ShortDescription`: Missing or incorrect `[role="_abstract"]` +**Target Results:** +- ✅ 0 errors, 0 warnings, 0 suggestions + +## DITA Warnings and Fixes + +**All DITA warnings must be fixed.** There are no acceptable warnings. Fix each reported issue according to the table below. + +| Warning | Fix | +|---------|-----| +| `AsciiDocDITA.ShortDescription` | **Delegated to [CQA #8](cqa-08-short-description-content.md).** Add `[role="_abstract"]` before the first paragraph after the title. Ensure the paragraph is 50-300 chars. | +| `AsciiDocDITA.AuthorLine` | **Auto-fixed.** Add a blank line after the title. The author line is not supported in DITA topics. | +| `AsciiDocDITA.DocumentTitle` | Add a level 0 heading (`= Title`). For master.adoc, ensure `= {title}` is present. | +| `AsciiDocDITA.DocumentId` | **Delegated to [CQA #10](cqa-10-titles-are-brief-complete-and-descriptive.md).** Add `[id="{context}"]` before the level 0 heading. | +| `AsciiDocDITA.BlockTitle` | **Auto-fixed.** Convert to a lead-in sentence ending with `:`. Block titles (`.Something`) are only valid for examples, figures, and tables. Skips block titles before tables, examples, source blocks, and images. | +| `AsciiDocDITA.TaskContents` | **Auto-fixed.** Add `.Procedure` before the first numbered steps list. | +| `AsciiDocDITA.TaskTitle` | Unsupported sub-heading inside a procedure module. Remove the heading, or convert to a `.Procedure` block title or bold lead-in text. | +| `AsciiDocDITA.TaskStep` | **Auto-fixed.** After `.Procedure` and before the first step: remove. After a list: attach to the preceding step with `+` continuation. | +| `AsciiDocDITA.ConceptLink` | Links inside body text of concepts or procedures. Move links to `.Additional resources` section. | +| `AsciiDocDITA.AssemblyContents` | Content after include directives in an assembly. Move text into a module, or before the includes. | +| `AsciiDocDITA.RelatedLinks` | `.Additional resources` items contain explanatory text. Items must be link-only — remove surrounding prose. | +| `AsciiDocDITA.CalloutList` | **Auto-fixed.** Convert callout list items to description list items (`<1> text` → `<1>:: text`). | +| `AsciiDocDITA.ExampleBlock` | Example block nested inside another block. Move outside the parent block, or convert to a source block. | + +**Auto-fixable with `--fix`:** +- AuthorLine — insert blank line after title +- BlockTitle — convert to lead-in sentence ending with `:` (skips titles before tables/examples/source/images) +- TaskContents — add `.Procedure` before first ordered list +- TaskStep — after `.Procedure` before first step: remove; after a list: replace blank line with `+` continuation +- CalloutList — convert `<1> text` to `<1>:: text` description list format + +**Delegated:** +- ShortDescription — [CQA #8](cqa-08-short-description-content.md) +- DocumentId — [CQA #10](cqa-10-titles-are-brief-complete-and-descriptive.md) + +**Not auto-fixed (manual):** +- DocumentTitle — requires writing a title +- TaskTitle — requires restructuring (remove heading or convert) +- ConceptLink — requires moving links to Additional resources +- AssemblyContents — requires moving content into modules +- RelatedLinks — requires removing prose around links +- ExampleBlock — requires restructuring nested blocks ## Assessment diff --git a/.claude/skills/cqa-02-assembly-structure.md b/.claude/skills/cqa-02-assembly-structure.md index fde904a29e1..cb7aeae77cc 100644 --- a/.claude/skills/cqa-02-assembly-structure.md +++ b/.claude/skills/cqa-02-assembly-structure.md @@ -2,161 +2,96 @@ ## Assemblies should contain only an introductory section and include statements +**Reference:** [Assembly template](../resources/assembly-template.adoc) + **Quality Level:** Required/non-negotiable ## Requirement -Assemblies must have this structure: -1. **Introduction** - One or more paragraphs marked with `[role="_abstract"]` -2. **Optional: Prerequisites** - Use second-level heading (`== Prerequisites`) so it appears in the TOC, consistent with Additional resources and Next steps -3. **Include statements** - For modules only -4. **Optional: Additional resources** - Links only, at the end after all includes +Assemblies must follow the [assembly template](../resources/assembly-template.adoc). Required structure: +1. **Content type** - `:_mod-docs-content-type: ASSEMBLY` on first line +2. **Context save** - `ifdef::context[:parent-context: {context}]` on second line (skip for `master.adoc`) +3. **ID** - `[id="assembly-name_{context}"]` +4. **Title** - `= Assembly title` +5. **Context set** - `:context: assembly-name` +6. **Introduction** - A single paragraph marked with `[role="_abstract"]` (50-300 chars) +7. **Optional: Prerequisites** - Use `== Prerequisites` heading (not `.Prerequisites` block title) +8. **Include statements** - For modules only, no text between includes +9. **Optional: Additional resources** - `[role="_additional-resources"]` then `== Additional resources`, links only, after all includes +10. **Context restore** (if nestable) - `ifdef::parent-context[...]` and `ifndef::parent-context[...]` **DITA Constraint:** DITA maps do not accept text between include statements for modules. -## Automated Validation +## Automated Validation and Fixing -### Run Complete Validation Script +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect assembly files without running the script. ```bash -./build/scripts/cqa-02-assembly-structure.sh [--fix] titles/<your-title>/master.adoc -``` - -**What the script validates:** -- Has `[role="_abstract"]` introduction -- No content between include statements -- `== Prerequisites` heading before first include (if present); `.Prerequisites` block title is an error -- `.Additional resources` at end after all includes (if present) -- Content type is ASSEMBLY -- No level 2+ subheadings (=== or deeper) -- No detailed content between abstract and includes - -**Target Results:** -- ✅ All assemblies have compliant structure -- ⚠️ Warnings indicate potential issues requiring manual review - -**Example output:** -``` -✓ All assemblies have compliant structure -Note: Warnings indicate potential issues that require manual review -``` - -## Verification - -**Manual inspection of each assembly file:** - -1. **Check assembly structure:** - ```bash - # List all assembly files - find assemblies/ -name "assembly-*.adoc" -o -name "master.adoc" - ``` - -2. **For each assembly, verify:** - - [ ] Has introduction paragraph(s) with `[role="_abstract"]` - - [ ] Only includes statements after introduction - - [ ] NO detailed content in assembly (move to modules) - - [ ] NO text between include statements - - [ ] Optional `== Prerequisites` heading before includes (if needed), using second-level heading for TOC visibility - - [ ] Optional `.Additional resources` is at end (after all includes) - -**What to look for:** - -✅ **Correct Structure:** -```asciidoc -= Assembly Title - -[role="_abstract"] -Brief introduction explaining what user accomplishes. - -== Prerequisites -* Prerequisite 1 -* Prerequisite 2 - -include::modules/con-concept.adoc[leveloffset=+1] +# 1. Report issues +./build/scripts/cqa-02-assembly-structure.sh titles/<your-title>/master.adoc -include::modules/proc-procedure.adoc[leveloffset=+1] +# 2. Auto-fix what can be fixed +./build/scripts/cqa-02-assembly-structure.sh --fix titles/<your-title>/master.adoc -.Additional resources -* link:https://example.com[Related documentation] -``` - -❌ **Incorrect - Detailed content in assembly:** -```asciidoc -= Assembly Title - -[role="_abstract"] -Introduction paragraph. +# 3. Re-run to verify remaining issues +./build/scripts/cqa-02-assembly-structure.sh titles/<your-title>/master.adoc -This is a detailed explanation of concepts. ← WRONG: Move to concept module +# 4. Attempt manual fixes for remaining issues -include::modules/proc-procedure.adoc[leveloffset=+1] +# 5. Re-run to verify remaining issues -Here are additional details. ← WRONG: No text between includes +# 6. If issues remain, report as failed and list the remaining issues ``` -## Common Violations and Fixes - -### Violation 1: Detailed content in assembly -**Problem:** Assembly contains explanatory text, bullets, or detailed information - -**Fix:** -1. Create a concept module (con-*.adoc) for explanatory content -2. Move detailed text to the concept module -3. Add include statement for the concept module -4. Keep only brief introduction in assembly - -### Violation 2: Text between include statements -**Problem:** Assembly has paragraphs or content between module includes - -**Fix:** -1. Move the text into one of the included modules -2. If text introduces a section, move it to the module being introduced -3. If text is standalone, create a new concept module - -### Violation 3: Prerequisites in wrong location or format -**Problem:** Prerequisites listed after includes, scattered throughout, or using block title syntax (`.Prerequisites`) instead of heading syntax - -**Fix:** -1. Move all prerequisites to a single `== Prerequisites` section (second-level heading, not `.Prerequisites` block title) -2. Place immediately after introduction, before first include -3. Use bulleted list format -4. Max 10 prerequisites -5. The second-level heading ensures Prerequisites appears in the TOC, consistent with `== Additional resources` and `== Next steps` - -### Violation 4: Additional resources with descriptive text -**Problem:** Additional resources section has explanatory content - -**Fix:** -1. Keep only links in `.Additional resources` -2. Remove descriptive text -3. Link text should be descriptive enough (no extra explanation needed) - -## Notes - -**Reference:** This requirement aligns with DITA map structure constraints. DITA maps can only contain references to topics (modules), not inline content. - -**Vale Check Status:** This will be added to Vale asciidoctor-dita-vale check (rule: AssemblyContents) - -**Context Management:** Assemblies that include other assemblies must properly set and restore context: -```asciidoc -// At top of nested assembly -ifdef::context[:parent-context: {context}] +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + +**What the script validates (12 checks):** +- Content type is ASSEMBLY on first line, not repeated +- Has `[role="_abstract"]` introduction, warns if length outside 50-300 chars +- Has `[id="..._{context}"]` attribute +- Has `:context:` attribute set after title, separated by blank line +- Context save on line 2, not repeated; context restore as exact last two lines (except `master.adoc`) +- No `.Prerequisites` block title (must use `== Prerequisites` heading) +- More than 10 prerequisites (warning) +- No level 3+ subheadings (`===` or deeper) +- `[role="_additional-resources"]` present if `== Additional resources` heading is used +- Prerequisites before first include +- Additional resources after last include +- No content between include statements -// At end of nested assembly -ifdef::parent-context[:context: {parent-context}] -ifndef::parent-context[:!context:] -``` +**Auto-fixable with `--fix`:** +- Content type on first line (insert/move) +- Remove duplicate content type lines +- Add context save on line 2 +- Add context restore as last two lines +- Remove duplicate context save/restore lines +- `.Prerequisites` block title → `== Prerequisites` heading +- `.Additional resources` block title → `[role="_additional-resources"]` + `== Additional resources` heading +- Missing `[role="_additional-resources"]` before `== Additional resources` heading +- Move `:context:` after title with blank line separator (if present but in wrong position) +- Add `_{context}` suffix to ID if missing +- Add missing `:context:` attribute (value derived from ID without `_{context}` suffix) + +**Not auto-fixed (delegated):** +- ID value mismatch with title — handled by [CQA #10 - Titles](cqa-10-titles-are-brief-complete-and-descriptive.md) +- Introduction length (50-300 chars) — handled by [CQA #8 - Short descriptions](cqa-08-short-description-content.md) + +**Not auto-fixed (manual):** +- Content between include statements — requires moving text into modules +- Missing `[role="_abstract"]` — delegated to [CQA #9 - Short description format](cqa-09-short-description-format.md) +- Level 3+ subheadings — requires restructuring +- Prerequisites count, order, completeness +- Include order and additional resources link validity ## Assessment ```yaml -title: +title: status: No data # Meets criteria | Mostly meets | Mostly does not meet | Does not meet | Not applicable notes: | - ``` diff --git a/.claude/skills/cqa-03-content-is-modularized.md b/.claude/skills/cqa-03-content-is-modularized.md index 6c2f364dcc6..003c8619e2a 100644 --- a/.claude/skills/cqa-03-content-is-modularized.md +++ b/.claude/skills/cqa-03-content-is-modularized.md @@ -6,69 +6,61 @@ **Quality Level:** Required/non-negotiable -## Command +## Automated Validation and Fixing -**Run content type detection and validation:** -```bash -./build/scripts/cqa-03-content-is-modularized.sh [--fix] titles/<your-title>/master.adoc -``` +**IMPORTANT:** ALWAYS use the script below. -**What the script does:** -- Detects and validates `:_mod-docs-content-type:` metadata -- Auto-fixes content type declarations where possible -- Reports compliant, violation, and auto-fixed counts -- Validates PROCEDURE structure (numbered vs unnumbered lists) -- Checks filename prefixes match declared types +```bash +# 1. Report issues +./build/scripts/cqa-03-content-is-modularized.sh titles/<your-title>/master.adoc -**Target Results:** -- ✅ All modules have correct content type metadata (ASSEMBLY, CONCEPT, PROCEDURE, REFERENCE, SNIPPET) -- ✅ All filenames use correct prefixes (`assembly-`, `con-`, `proc-`, `ref-`, `snip-`) -- ✅ All declared types match actual content structure +# 2. Auto-fix what can be fixed +./build/scripts/cqa-03-content-is-modularized.sh --fix titles/<your-title>/master.adoc -## Notes +# 3. Re-run to verify remaining issues +./build/scripts/cqa-03-content-is-modularized.sh titles/<your-title>/master.adoc -**Modularization Requirements:** +# 4. Attempt manual fixes for remaining issues -All content must follow Red Hat modular documentation structure: +# 5. Re-run to verify remaining issues -1. **Assemblies** - Combine modules to address a single user story - - Introduction paragraph (marked with `[role="_abstract"]`) - - Include statements for modules - - Optional: Prerequisites, Additional resources +# 6. If issues remain, report as failed and list the remaining issues +``` -2. **Concept Modules** (`con-*.adoc`) - Explain "what" and "why" - - `:_mod-docs-content-type: CONCEPT` - - No step-by-step instructions - - Optional subheadings allowed +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. -3. **Procedure Modules** (`proc-*.adoc`) - Step-by-step instructions - - `:_mod-docs-content-type: PROCEDURE` - - Standard sections only: `.Prerequisites`, `.Procedure`, `.Verification`, `.Troubleshooting`, `.Next steps` - - NO custom subheadings +**What the script does:** +- Detects and validates `:_mod-docs-content-type:` metadata (must be first line) +- Auto-fixes content type declarations in `--fix` mode +- Validates PROCEDURE structure (numbered vs unnumbered lists) +- Normalizes `.Procedure` and `.Verification` list formatting +- Checks filename prefixes match declared types -4. **Reference Modules** (`ref-*.adoc`) - Lookup data - - `:_mod-docs-content-type: REFERENCE` - - Tables, lists, specifications - - Optional subheadings allowed for complex content +**Target Results:** +- ✅ All modules have correct content type metadata +- ✅ All filenames use correct prefixes (`assembly-`, `con-`, `proc-`, `ref-`, `snip-`) -5. **Snippets** (`snip-*.adoc`) - Reusable content fragments - - `:_mod-docs-content-type: SNIPPET` - - NO structural elements (anchors, H1 headings, block titles) +## Module Types -**Critical Rule:** A module should not contain another module (no includes within modules) +| Type | Prefix | Content | Title form | +|------|--------|---------|------------| +| **ASSEMBLY** | `assembly-` / `master.adoc` | Combines modules for a user story | Imperative or noun phrase | +| **CONCEPT** | `con-` | Explains "what" and "why", no steps | Noun phrase | +| **PROCEDURE** | `proc-` | Step-by-step instructions, standard sections only | Imperative | +| **REFERENCE** | `ref-` | Lookup data (tables, lists) | Noun phrase | +| **SNIPPET** | `snip-` | Reusable fragments, no structural elements | N/A | -**Reference:** [Red Hat Modular Documentation Reference Guide](../resources/red-hat-modular-docs.md) +**Critical Rule:** A module should not contain another module (no includes within modules). ## Assessment ```yaml -title: +title: status: No data # Meets criteria | Mostly meets | Mostly does not meet | Does not meet | Not applicable notes: | - ``` diff --git a/.claude/skills/cqa-04-modules-use-official-templates.md b/.claude/skills/cqa-04-modules-use-official-templates.md index 11261d2e24a..e944f7b3a58 100644 --- a/.claude/skills/cqa-04-modules-use-official-templates.md +++ b/.claude/skills/cqa-04-modules-use-official-templates.md @@ -8,30 +8,44 @@ All modules must follow official Red Hat modular documentation templates for their content type. -## Commands +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect module files without running the script. ```bash -# Verify module template compliance +# 1. Report issues ./build/scripts/cqa-04-modules-use-official-templates.sh titles/<your-title>/master.adoc -# Fix mode (auto-fixes .Prerequisite → .Prerequisites) +# 2. Auto-fix what can be fixed ./build/scripts/cqa-04-modules-use-official-templates.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-04-modules-use-official-templates.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + ## Template Requirements by Module Type | Module Type | Required Elements | Allowed Sections | Prohibited | |-------------|-------------------|------------------|------------| | **CONCEPT**<br>`con-*.adoc` | • Intro paragraph (What/Why)<br>• Body content | • Subheadings (`===`)<br>• `.Additional resources` | ❌ Numbered steps<br>❌ Action items | -| **PROCEDURE**<br>`proc-*.adoc` | • Title (gerund form)<br>• Intro<br>• Steps (imperative) | • `.Prerequisites`<br>• `.Procedure`<br>• `.Verification`<br>• `.Troubleshooting`<br>• `.Next steps`<br>• `.Additional resources` | ❌ Custom subheadings (`===`)<br>❌ Non-standard sections | +| **PROCEDURE**<br>`proc-*.adoc` | • Title (imperative form)<br>• Intro<br>• Steps (imperative) | • `.Prerequisites`<br>• `.Procedure`<br>• `.Verification`<br>• `.Troubleshooting`<br>• `.Next steps`<br>• `.Additional resources` | ❌ Custom subheadings (`===`)<br>❌ Non-standard sections | | **REFERENCE**<br>`ref-*.adoc` | • Concise intro<br>• Organized data (tables/lists) | • Subheadings (`===`)<br>• `.Additional resources` | ❌ Lengthy explanations | ### Example: CONCEPT Module ```asciidoc +:_mod-docs-content-type: CONCEPT + [id="concept-name_{context}"] = Concept title (noun phrase) -:_mod-docs-content-type: CONCEPT Single intro: What is this? Why care? @@ -42,10 +56,11 @@ Body content with paragraphs, lists, tables. ### Example: PROCEDURE Module ```asciidoc -[id="procedure-name_{context}"] -= Creating the resource (gerund) :_mod-docs-content-type: PROCEDURE +[id="procedure-name_{context}"] += Create the resource (imperative) + Intro: What this accomplishes and why. .Prerequisites @@ -64,9 +79,10 @@ Intro: What this accomplishes and why. ### Example: REFERENCE Module ```asciidoc +:_mod-docs-content-type: REFERENCE + [id="reference-name_{context}"] = Reference title (noun phrase) -:_mod-docs-content-type: REFERENCE Brief intro to reference data. @@ -86,7 +102,7 @@ Brief intro to reference data. | **Explanations in REFERENCE** | Lengthy paragraphs | Move to CONCEPT, keep only data tables | | **No introduction** | Module starts with heading | Add intro paragraph after metadata | | **Imperative in prerequisites** | "Install the Operator" | "You have installed the Operator" | -| **Wrong title form (PROCEDURE)** | "Install X" (imperative) | "Installing X" (gerund) | +| **Wrong title form (PROCEDURE)** | "Installing X" (gerund) | "Install X" (imperative) | ## Validation Workflow diff --git a/.claude/skills/cqa-05-modular-elements-checklist.md b/.claude/skills/cqa-05-modular-elements-checklist.md index d57b8014dc1..8484638a166 100644 --- a/.claude/skills/cqa-05-modular-elements-checklist.md +++ b/.claude/skills/cqa-05-modular-elements-checklist.md @@ -10,14 +10,29 @@ All modules and assemblies must include required structural elements per the Red **IMPORTANT:** This requirement validates against the official checklist at [.claude/resources/modular-documentation-templates-checklist.md](../resources/modular-documentation-templates-checklist.md). Review that checklist before running validation. -## Automated Validation +## Automated Validation and Fixing -### Run Complete Validation Script +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. ```bash -./build/scripts/cqa-05-modular-elements-checklist.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-05-modular-elements-checklist.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-05-modular-elements-checklist.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-05-modular-elements-checklist.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script validates:** The script checks all requirements from [modular-documentation-templates-checklist.md](../resources/modular-documentation-templates-checklist.md): diff --git a/.claude/skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md b/.claude/skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md index e1b62aa6573..04f26750f5d 100644 --- a/.claude/skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md +++ b/.claude/skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md @@ -7,13 +7,29 @@ **Quality Level:** Required/non-negotiable -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect assembly files without running the script. -**Run assembly template and structure verification:** ```bash -./build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Checks assembly files have a title (= heading) - Validates module count (≤15 includes per assembly) diff --git a/.claude/skills/cqa-07-toc-max-3-levels.md b/.claude/skills/cqa-07-toc-max-3-levels.md index bf1a9687a47..b55433b9639 100644 --- a/.claude/skills/cqa-07-toc-max-3-levels.md +++ b/.claude/skills/cqa-07-toc-max-3-levels.md @@ -8,14 +8,29 @@ Content hierarchy should not exceed 3 levels in TOC to improve navigation and pr **Level counting:** For AEM migration, count from where your content starts (excluding Pantheon categories/book titles). -## Automated Validation +## Automated Validation and Fixing -### Run Complete Validation Script +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. ```bash -./build/scripts/cqa-07-toc-max-3-levels.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-07-toc-max-3-levels.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-07-toc-max-3-levels.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-07-toc-max-3-levels.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script validates:** - Scans all included .adoc files for heading depth - Identifies any headings deeper than level 3 (====) diff --git a/.claude/skills/cqa-08-short-description-content.md b/.claude/skills/cqa-08-short-description-content.md index 1f773567ef4..3c6dde4c194 100644 --- a/.claude/skills/cqa-08-short-description-content.md +++ b/.claude/skills/cqa-08-short-description-content.md @@ -167,13 +167,29 @@ grep -r "The following" modules/ assemblies/ - References → Describe INFORMATION - Assemblies → Summarize GOAL -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run short description content verification:** ```bash -./build/scripts/cqa-08-short-description-content.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-08-short-description-content.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-08-short-description-content.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-08-short-description-content.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Checks for self-referential language ("This section...", "This document...", etc.) - Detects empty abstracts after `[role="_abstract"]` diff --git a/.claude/skills/cqa-09-short-description-format.md b/.claude/skills/cqa-09-short-description-format.md index 3ce6851d0e9..8db9f21efed 100644 --- a/.claude/skills/cqa-09-short-description-format.md +++ b/.claude/skills/cqa-09-short-description-format.md @@ -16,13 +16,29 @@ Short descriptions must follow AsciiDoc/DITA format requirements: **Note:** For content quality requirements (WHY, keywords, self-referential language), see [CQA #8](cqa-08-short-description-content.md). -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run short description format verification:** ```bash -./build/scripts/cqa-09-short-description-format.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-09-short-description-format.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-09-short-description-format.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-09-short-description-format.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Checks for `[role="_abstract"]` marker presence - Validates character count (50-300 characters) diff --git a/.claude/skills/cqa-10-titles-are-brief-complete-and-descriptive.md b/.claude/skills/cqa-10-titles-are-brief-complete-and-descriptive.md index c66d3480010..4cbf6467693 100644 --- a/.claude/skills/cqa-10-titles-are-brief-complete-and-descriptive.md +++ b/.claude/skills/cqa-10-titles-are-brief-complete-and-descriptive.md @@ -12,8 +12,8 @@ All module and assembly titles must be: - **Content-appropriate:** Match module type conventions **References:** -- [Red Hat Supplementary Style Guide](../resources/red-hat-ssg.md) - Title capitalization and length -- [Red Hat Peer Review Guide](../resources/red-hat-peer-review.md) - Style checklist and title standards +- [Red Hat Supplementary Style Guide (CQA extract)](../resources/red-hat-ssg-for-cqa.md) - Title capitalization and length +- [Red Hat Peer Review Guide (CQA extract)](../resources/red-hat-peer-review-for-cqa.md) - Style checklist and title standards - [Modular Documentation Reference Guide](../resources/red-hat-modular-docs.md) - Title forms by type ## Verification @@ -93,14 +93,29 @@ done | sort -n - Include key technologies (OpenShift, Kubernetes, Helm) - Avoid marketing language ("amazing", "powerful") -## Script Integration +## Automated Validation and Fixing -The title/ID/filename alignment script helps enforce title correctness: +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. ```bash -./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + This script: - Converts gerunds to imperatives in procedure titles - Aligns IDs with titles diff --git a/.claude/skills/cqa-11-procedures-prerequisites.md b/.claude/skills/cqa-11-procedures-prerequisites.md index e97fff63772..a6bbf9e142c 100644 --- a/.claude/skills/cqa-11-procedures-prerequisites.md +++ b/.claude/skills/cqa-11-procedures-prerequisites.md @@ -12,13 +12,29 @@ If a procedure includes prerequisites: - Do not exceed 10 prerequisites - Do not include steps in prerequisites (prerequisites are completed states, not actions) -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run procedure prerequisites verification:** ```bash -./build/scripts/cqa-11-procedures-prerequisites.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-11-procedures-prerequisites.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-11-procedures-prerequisites.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-11-procedures-prerequisites.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Checks `.Prerequisites` label uses correct plural form - Validates prerequisite count (≤10 per procedure) diff --git a/.claude/skills/cqa-12-content-is-grammatically-correct-and-follows-rules.md b/.claude/skills/cqa-12-content-is-grammatically-correct-and-follows-rules.md index d8b730ca4dd..79d7f5002e7 100644 --- a/.claude/skills/cqa-12-content-is-grammatically-correct-and-follows-rules.md +++ b/.claude/skills/cqa-12-content-is-grammatically-correct-and-follows-rules.md @@ -3,8 +3,8 @@ ## Content is grammatically correct and follows rules of American English grammar **References:** -- [Red Hat Supplementary Style Guide](../resources/red-hat-ssg.md) - Grammar, style, terminology -- [Red Hat Peer Review Guide](../resources/red-hat-peer-review.md) - Editorial quality standards +- [Red Hat Supplementary Style Guide (CQA extract)](../resources/red-hat-ssg-for-cqa.md) - Grammar, style, terminology +- [Red Hat Peer Review Guide (CQA extract)](../resources/red-hat-peer-review-for-cqa.md) - Editorial quality standards **Quality Level:** Required/non-negotiable @@ -12,13 +12,29 @@ Content must follow American English grammar, Red Hat style standards: correct g **IMPORTANT:** Try hard to fix ALL Vale issues — errors, warnings, AND suggestions. Do not skip warnings or suggestions unless they are clearly false positives (e.g., code blocks, attributes.adoc). Every unfixed issue degrades content quality. -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run grammar and style verification:** ```bash -./build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Runs Vale with `.vale.ini` config on all included files - Reports grammar, spelling, style, and terminology issues diff --git a/.claude/skills/cqa-13-information-is-conveyed-using-the-correct-content.md b/.claude/skills/cqa-13-information-is-conveyed-using-the-correct-content.md index 7c4b3e73f19..7af899a5628 100644 --- a/.claude/skills/cqa-13-information-is-conveyed-using-the-correct-content.md +++ b/.claude/skills/cqa-13-information-is-conveyed-using-the-correct-content.md @@ -2,7 +2,7 @@ ## Information is conveyed using the correct content type -**Reference:** [Red Hat Web Content Types](../resources/content-types.md) +**Reference:** [Product Documentation Content Types (CQA extract)](../resources/content-types-for-cqa.md) **Quality Level:** Required/non-negotiable @@ -60,15 +60,31 @@ While RHDH documentation primarily uses Product documentation with modular struc - **FAQ**: Frequently asked questions (evolve-loop content) - **In-app content**: UI microcopy, tooltips, help text -**See [content-types.md](../resources/content-types.md) for complete definitions and guidance on choosing content types.** +**See [content-types-for-cqa.md](../resources/content-types-for-cqa.md) for complete definitions and guidance on choosing content types.** -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run content type verification:** ```bash -./build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Validates PROCEDURE files have `.Procedure` section with steps - Validates CONCEPT files do not have `.Procedure` sections diff --git a/.claude/skills/cqa-14-no-broken-links.md b/.claude/skills/cqa-14-no-broken-links.md index 6247e9fcbae..4f5e68beaeb 100644 --- a/.claude/skills/cqa-14-no-broken-links.md +++ b/.claude/skills/cqa-14-no-broken-links.md @@ -9,13 +9,29 @@ All links in the documentation must be valid and accessible. This includes: - External URLs to Red Hat domains and third-party sites - Cross-title links to other RHDH documentation titles -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run broken links verification:** ```bash -./build/scripts/cqa-14-no-broken-links.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-14-no-broken-links.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-14-no-broken-links.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-14-no-broken-links.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Checks `include::` references point to existing files - Checks `image::` references point to existing files diff --git a/.claude/skills/cqa-15-redirects-if-needed-are-in-place-and-work-correc.md b/.claude/skills/cqa-15-redirects-if-needed-are-in-place-and-work-correc.md deleted file mode 100644 index 1e6ac03fdf9..00000000000 --- a/.claude/skills/cqa-15-redirects-if-needed-are-in-place-and-work-correc.md +++ /dev/null @@ -1,36 +0,0 @@ -# CQA #15 - URLs and links - -## Redirects (if needed) are in place and work correctly - -**Quality Level:** Required/non-negotiable - - -## Command - -**Run redirects check:** -```bash -./build/scripts/cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh [--fix] titles/<your-title>/master.adoc -``` - -**What the script does:** -- Checks git history for renamed files in recent 5 commits -- Checks git history for deleted files in recent 5 commits -- Reports files that may need redirects -- Informational only (does not fail on findings) - -**Target Results:** -- ✅ No renamed or deleted files detected, or redirects reviewed - -## Assessment - -```yaml - -title: - -status: No data # Meets criteria | Mostly meets | Mostly does not meet | Does not meet | Not applicable - -notes: | - - - -``` diff --git a/.claude/skills/cqa-15-redirects.md b/.claude/skills/cqa-15-redirects.md new file mode 100644 index 00000000000..67cf99e69fa --- /dev/null +++ b/.claude/skills/cqa-15-redirects.md @@ -0,0 +1,52 @@ +# CQA #15 - URLs and links + +## Redirects (if needed) are in place and work correctly + +**Quality Level:** Required/non-negotiable + + +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. + +```bash +# 1. Report issues +./build/scripts/cqa-15-redirects.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-15-redirects.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-15-redirects.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues +``` + +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + +**What the script does:** +- Checks git history for renamed files in recent 5 commits +- Checks git history for deleted files in recent 5 commits +- Reports files that may need redirects +- Informational only (does not fail on findings) + +**Target Results:** +- ✅ No renamed or deleted files detected, or redirects reviewed + +## Assessment + +```yaml + +title: + +status: No data # Meets criteria | Mostly meets | Mostly does not meet | Does not meet | Not applicable + +notes: | + + + +``` diff --git a/.claude/skills/cqa-16-official-product-names-are-used.md b/.claude/skills/cqa-16-official-product-names-are-used.md index da74a80a55f..10691f5c713 100644 --- a/.claude/skills/cqa-16-official-product-names-are-used.md +++ b/.claude/skills/cqa-16-official-product-names-are-used.md @@ -96,12 +96,25 @@ The Vale rule `.vale-styles/DeveloperHub/Attributes.yml` checks for hardcoded product names inline during editing. It flags the same patterns as the script below. -### Run Complete Validation Script - ```bash +# 1. Report issues ./build/scripts/cqa-16-official-product-names-are-used.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-16-official-product-names-are-used.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-16-official-product-names-are-used.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script validates:** - All Red Hat product names (Developer Hub, OpenShift, ACS, Keycloak, RHEL, TAS, TPA, etc.) - All partner platform names (AWS, Azure, Google Cloud, EKS, AKS, GKE) @@ -109,11 +122,6 @@ The Vale rule `.vale-styles/DeveloperHub/Attributes.yml` checks for hardcoded pr **Skips:** source blocks, attribute definitions, comments, attributes.adoc, snippets -**Fix mode:** -```bash -./build/scripts/cqa-16-official-product-names-are-used.sh --fix titles/<your-title>/master.adoc -``` - After fixing, manually verify first occurrence in abstract uses `{product}` (not `{product-short}`). ### Check for Missing First Occurrence diff --git a/.claude/skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md b/.claude/skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md index 9ce6975723a..128b5197edd 100644 --- a/.claude/skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md +++ b/.claude/skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md @@ -7,13 +7,29 @@ **Quality Level:** Required/non-negotiable -## Command +## Automated Validation and Fixing + +**IMPORTANT:** ALWAYS run the script first, then fix. Do not manually inspect files without running the script. -**Run legal disclaimers verification:** ```bash -./build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh [--fix] titles/<your-title>/master.adoc +# 1. Report issues +./build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh titles/<your-title>/master.adoc + +# 2. Auto-fix what can be fixed +./build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh --fix titles/<your-title>/master.adoc + +# 3. Re-run to verify remaining issues +./build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh titles/<your-title>/master.adoc + +# 4. Attempt manual fixes for remaining issues + +# 5. Re-run to verify remaining issues + +# 6. If issues remain, report as failed and list the remaining issues ``` +**Additional options:** Use `--all` to run across all titles. Output markers: `[AUTOFIX]`, `[FIXED]`, `[MANUAL]`, `[-> CQA #NN]`. + **What the script does:** - Checks files mentioning "Technology Preview" include the official disclaimer snippet - Checks files mentioning "Developer Preview" include the official disclaimer snippet diff --git a/.claude/skills/cqa-master-workflow.md b/.claude/skills/cqa-main-workflow.md similarity index 64% rename from .claude/skills/cqa-master-workflow.md rename to .claude/skills/cqa-main-workflow.md index 2a2cd5b490a..f7ada4e2cef 100644 --- a/.claude/skills/cqa-master-workflow.md +++ b/.claude/skills/cqa-main-workflow.md @@ -1,6 +1,6 @@ -# CQA 2.1 Master Workflow +# CQA 2.1 Main Workflow -Execute all 17 CQA requirements in optimal order. Each links to detailed skill with commands, criteria, and fixes. +Execute all 19 CQA requirements in optimal order. Each links to detailed skill with commands, criteria, and fixes. **CRITICAL EXECUTION RULES:** 1. **Run skills in the exact order listed** - The sequence is optimized to minimize conflicts @@ -21,16 +21,47 @@ Execute all 17 CQA requirements in optimal order. Each links to detailed skill w --- +## Unified Script Interface + +Run all 19 CQA checks in workflow order with `cqa.sh`: + +```bash +# Run all checks for a title +./build/scripts/cqa.sh titles/<your-title>/master.adoc + +# Auto-fix all checks for a title +./build/scripts/cqa.sh --fix titles/<your-title>/master.adoc + +# Run all checks across all titles +./build/scripts/cqa.sh --all +``` + +Or run individual scripts (all share a common interface via `cqa-lib.sh`): + +```bash +./build/scripts/cqa-XX-*.sh [--fix] [--all] titles/<your-title>/master.adoc +``` + +**Output markers:** +- `[AUTOFIX]` - Can be auto-fixed with `--fix` +- `[FIXED]` - Was auto-fixed (in `--fix` mode) +- `[MANUAL]` - Requires human judgment +- `[-> CQA #NN AUTOFIX]` / `[-> CQA #NN MANUAL]` - Delegated to another CQA script + +--- + ## Process **For each requirement below:** Run validation/fixes until no new changes occur. **For the entire sequence:** Re-run all requirements until the full workflow is stable. +- [ ] **CQA #0:** Orphaned modules — `cqa-00-orphaned-modules.sh` — Find/delete unreferenced files +- [ ] **CQA #0:** Directory structure — `cqa-00-directory-structure.sh` — Verify `<category>_<context>` naming - [ ] Resources current. [Update all resources](update-all-resources.md) - [ ] **CQA #3:** [Content is modularized](cqa-03-content-is-modularized.md) - Modular structure, correct metadata/prefixes - [ ] **CQA #13:** [Correct content type](cqa-13-information-is-conveyed-using-the-correct-content.md) - Content matches declared type +- [ ] **CQA #10:** [Titles](cqa-10-titles-are-brief-complete-and-descriptive.md) - Brief, complete, descriptive; renames files/IDs - [ ] **CQA #8:** [Short description content](cqa-08-short-description-content.md) - WHY user should read, benefit-focused - [ ] **CQA #9:** [Short description format](cqa-09-short-description-format.md) - `[role="_abstract"]`, 50-300 chars -- [ ] **CQA #10:** [Titles](cqa-10-titles-are-brief-complete-and-descriptive.md) - Brief, complete, descriptive - [ ] **CQA #11:** [Prerequisites](cqa-11-procedures-prerequisites.md) - `.Prerequisites` label, max 10 items, completed states - [ ] **CQA #2:** [Assembly structure](cqa-02-assembly-structure.md) - Introduction + includes only - [ ] **CQA #5:** [Required elements](cqa-05-modular-elements-checklist.md) - All mandatory elements present @@ -42,30 +73,31 @@ Execute all 17 CQA requirements in optimal order. Each links to detailed skill w - [ ] **CQA #12:** [Grammar](cqa-12-content-is-grammatically-correct-and-follows-rules.md) - 0 errors, American English - [ ] **CQA #17:** [Disclaimers](cqa-17-includes-appropriate-legal-approved-disclaimers-f.md) - Legal-approved for Tech/Dev Preview - [ ] **CQA #14:** [No broken links](cqa-14-no-broken-links.md) - All xrefs/external links valid, build succeeds -- [ ] **CQA #15:** [Redirects](cqa-15-redirects-if-needed-are-in-place-and-work-correc.md) - Redirects in place if needed +- [ ] **CQA #15:** [Redirects](cqa-15-redirects.sh) - Redirects in place if needed --- ## Completion -**All 17 Requirements:** -- [ ] CQA #1: Vale DITA ✓ -- [ ] CQA #2: Assembly structure ✓ -- [ ] CQA #3: Modularized ✓ -- [ ] CQA #4: Templates ✓ -- [ ] CQA #5: Required elements ✓ -- [ ] CQA #6: One story ✓ -- [ ] CQA #7: TOC depth ✓ -- [ ] CQA #8: Short desc content ✓ -- [ ] CQA #9: Short desc format ✓ -- [ ] CQA #10: Titles ✓ -- [ ] CQA #11: Prerequisites ✓ -- [ ] CQA #12: Grammar ✓ -- [ ] CQA #13: Content type ✓ -- [ ] CQA #14: No broken links ✓ -- [ ] CQA #15: Redirects ✓ -- [ ] CQA #16: Product names ✓ -- [ ] CQA #17: Disclaimers ✓ +**All 18 Requirements:** +- [ ] CQA #0: Orphaned modules +- [ ] CQA #1: Vale DITA +- [ ] CQA #2: Assembly structure +- [ ] CQA #3: Modularized +- [ ] CQA #4: Templates +- [ ] CQA #5: Required elements +- [ ] CQA #6: One story +- [ ] CQA #7: TOC depth +- [ ] CQA #8: Short desc content +- [ ] CQA #9: Short desc format +- [ ] CQA #10: Titles +- [ ] CQA #11: Prerequisites +- [ ] CQA #12: Grammar +- [ ] CQA #13: Content type +- [ ] CQA #14: No broken links +- [ ] CQA #15: Redirects +- [ ] CQA #16: Product names +- [ ] CQA #17: Disclaimers **Final Steps:** - [ ] All automation scripts run @@ -73,13 +105,13 @@ Execute all 17 CQA requirements in optimal order. Each links to detailed skill w - [ ] Commit with JIRA reference - [ ] PR created -**CRITICAL:** Do not claim completion unless ALL checkboxes marked ✓ +**CRITICAL:** Do not claim completion unless ALL checkboxes marked --- ## Assessment -**Title:** _____ | **JIRA:** RHIDP-_____ | **Status:** ⬜ In Progress | ⬜ Complete | ⬜ Blocked +**Title:** _____ | **JIRA:** RHIDP-_____ | **Status:** In Progress | Complete | Blocked **Results:** - Vale DITA: _____ errors, _____ warnings diff --git a/.claude/skills/get-title-files.md b/.claude/skills/get-title-files.md index 9291c19e159..cf4ca927f94 100644 --- a/.claude/skills/get-title-files.md +++ b/.claude/skills/get-title-files.md @@ -10,64 +10,28 @@ Many CQA requirements need to validate or process ALL files that make up a docum - All modules included by those assemblies - Recursively, all nested includes -This helper provides a dedicated script to extract that complete file list for use in CQA validation commands. +This functionality is integrated into `cqa-lib.sh` via `cqa_collect_files()`. -## Script +## Usage -**Location:** [build/scripts/list-all-included-files-starting-from.sh](../../build/scripts/list-all-included-files-starting-from.sh) - -**Usage:** -```bash -./build/scripts/list-all-included-files-starting-from.sh titles/<title-name>/master.adoc -``` - -**Output:** Space-separated list of all files (master.adoc + all recursively included files) on a single line - -**How it works:** +All CQA scripts automatically use `cqa_collect_files()` when processing a title. The function: 1. Starts from the specified file (e.g., master.adoc) 2. Recursively extracts all `include::` statements -3. Resolves relative paths (../) correctly -4. Avoids infinite loops by tracking processed files -5. Returns sorted, deduplicated list on a single line - -## Example Usage - -**For titles/install-rhdh-osd-gcp/master.adoc:** +3. Resolves relative paths correctly +4. Deduplicates by realpath (handles symlinks) +5. Stores results in `_CQA_COLLECTED_FILES` array ```bash -./build/scripts/list-all-included-files-starting-from.sh titles/install-rhdh-osd-gcp/master.adoc -``` - -**Example output:** -``` -/path/to/artifacts/attributes.adoc /path/to/modules/install-rhdh-osd-gcp/proc-install-product-on-osd-short-on-gcp-short-using-the-helm-chart.adoc /path/to/modules/install-rhdh-osd-gcp/proc-install-product-on-osd-short-on-gcp-short-using-the-operator.adoc titles/install-rhdh-osd-gcp/master.adoc +# In a CQA script (after sourcing cqa-lib.sh): +cqa_collect_files "$target" +for file in "${_CQA_COLLECTED_FILES[@]}"; do + # process each file +done ``` -## Using with Vale - -**Vale DITA validation on title files:** -```bash -vale --config .vale-dita-only.ini \ - $(./build/scripts/list-all-included-files-starting-from.sh titles/<title-name>/master.adoc) -``` - -**Vale style validation on title files:** -```bash -vale --config .vale.ini \ - $(./build/scripts/list-all-included-files-starting-from.sh titles/<title-name>/master.adoc) -``` - -## Notes - -- Recursively follows all `include::` statements -- Handles relative paths (../) and absolute paths correctly -- Avoids infinite loops by tracking already-processed files -- Returns sorted, deduplicated list -- Works with attribute substitution in include paths (dynamically resolves {platform-id}, {context}, etc.) - -## When to Use +## When Used -Use this helper for CQA requirements that need to validate all title content: +Used by CQA requirements that validate all title content: - CQA #1: Vale DITA validation - CQA #8: Short description content - CQA #9: Short description format diff --git a/.claude/skills/update-all-resources.md b/.claude/skills/update-all-resources.md index fa8198aaded..36fd1795d86 100644 --- a/.claude/skills/update-all-resources.md +++ b/.claude/skills/update-all-resources.md @@ -60,4 +60,4 @@ done ## Usage in CQA Master Workflow -Phase 0 uses this skill to ensure all resources are current before starting CQA 2.1 compliance assessment. See [cqa-master-workflow.md](cqa-master-workflow.md) Phase 0. +Phase 0 uses this skill to ensure all resources are current before starting CQA 2.1 compliance assessment. See [cqa-main-workflow.md](cqa-main-workflow.md) Phase 0. diff --git a/.github/workflows/content-quality-assessment.yml b/.github/workflows/content-quality-assessment.yml index 2774a2ab81e..51c2cff1fa9 100644 --- a/.github/workflows/content-quality-assessment.yml +++ b/.github/workflows/content-quality-assessment.yml @@ -13,6 +13,8 @@ on: - 'assemblies/**' - 'modules/**' - 'artifacts/**' + - 'images/**' + - 'build/scripts/**' branches: - main - rhdh-1.** @@ -60,11 +62,11 @@ jobs: id: check run: | if [[ "${{ needs.check-commit-author.outputs.is_authorized }}" == "true" ]]; then - echo "✓ Commit author is in rhdh team - using internal environment" + echo "Commit author is in rhdh team - using internal environment" elif [[ "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]]; then - echo "✓ Internal PR (not from fork) - using internal environment" + echo "Internal PR (not from fork) - using internal environment" else - echo "✓ External PR from fork from non-rhdh team member - using external environment for security" + echo "External PR from fork from non-rhdh team member - using external environment for security" fi cqa-check: @@ -75,214 +77,79 @@ jobs: contents: read pull-requests: write checks: write + security-events: write steps: - - name: Checkout base branch to get trusted scripts + - name: Checkout main branch for trusted scripts uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ github.event.pull_request.base.ref }} + ref: main path: trusted-scripts + sparse-checkout: build/scripts - - name: Checkout PR branch for content to check + - name: Checkout PR branch for content uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} path: pr-content - - name: Setup reviewdog - uses: reviewdog/action-setup@19ad6fc8b7358ccdc9fd4a25c75dd93eeb097e1e # v1 - with: - reviewdog_version: latest - - - name: Get changed .adoc files - id: changed-files + - name: Copy trusted scripts into PR content run: | - cd pr-content - # Get list of changed .adoc files - git fetch origin ${{ github.base_ref }} - CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '\.adoc$' || echo "") - echo "changed_files<<EOF" >> $GITHUB_OUTPUT - echo "$CHANGED_FILES" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - # Count changed files - if [ -n "$CHANGED_FILES" ]; then - COUNT=$(echo "$CHANGED_FILES" | wc -l) - else - COUNT=0 - fi - echo "count=$COUNT" >> $GITHUB_OUTPUT - - - name: Run orphaned modules check - id: orphaned-check + cp trusted-scripts/build/scripts/cqa-lib.sh pr-content/build/scripts/cqa-lib.sh + cp trusted-scripts/build/scripts/cqa.sh pr-content/build/scripts/cqa.sh + for script in trusted-scripts/build/scripts/cqa-[0-9]*.sh; do + cp "$script" "pr-content/build/scripts/$(basename "$script")" + done + chmod +x pr-content/build/scripts/cqa*.sh + + - name: Run CQA checks (checklist) + id: cqa-checklist run: | - # Copy trusted script from base branch - cp trusted-scripts/build/scripts/fix-orphaned-modules.sh pr-content/build/scripts/fix-orphaned-modules.sh cd pr-content - chmod +x build/scripts/fix-orphaned-modules.sh - - # Capture output - OUTPUT=$(./build/scripts/fix-orphaned-modules.sh 2>&1) - - # Extract orphaned files count - ORPHANED_COUNT=$(echo "$OUTPUT" | grep "Orphaned files found:" | awk '{print $4}') - echo "orphaned_count=$ORPHANED_COUNT" >> $GITHUB_OUTPUT - - # Save full output for comment + OUTPUT=$(./build/scripts/cqa.sh --all 2>&1 || true) echo "output<<EOF" >> $GITHUB_OUTPUT echo "$OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - - name: Apply content type fixes and generate suggestions - id: content-type-fixes - if: steps.changed-files.outputs.count > 0 + - name: Run CQA checks (SARIF) + id: cqa-sarif run: | - # Copy trusted script from base branch - cp trusted-scripts/build/scripts/cqa-03-content-is-modularized.sh pr-content/build/scripts/cqa-03-content-is-modularized.sh cd pr-content - chmod +x build/scripts/cqa-03-content-is-modularized.sh + ./build/scripts/cqa.sh --all --format json > ${{ github.workspace }}/cqa-results.sarif 2>/dev/null || true - # Apply fixes to changed files - while IFS= read -r file; do - if [ -n "$file" ] && [ -f "$file" ]; then - ./build/scripts/cqa-03-content-is-modularized.sh "$file" >/dev/null 2>&1 || true - fi - done <<< "${{ steps.changed-files.outputs.changed_files }}" - - # Generate diff for reviewdog - git diff > /tmp/content-type.diff - - # Check if there are changes - if [ -s /tmp/content-type.diff ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - else - echo "has_changes=false" >> $GITHUB_OUTPUT - fi - - # Reset changes (don't commit) - git checkout . - - - name: Post content type suggestions via reviewdog - if: steps.content-type-fixes.outputs.has_changes == 'true' - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }} - run: | - cd pr-content - cat /tmp/content-type.diff | reviewdog -f=diff -f.diff.strip=0 \ - -name="CQA: Content Type" \ - -reporter=github-pr-review \ - -filter-mode=nofilter \ - -fail-on-error=false \ - -level=warning - - - name: Apply title/ID/filename fixes and generate suggestions - id: title-fixes - if: steps.changed-files.outputs.count > 0 - run: | - # Copy trusted script from base branch - cp trusted-scripts/build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh pr-content/build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh - cd pr-content - chmod +x build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh - - # Apply fixes to changed assemblies/modules/titles - while IFS= read -r file; do - if [ -n "$file" ] && [ -f "$file" ]; then - if [[ "$file" =~ (assemblies|modules|titles) ]]; then - ./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh "$file" >/dev/null 2>&1 || true - fi - fi - done <<< "${{ steps.changed-files.outputs.changed_files }}" - - # Generate diff for reviewdog (only content changes, not file renames) - git diff > /tmp/title-fixes.diff - - # Check if there are changes - if [ -s /tmp/title-fixes.diff ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - else - echo "has_changes=false" >> $GITHUB_OUTPUT - fi - - # Reset changes (don't commit) - git checkout . - - - name: Post title/ID/filename suggestions via reviewdog - if: steps.title-fixes.outputs.has_changes == 'true' - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }} - run: | - cd pr-content - cat /tmp/title-fixes.diff | reviewdog -f=diff -f.diff.strip=0 \ - -name="CQA: Title/ID/Filename" \ - -reporter=github-pr-review \ - -filter-mode=nofilter \ - -fail-on-error=false \ - -level=warning + - name: Upload SARIF to GitHub Code Scanning + if: always() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: cqa-results.sarif + category: content-quality-assessment + continue-on-error: true - - name: Post CQA summary as PR comment + - name: Post CQA checklist as PR comment if: always() uses: actions/github-script@v7 with: github-token: ${{ secrets.RHDH_BOT_TOKEN || secrets.GITHUB_TOKEN }} script: | - const orphanedCount = '${{ steps.orphaned-check.outputs.orphaned_count }}' || '0'; - const contentTypeChanged = '${{ steps.content-type-fixes.outputs.has_changes }}' === 'true'; - const titleChanged = '${{ steps.title-fixes.outputs.has_changes }}' === 'true'; - const changedFilesCount = '${{ steps.changed-files.outputs.count }}'; - - let comment = '## 📋 Content Quality Assessment Results\n\n'; - comment += `**Changed .adoc files in this PR:** ${changedFilesCount}\n\n`; - - // Orphaned modules check - comment += '### 🗑️ Orphaned Modules Check\n'; - if (orphanedCount === '0') { - comment += '✅ No orphaned files found\n\n'; - } else { - comment += `⚠️ Found **${orphanedCount}** orphaned file(s)\n\n`; - comment += '<details>\n<summary>Click to view orphaned files</summary>\n\n```\n'; - comment += `${{ steps.orphaned-check.outputs.output }}`; - comment += '\n```\n</details>\n\n'; - comment += '💡 To delete orphaned files, run:\n```bash\n./build/scripts/fix-orphaned-modules.sh --execute\n```\n\n'; - } - - // Content type check - comment += '### 📝 Content Type Validation\n'; - if (changedFilesCount === '0') { - comment += 'ℹ️ No .adoc files changed in this PR\n\n'; - } else if (!contentTypeChanged) { - comment += '✅ All changed files have correct content type metadata\n\n'; - } else { - comment += '💡 **Code suggestions posted** for content type fixes\n'; - comment += 'Click "View changes" in the Files tab to see and apply suggestions\n\n'; - } - - // Title/ID/filename alignment check - comment += '### 🏷️ Title/ID/Filename Alignment\n'; - if (changedFilesCount === '0') { - comment += 'ℹ️ No .adoc files changed in this PR\n\n'; - } else if (!titleChanged) { - comment += '✅ All changed files have aligned title/ID/filename\n\n'; - } else { - comment += '💡 **Code suggestions posted** for title/ID/filename alignment\n'; - comment += 'Click "View changes" in the Files tab to see and apply suggestions\n\n'; - comment += '⚠️ Note: File renames must be done manually using `git mv`\n\n'; - } + const cqaOutput = `${{ steps.cqa-checklist.outputs.output }}`; - // Footer + let comment = '## Content Quality Assessment Results\n\n'; + comment += '```\n'; + comment += cqaOutput; + comment += '\n```\n\n'; comment += '---\n'; - comment += '*Automated CQA check • See [CQA.md](../blob/main/CQA.md) for manual verification steps*\n'; + comment += '*Automated CQA check run on the entire repository*\n'; - // Find existing CQA comment + // Find existing CQA comment and update or create const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); - const botComment = comments.find(comment => - comment.user.type === 'Bot' && - comment.body.includes('Content Quality Assessment Results') + const botComment = comments.find(c => + c.user.type === 'Bot' && + c.body.includes('Content Quality Assessment Results') ); if (botComment) { diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 4748799464e..10a952d3825 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -123,25 +123,28 @@ jobs: -level=warning - name: Post shellcheck summary as PR comment - if: always() && steps.changed-files.outputs.count > 0 + if: always() uses: actions/github-script@v7 with: github-token: ${{ secrets.RHDH_BOT_TOKEN || secrets.GITHUB_TOKEN }} script: | - const changedFilesCount = '${{ steps.changed-files.outputs.count }}'; + const changedFilesCount = parseInt('${{ steps.changed-files.outputs.count }}', 10); const changedFiles = `${{ steps.changed-files.outputs.changed_files }}`.split('\n').filter(f => f); - let comment = '## 🐚 Shellcheck Analysis Results\n\n'; - comment += `**Changed shell scripts in this PR:** ${changedFilesCount}\n\n`; + let comment = '## Shellcheck Analysis Results\n\n'; - comment += '### Scripts analyzed:\n'; - changedFiles.forEach(file => { - comment += `- \`${file}\`\n`; - }); - comment += '\n'; - - comment += '💡 Check the **Files changed** tab for detailed shellcheck suggestions.\n\n'; - comment += 'All findings are reported as warnings and won\'t block the PR.\n\n'; + if (changedFilesCount > 0) { + comment += `**Changed shell scripts in this PR:** ${changedFilesCount}\n\n`; + comment += '### Scripts analyzed:\n'; + changedFiles.forEach(file => { + comment += `- \`${file}\`\n`; + }); + comment += '\n'; + comment += 'Check the **Files changed** tab for detailed shellcheck suggestions.\n\n'; + comment += 'All findings are reported as warnings and won\'t block the PR.\n\n'; + } else { + comment += 'No shell scripts were changed in this PR.\n\n'; + } comment += '---\n'; comment += '*Automated shellcheck analysis • See [shellcheck.net](https://www.shellcheck.net/) for details*\n'; @@ -153,9 +156,9 @@ jobs: issue_number: context.issue.number, }); - const botComment = comments.find(comment => - comment.user.type === 'Bot' && - comment.body.includes('Shellcheck Analysis Results') + const botComment = comments.find(c => + c.user.type === 'Bot' && + c.body.includes('Shellcheck Analysis Results') ); if (botComment) { @@ -165,7 +168,7 @@ jobs: comment_id: botComment.id, body: comment }); - } else { + } else if (changedFilesCount > 0) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.vscode/settings.json b/.vscode/settings.json index b72c532da00..752f8822489 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "argo", "argoproj", "artifactory", + "asciidoctor", "cron", "Entra", "GITOPS", @@ -27,6 +28,8 @@ "rhcr", "rhdh", "rollouts", + "sarif", + "SARIF", "scaffolder", "servicenow", "Skopeo", diff --git a/CQA.md b/CQA.md deleted file mode 100644 index bc47450ea89..00000000000 --- a/CQA.md +++ /dev/null @@ -1,267 +0,0 @@ -# CQA 2.1 Compliance - -Content Quality Assessment for Red Hat Developer Hub documentation in preparation for Adobe Experience Manager (AEM) migration. - -## Quick Start - -For CQA 2.1 compliance work: - -``` -Read .claude/skills/cqa-master-workflow.md - -CQA 2.1 compliance for [title name or path to master.adoc] -JIRA: RHIDP-[number] -``` - -The master workflow executes all 17 CQA requirements systematically with idempotency checks. - -## Reference Documentation - -- **Master workflow**: [.claude/skills/cqa-master-workflow.md](.claude/skills/cqa-master-workflow.md) - Execute all 17 requirements -- **Common issues**: [.claude/resources/cqa-common-issues.md](.claude/resources/cqa-common-issues.md) - Troubleshooting guide -- **Procedure style**: [.claude/resources/procedure-style-guide.md](.claude/resources/procedure-style-guide.md) - Formatting reference -- **Vale warnings**: [.claude/resources/vale-acceptable-warnings.md](.claude/resources/vale-acceptable-warnings.md) - Acceptable warnings - ---- - -## CQA 2.1 Requirements - -**Objective:** Make the title compliant with CQA 2.1 – Content Quality Assessment for Adobe Experience Manager (AEM) migration. - -**Acceptance Criteria:** - -1. **Vale DITA validation**: Run `vale --config .vale-dita-only.ini` against all AsciiDoc files included in the title. Result must be 0 errors, with only acceptable warnings documented (callouts, false positive concept links), 0 suggestions. - -2. **Content is modularized following Red Hat modular documentation rules** (see link:https://redhat-documentation.github.io/modular-docs/[Red Hat Modular Documentation Reference Guide]): - - Use official templates: assemblies, concept modules, procedure modules, reference modules, snippets - - Each module has correct `:_mod-docs-content-type:` metadata (ASSEMBLY, CONCEPT, PROCEDURE, REFERENCE, SNIPPET) - - Proper file naming conventions: `assembly-title.adoc`, `con-title.adoc`, `proc-title.adoc`, `ref-title.adoc`, `snip-*.adoc` (Convert title to lowercase with hyphens: "Install the Operator" → `proc-install-the-operator.adoc`) - * Standard prefixes required: `assembly-`, `con-`, `proc-`, `ref-`, `snip-` - * Alternative prefixes detected as violations: `concept-`, `procedure-`, `reference-`, `con_`, `proc_`, `ref_`, `snip_` (use `git mv` to rename) - - Anchors follow format: `[id="title_{context}"]` (Convert title to lowercase with hyphens: "Install the Operator" → `install-the-operator_{context}`) - - **No modules nested within modules** - modules should only be included in assemblies - - **Snippets** (`:_mod-docs-content-type: SNIPPET`) contain reusable content blocks but NO structural elements (no anchors, H1 headings, or block titles like .Prerequisites) - - **Module-specific rules**: - * Concept modules: Explain "what" and "why"; no step-by-step instructions; optional subheadings allowed - * Procedure modules: Step-by-step instructions only; NO custom subheadings (only standard: .Prerequisites, .Procedure, .Verification, .Troubleshooting, .Next steps); `.Procedure` section required; numbered lists (`. step`) for multi-step (2+), unnumbered list (`* step`) for single-step (cqa-03-content-is-modularized.sh auto-converts single numbered steps to unnumbered) - * Reference modules: Lookup data in lists/tables; optional subheadings allowed for complex content - * Assemblies: Introduction + include statements only; no detailed content - -3. **Assemblies contain only an introductory section, and then include statements for modules**: - - Assemblies have brief introduction (single concise paragraph required) - - No detailed content in assemblies (move to modules) - "a module should not contain another module" - - Introduction explains what the user accomplishes - - Optional components: Prerequisites (applying to entire assembly), Additional resources (links only, no descriptive text) - - Context is set and restored properly when nesting assemblies: - ```asciidoc - // At top of nested assembly - ifdef::context[:parent-context: {context}] - - // At end of nested assembly - ifdef::parent-context[:context: {parent-context}] - ifndef::parent-context[:!context:] - ``` - - No commented-out content - - All includes use `leveloffset=+1` (or appropriate level) - - All module/assembly titles must be H1 (`= Heading`) - -4. **Assemblies are one user story each**: - - Each assembly addresses a single user goal or task - - Clear scope and purpose - -5. **Content is not deeply nested in the TOC (≤3 levels)**: - - Maximum TOC depth: 3 levels - - Count from the title level (assembly = level 1, first include = level 2, etc.) - -6. **Modules and assemblies start with a clear short description**: - - Every module and assembly must have a **single, concise introductory paragraph** (Red Hat modular docs requirement) - - Mark with `[role="_abstract"]` immediately after the title for DITA compatibility - - Introduction should be 50-300 characters for AEM migration - - **Purpose of introduction**: - * Concept modules: Answer "What is this?" and "Why should users care?" - * Procedure modules: Explain what the user accomplishes - * Reference modules: Describe the data being presented - * Assemblies: Explain what user story/goal is addressed - - Introduction enables users to quickly determine if content is relevant - - Describes the value/purpose (not just "Learn about X") - - If an introduction paragraph already exists, add `[role="_abstract"]` to mark it (do NOT duplicate) - - Remove self-referential language ("Learn about", "This section describes", "This module explains") - -7. **In AsciiDoc, short descriptions must be**: - - Added as `[role="_abstract"]` immediately after the title - - **IMPORTANT**: The `[role="_abstract"]` line cannot be followed by an empty line - the abstract content must start on the very next line - - Between 50-300 characters - - Not duplicating existing content—mark the existing intro paragraph when appropriate - -8. **Titles are brief, complete, and descriptive** (following Red Hat modular documentation guide): - - **Concept modules**: Use noun phrases (e.g., "High availability with database and cache layers") - - **Procedure modules**: Use imperative form per Style Guide (e.g., "Install the Operator", "Configure the database") - * Note: Red Hat modular docs specify gerund phrases (e.g., "Installing the Operator"), but Style Guide requires imperative form. Only imperative form is acceptable. - - **Reference modules**: Use noun phrases (e.g., "Sizing requirements for Red Hat Developer Hub", "Configuration options reference") - - **Assembly titles**: - * Task-based assemblies: Use imperative form per Style Guide (e.g., "Install the Operator", "Configure the database") - * Non-task assemblies: Use noun phrases (e.g., "API reference") - - Avoid imperative verbs in concept/reference titles (bad: "Achieve high availability", good: "High availability") - -9. **If a procedure includes prerequisites, they are formatted correctly**: - - Use `.Prerequisites` block title (not a section heading `== Prerequisites`) - - Always use plural "Prerequisites" even for single item - - List prerequisites as bulleted items - - Prerequisites apply conditions or dependencies for the procedure - - N/A if no procedures in the title - - Standard procedure module sections (all optional except .Procedure): - * `.Prerequisites` - conditions that must be met - * `.Procedure` - the numbered steps (required) - * `.Verification` - how to confirm success - * `.Troubleshooting` - brief issue resolution (or link to separate troubleshooting procedure) - * `.Next steps` - links to related instructions only - * `.Additional resources` - links to related documentation - - **No custom subheadings allowed in procedures** - only use the standard sections above - -10. **Content is grammatically correct and uses American English**: - - Parallel structure in lists and phrases - - Correct verb agreement (e.g., "helps simplify and accelerate" not "helps simplify and accelerates") - - Proper grammar constructs (e.g., "by using" not just "using" after a noun) - -11. **Information is conveyed using the correct content type**: - - Concepts: explain what something is, why it matters - - Procedures: step-by-step instructions (numbered steps) - - References: tables, lists, specifications, sizing guides - -12. **No broken links**: - - All internal references use proper xref syntax - - All external links are valid Red Hat URLs or use attributes - - Cross-references use context-aware IDs - -13. **Official product names are used throughout**: - - Use attributes for product names: `{product}`, `{product-short}`, `{product-very-short}`, `{ocp-brand-name}`, `{company-name}` - - Follow Red Hat Official Product List (OPL) - - Never hardcode product names - -14. **Includes appropriate, legal-approved disclaimers for Technology Preview and Developer Preview features/content**: - - Tech Preview features have proper admonition blocks - - N/A if no preview features in the title - -**Process:** See [.claude/skills/cqa-master-workflow.md](.claude/skills/cqa-master-workflow.md) for complete execution workflow with idempotency checks. - ---- - -## Troubleshooting - -For common issues and fixes, see [.claude/resources/cqa-common-issues.md](.claude/resources/cqa-common-issues.md). - -## Automation Scripts - -Available scripts for CQA 2.1 compliance: - -```bash -# Content type detection and fixing -./build/scripts/cqa-03-content-is-modularized.sh [--fix] titles/<your-title>/master.adoc - -# Title/ID/filename alignment -./build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] titles/<your-title>/master.adoc - -# Orphaned module detection -./build/scripts/fix-orphaned-modules.sh # List only -./build/scripts/fix-orphaned-modules.sh --execute # Delete - -# Short description verification -./build/scripts/cqa-09-short-description-format.sh [--fix] titles/<your-title>/master.adoc - -# Build and preview generation -./build/scripts/build-ccutil.sh # Build all titles -./build/scripts/build-ccutil.sh -b <branch> # Build specific branch - -# Vale DITA validation -vale --config .vale-dita-only.ini titles/<your-title>/ - -# Vale language compliance -vale --config .vale.ini titles/<your-title>/ -``` - -## Style Guide - -For procedure formatting and style guidelines, see [.claude/resources/procedure-style-guide.md](.claude/resources/procedure-style-guide.md). - -## Validation - -### Validation Strategy - -Use a two-tier approach for efficient validation: - -**Fast validation after each change** - Use asciidoctor for quick syntax checks: -```bash -asciidoctor titles/<title-name>/master.adoc -o /tmp/test.html 2>&1 | grep -i error -``` - -This provides immediate feedback on: -- AsciiDoc syntax errors -- Broken include statements -- Invalid cross-references within the title -- Missing files or resources - -**Comprehensive validation at checkpoints** - Use build-ccutil.sh to validate all titles: -```bash -build/scripts/build-ccutil.sh 2>&1 | grep -E "(Unknown ID|fails to validate|Error)" -``` - -This validates: -- Cross-title xref statements (references from other titles) -- DITA compatibility -- Complete build chain -- All title dependencies - -**Why validate all titles:** Cross-references may exist from other titles pointing to the modules/assemblies you changed. The full build ensures these external references still work correctly. - -### DITA Validation (Required) - -Always run this command from the repository root to validate DITA compliance: - -```bash -vale --config .vale-dita-only.ini <path-to-assembly-file> -``` - -Or to validate all included files at once, run against the directory containing the modules. - -**Target**: 0 errors, only acceptable warnings (see Acceptable Warnings section), 0 suggestions - -### Red Hat Style Validation (Recommended) - -After DITA validation passes, run Vale with default config to check Red Hat style guidelines: - -```bash -vale assemblies/assembly-<name>.adoc modules/<category>/<name>/proc-*.adoc -``` - -**Target**: 0 errors, 0 warnings (suggestions about include directives can be ignored) - -### Build Validation (Required) - -After making changes, verify that all titles build successfully and all cross-references resolve: - -```bash -build/scripts/build-ccutil.sh -``` - -**Target**: All titles build successfully with "Finished html-single", no "Unknown ID" errors, exit code 0 - -This validates: -- All include statements are correct -- All cross-references (xrefs) resolve properly -- All AsciiDoc syntax is valid -- Content structure is compatible with DocBook XML transformation - -## Acceptable Warnings - -For details on acceptable Vale DITA warnings, see [.claude/resources/vale-acceptable-warnings.md](.claude/resources/vale-acceptable-warnings.md). - -## Success Criteria - -The work is complete when: -- Vale DITA validation shows: `0 errors, 0-15 acceptable warnings, 0 suggestions` -- Acceptable warnings are documented and verified as false positives or known limitations -- Vale Red Hat style validation shows: `0 errors, 0 warnings` -- Build validation (`build/scripts/build-ccutil.sh`) completes successfully with all titles built and no xref errors -- All 14 CQA 2.1 acceptance criteria are verified and met -- Changes are committed with proper JIRA reference in commit message -- Pull request is created with proper template and issue link diff --git a/assemblies/configure_configuring-rhdh/assembly-configure-external-postgresql-databases.adoc b/assemblies/configure_configuring-rhdh/assembly-configure-external-postgresql-databases.adoc index bfe9cda0475..d8c8e681ae0 100644 --- a/assemblies/configure_configuring-rhdh/assembly-configure-external-postgresql-databases.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-configure-external-postgresql-databases.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="configure-external-postgresql-databases_{context}"] = Configure external PostgreSQL databases @@ -22,3 +23,6 @@ include::../modules/configure_configuring-rhdh/proc-migrate-local-databases-to-a :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_configuring-rhdh/assembly-configure-high-availability-in-rhdh.adoc b/assemblies/configure_configuring-rhdh/assembly-configure-high-availability-in-rhdh.adoc index 484c29be9ec..14354218a4e 100644 --- a/assemblies/configure_configuring-rhdh/assembly-configure-high-availability-in-rhdh.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-configure-high-availability-in-rhdh.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="configure-high-availability-in-rhdh_{context}"] = Configure high availability in {product} @@ -33,3 +34,6 @@ include::../modules/configure_configuring-rhdh/proc-configure-high-availability- :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_configuring-rhdh/assembly-provision-and-use-your-custom-rhdh-configuration.adoc b/assemblies/configure_configuring-rhdh/assembly-provision-and-use-your-custom-rhdh-configuration.adoc index 938b069c105..3c8608396f5 100644 --- a/assemblies/configure_configuring-rhdh/assembly-provision-and-use-your-custom-rhdh-configuration.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-provision-and-use-your-custom-rhdh-configuration.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="provision-and-use-your-custom-rhdh-configuration_{context}"] = Provision and use your custom {product} configuration @@ -17,3 +18,6 @@ include::../modules/configure_configuring-rhdh/proc-use-the-rhdh-helm-chart-to-r :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_configuring-rhdh/assembly-rhdh-default-configuration.adoc b/assemblies/configure_configuring-rhdh/assembly-rhdh-default-configuration.adoc index 2c37e2b5089..9780be0abba 100644 --- a/assemblies/configure_configuring-rhdh/assembly-rhdh-default-configuration.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-rhdh-default-configuration.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="rhdh-default-configuration_{context}"] = {product} default configuration @@ -17,3 +18,6 @@ include::../modules/configure_configuring-rhdh/ref-time-syntax-in-rhdh.adoc[leve :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_configuring-rhdh/assembly-run-rhdh-behind-a-corporate-proxy.adoc b/assemblies/configure_configuring-rhdh/assembly-run-rhdh-behind-a-corporate-proxy.adoc index 443415e83e1..11b9ec4d709 100644 --- a/assemblies/configure_configuring-rhdh/assembly-run-rhdh-behind-a-corporate-proxy.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-run-rhdh-behind-a-corporate-proxy.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="run-rhdh-behind-a-corporate-proxy_{context}"] = Run {product} behind a corporate proxy @@ -27,3 +28,6 @@ include::../modules/configure_configuring-rhdh/proc-configure-proxy-information- :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_configuring-rhdh/assembly-troubleshoot-rhdh-configuration-issues.adoc b/assemblies/configure_configuring-rhdh/assembly-troubleshoot-rhdh-configuration-issues.adoc index 004047c0e05..4d4e64103b5 100644 --- a/assemblies/configure_configuring-rhdh/assembly-troubleshoot-rhdh-configuration-issues.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-troubleshoot-rhdh-configuration-issues.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="troubleshoot-rhdh-configuration-issues_{context}"] = Troubleshoot {product-short} configuration issues @@ -13,3 +14,6 @@ include::../modules/configure_configuring-rhdh/proc-fix-helm-overwriting-predefi :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_configuring-rhdh/assembly-use-the-dynamic-plugins-cache.adoc b/assemblies/configure_configuring-rhdh/assembly-use-the-dynamic-plugins-cache.adoc index 2bc18a8c91f..7caea6b14f8 100644 --- a/assemblies/configure_configuring-rhdh/assembly-use-the-dynamic-plugins-cache.adoc +++ b/assemblies/configure_configuring-rhdh/assembly-use-the-dynamic-plugins-cache.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="use-the-dynamic-plugins-cache_{context}"] = Use the dynamic plugins cache @@ -21,3 +22,6 @@ include::../modules/configure_configuring-rhdh/proc-configure-the-dynamic-plugin :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-about-software-templates.adoc b/assemblies/configure_customizing-rhdh/assembly-about-software-templates.adoc index d77c26e7d45..37fd96e9d22 100644 --- a/assemblies/configure_customizing-rhdh/assembly-about-software-templates.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-about-software-templates.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="about-software-templates_{context}"] = About Software Templates @@ -14,7 +15,8 @@ include::../modules/shared/proc-version-a-software-template-in-rhdh.adoc[levelof include::../modules/shared/proc-enable-software-template-version-update-notifications-in-rhdh.adoc[leveloffset=+1] [role="_additional-resources"] -.Additional resources +[role="_additional-resources"] +== Additional resources * link:https://developers.redhat.com/articles/2025/03/17/10-tips-better-backstage-software-templates#[10 tips for better {backstage} Software Templates] * {authentication-book-link}#assembly-auth-provider-github[Enabling the GitHub authentication provider] * link:https://backstage.io/docs/features/software-templates/writing-templates/[{backstage} documentation - Writing Templates] @@ -23,3 +25,6 @@ include::../modules/shared/proc-enable-software-template-version-update-notifica :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-automated-software-template-lifecycle-management.adoc b/assemblies/configure_customizing-rhdh/assembly-automated-software-template-lifecycle-management.adoc index 652a9295182..4748abfced7 100644 --- a/assemblies/configure_customizing-rhdh/assembly-automated-software-template-lifecycle-management.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-automated-software-template-lifecycle-management.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="automated-software-template-lifecycle-management_{context}"] = Automated Software Template lifecycle management @@ -19,3 +20,6 @@ include::../modules/shared/ref-template-synchronization-and-notification-outcome :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-configure-a-floating-action-button-in-rhdh.adoc b/assemblies/configure_customizing-rhdh/assembly-configure-a-floating-action-button-in-rhdh.adoc index 9f3306b3c1c..dca37eba53f 100644 --- a/assemblies/configure_customizing-rhdh/assembly-configure-a-floating-action-button-in-rhdh.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-configure-a-floating-action-button-in-rhdh.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="configure-a-floating-action-button-in-rhdh_{context}"] = Configure a floating action button in {product} @@ -21,3 +22,6 @@ include::../modules/shared/ref-floating-action-button-parameters.adoc[leveloffse :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-configure-the-global-header-in-rhdh.adoc b/assemblies/configure_customizing-rhdh/assembly-configure-the-global-header-in-rhdh.adoc index 23821982d3d..89da7e85f9e 100644 --- a/assemblies/configure_customizing-rhdh/assembly-configure-the-global-header-in-rhdh.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-configure-the-global-header-in-rhdh.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="configure-the-global-header-in-rhdh_{context}"] = Configure the global header in {product} @@ -34,3 +35,6 @@ include::../modules/shared/proc-enable-quicklinks-and-starred-items-after-an-upg :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-customize-rhdh-appearance.adoc b/assemblies/configure_customizing-rhdh/assembly-customize-rhdh-appearance.adoc index 9feca913f51..7651276e9ac 100644 --- a/assemblies/configure_customizing-rhdh/assembly-customize-rhdh-appearance.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-customize-rhdh-appearance.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="customize-rhdh-appearance_{context}"] = Customize {product} appearance @@ -67,3 +68,6 @@ include::../modules/shared/ref-custom-component-options-for-your-rhdh-instance.a :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-customize-the-home-page.adoc b/assemblies/configure_customizing-rhdh/assembly-customize-the-home-page.adoc index 0d661daffb2..92d9076c174 100644 --- a/assemblies/configure_customizing-rhdh/assembly-customize-the-home-page.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-customize-the-home-page.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="customize-the-home-page_{context}"] = Customize the Home page @@ -26,3 +27,6 @@ include::../modules/shared/proc-customize-quickaccesscard-card-icons-on-the-rhdh :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-customize-the-learning-paths-in-rhdh.adoc b/assemblies/configure_customizing-rhdh/assembly-customize-the-learning-paths-in-rhdh.adoc index f7f77daac74..2a86870e3f1 100644 --- a/assemblies/configure_customizing-rhdh/assembly-customize-the-learning-paths-in-rhdh.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-customize-the-learning-paths-in-rhdh.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="customize-the-learning-paths-in-rhdh_{context}"] = Customize the Learning Paths in {product} @@ -22,3 +23,6 @@ include::../modules/shared/proc-start-and-complete-lessons-in-learning-paths.ado :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-access-card.adoc b/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-access-card.adoc index 6360851d6c1..00cce006ce6 100644 --- a/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-access-card.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-access-card.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="customize-the-quick-access-card_{context}"] = Customize the Quick access card @@ -16,3 +17,6 @@ include::../modules/shared/proc-use-a-dedicated-service-to-provide-data-to-the-q :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-start-plugin.adoc b/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-start-plugin.adoc index 87cc2b31f08..b173d77533d 100644 --- a/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-start-plugin.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-customize-the-quick-start-plugin.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="customize-the-quick-start-plugin_{context}"] = Customize the quick start plugin @@ -23,3 +24,6 @@ include::../modules/shared/proc-enable-quick-start-localization-in-rhdh.adoc[lev :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-customize-the-tech-radar-page-in-rhdh.adoc b/assemblies/configure_customizing-rhdh/assembly-customize-the-tech-radar-page-in-rhdh.adoc index 15f6889a88f..1857d75c898 100644 --- a/assemblies/configure_customizing-rhdh/assembly-customize-the-tech-radar-page-in-rhdh.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-customize-the-tech-radar-page-in-rhdh.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="customize-the-tech-radar-page-in-rhdh_{context}"] = Customize the Tech Radar page in {product} @@ -27,3 +28,6 @@ include::../modules/shared/proc-customize-the-tech-radar-page-by-using-a-customi :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-localization-in-rhdh.adoc b/assemblies/configure_customizing-rhdh/assembly-localization-in-rhdh.adoc index c41d7bf4ff0..46832401c83 100644 --- a/assemblies/configure_customizing-rhdh/assembly-localization-in-rhdh.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-localization-in-rhdh.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="localization-in-rhdh_{context}"] = Localization in {product} @@ -29,3 +30,6 @@ include::../modules/shared/proc-implement-localization-support-for-your-custom-p :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-standardize-project-development-with-software-templates.adoc b/assemblies/configure_customizing-rhdh/assembly-standardize-project-development-with-software-templates.adoc index b26f421f06c..38d07646462 100644 --- a/assemblies/configure_customizing-rhdh/assembly-standardize-project-development-with-software-templates.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-standardize-project-development-with-software-templates.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="standardize-project-development-with-software-templates_{context}"] = Standardize project development with software templates @@ -21,3 +22,6 @@ include::../modules/shared/ref-extending-software-templates-for-complex-project- :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_customizing-rhdh/assembly-track-component-origin-and-software-template-version.adoc b/assemblies/configure_customizing-rhdh/assembly-track-component-origin-and-software-template-version.adoc index 93a83ea41f2..f8b520cbdaa 100644 --- a/assemblies/configure_customizing-rhdh/assembly-track-component-origin-and-software-template-version.adoc +++ b/assemblies/configure_customizing-rhdh/assembly-track-component-origin-and-software-template-version.adoc @@ -1,4 +1,5 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="track-component-origin-and-software-template-version_{context}"] = Track Component origin and Software Template version @@ -19,3 +20,6 @@ include::../modules/shared/proc-view-software-template-dependencies.adoc[levelof :context: {previouscontext} :!previouscontext: + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_techdocs-for-rhdh/assembly-configure-techdocs.adoc b/assemblies/configure_techdocs-for-rhdh/assembly-configure-techdocs.adoc index c3dcb1559fe..bb655625ded 100644 --- a/assemblies/configure_techdocs-for-rhdh/assembly-configure-techdocs.adoc +++ b/assemblies/configure_techdocs-for-rhdh/assembly-configure-techdocs.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: configure-techdocs +ifdef::context[:parent-context: {context}] [id="configure-techdocs_{context}"] = Configure TechDocs + +:context: configure-techdocs [role="_abstract"] The TechDocs plugin is preinstalled and enabled on a {product-short} instance by default. You can disable or enable the TechDocs plugin, and change other parameters, by configuring the {product} Helm chart or the {product} Operator ConfigMap. @@ -37,6 +39,10 @@ include::../modules/configure_techdocs-for-rhdh/ref-example-techdocs-plugin-conf include::../modules/configure_techdocs-for-rhdh/con-configuring-ci-cd-to-generate-and-publish-techdocs-sites.adoc[leveloffset=+1] [role="_additional-resources"] -.Additional resources +[role="_additional-resources"] +== Additional resources * {configuring-dynamic-plugins-book-link}[{configuring-dynamic-plugins-book-title}] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_techdocs-for-rhdh/assembly-install-and-configure-a-techdocs-add-on.adoc b/assemblies/configure_techdocs-for-rhdh/assembly-install-and-configure-a-techdocs-add-on.adoc index 6bbc22528d6..9eb2153ce3e 100644 --- a/assemblies/configure_techdocs-for-rhdh/assembly-install-and-configure-a-techdocs-add-on.adoc +++ b/assemblies/configure_techdocs-for-rhdh/assembly-install-and-configure-a-techdocs-add-on.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: install-and-configure-a-techdocs-add-on +ifdef::context[:parent-context: {context}] [id="install-and-configure-a-techdocs-add-on_{context}"] = Install and configure a TechDocs add-on + +:context: install-and-configure-a-techdocs-add-on [role="_abstract"] Install and configure TechDocs add-ons to extend the functionality of the TechDocs plugin in {product}. @@ -17,6 +19,10 @@ include::../modules/shared/proc-install-and-configure-an-external-techdocs-add-o include::../modules/shared/proc-install-and-configure-a-third-party-techdocs-add-on.adoc[leveloffset=+1] -.Additional resources +[role="_additional-resources"] +== Additional resources * {configuring-dynamic-plugins-book-link}[{configuring-dynamic-plugins-book-title}] * {installing-and-viewing-plugins-book-link}[{installing-and-viewing-plugins-book-title}] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_techdocs-for-rhdh/assembly-remove-a-techdocs-add-on.adoc b/assemblies/configure_techdocs-for-rhdh/assembly-remove-a-techdocs-add-on.adoc index 258d8ef0a36..60d5260c3ac 100644 --- a/assemblies/configure_techdocs-for-rhdh/assembly-remove-a-techdocs-add-on.adoc +++ b/assemblies/configure_techdocs-for-rhdh/assembly-remove-a-techdocs-add-on.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: remove-a-techdocs-add-on +ifdef::context[:parent-context: {context}] [id="remove-a-techdocs-add-on_{context}"] = Remove a TechDocs add-on + +:context: remove-a-techdocs-add-on [role="_abstract"] Remove or disable installed TechDocs add-ons from your {product} instance by using the Operator or the Helm chart. @@ -14,3 +16,6 @@ If you want to disable a plugin instead of removing it from your {product} insta include::../modules/shared/proc-remove-an-external-techdocs-add-on-from-your-configmap.adoc[leveloffset=+1] include::../modules/shared/proc-remove-an-external-techdocs-add-on-from-your-helm-chart.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_techdocs-for-rhdh/assembly-techdocs-add-ons.adoc b/assemblies/configure_techdocs-for-rhdh/assembly-techdocs-add-ons.adoc index 528e9f88c1b..99978e92509 100644 --- a/assemblies/configure_techdocs-for-rhdh/assembly-techdocs-add-ons.adoc +++ b/assemblies/configure_techdocs-for-rhdh/assembly-techdocs-add-ons.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: techdocs-add-ons +ifdef::context[:parent-context: {context}] [id="techdocs-add-ons_{context}"] = TechDocs add-ons + +:context: techdocs-add-ons [role="_abstract"] TechDocs add-ons are dynamic plugins that extend the functionality of the built-in TechDocs plugin. For example, you can use add-ons to report documentation issues, change text size, or view images in overlay in either the TechDocs Reader page or an Entity page. @@ -34,3 +36,6 @@ The following table describes the TechDocs add-ons that are available for {produ |=== The `backstage-plugin-techdocs-module-addons-contrib` plugin package exports both preinstalled and external add-ons supported by {company-name} to the TechDocs plugin. This plugin package is preinstalled on {product} and is enabled by default. If the plugin package is disabled, all of the TechDocs add-ons exported by the package as also disabled. + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/configure_techdocs-for-rhdh/assembly-use-techdocs-add-ons.adoc b/assemblies/configure_techdocs-for-rhdh/assembly-use-techdocs-add-ons.adoc index c7daefb247c..99bfcb14727 100644 --- a/assemblies/configure_techdocs-for-rhdh/assembly-use-techdocs-add-ons.adoc +++ b/assemblies/configure_techdocs-for-rhdh/assembly-use-techdocs-add-ons.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: use-techdocs-add-ons +ifdef::context[:parent-context: {context}] [id="use-techdocs-add-ons_{context}"] = Use TechDocs add-ons + +:context: use-techdocs-add-ons [role="_abstract"] After an administrator installs a TechDocs add-on in your {product} instance, you can use it to extend the functionality of the TechDocs plugin and enhance your documentation experience. @@ -12,3 +14,6 @@ include::../modules/shared/proc-use-the-reportissue-techdocs-add-on.adoc[levelof include::../modules/shared/proc-use-the-textsize-techdocs-add-on.adoc[leveloffset=+1] include::../modules/shared/proc-use-the-lightbox-techdocs-add-on.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/develop_manage-and-consume-technical-documentation-within-rhdh/assembly-add-documentation-to-techdocs-for-your-project.adoc b/assemblies/develop_manage-and-consume-technical-documentation-within-rhdh/assembly-add-documentation-to-techdocs-for-your-project.adoc index c22ccea4cab..e801cb9eb7d 100644 --- a/assemblies/develop_manage-and-consume-technical-documentation-within-rhdh/assembly-add-documentation-to-techdocs-for-your-project.adoc +++ b/assemblies/develop_manage-and-consume-technical-documentation-within-rhdh/assembly-add-documentation-to-techdocs-for-your-project.adoc @@ -1,9 +1,15 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="add-documentation-to-techdocs-for-your-project_{context}"] = Add documentation to TechDocs for your project +:context: add-documentation-to-techdocs-for-your-project + [role="_abstract"] After an administrator configures the TechDocs plugin, a developer can add documentation to TechDocs by importing it from a remote repository. Any authorized user or group can access the documentation that is imported into the TechDocs plugin. include::../modules/shared/proc-import-documentation-into-techdocs-from-a-remote-repository.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-centralize-your-software-components-in-the-rhdh-catalog.adoc b/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-centralize-your-software-components-in-the-rhdh-catalog.adoc index d1b8f7180f0..de90a1b5dd6 100644 --- a/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-centralize-your-software-components-in-the-rhdh-catalog.adoc +++ b/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-centralize-your-software-components-in-the-rhdh-catalog.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: centralize-your-software-components-in-the-rhdh-catalog +ifdef::context[:parent-context: {context}] [id="centralize-your-software-components-in-the-rhdh-catalog_{context}"] = Centralize your software components in the {product} catalog + +:context: centralize-your-software-components-in-the-rhdh-catalog [role="_abstract"] The {product} Software Catalog is a centralized system that gives you visibility into all the software across your ecosystem, including services, websites, libraries, and data pipelines. @@ -32,3 +34,6 @@ include::../modules/shared/proc-filter-components-by-text-in-the-rhdh-catalog.ad include::../modules/shared/proc-review-the-yaml-configuration-of-your-rhdh-software-catalog.adoc[leveloffset=+1] include::../modules/shared/proc-star-key-components-in-the-software-catalog.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-import-your-team-s-codebase-from-git.adoc b/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-import-your-team-s-codebase-from-git.adoc index f19a0be0371..a1c197d7ddd 100644 --- a/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-import-your-team-s-codebase-from-git.adoc +++ b/assemblies/develop_streamline-software-development-and-management-in-rhdh/assembly-import-your-team-s-codebase-from-git.adoc @@ -1,10 +1,12 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] -:context: import-your-team-s-codebase-from-git [id="import-your-team-s-codebase-from-git_{context}"] = Import your team's codebase from Git + +:context: import-your-team-s-codebase-from-git [role="_abstract"] {product} can automate GitHub repositories onboarding and track their import status. @@ -16,4 +18,6 @@ include::../modules/shared/proc-import-multiple-github-repositories.adoc[levelof include::../modules/develop_streamline-software-development-and-management-in-rhdh/proc-manage-the-added-git-repositories.adoc[leveloffset=+1] -include::../modules/shared/proc-monitor-bulk-import-actions-using-audit-logs.adoc[leveloffset=+1] \ No newline at end of file +include::../modules/shared/proc-monitor-bulk-import-actions-using-audit-logs.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/extend_shared/assembly-develop-and-deploy-dynamic-plugins-in-rhdh.adoc b/assemblies/extend_shared/assembly-develop-and-deploy-dynamic-plugins-in-rhdh.adoc deleted file mode 100644 index b5fd68aac73..00000000000 --- a/assemblies/extend_shared/assembly-develop-and-deploy-dynamic-plugins-in-rhdh.adoc +++ /dev/null @@ -1,49 +0,0 @@ -:_mod-docs-content-type: ASSEMBLY - -[id="develop-and-deploy-dynamic-plugins-in-rhdh_{context}"] -= Develop and deploy dynamic plugins in {product} -:context: develop-and-deploy-dynamic-plugins-in-rhdh - -include::assembly-overview-of-dynamic-plugins.adoc[leveloffset=+1] - -// Setting Up the Development Toolchain (1.9 tr) -include::../modules/shared/con-prepare-your-development-environment.adoc[leveloffset=+1] - -// Developing Plugins from Scratch -//include::../modules/shared/proc-developing-plugins-from-scratch.adoc[leveloffset=+1] - -[id="develop-and-deploy-dynamic-plugins-in-rhdh_{context}"] -== Develop a new plugin - -include::../modules/shared/con-determine-rhdh-version.adoc[leveloffset=+2] - -include::../modules/shared/proc-create-a-new-backstage-application.adoc[leveloffset=+2] - -include::../modules/shared/proc-create-a-new-plugin.adoc[leveloffset=+2] - -include::../modules/shared/proc-implement-a-plugin-component.adoc[leveloffset=+2] - -include::../modules/shared/proc-test-a-plugin-locally.adoc[leveloffset=+2] - -include::../modules/shared/proc-configure-frontend-plugin-wiring.adoc[leveloffset=+2] - -// Converting a custom plugin into an RHDH dynamic plugin -include::../modules/shared/proc-convert-a-custom-plugin-into-a-dynamic-plugin.adoc[leveloffset=+1] - -include::../modules/shared/con-using-the-dynamic-plugin-factory-to-convert-plugins-into-dynamic-plugins.adoc[leveloffset=+2] - -// Packaging and Publishing Artifacts -//include::../modules/shared/proc-packaging-and-publishing-artifacts.adoc[leveloffset=+1] - -// Deployment Configurations -== Deployment configurations -include::../modules/shared/proc-add-a-dynamic-plugin-to-rhdh.adoc[leveloffset=+2] - -// Testing with RHDH Local (1.9) -include::../modules/shared/proc-verify-plugins-locally.adoc[leveloffset=+1] -//include::../modules/shared/proc-configure-rhdh-local-to-install-plugins-by-using-extensions.adoc[leveloffset=+1] - -// Best Practices & Reference -//include::../modules/shared/ref-best-practices-and-reference.adoc[leveloffset=+1] - -// Examples \ No newline at end of file diff --git a/assemblies/extend_shared/assembly-overview-of-dynamic-plugins.adoc b/assemblies/extend_shared/assembly-overview-of-dynamic-plugins.adoc deleted file mode 100644 index 1b278d0d50a..00000000000 --- a/assemblies/extend_shared/assembly-overview-of-dynamic-plugins.adoc +++ /dev/null @@ -1,6 +0,0 @@ -:_mod-docs-content-type: ASSEMBLY - -[id="overview-of-dynamic-plugins_{context}"] -= Overview of dynamic plugins - -include::../modules/shared/con-dynamic-plugins.adoc[leveloffset=+1] diff --git a/assemblies/extend_shared/assembly-use-the-topology-plugin.adoc b/assemblies/extend_using-dynamic-plugins-in-rhdh/assembly-use-the-topology-plugin.adoc similarity index 100% rename from assemblies/extend_shared/assembly-use-the-topology-plugin.adoc rename to assemblies/extend_using-dynamic-plugins-in-rhdh/assembly-use-the-topology-plugin.adoc diff --git a/assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-interact-with-model-context-protocol-tools-for-rhdh.adoc b/assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-interact-with-model-context-protocol-tools-for-rhdh.adoc deleted file mode 100644 index bc8972b5140..00000000000 --- a/assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-interact-with-model-context-protocol-tools-for-rhdh.adoc +++ /dev/null @@ -1,40 +0,0 @@ -:_mod-docs-content-type: ASSEMBLY - -[id="interact-with-model-context-protocol-tools-for-rhdh_{context}"] -= Interact with Model Context Protocol tools for {product} -:context: interact-with-model-context-protocol-tools-for-rhdh - -[role="_abstract"] -Use the Model Context Protocol (MCP) server to enable AI clients to access {product-short} data and workflows through standardized MCP tools for software catalogs and TechDocs. - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/con-understanding-model-context-protocol.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-install-the-mcp-server-and-tool-plugins-in-rhdh.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-model-context-protocol-in-rhdh.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-mcp-clients-to-access-the-rhdh-server.adoc[leveloffset=+2] - -// Using docs - Beginning - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-rhdh-data-using-the-software-catalog-mcp-tools.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-fetching-entities-using-fetch-catalog-entities.adoc[leveloffset=+2] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-registering-entities-using-catalog-register-tool.adoc[leveloffset=+2] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-unregistering-entities-using-catalog-unregister-tool.adoc[leveloffset=+2] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-retrieving-software-template-metadata.adoc[leveloffset=+2] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-and-analyzing-documentation-using-the-techdocs-mcp-tools.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-retrieving-techdocs-urls-and-metadata-using-fetch-techdocs.adoc[leveloffset=+2] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-measuring-documentation-gaps-using-analyze-techdocs-coverage.adoc[leveloffset=+2] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-finding-a-specific-techdoc-using-retrieve-techdocs-content.adoc[leveloffset=+2] - -// Using docs - END - -include::assembly-troubleshoot-mcp-server-and-client-problems.adoc[leveloffset=+1] \ No newline at end of file diff --git a/assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-troubleshoot-mcp-server-and-client-problems.adoc b/assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-troubleshoot-mcp-server-and-client-problems.adoc deleted file mode 100644 index d45f08902df..00000000000 --- a/assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-troubleshoot-mcp-server-and-client-problems.adoc +++ /dev/null @@ -1,20 +0,0 @@ -:_mod-docs-content-type: ASSEMBLY - -[id="troubleshoot-mcp-server-and-client-problems_{context}"] -= Troubleshoot MCP server and client problems -:context: troubleshoot-mcp-server-and-client-problems - -[role="_abstract"] -Resolve common issues when using the Model Context Protocol (MCP) server including installation verification, log checking, error messages, and authentication problems. - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-verify-successful-installation-of-mcp-plugins.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-checking-mcp-tool-logs-for-status-and-errors.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/con-understand-and-respond-to-mcp-tool-error-messages.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-resolve-the-model-does-not-support-tool-calling-error.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-resolve-authentication-issues-when-tools-are-not-found.adoc[leveloffset=+1] - -include::../modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-resolve-nonsensical-mcp-tool-output.adoc[leveloffset=+1] \ No newline at end of file diff --git a/assemblies/shared/assembly-conditional-policies-reference.adoc b/assemblies/shared/assembly-conditional-policies-reference.adoc index d916bc0f931..a9e95bce991 100644 --- a/assemblies/shared/assembly-conditional-policies-reference.adoc +++ b/assemblies/shared/assembly-conditional-policies-reference.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="conditional-policies-reference_{context}"] = Conditional policies reference +:context: conditional-policies-reference + [role="_abstract"] Reference information about conditional policy rules, schemas, and examples for defining conditions with or without criteria. @@ -19,3 +22,6 @@ include::../modules/shared/ref-conditional-policy-without-criteria.adoc[leveloff include::../modules/shared/ref-conditional-policy-with-criteria.adoc[leveloffset=+1] include::../modules/shared/ref-conditional-policy-plugin-examples.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-configure-guest-access-with-rbac-ui.adoc b/assemblies/shared/assembly-configure-guest-access-with-rbac-ui.adoc index 2d9054c30c1..e23287105ca 100644 --- a/assemblies/shared/assembly-configure-guest-access-with-rbac-ui.adoc +++ b/assemblies/shared/assembly-configure-guest-access-with-rbac-ui.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="configure-guest-access-with-rbac-ui_{context}"] = Configure guest access with RBAC UI +:context: configure-guest-access-with-rbac-ui + [role="_abstract"] Enable guest access to test role and policy creation without configuring an authentication provider. @@ -16,3 +19,6 @@ Guest access is not recommended for production. include::../modules/shared/proc-configure-the-rbac-backend-plugin.adoc[leveloffset=+1] include::../modules/shared/proc-set-up-the-guest-authentication-provider.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-delegate-role-based-access-controls-rbac-access-in-rhdh.adoc b/assemblies/shared/assembly-delegate-role-based-access-controls-rbac-access-in-rhdh.adoc index 33a4f52d432..94b52eb086c 100644 --- a/assemblies/shared/assembly-delegate-role-based-access-controls-rbac-access-in-rhdh.adoc +++ b/assemblies/shared/assembly-delegate-role-based-access-controls-rbac-access-in-rhdh.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="delegate-role-based-access-controls-rbac-access-in-rhdh_{context}"] = Delegate role-based access controls (RBAC) access in {product} +:context: delegate-role-based-access-controls-rbac-access-in-rhdh + [role="_abstract"] Delegate RBAC responsibilities to team leads by using the multitenancy feature and IS_OWNER conditional rule. @@ -28,3 +31,6 @@ include::../modules/shared/proc-delegate-rbac-access-in-rhdh-by-using-the-web-ui include::../modules/shared/proc-delegate-rbac-access-in-rhdh-by-using-api.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-enable-authentication-with-github.adoc b/assemblies/shared/assembly-enable-authentication-with-github.adoc index afc0765469c..fda7ddc74b5 100644 --- a/assemblies/shared/assembly-enable-authentication-with-github.adoc +++ b/assemblies/shared/assembly-enable-authentication-with-github.adoc @@ -1,15 +1,19 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] :optional-steps: enable -[id="enable-authentication-with-github"] +[id="enable-authentication-with-github_{context}"] = Enable authentication with GitHub +:context: enable-authentication-with-github + [role="_abstract"] -You can enable authentication with GitHub to allow users to sign in to {product-short} using their GitHub credentials. -This integration also allows you to provision user and group data from GitHub to the {product-short} software catalog, enabling features that rely on synchronized user and group data. +You can enable authentication with GitHub to allow users to sign in to {product-short} using their GitHub credentials and provision user and group data to the software catalog. include::../modules/shared/proc-enable-user-authentication-with-github-with-optional-steps.adoc[leveloffset=+1] include::../modules/shared/proc-enable-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-enable-authentication-with-gitlab.adoc b/assemblies/shared/assembly-enable-authentication-with-gitlab.adoc index 0a734e6e1cb..cb09405ea46 100644 --- a/assemblies/shared/assembly-enable-authentication-with-gitlab.adoc +++ b/assemblies/shared/assembly-enable-authentication-with-gitlab.adoc @@ -1,9 +1,15 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="enable-authentication-with-gitlab_{context}"] = Enable authentication with GitLab +:context: enable-authentication-with-gitlab + [role="_abstract"] By configuring GitLab as an identity provider, you can enable users to authenticate with {product} and import your GitLab users and groups into the software catalog. include::../modules/shared/proc-enable-user-authentication-with-gitlab.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-enable-authentication-with-microsoft-azure.adoc b/assemblies/shared/assembly-enable-authentication-with-microsoft-azure.adoc index e5417c6e3aa..08d06f07bc0 100644 --- a/assemblies/shared/assembly-enable-authentication-with-microsoft-azure.adoc +++ b/assemblies/shared/assembly-enable-authentication-with-microsoft-azure.adoc @@ -1,12 +1,16 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] :optional-steps: enable [id="enable-authentication-with-microsoft-azure_{context}"] = Enable authentication with {azure-brand-name} +:context: enable-authentication-with-microsoft-azure + [role="_abstract"] -You can enable authentication wit {azure-brand-name} to allow users to sign in to {product-short} using their {azure-brand-name} credentials. -This integration also allows you to provision user and group data from {azure-brand-name} to the {product-short} software catalog, enabling features that rely on synchronized user and group data. +You can enable authentication with {azure-brand-name} to allow users to sign in to {product-short} using their {azure-brand-name} credentials and provision user and group data to the software catalog. include::../modules/shared/proc-enable-user-authentication-with-microsoft-azure-with-optional-steps.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-enable-authentication-with-rhbk.adoc b/assemblies/shared/assembly-enable-authentication-with-rhbk.adoc index c05053753c6..99f4e5171a7 100644 --- a/assemblies/shared/assembly-enable-authentication-with-rhbk.adoc +++ b/assemblies/shared/assembly-enable-authentication-with-rhbk.adoc @@ -1,12 +1,13 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] -[id="enable-authentication-with-rhbk"] +[id="enable-authentication-with-rhbk_{context}"] = Enable authentication with {rhbk-brand-name} ({rhbk}) +:context: enable-authentication-with-rhbk + [role="_abstract"] -You can enable authentication with {rhbk} to allow users to sign in to {product-short} using their {rhbk} credentials. -This integration also allows you to provision user and group data from {rhbk} or from an LDAP directory to the {product-short} software catalog, enabling features that rely on synchronized user and group data. -If the default provisioning options do not meet your needs, you can also create a custom transformer to provision user and group data from {rhbk} to the software catalog. +You can enable authentication with {rhbk} to allow users to sign in to {product-short} using their {rhbk} credentials and provision user and group data from {rhbk} or LDAP to the software catalog. include::../modules/shared/proc-enable-user-authentication-with-rhbk-with-optional-steps.adoc[leveloffset=+1] @@ -16,3 +17,5 @@ include::../modules/shared/proc-enable-user-provisioning-with-ldap.adoc[leveloff include::../modules/shared/proc-create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-enable-or-disable-authentication-with-the-guest-user.adoc b/assemblies/shared/assembly-enable-or-disable-authentication-with-the-guest-user.adoc index 0569e93bbd8..7c61a49403e 100644 --- a/assemblies/shared/assembly-enable-or-disable-authentication-with-the-guest-user.adoc +++ b/assemblies/shared/assembly-enable-or-disable-authentication-with-the-guest-user.adoc @@ -1,17 +1,18 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="enable-or-disable-authentication-with-the-guest-user_{context}"] = Enable or disable authentication with the Guest user -[role="_abstract"] --- -For trial or non-production environments, you can enable guest access to skip configuring authentication and authorization and explore {product-short} features. +:context: enable-or-disable-authentication-with-the-guest-user -For production environments, disable guest access to ensure secure authentication and authorization. --- +[role="_abstract"] +For trial or non-production environments, you can enable guest access to explore {product-short} features without configuring authentication. For production environments, disable guest access to ensure security. include::../modules/shared/proc-enable-the-guest-login.adoc[leveloffset=+1] include::../modules/shared/proc-disable-the-guest-login.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-enable-service-to-service-authentication.adoc b/assemblies/shared/assembly-enable-service-to-service-authentication.adoc index e052c57401b..78b7fabd8c8 100644 --- a/assemblies/shared/assembly-enable-service-to-service-authentication.adoc +++ b/assemblies/shared/assembly-enable-service-to-service-authentication.adoc @@ -1,18 +1,13 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] :previous-context: {context} -:context: enable-service-to-service-authentication -[id="enable-service-to-service-authentication"] +[id="enable-service-to-service-authentication_{context}"] = Enable service-to-service authentication -[role="_abstract"] --- -To secure communication between services and verify identities without manual intervention, you can configure service-to-service authentication. -{product} can use this mechanism for plugin-to-plugin communication, and for external service to plugin communication. -{product-short} supports the following service-to-service authentication methods: -* Static tokens -* JSON Web Key Sets (JWKS) --- +:context: enable-service-to-service-authentication +[role="_abstract"] +You can configure service-to-service authentication to secure communication between services, including plugin-to-plugin and external-service-to-plugin communication. [IMPORTANT] ==== @@ -31,5 +26,5 @@ include::../modules/shared//proc-enable-service-to-service-authentication-by-usi include::../modules/shared//proc-set-access-restrictions-to-external-service-tokens.adoc[leveloffset=+1] - -:context: {previous-context} +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-import-and-use-an-existing-software-template-for-faster-development.adoc b/assemblies/shared/assembly-import-and-use-an-existing-software-template-for-faster-development.adoc index 6cb5e86c3e9..e8744492de3 100644 --- a/assemblies/shared/assembly-import-and-use-an-existing-software-template-for-faster-development.adoc +++ b/assemblies/shared/assembly-import-and-use-an-existing-software-template-for-faster-development.adoc @@ -1,9 +1,11 @@ :_mod-docs-content-type: ASSEMBLY -:context: import-and-use-an-existing-software-template-for-faster-development +ifdef::context[:parent-context: {context}] [id="import-and-use-an-existing-software-template-for-faster-development_{context}"] = Import and use an existing Software Template for faster development + +:context: import-and-use-an-existing-software-template-for-faster-development [role="_abstract"] To standardize and accelerate the creation of new software, use Software Templates in {product} ({product-very-short}). @@ -16,6 +18,9 @@ include::../modules/shared/proc-search-and-filter-software-templates-in-your-rhd include::../modules/shared/proc-import-an-existing-software-template.adoc[leveloffset=+1] [role="_additional-resources"] -.Additional resources +[role="_additional-resources"] +== Additional resources * {about-book-link}[{about-book-title}] -* {customizing-book-link}[{customizing-book-title}] \ No newline at end of file +* {customizing-book-link}[{customizing-book-title}] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-manage-authorizations-by-using-external-files.adoc b/assemblies/shared/assembly-manage-authorizations-by-using-external-files.adoc index be64c04d6f9..f990ed8670e 100644 --- a/assemblies/shared/assembly-manage-authorizations-by-using-external-files.adoc +++ b/assemblies/shared/assembly-manage-authorizations-by-using-external-files.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="manage-authorizations-by-using-external-files_{context}"] = Manage authorizations by using external files +:context: manage-authorizations-by-using-external-files + [role="_abstract"] Configure permissions and roles in external files before starting {product-short} to automate maintenance. @@ -13,3 +16,5 @@ include::../modules/shared/proc-define-authorizations-in-external-files-by-using include::../modules/shared/proc-define-authorizations-in-external-files-by-using-helm.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-manage-authorizations-by-using-the-rest-api.adoc b/assemblies/shared/assembly-manage-authorizations-by-using-the-rest-api.adoc index 445b64d1571..c7c83840936 100644 --- a/assemblies/shared/assembly-manage-authorizations-by-using-the-rest-api.adoc +++ b/assemblies/shared/assembly-manage-authorizations-by-using-the-rest-api.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="manage-authorizations-by-using-the-rest-api_{context}"] = Manage authorizations by using the REST API +:context: manage-authorizations-by-using-the-rest-api + [role="_abstract"] Automate the maintenance of permission policies and roles by using the {product-short} RBAC REST API. @@ -31,3 +34,5 @@ include::../modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-an-e include::../modules/shared/ref-supported-rbac-rest-api-endpoints.adoc[leveloffset=+1] +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-manage-role-based-access-controls-rbac-using-the-rhdh-web-ui.adoc b/assemblies/shared/assembly-manage-role-based-access-controls-rbac-using-the-rhdh-web-ui.adoc index c125a9bf859..f57fa82663f 100644 --- a/assemblies/shared/assembly-manage-role-based-access-controls-rbac-using-the-rhdh-web-ui.adoc +++ b/assemblies/shared/assembly-manage-role-based-access-controls-rbac-using-the-rhdh-web-ui.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="manage-role-based-access-controls-rbac-using-the-rhdh-web-ui_{context}"] = Manage role-based access controls (RBAC) using the {product} Web UI +:context: manage-role-based-access-controls-rbac-using-the-rhdh-web-ui + [role="_abstract"] Use the {product-short} Web UI to create, modify, and delete roles and assign permissions to users and groups. @@ -20,3 +23,6 @@ include::../modules/shared/proc-edit-a-role-in-the-rhdh-web-ui.adoc[leveloffset= include::../modules/shared/proc-delete-a-role-in-the-rhdh-web-ui.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-permission-policies-reference.adoc b/assemblies/shared/assembly-permission-policies-reference.adoc index da5e1e9c022..cc41a421987 100644 --- a/assemblies/shared/assembly-permission-policies-reference.adoc +++ b/assemblies/shared/assembly-permission-policies-reference.adoc @@ -1,8 +1,11 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] [id="permission-policies-reference_{context}"] = Permission policies reference +:context: permission-policies-reference + [role="_abstract"] Reference information about permission policy types and available permissions for catalog, scaffolder, RBAC, Kubernetes, and plugin resources. @@ -30,3 +33,6 @@ include::../modules/shared/ref-tekton-permissions.adoc[leveloffset=+1] include::../modules/shared/ref-argocd-permissions.adoc[leveloffset=+1] include::../modules/shared/ref-quay-permissions.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/assemblies/shared/assembly-troubleshoot-authentication-issues.adoc b/assemblies/shared/assembly-troubleshoot-authentication-issues.adoc index fe1c0e174d3..7e3c0ab5302 100644 --- a/assemblies/shared/assembly-troubleshoot-authentication-issues.adoc +++ b/assemblies/shared/assembly-troubleshoot-authentication-issues.adoc @@ -1,9 +1,15 @@ :_mod-docs-content-type: ASSEMBLY +ifdef::context[:parent-context: {context}] -[id="troubleshoot-authentication-issues"] +[id="troubleshoot-authentication-issues_{context}"] = Troubleshoot authentication issues +:context: troubleshoot-authentication-issues + [role="_abstract"] -Learn how to troubleshoot authentication issues. +Learn how to troubleshoot common authentication issues. include::../modules/shared/proc-reduce-the-size-of-issued-tokens.adoc[leveloffset=+1] + +ifdef::parent-context[:context: {parent-context}] +ifndef::parent-context[:!context:] diff --git a/build/scripts/align-title-directories.sh b/build/scripts/align-title-directories.sh deleted file mode 100755 index 6d01b821ac2..00000000000 --- a/build/scripts/align-title-directories.sh +++ /dev/null @@ -1,906 +0,0 @@ -#!/bin/bash -# shellcheck disable=SC2128,SC2178,SC2086 -# SC2128/SC2178: owners variables are space-delimited strings, not arrays — intentional. -# SC2086: unquoted $owners passed to _compute_dest for word splitting — intentional. -# -# align-title-directories.sh -# -# Aligns title, assembly, and module directory names using <category>_<context> naming. -# -# Usage: -# ./build/scripts/align-title-directories.sh --list -# ./build/scripts/align-title-directories.sh --all (dry-run) -# ./build/scripts/align-title-directories.sh --all --exec (execute) -# ./build/scripts/align-title-directories.sh [--exec] <title-dir> [<new-context>] (single title) -# -# Directory naming convention: -# titles/<category>_<context>/ -# assemblies/<category>_<context>/ (owned by one title) -# assemblies/<category>_shared/ (shared within a category) -# assemblies/shared/ (shared across categories) -# modules/<category>_<context>/ (owned by one title) -# modules/<category>_shared/ (shared within a category) -# modules/shared/ (shared across categories) - -set -euo pipefail - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -info() { echo -e "${GREEN}[INFO]${NC} $*"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } -phase() { echo -e "\n${BLUE}=== $* ===${NC}"; } - -slugify_category() { - echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g' -} - -read_category() { - grep -m1 '^:_mod-docs-category:' "$1" 2>/dev/null | sed 's/^:_mod-docs-category: //' -} - -read_title() { - grep -m1 '^:title:' "$1" 2>/dev/null | sed 's/^:title: //' -} - -read_context() { - grep -m1 '^:context:' "$1" 2>/dev/null | sed 's/^:context: *//' | sed 's/ *$//' -} - -# Resolve local attributes defined in a master.adoc file. -resolve_local_attrs() { - local title="$1" - local master="$2" - - while IFS= read -r line; do - local attr_name attr_value - # shellcheck disable=SC2001 # sed needed for regex capture groups - attr_name=$(echo "$line" | sed 's/^:\([^:]*\):.*/\1/') - # shellcheck disable=SC2001 - attr_value=$(echo "$line" | sed 's/^:[^:]*: *//') - if [[ "$title" == *"{${attr_name}}"* ]]; then - title="${title//\{${attr_name}\}/${attr_value}}" - fi - done < <(grep -E '^:[a-z][-a-z0-9]*:' "$master" 2>/dev/null | grep -v '^:_mod-docs' | grep -v '^:context:' | grep -v '^:title:' | grep -v '^:subtitle:' | grep -v '^:abstract:' | grep -v '^:imagesdir:' || true) - - echo "$title" -} - -# Derive context slug from :title: -derive_context() { - local title="$1" - local master="${2:-}" - - if [[ -n "$master" && -f "$master" ]]; then - title=$(resolve_local_attrs "$title" "$master") - fi - - title="${title//\{ls-brand-name\}/developer-lightspeed-for-rhdh}" - title="${title//\{ls-short\}/developer-lightspeed-for-rhdh}" - title="${title//\{openshift-ai-connector-name\}/openshift-ai-connector-for-rhdh}" - title="${title//\{openshift-ai-connector-name-short\}/openshift-ai-connector-for-rhdh}" - title="${title//\{product\}/rhdh}" - title="${title//\{product-short\}/rhdh}" - title="${title//\{product-very-short\}/rhdh}" - title="${title//\{product-local\}/rhdh-local}" - title="${title//\{product-local-very-short\}/rhdh-local}" - title="${title//\{ocp-brand-name\}/ocp}" - title="${title//\{ocp-short\}/ocp}" - title="${title//\{ocp-very-short\}/ocp}" - title="${title//\{aks-brand-name\}/aks}" - title="${title//\{aks-name\}/aks}" - title="${title//\{aks-short\}/aks}" - title="${title//\{eks-brand-name\}/eks}" - title="${title//\{eks-name\}/eks}" - title="${title//\{eks-short\}/eks}" - title="${title//\{gke-brand-name\}/gke}" - title="${title//\{gke-short\}/gke}" - title="${title//\{gcp-brand-name\}/gcp}" - title="${title//\{gcp-short\}/gcp}" - title="${title//\{osd-brand-name\}/osd}" - title="${title//\{osd-short\}/osd}" - title="${title//\{rhacs-brand-name\}/acs}" - title="${title//\{rhacs-short\}/acs}" - title="${title//\{rhacs-very-short\}/acs}" - title="${title//\{rhoai-brand-name\}/openshift-ai}" - title="${title//\{rhoai-short\}/openshift-ai}" - title="${title//\{backstage\}/backstage}" - - title="${title,,}" - title="${title// /-}" - title="${title//_/-}" - # shellcheck disable=SC2001 # sed needed for regex character classes - title=$(echo "$title" | sed 's/([^)]*)//g') - # shellcheck disable=SC2001 - title=$(echo "$title" | sed 's/[^a-z0-9-]//g') - # shellcheck disable=SC2001 - title=$(echo "$title" | sed 's/-\{2,\}/-/g') - title=$(echo "$title" | sed 's/^-//;s/-$//') - - echo "$title" -} - -# Move all files from src dir to dest dir via git mv, then rmdir src -move_dir_contents() { - local src="$1" dest="$2" - for f in "$src"/*; do - [[ -e "$f" ]] || continue - local base - base=$(basename "$f") - if [[ -e "$dest/$base" ]]; then - warn " Skip (exists): $dest/$base" - else - git mv "$f" "$dest/" - fi - done - rmdir "$src" 2>/dev/null || true -} - -# ═══════════════════════════════════════════════════════════════════ -# --list mode -# ═══════════════════════════════════════════════════════════════════ -if [[ "${1:-}" == "--list" ]]; then - echo "Title directories with category and context:" - echo "=============================================" - printf "%-55s %-20s %-45s %-45s\n" "Directory" "Category" ":context:" "Dest" - printf "%-55s %-20s %-45s %-45s\n" "---------" "--------" "--------" "----" - for master in titles/*/master.adoc; do - dir=$(dirname "$master" | sed 's|titles/||') - context=$(read_context "$master" || echo "(none)") - cat=$(read_category "$master" || echo "(none)") - catslug=$(slugify_category "${cat:-(none)}") - dest="${catslug}_${context}" - printf "%-55s %-20s %-45s %-45s\n" "$dir" "$cat" "$context" "$dest" - done - exit 0 -fi - -# ═══════════════════════════════════════════════════════════════════ -# --all mode: process all titles with category-based naming -# ═══════════════════════════════════════════════════════════════════ -if [[ "${1:-}" == "--all" ]]; then - EXEC=false - [[ "${2:-}" == "--exec" ]] && EXEC=true - - # ── Phase 0: Pre-computation ────────────────────────────────── - phase "Phase 0: Pre-computation" - - declare -A CTX_CAT # context → category slug - declare -A CTX_DEST # context → <catslug>_<context> - declare -A DIR_CTX # title dir basename → context - TITLE_LIST=() - - for master in titles/*/master.adoc; do - d=$(basename "$(dirname "$master")") - ctx=$(read_context "$master") - cat=$(read_category "$master") - if [[ -z "$cat" ]]; then - error "No :_mod-docs-category: in $master — skipping" - continue - fi - cs=$(slugify_category "$cat") - CTX_CAT["$ctx"]="$cs" - CTX_DEST["$ctx"]="${cs}_${ctx}" - DIR_CTX["$d"]="$ctx" - TITLE_LIST+=("$ctx") - info "Title: $d → ${cs}_${ctx} ($cat)" - done - - # ── Build assembly ownership map ── - # ASM_FILE_OWNERS: "subdir/file.adoc" or "file.adoc" → "ctx1 ctx2 ..." - declare -A ASM_FILE_OWNERS - - for master in titles/*/master.adoc; do - d=$(basename "$(dirname "$master")") - ctx="${DIR_CTX[$d]}" - while IFS= read -r inc; do - [[ -z "$inc" ]] && continue - ASM_FILE_OWNERS["$inc"]="${ASM_FILE_OWNERS[$inc]:-} $ctx" - done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'include::assemblies/\K[^[]+' || true) - done - - # Propagate ownership to sub-assemblies (iterative) - changed=true - while $changed; do - changed=false - for af in "${!ASM_FILE_OWNERS[@]}"; do - [[ -f "assemblies/$af" ]] || continue - parent_owners="${ASM_FILE_OWNERS[$af]}" - asm_dir=$(dirname "$af") - - # Same-dir relative includes: include::assembly-foo.adoc[ - while IFS= read -r sub; do - [[ -z "$sub" ]] && continue - if [[ "$asm_dir" != "." ]]; then - sub_path="$asm_dir/$sub" - else - sub_path="$sub" - fi - old="${ASM_FILE_OWNERS[$sub_path]:-}" - combined=$(echo "$old $parent_owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) - if [[ "$combined" != "$old" ]]; then - ASM_FILE_OWNERS["$sub_path"]="$combined" - changed=true - fi - done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'include::\Kassembly-[^[]+' || true) - - # Cross-dir includes: include::../assemblies/<dir>/assembly-foo.adoc[ - while IFS= read -r sub_path; do - [[ -z "$sub_path" ]] && continue - old="${ASM_FILE_OWNERS[$sub_path]:-}" - combined=$(echo "$old $parent_owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) - if [[ "$combined" != "$old" ]]; then - ASM_FILE_OWNERS["$sub_path"]="$combined" - changed=true - fi - done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'include::\.\.\/assemblies/\K[^[]+' || true) - done - done - - # ── Build module dir ownership map ── - # MOD_DIR_OWNERS: "dir_name" → "ctx1 ctx2 ..." - declare -A MOD_DIR_OWNERS - - # From master.adoc - for master in titles/*/master.adoc; do - d=$(basename "$(dirname "$master")") - ctx="${DIR_CTX[$d]}" - while IFS= read -r md; do - [[ -z "$md" || "$md" == "shared" ]] && continue - MOD_DIR_OWNERS["$md"]="${MOD_DIR_OWNERS[$md]:-} $ctx" - done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'include::modules/\K[^/]+' | sort -u || true) - done - - # From assemblies (inherit owners) - for af in "${!ASM_FILE_OWNERS[@]}"; do - [[ -f "assemblies/$af" ]] || continue - owners="${ASM_FILE_OWNERS[$af]}" - while IFS= read -r md; do - [[ -z "$md" || "$md" == "shared" ]] && continue - MOD_DIR_OWNERS["$md"]="${MOD_DIR_OWNERS[$md]:-} $owners" - done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'include::(\.\.\/)?modules/\K[^/]+' | sort -u || true) - done - - # Deduplicate owners - for md in "${!MOD_DIR_OWNERS[@]}"; do - MOD_DIR_OWNERS["$md"]=$(echo "${MOD_DIR_OWNERS[$md]}" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) - done - - # ── Compute destinations ── - # Given space-separated owner contexts, return destination dir name - _compute_dest() { - local owners_str="$*" - local -a owners - read -ra owners <<< "$owners_str" - local n=${#owners[@]} - - if [[ $n -eq 0 ]]; then - echo "UNKNOWN" - return - fi - - if [[ $n -eq 1 ]]; then - echo "${CTX_DEST[${owners[0]}]}" - return - fi - - # Check if all same category - local first_cat="${CTX_CAT[${owners[0]}]}" - local all_same=true - for o in "${owners[@]}"; do - if [[ "${CTX_CAT[$o]:-}" != "$first_cat" ]]; then - all_same=false - break - fi - done - - if $all_same; then - echo "${first_cat}_shared" - else - echo "shared" - fi - } - - # Compute assembly dir destinations - declare -A ASM_DIR_DEST # old dir name → new dir name - for asm_dir in assemblies/*/; do - [[ -d "$asm_dir" ]] || continue - dn=$(basename "$asm_dir") - [[ "$dn" == "shared" || "$dn" == "modules" ]] && continue - - all_owners="" - for f in "$asm_dir"*.adoc; do - [[ -f "$f" ]] || continue - rel="${f#assemblies/}" - all_owners="$all_owners ${ASM_FILE_OWNERS[$rel]:-}" - done - all_owners=$(echo "$all_owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) - - if [[ -z "$(echo "$all_owners" | tr -d ' ')" ]]; then - warn "No owners for assemblies/$dn — keeping as-is" - ASM_DIR_DEST["$dn"]="$dn" - continue - fi - - dest=$(_compute_dest $all_owners) - ASM_DIR_DEST["$dn"]="$dest" - [[ "$dn" != "$dest" ]] && info " Assembly dir: $dn → $dest (owners: $all_owners)" - done - - # Compute flat assembly file destinations - declare -A FLAT_ASM_DEST=() # filename → dest dir name - FLAT_ASM_DEST[__sentinel__]=1 # bash 5.3 workaround: empty assoc array is "unbound" - for f in assemblies/*.adoc; do - [[ -f "$f" ]] || continue - bn=$(basename "$f") - owners="${ASM_FILE_OWNERS[$bn]:-}" - owners=$(echo "$owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) - - if [[ -z "$(echo "$owners" | tr -d ' ')" ]]; then - warn "No owners for flat assembly $bn — keeping as-is" - continue - fi - - dest=$(_compute_dest $owners) - FLAT_ASM_DEST["$bn"]="$dest" - info " Flat assembly: $bn → $dest (owners: $owners)" - done - unset 'FLAT_ASM_DEST[__sentinel__]' - - # Compute module dir destinations - declare -A MOD_DIR_DEST # old dir name → new dir name - for md in "${!MOD_DIR_OWNERS[@]}"; do - [[ -d "modules/$md" ]] || continue - owners="${MOD_DIR_OWNERS[$md]}" - - dest=$(_compute_dest $owners) - MOD_DIR_DEST["$md"]="$dest" - [[ "$md" != "$dest" ]] && info " Module dir: $md → $dest (owners: $owners)" - done - - # Check for untraced module dirs - for mod_dir in modules/*/; do - [[ -d "$mod_dir" ]] || continue - md=$(basename "$mod_dir") - [[ "$md" == "shared" ]] && continue - if [[ -z "${MOD_DIR_DEST[$md]:-}" ]]; then - warn "Module dir $md not referenced by any title — keeping as-is" - fi - done - - # ── Build image file ownership map ── - # IMG_FILE_OWNERS: "subdir/file.png" → "ctx1 ctx2 ..." - declare -A IMG_FILE_OWNERS - - # Helper: extract image references (subdir/file.png) from an adoc file - _extract_img_refs() { - grep -v '^//' "$1" 2>/dev/null | grep -oP 'image::?\K[^/]+/[^[\]]+' || true - } - - # From master.adoc - for master in titles/*/master.adoc; do - d=$(basename "$(dirname "$master")") - ctx="${DIR_CTX[$d]}" - while IFS= read -r ref; do - [[ -z "$ref" ]] && continue - IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $ctx" - done < <(_extract_img_refs "$master") - done - - # From modules in subdirs (inherit owners through MOD_DIR_OWNERS) - for mod_dir in modules/*/; do - [[ -d "$mod_dir" ]] || continue - md=$(basename "$mod_dir") - [[ "$md" == "shared" ]] && continue - owners="${MOD_DIR_OWNERS[$md]:-}" - [[ -z "$(echo "$owners" | tr -d ' ')" ]] && continue - for f in "$mod_dir"*.adoc; do - [[ -f "$f" ]] || continue - while IFS= read -r ref; do - [[ -z "$ref" ]] && continue - IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $owners" - done < <(_extract_img_refs "$f") - done - done - - # From shared modules — inherit owners from all titles/assemblies that include each module - # Build reverse index: shared_module_basename → owners (single grep pass per file) - declare -A SHARED_MOD_OWNERS - for master in titles/*/master.adoc; do - d=$(basename "$(dirname "$master")") - ctx="${DIR_CTX[$d]}" - while IFS= read -r sm; do - [[ -z "$sm" ]] && continue - SHARED_MOD_OWNERS["$sm"]="${SHARED_MOD_OWNERS[$sm]:-} $ctx" - done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'modules/shared/+\K[^[]+' || true) - done - for af in "${!ASM_FILE_OWNERS[@]}"; do - [[ -f "assemblies/$af" ]] || continue - owners="${ASM_FILE_OWNERS[$af]}" - while IFS= read -r sm; do - [[ -z "$sm" ]] && continue - SHARED_MOD_OWNERS["$sm"]="${SHARED_MOD_OWNERS[$sm]:-} $owners" - done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'modules/shared/+\K[^[]+' || true) - done - - # Now trace image refs from shared modules using pre-built ownership - for f in modules/shared/*.adoc; do - [[ -f "$f" ]] || continue - bn=$(basename "$f") - shared_mod_owners="${SHARED_MOD_OWNERS[$bn]:-}" - [[ -z "$(echo "$shared_mod_owners" | tr -d ' ')" ]] && continue - while IFS= read -r ref; do - [[ -z "$ref" ]] && continue - IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $shared_mod_owners" - done < <(_extract_img_refs "$f") - done - - # From assemblies in subdirs (inherit owners, skip shared) - for af in "${!ASM_FILE_OWNERS[@]}"; do - [[ -f "assemblies/$af" ]] || continue - asm_dir=$(dirname "$af") - [[ "$asm_dir" == "shared" ]] && continue - owners="${ASM_FILE_OWNERS[$af]}" - while IFS= read -r ref; do - [[ -z "$ref" ]] && continue - IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $owners" - done < <(_extract_img_refs "assemblies/$af") - done - - # From shared assemblies — inherit owners from all titles/assemblies that include them - for af in "${!ASM_FILE_OWNERS[@]}"; do - [[ -f "assemblies/$af" ]] || continue - asm_dir=$(dirname "$af") - [[ "$asm_dir" != "shared" ]] && continue - owners="${ASM_FILE_OWNERS[$af]}" - while IFS= read -r ref; do - [[ -z "$ref" ]] && continue - IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $owners" - done < <(_extract_img_refs "assemblies/$af") - done - - # Deduplicate owners - for ref in "${!IMG_FILE_OWNERS[@]}"; do - IMG_FILE_OWNERS["$ref"]=$(echo "${IMG_FILE_OWNERS[$ref]}" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) - done - - # Compute per-file destination directory - # IMG_FILE_DEST: "old_dir/file.png" → "new_dir" - declare -A IMG_FILE_DEST=() - IMG_FILE_DEST[__sentinel__]=1 # bash 5.3 workaround - - for ref in "${!IMG_FILE_OWNERS[@]}"; do - [[ -f "images/$ref" ]] || continue - owners="${IMG_FILE_OWNERS[$ref]}" - dest=$(_compute_dest $owners) - old_dir=$(dirname "$ref") - if [[ "$old_dir" != "$dest" ]]; then - IMG_FILE_DEST["$ref"]="$dest" - info " Image: $ref → $dest/ (owners: $owners)" - fi - done - unset 'IMG_FILE_DEST[__sentinel__]' - - # Check for unreferenced image files - for img_file in images/*/*; do - [[ -f "$img_file" ]] || continue - ref="${img_file#images/}" - if [[ -z "${IMG_FILE_OWNERS[$ref]:-}" ]]; then - warn "Image $ref not referenced by any .adoc file — keeping as-is" - fi - done - - if ! $EXEC; then - phase "DRY RUN COMPLETE" - echo "" - echo "Summary:" - echo " Titles to rename: $(for d in "${!DIR_CTX[@]}"; do ctx="${DIR_CTX[$d]}"; [[ "$d" != "${CTX_DEST[$ctx]}" ]] && echo x; done | wc -l)" - echo " Assembly dirs to rename: $(for d in "${!ASM_DIR_DEST[@]}"; do [[ "$d" != "${ASM_DIR_DEST[$d]}" ]] && echo x; done | wc -l)" - echo " Flat assemblies to move: ${#FLAT_ASM_DEST[@]}" - echo " Module dirs to rename: $(for d in "${!MOD_DIR_DEST[@]}"; do [[ "$d" != "${MOD_DIR_DEST[$d]}" ]] && echo x; done | wc -l)" - echo " Image files to move: ${#IMG_FILE_DEST[@]}" - echo "" - echo "Re-run with --exec to apply: $0 --all --exec" - exit 0 - fi - - # ── Phase 1: Rename title directories ───────────────────────── - phase "Phase 1: Rename title directories" - # Process in sorted order for deterministic output - for master in $(find titles -maxdepth 2 -name master.adoc | sort); do - d=$(basename "$(dirname "$master")") - ctx="${DIR_CTX[$d]}" - new_dir="${CTX_DEST[$ctx]}" - if [[ "$d" != "$new_dir" ]]; then - info "git mv titles/$d titles/$new_dir" - git mv "titles/$d" "titles/$new_dir" - fi - done - - # ── Phase 2: Move assembly directories and files ────────────── - phase "Phase 2: Move assemblies" - - # Rename existing subdirs (sorted to handle merges predictably) - for old_dir in $(echo "${!ASM_DIR_DEST[@]}" | tr ' ' '\n' | sort); do - new_dir="${ASM_DIR_DEST[$old_dir]}" - [[ "$old_dir" == "$new_dir" ]] && continue - [[ ! -d "assemblies/$old_dir" ]] && continue - - if [[ -d "assemblies/$new_dir" ]]; then - info "Merging assemblies/$old_dir → assemblies/$new_dir" - move_dir_contents "assemblies/$old_dir" "assemblies/$new_dir" - else - info "git mv assemblies/$old_dir assemblies/$new_dir" - git mv "assemblies/$old_dir" "assemblies/$new_dir" - fi - done - - # Move flat assembly files into subdirs - for bn in $(echo "${!FLAT_ASM_DEST[@]}" | tr ' ' '\n' | sort); do - dest="${FLAT_ASM_DEST[$bn]}" - [[ ! -f "assemblies/$bn" ]] && continue - mkdir -p "assemblies/$dest" - info "git mv assemblies/$bn assemblies/$dest/" - git mv "assemblies/$bn" "assemblies/$dest/" - done - - # ── Phase 3: Move module directories ────────────────────────── - phase "Phase 3: Move modules" - - for old_dir in $(echo "${!MOD_DIR_DEST[@]}" | tr ' ' '\n' | sort); do - new_dir="${MOD_DIR_DEST[$old_dir]}" - [[ "$old_dir" == "$new_dir" ]] && continue - [[ ! -d "modules/$old_dir" ]] && continue - - if [[ -d "modules/$new_dir" ]]; then - info "Merging modules/$old_dir → modules/$new_dir" - move_dir_contents "modules/$old_dir" "modules/$new_dir" - else - info "git mv modules/$old_dir modules/$new_dir" - git mv "modules/$old_dir" "modules/$new_dir" - fi - done - - # ── Phase 4: Move image files ─────────────────────────────────── - phase "Phase 4: Move images" - - for ref in $(echo "${!IMG_FILE_DEST[@]}" | tr ' ' '\n' | sort); do - dest="${IMG_FILE_DEST[$ref]}" - [[ ! -f "images/$ref" ]] && continue - bn=$(basename "$ref") - mkdir -p "images/$dest" - info "git mv images/$ref images/$dest/$bn" - git mv "images/$ref" "images/$dest/$bn" - done - - # Clean up empty image directories - find images/ -mindepth 1 -type d -empty -delete 2>/dev/null || true - - # ── Phase 5: Update include paths ───────────────────────────── - phase "Phase 5: Update include and image paths" - - # Build sed script for master.adoc files - master_sed="" - - # Assembly dir renames - for old_dir in "${!ASM_DIR_DEST[@]}"; do - new_dir="${ASM_DIR_DEST[$old_dir]}" - [[ "$old_dir" == "$new_dir" ]] && continue - master_sed="${master_sed}s|include::assemblies/${old_dir}/|include::assemblies/${new_dir}/|g;" - done - - # Flat assembly file moves: assemblies/file.adoc → assemblies/<dest>/file.adoc - for bn in "${!FLAT_ASM_DEST[@]}"; do - dest="${FLAT_ASM_DEST[$bn]}" - master_sed="${master_sed}s|include::assemblies/${bn}|include::assemblies/${dest}/${bn}|g;" - done - - # Module dir renames - for old_dir in "${!MOD_DIR_DEST[@]}"; do - new_dir="${MOD_DIR_DEST[$old_dir]}" - [[ "$old_dir" == "$new_dir" ]] && continue - master_sed="${master_sed}s|include::modules/${old_dir}/|include::modules/${new_dir}/|g;" - done - - # Image file moves: image::old_dir/file.png → image::new_dir/file.png (no ../ ever) - for ref in "${!IMG_FILE_DEST[@]}"; do - dest="${IMG_FILE_DEST[$ref]}" - bn=$(basename "$ref") - old_dir=$(dirname "$ref") - master_sed="${master_sed}s|image::${old_dir}/${bn}|image::${dest}/${bn}|g;" - master_sed="${master_sed}s|image:${old_dir}/${bn}|image:${dest}/${bn}|g;" - done - - # Apply to all master.adoc files - if [[ -n "$master_sed" ]]; then - for master in titles/*/master.adoc; do - sed -i "$master_sed" "$master" - done - info "Updated include paths in master.adoc files" - fi - - # Build sed script for assembly files - asm_sed="" - - # Module dir renames: ../modules/<old>/ → ../modules/<new>/ - # Also handle: modules/<old>/ → ../modules/<new>/ (flat assemblies that moved to subdirs) - for old_dir in "${!MOD_DIR_DEST[@]}"; do - new_dir="${MOD_DIR_DEST[$old_dir]}" - [[ "$old_dir" == "$new_dir" ]] && continue - asm_sed="${asm_sed}s|include::\.\.\/modules/${old_dir}/|include::../modules/${new_dir}/|g;" - asm_sed="${asm_sed}s|include::modules/${old_dir}/|include::../modules/${new_dir}/|g;" - done - - # Assembly cross-references: ../assemblies/<old>/ → ../assemblies/<new>/ - for old_dir in "${!ASM_DIR_DEST[@]}"; do - new_dir="${ASM_DIR_DEST[$old_dir]}" - [[ "$old_dir" == "$new_dir" ]] && continue - asm_sed="${asm_sed}s|include::\.\.\/assemblies/${old_dir}/|include::../assemblies/${new_dir}/|g;" - done - - # Image file moves in assembly files (same pattern, no ../) - for ref in "${!IMG_FILE_DEST[@]}"; do - dest="${IMG_FILE_DEST[$ref]}" - bn=$(basename "$ref") - old_dir=$(dirname "$ref") - asm_sed="${asm_sed}s|image::${old_dir}/${bn}|image::${dest}/${bn}|g;" - asm_sed="${asm_sed}s|image:${old_dir}/${bn}|image:${dest}/${bn}|g;" - done - - # Apply to all assembly .adoc files in subdirs - if [[ -n "$asm_sed" ]]; then - for asm_file in assemblies/*/*.adoc; do - [[ -f "$asm_file" ]] || continue - sed -i "$asm_sed" "$asm_file" - done - info "Updated include paths in assembly files" - fi - - # Update image references in module files - mod_img_sed="" - for ref in "${!IMG_FILE_DEST[@]}"; do - dest="${IMG_FILE_DEST[$ref]}" - bn=$(basename "$ref") - old_dir=$(dirname "$ref") - mod_img_sed="${mod_img_sed}s|image::${old_dir}/${bn}|image::${dest}/${bn}|g;" - mod_img_sed="${mod_img_sed}s|image:${old_dir}/${bn}|image:${dest}/${bn}|g;" - done - if [[ -n "$mod_img_sed" ]]; then - find modules/ -name '*.adoc' -exec sed -i "$mod_img_sed" {} + - info "Updated image paths in module files" - fi - - # Fix flat assemblies that moved into subdirs: - # They used include::modules/ (no ../) — now they need ../modules/ - # The asm_sed above already handles renaming, but for module dirs that - # did NOT rename, we still need to add the ../ prefix - for bn in "${!FLAT_ASM_DEST[@]}"; do - dest="${FLAT_ASM_DEST[$bn]}" - file="assemblies/$dest/$bn" - [[ -f "$file" ]] || continue - # Add ../ to any remaining include::modules/ that wasn't caught by renames - sed -i 's|include::modules/|include::../modules/|g' "$file" - # Fix any double ../ that might result - sed -i 's|include::\.\.\/\.\.\/modules/|include::../modules/|g' "$file" - done - info "Fixed ../ prefix for flat assemblies moved to subdirs" - - # Also fix flat assemblies that had include::assemblies/ (same-dir sub-assembly refs) - # These were previously flat, so they used include::assemblies/<dir>/file - # Now in a subdir, they should use include::../assemblies/<dir>/file - for bn in "${!FLAT_ASM_DEST[@]}"; do - dest="${FLAT_ASM_DEST[$bn]}" - file="assemblies/$dest/$bn" - [[ -f "$file" ]] || continue - # Only fix non-relative assembly includes (not include::assembly- which is same-dir) - sed -i 's|include::assemblies/|include::../assemblies/|g' "$file" - sed -i 's|include::\.\.\/\.\.\/assemblies/|include::../assemblies/|g' "$file" - done - - # ── Phase 6: Verify ─────────────────────────────────────────── - phase "Phase 6: Verification" - - ERRORS=0 - # Check that all include targets exist - for master in titles/*/master.adoc; do - title_dir=$(dirname "$master") - while IFS= read -r inc; do - [[ -z "$inc" ]] && continue - # Skip paths with unresolved AsciiDoc attributes (resolved at build time) - [[ "$inc" == *"{"*"}"* ]] && continue - # Resolve through symlinks in title dir - target="$title_dir/$inc" - if [[ ! -e "$target" ]]; then - error "$master: missing include target: $inc" - ERRORS=$((ERRORS + 1)) - fi - done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'include::\K[^[]+' || true) - done - - for asm_file in assemblies/*/*.adoc; do - [[ -f "$asm_file" ]] || continue - asm_dir=$(dirname "$asm_file") - while IFS= read -r inc; do - [[ -z "$inc" ]] && continue - # Skip paths with unresolved AsciiDoc attributes (resolved at build time) - [[ "$inc" == *"{"*"}"* ]] && continue - target="$asm_dir/$inc" - if [[ ! -e "$target" ]]; then - error "$asm_file: missing include target: $inc" - ERRORS=$((ERRORS + 1)) - fi - done < <(grep -v '^//' "$asm_file" 2>/dev/null | grep -oP 'include::\K[^[]+' || true) - done - - # Verify image references (block image:: and inline image: macros) - # Only match actual AsciiDoc image macros, not YAML `image:` keys in code blocks - for adoc_file in $(find titles modules assemblies -name '*.adoc' 2>/dev/null | sort); do - [[ -f "$adoc_file" ]] || continue - while IFS= read -r img_ref; do - [[ -z "$img_ref" ]] && continue - # Skip non-image refs: YAML container images, OCI URIs, refs without subdir/ - [[ "$img_ref" == *"://"* || "$img_ref" == *" "* || "$img_ref" == *"'"* ]] && continue - [[ "$img_ref" != *"/"* ]] && continue - # Image references should never contain ../ - if [[ "$img_ref" == *"../"* ]]; then - error "$adoc_file: image reference contains '../': image::$img_ref" - ERRORS=$((ERRORS + 1)) - continue - fi - # Check image file exists via images/ directory - if [[ ! -f "images/$img_ref" ]]; then - error "$adoc_file: missing image: images/$img_ref" - ERRORS=$((ERRORS + 1)) - fi - done < <(grep -v '^//' "$adoc_file" 2>/dev/null | grep -oP 'image::?\K[^[\]]+' || true) - done - - if [[ $ERRORS -gt 0 ]]; then - warn "$ERRORS broken reference(s) found — review and fix manually" - else - info "All include and image targets verified" - fi - - phase "Done" - info "Review: git diff --stat" - info "Build: ./build/scripts/build-ccutil.sh" - exit 0 -fi - -# ═══════════════════════════════════════════════════════════════════ -# Single-title mode (legacy) -# ═══════════════════════════════════════════════════════════════════ - -DRY_RUN=true -if [[ "${1:-}" == "--exec" ]]; then - DRY_RUN=false - shift -fi - -if [[ $# -lt 1 || $# -gt 2 ]]; then - echo "Usage: $0 [--exec] <title-dir> [<new-context>]" - echo " $0 --list" - echo " $0 --all [--exec]" - exit 1 -fi - -TITLE_PATH="$1" -TITLE_PATH="${TITLE_PATH%/}" -[[ "$TITLE_PATH" != titles/* ]] && TITLE_PATH="titles/$TITLE_PATH" - -OLD_DIR=$(basename "$TITLE_PATH") -MASTER="$TITLE_PATH/master.adoc" - -if [[ ! -f "$MASTER" ]]; then - error "File not found: $MASTER" - exit 1 -fi - -if [[ $# -eq 2 ]]; then - NEW_CONTEXT="$2" - info "Using explicit context: $NEW_CONTEXT" -else - TITLE_ATTR=$(read_title "$MASTER") - if [[ -z "$TITLE_ATTR" ]]; then - error "No :title: attribute found in $MASTER" - exit 1 - fi - NEW_CONTEXT=$(derive_context "$TITLE_ATTR" "$MASTER") - info "Derived context from ':title: $TITLE_ATTR'" - info " → $NEW_CONTEXT" -fi - -# Add category prefix if available -CAT=$(read_category "$MASTER") -if [[ -n "$CAT" ]]; then - CATSLUG=$(slugify_category "$CAT") - NEW_DIR="${CATSLUG}_${NEW_CONTEXT}" - info "Category: $CAT → prefix: $CATSLUG" -else - NEW_DIR="$NEW_CONTEXT" -fi - -if [[ "$OLD_DIR" == "$NEW_DIR" ]]; then - info "Directory already matches: $OLD_DIR" -fi - -info "Migrating: titles/$OLD_DIR → titles/$NEW_DIR" - -ASSEMBLY_INCLUDES=$(grep -v '^//' "$MASTER" 2>/dev/null | grep -oP 'include::assemblies/\K[^[]+' || true) -ASSEMBLY_COUNT=$(echo "$ASSEMBLY_INCLUDES" | grep -c '\.adoc$' || true) -ASSEMBLY_COUNT=${ASSEMBLY_COUNT:-0} -MODULE_DIRS=$(grep -v '^//' "$MASTER" 2>/dev/null | grep -oP 'include::modules/\K[^/]+' | sort -u || true) - -info "Found $ASSEMBLY_COUNT assembly includes" - -if $DRY_RUN; then - echo "" - echo "=== DRY RUN ===" - echo "Would rename: titles/$OLD_DIR → titles/$NEW_DIR" - exit 0 -fi - -# Rename title directory -if [[ "$OLD_DIR" != "$NEW_DIR" ]]; then - info "Renaming titles/$OLD_DIR → titles/$NEW_DIR" - git mv "titles/$OLD_DIR" "titles/$NEW_DIR" - MASTER="titles/$NEW_DIR/master.adoc" -fi - -# Move flat assemblies -if [[ $ASSEMBLY_COUNT -gt 0 ]]; then - ASSEMBLY_FILES=() - while IFS= read -r asm; do - [[ -z "$asm" ]] && continue - [[ -f "assemblies/$asm" ]] && ASSEMBLY_FILES+=("assemblies/$asm") - done <<< "$ASSEMBLY_INCLUDES" - - if [[ ${#ASSEMBLY_FILES[@]} -gt 0 ]]; then - mkdir -p "assemblies/$NEW_DIR" - for asm_file in "${ASSEMBLY_FILES[@]}"; do - asm_basename=$(basename "$asm_file") - if [[ ! -f "assemblies/$NEW_DIR/$asm_basename" ]]; then - info "Moving $asm_file → assemblies/$NEW_DIR/" - git mv "$asm_file" "assemblies/$NEW_DIR/" - fi - done - fi -fi - -# Move modules -if [[ -n "$MODULE_DIRS" ]]; then - while IFS= read -r mod_dir; do - [[ -z "$mod_dir" || "$mod_dir" == "$NEW_DIR" || "$mod_dir" == "shared" ]] && continue - if [[ -d "modules/$mod_dir" ]]; then - if [[ -d "modules/$NEW_DIR" ]]; then - info "Merging modules/$mod_dir/* → modules/$NEW_DIR/" - for f in "modules/$mod_dir"/*; do - [[ -e "$f" ]] && git mv "$f" "modules/$NEW_DIR/" - done - rmdir "modules/$mod_dir" 2>/dev/null || true - else - info "Renaming modules/$mod_dir → modules/$NEW_DIR" - git mv "modules/$mod_dir" "modules/$NEW_DIR" - fi - fi - done <<< "$MODULE_DIRS" -fi - -# Update include paths -info "Updating include paths" -sed -i "s|include::assemblies/assembly-|include::assemblies/$NEW_DIR/assembly-|g" "$MASTER" -if [[ -n "$MODULE_DIRS" ]]; then - while IFS= read -r mod_dir; do - [[ -z "$mod_dir" || "$mod_dir" == "$NEW_DIR" ]] && continue - sed -i "s|include::modules/$mod_dir/|include::modules/$NEW_DIR/|g" "$MASTER" - done <<< "$MODULE_DIRS" -fi - -# Update :context: -sed -i "s|^:context:.*|:context: $NEW_CONTEXT|" "$MASTER" - -info "Done! Review: git diff --stat" diff --git a/build/scripts/cqa-00-directory-structure.sh b/build/scripts/cqa-00-directory-structure.sh new file mode 100755 index 00000000000..fb8edb35c85 --- /dev/null +++ b/build/scripts/cqa-00-directory-structure.sh @@ -0,0 +1,764 @@ +#!/bin/bash +# shellcheck disable=SC2128,SC2178,SC2086 +# SC2128/SC2178: owners variables are space-delimited strings, not arrays — intentional. +# SC2086: unquoted $owners passed to _compute_dest for word splitting — intentional. +# +# cqa-00-directory-structure.sh +# +# Aligns title, assembly, module, and image directory names using <category>_<context> naming. +# Reports misnamed directories as [AUTOFIX] findings; --fix executes git mv operations. +# +# Usage: +# ./build/scripts/cqa-00-directory-structure.sh (report misnamed dirs) +# ./build/scripts/cqa-00-directory-structure.sh --fix (execute renames) +# ./build/scripts/cqa-00-directory-structure.sh [--fix] <title-dir> [<new-context>] +# +# Directory naming convention: +# titles/<category>_<context>/ +# assemblies/<category>_<context>/ (owned by one title) +# assemblies/<category>_shared/ (shared within a category) +# assemblies/shared/ (shared across categories) +# modules/<category>_<context>/ (owned by one title) +# modules/<category>_shared/ (shared within a category) +# modules/shared/ (shared across categories) + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" + +readonly _SHARED_DIR="shared" + +# Custom arg parsing: accept standard flags plus optional positional args for single-title mode +CQA_FIX_MODE=false +# shellcheck disable=SC2034 # CQA_FORMAT used by cqa-lib output functions +CQA_FORMAT="$_CQA_FMT_CHECKLIST" +_TITLE_ARG="" +_CONTEXT_ARG="" +# shellcheck disable=SC2034 +for arg in "$@"; do + case "$arg" in + --fix) CQA_FIX_MODE=true ;; + --all) ;; # accepted, ignored (always scans everything in --all mode) + --format=*) CQA_FORMAT="${arg#--format=}" ;; + --format) ;; # next arg handled below + -h|--help) + echo "Usage: $0 [--fix] [--format checklist|json]" >&2 + echo " $0 [--fix] <title-dir> [<new-context>]" >&2 + exit 0 + ;; + *) + if [[ "${_prev_arg:-}" == "--format" ]]; then + CQA_FORMAT="$arg" + elif [[ -z "$_TITLE_ARG" ]]; then + _TITLE_ARG="$arg" + elif [[ -z "$_CONTEXT_ARG" ]]; then + _CONTEXT_ARG="$arg" + fi + ;; + esac + _prev_arg="$arg" +done + +# ── Helpers ── + +slugify_category() { + local input="$1" + echo "$input" | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g' +} + +read_category() { + local file="$1" + grep -m1 '^:_mod-docs-category:' "$file" 2>/dev/null | sed 's/^:_mod-docs-category: //' +} + +read_title() { + local file="$1" + grep -m1 '^:title:' "$file" 2>/dev/null | sed 's/^:title: //' +} + +read_context() { + local file="$1" + grep -m1 '^:context:' "$file" 2>/dev/null | sed 's/^:context: *//' | sed 's/ *$//' +} + +resolve_local_attrs() { + local title="$1" + local master="$2" + + while IFS= read -r line; do + local attr_name attr_value + # shellcheck disable=SC2001 + attr_name=$(echo "$line" | sed 's/^:\([^:]*\):.*/\1/') + # shellcheck disable=SC2001 + attr_value=$(echo "$line" | sed 's/^:[^:]*: *//') + if [[ "$title" == *"{${attr_name}}"* ]]; then + title="${title//\{${attr_name}\}/${attr_value}}" + fi + done < <(grep -E '^:[a-z][-a-z0-9]*:' "$master" 2>/dev/null | grep -v '^:_mod-docs' | grep -v '^:context:' | grep -v '^:title:' | grep -v '^:subtitle:' | grep -v '^:abstract:' | grep -v '^:imagesdir:' || true) + + echo "$title" +} + +derive_context() { + local title="$1" + local master="${2:-}" + + if [[ -n "$master" && -f "$master" ]]; then + title=$(resolve_local_attrs "$title" "$master") + fi + + title="${title//\{ls-brand-name\}/developer-lightspeed-for-rhdh}" + title="${title//\{ls-short\}/developer-lightspeed-for-rhdh}" + title="${title//\{openshift-ai-connector-name\}/openshift-ai-connector-for-rhdh}" + title="${title//\{openshift-ai-connector-name-short\}/openshift-ai-connector-for-rhdh}" + title="${title//\{product\}/rhdh}" + title="${title//\{product-short\}/rhdh}" + title="${title//\{product-very-short\}/rhdh}" + title="${title//\{product-local\}/rhdh-local}" + title="${title//\{product-local-very-short\}/rhdh-local}" + title="${title//\{ocp-brand-name\}/ocp}" + title="${title//\{ocp-short\}/ocp}" + title="${title//\{ocp-very-short\}/ocp}" + title="${title//\{aks-brand-name\}/aks}" + title="${title//\{aks-name\}/aks}" + title="${title//\{aks-short\}/aks}" + title="${title//\{eks-brand-name\}/eks}" + title="${title//\{eks-name\}/eks}" + title="${title//\{eks-short\}/eks}" + title="${title//\{gke-brand-name\}/gke}" + title="${title//\{gke-short\}/gke}" + title="${title//\{gcp-brand-name\}/gcp}" + title="${title//\{gcp-short\}/gcp}" + title="${title//\{osd-brand-name\}/osd}" + title="${title//\{osd-short\}/osd}" + title="${title//\{rhacs-brand-name\}/acs}" + title="${title//\{rhacs-short\}/acs}" + title="${title//\{rhacs-very-short\}/acs}" + title="${title//\{rhoai-brand-name\}/openshift-ai}" + title="${title//\{rhoai-short\}/openshift-ai}" + title="${title//\{backstage\}/backstage}" + + title="${title,,}" + title="${title// /-}" + title="${title//_/-}" + # shellcheck disable=SC2001 + title=$(echo "$title" | sed 's/([^)]*)//g') + # shellcheck disable=SC2001 + title=$(echo "$title" | sed 's/[^a-z0-9-]//g') + # shellcheck disable=SC2001 + title=$(echo "$title" | sed 's/-\{2,\}/-/g') + title=$(echo "$title" | sed 's/^-//;s/-$//') + + echo "$title" +} + +move_dir_contents() { + local src="$1" dest="$2" + for f in "$src"/*; do + [[ -e "$f" ]] || continue + local base + base=$(basename "$f") + if [[ -e "$dest/$base" ]]; then + echo " Skip (exists): $dest/$base" >&2 + else + git mv "$f" "$dest/" + fi + done + rmdir "$src" 2>/dev/null || true + return 0 +} + +# ═══════════════════════════════════════════════════════════════════ +# Single-title mode (when a positional arg is given) +# ═══════════════════════════════════════════════════════════════════ +if [[ -n "$_TITLE_ARG" ]]; then + TITLE_PATH="$_TITLE_ARG" + TITLE_PATH="${TITLE_PATH%/}" + [[ "$TITLE_PATH" != titles/* ]] && TITLE_PATH="titles/$TITLE_PATH" + + OLD_DIR=$(basename "$TITLE_PATH") + MASTER="$TITLE_PATH/master.adoc" + + if [[ ! -f "$MASTER" ]]; then + echo "Error: File not found: $MASTER" >&2 + exit 1 + fi + + if [[ -n "$_CONTEXT_ARG" ]]; then + NEW_CONTEXT="$_CONTEXT_ARG" + else + TITLE_ATTR=$(read_title "$MASTER") + if [[ -z "$TITLE_ATTR" ]]; then + echo "Error: No :title: attribute found in $MASTER" >&2 + exit 1 + fi + NEW_CONTEXT=$(derive_context "$TITLE_ATTR" "$MASTER") + fi + + CAT=$(read_category "$MASTER") + if [[ -n "$CAT" ]]; then + CATSLUG=$(slugify_category "$CAT") + NEW_DIR="${CATSLUG}_${NEW_CONTEXT}" + else + NEW_DIR="$NEW_CONTEXT" + fi + + if [[ "$OLD_DIR" == "$NEW_DIR" ]]; then + echo "Directory already matches: $OLD_DIR" + exit 0 + fi + + if [[ "$CQA_FIX_MODE" != true ]]; then + echo "Would rename: titles/$OLD_DIR -> titles/$NEW_DIR" + echo "Run with --fix to execute" + exit 0 + fi + + echo "Renaming titles/$OLD_DIR -> titles/$NEW_DIR" + git mv "titles/$OLD_DIR" "titles/$NEW_DIR" + MASTER="titles/$NEW_DIR/master.adoc" + + ASSEMBLY_INCLUDES=$(grep -v '^//' "$MASTER" 2>/dev/null | grep -oP 'include::assemblies/\K[^[]+' || true) + ASSEMBLY_COUNT=$(echo "$ASSEMBLY_INCLUDES" | grep -c '\.adoc$' || true) + ASSEMBLY_COUNT=${ASSEMBLY_COUNT:-0} + MODULE_DIRS=$(grep -v '^//' "$MASTER" 2>/dev/null | grep -oP 'include::modules/\K[^/]+' | sort -u || true) + + if [[ $ASSEMBLY_COUNT -gt 0 ]]; then + ASSEMBLY_FILES=() + while IFS= read -r asm; do + [[ -z "$asm" ]] && continue + [[ -f "assemblies/$asm" ]] && ASSEMBLY_FILES+=("assemblies/$asm") + done <<< "$ASSEMBLY_INCLUDES" + + if [[ ${#ASSEMBLY_FILES[@]} -gt 0 ]]; then + mkdir -p "assemblies/$NEW_DIR" + for asm_file in "${ASSEMBLY_FILES[@]}"; do + asm_basename=$(basename "$asm_file") + if [[ ! -f "assemblies/$NEW_DIR/$asm_basename" ]]; then + git mv "$asm_file" "assemblies/$NEW_DIR/" + fi + done + fi + fi + + if [[ -n "$MODULE_DIRS" ]]; then + while IFS= read -r mod_dir; do + [[ -z "$mod_dir" || "$mod_dir" == "$NEW_DIR" || "$mod_dir" == "$_SHARED_DIR" ]] && continue + if [[ -d "modules/$mod_dir" ]]; then + if [[ -d "modules/$NEW_DIR" ]]; then + for f in "modules/$mod_dir"/*; do + [[ -e "$f" ]] && git mv "$f" "modules/$NEW_DIR/" + done + rmdir "modules/$mod_dir" 2>/dev/null || true + else + git mv "modules/$mod_dir" "modules/$NEW_DIR" + fi + fi + done <<< "$MODULE_DIRS" + fi + + sed -i "s|include::assemblies/assembly-|include::assemblies/$NEW_DIR/assembly-|g" "$MASTER" + if [[ -n "$MODULE_DIRS" ]]; then + while IFS= read -r mod_dir; do + [[ -z "$mod_dir" || "$mod_dir" == "$NEW_DIR" ]] && continue + sed -i "s|include::modules/$mod_dir/|include::modules/$NEW_DIR/|g" "$MASTER" + done <<< "$MODULE_DIRS" + fi + + sed -i "s|^:context:.*|:context: $NEW_CONTEXT|" "$MASTER" + echo "Done. Review: git diff --stat" + exit 0 +fi + +# ═══════════════════════════════════════════════════════════════════ +# Full repo mode (default — no positional arg) +# ═══════════════════════════════════════════════════════════════════ + +cqa_header "0" "Verify Directory Structure (<category>_<context> naming)" + +# ── Phase 0: Pre-computation ── + +declare -A CTX_CAT # context → category slug +declare -A CTX_DEST # context → <catslug>_<context> +declare -A DIR_CTX # title dir basename → context +TITLE_LIST=() + +for master in titles/*/master.adoc; do + d=$(basename "$(dirname "$master")") + ctx=$(read_context "$master") + cat=$(read_category "$master") + if [[ -z "$cat" ]]; then + cqa_file_start "$master" + cqa_fail_manual "$master" "" "Missing :_mod-docs-category: attribute" + continue + fi + cs=$(slugify_category "$cat") + CTX_CAT["$ctx"]="$cs" + CTX_DEST["$ctx"]="${cs}_${ctx}" + DIR_CTX["$d"]="$ctx" + TITLE_LIST+=("$ctx") +done + +# ── Build assembly ownership map ── +declare -A ASM_FILE_OWNERS + +for master in titles/*/master.adoc; do + d=$(basename "$(dirname "$master")") + ctx="${DIR_CTX[$d]:-}" + [[ -z "$ctx" ]] && continue + while IFS= read -r inc; do + [[ -z "$inc" ]] && continue + ASM_FILE_OWNERS["$inc"]="${ASM_FILE_OWNERS[$inc]:-} $ctx" + done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'include::assemblies/\K[^[]+' || true) +done + +# Propagate ownership to sub-assemblies (iterative) +changed=true +while $changed; do + changed=false + for af in "${!ASM_FILE_OWNERS[@]}"; do + [[ -f "assemblies/$af" ]] || continue + parent_owners="${ASM_FILE_OWNERS[$af]}" + asm_dir=$(dirname "$af") + + while IFS= read -r sub; do + [[ -z "$sub" ]] && continue + if [[ "$asm_dir" != "." ]]; then + sub_path="$asm_dir/$sub" + else + sub_path="$sub" + fi + old="${ASM_FILE_OWNERS[$sub_path]:-}" + combined=$(echo "$old $parent_owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) + if [[ "$combined" != "$old" ]]; then + ASM_FILE_OWNERS["$sub_path"]="$combined" + changed=true + fi + done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'include::\Kassembly-[^[]+' || true) + + while IFS= read -r sub_path; do + [[ -z "$sub_path" ]] && continue + old="${ASM_FILE_OWNERS[$sub_path]:-}" + combined=$(echo "$old $parent_owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) + if [[ "$combined" != "$old" ]]; then + ASM_FILE_OWNERS["$sub_path"]="$combined" + changed=true + fi + done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'include::\.\.\/assemblies/\K[^[]+' || true) + done +done + +# ── Build module dir ownership map ── +declare -A MOD_DIR_OWNERS + +for master in titles/*/master.adoc; do + d=$(basename "$(dirname "$master")") + ctx="${DIR_CTX[$d]:-}" + [[ -z "$ctx" ]] && continue + while IFS= read -r md; do + [[ -z "$md" || "$md" == "$_SHARED_DIR" ]] && continue + MOD_DIR_OWNERS["$md"]="${MOD_DIR_OWNERS[$md]:-} $ctx" + done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'include::modules/\K[^/]+' | sort -u || true) +done + +for af in "${!ASM_FILE_OWNERS[@]}"; do + [[ -f "assemblies/$af" ]] || continue + owners="${ASM_FILE_OWNERS[$af]}" + while IFS= read -r md; do + [[ -z "$md" || "$md" == "$_SHARED_DIR" ]] && continue + MOD_DIR_OWNERS["$md"]="${MOD_DIR_OWNERS[$md]:-} $owners" + done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'include::(\.\.\/)?modules/\K[^/]+' | sort -u || true) +done + +for md in "${!MOD_DIR_OWNERS[@]}"; do + MOD_DIR_OWNERS["$md"]=$(echo "${MOD_DIR_OWNERS[$md]}" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) +done + +# ── Compute destinations ── +_compute_dest() { + local owners_str="$*" + local -a owners + read -ra owners <<< "$owners_str" + local n=${#owners[@]} + + if [[ $n -eq 0 ]]; then + echo "UNKNOWN" + return + fi + + if [[ $n -eq 1 ]]; then + echo "${CTX_DEST[${owners[0]}]}" + return + fi + + local first_cat="${CTX_CAT[${owners[0]}]}" + local all_same=true + for o in "${owners[@]}"; do + if [[ "${CTX_CAT[$o]:-}" != "$first_cat" ]]; then + all_same=false + break + fi + done + + if $all_same; then + echo "${first_cat}_${_SHARED_DIR}" + else + echo "$_SHARED_DIR" + fi +} + +# ── Report misnamed directories ── + +# Title directories +for d in "${!DIR_CTX[@]}"; do + ctx="${DIR_CTX[$d]}" + expected="${CTX_DEST[$ctx]}" + if [[ "$d" != "$expected" ]]; then + cqa_file_start "titles/$d/master.adoc" + cqa_fail_autofix "titles/$d" "" "Title dir should be titles/$expected" "Renamed titles/$d -> titles/$expected" + fi +done + +# Assembly directories +declare -A ASM_DIR_DEST +for asm_dir in assemblies/*/; do + [[ -d "$asm_dir" ]] || continue + dn=$(basename "$asm_dir") + [[ "$dn" == "$_SHARED_DIR" || "$dn" == "modules" ]] && continue + + all_owners="" + for f in "$asm_dir"*.adoc; do + [[ -f "$f" ]] || continue + rel="${f#assemblies/}" + all_owners="$all_owners ${ASM_FILE_OWNERS[$rel]:-}" + done + all_owners=$(echo "$all_owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) + + if [[ -z "$(echo "$all_owners" | tr -d ' ')" ]]; then + ASM_DIR_DEST["$dn"]="$dn" + continue + fi + + dest=$(_compute_dest $all_owners) + ASM_DIR_DEST["$dn"]="$dest" + if [[ "$dn" != "$dest" ]]; then + cqa_file_start "assemblies/$dn" + cqa_fail_autofix "assemblies/$dn" "" "Assembly dir should be assemblies/$dest" "Renamed assemblies/$dn -> assemblies/$dest" + fi +done + +# Flat assembly files +declare -A FLAT_ASM_DEST=() +FLAT_ASM_DEST[__sentinel__]=1 +for f in assemblies/*.adoc; do + [[ -f "$f" ]] || continue + bn=$(basename "$f") + owners="${ASM_FILE_OWNERS[$bn]:-}" + owners=$(echo "$owners" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) + + if [[ -z "$(echo "$owners" | tr -d ' ')" ]]; then + continue + fi + + dest=$(_compute_dest $owners) + FLAT_ASM_DEST["$bn"]="$dest" + cqa_file_start "$f" + cqa_fail_autofix "$f" "" "Flat assembly should be in assemblies/$dest/" "Moved to assemblies/$dest/" +done +unset 'FLAT_ASM_DEST[__sentinel__]' + +# Module directories +declare -A MOD_DIR_DEST +for md in "${!MOD_DIR_OWNERS[@]}"; do + [[ -d "modules/$md" ]] || continue + owners="${MOD_DIR_OWNERS[$md]}" + dest=$(_compute_dest $owners) + MOD_DIR_DEST["$md"]="$dest" + if [[ "$md" != "$dest" ]]; then + cqa_file_start "modules/$md" + cqa_fail_autofix "modules/$md" "" "Module dir should be modules/$dest" "Renamed modules/$md -> modules/$dest" + fi +done + +# Image file ownership and destinations +declare -A IMG_FILE_OWNERS + +_extract_img_refs() { + local file="$1" + grep -v '^//' "$file" 2>/dev/null | grep -oP 'image::?\K[^/]+/[^[\]]+' || true +} + +for master in titles/*/master.adoc; do + d=$(basename "$(dirname "$master")") + ctx="${DIR_CTX[$d]:-}" + [[ -z "$ctx" ]] && continue + while IFS= read -r ref; do + [[ -z "$ref" ]] && continue + IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $ctx" + done < <(_extract_img_refs "$master") +done + +for mod_dir in modules/*/; do + [[ -d "$mod_dir" ]] || continue + md=$(basename "$mod_dir") + [[ "$md" == "$_SHARED_DIR" ]] && continue + owners="${MOD_DIR_OWNERS[$md]:-}" + [[ -z "$(echo "$owners" | tr -d ' ')" ]] && continue + for f in "$mod_dir"*.adoc; do + [[ -f "$f" ]] || continue + while IFS= read -r ref; do + [[ -z "$ref" ]] && continue + IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $owners" + done < <(_extract_img_refs "$f") + done +done + +# Shared modules image ownership +declare -A SHARED_MOD_OWNERS +for master in titles/*/master.adoc; do + d=$(basename "$(dirname "$master")") + ctx="${DIR_CTX[$d]:-}" + [[ -z "$ctx" ]] && continue + while IFS= read -r sm; do + [[ -z "$sm" ]] && continue + SHARED_MOD_OWNERS["$sm"]="${SHARED_MOD_OWNERS[$sm]:-} $ctx" + done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'modules/shared/+\K[^[]+' || true) +done +for af in "${!ASM_FILE_OWNERS[@]}"; do + [[ -f "assemblies/$af" ]] || continue + owners="${ASM_FILE_OWNERS[$af]}" + while IFS= read -r sm; do + [[ -z "$sm" ]] && continue + SHARED_MOD_OWNERS["$sm"]="${SHARED_MOD_OWNERS[$sm]:-} $owners" + done < <(grep -v '^//' "assemblies/$af" 2>/dev/null | grep -oP 'modules/shared/+\K[^[]+' || true) +done + +for f in modules/shared/*.adoc; do + [[ -f "$f" ]] || continue + bn=$(basename "$f") + shared_mod_owners="${SHARED_MOD_OWNERS[$bn]:-}" + [[ -z "$(echo "$shared_mod_owners" | tr -d ' ')" ]] && continue + while IFS= read -r ref; do + [[ -z "$ref" ]] && continue + IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $shared_mod_owners" + done < <(_extract_img_refs "$f") +done + +for af in "${!ASM_FILE_OWNERS[@]}"; do + [[ -f "assemblies/$af" ]] || continue + owners="${ASM_FILE_OWNERS[$af]}" + while IFS= read -r ref; do + [[ -z "$ref" ]] && continue + IMG_FILE_OWNERS["$ref"]="${IMG_FILE_OWNERS[$ref]:-} $owners" + done < <(_extract_img_refs "assemblies/$af") +done + +for ref in "${!IMG_FILE_OWNERS[@]}"; do + IMG_FILE_OWNERS["$ref"]=$(echo "${IMG_FILE_OWNERS[$ref]}" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' || true) +done + +declare -A IMG_FILE_DEST=() +IMG_FILE_DEST[__sentinel__]=1 + +for ref in "${!IMG_FILE_OWNERS[@]}"; do + [[ -f "images/$ref" ]] || continue + owners="${IMG_FILE_OWNERS[$ref]}" + dest=$(_compute_dest $owners) + old_dir=$(dirname "$ref") + if [[ "$old_dir" != "$dest" ]]; then + IMG_FILE_DEST["$ref"]="$dest" + cqa_file_start "images/$ref" + cqa_fail_autofix "images/$ref" "" "Image should be in images/$dest/" "Moved to images/$dest/" + fi +done +unset 'IMG_FILE_DEST[__sentinel__]' + +# ── Execute renames if --fix ── +if [[ "$CQA_FIX_MODE" == true ]]; then + + # Phase 1: Rename title directories + for master in $(find titles -maxdepth 2 -name master.adoc | sort); do + d=$(basename "$(dirname "$master")") + ctx="${DIR_CTX[$d]:-}" + [[ -z "$ctx" ]] && continue + new_dir="${CTX_DEST[$ctx]}" + if [[ "$d" != "$new_dir" ]]; then + git mv "titles/$d" "titles/$new_dir" + fi + done + + # Phase 2: Move assemblies + for old_dir in $(echo "${!ASM_DIR_DEST[@]}" | tr ' ' '\n' | sort); do + new_dir="${ASM_DIR_DEST[$old_dir]}" + [[ "$old_dir" == "$new_dir" ]] && continue + [[ ! -d "assemblies/$old_dir" ]] && continue + + if [[ -d "assemblies/$new_dir" ]]; then + move_dir_contents "assemblies/$old_dir" "assemblies/$new_dir" + else + git mv "assemblies/$old_dir" "assemblies/$new_dir" + fi + done + + for bn in $(echo "${!FLAT_ASM_DEST[@]}" | tr ' ' '\n' | sort); do + dest="${FLAT_ASM_DEST[$bn]}" + [[ ! -f "assemblies/$bn" ]] && continue + mkdir -p "assemblies/$dest" + git mv "assemblies/$bn" "assemblies/$dest/" + done + + # Phase 3: Move modules + for old_dir in $(echo "${!MOD_DIR_DEST[@]}" | tr ' ' '\n' | sort); do + new_dir="${MOD_DIR_DEST[$old_dir]}" + [[ "$old_dir" == "$new_dir" ]] && continue + [[ ! -d "modules/$old_dir" ]] && continue + + if [[ -d "modules/$new_dir" ]]; then + move_dir_contents "modules/$old_dir" "modules/$new_dir" + else + git mv "modules/$old_dir" "modules/$new_dir" + fi + done + + # Phase 4: Move images + for ref in $(echo "${!IMG_FILE_DEST[@]}" | tr ' ' '\n' | sort); do + dest="${IMG_FILE_DEST[$ref]}" + [[ ! -f "images/$ref" ]] && continue + bn=$(basename "$ref") + mkdir -p "images/$dest" + git mv "images/$ref" "images/$dest/$bn" + done + find images/ -mindepth 1 -type d -empty -delete 2>/dev/null || true + + # Phase 5: Update include paths + master_sed="" + for old_dir in "${!ASM_DIR_DEST[@]}"; do + new_dir="${ASM_DIR_DEST[$old_dir]}" + [[ "$old_dir" == "$new_dir" ]] && continue + master_sed="${master_sed}s|include::assemblies/${old_dir}/|include::assemblies/${new_dir}/|g;" + done + for bn in "${!FLAT_ASM_DEST[@]}"; do + dest="${FLAT_ASM_DEST[$bn]}" + master_sed="${master_sed}s|include::assemblies/${bn}|include::assemblies/${dest}/${bn}|g;" + done + for old_dir in "${!MOD_DIR_DEST[@]}"; do + new_dir="${MOD_DIR_DEST[$old_dir]}" + [[ "$old_dir" == "$new_dir" ]] && continue + master_sed="${master_sed}s|include::modules/${old_dir}/|include::modules/${new_dir}/|g;" + done + for ref in "${!IMG_FILE_DEST[@]}"; do + dest="${IMG_FILE_DEST[$ref]}" + bn=$(basename "$ref") + old_dir=$(dirname "$ref") + master_sed="${master_sed}s|image::${old_dir}/${bn}|image::${dest}/${bn}|g;" + master_sed="${master_sed}s|image:${old_dir}/${bn}|image:${dest}/${bn}|g;" + done + if [[ -n "$master_sed" ]]; then + for master in titles/*/master.adoc; do + sed -i "$master_sed" "$master" + done + fi + + asm_sed="" + for old_dir in "${!MOD_DIR_DEST[@]}"; do + new_dir="${MOD_DIR_DEST[$old_dir]}" + [[ "$old_dir" == "$new_dir" ]] && continue + asm_sed="${asm_sed}s|include::\.\.\/modules/${old_dir}/|include::../modules/${new_dir}/|g;" + asm_sed="${asm_sed}s|include::modules/${old_dir}/|include::../modules/${new_dir}/|g;" + done + for old_dir in "${!ASM_DIR_DEST[@]}"; do + new_dir="${ASM_DIR_DEST[$old_dir]}" + [[ "$old_dir" == "$new_dir" ]] && continue + asm_sed="${asm_sed}s|include::\.\.\/assemblies/${old_dir}/|include::../assemblies/${new_dir}/|g;" + done + for ref in "${!IMG_FILE_DEST[@]}"; do + dest="${IMG_FILE_DEST[$ref]}" + bn=$(basename "$ref") + old_dir=$(dirname "$ref") + asm_sed="${asm_sed}s|image::${old_dir}/${bn}|image::${dest}/${bn}|g;" + asm_sed="${asm_sed}s|image:${old_dir}/${bn}|image:${dest}/${bn}|g;" + done + if [[ -n "$asm_sed" ]]; then + for asm_file in assemblies/*/*.adoc; do + [[ -f "$asm_file" ]] || continue + sed -i "$asm_sed" "$asm_file" + done + fi + + mod_img_sed="" + for ref in "${!IMG_FILE_DEST[@]}"; do + dest="${IMG_FILE_DEST[$ref]}" + bn=$(basename "$ref") + old_dir=$(dirname "$ref") + mod_img_sed="${mod_img_sed}s|image::${old_dir}/${bn}|image::${dest}/${bn}|g;" + mod_img_sed="${mod_img_sed}s|image:${old_dir}/${bn}|image:${dest}/${bn}|g;" + done + if [[ -n "$mod_img_sed" ]]; then + find modules/ -name '*.adoc' -exec sed -i "$mod_img_sed" {} + + fi + + for bn in "${!FLAT_ASM_DEST[@]}"; do + dest="${FLAT_ASM_DEST[$bn]}" + file="assemblies/$dest/$bn" + [[ -f "$file" ]] || continue + sed -i 's|include::modules/|include::../modules/|g' "$file" + sed -i 's|include::\.\.\/\.\.\/modules/|include::../modules/|g' "$file" + done + + for bn in "${!FLAT_ASM_DEST[@]}"; do + dest="${FLAT_ASM_DEST[$bn]}" + file="assemblies/$dest/$bn" + [[ -f "$file" ]] || continue + sed -i 's|include::assemblies/|include::../assemblies/|g' "$file" + sed -i 's|include::\.\.\/\.\.\/assemblies/|include::../assemblies/|g' "$file" + done + + # Phase 6: Verify + ERRORS=0 + for master in titles/*/master.adoc; do + title_dir=$(dirname "$master") + while IFS= read -r inc; do + [[ -z "$inc" ]] && continue + [[ "$inc" == *"{"*"}"* ]] && continue + target="$title_dir/$inc" + if [[ ! -e "$target" ]]; then + cqa_fail_manual "$master" "" "Broken include after rename: $inc" + ERRORS=$((ERRORS + 1)) + fi + done < <(grep -v '^//' "$master" 2>/dev/null | grep -oP 'include::\K[^[]+' || true) + done + + for asm_file in assemblies/*/*.adoc; do + [[ -f "$asm_file" ]] || continue + asm_dir_path=$(dirname "$asm_file") + while IFS= read -r inc; do + [[ -z "$inc" ]] && continue + [[ "$inc" == *"{"*"}"* ]] && continue + target="$asm_dir_path/$inc" + if [[ ! -e "$target" ]]; then + cqa_fail_manual "$asm_file" "" "Broken include after rename: $inc" + ERRORS=$((ERRORS + 1)) + fi + done < <(grep -v '^//' "$asm_file" 2>/dev/null | grep -oP 'include::\K[^[]+' || true) + done + + for adoc_file in $(find titles modules assemblies -name '*.adoc' 2>/dev/null | sort); do + [[ -f "$adoc_file" ]] || continue + while IFS= read -r img_ref; do + [[ -z "$img_ref" ]] && continue + [[ "$img_ref" == *"://"* || "$img_ref" == *" "* || "$img_ref" == *"'"* ]] && continue + [[ "$img_ref" != *"/"* ]] && continue + if [[ "$img_ref" == *"../"* ]]; then + cqa_fail_manual "$adoc_file" "" "Image reference contains '../': image::$img_ref" + ERRORS=$((ERRORS + 1)) + continue + fi + if [[ ! -f "images/$img_ref" ]]; then + cqa_fail_manual "$adoc_file" "" "Missing image after rename: images/$img_ref" + ERRORS=$((ERRORS + 1)) + fi + done < <(grep -v '^//' "$adoc_file" 2>/dev/null | grep -oP 'image::?\K[^[\]]+' || true) + done +fi + +cqa_summary +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-00-orphaned-modules.sh b/build/scripts/cqa-00-orphaned-modules.sh new file mode 100755 index 00000000000..59d6d0e52be --- /dev/null +++ b/build/scripts/cqa-00-orphaned-modules.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# cqa-00-orphaned-modules.sh - Find and optionally delete orphaned modules (CQA #0) +# +# Usage: ./cqa-00-orphaned-modules.sh [--fix] [--format checklist|json] +# +# Unlike other CQA scripts, this operates on the entire repository (not per-title). +# The --all flag is accepted but ignored (always scans everything). +# A positional <file-path> argument is also accepted but ignored. +# +# Checks: +# - .adoc files in artifacts/, assemblies/, modules/ not referenced by any include:: +# - Image files in images/ not referenced by any .adoc file +# +# Autofix (--fix): Deletes orphaned files (git rm if tracked, rm otherwise) + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" + +# Custom arg parsing: accept standard flags but don't require a target file +CQA_FIX_MODE=false +# shellcheck disable=SC2034 # CQA_FORMAT/CQA_FIX_MODE used by cqa-lib output functions +CQA_FORMAT="checklist" +# shellcheck disable=SC2034 +for arg in "$@"; do + case "$arg" in + --fix) CQA_FIX_MODE=true ;; + --all) ;; # accepted, ignored (always scans everything) + --format=*) CQA_FORMAT="${arg#--format=}" ;; + --format) ;; # next arg handled below + checklist|json) + if [[ "${_prev_arg:-}" == "--format" ]]; then + CQA_FORMAT="$arg" + fi + ;; + -h|--help) + echo "Usage: $0 [--fix] [--format checklist|json]" >&2 + exit 0 + ;; + *) + # Accept and ignore positional args (file paths) for interface compatibility + ;; + esac + _prev_arg="$arg" +done + +cqa_header "0" "Find Orphaned Modules and Images" + +# ── Collect all include:: references ── +declare -A INCLUDED_BASENAMES=() + +while IFS= read -r inc_line; do + # Extract path from include::path[...] + inc_path="${inc_line#include::}" + inc_path="${inc_path%%\[*}" + inc_bn=$(basename "$inc_path") + + # If basename contains {attribute}, convert to regex pattern + if [[ "$inc_bn" == *"{"* ]]; then + INCLUDED_BASENAMES["pattern:$inc_bn"]=1 + else + INCLUDED_BASENAMES["$inc_bn"]=1 + fi +done < <(grep -rh "^include::" --include="*.adoc" . 2>/dev/null | sed 's/^[[:space:]]*//') + +# ── Collect all image references ── +declare -A REFERENCED_IMAGES=() + +while IFS= read -r img_ref; do + REFERENCED_IMAGES["$img_ref"]=1 +done < <(grep -roh 'image::[^[]*' --include="*.adoc" titles/ modules/ assemblies/ 2>/dev/null | sed 's/^image:://' | xargs -I{} basename "{}" 2>/dev/null | sort -u) + +# Also check inline image: references +while IFS= read -r img_ref; do + REFERENCED_IMAGES["$img_ref"]=1 +done < <(grep -roh 'image:[^:][^[]*' --include="*.adoc" titles/ modules/ assemblies/ 2>/dev/null | sed 's/^image://' | xargs -I{} basename "{}" 2>/dev/null | sort -u) + +# ── Helper: check if a basename matches any include pattern ── +_is_included() { + local basename="$1" + + # Direct match + [[ -n "${INCLUDED_BASENAMES[$basename]+x}" ]] && return 0 + + # Pattern match (for includes with {attribute} substitution) + for key in "${!INCLUDED_BASENAMES[@]}"; do + if [[ "$key" == pattern:* ]]; then + local pattern="${key#pattern:}" + # Convert {attribute} to .* and dots to [.] + local regex + regex=$(echo "$pattern" | sed 's/\./[.]/g' | sed 's/{[^}]*}/.*/g') + if [[ "$basename" =~ ^${regex}$ ]]; then + return 0 + fi + fi + done + + return 1 +} + +# ── Check .adoc files ── +ORPHANED_COUNT=0 +DELETED_COUNT=0 + +while IFS= read -r file; do + [[ -f "$file" ]] || continue + + # Skip template files + [[ "$file" == *.template.adoc ]] && continue + + cqa_file_start "$file" + + file_bn=$(basename "$file") + + if ! _is_included "$file_bn"; then + if [[ "$CQA_FIX_MODE" == true ]]; then + if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then + git rm -q "$file" 2>/dev/null || rm -f "$file" + else + rm -f "$file" + fi + DELETED_COUNT=$((DELETED_COUNT + 1)) + fi + cqa_fail_autofix "$file" "" "Orphaned .adoc file (not included anywhere)" "Deleted orphaned file" + ORPHANED_COUNT=$((ORPHANED_COUNT + 1)) + fi +done < <(find artifacts assemblies modules -name "*.adoc" -type f 2>/dev/null | sort) + +# ── Check image files ── +while IFS= read -r file; do + [[ -f "$file" ]] || continue + + cqa_file_start "$file" + + file_bn=$(basename "$file") + + if [[ -z "${REFERENCED_IMAGES[$file_bn]+x}" ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then + git rm -q "$file" 2>/dev/null || rm -f "$file" + else + rm -f "$file" + fi + DELETED_COUNT=$((DELETED_COUNT + 1)) + fi + cqa_fail_autofix "$file" "" "Orphaned image (not referenced by any .adoc file)" "Deleted orphaned image" + ORPHANED_COUNT=$((ORPHANED_COUNT + 1)) + fi +done < <(find images/ -type f 2>/dev/null | sort) + +# Clean up empty image directories after deletion +if [[ "$CQA_FIX_MODE" == true && $DELETED_COUNT -gt 0 ]]; then + find images/ -mindepth 1 -type d -empty -delete 2>/dev/null || true +fi + +cqa_summary +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-01-asciidoctor-dita-vale.sh b/build/scripts/cqa-01-asciidoctor-dita-vale.sh index 5a0907f2226..b990517baa8 100755 --- a/build/scripts/cqa-01-asciidoctor-dita-vale.sh +++ b/build/scripts/cqa-01-asciidoctor-dita-vale.sh @@ -1,115 +1,214 @@ #!/bin/bash -# cqa-01-asciidoctor-dita-vale.sh -# Validates AsciiDoc DITA compliance using Vale (CQA #1) +# cqa-01-asciidoctor-dita-vale.sh - Validates AsciiDoc DITA compliance using Vale (CQA #1) +# Usage: ./cqa-01-asciidoctor-dita-vale.sh [--fix] [--all] [--output line|JSON] <file-path> # -# Reference: .claude/skills/cqa-01-asciidoctor-dita-vale.md +# Checks: +# - AsciiDoc DITA compliance via Vale with .vale-dita-only.ini # -# Usage: ./cqa-01-asciidoctor-dita-vale.sh [--fix] <file-path> - -set -e - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 +# Autofix: +# - AuthorLine: insert blank line after title +# - CalloutList: convert callout items to description list +# - BlockTitle: convert invalid block titles to lead-in sentences +# - TaskContents: add .Procedure before first numbered list +# - TaskStep: fix blank lines around steps +# +# Delegates: +# - ShortDescription -> CQA #8 +# - DocumentId -> CQA #10 + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2034 # Used by delegation framework +CQA_DELEGATES_TO=("ShortDescription:8" "DocumentId:10") + +[[ -f ".vale-dita-only.ini" ]] || { echo "Error: .vale-dita-only.ini not found" >&2; exit 1; } + +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa01_check() { + local target="$1" + + cqa_header "1" "Vale AsciiDoc DITA Compliance" "$target" + + # Collect files excluding attributes.adoc + local vale_files=() + for f in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$f" != *.adoc ]] && continue + [[ "$(basename "$f")" == "attributes.adoc" ]] && continue + vale_files+=("$f") + done + + [[ ${#vale_files[@]} -gt 0 ]] || { echo "No files to validate."; return; } + + cqa_file_start "$target" + + # Legacy --output support for backward compat + if [[ "$CQA_OUTPUT_FORMAT" == "JSON" ]]; then + vale --config .vale-dita-only.ini --output JSON "${vale_files[@]}" + return $? + fi + + # --- Fix mode --- + if [[ "$CQA_FIX_MODE" == true ]]; then + local vale_json + vale_json=$(vale --config .vale-dita-only.ini --output JSON "${vale_files[@]}" 2>/dev/null || true) + + local issues_tsv + issues_tsv=$(echo "$vale_json" | python3 -c " +import json, sys +try: + d = json.load(sys.stdin) + for f, issues in d.items(): + for i in issues: + print(f\"{f}\t{i['Line']}\t{i['Check']}\") +except: pass +" 2>/dev/null) + + # Fix AuthorLine + while IFS=$'\t' read -r file line check; do + [[ "$check" == "AsciiDocDITA.AuthorLine" ]] || continue + local title_ln=$((line - 1)) + local title_content + title_content=$(sed -n "${title_ln}p" "$file") + if [[ "$title_content" == "= "* ]]; then + sed -i "${title_ln}a\\\\" "$file" + cqa_fail_autofix "$file" "$line" "AuthorLine: missing blank line after title" "Added blank line after title" + fi + done <<< "$issues_tsv" + + # Fix CalloutList + while IFS=$'\t' read -r file line check; do + [[ "$check" == "AsciiDocDITA.CalloutList" ]] || continue + local line_content + line_content=$(sed -n "${line}p" "$file") + if [[ "$line_content" =~ ^\<[0-9]+\>[[:space:]] ]]; then + sed -i "${line}s/^<\([0-9]*\)> /<\1>:: /" "$file" + cqa_fail_autofix "$file" "$line" "CalloutList: invalid format" "Converted to description list" + fi + done <<< "$issues_tsv" + + # Fix BlockTitle + while IFS=$'\t' read -r file line check; do + [[ "$check" == "AsciiDocDITA.BlockTitle" ]] || continue + local line_content + line_content=$(sed -n "${line}p" "$file") + local next_line + next_line=$(sed -n "$((line + 1))p" "$file") + # Skip if next line is a block delimiter (table, example, source, image) + if [[ "$next_line" == "|==="* || "$next_line" == "===="* || "$next_line" == "----"* || "$next_line" == "[source"* || "$next_line" == "image::"* ]]; then + cqa_fail_manual "$file" "$line" "BlockTitle before block element -- review manually" + continue + fi + if [[ "$line_content" == "."* ]]; then + local title_text="${line_content#.}" + sed -i "${line}s/^\..*/${title_text}:/" "$file" + cqa_fail_autofix "$file" "$line" "BlockTitle: invalid .Title format" "Converted to lead-in sentence" fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -if [[ ! -f ".vale-dita-only.ini" ]]; then - echo "Error: .vale-dita-only.ini configuration file not found" >&2 - exit 1 -fi - -# Get repository root -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="." - -# Function to get all included files -get_all_files() { - local file="$1" - "$REPO_ROOT/build/scripts/list-all-included-files-starting-from.sh" "$file" + done <<< "$issues_tsv" + + # Fix TaskContents + while IFS=$'\t' read -r file line check; do + [[ "$check" == "AsciiDocDITA.TaskContents" ]] || continue + local first_ol + first_ol=$(grep -n '^\. ' "$file" | head -1 | cut -d: -f1) + if [[ -n "$first_ol" ]]; then + sed -i "$((first_ol))i\\\\.Procedure" "$file" + cqa_fail_autofix "$file" "$first_ol" "TaskContents: missing .Procedure" "Added .Procedure before line $first_ol" + fi + done <<< "$issues_tsv" + + # Fix TaskStep (process in reverse line order) + declare -A taskstep_files=() + while IFS=$'\t' read -r file line check; do + [[ "$check" == "AsciiDocDITA.TaskStep" ]] || continue + taskstep_files["$file"]+="$line " + done <<< "$issues_tsv" + for file in "${!taskstep_files[@]}"; do + for line in $(echo "${taskstep_files[$file]}" | tr ' ' '\n' | sort -rn); do + [[ -z "$line" ]] && continue + local prev_ln=$((line - 1)) + local prev_content + prev_content=$(sed -n "${prev_ln}p" "$file") + if [[ -z "$prev_content" ]]; then + local prev_prev_content + prev_prev_content=$(sed -n "$((line - 2))p" "$file") + if [[ "$prev_prev_content" == ".Procedure" ]]; then + sed -i "${prev_ln}d" "$file" + cqa_fail_autofix "$file" "$line" "TaskStep: blank line after .Procedure" "Removed blank line" + else + sed -i "${prev_ln}s/^$/+/" "$file" + cqa_fail_autofix "$file" "$line" "TaskStep: detached from preceding step" "Attached with + continuation" + fi + fi + done + done + + # Handle delegated checks + while IFS=$'\t' read -r file line check; do + case "$check" in + AsciiDocDITA.ShortDescription*) + cqa_delegated "$file" "$line" "8" "ShortDescription issue (run CQA #8)" "manual" ;; + AsciiDocDITA.DocumentId*) + cqa_delegated "$file" "$line" "10" "DocumentId issue (run CQA #10)" "manual" ;; + *) ;; + esac + done <<< "$issues_tsv" + + return 0 + fi + + # --- Report mode --- + if [[ "$CQA_FORMAT" == "json" ]]; then + local vale_json + vale_json=$(vale --config .vale-dita-only.ini --output JSON "${vale_files[@]}" 2>/dev/null || true) + # Parse and add to SARIF + echo "$vale_json" | python3 -c " +import json, sys +try: + d = json.load(sys.stdin) + for f, issues in d.items(): + for i in issues: + check = i['Check'] + kind = 'autofix' + target = '' + fix_type = 'autofix' + if 'ShortDescription' in check: + kind = 'delegated' + target = '8' + fix_type = 'manual' + elif 'DocumentId' in check: + kind = 'delegated' + target = '10' + fix_type = 'manual' + elif check in ('AsciiDocDITA.DocumentTitle', 'AsciiDocDITA.TaskTitle', + 'AsciiDocDITA.ConceptLink', 'AsciiDocDITA.AssemblyContents', + 'AsciiDocDITA.RelatedLinks', 'AsciiDocDITA.ExampleBlock'): + kind = 'manual' + print(f\"{f}\t{i['Line']}\t{kind}\t{target}\t{fix_type}\t{check}: {i['Message']}\") +except: pass +" 2>/dev/null | while IFS=$'\t' read -r file line kind delegate_to fix_type message; do + case "$kind" in + autofix) cqa_fail_autofix "$file" "$line" "$message" ;; + manual) cqa_fail_manual "$file" "$line" "$message" ;; + delegated) cqa_delegated "$file" "$line" "$delegate_to" "$message" "$fix_type" ;; + esac + done + else + local vale_output + vale_output=$(vale --config .vale-dita-only.ini --output line "${vale_files[@]}" 2>/dev/null || true) + if [[ -z "$vale_output" ]]; then + cqa_file_pass "$target" + else + local total_count + total_count=$(echo "$vale_output" | wc -l) + echo "$vale_output" | head -20 + echo "" + cqa_fail_autofix "$target" "" "Vale found ${total_count} DITA compliance issues" "Run with --fix to auto-resolve" + fi + fi + return 0 } -echo "=== CQA #1: Validate AsciiDoc DITA Compliance with Vale ===" -echo "" -echo "Reference: .claude/skills/cqa-01-asciidoctor-dita-vale.md" -echo "Config: .vale-dita-only.ini" -echo "" - -# Get all files, excluding attributes.adoc (defines attribute values -# using literal product names, which triggers false positives) -ALL_FILES=$(get_all_files "$TARGET_FILE" | tr ' ' '\n' | grep -v '/attributes\.adoc$' | tr '\n' ' ') - -if [[ -z "$ALL_FILES" ]]; then - echo "Error: No files found to validate" >&2 - exit 1 -fi - -# Count files -FILE_COUNT=$(echo "$ALL_FILES" | wc -w) -echo "Validating $FILE_COUNT file(s)..." -echo "" - -# Run Vale with DITA-only config (JSON output for easy parsing) -# Note: Vale exit codes: -# 0 = no errors -# 1 = errors found -# 2 = usage error -# shellcheck disable=SC2086 -vale --config .vale-dita-only.ini --output JSON $ALL_FILES -VALE_EXIT=$? - -echo "" -echo "=== Summary ===" - -if [[ $VALE_EXIT -eq 0 ]]; then - echo "✓ All files pass AsciiDoc DITA validation" - echo "" - echo "Target: 0 errors, acceptable warnings only" - echo "See .claude/skills/cqa-01-asciidoctor-dita-vale.md for acceptable warning types" - exit 0 -elif [[ $VALE_EXIT -eq 1 ]]; then - echo "✗ Vale found issues (see output above)" - echo "" - echo "Required: 0 errors" - echo "" - echo "All warnings must be fixed. Common fixes:" - echo " - AsciiDocDITA.BlockTitle: In ref modules use == headings; in procs restructure" - echo " - AsciiDocDITA.CalloutList: Replace callouts with inline comments" - echo " - AsciiDocDITA.ConceptLink: Move inline links to .Additional resources" - echo " - AsciiDocDITA.DocumentId: Add [id=\"{context}\"] before heading in master.adoc" - echo " - AsciiDocDITA.RelatedLinks: .Additional resources must be link-only (no prose)" - echo " - AsciiDocDITA.TaskStep: Split description lists into separate procedures" - echo "" - echo "See .claude/skills/cqa-01-asciidoctor-dita-vale.md for details" - exit 1 -else - echo "✗ Vale encountered an error (exit code: $VALE_EXIT)" - exit $VALE_EXIT -fi +cqa_run_for_each_title _cqa01_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-02-assembly-structure.sh b/build/scripts/cqa-02-assembly-structure.sh index 88ccd1b7a53..bc1b3e13f4d 100755 --- a/build/scripts/cqa-02-assembly-structure.sh +++ b/build/scripts/cqa-02-assembly-structure.sh @@ -1,259 +1,265 @@ #!/bin/bash -# cqa-02-assembly-structure.sh -# Validates assembly structure compliance (CQA #2) +# cqa-02-assembly-structure.sh - Validates assembly structure compliance (CQA #2) +# Usage: ./cqa-02-assembly-structure.sh [--fix] [--all] <file-path> # -# Reference: .claude/skills/cqa-02-assembly-structure.md +# Checks: +# - Content type ASSEMBLY on first line, not repeated +# - [role="_abstract"] introduction present +# - Introduction length (50-300 chars for non-master files) +# - ID with _{context} suffix +# - :context: attribute after title +# - Context save/restore directives (non-master assemblies) +# - .Prerequisites as == heading (not block title) +# - .Additional resources with [role="_additional-resources"] +# - No level 3+ subheadings +# - No content between includes # -# Assemblies should contain only: -# 1. Introduction with [role="_abstract"] -# 2. Include statements for modules -# 3. Optional == Prerequisites before includes (heading syntax for TOC visibility) -# 4. Optional .Additional resources at end -# -# Usage: ./cqa-02-assembly-structure.sh [--fix] <file-path> - -set -e - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Get repository root -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="." +# Autofix: content type, context save/restore, ID suffix, :context:, prerequisites heading, +# additional resources format + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2329 +_lineno() { + local pattern="$1" + local file="$2" + grep -n "$pattern" "$file" 2>/dev/null | head -1 | cut -d: -f1 +} -# Function to get all included files -get_all_files() { +# shellcheck disable=SC2329 +_fix_content_type_first_line() { local file="$1" - "$REPO_ROOT/build/scripts/list-all-included-files-starting-from.sh" "$file" + sed -i '/^:_mod-docs-content-type:/d' "$file" + sed -i '1s/^/:_mod-docs-content-type: ASSEMBLY\n/' "$file" } -# Get all files -ALL_FILES=$(get_all_files "$TARGET_FILE") - -# Filter to only assembly files -ASSEMBLY_FILES=$(echo "$ALL_FILES" | tr ' ' '\n' | grep -E "assemblies/.*\.adoc$|titles/.*/master\.adoc$" || true) - -if [[ -z "$ASSEMBLY_FILES" ]]; then - echo "No assembly files found." - exit 0 -fi - -echo "=== CQA #2: Verify Assembly Structure ===" -echo "" -echo "Reference: .claude/skills/cqa-02-assembly-structure.md" -echo "" -echo "Assemblies should contain only:" -echo " - Introduction with [role=\"_abstract\"]" -echo " - Include statements for modules" -echo " - Optional == Prerequisites before includes (heading, not block title)" -echo " - Optional .Additional resources at end" -echo "" +# shellcheck disable=SC2329 +_fix_add_context_save() { + local file="$1" + sed -i '1a\ifdef::context[:parent-context: {context}]' "$file" +} -# Track violations -TOTAL_ASSEMBLIES=0 -VIOLATIONS=0 +# shellcheck disable=SC2329 +_fix_add_context_restore() { + local file="$1" + sed -i '/^ifdef::parent-context\[:context: {parent-context}\]$/d' "$file" + sed -i '/^ifndef::parent-context\[:!context:\]$/d' "$file" + sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$file" + printf '\nifdef::parent-context[:context: {parent-context}]\nifndef::parent-context[:!context:]\n' >> "$file" +} -# Check each assembly -while IFS= read -r file; do - if [[ ! -f "$file" ]]; then - continue +# shellcheck disable=SC2329 +_fix_context_line() { + local file="$1" title_ln="$2" + sed -i '/^:context:/d' "$file" + local id_value + id_value=$(grep -m1 '\[id="' "$file" | sed 's/.*\[id="\([^"]*\)".*/\1/' | sed 's/_{context}$//') + if [[ -n "$id_value" ]]; then + sed -i "${title_ln}a\\\\n:context: ${id_value}" "$file" fi +} - TOTAL_ASSEMBLIES=$((TOTAL_ASSEMBLIES + 1)) - FILE_VIOLATIONS=0 - - echo "Checking: $(basename "$file")" - - # Check 1: Has abstract - if ! grep -q '\[role="_abstract"\]' "$file"; then - echo " ✗ Missing [role=\"_abstract\"] introduction" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi +# shellcheck disable=SC2329 +_cqa02_check() { + local target="$1" - # Check 2: Detect content between includes (DITA violation) - # This is complex - we need to find text that appears after an include but before the next include - # Strategy: Look for lines that are not: - # - Empty lines - # - Include statements - # - Comment lines (//) - # - Metadata lines (:...) - # - Block titles (.Prerequisites, .Additional resources) - # - Context management (ifdef, ifndef) + cqa_header "2" "Verify Assembly Structure" "$target" - # Extract the section after abstract and before any context restoration - # Look for non-empty, non-include, non-metadata content between includes + # Filter to assembly files only + local assembly_files=() + for f in "${_CQA_COLLECTED_FILES[@]}"; do + if echo "$f" | grep -qE "assemblies/.*\.adoc$|titles/.*/master\.adoc$"; then + assembly_files+=("$f") + fi + done - # Simple heuristic: Count paragraphs (non-empty lines that aren't special syntax) - # Only check includes after the = title line (preamble includes like - # include::artifacts/attributes.adoc[] are not subject to this rule) - TITLE_LINE=$(grep -n "^= " "$file" | head -1 | cut -d: -f1 || echo "0") - FIRST_INCLUDE_LINE=$(tail -n +"${TITLE_LINE:-1}" "$file" | grep -n "^include::" | head -1 | cut -d: -f1 || echo "0") - if [[ "$FIRST_INCLUDE_LINE" != "0" && "$TITLE_LINE" != "0" ]]; then - FIRST_INCLUDE_LINE=$((TITLE_LINE + FIRST_INCLUDE_LINE - 1)) + if [[ ${#assembly_files[@]} -eq 0 ]]; then + echo "No assembly files found." + return fi - if [[ "$FIRST_INCLUDE_LINE" != "0" ]]; then - # Get content after first include (post-title) - CONTENT_AFTER_INCLUDES=$(tail -n +$((FIRST_INCLUDE_LINE + 1)) "$file") + for file in "${assembly_files[@]}"; do + [[ -f "$file" ]] || continue - # Check for problematic content between includes - # Problematic = paragraph text (not empty, not include, not block title, not metadata, not comment) - SUSPECT_LINES=$(echo "$CONTENT_AFTER_INCLUDES" | grep -v "^$" | \ - grep -v "^include::" | \ - grep -v "^//" | \ - grep -v "^:" | \ - grep -v "^ifdef::" | \ - grep -v "^ifndef::" | \ - grep -v "^endif::" | \ - grep -v "^\." | \ - grep -v "^=" | \ - grep -v "^\*" | \ - grep -v "^-" | \ - grep -v "^|" | \ - grep -v "^\[" | \ - grep -v "^----$" | \ - grep -v "^====$" || true) + cqa_file_start "$file" + local is_master + is_master=$([[ "$(basename "$file")" == "master.adoc" ]] && echo true || echo false) - if [[ -n "$SUSPECT_LINES" ]]; then - SUSPECT_COUNT=$(echo "$SUSPECT_LINES" | wc -l) - echo " ⚠ Warning: May contain content between includes ($SUSPECT_COUNT lines)" - echo " Review for paragraphs/text between include statements" + # Check 1: Content type on first line + local first_line + first_line=$(sed -n '1p' "$file") + if [[ "$first_line" != ":_mod-docs-content-type: ASSEMBLY" ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + _fix_content_type_first_line "$file" + fi + cqa_fail_autofix "$file" "1" "Content type ASSEMBLY not on first line" "Fixed content type on line 1" + fi + local ct_count + ct_count=$(grep -c '^:_mod-docs-content-type:' "$file" || true) + if [[ $ct_count -gt 1 ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + awk '/^:_mod-docs-content-type:/ && ++n > 1 {next} 1' "$file" > "$file.tmp" && mv "$file.tmp" "$file" + fi + cqa_fail_autofix "$file" "" "Content type appears $ct_count times" "Removed duplicates" fi - fi - - # Check 3: Prerequisites location and format - # Should use == Prerequisites heading (not .Prerequisites block title), before first include - PREREQ_BLOCK_LINE=$(grep -n "^\.Prerequisites" "$file" | cut -d: -f1 || true) - PREREQ_BLOCK_LINE=${PREREQ_BLOCK_LINE:-0} - PREREQ_HEADING_LINE=$(grep -n "^== Prerequisites" "$file" | cut -d: -f1 || true) - PREREQ_HEADING_LINE=${PREREQ_HEADING_LINE:-0} - - if [[ "$PREREQ_BLOCK_LINE" != "0" ]]; then - echo " ✗ Uses .Prerequisites block title instead of == Prerequisites heading" - echo " Use '== Prerequisites' for TOC visibility, consistent with Additional resources" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi - PREREQ_LINE=${PREREQ_HEADING_LINE:-$PREREQ_BLOCK_LINE} - PREREQ_LINE=${PREREQ_LINE:-0} - if [[ "$PREREQ_LINE" != "0" && "$FIRST_INCLUDE_LINE" != "0" ]]; then - if [[ "$PREREQ_LINE" -gt "$FIRST_INCLUDE_LINE" ]]; then - echo " ✗ Prerequisites appears after include statements" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Check 2: Has abstract + if ! grep -q '\[role="_abstract"\]' "$file"; then + cqa_delegated "$file" "" "9" "Missing [role=\"_abstract\"] introduction" fi - fi - # Check 4: Additional resources location (should be at end, after all includes if present) - RESOURCES_LINE=$(grep -n "^\.Additional resources" "$file" | head -1 | cut -d: -f1 || true) - RESOURCES_LINE=${RESOURCES_LINE:-0} - LAST_INCLUDE_LINE=$(grep -n "^include::" "$file" | tail -1 | cut -d: -f1 || true) - LAST_INCLUDE_LINE=${LAST_INCLUDE_LINE:-0} + # Check 3: Introduction length (non-master) + if [[ "$is_master" == false ]]; then + local abstract_ln + abstract_ln=$(_lineno '\[role="_abstract"\]' "$file") + if [[ -n "$abstract_ln" ]]; then + local intro + intro=$(sed -n "$((abstract_ln + 1))p" "$file") + local intro_len=${#intro} + if [[ $intro_len -lt 50 ]]; then + cqa_delegated "$file" "$((abstract_ln + 1))" "9" "Introduction too short (${intro_len} chars, recommend 50-300)" "manual" + elif [[ $intro_len -gt 300 ]]; then + cqa_delegated "$file" "$((abstract_ln + 1))" "9" "Introduction too long (${intro_len} chars, recommend 50-300)" "manual" + fi + fi + fi - if [[ "$RESOURCES_LINE" != "0" && "$LAST_INCLUDE_LINE" != "0" ]]; then - if [[ "$RESOURCES_LINE" -lt "$LAST_INCLUDE_LINE" ]]; then - echo " ✗ .Additional resources appears before include statements" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Check 4: ID with _{context} (non-master) + if [[ "$is_master" == false ]]; then + if ! grep -q '\[id=".*_{context}"\]' "$file"; then + if grep -q '\[id="[^"]*"\]' "$file"; then + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i 's/\[id="\([^"]*[^}]\)"\]/[id="\1_{context}"]/' "$file" + fi + cqa_fail_autofix "$file" "" "ID missing _{context} suffix" "Added _{context} suffix" + else + cqa_fail_manual "$file" "" "Missing [id=\"..._{context}\"] attribute" + fi + fi fi - fi - # Check 5: Content type should be ASSEMBLY - CONTENT_TYPE=$(head -20 "$file" | grep ":_mod-docs-content-type:" | sed 's/:_mod-docs-content-type:[[:space:]]*//' | sed 's/[[:space:]]*$//' || echo "") - if [[ -n "$CONTENT_TYPE" && "$CONTENT_TYPE" != "ASSEMBLY" ]]; then - echo " ⚠ Warning: Content type is '$CONTENT_TYPE' (expected ASSEMBLY)" - fi + # Check 5: :context: after title (non-master) + local context_ln + context_ln=$(_lineno "^:context:" "$file") + local need_ctx_fix=false + if [[ -z "$context_ln" ]]; then + cqa_fail_autofix "$file" "" "Missing :context: attribute" "Added :context:" + need_ctx_fix=true + elif [[ "$is_master" == false ]]; then + local title_ln_chk + title_ln_chk=$(_lineno "^= " "$file") + if [[ -n "$title_ln_chk" ]]; then + if [[ "$context_ln" -le "$title_ln_chk" ]]; then + cqa_fail_autofix "$file" "$context_ln" ":context: must appear after the title" "Moved :context: after title" + need_ctx_fix=true + fi + fi + fi + if [[ "$CQA_FIX_MODE" == true && "$need_ctx_fix" == true && "$is_master" == false ]]; then + local title_ln_chk + title_ln_chk=$(_lineno "^= " "$file") + [[ -n "$title_ln_chk" ]] && _fix_context_line "$file" "$title_ln_chk" + fi - # Check 6: No level 2+ subheadings (assemblies shouldn't have === or deeper) - if grep -q "^===[[:space:]]" "$file"; then - echo " ✗ Contains level 2+ subheadings (=== or deeper)" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi + # Check 6: Context save/restore (non-master) + if [[ "$is_master" == false ]]; then + local second_line + second_line=$(sed -n '2p' "$file") + if [[ "$second_line" != ifdef::context* ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + _fix_add_context_save "$file" + fi + cqa_fail_autofix "$file" "2" "Missing context save on line 2" "Added context save" + fi + local save_count + save_count=$(grep -c "^ifdef::context\[:parent-context" "$file" || true) + if [[ $save_count -gt 1 ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + awk '/^ifdef::context\[:parent-context/ && ++n > 1 {next} 1' "$file" > "$file.tmp" && mv "$file.tmp" "$file" + fi + cqa_fail_autofix "$file" "" "Context save appears $save_count times" "Removed duplicates" + fi - # Check 7: No detailed lists/content after abstract (before first include) - ABSTRACT_LINE=$(grep -n '\[role="_abstract"\]' "$file" | head -1 | cut -d: -f1 || echo "0") + local last_line penult_line need_restore=false + last_line=$(tail -1 "$file") + penult_line=$(tail -2 "$file" | head -1) + if [[ "$penult_line" != 'ifdef::parent-context[:context: {parent-context}]' ]]; then + cqa_fail_autofix "$file" "" "Missing context restore (second-to-last line)" "Added context restore" + need_restore=true + fi + if [[ "$last_line" != 'ifndef::parent-context[:!context:]' ]]; then + cqa_fail_autofix "$file" "" "Missing context restore (last line)" "Added context restore" + need_restore=true + fi + if [[ "$CQA_FIX_MODE" == true && "$need_restore" == true ]]; then + _fix_add_context_restore "$file" + fi + fi - if [[ "$ABSTRACT_LINE" != "0" && "$FIRST_INCLUDE_LINE" != "0" ]]; then - # Extract content between abstract and first include - BETWEEN_ABSTRACT_AND_INCLUDE=$(sed -n "$((ABSTRACT_LINE + 2)),$((FIRST_INCLUDE_LINE - 1))p" "$file") + # Check 7: Prerequisites must use == heading + if grep -q "^\.Prerequisites" "$file"; then + local prereq_ln + prereq_ln=$(_lineno "^\.Prerequisites" "$file") + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i 's/^\.Prerequisites$/== Prerequisites/' "$file" + fi + cqa_fail_autofix "$file" "$prereq_ln" "Uses .Prerequisites block title instead of == heading" "Changed to == Prerequisites" + fi - # Check for problematic content (paragraphs, lists, but allow Prerequisites) - # Remove .Prerequisites / == Prerequisites section and check what remains - FILTERED=$(echo "$BETWEEN_ABSTRACT_AND_INCLUDE" | \ - grep -v "^\.Prerequisites" | \ - grep -v "^== Prerequisites" | \ - grep -v "^$" | \ - grep -v "^\*" | \ - grep -v "^-" | \ - grep -v "^:" | \ - grep -v "^//" || true) + # Check 8: No level 3+ subheadings + if grep -q "^===[[:space:]]" "$file"; then + local sub_ln + sub_ln=$(grep -n "^===[[:space:]]" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$sub_ln" "Contains level 3+ subheadings (=== or deeper)" + fi - if [[ -n "$FILTERED" ]]; then - NON_EMPTY=$(echo "$FILTERED" | grep -v "^$" || true) - if [[ -n "$NON_EMPTY" ]]; then - echo " ⚠ Warning: May contain detailed content between abstract and includes" - echo " Review for explanatory text that should be in a concept module" + # Check 9: Additional resources format + if grep -q "^\.Additional resources" "$file"; then + local ar_ln + ar_ln=$(_lineno "^\.Additional resources" "$file") + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i 's/^\.Additional resources$/[role="_additional-resources"]\n== Additional resources/' "$file" + fi + cqa_fail_autofix "$file" "$ar_ln" "Uses .Additional resources block title" "Changed to [role] + == heading" + elif grep -q "^== Additional resources" "$file"; then + if ! grep -q '\[role="_additional-resources"\]' "$file"; then + local ar_ln + ar_ln=$(_lineno "^== Additional resources" "$file") + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i '/^== Additional resources/i\[role="_additional-resources"]' "$file" + fi + cqa_fail_autofix "$file" "$ar_ln" "Missing [role=\"_additional-resources\"] attribute" "Added role attribute" fi fi - fi - # Summary for this file - if [[ $FILE_VIOLATIONS -eq 0 ]]; then - echo " ✓ Structure compliant" - else - VIOLATIONS=$((VIOLATIONS + FILE_VIOLATIONS)) - fi - echo "" -done <<< "$ASSEMBLY_FILES" + # Check 10: No content between includes + local title_ln + title_ln=$(_lineno "^= " "$file") + if [[ -n "$title_ln" ]]; then + local -a include_lns + mapfile -t include_lns < <(tail -n +"$title_ln" "$file" | grep -n "^include::" | grep -v "artifacts/" | cut -d: -f1 | while read -r n; do echo $((title_ln + n - 1)); done) + if [[ ${#include_lns[@]} -gt 1 ]]; then + local first_inc=${include_lns[0]} + local last_inc=${include_lns[-1]} + local between + between=$(sed -n "$((first_inc + 1)),$((last_inc - 1))p" "$file" | \ + grep -v -E "^$|^include::|^//|^ifdef::|^ifndef::|^endif::|^\[role=|^\.Additional resources|^== " || true) + if [[ -n "$between" ]]; then + local between_count + between_count=$(echo "$between" | wc -l) + cqa_fail_manual "$file" "$first_inc" "Content between include statements ($between_count lines)" + fi + fi + fi -# Final summary -echo "=== Summary ===" -echo "Assemblies checked: $TOTAL_ASSEMBLIES" + if [[ "$_CQA_CURRENT_FILE_HAS_ISSUES" == false ]]; then + cqa_file_pass "$file" + fi + done + return 0 +} -if [[ $VIOLATIONS -eq 0 ]]; then - echo "✓ All assemblies have compliant structure" - echo "" - echo "Note: Warnings indicate potential issues that require manual review" - echo "See .claude/skills/cqa-02-assembly-structure.md for guidance" - exit 0 -else - echo "✗ Found $VIOLATIONS violation(s)" - echo "" - echo "Common fixes:" - echo " - Move detailed content to concept modules" - echo " - Remove text between include statements" - echo " - Use '== Prerequisites' heading (not .Prerequisites block title)" - echo " - Move Prerequisites before first include" - echo " - Remove subheadings from assemblies" - echo "" - echo "See .claude/skills/cqa-02-assembly-structure.md for details" - exit 1 -fi +cqa_run_for_each_title _cqa02_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-03-content-is-modularized.sh b/build/scripts/cqa-03-content-is-modularized.sh index 17a56c60158..cada5e0733b 100755 --- a/build/scripts/cqa-03-content-is-modularized.sh +++ b/build/scripts/cqa-03-content-is-modularized.sh @@ -1,864 +1,182 @@ #!/bin/bash -# Fix content type metadata based on file content analysis (CQA requirement #11) +# cqa-03-content-is-modularized.sh - Validates content type metadata (CQA #3) +# Usage: ./cqa-03-content-is-modularized.sh [--fix] [--all] <file-path> # -# Usage: ./cqa-03-content-is-modularized.sh [--fix] <file-path> -# --fix: Apply automatic fixes (add/update metadata, normalize list formatting) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-03-content-is-modularized.sh titles/install-rhdh-ocp/master.adoc +# Checks: +# - Content type metadata present and correct (:_mod-docs-content-type:) +# - Content type on first line, not duplicated +# - .Procedure and .Verification section list formatting # -# Content type detection logic: -# - ASSEMBLY: File includes one or more modules with proc-, ref-, or con- prefix, OR assembly- filename prefix -# - PROCEDURE: File has .Procedure section followed by steps, OR proc- filename prefix -# - CONCEPT: File has con- filename prefix (no distinctive content pattern) -# - REFERENCE: File has ref- filename prefix (no distinctive content pattern) -# - SNIPPET: File has snip- filename prefix (no distinctive content pattern) -# -# This script automatically: -# - Adds or updates :_mod-docs-content-type: metadata -# - Ensures metadata is on the first line of the file -# - Removes duplicate occurrences of the metadata - -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" +# Autofix: +# - Adds/fixes content type metadata +# - Normalizes section list formatting -# Constants for content type and pattern matching -readonly CONTENT_TYPE_PROCEDURE="PROCEDURE" -readonly PATTERN_PROCEDURE_SECTION="^\.Procedure" -readonly PATTERN_VERIFICATION_SECTION="^\.Verification" -readonly PATTERN_NUMBERED_ITEM="^\\.+ " -readonly PATTERN_UNNUMBERED_ITEM="^\* " -readonly PATTERN_NESTED_ITEM="^\*\* " -readonly PATTERN_INCLUDE="^include::" +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -# AWK patterns for extracting content after section blocks -readonly AWK_AFTER_PROCEDURE='/^\.Procedure$/{flag=1; next} flag && /^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{exit} flag' -readonly AWK_AFTER_VERIFICATION='/^\.Verification$/{flag=1; next} flag && /^\.(Prerequisites|Procedure|Troubleshooting|Next steps|Additional)/{exit} flag' +readonly _PATTERN_INCLUDE="^include::" -# Function to extract included files from a given file -get_includes() { +# shellcheck disable=SC2329 # Helper functions invoked from _cqa03_check +_detect_content_type() { local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - # Extract include:: statements and resolve relative paths - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - # Resolve relative path from file's directory - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - # Normalize and make relative to repo root - if [[ -f "$resolved_path" ]]; then - # Make path relative to REPO_ROOT - local normalized_path - normalized_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || normalized_path="$resolved_path" - echo "$normalized_path" - fi - done + local bn + bn=$(basename "$file" .adoc) + + # Content-based detection first + if grep -q "$_PATTERN_INCLUDE" "$file" 2>/dev/null && grep "$_PATTERN_INCLUDE" "$file" | grep -qE "(proc-|ref-|con-)"; then + echo "ASSEMBLY"; return + fi + if grep -q "^\.Procedure" "$file" 2>/dev/null; then + echo "PROCEDURE"; return + fi + + # Filename-based detection + case "$bn" in + assembly-*|master) echo "ASSEMBLY" ;; + proc-*) echo "PROCEDURE" ;; + con-*) echo "CONCEPT" ;; + ref-*) echo "REFERENCE" ;; + snip-*) echo "SNIPPET" ;; + attributes) echo "SNIPPET" ;; + *) echo "" ;; + esac } -# Function to recursively collect all files to process -collect_files() { +# shellcheck disable=SC2329 +_count_type_occurrences() { local file="$1" - local var_name="$2" - - # Use eval to access the array by name - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - # Skip if already processed - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return - fi - done - - # Add file to array - eval "${var_name}+=('$file')" - - # Get includes and process recursively - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") + grep -c "^:_mod-docs-content-type:" "$file" 2>/dev/null || echo "0" } -# Function to detect content type from file content -detect_content_type() { - local file="$1" - local basename_file - basename_file=$(basename "$file" .adoc) - - local content_type="" - local has_filename_violation=false - - # Check if file includes modules with proc-, ref-, or con- prefix - # This makes it an ASSEMBLY - if grep "^include::" "$file" 2>/dev/null | grep -qE "include::.*(proc-|ref-|con-)"; then - content_type="ASSEMBLY" - fi - - # Check if file has .Procedure section (sufficient for content-based detection) - # Structure validation (checking for proper list items) is done separately in validate_procedure_structure() - if [[ -z "$content_type" ]] && grep -q "^\.Procedure" "$file" 2>/dev/null; then - content_type="PROCEDURE" - fi - - # If content type detected from content, check filename compliance - if [[ -n "$content_type" ]]; then - # Check if filename uses alternative prefix for this type - if [[ "$content_type" == "$CONTENT_TYPE_PROCEDURE" && ( "$basename_file" == proc_* || "$basename_file" == procedure-* || "$basename_file" == procedure_* ) ]]; then - has_filename_violation=true - fi - # ASSEMBLY and other types don't have alternative prefixes to check - - if [[ "$has_filename_violation" == true ]]; then - echo "${content_type}:filename-violation" - else - echo "$content_type" - fi - return 0 - fi - - # No content pattern matched - fall back to filename-based detection - # These cannot be reliably detected from content patterns alone - - if [[ "$basename_file" == assembly-* ]]; then - echo "ASSEMBLY" - return 0 - fi - - # Standard prefixes (correct naming) - if [[ "$basename_file" == proc-* ]]; then - echo "PROCEDURE" - return 0 - fi - - if [[ "$basename_file" == con-* ]]; then - echo "CONCEPT" - return 0 - fi - - if [[ "$basename_file" == ref-* ]]; then - echo "REFERENCE" - return 0 - fi - - if [[ "$basename_file" == snip-* ]]; then - echo "SNIPPET" - return 0 - fi - - # Alternative prefixes (filename violations but still detectable) - if [[ "$basename_file" == proc_* ]] || [[ "$basename_file" == procedure-* ]] || [[ "$basename_file" == procedure_* ]]; then - echo "PROCEDURE:filename-violation" - return 0 - fi - - if [[ "$basename_file" == con_* ]] || [[ "$basename_file" == concept-* ]] || [[ "$basename_file" == concept_* ]]; then - echo "CONCEPT:filename-violation" - return 0 - fi - - if [[ "$basename_file" == ref_* ]] || [[ "$basename_file" == reference-* ]] || [[ "$basename_file" == reference_* ]]; then - echo "REFERENCE:filename-violation" - return 0 - fi - - if [[ "$basename_file" == snip_* ]]; then - echo "SNIPPET:filename-violation" - return 0 - fi - - # Cannot determine - return empty - echo "" +# shellcheck disable=SC2329 +_fix_content_type() { + local file="$1" type="$2" + sed -i '/^:_mod-docs-content-type:/d' "$file" + sed -i "1s/^/:_mod-docs-content-type: ${type}\n\n/" "$file" return 0 } -# Function to get current content type from file (must be on first line) -get_current_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - return 0 - else - echo "" - return 0 - fi -} +# shellcheck disable=SC2329 +_fix_section_lists() { + local file="$1" section="$2" -# Function to count total occurrences of content type metadata in file -count_content_type_occurrences() { - local file="$1" - local count - count=$(grep -c "^:_mod-docs-content-type:" "$file" 2>/dev/null || true) - if [[ -z "$count" ]]; then - echo "0" - return 0 - else - echo "$count" - return 0 - fi -} + grep -q "^\.${section}" "$file" 2>/dev/null || return 1 -# Function to remove all content type metadata from file -remove_all_content_type_metadata() { - local file="$1" - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^:_mod-docs-content-type:/d' "$file" - rm -f "${file}.bak" - fi - return 0 -} + local after + after=$(awk "/^\\.${section}\$/{flag=1; next} flag && /^\\.(Prerequisites|Procedure|Verification|Troubleshooting|Next steps|Additional)/{exit} flag" "$file" 2>/dev/null) -# Function to fix PROCEDURE structure - normalize list formatting -fix_procedure_structure() { - local file="$1" - - # Check if file has .Procedure section - if ! grep -q "$PATTERN_PROCEDURE_SECTION" "$file" 2>/dev/null; then - return 1 - fi + local includes unnumbered nested numbered + includes=$(echo "$after" | grep -c "$_PATTERN_INCLUDE" || true) + unnumbered=$(echo "$after" | grep -c "^\* " || true) + nested=$(echo "$after" | grep -c "^\*\* " || true) + numbered=$(echo "$after" | grep -cE "^\\.+ " || true) - # Get content after .Procedure section (until next section starting with .) - local after_procedure - after_procedure=$(awk '/^\.Procedure$/{flag=1; next} flag && /^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{exit} flag' "$file" 2>/dev/null) + [[ $includes -gt 0 ]] && return 1 - # Check for include statements (don't fix files with includes - they're valid) - local include_count - include_count=$(echo "$after_procedure" | grep -c "$PATTERN_INCLUDE" || true) - - if [[ $include_count -gt 0 ]]; then - # Has includes - don't try to fix - return 1 + local fix_type="" + if [[ $numbered -eq 1 && $unnumbered -eq 0 ]]; then + fix_type="single-to-unnumbered" + elif [[ $unnumbered -ge 1 && $numbered -ge 1 && $nested -eq 0 ]]; then + fix_type="mixed-to-numbered" + elif [[ $unnumbered -ge 2 && $numbered -eq 0 && $nested -eq 0 ]]; then + fix_type="unnumbered-to-numbered" fi - # Check for top-level unnumbered list items (starts with single * followed by space) - local unnumbered_count - unnumbered_count=$(echo "$after_procedure" | grep -c "$PATTERN_UNNUMBERED_ITEM" || true) - - # Check for nested unnumbered list items (starts with ** or more) - local nested_count - nested_count=$(echo "$after_procedure" | grep -c "$PATTERN_NESTED_ITEM" || true) - - # Check for numbered list items (starts with one or more dots followed by space) - local numbered_count - numbered_count=$(echo "$after_procedure" | grep -cE "$PATTERN_NUMBERED_ITEM" || true) + [[ -z "$fix_type" ]] && return 1 - # Fix if exactly 1 numbered step (convert to unnumbered) - if [[ $numbered_count -eq 1 && $unnumbered_count -eq 0 ]]; then - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^\.Procedure/,/^[^[:space:]]/{s/^\(\.\.\?\.* \)/* /}' "$file" - rm -f "${file}.bak" - fi - return 0 + if [[ "$CQA_FIX_MODE" == true ]]; then + case "$fix_type" in + single-to-unnumbered) + sed -i "/^\.${section}/,/^[^[:space:]]/{s/^\(\.\.\?\.* \)/* /}" "$file" ;; + mixed-to-numbered|unnumbered-to-numbered) + sed -i "/^\.${section}\$/,/^\.(Prerequisites|Procedure|Verification|Troubleshooting|Next steps|Additional)/{/^\./!s/^\* /. /}" "$file" ;; + esac fi - # Fix if mixed unnumbered and numbered items (multi-step procedure - convert all to numbered) - # This handles cases like 1 unnumbered + 1 numbered, which should both be numbered - if [[ $unnumbered_count -ge 1 && $numbered_count -ge 1 && $nested_count -eq 0 ]]; then - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^\.Procedure$/,/^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{/^\./!s/^\* /. /}' "$file" - rm -f "${file}.bak" - fi - return 0 - fi - - # Fix if 2+ unnumbered steps (convert to numbered) - # BUT: Don't convert if there are nested items - nested structure indicates - # a complex single step or steps that should stay unnumbered - if [[ $unnumbered_count -ge 2 && $numbered_count -eq 0 && $nested_count -eq 0 ]]; then - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^\.Procedure$/,/^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{/^\./!s/^\* /. /}' "$file" - rm -f "${file}.bak" - fi - return 0 - fi + local section_ln + section_ln=$(grep -n "^\.${section}$" "$file" | head -1 | cut -d: -f1) - return 1 + case "$fix_type" in + single-to-unnumbered) cqa_fail_autofix "$file" "$section_ln" "Single numbered step in .${section} -- convert to unnumbered" "Converted to unnumbered" ;; + mixed-to-numbered) cqa_fail_autofix "$file" "$section_ln" "Mixed list in .${section} -- convert to numbered" "Converted to numbered" ;; + unnumbered-to-numbered) cqa_fail_autofix "$file" "$section_ln" "Multiple unnumbered items in .${section} -- convert to numbered" "Converted to numbered" ;; + esac + return 0 } -# Function to fix VERIFICATION structure - normalize list formatting -fix_verification_structure() { - local file="$1" - - # Check if file has .Verification section - if ! grep -q "$PATTERN_VERIFICATION_SECTION" "$file" 2>/dev/null; then - return 1 - fi - - # Get content after .Verification section (until next section heading or end of file) - local after_verification - after_verification=$(awk '/^\.Verification$/{flag=1; next} flag && /^\.(Prerequisites|Procedure|Troubleshooting|Next steps|Additional)/{exit} flag' "$file" 2>/dev/null) - - # Check for include statements (don't fix files with includes - they're valid) - local include_count - include_count=$(echo "$after_verification" | grep -c "$PATTERN_INCLUDE" || true) - - if [[ $include_count -gt 0 ]]; then - # Has includes - don't try to fix - return 1 - fi +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa03_check() { + local target="$1" - # Check for top-level unnumbered list items (starts with single * followed by space) - local unnumbered_count - unnumbered_count=$(echo "$after_verification" | grep -c "$PATTERN_UNNUMBERED_ITEM" || true) + cqa_header "3" "Verify Content Type Metadata" "$target" - # Check for nested unnumbered list items (starts with ** or more) - local nested_count - nested_count=$(echo "$after_verification" | grep -c "$PATTERN_NESTED_ITEM" || true) + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ -f "$file" ]] || continue - # Check for numbered list items (starts with one or more dots followed by space) - local numbered_count - numbered_count=$(echo "$after_verification" | grep -cE "$PATTERN_NUMBERED_ITEM" || true) + cqa_file_start "$file" - # Fix if exactly 1 numbered step (convert to unnumbered) - if [[ $numbered_count -eq 1 && $unnumbered_count -eq 0 ]]; then - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^\.Verification/,/^[^[:space:]]/{s/^\(\.\.\?\.* \)/* /}' "$file" - rm -f "${file}.bak" + local detected + detected=$(_detect_content_type "$file") + if [[ -z "$detected" ]]; then + continue fi - return 0 - fi - # Fix if mixed unnumbered and numbered items (multi-step - convert all to numbered) - if [[ $unnumbered_count -ge 1 && $numbered_count -ge 1 && $nested_count -eq 0 ]]; then - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^\.Verification$/,/^\.(Prerequisites|Procedure|Troubleshooting|Next steps|Additional)/{/^\./!s/^\* /. /}' "$file" - rm -f "${file}.bak" - fi - return 0 - fi + local current + current=$(cqa_get_content_type "$file") + local occurrences + occurrences=$(_count_type_occurrences "$file") + local needs_fix=false - # Fix if 2+ unnumbered steps (convert to numbered) - # BUT: Don't convert if there are nested items - nested structure indicates - # a complex single step or steps that should stay unnumbered - if [[ $unnumbered_count -ge 2 && $numbered_count -eq 0 && $nested_count -eq 0 ]]; then - if [[ "$FIX_MODE" == true ]]; then - sed -i.bak '/^\.Verification$/,/^\.(Prerequisites|Procedure|Troubleshooting|Next steps|Additional)/{/^\./!s/^\* /. /}' "$file" - rm -f "${file}.bak" + if [[ "$current" != "$detected" ]] || [[ "$occurrences" -ne 1 ]]; then + needs_fix=true fi - return 0 - fi - - return 1 -} - -# Function to validate PROCEDURE structure -validate_procedure_structure() { - local file="$1" - - # Check if file has .Procedure section - if ! grep -q "^\.Procedure" "$file" 2>/dev/null; then - echo "Missing .Procedure section" - return 1 - fi - - # Get content after .Procedure section (until next section starting with .) - local after_procedure - after_procedure=$(awk '/^\.Procedure$/{flag=1; next} flag && /^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{exit} flag' "$file" 2>/dev/null) - - # Check for include statements (valid pattern - procedure steps in snippets) - local include_count - include_count=$(echo "$after_procedure" | grep -c "$PATTERN_INCLUDE" || true) - - # Check for single unnumbered list item (starts with *) - local unnumbered_count - unnumbered_count=$(echo "$after_procedure" | grep -c "$PATTERN_UNNUMBERED_ITEM" || true) - # Check for numbered list items (starts with one or more dots followed by space) - local numbered_count - numbered_count=$(echo "$after_procedure" | grep -cE "$PATTERN_NUMBERED_ITEM" || true) - - # Valid patterns: - # 1. One or more include statements (procedure steps in snippets, with or without additional steps) - # 2. Exactly one unnumbered item (single bullet) - # 3. Two or more numbered items (numbered steps) - if [[ $include_count -gt 0 ]]; then - # Valid: has include statements - any combination with includes is valid - return 0 - elif [[ $unnumbered_count -eq 1 && $numbered_count -eq 0 ]]; then - # Valid: single unnumbered item - return 0 - elif [[ $numbered_count -ge 2 ]]; then - # Valid: multiple numbered items (at least 2) - return 0 - elif [[ $numbered_count -eq 1 && $unnumbered_count -eq 0 ]]; then - # Invalid: only one numbered item without includes (should be multiple or use unnumbered) - echo ".Procedure section has only 1 numbered step (should be multiple numbered steps or 1 unnumbered item)" - return 1 - else - # Invalid or unknown structure - be permissive and accept it - return 0 - fi -} - -# Function to process a single file -process_file() { - local file="$1" - - # Handle special files with fixed content types - local basename_file - basename_file=$(basename "$file") - - # attributes.adoc files are SNIPPET type - if [[ "$basename_file" == "attributes.adoc" ]]; then - local current_type - current_type=$(get_current_content_type "$file") - local occurrence_count - occurrence_count=$(count_content_type_occurrences "$file") - - if [[ "$current_type" == "SNIPPET" ]] && [[ "$occurrence_count" -eq 1 ]]; then - # Compliant - no output - return 0 - else - # Fix needed - echo "" - if [[ "$FIX_MODE" == true ]]; then - echo "📝 $file" - if [[ "$occurrence_count" -gt 0 ]]; then - remove_all_content_type_metadata "$file" - fi - sed -i.bak "1s/^/:_mod-docs-content-type: SNIPPET\n\n/" "$file" - rm -f "${file}.bak" - else - echo "✗ $file" + if [[ "$needs_fix" == true ]]; then + if [[ -z "$current" && "$occurrences" -eq 0 ]]; then + cqa_fail_autofix "$file" "1" "Missing :_mod-docs-content-type: -- add ${detected}" "Added :_mod-docs-content-type: ${detected}" + elif [[ -z "$current" && "$occurrences" -gt 0 ]]; then + cqa_fail_autofix "$file" "1" "Content type not on first line -- move to line 1" "Moved to first line" + elif [[ "$current" != "$detected" ]]; then + cqa_fail_autofix "$file" "1" "Content type: ${current} -> ${detected}" "Changed to ${detected}" fi - echo " + Add :_mod-docs-content-type: SNIPPET" - echo "" - return 0 - fi - fi - - # master.adoc files are ASSEMBLY type - if [[ "$basename_file" == "master.adoc" ]]; then - local current_type - current_type=$(get_current_content_type "$file") - local occurrence_count - occurrence_count=$(count_content_type_occurrences "$file") - - if [[ "$current_type" == "ASSEMBLY" ]] && [[ "$occurrence_count" -eq 1 ]]; then - # Compliant - no output - return 0 - else - # Fix needed - echo "" - if [[ "$FIX_MODE" == true ]]; then - echo "📝 $file" - if [[ "$occurrence_count" -gt 0 ]]; then - remove_all_content_type_metadata "$file" - fi - sed -i.bak "1s/^/:_mod-docs-content-type: ASSEMBLY\n\n/" "$file" - rm -f "${file}.bak" - else - echo "✗ $file" + if [[ "$occurrences" -gt 1 ]]; then + cqa_fail_autofix "$file" "" "Content type appears $occurrences times -- remove duplicates" "Removed $((occurrences - 1)) duplicate(s)" fi - echo " + Add :_mod-docs-content-type: ASSEMBLY" - echo "" - return 0 - fi - fi - - # Detect content type from content - local detected_type_raw - detected_type_raw=$(detect_content_type "$file") - - # Skip if we can't detect a type - if [[ -z "$detected_type_raw" ]]; then - echo "? $file (cannot determine content type)" - return 0 - fi - - # Check for filename violation - local detected_type - local filename_violation=false - if [[ "$detected_type_raw" == *":filename-violation" ]]; then - detected_type="${detected_type_raw%:filename-violation}" - filename_violation=true - else - detected_type="$detected_type_raw" - fi - - # Get current content type (only from first line) - local current_type - current_type=$(get_current_content_type "$file") - - # Count total occurrences of content type metadata - local occurrence_count - occurrence_count=$(count_content_type_occurrences "$file") - # Check if everything is correct: - # - Detected type matches current type - # - Exactly 1 occurrence - # - It's on the first line (which we already know if current_type is set) - if [[ "$current_type" == "$detected_type" ]] && [[ "$occurrence_count" -eq 1 ]]; then - # Compliant - check for warnings - local has_warnings=false - - # Check for filename violation - if [[ "$filename_violation" == true ]]; then - if [[ "$has_warnings" == false ]]; then - echo "" - echo "⚠️ $file" - has_warnings=true - fi - local basename_file - basename_file=$(basename "$file" .adoc) - echo " Filename violation: Use standard prefix format (e.g., proc- not proc_ or procedure-)" - echo " Current: ${basename_file}.adoc" + [[ "$CQA_FIX_MODE" == true ]] && _fix_content_type "$file" "$detected" fi - # Fix and validate PROCEDURE structure if applicable - if [[ "$detected_type" == "$CONTENT_TYPE_PROCEDURE" ]]; then - # Detect what needs fixing before we fix it - local after_procedure - after_procedure=$(awk '/^\.Procedure$/{flag=1; next} flag && /^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{exit} flag' "$file" 2>/dev/null) - local unnumbered_before - unnumbered_before=$(echo "$after_procedure" | grep -c "$PATTERN_UNNUMBERED_ITEM" || true) - local nested_before - nested_before=$(echo "$after_procedure" | grep -c "$PATTERN_NESTED_ITEM" || true) - local numbered_before - numbered_before=$(echo "$after_procedure" | grep -cE "$PATTERN_NUMBERED_ITEM" || true) - local include_before - include_before=$(echo "$after_procedure" | grep -c "$PATTERN_INCLUDE" || true) - - # Try to fix PROCEDURE structure issues - if fix_procedure_structure "$file"; then - echo "" - if [[ "$FIX_MODE" == true ]]; then - echo "📝 $file" - else - echo "✗ $file" - fi - # Determine what was fixed based on counts - if [[ $numbered_before -eq 1 && $unnumbered_before -eq 0 && $include_before -eq 0 ]]; then - echo " * Convert single numbered step to unnumbered item" - elif [[ $unnumbered_before -ge 1 && $numbered_before -ge 1 && $include_before -eq 0 && $nested_before -eq 0 ]]; then - echo " * Convert mixed list formatting to numbered steps (multi-step procedure)" - elif [[ $unnumbered_before -ge 2 && $numbered_before -eq 0 && $include_before -eq 0 && $nested_before -eq 0 ]]; then - echo " * Convert multiple unnumbered items to numbered steps" - else - echo " * Normalize .Procedure list formatting" - fi - echo "" - return 0 - fi + # Fix and validate section lists for PROCEDURE files + if [[ "$detected" == "PROCEDURE" ]]; then + _fix_section_lists "$file" "Procedure" || true + _fix_section_lists "$file" "Verification" || true # Validate PROCEDURE structure - local validation_msg - validation_msg=$(validate_procedure_structure "$file") - if [[ -n "$validation_msg" ]]; then - if [[ "$has_warnings" == false ]]; then - echo "" - echo "⚠️ $file" - has_warnings=true - fi - echo " $validation_msg" - fi - fi - - # Fix VERIFICATION structure if applicable (same rules as PROCEDURE) - if grep -q "$PATTERN_VERIFICATION_SECTION" "$file" 2>/dev/null; then - # Detect what needs fixing before we fix it - local after_verification - after_verification=$(awk '/^\.Verification$/{flag=1; next} flag && /^\.(Prerequisites|Procedure|Troubleshooting|Next steps|Additional)/{exit} flag' "$file" 2>/dev/null) - local unnumbered_verif_before - unnumbered_verif_before=$(echo "$after_verification" | grep -c "$PATTERN_UNNUMBERED_ITEM" || true) - local nested_verif_before - nested_verif_before=$(echo "$after_verification" | grep -c "$PATTERN_NESTED_ITEM" || true) - local numbered_verif_before - numbered_verif_before=$(echo "$after_verification" | grep -cE "$PATTERN_NUMBERED_ITEM" || true) - local include_verif_before - include_verif_before=$(echo "$after_verification" | grep -c "$PATTERN_INCLUDE" || true) - - # Try to fix VERIFICATION structure issues - if fix_verification_structure "$file"; then - if [[ "$has_warnings" == false ]]; then - echo "" - echo "📝 $file" - has_warnings=true - fi - # Determine what was fixed based on counts - if [[ $numbered_verif_before -eq 1 && $unnumbered_verif_before -eq 0 && $include_verif_before -eq 0 ]]; then - echo " * Convert single numbered step in .Verification to unnumbered item" - elif [[ $unnumbered_verif_before -ge 1 && $numbered_verif_before -ge 1 && $include_verif_before -eq 0 && $nested_verif_before -eq 0 ]]; then - echo " * Convert mixed list formatting in .Verification to numbered steps" - elif [[ $unnumbered_verif_before -ge 2 && $numbered_verif_before -eq 0 && $include_verif_before -eq 0 && $nested_verif_before -eq 0 ]]; then - echo " * Convert multiple unnumbered items in .Verification to numbered steps" - else - echo " * Normalize .Verification list formatting" + if grep -q "^\.Procedure" "$file" 2>/dev/null; then + local after + after=$(awk '/^\.Procedure$/{flag=1; next} flag && /^\.(Prerequisites|Verification|Troubleshooting|Next steps|Additional)/{exit} flag' "$file" 2>/dev/null) + local numbered + numbered=$(echo "$after" | grep -cE "^\\.+ " || true) + local unnumbered + unnumbered=$(echo "$after" | grep -c "^\* " || true) + local includes + includes=$(echo "$after" | grep -c "$_PATTERN_INCLUDE" || true) + # Skip single-step check if there are includes (they may contain additional steps) + if [[ $numbered -eq 1 && $unnumbered -eq 0 && $includes -eq 0 ]]; then + local proc_ln + proc_ln=$(grep -n "^\.Procedure$" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$proc_ln" ".Procedure has only 1 numbered step (should be multiple or 1 unnumbered)" fi fi fi - if [[ "$has_warnings" == true ]]; then - echo "" - fi - return 0 - fi - - # Changes needed - show header - echo "" - if [[ "$FIX_MODE" == true ]]; then - echo "📝 $file" - else - echo "✗ $file" - fi - - # Track what we're fixing - local fixes=() - - # Determine what needs fixing - if [[ -z "$current_type" ]]; then - if [[ "$occurrence_count" -gt 0 ]]; then - fixes+=("Move content type to first line") - else - fixes+=("Add :_mod-docs-content-type: ${detected_type}") - fi - else - if [[ "$current_type" != "$detected_type" ]]; then - fixes+=("Content type: ${current_type} → ${detected_type}") - fi - fi - - if [[ "$occurrence_count" -gt 1 ]]; then - fixes+=("Remove $((occurrence_count - 1)) duplicate(s)") - fi - - # Apply fixes if in fix mode - if [[ "$FIX_MODE" == true ]]; then - if [[ "$occurrence_count" -gt 0 ]]; then - remove_all_content_type_metadata "$file" - fi - sed -i.bak "1s/^/:_mod-docs-content-type: ${detected_type}\n\n/" "$file" - rm -f "${file}.bak" - fi - - # Show what was fixed/found - for fix in "${fixes[@]}"; do - if [[ "$fix" == "Add"* ]]; then - echo " + $fix" - else - echo " * $fix" + if [[ "$needs_fix" == false ]] && [[ "$_CQA_CURRENT_FILE_HAS_ISSUES" == false ]]; then + cqa_file_pass "$file" fi done - - # Show filename violation warning if applicable - if [[ "$filename_violation" == true ]]; then - local basename_file - basename_file=$(basename "$file" .adoc) - echo " ⚠️ Filename violation: Use standard prefix format (e.g., proc- not proc_ or procedure-)" - echo " Current: ${basename_file}.adoc" - fi - - # Fix and validate PROCEDURE structure if applicable - if [[ "$detected_type" == "$CONTENT_TYPE_PROCEDURE" ]]; then - # Detect what needs fixing before we fix it - local after_procedure - after_procedure=$(awk "$AWK_AFTER_PROCEDURE" "$file" 2>/dev/null) - local unnumbered_before - unnumbered_before=$(echo "$after_procedure" | grep -c "$PATTERN_UNNUMBERED_ITEM" || true) - local nested_before - nested_before=$(echo "$after_procedure" | grep -c "$PATTERN_NESTED_ITEM" || true) - local numbered_before - numbered_before=$(echo "$after_procedure" | grep -cE "$PATTERN_NUMBERED_ITEM" || true) - local include_before - include_before=$(echo "$after_procedure" | grep -c "$PATTERN_INCLUDE" || true) - - # Try to fix PROCEDURE structure issues - if fix_procedure_structure "$file"; then - # Determine what was fixed based on counts - if [[ $numbered_before -eq 1 && $unnumbered_before -eq 0 && $include_before -eq 0 ]]; then - echo " * Convert single numbered step to unnumbered item" - elif [[ $unnumbered_before -ge 1 && $numbered_before -ge 1 && $include_before -eq 0 && $nested_before -eq 0 ]]; then - echo " * Convert mixed list formatting to numbered steps (multi-step procedure)" - elif [[ $unnumbered_before -ge 2 && $numbered_before -eq 0 && $include_before -eq 0 && $nested_before -eq 0 ]]; then - echo " * Convert multiple unnumbered items to numbered steps" - else - echo " * Normalize .Procedure list formatting" - fi - fi - - # Validate PROCEDURE structure - local validation_msg - validation_msg=$(validate_procedure_structure "$file") - if [[ -n "$validation_msg" ]]; then - echo " $validation_msg" - fi - fi - - # Fix VERIFICATION structure if applicable (same rules as PROCEDURE) - if grep -q "^\.Verification" "$file" 2>/dev/null; then - # Detect what needs fixing before we fix it - local after_verification - after_verification=$(awk "$AWK_AFTER_VERIFICATION" "$file" 2>/dev/null) - local unnumbered_verif_before - unnumbered_verif_before=$(echo "$after_verification" | grep -c "^\* " || true) - local nested_verif_before - nested_verif_before=$(echo "$after_verification" | grep -c "^\*\* " || true) - local numbered_verif_before - numbered_verif_before=$(echo "$after_verification" | grep -cE "^\\.+ " || true) - local include_verif_before - include_verif_before=$(echo "$after_verification" | grep -c "^include::" || true) - - # Try to fix VERIFICATION structure issues - if fix_verification_structure "$file"; then - # Determine what was fixed based on counts - if [[ $numbered_verif_before -eq 1 && $unnumbered_verif_before -eq 0 && $include_verif_before -eq 0 ]]; then - echo " * Convert single numbered step in .Verification to unnumbered item" - elif [[ $unnumbered_verif_before -ge 1 && $numbered_verif_before -ge 1 && $include_verif_before -eq 0 && $nested_verif_before -eq 0 ]]; then - echo " * Convert mixed list formatting in .Verification to numbered steps" - elif [[ $unnumbered_verif_before -ge 2 && $numbered_verif_before -eq 0 && $include_verif_before -eq 0 && $nested_verif_before -eq 0 ]]; then - echo " * Convert multiple unnumbered items in .Verification to numbered steps" - else - echo " * Normalize .Verification list formatting" - fi - fi - fi - - echo "" + return 0 } -# Color codes for summary -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -echo "=== CQA #3: Verify Content Type Metadata ===" -echo "" -if [[ "$FIX_MODE" == true ]]; then - echo -e "${YELLOW}FIX MODE${NC} - Will apply automatic fixes" - echo "" -fi - -# Collect files to process -FILES_TO_PROCESS=() -echo "Processing file and includes: $TARGET_FILE" -echo "" -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -# Process each file -PROCESSED=0 -COMPLIANT=0 -CHANGED=0 -CANNOT_DETERMINE=0 -FILENAME_VIOLATIONS=0 -MISSING_PROCEDURE_SECTION=0 -INVALID_PROCEDURE_STRUCTURE=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - PROCESSED=$((PROCESSED + 1)) - - # Check if file was modified (by checking if it shows a change message) - OUTPUT=$(process_file "$file") - - if [[ "$OUTPUT" == *"📝"* ]] || [[ "$OUTPUT" == *"✗"* ]]; then - CHANGED=$((CHANGED + 1)) - echo "$OUTPUT" - elif [[ "$OUTPUT" == *"?"* ]]; then - CANNOT_DETERMINE=$((CANNOT_DETERMINE + 1)) - echo "$OUTPUT" - elif [[ "$OUTPUT" == *"⚠️"* ]]; then - # Validation warning - still compliant but has structural issues - COMPLIANT=$((COMPLIANT + 1)) - - # Track violation types - if [[ "$OUTPUT" == *"Filename violation"* ]]; then - FILENAME_VIOLATIONS=$((FILENAME_VIOLATIONS + 1)) - fi - if [[ "$OUTPUT" == *"Missing .Procedure section"* ]]; then - MISSING_PROCEDURE_SECTION=$((MISSING_PROCEDURE_SECTION + 1)) - fi - if [[ "$OUTPUT" == *".Procedure section has only 1 numbered step"* ]] || [[ "$OUTPUT" == *".Procedure section not followed by proper list structure"* ]]; then - INVALID_PROCEDURE_STRUCTURE=$((INVALID_PROCEDURE_STRUCTURE + 1)) - fi - - echo "$OUTPUT" - else - # No output = compliant - COMPLIANT=$((COMPLIANT + 1)) - fi -done - -echo "" -echo "=== Summary ===" -echo "Files processed: $PROCESSED" -echo "Compliant content type attribute: $COMPLIANT" - -# Show violation breakdown if any violations found -TOTAL_VIOLATIONS=$((FILENAME_VIOLATIONS + MISSING_PROCEDURE_SECTION + INVALID_PROCEDURE_STRUCTURE)) -if [[ $TOTAL_VIOLATIONS -gt 0 ]]; then - echo "" - echo "Violation breakdown:" - if [[ $FILENAME_VIOLATIONS -gt 0 ]]; then - echo " - Filename violations: $FILENAME_VIOLATIONS" - fi - if [[ $MISSING_PROCEDURE_SECTION -gt 0 ]]; then - echo " - Missing .Procedure section: $MISSING_PROCEDURE_SECTION" - fi - if [[ $INVALID_PROCEDURE_STRUCTURE -gt 0 ]]; then - echo " - Invalid .Procedure structure: $INVALID_PROCEDURE_STRUCTURE" - fi -fi - -if [[ $CHANGED -gt 0 ]]; then - echo "" - if [[ "$FIX_MODE" == true ]]; then - echo -e "${GREEN}✓ Updated $CHANGED file(s)${NC}" - else - echo -e "${RED}✗ Found $CHANGED file(s) with issues${NC}" - echo "" - echo "Run with --fix to apply automatic fixes:" - echo " $0 --fix $TARGET_FILE" - fi -fi -if [[ $CANNOT_DETERMINE -gt 0 ]]; then - echo "" - echo "? Cannot determine content type for $CANNOT_DETERMINE file(s)" -fi +cqa_run_for_each_title _cqa03_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-04-modules-use-official-templates.sh b/build/scripts/cqa-04-modules-use-official-templates.sh index 2605b757c09..dab242a7a1c 100755 --- a/build/scripts/cqa-04-modules-use-official-templates.sh +++ b/build/scripts/cqa-04-modules-use-official-templates.sh @@ -1,311 +1,109 @@ #!/bin/bash # cqa-04-modules-use-official-templates.sh -# Verify modules use official Red Hat modular documentation templates (CQA requirement #4) +# Verify modules use official Red Hat modular documentation templates (CQA #4) # -# Usage: ./cqa-04-modules-use-official-templates.sh [--fix] <file-path> -# --fix: Apply automatic fixes where possible -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-04-modules-use-official-templates.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-04-modules-use-official-templates.sh [--fix] [--all] <file-path> # # Checks: # - PROCEDURE modules must not have custom subheadings (===) # - PROCEDURE modules must have a .Procedure section -# - PROCEDURE modules must have .Prerequisites (not .Prerequisite) -# - All modules must have an intro paragraph after the title -# - CONCEPT modules must not have numbered steps +# - .Prerequisite (singular) should be .Prerequisites (plural) +# - All modules must have an intro paragraph ([role="_abstract"]) +# - CONCEPT modules must not have .Procedure sections +# +# Autofix: +# - .Prerequisite -> .Prerequisites +# - Inserts missing [role="_abstract"] marker +# - Inserts missing .Procedure section before first numbered list # # Skips: # - ASSEMBLY and SNIPPET files # - master.adoc and attributes.adoc files -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa04_check() { + local target="$1" + + cqa_header "4" "Verify Module Templates" "$target" + + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue + [[ "$(basename "$file")" == "master.adoc" ]] && continue + + local content_type + content_type=$(cqa_get_content_type "$file") + [[ -z "$content_type" ]] && continue + [[ "$content_type" == "ASSEMBLY" ]] && continue + [[ "$content_type" == "SNIPPET" ]] && continue + + cqa_file_start "$file" + cqa_compute_block_ranges "$file" + + # Check 1: PROCEDURE modules must not have custom subheadings (===) + if [[ "$content_type" == "PROCEDURE" ]]; then + while IFS=: read -r line_num line_content; do + [[ -z "$line_num" ]] && continue + cqa_is_in_block "$file" "$line_num" && continue + cqa_fail_manual "$file" "$line_num" "Custom subheading in PROCEDURE: $line_content -- extract to separate module" + done < <(grep -n "^=== " "$file" 2>/dev/null || true) + fi -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 + # Check 2: PROCEDURE modules must have .Procedure section + if [[ "$content_type" == "PROCEDURE" ]]; then + if ! grep -q "^\.Procedure" "$file" 2>/dev/null; then + if [[ "$CQA_FIX_MODE" == true ]]; then + # Find the first ordered list item and insert .Procedure before it + local first_ol + first_ol=$(grep -n '^\. ' "$file" | head -1 | cut -d: -f1) + if [[ -n "$first_ol" ]]; then + sed -i "${first_ol}i\\\\.Procedure" "$file" + cqa_fail_autofix "$file" "$first_ol" "Missing .Procedure section" "Inserted .Procedure before first numbered list" + else + cqa_fail_manual "$file" "" "Missing .Procedure section (no numbered list found to insert before)" + fi + else + cqa_fail_autofix "$file" "" "Missing .Procedure section" "Insert .Procedure before numbered steps" + fi fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" fi - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" + # Check 3: .Prerequisite should be .Prerequisites (plural) + if [[ "$content_type" == "PROCEDURE" ]]; then + while IFS=: read -r line_num line_content; do + [[ -z "$line_num" ]] && continue + cqa_is_in_block "$file" "$line_num" && continue + if [[ "$line_content" == ".Prerequisite" ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i "${line_num}s/^\.Prerequisite$/.Prerequisites/" "$file" + fi + cqa_fail_autofix "$file" "$line_num" ".Prerequisite should be .Prerequisites (plural)" "Changed to .Prerequisites" + fi + done < <(grep -n "^\.Prerequisite$" "$file" 2>/dev/null || true) fi - done -} -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return + # Check 4: All modules must have an intro paragraph + if ! grep -q '\[role="_abstract"\]' "$file" 2>/dev/null; then + cqa_delegated "$file" "" "9" "Missing [role=\"_abstract\"] intro paragraph" fi - done - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Pre-compute source block ranges for a file -declare -A BLOCK_RANGES - -compute_block_ranges() { - local file="$1" - - if [[ -n "${BLOCK_RANGES[$file]+x}" ]]; then - return - fi - - local ranges="" - local in_block=false - local block_start=0 - local line_num=0 - - while IFS= read -r line; do - line_num=$((line_num + 1)) - if [[ "$line" =~ ^----+$ ]] || [[ "$line" =~ ^\.\.\.\.+$ ]]; then - if [[ "$in_block" == false ]]; then - in_block=true - block_start=$line_num - else - in_block=false - ranges="$ranges $block_start:$line_num" + # Check 5: CONCEPT modules must not have .Procedure sections + if [[ "$content_type" == "CONCEPT" ]]; then + if grep -q "^\.Procedure" "$file" 2>/dev/null; then + cqa_fail_manual "$file" "" "CONCEPT module has .Procedure section (move to a PROCEDURE module)" fi fi - done < "$file" - BLOCK_RANGES[$file]="$ranges" -} - -is_in_block() { - local file="$1" - local line_num="$2" - - local ranges="${BLOCK_RANGES[$file]}" - for range in $ranges; do - local start end - start="${range%%:*}" - end="${range##*:}" - if [[ $line_num -ge $start ]] && [[ $line_num -le $end ]]; then - return 0 + if [[ "$_CQA_CURRENT_FILE_HAS_ISSUES" == false ]]; then + cqa_file_pass "$file" fi done - return 1 + return 0 } -# Get content type from first line of file -get_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - fi -} - -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -echo "=== CQA #4: Verify Module Templates ===" -echo "" -echo "Reference: .claude/skills/cqa-04-modules-use-official-templates.md" -echo "" -if [[ "$FIX_MODE" == true ]]; then - echo -e "${YELLOW}FIX MODE${NC} - Will apply automatic fixes where possible" - echo "" -fi - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -TOTAL_VIOLATIONS=0 -FILES_WITH_VIOLATIONS=0 -FILES_CHECKED=0 -FILES_FIXED=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue - - # Skip special files - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - [[ "$(basename "$file")" == "master.adoc" ]] && continue - - # Get content type - content_type=$(get_content_type "$file") - [[ -z "$content_type" ]] && continue - - # Skip ASSEMBLY and SNIPPET - [[ "$content_type" == "ASSEMBLY" ]] && continue - [[ "$content_type" == "SNIPPET" ]] && continue - - FILES_CHECKED=$((FILES_CHECKED + 1)) - compute_block_ranges "$file" - - file_violations=() - - # === Check 1: PROCEDURE modules must not have custom subheadings (===) === - if [[ "$content_type" == "PROCEDURE" ]]; then - while IFS=: read -r line_num line_content; do - [[ -z "$line_num" ]] && continue - if is_in_block "$file" "$line_num"; then - continue - fi - file_violations+=("Line $line_num: Custom subheading in PROCEDURE (not allowed): $line_content") - done < <(grep -n "^=== " "$file" 2>/dev/null || true) - fi - - # === Check 2: PROCEDURE modules must have .Procedure section === - if [[ "$content_type" == "PROCEDURE" ]]; then - if ! grep -q "^\.Procedure" "$file" 2>/dev/null; then - file_violations+=("Missing .Procedure section (required for PROCEDURE modules)") - fi - fi - - # === Check 3: .Prerequisite should be .Prerequisites (plural) === - if [[ "$content_type" == "PROCEDURE" ]]; then - while IFS=: read -r line_num line_content; do - [[ -z "$line_num" ]] && continue - if is_in_block "$file" "$line_num"; then - continue - fi - # Only flag singular .Prerequisite, not .Prerequisites - if [[ "$line_content" == ".Prerequisite" ]]; then - file_violations+=("Line $line_num: .Prerequisite should be .Prerequisites (plural)") - fi - done < <(grep -n "^\.Prerequisite$" "$file" 2>/dev/null || true) - fi - - # === Check 4: All modules must have an intro paragraph === - # Check for [role="_abstract"] marker - if ! grep -q '\[role="_abstract"\]' "$file" 2>/dev/null; then - file_violations+=("Missing [role=\"_abstract\"] intro paragraph after title") - fi - - # === Check 5: CONCEPT modules must not have numbered steps === - if [[ "$content_type" == "CONCEPT" ]]; then - if grep -q "^\.Procedure" "$file" 2>/dev/null; then - file_violations+=("CONCEPT module has .Procedure section (move to a PROCEDURE module)") - fi - fi - - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - FILES_WITH_VIOLATIONS=$((FILES_WITH_VIOLATIONS + 1)) - TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + ${#file_violations[@]})) - - echo -e "${RED}x${NC} $file [$content_type]" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - - if [[ "$FIX_MODE" == true ]]; then - local_fixed=false - - # Fix: .Prerequisite → .Prerequisites - if grep -q "^\.Prerequisite$" "$file" 2>/dev/null; then - sed -i 's/^\.Prerequisite$/.Prerequisites/' "$file" - local_fixed=true - echo -e " ${YELLOW}Fixed:${NC} .Prerequisite -> .Prerequisites" - fi - - if [[ "$local_fixed" == true ]]; then - FILES_FIXED=$((FILES_FIXED + 1)) - fi - fi - echo "" - else - echo -e "${GREEN}v${NC} $file [$content_type]" - fi -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $FILES_CHECKED" - -if [[ "$FIX_MODE" == true ]] && [[ $FILES_FIXED -gt 0 ]]; then - echo -e "${YELLOW}Fixed $FILES_FIXED file(s)${NC}" - echo "" - echo "Review fixes and verify manually:" - echo " - Custom subheadings in PROCEDURE must be removed manually" - echo " - Missing .Procedure sections must be added manually" - echo " - Missing [role=\"_abstract\"] intro must be added manually" - exit 0 -elif [[ $TOTAL_VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v All modules follow official templates${NC}" - exit 0 -else - echo -e "${RED}x Found $TOTAL_VIOLATIONS violation(s) in $FILES_WITH_VIOLATIONS file(s)${NC}" - echo "" - echo "Run with --fix to apply automatic fixes (singular .Prerequisite):" - echo " $0 --fix $TARGET_FILE" - echo "" - echo "Manual fixes required for:" - echo " - Custom subheadings in PROCEDURE: remove or convert to standard sections" - echo " - Missing .Procedure section: add .Procedure followed by numbered steps" - echo " - Missing intro paragraph: add [role=\"_abstract\"] paragraph after title" - exit 1 -fi +cqa_run_for_each_title _cqa04_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-05-modular-elements-checklist.sh b/build/scripts/cqa-05-modular-elements-checklist.sh index afa95b64e4e..dbe6628d406 100755 --- a/build/scripts/cqa-05-modular-elements-checklist.sh +++ b/build/scripts/cqa-05-modular-elements-checklist.sh @@ -2,283 +2,219 @@ # cqa-05-modular-elements-checklist.sh # Validates all required modular elements per CQA #5 # -# Reference: .claude/resources/modular-documentation-templates-checklist.md +# Usage: ./cqa-05-modular-elements-checklist.sh [--fix] [--all] <file-path> # -# Usage: ./cqa-05-modular-elements-checklist.sh [--fix] <file-path> +# Checks: +# - Content type metadata, topic ID with {context}, single H1 title +# - Short introduction [role="_abstract"], blank line after H1 +# - Image alt text, no admonition titles +# - Nested assembly context handling, :context: declaration +# - Assembly: blank lines between includes, no level 2+ subheadings, no block titles +# - Procedure: .Procedure block title, standard sections only +# +# Autofix: +# - Inserts blank line after H1 title +# - Removes admonition titles +# - Adds missing image alt text quotes -set -e +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -# Constants for pattern matching readonly PATTERN_BLOCK_TITLE='^\.[A-Z]' -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Get repository root -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="." - -# Function to get all included files -get_all_files() { - local file="$1" - "$REPO_ROOT/build/scripts/list-all-included-files-starting-from.sh" "$file" -} - -# Get all files (space-separated list) -ALL_FILES=$(get_all_files "$TARGET_FILE") - -# Convert space-separated list to newline-separated -# Filter to only .adoc files in assemblies/, modules/, or master.adoc in titles/ -MODULE_FILES=$(echo "$ALL_FILES" | tr ' ' '\n' | grep -E "assemblies/|modules/|titles/.*master\.adoc$" | grep "\.adoc$" || true) - -if [[ -z "$MODULE_FILES" ]]; then - echo "No module or assembly files found." - exit 0 -fi +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa05_check() { + local target="$1" -echo "=== CQA #5: Verify Required Modular Elements ===" -echo "" -echo "Reference: .claude/resources/modular-documentation-templates-checklist.md" -echo "" + cqa_header "5" "Verify Required Modular Elements" "$target" -# Track violations -TOTAL_FILES=0 -VIOLATIONS=0 + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ -f "$file" ]] || continue + [[ "$file" != *.adoc ]] && continue -# Check each file -while IFS= read -r file; do - if [[ ! -f "$file" ]]; then - continue - fi - - TOTAL_FILES=$((TOTAL_FILES + 1)) - FILE_VIOLATIONS=0 - - # Get content type - CONTENT_TYPE=$(head -1 "$file" 2>/dev/null | grep ":_mod-docs-content-type:" | sed 's/:_mod-docs-content-type:[[:space:]]*//' | sed 's/[[:space:]]*$//' || echo "") - - # Determine if this is a nested assembly - IS_NESTED_ASSEMBLY=false - if [[ "$CONTENT_TYPE" = "ASSEMBLY" ]] && grep -q "ifdef::context\[:parent-context:" "$file"; then - IS_NESTED_ASSEMBLY=true - fi - - echo "Checking: $(basename "$file") (${CONTENT_TYPE:-UNKNOWN})" - - # ALL MODULES AND ASSEMBLIES - - # Check 1: Has content type metadata - if [[ -z "$CONTENT_TYPE" ]]; then - echo " ✗ Missing :_mod-docs-content-type: metadata" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi - - # Check 2: Has topic ID with {context} - # Exception: master.adoc files use [id="{context}"] without underscore prefix - if [[ "$(basename "$file")" == "master.adoc" ]]; then - # master.adoc: should have [id="{context}"] (no underscore prefix) - if ! grep -q '\[id="{context}"\]' "$file" && ! grep -q "\[id='{context}'\]" "$file"; then - echo " ✗ Missing or incorrect topic ID (master.adoc should use [id=\"{context}\"])" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi - else - # Regular assemblies/modules: should have [id="name_{context}"] - if ! grep -q '\[id=".*_{context}"\]' "$file" && ! grep -q "\[id='.*_{context}'\]" "$file"; then - echo " ✗ Missing or incorrect topic ID (must include _{context})" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Filter to assemblies/, modules/, or master.adoc in titles/ + if ! echo "$file" | grep -qE "assemblies/|modules/|titles/.*master\.adoc$"; then + continue fi - fi - # Check 3: Has exactly one H1 title - H1_COUNT=$(grep -c "^= " "$file" || echo "0") - if [[ "$H1_COUNT" -ne 1 ]]; then - echo " ✗ Has $H1_COUNT H1 titles (should be exactly 1)" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi + cqa_file_start "$file" - # Check 4: Has short introduction (abstract) - if ! grep -q '\[role="_abstract"\]' "$file"; then - echo " ✗ Missing [role=\"_abstract\"] short introduction" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi + local content_type + content_type=$(cqa_get_content_type "$file") - # Check 5: Has blank line between H1 and intro - # Extract lines around H1 title - # Skip document attributes (lines starting with :) which must follow title immediately - H1_LINE=$(grep -n "^= " "$file" | head -1 | cut -d: -f1) - if [[ -n "$H1_LINE" ]]; then - CHECK_LINE=$((H1_LINE + 1)) - NEXT_CONTENT=$(sed -n "${CHECK_LINE}p" "$file") - # Skip past document attribute lines (:key: value) - while [[ "$NEXT_CONTENT" =~ ^: ]]; do - CHECK_LINE=$((CHECK_LINE + 1)) - NEXT_CONTENT=$(sed -n "${CHECK_LINE}p" "$file") - done - if [[ -n "$NEXT_CONTENT" ]]; then - echo " ✗ Missing blank line after H1 title" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + local is_nested_assembly=false + if [[ "$content_type" == "ASSEMBLY" ]] && grep -q "ifdef::context\[:parent-context:" "$file"; then + is_nested_assembly=true fi - fi - - # Check 6: Image alt text (if images present) - if grep -q "^image::" "$file"; then - if grep "^image::" "$file" | grep -v '\["' > /dev/null; then - echo " ✗ Image(s) missing alt text in quotes" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi - fi - - # Check 7: Admonitions do not include titles - if grep -E "^\.(NOTE|WARNING|IMPORTANT|TIP|CAUTION)" "$file" > /dev/null 2>&1; then - echo " ✗ Admonition has title (should not have title)" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi - # NESTED ASSEMBLY FILES - if [[ "$IS_NESTED_ASSEMBLY" = true ]]; then - # Check 8: Has parent-context at top - if ! grep -q "ifdef::context\[:parent-context: {context}\]" "$file"; then - echo " ✗ Nested assembly missing parent-context preservation at top" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Snippets: only check for titles (which they must not have) + if [[ "$content_type" == "SNIPPET" ]]; then + if grep -q "^= " "$file"; then + local snippet_title + snippet_title=$(grep "^= " "$file" | head -1 | sed 's/^= //') + cqa_fail_manual "$file" "" "Snippet has title '= ${snippet_title}' -- remove from snippet and add to including files" + fi + if grep -E "$PATTERN_BLOCK_TITLE" "$file" > /dev/null 2>&1; then + local bt + bt=$(grep -E "$PATTERN_BLOCK_TITLE" "$file" | head -1) + cqa_fail_manual "$file" "" "Snippet has block title '${bt}' -- move to including files" + fi + cqa_file_pass "$file" + continue fi - # Check 9: Has context restoration at bottom - if ! grep -q "ifdef::parent-context\[:context: {parent-context}\]" "$file" || \ - ! grep -q "ifndef::parent-context\[:\!context:\]" "$file"; then - echo " ✗ Nested assembly missing context restoration at bottom" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Check 1: Has content type metadata + if [[ -z "$content_type" ]]; then + cqa_delegated "$file" "" "3" "Missing :_mod-docs-content-type: metadata" fi - # Check 10: Has :context: declaration - if ! grep -q "^:context: " "$file"; then - echo " ✗ Nested assembly missing :context: declaration" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Check 2: Has topic ID with {context} + if [[ "$(basename "$file")" == "master.adoc" ]]; then + if ! grep -q '\[id="{context}"\]' "$file" && ! grep -q "\[id='{context}'\]" "$file"; then + cqa_delegated "$file" "" "2" "Missing or incorrect topic ID (master.adoc should use [id=\"{context}\"])" + fi + else + if ! grep -q '\[id=".*_{context}"\]' "$file" && ! grep -q "\[id='.*_{context}'\]" "$file"; then + if grep -q '\[id="[^"]*"\]' "$file"; then + # Has an ID but missing _{context} suffix — autofix + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i 's/\[id="\([^"]*[^}]\)"\]/[id="\1_{context}"]/' "$file" + fi + cqa_fail_autofix "$file" "" "Topic ID missing _{context} suffix" "Added _{context} suffix" + else + cqa_fail_manual "$file" "" "Missing [id=\"..._{context}\"] topic ID" + fi + fi fi - fi - # ALL ASSEMBLY FILES - if [[ "$CONTENT_TYPE" = "ASSEMBLY" ]]; then - # Check 11: Blank lines between includes - # Look for consecutive include:: lines - if grep -A 1 "^include::" "$file" | grep -B 1 "^include::" | grep -v "^--$" | grep -v "^include::" > /dev/null 2>&1; then - echo " ⚠ Warning: May be missing blank lines between include statements" + # Check 3: Has exactly one H1 title + local h1_count + h1_count=$(grep -c "^= " "$file" || echo "0") + if [[ "$h1_count" -ne 1 ]]; then + cqa_fail_manual "$file" "" "Has $h1_count H1 titles (should be exactly 1)" + fi + + # Check 4: Has short introduction (abstract) + if ! grep -q '\[role="_abstract"\]' "$file"; then + cqa_delegated "$file" "" "9" "Missing [role=\"_abstract\"] short introduction" + fi + + # Check 5: Has blank line after H1 + local h1_line + h1_line=$(grep -n "^= " "$file" | head -1 | cut -d: -f1) + if [[ -n "$h1_line" ]]; then + local check_line=$((h1_line + 1)) + local next_content + next_content=$(sed -n "${check_line}p" "$file") + # Skip past document attribute lines (:key: value) + while [[ "$next_content" =~ ^: ]]; do + check_line=$((check_line + 1)) + next_content=$(sed -n "${check_line}p" "$file") + done + if [[ -n "$next_content" ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i "${check_line}i\\\\" "$file" + fi + cqa_fail_autofix "$file" "$check_line" "Missing blank line after H1 title" "Inserted blank line" + fi fi - # Check 12: No level 2+ subheadings - if grep -E "^===[[:space:]]" "$file" > /dev/null 2>&1; then - echo " ✗ Assembly contains level 2+ subheadings (=== or deeper)" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Check 6: Image alt text + if grep -q "^image::" "$file"; then + if grep "^image::" "$file" | grep -v '\["' > /dev/null; then + local img_ln + img_ln=$(grep -n "^image::" "$file" | grep -v '\["' | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$img_ln" "Image(s) missing alt text in quotes" + fi fi - # Check 13: No block titles (except .Additional resources) - # Block titles start with . followed by capital letter - if grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v "\.Additional resources" > /dev/null 2>&1; then - echo " ✗ Assembly contains block titles (only .Additional resources allowed)" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Check 7: Admonitions do not include titles + if grep -En "^\.(NOTE|WARNING|IMPORTANT|TIP|CAUTION)" "$file" > /dev/null 2>&1; then + local adm_ln + adm_ln=$(grep -En "^\.(NOTE|WARNING|IMPORTANT|TIP|CAUTION)" "$file" | head -1 | cut -d: -f1) + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i '/^\.\(NOTE\|WARNING\|IMPORTANT\|TIP\|CAUTION\)/d' "$file" + fi + cqa_fail_autofix "$file" "$adm_ln" "Admonition has title (should not have title)" "Removed admonition title" fi - fi - # CONCEPT OR REFERENCE MODULE - if [[ "$CONTENT_TYPE" = "CONCEPT" || "$CONTENT_TYPE" = "REFERENCE" ]]; then - # Check 14: No imperative instructions (check for numbered lists as proxy) - if grep -E "^\. [A-Z]" "$file" | grep -v "^\.\." > /dev/null 2>&1; then - echo " ⚠ Warning: May contain imperative instructions (numbered list detected)" + # Nested assembly checks + if [[ "$is_nested_assembly" == true ]]; then + if ! grep -q "ifdef::context\[:parent-context: {context}\]" "$file"; then + cqa_delegated "$file" "" "2" "Nested assembly missing parent-context preservation at top" + fi + if ! grep -q "ifdef::parent-context\[:context: {parent-context}\]" "$file" || \ + ! grep -q "ifndef::parent-context\[:\!context:\]" "$file"; then + cqa_delegated "$file" "" "2" "Nested assembly missing context restoration at bottom" + fi + if ! grep -q "^:context: " "$file"; then + cqa_delegated "$file" "" "2" "Nested assembly missing :context: declaration" + fi fi - # Check 15: No level 2+ subheadings (allowed in REFERENCE, not in CONCEPT per strict interpretation) - # Note: The checklist says "Does not contain a level 2 (===) section title (H3) or lower.." - # but modular docs allow subheadings in CONCEPT/REFERENCE for organization - # We'll make this a warning, not an error - - # Check 16: No block titles except .Additional resources or .Next steps - # Block titles start with . followed by capital letter - if grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v "\.Additional resources" | grep -v "\.Additional references" | grep -v "\.Next steps" > /dev/null 2>&1; then - echo " ⚠ Warning: Contains block titles other than .Additional resources or .Next steps" + # Assembly-specific checks + if [[ "$content_type" == "ASSEMBLY" ]]; then + if grep -E "^===[[:space:]]" "$file" > /dev/null 2>&1; then + cqa_fail_manual "$file" "" "Assembly contains level 2+ subheadings (=== or deeper)" + fi + if grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v "\.Additional resources" > /dev/null 2>&1; then + cqa_delegated "$file" "" "2" "Assembly contains block titles (only .Additional resources allowed)" "manual" + fi fi - fi - # PROCEDURE MODULE - if [[ "$CONTENT_TYPE" = "PROCEDURE" ]]; then - # Check 17: Has .Procedure block title - if ! grep -q "^\.Procedure$" "$file"; then - echo " ✗ Missing .Procedure block title" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + # Concept/Reference checks + if [[ "$content_type" == "CONCEPT" || "$content_type" == "REFERENCE" ]]; then + # Subheadings: == (H2) allowed for complex content, but === (H3) or deeper forbidden + if grep -E "^===[[:space:]]" "$file" > /dev/null 2>&1; then + local sh_ln + sh_ln=$(grep -En "^===[[:space:]]" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$sh_ln" "${content_type} contains level 3+ subheadings (=== or deeper) -- only == (H2) subheadings allowed" + fi + # Block titles: only .Additional resources and .Next steps allowed + if grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v "\.Additional resources" | grep -v "\.Next steps" > /dev/null 2>&1; then + local bt_ln + bt_ln=$(grep -En "$PATTERN_BLOCK_TITLE" "$file" | grep -v "\.Additional resources" | grep -v "\.Next steps" | head -1 | cut -d: -f1) + local bt_text + bt_text=$(sed -n "${bt_ln}p" "$file") + cqa_fail_manual "$file" "$bt_ln" "Non-standard block title: ${bt_text}" + fi fi - # Check 18: Only one .Procedure block title - PROCEDURE_COUNT=$(grep -c "^\.Procedure" "$file" || echo "0") - if [[ "$PROCEDURE_COUNT" -gt 1 ]]; then - echo " ✗ Has $PROCEDURE_COUNT .Procedure block titles (should be exactly 1)" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) - fi + # Procedure checks: no subheadings allowed at all + if [[ "$content_type" == "PROCEDURE" ]]; then + if grep -E "^==[[:space:]]" "$file" > /dev/null 2>&1; then + local sh_ln + sh_ln=$(grep -En "^==[[:space:]]" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$sh_ln" "Procedure contains subheadings (== or deeper) -- procedures must not have subheadings" + fi + if ! grep -q "^\.Procedure$" "$file"; then + cqa_delegated "$file" "" "4" "Missing .Procedure block title" + else + local proc_count + proc_count=$(grep -c "^\.Procedure" "$file" || echo "0") + if [[ "$proc_count" -gt 1 ]]; then + cqa_fail_manual "$file" "" "Has $proc_count .Procedure block titles (should be exactly 1)" + fi + if grep "^\.Procedure " "$file" > /dev/null 2>&1; then + cqa_fail_manual "$file" "" ".Procedure block title has embellishments (should be just '.Procedure')" + fi + fi - # Check 19: No embellishments of .Procedure - if grep "^\.Procedure " "$file" > /dev/null 2>&1; then - echo " ✗ .Procedure block title has embellishments (should be just '.Procedure')" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + local allowed_blocks="^\\.Prerequisites$|^\\.Prerequisite$|^\\.Procedure$|^\\.Verification$|^\\.Results$|^\\.Result$|^\\.Troubleshooting$|^\\.Troubleshooting steps$|^\\.Troubleshooting step$|^\\.Next steps$|^\\.Next step$|^\\.Additional resources$" + if grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v -E "$allowed_blocks" > /dev/null 2>&1; then + local violating + violating=$(grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v -E "$allowed_blocks" | head -1) + cqa_fail_manual "$file" "" "Non-standard block title: $violating" + fi fi - # Check 20: Only standard block titles - # Block titles start with . followed by a capital letter (not a space or number) - ALLOWED_BLOCKS="^\\.Prerequisites$|^\\.Prerequisite$|^\\.Procedure$|^\\.Verification$|^\\.Results$|^\\.Result$|^\\.Troubleshooting$|^\\.Troubleshooting steps$|^\\.Troubleshooting step$|^\\.Next steps$|^\\.Next step$|^\\.Additional resources$" - # Match lines starting with . followed by capital letter (block titles) - # Exclude numbered lists (. followed by space) and nested lists (..) - if grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v -E "$ALLOWED_BLOCKS" > /dev/null 2>&1; then - echo " ✗ Contains non-standard block titles" - VIOLATING_BLOCKS=$(grep -E "$PATTERN_BLOCK_TITLE" "$file" | grep -v -E "$ALLOWED_BLOCKS" | head -3) - echo " Examples: $VIOLATING_BLOCKS" - FILE_VIOLATIONS=$((FILE_VIOLATIONS + 1)) + if [[ "$_CQA_CURRENT_FILE_HAS_ISSUES" == false ]]; then + cqa_file_pass "$file" fi - fi - - # Summary for this file - if [[ $FILE_VIOLATIONS -eq 0 ]]; then - echo " ✓ All checks passed" - else - VIOLATIONS=$((VIOLATIONS + FILE_VIOLATIONS)) - fi - echo "" -done <<< "$MODULE_FILES" + done + return 0 +} -# Final summary -echo "=== Summary ===" -echo "Files checked: $TOTAL_FILES" -if [[ $VIOLATIONS -eq 0 ]]; then - echo "✓ All files pass modular elements validation" - exit 0 -else - echo "✗ Found $VIOLATIONS violation(s)" - echo "" - echo "See .claude/resources/modular-documentation-templates-checklist.md for details" - exit 1 -fi +cqa_run_for_each_title _cqa05_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh b/build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh index 2c98a2e23df..42e33e4db87 100755 --- a/build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh +++ b/build/scripts/cqa-06-assemblies-use-the-official-template-assemblies-ar.sh @@ -2,198 +2,71 @@ # cqa-06-assemblies-use-the-official-template-assemblies-ar.sh # Validates assemblies follow the official template and tell one user story (CQA #6) # -# Usage: ./cqa-06-assemblies-use-the-official-template-assemblies-ar.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-06-assemblies-use-the-official-template-assemblies-ar.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-06-assemblies-use-the-official-template-assemblies-ar.sh [--fix] [--all] <file-path> # # Checks: # - Assembly has exactly one user story (single focus topic) -# - Assembly title reflects the user story -# - Assembly does not combine unrelated procedures -# - Module count is reasonable (not too many for a single story) +# - Assembly title present +# - Not too many nested assembly includes (max 3) +# - Module count reasonable (max 15) +# +# Autofix (--fix stub): +# - Reports [MANUAL] items (splitting assemblies requires human judgment) # # Skips: # - Non-assembly files (PROCEDURE, CONCEPT, REFERENCE, SNIPPET) # - attributes.adoc files -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -# Parse arguments -FIX_MODE=false -TARGET_FILE="" +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa06_check() { + local target="$1" -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done + cqa_header "6" "Verify Assemblies Follow Official Template (One User Story)" "$target" -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi + local content_type + content_type=$(cqa_get_content_type "$file") + [[ -z "$content_type" ]] && continue + [[ "$content_type" != "ASSEMBLY" ]] && continue -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi + cqa_file_start "$file" - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path + local file_has_issue=false + local include_count + include_count=$(grep -c "^include::" "$file" 2>/dev/null || true) - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" + # Check for excessive nested assembly includes + local assembly_includes + assembly_includes=$(grep "^include::" "$file" 2>/dev/null | grep -c "assembly-" || true) + if [[ $assembly_includes -gt 3 ]]; then + cqa_fail_manual "$file" "" "Has $assembly_includes nested assembly includes (may cover multiple user stories, max 3)" + file_has_issue=true fi - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" + # Check for excessive module count + if [[ $include_count -gt 15 ]]; then + cqa_fail_manual "$file" "" "Has $include_count includes (consider splitting -- may cover multiple user stories, max 15)" + file_has_issue=true fi - done -} -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" + # Check assembly has a title + if ! grep -q "^= " "$file" 2>/dev/null; then + cqa_fail_manual "$file" "" "Missing assembly title (= Title)" + file_has_issue=true + fi - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return + if [[ "$file_has_issue" == false ]]; then + cqa_file_pass "$file" fi done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Get content type from first line of file -get_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - fi } -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -echo "=== CQA #6: Verify Assemblies Follow Official Template (One User Story) ===" -echo "" -echo "Reference: .claude/skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md" -echo "" - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -# Track violations -TOTAL_FILES=0 -VIOLATIONS=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue - - # Skip attributes.adoc and master.adoc - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - - # Get content type - content_type=$(get_content_type "$file") - [[ -z "$content_type" ]] && continue - - # Only check ASSEMBLY files - [[ "$content_type" != "ASSEMBLY" ]] && continue - - TOTAL_FILES=$((TOTAL_FILES + 1)) - - file_violations=() - - # Count included modules - include_count=$(grep -c "^include::" "$file" 2>/dev/null || true) - - # Check for multiple unrelated assembly includes (nested assemblies) - assembly_includes=$(grep "^include::" "$file" 2>/dev/null | grep -c "assembly-" || true) - if [[ $assembly_includes -gt 3 ]]; then - file_violations+=("Has $assembly_includes nested assembly includes (may cover multiple user stories)") - fi - - # Check for excessive module count (suggests multiple stories) - if [[ $include_count -gt 15 ]]; then - file_violations+=("Has $include_count includes (consider splitting - may cover multiple user stories)") - fi - - # Check assembly has a title - if ! grep -q "^= " "$file" 2>/dev/null; then - file_violations+=("Missing assembly title (= Title)") - fi - - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - VIOLATIONS=$((VIOLATIONS + 1)) - echo -e "${RED}x${NC} $file [$content_type] ($include_count includes)" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - echo "" - else - echo -e "${GREEN}v${NC} $file [$content_type] ($include_count includes)" - fi -done - -echo "" -echo "=== Summary ===" -echo "Assemblies checked: $TOTAL_FILES" - -if [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v All assemblies follow the one-user-story pattern${NC}" - exit 0 -else - echo -e "${RED}x Found $VIOLATIONS assembly(ies) with potential issues${NC}" - echo "" - echo "Review flagged assemblies to ensure each tells a single user story." - echo "See .claude/skills/cqa-06-assemblies-use-the-official-template-assemblies-ar.md" - exit 1 -fi +cqa_run_for_each_title _cqa06_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-07-toc-max-3-levels.sh b/build/scripts/cqa-07-toc-max-3-levels.sh index 7b920caa76c..c1fc56907c0 100755 --- a/build/scripts/cqa-07-toc-max-3-levels.sh +++ b/build/scripts/cqa-07-toc-max-3-levels.sh @@ -2,204 +2,90 @@ # cqa-07-toc-max-3-levels.sh # Validates TOC depth does not exceed 3 levels (CQA #7) # -# Usage: ./cqa-07-toc-max-3-levels.sh [--fix] <file-path> -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-07-toc-max-3-levels.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-07-toc-max-3-levels.sh [--fix] [--all] <file-path> # # Checks: # - Heading depth must not exceed 3 levels (= == ===) # - Level 4+ (==== or deeper) is a violation # +# Autofix: +# - Promotes ==== to === when it's the only level 4+ in the file +# # Skips: # - Content inside source/listing blocks (----, ....) # - attributes.adoc files -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" - fi - done -} - -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return - fi - done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa07_check() { + local target="$1" -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' + cqa_header "7" "Verify TOC Depth (Max 3 Levels)" "$target" -echo "=== CQA #7: Verify TOC Depth (Max 3 Levels) ===" -echo "" -echo "Reference: .claude/skills/cqa-07-toc-max-3-levels.md" -echo "" + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS + cqa_file_start "$file" + cqa_compute_block_ranges "$file" -# Track violations -TOTAL_FILES=0 -VIOLATIONS=0 -MAX_DEPTH_FOUND=0 + local local_max=0 + local violation_lines=() + local violation_line_nums=() + local line_num=0 -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue + # shellcheck disable=SC2094 # cqa_is_in_block reads cached data, not $file + while IFS= read -r line; do + line_num=$((line_num + 1)) - # Skip attributes.adoc - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - - TOTAL_FILES=$((TOTAL_FILES + 1)) - - # Find max heading depth, skipping source/listing blocks - local_max=0 - in_block=false - violation_lines=() - - while IFS= read -r line; do - # Track source/listing block boundaries - if [[ "$line" =~ ^----+$ ]] || [[ "$line" =~ ^\.\.\.\.+$ ]]; then - if [[ "$in_block" == false ]]; then - in_block=true - else - in_block=false + if cqa_is_in_block "$file" "$line_num"; then + continue fi - continue - fi - # Skip content inside blocks - if [[ "$in_block" == true ]]; then - continue - fi - - # Check for headings (= followed by space) - if [[ "$line" =~ ^(=+)[[:space:]] ]]; then - depth=${#BASH_REMATCH[1]} - if [[ $depth -gt $local_max ]]; then - local_max=$depth + # Check for headings (= followed by space) + if [[ "$line" =~ ^(=+)[[:space:]] ]]; then + local depth=${#BASH_REMATCH[1]} + if [[ $depth -gt $local_max ]]; then + local_max=$depth + fi + if [[ $depth -gt 3 ]]; then + violation_lines+=("Level $depth: $line") + violation_line_nums+=("$line_num") + fi fi - if [[ $depth -gt 3 ]]; then - violation_lines+=("Level $depth: $line") + done < "$file" + + if [[ ${#violation_lines[@]} -gt 0 ]]; then + # Autofix: promote ==== to === when safe + if [[ "$CQA_FIX_MODE" == true && ${#violation_line_nums[@]} -le 3 ]]; then + for i in "${!violation_line_nums[@]}"; do + local vln="${violation_line_nums[$i]}" + local vline + vline=$(sed -n "${vln}p" "$file") + # Replace leading ==== (or more) with === + if [[ "$vline" =~ ^===+[[:space:]] ]]; then + sed -i "${vln}s/^=\{4,\}/===/" "$file" + cqa_fail_autofix "$file" "$vln" "${violation_lines[$i]}" "Promoted to level 3 (===)" + fi + done + else + for i in "${!violation_lines[@]}"; do + if [[ "$CQA_FIX_MODE" == true ]]; then + cqa_fail_manual "$file" "${violation_line_nums[$i]}" "${violation_lines[$i]} -- too many deep headings for safe auto-promote" + else + cqa_fail_autofix "$file" "${violation_line_nums[$i]}" "${violation_lines[$i]}" "Promote to level 3 (===)" + fi + done fi + else + cqa_file_pass "$file" fi - done < "$file" - - if [[ $local_max -gt $MAX_DEPTH_FOUND ]]; then - MAX_DEPTH_FOUND=$local_max - fi - - # Report status - if [[ $local_max -eq 0 ]]; then - echo -e " - $(basename "$file"): No headings" - elif [[ $local_max -le 3 ]]; then - echo -e " ${GREEN}v${NC} $(basename "$file"): Max depth $local_max" - else - echo -e " ${RED}x${NC} $(basename "$file"): Max depth $local_max (exceeds limit of 3)" - VIOLATIONS=$((VIOLATIONS + 1)) - for vline in "${violation_lines[@]}"; do - echo " $vline" - done - fi -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $TOTAL_FILES" -echo "Maximum heading depth found: $MAX_DEPTH_FOUND" + done + return 0 +} -if [[ $VIOLATIONS -eq 0 && $MAX_DEPTH_FOUND -le 3 ]]; then - echo -e "${GREEN}v All files comply with TOC depth requirement (max 3 levels)${NC}" - exit 0 -else - if [[ $VIOLATIONS -gt 0 ]]; then - echo -e "${RED}x Found $VIOLATIONS file(s) with TOC depth violations${NC}" - fi - echo "" - echo "TOC Level Guidelines:" - echo " Level 1 (=): Book/main assembly title" - echo " Level 2 (==): Major sections" - echo " Level 3 (===): Sub-sections/procedure headings" - echo " Level 4+ (====): NOT ALLOWED" - echo "" - echo "See .claude/skills/cqa-07-toc-max-3-levels.md for restructuring strategies" - exit 1 -fi +cqa_run_for_each_title _cqa07_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-08-short-description-content.sh b/build/scripts/cqa-08-short-description-content.sh index 46c3b07f3ff..1b24a8597e0 100755 --- a/build/scripts/cqa-08-short-description-content.sh +++ b/build/scripts/cqa-08-short-description-content.sh @@ -2,116 +2,23 @@ # cqa-08-short-description-content.sh # Validates short description content quality (CQA #8) # -# Usage: ./cqa-08-short-description-content.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-08-short-description-content.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-08-short-description-content.sh [--fix] [--all] <file-path> # # Checks: # - No self-referential language ("This section...", "This document...") # - Abstract present (has [role="_abstract"] marker) +# - Abstract not empty +# +# Autofix: +# - Removes self-referential prefixes ("This section describes" -> "") # # Skips: # - SNIPPET files # - attributes.adoc and master.adoc files -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" - fi - done -} - -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return - fi - done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Get content type from first line of file -get_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - fi -} +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" # Self-referential patterns to detect SELF_REF_PATTERNS=( @@ -130,103 +37,95 @@ SELF_REF_PATTERNS=( "In this document" ) -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -echo "=== CQA #8: Verify Short Description Content Quality ===" -echo "" -echo "Reference: .claude/skills/cqa-08-short-description-content.md" -echo "" - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -TOTAL_FILES=0 -VIOLATIONS=0 +# Self-referential prefixes that can be auto-removed +SELF_REF_REMOVABLE=( + "This section describes " + "This section explains " + "This section provides " + "This document describes " + "This document explains " + "This topic describes " + "This topic explains " + "In this section, you " + "In this section, we " +) -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa08_check() { + local target="$1" - # Skip special files - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - [[ "$(basename "$file")" == "master.adoc" ]] && continue + cqa_header "8" "Verify Short Description Content Quality" "$target" - # Get content type - content_type=$(get_content_type "$file") - [[ -z "$content_type" ]] && continue + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue + [[ "$(basename "$file")" == "master.adoc" ]] && continue - # Skip SNIPPET - [[ "$content_type" == "SNIPPET" ]] && continue + local content_type + content_type=$(cqa_get_content_type "$file") + [[ -z "$content_type" ]] && continue + [[ "$content_type" == "SNIPPET" ]] && continue - TOTAL_FILES=$((TOTAL_FILES + 1)) + cqa_file_start "$file" - file_violations=() + # Check for [role="_abstract"] marker + if ! grep -q '\[role="_abstract"\]' "$file" 2>/dev/null; then + cqa_delegated "$file" "" "9" "Missing [role=\"_abstract\"] marker" + continue + fi - # Check for [role="_abstract"] marker - if ! grep -q '\[role="_abstract"\]' "$file" 2>/dev/null; then - file_violations+=("Missing [role=\"_abstract\"] marker") - else # Extract the abstract text (line after [role="_abstract"]) - abstract_text=$(awk '/\[role="_abstract"\]/{getline; print}' "$file" 2>/dev/null) + local abstract_line + abstract_line=$(grep -n '\[role="_abstract"\]' "$file" | head -1 | cut -d: -f1) + local abstract_text + abstract_text=$(sed -n "$((abstract_line + 1))p" "$file" 2>/dev/null) + + # Check if abstract is empty + if [[ -z "$abstract_text" ]] || [[ "$abstract_text" =~ ^[[:space:]]*$ ]]; then + cqa_fail_manual "$file" "$((abstract_line + 1))" "Empty abstract (no text after [role=\"_abstract\"])" + continue + fi # Check for self-referential language in abstract + local found_self_ref=false for pattern in "${SELF_REF_PATTERNS[@]}"; do if echo "$abstract_text" | grep -qi "$pattern" 2>/dev/null; then - file_violations+=("Self-referential language in abstract: \"$pattern\"") + found_self_ref=true + # Check if this is auto-removable + local can_autofix=false + for removable in "${SELF_REF_REMOVABLE[@]}"; do + if echo "$abstract_text" | grep -qi "^${removable}" 2>/dev/null; then + can_autofix=true + if [[ "$CQA_FIX_MODE" == true ]]; then + # Remove the self-referential prefix, capitalize next word + local next_line=$((abstract_line + 1)) + sed -i "${next_line}s/${removable}//I" "$file" + # Capitalize first letter of remaining text + local remaining + remaining=$(sed -n "${next_line}p" "$file") + local first_char="${remaining:0:1}" + local upper_char + upper_char=$(echo "$first_char" | tr '[:lower:]' '[:upper:]') + sed -i "${next_line}s/^${first_char}/${upper_char}/" "$file" + cqa_fail_autofix "$file" "$next_line" "Self-referential: \"$pattern\"" "Removed self-referential prefix" + else + cqa_fail_autofix "$file" "$((abstract_line + 1))" "Self-referential language in abstract: \"$pattern\"" "Remove prefix" + fi + break + fi + done + if [[ "$can_autofix" == false ]]; then + cqa_fail_manual "$file" "$((abstract_line + 1))" "Self-referential language in abstract: \"$pattern\" -- rewrite needed" + fi fi done - # Also check if abstract is empty - if [[ -z "$abstract_text" ]] || [[ "$abstract_text" =~ ^[[:space:]]*$ ]]; then - file_violations+=("Empty abstract (no text after [role=\"_abstract\"])") - fi - fi - - # Check for self-referential language anywhere in the first 10 lines after title - intro_text=$(awk '/^= /{found=1; next} found && NR<=15{print}' "$file" 2>/dev/null) - for pattern in "${SELF_REF_PATTERNS[@]}"; do - if echo "$intro_text" | grep -qi "$pattern" 2>/dev/null; then - # Only flag if not already caught in abstract check - already_flagged=false - for v in "${file_violations[@]}"; do - if [[ "$v" == *"$pattern"* ]]; then - already_flagged=true - break - fi - done - if [[ "$already_flagged" == false ]]; then - file_violations+=("Self-referential language in intro: \"$pattern\"") - fi + if [[ "$found_self_ref" == false ]]; then + cqa_file_pass "$file" fi done + return 0 +} - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - VIOLATIONS=$((VIOLATIONS + 1)) - echo -e "${RED}x${NC} $file [$content_type]" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - echo "" - else - echo -e "${GREEN}v${NC} $file [$content_type]" - fi -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $TOTAL_FILES" - -if [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v All short descriptions meet content quality requirements${NC}" - exit 0 -else - echo -e "${RED}x Found $VIOLATIONS file(s) with content quality issues${NC}" - echo "" - echo "See .claude/skills/cqa-08-short-description-content.md for guidelines" - exit 1 -fi +cqa_run_for_each_title _cqa08_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-09-short-description-format.sh b/build/scripts/cqa-09-short-description-format.sh index a5332193628..f04c83ac616 100755 --- a/build/scripts/cqa-09-short-description-format.sh +++ b/build/scripts/cqa-09-short-description-format.sh @@ -1,216 +1,124 @@ #!/bin/bash -# Verify short descriptions (abstracts) per CQA requirements #6 and #7 +# cqa-09-short-description-format.sh +# Validates short description formatting (CQA #9) # -# Usage: ./cqa-09-short-description-format.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-09-short-description-format.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-09-short-description-format.sh [--fix] [--all] <file-path> # -# Requirements: -# - Every module and assembly must have a single, concise introductory paragraph -# - Mark with [role="_abstract"] immediately after the title -# - Introduction should be 50-300 characters for AEM migration -# - The [role="_abstract"] line cannot be followed by an empty line - -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - # Extract include:: statements and resolve relative paths - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - # Resolve relative path from file's directory - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - # Normalize and make relative to repo root - if [[ -f "$resolved_path" ]]; then - # Make path relative to REPO_ROOT - # shellcheck disable=SC2269 # Intentional fallback to original path if realpath fails - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" - fi - done -} - -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - # Use eval to access the array by name - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - # Skip if already processed - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return +# Checks: +# - [role="_abstract"] marker present +# - No empty line after [role="_abstract"] +# - Abstract length: 50-300 characters +# +# Autofix: +# - Removes blank line after [role="_abstract"] +# - Inserts [role="_abstract"] marker when missing (before first paragraph after title) +# +# Skips: +# - SNIPPET files + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa09_check() { + local target="$1" + + cqa_header "9" "Verify Short Description Format" "$target" + + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + + local content_type + content_type=$(cqa_get_content_type "$file") + [[ -z "$content_type" ]] && continue + [[ "$content_type" == "SNIPPET" ]] && continue + + cqa_file_start "$file" + + # Check for [role="_abstract"] + if ! grep -q '^\[role="_abstract"\]' "$file"; then + if [[ "$CQA_FIX_MODE" == true ]]; then + # Find the first non-empty, non-metadata line after the title + local title_ln + title_ln=$(grep -n "^= " "$file" | head -1 | cut -d: -f1) + if [[ -n "$title_ln" ]]; then + local insert_ln=$((title_ln + 1)) + local next_content + next_content=$(sed -n "${insert_ln}p" "$file") + # Skip past :context:, :attr:, blank lines, [id=...] etc. + while [[ "$next_content" =~ ^: ]] || [[ -z "$next_content" ]] || [[ "$next_content" =~ ^\[id= ]] || [[ "$next_content" =~ ^ifdef:: ]]; do + insert_ln=$((insert_ln + 1)) + next_content=$(sed -n "${insert_ln}p" "$file") + # Safety: don't go past end of file + [[ $insert_ln -gt $(wc -l < "$file") ]] && break + done + # Insert [role="_abstract"] before the first content line + sed -i "${insert_ln}i\\[role=\"_abstract\"]" "$file" + cqa_fail_autofix "$file" "$insert_ln" "Missing [role=\"_abstract\"] marker" "Inserted [role=\"_abstract\"] before first paragraph" + fi + else + cqa_fail_autofix "$file" "" "Missing [role=\"_abstract\"] marker" "Insert marker before first paragraph" + fi + continue fi - done - - # Add file to array - eval "${var_name}+=('$file')" - - # Get includes and process recursively - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" + # Get line number of [role="_abstract"] + local abstract_line + abstract_line=$(grep -n '^\[role="_abstract"\]' "$file" | head -1 | cut -d: -f1) + local next_line=$((abstract_line + 1)) + local next_content + next_content=$(sed -n "${next_line}p" "$file") + + # Check if next line is empty (violation) + if [[ -z "$next_content" ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i "${next_line}d" "$file" + cqa_fail_autofix "$file" "$next_line" "Empty line after [role=\"_abstract\"]" "Removed blank line" else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 + cqa_fail_autofix "$file" "$next_line" "Empty line after [role=\"_abstract\"] (abstract must start on next line)" fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -echo "=== CQA Requirements #6 & #7: Verify Short Descriptions ===" -echo "" - -# Collect files to process -FILES_TO_PROCESS=() -echo "Processing file and includes: $TARGET_FILE" -echo "" -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -VIOLATIONS=0 -CHECKED=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Extract content type - CONTENT_TYPE=$(grep "^:_mod-docs-content-type:" "$file" | sed 's/^:_mod-docs-content-type: *//' | sed 's/ *$//') - - if [[ -z "$CONTENT_TYPE" ]]; then - continue - fi - - # Skip snippets - they don't need abstracts - if [[ "$CONTENT_TYPE" == "SNIPPET" ]]; then - continue - fi - - CHECKED=$((CHECKED + 1)) - - # Check for [role="_abstract"] - if ! grep -q '^\[role="_abstract"\]' "$file"; then - echo -e "${RED}✗${NC} $file" - echo " Issue: Missing [role=\"_abstract\"] marker" - echo "" - VIOLATIONS=$((VIOLATIONS + 1)) - continue - fi - - # Get line number of [role="_abstract"] - ABSTRACT_LINE=$(grep -n '^\[role="_abstract"\]' "$file" | head -1 | cut -d: -f1) + continue + fi - # Check if next line is empty (violation) - NEXT_LINE=$((ABSTRACT_LINE + 1)) - NEXT_LINE_CONTENT=$(sed -n "${NEXT_LINE}p" "$file") + # Extract abstract text (can be multi-line, ends at first empty line or next section) + local abstract_text="" + local ln=$next_line + while true; do + local line_content + line_content=$(sed -n "${ln}p" "$file") + # Stop at empty line, section marker, or include statement + if [[ -z "$line_content" ]] || [[ "$line_content" =~ ^\. ]] || [[ "$line_content" =~ ^include:: ]]; then + break + fi + abstract_text="${abstract_text}${line_content} " + ln=$((ln + 1)) + done + + # Clean and count + abstract_text=$(echo "$abstract_text" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -s ' ') + + # If abstract is a single attribute reference like {abstract}, resolve it + if [[ "$abstract_text" =~ ^\{([a-z][-a-z0-9]*)\}$ ]]; then + local attr_name="${BASH_REMATCH[1]}" + local attr_value + attr_value=$(grep -m1 "^:${attr_name}:" "$file" 2>/dev/null | sed "s/^:${attr_name}: *//" || true) + if [[ -n "$attr_value" ]]; then + abstract_text="$attr_value" + fi + fi - if [[ -z "$NEXT_LINE_CONTENT" ]]; then - echo -e "${RED}✗${NC} $file" - echo " Issue: Empty line after [role=\"_abstract\"] (abstract must start on next line)" - echo "" - VIOLATIONS=$((VIOLATIONS + 1)) - continue - fi + local char_count=${#abstract_text} - # Extract abstract text (can be multi-line, ends at first empty line or next section) - ABSTRACT_TEXT="" - LINE_NUM=$NEXT_LINE - while true; do - LINE_CONTENT=$(sed -n "${LINE_NUM}p" "$file") - # Stop at empty line, section marker, or include statement - if [[ -z "$LINE_CONTENT" ]] || [[ "$LINE_CONTENT" =~ ^\. ]] || [[ "$LINE_CONTENT" =~ ^include:: ]]; then - break + if [[ $char_count -lt 50 ]]; then + cqa_fail_manual "$file" "$next_line" "Abstract too short (${char_count} chars, minimum 50)" + elif [[ $char_count -gt 300 ]]; then + cqa_fail_manual "$file" "$next_line" "Abstract too long (${char_count} chars, maximum 300)" + else + cqa_file_pass "$file" fi - ABSTRACT_TEXT="${ABSTRACT_TEXT}${LINE_CONTENT} " - LINE_NUM=$((LINE_NUM + 1)) done +} - # Remove leading/trailing whitespace and collapse multiple spaces - ABSTRACT_TEXT=$(echo "$ABSTRACT_TEXT" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -s ' ') - - # Calculate character count (excluding AsciiDoc attributes) - # For character count, we need to consider rendered text - CHAR_COUNT=${#ABSTRACT_TEXT} - - # Check character count (50-300 characters) - if [[ $CHAR_COUNT -lt 50 ]]; then - echo -e "${YELLOW}⚠${NC} $file" - echo " Issue: Abstract too short ($CHAR_COUNT chars, minimum 50)" - echo " Text: $ABSTRACT_TEXT" - echo "" - VIOLATIONS=$((VIOLATIONS + 1)) - elif [[ $CHAR_COUNT -gt 300 ]]; then - echo -e "${YELLOW}⚠${NC} $file" - echo " Issue: Abstract too long ($CHAR_COUNT chars, maximum 300)" - echo " Text: ${ABSTRACT_TEXT:0:100}..." - echo "" - VIOLATIONS=$((VIOLATIONS + 1)) - else - echo -e "${GREEN}✓${NC} $file ($CHAR_COUNT chars)" - fi - -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $CHECKED" -if [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}✓ All files have compliant short descriptions${NC}" - exit 0 -else - echo -e "${RED}✗ Found $VIOLATIONS violation(s)${NC}" - exit 1 -fi +cqa_run_for_each_title _cqa09_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh b/build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh index 61612509158..f8e252b7db0 100755 --- a/build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh +++ b/build/scripts/cqa-10-titles-are-brief-complete-and-descriptive.sh @@ -1,678 +1,326 @@ #!/bin/bash # cqa-10-titles-are-brief-complete-and-descriptive.sh -# Aligns title, ID, context, and filename per CQA.md rules +# Aligns title, ID, context, and filename per CQA rules (CQA #10) # -# Usage: ./cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] <file-path> -# --fix: Apply automatic fixes (title form, IDs, filenames, xrefs) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-10-titles-are-brief-complete-and-descriptive.sh titles/install-rhdh-ocp/master.adoc -# Processes: master.adoc → assemblies → all included modules (recursive) +# Usage: ./cqa-10-titles-are-brief-complete-and-descriptive.sh [--fix] [--all] <file-path> # -# This script follows CQA.md Step 5 (Title/ID/Filename Compliance): -# STEP 0: Ensure content type metadata exists (CQA requirement #2) -# - Reads module type from :_mod-docs-content-type: metadata (first line) -# - If metadata is missing, skips the file (run cqa-03-content-is-modularized.sh first) -# STEP 1: Fix titles FIRST - Title is source of truth (CQA requirement #8) -# - Procedures: Use imperative form ("Install" not "Installing") -# - Concepts: Use noun phrases ("High availability" not "Achieve high availability") -# - References: Use noun phrases ("Configuration options" not "Configure options") -# - Assemblies with procedures: Use imperative form ("Install" not "Installing") -# - Assemblies without procedures: Use noun phrases ("API reference" not "Configure API") -# STEP 2: Update IDs and context to match title -# STEP 3: Update all xrefs pointing to changed ID -# STEP 4: Rename file to match title (using prefix from content type) -# STEP 5: Update all include statements - -set -e - -# Convert a gerund to imperative form (e.g., "Installing" → "Install") -# Handles three patterns: -# 1. Doubled consonant: "Running" → "Run" (remove doubled consonant) -# 2. Silent 'e' dropped: "Configuring" → "Configure" (add 'e' back) -# 3. Simple '-ing': "Deploying" → "Deploy" (just strip) -# Args: $1 = gerund word (e.g., "Installing" or "installing") -# Returns: imperative form preserving original capitalization +# Autofix: +# - Converts gerund titles to imperative (160+ rules) +# - Updates IDs and context to match title +# - Renames files via git mv +# - Updates all xrefs and include statements + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2034 +CQA_DELEGATES_TO=("DocumentId:10") + +# shellcheck disable=SC2329 gerund_to_imperative() { local word="$1" local lower lower=$(echo "$word" | tr '[:upper:]' '[:lower:]') - - # Remove 'ing' suffix to get stem local stem="${lower%ing}" local result="" case "$lower" in - # Explicit mappings for common documentation verbs - # -- Doubled consonant verbs -- - running) result="run" ;; - setting) result="set" ;; - getting) result="get" ;; - putting) result="put" ;; - cutting) result="cut" ;; - stopping) result="stop" ;; - dropping) result="drop" ;; - mapping) result="map" ;; - planning) result="plan" ;; - scanning) result="scan" ;; - shipping) result="ship" ;; - shopping) result="shop" ;; - skipping) result="skip" ;; - snapping) result="snap" ;; - spinning) result="spin" ;; - splitting) result="split" ;; - stepping) result="step" ;; - stripping) result="strip" ;; - swapping) result="swap" ;; - tapping) result="tap" ;; - trimming) result="trim" ;; - wrapping) result="wrap" ;; - beginning) result="begin" ;; - # -- Silent 'e' verbs -- - configuring) result="configure" ;; - creating) result="create" ;; - enabling) result="enable" ;; - disabling) result="disable" ;; - managing) result="manage" ;; - upgrading) result="upgrade" ;; - updating) result="update" ;; - removing) result="remove" ;; - deleting) result="delete" ;; - editing) result="edit" ;; - resolving) result="resolve" ;; - authorizing) result="authorize" ;; - validating) result="validate" ;; - customizing) result="customize" ;; - integrating) result="integrate" ;; - migrating) result="migrate" ;; - generating) result="generate" ;; - defining) result="define" ;; - overriding) result="override" ;; - retrieving) result="retrieve" ;; - preparing) result="prepare" ;; - scaling) result="scale" ;; - securing) result="secure" ;; - authenticating) result="authenticate" ;; - automating) result="automate" ;; - bootstrapping) result="bootstrap" ;; - restoring) result="restore" ;; - replacing) result="replace" ;; - browsing) result="browse" ;; - closing) result="close" ;; - composing) result="compose" ;; - describing) result="describe" ;; - ensuring) result="ensure" ;; - using) result="use" ;; - including) result="include" ;; - invoking) result="invoke" ;; - providing) result="provide" ;; - producing) result="produce" ;; - reducing) result="reduce" ;; - releasing) result="release" ;; - requiring) result="require" ;; - subscribing) result="subscribe" ;; - changing) result="change" ;; - locating) result="locate" ;; - navigating) result="navigate" ;; - operating) result="operate" ;; - isolating) result="isolate" ;; - # -- Simple '-ing' removal verbs -- - installing) result="install" ;; - deploying) result="deploy" ;; - building) result="build" ;; - adding) result="add" ;; - testing) result="test" ;; - monitoring) result="monitor" ;; - checking) result="check" ;; - importing) result="import" ;; - exporting) result="export" ;; - connecting) result="connect" ;; - disconnecting) result="disconnect" ;; - adjusting) result="adjust" ;; - restarting) result="restart" ;; - starting) result="start" ;; - registering) result="register" ;; - unregistering) result="unregister" ;; - assigning) result="assign" ;; - reviewing) result="review" ;; - accessing) result="access" ;; - fetching) result="fetch" ;; - searching) result="search" ;; - finding) result="find" ;; - provisioning) result="provision" ;; - encrypting) result="encrypt" ;; - mounting) result="mount" ;; - unmounting) result="unmount" ;; - attaching) result="attach" ;; - detaching) result="detach" ;; - extending) result="extend" ;; - limiting) result="limit" ;; - inspecting) result="inspect" ;; - triggering) result="trigger" ;; - troubleshooting) result="troubleshoot" ;; - understanding) result="understand" ;; - publishing) result="publish" ;; - selecting) result="select" ;; - tracking) result="track" ;; - transforming) result="transform" ;; - viewing) result="view" ;; - verifying) result="verify" ;; - modifying) result="modify" ;; - specifying) result="specify" ;; - applying) result="apply" ;; + running) result="run" ;; setting) result="set" ;; getting) result="get" ;; + putting) result="put" ;; cutting) result="cut" ;; stopping) result="stop" ;; + dropping) result="drop" ;; mapping) result="map" ;; planning) result="plan" ;; + scanning) result="scan" ;; shipping) result="ship" ;; shopping) result="shop" ;; + skipping) result="skip" ;; snapping) result="snap" ;; spinning) result="spin" ;; + splitting) result="split" ;; stepping) result="step" ;; stripping) result="strip" ;; + swapping) result="swap" ;; tapping) result="tap" ;; trimming) result="trim" ;; + wrapping) result="wrap" ;; beginning) result="begin" ;; + configuring) result="configure" ;; creating) result="create" ;; + enabling) result="enable" ;; disabling) result="disable" ;; + managing) result="manage" ;; upgrading) result="upgrade" ;; + updating) result="update" ;; removing) result="remove" ;; + deleting) result="delete" ;; editing) result="edit" ;; + resolving) result="resolve" ;; authorizing) result="authorize" ;; + validating) result="validate" ;; customizing) result="customize" ;; + integrating) result="integrate" ;; migrating) result="migrate" ;; + generating) result="generate" ;; defining) result="define" ;; + overriding) result="override" ;; retrieving) result="retrieve" ;; + preparing) result="prepare" ;; scaling) result="scale" ;; + securing) result="secure" ;; authenticating) result="authenticate" ;; + automating) result="automate" ;; bootstrapping) result="bootstrap" ;; + restoring) result="restore" ;; replacing) result="replace" ;; + browsing) result="browse" ;; closing) result="close" ;; + composing) result="compose" ;; describing) result="describe" ;; + ensuring) result="ensure" ;; using) result="use" ;; + including) result="include" ;; invoking) result="invoke" ;; + providing) result="provide" ;; producing) result="produce" ;; + reducing) result="reduce" ;; releasing) result="release" ;; + requiring) result="require" ;; subscribing) result="subscribe" ;; + changing) result="change" ;; locating) result="locate" ;; + navigating) result="navigate" ;; operating) result="operate" ;; + isolating) result="isolate" ;; installing) result="install" ;; + deploying) result="deploy" ;; building) result="build" ;; + adding) result="add" ;; testing) result="test" ;; + monitoring) result="monitor" ;; checking) result="check" ;; + importing) result="import" ;; exporting) result="export" ;; + connecting) result="connect" ;; disconnecting) result="disconnect" ;; + adjusting) result="adjust" ;; restarting) result="restart" ;; + starting) result="start" ;; registering) result="register" ;; + unregistering) result="unregister" ;; assigning) result="assign" ;; + reviewing) result="review" ;; accessing) result="access" ;; + fetching) result="fetch" ;; searching) result="search" ;; + finding) result="find" ;; provisioning) result="provision" ;; + encrypting) result="encrypt" ;; mounting) result="mount" ;; + unmounting) result="unmount" ;; attaching) result="attach" ;; + detaching) result="detach" ;; extending) result="extend" ;; + limiting) result="limit" ;; inspecting) result="inspect" ;; + triggering) result="trigger" ;; troubleshooting) result="troubleshoot" ;; + understanding) result="understand" ;; publishing) result="publish" ;; + selecting) result="select" ;; tracking) result="track" ;; + transforming) result="transform" ;; viewing) result="view" ;; + verifying) result="verify" ;; modifying) result="modify" ;; + specifying) result="specify" ;; applying) result="apply" ;; *) - # Generic fallback with heuristic rules local last_two="${stem: -2}" - - # Rule 1: Doubled consonant (not ll/ss/ff/zz) → remove one - # e.g., "runn" → "run", "sett" → "set" if [[ ${#stem} -ge 3 ]] && [[ "${last_two:0:1}" == "${last_two:1:1}" ]] && \ [[ "${last_two:0:1}" =~ [bcdfghjkmnpqrtvwxyz] ]]; then result="${stem%?}" - # Rule 2: Stem ends in 'v' → add 'e' (English words rarely end in bare 'v') - # e.g., "resolv" → "resolve", "remov" → "remove" elif [[ "$stem" =~ [v]$ ]]; then result="${stem}e" - # Rule 3: Stem ends in vowel+'z' → add 'e' - # e.g., "authoriz" → "authorize", "customiz" → "customize" elif [[ "$stem" =~ [aeiou]z$ ]]; then result="${stem}e" - # Rule 4: Stem ends in vowel+'c' → add 'e' - # e.g., "replac" → "replace", "produc" → "produce" elif [[ "$stem" =~ [aeiou]c$ ]]; then result="${stem}e" - # Rule 5: Otherwise just strip 'ing' else result="$stem" fi - echo " ⚠ Unknown gerund '${word}' → '${result}' (consider adding to gerund_to_imperative)" >&2 ;; esac - # Preserve original capitalization if [[ "$word" =~ ^[A-Z] ]]; then result="$(echo "${result:0:1}" | tr '[:lower:]' '[:upper:]')${result:1}" fi - echo "$result" } -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - # Extract include:: statements and resolve relative paths - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - # Get repository root (where .git is) - local repo_root - repo_root=$(cd "$(dirname "$file")" && git rev-parse --show-toplevel 2>/dev/null) || repo_root="." - - # Resolve relative path from file's directory - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - # Normalize and make relative to repo root - if [[ -f "$resolved_path" ]]; then - # Make path relative to repo root - # shellcheck disable=SC2269 # Intentional fallback to original path if realpath fails - resolved_path=$(realpath --relative-to="$repo_root" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" - fi - done -} - -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - # Use eval to access the array by name - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - # Skip if already processed - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return - fi - done - - # Add file to array - eval "${var_name}+=('$file')" - - # Get includes and process recursively - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Function to get content type from file (always first line) -get_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - return 0 - else - echo "" - return 0 - fi -} - -# Function to resolve an attribute value -# Args: $1 = attribute name (without braces), $2 = file to search first -# Returns: resolved value or original attribute name if not found -resolve_attribute() { +# shellcheck disable=SC2329 # Helper functions invoked from _cqa10_check/_process_file +_resolve_attribute() { local attr_name="$1" local search_file="$2" local attr_value="" - - # First, try to find in the current file if [[ -f "$search_file" ]]; then attr_value=$(grep "^:${attr_name}:" "$search_file" 2>/dev/null | head -1 | sed "s/^:${attr_name}:[[:space:]]*//" | sed 's/[[:space:]]*$//') fi - - # If not found, try artifacts/attributes.adoc if [[ -z "$attr_value" ]] && [[ -f "artifacts/attributes.adoc" ]]; then attr_value=$(grep "^:${attr_name}:" "artifacts/attributes.adoc" 2>/dev/null | head -1 | sed "s/^:${attr_name}:[[:space:]]*//" | sed 's/[[:space:]]*$//') fi - - # If still not found, return the attribute name - if [[ -z "$attr_value" ]]; then - echo "$attr_name" - else - echo "$attr_value" - fi + echo "${attr_value:-$attr_name}" } -# Function to expand all attributes in a string -# Args: $1 = string with attributes, $2 = file to search first -expand_attributes() { +# shellcheck disable=SC2329 +_expand_attributes() { local input="$1" local search_file="$2" local output="$input" - - # Find all {attribute} patterns and expand them iteratively while [[ "$output" =~ \{([^}]+)\} ]]; do local attr_name="${BASH_REMATCH[1]}" local attr_value - attr_value=$(resolve_attribute "$attr_name" "$search_file") - - # Replace {attribute} with its value + attr_value=$(_resolve_attribute "$attr_name" "$search_file") output="${output//\{$attr_name\}/$attr_value}" done - echo "$output" } -# Function to convert title with attributes to ID-friendly form -# Replaces product attributes with short forms instead of fully expanding them -# This prevents excessively long IDs/filenames like "red-hat-developer-hub" -title_to_id_form() { +# shellcheck disable=SC2329 +_title_to_id_form() { local title="$1" echo "$title" | \ - sed 's/{product-very-short}/rhdh/g' | \ - sed 's/{product-short}/rhdh/g' | \ - sed 's/{product}/rhdh/g' | \ - sed 's/{product-custom-resource-type}//g' | \ - sed 's/{rhbk-brand-name}/rhbk/g' | \ - sed 's/{rhbk}/rhbk/g' | \ + sed 's/{product-very-short}/rhdh/g' | sed 's/{product-short}/rhdh/g' | \ + sed 's/{product}/rhdh/g' | sed 's/{product-custom-resource-type}//g' | \ + sed 's/{rhbk-brand-name}/rhbk/g' | sed 's/{rhbk}/rhbk/g' | \ sed 's/{azure-brand-name}/microsoft-azure/g' | \ - sed 's/{ocp-brand-name}/ocp/g' | \ - sed 's/{ocp-short}/ocp/g' | \ + sed 's/{ocp-brand-name}/ocp/g' | sed 's/{ocp-short}/ocp/g' | \ sed 's/{[^}]*}//g' } -# Function to process a single file -process_file() { +# shellcheck disable=SC2329 +_process_file() { local FILE="$1" -# Determine module type from content type metadata (always) -CONTENT_TYPE=$(get_content_type "$FILE") - -if [[ -z "$CONTENT_TYPE" ]]; then - echo "? $FILE (no content type metadata - run cqa-03-content-is-modularized.sh first)" - return 0 -fi - -# Determine prefix and expected form based on content type -case "$CONTENT_TYPE" in - PROCEDURE) - PREFIX="proc-" - MODULE_TYPE="PROCEDURE" - EXPECTED_FORM="imperative" - ;; - CONCEPT) - PREFIX="con-" - MODULE_TYPE="CONCEPT" - EXPECTED_FORM="noun phrase" - ;; - REFERENCE) - PREFIX="ref-" - MODULE_TYPE="REFERENCE" - EXPECTED_FORM="noun phrase" - ;; - ASSEMBLY) - PREFIX="assembly-" - MODULE_TYPE="ASSEMBLY" - # Assemblies use imperative form IF they include procedures, otherwise noun phrases - if grep -q "include::.*proc-.*\.adoc" "$FILE"; then - EXPECTED_FORM="imperative" - else - EXPECTED_FORM="noun phrase" - fi - ;; - SNIPPET) - PREFIX="snip-" - MODULE_TYPE="SNIPPET" - EXPECTED_FORM="any" - ;; - *) - echo "? $FILE (unknown content type: $CONTENT_TYPE)" + local CONTENT_TYPE + CONTENT_TYPE=$(cqa_get_content_type "$FILE") + if [[ -z "$CONTENT_TYPE" ]]; then + cqa_delegated "$FILE" "" "3" "No content type metadata -- run CQA #3 first" return 0 - ;; -esac - -# Track if any changes will be made -WILL_CHANGE=false - -# STEP 0: Add content type metadata if missing -ADDED_METADATA=false -if ! grep -q "^:_mod-docs-content-type:" "$FILE"; then - ADDED_METADATA=true - WILL_CHANGE=true -fi - -# Extract current title (H1 heading) and expand attributes -TITLE_RAW=$(grep "^= " "$FILE" | head -1 | sed 's/^= //') -if [ -z "$TITLE_RAW" ]; then - if [ "$MODULE_TYPE" = "SNIPPET" ]; then - # Snippets must NOT have a title - validate absence - echo "✓ $FILE (snippet: no title, as expected)" + fi + + local PREFIX MODULE_TYPE EXPECTED_FORM + case "$CONTENT_TYPE" in + PROCEDURE) PREFIX="proc-"; MODULE_TYPE="PROCEDURE"; EXPECTED_FORM="imperative" ;; + CONCEPT) PREFIX="con-"; MODULE_TYPE="CONCEPT"; EXPECTED_FORM="noun phrase" ;; + REFERENCE) PREFIX="ref-"; MODULE_TYPE="REFERENCE"; EXPECTED_FORM="noun phrase" ;; + ASSEMBLY) + PREFIX="assembly-"; MODULE_TYPE="ASSEMBLY" + if grep -q "include::.*proc-.*\.adoc" "$FILE"; then + EXPECTED_FORM="imperative" + else + EXPECTED_FORM="noun phrase" + fi + ;; + SNIPPET) + # Snippets: validate no title + local snippet_title + snippet_title=$(grep "^= " "$FILE" | head -1 | sed 's/^= //') + if [[ -n "$snippet_title" ]]; then + cqa_fail_manual "$FILE" "" "Snippet has a title '= ${snippet_title}' -- snippets must not have titles" + else + cqa_file_pass "$FILE" + fi + return 0 + ;; + *) return 0 ;; + esac + + local WILL_CHANGE=false + + # Extract title + local TITLE_RAW + TITLE_RAW=$(grep "^= " "$FILE" | head -1 | sed 's/^= //') + if [[ -z "$TITLE_RAW" ]]; then + cqa_fail_manual "$FILE" "" "No title found (looking for '= Title')" return 0 - else - echo "Error: No title found in $FILE (looking for '= Title')" - exit 1 fi -fi - -# Snippets should NOT have titles -if [ "$MODULE_TYPE" = "SNIPPET" ] && [ -n "$TITLE_RAW" ]; then - echo "✗ $FILE" - echo " * Snippet has a title '= ${TITLE_RAW}' - snippets must not have titles" - return 0 -fi - -# Expand any attributes in the title (e.g., {title} → actual title value) -TITLE=$(expand_attributes "$TITLE_RAW" "$FILE") - -# STEP 1: Check if title needs fixing -FIXED_TITLE="$TITLE" -FIXED_TITLE_RAW="$TITLE_RAW" -TITLE_CHANGED=false -if [ "$EXPECTED_FORM" = "imperative" ]; then - # Extract first word (handling attributes) - FIRST_WORD=$(echo "$TITLE" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]].*//') - - if [[ "$FIRST_WORD" =~ ing$ ]] && [[ ! "$FIRST_WORD" =~ ^\{.*\}$ ]]; then - # Convert gerund to imperative using shared function - IMPERATIVE_WORD=$(gerund_to_imperative "$FIRST_WORD") - FIXED_TITLE="${IMPERATIVE_WORD}${TITLE#"$FIRST_WORD"}" - FIXED_TITLE_RAW="${IMPERATIVE_WORD}${TITLE_RAW#"$FIRST_WORD"}" - - TITLE_CHANGED=true - WILL_CHANGE=true + + local TITLE + TITLE=$(_expand_attributes "$TITLE_RAW" "$FILE") + + # Check title form + local FIXED_TITLE="$TITLE" FIXED_TITLE_RAW="$TITLE_RAW" TITLE_CHANGED=false + if [[ "$EXPECTED_FORM" == "imperative" ]]; then + local FIRST_WORD + FIRST_WORD=$(echo "$TITLE" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]].*//') + if [[ "$FIRST_WORD" =~ ing$ ]] && [[ ! "$FIRST_WORD" =~ ^\{.*\}$ ]]; then + local IMPERATIVE_WORD + IMPERATIVE_WORD=$(gerund_to_imperative "$FIRST_WORD") + FIXED_TITLE="${IMPERATIVE_WORD}${TITLE#"$FIRST_WORD"}" + FIXED_TITLE_RAW="${IMPERATIVE_WORD}${TITLE_RAW#"$FIRST_WORD"}" + TITLE_CHANGED=true; WILL_CHANGE=true; TITLE="$FIXED_TITLE" + fi + + # Fix additional gerunds after "and" + while [[ "$FIXED_TITLE" =~ (.*[[:space:]]and[[:space:]])([A-Za-z]+ing)([[:space:]].*)$ ]]; do + local GERUND="${BASH_REMATCH[2]}" + local IMPERATIVE + IMPERATIVE=$(gerund_to_imperative "$GERUND") + FIXED_TITLE="${BASH_REMATCH[1]}${IMPERATIVE}${BASH_REMATCH[3]}" + FIXED_TITLE_RAW="${FIXED_TITLE_RAW//"$GERUND"/"$IMPERATIVE"}" + TITLE_CHANGED=true; WILL_CHANGE=true + done TITLE="$FIXED_TITLE" fi - # Check for additional gerunds in the rest of the title (e.g., "Enable and authorizing") - # Look for " and <word>ing " patterns and convert them to imperative - while [[ "$FIXED_TITLE" =~ (.*[[:space:]]and[[:space:]])([A-Za-z]+ing)([[:space:]].*)$ ]]; do - TITLE_PREFIX="${BASH_REMATCH[1]}" - GERUND="${BASH_REMATCH[2]}" - TITLE_SUFFIX="${BASH_REMATCH[3]}" + # Extract current ID + local CURRENT_ID + CURRENT_ID=$(grep "\[id=" "$FILE" | head -1 | sed 's/.*\[id="//;s/.*\[id='"'"'//' | sed 's/["'"'"'\]]*$//' | sed 's/_{context}.*//' | sed 's/_.*//') - IMPERATIVE=$(gerund_to_imperative "$GERUND") + # Expected ID from title + local TITLE_FOR_ID + TITLE_FOR_ID=$(_title_to_id_form "$FIXED_TITLE_RAW") + local EXPECTED_ID + EXPECTED_ID=$(echo "$TITLE_FOR_ID" | \ + tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | \ + sed 's/--*/-/g' | sed 's/^-//;s/-$//' | \ + sed 's/\brhdh-rhdh\b/rhdh/g' | sed 's/\brhbk-rhbk\b/rhbk/g' | sed 's/\bocp-ocp\b/ocp/g') - FIXED_TITLE="${TITLE_PREFIX}${IMPERATIVE}${TITLE_SUFFIX}" - FIXED_TITLE_RAW="${FIXED_TITLE_RAW//"$GERUND"/"$IMPERATIVE"}" - TITLE_CHANGED=true - WILL_CHANGE=true - done + local EXPECTED_FILENAME="${PREFIX}${EXPECTED_ID}.adoc" + local NEW_FILE + NEW_FILE="$(dirname "$FILE")/$EXPECTED_FILENAME" - TITLE="$FIXED_TITLE" - - # Validate that the first word is a known imperative verb - # This catches truncated words like "Delegat" (should be "Delegate") - FIRST_WORD_CHECK=$(echo "$TITLE" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]].*//') - if [[ ! "$FIRST_WORD_CHECK" =~ ^\{.*\}$ ]]; then - FIRST_LOWER=$(echo "$FIRST_WORD_CHECK" | tr '[:upper:]' '[:lower:]') - # Known valid imperative verbs (from gerund_to_imperative + common doc verbs) - KNOWN_IMPERATIVES=" run set get put cut stop drop map plan scan ship shop skip snap spin split step strip swap tap trim wrap begin configure create enable disable manage upgrade update remove delete edit resolve authorize validate customize integrate migrate generate define override retrieve prepare scale secure authenticate automate bootstrap restore replace browse close compose describe ensure use include invoke provide produce reduce release require subscribe change locate navigate operate isolate install deploy build add test monitor check import export connect disconnect adjust restart start register unregister assign review access fetch search find provision encrypt mount unmount attach detach extend limit inspect trigger troubleshoot understand publish select track transform view verify modify specify apply send download design delegate determine gather interact make " - if ! echo "$KNOWN_IMPERATIVES" | grep -q " ${FIRST_LOWER} "; then - echo " ⚠ Title starts with '${FIRST_WORD_CHECK}' which is not a recognized imperative verb (possible truncation?)" - WILL_CHANGE=true - fi + if [[ "$CURRENT_ID" != "$EXPECTED_ID" ]] || [[ "$FILE" != "$NEW_FILE" ]]; then + WILL_CHANGE=true fi -fi - -# Extract current ID (before _{context}) -CURRENT_ID=$(grep "\[id=" "$FILE" | head -1 | sed 's/.*\[id="//;s/.*\[id='"'"'//' | sed 's/["'"'"'\]]*$//' | sed 's/_{context}.*//' | sed 's/_.*//') - -# Convert title to expected ID: -# 1. Use raw title with short forms for attributes (e.g., {product} → rhdh) -# 2. Lowercase everything -# 3. Replace non-alphanumeric with hyphens -# 4. Clean up multiple/leading/trailing hyphens and duplicate abbreviations -TITLE_FOR_ID=$(title_to_id_form "$FIXED_TITLE_RAW") -EXPECTED_ID=$(echo "$TITLE_FOR_ID" | \ - tr '[:upper:]' '[:lower:]' | \ - sed 's/[^a-z0-9-]/-/g' | \ - sed 's/--*/-/g' | \ - sed 's/^-//;s/-$//' | \ - sed 's/\brhdh-rhdh\b/rhdh/g' | \ - sed 's/\brhbk-rhbk\b/rhbk/g' | \ - sed 's/\bocp-ocp\b/ocp/g') - -# Expected filename -EXPECTED_FILENAME="${PREFIX}${EXPECTED_ID}.adoc" -NEW_FILE="$(dirname "$FILE")/$EXPECTED_FILENAME" - -# Check if changes are needed -if [ "$CURRENT_ID" != "$EXPECTED_ID" ] || [ "$FILE" != "$NEW_FILE" ]; then - WILL_CHANGE=true -fi - -# If no changes needed, just show checkmark and return -if [ "$WILL_CHANGE" = false ]; then - echo "✓ $FILE" - return 0 -fi - -# Changes needed - show header -echo "" -if [ "$FIX_MODE" = true ]; then - echo "📝 $FILE" -else - echo "✗ $FILE" -fi - -# Report and optionally apply changes -if [ "$ADDED_METADATA" = true ]; then - if [ "$FIX_MODE" = true ]; then - sed -i.bak "1s/^/:_mod-docs-content-type: ${MODULE_TYPE}\n\n/" "$FILE" - rm -f "${FILE}.bak" + + if [[ "$WILL_CHANGE" == false ]]; then + cqa_file_pass "$FILE" + return 0 fi - echo " + Added :_mod-docs-content-type: ${MODULE_TYPE}" -fi - -if [ "$TITLE_CHANGED" = true ]; then - OLD_TITLE=$(grep "^= " "$FILE" | head -1 | sed 's/^= //') - if [ "$FIX_MODE" = true ]; then - # Use raw fixed title (preserving attributes like {product}) for write-back - sed -i.bak "s/^= ${OLD_TITLE}/= ${FIXED_TITLE_RAW}/" "$FILE" - rm -f "${FILE}.bak" + + cqa_file_start "$FILE" + + # Report/apply title change + if [[ "$TITLE_CHANGED" == true ]]; then + local OLD_TITLE + OLD_TITLE=$(grep "^= " "$FILE" | head -1 | sed 's/^= //') + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i.bak "s/^= ${OLD_TITLE}/= ${FIXED_TITLE_RAW}/" "$FILE" + rm -f "${FILE}.bak" + fi + cqa_fail_autofix "$FILE" "" "Title: ${OLD_TITLE} -> ${FIXED_TITLE_RAW}" "Changed title to imperative" fi - echo " * Title: ${OLD_TITLE} → ${FIXED_TITLE_RAW}" -fi -if [ "$CURRENT_ID" != "$EXPECTED_ID" ]; then - if [ "$FIX_MODE" = true ]; then - if [ "$MODULE_TYPE" = "ASSEMBLY" ]; then - # Handle IDs with _{context} suffix - sed -i.bak "s/\[id=\"[^\"]*_{context}\"\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" - sed -i.bak "s/\[id='[^']*_{context}'\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" - # Handle IDs without _{context} suffix (add it) - sed -i.bak "s/\[id=\"${CURRENT_ID}\"\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" - sed -i.bak "s/\[id='${CURRENT_ID}'\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" - sed -i.bak "s/^:context: .*$/:context: ${EXPECTED_ID}/" "$FILE" - else - # Handle IDs with _{context} suffix + # Report/apply ID change + if [[ "$CURRENT_ID" != "$EXPECTED_ID" ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then sed -i.bak "s/\[id=\"[^\"]*_{context}\"\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" sed -i.bak "s/\[id='[^']*_{context}'\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" - # Handle IDs without _{context} suffix (add it) sed -i.bak "s/\[id=\"${CURRENT_ID}\"\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" sed -i.bak "s/\[id='${CURRENT_ID}'\]/[id=\"${EXPECTED_ID}_{context}\"]/" "$FILE" + if [[ "$MODULE_TYPE" == "ASSEMBLY" ]]; then + sed -i.bak "s/^:context: .*$/:context: ${EXPECTED_ID}/" "$FILE" + fi + rm -f "${FILE}.bak" + + # Update xrefs + local xref_count=0 + while read -r xref_file; do + sed -i.bak "s/xref:${CURRENT_ID}_/xref:${EXPECTED_ID}_/g" "$xref_file" + rm -f "${xref_file}.bak" + xref_count=$((xref_count + 1)) + done < <(grep -rl "xref:${CURRENT_ID}_" assemblies/ modules/ titles/ 2>/dev/null || true) fi - rm -f "${FILE}.bak" - fi - if [ "$MODULE_TYPE" = "ASSEMBLY" ]; then - echo " * ID: ${CURRENT_ID} → ${EXPECTED_ID}" - echo " * Context: ${CURRENT_ID} → ${EXPECTED_ID}" - else - echo " * ID: ${CURRENT_ID} → ${EXPECTED_ID}" + cqa_fail_autofix "$FILE" "" "ID: ${CURRENT_ID} -> ${EXPECTED_ID}" "Updated ID and context" fi -fi - -if [ "$CURRENT_ID" != "$EXPECTED_ID" ] && [ "$FIX_MODE" = true ]; then - XREF_COUNT=0 - while read -r xref_file; do - sed -i.bak "s/xref:${CURRENT_ID}_/xref:${EXPECTED_ID}_/g" "$xref_file" - rm -f "${xref_file}.bak" - XREF_COUNT=$((XREF_COUNT + 1)) - done < <(grep -rl "xref:${CURRENT_ID}_" assemblies/ modules/ titles/ 2>/dev/null) - - if [ $XREF_COUNT -gt 0 ]; then - echo " * Updated $XREF_COUNT xref(s)" - fi -fi - -if [ "$FILE" != "$NEW_FILE" ]; then - OLD_BASENAME=$(basename "$FILE") - NEW_BASENAME=$(basename "$NEW_FILE") - echo " * File: $(basename "$FILE") → $NEW_BASENAME" - - if [ "$FIX_MODE" = true ]; then - git mv "$FILE" "$NEW_FILE" 2>/dev/null || mv "$FILE" "$NEW_FILE" - - INCLUDE_COUNT=0 - while read -r include_file; do - if grep -q "include::.*${OLD_BASENAME}\[" "$include_file"; then - sed -i.bak "s|include::\(.*\)${OLD_BASENAME}\[|include::\1${NEW_BASENAME}[|g" "$include_file" - rm -f "${include_file}.bak" - INCLUDE_COUNT=$((INCLUDE_COUNT + 1)) - fi - done < <(find assemblies/ modules/ titles/ -name "*.adoc" -type f 2>/dev/null) - if [ $INCLUDE_COUNT -gt 0 ]; then - echo " * Updated $INCLUDE_COUNT include(s)" + # Report/apply file rename + if [[ "$FILE" != "$NEW_FILE" ]]; then + local OLD_BASENAME + OLD_BASENAME=$(basename "$FILE") + local NEW_BASENAME + NEW_BASENAME=$(basename "$NEW_FILE") + if [[ "$CQA_FIX_MODE" == true ]]; then + git mv "$FILE" "$NEW_FILE" 2>/dev/null || mv "$FILE" "$NEW_FILE" + while read -r include_file; do + if grep -q "include::.*${OLD_BASENAME}\[" "$include_file"; then + sed -i.bak "s|include::\(.*\)${OLD_BASENAME}\[|include::\1${NEW_BASENAME}[|g" "$include_file" + rm -f "${include_file}.bak" + fi + done < <(find assemblies/ modules/ titles/ -name "*.adoc" -type f 2>/dev/null) + FILE="$NEW_FILE" fi - - FILE="$NEW_FILE" + cqa_fail_autofix "$FILE" "" "File: ${OLD_BASENAME} -> ${NEW_BASENAME}" "Renamed file and updated includes" fi -fi } -# Main script -FIX_MODE=false -TARGET_FILE="" +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa10_check() { + local target="$1" -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [ ! -f "$TARGET_FILE" ]; then - echo "Error: File not found: $TARGET_FILE" - exit 1 -fi - -# Collect all files to process (target + includes) -ALL_FILES=() -collect_files "$TARGET_FILE" ALL_FILES - -# Separate module files from non-module files -MODULE_FILES=() -SKIPPED_FILES=() - -for file in "${ALL_FILES[@]}"; do - # Skip non-.adoc files - if [[ "$file" != *.adoc ]]; then - continue - fi + cqa_header "10" "Verify Titles Are Brief, Complete, and Descriptive" "$target" - # Skip attributes.adoc and master.adoc files (special files) - basename_file=$(basename "$file") - if [[ "$basename_file" == "attributes.adoc" ]] || [[ "$basename_file" == "master.adoc" ]]; then - SKIPPED_FILES+=("$file") - continue - fi + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue + [[ "$(basename "$file")" == "master.adoc" ]] && continue - # Check if file has content type metadata or module prefix - content_type=$(get_content_type "$file") - basename_no_ext=$(basename "$file" .adoc) + local content_type + content_type=$(cqa_get_content_type "$file") + local basename_no_ext + basename_no_ext=$(basename "$file" .adoc) - if [[ -n "$content_type" ]] || [[ "$basename_no_ext" =~ ^(proc|con|ref|assembly|snip)- ]]; then - MODULE_FILES+=("$file") - else - SKIPPED_FILES+=("$file") - fi -done - -# Show what will be processed -echo "=== Found ${#ALL_FILES[@]} file(s) in include tree ===" -if [[ ${#SKIPPED_FILES[@]} -gt 0 ]]; then - echo "Skipping ${#SKIPPED_FILES[@]} non-module file(s): ${SKIPPED_FILES[*]}" -fi -echo "Processing ${#MODULE_FILES[@]} module file(s)" -echo "" - -# Process each module file -for file in "${MODULE_FILES[@]}"; do - process_file "$file" -done - -echo "" -echo "=== Summary ===" -echo "✓ Processed ${#MODULE_FILES[@]} module file(s)" + if [[ -n "$content_type" ]] || [[ "$basename_no_ext" =~ ^(proc|con|ref|assembly|snip)- ]]; then + _process_file "$file" + fi + done +} + +cqa_run_for_each_title _cqa10_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-11-procedures-prerequisites.sh b/build/scripts/cqa-11-procedures-prerequisites.sh index 8bfceffab24..a3706002bae 100755 --- a/build/scripts/cqa-11-procedures-prerequisites.sh +++ b/build/scripts/cqa-11-procedures-prerequisites.sh @@ -2,215 +2,86 @@ # cqa-11-procedures-prerequisites.sh # Validates procedure prerequisites requirements (CQA #11) # -# Usage: ./cqa-11-procedures-prerequisites.sh [--fix] <file-path> -# --fix: Fix singular .Prerequisite to .Prerequisites -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-11-procedures-prerequisites.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-11-procedures-prerequisites.sh [--fix] [--all] <file-path> # # Checks: # - .Prerequisites label used (not .Prerequisite singular) # - Prerequisites use bulleted list (not numbered) # - No more than 10 prerequisites per procedure -# - No imperative instructions in prerequisites (should be completed states) +# +# Autofix: +# - .Prerequisite -> .Prerequisites +# - Numbered list -> bulleted list in prerequisites section # # Skips: # - Non-PROCEDURE files -# - SNIPPET, CONCEPT, REFERENCE, ASSEMBLY files # - attributes.adoc and master.adoc files -# - Content inside source/listing blocks - -set -e -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -# Parse arguments -FIX_MODE=false -TARGET_FILE="" +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa11_check() { + local target="$1" -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done + cqa_header "11" "Verify Procedure Prerequisites" "$target" -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue + [[ "$(basename "$file")" == "master.adoc" ]] && continue -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi + local content_type + content_type=$(cqa_get_content_type "$file") + [[ -z "$content_type" ]] && continue + [[ "$content_type" != "PROCEDURE" ]] && continue -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi + cqa_file_start "$file" - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi + local file_has_issue=false - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" + # Check 1: Singular .Prerequisite (should be .Prerequisites) + if grep -q "^\.Prerequisite$" "$file" 2>/dev/null; then + local prereq_ln + prereq_ln=$(grep -n "^\.Prerequisite$" "$file" | head -1 | cut -d: -f1) + if [[ "$CQA_FIX_MODE" == true ]]; then + sed -i 's/^\.Prerequisite$/.Prerequisites/' "$file" + fi + cqa_fail_autofix "$file" "$prereq_ln" ".Prerequisite should be .Prerequisites (plural)" "Fixed: .Prerequisite -> .Prerequisites" + file_has_issue=true fi - done -} -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" + # Check 2: Count prerequisites (max 10) + if grep -q "^\.Prerequisites" "$file" 2>/dev/null; then + local prereq_items + prereq_items=$(awk '/^\.Prerequisites/{flag=1; next} flag && /^\.(Procedure|Verification|Troubleshooting|Next steps|Additional)/{exit} flag && /^\* /{count++} END{print count+0}' "$file" 2>/dev/null) + if [[ $prereq_items -gt 10 ]]; then + cqa_fail_manual "$file" "" "Too many prerequisites: $prereq_items (max 10) -- combine or prioritize" + file_has_issue=true + fi - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" + # Check 3: Prerequisites using numbered list (should be bulleted) + local numbered_prereqs + numbered_prereqs=$(awk '/^\.Prerequisites/{flag=1; next} flag && /^\.(Procedure|Verification|Troubleshooting|Next steps|Additional)/{exit} flag && /^\. /{count++} END{print count+0}' "$file" 2>/dev/null) + if [[ $numbered_prereqs -gt 0 ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + # Convert numbered list items to bulleted in prerequisites section + sed -i '/^\.Prerequisites$/,/^\.\(Procedure\|Verification\|Troubleshooting\|Next steps\|Additional\)/{/^\.Prerequisites$/!{/^\.\(Procedure\|Verification\|Troubleshooting\|Next steps\|Additional\)/!s/^\. /* /}}' "$file" + fi + local prereq_ln + prereq_ln=$(grep -n "^\.Prerequisites" "$file" | head -1 | cut -d: -f1) + cqa_fail_autofix "$file" "$prereq_ln" "Prerequisites use numbered list ($numbered_prereqs items) -- should use bullets (*)" "Converted numbered to bulleted list" + file_has_issue=true + fi + fi - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return + if [[ "$file_has_issue" == false ]]; then + cqa_file_pass "$file" fi done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") + return 0 } -# Get content type from first line of file -get_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - fi -} - -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -echo "=== CQA #11: Verify Procedure Prerequisites ===" -echo "" -echo "Reference: .claude/skills/cqa-11-procedures-prerequisites.md" -echo "" -if [[ "$FIX_MODE" == true ]]; then - echo -e "${YELLOW}FIX MODE${NC} - Will fix singular .Prerequisite to .Prerequisites" - echo "" -fi - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -TOTAL_FILES=0 -VIOLATIONS=0 -FILES_FIXED=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue - - # Skip special files - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - [[ "$(basename "$file")" == "master.adoc" ]] && continue - - # Get content type - content_type=$(get_content_type "$file") - [[ -z "$content_type" ]] && continue - - # Only check PROCEDURE files - [[ "$content_type" != "PROCEDURE" ]] && continue - - TOTAL_FILES=$((TOTAL_FILES + 1)) - - file_violations=() - - # Check 1: Singular .Prerequisite (should be .Prerequisites) - if grep -q "^\.Prerequisite$" "$file" 2>/dev/null; then - file_violations+=("Singular .Prerequisite should be .Prerequisites") - if [[ "$FIX_MODE" == true ]]; then - sed -i 's/^\.Prerequisite$/.Prerequisites/' "$file" - FILES_FIXED=$((FILES_FIXED + 1)) - fi - fi - - # Check 2: Count prerequisites (max 10) - if grep -q "^\.Prerequisites" "$file" 2>/dev/null; then - prereq_items=$(awk '/^\.Prerequisites/{flag=1; next} flag && /^\.(Procedure|Verification|Troubleshooting|Next steps|Additional)/{exit} flag && /^\* /{count++} END{print count+0}' "$file" 2>/dev/null) - if [[ $prereq_items -gt 10 ]]; then - file_violations+=("Too many prerequisites: $prereq_items (max 10)") - fi - - # Check 3: Prerequisites using numbered list (should be bulleted) - numbered_prereqs=$(awk '/^\.Prerequisites/{flag=1; next} flag && /^\.(Procedure|Verification|Troubleshooting|Next steps|Additional)/{exit} flag && /^\. /{count++} END{print count+0}' "$file" 2>/dev/null) - if [[ $numbered_prereqs -gt 0 ]]; then - file_violations+=("Prerequisites use numbered list ($numbered_prereqs items) - should use bullets (*)") - fi - fi - - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - VIOLATIONS=$((VIOLATIONS + 1)) - echo -e "${RED}x${NC} $file" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - echo "" - else - echo -e "${GREEN}v${NC} $file" - fi -done - -echo "" -echo "=== Summary ===" -echo "Procedures checked: $TOTAL_FILES" - -if [[ "$FIX_MODE" == true ]] && [[ $FILES_FIXED -gt 0 ]]; then - echo -e "${YELLOW}Fixed $FILES_FIXED file(s)${NC}" -fi - -if [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v All procedure prerequisites meet requirements${NC}" - exit 0 -else - echo -e "${RED}x Found violations in $VIOLATIONS file(s)${NC}" - if [[ "$FIX_MODE" != true ]]; then - echo "" - echo "Run with --fix to auto-fix singular .Prerequisite:" - echo " $0 --fix $TARGET_FILE" - fi - exit 1 -fi +cqa_run_for_each_title _cqa11_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh b/build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh index 102ccfbb174..50b9f61ee6d 100755 --- a/build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh +++ b/build/scripts/cqa-12-content-is-grammatically-correct-and-follows-rules.sh @@ -2,72 +2,102 @@ # cqa-12-content-is-grammatically-correct-and-follows-rules.sh # Validates grammar and style using Vale (CQA #12) # -# Usage: ./cqa-12-content-is-grammatically-correct-and-follows-rules.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-12-content-is-grammatically-correct-and-follows-rules.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-12-content-is-grammatically-correct-and-follows-rules.sh [--fix] [--all] <file-path> # # Checks: # - Runs Vale with .vale.ini (grammar, spelling, style, terminology) # - Reports errors, warnings, and suggestions # +# Autofix: +# - Passes --fix through to Vale (Vale supports --fix for some rules) +# # Requires: # - vale CLI installed # - .vale.ini configuration file -set -e +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" +[[ -f ".vale.ini" ]] || { echo "Error: .vale.ini configuration file not found" >&2; exit 1; } -# Parse arguments -FIX_MODE=false -TARGET_FILE="" +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa12_check() { + local target="$1" -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done + cqa_header "12" "Verify Grammar and Style (Vale)" "$target" + + # Filter out attributes.adoc from collected files + local vale_files=() + for f in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$f" != *.adoc ]] && continue + [[ "$(basename "$f")" == "attributes.adoc" ]] && continue + vale_files+=("$f") + done -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi + if [[ ${#vale_files[@]} -eq 0 ]]; then + echo "No files to validate." + return + fi -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi + cqa_file_start "$target" -if [[ ! -f ".vale.ini" ]]; then - echo "Error: .vale.ini configuration file not found" >&2 - exit 1 -fi + if [[ "$CQA_FORMAT" == "json" ]]; then + # SARIF mode: get Vale JSON, convert to SARIF results + local vale_json + vale_json=$(vale --config .vale.ini --output JSON "${vale_files[@]}" 2>/dev/null || true) -# Get all included files, excluding attributes.adoc (defines attribute values -# using literal product names, which intentionally triggers DeveloperHub.Attributes rules) -ALL_FILES=$("$REPO_ROOT/build/scripts/list-all-included-files-starting-from.sh" "$TARGET_FILE" | tr ' ' '\n' | grep -v '/attributes\.adoc$' | tr '\n' ' ') + python3 -c " +import json, sys +try: + d = json.loads('''$vale_json''') + count = 0 + for f, issues in d.items(): + for i in issues: + count += 1 + print(f\"{f}\t{i['Line']}\t{i['Severity']}\t{i['Check']}: {i['Message']}\") + if count == 0: + sys.exit(0) + else: + sys.exit(1) +except Exception as e: + print(f'Error parsing Vale JSON: {e}', file=sys.stderr) + sys.exit(2) +" 2>/dev/null | while IFS=$'\t' read -r file line _severity message; do + cqa_fail_manual "$file" "$line" "$message" + done + else + # Checklist mode: run Vale and format output + if [[ "$CQA_FIX_MODE" == true ]]; then + echo "Running Vale with grammar/style checks..." + echo "(Vale --fix is not yet supported; showing issues for manual fix)" + echo "" + fi -if [[ -z "$ALL_FILES" ]]; then - echo "Error: No files found to validate" >&2 - exit 1 -fi + local vale_output + vale_output=$(vale --config .vale.ini --output line "${vale_files[@]}" 2>/dev/null || true) + + if [[ -z "$vale_output" ]]; then + cqa_file_pass "$target" + else + # Only report as failure if there are errors (not just warnings/suggestions) + local error_count + error_count=$(echo "$vale_output" | grep -c ':error:' || echo "0") + local warning_count + warning_count=$(echo "$vale_output" | grep -c ':warning:' || echo "0") + local total_count + total_count=$(echo "$vale_output" | wc -l) + + if [[ "$error_count" -gt 0 ]]; then + echo "$vale_output" | grep ':error:' | head -20 + echo "" + cqa_fail_manual "$target" "" "Vale found ${error_count} errors (${warning_count} warnings, ${total_count} total issues)" + else + cqa_file_pass "$target" + fi + fi + fi +} -# Run Vale with grammar/style config (JSON output only) -# shellcheck disable=SC2086 -vale --config .vale.ini --output JSON $ALL_FILES +cqa_run_for_each_title _cqa12_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh b/build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh index 388ba581df5..1ba2f889174 100755 --- a/build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh +++ b/build/scripts/cqa-13-information-is-conveyed-using-the-correct-content.sh @@ -2,249 +2,105 @@ # cqa-13-information-is-conveyed-using-the-correct-content.sh # Validates content matches its declared content type (CQA #13) # -# Usage: ./cqa-13-information-is-conveyed-using-the-correct-content.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-13-information-is-conveyed-using-the-correct-content.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-13-information-is-conveyed-using-the-correct-content.sh [--fix] [--all] <file-path> # # Checks: # - PROCEDURE files have .Procedure section with numbered steps -# - CONCEPT files do not have .Procedure sections or numbered steps +# - CONCEPT files do not have .Procedure sections # - REFERENCE files do not have .Procedure sections -# - ASSEMBLY files contain only intro + includes (no detailed content) -# - SNIPPET files have no structural elements (anchors, H1 headings, block titles) +# - Filename prefix matches content type +# +# Autofix: +# - Filename prefix correction (via git mv) +# - Content type metadata correction # # Skips: -# - attributes.adoc and master.adoc files -# - Content inside source/listing blocks - -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 +# - SNIPPET files, attributes.adoc, master.adoc + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa13_check() { + local target="$1" + + cqa_header "13" "Verify Content Matches Declared Type" "$target" + + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue + [[ "$(basename "$file")" == "master.adoc" ]] && continue + + local content_type + content_type=$(cqa_get_content_type "$file") + [[ -z "$content_type" ]] && continue + [[ "$content_type" == "SNIPPET" ]] && continue + + cqa_file_start "$file" + + local file_has_issue=false + + case "$content_type" in + PROCEDURE) + if ! grep -q "^\.Procedure" "$file" 2>/dev/null; then + cqa_fail_manual "$file" "" "PROCEDURE without .Procedure section" + file_has_issue=true + fi + ;; + CONCEPT) + if grep -q "^\.Procedure" "$file" 2>/dev/null; then + cqa_fail_manual "$file" "" "CONCEPT has .Procedure section (should be PROCEDURE type or remove steps)" + file_has_issue=true + fi + ;; + REFERENCE) + if grep -q "^\.Procedure" "$file" 2>/dev/null; then + cqa_fail_manual "$file" "" "REFERENCE has .Procedure section (should be PROCEDURE type or remove steps)" + file_has_issue=true + fi + ;; + esac + + # Check filename prefix matches content type + local basename_file + basename_file=$(basename "$file" .adoc) + local expected_prefix="" + case "$content_type" in + PROCEDURE) expected_prefix="proc-" ;; + CONCEPT) expected_prefix="con-" ;; + REFERENCE) expected_prefix="ref-" ;; + ASSEMBLY) expected_prefix="assembly-" ;; + esac + + if [[ -n "$expected_prefix" ]] && [[ ! "$basename_file" =~ ^${expected_prefix} ]]; then + if [[ "$CQA_FIX_MODE" == true ]]; then + # Auto-rename: strip existing prefix, add correct one + local new_basename="${expected_prefix}${basename_file#*-}" + local new_file + new_file="$(dirname "$file")/${new_basename}.adoc" + if [[ "$file" != "$new_file" ]]; then + git mv "$file" "$new_file" 2>/dev/null || mv "$file" "$new_file" + # Update include statements across the repo + local old_bn + old_bn=$(basename "$file") + local new_bn + new_bn=$(basename "$new_file") + while IFS= read -r inc_file; do + sed -i "s|${old_bn}|${new_bn}|g" "$inc_file" + done < <(grep -rl "$old_bn" assemblies/ modules/ titles/ 2>/dev/null || true) + fi fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" + cqa_fail_autofix "$file" "" "Filename prefix mismatch: expected ${expected_prefix} for ${content_type} (got: $basename_file)" "Renamed to ${expected_prefix}${basename_file#*-}.adoc" + file_has_issue=true fi - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" + if [[ "$file_has_issue" == false ]]; then + cqa_file_pass "$file" fi done + return 0 } -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return - fi - done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Get content type from first line of file -get_content_type() { - local file="$1" - local first_line - first_line=$(head -1 "$file" 2>/dev/null) - if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then - echo "${BASH_REMATCH[1]}" - fi -} - -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -echo "=== CQA #13: Verify Content Matches Declared Type ===" -echo "" -echo "Reference: .claude/skills/cqa-13-information-is-conveyed-using-the-correct-content.md" -echo "" - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -TOTAL_FILES=0 -VIOLATIONS=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue - - # Skip special files - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - [[ "$(basename "$file")" == "master.adoc" ]] && continue - - # Get content type - content_type=$(get_content_type "$file") - [[ -z "$content_type" ]] && continue - - # Skip SNIPPET for most checks - [[ "$content_type" == "SNIPPET" ]] && continue - - TOTAL_FILES=$((TOTAL_FILES + 1)) - - file_violations=() - - case "$content_type" in - PROCEDURE) - # PROCEDURE must have .Procedure section - if ! grep -q "^\.Procedure" "$file" 2>/dev/null; then - file_violations+=("PROCEDURE without .Procedure section") - fi - ;; - CONCEPT) - # CONCEPT must not have .Procedure section - if grep -q "^\.Procedure" "$file" 2>/dev/null; then - file_violations+=("CONCEPT has .Procedure section (should be PROCEDURE or remove steps)") - fi - ;; - REFERENCE) - # REFERENCE must not have .Procedure section - if grep -q "^\.Procedure" "$file" 2>/dev/null; then - file_violations+=("REFERENCE has .Procedure section (should be PROCEDURE or remove steps)") - fi - ;; - ASSEMBLY) - # ASSEMBLY should not have detailed content (only intro + includes) - # Count non-include, non-empty, non-comment, non-metadata lines after title - detail_lines=$(awk ' - /^= /{found=1; next} - found && /^include::/{next} - found && /^\[role="_abstract"\]/{next} - found && /^\[id=/{next} - found && /^:_mod-docs-content-type:/{next} - found && /^:context:/{next} - found && /^ifdef::|^ifndef::|^endif::/{next} - found && /^\/\//{next} - found && /^\.Prerequisites/{next} - found && /^\.Additional resources/{next} - found && /^\* /{next} - found && /^$/{next} - found && /^ifeval::|^endif::/{next} - found{count++} - END{print count+0} - ' "$file" 2>/dev/null) - if [[ $detail_lines -gt 5 ]]; then - file_violations+=("ASSEMBLY has $detail_lines lines of detailed content (should only have intro + includes)") - fi - ;; - esac - - # Check filename prefix matches content type - basename_file=$(basename "$file" .adoc) - case "$content_type" in - PROCEDURE) - if [[ ! "$basename_file" =~ ^proc- ]]; then - file_violations+=("Filename prefix mismatch: expected proc- for PROCEDURE (got: $basename_file)") - fi - ;; - CONCEPT) - if [[ ! "$basename_file" =~ ^con- ]]; then - file_violations+=("Filename prefix mismatch: expected con- for CONCEPT (got: $basename_file)") - fi - ;; - REFERENCE) - if [[ ! "$basename_file" =~ ^ref- ]]; then - file_violations+=("Filename prefix mismatch: expected ref- for REFERENCE (got: $basename_file)") - fi - ;; - ASSEMBLY) - if [[ ! "$basename_file" =~ ^assembly- ]]; then - file_violations+=("Filename prefix mismatch: expected assembly- for ASSEMBLY (got: $basename_file)") - fi - ;; - esac - - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - VIOLATIONS=$((VIOLATIONS + 1)) - echo -e "${RED}x${NC} $file [$content_type]" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - echo "" - else - echo -e "${GREEN}v${NC} $file [$content_type]" - fi -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $TOTAL_FILES" - -if [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v All content matches declared types${NC}" - exit 0 -else - echo -e "${RED}x Found $VIOLATIONS file(s) with content type mismatches${NC}" - echo "" - echo "See .claude/skills/cqa-13-information-is-conveyed-using-the-correct-content.md" - exit 1 -fi +cqa_run_for_each_title _cqa13_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-14-no-broken-links.sh b/build/scripts/cqa-14-no-broken-links.sh index 2500bd9c449..caccbd6640d 100755 --- a/build/scripts/cqa-14-no-broken-links.sh +++ b/build/scripts/cqa-14-no-broken-links.sh @@ -2,186 +2,87 @@ # cqa-14-no-broken-links.sh # Validates no broken links exist (CQA #14) # -# Usage: ./cqa-14-no-broken-links.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-14-no-broken-links.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-14-no-broken-links.sh [--fix] [--all] <file-path> # # Checks: -# - Internal xrefs point to existing files -# - Anchor IDs referenced in xrefs exist in target files # - include:: targets exist # - Image references point to existing files # +# Autofix (--fix stub): +# - Reports [MANUAL] items (fixing broken links requires human judgment on correct target) +# # Note: For full link validation including external URLs, run build-ccutil.sh -# which executes htmltest on the generated HTML. - -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa14_check() { + local target="$1" -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi + cqa_header "14" "Verify No Broken Links" "$target" -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return + # Resolve :imagesdir: from the target, then fall back to artifacts/attributes.adoc + local imagesdir="" + imagesdir=$(grep -m1 '^:imagesdir:' "$target" 2>/dev/null | sed 's/^:imagesdir: *//' || true) + if [[ -z "$imagesdir" ]] && [[ -f "artifacts/attributes.adoc" ]]; then + imagesdir=$(grep -m1 '^:imagesdir:' "artifacts/attributes.adoc" 2>/dev/null | sed 's/^:imagesdir: *//' || true) fi - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" - fi - done -} - -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + + cqa_file_start "$file" + + local file_has_issue=false + local file_dir + file_dir=$(dirname "$file") + + # Check 1: Broken include:: references + while IFS= read -r line; do + local include_path + include_path=$(echo "$line" | sed 's/^include:://' | sed 's/\[.*//') + # Skip lines with attribute substitutions + [[ "$include_path" == *"{"* ]] && continue + local local_path="$file_dir/$include_path" + if [[ ! -f "$local_path" ]]; then + local line_num + line_num=$(grep -n "include::${include_path}" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$line_num" "Broken include: $include_path" + file_has_issue=true + fi + done < <(grep "^include::" "$file" 2>/dev/null || true) + + # Check 2: Broken image references + while IFS=: read -r line_num line_content; do + [[ -z "$line_content" ]] && continue + local image_path + image_path=$(echo "$line_content" | sed -E 's/.*image::?([^[]*)\[.*/\1/') + [[ "$image_path" == *"{"* ]] && continue + # Skip empty, URLs, or paths with spaces/quotes (likely YAML in code blocks) + [[ -z "$image_path" || "$image_path" == *"://"* || "$image_path" == *" "* || "$image_path" == *"'"* ]] && continue + # Resolve image path using :imagesdir: if set + local resolved=false + if [[ -n "$imagesdir" ]] && [[ -f "$imagesdir/$image_path" ]]; then + resolved=true + elif [[ -f "$file_dir/$image_path" ]]; then + resolved=true + elif [[ -f "$image_path" ]]; then + resolved=true + fi + if [[ "$resolved" == false ]]; then + cqa_fail_manual "$file" "$line_num" "Broken image: $image_path" + file_has_issue=true + fi + done < <(grep -n "image::.*\[" "$file" 2>/dev/null || true) - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return + if [[ "$file_has_issue" == false ]]; then + cqa_file_pass "$file" fi done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") + return 0 } -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -echo "=== CQA #14: Verify No Broken Links ===" -echo "" -echo "Reference: .claude/skills/cqa-14-no-broken-links.md" -echo "" - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -TOTAL_FILES=0 -VIOLATIONS=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue - - TOTAL_FILES=$((TOTAL_FILES + 1)) - - file_violations=() - file_dir=$(dirname "$file") - - # Check 1: Broken include:: references - while IFS= read -r line; do - include_path=$(echo "$line" | sed 's/^include:://' | sed 's/\[.*//') - # Skip lines with attribute substitutions - if [[ "$include_path" == *"{"* ]]; then - continue - fi - local_path="$file_dir/$include_path" - if [[ ! -f "$local_path" ]]; then - file_violations+=("Broken include: $include_path") - fi - done < <(grep "^include::" "$file" 2>/dev/null || true) - - # Check 2: Broken image references - while IFS=: read -r line_num line_content; do - [[ -z "$line_content" ]] && continue - image_path=$(echo "$line_content" | sed -E 's/.*image::?([^[]*)\[.*/\1/') - # Skip lines with attribute substitutions - if [[ "$image_path" == *"{"* ]]; then - continue - fi - if [[ -n "$image_path" ]] && [[ ! -f "$file_dir/$image_path" ]] && [[ ! -f "$image_path" ]]; then - file_violations+=("Line $line_num: Broken image: $image_path") - fi - done < <(grep -n "image::.*\[" "$file" 2>/dev/null || true) - - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - VIOLATIONS=$((VIOLATIONS + 1)) - echo -e "${RED}x${NC} $file" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - echo "" - else - echo -e "${GREEN}v${NC} $file" - fi -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $TOTAL_FILES" - -if [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v No broken links found${NC}" - echo "" - echo "Note: For full validation including external URLs, run:" - echo " ./build/scripts/build-ccutil.sh" - exit 0 -else - echo -e "${RED}x Found broken links in $VIOLATIONS file(s)${NC}" - echo "" - echo "See .claude/skills/cqa-14-no-broken-links.md" - exit 1 -fi +cqa_run_for_each_title _cqa14_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh b/build/scripts/cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh deleted file mode 100755 index ec90464d770..00000000000 --- a/build/scripts/cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -# cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh -# Checks if redirects are needed and in place (CQA #15) -# -# Usage: ./cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-15-redirects-if-needed-are-in-place-and-work-correc.sh titles/install-rhdh-ocp/master.adoc -# -# Checks: -# - Detects renamed or moved files that may need redirects -# - Reports files with changed IDs that may affect external links -# -# Note: Redirect implementation depends on the publishing platform. -# This script identifies files that MAY need redirects. - -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Color codes -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -echo "=== CQA #15: Check Redirects ===" -echo "" -echo "Reference: .claude/skills/cqa-15-redirects-if-needed-are-in-place-and-work-correc.md" -echo "" - -# Check for renamed files in git (staged or recent commits) -RENAMED_FILES=$(git diff --name-status --diff-filter=R HEAD~5..HEAD -- 'assemblies/' 'modules/' 'titles/' 2>/dev/null || true) -STAGED_RENAMES=$(git diff --cached --name-status --diff-filter=R -- 'assemblies/' 'modules/' 'titles/' 2>/dev/null || true) - -NEEDS_REVIEW=0 - -if [[ -n "$RENAMED_FILES" ]]; then - echo -e "${YELLOW}Renamed files in recent commits (may need redirects):${NC}" - echo "$RENAMED_FILES" | while IFS=$'\t' read -r status old_file new_file; do - echo " $old_file → $new_file" - done - echo "" - NEEDS_REVIEW=$((NEEDS_REVIEW + 1)) -fi - -if [[ -n "$STAGED_RENAMES" ]]; then - echo -e "${YELLOW}Renamed files staged for commit (may need redirects):${NC}" - echo "$STAGED_RENAMES" | while IFS=$'\t' read -r status old_file new_file; do - echo " $old_file → $new_file" - done - echo "" - NEEDS_REVIEW=$((NEEDS_REVIEW + 1)) -fi - -# Check for deleted files -DELETED_FILES=$(git diff --name-status --diff-filter=D HEAD~5..HEAD -- 'assemblies/' 'modules/' 'titles/' 2>/dev/null || true) -if [[ -n "$DELETED_FILES" ]]; then - echo -e "${YELLOW}Deleted files in recent commits (may need redirects):${NC}" - # shellcheck disable=SC2034 - echo "$DELETED_FILES" | while IFS=$'\t' read -r status deleted_file; do - echo " $deleted_file" - done - echo "" - NEEDS_REVIEW=$((NEEDS_REVIEW + 1)) -fi - -echo "=== Summary ===" - -if [[ $NEEDS_REVIEW -eq 0 ]]; then - echo -e "${GREEN}v No renamed or deleted files detected - redirects likely not needed${NC}" - exit 0 -else - echo -e "${YELLOW}Review items above to determine if redirects are needed${NC}" - echo "" - echo "Redirect implementation depends on the publishing platform." - echo "See .claude/skills/cqa-15-redirects-if-needed-are-in-place-and-work-correc.md" - exit 0 -fi diff --git a/build/scripts/cqa-15-redirects.sh b/build/scripts/cqa-15-redirects.sh new file mode 100755 index 00000000000..3487ee5a813 --- /dev/null +++ b/build/scripts/cqa-15-redirects.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# cqa-15-redirects.sh +# Checks if redirects are needed and in place (CQA #15) +# +# Usage: ./cqa-15-redirects.sh [--fix] [--all] <file-path> +# +# Checks: +# - Detects renamed or moved files that may need redirects +# - Reports files with changed IDs that may affect external links +# +# Autofix (--fix stub): +# - Reports [MANUAL] items (redirect implementation is platform-dependent) +# +# Note: Redirect implementation depends on the publishing platform. + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa15_check() { + local target="$1" + + cqa_header "15" "Check Redirects" "$target" + + cqa_file_start "$target" + + local needs_review=false + + # Check for renamed files in git (staged or recent commits) + local renamed_files + renamed_files=$(git diff --name-status --diff-filter=R HEAD~5..HEAD -- 'assemblies/' 'modules/' 'titles/' 2>/dev/null || true) + local staged_renames + staged_renames=$(git diff --cached --name-status --diff-filter=R -- 'assemblies/' 'modules/' 'titles/' 2>/dev/null || true) + + if [[ -n "$renamed_files" ]]; then + while IFS=$'\t' read -r _status old_file new_file; do + [[ -z "$old_file" ]] && continue + cqa_fail_manual "$target" "" "Renamed in recent commit: $old_file -> $new_file -- may need redirect" + needs_review=true + done <<< "$renamed_files" + fi + + if [[ -n "$staged_renames" ]]; then + while IFS=$'\t' read -r _status old_file new_file; do + [[ -z "$old_file" ]] && continue + cqa_fail_manual "$target" "" "Renamed (staged): $old_file -> $new_file -- may need redirect" + needs_review=true + done <<< "$staged_renames" + fi + + # Check for deleted files + local deleted_files + deleted_files=$(git diff --name-status --diff-filter=D HEAD~5..HEAD -- 'assemblies/' 'modules/' 'titles/' 2>/dev/null || true) + if [[ -n "$deleted_files" ]]; then + while IFS=$'\t' read -r _status deleted_file; do + [[ -z "$deleted_file" ]] && continue + cqa_fail_manual "$target" "" "Deleted in recent commit: $deleted_file -- may need redirect" + needs_review=true + done <<< "$deleted_files" + fi + + if [[ "$needs_review" == false ]]; then + cqa_file_pass "$target" + fi + return 0 +} + +cqa_run_for_each_title _cqa15_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-16-official-product-names-are-used.sh b/build/scripts/cqa-16-official-product-names-are-used.sh index aea88900668..ed799233bce 100755 --- a/build/scripts/cqa-16-official-product-names-are-used.sh +++ b/build/scripts/cqa-16-official-product-names-are-used.sh @@ -2,15 +2,14 @@ # cqa-16-official-product-names-are-used.sh # Verify and fix official product name usage per CQA requirement #16 # -# Usage: ./cqa-16-official-product-names-are-used.sh [--fix] <file-path> -# --fix: Apply automatic fixes (replace hardcoded names with attributes) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-16-official-product-names-are-used.sh titles/install-rhdh-ocp/master.adoc -# Processes: master.adoc → assemblies → all included modules (recursive) +# Usage: ./cqa-16-official-product-names-are-used.sh [--fix] [--all] <file-path> # # Checks for hardcoded product names that should use AsciiDoc attributes. # See .vale-styles/DeveloperHub/Attributes.yml for the full list. # +# Autofix: +# - Replaces hardcoded names with attribute references +# # Skips: # - Content inside source/listing blocks (----, ....) # - AsciiDoc attribute definitions (:attr: value) @@ -18,155 +17,12 @@ # - artifacts/attributes.adoc (defines the attributes) # - Snippet files -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -FIX_MODE=false -TARGET_FILE="" - -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done - -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi - -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi - -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi - - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path - - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" - fi - - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" - fi - done -} - -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return - fi - done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") -} - -# Check if a line is inside a source/listing block -# Args: $1 = file, $2 = line number -# Uses pre-computed block ranges for performance -# -# Populates BLOCK_RANGES associative array keyed by file path. -# Each value is a space-separated list of "start:end" pairs. -declare -A BLOCK_RANGES - -compute_block_ranges() { - local file="$1" - - if [[ -n "${BLOCK_RANGES[$file]+x}" ]]; then - return - fi - - local ranges="" - local in_block=false - local block_start=0 - local line_num=0 - - while IFS= read -r line; do - line_num=$((line_num + 1)) - if [[ "$line" =~ ^----+$ ]] || [[ "$line" =~ ^\.\.\.\.+$ ]]; then - if [[ "$in_block" == false ]]; then - in_block=true - block_start=$line_num - else - in_block=false - ranges="$ranges $block_start:$line_num" - fi - fi - done < "$file" - - BLOCK_RANGES[$file]="$ranges" -} - -is_in_block() { - local file="$1" - local line_num="$2" - - local ranges="${BLOCK_RANGES[$file]}" - for range in $ranges; do - local start end - start="${range%%:*}" - end="${range##*:}" - if [[ $line_num -ge $start ]] && [[ $line_num -le $end ]]; then - return 0 - fi - done - return 1 -} +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" # Define product name patterns and their replacements # Format: "pattern|replacement|strip_attrs|parent_pattern" -# pattern: Literal text to search for -# replacement: Suggested attribute (for reporting) -# strip_attrs: Comma-separated attribute names to strip when filtering false positives -# parent_pattern: Longer pattern that contains this one (to avoid double-flagging) -# -# IMPORTANT: Patterns are checked in order. Longer patterns MUST come first -# to avoid partial matches (e.g., "Red Hat OpenShift Container Platform" -# before "OpenShift Container Platform"). PATTERNS=( # Red Hat Platforms (longest first) 'Red Hat Advanced Developer Suite|{rhads-brand-name}|rhads-brand-name|' @@ -222,174 +78,87 @@ PATTERNS=( 'Google Cloud|{gcp-brand-name}|gcp-brand-name|Google Cloud Platform' ) -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -echo "=== CQA #16: Verify Official Product Names ===" -echo "" -echo "Reference: .claude/skills/cqa-16-official-product-names-are-used.md" -echo "" -if [[ "$FIX_MODE" == true ]]; then - echo -e "${YELLOW}FIX MODE${NC} - Will apply automatic replacements" - echo "" -fi - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa16_check() { + local target="$1" -TOTAL_VIOLATIONS=0 -FILES_WITH_VIOLATIONS=0 -FILES_CHECKED=0 -FILES_FIXED=0 + cqa_header "16" "Verify Official Product Names" "$target" -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - # Skip attributes.adoc (defines the attributes) - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - - # Skip snippet files - local_content_type=$(head -1 "$file" 2>/dev/null) - if [[ "$local_content_type" =~ ^:_mod-docs-content-type:.*SNIPPET ]]; then - continue - fi - - FILES_CHECKED=$((FILES_CHECKED + 1)) - - # Pre-compute source block ranges for this file - compute_block_ranges "$file" - - file_violations=0 - - # Check each pattern - for pattern_entry in "${PATTERNS[@]}"; do - IFS='|' read -r pattern replacement strip_attrs parent_pattern <<< "$pattern_entry" - - # Search for hardcoded pattern in the file - while IFS=: read -r line_num line_content; do - [[ -z "$line_num" ]] && continue + local local_content_type + local_content_type=$(head -1 "$file" 2>/dev/null) + if [[ "$local_content_type" =~ ^:_mod-docs-content-type:.*SNIPPET ]]; then + continue + fi - # Skip lines inside source/listing blocks - if is_in_block "$file" "$line_num"; then - continue - fi + cqa_file_start "$file" + cqa_compute_block_ranges "$file" - # Skip AsciiDoc attribute definitions (lines starting with :attr:) - if [[ "$line_content" =~ ^:[a-zA-Z] ]]; then - continue - fi + local file_violations=0 - # Skip AsciiDoc comments - if [[ "$line_content" =~ ^// ]]; then - continue - fi + # Check each pattern + for pattern_entry in "${PATTERNS[@]}"; do + IFS='|' read -r pattern replacement strip_attrs parent_pattern <<< "$pattern_entry" - # Strip attribute references to avoid false positives - # e.g., a line with {product-short} should not flag "Developer Hub" - stripped="$line_content" - IFS=',' read -ra attrs <<< "$strip_attrs" - for attr in "${attrs[@]}"; do - stripped="${stripped//\{${attr}\}/}" - done - if ! echo "$stripped" | grep -q "$pattern"; then - continue - fi + while IFS=: read -r line_num line_content; do + [[ -z "$line_num" ]] && continue - # If this pattern is a substring of a parent pattern, - # check if the match is actually part of the parent - if [[ -n "$parent_pattern" ]]; then - if echo "$line_content" | grep -q "$parent_pattern"; then - # Remove parent pattern occurrences and check if standalone match remains - standalone="${line_content//$parent_pattern/}" - if ! echo "$standalone" | grep -q "$pattern"; then - continue - fi + # Skip lines inside source/listing blocks + if cqa_is_in_block "$file" "$line_num"; then + continue fi - fi - file_violations=$((file_violations + 1)) - - if [[ "$FIX_MODE" == false ]]; then - if [[ $file_violations -eq 1 ]]; then - echo -e "${RED}✗${NC} $file" + # Skip attribute definitions and comments + [[ "$line_content" =~ ^:[a-zA-Z] ]] && continue + [[ "$line_content" =~ ^// ]] && continue + + # Strip attribute references to avoid false positives + local stripped="$line_content" + IFS=',' read -ra attrs <<< "$strip_attrs" + for attr in "${attrs[@]}"; do + stripped="${stripped//\{${attr}\}/}" + done + if ! echo "$stripped" | grep -q "$pattern"; then + continue fi - echo " Line $line_num: Hardcoded \"$pattern\" → $replacement" - fi - done < <(grep -n "$pattern" "$file" 2>/dev/null || true) - done + # If this pattern is a substring of a parent pattern, check for parent + if [[ -n "$parent_pattern" ]]; then + if echo "$line_content" | grep -q "$parent_pattern"; then + local standalone="${line_content//$parent_pattern/}" + if ! echo "$standalone" | grep -q "$pattern"; then + continue + fi + fi + fi - if [[ $file_violations -gt 0 ]]; then - FILES_WITH_VIOLATIONS=$((FILES_WITH_VIOLATIONS + 1)) - TOTAL_VIOLATIONS=$((TOTAL_VIOLATIONS + file_violations)) + file_violations=$((file_violations + 1)) + cqa_fail_autofix "$file" "$line_num" "Hardcoded \"$pattern\" -> $replacement" "Replaced with $replacement" - if [[ "$FIX_MODE" == false ]]; then - echo "" - fi + done < <(grep -n "$pattern" "$file" 2>/dev/null || true) + done - if [[ "$FIX_MODE" == true ]]; then + if [[ $file_violations -gt 0 && "$CQA_FIX_MODE" == true ]]; then # Apply fixes: process patterns longest first (already ordered) - cp "$file" "${file}.bak" - local_fixed=false - for pattern_entry in "${PATTERNS[@]}"; do IFS='|' read -r pattern replacement _ _ <<< "$pattern_entry" - - # Determine the fix attribute (first one if multiple suggested) - # "or" separates alternatives; use the first one for auto-fix - fix_attr="${replacement%% or *}" - + local fix_attr="${replacement%% or *}" if grep -q "$pattern" "$file"; then - # Skip attribute definitions and comments sed -i "/^:/!{/^\/\//!s/$pattern/$fix_attr/g}" "$file" - local_fixed=true fi done - - # Fix double-bracing artifacts from nested replacements + # Fix double-bracing artifacts sed -i 's/{{/{/g; s/}}/}/g' "$file" - - if [[ "$local_fixed" == true ]]; then - FILES_FIXED=$((FILES_FIXED + 1)) - echo -e "${YELLOW}📝${NC} $file - Fixed $file_violations violation(s)" - rm -f "${file}.bak" - else - mv "${file}.bak" "$file" - fi fi - else - echo -e "${GREEN}✓${NC} $file" - fi -done -echo "" -echo "=== Summary ===" -echo "Files checked: $FILES_CHECKED" + if [[ $file_violations -eq 0 ]]; then + cqa_file_pass "$file" + fi + done +} -if [[ "$FIX_MODE" == true ]] && [[ $FILES_FIXED -gt 0 ]]; then - echo -e "${YELLOW}📝 Fixed $FILES_FIXED file(s) with $TOTAL_VIOLATIONS violation(s)${NC}" - echo "" - echo "Review fixes and verify:" - echo " 1. First occurrence in abstract uses {product} (not {product-short})" - echo " 2. Source code blocks were not modified" - echo " 3. Attribute definitions were not modified" - exit 0 -elif [[ $TOTAL_VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}✓ All files use official product name attributes${NC}" - exit 0 -else - echo -e "${RED}✗ Found $TOTAL_VIOLATIONS violation(s) in $FILES_WITH_VIOLATIONS file(s)${NC}" - echo "" - echo "Run with --fix to apply automatic replacements:" - echo " $0 --fix $TARGET_FILE" - echo "" - echo "After fixing, manually verify:" - echo " - First occurrence in abstract uses {product} (not {product-short})" - echo " - Source code blocks were not modified" - exit 1 -fi +cqa_run_for_each_title _cqa16_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh b/build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh index f1d293adf44..d1e6d566eca 100755 --- a/build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh +++ b/build/scripts/cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh @@ -2,194 +2,81 @@ # cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh # Validates Technology Preview and Developer Preview disclaimers (CQA #17) # -# Usage: ./cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh [--fix] <file-path> -# --fix: Currently no automatic fixes available (validation only) -# file: Processes the specified file and all its includes recursively -# Example: ./cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh titles/install-rhdh-ocp/master.adoc +# Usage: ./cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh [--fix] [--all] <file-path> # # Checks: # - Files mentioning "Technology Preview" include the official disclaimer snippet # - Files mentioning "Developer Preview" include the official disclaimer snippet # - Disclaimer snippets are properly included (not hardcoded) # +# Autofix (--fix stub): +# - Reports [MANUAL] items as checklist (no auto-insert — snippet path varies) +# # Skips: # - attributes.adoc files # - Snippet files (snip-*.adoc) — these ARE the disclaimers # - Content inside source/listing blocks -set -e +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" +# shellcheck disable=SC2329 # Invoked indirectly via cqa_run_for_each_title +_cqa17_check() { + local target="$1" -# Parse arguments -FIX_MODE=false -TARGET_FILE="" + cqa_header "17" "Verify Legal Disclaimers for Preview Features" "$target" -# shellcheck disable=SC2034 -for arg in "$@"; do - case "$arg" in - --fix) FIX_MODE=true ;; - *) - if [[ -z "$TARGET_FILE" ]]; then - TARGET_FILE="$arg" - else - echo "Error: unexpected argument: $arg" >&2 - echo "Usage: $0 [--fix] <file-path>" >&2 - exit 1 - fi - ;; - esac -done + for file in "${_CQA_COLLECTED_FILES[@]}"; do + [[ "$file" != *.adoc ]] && continue + [[ "$(basename "$file")" == "attributes.adoc" ]] && continue + [[ "$(basename "$file")" == snip-* ]] && continue -if [[ -z "$TARGET_FILE" ]]; then - echo "Usage: $0 [--fix] <file-path>" >&2 - echo "" >&2 - echo "Examples:" >&2 - echo " $0 titles/install-rhdh-ocp/master.adoc" >&2 - echo " $0 --fix titles/install-rhdh-ocp/master.adoc" >&2 - exit 1 -fi + cqa_file_start "$file" -if [[ ! -f "$TARGET_FILE" ]]; then - echo "Error: File not found: $TARGET_FILE" >&2 - exit 1 -fi + local file_has_issue=false -# Function to extract included files from a given file -get_includes() { - local file="$1" - if [[ ! -f "$file" ]]; then - return - fi + # Pre-compute block ranges to skip source/listing blocks + cqa_compute_block_ranges "$file" - grep "^include::" "$file" 2>/dev/null | sed 's/^include:://' | sed 's/\[.*//' | while read -r include_path; do - local dir - dir=$(dirname "$file") - local resolved_path + # Check for Technology Preview mentions (outside source blocks) + local has_tp_mention=false + while IFS=: read -r tp_ln _; do + [[ -z "$tp_ln" ]] && continue + cqa_is_in_block "$file" "$tp_ln" && continue + has_tp_mention=true + break + done < <(grep -ni "technology preview" "$file" 2>/dev/null || true) - if [[ "$include_path" == /* ]]; then - resolved_path="$include_path" - elif [[ "$include_path" == ../* ]]; then - resolved_path="$dir/$include_path" - else - resolved_path="$dir/$include_path" + if [[ "$has_tp_mention" == true ]] && ! grep -q "include::.*snip-.*tech.*preview\|include::.*snip-.*tp-\|{technology-preview}\|access.redhat.com/support/offerings/techpreview" "$file" 2>/dev/null; then + local line_num + line_num=$(grep -ni "technology preview" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$line_num" "Mentions 'Technology Preview' but may not include official disclaimer snippet" + file_has_issue=true fi - if [[ -f "$resolved_path" ]]; then - # shellcheck disable=SC2269 - resolved_path=$(realpath --relative-to="$REPO_ROOT" "$resolved_path" 2>/dev/null) || resolved_path="$resolved_path" - echo "$resolved_path" + # Check for Developer Preview mentions (outside source blocks) + local has_dp_mention=false + while IFS=: read -r dp_ln _; do + [[ -z "$dp_ln" ]] && continue + cqa_is_in_block "$file" "$dp_ln" && continue + has_dp_mention=true + break + done < <(grep -ni "developer preview" "$file" 2>/dev/null || true) + + if [[ "$has_dp_mention" == true ]] && ! grep -q "include::.*snip-.*dev.*preview\|include::.*snip-.*dp-\|{developer-preview}" "$file" 2>/dev/null; then + local line_num + line_num=$(grep -ni "developer preview" "$file" | head -1 | cut -d: -f1) + cqa_fail_manual "$file" "$line_num" "Mentions 'Developer Preview' but may not include official disclaimer snippet" + file_has_issue=true fi - done -} -# Function to recursively collect all files to process -collect_files() { - local file="$1" - local var_name="$2" - - local current_files - eval "current_files=(\"\${${var_name}[@]}\")" - - for existing_file in "${current_files[@]}"; do - if [[ "$existing_file" == "$file" ]]; then - return + if [[ "$file_has_issue" == false ]]; then + cqa_file_pass "$file" fi done - - eval "${var_name}+=('$file')" - - while IFS= read -r included_file; do - collect_files "$included_file" "$var_name" - done < <(get_includes "$file") + return 0 } -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -echo "=== CQA #17: Verify Legal Disclaimers for Preview Features ===" -echo "" -echo "Reference: .claude/skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md" -echo "" - -# Collect files -FILES_TO_PROCESS=() -collect_files "$TARGET_FILE" FILES_TO_PROCESS - -TOTAL_FILES=0 -VIOLATIONS=0 -PREVIEW_FILES=0 - -for file in "${FILES_TO_PROCESS[@]}"; do - # Skip non-.adoc files - [[ "$file" != *.adoc ]] && continue - - # Skip attributes.adoc - [[ "$(basename "$file")" == "attributes.adoc" ]] && continue - - # Skip snippet files (they ARE the disclaimers) - [[ "$(basename "$file")" == snip-* ]] && continue - - TOTAL_FILES=$((TOTAL_FILES + 1)) - - file_violations=() - - # Check for Technology Preview mentions - if grep -qi "technology preview" "$file" 2>/dev/null; then - PREVIEW_FILES=$((PREVIEW_FILES + 1)) - - # Check if file includes a tech preview disclaimer snippet - if ! grep -q "include::.*snip-.*tech.*preview\|include::.*snip-.*tp-" "$file" 2>/dev/null; then - # Check if there's an attribute reference for the disclaimer - if ! grep -q "{technology-preview}" "$file" 2>/dev/null; then - file_violations+=("Mentions 'Technology Preview' but may not include official disclaimer snippet") - fi - fi - fi - - # Check for Developer Preview mentions - if grep -qi "developer preview" "$file" 2>/dev/null; then - PREVIEW_FILES=$((PREVIEW_FILES + 1)) - - # Check if file includes a dev preview disclaimer snippet - if ! grep -q "include::.*snip-.*dev.*preview\|include::.*snip-.*dp-" "$file" 2>/dev/null; then - if ! grep -q "{developer-preview}" "$file" 2>/dev/null; then - file_violations+=("Mentions 'Developer Preview' but may not include official disclaimer snippet") - fi - fi - fi - - # Report results - if [[ ${#file_violations[@]} -gt 0 ]]; then - VIOLATIONS=$((VIOLATIONS + 1)) - echo -e "${RED}x${NC} $file" - for violation in "${file_violations[@]}"; do - echo " $violation" - done - echo "" - fi -done - -echo "" -echo "=== Summary ===" -echo "Files checked: $TOTAL_FILES" -echo "Files mentioning preview features: $PREVIEW_FILES" - -if [[ $PREVIEW_FILES -eq 0 ]]; then - echo -e "${GREEN}v No preview feature mentions found - disclaimers not needed${NC}" - exit 0 -elif [[ $VIOLATIONS -eq 0 ]]; then - echo -e "${GREEN}v All preview features have appropriate disclaimers${NC}" - exit 0 -else - echo -e "${YELLOW}Found $VIOLATIONS file(s) that may need disclaimer review${NC}" - echo "" - echo "Verify each flagged file includes the official legal-approved disclaimer." - echo "Use snippets for disclaimers in assembly files." - echo "See .claude/skills/cqa-17-includes-appropriate-legal-approved-disclaimers-f.md" - exit 1 -fi +cqa_run_for_each_title _cqa17_check +exit "$(cqa_exit_code)" diff --git a/build/scripts/cqa-lib.sh b/build/scripts/cqa-lib.sh new file mode 100644 index 00000000000..ea84066223b --- /dev/null +++ b/build/scripts/cqa-lib.sh @@ -0,0 +1,660 @@ +#!/bin/bash +# cqa-lib.sh — Shared library for CQA scripts +# Source this file from any cqa-*.sh script: +# source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +# +# Provides: +# - Argument parsing (--fix, --all, --title PATTERN, --format checklist|json) +# - File discovery (cqa_collect_files, cqa_get_content_type) +# - Block range helpers (cqa_compute_block_ranges, cqa_is_in_block) +# - Output helpers (cqa_pass, cqa_fail_autofix, cqa_fail_manual, cqa_fixed, +# cqa_delegated, cqa_file_header, cqa_summary) +# - SARIF JSON output +# +# After sourcing, scripts get these variables: +# CQA_FIX_MODE - true/false +# CQA_ALL_MODE - true/false +# CQA_TITLE_PATTERN - glob pattern for --title +# CQA_FORMAT - "checklist" or "json" +# CQA_TARGET_FILE - path to target file (empty if --all) +# CQA_TARGET_FILES - array of master.adoc files to process +# CQA_REPO_ROOT - absolute path to repository root +# CQA_SCRIPT_DIR - absolute path to build/scripts/ + +# Prevent double-sourcing +[[ -n "${_CQA_LIB_LOADED+x}" ]] && return 0 +_CQA_LIB_LOADED=1 + +readonly _CQA_FMT_CHECKLIST="checklist" + +set -e + +# ── Repository root and script directory ── +CQA_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CQA_REPO_ROOT="$(cd "$CQA_SCRIPT_DIR/../.." && pwd)" +cd "$CQA_REPO_ROOT" + +# ── Argument parsing ── +CQA_FIX_MODE=false +CQA_ALL_MODE=false +CQA_TITLE_PATTERN="" +CQA_FORMAT="$_CQA_FMT_CHECKLIST" +CQA_TARGET_FILE="" +# shellcheck disable=SC2034 # Used by cqa-01 +CQA_OUTPUT_FORMAT="" # legacy --output line|JSON for cqa-01 +declare -a CQA_TARGET_FILES=() + +cqa_parse_args() { + local script_name="$1" + shift + + local expect_title=false + local expect_output=false + + for arg in "$@"; do + if [[ "$expect_title" == true ]]; then + CQA_TITLE_PATTERN="$arg" + expect_title=false + continue + fi + if [[ "$expect_output" == true ]]; then + # shellcheck disable=SC2034 + CQA_OUTPUT_FORMAT="$arg" + expect_output=false + continue + fi + case "$arg" in + --fix) CQA_FIX_MODE=true ;; + --all) CQA_ALL_MODE=true ;; + --title) expect_title=true ;; + --format) + shift_next=true + # handled below + ;; + --format=*) + CQA_FORMAT="${arg#--format=}" ;; + --output) + expect_output=true ;; + -h|--help) + _cqa_usage "$script_name" + exit 0 + ;; + *) + if [[ "$shift_next" == true ]]; then + CQA_FORMAT="$arg" + shift_next=false + elif [[ -z "$CQA_TARGET_FILE" ]]; then + CQA_TARGET_FILE="$arg" + else + echo "Error: unexpected argument: $arg" >&2 + _cqa_usage "$script_name" + exit 1 + fi + ;; + esac + done + + # Validate format + case "$CQA_FORMAT" in + "$_CQA_FMT_CHECKLIST"|json) ;; + *) echo "Error: --format must be '$_CQA_FMT_CHECKLIST' or 'json'" >&2; exit 1 ;; + esac + + # Resolve targets + if [[ "$CQA_ALL_MODE" == true ]]; then + _cqa_discover_all_titles + elif [[ -n "$CQA_TARGET_FILE" ]]; then + if [[ ! -f "$CQA_TARGET_FILE" ]]; then + echo "Error: File not found: $CQA_TARGET_FILE" >&2 + exit 1 + fi + CQA_TARGET_FILES=("$CQA_TARGET_FILE") + else + _cqa_usage "$script_name" + exit 1 + fi +} + +_cqa_usage() { + local script_name="$1" + echo "Usage: $script_name [--fix] [--all | --title PATTERN | <file-path>] [--format checklist|json]" >&2 + echo "" >&2 + echo "Options:" >&2 + echo " --fix Apply automatic fixes" >&2 + echo " --all Process all titles" >&2 + echo " --title PATTERN Process titles matching glob pattern" >&2 + echo " --format FORMAT Output format: checklist (default) or json (SARIF)" >&2 + echo "" >&2 + echo "Examples:" >&2 + echo " $script_name titles/install-rhdh-ocp/master.adoc" >&2 + echo " $script_name --fix --all" >&2 + echo " $script_name --title 'install*' --format json" >&2 + return 0 +} + +_cqa_discover_all_titles() { + local pattern="${CQA_TITLE_PATTERN:-*}" + local seen_realpaths=() + + while IFS= read -r master; do + local real_path + real_path=$(realpath "$master" 2>/dev/null) || real_path="$master" + + # Skip symlink duplicates + local is_dup=false + for seen in "${seen_realpaths[@]}"; do + if [[ "$seen" == "$real_path" ]]; then + is_dup=true + break + fi + done + [[ "$is_dup" == true ]] && continue + + seen_realpaths+=("$real_path") + CQA_TARGET_FILES+=("$master") + done < <(find titles/ -maxdepth 2 -name "master.adoc" -path "titles/${pattern}/master.adoc" 2>/dev/null | sort) + return 0 +} + +# ── File discovery ── +# Replaces list-all-included-files-starting-from.sh and custom collect_files() +# Uses realpath dedup to handle symlinks correctly + +declare -A _CQA_SEEN_FILES=() + +cqa_collect_files() { + local start_file="$1" + _CQA_SEEN_FILES=() + _CQA_COLLECTED_FILES=() + _cqa_collect_recursive "$start_file" + return 0 +} + +_cqa_collect_recursive() { + local file="$1" + + [[ -f "$file" ]] || return 0 + + # Resolve to repo-relative path + local rel_path + rel_path=$(realpath --relative-to="$CQA_REPO_ROOT" "$file" 2>/dev/null) || rel_path="$file" + + # Dedup by realpath (handles symlinks) + local real_path + real_path=$(realpath "$file" 2>/dev/null) || real_path="$rel_path" + + [[ -n "${_CQA_SEEN_FILES[$real_path]+x}" ]] && return 0 + _CQA_SEEN_FILES[$real_path]=1 + + _CQA_COLLECTED_FILES+=("$rel_path") + + # Extract and resolve includes + local dir + dir=$(dirname "$rel_path") + + while IFS= read -r line; do + local include_path + include_path=$(echo "$line" | sed 's/^include:://' | sed 's/\[.*//') + + local resolved + if [[ "$include_path" == /* ]]; then + resolved="$include_path" + else + resolved="$dir/$include_path" + fi + + [[ -f "$resolved" ]] && _cqa_collect_recursive "$resolved" + done < <(grep "^include::" "$rel_path" 2>/dev/null || true) +} + +# Get the collected files array (call after cqa_collect_files) +cqa_get_collected_files() { + echo "${_CQA_COLLECTED_FILES[@]}" +} + +# ── Content type ── + +cqa_get_content_type() { + local file="$1" + local first_line + first_line=$(head -1 "$file" 2>/dev/null) + if [[ "$first_line" =~ ^:_mod-docs-content-type:[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]; then + echo "${BASH_REMATCH[1]}" + fi + return 0 +} + +# ── Block range helpers ── +# Pre-compute source/listing block ranges for a file to skip content inside them + +declare -A _CQA_BLOCK_RANGES=() + +cqa_compute_block_ranges() { + local file="$1" + + [[ -n "${_CQA_BLOCK_RANGES[$file]+x}" ]] && return 0 + + local ranges="" + local in_block=false + local block_start=0 + local line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + if [[ "$line" =~ ^----+$ ]] || [[ "$line" =~ ^\.\.\.\.+$ ]] || [[ "$line" =~ ^\+\+\+\++$ ]]; then + if [[ "$in_block" == false ]]; then + in_block=true + block_start=$line_num + else + in_block=false + ranges="$ranges $block_start:$line_num" + fi + fi + done < "$file" + + _CQA_BLOCK_RANGES[$file]="$ranges" +} + +cqa_is_in_block() { + local file="$1" + local line_num="$2" + + local ranges="${_CQA_BLOCK_RANGES[$file]}" + for range in $ranges; do + local start="${range%%:*}" + local end="${range##*:}" + if [[ $line_num -ge $start ]] && [[ $line_num -le $end ]]; then + return 0 + fi + done + return 1 +} + +# ── Output formatting ── +# Standardized output as actionable checklist + +# Colors (only for checklist format, suppressed in json mode) +if [[ "$CQA_FORMAT" != "json" ]] && [[ -t 1 ]]; then + _C_RED='\033[0;31m' + _C_GREEN='\033[0;32m' + _C_YELLOW='\033[1;33m' + _C_CYAN='\033[0;36m' + _C_NC='\033[0m' +else + _C_RED='' _C_GREEN='' _C_YELLOW='' _C_CYAN='' _C_NC='' +fi + +# SARIF accumulator for json mode +declare -a _CQA_SARIF_RESULTS=() +_CQA_SARIF_TOOL_NAME="" +_CQA_SARIF_TOOL_VERSION="1.0.0" + +cqa_set_tool_info() { + _CQA_SARIF_TOOL_NAME="$1" + _CQA_SARIF_TOOL_VERSION="${2:-1.0.0}" +} + +# Counters +_CQA_TOTAL_FILES=0 +_CQA_FILES_WITH_ISSUES=0 +_CQA_TOTAL_AUTOFIX=0 +_CQA_TOTAL_FIXED=0 +_CQA_TOTAL_MANUAL=0 +_CQA_TOTAL_DELEGATED=0 +_CQA_TOTAL_PASS=0 +_CQA_CURRENT_FILE="" +_CQA_CURRENT_FILE_HAS_ISSUES=false + +cqa_reset_counters() { + _CQA_TOTAL_FILES=0 + _CQA_FILES_WITH_ISSUES=0 + _CQA_TOTAL_AUTOFIX=0 + _CQA_TOTAL_FIXED=0 + _CQA_TOTAL_MANUAL=0 + _CQA_TOTAL_DELEGATED=0 + _CQA_TOTAL_PASS=0 + return 0 +} + +# Start processing a new file +cqa_file_start() { + local file="$1" + _CQA_TOTAL_FILES=$((_CQA_TOTAL_FILES + 1)) + _CQA_CURRENT_FILE="$file" + _CQA_CURRENT_FILE_HAS_ISSUES=false + return 0 +} + +# File header (printed on first issue for that file) +_cqa_ensure_file_header() { + if [[ "$_CQA_CURRENT_FILE_HAS_ISSUES" == false ]]; then + _CQA_CURRENT_FILE_HAS_ISSUES=true + _CQA_FILES_WITH_ISSUES=$((_CQA_FILES_WITH_ISSUES + 1)) + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + echo "" + echo "### $_CQA_CURRENT_FILE" + fi + fi + return 0 +} + +# Report a passing check +cqa_pass() { + local line="${1:-}" + local desc="$2" + _CQA_TOTAL_PASS=$((_CQA_TOTAL_PASS + 1)) + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + if [[ -n "$line" ]]; then + echo -e "- [x] Line ${line}: ${desc}" + else + echo -e "- [x] ${desc}" + fi + fi + # SARIF: passes are not reported + return 0 +} + +# Report an autofixable issue (report mode) or a fixed issue (fix mode) +cqa_fail_autofix() { + local file="${1:-$_CQA_CURRENT_FILE}" + local line="${2:-}" + local desc="$3" + local fix_desc="${4:-$desc}" + + _cqa_ensure_file_header + + if [[ "$CQA_FIX_MODE" == true ]]; then + _CQA_TOTAL_FIXED=$((_CQA_TOTAL_FIXED + 1)) + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + if [[ -n "$line" ]]; then + echo -e "- [x] ${_C_GREEN}[FIXED]${_C_NC} ${file}: Line ${line}: ${fix_desc}" + else + echo -e "- [x] ${_C_GREEN}[FIXED]${_C_NC} ${file}: ${fix_desc}" + fi + fi + else + _CQA_TOTAL_AUTOFIX=$((_CQA_TOTAL_AUTOFIX + 1)) + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + if [[ -n "$line" ]]; then + echo -e "- [ ] ${_C_YELLOW}[AUTOFIX]${_C_NC} ${file}: Line ${line}: ${desc}" + else + echo -e "- [ ] ${_C_YELLOW}[AUTOFIX]${_C_NC} ${file}: ${desc}" + fi + fi + fi + + if [[ "$CQA_FORMAT" == "json" ]]; then + _cqa_sarif_add "$file" "$line" "warning" "$desc" "autofix" + fi + return 0 +} + +# Report a manual-only issue +cqa_fail_manual() { + local file="${1:-$_CQA_CURRENT_FILE}" + local line="${2:-}" + local desc="$3" + + _cqa_ensure_file_header + _CQA_TOTAL_MANUAL=$((_CQA_TOTAL_MANUAL + 1)) + + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + if [[ -n "$line" ]]; then + echo -e "- [ ] ${_C_RED}[MANUAL]${_C_NC} ${file}: Line ${line}: ${desc}" + else + echo -e "- [ ] ${_C_RED}[MANUAL]${_C_NC} ${file}: ${desc}" + fi + fi + + if [[ "$CQA_FORMAT" == "json" ]]; then + _cqa_sarif_add "$file" "$line" "error" "$desc" "manual" + fi + return 0 +} + +# Report a delegated issue (handled by another CQA script) +# Args: file line target_cqa desc [fix_type] +# fix_type: "autofix" (default) or "manual" +cqa_delegated() { + local file="${1:-$_CQA_CURRENT_FILE}" + local line="${2:-}" + local target_cqa="$3" + local desc="$4" + local fix_type="${5:-autofix}" + + _cqa_ensure_file_header + _CQA_TOTAL_DELEGATED=$((_CQA_TOTAL_DELEGATED + 1)) + + local fix_label + if [[ "$fix_type" == "manual" ]]; then + fix_label=" MANUAL" + else + fix_label=" AUTOFIX" + fi + + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + if [[ -n "$line" ]]; then + echo -e "- [ ] ${_C_CYAN}[-> CQA #${target_cqa}${fix_label}]${_C_NC} ${file}: Line ${line}: ${desc}" + else + echo -e "- [ ] ${_C_CYAN}[-> CQA #${target_cqa}${fix_label}]${_C_NC} ${file}: ${desc}" + fi + fi + + if [[ "$CQA_FORMAT" == "json" ]]; then + _cqa_sarif_add "$file" "$line" "note" "Delegated to CQA #${target_cqa}: $desc" "delegated" + fi + return 0 +} + +# Mark file as all-passed (call if no issues were found for the file) +cqa_file_pass() { + local file="${1:-$_CQA_CURRENT_FILE}" + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" && "$_CQA_CURRENT_FILE_HAS_ISSUES" == false ]]; then + echo -e "${_C_GREEN}- [x]${_C_NC} ${file}" + fi + return 0 +} + +# Print section header +cqa_header() { + local cqa_num="$1" + local title="$2" + local target="${3:-}" + + if [[ "$CQA_FORMAT" == "$_CQA_FMT_CHECKLIST" ]]; then + echo "## CQA #${cqa_num}: ${title}" + if [[ -n "$target" ]]; then + echo "Processing: ${target}" + fi + if [[ "$CQA_FIX_MODE" == true ]]; then + echo -e "${_C_YELLOW}Mode: --fix${_C_NC}" + fi + echo "" + fi + + cqa_set_tool_info "cqa-${cqa_num}" + return 0 +} + +# Print summary +cqa_summary() { + if [[ "$CQA_FORMAT" == "json" ]]; then + _cqa_sarif_emit + return + fi + + echo "" + echo "### Summary" + echo "Files: ${_CQA_TOTAL_FILES} checked, ${_CQA_FILES_WITH_ISSUES} with issues" + + if [[ "$CQA_FIX_MODE" == true ]]; then + echo "Fixed: ${_CQA_TOTAL_FIXED} automatically" + local remaining=$((_CQA_TOTAL_MANUAL + _CQA_TOTAL_DELEGATED)) + if [[ $remaining -gt 0 ]]; then + echo "Remaining: ${remaining} (${_CQA_TOTAL_MANUAL} manual, ${_CQA_TOTAL_DELEGATED} delegated)" + fi + else + local total_issues=$((_CQA_TOTAL_AUTOFIX + _CQA_TOTAL_MANUAL + _CQA_TOTAL_DELEGATED)) + if [[ $total_issues -gt 0 ]]; then + echo "Violations: ${total_issues} total (${_CQA_TOTAL_AUTOFIX} autofixable, ${_CQA_TOTAL_MANUAL} manual, ${_CQA_TOTAL_DELEGATED} delegated)" + if [[ $_CQA_TOTAL_AUTOFIX -gt 0 ]]; then + echo "Run with --fix to auto-resolve ${_CQA_TOTAL_AUTOFIX} issues." + fi + else + echo -e "${_C_GREEN}All checks passed.${_C_NC}" + fi + fi +} + +# Return appropriate exit code +cqa_exit_code() { + local remaining=$((_CQA_TOTAL_MANUAL + _CQA_TOTAL_DELEGATED)) + if [[ "$CQA_FIX_MODE" == true ]]; then + [[ $remaining -gt 0 ]] && echo 1 || echo 0 + else + local total=$((_CQA_TOTAL_AUTOFIX + _CQA_TOTAL_MANUAL + _CQA_TOTAL_DELEGATED)) + [[ $total -gt 0 ]] && echo 1 || echo 0 + fi + return 0 +} + +# ── SARIF output ── + +_cqa_sarif_add() { + local file="$1" + local line="$2" + local level="$3" # error, warning, note + local message="$4" + local kind="${5:-fail}" # autofix, manual, delegated + + local line_num="${line:-1}" + + # Escape JSON strings + message="${message//\\/\\\\}" + message="${message//\"/\\\"}" + file="${file//\\/\\\\}" + file="${file//\"/\\\"}" + + _CQA_SARIF_RESULTS+=("{ + \"ruleId\": \"${_CQA_SARIF_TOOL_NAME}.${kind}\", + \"level\": \"${level}\", + \"message\": { \"text\": \"${message}\" }, + \"locations\": [{ + \"physicalLocation\": { + \"artifactLocation\": { \"uri\": \"${file}\" }, + \"region\": { \"startLine\": ${line_num} } + } + }], + \"properties\": { \"fixability\": \"${kind}\" } + }") + return 0 +} + +_cqa_sarif_emit() { + local results="" + local first=true + for r in "${_CQA_SARIF_RESULTS[@]}"; do + if [[ "$first" == true ]]; then + results="$r" + first=false + else + results="${results},${r}" + fi + done + + cat <<SARIF_EOF +{ + "\$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{ + "tool": { + "driver": { + "name": "${_CQA_SARIF_TOOL_NAME}", + "version": "${_CQA_SARIF_TOOL_VERSION}", + "informationUri": "https://github.com/redhat-developer/red-hat-developers-documentation-rhdh" + } + }, + "results": [${results}] + }] +} +SARIF_EOF + return 0 +} + +# ── Delegation metadata ── +# Scripts declare what they delegate to other scripts +# Format: "check_name:target_cqa_number" +# shellcheck disable=SC2034 # Used by individual CQA scripts +declare -a CQA_DELEGATES_TO=() + +# ── Common file skip helpers ── + +cqa_should_skip_file() { + local file="$1" + local bn + bn=$(basename "$file") + + # Skip non-adoc + [[ "$file" != *.adoc ]] && return 0 + + # Skip attributes.adoc + [[ "$bn" == "attributes.adoc" ]] && return 0 + + return 1 +} + +# ── Multi-title runner ── +# Wraps a script's main logic to run across --all targets + +cqa_run_for_each_title() { + local callback="$1" # function name to call with each target file + + if [[ "$CQA_FORMAT" == "json" ]]; then + # JSON: accumulate all results, emit once at end + for target in "${CQA_TARGET_FILES[@]}"; do + cqa_collect_files "$target" + "$callback" "$target" + done + _cqa_sarif_emit + else + if [[ ${#CQA_TARGET_FILES[@]} -eq 1 ]]; then + cqa_collect_files "${CQA_TARGET_FILES[0]}" + "$callback" "${CQA_TARGET_FILES[0]}" + cqa_summary + else + # Multiple titles: run each, then grand summary + local grand_files=0 grand_issues=0 grand_autofix=0 grand_fixed=0 grand_manual=0 grand_delegated=0 + for target in "${CQA_TARGET_FILES[@]}"; do + echo "" + echo "---" + echo "**Title: $(dirname "$target" | sed 's|titles/||')**" + echo "" + + cqa_reset_counters + cqa_collect_files "$target" + "$callback" "$target" + cqa_summary + + grand_files=$((grand_files + _CQA_TOTAL_FILES)) + grand_issues=$((grand_issues + _CQA_FILES_WITH_ISSUES)) + grand_autofix=$((grand_autofix + _CQA_TOTAL_AUTOFIX)) + grand_fixed=$((grand_fixed + _CQA_TOTAL_FIXED)) + grand_manual=$((grand_manual + _CQA_TOTAL_MANUAL)) + grand_delegated=$((grand_delegated + _CQA_TOTAL_DELEGATED)) + done + + echo "" + echo "---" + echo "## Grand Total (${#CQA_TARGET_FILES[@]} titles)" + echo "Files: ${grand_files} checked, ${grand_issues} with issues" + if [[ "$CQA_FIX_MODE" == true ]]; then + echo "Fixed: ${grand_fixed} automatically" + echo "Remaining: $((grand_manual + grand_delegated)) (${grand_manual} manual, ${grand_delegated} delegated)" + else + local total=$((grand_autofix + grand_manual + grand_delegated)) + echo "Violations: ${total} total (${grand_autofix} autofixable, ${grand_manual} manual, ${grand_delegated} delegated)" + fi + fi + fi + return 0 +} diff --git a/build/scripts/cqa.sh b/build/scripts/cqa.sh new file mode 100755 index 00000000000..96c7b46e31a --- /dev/null +++ b/build/scripts/cqa.sh @@ -0,0 +1,191 @@ +#!/bin/bash +# cqa.sh — Run all CQA checks in optimal workflow order +# +# Usage: ./build/scripts/cqa.sh [--fix] [--all] [--title PATTERN] [--format checklist|json] <file-path> +# +# Runs all 18 CQA scripts in the order defined by cqa-main-workflow.md. +# Passes all arguments through to each script. +# +# With --all (checklist mode): displays a compact summary checklist. +# With --format json: outputs a single merged SARIF JSON document. +# With a single file: shows full per-file output from each script. +# +# Examples: +# ./build/scripts/cqa.sh titles/install-rhdh-ocp/master.adoc +# ./build/scripts/cqa.sh --fix --all +# ./build/scripts/cqa.sh --all +# ./build/scripts/cqa.sh --all --format json + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cqa-lib.sh" +cqa_parse_args "$0" "$@" + +# CQA scripts in optimal workflow order (matches cqa-main-workflow.md) +CQA_SCRIPTS=( + "cqa-00-orphaned-modules.sh" + "cqa-00-directory-structure.sh" + "cqa-03-content-is-modularized.sh" + "cqa-13-information-is-conveyed-using-the-correct-content.sh" + "cqa-10-titles-are-brief-complete-and-descriptive.sh" + "cqa-08-short-description-content.sh" + "cqa-09-short-description-format.sh" + "cqa-11-procedures-prerequisites.sh" + "cqa-02-assembly-structure.sh" + "cqa-05-modular-elements-checklist.sh" + "cqa-04-modules-use-official-templates.sh" + "cqa-06-assemblies-use-the-official-template-assemblies-ar.sh" + "cqa-07-toc-max-3-levels.sh" + "cqa-16-official-product-names-are-used.sh" + "cqa-01-asciidoctor-dita-vale.sh" + "cqa-12-content-is-grammatically-correct-and-follows-rules.sh" + "cqa-17-includes-appropriate-legal-approved-disclaimers-f.sh" + "cqa-14-no-broken-links.sh" + "cqa-15-redirects.sh" +) + +# Build argument list to pass through (reconstruct from parsed values) +pass_args=() +[[ "$CQA_FIX_MODE" == true ]] && pass_args+=("--fix") +[[ "$CQA_ALL_MODE" == true ]] && pass_args+=("--all") +[[ -n "$CQA_TITLE_PATTERN" ]] && pass_args+=("--title" "$CQA_TITLE_PATTERN") +[[ -n "$CQA_TARGET_FILE" ]] && pass_args+=("$CQA_TARGET_FILE") + +total=0 +passed=0 +failed=0 + +# ── JSON/SARIF mode: merge all scripts into one SARIF document ── +if [[ "$CQA_FORMAT" == "json" ]]; then + all_runs="" + + for script in "${CQA_SCRIPTS[@]}"; do + script_path="${CQA_SCRIPT_DIR}/${script}" + [[ -x "$script_path" ]] || continue + + total=$((total + 1)) + + # Run script in JSON mode, capture SARIF output + sarif_output=$("$script_path" "${pass_args[@]}" --format json 2>/dev/null || true) + + # Extract the runs array content (the single run object) + run_object=$(echo "$sarif_output" | python3 -c " +import json, sys +try: + d = json.load(sys.stdin) + runs = d.get('runs', []) + if runs: + print(json.dumps(runs[0])) +except: + pass +" 2>/dev/null || true) + + if [[ -n "$run_object" ]]; then + if [[ -n "$all_runs" ]]; then + all_runs="${all_runs},${run_object}" + else + all_runs="$run_object" + fi + fi + done + + # Emit merged SARIF + cat <<SARIF_EOF +{ + "\$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [${all_runs}] +} +SARIF_EOF + exit 0 +fi + +# ── Summary checklist mode (--all) ── +if [[ "$CQA_ALL_MODE" == true ]]; then + echo "## CQA Summary Checklist" + echo "" + + for script in "${CQA_SCRIPTS[@]}"; do + script_path="${CQA_SCRIPT_DIR}/${script}" + [[ -x "$script_path" ]] || continue + + total=$((total + 1)) + + # Extract CQA number from filename (strip prefix and leading zeros) + cqa_num="${script#cqa-}" + cqa_num="${cqa_num%%-*}" + cqa_num=$((10#$cqa_num)) + + # Run script, capture output + output=$("$script_path" "${pass_args[@]}" 2>&1 || true) + + # Extract the CQA header line for the check name + cqa_name=$(echo "$output" | grep "^## CQA #" | head -1 | sed 's/^## CQA #[0-9]*: //') + + # Collect issue lines (AUTOFIX, MANUAL, FIXED, delegated) — file path is included by cqa-lib.sh + script_issues=$(echo "$output" | grep -E '^- \[ \] \[' | grep -E '\[AUTOFIX\]|\[MANUAL\]|\[-> CQA' || true) + + if [[ -z "$script_issues" ]]; then + echo "- [x] **CQA #${cqa_num}:** ${cqa_name}" + passed=$((passed + 1)) + else + echo "- [ ] **CQA #${cqa_num}:** ${cqa_name}" + failed=$((failed + 1)) + + echo "$script_issues" | sed 's/^- \[ \] //' | while IFS= read -r line; do + echo " - ${line}" + done + fi + done + + echo "" + echo "---" + echo "**Total:** ${total} checks | ${passed} passed | ${failed} with issues" + + if [[ $failed -gt 0 ]]; then + echo "" + echo "To auto-fix what can be auto-fixed, run:" + echo '```' + echo "./build/scripts/cqa.sh --fix --all" + echo '```' + echo "To fix remaining issues, copy-paste this prompt to Claude:" + echo '```' + echo "Run ./build/scripts/cqa.sh --all, then for each failing CQA check, read the matching .claude/skills/cqa-*.md skill file and fix the [MANUAL] issues following the skill instructions." + echo '```' + exit 1 + fi + exit 0 +fi + +# ── Verbose mode (single title): show full output from each script ── +for script in "${CQA_SCRIPTS[@]}"; do + script_path="${CQA_SCRIPT_DIR}/${script}" + if [[ ! -x "$script_path" ]]; then + echo "WARNING: Script not found or not executable: $script_path" >&2 + continue + fi + + total=$((total + 1)) + echo "" + echo "========================================" + + if "$script_path" "${pass_args[@]}"; then + passed=$((passed + 1)) + else + failed=$((failed + 1)) + fi +done + +echo "" +echo "========================================" +echo "## CQA Summary" +echo "Scripts run: $total | Passed: $passed | Failed: $failed" + +if [[ $failed -gt 0 ]]; then + echo "" + echo "To auto-fix what can be auto-fixed, run:" + echo " ./build/scripts/cqa.sh --fix ${pass_args[*]}" + echo "" + echo "To fix remaining issues, copy-paste this prompt to Claude:" + echo " Run ./build/scripts/cqa.sh ${pass_args[*]}, then for each failing CQA check, read the matching .claude/skills/cqa-*.md skill file and fix the [MANUAL] issues following the skill instructions." + exit 1 +fi diff --git a/build/scripts/fix-orphaned-modules.sh b/build/scripts/fix-orphaned-modules.sh deleted file mode 100755 index da3cc4930f6..00000000000 --- a/build/scripts/fix-orphaned-modules.sh +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/bash -# Find and optionally delete orphaned modules (files not included anywhere) -# -# Usage: ./fix-orphaned-modules.sh [--execute] -# --execute: Actually delete orphaned files (default is dry-run mode) -# -# This script finds all .adoc files in artifacts/, assemblies/, and modules/ -# directories that are not referenced by any include:: statement in .adoc files -# -# By default, runs in dry-run mode showing what would be deleted. -# Use --execute flag to actually delete the orphaned files. - -set -e - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -cd "$REPO_ROOT" - -# Parse arguments -EXECUTE=false -if [[ "$1" == "--execute" ]]; then - EXECUTE=true -fi - -# Color codes -RED='\033[0;31m' -YELLOW='\033[1;33m' -GREEN='\033[0;32m' -NC='\033[0m' # No Color - -echo "=== Find Orphaned Modules and Resources ===" -echo "" - -if [[ "$EXECUTE" == "false" ]]; then - echo -e "${YELLOW}DRY-RUN MODE${NC} - No files will be deleted" - echo "Run with --execute flag to actually delete orphaned files" - echo "" -fi - -# Find all .adoc files that could be included -echo "Collecting files to check..." -FILES_TO_CHECK=() - -# Find all .adoc files in target directories -while IFS= read -r file; do - FILES_TO_CHECK+=("$file") -done < <(find artifacts assemblies modules -name "*.adoc" -type f 2>/dev/null | sort) - -echo "Found ${#FILES_TO_CHECK[@]} files to check" -echo "" - -# Function to check if a file is included anywhere -is_file_included() { - local target_file="$1" - local basename - basename=$(basename "$target_file") - - # Search for include:: statements that reference this file - # We need to check: - # 1. Direct filename match - # 2. Includes using attributes (like proc-something-{platform-id}.adoc or {docdir}/file.adoc) - - # First, try direct basename match (handles most cases without attributes) - # Use cut to extract only the include path, not the filename prefix from grep output - if grep -r "^include::" . --include="*.adoc" 2>/dev/null | cut -d: -f2- | grep -q "$basename"; then - return 0 # File is included - fi - - # Handle attribute substitution dynamically - # Find all include:: statements that contain {...} attributes - # Convert each to a regex pattern and check if our file matches any of them - while IFS= read -r include_line; do - # Extract the path from include::path[...] - local include_path - # Remove "include::" prefix and everything from "[" onwards - # shellcheck disable=SC2001 # sed is appropriate for regex capture groups - include_path=$(echo "$include_line" | sed 's/^include::\([^[]*\).*/\1/') - - # Get just the basename from the include path - local include_basename - include_basename=$(basename "$include_path") - - # Convert include pattern to regex by replacing {attribute} with wildcard - local regex_pattern - # Escape dots for regex matching (use [.] instead of \. for bash compatibility) - # shellcheck disable=SC2001 # sed is appropriate for complex global replacements - regex_pattern=$(echo "$include_basename" | sed 's/\./[.]/g') - # shellcheck disable=SC2001 # sed is appropriate for complex pattern replacement - regex_pattern=$(echo "$regex_pattern" | sed 's/{[^}]*}/.*/g') # Replace {...} with .* - - # Check if our file matches this pattern - if [[ "$basename" =~ ^${regex_pattern}$ ]]; then - return 0 # File matches an include with attribute substitution - fi - done < <(grep -r "^include::" . --include="*.adoc" 2>/dev/null | cut -d: -f2- | grep '{') - - return 1 # File is not included -} - -# Find orphaned files -echo "Checking for orphaned files..." -ORPHANED_FILES=() -TOTAL=0 -CHECKED=0 - -for file in "${FILES_TO_CHECK[@]}"; do - TOTAL=$((TOTAL + 1)) - - # Skip if file doesn't exist - if [[ ! -f "$file" ]]; then - continue - fi - - # Skip template files (*.template.adoc) - these are source files for generation, not meant to be included - if [[ "$file" == *.template.adoc ]]; then - continue - fi - - CHECKED=$((CHECKED + 1)) - - # Show progress every 50 files - if [[ $((CHECKED % 50)) -eq 0 ]]; then - echo " Checked $CHECKED/$TOTAL files..." - fi - - # Check if file is included anywhere - if ! is_file_included "$file"; then - ORPHANED_FILES+=("$file") - fi -done - -echo " Checked $CHECKED/$TOTAL files" -echo "" - -# ── Check for orphaned images ── -echo "Checking for orphaned images..." - -ORPHANED_IMAGES=() -IMG_CHECKED=0 - -while IFS= read -r img_file; do - [[ -f "$img_file" ]] || continue - IMG_CHECKED=$((IMG_CHECKED + 1)) - - img_basename=$(basename "$img_file") - - # Check if any .adoc file references this image (by basename, handles subdir moves) - if grep -rq "$img_basename" --include="*.adoc" titles/ modules/ assemblies/ 2>/dev/null; then - continue - fi - - ORPHANED_IMAGES+=("$img_file") -done < <(find images/ -type f 2>/dev/null | sort) - -echo " Checked $IMG_CHECKED image files" -echo "" - -# Report findings -if [[ ${#ORPHANED_FILES[@]} -eq 0 && ${#ORPHANED_IMAGES[@]} -eq 0 ]]; then - echo -e "${GREEN}✓ No orphaned files found${NC}" - exit 0 -fi - -if [[ ${#ORPHANED_FILES[@]} -gt 0 ]]; then - echo -e "${YELLOW}Found ${#ORPHANED_FILES[@]} orphaned .adoc file(s):${NC}" - echo "" - for file in "${ORPHANED_FILES[@]}"; do - echo " $file" - done - echo "" -fi - -if [[ ${#ORPHANED_IMAGES[@]} -gt 0 ]]; then - echo -e "${YELLOW}Found ${#ORPHANED_IMAGES[@]} orphaned image(s):${NC}" - echo "" - for file in "${ORPHANED_IMAGES[@]}"; do - echo " $file" - done -fi - -echo "" - -# Combine all orphaned files -ALL_ORPHANED=("${ORPHANED_FILES[@]}" "${ORPHANED_IMAGES[@]}") - -echo "" - -# Delete files if in execute mode -if [[ "$EXECUTE" == "true" ]]; then - echo -e "${RED}DELETING ORPHANED FILES...${NC}" - echo "" - - DELETED=0 - for file in "${ALL_ORPHANED[@]}"; do - # Check if file is tracked by git - if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then - echo " git rm $file" - git rm "$file" 2>/dev/null || rm -f "$file" - else - echo " rm $file" - rm -f "$file" - fi - DELETED=$((DELETED + 1)) - done - - # Clean up empty image directories - find images/ -mindepth 1 -type d -empty -delete 2>/dev/null || true - - echo "" - echo -e "${GREEN}✓ Deleted $DELETED orphaned file(s)${NC}" - echo "" - echo "Next steps:" - echo " 1. Review the changes: git status" - echo " 2. If correct, commit: git add -A && git commit -m 'Remove orphaned modules and images'" - echo " 3. If incorrect, revert: git checkout ." -else - echo -e "${YELLOW}DRY-RUN MODE${NC} - No files were deleted" - echo "" - echo "To delete these files, run:" - echo " ./build/scripts/fix-orphaned-modules.sh --execute" -fi - -echo "" -echo "=== Summary ===" -echo "Total .adoc files checked: $CHECKED" -echo "Total image files checked: $IMG_CHECKED" -echo "Orphaned .adoc files: ${#ORPHANED_FILES[@]}" -echo "Orphaned images: ${#ORPHANED_IMAGES[@]}" -if [[ "$EXECUTE" == "true" ]]; then - echo -e "${GREEN}Status: Files deleted${NC}" -else - echo -e "${YELLOW}Status: Dry-run (no changes made)${NC}" -fi diff --git a/build/scripts/list-all-included-files-starting-from.sh b/build/scripts/list-all-included-files-starting-from.sh deleted file mode 100755 index a0520060c8a..00000000000 --- a/build/scripts/list-all-included-files-starting-from.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -# List all files recursively included from a starting file -# Usage: list-all-included-files-starting-from.sh <file> -# Output: space-separated list of files on a single line - -set -e - -if [[ $# -ne 1 ]]; then - echo "Usage: $0 <file>" >&2 - exit 1 -fi - -START_FILE="$1" - -if [[ ! -f "$START_FILE" ]]; then - echo "Error: File not found: $START_FILE" >&2 - exit 1 -fi - -# Temporary file to collect results -TEMP_FILE=$(mktemp) -trap 'rm -f "$TEMP_FILE"' EXIT - -# Function to recursively find includes -find_includes() { - local file=$1 - local dir - dir=$(dirname "$file") - - # Print the file - echo "$file" >> "$TEMP_FILE" - - # Find and process includes - while IFS= read -r line; do - # Extract include path from include::path[options] - local include_path - # shellcheck disable=SC2001 # sed is appropriate for regex capture - include_path=$(echo "$line" | sed 's/^include::\([^[]*\).*/\1/') - - # Resolve relative paths - if [[ "$include_path" == ../* ]]; then - include_path="$dir/$include_path" - elif [[ "$include_path" != /* ]]; then - include_path="$dir/$include_path" - fi - - # Normalize path - include_path=$(realpath -m "$include_path" 2>/dev/null || echo "$include_path") - - if [[ -f "$include_path" ]] && ! grep -qFx "$include_path" "$TEMP_FILE" 2>/dev/null; then - find_includes "$include_path" - fi - done < <(grep "^include::" "$file" 2>/dev/null || true) - - return 0 -} - -# Get all files -find_includes "$START_FILE" - -# Output as single line, space-separated, sorted and unique -sort -u "$TEMP_FILE" | tr '\n' ' ' -echo # Add newline at end diff --git a/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-helm-chart.adoc b/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-helm-chart.adoc index dee3c6c614b..a02aaeb0ef3 100644 --- a/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-helm-chart.adoc +++ b/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-helm-chart.adoc @@ -13,8 +13,8 @@ Creating an `ObjectBucketClaim` custom resource (CR) automatically generates bot * `BUCKET_PORT` * `BUCKET_REGION` * `BUCKET_SUBREGION` -* `AWS_ACCESS_KEY_ID` -* `AWS_SECRET_ACCESS_KEY` +* `{aws-short}_ACCESS_KEY_ID` +* `{aws-short}_SECRET_ACCESS_KEY` These variables are then used in the TechDocs plugin configuration. diff --git a/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-operator.adoc b/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-operator.adoc index b487b2bef5c..5b8061a1ef1 100644 --- a/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-operator.adoc +++ b/modules/configure_techdocs-for-rhdh/proc-make-object-storage-accessible-to-containers-by-using-the-operator.adoc @@ -13,8 +13,8 @@ Creating an `ObjectBucketClaim` custom resource (CR) automatically generates bot * `BUCKET_PORT` * `BUCKET_REGION` * `BUCKET_SUBREGION` -* `AWS_ACCESS_KEY_ID` -* `AWS_SECRET_ACCESS_KEY` +* `{aws-short}_ACCESS_KEY_ID` +* `{aws-short}_SECRET_ACCESS_KEY` These variables are then used in the TechDocs plugin configuration. @@ -30,7 +30,7 @@ These variables are then used in the TechDocs plugin configuration. [source,yaml] ---- apiVersion: rhdh.redhat.com/v1alpha5 -kind: Backstage +kind: {backstage} metadata: name: <name> spec: diff --git a/modules/develop_streamline-software-development-and-management-in-rhdh/proc-manage-the-added-git-repositories.adoc b/modules/develop_streamline-software-development-and-management-in-rhdh/proc-manage-the-added-git-repositories.adoc index b9c93ef2764..ad4c3243e1e 100644 --- a/modules/develop_streamline-software-development-and-management-in-rhdh/proc-manage-the-added-git-repositories.adoc +++ b/modules/develop_streamline-software-development-and-management-in-rhdh/proc-manage-the-added-git-repositories.adoc @@ -20,7 +20,7 @@ Waiting for approval:: There is an open pull request or merge request adding a ` You can: * Click *pencil* icon on the right to see details about the pull request or merge request. You can use the detailed view to edit the request content right from {product-short}. * Delete the Import job, this action closes the import pull request or merge request as well. -* To transition the Import job to the _Added_ state, merge the import pull request or merge request from the Git repository. +* To move the Import job to the _Added_ state, merge the import pull request or merge request from the Git repository. Empty:: {product-short} is unable to determine the import job status because the Git repository is imported from other sources but does not have a `catalog-info.yaml` file and lacks any import pull or merge request adding it. + diff --git a/modules/discover_about-rhdh/con-integrations-in-rhdh.adoc b/modules/discover_about-rhdh/con-integrations-in-rhdh.adoc index 2bea986a4e9..7a8b79dd6c1 100644 --- a/modules/discover_about-rhdh/con-integrations-in-rhdh.adoc +++ b/modules/discover_about-rhdh/con-integrations-in-rhdh.adoc @@ -23,7 +23,7 @@ While {product} focuses on the inner loop (code, build, and test), {rhads-very-s * Vulnerability detection * Deployment -{rhads-very-short} includes tools like {rhtas-brand-name} ({rhtas-very-short}) for code integrity, {rhtpa-brand-name} ({rhtpa-very-short}) for automated Software build of Materials (SBOM) creation, and {rhacs-brand-name} ({rhacs-very-short}) for vulnerability scanning. +{rhads-very-short} includes tools such as {rhtas-brand-name} ({rhtas-very-short}) for code integrity, {rhtpa-brand-name} ({rhtpa-very-short}) for automated Software build of Materials (SBOM) creation, and {rhacs-brand-name} ({rhacs-very-short}) for vulnerability scanning. == Extending {backstage} with {product} {product} which is a fully supported, enterprise-grade productized version of upstream {backstage} extends the upstream project by adding: diff --git a/modules/discover_about-rhdh/con-system-architecture-for-deployment-planning.adoc b/modules/discover_about-rhdh/con-system-architecture-for-deployment-planning.adoc index 36ca5d95661..e5a198fc864 100644 --- a/modules/discover_about-rhdh/con-system-architecture-for-deployment-planning.adoc +++ b/modules/discover_about-rhdh/con-system-architecture-for-deployment-planning.adoc @@ -34,7 +34,7 @@ The stateless design allows you to scale the backend horizontally by running mul {product-very-short} requires PostgreSQL for persistence. For production environments, use a logical cache to improve performance. PostgreSQL database:: -Stores indexed Software Catalog entities (synchronized from external systems like Git repositories and CI/CD platforms), profiles, authentication data, and backend state. You must configure PostgreSQL with high availability (HA) for production deployments. +Stores indexed Software Catalog entities (synchronized from external systems such as Git repositories and CI/CD platforms), profiles, authentication data, and backend state. You must configure PostgreSQL with high availability (HA) for production deployments. Redis Cache (Optional):: Configure Redis as a shared logical cache across backend instances to improve performance for frequently accessed data, such as rendered TechDocs and catalog entities. diff --git a/modules/install_installing-rhdh-on-ocp/con-rhdh-installation-methods-on-ocp.adoc b/modules/install_installing-rhdh-on-ocp/con-rhdh-installation-methods-on-ocp.adoc new file mode 100644 index 00000000000..2f9a397e669 --- /dev/null +++ b/modules/install_installing-rhdh-on-ocp/con-rhdh-installation-methods-on-ocp.adoc @@ -0,0 +1,30 @@ +:_mod-docs-content-type: CONCEPT + +[id="rhdh-installation-methods-on-ocp_{context}"] += {product} installation methods on {ocp-short} + +[role="_abstract"] +You can install {product} on {ocp-short} by using one of the following installers: + +The {product} Operator:: ++ +-- +* Ready for immediate use in {ocp-short} after an administrator installs it with OperatorHub +* Uses Operator Lifecycle Management (OLM) to manage automated subscription updates on {ocp-short} +* Requires preinstallation of Operator Lifecycle Management (OLM) to manage automated subscription updates on Kubernetes +-- + +The {product} Helm chart:: ++ +-- +* Ready for immediate use in both {ocp-short} and Kubernetes +* Requires manual installation and management +-- + +[IMPORTANT] +==== +You must set the `baseUrl` in `{my-app-config-file}` to match the external URL of your {product-short} instance (for example, `pass:[https://rhdh.example.com]`). +This value is required for the {product} to function correctly. If it is not set, front-end and back-end services cannot communicate properly, and features might not work as expected. +==== + +Use the installation method that best meets your needs and preferences. diff --git a/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-mcp-clients-to-access-the-rhdh-server.adoc b/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-mcp-clients-to-access-the-rhdh-server.adoc index 10d14621fb2..ab157702450 100644 --- a/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-mcp-clients-to-access-the-rhdh-server.adoc +++ b/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-mcp-clients-to-access-the-rhdh-server.adoc @@ -112,7 +112,7 @@ curl -X POST \ -d `{"query": "Can you give me all catalog templates of type 'service', "model": "gpt-4o-mini", "provider": "openai"}` \ _<url>_/v1/streaming_query ---- - ++ where: `<url>`:: Enter the {lcs-short} endpoint. You can use localhost(pass:c,a,q:[`<{product-very-short}_servicename>.{my-product-namespace}.svc.cluster.local:8080`]) or the service name for this field if you are inside the {backstage} container. diff --git a/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-rhdh-data-using-the-software-catalog-mcp-tools.adoc b/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-rhdh-data-using-the-software-catalog-mcp-tools.adoc index 6a55226da74..d5aeea479b0 100644 --- a/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-rhdh-data-using-the-software-catalog-mcp-tools.adoc +++ b/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-rhdh-data-using-the-software-catalog-mcp-tools.adoc @@ -8,7 +8,7 @@ Manage and retrieve Software Catalog entities including Components, Systems, Res Use Software Catalog MCP tools to manage and retrieve {product-very-short} entities such as *Components*, *Systems*, *Resources*, *APIs*, *Locations*, *Users*, and *Groups*. -.Software Catalog tool reference +Software Catalog tool reference: The `software-catalog-mcp-tool` plugin provides tools to interact with the software catalog. By default, these tools return data in a JSON array format. diff --git a/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-fetching-entities-using-fetch-catalog-entities.adoc b/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-fetching-entities-using-fetch-catalog-entities.adoc index d947e2b3e2f..44fab404965 100644 --- a/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-fetching-entities-using-fetch-catalog-entities.adoc +++ b/modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-fetching-entities-using-fetch-catalog-entities.adoc @@ -8,7 +8,7 @@ List {product-short} entities including components, APIs, and resources using th The `fetch-catalog-entities` tool lists {product-very-short} entities, including components, APIs, and resources. -.Common query examples +Common query examples: * "Fetch all ai-model resources in the {backstage} catalog" * "Fetch the API definition for the beneficiary-management-api API" * "Construct a curl command based on the API definition for the “insert beneficiary” endpoint in the beneficiary-management-api" diff --git a/modules/observability_adoption-insights-in-rhdh/con-adoption-insights-overview.adoc b/modules/observability_adoption-insights-in-rhdh/con-adoption-insights-overview.adoc new file mode 100644 index 00000000000..c9d27a1038a --- /dev/null +++ b/modules/observability_adoption-insights-in-rhdh/con-adoption-insights-overview.adoc @@ -0,0 +1,23 @@ +:_mod-docs-content-type: CONCEPT + +[id="adoption-insights-overview_{context}"] += Adoption Insights overview + +[role="_abstract"] +The {product} instance includes the Adoption Insights plugin preinstalled and enabled by default. + +As organizations generate an increasing number of data events, there is a growing need for detailed insights into the adoption and engagement metrics of the internal developer portal. These insights help platform engineers make data-driven decisions to improve performance and usability, and convert them into actionable insights. + +You can use Adoption Insights in {product} to visualize key metrics and trends to get information about the usage of {product-short} in your organization. The information provided by Adoption Insights in {product-short} helps you pinpoint areas of improvement, highlights popular features, and evaluates progress toward adoption goals. You can also monitor user growth against licensed users and identify trends over time. + +The Adoption Insights dashboard in {product-short} includes the following cards: + +* *Active users* +* *Total number of users* +* *Top catalog entities* +* *Top 3 templates* +* *Top 3 techdocs* +* *Top 3 plugins* +* *Portal searches* + +image::observability_adoption-insights-in-rhdh/adoption-insights.jpg["Adoption Insights dashboard in {product-short}"] diff --git a/modules/observability_audit-logs-in-rhdh/con-audit-logs-overview.adoc b/modules/observability_audit-logs-in-rhdh/con-audit-logs-overview.adoc new file mode 100644 index 00000000000..a6ae551e561 --- /dev/null +++ b/modules/observability_audit-logs-in-rhdh/con-audit-logs-overview.adoc @@ -0,0 +1,34 @@ +:_mod-docs-content-type: CONCEPT + +[id="audit-logs-overview_{context}"] += Audit logs overview + +[role="_abstract"] +Audit logs are a chronological set of records documenting the user activities, system events, and data changes that affect your {product} users, administrators, or components. + +Administrators can view {product-short} audit logs in the {ocp-short} web console to monitor scaffolder events, changes to the RBAC system, and changes to the Catalog database. +Audit logs include the following information: + +* Name of the audited event +* Actor that triggered the audited event, for example, terminal, port, IP address, or hostname +* Event metadata, for example, date, time +* Event status, for example, `success`, `failure` +* Severity levels, for example, `info`, `debug`, `warn`, `error` + +You can use the information in the audit log to achieve the following goals: + +Enhance security:: +Trace activities, including those initiated by automated systems and software templates, back to their source. +Know when software templates are executed, and the details of application and component installations, updates, configuration changes, and removals. + +Automate compliance:: +Use streamlined processes to view log data for specified points in time for auditing purposes or continuous compliance maintenance. + +Debug issues:: +Use access records and activity details to fix issues with software templates or plugins. + +[NOTE] +==== +Audit logs are not forwarded to the internal log store by default because the internal log store does not offer secure storage. +You are responsible for ensuring that the system to which you forward audit logs is compliant with your organizational and governmental regulations, and is properly secured. +==== diff --git a/modules/shared/con-determine-rhdh-version.adoc b/modules/shared/con-determine-rhdh-version.adoc index 3a4d1c5af48..59f50442995 100644 --- a/modules/shared/con-determine-rhdh-version.adoc +++ b/modules/shared/con-determine-rhdh-version.adoc @@ -2,6 +2,7 @@ [id="determine-rhdh-version_{context}"] = Determine {product-very-short} version + To ensure your plugin uses dependencies compatible with the {product-very-short} instance that it will run on, check the target {product-very-short} version and identify the compatible {backstage} version. .{product-very-short} compatibility matrix diff --git a/modules/shared/con-dynamic-plugins.adoc b/modules/shared/con-dynamic-plugins.adoc index d2f228920fb..a4c1c4e38e8 100644 --- a/modules/shared/con-dynamic-plugins.adoc +++ b/modules/shared/con-dynamic-plugins.adoc @@ -2,6 +2,7 @@ [id="dynamic-plugins_{context}"] = Dynamic Plugins + {product} implements a dynamic plugin system. You can install, configure, and load plugins at runtime without changing or rebuilding the application. You only need a restart. You can load these plugins from NPM, tarballs, or OCI compliant container images. With dynamic plugins, instead of modifying the {backstage} application itself, you create a `dynamic-plugins.yaml` file to specify the plugins that {product} will install and enable at startup. For example, the following configuration loads a plugin named `plugin-name`, which is stored in a `Quay.io` container image at `quay.io/account-name/image-name`: diff --git a/modules/shared/con-prepare-your-development-environment.adoc b/modules/shared/con-prepare-your-development-environment.adoc index dfda6c4e480..6efe66e7e49 100644 --- a/modules/shared/con-prepare-your-development-environment.adoc +++ b/modules/shared/con-prepare-your-development-environment.adoc @@ -6,7 +6,7 @@ //[id="concept-technical-prerequisites-and-development-toolchain"] = Technical prerequisites and development toolchain Before creating or converting plugins for {product} ({product-very-short}), you must establish a specific local development toolchain. This toolchain allows you to write standard {backstage} code, convert it into a dynamic format, and package it for deployment without rebuilding the core {product-very-short} platform. -.Required skills and languages +Required skills and languages: To develop dynamic plugins, you must have experience with the following: JavaScript and TypeScript:: Used for {backstage} frontend and backend development. diff --git a/modules/shared/proc-add-a-dynamic-plugin-to-rhdh.adoc b/modules/shared/proc-add-a-dynamic-plugin-to-rhdh.adoc index 4c68fa430ab..189fe8fd8b1 100644 --- a/modules/shared/proc-add-a-dynamic-plugin-to-rhdh.adoc +++ b/modules/shared/proc-add-a-dynamic-plugin-to-rhdh.adoc @@ -60,7 +60,7 @@ plugins: if: allOf: - isKind: component - ++ # Option 2: Load from local directory (for local RHDH testing) # - package: ./local-plugins/simple-example # disabled: false diff --git a/modules/shared/proc-add-video-content-to-enhance-techdocs.adoc b/modules/shared/proc-add-video-content-to-enhance-techdocs.adoc index 24fe6b11cd5..ef7a662c386 100644 --- a/modules/shared/proc-add-video-content-to-enhance-techdocs.adoc +++ b/modules/shared/proc-add-video-content-to-enhance-techdocs.adoc @@ -8,7 +8,7 @@ You can use `<iframe>` elements to add video content to enhance your experience .Prerequisites -* An administrator has configured your AWS S3 bucket to store TechDocs sites. +* An administrator has configured your {aws-short} S3 bucket to store TechDocs sites. * An administrator has configured the appropriate `techdocs.sanitizer.allowedIframeHosts` and `backend.csp` settings in your `app-config.yaml` file. .Procedure @@ -57,7 +57,7 @@ techdocs: publisher: type: awsS3 awsS3: - bucketName: ${AWS_S3_BUCKET_NAME} - accountId: ${AWS_ACCOUNT_ID} - region: ${AWS_REGION} + bucketName: ${aws-short}_S3_BUCKET_NAME} + accountId: ${aws-short}_ACCOUNT_ID} + region: ${aws-short}_REGION} ---- diff --git a/modules/shared/proc-configure-frontend-plugin-wiring.adoc b/modules/shared/proc-configure-frontend-plugin-wiring.adoc index ecc345e5042..f5386981fa3 100644 --- a/modules/shared/proc-configure-frontend-plugin-wiring.adoc +++ b/modules/shared/proc-configure-frontend-plugin-wiring.adoc @@ -2,6 +2,7 @@ [id="configure-frontend-plugin-wiring_{context}"] = Configure frontend plugin wiring + Front-end plugin wiring integrates dynamic front-end plugin components, such as new pages, UI extensions, icons, and APIs, into {product}. Because the dynamic plugins load at runtime, the core application must discover and connect the exported assets of the plugin to the appropriate user interface systems and locations. diff --git a/modules/shared/proc-create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc b/modules/shared/proc-create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc index e7e53c810c3..e525ac390c8 100644 --- a/modules/shared/proc-create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc +++ b/modules/shared/proc-create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog"] +[id="create-a-custom-transformer-to-provision-users-from-rhbk-to-the-software-catalog_{context}"] = Create a custom transformer to provision users from {rhbk-brand-name} ({rhbk}) to the software catalog [role="_abstract"] @@ -8,7 +8,7 @@ Customize how {product} provisions users and groups to {product} software catalo by creating a backend module that uses the `keycloakTransformerExtensionPoint` to offer custom user and group transformers for the Keycloak backend. .Prerequisites -* You have xref:enable-user-authentication-with-rhbk-with-optional-steps[enabled provisioning users from {rhbk-brand-name} ({rhbk}) to the software catalog]. +* You have xref:enable-user-authentication-with-rhbk-with-optional-steps_{context}[enabled provisioning users from {rhbk-brand-name} ({rhbk}) to the software catalog]. .Procedure . Create a new backend module with the `yarn new` command. diff --git a/modules/shared/proc-create-a-new-plugin.adoc b/modules/shared/proc-create-a-new-plugin.adoc index 05a0b1beda4..e1f0cb965c1 100644 --- a/modules/shared/proc-create-a-new-plugin.adoc +++ b/modules/shared/proc-create-a-new-plugin.adoc @@ -2,6 +2,7 @@ [id="create-a-new-plugin_{context}"] = Create a new plugin + .Prerequisites * You have created a {backstage} application. @@ -13,7 +14,7 @@ $ cd rhdh-plugin-dev $ yarn new -- -.Output from the `yarn new` command +Output from the `yarn new` command: + [source,terminal] -- diff --git a/modules/shared/proc-define-authorizations-in-external-files-by-using-helm.adoc b/modules/shared/proc-define-authorizations-in-external-files-by-using-helm.adoc index 4ab18e7639b..351ac0e42c5 100644 --- a/modules/shared/proc-define-authorizations-in-external-files-by-using-helm.adoc +++ b/modules/shared/proc-define-authorizations-in-external-files-by-using-helm.adoc @@ -11,7 +11,7 @@ You need to prepare your files, upload them to your {ocp-short} project, and configure {product-short} to use the external files. .Prerequisites -* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_{context}[You enabled the RBAC feature]. +* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_authorization-in-rhdh[You enabled the RBAC feature]. .Procedure . Define your policies in a `rbac-policies.csv` CSV file by using the following format: @@ -29,7 +29,7 @@ Role entity reference, such as: `role:default/guest`. _<permission>_:: Permission, such as: `bulk.import`, `catalog.entity.read`, or `catalog.entity.refresh`, or permission resource type, such as: `bulk-import` or `catalog-entity`. + -See: xref:permission-policies-reference_{context}[Permission policies reference]. +See: xref:permission-policies-reference_authorization-in-rhdh[Permission policies reference]. _<action>_:: Action type, such as: `use`, `read`, `create`, `update`, `delete`. @@ -69,7 +69,7 @@ permissionMapping: conditions: _<conditions>_ ---- + -See: xref:conditional-policies-reference_{context}[Conditional policies reference]. +See: xref:conditional-policies-reference_authorization-in-rhdh[Conditional policies reference]. . Upload your `rbac-policies.csv` and `rbac-conditional-policies.yaml` files to a `rbac-policies` config map in your {ocp-short} project containing {product-short}. + diff --git a/modules/shared/proc-define-authorizations-in-external-files-by-using-the-operator.adoc b/modules/shared/proc-define-authorizations-in-external-files-by-using-the-operator.adoc index 17288e3d83e..39d4d86c952 100644 --- a/modules/shared/proc-define-authorizations-in-external-files-by-using-the-operator.adoc +++ b/modules/shared/proc-define-authorizations-in-external-files-by-using-the-operator.adoc @@ -11,7 +11,7 @@ You need to prepare your files, upload them to your {ocp-short} project, and configure {product-short} to use the external files. .Prerequisites -* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_{context}[You enabled the RBAC feature]. +* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_authorization-in-rhdh[You enabled the RBAC feature]. .Procedure . Define your policies in a `rbac-policies.csv` CSV file by using the following format: @@ -29,7 +29,7 @@ Role entity reference, such as: `role:default/guest`. _<permission>_:: Permission, such as: `bulk.import`, `catalog.entity.read`, or `catalog.entity.refresh`, or permission resource type, such as: `bulk-import` or `catalog-entity`. + -See: xref:permission-policies-reference_{context}[Permission policies reference]. +See: xref:permission-policies-reference_authorization-in-rhdh[Permission policies reference]. _<action>_:: Action type, such as: `use`, `read`, `create`, `update`, `delete`. @@ -68,7 +68,7 @@ permissionMapping: conditions: _<conditions>_ ---- + -See: xref:conditional-policies-reference_{context}[Conditional policies reference]. +See: xref:conditional-policies-reference_authorization-in-rhdh[Conditional policies reference]. . Upload your `rbac-policies.csv` and `rbac-conditional-policies.yaml` files to a `rbac-policies` config map in your {ocp-short} project containing {product-short}. + diff --git a/modules/shared/proc-determine-permission-policy-and-role-configuration-source.adoc b/modules/shared/proc-determine-permission-policy-and-role-configuration-source.adoc index 7eecf11f041..e7f30f47956 100644 --- a/modules/shared/proc-determine-permission-policy-and-role-configuration-source.adoc +++ b/modules/shared/proc-determine-permission-policy-and-role-configuration-source.adoc @@ -13,7 +13,7 @@ You can only use this source to change the resource. The available sources are: Configuration file:: -Configure roles and policies in the {product-short} `{my-app-config-file}` configuration file, for instance to xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_{context}[declare your policy administrators]. +Configure roles and policies in the {product-short} `{my-app-config-file}` configuration file, for instance to xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_authorization-in-rhdh[declare your policy administrators]. + The Configuration file pertains to the default `role:default/rbac_admin` role provided by the RBAC plugin. The default role has limited permissions to create, read, update, delete permission policies or roles, and to read catalog entities. diff --git a/modules/shared/proc-enable-and-give-access-to-the-role-based-access-control-rbac-feature.adoc b/modules/shared/proc-enable-and-give-access-to-the-role-based-access-control-rbac-feature.adoc index da94a624f58..dcf9550edb9 100644 --- a/modules/shared/proc-enable-and-give-access-to-the-role-based-access-control-rbac-feature.adoc +++ b/modules/shared/proc-enable-and-give-access-to-the-role-based-access-control-rbac-feature.adoc @@ -68,7 +68,7 @@ permission: - permission ---- -.. To specify the plugins with permissions by using the RBAC REST API permissions endpoint, see the xref:rbac-rest-api-permission-endpoints_{context}[RBAC REST API permissions endpoint]. +.. To specify the plugins with permissions by using the RBAC REST API permissions endpoint, see the xref:supported-rbac-rest-api-endpoints_manage-authorizations-by-using-the-rest-api[RBAC REST API permissions endpoint]. .Verification . Sign out from the existing {product} session and log in again using the declared policy administrator account. diff --git a/modules/shared/proc-enable-auto-logout-for-inactive-users.adoc b/modules/shared/proc-enable-auto-logout-for-inactive-users.adoc index 8291492e99b..da2af31b906 100644 --- a/modules/shared/proc-enable-auto-logout-for-inactive-users.adoc +++ b/modules/shared/proc-enable-auto-logout-for-inactive-users.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="enable-auto-logout-for-inactive-users"] +[id="enable-auto-logout-for-inactive-users_{context}"] = Enable auto-logout for inactive users [role="_abstract"] diff --git a/modules/shared/proc-enable-quicklinks-and-starred-items-after-an-upgrade.adoc b/modules/shared/proc-enable-quicklinks-and-starred-items-after-an-upgrade.adoc index 507a973ff8f..7a1053ffeb7 100644 --- a/modules/shared/proc-enable-quicklinks-and-starred-items-after-an-upgrade.adoc +++ b/modules/shared/proc-enable-quicklinks-and-starred-items-after-an-upgrade.adoc @@ -8,8 +8,8 @@ If you upgrade from {product} `1.6` or earlier, {product} does not automatically .Prerequisites -. You have access to your {product} configuration files. -. You have administrative permissions to modify ConfigMaps (if using the Operator). +* You have access to your {product} configuration files. +* You have administrative permissions to modify ConfigMaps (if using the Operator). .Procedure diff --git a/modules/shared/proc-enable-service-to-service-authentication-by-using-a-static-token.adoc b/modules/shared/proc-enable-service-to-service-authentication-by-using-a-static-token.adoc index 32b87ff82a3..17d15d52f52 100644 --- a/modules/shared/proc-enable-service-to-service-authentication-by-using-a-static-token.adoc +++ b/modules/shared/proc-enable-service-to-service-authentication-by-using-a-static-token.adoc @@ -1,18 +1,10 @@ :_mod-docs-content-type: PROCEDURE -[id="enable-service-to-service-authentication-by-using-a-static-token"] +[id="enable-service-to-service-authentication-by-using-a-static-token_{context}"] = Enable service-to-service authentication by using a static token [role="_abstract"] -You can use a static token to enable service-to-service authentication. -This method is simpler to set up compared to using JSON Web Key Sets (JWKS) tokens, but it requires careful management of the static token to ensure security. -While it might not be suitable for all scenarios, particularly for compliance needs, it offers a practical solution for many development and testing use cases. - -The key to successful implementation lies in: - -* Proper token generation and management. -* Careful access control and restriction. -* Regular monitoring and auditing. +You can use a static token to enable service-to-service authentication. This method is simpler to set up than JWKS tokens but requires careful token management to ensure security. * Following security best practices. Some security best practices when using static tokens include: diff --git a/modules/shared/proc-enable-sidebar-menu-items-localization-in-rhdh.adoc b/modules/shared/proc-enable-sidebar-menu-items-localization-in-rhdh.adoc index a1512f5dfe9..212cab9c825 100644 --- a/modules/shared/proc-enable-sidebar-menu-items-localization-in-rhdh.adoc +++ b/modules/shared/proc-enable-sidebar-menu-items-localization-in-rhdh.adoc @@ -14,7 +14,7 @@ If a translation key is present but the corresponding localized string is missin .Prerequisites * You have enabled localization in your {product-very-short} application. -.Procedure +.Procedure . For sidebar menu items in your configuration file, you must define both the original text and the new localization keys. For example, in the `dynamicPlugins.frontend.default.main-menu-items.menuItems.default.favorites` section of your `{my-app-config-file}` file, add the `titleKey`, as follows: + diff --git a/modules/shared/proc-enable-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc b/modules/shared/proc-enable-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc index 2e0bd8a2564..ab35b6eb3bc 100644 --- a/modules/shared/proc-enable-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc +++ b/modules/shared/proc-enable-user-authentication-with-github-as-an-auxiliary-authentication-provider.adoc @@ -1,12 +1,10 @@ :_mod-docs-content-type: PROCEDURE -[id="enable-user-authentication-with-github-as-an-auxiliary-authentication-provider"] +[id="enable-user-authentication-with-github-as-an-auxiliary-authentication-provider_{context}"] = Enable user authentication with GitHub as an auxiliary authentication provider [role="_abstract"] -If your primary authentication provider is not GitHub, users might lack the permissions needed for templates or plugins that require GitHub access. The recommended solution is to configure GitHub as an auxiliary authentication provider. This approach uses the primary provider for user identity management and the auxiliary provider to grant the necessary GitHub permissions, without re-resolving the user's identity. - -Give users access to these features by configuring GitHub as an auxiliary authentication provider. +If your primary authentication provider is not GitHub, you can configure GitHub as an auxiliary provider to grant users the GitHub permissions needed for templates or plugins, without re-resolving user identities. .Prerequisites include::snip-enabling-user-authentication-with-github-common-prerequisites.adoc[] diff --git a/modules/shared/proc-enable-user-authentication-with-github-with-optional-steps.adoc b/modules/shared/proc-enable-user-authentication-with-github-with-optional-steps.adoc index 670cb67c135..9ef14de551e 100644 --- a/modules/shared/proc-enable-user-authentication-with-github-with-optional-steps.adoc +++ b/modules/shared/proc-enable-user-authentication-with-github-with-optional-steps.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="enable-user-authentication-with-github-with-optional-steps"] +[id="enable-user-authentication-with-github-with-optional-steps_{context}"] = Enable user authentication with GitHub, with optional steps [role="_abstract"] diff --git a/modules/shared/proc-enable-user-authentication-with-gitlab.adoc b/modules/shared/proc-enable-user-authentication-with-gitlab.adoc index aea89a6da14..4fee2edd526 100644 --- a/modules/shared/proc-enable-user-authentication-with-gitlab.adoc +++ b/modules/shared/proc-enable-user-authentication-with-gitlab.adoc @@ -4,7 +4,7 @@ = Enable user authentication with GitLab [role="_abstract"] -You can enable authentication with GitLab to allow users to sign in to {product-short} using their GitLab credentials. This integration also allows you to provision user and group data from GitLab to the {product-short} software catalog, enabling features that rely on synchronized user and group data. +You can enable authentication with GitLab to allow users to sign in to {product-short} using their GitLab credentials and provision user and group data to the software catalog. .Prerequisites include::snip-enable-user-authentication-with-gitlab-common-prerequisites.adoc[] diff --git a/modules/shared/proc-enable-user-authentication-with-rhbk-with-optional-steps.adoc b/modules/shared/proc-enable-user-authentication-with-rhbk-with-optional-steps.adoc index b45f201925f..a1508c2af26 100644 --- a/modules/shared/proc-enable-user-authentication-with-rhbk-with-optional-steps.adoc +++ b/modules/shared/proc-enable-user-authentication-with-rhbk-with-optional-steps.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="enable-user-authentication-with-rhbk-with-optional-steps"] +[id="enable-user-authentication-with-rhbk-with-optional-steps_{context}"] = Enable user authentication with {rhbk-brand-name} ({rhbk}), with optional steps [role="_abstract"] diff --git a/modules/shared/proc-enable-user-provisioning-with-ldap.adoc b/modules/shared/proc-enable-user-provisioning-with-ldap.adoc index e678ed1a321..ee6f03fdbdd 100644 --- a/modules/shared/proc-enable-user-provisioning-with-ldap.adoc +++ b/modules/shared/proc-enable-user-provisioning-with-ldap.adoc @@ -1,13 +1,13 @@ :_mod-docs-content-type: PROCEDURE -[id="enable-user-provisioning-with-ldap"] +[id="enable-user-provisioning-with-ldap_{context}"] = Enable user provisioning with LDAP [role="_abstract"] When {rhbk-brand-name} ({rhbk}) depends on Lightweight Directory Access Protocol (LDAP) to resolve user and group identities, you can opt to provision users and groups from LDAP directly to the {product} software catalog, rather than using the {rhbk} provisioning mechanism. .Prerequisites -* You have configured xref:enable-authentication-with-rhbk[authentication with {rhbk-brand-name} ({rhbk})]. +* You have configured xref:enable-authentication-with-rhbk_{parent-context}[authentication with {rhbk-brand-name} ({rhbk})]. * You have collected the required LDAP credentials: diff --git a/modules/shared/proc-install-and-configure-an-external-techdocs-add-on-using-the-operator.adoc b/modules/shared/proc-install-and-configure-an-external-techdocs-add-on-using-the-operator.adoc index c0ef86a7a45..3886d2a273e 100644 --- a/modules/shared/proc-install-and-configure-an-external-techdocs-add-on-using-the-operator.adoc +++ b/modules/shared/proc-install-and-configure-an-external-techdocs-add-on-using-the-operator.adoc @@ -63,7 +63,7 @@ where: _<external_techdocs_add-on>_:: Specifies the external TechDocs add-on that you want to install, for example, `TextSize` or `LightBox`. . Click *Create*. . In the web console navigation menu, click *Topology*. -. Click on the overflow menu for the {product} instance that you want to use and select *Edit {product-custom-resource-type}* to load the YAML view of the {product} instance. +. Click the overflow menu for the {product} instance that you want to use and select *Edit {product-custom-resource-type}* to load the YAML view of the {product} instance. . In your `{product-custom-resource-type}` CR, add the `dynamicPluginsConfigMapName: _<dynamic_plugins_configmap>_` key-value pair. For example: + [source,yaml] diff --git a/modules/shared/proc-reduce-the-size-of-issued-tokens.adoc b/modules/shared/proc-reduce-the-size-of-issued-tokens.adoc index d2c97971d95..ec36fe0da3b 100644 --- a/modules/shared/proc-reduce-the-size-of-issued-tokens.adoc +++ b/modules/shared/proc-reduce-the-size-of-issued-tokens.adoc @@ -4,10 +4,7 @@ = Reduce the size of issued tokens [role="_abstract"] -By default, the authentication backend issues user identity tokens with ownership references of the user in the `ent` claim of the JSON Web Token (JWT) payload. -This makes it easier for consumers of the token to resolve ownership of the user. -However, depending on the structure of your organization and how you resolve ownership claims, the tokens can grow large and cause HTTP errors that prevent you from accessing parts of {product-very-short}. -Use the `omitIdentityTokenOwnershipClaim` flag to remove the `ent` claim from tokens and reduce their size. +If user identity tokens grow large and cause HTTP errors, you can use the `omitIdentityTokenOwnershipClaim` flag to remove the `ent` claim from the JWT payload and reduce token size. .Procedure diff --git a/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-a-rest-client.adoc b/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-a-rest-client.adoc index b418f357034..08be58636b1 100644 --- a/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-a-rest-client.adoc +++ b/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-a-rest-client.adoc @@ -9,7 +9,7 @@ Send RBAC REST API requests by using any REST client with authorization tokens a You can send RBAC REST API requests by using any REST client. .Prerequisites -* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_{context}[You have access to the RBAC feature]. +* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_authorization-in-rhdh[You have access to the RBAC feature]. .Procedure include::snip-finding-bearer-token.adoc[] diff --git a/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-an-external-service.adoc b/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-an-external-service.adoc index b4aee35c27c..ec9c8c4bdde 100644 --- a/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-an-external-service.adoc +++ b/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-an-external-service.adoc @@ -9,7 +9,7 @@ Send GET requests to the RBAC REST API from an external service authenticated wi You can send GET requests to the RBAC REST API by using an external service authenticated by using a service-to-service token. .Prerequisites -* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_{context}[You have access to the RBAC feature]. +* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_authorization-in-rhdh[You have access to the RBAC feature]. * The external service can send HTTP GET requests, and is {authentication-book-link}#service-to-service-authentication[authenticated by using a service-to-service token]. diff --git a/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-the-curl-utility.adoc b/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-the-curl-utility.adoc index e168b4e9b8a..4522499eeeb 100644 --- a/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-the-curl-utility.adoc +++ b/modules/shared/proc-send-requests-to-the-rbac-rest-api-by-using-the-curl-utility.adoc @@ -9,7 +9,7 @@ Send RBAC REST API requests by using the curl utility to create, update, and del You can send RBAC REST API requests by using the curl utility. .Prerequisites -* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_{context}[You have access to the RBAC feature]. +* xref:enable-and-give-access-to-the-role-based-access-control-rbac-feature_authorization-in-rhdh[You have access to the RBAC feature]. .Procedure include::snip-finding-bearer-token.adoc[] diff --git a/modules/shared/proc-set-access-restrictions-to-external-service-tokens.adoc b/modules/shared/proc-set-access-restrictions-to-external-service-tokens.adoc index 6684e7a07f3..7c56b70778d 100644 --- a/modules/shared/proc-set-access-restrictions-to-external-service-tokens.adoc +++ b/modules/shared/proc-set-access-restrictions-to-external-service-tokens.adoc @@ -1,6 +1,6 @@ :_mod-docs-content-type: PROCEDURE -[id="set-access-restrictions-to-external-service-tokens"] +[id="set-access-restrictions-to-external-service-tokens_{context}"] = Set access restrictions to external service tokens [role="_abstract"] diff --git a/modules/shared/proc-start-and-complete-lessons-in-learning-paths.adoc b/modules/shared/proc-start-and-complete-lessons-in-learning-paths.adoc index 74a459a4bc8..83b9dd7b4bb 100644 --- a/modules/shared/proc-start-and-complete-lessons-in-learning-paths.adoc +++ b/modules/shared/proc-start-and-complete-lessons-in-learning-paths.adoc @@ -7,8 +7,8 @@ As a developer, you can start a course and complete the lessons at your own pace. .Prerequisites -. You can log in to developers.redhat.com -. Your platform engineer has granted you access to the Learning Paths plugin. +* You can log in to developers.redhat.com +* Your platform engineer has granted you access to the Learning Paths plugin. .Procedure diff --git a/modules/shared/proc-streamline-documentation-builds-using-github-actions.adoc b/modules/shared/proc-streamline-documentation-builds-using-github-actions.adoc index dc3628e0a33..fdec77e9dde 100644 --- a/modules/shared/proc-streamline-documentation-builds-using-github-actions.adoc +++ b/modules/shared/proc-streamline-documentation-builds-using-github-actions.adoc @@ -4,7 +4,7 @@ = Streamline documentation builds using GitHub Actions [role="_abstract"] -For production use, deploy TechDocs by building documentation in CI/CD, publishing to external storage like AWS S3, and configuring read-only mode. You can automate this workflow using GitHub Actions to generate and publish TechDocs when users update documentation files. +For production use, deploy TechDocs by building documentation in CI/CD, publishing to external storage such as {aws-short} S3, and configuring read-only mode. You can automate this workflow using GitHub Actions to generate and publish TechDocs when users update documentation files. .Prerequisites @@ -12,9 +12,9 @@ For production use, deploy TechDocs by building documentation in CI/CD, publishi * Your organization has documentation files stored in a remote repository. * You have an mkdocs.yaml file located in the root directory of your repository. * You have the `catalog.entity.create` and `catalog.location.create` permissions to import documentation into TechDocs from a remote repository. -* You have an AWS S3 bucket to store your TechDocs sites. +* You have an {aws-short} S3 bucket to store your TechDocs sites. * Minimal IAM Policies are configured for your S3 bucket, granting both Write and Read access. -* An administrator has created an IAM User, attached the necessary policy, and generated an access key in the AWS console. +* An administrator has created an IAM User, attached the necessary policy, and generated an access key in the {aws-short} console. .Procedure @@ -27,7 +27,7 @@ The `rhdh-techdocs-pipeline` repository contains a `generate-and-publish-techdoc ==== + .. Use the GitHub GUI to make sure that all of the permissions required to run the workflow are enabled. -.. Add the *Repository secrets* required to connect the workflow to your AWS account, for example, `TECHDOCS_S3_BUCKET_NAME`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`. +.. Add the *Repository secrets* required to connect the workflow to your {aws-short} account, for example, `TECHDOCS_S3_BUCKET_NAME`, `{aws-short}_ACCESS_KEY_ID`, `{aws-short}_SECRET_ACCESS_KEY`, `{aws-short}_REGION`. + [NOTE] ==== @@ -45,15 +45,15 @@ techdocs: publisher: type: awsS3 awsS3: - bucketName: ${AWS_S3_BUCKET_NAME} - accountId: ${AWS_ACCOUNT_ID} - region: ${AWS_REGION} + bucketName: ${aws-short}_S3_BUCKET_NAME} + accountId: ${aws-short}_ACCOUNT_ID} + region: ${aws-short}_REGION} aws: accounts: - - accountId: ${AWS_ACCOUNT_ID} - accessKeyId: ${AWS_ACCESS_KEY_ID} - secretAccessKey: ${AWS_SECRET_ACCESS_KEY} + - accountId: ${aws-short}_ACCOUNT_ID} + accessKeyId: ${aws-short}_ACCESS_KEY_ID} + secretAccessKey: ${aws-short}_SECRET_ACCESS_KEY} catalog: locations: diff --git a/modules/shared/proc-test-a-plugin-locally.adoc b/modules/shared/proc-test-a-plugin-locally.adoc index 42063e74b8c..5c49d81e045 100644 --- a/modules/shared/proc-test-a-plugin-locally.adoc +++ b/modules/shared/proc-test-a-plugin-locally.adoc @@ -16,7 +16,7 @@ This file is the entry point for the Local Development Sandbox, it serves as a t When you are developing a plugin, you do not need to boot up an entire {backstage} (or {product-very-short}) production-grade instance just to see a UI change. Instead, you use the _Dev App_ which is a lightweight, stripped-down version of the {backstage} frontend. -.Primary functions of the Dev App +Primary functions of the Dev App: Plugin isolation:: It allows you to run your plugin in a standalone wrapper. This is what loads when you run yarn start from within the plugin directory. diff --git a/modules/shared/proc-update-existing-components-in-your-rhdh-catalog.adoc b/modules/shared/proc-update-existing-components-in-your-rhdh-catalog.adoc index 4a8231e523b..2f9f00e2cb0 100644 --- a/modules/shared/proc-update-existing-components-in-your-rhdh-catalog.adoc +++ b/modules/shared/proc-update-existing-components-in-your-rhdh-catalog.adoc @@ -27,5 +27,5 @@ This action redirects you to the YAML file on GitHub. + [NOTE] ==== -After you merge your changes, the updated metadata in the Software Catalog appears after some time. +After you merge your changes, the updated metadata in the Software Catalog is displayed after some time. ==== diff --git a/modules/shared/proc-update-the-system-prompt-in.adoc b/modules/shared/proc-update-the-system-prompt-in.adoc index 806b2df7e24..0d1685ca6ba 100644 --- a/modules/shared/proc-update-the-system-prompt-in.adoc +++ b/modules/shared/proc-update-the-system-prompt-in.adoc @@ -16,5 +16,5 @@ lightspeed: # ... other lightspeed configurations systemPrompt: "You are a helpful assistant focused on Red Hat Developer Hub development." ---- - ++ Set `systemPrompt` to prefix all queries sent by {ls-short} to the LLM with this instruction, guiding the model to generate more tailored responses. diff --git a/modules/shared/proc-use-the-lightbox-techdocs-add-on.adoc b/modules/shared/proc-use-the-lightbox-techdocs-add-on.adoc index d784a8d03fb..3b83c588d99 100644 --- a/modules/shared/proc-use-the-lightbox-techdocs-add-on.adoc +++ b/modules/shared/proc-use-the-lightbox-techdocs-add-on.adoc @@ -10,7 +10,7 @@ Use the `LightBox` add-on to view enlarged images in a lightbox overlay window a * The `LightBox` add-on is installed and enabled in your TechDocs plugin. .Procedure -. In your TechDocs documentation, click on the image that you want to view in a lightbox. +. In your TechDocs documentation, click the image that you want to view in a lightbox. . In the lightbox, you can do any of the following actions: . Click the image or scroll to zoom in or zoom out. . Click the arrow to navigate between images. diff --git a/modules/shared/ref-conditional-policy-plugin-examples.adoc b/modules/shared/ref-conditional-policy-plugin-examples.adoc index 9622c862087..5c22b584bdc 100644 --- a/modules/shared/ref-conditional-policy-plugin-examples.adoc +++ b/modules/shared/ref-conditional-policy-plugin-examples.adoc @@ -58,4 +58,4 @@ The previous example of Quay plugin prevents the role `role:default/developer` f Note that `permissionMapping` contains `use`, signifying that `scaffolder-action` resource type permission does not have a permission policy. .Additional resources -* xref:permission-policies-reference_{context}[] +* xref:permission-policies-reference_authorization-in-rhdh[] diff --git a/modules/shared/ref-supported-rbac-rest-api-endpoints.adoc b/modules/shared/ref-supported-rbac-rest-api-endpoints.adoc index 0ec328e44e3..fc8551d72a9 100644 --- a/modules/shared/ref-supported-rbac-rest-api-endpoints.adoc +++ b/modules/shared/ref-supported-rbac-rest-api-endpoints.adoc @@ -89,7 +89,7 @@ Creates a role in {product-short}. + Updates `memberReferences`, `kind`, `namespace`, or `name` for a role in {product-short}. -.Request parameters +Request parameters: The request body contains the `oldRole` and `newRole` objects: [cols="15%,45%,15%,25%", frame="all", options="header"] |=== @@ -340,7 +340,7 @@ Creates a permission policy for a specified entity. + Updates a permission policy for a specified entity. -.Request parameters +Request parameters: The request body contains the `oldPolicy` and `newPolicy` objects: [cols="15%,45%,15%,25%", frame="all", options="header"] |=== diff --git a/modules/shared/snip-enable-user-authentication-with-gitlab-common-first-steps.adoc b/modules/shared/snip-enable-user-authentication-with-gitlab-common-first-steps.adoc index 835c0e7abb6..a754c261248 100644 --- a/modules/shared/snip-enable-user-authentication-with-gitlab-common-first-steps.adoc +++ b/modules/shared/snip-enable-user-authentication-with-gitlab-common-first-steps.adoc @@ -85,7 +85,7 @@ Optional. Specify the types of group memberships to include during ingestion. Yo Optional. Filters found groups based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything. `restrictUsersToGroup`:: -Set to `true` to ingest only users who are direct members of the configured group. +Set to `true` to import only users who are direct members of the configured group. `includeUsersWithoutSeat`:: Set to `true` to include users who do not occupy a paid seat. This setting applies only to GitLab SaaS. diff --git a/titles/configure_configuring-rhdh/master.adoc b/titles/configure_configuring-rhdh/master.adoc index bce73ecbff7..5c4c393934c 100644 --- a/titles/configure_configuring-rhdh/master.adoc +++ b/titles/configure_configuring-rhdh/master.adoc @@ -2,10 +2,12 @@ :_mod-docs-category: Configure include::artifacts/attributes.adoc[] + :title: Configuring {product} :subtitle: Adding custom config maps and secrets to configure your {product} instance to work in your IT ecosystem. :abstract: Configure {product} for production by adding custom config maps and secrets to work in your IT ecosystem. :context: configuring-rhdh + :platform-id: ocp :platform-long: {ocp-brand-name} ({ocp-very-short}) :platform: {ocp-short} diff --git a/titles/configure_customizing-rhdh/master.adoc b/titles/configure_customizing-rhdh/master.adoc index 402de7797e2..18fd6ee16f4 100644 --- a/titles/configure_customizing-rhdh/master.adoc +++ b/titles/configure_customizing-rhdh/master.adoc @@ -2,10 +2,12 @@ :_mod-docs-category: Configure include::artifacts/attributes.adoc[] + :title: Customizing {product} :subtitle: Customizing {product} appearance and features, such as templates, Learning Paths, Tech Radar, Home page, and quick access cards :abstract: Authorized users can customize {product} ({product-very-short}) appearance and features, such as templates, Learning Paths, Tech Radar, Home page, and quick access cards. :context: customizing-rhdh + [id="{context}"] = {title} diff --git a/titles/configure_techdocs-for-rhdh/master.adoc b/titles/configure_techdocs-for-rhdh/master.adoc index 0e9ef979cac..99c0cf8d282 100644 --- a/titles/configure_techdocs-for-rhdh/master.adoc +++ b/titles/configure_techdocs-for-rhdh/master.adoc @@ -14,15 +14,10 @@ include::artifacts/attributes.adoc[] [role="_abstract"] {abstract} -// about TechDocs include::modules/shared/con-about-techdocs.adoc[leveloffset=+1] -// TechDocs configuration include::assemblies/configure_techdocs-for-rhdh/assembly-configure-techdocs.adoc[leveloffset=+1] -// using TechDocs - moved to the new title "Manage and consume technical documentation within RHDH" - -// TechDocs add-ons include::assemblies/configure_techdocs-for-rhdh/assembly-techdocs-add-ons.adoc[leveloffset=+1] include::assemblies/configure_techdocs-for-rhdh/assembly-install-and-configure-a-techdocs-add-on.adoc[leveloffset=+2] diff --git a/titles/control-access_authentication-in-rhdh/master.adoc b/titles/control-access_authentication-in-rhdh/master.adoc index e21ad335bab..f72fcd399be 100644 --- a/titles/control-access_authentication-in-rhdh/master.adoc +++ b/titles/control-access_authentication-in-rhdh/master.adoc @@ -12,8 +12,7 @@ include::artifacts/attributes.adoc[] = {title} [role="_abstract"] -You can enable authentication in {product} to allow users to sign in to {product} using credentials from an external identity provider, such as {rhbk}, GitHub, or {azure-brand-name}. -This integration also allows you to provision user and group data from the identity provider to the {product-short} software catalog, enabling features that rely on synchronized user and group data. +You can enable authentication in {product} to allow users to sign in using credentials from an external identity provider, such as {rhbk}, GitHub, or {azure-brand-name}, and provision user and group data to the software catalog. include::modules/shared/con-understand-authentication-and-user-provisioning.adoc[leveloffset=+1] diff --git a/titles/control-access_authorization-in-rhdh/master.adoc b/titles/control-access_authorization-in-rhdh/master.adoc index bc3150199ed..e080f731cb9 100644 --- a/titles/control-access_authorization-in-rhdh/master.adoc +++ b/titles/control-access_authorization-in-rhdh/master.adoc @@ -2,11 +2,12 @@ :_mod-docs-category: Control access include::artifacts/attributes.adoc[] + :context: authorization-in-rhdh -:imagesdir: images :title: Authorization in {product} :subtitle: Configuring authorization by using role-based access control (RBAC) in {product} :abstract: {product} ({product-very-short}) administrators can use role-based access control (RBAC) to manage authorizations of other users. + [id="{context}"] = {title} diff --git a/titles/develop_manage-and-consume-technical-documentation-within-rhdh/master.adoc b/titles/develop_manage-and-consume-technical-documentation-within-rhdh/master.adoc index ec35d8f8ab5..895a908139a 100644 --- a/titles/develop_manage-and-consume-technical-documentation-within-rhdh/master.adoc +++ b/titles/develop_manage-and-consume-technical-documentation-within-rhdh/master.adoc @@ -2,15 +2,17 @@ :_mod-docs-category: Develop include::artifacts/attributes.adoc[] + :title: Manage and consume technical documentation within {product} :context: manage-and-consume-technical-documentation-within-rhdh :subtitle: Managing the documentation lifecycle - add, search, view, and edit content - using the TechDocs plugin in {product} ({product-very-short}) +:abstract: The TechDocs plugin provides complete documentation lifecycle management. Authorized administrators set up the service, allowing developers to manage documentation directly in {product} ({product-very-short}), including adding, searching, viewing, and editing content. [id="{context}"] = {title} [role="_abstract"] -The TechDocs plugin provides complete documentation lifecycle management. Authorized administrators set up the service, allowing developers to manage documentation directly in {product} ({product-very-short}), including adding, searching, viewing, and editing content. +{abstract} include::assemblies/develop_manage-and-consume-technical-documentation-within-rhdh/assembly-add-documentation-to-techdocs-for-your-project.adoc[leveloffset=+1] diff --git a/titles/develop_streamline-software-development-and-management-in-rhdh/master.adoc b/titles/develop_streamline-software-development-and-management-in-rhdh/master.adoc index 95aec42a09a..ee450989f84 100644 --- a/titles/develop_streamline-software-development-and-management-in-rhdh/master.adoc +++ b/titles/develop_streamline-software-development-and-management-in-rhdh/master.adoc @@ -2,6 +2,7 @@ :_mod-docs-category: Develop include::artifacts/attributes.adoc[] + :context: streamline-software-development-and-management-in-rhdh :title: Streamline software development and management in {product} :subtitle: Automating the development lifecycle, improving code quality, monitoring deployments, and managing services by using tools and plugins in {product} ({product-very-short}) diff --git a/titles/discover_about-rhdh/master.adoc b/titles/discover_about-rhdh/master.adoc index 47840976826..7cb5174f9f8 100644 --- a/titles/discover_about-rhdh/master.adoc +++ b/titles/discover_about-rhdh/master.adoc @@ -2,16 +2,17 @@ :_mod-docs-category: Discover include::artifacts/attributes.adoc[] -:imagesdir: images + :title: About {product} :subtitle: {product} is a customizable developer portal with enterprise-level support and a centralized software catalog that you can use to build high-quality software efficiently in a streamlined development environment -:abstract: {product} ({product-very-short}) is a customizable developer portal with enterprise-level support and a centralized software catalog that you can use to build high-quality software efficiently in a streamlined development environment. +:abstract: Learn about {product}, an enterprise-grade internal developer portal that helps teams simplify software delivery and accelerate development. :context: about-rhdh + [id="{context}"] = {title} [role="_abstract"] -Learn about {product}, an enterprise-grade internal developer portal that helps teams simplify software delivery and accelerate development. +{abstract} {product} ({product-very-short}) is an enterprise-grade internal developer portal (IDP) that helps simplify and accelerate software delivery. It provides a customizable web-based interface that centralizes access to key development resources, including source code repositories, CI and CD pipelines, APIs, documentation, and runtime environments. @@ -28,7 +29,8 @@ include::modules/discover_about-rhdh/ref-sizing-requirements-for-rhdh.adoc[level include::modules/discover_about-rhdh/ref-rhdh-support.adoc[leveloffset=+1] [role="_additional-resources"] -.Additional resources +[role="_additional-resources"] +== Additional resources * {installing-on-eks-book-link}[{installing-on-eks-book-title}] * {installing-on-osd-on-gcp-book-link}[{installing-on-osd-on-gcp-book-title}] * {installing-on-gke-book-link}[{installing-on-gke-book-title}] diff --git a/titles/extend_configuring-dynamic-plugins/master.adoc b/titles/extend_configuring-dynamic-plugins/master.adoc index 4a71f779f45..f5318304cea 100644 --- a/titles/extend_configuring-dynamic-plugins/master.adoc +++ b/titles/extend_configuring-dynamic-plugins/master.adoc @@ -2,9 +2,8 @@ :_mod-docs-category: Extend include::artifacts/attributes.adoc[] -:context: configuring-dynamic-plugins -:imagesdir: images +:context: configuring-dynamic-plugins :title: Configuring dynamic plugins :subtitle: Configuring dynamic plugins in {product} :abstract: As a platform engineer, you can configure dynamic plugins in {product} ({product-very-short}) to access your development infrastructure or software development tools. diff --git a/titles/extend_develop-and-deploy-pugins-in-rhdh/master.adoc b/titles/extend_develop-and-deploy-pugins-in-rhdh/master.adoc index a18c3186c03..0a810a44adc 100644 --- a/titles/extend_develop-and-deploy-pugins-in-rhdh/master.adoc +++ b/titles/extend_develop-and-deploy-pugins-in-rhdh/master.adoc @@ -1,15 +1,45 @@ :_mod-docs-content-type: ASSEMBLY :_mod-docs-category: Extend -[id="title-plugins-rhdh-about"] include::artifacts/attributes.adoc[] :context: develop-and-deploy-pugins-in-rhdh -:imagesdir: images :title: Develop and deploy dynamic plugins in {product} :subtitle: {product-very-short} dynamic plugins: From development to deployment :abstract: As a {product} ({product-very-short}) user, you can learn about {product-very-short} plugins. -// = Introduction to plugins -// include::modules/shared/con-plugins-in-rhdh.adoc[leveloffset=+1] +[id="{context}"] += {title} -include::assemblies/extend_shared/assembly-develop-and-deploy-dynamic-plugins-in-rhdh.adoc[] +[role="_abstract"] +{abstract} + +include::modules/shared/con-dynamic-plugins.adoc[leveloffset=+1] + +// Setting Up the Development Toolchain (1.9 tr) +include::modules/shared/con-prepare-your-development-environment.adoc[leveloffset=+1] + +== Develop a new plugin + +include::modules/shared/con-determine-rhdh-version.adoc[leveloffset=+2] + +include::modules/shared/proc-create-a-new-backstage-application.adoc[leveloffset=+2] + +include::modules/shared/proc-create-a-new-plugin.adoc[leveloffset=+2] + +include::modules/shared/proc-implement-a-plugin-component.adoc[leveloffset=+2] + +include::modules/shared/proc-test-a-plugin-locally.adoc[leveloffset=+2] + +include::modules/shared/proc-configure-frontend-plugin-wiring.adoc[leveloffset=+2] + +// Converting a custom plugin into a dynamic plugin +include::modules/shared/proc-convert-a-custom-plugin-into-a-dynamic-plugin.adoc[leveloffset=+1] + +include::modules/shared/con-using-the-dynamic-plugin-factory-to-convert-plugins-into-dynamic-plugins.adoc[leveloffset=+2] + +== Deployment configurations + +include::modules/shared/proc-add-a-dynamic-plugin-to-rhdh.adoc[leveloffset=+2] + +// Testing with {product-local-very-short} (1.9) +include::modules/shared/proc-verify-plugins-locally.adoc[leveloffset=+1] diff --git a/titles/extend_dynamic-plugins-reference/master.adoc b/titles/extend_dynamic-plugins-reference/master.adoc index 3146f8abbd1..e3de623c302 100644 --- a/titles/extend_dynamic-plugins-reference/master.adoc +++ b/titles/extend_dynamic-plugins-reference/master.adoc @@ -2,9 +2,8 @@ :_mod-docs-category: Extend include::artifacts/attributes.adoc[] -:context: dynamic-plugins-reference -:imagesdir: images +:context: dynamic-plugins-reference :title: Dynamic plugins reference :subtitle: {product} is preinstalled with a selection of dynamic plugins that you can enable and configure to extend {product-short} functionality :abstract: {product} ({product-very-short}) is preinstalled with a selection of dynamic plugins that users can enable and configure to extend {product-very-short} functionality. diff --git a/titles/extend_installing-and-viewing-plugins-in-rhdh/master.adoc b/titles/extend_installing-and-viewing-plugins-in-rhdh/master.adoc index 4feb7d7017e..111b24d7b09 100644 --- a/titles/extend_installing-and-viewing-plugins-in-rhdh/master.adoc +++ b/titles/extend_installing-and-viewing-plugins-in-rhdh/master.adoc @@ -2,9 +2,8 @@ :_mod-docs-category: Extend include::artifacts/attributes.adoc[] -:context: installing-and-viewing-plugins-in-rhdh -:imagesdir: images +:context: installing-and-viewing-plugins-in-rhdh :title: Installing and viewing plugins in {product} :subtitle: Installing plugins in {product} :abstract: Administrative users can install and configure plugins to enable other users to use plugins to extend {product} ({product-very-short}) functionality. diff --git a/titles/extend_orchestrator-in-rhdh/master.adoc b/titles/extend_orchestrator-in-rhdh/master.adoc index 3c44f797ce6..11d04275e21 100644 --- a/titles/extend_orchestrator-in-rhdh/master.adoc +++ b/titles/extend_orchestrator-in-rhdh/master.adoc @@ -2,16 +2,17 @@ :_mod-docs-category: Extend include::artifacts/attributes.adoc[] + :context: orchestrator-in-rhdh -:imagesdir: images :title: Orchestrator in {product} :subtitle: Orchestrator enables serverless workflows for cloud migration, onboarding, and customization in {product} -:abstract: As an administrator, you can use Orchestrator to enable serverless workflows in {product} to support cloud migration, developer onboarding, and custom workflows. +:abstract: Use Orchestrator to enable serverless workflows in {product} to support cloud migration, developer onboarding, and custom workflows. + [id="{context}"] = {title} [role="_abstract"] -Use Orchestrator to enable serverless workflows in {product} to support cloud migration, developer onboarding, and custom workflows. +{abstract} include::assemblies/extend_orchestrator-in-rhdh/assembly-about-orchestrator-in-rhdh.adoc[leveloffset=+1] diff --git a/titles/extend_troubleshooting-rhdh-plugins/master.adoc b/titles/extend_troubleshooting-rhdh-plugins/master.adoc index 926786667f6..be3c8e6ca43 100644 --- a/titles/extend_troubleshooting-rhdh-plugins/master.adoc +++ b/titles/extend_troubleshooting-rhdh-plugins/master.adoc @@ -2,11 +2,12 @@ :_mod-docs-category: Extend include::artifacts/attributes.adoc[] + :context: troubleshooting-rhdh-plugins -:imagesdir: images :title: Troubleshooting {product} plugins :subtitle: Troubleshooting {product-very-short} plugins :abstract: You can troubleshoot common problems with {product} ({product-very-short}) plugins, such as pod startup failures caused by missing plugin configuration. + [id="{context}"] = {title} diff --git a/titles/extend_using-dynamic-plugins-in-rhdh/master.adoc b/titles/extend_using-dynamic-plugins-in-rhdh/master.adoc index fd562cd3961..071212a97a5 100644 --- a/titles/extend_using-dynamic-plugins-in-rhdh/master.adoc +++ b/titles/extend_using-dynamic-plugins-in-rhdh/master.adoc @@ -2,11 +2,12 @@ :_mod-docs-category: Extend include::artifacts/attributes.adoc[] + :context: using-dynamic-plugins-in-rhdh -:imagesdir: images :title: Using dynamic plugins in {product} :subtitle: Using {product} plugins to access your development infrastructure and software development tools :abstract: You can use {product} ({product-very-short}) dynamic plugins to interact with your development infrastructure and software development tools. + [id="{context}"] = {title} @@ -25,4 +26,4 @@ include::modules/shared/proc-use-the-nexus-repository-manager-plugin.adoc[levelo include::modules/shared/proc-use-the-tekton-plugin.adoc[leveloffset=+1] -include::assemblies/extend_shared/assembly-use-the-topology-plugin.adoc[leveloffset=+1] +include::assemblies/extend_using-dynamic-plugins-in-rhdh/assembly-use-the-topology-plugin.adoc[leveloffset=+1] diff --git a/titles/get-started_navigate-rhdh-on-your-first-day/master.adoc b/titles/get-started_navigate-rhdh-on-your-first-day/master.adoc index 6787a6f8f60..b43fbfadbd5 100644 --- a/titles/get-started_navigate-rhdh-on-your-first-day/master.adoc +++ b/titles/get-started_navigate-rhdh-on-your-first-day/master.adoc @@ -2,6 +2,7 @@ :_mod-docs-category: Get started include::artifacts/attributes.adoc[] + :context: navigate-rhdh-on-your-first-day :title: Navigate {product} on your first day :subtitle: Log in, navigate the {product} ({product-very-short}) interface, and personalize your workspace to become productive immediately diff --git a/titles/get-started_setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc b/titles/get-started_setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc index 0b308de305a..fee2efbf9fe 100644 --- a/titles/get-started_setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc +++ b/titles/get-started_setting-up-and-configuring-your-first-red-hat-developer-hub-instance/master.adoc @@ -2,11 +2,14 @@ :_mod-docs-category: Get started include::artifacts/attributes.adoc[] + :title: Setting up and configuring your first {product} instance :subtitle: Prepare your IT infrastructure including {ocp-brand-name} and required external components, and run your first {product} ({product-very-short}) instance in production. :abstract: Prepare your IT infrastructure including {ocp-brand-name} and required external components, and run your first {product} ({product-very-short}) instance in production. :context: setting-up-and-configuring-your-first-red-hat-developer-hub-instance + :optional-steps: disable + [id="{context}"] = {title} diff --git a/titles/install_installing-rhdh-in-an-air-gapped-environment/master.adoc b/titles/install_installing-rhdh-in-an-air-gapped-environment/master.adoc index 04af96325fb..2201eb89a20 100644 --- a/titles/install_installing-rhdh-in-an-air-gapped-environment/master.adoc +++ b/titles/install_installing-rhdh-in-an-air-gapped-environment/master.adoc @@ -2,25 +2,24 @@ :_mod-docs-category: Install include::artifacts/attributes.adoc[] + :title: Installing {product} in an air-gapped environment :context: installing-rhdh-in-an-air-gapped-environment :subtitle: Running {product} on {ocp-brand-name} in a network restricted environment by using either the Operator or Helm chart :abstract: Platform administrators can configure roles, permissions, and other settings to enable other authorized users to deploy an air-gapped {product} ({product-very-short}) instance on any supported platform by using either the Operator or Helm chart. +:optional-steps: disable + [id="{context}"] = {title} -:imagesdir: images -:optional-steps: disable [role="_abstract"] Configure and deploy {product-short} on a supported platform in a network-restricted environment by using either the Operator or Helm chart. include::modules/install_installing-rhdh-in-an-air-gapped-environment/con-air-gapped-environment.adoc[leveloffset=+1] -// OCP include::assemblies/install_installing-rhdh-in-an-air-gapped-environment/assembly-install-rhdh-in-an-air-gapped-environment-with-the-operator.adoc[leveloffset=+1] include::assemblies/install_installing-rhdh-in-an-air-gapped-environment/assembly-install-rhdh-on-ocp-in-an-air-gapped-environment-with-the-helm-chart.adoc[leveloffset=+1] -// Kubernetes include::assemblies/install_installing-rhdh-in-an-air-gapped-environment/assembly-install-rhdh-on-a-supported-kubernetes-platform-in-an-air-gapped-environment-with-the-helm-chart.adoc[leveloffset=+1] diff --git a/titles/install_installing-rhdh-on-aks/master.adoc b/titles/install_installing-rhdh-on-aks/master.adoc index 132d52f90d1..26bdea52bbc 100644 --- a/titles/install_installing-rhdh-on-aks/master.adoc +++ b/titles/install_installing-rhdh-on-aks/master.adoc @@ -2,6 +2,7 @@ :_mod-docs-category: Install include::artifacts/attributes.adoc[] + :platform-cli: kubectl :platform-cli-name: Kubernetes CLI ('kubectl') :platform-cli-link: link:https://kubernetes.io/docs/tasks/tools/#kubectl[{platform-cli-name}] @@ -10,14 +11,15 @@ include::artifacts/attributes.adoc[] :platform: {aks-short} :a-platform-generic: a Kubernetes :platform-generic: Kubernetes +:optional-steps: disable + :title: Installing {product} on {platform-long} :subtitle: Running {product} on {platform-long} by using either the Operator or Helm chart :abstract: {product} ({product-very-short}) is an enterprise-grade platform for building developer portals. Administrative users can configure roles, permissions, and other settings to enable other authorized users to deploy a {product-very-short} instance on {platform-long} using either the Operator or Helm chart. :context: installing-rhdh-on-aks + [id="{context}"] = {title} -:imagesdir: images -:optional-steps: disable {abstract} diff --git a/titles/install_installing-rhdh-on-eks/master.adoc b/titles/install_installing-rhdh-on-eks/master.adoc index 18dddb0b0ef..3aec5e2d5ed 100644 --- a/titles/install_installing-rhdh-on-eks/master.adoc +++ b/titles/install_installing-rhdh-on-eks/master.adoc @@ -2,6 +2,7 @@ :_mod-docs-category: Install include::artifacts/attributes.adoc[] + :platform-cli: kubectl :platform-cli-name: Kubernetes CLI ('kubectl') :platform-cli-link: link:https://kubernetes.io/docs/tasks/tools/#kubectl[{platform-cli-name}] @@ -10,14 +11,15 @@ include::artifacts/attributes.adoc[] :platform-id: eks :a-platform-generic: a Kubernetes :platform-generic: Kubernetes +:optional-steps: disable + :title: Installing {product} on {platform-long} :subtitle: Running {product} on {platform-long} by using either the Operator or Helm chart :abstract: {product} ({product-very-short}) is an enterprise-grade platform for building developer portals. Administrative users can configure roles, permissions, and other settings to enable other authorized users to deploy a {product-very-short} instance on {platform-long} using either the Operator or Helm chart. :context: installing-rhdh-on-eks + [id="{context}"] = {title} -:imagesdir: images -:optional-steps: disable [role="_abstract"] {abstract} diff --git a/titles/install_installing-rhdh-on-gke/master.adoc b/titles/install_installing-rhdh-on-gke/master.adoc index dca75e2f279..a0caaa5de15 100644 --- a/titles/install_installing-rhdh-on-gke/master.adoc +++ b/titles/install_installing-rhdh-on-gke/master.adoc @@ -10,14 +10,15 @@ include::artifacts/attributes.adoc[] :platform: {gke-short} :a-platform-generic: a Kubernetes :platform-generic: Kubernetes +:optional-steps: disable + :title: Installing {product} on {platform-long} :subtitle: Running {product} on {platform-long} by using either the Operator or Helm chart :abstract: {product} ({product-very-short}) is an enterprise-grade platform for building developer portals. Administrative users can configure roles, permissions, and other settings to enable other authorized users to deploy a {product-very-short} instance on {platform-long} using either the Operator or Helm chart. :context: installing-rhdh-on-gke + [id="{context}"] = {title} -:imagesdir: images -:optional-steps: disable [role="_abstract"] {abstract} diff --git a/titles/install_installing-rhdh-on-ocp/master.adoc b/titles/install_installing-rhdh-on-ocp/master.adoc index 9dae2f8b130..abb85f9f71b 100644 --- a/titles/install_installing-rhdh-on-ocp/master.adoc +++ b/titles/install_installing-rhdh-on-ocp/master.adoc @@ -2,49 +2,22 @@ :_mod-docs-category: Install include::artifacts/attributes.adoc[] + :title: Install {product} on {ocp-short} :subtitle: Running {product} on {ocp-brand-name} by using either the Operator or Helm chart :abstract: Platform administrators can configure roles, permissions, and other settings to enable other authorized users to deploy a {product} ({product-very-short}) instance on {ocp-brand-name} using either the Operator or Helm chart. :context: installing-rhdh-on-ocp + [id="{context}"] = {title} -:imagesdir: images [role="_abstract"] -Platform administrators can configure roles, permissions, and other settings to enable other authorized users to deploy a {product} ({product-very-short}) instance on {ocp-brand-name} using either the Operator or Helm chart. - -// ocp deployment -//include::assemblies/assembly-install-rhdh-ocp.adoc[leveloffset=+1] - -You can install {product} on {ocp-short} by using one of the following installers: - -The {product} Operator:: -+ --- -* Ready for immediate use in {ocp-short} after an administrator installs it with OperatorHub -* Uses Operator Lifecycle Management (OLM) to manage automated subscription updates on {ocp-short} -* Requires preinstallation of Operator Lifecycle Management (OLM) to manage automated subscription updates on Kubernetes --- - -The {product} Helm chart:: -+ --- -* Ready for immediate use in both {ocp-short} and Kubernetes -* Requires manual installation and management --- - -[IMPORTANT] -==== -You must set the `baseUrl` in `{my-app-config-file}` to match the external URL of your {product-short} instance (for example, `pass:[https://rhdh.example.com]`). -This value is required for the {product} to function correctly. If it is not set, front-end and back-end services cannot communicate properly, and features might not work as expected. -==== +{abstract} -Use the installation method that best meets your needs and preferences. +include::modules/install_installing-rhdh-on-ocp/con-rhdh-installation-methods-on-ocp.adoc[leveloffset=+1] -// Operator method include::assemblies/install_installing-rhdh-on-ocp/assembly-install-rhdh-on-ocp-with-the-operator.adoc[leveloffset=+1] -// Helm chart method include::assemblies/install_installing-rhdh-on-ocp/assembly-install-rhdh-on-ocp-with-the-helm-chart.adoc[leveloffset=+1] .Additional resources diff --git a/titles/install_installing-rhdh-on-osd-on-gcp/master.adoc b/titles/install_installing-rhdh-on-osd-on-gcp/master.adoc index cdec2758105..839ed0f8a5e 100644 --- a/titles/install_installing-rhdh-on-osd-on-gcp/master.adoc +++ b/titles/install_installing-rhdh-on-osd-on-gcp/master.adoc @@ -2,11 +2,11 @@ :_mod-docs-category: Install include::artifacts/attributes.adoc[] + :title: Installing {product} on {osd-short} on {gcp-brand-name} :subtitle: Running {product} on {osd-brand-name} by using either the Operator or Helm chart :abstract: Deploy {product-short} on {osd-short} on {gcp-short} to provide a unified developer experience platform using either the Operator for centralized management or Helm for flexible configuration. :context: installing-rhdh-on-osd-on-gcp -:imagesdir: images [id="{context}"] = {title} diff --git a/titles/integrate_accelerating-ai-development-with-openshift-ai-connector-for-rhdh/master.adoc b/titles/integrate_accelerating-ai-development-with-openshift-ai-connector-for-rhdh/master.adoc index 31d03ae94dc..fb1cb52e1a9 100644 --- a/titles/integrate_accelerating-ai-development-with-openshift-ai-connector-for-rhdh/master.adoc +++ b/titles/integrate_accelerating-ai-development-with-openshift-ai-connector-for-rhdh/master.adoc @@ -2,16 +2,17 @@ :_mod-docs-category: Integrate include::artifacts/attributes.adoc[] + :context: accelerating-ai-development-with-openshift-ai-connector-for-rhdh -:imagesdir: images :title: Accelerate AI development with {openshift-ai-connector-name} :subtitle: Installing, configuring, and troubleshooting {openshift-ai-connector-name} -:abstract: As a developer, when you require access to centralized AI/ML services, you can integrate AI models and model servers from {rhoai-brand-name} directly into the {product} ({product-very-short}) Catalog, so that you can provide a single, consistent hub for discovering, managing, and consuming all components, accelerating time-to-market. +:abstract: Integrate AI models and model servers from {rhoai-brand-name} directly into the {product} ({product-very-short}) Catalog to provide a unified hub for discovering and consuming AI components. + [id="{context}"] = {title} [role="_abstract"] -Integrate AI models and model servers from {rhoai-brand-name} directly into the {product} ({product-very-short}) Catalog to provide a unified hub for discovering and consuming AI components. +{abstract} include::modules/integrate_accelerating-ai-development-with-openshift-ai-connector-for-rhdh/con-understand-how-ai-assets-map-to-the-rhdh-catalog.adoc[leveloffset=+1] diff --git a/titles/integrate_integrating-rhdh-with-github/master.adoc b/titles/integrate_integrating-rhdh-with-github/master.adoc index 04041a45db3..211cc4727de 100644 --- a/titles/integrate_integrating-rhdh-with-github/master.adoc +++ b/titles/integrate_integrating-rhdh-with-github/master.adoc @@ -2,16 +2,17 @@ :_mod-docs-category: Integrate include::artifacts/attributes.adoc[] + :context: integrating-rhdh-with-github :imagesdir: images :title: Integrating {product} with GitHub :subtitle: Configuring integration to the GitHub Git provider in {product} -:abstract: As a {product} ({product-very-short}) platform engineer, you can integrate {product-very-short} with the GitHub Git provider. +:abstract: As a {product} platform engineer, you can integrate {product-short} with the GitHub Git provider. [id="{context}"] = {title} [role="_abstract"] -As a {product} platform engineer, you can integrate {product-short} with the GitHub Git provider. +{abstract} include::assemblies/integrate_integrating-rhdh-with-github/assembly-integrate-with-github-in-rhdh.adoc[leveloffset=+1] diff --git a/titles/integrate_interacting-with-developer-lightspeed-for-rhdh/master.adoc b/titles/integrate_interacting-with-developer-lightspeed-for-rhdh/master.adoc index 6bd35c6b3a6..0df23956a35 100644 --- a/titles/integrate_interacting-with-developer-lightspeed-for-rhdh/master.adoc +++ b/titles/integrate_interacting-with-developer-lightspeed-for-rhdh/master.adoc @@ -2,16 +2,17 @@ :_mod-docs-category: Integrate include::artifacts/attributes.adoc[] + :context: interacting-with-developer-lightspeed-for-rhdh -:imagesdir: images :title: Interacting with {ls-brand-name} :subtitle: Leverage Artificial Intelligence (AI)-driven expertise of the {ls-brand-name} ({ls-short}) virtual assistant to help you use {product} ({product-very-short}) -:abstract: Leverage Artificial Intelligence (AI)-driven expertise of the {ls-brand-name} ({ls-short}) virtual assistant to help you use {product} ({product-very-short}) +:abstract: {ls-brand-name} ({ls-short}) is an AI-powered virtual assistant for {product} ({product-very-short}). You can interact with {ls-short} to explore {product-very-short} capabilities in detail. + [id="{context}"] = {title} [role="_abstract"] -{ls-brand-name} ({ls-short}) is an AI-powered virtual assistant for {product} ({product-very-short}). You can interact with {ls-short} to explore {product-very-short} capabilities in detail. +{abstract} include::modules/shared/con-about.adoc[leveloffset=+1] diff --git a/titles/integrate_interacting-with-model-context-protocol-tools-for-rhdh/master.adoc b/titles/integrate_interacting-with-model-context-protocol-tools-for-rhdh/master.adoc index 25a108825d3..b6f8c63f923 100644 --- a/titles/integrate_interacting-with-model-context-protocol-tools-for-rhdh/master.adoc +++ b/titles/integrate_interacting-with-model-context-protocol-tools-for-rhdh/master.adoc @@ -2,8 +2,8 @@ :_mod-docs-category: Integrate include::artifacts/attributes.adoc[] + :context: interacting-with-model-context-protocol-tools-for-rhdh -:imagesdir: images :title: Interacting with Model Context Protocol tools for {product} :subtitle: Leveraging the Model Context Protocol (MCP) server to integrate {product} ({product-very-short}) with AI clients :abstract: Leverage the Model Context Protocol (MCP) server to integrate {product} with AI clients through a standardized method for accessing {product-very-short} information and workflows using defined MCP tools. @@ -14,4 +14,42 @@ include::artifacts/attributes.adoc[] [role="_abstract"] {abstract} -include::assemblies/integrate_interacting-with-model-context-protocol-tools-for-rhdh/assembly-interact-with-model-context-protocol-tools-for-rhdh.adoc[leveloffset=+1] +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/con-understanding-model-context-protocol.adoc[leveloffset=+1] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-install-the-mcp-server-and-tool-plugins-in-rhdh.adoc[leveloffset=+1] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-model-context-protocol-in-rhdh.adoc[leveloffset=+1] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-configure-mcp-clients-to-access-the-rhdh-server.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-rhdh-data-using-the-software-catalog-mcp-tools.adoc[leveloffset=+1] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-fetching-entities-using-fetch-catalog-entities.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-registering-entities-using-catalog-register-tool.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-unregistering-entities-using-catalog-unregister-tool.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-retrieving-software-template-metadata.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-accessing-and-analyzing-documentation-using-the-techdocs-mcp-tools.adoc[leveloffset=+1] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-retrieving-techdocs-urls-and-metadata-using-fetch-techdocs.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-measuring-documentation-gaps-using-analyze-techdocs-coverage.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-finding-a-specific-techdoc-using-retrieve-techdocs-content.adoc[leveloffset=+2] + +== Troubleshoot MCP server and client problems + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-verify-successful-installation-of-mcp-plugins.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/ref-checking-mcp-tool-logs-for-status-and-errors.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/con-understand-and-respond-to-mcp-tool-error-messages.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-resolve-the-model-does-not-support-tool-calling-error.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-resolve-authentication-issues-when-tools-are-not-found.adoc[leveloffset=+2] + +include::modules/integrate_interacting-with-model-context-protocol-tools-for-rhdh/proc-resolve-nonsensical-mcp-tool-output.adoc[leveloffset=+2] diff --git a/titles/observability_adoption-insights-in-rhdh/master.adoc b/titles/observability_adoption-insights-in-rhdh/master.adoc index 3ece7c1b76b..6f3ddc4c6c8 100644 --- a/titles/observability_adoption-insights-in-rhdh/master.adoc +++ b/titles/observability_adoption-insights-in-rhdh/master.adoc @@ -2,34 +2,19 @@ :_mod-docs-category: Observability include::artifacts/attributes.adoc[] + :context: adoption-insights-in-rhdh -:imagesdir: images :title: Adoption Insights in {product} :subtitle: Delivering detailed analytics on adoption and engagement within your internal developer portal :abstract: As a platform engineer, you can configure Adoption Insights in {product} ({product-very-short}) to visualize key metrics and trends to get information about the usage of {product-very-short} in your organization. + [id="{context}"] = {title} [role="_abstract"] -Visualize key metrics and trends to monitor adoption, engagement, and usage of {product} through the Adoption Insights plugin dashboard. - -The {product} instance includes the Adoption Insights plugin preinstalled and enabled by default. - -As organizations generate an increasing number of data events, there is a growing need for detailed insights into the adoption and engagement metrics of the internal developer portal. These insights help platform engineers make data-driven decisions to improve performance and usability, and convert them into actionable insights. - -You can use Adoption Insights in {product} to visualize key metrics and trends to get information about the usage of {product-short} in your organization. The information provided by Adoption Insights in {product-short} helps you pinpoint areas of improvement, highlights popular features, and evaluates progress toward adoption goals. You can also monitor user growth against licensed users and identify trends over time. - -The Adoption Insights dashboard in {product-short} includes the following cards: - -* *Active users* -* *Total number of users* -* *Top catalog entities* -* *Top 3 templates* -* *Top 3 techdocs* -* *Top 3 plugins* -* *Portal searches* +{abstract} -image::observability_adoption-insights-in-rhdh/adoption-insights.jpg["Adoption Insights dashboard in {product-short}"] +include::modules/observability_adoption-insights-in-rhdh/con-adoption-insights-overview.adoc[leveloffset=+1] include::modules/observability_adoption-insights-in-rhdh/proc-enable-the-adoption-insights-plugin.adoc[leveloffset=+1] diff --git a/titles/observability_audit-logs-in-rhdh/master.adoc b/titles/observability_audit-logs-in-rhdh/master.adoc index 6991729e283..eae1024f1f9 100644 --- a/titles/observability_audit-logs-in-rhdh/master.adoc +++ b/titles/observability_audit-logs-in-rhdh/master.adoc @@ -2,44 +2,19 @@ :_mod-docs-category: Observability include::artifacts/attributes.adoc[] + :context: audit-logs-in-rhdh -:imagesdir: images :title: Audit logs in {product} :subtitle: Tracking user activities, system events, and data changes with {product} audit logs -:abstract: As a {product} ({product-very-short}) administrator, you can track user activities, system events, and data changes with {product-short} audit logs. +:abstract: Track user activities, system events, and data changes with audit logs to enhance security, automate compliance, and debug issues. + [id="{context}"] = {title} [role="_abstract"] -Track user activities, system events, and data changes with audit logs to enhance security, automate compliance, and debug issues. - -Audit logs are a chronological set of records documenting the user activities, system events, and data changes that affect your {product} users, administrators, or components. -Administrators can view {product-short} audit logs in the {ocp-short} web console to monitor scaffolder events, changes to the RBAC system, and changes to the Catalog database. -Audit logs include the following information: - -* Name of the audited event -* Actor that triggered the audited event, for example, terminal, port, IP address, or hostname -* Event metadata, for example, date, time -* Event status, for example, `success`, `failure` -* Severity levels, for example, `info`, `debug`, `warn`, `error` - -You can use the information in the audit log to achieve the following goals: - -Enhance security:: -Trace activities, including those initiated by automated systems and software templates, back to their source. -Know when software templates are executed, and the details of application and component installations, updates, configuration changes, and removals. - -Automate compliance:: -Use streamlined processes to view log data for specified points in time for auditing purposes or continuous compliance maintenance. - -Debug issues:: -Use access records and activity details to fix issues with software templates or plugins. +{abstract} -[NOTE] -==== -Audit logs are not forwarded to the internal log store by default because the internal log store does not offer secure storage. -You are responsible for ensuring that the system to which you forward audit logs is compliant with your organizational and governmental regulations, and is properly secured. -==== +include::modules/observability_audit-logs-in-rhdh/con-audit-logs-overview.adoc[leveloffset=+1] include::modules/observability_audit-logs-in-rhdh/proc-configure-audit-logs-for-rhdh-on-ocp.adoc[leveloffset=+1] diff --git a/titles/observability_evaluate-project-health-using-scorecards/master.adoc b/titles/observability_evaluate-project-health-using-scorecards/master.adoc index 87070784076..af30dfd3fdd 100644 --- a/titles/observability_evaluate-project-health-using-scorecards/master.adoc +++ b/titles/observability_evaluate-project-health-using-scorecards/master.adoc @@ -2,11 +2,12 @@ :_mod-docs-category: Observability include::artifacts/attributes.adoc[] + :context: evaluate-project-health-using-scorecards -:imagesdir: images :title: Evaluate project health using Scorecards :subtitle: Setting up, configuring, and managing customizable Project Health Scorecards :abstract: When using {product} ({product-very-short}), you can visualize the health, security posture, and compliance of your software components so that you can centrally monitor Key Performance Indicators (KPIs) collected from third-party systems like GitHub and Jira without consulting multiple external tools. + [id="{context}"] = {title} diff --git a/titles/observability_monitoring-and-logging/master.adoc b/titles/observability_monitoring-and-logging/master.adoc index ea16a2e2b75..f003df483c9 100644 --- a/titles/observability_monitoring-and-logging/master.adoc +++ b/titles/observability_monitoring-and-logging/master.adoc @@ -2,22 +2,20 @@ :_mod-docs-category: Observability include::artifacts/attributes.adoc[] + :context: monitoring-and-logging -:imagesdir: images :title: Monitoring and logging :subtitle: Tracking performance and capturing insights with monitoring and logging tools in {product} -:abstract: As a {product} ({product-very-short}) Operations or Project Manager, you can monitor performance and gather insights by using the {product} monitoring and logging tools. +:abstract: Monitor performance and gather insights from {product-short} by configuring log levels, enabling metrics on {ocp-short}, and integrating with cloud monitoring services on {aws-short} and {aks-short}. [id="{context}"] = {title} [role="_abstract"] -Monitor performance and gather insights from {product-short} by configuring log levels, enabling metrics on {ocp-short}, and integrating with cloud monitoring services on {aws-short} and {aks-short}. +{abstract} -//log levels info include::assemblies/observability_monitoring-and-logging/assembly-log-levels.adoc[leveloffset=+1] -//metrics include::assemblies/observability_monitoring-and-logging/assembly-enable-observability-for-rhdh-on-ocp.adoc[leveloffset=+1] include::assemblies/observability_monitoring-and-logging/assembly-monitoring-and-logging-rhdh-on.adoc[leveloffset=+1] diff --git a/titles/observability_telemetry-data-collection-and-analysis/master.adoc b/titles/observability_telemetry-data-collection-and-analysis/master.adoc index 6500b2bbfd2..5d968989bde 100644 --- a/titles/observability_telemetry-data-collection-and-analysis/master.adoc +++ b/titles/observability_telemetry-data-collection-and-analysis/master.adoc @@ -2,8 +2,8 @@ :_mod-docs-category: Observability include::artifacts/attributes.adoc[] + :context: telemetry-data-collection-and-analysis -:imagesdir: images :title: Telemetry data collection and analysis :subtitle: Collecting and analyzing web analytics and system observability data to enhance {product} experience :abstract: As a {product} ({product-very-short}) administrator, you can collect and analyze two types of telemetry data to enhance the {product} experience: web analytics by using Segment and system observability by using OpenTelemetry. diff --git a/titles/upgrade_upgrade-rhdh/master.adoc b/titles/upgrade_upgrade-rhdh/master.adoc index b3d397fe175..5db1838210b 100644 --- a/titles/upgrade_upgrade-rhdh/master.adoc +++ b/titles/upgrade_upgrade-rhdh/master.adoc @@ -2,11 +2,12 @@ :_mod-docs-category: Upgrade include::artifacts/attributes.adoc[] + :title: Upgrading {product} :subtitle: Upgrading a {product} instance to a later version by using either the Operator or Helm chart :abstract: You can upgrade a {product} ({product-very-short}) instance to a later version by using either the Operator or Helm chart. :context: upgrade-rhdh -:imagesdir: images + [id="{context}"] = {title}