From 3958928a9aac8de61d577392aa206fe157ffbf24 Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Mon, 5 Jan 2026 22:02:52 +0000 Subject: [PATCH 1/9] fix: auto-bump patch version when tag exists in workflow_run trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, when the Release workflow was triggered by workflow_run (after CI completes on main), it would skip the release if the current version tag already existed. This required manual version bumps before each release. Now, when triggered by workflow_run: - If current version tag doesn't exist → release with current version - If current version tag exists → auto-bump patch version, update Cargo.toml, then release This makes releases fully automatic after merging PRs to main. --- .github/workflows/release.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2c87cf..9f74c6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,8 +108,14 @@ jobs: # Check if this version tag already exists if git tag -l "v$CURRENT_VERSION" | grep -q .; then - echo "Tag v$CURRENT_VERSION already exists, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT + echo "Tag v$CURRENT_VERSION already exists, auto-bumping patch version" + # Auto-bump patch version + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + echo "Auto-bumped to: $NEW_VERSION" + echo "should_release=true" >> $GITHUB_OUTPUT + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "needs_bump=true" >> $GITHUB_OUTPUT else echo "New version v$CURRENT_VERSION detected, will release" echo "should_release=true" >> $GITHUB_OUTPUT From 8f78d7bf890f3cf91bb97ae7496255085f5d11ca Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Mon, 5 Jan 2026 22:16:10 +0000 Subject: [PATCH 2/9] fix: use RELEASE_TOKEN for auto-bump in release workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses RELEASE_TOKEN (a PAT with Contents write permission) to bypass branch protection when auto-bumping the version. Setup required: 1. Create a Fine-Grained PAT at GitHub Settings → Developer Settings → Personal Access Tokens 2. Grant it 'Contents: Read and write' permission for this repo 3. Add it as a repository secret named RELEASE_TOKEN The workflow falls back to GITHUB_TOKEN if RELEASE_TOKEN is not set, which will fail on protected branches but work on unprotected ones. --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f74c6c..725dab4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,6 +129,7 @@ jobs: echo "should_release=false" >> $GITHUB_OUTPUT # Bump version in Cargo.toml if needed + # Uses RELEASE_TOKEN (PAT) to bypass branch protection, falls back to GITHUB_TOKEN bump-version: needs: check if: needs.check.outputs.should_release == 'true' && needs.check.outputs.needs_bump == 'true' @@ -136,7 +137,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} - name: Update Cargo.toml version run: | @@ -154,7 +155,7 @@ jobs: git commit -m "chore: bump version to $VERSION [skip ci]" git push env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} build: needs: [check, bump-version] From b4453e632e5d30afeda0cc5cc6c853979ab13a80 Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Mon, 5 Jan 2026 23:28:48 +0000 Subject: [PATCH 3/9] fix: replace OpenAI with git-cliff for changelog generation - Remove OpenAI API dependency for changelog generation - Use git-cliff (Rust-based, free, no API keys needed) - Parses conventional commits automatically - Categorizes commits with emoji prefixes - Falls back to simple format if git-cliff fails --- .github/workflows/release.yml | 238 ++++++++++------------------------ 1 file changed, 71 insertions(+), 167 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 725dab4..bc99c73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -206,7 +206,7 @@ jobs: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - # Generate AI-powered changelog + # Generate changelog using git-cliff (fast, free, no API keys needed) changelog: needs: [check, bump-version, build] if: | @@ -224,177 +224,80 @@ jobs: - name: Pull latest changes run: git pull origin ${{ github.ref_name }} || true - - name: Get previous tag - id: prev_tag + - name: Install git-cliff run: | - # Get the most recent tag before this release - PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | head -2 | tail -1) - if [ -z "$PREV_TAG" ]; then - # No previous tag, use first commit - PREV_TAG=$(git rev-list --max-parents=0 HEAD) - fi - echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT - echo "Previous tag: $PREV_TAG" + # Install git-cliff (Rust-based changelog generator) + curl -sSfL https://github.com/orhun/git-cliff/releases/download/v2.7.0/git-cliff-2.7.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv git-cliff-2.7.0/git-cliff /usr/local/bin/ + git-cliff --version - - name: Collect commits - id: commits - run: | - PREV_TAG="${{ steps.prev_tag.outputs.prev_tag }}" - - # Get commits with conventional commit format parsing - echo "## Commits since $PREV_TAG" > commits.md - echo "" >> commits.md - - # Group commits by type - git log "$PREV_TAG"..HEAD --pretty=format:"%s|%h|%an" | while IFS='|' read -r msg hash author; do - echo "$msg|$hash|$author" - done > raw_commits.txt - - # Parse and categorize commits - echo "### ✨ Features" > features.md - echo "" >> features.md - echo "### 🐛 Bug Fixes" > fixes.md - echo "" >> fixes.md - echo "### 🔒 Security" > security.md - echo "" >> security.md - echo "### 📚 Documentation" > docs.md - echo "" >> docs.md - echo "### 🔧 Maintenance" > chores.md - echo "" >> chores.md - echo "### 🎨 Refactoring" > refactor.md - echo "" >> refactor.md - echo "### ⚡ Performance" > perf.md - echo "" >> perf.md - echo "### 🧪 Tests" > tests.md - echo "" >> tests.md - echo "### 📦 Other Changes" > other.md - echo "" >> other.md - - while IFS='|' read -r msg hash author; do - # Extract type from conventional commit - if [[ "$msg" =~ ^feat(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> features.md - elif [[ "$msg" =~ ^fix(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> fixes.md - elif [[ "$msg" =~ ^security(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> security.md - elif [[ "$msg" =~ ^docs?(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> docs.md - elif [[ "$msg" =~ ^chore(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> chores.md - elif [[ "$msg" =~ ^refactor(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> refactor.md - elif [[ "$msg" =~ ^perf(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> perf.md - elif [[ "$msg" =~ ^test(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> tests.md - else - # Other commits - echo "- ${msg} (\`${hash}\`) - @${author}" >> other.md - fi - done < raw_commits.txt - - - name: Generate changelog with AI + - name: Generate changelog id: generate - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | VERSION="${{ needs.check.outputs.version }}" - # Combine categorized commits - { - echo "# Release v${VERSION}" - echo "" - echo "Released on $(date '+%Y-%m-%d')" - echo "" - - # Add non-empty sections - for file in features.md fixes.md security.md docs.md refactor.md perf.md tests.md chores.md other.md; do - if [ -f "$file" ] && [ $(wc -l < "$file") -gt 2 ]; then - cat "$file" - echo "" - fi - done - } > changelog_draft.md - - # If OpenAI API key is available, enhance with AI - if [ -n "$OPENAI_API_KEY" ]; then - echo "Enhancing changelog with AI..." - - # Prepare prompt for AI - COMMITS=$(cat raw_commits.txt | head -50) - - # Call OpenAI API to generate summary - RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d @- < changelog.md - else - echo "AI summary generation failed, using draft changelog" - cp changelog_draft.md changelog.md - fi - else - echo "No OpenAI API key, using standard changelog" - cp changelog_draft.md changelog.md + # Get previous tag for range + PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | head -2 | tail -1) + if [ -z "$PREV_TAG" ]; then + PREV_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "Generating changelog from $PREV_TAG to HEAD" + + # Create git-cliff config inline + cat > cliff.toml << 'CLIFF_CONFIG' + [changelog] + header = "" + body = """ + # Release v{{ version }} + + Released on {{ timestamp | date(format="%Y-%m-%d") }} + + {% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | split(pat="\n") | first | trim }} (`{{ commit.id | truncate(length=7, end="") }}`) - @{{ commit.author.name }} + {%- endfor %} + {% endfor %} + """ + footer = "" + trim = true + + [git] + conventional_commits = true + filter_unconventional = false + split_commits = false + commit_parsers = [ + { message = "^feat", group = "✨ Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^security", group = "🔒 Security" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor", group = "🎨 Refactoring" }, + { message = "^test", group = "🧪 Tests" }, + { message = "^chore", group = "🔧 Maintenance" }, + { message = "^ci", group = "🔧 Maintenance" }, + { message = "^build", group = "🔧 Maintenance" }, + { message = ".*", group = "📦 Other" }, + ] + filter_commits = false + tag_pattern = "v[0-9].*" + CLIFF_CONFIG + + # Generate changelog for this release + git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md + + # If git-cliff fails or produces empty output, fall back to simple format + if [ ! -s changelog.md ]; then + echo "git-cliff produced empty output, using fallback" + { + echo "# Release v${VERSION}" + echo "" + echo "Released on $(date '+%Y-%m-%d')" + echo "" + echo "### 📦 Changes" + echo "" + git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" + } > changelog.md fi # Output changelog for use in release @@ -402,7 +305,8 @@ jobs: cat changelog.md >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - # Also save as artifact + # Display changelog + echo "=== Generated Changelog ===" cat changelog.md - name: Upload changelog artifact From 9489ecee5190f0d9c7f4cf93d965235bd2dd2ae8 Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Wed, 7 Jan 2026 17:24:23 +0000 Subject: [PATCH 4/9] fix: address CodeRabbit review comments - Upgrade git-cliff from v2.7.0 to v2.11.0 to address CVE-2024-32650 - Add SHA512 checksum verification before installing git-cliff binary - Improve error handling to capture git-cliff exit code - Distinguish between git-cliff failure vs empty output scenarios --- .github/workflows/release.yml | 50 +++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc99c73..15f26e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -226,9 +226,27 @@ jobs: - name: Install git-cliff run: | - # Install git-cliff (Rust-based changelog generator) - curl -sSfL https://github.com/orhun/git-cliff/releases/download/v2.7.0/git-cliff-2.7.0-x86_64-unknown-linux-gnu.tar.gz | tar xz - sudo mv git-cliff-2.7.0/git-cliff /usr/local/bin/ + # Install git-cliff v2.11.0 (Rust-based changelog generator) + # Using latest version to address CVE-2024-32650 in rustls dependency + VERSION="2.11.0" + TARBALL="git-cliff-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" + DOWNLOAD_URL="https://github.com/orhun/git-cliff/releases/download/v${VERSION}" + + # Download the tarball and checksum + curl -sSfL "${DOWNLOAD_URL}/${TARBALL}" -o "${TARBALL}" + curl -sSfL "${DOWNLOAD_URL}/${TARBALL}.sha512" -o "${TARBALL}.sha512" + + # Verify checksum before installation + echo "Verifying checksum..." + sha512sum -c "${TARBALL}.sha512" + + # Extract and install + tar xzf "${TARBALL}" + sudo mv "git-cliff-${VERSION}/git-cliff" /usr/local/bin/ + + # Cleanup + rm -rf "${TARBALL}" "${TARBALL}.sha512" "git-cliff-${VERSION}" + git-cliff --version - name: Generate changelog @@ -283,12 +301,14 @@ jobs: tag_pattern = "v[0-9].*" CLIFF_CONFIG - # Generate changelog for this release - git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md + # Generate changelog for this release with proper error handling + # Capture exit code to distinguish between failure and empty output + CLIFF_EXIT_CODE=0 + git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md 2>&1 || CLIFF_EXIT_CODE=$? - # If git-cliff fails or produces empty output, fall back to simple format - if [ ! -s changelog.md ]; then - echo "git-cliff produced empty output, using fallback" + # Check if git-cliff failed (non-zero exit code) + if [ $CLIFF_EXIT_CODE -ne 0 ]; then + echo "git-cliff failed with exit code $CLIFF_EXIT_CODE, using fallback" { echo "# Release v${VERSION}" echo "" @@ -298,6 +318,20 @@ jobs: echo "" git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" } > changelog.md + # Check if git-cliff succeeded but produced empty output + elif [ ! -s changelog.md ]; then + echo "git-cliff produced empty output (no commits in range), using fallback" + { + echo "# Release v${VERSION}" + echo "" + echo "Released on $(date '+%Y-%m-%d')" + echo "" + echo "### 📦 Changes" + echo "" + git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" + } > changelog.md + else + echo "git-cliff succeeded with valid output" fi # Output changelog for use in release From 37066e481a110bc8c8ed3c1be62394fd6721f685 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:58:23 +0000 Subject: [PATCH 5/9] fix: satisfy clippy manual_checked_ops on current stable CI installs the latest stable toolchain, whose clippy now rejects the manual zero-check division in progress_percent. Behaviour is unchanged. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016ob6xGh8JYcLdFcc4mS4q3 --- src/tools/planning.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/planning.rs b/src/tools/planning.rs index 9ea98be..5d7687e 100644 --- a/src/tools/planning.rs +++ b/src/tools/planning.rs @@ -732,7 +732,7 @@ impl ToolHandler for ViewProgressTool { "in_progress": in_progress, "failed": failed, "pending": pending, - "progress_percent": if total > 0 { (completed * 100) / total } else { 0 } + "progress_percent": (completed * 100).checked_div(total).unwrap_or(0) }); Ok(success_result(serde_json::to_string_pretty(&progress)?)) } From a74e443d4086d0d45038ad1cf6fec0916bb86b79 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:59:14 +0000 Subject: [PATCH 6/9] fix: correct changelog range, heading and noise in release workflow - pick the newest v* tag other than the one being released as the previous release; 'head -2 | tail -1' skipped the real previous release on workflow_run/dispatch and re-listed it in the new notes - stop emitting 'Release vv2.1.2' (git-cliff's version already has the v) - keep git-cliff stderr out of changelog.md (its WARN lines leaked into the release body) and show it in the job log instead - skip version-bump and merge commits in the notes - handle the no-previous-tag case (a bare HEAD is not a valid cliff range) - use a random GITHUB_OUTPUT delimiter instead of a fixed EOF Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016ob6xGh8JYcLdFcc4mS4q3 --- .github/workflows/release.yml | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15f26e7..48c10ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -254,19 +254,25 @@ jobs: run: | VERSION="${{ needs.check.outputs.version }}" - # Get previous tag for range - PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | head -2 | tail -1) - if [ -z "$PREV_TAG" ]; then - PREV_TAG=$(git rev-list --max-parents=0 HEAD) + # Previous release = newest v* tag other than the one being released. + # (On workflow_run/workflow_dispatch the new tag does not exist yet, so + # the newest tag is the previous release; on a tag push it is the new + # tag and must be excluded.) + PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | grep -vx "v${VERSION}" | head -1 || true) + if [ -n "$PREV_TAG" ]; then + RANGE="${PREV_TAG}..HEAD" + else + # No previous release: leave RANGE empty to cover the full history + RANGE="" fi - echo "Generating changelog from $PREV_TAG to HEAD" + echo "Generating changelog for ${RANGE:-full history}" # Create git-cliff config inline cat > cliff.toml << 'CLIFF_CONFIG' [changelog] header = "" body = """ - # Release v{{ version }} + # Release {{ version }} Released on {{ timestamp | date(format="%Y-%m-%d") }} @@ -292,6 +298,8 @@ jobs: { message = "^perf", group = "⚡ Performance" }, { message = "^refactor", group = "🎨 Refactoring" }, { message = "^test", group = "🧪 Tests" }, + { message = "^chore: bump version", skip = true }, + { message = "^Merge ", skip = true }, { message = "^chore", group = "🔧 Maintenance" }, { message = "^ci", group = "🔧 Maintenance" }, { message = "^build", group = "🔧 Maintenance" }, @@ -304,7 +312,8 @@ jobs: # Generate changelog for this release with proper error handling # Capture exit code to distinguish between failure and empty output CLIFF_EXIT_CODE=0 - git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md 2>&1 || CLIFF_EXIT_CODE=$? + git-cliff --config cliff.toml --tag "v${VERSION}" ${RANGE:+"$RANGE"} > changelog.md 2> cliff.err || CLIFF_EXIT_CODE=$? + cat cliff.err >&2 # Check if git-cliff failed (non-zero exit code) if [ $CLIFF_EXIT_CODE -ne 0 ]; then @@ -316,7 +325,7 @@ jobs: echo "" echo "### 📦 Changes" echo "" - git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" + git log "${RANGE:-HEAD}" --pretty=format:"- %s (\`%h\`) - @%an" } > changelog.md # Check if git-cliff succeeded but produced empty output elif [ ! -s changelog.md ]; then @@ -328,16 +337,17 @@ jobs: echo "" echo "### 📦 Changes" echo "" - git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" + git log "${RANGE:-HEAD}" --pretty=format:"- %s (\`%h\`) - @%an" } > changelog.md else echo "git-cliff succeeded with valid output" fi # Output changelog for use in release - echo "changelog<> $GITHUB_OUTPUT + DELIM="CHANGELOG_$(openssl rand -hex 8)" + echo "changelog<<${DELIM}" >> $GITHUB_OUTPUT cat changelog.md >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "${DELIM}" >> $GITHUB_OUTPUT # Display changelog echo "=== Generated Changelog ===" From c3a74dd77ad722efcbf02064d535bbe9ee9aab6e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:09:44 +0000 Subject: [PATCH 7/9] fix: allow clippy::double_must_use on async_trait traits Rust 1.99 clippy flags the boxed futures that #[async_trait] generates for ToolHandler and Transport, which fails the CI clippy step (-D warnings) and would block the post-merge release. No behaviour change. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016ob6xGh8JYcLdFcc4mS4q3 --- src/mcp/handler.rs | 2 ++ src/mcp/transport.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/mcp/handler.rs b/src/mcp/handler.rs index eacc3f6..12254f0 100644 --- a/src/mcp/handler.rs +++ b/src/mcp/handler.rs @@ -9,6 +9,8 @@ use crate::error::Result; use crate::mcp::protocol::{Tool, ToolResult}; /// Handler for MCP tool calls. +// async_trait expands to boxed futures, which trips clippy::double_must_use on newer stable. +#[allow(clippy::double_must_use)] #[async_trait] pub trait ToolHandler: Send + Sync { /// Get the tool definition. diff --git a/src/mcp/transport.rs b/src/mcp/transport.rs index eca05bd..296f3ed 100644 --- a/src/mcp/transport.rs +++ b/src/mcp/transport.rs @@ -20,6 +20,8 @@ pub enum Message { } /// Transport trait for MCP communication. +// async_trait expands to boxed futures, which trips clippy::double_must_use on newer stable. +#[allow(clippy::double_must_use)] #[async_trait] pub trait Transport: Send + Sync { /// Start the transport, returning channels for messages. From c00f633f53938c6ea24da9e3396566b4a2556544 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:16:43 +0000 Subject: [PATCH 8/9] test: cover progress_percent in view_progress Empty plan reports 0 (the guarded divide-by-zero path); 1 of 3 completed reports 33. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016ob6xGh8JYcLdFcc4mS4q3 --- src/tools/planning.rs | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/tools/planning.rs b/src/tools/planning.rs index 5d7687e..a2ba43f 100644 --- a/src/tools/planning.rs +++ b/src/tools/planning.rs @@ -974,3 +974,68 @@ impl ToolHandler for RollbackPlanTool { Ok(success_result(serde_json::to_string_pretty(&result)?)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::mcp::protocol::ContentBlock; + use tempfile::TempDir; + + fn step(id: u32, status: StepStatus) -> Step { + Step { + id, + title: format!("step {id}"), + description: String::new(), + status, + step_type: StepType::Implementation, + dependencies: vec![], + affected_files: vec![], + estimated_duration: None, + actual_duration: None, + completed_at: None, + error: None, + output: None, + requires_approval: false, + approval: None, + rollback: None, + validation: vec![], + } + } + + async fn progress_percent(steps: Vec) -> u64 { + let temp = TempDir::new().unwrap(); + let service = Arc::new(PlanningService::new(temp.path()).await.unwrap()); + let plan = service + .create_plan("plan".to_string(), String::new()) + .await + .unwrap(); + for s in steps { + service.add_step(&plan.id, s).await.unwrap(); + } + + let tool = ViewProgressTool::new(service); + let args = HashMap::from([("plan_id".to_string(), Value::String(plan.id))]); + let result = tool.execute(args).await.unwrap(); + let ContentBlock::Text { text } = &result.content[0] else { + panic!("expected text content"); + }; + let json: Value = serde_json::from_str(text).unwrap(); + json["progress_percent"].as_u64().unwrap() + } + + #[tokio::test] + async fn view_progress_is_zero_for_empty_plan() { + assert_eq!(progress_percent(vec![]).await, 0); + } + + #[tokio::test] + async fn view_progress_reports_completed_share() { + let steps = vec![ + step(1, StepStatus::Completed), + step(2, StepStatus::Pending), + step(3, StepStatus::Pending), + ]; + // 1 of 3 completed, integer division + assert_eq!(progress_percent(steps).await, 33); + } +} From 8246ef66f054f721bf289fab5aa7f01ce3763b5a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 22:16:43 +0000 Subject: [PATCH 9/9] fix: pick the previous tag by version order in release changelog Choose the newest v* tag strictly older than the version being released, so dispatching an older version never uses a newer tag as the range start. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016ob6xGh8JYcLdFcc4mS4q3 --- .github/workflows/release.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48c10ba..a08dbaf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -254,11 +254,13 @@ jobs: run: | VERSION="${{ needs.check.outputs.version }}" - # Previous release = newest v* tag other than the one being released. - # (On workflow_run/workflow_dispatch the new tag does not exist yet, so - # the newest tag is the previous release; on a tag push it is the new - # tag and must be excluded.) - PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | grep -vx "v${VERSION}" | head -1 || true) + # Previous release = newest v* tag strictly older (by version order) + # than the one being released. The new tag does not exist yet on + # workflow_run/workflow_dispatch and does on a tag push; either way it + # is not its own predecessor, and a re-release of an older version never + # picks a newer tag. + PREV_TAG=$( (git tag -l 'v*'; echo "v${VERSION}") | sort -uV | + awk -v target="v${VERSION}" '$0 == target { exit } { prev = $0 } END { print prev }') if [ -n "$PREV_TAG" ]; then RANGE="${PREV_TAG}..HEAD" else