diff --git a/.claude/skills/abstract/SKILL.md b/.claude/skills/abstract/SKILL.md index 20ce057e45a..56f0a4e419c 100644 --- a/.claude/skills/abstract/SKILL.md +++ b/.claude/skills/abstract/SKILL.md @@ -1,38 +1,35 @@ --- -name: abstract +name: Abstracts description: Review or write an abstract (also called a short description) for a documentation module --- -# Review or write abstract +#### Overview -## Overview +An abstract is the first paragraph of a documentation module that helps readers and AI-powered search tools find the information they need and confirm they are in the right place. -Review and improve the abstract for this file. Write it if it doesn't exist yet. +An abstract follows after the module heading and is prefixed by the `[role="_abstract"]` AsciiDoc tag. -## Definition of an abstract +Abstracts typically contain: +- The "What" - what the content covers +- The "Why" - why it matters or when to use it +- Where appropriate, an example use case -An abstract is the first paragraph of the module. -It follows after the module heading. -It is prefixed by the `[role="_abstract"]` AsciiDoc tag. +**Guidelines:** +- Do not simply repeat the heading of the module; build upon it +- Avoid self-referential language (e.g., "This procedure...", "This module...", "This table...") +- Avoid feature-centric language (e.g., "This feature...") +- Do not use sentence fragments +- When addressing the user, address them as "you" +- Follow these length constraints: 50-300 characters, 1-2 sentences, a single paragraph -Abstracts, also called short descriptions, help readers and AI-powered search tools find the information that they need and confirm that they are in the right place. +**Module-type-specific guidelines:** +- `CONCEPT` modules: See [references/concept.md](references/concept.md) +- `PROCEDURE` modules: See [references/procedure.md](references/procedure.md) +- `REFERENCE` modules: See [references/reference.md](references/reference.md) -Abstracts typically contain the following information: +#### Instructions -- The "What" -- The "Why" -- Where appropriate, an example use case - -## Instructions +Review and improve the abstract for this file. Write it if it doesn't exist yet. -When reviewing or writing the abstract, follow these principles: +When reviewing or writing the abstract, follow the guidelines in the Overview section. -- Do not simply repeat the heading of the module; build upon it. -- Avoid self-referential language (for example: avoid "This procedure...", "This module...", "This table..."). -- Avoid feature-centric language (for example: avoid "This feature..."). -- Do not use sentence fragments. -- When addressing the user, address them as "you". -- Follow these length constraints: 50-300 characters, 1-2 sentences, a single paragraph. -- For module-type-specific abstract rules, look up the reference file that matches the `:_mod-docs-content-type:` AsciiDoc attribute in the module (open and apply that file in full): - - `CONCEPT` → [references/concept.md](references/concept.md) - - `PROCEDURE` → [references/procedure.md](references/procedure.md) - - `REFERENCE` → [references/reference.md](references/reference.md) +For module-type-specific abstract rules, look up the reference file that matches the `:_mod-docs-content-type:` AsciiDoc attribute in the module (open and apply that file in full). diff --git a/.claude/skills/heading/SKILL.md b/.claude/skills/heading/SKILL.md index 8463d1eb5fc..d73d8cdbd2f 100644 --- a/.claude/skills/heading/SKILL.md +++ b/.claude/skills/heading/SKILL.md @@ -1,57 +1,51 @@ --- -name: heading +name: Headings description: Review or write heading --- +#### Overview -# Review or write heading +Headings should be clear, concise, and use familiar keywords that help users understand what the content covers. -## Overview +**General principles for all headings:** +- Make the heading 3-11 words long +- Use clear headings with familiar keywords for users +- Ensure the heading summarizes the contents of the part of the documentation it introduces -Review and improve the heading for this file. -Write it if it doesn't exist yet. +**Module-type-specific principles:** +- **Concepts** (con_*.adoc): Do not start with a gerund or verb. Use a noun phrase and include nouns that appear in the body text. +- **Procedures** (proc_*.adoc): Start the heading with a gerund (e.g., "Configuring...", "Creating..."). +- **References** (ref_*.adoc): Do not start with a gerund or verb. Include nouns that appear in the body text. +- **Assemblies** containing procedures: Start the heading with a gerund. +- **Assemblies** containing only concepts or references: Do not start with a gerund or verb. Use a noun phrase. -## Instructions +#### Examples -Follow these general principles for all headings: +**Concept headings:** +- Provisioning methods in {Project} +- Security considerations in {Project} +- Host groups overview -* Make the heading 3-11 words long. -* Use clear headings with familiar keywords for users. -* Ensure the heading summarizes the contents of the part of the documentation it introduces. +**Procedure headings:** +- Deploying SSH keys during provisioning +- Opening required ports +- Configuring pull-based transport for remote execution -Additional principles for specific heading types: +**Reference headings:** +- Job template examples and extensions +- Host parameter hierarchy +- Operating system requirements -* For concepts (con_*.adoc): Do not start the heading with a gerund or verb. Use a noun phrase and include nouns or noun phrases that appear in the body text. -* For procedures (proc_*.adoc): Start the heading with a gerund. -* For references (ref_*.adoc): Do not start the heading with a gerund or verb. Include nouns that appear in the body text. -* For assemblies (the first con_*.adoc in an assembly_*.adoc) that contain at least one procedure: Start the heading with a gerund. -* For assemblies (the first con_*.adoc in an assembly_*.adoc) that contain only concepts or references: Do not start the heading with a gerund or verb. Use a noun phrase. +**Assembly headings:** +- Connecting AI applications to the MCP server for {Project} +- Configuring {SmartProxy} and hosts to authenticate with SSH certificates during remote execution +- Content and patch management with {Project} -## Post-command cleanup +#### Instructions -* If you rename a heading, use the `.cursor/skills/refactor-adoc.md` command to update the module's ID, filename, and all references and links to the module in the repository. +Review and improve the heading for this file. Write it if it doesn't exist yet. -## Examples of good headings +Follow the principles outlined in the Overview section above. -Concept headings: +#### Post-command cleanup -* Provisioning methods in {Project} -* Security considerations in {Project} -* Host groups overview - -Procedure headings: - -* Deploying SSH keys during provisioning -* Opening required ports -* Configuring pull-based transport for remote execution - -Reference headings: - -* Job template examples and extensions -* Host parameter hierarchy -* Operating system requirements - -Assembly headings: - -* Connecting AI applications to the MCP server for {Project} -* Configuring {SmartProxy} and hosts to authenticate with SSH certificates during remote execution -* Content and patch management with {Project} +If you rename a heading, use the `/refactor-adoc` command to update the module's ID, filename, and all references and links to the module in the repository. diff --git a/.claude/skills/personas/SKILL.md b/.claude/skills/personas/SKILL.md new file mode 100644 index 00000000000..9fcd4761548 --- /dev/null +++ b/.claude/skills/personas/SKILL.md @@ -0,0 +1,37 @@ +--- +name: User personas +description: Understand and apply Foreman user personas when writing documentation +--- +#### Overview + +Foreman documentation targets different user personas with varying responsibilities and permissions. Understanding the target persona helps ensure documentation addresses the user's needs and capabilities appropriately. + +**User persona:** +- Has limited permissions +- Runs regular Foreman operations +- Example responsibilities: managing content, provisioning hosts, managing hosts + +**Admin persona:** +- Has unlimited permissions, including root access to the Foreman Server +- Example responsibilities: managing Foreman server, configuring system settings + +**Architect persona:** +- Has no permissions, does not perform practical administrative or management tasks +- Example responsibilities: evaluating whether Foreman/Katello meets organizational needs, planning deployment architecture + +**When to consider personas:** +- Use persona-appropriate language and assume persona-appropriate knowledge +- For User persona: Focus on tasks they can perform with limited permissions +- For Admin persona: Include system administration and configuration tasks +- For Architect persona: Provide planning, architecture, and decision-making information + +#### Instructions + +When reviewing or writing documentation, identify the target persona and ensure: + +1. The content matches the persona's permission level and responsibilities +2. Technical depth is appropriate for the persona's expertise +3. Prerequisites assume the persona's typical environment +4. Examples and use cases reflect the persona's real-world scenarios + +If content targets multiple personas, consider splitting it or clearly indicating which sections apply to which personas. diff --git a/.claude/skills/prerequisites/SKILL.md b/.claude/skills/prerequisites/SKILL.md index 597faa96eec..8348b1782dc 100644 --- a/.claude/skills/prerequisites/SKILL.md +++ b/.claude/skills/prerequisites/SKILL.md @@ -1,43 +1,51 @@ --- -name: prerequisites +name: Prerequisites description: Review or add prerequisites --- -# Review or add prerequisites - -## Overview - -Review prerequisites in this file to make sure they are labeled correctly and use consistent formatting. Prerequisites are a bulleted list of conditions that must be satisfied before the user starts the procedure. - -## Instructions +#### Overview + +Prerequisites are a bulleted list of conditions that must be satisfied before the user starts a procedure. Only procedure modules (proc_*.adoc) can include a `.Prerequisites` section. + +**What prerequisites are:** +- Checks that are true before the user begins +- Conditions the user must have completed beforehand +- Items the user must have ready +- Actions that the user, another person, or technology has completed before the user can begin + +**Formatting guidelines:** +- The first word of each prerequisite must be capitalized +- Each prerequisite must start with a bullet point +- Prerequisites must not use imperative voice +- If prerequisites are full sentences, end all with a period +- If prerequisites are sentence fragments, do not use any end punctuation +- Use passive voice for prerequisites representing an action not completed by the current user +- Prerequisites must use parallel language (all sentences or all fragments, not mixed) + +**Content guidelines:** +- Focus on relevant prerequisites that users might not otherwise be aware of +- Do not list obvious prerequisites +- Do not include procedure steps in prerequisites +- Do not exceed 10 prerequisites + +#### Examples + +**Good prerequisites:** +- The `kernelcare` package is installed on your hosts. +- The base system of the {SmartProxy} is registered to the newly upgraded {ProjectServer}. +- Your user account has a role that grants the `view_policies` permission. +- You are logged in to the registry.redhat.io container registry. +- If you use `dzdo` for Ansible jobs, the `community.general` Ansible collection must be installed. + +**Bad prerequisites (and why):** +- You are logged in. *(Obvious prerequisite)* +- The host is registered to {Project}. *(Obvious prerequisite)* +- At least one host exists in {Project}. *(Obvious prerequisite)* +- Ensure the `kernelcare` package is installed on your hosts. *(Uses imperative voice)* +- Install the `kernelcare` package on your hosts. *(Phrased like a procedure step)* + +#### Instructions 1. Only process procedure modules (proc_*.adoc). These are the only modules that can include a `.Prerequisites` section. -2. If a section titled `.Prerequisites` exists in the file, ensure it uses consistent formatting: - - The first word of each prerequisite must be capitalized. - - Each prerequisite must start with a bullet point. - - Prerequisites must not use imperative voice. - - If the prerequisites are full sentences, end all prerequisites with a period. - - If the prerequisites are sentence fragments, do not use any end punctuation. - - Use passive voice for prerequisites that represent an action that is not completed by the current user. For example, having a configuration enabled by a system admin. - - Prerequisites must use parallel language. For example, if one bullet is a complete sentence, write the other bullets as complete sentences. -2. If a section titled `.Prerequisites` exists in the file, ensure it contains information suitable for prerequisites: - - Prerequisites are checks that are true, checks that the user must have completed before they begin a procedure, or items that the user must have ready before beginning a procedure. Prerequisites can also be actions that the user, another person, or piece of technology has completed before the user can begin the procedure. - - Focus on relevant prerequisites that users might not otherwise be aware of. Do not list obvious prerequisites. - - Do not include procedure steps in prerequisites. If `.Prerequisites` includes a prerequisite that would be more suitable as a procedure step, move it to the `.Procedure` section. - - Do not exceed 10 prerequisites. If `.Prerequisites` includes more than 10 list items, flag this as an issue for human review. -3. If a procedure module does not include `.Prerequisites` section, scan the module to identify steps that meet criteria for prerequisites. If a step or steps like this exist, create a `.Prerequisites` section and rephrase the step or steps as prerequisites in this section. - -## Examples of good prerequisites - -* The `kernelcare` package is installed on your hosts. -* The base system of the {SmartProxy} is registered to the newly upgraded {ProjectServer}. -* Your user account has a role that grants the `view_policies` permission. -* You are logged in to the registry.redhat.io container registry. -* If you use `dzdo` for Ansible jobs, the `community.general` Ansible collection must be installed. - -## Examples of bad prerequisites - -* You are logged in. (This is an obvious prerequisite.) -* The host is registered to {Project}. (This is an obvious prerequisite.) -* At least one host exists in {Project}. (This is an obvious prerequisite.) -* Ensure the `kernelcare` package is installed on your hosts. (This prerequisite uses imperative voice.) -* Install the `kernelcare` package on your hosts. (This prerequisite is phrase like a procedure step.) +2. If a section titled `.Prerequisites` exists in the file, ensure it uses consistent formatting as described in the Overview. +3. If a section titled `.Prerequisites` exists in the file, ensure it contains information suitable for prerequisites. +4. If a procedure module does not include a `.Prerequisites` section, scan the module to identify steps that meet criteria for prerequisites. If such steps exist, create a `.Prerequisites` section and rephrase the steps as prerequisites. diff --git a/.claude/skills/refactor-adoc/SKILL.md b/.claude/skills/refactor-adoc/SKILL.md index edcd70f0ad4..d9d24f88af6 100644 --- a/.claude/skills/refactor-adoc/SKILL.md +++ b/.claude/skills/refactor-adoc/SKILL.md @@ -1,22 +1,30 @@ --- -name: refactor-adoc +name: Rename a module description: Refactor adoc file by a given title disable-model-invocation: true --- -# Refactor adoc file by a given title +#### Overview -## Overview - -Change the title (if present), ID (if present), and file name of an assembly, module, or snippet according to a new title. -Update the includes and any ID references across all documentation. - -Usage: +This command changes the title, ID, and filename of an assembly, module, or snippet according to a new title, and updates all includes and ID references across the documentation. +**Usage:** ``` /refactor-adoc @old-file.adoc "New title" ``` -## Instructions +**What it does:** +- Renames the file with the appropriate prefix (e.g., `proc_new-title.adoc`) +- Updates the ID if present (e.g., `[id="new-title_{context}"]`) +- Updates the title if present (e.g., `= New title`) +- Updates all `include::` directives that reference this file +- Updates all cross-references to the old ID throughout the documentation + +**When to use:** +- When you need to rename a documentation file to better match its content +- After significantly revising the content of a module +- When improving heading clarity requires filename changes + +#### Instructions Refactor the following `.adoc` file according to the following title. Follow these principles: diff --git a/.claude/skills/review-assembly-user-story/SKILL.md b/.claude/skills/review-assembly-user-story/SKILL.md index 302d84c713d..7da366f51e3 100644 --- a/.claude/skills/review-assembly-user-story/SKILL.md +++ b/.claude/skills/review-assembly-user-story/SKILL.md @@ -1,31 +1,43 @@ --- -name: review-assembly-user-story +name: An assembly covers a single user story description: Review assembly to ensure it follows the one-user-story principle --- -# Review assembly for single user story +#### Overview -## Overview - -Review an assembly file to ensure it describes a single user story. -If the assembly contains multiple user stories, split it into separate assembly files. - -Usage: +An assembly must cover a single user story - what the user wants to accomplish. When an assembly attempts to describe multiple user stories, it often loses focus, becomes too long, and can be confusing to follow. +**Usage:** ``` /review-assembly-user-story @assembly_example.adoc ``` -## Reasoning +**The one-user-story principle:** +- Each assembly should answer "What does the user want to accomplish?" +- All modules in an assembly should support the same goal +- Think from the user's perspective, not the product's features + +**When an assembly needs splitting:** +- It contains procedures for multiple independent tasks that users might want to do separately +- The concept module describes multiple distinct use cases or workflows +- Different sections serve different user goals +- Modules could be logically grouped into 2 or more cohesive assemblies + +**When an assembly is fine:** +- All procedures work toward a single user goal +- Procedures are sequential steps or alternative approaches for the same task +- Modules provide context, reference, or examples for one user story -An assembly must cover a single user story (what the user wants to accomplish). -When an assembly attempts to describe multiple user stories, it often loses its focus, becomes too long, and can be confusing to follow. +**Important considerations:** +- **Inverse operations belong together** - Creating and deleting, or enabling and disabling, are part of the same user story +- **Alternative methods belong together** - Multiple procedures for the same task (web UI, CLI, API) belong in the same assembly +- **Sequential workflows belong together** - If steps must be done in order for one goal, they belong in the same assembly -## Instructions +#### Instructions Review the assembly file to determine if it follows the one-user-story principle. Each assembly must describe a single user story. -### Step 1: Analyze the assembly +##### Step 1: Analyze the assembly Read the assembly file and: @@ -34,7 +46,7 @@ Read the assembly file and: 3. **Review included modules** - Do they all support the same user story, or do they describe separate independent tasks? 4. **Look for clear boundaries** - Are there sections that could stand alone as separate user stories? -### Step 2: Determine if splitting is needed +##### Step 2: Determine if splitting is needed The assembly needs splitting if: @@ -51,7 +63,7 @@ The assembly is fine if: - Procedures are sequential steps or alternative approaches for the same task - Modules provide context, reference, or examples for one user story -### Step 3: If splitting is needed +##### Step 3: If splitting is needed For each identified user story: @@ -61,7 +73,7 @@ For each identified user story: 4. **Update parent files** - Replace the original assembly include with includes for all new assemblies in the `master.adoc` file 5. **Delete the original assembly** - Once all content is split and includes are updated -### Step 4: Report findings +##### Step 4: Report findings Provide a summary that includes: @@ -69,19 +81,15 @@ Provide a summary that includes: - If splitting occurred, list the new assembly files created and their user stories - Any recommendations for improving the assembly structure -## Principles +#### Examples -- **One user story per assembly** - Each assembly should answer "What does the user want to accomplish?" -- **User-centric organization** - Think from the user's perspective, not the product's features -- **Cohesive modules** - All modules in an assembly should support the same goal - -## Examples - -### Good examples (single user story): +**Good examples (single user story):** - `assembly_backing-up-server-and-proxy.adoc` - User wants to back up their system - `assembly_configuring-email-notifications.adoc` - User wants to set up email notifications +- `assembly_managing-organizations.adoc` - User wants to create and delete organizations (inverse operations belong together) -### Important considerations: -- **Inverse operations belong together** - Creating and deleting (e.g., organizations), or enabling and disabling (e.g., external authentication) are part of the same user story, as users may want to revert their actions -- **Alternative methods belong together** - Multiple procedures for the same task (web UI, CLI, API) belong in the same assembly -- **Sequential workflows belong together** - If steps must be done in order for one goal, they belong in the same assembly +#### Principles + +- **One user story per assembly** - Each assembly should answer "What does the user want to accomplish?" +- **User-centric organization** - Think from the user's perspective, not the product's features +- **Cohesive modules** - All modules in an assembly should support the same goal diff --git a/.claude/skills/split-web-ui-cli/SKILL.md b/.claude/skills/split-web-ui-cli/SKILL.md index e7be5c5dd2a..079695e58ec 100644 --- a/.claude/skills/split-web-ui-cli/SKILL.md +++ b/.claude/skills/split-web-ui-cli/SKILL.md @@ -1,15 +1,33 @@ --- -name: split-web-ui-cli +name: Split a module into web UI and CLI modules description: Split a module into web UI and CLI modules disable-model-invocation: true --- -# Split a module into web UI and CLI modules +#### Overview -## Overview +When a procedure module contains both web UI steps (`.Procedure`) and CLI steps (`.CLI procedure`) in the same file, it should be split into two separate files for better modularity and clarity. -This file includes a web UI procedure (`.Procedure`) and a CLI procedure (`.CLI procedure`). Split it into two files: `proc_*-by-using-web-ui.adoc` and `proc_*-by-using-cli.adoc`. +**Usage:** +``` +/split-web-ui-cli @proc_example.adoc +``` -## Instructions +**What it creates:** +- `proc_original-name-by-using-web-ui.adoc` - Contains the web UI procedure +- `proc_original-name-by-using-cli.adoc` - Contains the CLI procedure + +**File structure:** +- Both files include the introduction text from the original file +- Both files have adjusted IDs: `[id="original-id-by-using-web-ui"]` and `[id="original-id-by-using-cli"]` +- Both files have adjusted headings: `= Original heading by using {ProjectWebUI}` and `= Original heading by using Hammer CLI` +- Both files have adjusted abstracts if needed + +**When to use:** +- When a single procedure file contains both `.Procedure` and `.CLI procedure` sections +- When you want to make procedures more modular and easier to maintain +- When users need to choose between different interfaces for the same task + +#### Instructions Follow these principles: diff --git a/.claude/skills/validate-contribution/SKILL.md b/.claude/skills/validate-contribution/SKILL.md new file mode 100644 index 00000000000..382dd6e840d --- /dev/null +++ b/.claude/skills/validate-contribution/SKILL.md @@ -0,0 +1,111 @@ +--- +name: Documentation conventions +description: Validate and apply Foreman documentation conventions +disable-model-invocation: false +--- +#### Overview + +Foreman documentation follows specific conventions for code, images, attributes, conditionals, and file structure. These conventions ensure consistency across all guides and enable multi-product builds from a single source. + +**Code conventions:** +- Use UTF-8 character encoding in source files +- Do not add trailing whitespace on lines or in files +- Surround user input with underscores to indicate variable input (e.g., `hammer organization create --name "_My_Organization_"`) + +**Images:** +- Save guide-specific images to `guides/doc-/images/` +- Save shared images to `guides/common/images/` +- Create editable diagrams using [diagrams.net](https://www.diagrams.net/) in `drawio` format, place in `guides/image-sources/`, export to SVG + +**AsciiDoc attributes:** +- Use attributes instead of hardcoded product names: `{Project}`, `{ProjectServer}`, `{SmartProxy}` instead of "Foreman", "Satellite", "Capsule" +- To use attributes in code blocks, add `subs="+quotes,attributes"` option +- Attribute files: `attributes.adoc`, `attributes-base.adoc`, `attributes-foreman-el.adoc`, `attributes-foreman-deb.adoc`, `attributes-katello.adoc`, `attributes-satellite.adoc`, `attributes-orcharhino.adoc` + +**Conditional content:** +- Use `ifdef::katello[]` to show content only for specific builds +- Use `ifndef::satellite[]` to hide content for specific builds +- Use comma for logic "or": `ifdef::katello,satellite[]` +- For context-specific content, combine `ifdef` and `ifeval` + +**File structure:** +- Follow the [Modular documentation framework](https://redhat-documentation.github.io/modular-docs/) +- **Assemblies** (`assembly_*.adoc`): User stories at the top of `common/` - do not nest assemblies +- **Concept modules** (`con_*.adoc`): Explain what and why, start with `:_mod-docs-content-type: CONCEPT` +- **Procedure modules** (`proc_*.adoc`): Explain how, start with `:_mod-docs-content-type: PROCEDURE` +- **Reference modules** (`ref_*.adoc`): Tables and options, start with `:_mod-docs-content-type: REFERENCE` +- **Snippets** (`snip_*.adoc`): Reusable text fragments without IDs + +#### Examples + +**Good attribute usage:** +```asciidoc +Navigate to {ProjectWebUI} and click *Hosts*. +``` + +**Bad (hardcoded):** +```asciidoc +Navigate to Satellite Web UI and click *Hosts*. +``` + +**Good conditional:** +```asciidoc +ifdef::katello[] +This feature requires the Katello plugin. +endif::[] +``` + +**Good user input:** +```asciidoc +hammer organization create --name "_My_Organization_" +``` + +#### Instructions + +When invoked (manually or automatically after making changes), perform validation and auto-fixes on modified `.adoc` files in `guides/`: + +**1. File Naming Validation** +- Check: Files in `guides/common/modules/` must have correct prefixes (`con_`, `proc_`, `ref_`, `snip_`) +- Auto-fix: Rename file and update all includes if prefix doesn't match content type + +**2. Content Type Attribute** +- Check: All modules (except snippets) must have content type on first line +- Auto-fix: Add missing content type based on file prefix + +**3. Module ID Format** +- Check: All modules (except snippets) must have ID: `[id="title-with-dashes_{context}"]` +- Auto-fix: Add or fix ID to match title and include `_{context}` suffix + +**4. Trailing Whitespace** +- Check: No trailing whitespace on any lines or at end of files +- Auto-fix: Remove all trailing whitespace + +**5. Hardcoded Product Names** +- Check: Scan for "Foreman", "Satellite", "orcharhino", "Capsule" in non-conditional context +- Auto-fix: Replace with appropriate attributes (`{Project}`, `{SmartProxy}`, etc.) + +**6. Vale Linting** +- Check: Run `vale` on modified files +- Report: List style violations (don't auto-fix, just report) + +**7. Build Test** +- Check: Ensure modified files build successfully +- Report: Show errors if build fails + +**Output format:** +``` +✅ Validated contribution +Fixed: + - Removed trailing whitespace from 3 files + - Added content type attribute to proc_example.adoc + +⚠ Warnings: + - Vale: 2 style issues in proc_example.adoc + +✓ Build: All modified files build successfully +``` + +**Skip validation when:** +- Changes are only to non-documentation files (Makefiles, scripts, config) +- Changes are only to `web/` directory +- Changes are to CONTRIBUTING.md diff --git a/.vale/styles/foreman-documentation/AvoidBrandedTerminology.yml b/.vale/styles/foreman-documentation/AvoidBrandedTerminology.yml index 84680352d8a..f7c92c66fdf 100644 --- a/.vale/styles/foreman-documentation/AvoidBrandedTerminology.yml +++ b/.vale/styles/foreman-documentation/AvoidBrandedTerminology.yml @@ -1,4 +1,4 @@ -# Suggest a replacement for downstream branded terms. +# Do not use branded terms in documentation. Use AsciiDoc attributes like {Project}, {ProjectServer}, and {SmartProxy} instead of product-specific names like "Satellite", "orcharhino", or "Capsule". This ensures the documentation works for all build targets. --- extends: substitution level: error diff --git a/.vale/styles/foreman-documentation/Capitalization.yml b/.vale/styles/foreman-documentation/Capitalization.yml index 7c6ce9104c9..924a25015c1 100644 --- a/.vale/styles/foreman-documentation/Capitalization.yml +++ b/.vale/styles/foreman-documentation/Capitalization.yml @@ -1,4 +1,4 @@ -# Suggest proper capitalization +# Use proper capitalization for product names and technical terms. For example, write "Ansible" not "ansible", "PostgreSQL" not "postgresql", and "lifecycle environment" not "Lifecycle Environment". Different capitalization may be required in specific contexts like Web UI buttons, settings, or templates. --- extends: substitution ignorecase: false diff --git a/.vale/styles/foreman-documentation/OneSentencePerLine.yml b/.vale/styles/foreman-documentation/OneSentencePerLine.yml index 0f5f46f1443..3b92e811b0d 100644 --- a/.vale/styles/foreman-documentation/OneSentencePerLine.yml +++ b/.vale/styles/foreman-documentation/OneSentencePerLine.yml @@ -1,4 +1,4 @@ -# Report that a line includes more than one sentence. +# Write one sentence per line. This makes diffs easier to read, simplifies reviewing changes, and helps track modifications to individual sentences. End each sentence with proper punctuation (period, question mark, or exclamation point) before starting a new line. --- extends: occurrence message: "Write one sentence per line." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5ad79998df..453acfc18d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,10 +19,8 @@ As a maintainer, I will: * Only merge PRs if the Github Actions are green. * Try to review PRs in a timely manner. * Keep non-trivial PRs open for at least 24 hours (72 hours if over the weekend) to allow for input from the community. -Examples of trivial PRs: Fixing a typo, fixing markup, fixing links, updating release version configuration files (e.g., `web/releases/*.json`). -Time-critical release changes, such as release notes, can be merged without waiting 24 hours, but still require peer review and an ACK. +Examples of trivial PRs: Fixing a typo, fixing markup, or fixing links. Non-trivial PRs might not only benefit from additional review but they also represent an opportunity for community members to ask questions and learn. -All PRs, including trivial ones, require peer review and an ACK before merging. ## Pull request checklists @@ -59,148 +57,8 @@ Each PR should undergo style review. * [ ] The PR conforms with the team's style guidelines. * [ ] The PR introduces documentation that describes a user story rather than a product feature. -## Foreman documentation conventions guide +## Additional resources -The `Foreman documentation conventions guide` describes guidelines specific to working on Foreman documentation. -It complements, but should not duplicate, the following resources: - -* The AsciiDoc, RedHat, and project-specific foreman-documentation style packages for the Vale linter. +* The AsciiDoc and RedHat style packages for the Vale linter. See [Vale for writers at Red Hat](https://redhat-documentation.github.io/vale-at-red-hat/docs/main/user-guide/introduction/). * [Red Hat supplementary style guide for product documentation](https://redhat-documentation.github.io/supplementary-style-guide/) - -### Code conventions - -Use the following markup conventions: - -* Use UTF-8 character encoding in source files. -* Do not add trailing whitespace on lines and in files. -Some editors can help with this. -For example, VS Code has multiple settings related to handling whitespaces. -Whitespace after partial files has to be handled in the file using the `include::` directive. -* Surround user input with underscores (`_`) to indicate variable input, for example, `hammer organization create --name "_My_Organization_"`. - -### Images - -Each guide directory contains an `images/` subdirectory with `images/common` symlink into the `common/images/` directory. - -* Save images local to the guide to the `images/` directory. -* Save images which are supposed to be reused across guides to the `images/common/` directory. - -You can create upstream diagrams using [diagrams.net](https://www.diagrams.net/). -Place the editable diagram in `drawio` format in `guides/image-sources/`. -For inclusion in the content, export diagrams to SVG and place them as described above. - -### AsciiDoc attributes for different build targets - -Because the content in this repository is shared between the upstream Foreman community and branded downstream products, many terms need to be written using AsciiDoc attributes. -For example, never write "Foreman", "Satellite", or "orcharhino" words directly, but use the `{Project}` attribute. - -The attributes used in this repository are defined in the following files: - -* [attributes.adoc](common/attributes.adoc): version definitions and includes for other attribute files. -* [attributes-base.adoc](common/attributes-base.adoc): base attributes common for all builds. -* [attributes-foreman-el.adoc](common/attributes-foreman-el.adoc): base overrides for foreman-el build. -* [attributes-foreman-deb.adoc](common/attributes-foreman-deb.adoc): base overrides for foreman-deb build. -* [attributes-katello.adoc](common/attributes-katello.adoc): base overrides for katello build. -* [attributes-satellite.adoc](common/attributes-satellite.adoc): base overrides for satellite build. -* [attributes-orcharhino.adoc](common/attributes-orcharhino.adoc): base overrides for orcharhino build. - -By default, attributes cannot be used in shell or code examples. -To use them, use the "attributes" keyword: - - [options="nowrap" subs="+quotes,attributes"] - ---- - # ls {AttributeName} - ---- - -### Conditional content - -If a piece of your content, such as a block, paragraph, warning, or chapter, is specific for a certain build target, use AsciiDoc conditionals to show or hide it. - -To show a piece of content only for the `katello` build: - - ifdef::katello[] - NOTE: This part is only relevant for Foreman with the Katello plugin. - endif::[] - -To hide a piece of content for `katello` but show it for all other builds: - - ifndef::katello[] - NOTE: This part is relevant for Foreman without the Katello plugin, but also Satellite and orcharhino. - endif::[] - -Use comma for logic "or": - - ifdef::katello,satellite[] - NOTE: This part is only relevant for deployments with Katello plugin or in Satellite environment. - endif::[] - -Some files are included in different contexts, there are attributes to find the correct context. -In these cases use both `ifdef` and `ifeval`: - - ifdef::foreman-el,foreman-deb[] - ifeval::["{context}" == "{project-context}"] - * A minimum of 4 GB RAM is required for {ProjectServer} to function. - endif::[] - endif::[] - -### File structure - -If you create a new file, use the file structure described here. - -Documentation in this directory follows a modular structure described in the [Modular documentation reference guide](https://redhat-documentation.github.io/modular-docs/). -To write new documentation, you can use [modular documentation templates](https://github.com/redhat-documentation/modular-docs/tree/main/modular-docs-manual/files) or copy an existing file from `guides/common/modules/` and adapt it. - -Included files are kept in the `common/` subdirectory and have prefixes to distinguish their type of content. - -Assemblies are kept at the top of the `common/` subdirectory: - -* [`assembly`](https://redhat-documentation.github.io/modular-docs/#forming-assemblies): Files starting with `assembly_` contain user stories and the modules required to accomplish those user stories. -See the [assembly template](https://raw.githubusercontent.com/redhat-documentation/modular-docs/master/modular-docs-manual/files/TEMPLATE_ASSEMBLY_a-collection-of-modules.adoc). -* In this repository, do not nest assemblies. This means that assemblies cannot contain includes of other assemblies. This guidance overrides the Modular documentation recommendations. -* Do not include assemblies without leveloffset or with leveloffset of two or more in `master.adoc` files. - -Modules are kept in the `common/modules/` subdirectory: - -* [`con`](https://redhat-documentation.github.io/modular-docs/#creating-concept-modules): Files starting with `con_` contain concepts and explain the _what_ and _why_. -Their first line contains the `:_mod-docs-content-type: CONCEPT` attribute. -See the [concept template](https://raw.githubusercontent.com/redhat-documentation/modular-docs/master/modular-docs-manual/files/TEMPLATE_CONCEPT_concept-explanation.adoc). -* [`proc`](https://redhat-documentation.github.io/modular-docs/#creating-procedure-modules): Files starting with `proc_` contain procedures and explain _how_ to achieve a specific goal. -Their first line contains the `:_mod-docs-content-type: PROCEDURE` attribute. -See the [procedure template](https://raw.githubusercontent.com/redhat-documentation/modular-docs/master/modular-docs-manual/files/TEMPLATE_PROCEDURE_doing-one-procedure.adoc). -* [`ref`](https://redhat-documentation.github.io/modular-docs/#creating-reference-modules): Files starting with `ref_` contain references and append other files, for example tables with options. -Their first line contains the `:_mod-docs-content-type: REFERENCE` attribute. -See the [reference template](https://raw.githubusercontent.com/redhat-documentation/modular-docs/master/modular-docs-manual/files/TEMPLATE_REFERENCE_reference-material.adoc). -* [`snip`](https://redhat-documentation.github.io/modular-docs/#using_text_snippets_or_text_fragments_writing-mod-docs): Files starting with `snip_` contain snippets that are reused throughout multiple guides, for example admonitions. -Snippets must not contain an ID. - -### Foreman user personas - -In documentation, a user persona is the target user who will be reading the documentation. -Understanding and identifying the target persona of a piece of content helps ensure that the documentation will properly address the user's needs and capabilities. - -Foreman users include people with varying responsibilities and permissions. -Especially in larger organizations, different tasks are performed by different teams. -Therefore, when contributing to Foreman documentation, it can be useful to be aware of and distinguish Foreman user personas. - -#### *User* persona - -* Has limited permissions -* Runs regular Foreman operations -* Example responsibilities: managing content, provisioning hosts, and managing hosts - -#### *Admin* persona - -* Has unlimited permissions, including root access to the Foreman Server -* Example responsibilities: managing Foreman server - -#### *Architect* persona - -* Has no permissions, does not perform practical administrative or management tasks -* Example responsibilities: determining whether Foreman/Katello meets the needs of their organization and planning the deployment - -### Further Information - -* [Contributing Guidelines for Github documentation](https://github.com/github/docs/blob/main/CONTRIBUTING.md) -* [theforeman.org/contribute](https://theforeman.org/contribute.html) -* [7 Git tips for technical writers](https://opensource.com/article/22/11/git-tips-technical-writers) diff --git a/Gemfile b/Gemfile index 039f9d01c19..0e1ace3f0cb 100644 --- a/Gemfile +++ b/Gemfile @@ -6,3 +6,7 @@ gem 'sass' # For TOC generation gem 'nokogiri' + +# For Contributors' Guide (markdown to HTML) +gem 'kramdown' +gem 'kramdown-parser-gfm' diff --git a/guides/Makefile b/guides/Makefile index eeca1098300..e57eca84c72 100644 --- a/guides/Makefile +++ b/guides/Makefile @@ -1,14 +1,21 @@ SHELL := /bin/bash -SUBDIRS = $(shell ls -d doc-*) +# All doc- directories except doc-Contributing (which has separate build system) +SUBDIRS = $(shell ls -d doc-* | grep -v 'doc-Contributing') +CONTRIBUTING_DIR = doc-Contributing -.PHONY: all clean html linkchecker linkchecker-tryer serve subdirs $(SUBDIRS) +.PHONY: all clean html linkchecker linkchecker-tryer serve subdirs $(SUBDIRS) contributing all: html -html: subdirs +html: subdirs contributing subdirs: $(SUBDIRS) +contributing: + @if [ -d "$(CONTRIBUTING_DIR)" ]; then \ + $(MAKE) -C "$(CONTRIBUTING_DIR)" html; \ + fi + $(SUBDIRS): $(MAKE) -C "$@" $(MAKECMDGOALS) @@ -16,6 +23,9 @@ clean: @for DIR in $(SUBDIRS) ; do \ $(MAKE) -s --directory="$$DIR" clean ; \ done + @if [ -d "$(CONTRIBUTING_DIR)" ]; then \ + $(MAKE) -s --directory="$(CONTRIBUTING_DIR)" clean ; \ + fi linkchecker: find build -type f -name index\*html | xargs linkchecker -r1 -f common/linkchecker.ini --check-extern diff --git a/guides/doc-Contributing/Makefile b/guides/doc-Contributing/Makefile new file mode 100644 index 00000000000..d699240417a --- /dev/null +++ b/guides/doc-Contributing/Makefile @@ -0,0 +1,26 @@ +SHELL := /bin/bash +BUILD_DIR = ../build/Contributing +OUTPUT = $(BUILD_DIR)/index.html + +# Find all skill files and vale rules dynamically +SKILL_FILES = $(shell find ../../.cursor/skills -name 'SKILL.md' 2>/dev/null) +VALE_RULES = $(shell find ../../.vale/styles/foreman-documentation -name '*.yml' 2>/dev/null) +SOURCES = build.rb template.html ../../CONTRIBUTING.md $(SKILL_FILES) $(VALE_RULES) + +.PHONY: all html clean serve + +all: html + +html: $(OUTPUT) + +$(OUTPUT): $(SOURCES) + @mkdir -p $(BUILD_DIR) + bundle exec ruby build.rb + +clean: + @rm -rf $(BUILD_DIR) + +serve: html + @echo "Serving Contributors' Guide at http://localhost:8813" + @echo "Press Ctrl+C to stop" + python3 -m http.server --directory $(BUILD_DIR) 8813 diff --git a/guides/doc-Contributing/README.md b/guides/doc-Contributing/README.md new file mode 100644 index 00000000000..8e04c632339 --- /dev/null +++ b/guides/doc-Contributing/README.md @@ -0,0 +1,158 @@ +# Contributors' Guide + +This directory contains a standalone guide for documentation contributors that consolidates all contribution resources into a single HTML page. + +## What's Included + +The guide automatically combines: + +- **CONTRIBUTING.md** - Introduction to contribution guidelines +- **Documentation Skills** - All slash commands in `.claude/skills/*/SKILL.md` +- **Vale Rules** - Project-specific linting rules in `.vale/styles/foreman-documentation/` + +Skills are automatically organized into categories in the generated guide for easier navigation. + +## Building Locally + +### Build the guide: + +```bash +cd guides/doc-Contributing +make html +``` + +Output: `guides/build/Contributing/index.html` + +**Note:** `make html` only rebuilds if source files have changed. If you want to force a fresh build, clean build artifacts first: + +```bash +make clean && make html +``` + +Otherwise, you'll get an error "Nothing to be done for 'html'" + +### View the guide: + +```bash +make serve +# Opens at http://localhost:8813 +``` + +## Auto-Rebuild + +The guide **automatically rebuilds** when source files change: + +### On GitHub (CI/CD) + +When you push changes to: +- `CONTRIBUTING.md` +- Any skill file (`.claude/skills/*/SKILL.md`) +- Any Vale rule (`.vale/styles/foreman-documentation/*.yml`) + +GitHub Actions will: +1. Detect the change via Makefile dependencies +2. Rebuild the guide automatically +3. Include it in PR previews + +No manual intervention needed! + +### Locally + +The Makefile tracks dependencies automatically. Running `make html` will: +- Check if source files changed since last build +- Rebuild only if needed +- Skip if already up-to-date + +## File Structure + +``` +guides/doc-Contributing/ +├── README.md # This file +├── Makefile # Build system with dependency tracking +├── build.rb # Ruby script: concatenate markdown → HTML +├── template.html # HTML5 template with CSS and TOC +└── build/ # Generated output (gitignored) + └── index.html # Final HTML guide +``` + +## How It Works + +1. **build.rb** reads and concatenates: + - CONTRIBUTING.md content + - All skill files (strips YAML frontmatter) + - Vale rules (formatted as YAML code blocks) + +2. **kramdown** converts the combined markdown to HTML + +3. **template.html** wraps the HTML with: + - Responsive CSS styling + - Table of contents sidebar + - Syntax highlighting + +4. Output written to `guides/build/Contributing/index.html` + +## Integration with Main Build + +The contributors' guide is built alongside other guides: + +```bash +# From repository root +make html # Builds all guides + contributors' guide +make build-foreman-el # Builds foreman-el guides + contributors' guide +``` + +The `guides/Makefile` excludes `doc-Contributing` from the BUILD matrix and builds it separately via the `contributing` target. + +## Troubleshooting + +### Make says "Nothing to be done for 'html'" + +This is normal! Make only rebuilds when source files have changed since the last build. + +**To force a rebuild:** +```bash +make clean && make html +``` + +**To verify auto-rebuild works:** +1. Modify a source file: `touch ../../CONTRIBUTING.md` +2. Run `make html` - should rebuild +3. Run `make html` again - should say "Nothing to be done" (correct!) + +### Build fails with "kramdown not found" + +Run `bundle install` from the repository root to install dependencies. + +### Output file not updating after source changes + +Check that source files are actually newer than the output: +```bash +stat -c '%Y' ../../CONTRIBUTING.md ../build/Contributing/index.html +``` + +The source timestamp should be larger (newer) than the output timestamp. + +### Serve port already in use + +The default port is 8813. Change it: +```bash +python3 -m http.server --directory ../build/Contributing 9000 +``` + +## Adding New Content + +### Add a new skill: + +1. Create `.claude/skills/new-skill/SKILL.md` +2. Run `make html` - automatically detected and included + +### Add a new Vale rule: + +1. Create `.vale/styles/foreman-documentation/NewRule.yml` +2. Run `make html` - automatically detected and included + +## Questions? + +- For build system questions: See `guides/common/Makefile` for AsciiDoc guide patterns +- For markdown syntax: See [kramdown documentation](https://kramdown.gettalong.org/) +- For contribution questions: Read the generated guide at `guides/build/Contributing/index.html` diff --git a/guides/doc-Contributing/build.rb b/guides/doc-Contributing/build.rb new file mode 100644 index 00000000000..b0d1f4ab7a2 --- /dev/null +++ b/guides/doc-Contributing/build.rb @@ -0,0 +1,448 @@ +#!/usr/bin/env ruby +# Build script for Contributors' Guide +# Concatenates markdown sources and converts to HTML + +require 'kramdown' +require 'fileutils' + +# Paths +ROOT_DIR = File.expand_path('../..', __dir__) +GUIDES_BUILD_DIR = File.join(File.dirname(__dir__), 'build') +OUTPUT_DIR = File.join(GUIDES_BUILD_DIR, 'Contributing') +OUTPUT_FILE = File.join(OUTPUT_DIR, 'index.html') +TEMPLATE_FILE = File.join(__dir__, 'template.html') + +# Source files +CONTRIBUTING_MD = File.join(ROOT_DIR, 'CONTRIBUTING.md') +SKILLS_DIR = File.join(ROOT_DIR, '.claude', 'skills') +VALE_RULES_DIR = File.join(ROOT_DIR, '.vale', 'styles', 'foreman-documentation') + +def strip_frontmatter(content) + # Remove YAML frontmatter between --- delimiters + content.sub(/\A---\s*\n.*?\n---\s*\n/m, '') +end + +def extract_visible_sections(content) + # Extract sections meant to be visible (#### Overview and #### Examples) + lines = content.lines + result = [] + capturing = false + section_content = [] + in_code_block = false + + lines.each do |line| + # Track code blocks (````) + if line.strip.start_with?('```') + in_code_block = !in_code_block + section_content << line if capturing + next + end + + # Skip heading detection inside code blocks + if in_code_block + section_content << line if capturing + next + end + + # Check if this is a level-4 heading + if line =~ /^####\s+(.+)$/ + heading = $1.strip + + # Save previous section if we were capturing + if capturing && !section_content.empty? + result.concat(section_content) + section_content = [] + end + + # Start capturing if this is Overview or Examples (case-insensitive, flexible) + if heading =~ /^Overview$/i || heading =~ /^Examples/i + capturing = true + section_content << line + else + capturing = false + end + # Check if this is a heading of level 1-3 (stops any section) + elsif line =~ /^#\{1,3\}\s+/ + # Save previous section if we were capturing + if capturing && !section_content.empty? + result.concat(section_content) + section_content = [] + end + capturing = false + # Regular content line + elsif capturing + section_content << line + end + end + + # Don't forget the last section + if capturing && !section_content.empty? + result.concat(section_content) + end + + result.join +end + +def extract_ai_sections(content) + # Extract sections meant for AI tools (everything EXCEPT Overview and Examples) + lines = content.lines + result = [] + capturing = true # Start capturing by default + section_content = [] + in_code_block = false + + lines.each do |line| + # Track code blocks (````) + if line.strip.start_with?('```') + in_code_block = !in_code_block + section_content << line if capturing + next + end + + # Skip heading detection inside code blocks + if in_code_block + section_content << line if capturing + next + end + + # Check if this is a level-4 heading + if line =~ /^####\s+(.+)$/ + heading = $1.strip + + # Save previous section if we were capturing + if capturing && !section_content.empty? + result.concat(section_content) + section_content = [] + end + + # Stop capturing if this is Overview or Examples (opposite of visible sections) + if heading =~ /^Overview$/i || heading =~ /^Examples/i + capturing = false + else + capturing = true + section_content << line + end + # Check if this is a heading of level 1-3 (stops any section) + elsif line =~ /^#\{1,3\}\s+/ + # Save previous section if we were capturing + if capturing && !section_content.empty? + result.concat(section_content) + section_content = [] + end + capturing = false + # Regular content line + elsif capturing + section_content << line + end + end + + # Don't forget the last section + if capturing && !section_content.empty? + result.concat(section_content) + end + + result.join +end + +def extract_frontmatter_field(content, field) + match = content.match(/\A---\s*\n(.*?)\n---\s*\n/m) + return nil unless match + + match[1].each_line do |line| + stripped = line.strip + prefix = "#{field}:" + next unless stripped.start_with?(prefix) + + return stripped[prefix.length..].strip + end + nil +end + +def categorize_skill(skill_dir_name) + # Map skill directory names to categories + categories = { + 'abstract' => 'AI skills for style guidelines', + 'heading' => 'AI skills for style guidelines', + 'prerequisites' => 'AI skills for style guidelines', + 'personas' => 'AI skills for style guidelines', + 'review-assembly-user-story' => 'AI skills for structure', + 'split-web-ui-cli' => 'AI skills for file management', + 'refactor-adoc' => 'AI skills for file management', + 'validate-contribution' => 'AI skills for contribution guidelines' + } + + categories[skill_dir_name] || 'Other' +end + +def read_skill_files + skills = [] + skipped = [] + return skills unless Dir.exist?(SKILLS_DIR) + + # Look for skills: .claude/skills/skill-name/SKILL.md + Dir.glob(File.join(SKILLS_DIR, '*', 'SKILL.md')).sort.each do |skill_file| + skill_dir_name = File.basename(File.dirname(skill_file)) + content = File.read(skill_file) + skill_name = extract_frontmatter_field(content, 'name') || skill_dir_name + content = strip_frontmatter(content) + + # Extract sections for human readers (Overview and Examples) + visible_content = extract_visible_sections(content) + + # Extract sections for AI tools (everything else) + ai_content = extract_ai_sections(content) + + # Skip skills that have no content at all + if visible_content.strip.empty? && ai_content.strip.empty? + skipped << skill_name + next + end + + skills << { + name: skill_name, + dir_name: skill_dir_name, + category: categorize_skill(skill_dir_name), + visible_content: visible_content, + ai_content: ai_content + } + end + + # Report skipped skills + unless skipped.empty? + puts " - Skipped #{skipped.length} skill(s) without any content:" + skipped.each { |name| puts " - #{name}" } + end + + skills +end + +def read_vale_rules + rules = [] + return rules unless Dir.exist?(VALE_RULES_DIR) + + Dir.glob(File.join(VALE_RULES_DIR, '*.yml')).sort.each do |rule_file| + rule_name = File.basename(rule_file, '.yml') + content = File.read(rule_file) + + # Extract description from YAML comments at the top + description = [] + yaml_content = [] + in_yaml = false + + content.lines.each do |line| + if line.strip.start_with?('#') && !in_yaml + # Comment line before YAML starts + description << line.sub(/^#\s*/, '').strip + elsif line.strip == '---' + # YAML document start + in_yaml = true + yaml_content << line + elsif in_yaml + # YAML content + yaml_content << line + end + end + + rules << { + name: rule_name, + description: description.join(' '), + yaml: yaml_content.join + } + end + + rules +end + +def build_markdown + markdown = [] + + # Add title + markdown << "# About the Foreman Documentation Contributors' Guide" + markdown << "" + markdown << "The Foreman Documentation Contributors' Guide consolidates contribution resources for the Foreman Documentation project. It consolidates resources from the following sources:" + markdown << " - [CONTRIBUTING.md](../../CONTRIBUTING.md)" + markdown << " - [Documentation Skills](../../.claude/skills)" + markdown << " - [Vale Rules for Foreman documentation](../../.vale/styles/foreman-documentation)" + markdown << "" + markdown << "The guide concatenates resources from the above sources into a single guide for easy reference. For more information, see the [README](README.md)." + markdown << "" + markdown << "Last updated: #{Time.now.strftime('%Y-%m-%d')}" + markdown << "" + markdown << "---" + markdown << "" + + # Add CONTRIBUTING.md + if File.exist?(CONTRIBUTING_MD) + markdown << File.read(CONTRIBUTING_MD) + markdown << "" + markdown << "---" + markdown << "" + end + + # Add skills section + skills = read_skill_files + unless skills.empty? + # Group skills by category + skills_by_category = skills.group_by { |skill| skill[:category] } + + # Define category order for consistent presentation + category_order = ['AI skills for style guidelines', 'AI skills for structure', 'AI skills for file management', 'AI skills for contribution guidelines', 'Other'] + + category_order.each do |category| + next unless skills_by_category[category] + + markdown << "# #{category}" + markdown << "" + markdown << "The repository includes specialized skills (slash commands) for common documentation tasks." + markdown << "These can be invoked in AI-assisted editors like Claude Code or Cursor." + markdown << "" + + skills_by_category[category].each do |skill| + markdown << "## #{skill[:name]}" + markdown << "" + + # Add visible content (Overview and Examples) first + unless skill[:visible_content].strip.empty? + markdown << skill[:visible_content] + markdown << "" + end + + # Add AI-specific content (Instructions, etc.) in a collapsible section + unless skill[:ai_content].strip.empty? + markdown << "
" + markdown << "View AI tool instructions" + markdown << "" + markdown << skill[:ai_content] + markdown << "" + markdown << "
" + markdown << "" + end + end + + markdown << "---" + markdown << "" + end + end + + # Add Vale rules section + rules = read_vale_rules + unless rules.empty? + markdown << "# Foreman-documentation custom Vale rules" + markdown << "" + markdown << "The following custom Vale rules enforce documentation standards specific to Foreman." + markdown << "Run the Vale linter locally or in CI to check AsciiDoc files against these rules." + markdown << "" + + rules.each do |rule| + markdown << "## #{rule[:name]}" + markdown << "" + + # Add description if it exists + unless rule[:description].empty? + markdown << rule[:description] + markdown << "" + end + + # Embed the YAML content in a collapsible section + markdown << "
" + markdown << "View rule definition" + markdown << "" + markdown << "
#{rule[:yaml]}
" + markdown << "" + markdown << "
" + markdown << "" + end + + markdown << "---" + markdown << "" + end + + markdown.join("\n") +end + +def convert_to_html(markdown_content) + # Convert markdown to HTML using kramdown + doc = Kramdown::Document.new( + markdown_content, + input: 'GFM', + syntax_highlighter: 'rouge', + syntax_highlighter_opts: { + line_numbers: false, + css_class: 'highlight' + }, + auto_ids: true, + toc_levels: (1..3).to_a + ) + + doc.to_html +end + +def generate_toc(markdown_content) + # Extract headings for TOC + toc = [] + markdown_content.each_line do |line| + # Match markdown headings (# through ###) + if line =~ /^(\#{1,3})\s+(.+)$/ + level = $1.length + title = $2.strip + # Create anchor from title + anchor = title.downcase.gsub(/[^\w\s-]/, '').gsub(/\s+/, '-') + toc << { level: level, title: title, anchor: anchor } + end + end + toc +end + +def inject_into_template(html_content, markdown_content) + unless File.exist?(TEMPLATE_FILE) + puts "Warning: Template file not found at #{TEMPLATE_FILE}" + puts "Creating basic HTML output without template." + return "#{html_content}" + end + + template = File.read(TEMPLATE_FILE) + toc = generate_toc(markdown_content) + + # Build TOC HTML + toc_html = toc.map do |item| + indent = ' ' * (item[:level] - 1) + "#{indent}
  • #{item[:title]}
  • " + end.join("\n") + + # Replace placeholders in template + template.gsub!('{{CONTENT}}', html_content) + template.gsub!('{{TOC}}', toc_html) + template.gsub!('{{BUILD_DATE}}', Time.now.strftime('%Y-%m-%d %H:%M:%S')) + + template +end + +# Main build process +def build + puts "Building Contributors' Guide..." + + # Create output directory + FileUtils.mkdir_p(OUTPUT_DIR) + + # Build markdown content + puts " - Concatenating markdown sources..." + markdown_content = build_markdown + + # Convert to HTML + puts " - Converting to HTML..." + html_content = convert_to_html(markdown_content) + + # Inject into template + puts " - Applying template..." + final_html = inject_into_template(html_content, markdown_content) + + # Write output + puts " - Writing to #{OUTPUT_FILE}..." + File.write(OUTPUT_FILE, final_html) + + puts "Build complete! Output: #{OUTPUT_FILE}" + puts " Size: #{File.size(OUTPUT_FILE)} bytes" +end + +# Run build +build diff --git a/guides/doc-Contributing/template.html b/guides/doc-Contributing/template.html new file mode 100644 index 00000000000..fc40c83247f --- /dev/null +++ b/guides/doc-Contributing/template.html @@ -0,0 +1,311 @@ + + + + + + Contributors' Guide - Foreman Documentation + + + +
    + + +
    + {{CONTENT}} + + +
    +
    + +