mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 06:20:13 +00:00
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:
co-authored by
github-actions[bot] <github-actions[bot]@users.noreply.github.com>
parent
49c4d299c0
commit
b6d65209cd
@@ -13,10 +13,16 @@ on:
|
|||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pr_number:
|
||||||
|
description: 'PR number to review'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
# Cancel in-progress runs for same PR
|
# Cancel in-progress runs for same PR
|
||||||
concurrency:
|
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
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -30,6 +36,7 @@ jobs:
|
|||||||
# - Comment event: only if it's a PR and contains /review
|
# - Comment event: only if it's a PR and contains /review
|
||||||
if: >
|
if: >
|
||||||
github.event_name == 'pull_request' ||
|
github.event_name == 'pull_request' ||
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
(github.event_name == 'issue_comment' &&
|
(github.event_name == 'issue_comment' &&
|
||||||
github.event.issue.pull_request &&
|
github.event.issue.pull_request &&
|
||||||
contains(github.event.comment.body, '/review'))
|
contains(github.event.comment.body, '/review'))
|
||||||
@@ -52,6 +59,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||||
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
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
|
else
|
||||||
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
|
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kaitranntt/ccs",
|
"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",
|
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
|
|||||||
@@ -5,12 +5,17 @@
|
|||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { BinaryManagerConfig } from '../types';
|
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 { downloadAndInstall, deleteBinary, getBinaryPath } from './installer';
|
||||||
import { info, warn } from '../../utils/ui';
|
import { info, warn } from '../../utils/ui';
|
||||||
import { isCliproxyRunning } from '../stats-fetcher';
|
import { isCliproxyRunning } from '../stats-fetcher';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../config-generator';
|
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 */
|
/** Log helper */
|
||||||
function log(message: string, verbose: boolean): void {
|
function log(message: string, verbose: boolean): void {
|
||||||
@@ -46,15 +51,26 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean):
|
|||||||
const currentVersion = updateResult.currentVersion;
|
const currentVersion = updateResult.currentVersion;
|
||||||
const latestVersion = updateResult.latestVersion;
|
const latestVersion = updateResult.latestVersion;
|
||||||
|
|
||||||
// Check if user is on known unstable version - inform but don't force downgrade
|
// Check if user is on known faulty version - recommend upgrade
|
||||||
if (isAboveMaxStable(currentVersion)) {
|
if (isVersionFaulty(currentVersion)) {
|
||||||
console.log(
|
console.log(
|
||||||
warn(
|
warn(
|
||||||
`CLIProxy Plus v${currentVersion} has known stability issues. ` +
|
`CLIProxy Plus v${currentVersion} has known bugs (v${CLIPROXY_FAULTY_RANGE.min.replace(/-\d+$/, '')}-${CLIPROXY_FAULTY_RANGE.max.replace(/-\d+$/, '')}). ` +
|
||||||
`Stable version: v${CLIPROXY_MAX_STABLE_VERSION}`
|
`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;
|
if (!updateResult.hasUpdate) return;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
GITHUB_API_ALL_RELEASES,
|
GITHUB_API_ALL_RELEASES,
|
||||||
VersionListResult,
|
VersionListResult,
|
||||||
} from './types';
|
} 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)
|
* Compare semver versions (true if latest > current)
|
||||||
@@ -43,6 +43,17 @@ export function isNewerVersion(latest: string, current: string): boolean {
|
|||||||
return false; // Equal versions
|
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
|
* Fetch latest version from GitHub API
|
||||||
*/
|
*/
|
||||||
@@ -123,9 +134,9 @@ export async function fetchAllVersions(verbose = false): Promise<VersionListResu
|
|||||||
|
|
||||||
const latest = versions[0] || '';
|
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 =
|
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;
|
CLIPROXY_MAX_STABLE_VERSION;
|
||||||
|
|
||||||
const result: VersionListResult = {
|
const result: VersionListResult = {
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ export const CLIPROXY_FALLBACK_VERSION = '6.6.40-0';
|
|||||||
*/
|
*/
|
||||||
export const CLIPROXY_MAX_STABLE_VERSION = '6.6.80-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 */
|
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
|
||||||
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
|
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { getPortProcess, isCLIProxyProcess } from '../../utils/port-utils';
|
import { getPortProcess, isCLIProxyProcess } from '../../utils/port-utils';
|
||||||
import type { HealthCheck } from './types';
|
import type { HealthCheck } from './types';
|
||||||
import { CLIPROXY_MAX_STABLE_VERSION } from '../../cliproxy/platform-detector';
|
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
|
* Check CLIProxy binary installation
|
||||||
@@ -26,17 +26,30 @@ export function checkCliproxyBinary(): HealthCheck {
|
|||||||
const version = getInstalledCliproxyVersion();
|
const version = getInstalledCliproxyVersion();
|
||||||
const binaryPath = getCLIProxyPath();
|
const binaryPath = getCLIProxyPath();
|
||||||
|
|
||||||
|
// Check if version is in faulty range (v81-85)
|
||||||
|
const isFaulty = isVersionFaulty(version);
|
||||||
// Check if version exceeds stable cap
|
// Check if version exceeds stable cap
|
||||||
const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION);
|
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) {
|
if (isUnstable) {
|
||||||
return {
|
return {
|
||||||
id: 'cliproxy-binary',
|
id: 'cliproxy-binary',
|
||||||
name: 'CLIProxy Binary',
|
name: 'CLIProxy Binary',
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
message: `v${version} (unstable)`,
|
message: `v${version} (experimental)`,
|
||||||
details: binaryPath,
|
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,8 +26,15 @@ import {
|
|||||||
getInstalledCliproxyVersion,
|
getInstalledCliproxyVersion,
|
||||||
installCliproxyVersion,
|
installCliproxyVersion,
|
||||||
} from '../../cliproxy/binary-manager';
|
} from '../../cliproxy/binary-manager';
|
||||||
import { fetchAllVersions, isNewerVersion } from '../../cliproxy/binary/version-checker';
|
import {
|
||||||
import { CLIPROXY_MAX_STABLE_VERSION } from '../../cliproxy/platform-detector';
|
fetchAllVersions,
|
||||||
|
isNewerVersion,
|
||||||
|
isVersionFaulty,
|
||||||
|
} from '../../cliproxy/binary/version-checker';
|
||||||
|
import {
|
||||||
|
CLIPROXY_MAX_STABLE_VERSION,
|
||||||
|
CLIPROXY_FAULTY_RANGE,
|
||||||
|
} from '../../cliproxy/platform-detector';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -549,6 +556,7 @@ router.get('/versions', async (_req: Request, res: Response): Promise<void> => {
|
|||||||
...result,
|
...result,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
maxStableVersion: CLIPROXY_MAX_STABLE_VERSION,
|
maxStableVersion: CLIPROXY_MAX_STABLE_VERSION,
|
||||||
|
faultyRange: CLIPROXY_FAULTY_RANGE,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: (error as Error).message });
|
res.status(500).json({ error: (error as Error).message });
|
||||||
@@ -575,14 +583,24 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if version is unstable
|
// Check if version is faulty (v81-85) or experimental (above max stable)
|
||||||
const isUnstable = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION);
|
const isFaulty = isVersionFaulty(version);
|
||||||
|
const isExperimental = isNewerVersion(version, CLIPROXY_MAX_STABLE_VERSION);
|
||||||
|
|
||||||
if (isUnstable && !force) {
|
if (isFaulty && !force) {
|
||||||
res.json({
|
res.json({
|
||||||
success: false,
|
success: false,
|
||||||
requiresConfirmation: true,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -599,7 +617,8 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
|||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
version,
|
version,
|
||||||
isUnstable,
|
isFaulty,
|
||||||
|
isExperimental,
|
||||||
message: `Successfully installed CLIProxy Plus v${version}`,
|
message: `Successfully installed CLIProxy Plus v${version}`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user