feat(ci): add workflow_dispatch for AI review (#291)

* feat(ci): add AI code review workflow with Claude via CLIProxyAPI

- Self-hosted runner calls CLIProxyAPI at localhost:8317
- Triggers on PR open/update and /review comment
- Uses gemini-claude-opus-4-5-thinking for deep reviews
- Posts summary + inline comments via gh CLI
- Handles self-PR fallback to COMMENT mode

* chore(release): 7.15.0-dev.1 [skip ci]

* refactor(ci): use GitHub App for reviewer identity + new review format

- Posts as ccs-agy-reviewer[bot] via GitHub App token
- New review format: structured markdown with verdict, summary, issues table
- Single PR comment instead of inline comments
- Concise, focused on PR changes only

* chore(release): 7.15.0-dev.2 [skip ci]

* refactor(ci): switch to Claude Code CLI for reviews

- Use claude -p instead of custom TypeScript script
- Auto-install if not present on runner
- CLIProxyAPI via env vars (ANTHROPIC_BASE_URL, ANTHROPIC_MODEL)
- Allowed tools: Read, Glob, Grep
- Max 3 turns for file exploration

* chore(release): 7.15.0-dev.3 [skip ci]

* chore(release): 7.15.0-dev.4 [skip ci]

* feat(ci): add workflow_dispatch for manual review trigger

* chore(release): 7.15.0-dev.5 [skip ci]

* refactor(cliproxy): add faulty version range infrastructure (#289)

* refactor(cliproxy): add faulty version range infrastructure

- Add CLIPROXY_FAULTY_RANGE constant for marking known buggy versions
- Add isVersionFaulty() helper to version-checker.ts
- Update lifecycle.ts to warn users on faulty versions with upgrade suggestion
- Update health checks to distinguish faulty vs experimental versions
- Update stats routes to include faultyRange in API response
- Skip faulty versions when calculating latestStable

Infrastructure ready for future version promotions. Currently:
- v80 and below: stable
- v81+: marked as faulty (context cancellation bugs)

* chore: trigger review

* chore: re-trigger review

* chore: test PR trigger

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Kai (Tam Nhu) Tran
2026-01-07 02:25:49 -08:00
committed by GitHub
co-authored by github-actions[bot] <github-actions[bot]@users.noreply.github.com>
parent 49c4d299c0
commit b6d65209cd
7 changed files with 97 additions and 22 deletions
+10 -1
View File
@@ -13,10 +13,16 @@ on:
types: [opened, synchronize, reopened]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: string
# Cancel in-progress runs for same PR
concurrency:
group: ai-review-${{ github.event.pull_request.number || github.event.issue.number }}
group: ai-review-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
cancel-in-progress: true
jobs:
@@ -30,6 +36,7 @@ jobs:
# - Comment event: only if it's a PR and contains /review
if: >
github.event_name == 'pull_request' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/review'))
@@ -52,6 +59,8 @@ jobs:
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT
else
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
fi
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "7.15.0-dev.3",
"version": "7.15.0-dev.5",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+23 -7
View File
@@ -5,12 +5,17 @@
import * as fs from 'fs';
import { BinaryManagerConfig } from '../types';
import { checkForUpdates, fetchLatestVersion, isNewerVersion } from './version-checker';
import {
checkForUpdates,
fetchLatestVersion,
isNewerVersion,
isVersionFaulty,
} from './version-checker';
import { downloadAndInstall, deleteBinary, getBinaryPath } from './installer';
import { info, warn } from '../../utils/ui';
import { isCliproxyRunning } from '../stats-fetcher';
import { CLIPROXY_DEFAULT_PORT } from '../config-generator';
import { CLIPROXY_MAX_STABLE_VERSION } from '../platform-detector';
import { CLIPROXY_MAX_STABLE_VERSION, CLIPROXY_FAULTY_RANGE } from '../platform-detector';
/** Log helper */
function log(message: string, verbose: boolean): void {
@@ -46,15 +51,26 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean):
const currentVersion = updateResult.currentVersion;
const latestVersion = updateResult.latestVersion;
// Check if user is on known unstable version - inform but don't force downgrade
if (isAboveMaxStable(currentVersion)) {
// Check if user is on known faulty version - recommend upgrade
if (isVersionFaulty(currentVersion)) {
console.log(
warn(
`CLIProxy Plus v${currentVersion} has known stability issues. ` +
`Stable version: v${CLIPROXY_MAX_STABLE_VERSION}`
`CLIProxy Plus v${currentVersion} has known bugs (v${CLIPROXY_FAULTY_RANGE.min.replace(/-\d+$/, '')}-${CLIPROXY_FAULTY_RANGE.max.replace(/-\d+$/, '')}). ` +
`Upgrade to v${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')} recommended.`
)
);
console.log(
info(
`Run "ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}" to upgrade`
)
);
} else if (isAboveMaxStable(currentVersion)) {
// Version newer than max stable (experimental)
console.log(
warn(
`CLIProxy Plus v${currentVersion} is experimental (above stable v${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')})`
)
);
console.log(info('Run "ccs cliproxy install 80" to downgrade, or wait for upstream fix'));
}
if (!updateResult.hasUpdate) return;
+14 -3
View File
@@ -17,7 +17,7 @@ import {
GITHUB_API_ALL_RELEASES,
VersionListResult,
} from './types';
import { CLIPROXY_MAX_STABLE_VERSION } from '../platform-detector';
import { CLIPROXY_MAX_STABLE_VERSION, CLIPROXY_FAULTY_RANGE } from '../platform-detector';
/**
* Compare semver versions (true if latest > current)
@@ -43,6 +43,17 @@ export function isNewerVersion(latest: string, current: string): boolean {
return false; // Equal versions
}
/**
* Check if version is within the faulty range (v81-85)
* @returns true if version has known critical bugs
*/
export function isVersionFaulty(version: string): boolean {
const { min, max } = CLIPROXY_FAULTY_RANGE;
const atOrAboveMin = !isNewerVersion(min, version); // version >= min
const atOrBelowMax = !isNewerVersion(version, max); // version <= max
return atOrAboveMin && atOrBelowMax;
}
/**
* Fetch latest version from GitHub API
*/
@@ -123,9 +134,9 @@ export async function fetchAllVersions(verbose = false): Promise<VersionListResu
const latest = versions[0] || '';
// Find latest stable (not newer than max stable)
// Find latest stable (not newer than max stable AND not in faulty range)
const latestStable =
versions.find((v) => !isNewerVersion(v, CLIPROXY_MAX_STABLE_VERSION)) ||
versions.find((v) => !isNewerVersion(v, CLIPROXY_MAX_STABLE_VERSION) && !isVersionFaulty(v)) ||
CLIPROXY_MAX_STABLE_VERSION;
const result: VersionListResult = {
+7
View File
@@ -21,6 +21,13 @@ export const CLIPROXY_FALLBACK_VERSION = '6.6.40-0';
*/
export const CLIPROXY_MAX_STABLE_VERSION = '6.6.80-0';
/**
* Faulty version range - versions with known critical bugs
* v81+ have context cancellation bugs causing intermittent 500 errors
* When a stable version is found, update MAX_STABLE and set faulty range accordingly
*/
export const CLIPROXY_FAULTY_RANGE = { min: '6.6.81-0', max: '6.6.999-0' };
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
+16 -3
View File
@@ -16,7 +16,7 @@ import {
import { getPortProcess, isCLIProxyProcess } from '../../utils/port-utils';
import type { HealthCheck } from './types';
import { CLIPROXY_MAX_STABLE_VERSION } from '../../cliproxy/platform-detector';
import { isNewerVersion } from '../../cliproxy/binary/version-checker';
import { isNewerVersion, isVersionFaulty } from '../../cliproxy/binary/version-checker';
/**
* Check CLIProxy binary installation
@@ -26,17 +26,30 @@ export function checkCliproxyBinary(): HealthCheck {
const version = getInstalledCliproxyVersion();
const binaryPath = getCLIProxyPath();
// Check if version is in faulty range (v81-85)
const isFaulty = isVersionFaulty(version);
// Check if version exceeds stable cap
const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION);
if (isFaulty) {
return {
id: 'cliproxy-binary',
name: 'CLIProxy Binary',
status: 'warning',
message: `v${version} (faulty)`,
details: binaryPath,
fix: `Upgrade: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`,
};
}
if (isUnstable) {
return {
id: 'cliproxy-binary',
name: 'CLIProxy Binary',
status: 'warning',
message: `v${version} (unstable)`,
message: `v${version} (experimental)`,
details: binaryPath,
fix: `Downgrade: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`,
fix: `Stable: ccs cliproxy install ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}`,
};
}
+26 -7
View File
@@ -26,8 +26,15 @@ import {
getInstalledCliproxyVersion,
installCliproxyVersion,
} from '../../cliproxy/binary-manager';
import { fetchAllVersions, isNewerVersion } from '../../cliproxy/binary/version-checker';
import { CLIPROXY_MAX_STABLE_VERSION } from '../../cliproxy/platform-detector';
import {
fetchAllVersions,
isNewerVersion,
isVersionFaulty,
} from '../../cliproxy/binary/version-checker';
import {
CLIPROXY_MAX_STABLE_VERSION,
CLIPROXY_FAULTY_RANGE,
} from '../../cliproxy/platform-detector';
const router = Router();
@@ -549,6 +556,7 @@ router.get('/versions', async (_req: Request, res: Response): Promise<void> => {
...result,
currentVersion,
maxStableVersion: CLIPROXY_MAX_STABLE_VERSION,
faultyRange: CLIPROXY_FAULTY_RANGE,
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
@@ -575,14 +583,24 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
return;
}
// Check if version is unstable
const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION);
// Check if version is faulty (v81-85) or experimental (above max stable)
const isFaulty = isVersionFaulty(version);
const isExperimental = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION);
if (isUnstable && !force) {
if (isFaulty && !force) {
res.json({
success: false,
requiresConfirmation: true,
message: `Version ${version} is unstable (above max stable ${CLIPROXY_MAX_STABLE_VERSION}). Set force=true to proceed.`,
message: `Version ${version} has known bugs (v${CLIPROXY_FAULTY_RANGE.min.replace(/-\d+$/, '')}-${CLIPROXY_FAULTY_RANGE.max.replace(/-\d+$/, '')}). Set force=true to proceed.`,
});
return;
}
if (isExperimental && !force) {
res.json({
success: false,
requiresConfirmation: true,
message: `Version ${version} is experimental (above stable ${CLIPROXY_MAX_STABLE_VERSION.replace(/-\d+$/, '')}). Set force=true to proceed.`,
});
return;
}
@@ -599,7 +617,8 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
res.json({
success: true,
version,
isUnstable,
isFaulty,
isExperimental,
message: `Successfully installed CLIProxy Plus v${version}`,
});
} catch (error) {