diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 96d22d3f..431cdbde 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -24,11 +24,12 @@ on: required: true type: string -# Smart concurrency: Prevents self-cancellation when bot posts review comment +# Smart concurrency: Prevents self-cancellation when non-command comments arrive # -# Problem: When the bot posts a review comment, GitHub fires an issue_comment event. -# This new workflow run joins the same concurrency group and cancels the in-progress -# run BEFORE job `if` conditions are evaluated - causing "operation was canceled" error. +# Problem: When a bot or maintainer posts a normal PR note, GitHub fires an +# issue_comment event. A loose substring match on "/review" can accidentally +# treat text like "CI/review" as a manual command, which joins the PR +# concurrency group and cancels the in-progress review. # # Solution: Non-actionable triggers (bot comments, comments without /review) get a # unique per-run group, while legitimate triggers share the PR-based group for proper @@ -38,7 +39,7 @@ concurrency: ai-review-${{ github.event_name == 'issue_comment' && ( github.event.comment.user.type == 'Bot' || - !contains(github.event.comment.body, '/review') + !startsWith(github.event.comment.body, '/review') ) && format('skip-{0}', github.run_id) || github.event.pull_request.number || github.event.issue.number || @@ -66,13 +67,13 @@ jobs: # Conditions: # - PR event: on opened, synchronize (new commits), or reopened - # - Comment event: only if it's a PR, contains /review, and NOT from a bot + # - Comment event: only if it's a PR, starts with /review, and NOT from a bot if: > github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - contains(github.event.comment.body, '/review') && + startsWith(github.event.comment.body, '/review') && github.event.comment.user.type != 'Bot' && contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.comment.author_association)) diff --git a/.npmignore b/.npmignore index 3df4e806..5b875530 100644 --- a/.npmignore +++ b/.npmignore @@ -12,9 +12,10 @@ docs/ .gitignore .gitmodules -# Development tools (keep installer scripts) +# Development tools (keep installer scripts and docker entrypoints) *.sh !installers/*.sh +!docker/*.sh # Logs and temp *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7c55f8..6f4d94a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [7.61.1](https://github.com/kaitranntt/ccs/compare/v7.61.0...v7.61.1) (2026-03-28) + +### Bug Fixes + +* **ci:** require explicit /review command for AI review reruns ([3ebf17f](https://github.com/kaitranntt/ccs/commit/3ebf17f1701f9d7fd60aade0ec6bd8147b91e761)) + ## [7.61.0](https://github.com/kaitranntt/ccs/compare/v7.60.1...v7.61.0) (2026-03-27) ### Features diff --git a/package.json b/package.json index f8b205a8..df1e2e28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.61.0", + "version": "7.61.1", "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more", "keywords": [ "cli", diff --git a/tests/unit/docker/docker-assets-bundled.test.ts b/tests/unit/docker/docker-assets-bundled.test.ts new file mode 100644 index 00000000..ec681518 --- /dev/null +++ b/tests/unit/docker/docker-assets-bundled.test.ts @@ -0,0 +1,21 @@ +import { existsSync } from 'fs'; +import { describe, expect, it } from 'bun:test'; +import { getDockerAssetPaths } from '../../../src/docker/docker-assets'; + +describe('docker bundled assets', () => { + const assets = getDockerAssetPaths(); + + it('resolves all required asset paths', () => { + expect(assets.composeFile).toContain('docker-compose.integrated.yml'); + expect(assets.dockerfile).toContain('Dockerfile.integrated'); + expect(assets.supervisordConfig).toContain('supervisord.conf'); + expect(assets.entrypoint).toContain('entrypoint-integrated.sh'); + }); + + it('all bundled assets exist on disk', () => { + expect(existsSync(assets.composeFile)).toBe(true); + expect(existsSync(assets.dockerfile)).toBe(true); + expect(existsSync(assets.supervisordConfig)).toBe(true); + expect(existsSync(assets.entrypoint)).toBe(true); + }); +});