From c0fb11e7d05e0a434a1edfc412c617d127b7d5e2 Mon Sep 17 00:00:00 2001 From: Bruno Dias Date: Wed, 15 Apr 2026 12:18:07 +0100 Subject: [PATCH 01/13] Update availability information for AI-Generated Runbooks Clarified that AI-Generated Runbooks are only available in Runbook Automation SaaS and not in the Self-Hosted product. --- docs/manual/jobs/ai-generated-runbooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/jobs/ai-generated-runbooks.md b/docs/manual/jobs/ai-generated-runbooks.md index 0867ad064..a6634d7c1 100644 --- a/docs/manual/jobs/ai-generated-runbooks.md +++ b/docs/manual/jobs/ai-generated-runbooks.md @@ -19,7 +19,7 @@ AI-Generated Runbooks also helps experienced authors of Jobs by reducing the tim ## How to Enable AI-Generated Runbooks :::tip Product Availability -The _AI-Generated Runbooks_ feature is only available in Runbook Automation. +The _AI-Generated Runbooks_ feature is only available in Runbook Automation SaaS. It is not available in the Self-Hosted product. New users and current Runbook Automation customers are encouraged to [start a trial](https://www.pagerduty.com/sign-up/runbook-automation/) of Runbook Automation to try out the _AI-Generated Runbooks_ feature. ::: @@ -85,4 +85,4 @@ However, the generated content does not yet produce: 5. Notification Plugins 6. ROI Metric configuration -These features will be produced by _AI-Generated Runbooks_ in future releases. \ No newline at end of file +These features will be produced by _AI-Generated Runbooks_ in future releases. From ade7d3e5f065f3cf3bd42b0071b0936f231e74f9 Mon Sep 17 00:00:00 2001 From: Bruno Dias Date: Wed, 15 Apr 2026 13:23:48 +0100 Subject: [PATCH 02/13] Update docs/manual/jobs/ai-generated-runbooks.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/manual/jobs/ai-generated-runbooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/jobs/ai-generated-runbooks.md b/docs/manual/jobs/ai-generated-runbooks.md index a6634d7c1..b892958ee 100644 --- a/docs/manual/jobs/ai-generated-runbooks.md +++ b/docs/manual/jobs/ai-generated-runbooks.md @@ -19,7 +19,7 @@ AI-Generated Runbooks also helps experienced authors of Jobs by reducing the tim ## How to Enable AI-Generated Runbooks :::tip Product Availability -The _AI-Generated Runbooks_ feature is only available in Runbook Automation SaaS. It is not available in the Self-Hosted product. +The _AI-Generated Runbooks_ feature is only available in Runbook Automation SaaS. It is not available in Runbook Automation Self-Hosted. New users and current Runbook Automation customers are encouraged to [start a trial](https://www.pagerduty.com/sign-up/runbook-automation/) of Runbook Automation to try out the _AI-Generated Runbooks_ feature. ::: From 48ca4bbfa71024f637bdff174a62fcded3f51c38 Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:04:54 -0600 Subject: [PATCH 03/13] Add AI scaffolding for Claude Code and Cursor Part of the AI sprint: establish a shared .claude/ directory consumed by both Claude Code and Cursor so the repository is AI-ready. - .claude/docs/: canonical reference (writing style, local dev, infrastructure, PR feed, DocSearch filters) in kebab-case - .claude/skills/: starter workflow skills for the two automated pipelines (write-release-notes, generate-pr-feed) - .claude/rules/: auto-loaded rule enforcing release-notes PR discipline - .claude/README.md + CONTRIBUTING.md: structure and extension guide - CLAUDE.md (repo root): conventions index; AGENTS.md symlinks to it - .cursor/ symlinks docs, rules, skills into .claude/ so both tools draw from a single source of truth - dev-docs/DOCSEARCH_FILTERS_README.md and PR-FEED-README.md: now symlinks into .claude/docs/ (external paths preserved) - .github/copilot-instructions.md slimmed to a pointer into CLAUDE.md and .claude/docs/writing-style.md to prevent drift Made-with: Cursor --- .claude/CONTRIBUTING.md | 172 +++++++++ .claude/README.md | 87 +++++ .claude/docs/docsearch-filters.md | 109 ++++++ .claude/docs/infrastructure.md | 36 ++ .claude/docs/local-development.md | 64 ++++ .claude/docs/pr-feed.md | 371 +++++++++++++++++++ .claude/docs/writing-style.md | 79 +++++ .claude/rules/release-notes-pr.md | 35 ++ .claude/settings.json | 91 +++++ .claude/skills/generate-pr-feed/SKILL.md | 109 ++++++ .claude/skills/write-release-notes/SKILL.md | 116 ++++++ .cursor/docs | 1 + .cursor/rules | 1 + .cursor/skills | 1 + .github/copilot-instructions.md | 78 ++-- .gitignore | 1 + AGENTS.md | 1 + CLAUDE.md | 92 +++++ dev-docs/DOCSEARCH_FILTERS_README.md | 110 +----- dev-docs/PR-FEED-README.md | 372 +------------------- 20 files changed, 1392 insertions(+), 534 deletions(-) create mode 100644 .claude/CONTRIBUTING.md create mode 100644 .claude/README.md create mode 100644 .claude/docs/docsearch-filters.md create mode 100644 .claude/docs/infrastructure.md create mode 100644 .claude/docs/local-development.md create mode 100644 .claude/docs/pr-feed.md create mode 100644 .claude/docs/writing-style.md create mode 100644 .claude/rules/release-notes-pr.md create mode 100644 .claude/settings.json create mode 100644 .claude/skills/generate-pr-feed/SKILL.md create mode 100644 .claude/skills/write-release-notes/SKILL.md create mode 120000 .cursor/docs create mode 120000 .cursor/rules create mode 120000 .cursor/skills create mode 120000 AGENTS.md create mode 100644 CLAUDE.md mode change 100644 => 120000 dev-docs/DOCSEARCH_FILTERS_README.md mode change 100644 => 120000 dev-docs/PR-FEED-README.md diff --git a/.claude/CONTRIBUTING.md b/.claude/CONTRIBUTING.md new file mode 100644 index 000000000..3b55a56af --- /dev/null +++ b/.claude/CONTRIBUTING.md @@ -0,0 +1,172 @@ +# Contributing to the AI Setup + +Guide for maintaining and extending the Claude Code configuration in this repository. + +--- + +## Directory Structure + +``` +.claude/ +├── docs/ # Reference documentation (human-authored) +├── rules/ # Context-aware rules (auto-loaded by glob patterns) +├── skills/ # Workflow automation skills +└── settings.json # Permissions and hooks +``` + +Additional directories (`hooks/`, `commands/`, `artifacts/`, `specs/`, `plans/`) may be added as the setup grows. Create them only when populating them with content. + +--- + +## Writing Documentation + +### Doc Structure + +Docs live in `.claude/docs/` and are referenced from `CLAUDE.md`: + +```markdown +# Topic Name + +## Overview +Brief description + +## Section 1 +Content + +--- + +## Related Documentation +- Links to related files +``` + +### Guidelines + +- **One topic per file** — do not mix release-notes workflow with infrastructure. +- **Reference other docs** — link instead of duplicating. +- **Keep it actionable** — docs should help the agent do something. +- **Update `CLAUDE.md`** — add new docs to the Documentation table with a clear "when to read" trigger. + +### Naming Conventions + +- Use kebab-case: `pr-feed.md`, `release-notes.md`, `local-development.md`. +- Names should describe the topic, not the tool. +- When migrating from `dev-docs/`, preserve the original path as a symlink into `.claude/docs/` so external links keep working. + +--- + +## Creating New Rules + +### Rule Structure + +Rules live in `.claude/rules/` and auto-load based on file patterns: + +```markdown +--- +description: Rule description +globs: + - "docs/history/**/version-*.md" + - "**/CHANGELOG.md" +alwaysApply: false +--- + +# Rule Name + +## Mandatory +1. Rule 1 +2. Rule 2 + +## Before Completing +Verification steps +``` + +### Guidelines + +- **Rules enforce constraints** — use for things that MUST be followed (not suggestions). +- **Keep rules short** — reference docs files for detailed guidance. +- **Use specific globs** — only load when relevant files are touched. +- **Do not duplicate docs** — rules point to docs; docs contain the details. + +### When to Create a Rule vs. a Doc + +| Use a Rule | Use a Doc | +|---|---| +| Must be enforced every time | Reference material | +| Short (under 50 lines) | Detailed guidance | +| Applies to specific file types | Applies broadly | +| Contains DO / DON'T constraints | Contains how-to instructions | + +--- + +## Creating New Skills + +### Skill Structure + +Each skill lives in `.claude/skills//` with a `SKILL.md` file: + +``` +.claude/skills/my-skill/ +├── SKILL.md # Main skill definition (required) +├── checklist.md # Verification checklist (optional) +└── examples/ # Example files (optional) +``` + +### SKILL.md Format + +```markdown +--- +name: my-skill +description: One-line description of what the skill does +--- + +# Skill Name + +## When to Use +- Describe when this skill should be invoked + +## Process +### Phase 1: ... +### Phase 2: ... + +## Checklist +- [ ] Verification items +``` + +### Guidelines + +- **Keep skills under 500 lines** — move checklists and examples to separate files. +- **One skill, one responsibility** — split complex workflows into multiple skills. +- **Reference docs, do not duplicate** — point to `.claude/docs/` files instead of repeating content. +- **Include verification** — every skill should have a way to verify its output. +- **Test with the agent** — invoke the skill and verify it produces correct results. + +### Naming Conventions + +- Use kebab-case: `write-release-notes`, `generate-pr-feed`. +- Prefix with an action verb: `write-`, `generate-`, `create-`, `validate-`. + +--- + +## Updating CLAUDE.md + +`CLAUDE.md` is what the agent reads at the start of every session. When making changes: + +- **Add new docs** to the Documentation table with a "when to read" trigger. +- **Add new rules** to the Rules section. +- **Add new skills** to the Skills section with a one-line description. +- **Keep it concise** — `CLAUDE.md` is an index, not a manual. + +--- + +## Checklist for AI Setup Changes + +Before submitting changes to `.claude/`: + +- [ ] New docs added to `CLAUDE.md` Documentation table +- [ ] New rules added to `CLAUDE.md` Rules section +- [ ] New skills added to `CLAUDE.md` Skills section +- [ ] Skills under 500 lines (move checklists to separate files) +- [ ] No content duplicated between docs +- [ ] Rules reference docs (not duplicate them) +- [ ] File names follow kebab-case convention +- [ ] Internal links verified (relative paths resolve correctly) +- [ ] Tested with the agent (invoke skill, verify output) diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 000000000..3370fd36f --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,87 @@ +# .claude Directory Structure + +This directory contains Claude Code configuration, documentation, skills, and rules for the Rundeck documentation project (VuePress 2 site published to `docs.rundeck.com`). + +## Directory Contents + +``` +.claude/ +├── README.md # This file +├── CONTRIBUTING.md # Guide for extending this setup +├── settings.json # Claude Code settings (permissions, hooks) +├── .sdlc-setup-done # sdlc-workflow setup marker (gitignored) +├── docs/ # Human-authored reference documentation +│ ├── writing-style.md +│ ├── infrastructure.md +│ ├── local-development.md +│ ├── docsearch-filters.md +│ └── pr-feed.md +├── rules/ # Context-aware rules (auto-loaded by glob) +│ └── release-notes-pr.md +└── skills/ # Workflow automation skills + ├── write-release-notes/ + └── generate-pr-feed/ +``` + +All directories are created only when they hold concrete content. Additional directories (`hooks/`, `commands/`, `artifacts/`, `specs/`, `plans/`) may be added as the setup grows. + +--- + +## Documentation (`docs/`) + +Human-authored reference material consulted by the agent and developers alike. + +| File | When to read | +|---|---| +| `docs/writing-style.md` | Authoring or editing any page under `docs/` — voice, markdown, DocSearch, content structure | +| `docs/local-development.md` | Setting up locally, running the dev server, generating release notes | +| `docs/infrastructure.md` | Touching CI workflows, deployment branches, secrets, or the NPM registry config | +| `docs/pr-feed.md` | Working on the SaaS PR feed generator (`npm run pr-feed`) or `pr-feed.mjs` | +| `docs/docsearch-filters.md` | Modifying the DocSearch filter component or `.vuepress/plugins/docsearch-filters.ts` | + +Legacy paths at `dev-docs/DOCSEARCH_FILTERS_README.md` and `dev-docs/PR-FEED-README.md` are symlinks into `docs/` — the canonical copies live here. + +--- + +## Rules (`rules/`) + +Context-aware rules auto-load based on file patterns via frontmatter globs: + +```markdown +--- +description: Rule description +globs: + - "docs/history/**/version-*.md" +alwaysApply: false +--- +``` + +| Rule | Applies to | +|---|---| +| `rules/release-notes-pr.md` | PR descriptions and generated release-notes files | + +--- + +## Skills (`skills/`) + +Workflow automation skills invoked by the agent on demand: + +| Skill | Purpose | +|---|---| +| `write-release-notes` | Guide the full release-notes workflow (`npm run notes:draft` → tag → `npm run notes`) with verification steps | +| `generate-pr-feed` | Guide the weekly SaaS PR-feed update (`npm run pr-feed`) including `pr-feed-config.json` maintenance | + +Each skill lives in `skills//SKILL.md` with optional `checklist.md` / `examples/` siblings. + +--- + +## settings.json + +Claude Code configuration for permissions (and hooks if added later). Permissions allow the agent to run documented npm scripts, inspect git, edit docs files, and call Atlassian MCP for Jira/Confluence lookups. + +--- + +## Related Files at Repo Root + +- `CLAUDE.md` — canonical index of conventions, docs, rules, and skills (agent reads this first) +- `AGENTS.md` — symlink to `CLAUDE.md` (for tools that prefer the AGENTS convention) diff --git a/.claude/docs/docsearch-filters.md b/.claude/docs/docsearch-filters.md new file mode 100644 index 000000000..f374f147d --- /dev/null +++ b/.claude/docs/docsearch-filters.md @@ -0,0 +1,109 @@ +# DocSearch Filters Integration + +This guide explains how to integrate the section filtering component into your Rundeck documentation search. + +## Components Created + +### 1. DocSearchFilters.vue +- Location: `docs/.vuepress/components/DocSearchFilters.vue` +- A Vue component that provides a filter button with a dropdown panel +- Shows section checkboxes (Learning, User Guide, API, Administration, Developer, Release Notes, General) +- Persists filter selections in localStorage +- Dispatches custom events when filters change + +### 2. docsearch-filters.ts Plugin +- Location: `docs/.vuepress/plugins/docsearch-filters.ts` +- Client-side plugin that integrates filters with DocSearch +- Listens for filter update events and applies them to DocSearch +- Monitors DocSearch modal for filter restoration + +### 3. Client Configuration Updates +- Updated `docs/.vuepress/client.ts` to initialize the filter integration +- Imports and calls `initializeDocSearchFilters()` on app startup + +## Integration in Layout + +The filter button is **automatically injected** into the navbar by the client configuration. No manual component placement is needed. + +The `injectDocSearchFiltersIntoNavbar()` function in `client.ts` automatically: +1. Waits for the DocSearch container to be rendered +2. Creates a wrapper element next to the search container +3. Mounts the `DocSearchFilters` component dynamically + +This ensures the filter component appears right next to the search button without requiring manual template modifications. + +## How It Works + +1. **User clicks the filter button** (funnel icon with badge) +2. **Filter dropdown opens** showing available section tags +3. **User selects/deselects sections** via checkboxes +4. **Selections are stored** in localStorage for persistence +5. **Filter state is dispatched** via custom `docsearch-filters-updated` event +6. **Plugin intercepts Algolia requests** - The `docsearch-filters.ts` plugin patches `fetch` and `XMLHttpRequest` to intercept all Algolia API calls +7. **Facet filters are injected** - Selected sections are added to the request's `facetFilters` parameter as OR conditions (e.g., `tags:Learning OR tags:API`) +8. **Results are filtered by Algolia** - Algolia returns only results matching the selected section tags +9. **Search input is triggered** - An input event is dispatched to refresh the search results with the new filters applied + +## Configuration + +### Available Sections +The component currently supports these sections (from `config.json` tags): +- Learning +- User Guide +- API +- Administration +- Developer +- Release Notes +- General + +To add new sections: +1. Update `.docsearch/config.json` to add new `start_urls` with tags +2. Update the `sections` array in `DocSearchFilters.vue` + +### VuePress Configuration +The DocSearch configuration in `docs/.vuepress/config.ts` includes: +```typescript +searchParameters: { + hitsPerPage: 100, + facetFilters: [`version:${setup.base}`], + facets: ['tags'] +} +``` + +The `facets: ['tags']` tells Algolia to include tags as filterable attributes. + +## Styling + +The component uses VuePress theme variables for styling: +- `--c-brand` - Brand color +- `--c-border` - Border color +- `--c-text-secondary` - Secondary text color +- `--c-bg`, `--c-bg-light` - Background colors + +It automatically adapts to dark mode using `html.dark` selector. + +## Testing + +To test the filters: + +1. Build/run the docs: `npm run docs:dev` +2. Click the filter button in the navbar +3. Select a section (e.g., "Learning") +4. Perform a search +5. Results should only show items tagged with the selected section +6. Refresh the page - filter selections persist via localStorage + +## Troubleshooting + +### Filters not appearing in results +- Ensure Algolia index has been re-scraped with new tags +- Check that `facets: ['tags']` is in the config +- Verify the section tags match what's in `config.json` + +### localStorage not working +- Browser privacy mode disables localStorage +- Clear localStorage and refresh if there are issues + +### Component not visible +- Ensure DocSearchFilters component is imported and placed in the layout +- Check browser console for Vue component registration errors diff --git a/.claude/docs/infrastructure.md b/.claude/docs/infrastructure.md new file mode 100644 index 000000000..dc2f5e549 --- /dev/null +++ b/.claude/docs/infrastructure.md @@ -0,0 +1,36 @@ +## Infrastructure + +### CI/CD — GitHub Actions + +| Workflow | Trigger | Purpose | +|---|---|---| +| `check-milestone.yml` | PR open/sync/reopen on `4.0.x` | Enforces milestone on every PR | +| `snyk-scan.yml` | PR + push | Snyk security scan; monitors default branch, blocks on high-severity vulns | +| `update-pr-feed.yml` | `workflow_dispatch` | Updates PR feed config for a given RBA tag, regenerates feed files, opens a PR | + +Node.js version is pinned via `.nvmrc` (22.22.0); all workflows use `actions/setup-node@v4` with `node-version-file: '.nvmrc'`. + +### NPM Registry + +Private registry at `npm.artifacts.pd-internal.com` (Cloudsmith). Configured in `.npmrc`. Requires `CLOUDSMITH_NPM_TOKEN` secret in CI and locally. + +### Deployment + +Docs are published to `docs.rundeck.com` based on branch/tag naming: + +| Branch / Tag pattern | Published URL | +|---|---| +| `4.0.x` | `docs.rundeck.com/4.0.x/` | +| `4.13.0` | `docs.rundeck.com/4.13.0/` | +| Tag `v4.13.0-20230515` | `docs.rundeck.com/4.13.0/` **and** `docs.rundeck.com/docs/` (latest) | + +Maintenance branches for older versions follow the pattern `git checkout -b 3.2.8`. + +### Secrets + +| Secret | Used by | +|---|---| +| `CLOUDSMITH_NPM_TOKEN` | `snyk-scan.yml`, `update-pr-feed.yml` | +| `SNYK_TOKEN` | `snyk-scan.yml` | +| `GH_API_TOKEN` | `update-pr-feed.yml` (GitHub API for PR feed) | +| `GITHUB_TOKEN` | `check-milestone.yml`, `update-pr-feed.yml` (built-in) | diff --git a/.claude/docs/local-development.md b/.claude/docs/local-development.md new file mode 100644 index 000000000..ed2fb24eb --- /dev/null +++ b/.claude/docs/local-development.md @@ -0,0 +1,64 @@ +## Local Development + +VuePress 2 documentation site for Rundeck. Content lives in `docs/`, config in `docs/.vuepress/`. + +### Prerequisites + +- Node.js 22.22.0 (managed via nvm — version pinned in `.nvmrc`) +- Cloudsmith NPM token (`CLOUDSMITH_NPM_TOKEN`) for the internal registry at `npm.artifacts.pd-internal.com` +- GitHub API token (`GH_API_TOKEN`) only needed for release notes generation + +### Setup + +```shell +nvm install && nvm use +export CLOUDSMITH_NPM_TOKEN= +npm install +``` + +If you don't have Cloudsmith access, delete `.npmrc` first: + +```shell +rm .npmrc +npm install +``` + +### Running locally + +```shell +npm run docs:dev # dev server with hot reload +npm run docs:build # production build +npm run docs:no-cache # dev server, skip cache +npm run docs:clean-dev # dev server, clear cache first +``` + +### Release notes + +```shell +# Draft (safe, doesn't modify config files) +npm run notes:draft -- --milestone=5.17.0 + +# Final (updates sidebar, navbar, setup.js, pr-feed-config.json) +npm run notes -- --milestone=5.17.0 +``` + +Requires `GH_API_TOKEN` in a `.env` file at the repo root. PRs must have the `release-notes/include` label. + +### PR feed (SaaS development updates) + +```shell +npm run pr-feed +``` + +Regenerates RSS/Atom feeds and `docs/history/updates/index.md` for SaaS deployments not yet in a self-hosted release. See `.claude/docs/pr-feed.md` for details. + +### Troubleshooting + +```shell +# Clean reinstall +rm -rf node_modules package-lock.json +npm install +``` + +- If `nvm` is not found, restart terminal after installation +- If dev server shows stale content, clear browser cache or use `npm run docs:no-cache` diff --git a/.claude/docs/pr-feed.md b/.claude/docs/pr-feed.md new file mode 100644 index 000000000..292867c86 --- /dev/null +++ b/.claude/docs/pr-feed.md @@ -0,0 +1,371 @@ +# PR Feed Generator + +This script generates RSS/Atom feeds and markdown pages from recently merged pull requests in both the Rundeck repositories. It's designed to be run as part of the SaaS release process to keep customers informed about development updates deployed to the Runbook Automation SaaS platform. + +## Use Case + +**SaaS Release Communication**: The Runbook Automation team releases to SaaS approximately weekly (and sometimes more frequently for urgent updates). These changes come from both the public `rundeck/rundeck` repo and the private `rundeckpro/rundeckpro` repo. Since customers can see public repo changes but not private ones, this tool captures and communicates the full scope of updates deployed to the SaaS platform. + +## Features + +- Fetches merged PRs from **both** repositories: + - **Private**: `rundeckpro/rundeckpro` (filtered by `release-notes/include` label) + - **Public**: `rundeck/rundeck` (filtered by `release-notes/include` label) +- **Tag-based comparison**: Uses git tags instead of dates for accurate PR detection +- **SaaS cut support**: Limits PRs to those included in the most recent SaaS deployment cut +- **Submodule awareness**: Automatically finds the correct rundeck commit from rundeckpro tags +- **All merge strategies**: Handles merge commits, squash merges, and rebase merges +- Combines and sorts PRs by merge date across both repos +- Automatically removes `RUN-XXXX` prefixes from PR titles (matching notes.mjs logic) +- Generates both RSS 2.0 and Atom feeds +- Creates VuePress-compatible markdown pages +- Template-based content using Nunjucks (like notes.mjs) +- Comprehensive error handling + +## Initial Setup (One-time) + +1. **Create `.env` file** in project root: + ```bash + touch .env + ``` + +2. **Add your GitHub token**: + ```env + GH_API_TOKEN=ghp_your_actual_token_here + ``` + + Get a token at: https://github.com/settings/tokens (needs `repo` scope) + +3. **Verify Dependencies** - Already in `package.json`: + - `@octokit/rest` - GitHub API client + - `dotenv` - Environment variable loading + - `yargs` - Command-line argument parsing + - `nunjucks` - Template engine + +## Weekly Release Process + +Run after each SaaS deployment: + +```bash +npm run pr-feed +``` + +The script will automatically: +1. Use git tag comparison (v5.17.0 to SaaS cut tag) to find PRs +2. For rundeckpro: Compare v5.17.0 to the `lastSaasCut` tag +3. For rundeck: Extract the submodule commit from the SaaS cut tag and use that as the endpoint +4. Show only PRs that were included in the most recent SaaS deployment + +Then commit the generated files: + +```bash +git add docs/history/updates/index.md +git add docs/.vuepress/public/feeds/ +git commit -m "Update SaaS deployment feed" +git push +``` + +### Updating the Configuration + +The `pr-feed-config.json` file tracks three key values: + +```json +{ + "lastSelfHostedRelease": { + "version": "5.17.0", + "lastSelfHostedDate": "2025-10-22", + "lastSaasRelease": "2025-11-04", + "lastSaasCut": "rba/5.18-RBA-20251030-2f39445-a6d9e14", + "description": "Last self-hosted release version and date" + } +} +``` + +**When to update each field:** + +1. **`version` and `lastSelfHostedDate`**: Updated automatically by `notes.mjs` when creating self-hosted release notes + ```bash + node ./docs/.vuepress/notes.mjs --milestone=5.18.0 + # Automatically updates version and date + ``` + +2. **`lastSaasRelease`**: Update manually after deploying to SaaS production (displayed on the updates page) + +3. **`lastSaasCut`**: Update manually each time you cut a release (typically weekly) + - This is the tag created when you build the release candidate + - Format: `rba/${vNum}-RBA-${vDate}-${coreSha}-${proSha}` + - `vNum`: Next major version (e.g., `5.18`) + - `vDate`: Date the cut was made (YYYYMMDD) + - `coreSha`: Short SHA of the rundeck (core) submodule commit + - `proSha`: Short SHA of the rundeckpro commit + - Example: `rba/5.18-RBA-20251030-2f39445-a6d9e14` + - The script parses this tag to extract the exact commit SHAs + - PRs merged after these commits will NOT appear in the feed until the next cut + +**Example workflow:** +1. Wednesday: Cut release → Update `lastSaasCut` with the new tag +2. Monday: Deploy to production → Update `lastSaasRelease` with deployment date +3. Monday: Run `npm run pr-feed` → Shows PRs between last release and the cut tag +4. Repeat weekly + +## Command Reference + +### Basic Commands + +```bash +# Default: Show PRs since last self-hosted release +npm run pr-feed + +# Override to use time-based lookback +npm run pr-feed -- --days=7 + +# Include specific section from PR descriptions +npm run pr-feed -- --include-section="Customer Summary" + +# Different repository +node ./docs/.vuepress/pr-feed.mjs --owner=rundeck --repo=rundeck +``` + +### Command-Line Options + +| Option | Alias | Default | Description | +|--------|-------|---------|-------------| +| `--days` | `-d` | *(uses config)* | Number of days to look back (overrides tag-based mode) | +| `--since-release` | | `true` | Use tag-based comparison from last self-hosted release | +| `--labels` | `-l` | `release-notes/include` | Labels to include (space-separated) | +| `--exclude-labels` | | `wip, do-not-publish` | Labels to exclude | +| `--max-prs` | | `100` | Maximum number of PRs to fetch per repository | +| `--include-section` | | `Release Notes` | Include specific section from PR body | +| `--output-dir` | | `docs/history/updates` | Output directory for markdown page | +| `--help` | | | Show help message | + +### How It Works + +**Tag-Based Comparison (Default):** +1. Reads `pr-feed-config.json` to get: + - Last self-hosted release version (e.g., `5.17.0`) + - SaaS cut tag (e.g., `rba/5.18-RBA-20251030-2f39445-a6d9e14`) +2. Parses the SaaS cut tag to extract: + - `coreSha` (2f39445): The rundeck submodule commit + - `proSha` (a6d9e14): The rundeckpro commit +3. For **rundeckpro**: Compares `v5.17.0` tag to `proSha` commit using `git.compareCommits` +4. For **rundeck**: Compares `v5.17.0` tag to `coreSha` commit using `git.compareCommits` +5. Finds all commits between these points and extracts associated PRs +6. Handles all merge strategies: merge commits, squash merges, and rebase merges +7. Filters by `release-notes/include` label +8. Combines results and sorts by merge date + +**Why Tag-Based?** +- More accurate than date-based queries +- Tags represent actual git history, not approximate dates +- Correctly handles retroactive tagging scenarios +- Aligns with actual release cuts and deployments +- Ensures you only see PRs that are truly included in the deployment + +**Tag Format Benefits:** +- Minimal API lookups needed - commit SHAs are directly in the tag (no tag object API calls needed) +- Guarantees accuracy - uses the exact commits from the build +- Simple parsing - reliable extraction from standardized format +- Fast execution - no need to traverse git trees + +## What Gets Generated + +### 1. Markdown Page +**Location**: `docs/history/updates/index.md` +**URL**: https://docs.rundeck.com/docs/history/updates/ + +A complete, standalone VuePress page with: +- Context about the updates and SaaS releases +- Subscription links for RSS/Atom feeds +- Information about the difference between SaaS and self-hosted +- All PRs listed under "Recent Changes" heading +- Rich formatting with cleaned PR titles and merge dates +- Optional "Release Notes" sections from PR descriptions +- Automatic reference to the last self-hosted release version + +**Note**: This file is completely regenerated on each run from the template. To modify static content, edit the template (see below). + +### 2. RSS Feed +**Location**: `docs/.vuepress/public/feeds/development.xml` +**URL**: https://docs.rundeck.com/feeds/development.xml + +RSS 2.0 feed compatible with most feed readers. + +### 3. Atom Feed +**Location**: `docs/.vuepress/public/feeds/development-atom.xml` +**URL**: https://docs.rundeck.com/feeds/development-atom.xml + +Atom 1.0 feed for modern feed readers. + +## Editing Static Content + +The generated `index.md` page is created from a Nunjucks template, similar to how `notes.mjs` works: + +**Template Location**: `docs/.vuepress/pr-feed.md.nj` + +To change the static parts of the page (headings, descriptions, subscription text, etc.), edit the template file directly. + +### Template Variables + +- `{{currentDate}}` - ISO timestamp for frontmatter +- `{{lastUpdated}}` - Formatted date string (YYYY-MM-DD) +- `{{periodDescription}}` - Description like "merged since the last self-hosted release" +- `{{releaseInfo}}` - Optional release version and date info +- `{{prs}}` - Array of PR objects with: + - `cleanTitle` - PR title with RUN-XXXX prefixes removed + - `mergedDate` - Formatted merge date + - `sectionContent` - Optional extracted section (if `--include-section` used) + +After editing the template, run `npm run pr-feed` to regenerate the page with your changes. + +## PR Description Best Practices + +Structure your PR descriptions with clear sections to take advantage of the `--include-section` feature: + +```markdown +## Release Notes +Brief customer-facing description of the change. +This will be included in the feed by default. + +## PR Details +Implementation specifics and technical details. + +## Testing +Testing approach and verification steps. +``` + +**Default section**: `## Release Notes` is extracted by default. Make sure to include another section header (like `## PR Details`) to separate customer-facing content from internal details. + +## Integration with Release Process + +### SaaS Deployment +1. Deploy to SaaS production +2. Run `npm run pr-feed` +3. Review generated `docs/history/updates/index.md` +4. Commit and push changes + +### Self-Hosted Release +When creating a new self-hosted release, `notes.mjs` automatically updates the PR feed baseline: + +```bash +node ./docs/.vuepress/notes.mjs --milestone=5.9.0 +# Automatically updates pr-feed-config.json +``` + +Next time you run `npm run pr-feed`, it will show PRs merged after this release date. + +## Troubleshooting + +### "GH_API_TOKEN not set" +Create `.env` file with your GitHub token (see Initial Setup). + +### "Repository not found" +- Verify your token has the `repo` scope +- Check that you have access to the private repository +- Ensure the owner/repo names are correct + +### "No PRs found" +- No PRs were merged in the specified time period +- No PRs have the required labels +- Try expanding the date range: `--days=30` + +### PR titles still have RUN-XXXX prefixes +The script automatically removes `RUN-XXXX` prefixes using the regex pattern: `/^(RUN-[0-9]+\s*)+:?\s*/g` + +If you see prefixes that weren't removed, they may not match this pattern. + +## Customization + +### Time-Based Override +For testing or special cases, you can override tag-based comparison with time-based: +```bash +npm run pr-feed -- --days=7 +``` + +### Label Filtering +Use different labels: +```bash +node ./docs/.vuepress/pr-feed.mjs --labels feature bugfix enhancement +``` + +### Output Location +```bash +node ./docs/.vuepress/pr-feed.mjs --output-dir=./docs/some-other-location +``` + +## Implementation Notes + +### Shared Utilities with notes.mjs + +Both `pr-feed.mjs` and `notes.mjs` now use shared functions from `pr-utils.mjs`: + +**Shared Functions:** +- `fetchPRsBetweenTags()` - Compare two git tags to find all PRs +- `extractPRSection()` - Extract specific sections from PR bodies (e.g., "Release Notes") +- `cleanPRTitle()` - Remove RUN-XXXX prefixes from PR titles +- `parseSaasCutTag()` - Parse SaaS cut tag format to extract commit SHAs +- `getPreviousVersion()` - Auto-decrement version numbers (e.g., 5.17.0 → 5.16.0) + +This ensures consistent behavior across both scripts and reduces code duplication. + +### Pattern Matching with notes.mjs + +This script follows the same patterns as `notes.mjs`: +- Uses ES modules (`.mjs` extension) +- Uses Nunjucks templates for content generation +- Uses Octokit for GitHub API access +- Loads environment variables with `dotenv` +- Parses CLI arguments with `yargs` +- Follows the same code style and structure +- Both now use tag-based comparison (not milestones) + +### Key Differences from notes.mjs + +- **SaaS vs Self-Hosted**: Focuses on SaaS deployments vs version-specific self-hosted releases +- **Continuous updates**: Generates standalone pages for ongoing updates vs one-time release notes +- **SaaS cut awareness**: Limits PRs to those in the deployment cut (not everything in main) +- **Submodule handling**: Automatically resolves rundeck submodule commits from rundeckpro tags +- **Customer communication**: Designed for SaaS customers via RSS/Atom feeds +- **Feed generation**: Creates RSS 2.0 and Atom 1.0 feeds + +### Similarity to notes.mjs + +- **Both use tag-based comparison**: Compare git tags to find PRs between releases +- **Both handle all merge strategies**: Merge commits, squash merges, and rebase merges +- **Both use shared utilities**: Common functions from `pr-utils.mjs` +- **Both extract PR sections**: Pull "Release Notes" sections from PR bodies +- **Both filter by labels**: Use `release-notes/include` for enterprise PRs + +## Technical Details + +### Merge Strategy Support +The script handles all three GitHub merge strategies: +1. **Merge commits**: Detected via "Merge pull request #X" in commit message +2. **Squash merges**: Detected via `listPullRequestsAssociatedWithCommit` API +3. **Rebase merges**: Each rebased commit is associated with its PR via GitHub API + +### SaaS Cut Tag Parsing +The `lastSaasCut` tag follows the format: `rba/${vNum}-RBA-${vDate}-${coreSha}-${proSha}` + +Example: `rba/5.18-RBA-20251030-2f39445-a6d9e14` +- `5.18` - Next major version number +- `20251030` - Cut date (October 30, 2025) +- `2f39445` - Short SHA of rundeck (core) submodule commit +- `a6d9e14` - Short SHA of rundeckpro commit + +The script parses this format to extract commit SHAs directly, eliminating the need for: +- API calls to fetch tag objects +- Git tree traversal to find submodules +- Handling of annotated vs lightweight tags + +This ensures the exact commits used in the build are compared, guaranteeing accuracy. + +## Related Documentation + +- **[README.md](../../README.md)** - Main documentation project setup and release notes generation +- **`notes.mjs`** - Self-hosted release notes generator (uses same shared utilities) +- **`pr-utils.mjs`** - Shared utility functions used by both scripts + +## License + +This script is part of the Rundeck documentation project and follows the same license as the main repository. diff --git a/.claude/docs/writing-style.md b/.claude/docs/writing-style.md new file mode 100644 index 000000000..83e6ce932 --- /dev/null +++ b/.claude/docs/writing-style.md @@ -0,0 +1,79 @@ +# Writing Style & Markdown Conventions + +Conventions for authoring and editing Rundeck documentation content (everything under `docs/` that ships to `docs.rundeck.com`). + +## Voice & Language + +- Write clear, concise technical prose; avoid marketing language. +- Maintain consistent terminology across pages (e.g. "job", "node", "execution" — do not alternate synonyms). +- Use practical examples wherever a concept can be illustrated by code or a CLI invocation. +- Cross-reference related pages instead of duplicating content. +- **Never use emojis in content.** If decorative iconography is absolutely required, Font Awesome is available via VuePress components. + +## Markdown Conventions + +- Use ATX-style headers only (`#`, `##`, `###`). No Setext (`===` / `---`) headings. +- Respect heading hierarchy: one `#` per page, then `##` → `###` without skipping levels. +- Fenced code blocks MUST declare a language for syntax highlighting: + + ```bash + rd projects list + ``` + + Use `text` or `plain` for output / non-language snippets. +- Numbered lists for sequential steps; bullets for unordered collections. +- Tables require a header row and consistent column alignment. +- All images require descriptive alt text. Images live under `docs/.vuepress/public/assets/img/`. + +## Page Structure Checklist + +- [ ] Exactly one H1 (`#`) at the top +- [ ] Logical H2/H3 hierarchy, no skipped levels +- [ ] Every code block tagged with a language +- [ ] Every image has meaningful alt text +- [ ] Cross-references use relative links (not absolute `docs.rundeck.com` URLs) +- [ ] No emojis + +## Search / DocSearch Architecture + +Search is powered by Algolia DocSearch via `@vuepress/plugin-docsearch`. + +| Concept | Value / Location | +|---|---| +| Index name | `prod_rundeck_docs` | +| Scraper | `algolia/docsearch-scraper` Docker image, driven by CircleCI | +| Scraper config | `.docsearch/config.json` (selectors, start URLs, faceting attributes) | +| VuePress plugin config | `docs/.vuepress/config.ts` (`appId`, `apiKey`, `searchParameters`) | +| Current facets | `version` (e.g. `docs`, `4.0.x`), `lang` | +| Section tags | Applied via URL patterns in `start_urls` (Learning, User Guide, API, Administration, Developer, Release Notes, General) | + +The custom section-filter UI is documented in `.claude/docs/docsearch-filters.md`. + +### When Search Won't Update + +- After adding new `start_urls` with tags to `.docsearch/config.json`, re-scrape the index — existing records carry stale tags until re-indexed. +- `searchParameters.facetFilters` in `docs/.vuepress/config.ts` MUST include `version:${setup.base}` so results scope correctly to the current version. +- `searchParameters.facets` MUST include `'tags'` for the filter UI to work. + +## Content Directory Structure + +User-facing content published to `docs.rundeck.com`: + +| Path | Purpose | +|---|---| +| `docs/` | Root of published content | +| `docs/manual/` | Core product documentation | +| `docs/administration/` | Administration guides | +| `docs/api/` | API documentation | +| `docs/developer/` | Developer documentation | +| `docs/learning/` | Tutorials and learning resources | +| `docs/history/` | Release notes (auto-generated by `npm run notes`) | +| `docs/history/updates/` | SaaS PR feed page (auto-generated by `npm run pr-feed`) | +| `docs/.vuepress/` | VuePress configuration, components, plugins | +| `docs/.vuepress/public/` | Static assets (images, feeds, etc.) | + +## Related Documentation + +- `.claude/docs/local-development.md` — running the site locally +- `.claude/docs/docsearch-filters.md` — custom section-filter component +- `.claude/docs/pr-feed.md` — SaaS PR feed generator diff --git a/.claude/rules/release-notes-pr.md b/.claude/rules/release-notes-pr.md new file mode 100644 index 000000000..37b308697 --- /dev/null +++ b/.claude/rules/release-notes-pr.md @@ -0,0 +1,35 @@ +--- +description: PR description requirements for release-notes inclusion and generated notes files +globs: + - "docs/history/**/version-*.md" + - "docs/history/**/draft.md" + - "docs/history/updates/index.md" +alwaysApply: false +--- + +# Release-Notes PR Conventions + +## Mandatory + +1. PRs intended for customer-facing release notes MUST carry the `release-notes/include` label. Without it, they are silently excluded from both `notes.mjs` and `pr-feed.mjs`. +2. PR descriptions MUST include a `## Release Notes` section containing the customer-facing copy. Add a second heading (e.g. `## Technical Details` or `## PR Details`) to separate internal notes from extracted content. +3. PR titles should be free of `RUN-XXXX:` Jira prefixes in the customer-facing output. The scripts strip `^(RUN-[0-9]+\s*)+:?\s*` automatically; non-matching prefixes must be cleaned manually in the generated markdown. +4. Never edit generated `docs/history/_x/version-.md` content that originated from PR bodies — fix the PR description and re-run the notes script. Manual edits are reserved for the Overview, dates, and final curation. +5. Never commit the results of `notes.mjs` / `pr-feed.mjs` in isolation — the side-effect config files (`.docsearch/config.json`, `pr-feed-config.json`, `setup.js`, `sidebar-menus/history.ts`, `navbar-menus/about.js`) must be staged in the same commit. + +## Detailed Guidance + +See `.claude/docs/pr-feed.md` for: +- Tag-based comparison logic +- `lastSaasCut` format (`rba/-RBA---`) +- Template variables available in `pr-feed.md.nj` + +See `.claude/skills/write-release-notes/SKILL.md` for the end-to-end self-hosted release workflow. +See `.claude/skills/generate-pr-feed/SKILL.md` for the weekly SaaS PR-feed workflow. + +## Before Completing + +- [ ] Label presence verified on the contributing PRs +- [ ] `## Release Notes` section exists and is customer-appropriate +- [ ] All side-effect config files staged alongside the notes markdown +- [ ] No manual edits to PR-derived content in generated files diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..7e9d325c9 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,91 @@ +{ + "permissions": { + "allow": [ + "Read", + "Read(~/.cache/pagerduty/**)", + "Read(~/.claude/plugins/**)", + "Glob", + "Grep", + "WebFetch", + "WebSearch", + "Task", + "mcp__atlassian__atlassianUserInfo", + "mcp__atlassian__getAccessibleAtlassianResources", + "mcp__atlassian__getConfluencePage", + "mcp__atlassian__searchConfluenceUsingCql", + "mcp__atlassian__getConfluenceSpaces", + "mcp__atlassian__getPagesInConfluenceSpace", + "mcp__atlassian__getConfluencePageFooterComments", + "mcp__atlassian__getConfluencePageInlineComments", + "mcp__atlassian__getConfluencePageDescendants", + "mcp__atlassian__getJiraIssue", + "mcp__atlassian__getTransitionsForJiraIssue", + "mcp__atlassian__getJiraIssueRemoteIssueLinks", + "mcp__atlassian__getVisibleJiraProjects", + "mcp__atlassian__getJiraProjectIssueTypesMetadata", + "mcp__atlassian__getJiraIssueTypeMetaWithFields", + "mcp__atlassian__searchJiraIssuesUsingJql", + "mcp__atlassian__lookupJiraAccountId", + "mcp__atlassian__search", + "mcp__atlassian__fetch", + "mcp__plugin_sdlc-workflow_atlassian__atlassianUserInfo", + "mcp__plugin_sdlc-workflow_atlassian__getAccessibleAtlassianResources", + "mcp__plugin_sdlc-workflow_atlassian__getConfluencePage", + "mcp__plugin_sdlc-workflow_atlassian__searchConfluenceUsingCql", + "mcp__plugin_sdlc-workflow_atlassian__getConfluenceSpaces", + "mcp__plugin_sdlc-workflow_atlassian__getPagesInConfluenceSpace", + "mcp__plugin_sdlc-workflow_atlassian__getConfluencePageFooterComments", + "mcp__plugin_sdlc-workflow_atlassian__getConfluencePageInlineComments", + "mcp__plugin_sdlc-workflow_atlassian__getConfluencePageDescendants", + "mcp__plugin_sdlc-workflow_atlassian__getJiraIssue", + "mcp__plugin_sdlc-workflow_atlassian__getTransitionsForJiraIssue", + "mcp__plugin_sdlc-workflow_atlassian__getJiraIssueRemoteIssueLinks", + "mcp__plugin_sdlc-workflow_atlassian__getVisibleJiraProjects", + "mcp__plugin_sdlc-workflow_atlassian__getJiraProjectIssueTypesMetadata", + "mcp__plugin_sdlc-workflow_atlassian__getJiraIssueTypeMetaWithFields", + "mcp__plugin_sdlc-workflow_atlassian__searchJiraIssuesUsingJql", + "mcp__plugin_sdlc-workflow_atlassian__lookupJiraAccountId", + "mcp__plugin_sdlc-workflow_atlassian__search", + "mcp__plugin_sdlc-workflow_atlassian__fetch", + "Bash(git status*)", + "Bash(git diff*)", + "Bash(git log*)", + "Bash(git show*)", + "Bash(git branch*)", + "Bash(git rev-parse*)", + "Bash(git remote*)", + "Bash(git describe*)", + "Bash(git ls-files*)", + "Bash(git ls-tree*)", + "Bash(npm run docs:*)", + "Bash(npm run notes*)", + "Bash(npm run pr-feed*)", + "Bash(npm install*)", + "Bash(ls *)", + "Bash(wc *)", + "Bash(which *)", + "Bash(command -v *)", + "Bash(mkdir -p .claude/specs*)", + "Bash(mkdir -p .claude/plans*)", + "Bash(mkdir -p docs*)", + "Write(.claude/specs/*.md)", + "Edit(.claude/specs/*.md)", + "Write(.claude/plans/*.md)", + "Edit(.claude/plans/*.md)", + "Write(AGENTS.md)", + "Edit(AGENTS.md)", + "Write(CLAUDE.md)", + "Write(docs/*.md)", + "Edit(docs/*.md)", + "Edit(.gitignore)", + "Bash(bash */hooks/scripts/sync-standards.sh*)", + "Bash(bash */skills/start-task/scripts/validate-spec.sh *)", + "Bash(bash */skills/verify-implementation/scripts/validate-holdout-scenarios.sh*)", + "Bash(git add*)", + "Bash(git commit*)", + "Bash(gh auth status*)", + "Bash(gh pr create*)", + "Bash(gh pr view*)" + ] + } +} diff --git a/.claude/skills/generate-pr-feed/SKILL.md b/.claude/skills/generate-pr-feed/SKILL.md new file mode 100644 index 000000000..69b0bf898 --- /dev/null +++ b/.claude/skills/generate-pr-feed/SKILL.md @@ -0,0 +1,109 @@ +--- +name: generate-pr-feed +description: Regenerate the SaaS PR feed (RSS/Atom + updates markdown) after a SaaS deployment using tag-based PR comparison. Use when the user asks to "update the PR feed", "regenerate RSS feed", "update docs/history/updates", or mentions a new SaaS cut tag (e.g. rba/5.18-RBA-...). +--- + +# Generate PR Feed + +Runs the weekly SaaS PR feed update: compares the last self-hosted release tag against the latest SaaS cut tag to produce RSS 2.0, Atom 1.0, and a VuePress-compatible markdown page listing all PRs deployed to the SaaS platform. + +## When to Use + +- The user says "regenerate the PR feed", "update the updates page", "run pr-feed", or mentions a freshly cut SaaS tag. +- A SaaS deployment has just completed and the public-facing feed needs refreshing. +- The user wants to preview upcoming SaaS updates before committing. + +## Reference Documentation + +Read before executing: + +- `.claude/docs/pr-feed.md` — comprehensive guide (tag parsing, template variables, troubleshooting) +- `.claude/docs/local-development.md` — environment setup + +## Prerequisites + +1. Node 22.22.0 (`nvm install && nvm use`). +2. `.env` with `GH_API_TOKEN` that has `repo` scope and access to `rundeckpro/rundeckpro` and `rundeck/rundeck`. +3. `docs/.vuepress/pr-feed-config.json` updated with the latest `lastSaasCut` tag (see Phase 1). + +## Process + +### Phase 1: Update `pr-feed-config.json` + +Open `docs/.vuepress/pr-feed-config.json` and confirm/update: + +| Field | When to update | Who | +|---|---|---| +| `version`, `lastSelfHostedDate` | Auto-updated by `notes.mjs` when producing a self-hosted release | the `write-release-notes` skill | +| `lastSaasRelease` | After deploying to SaaS production (date displayed on the updates page) | manual | +| `lastSaasCut` | Each time a release is cut (typically weekly) | manual | + +`lastSaasCut` format: `rba/-RBA---` +Example: `rba/5.18-RBA-20251030-2f39445-a6d9e14` + +The script parses this tag to extract exact commit SHAs; any deviation from the format will break parsing. + +### Phase 2: Run the generator + +```bash +npm run pr-feed +``` + +The script will: +1. Load config, parse `lastSaasCut` into `coreSha` + `proSha`. +2. Compare `v` → `proSha` in `rundeckpro/rundeckpro`. +3. Compare `v` → `coreSha` in `rundeck/rundeck`. +4. Combine PRs, filter by `release-notes/include`, sort by merge date. +5. Strip `RUN-XXXX:` prefixes from titles. +6. Render `docs/history/updates/index.md` from `docs/.vuepress/pr-feed.md.nj`. +7. Write `docs/.vuepress/public/feeds/development.xml` (RSS) and `development-atom.xml` (Atom). + +### Phase 3: Review output + +- `docs/history/updates/index.md` — the page published at `docs.rundeck.com/docs/history/updates/` +- `docs/.vuepress/public/feeds/development.xml` — RSS 2.0 +- `docs/.vuepress/public/feeds/development-atom.xml` — Atom 1.0 + +Verify PR titles look clean, merge dates are present, and `## Release Notes` sections rendered where expected. + +### Phase 4: Commit + +```bash +git add docs/history/updates/index.md +git add docs/.vuepress/public/feeds/ +git add docs/.vuepress/pr-feed-config.json +git commit -m "Update SaaS deployment feed" +``` + +Do NOT push without the user's explicit instruction. + +## Customisation (Rare) + +| Option | Purpose | +|---|---| +| `--days=N` | Override tag-based comparison with a time window (testing only) | +| `--include-section="Customer Summary"` | Extract a different PR body section | +| `--max-prs=N` | Cap PRs per repo (default 100) | +| `--output-dir=...` | Write markdown elsewhere | + +Full flag reference lives in `.claude/docs/pr-feed.md`. + +## Troubleshooting + +| Symptom | Cause / Fix | +|---|---| +| `GH_API_TOKEN not set` | Create `.env` with a valid token | +| `Repository not found` | Token missing `repo` scope or lacks access to the private repo | +| No PRs found | Tag comparison range is empty, or no PRs carry `release-notes/include` | +| Titles still have `RUN-XXXX:` prefixes | Prefix does not match `/^(RUN-[0-9]+\s*)+:?\s*/g` — inspect the original PR title | +| Parse error on `lastSaasCut` | Tag does not match `rba/-RBA---` | + +## Checklist + +- [ ] `pr-feed-config.json` `lastSaasCut` updated to the latest cut tag +- [ ] `lastSaasRelease` reflects the actual production deployment date +- [ ] `npm run pr-feed` completes without errors +- [ ] `docs/history/updates/index.md` renders cleanly (titles, dates, sections) +- [ ] RSS and Atom feeds present under `docs/.vuepress/public/feeds/` +- [ ] Config + generated files staged together +- [ ] Commit message follows `Update SaaS deployment feed` convention diff --git a/.claude/skills/write-release-notes/SKILL.md b/.claude/skills/write-release-notes/SKILL.md new file mode 100644 index 000000000..609814084 --- /dev/null +++ b/.claude/skills/write-release-notes/SKILL.md @@ -0,0 +1,116 @@ +--- +name: write-release-notes +description: Generate self-hosted release notes for a Rundeck version using tag-based PR comparison. Use when preparing release notes for a milestone (e.g. 5.17.0), previewing upcoming notes via draft mode, or troubleshooting the notes pipeline. +--- + +# Write Release Notes + +Guides the full release-notes workflow for a Rundeck self-hosted milestone using `npm run notes` / `npm run notes:draft`. Auto-loads prerequisites, runs the draft, and helps finalise after tagging. + +## When to Use + +- The user asks to "generate release notes", "prepare release notes", "draft release notes for X.Y.Z", or similar. +- The user mentions milestones like `5.17.0`, `5.18.0` in a release context. +- CI or a team member reports the notes pipeline produced unexpected output and help is needed. + +## Reference Documentation + +Read these before executing: + +- `.claude/docs/local-development.md` — environment setup (Node via nvm, Cloudsmith token) +- `.claude/docs/pr-feed.md` — the shared utilities (`pr-utils.mjs`) and tag-parsing logic used by `notes.mjs` +- `README.md` (repo root) — the authoritative end-to-end release-notes workflow (section: "How to Create Release Notes") + +## Prerequisites + +1. Node 22.22.0 active (`nvm install && nvm use`). +2. Repo dependencies installed (`npm install`, requires `CLOUDSMITH_NPM_TOKEN` or `.npmrc` removed). +3. `.env` file at the repo root with `GH_API_TOKEN=ghp_...` (needs `repo` scope; must have access to `rundeckpro/rundeckpro` and `rundeckpro/sidecar`). +4. All contributing PRs carry the `release-notes/include` label. +5. PR descriptions use a `## Release Notes` section for customer-facing copy. + +## Process + +### Phase 1: Draft preview (safe, does not modify configs) + +```bash +npm run notes:draft -- --milestone= +``` + +- If the tag `v` does not yet exist, draft mode falls back to `HEAD` and prints a warning — this is expected. +- Output: `docs/history/_x/draft.md`. +- Review the draft with the user before proceeding. + +### Phase 2: Tag the release + +```bash +git tag v +git push origin v +``` + +Only tag when the user has confirmed the draft content. + +### Phase 3: Generate final notes (updates configs) + +```bash +npm run notes -- --milestone= +``` + +This updates, in addition to the notes markdown: +- `.docsearch/config.json` — search indexing version +- `docs/.vuepress/setup.js` — version info +- `docs/.vuepress/sidebar-menus/history.ts` — sidebar link +- `docs/.vuepress/navbar-menus/about.js` — navbar link +- `docs/.vuepress/pr-feed-config.json` — PR feed baseline + +### Phase 4: Manual edits + +Open `docs/history/_x/version-.md` and fill in: +- Release date +- Overview section +- Any final descriptions or curated ordering + +### Phase 5: Commit + +```bash +git add docs/history/_x/version-.md +git add docs/.vuepress/sidebar-menus/history.ts +git add docs/.vuepress/navbar-menus/about.js +git add docs/.vuepress/setup.js +git add .docsearch/config.json +git add docs/.vuepress/pr-feed-config.json +git commit -m "Release notes for " +``` + +Do NOT push without the user's explicit instruction. + +## Version Auto-Detection + +| Target | Auto-detected previous | Compares | +|---|---|---| +| `5.17.0` | `5.16.0` | v5.16.0 → v5.17.0 | +| `5.17.1` | `5.17.0` | v5.17.0 → v5.17.1 | +| `5.0.0` | `4.17.0` | v4.17.0 → v5.0.0 | + +Override with `--from-version=` for patch releases or special ranges. + +## Troubleshooting + +| Symptom | Cause / Fix | +|---|---| +| `Tag X.Y.Z not found` in draft mode | Normal before tagging — draft uses `HEAD`. Ignore. | +| `Tag X.Y.Z not found` in final mode | Create the tag first (Phase 2). | +| `GH_API_TOKEN environment variable is not set` | Create `.env` with a GitHub token with `repo` scope. | +| No PRs found | Verify tags exist, PRs are merged (not just closed), and carry `release-notes/include`. | +| Warnings about `docs` / `ansible-plugin` repos | Expected — those repos do not use version tags; they are skipped gracefully. | + +## Checklist + +- [ ] Node version matches `.nvmrc` +- [ ] `.env` present with valid `GH_API_TOKEN` +- [ ] Draft generated and reviewed with the user +- [ ] Git tag created and pushed +- [ ] Final notes generated +- [ ] Manual edits applied (date, overview) +- [ ] All config files staged together with the notes markdown +- [ ] Commit message follows `Release notes for ` convention diff --git a/.cursor/docs b/.cursor/docs new file mode 120000 index 000000000..daf0269c6 --- /dev/null +++ b/.cursor/docs @@ -0,0 +1 @@ +../.claude/docs \ No newline at end of file diff --git a/.cursor/rules b/.cursor/rules new file mode 120000 index 000000000..526948310 --- /dev/null +++ b/.cursor/rules @@ -0,0 +1 @@ +../.claude/rules \ No newline at end of file diff --git a/.cursor/skills b/.cursor/skills new file mode 120000 index 000000000..454b8427c --- /dev/null +++ b/.cursor/skills @@ -0,0 +1 @@ +../.claude/skills \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fc283a66c..e3cf00132 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,56 +1,26 @@ # GitHub Copilot Instructions for Rundeck Documentation -You are an AI assistant helping maintain the Rundeck documentation site. - -## Project Context -- This repository contains documentation for Rundeck, an open-source job scheduler and runbook automation tool -- Documentation is primarily written in Markdown and organized by product versions -- Documentation follows a specific structure with product versions, features, and administration guides -- Documentation is built using VuePress 2 and the Hope Theme. -- Never use emojis within content. If absolutely necessary font awesome is available for page content. - -## Key Documentation Guidelines -- Use clear, concise language -- Follow technical writing best practices -- Include practical examples where appropriate -- Ensure all code examples are properly formatted and functional -- Maintain consistent terminology throughout the documentation -- Use proper heading hierarchy (H1 > H2 > H3) -- Include descriptive alt text for images - -## Common Tasks -- Creating new documentation pages -- Updating existing documentation for new releases -- Fixing formatting issues in Markdown files -- Improving code examples -- Enhancing clarity of technical explanations -- Cross-referencing related documentation - -## Directory Structure -- `/docs/` - Main documentation content -- `/docs/manual/` - Core product documentation -- `/docs/administration/` - Administration guides -- `/docs/api/` - API documentation -- `/docs/developer/` - Developer documentation -- `/docs/learning/` - Tutorials and learning resources - -## Markdown Guidelines -- Use ATX-style headers (`#` for H1, `##` for H2) -- Code blocks should specify language for proper syntax highlighting -- Use numbered lists for sequential steps -- Use bullet points for non-sequential items -- Tables should have headers and consistent column formatting - -## Search Setup - -### Architecture -- **Search Provider**: Algolia DocSearch via `@vuepress/plugin-docsearch` -- **Index Name**: `prod_rundeck_docs` -- **Indexing**: Automated via CircleCI using `algolia/docsearch-scraper` Docker image -- **Configuration Files**: - - `.docsearch/config.json` - Algolia scraper configuration (selectors, start URLs, faceting attributes) - - `docs/.vuepress/config.ts` - VuePress DocSearch plugin configuration (appId, apiKey, search parameters) -- **Current Facets**: `version` (filters by docs version like "docs", "4.0.x") and `lang` -- **Section-Based Filtering**: Uses `tags` attribute to enable filtering by documentation section (learning, manual, api, administration, developer, etc.) -- **Indexing Strategy**: Tags are applied via URL patterns in `start_urls` to categorize content by documentation section -- **Custom Implementation**: Section filter checkboxes can be added via custom search UI using Algolia InstantSearch or DocSearch customization +You are an AI assistant helping maintain the Rundeck documentation site (VuePress 2, published to `docs.rundeck.com`). + +## Canonical Conventions + +All writing, markdown, content-structure, and tooling conventions for this repository are maintained in a single canonical location to prevent drift across AI tools: + +- **`CLAUDE.md`** (repo root) — project conventions index (symlinked as `AGENTS.md`) +- **`.claude/docs/writing-style.md`** — voice, markdown rules, page structure, DocSearch architecture, content directory layout +- **`.claude/docs/local-development.md`** — environment setup and dev server +- **`.claude/docs/pr-feed.md`** — SaaS PR feed generator +- **`.claude/docs/docsearch-filters.md`** — custom DocSearch section filter +- **`.claude/docs/infrastructure.md`** — CI, deployment, secrets + +Read `CLAUDE.md` first; follow its references into `.claude/docs/` as needed. + +## Critical Constraints (Quick Reference) + +- **Never use emojis in published content.** Font Awesome is available if decorative icons are strictly required. +- **Use ATX-style headers only** (`#`, `##`, `###`); one H1 per page, no skipped levels. +- **Every fenced code block declares its language.** +- **Never hand-edit PR-derived content in generated files** under `docs/history/**/version-*.md` or `docs/history/updates/index.md` — fix the originating PR and re-run the generator. +- **PRs for release notes MUST carry the `release-notes/include` label** and contain a `## Release Notes` section. + +Full detail and rationale in `.claude/docs/writing-style.md` and `CLAUDE.md`. diff --git a/.gitignore b/.gitignore index bd376f1eb..56a4e5537 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ node_modules/ hope/.vuepress/.cache/ hope/.vuepress/.temp/ hope/.vuepress/dist/ +.claude/.sdlc-setup-done diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 000000000..681311eb9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..1d5519acb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,92 @@ +# Project Conventions + +VuePress 2 documentation site serving Rundeck product documentation (published to `docs.rundeck.com`). Content is Markdown under `docs/`, configuration under `docs/.vuepress/`. + +## Documentation + +Detailed conventions and guidance organised by topic: + +### Writing & Content + +- **`.claude/docs/writing-style.md`** — Voice, markdown conventions, page structure checklist, Algolia DocSearch architecture, and content directory layout +- **`.claude/docs/docsearch-filters.md`** — Custom section-filter component (`DocSearchFilters.vue` + `docsearch-filters.ts`) + +### Environment & Infrastructure + +- **`.claude/docs/local-development.md`** — Node setup, Cloudsmith token, dev server, troubleshooting +- **`.claude/docs/infrastructure.md`** — CI workflows, deployment branch/tag rules, NPM registry, secrets + +### Release Pipelines + +- **`.claude/docs/pr-feed.md`** — SaaS PR feed generator (`npm run pr-feed`), tag parsing, templates, troubleshooting + +## Writing Conventions + +### Voice +Clear, concise technical prose. Consistent terminology across pages. Never use emojis in content (Font Awesome is available if decorative icons are strictly required). + +### Markdown +ATX-style headers only. One H1 per page; never skip heading levels. Every fenced code block declares its language. Every image has descriptive alt text. + +### Cross-References +Prefer relative links over absolute `docs.rundeck.com` URLs so that versioned builds resolve correctly. + +Full details in `.claude/docs/writing-style.md`. + +## Content Structure + +| Path | Purpose | +|---|---| +| `docs/manual/` | Core product documentation | +| `docs/administration/` | Administration guides | +| `docs/api/` | API documentation | +| `docs/developer/` | Developer documentation | +| `docs/learning/` | Tutorials and learning resources | +| `docs/history/` | Release notes (generated — see Critical Rules) | +| `docs/history/updates/` | SaaS PR feed page (generated) | +| `docs/.vuepress/` | VuePress config, components, plugins | +| `docs/.vuepress/public/assets/img/` | Image assets | + +## Release Notes & PR Conventions + +### PR Titles +Format when the PR relates to a RUN ticket: `RUN-1234: Description`. The `RUN-XXXX:` prefix is stripped automatically from release notes output. + +### Release-Notes Inclusion +PRs intended for customer-facing release notes MUST carry the `release-notes/include` label and contain a `## Release Notes` section in the body. Without both, the PR is silently excluded from `npm run notes` and `npm run pr-feed`. + +### Generated Files +Files under `docs/history/**/version-*.md` and `docs/history/updates/index.md` are generated. Never hand-edit PR-derived content — fix the originating PR description and re-run the generator. Manual edits are reserved for Overview, dates, and curation. + +## Rules + +Context-aware rules that load automatically based on file patterns: + +- **`.claude/rules/release-notes-pr.md`** — Release-notes PR discipline (label, `## Release Notes` section, generated-file boundaries). Loads for `docs/history/**/version-*.md`, `docs/history/**/draft.md`, and `docs/history/updates/index.md`. + +## Skills + +Available skills for common workflows: + +**Release pipeline:** +- **`write-release-notes`** — End-to-end self-hosted release-notes workflow: draft → tag → final → manual edits → commit. +- **`generate-pr-feed`** — Weekly SaaS PR-feed regeneration (`npm run pr-feed`), including `pr-feed-config.json` maintenance. + +Invoke via the Skill tool: `Skill(skill_name="write-release-notes")`. + +## Critical Rules + +1. **Never commit release-notes output in isolation.** The generated `docs/history/_x/version-.md` MUST be staged alongside `.docsearch/config.json`, `docs/.vuepress/setup.js`, `docs/.vuepress/sidebar-menus/history.ts`, `docs/.vuepress/navbar-menus/about.js`, and `docs/.vuepress/pr-feed-config.json`. +2. **Never hand-edit PR-derived content in generated files.** Fix the PR description and re-run `npm run notes` / `npm run pr-feed`. +3. **Never push to remote without explicit user instruction.** +4. **Never bypass the `release-notes/include` label** — it is the single source of truth for customer-visible changes. +5. **Never introduce emojis into published content.** + +## Extending the AI Setup + +- **`.claude/README.md`** — directory structure +- **`.claude/CONTRIBUTING.md`** — how to add docs, rules, and skills + +## Compaction Note + +When compacting, preserve the Documentation table, Writing Conventions, Content Structure, Rules, Skills, and Critical Rules sections. diff --git a/dev-docs/DOCSEARCH_FILTERS_README.md b/dev-docs/DOCSEARCH_FILTERS_README.md deleted file mode 100644 index f374f147d..000000000 --- a/dev-docs/DOCSEARCH_FILTERS_README.md +++ /dev/null @@ -1,109 +0,0 @@ -# DocSearch Filters Integration - -This guide explains how to integrate the section filtering component into your Rundeck documentation search. - -## Components Created - -### 1. DocSearchFilters.vue -- Location: `docs/.vuepress/components/DocSearchFilters.vue` -- A Vue component that provides a filter button with a dropdown panel -- Shows section checkboxes (Learning, User Guide, API, Administration, Developer, Release Notes, General) -- Persists filter selections in localStorage -- Dispatches custom events when filters change - -### 2. docsearch-filters.ts Plugin -- Location: `docs/.vuepress/plugins/docsearch-filters.ts` -- Client-side plugin that integrates filters with DocSearch -- Listens for filter update events and applies them to DocSearch -- Monitors DocSearch modal for filter restoration - -### 3. Client Configuration Updates -- Updated `docs/.vuepress/client.ts` to initialize the filter integration -- Imports and calls `initializeDocSearchFilters()` on app startup - -## Integration in Layout - -The filter button is **automatically injected** into the navbar by the client configuration. No manual component placement is needed. - -The `injectDocSearchFiltersIntoNavbar()` function in `client.ts` automatically: -1. Waits for the DocSearch container to be rendered -2. Creates a wrapper element next to the search container -3. Mounts the `DocSearchFilters` component dynamically - -This ensures the filter component appears right next to the search button without requiring manual template modifications. - -## How It Works - -1. **User clicks the filter button** (funnel icon with badge) -2. **Filter dropdown opens** showing available section tags -3. **User selects/deselects sections** via checkboxes -4. **Selections are stored** in localStorage for persistence -5. **Filter state is dispatched** via custom `docsearch-filters-updated` event -6. **Plugin intercepts Algolia requests** - The `docsearch-filters.ts` plugin patches `fetch` and `XMLHttpRequest` to intercept all Algolia API calls -7. **Facet filters are injected** - Selected sections are added to the request's `facetFilters` parameter as OR conditions (e.g., `tags:Learning OR tags:API`) -8. **Results are filtered by Algolia** - Algolia returns only results matching the selected section tags -9. **Search input is triggered** - An input event is dispatched to refresh the search results with the new filters applied - -## Configuration - -### Available Sections -The component currently supports these sections (from `config.json` tags): -- Learning -- User Guide -- API -- Administration -- Developer -- Release Notes -- General - -To add new sections: -1. Update `.docsearch/config.json` to add new `start_urls` with tags -2. Update the `sections` array in `DocSearchFilters.vue` - -### VuePress Configuration -The DocSearch configuration in `docs/.vuepress/config.ts` includes: -```typescript -searchParameters: { - hitsPerPage: 100, - facetFilters: [`version:${setup.base}`], - facets: ['tags'] -} -``` - -The `facets: ['tags']` tells Algolia to include tags as filterable attributes. - -## Styling - -The component uses VuePress theme variables for styling: -- `--c-brand` - Brand color -- `--c-border` - Border color -- `--c-text-secondary` - Secondary text color -- `--c-bg`, `--c-bg-light` - Background colors - -It automatically adapts to dark mode using `html.dark` selector. - -## Testing - -To test the filters: - -1. Build/run the docs: `npm run docs:dev` -2. Click the filter button in the navbar -3. Select a section (e.g., "Learning") -4. Perform a search -5. Results should only show items tagged with the selected section -6. Refresh the page - filter selections persist via localStorage - -## Troubleshooting - -### Filters not appearing in results -- Ensure Algolia index has been re-scraped with new tags -- Check that `facets: ['tags']` is in the config -- Verify the section tags match what's in `config.json` - -### localStorage not working -- Browser privacy mode disables localStorage -- Clear localStorage and refresh if there are issues - -### Component not visible -- Ensure DocSearchFilters component is imported and placed in the layout -- Check browser console for Vue component registration errors diff --git a/dev-docs/DOCSEARCH_FILTERS_README.md b/dev-docs/DOCSEARCH_FILTERS_README.md new file mode 120000 index 000000000..ee4bbd6e5 --- /dev/null +++ b/dev-docs/DOCSEARCH_FILTERS_README.md @@ -0,0 +1 @@ +../.claude/docs/docsearch-filters.md \ No newline at end of file diff --git a/dev-docs/PR-FEED-README.md b/dev-docs/PR-FEED-README.md deleted file mode 100644 index c80ff0b67..000000000 --- a/dev-docs/PR-FEED-README.md +++ /dev/null @@ -1,371 +0,0 @@ -# PR Feed Generator - -This script generates RSS/Atom feeds and markdown pages from recently merged pull requests in both the Rundeck repositories. It's designed to be run as part of the SaaS release process to keep customers informed about development updates deployed to the Runbook Automation SaaS platform. - -## Use Case - -**SaaS Release Communication**: The Runbook Automation team releases to SaaS approximately weekly (and sometimes more frequently for urgent updates). These changes come from both the public `rundeck/rundeck` repo and the private `rundeckpro/rundeckpro` repo. Since customers can see public repo changes but not private ones, this tool captures and communicates the full scope of updates deployed to the SaaS platform. - -## Features - -- Fetches merged PRs from **both** repositories: - - **Private**: `rundeckpro/rundeckpro` (filtered by `release-notes/include` label) - - **Public**: `rundeck/rundeck` (filtered by `release-notes/include` label) -- **Tag-based comparison**: Uses git tags instead of dates for accurate PR detection -- **SaaS cut support**: Limits PRs to those included in the most recent SaaS deployment cut -- **Submodule awareness**: Automatically finds the correct rundeck commit from rundeckpro tags -- **All merge strategies**: Handles merge commits, squash merges, and rebase merges -- Combines and sorts PRs by merge date across both repos -- Automatically removes `RUN-XXXX` prefixes from PR titles (matching notes.mjs logic) -- Generates both RSS 2.0 and Atom feeds -- Creates VuePress-compatible markdown pages -- Template-based content using Nunjucks (like notes.mjs) -- Comprehensive error handling - -## Initial Setup (One-time) - -1. **Create `.env` file** in project root: - ```bash - touch .env - ``` - -2. **Add your GitHub token**: - ```env - GH_API_TOKEN=ghp_your_actual_token_here - ``` - - Get a token at: https://github.com/settings/tokens (needs `repo` scope) - -3. **Verify Dependencies** - Already in `package.json`: - - `@octokit/rest` - GitHub API client - - `dotenv` - Environment variable loading - - `yargs` - Command-line argument parsing - - `nunjucks` - Template engine - -## Weekly Release Process - -Run after each SaaS deployment: - -```bash -npm run pr-feed -``` - -The script will automatically: -1. Use git tag comparison (v5.17.0 to SaaS cut tag) to find PRs -2. For rundeckpro: Compare v5.17.0 to the `lastSaasCut` tag -3. For rundeck: Extract the submodule commit from the SaaS cut tag and use that as the endpoint -4. Show only PRs that were included in the most recent SaaS deployment - -Then commit the generated files: - -```bash -git add docs/history/updates/index.md -git add docs/.vuepress/public/feeds/ -git commit -m "Update SaaS deployment feed" -git push -``` - -### Updating the Configuration - -The `pr-feed-config.json` file tracks three key values: - -```json -{ - "lastSelfHostedRelease": { - "version": "5.17.0", - "lastSelfHostedDate": "2025-10-22", - "lastSaasRelease": "2025-11-04", - "lastSaasCut": "rba/5.18-RBA-20251030-2f39445-a6d9e14", - "description": "Last self-hosted release version and date" - } -} -``` - -**When to update each field:** - -1. **`version` and `lastSelfHostedDate`**: Updated automatically by `notes.mjs` when creating self-hosted release notes - ```bash - node ./docs/.vuepress/notes.mjs --milestone=5.18.0 - # Automatically updates version and date - ``` - -2. **`lastSaasRelease`**: Update manually after deploying to SaaS production (displayed on the updates page) - -3. **`lastSaasCut`**: Update manually each time you cut a release (typically weekly) - - This is the tag created when you build the release candidate - - Format: `rba/${vNum}-RBA-${vDate}-${coreSha}-${proSha}` - - `vNum`: Next major version (e.g., `5.18`) - - `vDate`: Date the cut was made (YYYYMMDD) - - `coreSha`: Short SHA of the rundeck (core) submodule commit - - `proSha`: Short SHA of the rundeckpro commit - - Example: `rba/5.18-RBA-20251030-2f39445-a6d9e14` - - The script parses this tag to extract the exact commit SHAs - - PRs merged after these commits will NOT appear in the feed until the next cut - -**Example workflow:** -1. Wednesday: Cut release → Update `lastSaasCut` with the new tag -2. Monday: Deploy to production → Update `lastSaasRelease` with deployment date -3. Monday: Run `npm run pr-feed` → Shows PRs between last release and the cut tag -4. Repeat weekly - -## Command Reference - -### Basic Commands - -```bash -# Default: Show PRs since last self-hosted release -npm run pr-feed - -# Override to use time-based lookback -npm run pr-feed -- --days=7 - -# Include specific section from PR descriptions -npm run pr-feed -- --include-section="Customer Summary" - -# Different repository -node ./docs/.vuepress/pr-feed.mjs --owner=rundeck --repo=rundeck -``` - -### Command-Line Options - -| Option | Alias | Default | Description | -|--------|-------|---------|-------------| -| `--days` | `-d` | *(uses config)* | Number of days to look back (overrides tag-based mode) | -| `--since-release` | | `true` | Use tag-based comparison from last self-hosted release | -| `--labels` | `-l` | `release-notes/include` | Labels to include (space-separated) | -| `--exclude-labels` | | `wip, do-not-publish` | Labels to exclude | -| `--max-prs` | | `100` | Maximum number of PRs to fetch per repository | -| `--include-section` | | `Release Notes` | Include specific section from PR body | -| `--output-dir` | | `docs/history/updates` | Output directory for markdown page | -| `--help` | | | Show help message | - -### How It Works - -**Tag-Based Comparison (Default):** -1. Reads `pr-feed-config.json` to get: - - Last self-hosted release version (e.g., `5.17.0`) - - SaaS cut tag (e.g., `rba/5.18-RBA-20251030-2f39445-a6d9e14`) -2. Parses the SaaS cut tag to extract: - - `coreSha` (2f39445): The rundeck submodule commit - - `proSha` (a6d9e14): The rundeckpro commit -3. For **rundeckpro**: Compares `v5.17.0` tag to `proSha` commit using `git.compareCommits` -4. For **rundeck**: Compares `v5.17.0` tag to `coreSha` commit using `git.compareCommits` -5. Finds all commits between these points and extracts associated PRs -6. Handles all merge strategies: merge commits, squash merges, and rebase merges -7. Filters by `release-notes/include` label -8. Combines results and sorts by merge date - -**Why Tag-Based?** -- More accurate than date-based queries -- Tags represent actual git history, not approximate dates -- Correctly handles retroactive tagging scenarios -- Aligns with actual release cuts and deployments -- Ensures you only see PRs that are truly included in the deployment - -**Tag Format Benefits:** -- Minimal API lookups needed - commit SHAs are directly in the tag (no tag object API calls needed) -- Guarantees accuracy - uses the exact commits from the build -- Simple parsing - reliable extraction from standardized format -- Fast execution - no need to traverse git trees - -## What Gets Generated - -### 1. Markdown Page -**Location**: `docs/history/updates/index.md` -**URL**: https://docs.rundeck.com/docs/history/updates/ - -A complete, standalone VuePress page with: -- Context about the updates and SaaS releases -- Subscription links for RSS/Atom feeds -- Information about the difference between SaaS and self-hosted -- All PRs listed under "Recent Changes" heading -- Rich formatting with cleaned PR titles and merge dates -- Optional "Release Notes" sections from PR descriptions -- Automatic reference to the last self-hosted release version - -**Note**: This file is completely regenerated on each run from the template. To modify static content, edit the template (see below). - -### 2. RSS Feed -**Location**: `docs/.vuepress/public/feeds/development.xml` -**URL**: https://docs.rundeck.com/feeds/development.xml - -RSS 2.0 feed compatible with most feed readers. - -### 3. Atom Feed -**Location**: `docs/.vuepress/public/feeds/development-atom.xml` -**URL**: https://docs.rundeck.com/feeds/development-atom.xml - -Atom 1.0 feed for modern feed readers. - -## Editing Static Content - -The generated `index.md` page is created from a Nunjucks template, similar to how `notes.mjs` works: - -**Template Location**: `docs/.vuepress/pr-feed.md.nj` - -To change the static parts of the page (headings, descriptions, subscription text, etc.), edit the template file directly. - -### Template Variables - -- `{{currentDate}}` - ISO timestamp for frontmatter -- `{{lastUpdated}}` - Formatted date string (YYYY-MM-DD) -- `{{periodDescription}}` - Description like "merged since the last self-hosted release" -- `{{releaseInfo}}` - Optional release version and date info -- `{{prs}}` - Array of PR objects with: - - `cleanTitle` - PR title with RUN-XXXX prefixes removed - - `mergedDate` - Formatted merge date - - `sectionContent` - Optional extracted section (if `--include-section` used) - -After editing the template, run `npm run pr-feed` to regenerate the page with your changes. - -## PR Description Best Practices - -Structure your PR descriptions with clear sections to take advantage of the `--include-section` feature: - -```markdown -## Release Notes -Brief customer-facing description of the change. -This will be included in the feed by default. - -## PR Details -Implementation specifics and technical details. - -## Testing -Testing approach and verification steps. -``` - -**Default section**: `## Release Notes` is extracted by default. Make sure to include another section header (like `## PR Details`) to separate customer-facing content from internal details. - -## Integration with Release Process - -### SaaS Deployment -1. Deploy to SaaS production -2. Run `npm run pr-feed` -3. Review generated `docs/history/updates/index.md` -4. Commit and push changes - -### Self-Hosted Release -When creating a new self-hosted release, `notes.mjs` automatically updates the PR feed baseline: - -```bash -node ./docs/.vuepress/notes.mjs --milestone=5.9.0 -# Automatically updates pr-feed-config.json -``` - -Next time you run `npm run pr-feed`, it will show PRs merged after this release date. - -## Troubleshooting - -### "GH_API_TOKEN not set" -Create `.env` file with your GitHub token (see Initial Setup). - -### "Repository not found" -- Verify your token has the `repo` scope -- Check that you have access to the private repository -- Ensure the owner/repo names are correct - -### "No PRs found" -- No PRs were merged in the specified time period -- No PRs have the required labels -- Try expanding the date range: `--days=30` - -### PR titles still have RUN-XXXX prefixes -The script automatically removes `RUN-XXXX` prefixes using the regex pattern: `/^(RUN-[0-9]+\s*)+:?\s*/g` - -If you see prefixes that weren't removed, they may not match this pattern. - -## Customization - -### Time-Based Override -For testing or special cases, you can override tag-based comparison with time-based: -```bash -npm run pr-feed -- --days=7 -``` - -### Label Filtering -Use different labels: -```bash -node ./docs/.vuepress/pr-feed.mjs --labels feature bugfix enhancement -``` - -### Output Location -```bash -node ./docs/.vuepress/pr-feed.mjs --output-dir=./docs/some-other-location -``` - -## Implementation Notes - -### Shared Utilities with notes.mjs - -Both `pr-feed.mjs` and `notes.mjs` now use shared functions from `pr-utils.mjs`: - -**Shared Functions:** -- `fetchPRsBetweenTags()` - Compare two git tags to find all PRs -- `extractPRSection()` - Extract specific sections from PR bodies (e.g., "Release Notes") -- `cleanPRTitle()` - Remove RUN-XXXX prefixes from PR titles -- `parseSaasCutTag()` - Parse SaaS cut tag format to extract commit SHAs -- `getPreviousVersion()` - Auto-decrement version numbers (e.g., 5.17.0 → 5.16.0) - -This ensures consistent behavior across both scripts and reduces code duplication. - -### Pattern Matching with notes.mjs - -This script follows the same patterns as `notes.mjs`: -- Uses ES modules (`.mjs` extension) -- Uses Nunjucks templates for content generation -- Uses Octokit for GitHub API access -- Loads environment variables with `dotenv` -- Parses CLI arguments with `yargs` -- Follows the same code style and structure -- Both now use tag-based comparison (not milestones) - -### Key Differences from notes.mjs - -- **SaaS vs Self-Hosted**: Focuses on SaaS deployments vs version-specific self-hosted releases -- **Continuous updates**: Generates standalone pages for ongoing updates vs one-time release notes -- **SaaS cut awareness**: Limits PRs to those in the deployment cut (not everything in main) -- **Submodule handling**: Automatically resolves rundeck submodule commits from rundeckpro tags -- **Customer communication**: Designed for SaaS customers via RSS/Atom feeds -- **Feed generation**: Creates RSS 2.0 and Atom 1.0 feeds - -### Similarity to notes.mjs - -- **Both use tag-based comparison**: Compare git tags to find PRs between releases -- **Both handle all merge strategies**: Merge commits, squash merges, and rebase merges -- **Both use shared utilities**: Common functions from `pr-utils.mjs` -- **Both extract PR sections**: Pull "Release Notes" sections from PR bodies -- **Both filter by labels**: Use `release-notes/include` for enterprise PRs - -## Technical Details - -### Merge Strategy Support -The script handles all three GitHub merge strategies: -1. **Merge commits**: Detected via "Merge pull request #X" in commit message -2. **Squash merges**: Detected via `listPullRequestsAssociatedWithCommit` API -3. **Rebase merges**: Each rebased commit is associated with its PR via GitHub API - -### SaaS Cut Tag Parsing -The `lastSaasCut` tag follows the format: `rba/${vNum}-RBA-${vDate}-${coreSha}-${proSha}` - -Example: `rba/5.18-RBA-20251030-2f39445-a6d9e14` -- `5.18` - Next major version number -- `20251030` - Cut date (October 30, 2025) -- `2f39445` - Short SHA of rundeck (core) submodule commit -- `a6d9e14` - Short SHA of rundeckpro commit - -The script parses this format to extract commit SHAs directly, eliminating the need for: -- API calls to fetch tag objects -- Git tree traversal to find submodules -- Handling of annotated vs lightweight tags - -This ensures the exact commits used in the build are compared, guaranteeing accuracy. - -## Related Documentation - -- **[README.md](../README.md)** - Main documentation project setup and release notes generation -- **`notes.mjs`** - Self-hosted release notes generator (uses same shared utilities) -- **`pr-utils.mjs`** - Shared utility functions used by both scripts - -## License - -This script is part of the Rundeck documentation project and follows the same license as the main repository. diff --git a/dev-docs/PR-FEED-README.md b/dev-docs/PR-FEED-README.md new file mode 120000 index 000000000..f924d55ab --- /dev/null +++ b/dev-docs/PR-FEED-README.md @@ -0,0 +1 @@ +../.claude/docs/pr-feed.md \ No newline at end of file From 8a643a4f406e71c0f747e07bf84c5f7a81dbb3c2 Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:12:58 -0600 Subject: [PATCH 04/13] Address Copilot review comments on PR #1793 - docsearch-filters.md: replace outdated "import/place in layout" troubleshooting advice with pointers to the actual auto-injection logic in docs/.vuepress/client.ts (#docsearch-container) - pr-feed.md: remove git push from the sample commit block; add explicit reminder of the "no push without instruction" rule - write-release-notes SKILL: split Phase 2 into "create tag" and "push tag" so the push step requires explicit confirmation; add Phase 6 to gate the final push - settings.json: - drop Read(~/.cache/pagerduty/**) now that the manifest pre-task block has been removed - broaden Write/Edit docs globs to docs/**/*.md and docs/.vuepress/** so the agent can actually edit manual/administration/api/developer content - add .docsearch/config.json and .claude/** for legitimate maintenance writes - local-development.md, infrastructure.md: promote top heading to H1 for consistency with the other .claude/docs/* files - infrastructure.md: correct check-milestone.yml trigger description to reflect pull_request_target with opened/synchronize/reopened/ milestoned/demilestoned on 4.0.x Made-with: Cursor --- .claude/docs/docsearch-filters.md | 5 +++-- .claude/docs/infrastructure.md | 14 +++++++------- .claude/docs/local-development.md | 14 +++++++------- .claude/docs/pr-feed.md | 3 ++- .claude/settings.json | 12 +++++++++--- .claude/skills/write-release-notes/SKILL.md | 20 ++++++++++++++++++-- 6 files changed, 46 insertions(+), 22 deletions(-) diff --git a/.claude/docs/docsearch-filters.md b/.claude/docs/docsearch-filters.md index f374f147d..3b75c9393 100644 --- a/.claude/docs/docsearch-filters.md +++ b/.claude/docs/docsearch-filters.md @@ -105,5 +105,6 @@ To test the filters: - Clear localStorage and refresh if there are issues ### Component not visible -- Ensure DocSearchFilters component is imported and placed in the layout -- Check browser console for Vue component registration errors +- Check the navbar injection logic in `docs/.vuepress/client.ts` (`injectDocSearchFiltersIntoNavbar()`) +- Verify the `#docsearch-container` mount target exists in the rendered navbar DOM +- Check the browser console for client-side mount or selector errors diff --git a/.claude/docs/infrastructure.md b/.claude/docs/infrastructure.md index dc2f5e549..964132e56 100644 --- a/.claude/docs/infrastructure.md +++ b/.claude/docs/infrastructure.md @@ -1,20 +1,20 @@ -## Infrastructure +# Infrastructure -### CI/CD — GitHub Actions +## CI/CD — GitHub Actions | Workflow | Trigger | Purpose | |---|---|---| -| `check-milestone.yml` | PR open/sync/reopen on `4.0.x` | Enforces milestone on every PR | -| `snyk-scan.yml` | PR + push | Snyk security scan; monitors default branch, blocks on high-severity vulns | +| `check-milestone.yml` | `pull_request_target` (`opened`, `synchronize`, `reopened`, `milestoned`, `demilestoned`) on `4.0.x` | Enforces milestone on every PR | +| `snyk-scan.yml` | `pull_request` + `push` | Snyk security scan; monitors default branch, blocks on high-severity vulns | | `update-pr-feed.yml` | `workflow_dispatch` | Updates PR feed config for a given RBA tag, regenerates feed files, opens a PR | Node.js version is pinned via `.nvmrc` (22.22.0); all workflows use `actions/setup-node@v4` with `node-version-file: '.nvmrc'`. -### NPM Registry +## NPM Registry Private registry at `npm.artifacts.pd-internal.com` (Cloudsmith). Configured in `.npmrc`. Requires `CLOUDSMITH_NPM_TOKEN` secret in CI and locally. -### Deployment +## Deployment Docs are published to `docs.rundeck.com` based on branch/tag naming: @@ -26,7 +26,7 @@ Docs are published to `docs.rundeck.com` based on branch/tag naming: Maintenance branches for older versions follow the pattern `git checkout -b 3.2.8`. -### Secrets +## Secrets | Secret | Used by | |---|---| diff --git a/.claude/docs/local-development.md b/.claude/docs/local-development.md index ed2fb24eb..2478a1839 100644 --- a/.claude/docs/local-development.md +++ b/.claude/docs/local-development.md @@ -1,14 +1,14 @@ -## Local Development +# Local Development VuePress 2 documentation site for Rundeck. Content lives in `docs/`, config in `docs/.vuepress/`. -### Prerequisites +## Prerequisites - Node.js 22.22.0 (managed via nvm — version pinned in `.nvmrc`) - Cloudsmith NPM token (`CLOUDSMITH_NPM_TOKEN`) for the internal registry at `npm.artifacts.pd-internal.com` - GitHub API token (`GH_API_TOKEN`) only needed for release notes generation -### Setup +## Setup ```shell nvm install && nvm use @@ -23,7 +23,7 @@ rm .npmrc npm install ``` -### Running locally +## Running locally ```shell npm run docs:dev # dev server with hot reload @@ -32,7 +32,7 @@ npm run docs:no-cache # dev server, skip cache npm run docs:clean-dev # dev server, clear cache first ``` -### Release notes +## Release notes ```shell # Draft (safe, doesn't modify config files) @@ -44,7 +44,7 @@ npm run notes -- --milestone=5.17.0 Requires `GH_API_TOKEN` in a `.env` file at the repo root. PRs must have the `release-notes/include` label. -### PR feed (SaaS development updates) +## PR feed (SaaS development updates) ```shell npm run pr-feed @@ -52,7 +52,7 @@ npm run pr-feed Regenerates RSS/Atom feeds and `docs/history/updates/index.md` for SaaS deployments not yet in a self-hosted release. See `.claude/docs/pr-feed.md` for details. -### Troubleshooting +## Troubleshooting ```shell # Clean reinstall diff --git a/.claude/docs/pr-feed.md b/.claude/docs/pr-feed.md index 292867c86..7fbce7a8c 100644 --- a/.claude/docs/pr-feed.md +++ b/.claude/docs/pr-feed.md @@ -62,9 +62,10 @@ Then commit the generated files: git add docs/history/updates/index.md git add docs/.vuepress/public/feeds/ git commit -m "Update SaaS deployment feed" -git push ``` +Do not push to the remote without explicit user instruction — see `CLAUDE.md` Critical Rules. + ### Updating the Configuration The `pr-feed-config.json` file tracks three key values: diff --git a/.claude/settings.json b/.claude/settings.json index 7e9d325c9..028be2ffe 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,7 +2,6 @@ "permissions": { "allow": [ "Read", - "Read(~/.cache/pagerduty/**)", "Read(~/.claude/plugins/**)", "Glob", "Grep", @@ -75,8 +74,15 @@ "Write(AGENTS.md)", "Edit(AGENTS.md)", "Write(CLAUDE.md)", - "Write(docs/*.md)", - "Edit(docs/*.md)", + "Edit(CLAUDE.md)", + "Write(docs/**/*.md)", + "Edit(docs/**/*.md)", + "Write(docs/.vuepress/**)", + "Edit(docs/.vuepress/**)", + "Write(.docsearch/config.json)", + "Edit(.docsearch/config.json)", + "Write(.claude/**)", + "Edit(.claude/**)", "Edit(.gitignore)", "Bash(bash */hooks/scripts/sync-standards.sh*)", "Bash(bash */skills/start-task/scripts/validate-spec.sh *)", diff --git a/.claude/skills/write-release-notes/SKILL.md b/.claude/skills/write-release-notes/SKILL.md index 609814084..ad9d7d60f 100644 --- a/.claude/skills/write-release-notes/SKILL.md +++ b/.claude/skills/write-release-notes/SKILL.md @@ -43,12 +43,19 @@ npm run notes:draft -- --milestone= ### Phase 2: Tag the release +Only proceed when the user has confirmed the draft content. Split tag creation from pushing so the user can confirm each step. + +Create the local tag: + ```bash git tag v -git push origin v ``` -Only tag when the user has confirmed the draft content. +Push the tag ONLY after the user explicitly confirms (Critical Rule: never push without explicit instruction): + +```bash +git push origin v +``` ### Phase 3: Generate final notes (updates configs) @@ -84,6 +91,15 @@ git commit -m "Release notes for " Do NOT push without the user's explicit instruction. +### Phase 6: Push (only on explicit instruction) + +Once the user confirms, push the commit (and, if not already pushed, the tag): + +```bash +git push +git push origin v # if not already pushed in Phase 2 +``` + ## Version Auto-Detection | Target | Auto-detected previous | Compares | From 90d05943fb53b2ce8d5da822e2a040607bd3b8a0 Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:26:45 -0600 Subject: [PATCH 05/13] Address second round of Copilot review comments on PR #1793 Factual corrections verified against docs/.vuepress/notes.mjs, docs/.vuepress/pr-feed.mjs, and package.json: - .claude/README.md: correct symlink destination wording to ".claude/docs/" (not "docs/") so the canonical source is clear - .claude/docs/pr-feed.md: - remove invalid "--owner=... --repo=..." example - correct --include-section default column: the script has no default; "Release Notes" is supplied by the npm run pr-feed script in package.json - CLAUDE.md, .claude/skills/write-release-notes/SKILL.md, .claude/rules/release-notes-pr.md: remove ".docsearch/config.json" from the release-notes side-effect staging list since notes.mjs no longer updates it (updateDocsearchVersion is commented out). Add a "run git status to catch any other modified files" instruction so guidance stays accurate if the script evolves. Made-with: Cursor --- .claude/README.md | 2 +- .claude/docs/pr-feed.md | 5 +---- .claude/rules/release-notes-pr.md | 2 +- .claude/skills/write-release-notes/SKILL.md | 4 ++-- CLAUDE.md | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.claude/README.md b/.claude/README.md index 3370fd36f..99cb4359f 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -39,7 +39,7 @@ Human-authored reference material consulted by the agent and developers alike. | `docs/pr-feed.md` | Working on the SaaS PR feed generator (`npm run pr-feed`) or `pr-feed.mjs` | | `docs/docsearch-filters.md` | Modifying the DocSearch filter component or `.vuepress/plugins/docsearch-filters.ts` | -Legacy paths at `dev-docs/DOCSEARCH_FILTERS_README.md` and `dev-docs/PR-FEED-README.md` are symlinks into `docs/` — the canonical copies live here. +Legacy paths at `dev-docs/DOCSEARCH_FILTERS_README.md` and `dev-docs/PR-FEED-README.md` are symlinks into `.claude/docs/` — the canonical copies live here. --- diff --git a/.claude/docs/pr-feed.md b/.claude/docs/pr-feed.md index 7fbce7a8c..bdd3f31a1 100644 --- a/.claude/docs/pr-feed.md +++ b/.claude/docs/pr-feed.md @@ -122,9 +122,6 @@ npm run pr-feed -- --days=7 # Include specific section from PR descriptions npm run pr-feed -- --include-section="Customer Summary" - -# Different repository -node ./docs/.vuepress/pr-feed.mjs --owner=rundeck --repo=rundeck ``` ### Command-Line Options @@ -136,7 +133,7 @@ node ./docs/.vuepress/pr-feed.mjs --owner=rundeck --repo=rundeck | `--labels` | `-l` | `release-notes/include` | Labels to include (space-separated) | | `--exclude-labels` | | `wip, do-not-publish` | Labels to exclude | | `--max-prs` | | `100` | Maximum number of PRs to fetch per repository | -| `--include-section` | | `Release Notes` | Include specific section from PR body | +| `--include-section` | | *(none)* | Include specific section from PR body. The `npm run pr-feed` script passes `--include-section="Release Notes"` by default; when invoking `pr-feed.mjs` directly, no default applies. | | `--output-dir` | | `docs/history/updates` | Output directory for markdown page | | `--help` | | | Show help message | diff --git a/.claude/rules/release-notes-pr.md b/.claude/rules/release-notes-pr.md index 37b308697..dbbb6b335 100644 --- a/.claude/rules/release-notes-pr.md +++ b/.claude/rules/release-notes-pr.md @@ -15,7 +15,7 @@ alwaysApply: false 2. PR descriptions MUST include a `## Release Notes` section containing the customer-facing copy. Add a second heading (e.g. `## Technical Details` or `## PR Details`) to separate internal notes from extracted content. 3. PR titles should be free of `RUN-XXXX:` Jira prefixes in the customer-facing output. The scripts strip `^(RUN-[0-9]+\s*)+:?\s*` automatically; non-matching prefixes must be cleaned manually in the generated markdown. 4. Never edit generated `docs/history/_x/version-.md` content that originated from PR bodies — fix the PR description and re-run the notes script. Manual edits are reserved for the Overview, dates, and final curation. -5. Never commit the results of `notes.mjs` / `pr-feed.mjs` in isolation — the side-effect config files (`.docsearch/config.json`, `pr-feed-config.json`, `setup.js`, `sidebar-menus/history.ts`, `navbar-menus/about.js`) must be staged in the same commit. +5. Never commit the results of `notes.mjs` / `pr-feed.mjs` in isolation — the side-effect config files (`pr-feed-config.json`, `setup.js`, `sidebar-menus/history.ts`, `navbar-menus/about.js`) must be staged in the same commit. Run `git status` after the generator to catch any other modified files. ## Detailed Guidance diff --git a/.claude/skills/write-release-notes/SKILL.md b/.claude/skills/write-release-notes/SKILL.md index ad9d7d60f..413ba32fd 100644 --- a/.claude/skills/write-release-notes/SKILL.md +++ b/.claude/skills/write-release-notes/SKILL.md @@ -64,7 +64,6 @@ npm run notes -- --milestone= ``` This updates, in addition to the notes markdown: -- `.docsearch/config.json` — search indexing version - `docs/.vuepress/setup.js` — version info - `docs/.vuepress/sidebar-menus/history.ts` — sidebar link - `docs/.vuepress/navbar-menus/about.js` — navbar link @@ -84,11 +83,12 @@ git add docs/history/_x/version-.md git add docs/.vuepress/sidebar-menus/history.ts git add docs/.vuepress/navbar-menus/about.js git add docs/.vuepress/setup.js -git add .docsearch/config.json git add docs/.vuepress/pr-feed-config.json git commit -m "Release notes for " ``` +Use `git status` to confirm no additional side-effect files were modified; stage any that appear. + Do NOT push without the user's explicit instruction. ### Phase 6: Push (only on explicit instruction) diff --git a/CLAUDE.md b/CLAUDE.md index 1d5519acb..5217c6168 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,7 @@ Invoke via the Skill tool: `Skill(skill_name="write-release-notes")`. ## Critical Rules -1. **Never commit release-notes output in isolation.** The generated `docs/history/_x/version-.md` MUST be staged alongside `.docsearch/config.json`, `docs/.vuepress/setup.js`, `docs/.vuepress/sidebar-menus/history.ts`, `docs/.vuepress/navbar-menus/about.js`, and `docs/.vuepress/pr-feed-config.json`. +1. **Never commit release-notes output in isolation.** The generated `docs/history/_x/version-.md` MUST be staged alongside `docs/.vuepress/setup.js`, `docs/.vuepress/sidebar-menus/history.ts`, `docs/.vuepress/navbar-menus/about.js`, and `docs/.vuepress/pr-feed-config.json`. Run `git status` after the generator to catch any other modified files and stage them too. 2. **Never hand-edit PR-derived content in generated files.** Fix the PR description and re-run `npm run notes` / `npm run pr-feed`. 3. **Never push to remote without explicit user instruction.** 4. **Never bypass the `release-notes/include` label** — it is the single source of truth for customer-visible changes. From 889b1376af2cda140cfd304e234b61430e078abe Mon Sep 17 00:00:00 2001 From: Filipe Roque Date: Thu, 23 Apr 2026 12:16:03 +0100 Subject: [PATCH 06/13] Fix typo in script file step description --- docs/manual/jobs/job-plugins/node-steps/builtin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/jobs/job-plugins/node-steps/builtin.md b/docs/manual/jobs/job-plugins/node-steps/builtin.md index 0586fc613..636d75b17 100644 --- a/docs/manual/jobs/job-plugins/node-steps/builtin.md +++ b/docs/manual/jobs/job-plugins/node-steps/builtin.md @@ -20,7 +20,7 @@ If an exception is thrown with the message `Cannot run program, error = 26 Text ### Script file step -Executes the script file local to the sever to the filtered Node +Executes the script file local to the server to the filtered Node set. Arguments can be passed to the script by specifying them in the lower text field. @@ -262,4 +262,4 @@ On version 3.4.1 quoting in jobs was changed. If you want to keep using the same ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.bash} rundeck.feature.quoting.backwardCompatible=true ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -::: \ No newline at end of file +::: From 9c13aeb8ea1d1931d3c77ee74ebe864624c61882 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:28:58 +0000 Subject: [PATCH 07/13] Fix node filter description wording Agent-Logs-Url: https://github.com/rundeck/docs/sessions/6960f553-1606-4f65-95c1-0d0c5d33e5c7 Co-authored-by: jtobard <2894508+jtobard@users.noreply.github.com> --- docs/manual/11-node-filters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/11-node-filters.md b/docs/manual/11-node-filters.md index 3c8d2de6f..f29c98873 100644 --- a/docs/manual/11-node-filters.md +++ b/docs/manual/11-node-filters.md @@ -40,7 +40,7 @@ Include mynode and exclude mynode2: mynode1 !nodename: mynode2 -Include nodes with both of the tags `www` and `prod` or either of the given hostnames: +Include nodes with both the `www` and `prod` tags and either of the given hostnames: tags: www+prod hostname: dev1.example.com,dev2.example.com From 0f9c4bb58b4060276f4f0fa274374d86ac4b00ad Mon Sep 17 00:00:00 2001 From: smartinellibenedetti <139791797+smartinellibenedetti@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:35:20 -0600 Subject: [PATCH 08/13] Update SKILL.md --- .claude/skills/write-release-notes/SKILL.md | 31 +++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.claude/skills/write-release-notes/SKILL.md b/.claude/skills/write-release-notes/SKILL.md index 413ba32fd..c5f015f22 100644 --- a/.claude/skills/write-release-notes/SKILL.md +++ b/.claude/skills/write-release-notes/SKILL.md @@ -5,7 +5,7 @@ description: Generate self-hosted release notes for a Rundeck version using tag- # Write Release Notes -Guides the full release-notes workflow for a Rundeck self-hosted milestone using `npm run notes` / `npm run notes:draft`. Auto-loads prerequisites, runs the draft, and helps finalise after tagging. +Guides the full release-notes workflow for a Rundeck self-hosted milestone using `npm run notes`. Auto-loads prerequisites, runs the draft, and helps finalise after tagging. ## When to Use @@ -31,17 +31,32 @@ Read these before executing: ## Process -### Phase 1: Draft preview (safe, does not modify configs) +### Phase 1: Gather parameters +Before running anything, confirm with the user: + +1. **Target milestone** (required) — e.g. `5.20.1`. If the user has not stated it, ask. +2. **Baseline version** (`--from-version`, optional) — ask the user: *"Should I auto-detect the starting version from the previous tag, or do you want to specify it explicitly (e.g. `--from-version=5.20.0`)?"* Auto-detection is the default and usually correct; an explicit baseline is needed for patch releases or unusual ranges. + +### Phase 2: Run the draft preview (safe — does not modify configs) + +Use the form that matches what the user provided in Phase 1: + +**If the user did not supply `--from-version` (auto-detect):** ```bash npm run notes:draft -- --milestone= ``` +**If the user supplied an explicit `--from-version`:** +```bash +npm run notes -- --milestone= --from-version= --draft +``` + - If the tag `v` does not yet exist, draft mode falls back to `HEAD` and prints a warning — this is expected. - Output: `docs/history/_x/draft.md`. - Review the draft with the user before proceeding. -### Phase 2: Tag the release +### Phase 3: Tag the release Only proceed when the user has confirmed the draft content. Split tag creation from pushing so the user can confirm each step. @@ -57,7 +72,7 @@ Push the tag ONLY after the user explicitly confirms (Critical Rule: never push git push origin v ``` -### Phase 3: Generate final notes (updates configs) +### Phase 4: Generate final notes (updates configs) ```bash npm run notes -- --milestone= @@ -69,14 +84,14 @@ This updates, in addition to the notes markdown: - `docs/.vuepress/navbar-menus/about.js` — navbar link - `docs/.vuepress/pr-feed-config.json` — PR feed baseline -### Phase 4: Manual edits +### Phase 5: Manual edits Open `docs/history/_x/version-.md` and fill in: - Release date - Overview section - Any final descriptions or curated ordering -### Phase 5: Commit +### Phase 6: Commit ```bash git add docs/history/_x/version-.md @@ -91,7 +106,7 @@ Use `git status` to confirm no additional side-effect files were modified; stage Do NOT push without the user's explicit instruction. -### Phase 6: Push (only on explicit instruction) +### Phase 7: Push (only on explicit instruction) Once the user confirms, push the commit (and, if not already pushed, the tag): @@ -108,7 +123,7 @@ git push origin v # if not already pushed in Phase 2 | `5.17.1` | `5.17.0` | v5.17.0 → v5.17.1 | | `5.0.0` | `4.17.0` | v4.17.0 → v5.0.0 | -Override with `--from-version=` for patch releases or special ranges. +If the user provides `--from-version`, pass it explicitly (Phase 2 explicit form). Otherwise, auto-detection is used. Ask the user in Phase 1 if the milestone is a patch release and the baseline is ambiguous. ## Troubleshooting From 096b8f94ff95120260ac3789b4cae4d9a5afd7b7 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Mon, 27 Apr 2026 14:27:18 -0400 Subject: [PATCH 09/13] Add performance tuning section for high-throughput Runners Document the JVM system properties available to tune Runner capacity when users hit "Runner did not deliver reports in the configured timeout period" errors under heavy load. Covers: - Operation concurrency (runner.operations.maxRunning) - Report delivery batching (sendRate, sendBatchSize) with a warning that changing these puts additional load on the server and the defaults are recommended - Micronaut HTTP client pool and timeouts Co-Authored-By: Claude Sonnet 4.5 --- docs/administration/runner/runner-config.md | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/docs/administration/runner/runner-config.md b/docs/administration/runner/runner-config.md index 05f5b1d12..1f97f68c4 100644 --- a/docs/administration/runner/runner-config.md +++ b/docs/administration/runner/runner-config.md @@ -57,6 +57,99 @@ Example: java -Drunner.rundeck.overrideTempDir=true -Drunner.dirs.tmp=/your/custom/dir -jar runner.jar ``` +## Performance tuning for high-throughput Runners + +When a Runner handles a large volume of concurrent operations or produces a large volume of log output, the default configuration may not be sufficient. In that case, the server can emit the error: + +``` +Failed: IOFailure: Runner did not deliver reports in the configured timeout period +``` + +This error means the server did not receive any status report from the Runner for a consecutive 10-minute window for an in-flight operation. It typically indicates that the Runner is saturated, not that the operation itself hung. + +The properties below are the main levers available to increase capacity and avoid this class of error. All of them are JVM system properties — pass them with `-D` on the Runner command line (or via `command:` in Docker Compose). + +### Operation concurrency + +The Runner runs each operation in a thread from a fixed-size pool. When the pool is full, additional operations wait in an unbounded queue, and no status reports are emitted for queued operations. + +#### `runner.operations.maxRunning` + +* **Default:** `50` +* **Purpose:** Maximum number of operations the Runner will execute in parallel. +* **When to increase:** If you regularly submit bursts of more than 50 operations and see operations sitting "Queued" for a long time, or if the server reports timeouts for operations that were sitting in the queue. +* **Caveats:** More concurrency means more threads, more memory and more concurrent log streams. Increase [`-Xmx`](#configure-java-heap-size) proportionally. + +Example — double the default concurrency: + +``` +java -Drunner.operations.maxRunning=100 -Xmx8g -jar pd-runner.jar +``` + +### Report delivery + +The Runner batches status reports in memory and flushes them to the server on a fixed interval. Under high log volume, the batch may fill before the interval elapses; if the flush rate is too slow, the in-memory queue grows and the server eventually times out waiting for a batch. + +:::warning +Changing these values increases the rate and/or size of HTTP requests that reach the server. On busy deployments this can significantly raise CPU, memory and database load on the Rundeck server. **The default values are recommended.** Only tune these properties after confirming the Runner is the bottleneck, and validate the server's resource usage after each change. +::: + +#### `runner.reporter.sendRate` + +* **Default:** `2s` +* **Purpose:** How often the Runner flushes queued reports to the server. +* **When to decrease:** When operations produce heavy log output and reports accumulate in memory faster than they are sent. A lower value (e.g. `1s`) flushes more often but increases HTTP request rate. + +#### `runner.reporter.sendBatchSize` + +* **Default:** `1000` +* **Purpose:** Maximum number of reports sent in a single HTTP request. +* **When to increase:** When you see the Runner hitting the batch cap repeatedly (reports accumulate because each flush can only drain 1000 at a time). Larger batches are more efficient per request but produce larger payloads. + +Example — flush faster with bigger batches: + +``` +java -Drunner.reporter.sendRate=1s -Drunner.reporter.sendBatchSize=2000 -jar pd-runner.jar +``` + +### HTTP client tuning + +The Runner uses a Micronaut HTTP client to deliver reports and poll for new operations. Under load, the default connection pool and timeouts may be too small. + +#### `micronaut.http.client.pool.max-connections` + +* **Default:** `50` +* **Purpose:** Size of the HTTP connection pool used by the Runner to talk to the server. +* **When to increase:** When the Runner is running many concurrent operations (`runner.operations.maxRunning` increased) and you also increase the polling / reporting frequency. A good rule of thumb is to keep this greater than or equal to `maxRunning`. + +#### `micronaut.http.client.pool.acquire-timeout` + +* **Default:** `10s` +* **Purpose:** Maximum time a thread will wait for a free connection in the pool before failing. +* **When to increase:** When you see intermittent `HttpClientException` or "connection not available" errors under load. Increase together with `max-connections`. + +#### `micronaut.http.client.read-timeout` + +* **Default:** `60s` +* **Purpose:** Maximum time to wait for a single HTTP response from the server. +* **When to increase:** Only when the network path to the server is slow or the server is under heavy load and legitimately takes longer to acknowledge batched reports. + +#### `micronaut.http.client.connect-timeout` + +* **Default:** `10s` +* **Purpose:** Maximum time to establish a TCP connection to the server. + +Example — expand the HTTP pool for a high-concurrency Runner: + +``` +java \ + -Drunner.operations.maxRunning=100 \ + -Dmicronaut.http.client.pool.max-connections=120 \ + -Dmicronaut.http.client.pool.acquire-timeout=30s \ + -Xmx8g \ + -jar pd-runner.jar +``` + ## Runner APIs [Runner APIs](/api/index.md#runner-management) are available to create, edit, download, and delete Runners. From 240847895e26c8a872cd5d7ff5500f58f9dce346 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:04:11 +0000 Subject: [PATCH 10/13] Standardize jar filename to pd-runner.jar across runner-config.md Agent-Logs-Url: https://github.com/rundeck/docs/sessions/f81ab751-17b4-4ece-a887-54e2cd3d4700 Co-authored-by: ltamaster <6034968+ltamaster@users.noreply.github.com> --- docs/administration/runner/runner-config.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/administration/runner/runner-config.md b/docs/administration/runner/runner-config.md index 1f97f68c4..a1ee8d45e 100644 --- a/docs/administration/runner/runner-config.md +++ b/docs/administration/runner/runner-config.md @@ -12,7 +12,7 @@ Runners can be configured to connect through a HTTP/HTTPS proxy. Proxies are com The following example will allow the runner to connect through the secure company proxy with address wp.acme.corp. ``` -java -Dmicronaut.http.client.proxy-type=http -Dmicronaut.http.client.proxy-address=wp.acme.corp:443 -jar pdrunner.jar +java -Dmicronaut.http.client.proxy-type=http -Dmicronaut.http.client.proxy-address=wp.acme.corp:443 -jar pd-runner.jar ``` 1. `-Dmicronaut.http.client.proxy-type` is set to `http` @@ -23,7 +23,7 @@ java -Dmicronaut.http.client.proxy-type=http -Dmicronaut.http.client.proxy-addre The following example adds basic auth proxy configuration to the runner. The proxy-type and proxy-address settings are the same as the unauthenticated access example. ``` -java -Dmicronaut.http.client.proxy-type=http -Dmicronaut.http.client.proxy-address=wp.acme.corp:443 -Dmicronaut.http.client.proxy-username=proxyUsernameString -Dmicronaut.http.client.proxy-password=proxyPassString -jar pdrunner.jar +java -Dmicronaut.http.client.proxy-type=http -Dmicronaut.http.client.proxy-address=wp.acme.corp:443 -Dmicronaut.http.client.proxy-username=proxyUsernameString -Dmicronaut.http.client.proxy-password=proxyPassString -jar pd-runner.jar ``` 1. `-Dmicronaut.http.client.proxy-username` is set to the user that is allowed to connect through the secure proxy. @@ -39,7 +39,7 @@ To configure the Java heap size for the Runner, add these parameters when starti Example: ``` -java -Xms4g -Xmx6g -jar runner.jar +java -Xms4g -Xmx6g -jar pd-runner.jar ``` In this example, the Runner will start with an initial heap size of 4GB and can use a maximum of 6GB. @@ -54,7 +54,7 @@ To override the temporary directory used by the Runner, add these parameters whe Example: ``` -java -Drunner.rundeck.overrideTempDir=true -Drunner.dirs.tmp=/your/custom/dir -jar runner.jar +java -Drunner.rundeck.overrideTempDir=true -Drunner.dirs.tmp=/your/custom/dir -jar pd-runner.jar ``` ## Performance tuning for high-throughput Runners From 8abbdfe19319d12c5cc5e4c13d937ad1a0eaf1c6 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Tue, 5 May 2026 11:37:23 -0300 Subject: [PATCH 11/13] Update encryptable-properties docs for AES-256-GCM encryption Reflects the migration from Jasypt to AES-256-GCM in Rundeck 6.0: - Remove references to "Jasypt encryption library" - Add upgrade note about automatic backward compatibility - Clarify that algorithm config only affects legacy decryption - Add section on re-encrypting legacy values Co-authored-by: Cursor --- .../configuration/encryptable-properties.md | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/docs/administration/configuration/encryptable-properties.md b/docs/administration/configuration/encryptable-properties.md index caee2db54..cfafe87e3 100644 --- a/docs/administration/configuration/encryptable-properties.md +++ b/docs/administration/configuration/encryptable-properties.md @@ -13,7 +13,7 @@ For instance you might want to encrypt the bind password to your LDAP server. Le ### Encrypting Property values -Runbook Automation has a feature to allow you to generate encrypted passwords using the Jasypt encryption library. The following instructions show how to encrypt a password with this utility from the command line. +Runbook Automation includes a built-in utility to generate encrypted property values using AES-256-GCM authenticated encryption. The following instructions show how to encrypt a password with this utility from the command line. cd into the directory where your rundeck.war is located run: @@ -38,11 +38,15 @@ binder123 (this won't be displayed) *Verify Value To Encrypt (Verify the text you want to encrypt): ==ENCRYPTED OUTPUT== -encrypted: bbnJmDtx82/NOeUc9ahULGVAH+RdSLG5 +encrypted: AQD3f8k2...base64-encoded-value... ``` -You will take the `encrypted:` value from the ENCRYPTED OUTPUT section which will have a value that looks like: `bbnJmDtx82/NOeUc9ahULGVAH+RdSLG5` (note that it will not be this value) and use it in your rundeck-config.properties file like this: -`rundeck.security.ldap.bindPassword=ENC(bbnJmDtx82/NOeUc9ahULGVAH+RdSLG5)` +You will take the `encrypted:` value from the ENCRYPTED OUTPUT section and use it in your rundeck-config.properties file like this: +`rundeck.security.ldap.bindPassword=ENC(AQD3f8k2...base64-encoded-value...)` + +:::tip Upgrade Note +Starting with Rundeck 6.0, new encrypted values use AES-256-GCM. Existing `ENC()` values encrypted with previous versions are automatically detected and decrypted without any configuration changes. +::: ### Decrypting rundeck-config.properties @@ -56,38 +60,18 @@ RDECK_JVM_SETTINGS=-Drd.encryption.default.password=1PwdToBindThem$ Then we would start our Runbook Automation installation. After the application has completed the bootstrap process and is responding to requests, the environment variable can be unset for security purposes. -### Advanced Usage - -If you wish to customize the algorithm, provider, or keyObtentions the Jasypt encryptor will use to encrypt the password, you can do this by passing those -values as system properties when you launch the encryption utility. - -For example, if you wish to use the `PBEWITHSHA256AND256BITAES-CBC-BC` algorithm to encrypt your password, you could do it like this: - -```shell -> java -jar -Drd.encryption.STRONG.algorithm=PBEWITHSHA256AND256BITAES-CBC-BC rundeckpro-cluster-3.0.0-SNAPSHOT.war --encryptpwd Jasypt -Required values are marked with: * -Encrypter Config (The base property name used in RD_ENCRYPTION_ or rd.encryption. ('default' is the default value)): -STRONG -*Master Password (Master password used to encrypt the value): -1PwdToBindThem$ (this won't be displayed) -*Verify Master Password (Verify master password): - -*Value To Encrypt (The text you want to encrypt): -binder123 (this won't be displayed) -*Verify Value To Encrypt (Verify the text you want to encrypt): - -==ENCRYPTED OUTPUT== -encrypted: i67e4g3jAUML0KCh+KwmnqX9lCflThMuu6CXm++VSqU= -``` +### Advanced Usage (Legacy Decryption) -Notice we are setting an rd.encryption config with the name STRONG. Then when prompted for the `Encrypter Config` by the tool we type in the value `STRONG`. This sets the encryptor to use the algorithm passed by `rd.encryption.STRONG.algorithm` instead of the default configuration which uses a different algorithm. +If you have existing encrypted values that were generated with a custom algorithm, provider, or keyObtentions in a previous Rundeck version, you can configure those settings so the system can decrypt them correctly. -To use your custom encrypted password when you start Rundeck, it is very important to ensure that the same system properties you used at encrypt time are set at launch time. Otherwise Rundeck will use the default decryptor settings which will not match your customized settings, and startup will fail. +:::warning +These settings only affect **decryption of legacy values**. All new encryptions use AES-256-GCM regardless of these settings. +::: -To start Rundeck with the settings in our example, the startup string would be something like: +For example, if your existing encrypted values were generated with the `PBEWITHSHA256AND256BITAES-CBC-BC` algorithm: ```shell -java -jar -Drd.encryption.STRONG.algorithm=PBEWITHSHA256AND256BITAES-CBC-BC -Drundeck.encrypter.config.name=STRONG rundeckpro-cluster-3.0.0-SNAPSHOT.war +java -jar -Drd.encryption.STRONG.algorithm=PBEWITHSHA256AND256BITAES-CBC-BC -Drundeck.encrypter.config.name=STRONG rundeck.war ``` If you would rather use environment variables to set the encryption settings you can use: @@ -98,3 +82,13 @@ and config settings can be supplied like: For the example above, these would be: `export RUNDECK_PROP_DECRYPTER_CONFIG=STRONG` `export RD_ENCRYPTION_STRONG_ALGORITHM=PBEWITHSHA256AND256BITAES-CBC-BC` + +### Re-encrypting Legacy Values + +To migrate existing legacy-encrypted values to AES-256-GCM, simply re-run the encryption utility with the same master password: + +```shell +java -jar rundeck.war --encryptpwd Jasypt +``` + +Enter the plaintext value you want to encrypt. The output will be in the new AES-256-GCM format. Replace the old `ENC(...)` value in your configuration file with the new one. Both old and new formats are supported simultaneously, so migration can be done incrementally. From 350e2c527942791305e70c99bd7bc5933edd1f75 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Tue, 5 May 2026 11:50:15 -0300 Subject: [PATCH 12/13] Address review feedback: clarify Jasypt CLI name, fix code blocks - Add info callout explaining 'Jasypt' is a legacy CLI name kept for compat - Add language tag (text) to interactive prompt code block - Fix Java -D property ordering (must come before -jar) - Add explanation of Encrypter Config name linkage - Add info callout in re-encryption section about legacy provider name Co-authored-by: Cursor --- .../configuration/encryptable-properties.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/administration/configuration/encryptable-properties.md b/docs/administration/configuration/encryptable-properties.md index cfafe87e3..ab3edd255 100644 --- a/docs/administration/configuration/encryptable-properties.md +++ b/docs/administration/configuration/encryptable-properties.md @@ -20,12 +20,15 @@ run: ```shell java -jar rundeck.war --encryptpwd Jasypt - ``` +:::info +The `Jasypt` argument is a legacy CLI name kept for backward compatibility. The actual encryption now uses AES-256-GCM — the Jasypt library is no longer included. +::: + You will receive prompts for information that look like the following: -``` +```text Required values are marked with: * Encrypter Config (The base property name used in RD_ENCRYPTION_ or rd.encryption. ('default' is the default value)): @@ -68,12 +71,16 @@ If you have existing encrypted values that were generated with a custom algorith These settings only affect **decryption of legacy values**. All new encryptions use AES-256-GCM regardless of these settings. ::: -For example, if your existing encrypted values were generated with the `PBEWITHSHA256AND256BITAES-CBC-BC` algorithm: +For example, if your existing encrypted values were generated with the `PBEWITHSHA256AND256BITAES-CBC-BC` algorithm, set the corresponding system properties when starting Rundeck: ```shell -java -jar -Drd.encryption.STRONG.algorithm=PBEWITHSHA256AND256BITAES-CBC-BC -Drundeck.encrypter.config.name=STRONG rundeck.war +java -Drd.encryption.STRONG.algorithm=PBEWITHSHA256AND256BITAES-CBC-BC \ + -Drundeck.encrypter.config.name=STRONG \ + -jar rundeck.war ``` +The `Encrypter Config` name (`STRONG` in this example) links the algorithm system property to the encrypted values. When the application encounters an `ENC(...)` value, it uses the configured algorithm and provider to decrypt legacy data. + If you would rather use environment variables to set the encryption settings you can use: `RUNDECK_PROP_DECRYPTER_CONFIG` to set the config to use and config settings can be supplied like: @@ -85,10 +92,14 @@ For the example above, these would be: ### Re-encrypting Legacy Values -To migrate existing legacy-encrypted values to AES-256-GCM, simply re-run the encryption utility with the same master password: +To migrate existing legacy-encrypted values to AES-256-GCM, re-run the encryption utility with the same master password: ```shell java -jar rundeck.war --encryptpwd Jasypt ``` +:::info +The `Jasypt` argument is the legacy CLI provider name retained for backward compatibility. The utility now encrypts using AES-256-GCM. +::: + Enter the plaintext value you want to encrypt. The output will be in the new AES-256-GCM format. Replace the old `ENC(...)` value in your configuration file with the new one. Both old and new formats are supported simultaneously, so migration can be done incrementally. From 96dec1bac3798ad8d4c2bc092ba610277219f35d Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Tue, 5 May 2026 12:56:44 -0300 Subject: [PATCH 13/13] Update all Jasypt references to AES-GCM encryption across docs - bundled-plugins.md: Rewrite plugin section with new/upgrade examples, clarify algorithm/provider only needed for legacy decryption - storage-facility.md: Update section title, examples, remove algorithm options table (no longer configurable) - secrets-overview.md: Update plugin link and example config - full-list.md: Update plugin name and link - storage-converter-plugins.md: Update plugin description - migrate-to-mysql.md: Update link - azure-vault.md: Update converter type reference Co-authored-by: Cursor --- .../configuration/plugins/bundled-plugins.md | 83 +++++++++++-------- .../configuration/storage-facility.md | 54 ++++++------ docs/developer/storage-converter-plugins.md | 2 +- .../getting-started/secrets-overview.md | 7 +- docs/learning/howto/migrate-to-mysql.md | 2 +- .../storage-plugins/azure-vault.md | 2 +- docs/manual/plugins/full-list.md | 2 +- 7 files changed, 83 insertions(+), 69 deletions(-) diff --git a/docs/administration/configuration/plugins/bundled-plugins.md b/docs/administration/configuration/plugins/bundled-plugins.md index 0cf52289b..b6f530a82 100644 --- a/docs/administration/configuration/plugins/bundled-plugins.md +++ b/docs/administration/configuration/plugins/bundled-plugins.md @@ -98,24 +98,26 @@ Provides a Workflow Step: File: `rundeck-flow-control-plugin-{{$rundeckVersionFull}}.jar` -## Jasypt Encryption Plugin +## AES-GCM Encryption Plugin Provides an encryption [storage converter](/administration/configuration/storage-facility.md#storage-converters) for the Storage facility. Can be used to encrypt the contents of Key Storage, and Project Configuration stored in the DB or on disk. -This plugin provides password based encryption for storage contents. -It uses the [Jasypt][] encryption library. The built in Java JCE is used unless another provider is specified, [Bouncycastle][] can be used by specifying the 'BC' provider name. +This plugin provides password-based authenticated encryption using AES-256-GCM with PBKDF2 key derivation via [Bouncycastle][]. It can also transparently decrypt data encrypted by the previous Jasypt-based plugin, enabling seamless upgrades. -[jasypt]: http://www.jasypt.org/ [bouncycastle]: https://www.bouncycastle.org/ -Password, algorithm, provider, etc can be specified directly, or via environment variables (the `*EnvVarName` properties), or Java System properties (the `*SysPropName` properties). +Password can be specified directly, or via environment variables (the `*EnvVarName` properties), or Java System properties (the `*SysPropName` properties). To enable it, see [Configuring - Storage Converter Plugins](/administration/configuration/plugins/configuring.md#storage-converter-plugins). See also: [Key Storage](/manual/key-storage/index.md) -Provider type: `jasypt-encryption` +Provider type: `aes-gcm-encryption` + +:::tip Backward Compatibility +The legacy provider name `jasypt-encryption` is still supported as an alias. Existing configurations do not need to be changed after upgrade. +::: The following encryption properties marked with `*` can be set directly, using the property name shown, @@ -126,54 +128,65 @@ or `SysPropName` to use the Java System Property. If a System Property is specified: it is read in once and used by the initialization of the converter plugin, then the Java System Property is set to null so it cannot be read again. -Configuration properties: +#### Configuration properties -`encryptorType` +`password*` +: The encryption password. This is the only required property. -: Jasypt Encryptor to use. Either `basic`, `strong`, or `custom`. Default: 'basic'. +Example configuration for **new installations**: - * `basic` uses algorithm `PBEWithMD5AndDES` - * `strong` requires use of the JCE Unlimited Strength policy files. (Algorithm: `PBEWithMD5AndTripleDES`) - * `custom` is required to specify the algorithm. +```properties +rundeck.storage.converter.1.type=aes-gcm-encryption +rundeck.storage.converter.1.path=keys +rundeck.storage.converter.1.config.password=YOUR_ENCRYPTION_PASSWORD +``` -`password*` -: the password. +```properties +rundeck.config.storage.converter.1.type=aes-gcm-encryption +rundeck.config.storage.converter.1.path=projects +rundeck.config.storage.converter.1.config.password=YOUR_ENCRYPTION_PASSWORD +``` + +#### Legacy properties (only needed when upgrading from a previous version) + +The following properties are only needed if you are upgrading from a Rundeck version that used the old `jasypt-encryption` plugin and you have existing encrypted data in your database. They tell the plugin how to **decrypt** that old data. New encryptions always use AES-256-GCM regardless of these settings. + +:::info +If you are upgrading from a standard Rundeck installation (default settings), just add `encryptorType=custom` and use the same password you had before. No other legacy properties are needed. +::: + +`encryptorType` +: Identifies the legacy encryptor format used on existing data. Either `basic` or `custom`. Default: 'custom'. -`algorithm*` -: the encryption algorithm. + * `custom` — decrypts using algorithm `PBEWITHSHA256AND128BITAES-CBC-BC` with BC provider (the Rundeck default since 2014) + * `basic` — decrypts using algorithm `PBEWithMD5AndDES` -`provider*` -: the provider name. 'BC' indicates Bouncycastle. +`algorithm` +: (optional) Only needed if you previously configured a non-default algorithm. Overrides the legacy algorithm for decrypting existing data. -`providerClassName*` -: Java class name of the provider. +`provider` +: (optional) Only needed if you previously used a non-default JCE provider. Default: 'BC' (Bouncycastle). -`keyObtentionIterations*` -: Number of hashes to use for the password when generating the key, default is 1000. +`keyObtentionIterations` +: (optional) Only needed if you previously changed this from the default. Default: 1000. -Example configuration for the Key Storage facility: +Example configuration for an **upgrade** from a standard Rundeck installation: ```properties -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=keys +rundeck.storage.converter.1.config.password=YOUR_EXISTING_PASSWORD rundeck.storage.converter.1.config.encryptorType=custom -rundeck.storage.converter.1.config.passwordEnvVarName=ENC_PASSWORD -rundeck.storage.converter.1.config.algorithm=PBEWITHSHA256AND128BITAES-CBC-BC -rundeck.storage.converter.1.config.provider=BC ``` -Example configuration for the Project Configuration storage facility: - ```properties -rundeck.config.storage.converter.1.type=jasypt-encryption -rundeck.config.storage.converter.1.path=/ -rundeck.config.storage.converter.1.config.password=sekrit +rundeck.config.storage.converter.1.type=aes-gcm-encryption +rundeck.config.storage.converter.1.path=projects +rundeck.config.storage.converter.1.config.password=YOUR_EXISTING_PASSWORD rundeck.config.storage.converter.1.config.encryptorType=custom -rundeck.config.storage.converter.1.config.algorithm=PBEWITHSHA256AND128BITAES-CBC-BC -rundeck.config.storage.converter.1.config.provider=BC ``` -File: `rundeck-jasypt-encryption-plugin-{{$rundeckVersionFull}}.jar` +File: `rundeck-aes-gcm-encryption-plugin-{{$rundeckVersionFull}}.jar` :::tip Note: the specific PBE algorithms available for use with the `encryptorType=custom` come from installed JCE providers. BouncyCastle is included but others are provided by the specific JDK you use. Here is a sample list of PBE providers using BouncyCastle and OpenJDK 1.8: diff --git a/docs/administration/configuration/storage-facility.md b/docs/administration/configuration/storage-facility.md index f9bd3058d..23ebc3c3d 100644 --- a/docs/administration/configuration/storage-facility.md +++ b/docs/administration/configuration/storage-facility.md @@ -86,7 +86,7 @@ rundeck.storage.provider.1.path=/keys **Encryption converter:** ```properties # Format: rundeck.storage.converter.[index].[property] -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=/keys rundeck.storage.converter.1.config.password=CHANGE_THIS_PASSWORD rundeck.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 @@ -186,7 +186,7 @@ rundeck.config.storage.provider.1.type=db rundeck.config.storage.provider.1.path=/ # Optional: Encryption for project configs -rundeck.config.storage.converter.1.type=jasypt-encryption +rundeck.config.storage.converter.1.type=aes-gcm-encryption rundeck.config.storage.converter.1.path=/ rundeck.config.storage.converter.1.config.password=CHANGE_THIS_PASSWORD ``` @@ -423,9 +423,13 @@ Disk/Database → Storage Backend → [Converter: Decrypt] → User/API - **Transparent:** Application doesn't know encryption is happening - **Metadata stored separately:** Encryption info stored with file metadata -### Encryption with Jasypt Plugin +### Encryption with AES-GCM Plugin -Rundeck includes the **Jasypt Encryption Plugin** for encrypting stored data. This is the most common converter configuration. +Rundeck includes the **AES-GCM Encryption Plugin** for encrypting stored data using AES-256-GCM authenticated encryption. This is the most common converter configuration. + +:::tip Upgrade Note +Prior to Rundeck 6.0, this plugin was called `jasypt-encryption`. The legacy name still works as an alias — existing configurations do not need to be changed. +::: **When to use encryption:** - **Required:** Production environments storing sensitive keys/passwords @@ -439,31 +443,29 @@ Rundeck includes the **Jasypt Encryption Plugin** for encrypting stored data. Th ```properties # Encrypt all keys -rundeck.storage.converter.1.type=jasypt-encryption -rundeck.storage.converter.1.path=/keys +rundeck.storage.converter.1.type=aes-gcm-encryption +rundeck.storage.converter.1.path=keys rundeck.storage.converter.1.config.password=YOUR_ENCRYPTION_PASSWORD_HERE -rundeck.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 +rundeck.storage.converter.1.config.encryptorType=custom ``` **Project Storage encryption (optional):** ```properties # Encrypt project configurations -rundeck.config.storage.converter.1.type=jasypt-encryption -rundeck.config.storage.converter.1.path=/ +rundeck.config.storage.converter.1.type=aes-gcm-encryption +rundeck.config.storage.converter.1.path=projects rundeck.config.storage.converter.1.config.password=YOUR_ENCRYPTION_PASSWORD_HERE -rundeck.config.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 +rundeck.config.storage.converter.1.config.encryptorType=custom ``` -#### Encryption Algorithm Options +#### Encryption Algorithm -| Algorithm | Security | Performance | JVM Requirement | -|-----------|----------|-------------|-----------------| -| `PBEWITHHMACSHA256ANDAES_256` | High (recommended) | Good | JCE Unlimited Strength (Java 8+) | -| `PBEWITHMD5ANDDES` | Low (legacy) | Fast | Standard JVM | -| `PBEWITHSHA256AND256BITAES-CBC-BC` | High | Good | Bouncy Castle library | +Starting with Rundeck 6.0, the encryption algorithm is **AES-256-GCM** (authenticated encryption with PBKDF2-SHA256 key derivation). This is not configurable — all new data is encrypted with the strongest available standard. There is no need to choose an algorithm. -**Recommendation:** Use `PBEWITHHMACSHA256ANDAES_256` for production. +:::info +The `algorithm`, `provider`, and `encryptorType` properties are only relevant for **decrypting legacy data** from previous Rundeck versions. See the [AES-GCM Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#aes-gcm-encryption-plugin) documentation for details. +::: #### Managing Encryption Passwords @@ -523,7 +525,7 @@ rundeck.storage.converter.1.config.password=${KMS_RETRIEVED_PASSWORD} 2. **Add converter configuration:** ```properties -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=/keys rundeck.storage.converter.1.config.password=${RD_STORAGE_PASSWORD} ``` @@ -613,7 +615,7 @@ echo "Re-encryption complete" - **Verification:** Check `rundeck-config.properties` on all nodes **See also:** -- [Jasypt Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#jasypt-encryption-plugin) - Detailed configuration +- [AES-GCM Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#aes-gcm-encryption-plugin) - Detailed configuration - [Storage Converter Plugin Development](/developer/storage-converter-plugins.md) - Custom converters --- @@ -910,7 +912,7 @@ rundeck.storage.provider.1.path=/keys rundeck.projectsStorageType=db # Encryption - MUST be identical -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=/keys rundeck.storage.converter.1.config.password=${RD_STORAGE_PASSWORD} rundeck.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 @@ -1124,7 +1126,7 @@ rundeck.projectsStorageType=filesystem rundeck.storage.provider.1.type=db rundeck.storage.provider.1.path=/keys -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=/keys rundeck.storage.converter.1.config.password=${RD_STORAGE_PASSWORD} rundeck.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 @@ -1160,7 +1162,7 @@ rundeck.clusterMode.enabled=true rundeck.storage.provider.1.type=db rundeck.storage.provider.1.path=/keys -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=/keys rundeck.storage.converter.1.config.password=${RD_STORAGE_PASSWORD} rundeck.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 @@ -1168,7 +1170,7 @@ rundeck.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 # Project Storage - shared database with encryption rundeck.projectsStorageType=db -rundeck.config.storage.converter.1.type=jasypt-encryption +rundeck.config.storage.converter.1.type=aes-gcm-encryption rundeck.config.storage.converter.1.path=/ rundeck.config.storage.converter.1.config.password=${RD_PROJECT_STORAGE_PASSWORD} rundeck.config.storage.converter.1.config.algorithm=PBEWITHHMACSHA256ANDAES_256 @@ -1201,7 +1203,7 @@ export DB_PASSWORD="database_password" rundeck.storage.provider.1.type=db rundeck.storage.provider.1.path=/keys -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=/keys rundeck.storage.converter.1.config.password=${RD_STORAGE_PASSWORD} @@ -1219,7 +1221,7 @@ dataSource.url=jdbc:mysql://dbhost:3306/rundeck ### Security -1. **Always encrypt in production** - Use jasypt-encryption converter for key storage +1. **Always encrypt in production** - Use aes-gcm-encryption converter for key storage 2. **Separate encryption passwords** - Use different passwords for keys vs projects 3. **Secure password storage** - Store encryption passwords in external vault (not in config files) 4. **Rotate passwords** - Plan for periodic encryption password rotation @@ -1265,7 +1267,7 @@ dataSource.url=jdbc:mysql://dbhost:3306/rundeck - [Key Storage](/manual/key-storage/index.md) - User guide for managing keys - [Project Configuration](/manual/projects/configuration.md) - Project setup and storage - [Database Configuration](/administration/configuration/database/index.md) - External database setup -- [Jasypt Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#jasypt-encryption-plugin) - Encryption details +- [AES-GCM Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#aes-gcm-encryption-plugin) - Encryption details - [Storage Plugin Development](/developer/storage-plugins.md) - Custom storage backends - [Storage Converter Plugin Development](/developer/storage-converter-plugins.md) - Custom converters - [Cluster Configuration](/administration/cluster/) - High-availability setup diff --git a/docs/developer/storage-converter-plugins.md b/docs/developer/storage-converter-plugins.md index 703028d75..41a32a2b6 100644 --- a/docs/developer/storage-converter-plugins.md +++ b/docs/developer/storage-converter-plugins.md @@ -52,7 +52,7 @@ Storage converters ensure this data is **encrypted at rest**, even if someone ga **Built-in Encryption:** Rundeck includes: -- **JasyptEncryption** - AES encryption with master password +- **AES-GCM Encryption** - AES-256-GCM authenticated encryption with master password - Configured via `framework.properties` Create custom plugins for: diff --git a/docs/learning/getting-started/secrets-overview.md b/docs/learning/getting-started/secrets-overview.md index 626c18f4e..610d93339 100644 --- a/docs/learning/getting-started/secrets-overview.md +++ b/docs/learning/getting-started/secrets-overview.md @@ -11,15 +11,14 @@ _The interface to upload a key to the Rundeck keystore_ ### [Rundeck Key Storage](/manual/key-storage/index.md#rundeck-key-storage) Rundeck Key Storage is the space that Rundeck Admins can use to store current sensitive private key/password data ("keys") storage that can be utilized across Rundeck. By default, Rundeck stores these keys on the internal [backend database](/administration/configuration/database/#database-overview). These keys can be used to customize the automation environment's plugins, node executors, and other components. -Rundeck also has [Key Storage Encryption](/administration/configuration/plugins/bundled-plugins.md#jasypt-encryption-plugin). This enables the encryption of keys and passwords saved on the Rundeck Key Storage (at the Rundeck backend). The following setting allows this encryption and is predefined in the `rundeck-config.properties` file: +Rundeck also has [Key Storage Encryption](/administration/configuration/plugins/bundled-plugins.md#aes-gcm-encryption-plugin). This enables the encryption of keys and passwords saved on the Rundeck Key Storage (at the Rundeck backend). The following setting allows this encryption and is predefined in the `rundeck-config.properties` file: -``` +```properties # Encryption for key storage rundeck.storage.provider.1.type=db rundeck.storage.provider.1.path=keys -rundeck.storage.converter.1.type=jasypt-encryption +rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=keys -rundeck.storage.converter.1.config.encryptorType=custom rundeck.storage.converter.1.config.password=encryption_password rundeck.storage.converter.1.config.algorithm=PBEWITHSHA256AND128BITAES-CBC-BC rundeck.storage.converter.1.config.provider=BC diff --git a/docs/learning/howto/migrate-to-mysql.md b/docs/learning/howto/migrate-to-mysql.md index 4fd1b142e..ecec8760a 100644 --- a/docs/learning/howto/migrate-to-mysql.md +++ b/docs/learning/howto/migrate-to-mysql.md @@ -66,7 +66,7 @@ Enable DB storage for Project configurations, and Key Storage. Optionally enable For more info refer to: - [Security - Key Storage](/manual/key-storage/index.md) -- [Configuring Plugins - Bundled Plugins - Jasypt Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#jasypt-encryption-plugin) +- [Configuring Plugins - Bundled Plugins - AES-GCM Encryption Plugin](/administration/configuration/plugins/bundled-plugins.md#aes-gcm-encryption-plugin) - [Storage Facility](/administration/configuration/storage-facility.md) ## Start Rundeck diff --git a/docs/manual/key-storage/storage-plugins/azure-vault.md b/docs/manual/key-storage/storage-plugins/azure-vault.md index 5fef3344f..05ada6e3c 100644 --- a/docs/manual/key-storage/storage-plugins/azure-vault.md +++ b/docs/manual/key-storage/storage-plugins/azure-vault.md @@ -107,7 +107,7 @@ Soft deleted secrets can be found in the *Manage deleted secrets* window in the ::: ### Storage Converters and Encryption -When you have external key storage configured for rundeck such as the Azure Key Vault plugin, you may not need the [Storage Converter](/manual/key-storage/index.md#key-data-storage-converter) (encryption) as you already have a security layer from provider-side, but if you are already using a storage converter plugin, such as `jasypt-encryption` configured via the `rundeck.storage.converter.1.type=jasypt-encryption` setting, you can still use the Azure Vault Key Storage Plugin. Keys created or updated will be encrypted with their values `base64` encoded and the necessary metadata required for decryption stored as object tags. +When you have external key storage configured for rundeck such as the Azure Key Vault plugin, you may not need the [Storage Converter](/manual/key-storage/index.md#key-data-storage-converter) (encryption) as you already have a security layer from provider-side, but if you are already using a storage converter plugin, such as `aes-gcm-encryption` configured via the `rundeck.storage.converter.1.type=aes-gcm-encryption` setting, you can still use the Azure Vault Key Storage Plugin. Keys created or updated will be encrypted with their values `base64` encoded and the necessary metadata required for decryption stored as object tags. :::warning Using an encryption storage converter If your rundeck instance has an storage converter enabled and some of the secrets present in the configured Key Vault are used in places other than rundeck, you should try to avoid updating them through rundeck key storage as encrypted keys will only be usable in Rundeck instances that know the encryption password used to encrypt the actual value. diff --git a/docs/manual/plugins/full-list.md b/docs/manual/plugins/full-list.md index fe50b3af2..69241b3ab 100644 --- a/docs/manual/plugins/full-list.md +++ b/docs/manual/plugins/full-list.md @@ -75,7 +75,7 @@ Resource Format | [JSON](/manual/document-format-reference/resource-json-v10.md) SCM | [Git](/manual/projects/scm/git.md) | Imports or exports jobs from a Git repository | Community SCM | [Job Replication](/manual/projects/scm/job-replication.md) | Replicates job state between Rundeck Cluster instances | Enterprise SSO | [Okta](/administration/security/sso/index.md) | Allows you to use Okta to log into Rundeck | Enterprise -Storage Converter | [Encyption](/administration/configuration/plugins/bundled-plugins.md#jasypt-encryption-plugin) | Encrypts Key Storage and Project configuration data | Built-in +Storage Converter | [Encryption](/administration/configuration/plugins/bundled-plugins.md#aes-gcm-encryption-plugin) | Encrypts Key Storage and Project configuration data (AES-256-GCM) | Built-in Webhook | [Run Job](/manual/webhooks/run-job.md) | Runs a job when a webhook event is received | Built In Webhook | [Routing Run Job](/manual/webhooks/advanced-run-job.md) | Advanced rule processing of webhook event data to run jobs. | Enterprise