diff --git a/.agents/skills/changelog-to-jira/README.md b/.agents/skills/changelog-to-jira/README.md new file mode 100644 index 000000000..8839c7fb7 --- /dev/null +++ b/.agents/skills/changelog-to-jira/README.md @@ -0,0 +1,48 @@ +# changelog-to-jira + +Creates missing DGW Jira tickets from `CHANGELOG.md` entries, transitions them to Done, and writes the ticket links back into the file. + +## Prerequisites + +### PowerShell 7+ + +```powershell +winget install Microsoft.PowerShell +``` + +### JiraPS module + +```powershell +Install-Module JiraPS -Scope CurrentUser +``` + +### Atlassian API token + +Generate one at . + +The token must have the following Jira scopes: + +| Scope | Why | +|---|---| +| `read:jira-work` | Read issues and transitions | +| `write:jira-work` | Create issues, add links, perform transitions | + +### Environment variables + +Add to your PowerShell profile (`$PROFILE`): + +```powershell +$env:JIRA_URL = "https://devolutions.atlassian.net" +$env:JIRA_EMAIL = "you@devolutions.net" +$env:JIRA_API_TOKEN = "your-api-token-here" +``` + +### Atlassian MCP server + +The skill uses the Atlassian remote MCP server for assignee lookups. Add it to Claude Code: + +```bash +claude mcp add --transport sse claude_ai_Atlassian https://mcp.atlassian.com/v1/sse +``` + +This registers a server named `claude_ai_Atlassian`, which is the name the skill expects. On first use, Claude Code will prompt you to authenticate via OAuth. diff --git a/.agents/skills/changelog-to-jira/SKILL.md b/.agents/skills/changelog-to-jira/SKILL.md new file mode 100644 index 000000000..1139b9cb4 --- /dev/null +++ b/.agents/skills/changelog-to-jira/SKILL.md @@ -0,0 +1,270 @@ +--- +name: changelog-to-jira +description: Creates missing DGW Jira tickets from CHANGELOG.md entries and updates the file with the new ticket links. +compatibility: + tools: + - Bash + - Read + - Edit + - mcp__claude_ai_Atlassian__lookupJiraAccountId + - mcp__claude_ai_Atlassian__getJiraIssue + - mcp__claude_ai_Atlassian__searchJiraIssuesUsingJql + - mcp__claude_ai_Atlassian__getIssueLinkTypes + - mcp__claude_ai_Atlassian__getJiraProjectIssueTypesMetadata +--- + +# Changelog to Jira Skill + +This skill reads `CHANGELOG.md`, identifies entries that don't have a `DGW-*` Jira ticket, groups similar entries together, creates them in the DGW project, infers the assignee from the commit author, transitions them to Done, and updates `CHANGELOG.md` with the new ticket links. + +## Scope + +**Only process entries for these components** (text inside `_italics_`): +- `dgw` — Devolutions Gateway core +- `installer` — Devolutions Gateway installer +- `agent` — Devolutions Agent +- `agent-installer` — Agent installer + +Skip entries for any other component (e.g., `webapp`, `jetsocat`, `crates`). + +**Always default to the latest release** — the first `## VERSION (DATE)` block in the file. + +## Audience + +Ticket descriptions are for **QA technicians, support technicians, and marketing people**. Write them from a consumer perspective — what changed for the user, not how it was implemented. Avoid internal code references, crate names, and architecture notes. + +**Length:** calibrate to the complexity of the change. +- A bug fix or minor improvement: 1–2 sentences is enough. +- A new feature that requires configuration or has non-obvious behaviour: write as much as needed. Include what the feature does, how to enable or configure it, and any relevant defaults or caveats. This gives QA and support enough context to test and explain it without needing to read the code. + +**Examples:** + +Bug fix (short): +> Fixed a crash that could occur when the Gateway service was restarted while an active session was recording. + +New feature needing configuration (longer): +> Gateway and Agent now support outbound proxy configuration for all network traffic. The proxy mode is controlled by the `Proxy.Mode` field in the configuration file: +> - `System` (default): auto-detects proxy settings from environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) or system settings (WinHTTP on Windows, `/etc/sysconfig/proxy` on RHEL/SUSE, system preferences on macOS). +> - `Manual`: uses explicitly configured URLs. Set `Proxy.Http`, `Proxy.Https`, or `Proxy.All` to a proxy URL (e.g. `http://proxy.corp:8080` or `socks5://proxy.corp:1080`). +> - `Off`: disables proxy entirely. +> +> HTTP, HTTPS, SOCKS4, and SOCKS5 proxies are supported. + +## Workflow + +### Step 1: Read and Parse CHANGELOG.md + +Read `CHANGELOG.md` from the current working directory. Take the first `## VERSION (DATE)` block. + +**Entry format:** +``` +## VERSION (DATE) + +### Section (Features / Bug Fixes / Security / Performance / etc.) + +- _component_: short description ([#PR](github-pr-url)) ([commit](commit-url)) ([TICKET-ID](jira-url)) + + Optional multi-line body with more detail. +``` + +Parse each entry into: +- `section`: Features / Bug Fixes / Security / Performance / etc. +- `component`: text inside `_italics_` +- `description`: the short description text +- `body`: optional indented multi-line description +- `github_pr`: PR number if present (e.g., `#1676`) +- `commit_hash`: short hash from the commit URL if present +- `existing_tickets`: all Jira ticket IDs found (pattern: `[A-Z]+-\d+` with `atlassian.net/browse` URL) + +Filter to only entries whose `component` is one of: `dgw`, `installer`, `agent`, `agent-installer`. + +### Step 2: Classify and Group Entries + +#### Classify each entry + +| Case | Condition | Action | +|---|---|---| +| **Already done** | Has a `DGW-*` ticket | Skip | +| **Cross-project** | Has a ticket from another project (e.g., `ARC-353`, `PI-651`) | Create a DGW ticket linked to the original | +| **Missing** | No Jira ticket | Create a new DGW ticket | + +#### Group similar entries into a single ticket + +Before planning ticket creation, look for entries that describe **the same underlying change** and should be represented by one ticket rather than several. Group them when: + +- They share the same PR number (most reliable signal — same code change, multiple components affected) +- They are clearly two sides of the same feature (e.g., both `dgw` and `agent` entries describe adding the same capability) +- The descriptions are semantically equivalent (same fix or feature, slightly different wording) + +When grouping, create **one ticket** that: +- Lists all affected components in the summary: `[dgw, agent] Feature description` +- Has a description that covers all components +- Maps CHANGELOG.md lines for all grouped entries to this single ticket + +Do not force groupings. When in doubt, create separate tickets. + +### Step 3: Show the Plan + +Before creating anything, show a summary table and wait for confirmation: + +``` +Latest release: 2026.1.0 (2026-02-23) + +Will CREATE (no ticket): + [Features] dgw: Add CredSSP certificate configuration keys (#1676) + [Bug Fixes] agent-installer: Specify ARM64 platform for ARM64 installer + +Will CREATE (grouped — same change): + [Features] dgw + agent: RDM pipe passthrough logic (#1701) + • dgw: add pipe passthrough for RDM + • agent: handle pipe passthrough messages from RDM + +Will CREATE + LINK (cross-project ticket): + [Features] agent: RDM messages and pipe passthrough logic → PI-651 + +Already have DGW tickets (skipping): + DGW-341 — Improve real-time performance of session shadowing + +Proceed? +``` + +### Step 4: Infer Assignee from Commit Author + +For every entry that needs a new ticket (missing or cross-project), infer the assignee by looking up the commit author in git and resolving them to a Jira account. + +**For entries with a commit hash:** + +```bash +git log --format="%ae %an" -1 +``` + +This gives you the author's email and name. Then look up their Jira account: + +``` +mcp__claude_ai_Atlassian__lookupJiraAccountId → query = "" +``` + +Use the email first — it's the most reliable identifier. If no match, try the display name. + +**For cross-project entries:** Also fetch the original ticket's assignee (`getJiraIssue → assignee.accountId`) as a fallback if the commit lookup yields no result. Prefer the commit author when both are available. + +**If no commit hash is available**, or the author lookup returns no result, omit the assignee field — do not block ticket creation. + +**Cache the results** — if two entries share the same commit author, reuse the resolved `accountId` without a second lookup. + +### Step 5: Build the ticket plan and run the script + +Construct a `jira-plan.json` file in the repo root with all tickets to create: + +```json +{ + "tickets": [ + { + "id": "t1", + "summary": "[dgw] Add CredSSP certificate configuration keys", + "issuetype": "Story", + "description": "Consumer-facing description. Length should match complexity — see Audience section.", + "assignee_account_id": "557058:eeb3e2d6-ef7a-463e-9d9e-5834dd925adb", + "cross_project_link": null + }, + { + "id": "t2", + "summary": "[dgw, agent] RDM pipe passthrough logic", + "issuetype": "Bug", + "description": "...", + "assignee_account_id": null, + "cross_project_link": "PI-651" + } + ] +} +``` + +**Issue type by section:** +| Section | `issuetype` | +|---|---| +| Features | `Story` | +| Bug Fixes | `Bug` | +| Security | `Bug` | +| Performance | `Story` | +| Other | `Task` | + +**Summary format:** `[component] Capitalized short description` +- Single entry: `[dgw] Add CredSSP certificate configuration keys` +- Grouped entry: `[dgw, agent] Add CredSSP certificate configuration keys` + +**Description:** 2–4 sentences, consumer-facing. Rewrite for a non-technical audience. For cross-project entries, append: `Related ticket: ARC-353`. + +Keep a local map of `id → CHANGELOG.md line(s)` — you will need it in Step 7 to write back the ticket keys. + +Then run the script, capturing its JSON output: + +```powershell +pwsh /scripts/invoke-jira.ps1 -PlanFile jira-plan.json +``` + +The script handles everything mechanical: creating each ticket, linking cross-project tickets, walking the DGW workflow to Done (`Backlog → To Do → Development → Reviewing → Done`), and deleting `jira-plan.json` when done. + +The result JSON is written to stdout: + +```json +{ + "created": [ + { "id": "t1", "key": "DGW-400" }, + { "id": "t2", "key": "DGW-401" } + ], + "errors": [] +} +``` + +If `errors` is non-empty, report the failures to the user before continuing. + +### Step 7: Update CHANGELOG.md + +After creating all tickets, automatically update `CHANGELOG.md` to append the new ticket links to each affected entry line. + +Entry format for the appended link: `([DGW-400](https://devolutions.atlassian.net/browse/DGW-400))` + +It goes at the end of the `-` line, after existing links. For grouped entries, add the same ticket link to **all** the CHANGELOG.md lines that were grouped together. Example: + +Before: +``` +- _dgw_: add CredSSP certificate configuration keys ([#1676](...)) ([443e5f0b02](...)) +``` +After: +``` +- _dgw_: add CredSSP certificate configuration keys ([#1676](...)) ([443e5f0b02](...)) ([DGW-400](https://devolutions.atlassian.net/browse/DGW-400)) +``` + +### Step 8: Summary Report + +``` +Created N DGW tickets for version 2026.1.0: + DGW-400: [dgw] Add CredSSP certificate configuration keys (assignee: Alice) + DGW-401: [agent-installer] Specify ARM64 platform for ARM64 installer (assignee: Bob) + DGW-402: [dgw, agent] RDM pipe passthrough logic (grouped 2 entries) (assignee: Carol) + DGW-403: [agent] RDM messages and pipe passthrough logic (→ PI-651) (assignee: Dave) + +All tickets transitioned to Done. + +Skipped 1 (already had DGW ticket): + DGW-341 — Improve real-time performance of session shadowing + +CHANGELOG.md updated with new ticket links. +``` + +## Edge Cases + +- **Multi-component entries** like `_dgw,agent_`: Process it (both components are in scope), use the combined form in the summary. +- **No body text**: Write a 1-sentence description from the summary alone — don't leave it blank. +- **Unknown link type**: Call `mcp__claude_ai_Atlassian__getIssueLinkTypes` to find "relates to". +- **Unknown issue types**: Call `mcp__claude_ai_Atlassian__getJiraProjectIssueTypesMetadata` for project `DGW`. +- **Commit author not in Jira**: If `lookupJiraAccountId` returns no results (e.g., a contractor or external contributor), skip the assignee — don't block ticket creation. +- **No "Done" transition**: If the project uses different terminal state names, pick the closest one. If genuinely ambiguous, skip transitioning and note it in the summary. + +## Atlassian MCP Tools + +These are the only MCP tools still needed — ticket creation, linking, and transitions are handled by the PowerShell script. + +- `mcp__claude_ai_Atlassian__lookupJiraAccountId` — resolve a commit author's email or name to a Jira account ID (Step 4) +- `mcp__claude_ai_Atlassian__getJiraIssue` — fetch cross-project ticket details (assignee fallback, Step 4) +- `mcp__claude_ai_Atlassian__searchJiraIssuesUsingJql` — search existing tickets if needed diff --git a/.agents/skills/changelog-to-jira/scripts/invoke-jira.ps1 b/.agents/skills/changelog-to-jira/scripts/invoke-jira.ps1 new file mode 100644 index 000000000..3751260ed --- /dev/null +++ b/.agents/skills/changelog-to-jira/scripts/invoke-jira.ps1 @@ -0,0 +1,164 @@ +#!/usr/bin/env pwsh +# invoke-jira.ps1 +# +# Creates Jira tickets from a JSON plan, links cross-project tickets, and +# walks each ticket through the DGW workflow to Done. +# On full success, deletes the plan file; on any error, keeps it for debugging. +# +# Usage: +# pwsh invoke-jira.ps1 -PlanFile jira-plan.json +# +# Result JSON is written to stdout. +# +# Required environment variables: +# JIRA_URL — e.g. https://devolutions.atlassian.net +# JIRA_EMAIL — your Atlassian account email +# JIRA_API_TOKEN — Atlassian API token (not your password) +# +# See README.md for the full input/output JSON schema and prerequisites. + +param( + [Parameter(Mandatory)] [string] $PlanFile +) + +$ErrorActionPreference = "Stop" + +# --------------------------------------------------------------------------- +# 1. Validate environment +# --------------------------------------------------------------------------- + +$JiraUrl = $env:JIRA_URL +$JiraEmail = $env:JIRA_EMAIL +$JiraToken = $env:JIRA_API_TOKEN + +if (-not $JiraUrl -or -not $JiraEmail -or -not $JiraToken) { + Write-Error "Missing required environment variables. Set JIRA_URL, JIRA_EMAIL, and JIRA_API_TOKEN." + exit 1 +} + +if (-not (Test-Path $PlanFile)) { + Write-Error "Plan file not found: $PlanFile" + exit 1 +} + +# --------------------------------------------------------------------------- +# 2. Connect to Jira +# --------------------------------------------------------------------------- + +if (-not (Get-Module -ListAvailable -Name JiraPS)) { + Write-Error "JiraPS module is not installed. Run: Install-Module JiraPS -Scope CurrentUser" + exit 1 +} + +Import-Module JiraPS -ErrorAction Stop + +Set-JiraConfigServer -Server $JiraUrl + +$SecureToken = ConvertTo-SecureString $JiraToken -AsPlainText -Force +$Credential = [System.Management.Automation.PSCredential]::new($JiraEmail, $SecureToken) +New-JiraSession -Credential $Credential | Out-Null + +Write-Verbose "Connected to $JiraUrl as $JiraEmail" + +# --------------------------------------------------------------------------- +# 3. DGW workflow transition chain +# Backlog(961) → To Do(861) → Development(731) → Reviewing(741) → Done +# These IDs are stable for the DGW project. If a transition fails, +# fetch the current transitions with Get-JiraIssueTransition and update +# this list. +# --------------------------------------------------------------------------- + +$TransitionChain = @(961, 861, 731, 741) + +# --------------------------------------------------------------------------- +# 4. Process tickets +# --------------------------------------------------------------------------- + +$plan = Get-Content $PlanFile -Raw | ConvertFrom-Json + +$created = [System.Collections.Generic.List[hashtable]]::new() +$errors = [System.Collections.Generic.List[hashtable]]::new() + +foreach ($ticket in $plan.tickets) { + Write-Host "`nProcessing: $($ticket.summary)" + + try { + # --- Create the issue --- + $issueParams = @{ + Project = "DGW" + Summary = $ticket.summary + IssueType = $ticket.issuetype + Description = $ticket.description + } + if ($ticket.assignee_account_id) { + $issueParams["Assignee"] = $ticket.assignee_account_id + } + + $issue = New-JiraIssue @issueParams + Write-Host " Created $($issue.Key)" + + # --- Link to cross-project ticket --- + if ($ticket.cross_project_link) { + $linkType = Get-JiraIssueLinkType | Where-Object { $_.Name -eq "Relates" } | Select-Object -First 1 + if (-not $linkType) { + Write-Warning " Could not find 'Relates' link type — skipping link to $($ticket.cross_project_link)" + } else { + $issueLink = New-JiraIssueLink -Type $linkType -OutwardIssue $ticket.cross_project_link + Add-JiraIssueLink -Issue $issue.Key -IssueLink $issueLink + Write-Host " Linked $($issue.Key) → $($ticket.cross_project_link)" + } + } + + # --- Walk through transition chain to Done --- + $allTransitionsSucceeded = $true + foreach ($transId in $TransitionChain) { + try { + Invoke-JiraIssueTransition -Issue $issue.Key -Transition $transId + } catch { + Write-Warning " Transition $transId failed for $($issue.Key): $_" + Write-Warning " Remaining transitions skipped — ticket may need manual progression." + $allTransitionsSucceeded = $false + break + } + } + if ($allTransitionsSucceeded) { + Write-Host " Transitioned $($issue.Key) to Done" + } else { + Write-Host " Did not fully transition $($issue.Key) to Done" + } + + $created.Add(@{ id = $ticket.id; key = $issue.Key }) + } + catch { + Write-Warning " FAILED: $_" + $errors.Add(@{ id = $ticket.id; summary = $ticket.summary; error = "$_" }) + } +} + +# --------------------------------------------------------------------------- +# 5. Clean up plan file and output results +# --------------------------------------------------------------------------- + +if ($errors.Count -eq 0) { + try { + Remove-Item -LiteralPath $PlanFile -ErrorAction Stop + } + catch { + Write-Warning "Failed to delete plan file '$PlanFile': $_" + } +} +else { + Write-Warning "Plan file '$PlanFile' kept for debugging because there were $($errors.Count) error(s)." +} + +$result = @{ + created = $created.ToArray() + errors = $errors.ToArray() +} + +Write-Output ($result | ConvertTo-Json -Depth 5) + +if ($errors.Count -gt 0) { + Write-Warning "$($errors.Count) ticket(s) failed — see the errors array in the output." + exit 1 +} diff --git a/.agents/skills/prepare-release/README.md b/.agents/skills/prepare-release/README.md new file mode 100644 index 000000000..d1daa17b0 --- /dev/null +++ b/.agents/skills/prepare-release/README.md @@ -0,0 +1,43 @@ +# prepare-release + +Prepares a Devolutions Gateway / Devolutions Agent release commit: bumps the version, generates and inserts the changelog, creates Jira tickets, generates the ToolBox changelog, and produces the final release commit. + +## Prerequisites + +### git-cliff + +Used to generate unreleased changelog entries from conventional commits. + +```bash +cargo install git-cliff +``` + +Or via a pre-built binary: + +### PowerShell 7+ + +Used by the version bump script. + +```powershell +winget install Microsoft.PowerShell +``` + +### changelog-to-jira prerequisites + +This skill calls `/changelog-to-jira` internally. All of its prerequisites must also be satisfied: + +- **JiraPS module** — `Install-Module JiraPS -Scope CurrentUser` +- **Atlassian API token** — generate at with scopes `read:jira-work` and `write:jira-work` +- **Environment variables** in your PowerShell profile (`$PROFILE`): + + ```powershell + $env:JIRA_URL = "https://devolutions.atlassian.net" + $env:JIRA_EMAIL = "you@devolutions.net" + $env:JIRA_API_TOKEN = "your-api-token-here" + ``` + +- **Atlassian MCP server** configured in Claude Code: + + ```bash + claude mcp add --transport sse claude_ai_Atlassian https://mcp.atlassian.com/v1/sse + ``` diff --git a/.agents/skills/prepare-release/SKILL.md b/.agents/skills/prepare-release/SKILL.md new file mode 100644 index 000000000..5be6c8746 --- /dev/null +++ b/.agents/skills/prepare-release/SKILL.md @@ -0,0 +1,181 @@ +--- +name: prepare-release +description: "Prepares a Devolutions Gateway / Devolutions Agent release commit: bumps the version, generates and inserts the changelog, creates Jira tickets, generates the ToolBox changelog, and produces the final release commit." +compatibility: + tools: + - Bash + - Read + - Edit + - mcp__claude_ai_Atlassian__lookupJiraAccountId + - mcp__claude_ai_Atlassian__getJiraIssue + - mcp__claude_ai_Atlassian__searchJiraIssuesUsingJql +--- + +# Prepare Release Skill + +This skill walks through every step needed to produce the `chore(release): prepare for ` commit for Devolutions Gateway and Devolutions Agent. + +> The user is responsible for creating and checking out the release branch beforehand. This skill does **not** create or switch branches. + +--- + +## Step 1: Determine the target version + +Run the bundled script to resolve the version, passing the user's argument (if any): + +```powershell +# From the repo root: +pwsh /scripts/resolve-version.ps1 -Arg "" +``` + +The script prints the resolved version to stdout and exits with: +- **0** — version resolved successfully; use the printed string. +- **1** — error (malformed VERSION file or bad argument); report and stop. +- **2** — no argument was provided; the script prints the *current* version. In this case ask the user: "What version are we releasing? Current is ``. You can type an explicit version (e.g. `2026.2.0`), or `hotfix` / `cycle`." Then re-run the script with their answer. + +Show the resolved version to the user and wait for confirmation before proceeding. + +--- + +## Step 2: Bump the version + +Run the PowerShell bump script from the repo root: + +```powershell +./tools/bump-version.ps1 -NewVersion +``` + +This updates `VERSION`, `Cargo.toml`, `Cargo.lock`, `fuzz/Cargo.lock`, the `.csproj`, `.psd1`, `AppxManifest.xml`, **and** the two Linux packaging changelogs (`package/Linux/CHANGELOG.md`, `package/AgentLinux/CHANGELOG.md`). No further changes to those files are needed. + +If the script fails (e.g. the new version equals the current version), report the error and stop. + +--- + +## Step 3: Generate unreleased changelog entries + +Run git-cliff to get the entries that haven't been released yet: + +```bash +git cliff --unreleased +``` + +The output starts with `## [Unreleased]` followed by section headers (`### Features`, `### Bug Fixes`, etc.) and individual bullet entries. + +--- + +## Step 4: Insert the changelog into CHANGELOG.md + +Open `CHANGELOG.md`. The file begins with: + +``` +# Changelog + +This document provides a list of notable changes introduced in ... + +## () +... +``` + +Insert the new version block **between the introductory paragraph and the first existing `## ...` version line**. + +Replace the `## [Unreleased]` header that git-cliff produced with the correct format: + +``` +## () +``` + +where `` is today's date in `YYYY-MM-DD` format (e.g. `## 2026.2.0 (2026-04-01)`). + +Keep the rest of the git-cliff output (section headers and bullet entries) — but apply the cleanup steps in Step 5 before writing to the file. + +--- + +## Step 5: Clean up the generated entries + +Before writing the new block to `CHANGELOG.md`, apply these improvements: + +### Strip git-cliff section prefixes +git-cliff emits HTML comment sort-order prefixes in section titles (e.g. `### Features`). Strip them so headings render cleanly: `### Features`. + +### Resolve "Please Sort" entries +If git-cliff produced a `### Please Sort` section, it means those commits didn't follow the conventional-commits format. For each such entry: +1. Run `git show ` to inspect the diff. +2. Based on what actually changed, classify the commit into the correct section (Features, Bug Fixes, Improvements, etc.). +3. Move it there. If the commit is purely internal with no user-visible effect, remove it entirely. + +If you cannot confidently classify a "Please Sort" entry after reading the diff, keep it in a `### Please Sort` section and flag it to the user at the end. + +### Fix spelling mistakes +Scan all entry titles for obvious spelling errors (typos in common words, wrong capitalisation of proper nouns like "Gateway", "CredSSP", "RDP", etc.) and correct them. + +### Improve unclear commit messages +If a commit title is vague (e.g. "fix issue", "update code", "misc changes"), run `git show ` and rewrite the entry title to accurately describe what changed — keep it concise and factual, matching the style of other entries. + +### Final check +After cleanup, if there are any remaining issues you aren't confident about, briefly list them for the user after writing the file (don't block on them — just inform). + +--- + +## Step 6: Update Linux packaging changelogs + +After inserting and cleaning up the entries, check whether any of them explicitly change Linux packaging. Look for entries that: +- Fix DEB or RPM package manifests, directory layouts, or packaging scripts +- Change how the package installs, configures, or integrates with the OS (systemd units, paths, permissions, etc.) + +For each such entry, add a plain-English bullet to the relevant packaging changelog(s): +- `package/Linux/CHANGELOG.md` — for changes affecting the Gateway Linux package +- `package/AgentLinux/CHANGELOG.md` — for changes affecting the Agent Linux package + +The `bump-version.ps1` script already prepended a `## VERSION (DATE)\n\n- No changes.` section. Replace `- No changes.` with the actual bullets, or leave it as-is if there are genuinely no packaging-related changes. + +--- + +## Step 7: User review of CHANGELOG.md + +Before touching Jira, show the user the new version block that was written to `CHANGELOG.md` and ask them to review it: + +> "Here's the changelog for ****. Please review it — you can edit `CHANGELOG.md` directly now if anything needs adjusting. Type **go** when you're ready to continue." + +Wait for the user to type "go" (case-insensitive) before proceeding. If they send corrections or ask for specific changes, apply them to `CHANGELOG.md` first, then wait for "go" again. + +--- + +## Step 8: Create Jira tickets + +Run the `/changelog-to-jira` skill. + +This reads the freshly-updated `CHANGELOG.md`, creates any missing `DGW-*` Jira tickets, links cross-project tickets, and updates `CHANGELOG.md` with the new ticket links in one go. Let that skill drive the interaction; come back here when it is finished. + +--- + +## Step 9: Generate the ToolBox changelog + +Run the `/toolbox-changelog` skill (no version argument needed — it defaults to the most recent version block, which is the one we just inserted). + +Let that skill run to completion and present its output to the user. + +--- + +## Step 10: Create the release commit + +Once both sub-skills are done and the user is happy, stage all modified files and create the commit: + +```bash +git add -A +git commit -m "chore(release): prepare for " +``` + +--- + +## Quick reference: files touched by this skill + +| File | How it changes | +|------|---------------| +| `VERSION` | Bumped by `bump-version.ps1` | +| `Cargo.toml`, `Cargo.lock`, `fuzz/Cargo.lock` | Version bumped | +| `dotnet/DesktopAgent/DesktopAgent.csproj` | Version bumped | +| `powershell/DevolutionsGateway/DevolutionsGateway.psd1` | Version bumped | +| `crates/devolutions-pedm-shell-ext/AppxManifest.xml` | Version bumped | +| `package/Linux/CHANGELOG.md` | New section prepended by `bump-version.ps1` | +| `package/AgentLinux/CHANGELOG.md` | New section prepended by `bump-version.ps1` | +| `CHANGELOG.md` | New version block inserted; Jira ticket links added | diff --git a/.agents/skills/prepare-release/scripts/resolve-version.ps1 b/.agents/skills/prepare-release/scripts/resolve-version.ps1 new file mode 100644 index 000000000..f97f073db --- /dev/null +++ b/.agents/skills/prepare-release/scripts/resolve-version.ps1 @@ -0,0 +1,104 @@ +#!/usr/bin/env pwsh +# resolve-version.ps1 +# Resolves the next release version given an argument and the current VERSION file. +# +# Usage: +# ./resolve-version.ps1 [-Arg ] [-VersionFile ] +# +# Argument values: +# hotfix Bump the patch number (third component). E.g. 2026.2.0 -> 2026.2.1 +# cycle Bump the release number (second component), reset patch to 0. +# The release number must stay between 1 and 3. +# If already 3, roll over: bump year and set release to 1. +# E.g. 2026.2.1 -> 2026.3.0, 2026.3.0 -> 2027.1.0 +# Use the provided version string as-is (no calculation). +# (empty) Print the current version and exit with code 2 to signal +# that the caller should ask the user for input. +# +# Output: +# Prints the resolved version string to stdout. +# Exits with code 0 on success, 1 on error, 2 when no argument was provided. + +param( + [string] $Arg = "", + [string] $VersionFile = "./VERSION" +) + +$ErrorActionPreference = "Stop" + +if (-not (Test-Path $VersionFile)) { + Write-Error "VERSION file not found at '$VersionFile'" + exit 1 +} + +$current = (Get-Content $VersionFile -Raw).Trim() + +if ($Arg -eq "") { + Write-Output $current + exit 2 +} + +if ($Arg -eq "hotfix") { + $parts = $current -split '\.' + if ($parts.Count -ne 3) { + Write-Error "Unexpected version format in VERSION file: '$current'" + exit 1 + } + $release = [int]$parts[1] + if ($release -lt 1 -or $release -gt 3) { + Write-Error "Release component '$release' in VERSION file is out of range [1,3]: '$current'" + exit 1 + } + $parts[2] = [string]([int]$parts[2] + 1) + Write-Output ($parts -join '.') + exit 0 +} + +if ($Arg -eq "cycle") { + $parts = $current -split '\.' + if ($parts.Count -ne 3) { + Write-Error "Unexpected version format in VERSION file: '$current'" + exit 1 + } + $year = [int]$parts[0] + $release = [int]$parts[1] + + if ($release -lt 1 -or $release -gt 3) { + Write-Error "Release component '$release' in VERSION file is out of range [1,3]: '$current'" + exit 1 + } + if ($release -eq 3) { + $year = $year + 1 + $release = 1 + } else { + $release = $release + 1 + } + Write-Output "$year.$release.0" + exit 0 +} + +# Explicit version string — parse components and validate consistently +if ($Arg -match '^\d{4}\.\d+\.\d+$') { + $explicitParts = $Arg -split '\.' + $year = 0 + $release = 0 + $patch = 0 + + if (-not [int]::TryParse($explicitParts[0], [ref]$year) -or + -not [int]::TryParse($explicitParts[1], [ref]$release) -or + -not [int]::TryParse($explicitParts[2], [ref]$patch)) { + Write-Error "Explicit version '$Arg' must contain numeric year, release, and patch components." + exit 1 + } + + if ($release -lt 1 -or $release -gt 3) { + Write-Error "Release component '$release' in explicit version argument is out of range [1,3]: '$Arg'" + exit 1 + } + + Write-Output $Arg + exit 0 +} + +Write-Error "Unrecognised argument '$Arg'. Expected 'hotfix', 'cycle', or an explicit version like '2026.2.0'." +exit 1 diff --git a/.agents/skills/toolbox-changelog/README.md b/.agents/skills/toolbox-changelog/README.md new file mode 100644 index 000000000..90fbd0a17 --- /dev/null +++ b/.agents/skills/toolbox-changelog/README.md @@ -0,0 +1,9 @@ +# toolbox-changelog + +Generates user-facing changelogs for Devolutions Gateway and Devolutions Agent in Devolutions ToolBox format from `CHANGELOG.md`. + +## Prerequisites + +No additional tools required beyond a working git repository. + +Git is used to inspect commit diffs (`git show`) when a changelog entry needs more context to produce an accurate user-facing description. diff --git a/.agents/skills/toolbox-changelog/SKILL.md b/.agents/skills/toolbox-changelog/SKILL.md new file mode 100644 index 000000000..2a1be4f0d --- /dev/null +++ b/.agents/skills/toolbox-changelog/SKILL.md @@ -0,0 +1,108 @@ +--- +name: toolbox-changelog +description: Generates user-facing changelogs for Devolutions Gateway and Devolutions Agent in Devolutions ToolBox format from CHANGELOG.md. +compatibility: + tools: + - Bash + - Read +--- + +# ToolBox Changelog Generator + +Read `CHANGELOG.md` from the project root and produce two user-facing changelogs — one for **Devolutions Gateway** and one for **Devolutions Agent** — ready to paste into the Devolutions ToolBox change history. + +## Step 1: Determine the target version + +- If the user provided a version argument (e.g. `/toolbox-changelog 2025.3.4`), find the `## 2025.3.4 (...)` block in CHANGELOG.md. +- Otherwise, use the **first** (most recent) version block in the file. + +## Step 2: Collect relevant entries + +For each entry in the target version block, check the scope (the text in `_italics_` before the colon). An entry can have multiple scopes separated by commas (e.g. `_dgw,agent_:`). + +**Scope routing:** + +| Scope | Product | Label | +|----------------|----------------------|-------------| +| `dgw` | Devolutions Gateway | `Service` | +| `installer` | Devolutions Gateway | `Installer` | +| `agent` | Devolutions Agent | `Service` | +| `agent-installer` | Devolutions Agent | `Installer` | + +Ignore entries with any other scope (`webapp`, `jetsocat`, `build`, etc.) — they are internal or belong to other products. + +When a single entry has multiple scopes (e.g. `_dgw,agent_:`), emit a separate line for each product it belongs to. + +## Step 3: Classify into categories + +Map each CHANGELOG section to a user-facing category: + +| CHANGELOG section | Category | +|-------------------|---------------| +| `### Features` | New features | +| `### Performance` | Improvements | +| `### Security` | Improvements | +| `### Bug Fixes` | Fixes | +| `### Build` | *(skip)* | + +If unsure, use your judgment: new capabilities → New features, polish/performance/UX → Improvements, fixes → Fixes. + +## Step 4: Write user-friendly descriptions + +The raw commit message title is often too technical. Rewrite each entry in plain English that a non-developer end user would understand: + +- Remove implementation details (crate names, internal module names, PR numbers, commit hashes, protocol internals). +- Use active voice and plain language. +- Preserve the key user-visible benefit. +- Keep it to one line. + +**Good examples:** +- `Service - Add options to use a dedicated certificate for CredSSP credential injection` +- `Installer - Allow downloading keys even when the certificate isn't trusted (useful in restricted/air-gapped environments)` +- `Service - Reduce noisy logs: benign client disconnects are now logged as DEBUG instead of ERROR` +- `Service - Add outbound proxy configuration support for HTTP/HTTPS and SOCKS` +- `Service - Automatically generate a self-signed certificate for CredSSP when no TLS certificate is configured` + +**Bad examples (too technical):** +- `Service - Replace reqwest system-proxy with proxy_cfg crate for PAC file support` ← internal implementation detail +- `Service - Downgrade BrokenPipe/ConnectionReset/UnexpectedEof from ERROR to DEBUG` ← raw code terms +- `Installer - Fix 9a9f31ad71` ← commit hash, no context + +Use the multi-line description under each entry (when present) to understand the user-facing impact — but don't reproduce it verbatim; summarize what the user gains. + +**Handling vague entries:** When a commit message title is ambiguous and there is no multi-line description, inspect the commit diff to understand what actually changed. Each CHANGELOG entry contains a commit hash as a link — run `git show ` to read the diff. Use what you find to write an accurate, user-friendly description. Only omit an entry if the diff confirms it is purely internal with no user-visible effect. + +## Step 5: Output format + +Print the final result. Omit any category that has no entries. + +``` +Devolutions Gateway + +New features +Service - ... +Installer - ... + +Improvements +Service - ... + +Fixes +Service - ... +Installer - ... + +--- + +Devolutions Agent + +New features +Service - ... + +Improvements +Service - ... + +Fixes +Service - ... +Installer - ... +``` + +Each entry is on its own line. Categories are separated by a blank line. The two product sections are separated by `---` and a blank line. This makes it easy to copy individual lines directly into the Devolutions ToolBox.