@kaitranntt/ccs 7.15.0-dev.1 → 7.15.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "7.15.0-dev.1",
3
+ "version": "7.15.0-dev.2",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",
@@ -2,11 +2,12 @@
2
2
  /**
3
3
  * AI Code Reviewer for CCS CLI
4
4
  *
5
- * Fetches PR diff, calls Claude via CLIProxyAPI, posts review to GitHub.
5
+ * Fetches PR diff, calls Claude via CLIProxyAPI, posts review as comment.
6
6
  * Runs on self-hosted runner with localhost access to CLIProxyAPI:8317.
7
+ * Posts as ccs-agy-reviewer[bot] via GitHub App token.
7
8
  *
8
9
  * Usage: bun run scripts/code-reviewer.ts <PR_NUMBER>
9
- * Env: CLIPROXY_API_KEY, GITHUB_REPOSITORY (optional)
10
+ * Env: CLIPROXY_API_KEY, GITHUB_REPOSITORY, GH_TOKEN
10
11
  */
11
12
 
12
13
  import { $ } from 'bun';
@@ -22,64 +23,52 @@ interface PRContext {
22
23
  diff: string;
23
24
  }
24
25
 
25
- interface ReviewComment {
26
- path: string;
27
- line: number;
28
- body: string;
29
- severity: 'critical' | 'warning' | 'suggestion' | 'nitpick';
30
- }
26
+ // Config
27
+ const MAX_DIFF_LINES = 10000;
28
+ const CLIPROXY_URL = process.env.CLIPROXY_URL || 'http://localhost:8317';
29
+ const MODEL = process.env.REVIEW_MODEL || 'gemini-claude-opus-4-5-thinking';
31
30
 
32
- interface ReviewResult {
33
- summary: string;
34
- approvalStatus: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT';
35
- comments: ReviewComment[];
36
- securityIssues: string[];
37
- suggestions: string[];
38
- }
31
+ // System prompt for code review - new style
32
+ const CODE_REVIEWER_SYSTEM_PROMPT = `You are the CCS AGY Code Reviewer, an expert AI assistant reviewing pull requests for the CCS CLI project.
39
33
 
40
- // System prompt for code review
41
- const CODE_REVIEWER_SYSTEM_PROMPT = `You are an expert code reviewer for the CCS CLI project, a TypeScript/Node.js tool for managing Claude Code accounts.
34
+ ## Review Guidelines
35
+ - Focus ONLY on changes in this PR - don't suggest unrelated improvements
36
+ - Be concise - no fluff, no excessive praise
37
+ - Provide specific file:line references for issues
38
+ - Verify claims before making them (check if patterns exist, check actual code)
39
+ - Avoid over-engineering suggestions for simple fixes
42
40
 
43
- ## Your Role
44
- Review pull requests thoroughly for:
45
- 1. **Bugs & Logic Errors** - Race conditions, off-by-one, null handling
46
- 2. **Security Issues** - Injection, secrets exposure, auth bypass
47
- 3. **Code Quality** - YAGNI, KISS, DRY violations; readability
48
- 4. **TypeScript Best Practices** - Proper typing, no \`any\`, null safety
49
- 5. **CCS Conventions** - ASCII only (no emojis), conventional commits
41
+ ## Check For
42
+ 1. **Bugs**: Logic errors, edge cases, null handling, race conditions
43
+ 2. **Security**: Injection, auth bypass, secrets exposure, data leaks
44
+ 3. **Performance**: N+1 queries, missing indexes, inefficient algorithms
45
+ 4. **TypeScript**: Proper typing, no \`any\`, null safety
46
+ 5. **Consistency**: Similar patterns exist elsewhere that need same fix?
50
47
 
51
48
  ## Output Format
52
- Respond with ONLY valid JSON matching this schema:
53
-
54
- \`\`\`json
55
- {
56
- "summary": "2-3 sentence overall assessment",
57
- "approvalStatus": "APPROVE" | "REQUEST_CHANGES" | "COMMENT",
58
- "comments": [
59
- {
60
- "path": "relative/path/to/file.ts",
61
- "line": 42,
62
- "body": "Specific feedback for this line",
63
- "severity": "critical" | "warning" | "suggestion" | "nitpick"
64
- }
65
- ],
66
- "securityIssues": ["List of security concerns if any"],
67
- "suggestions": ["General improvement suggestions"]
68
- }
69
- \`\`\`
49
+ Structure your response EXACTLY like this (no code fences, render as markdown):
70
50
 
71
- ## Guidelines
72
- - Be constructive, not harsh
73
- - Prioritize critical issues over nitpicks
74
- - Reference specific lines and provide fix suggestions
75
- - If no issues: approvalStatus = "APPROVE", comments = []
76
- - Max 10 inline comments (focus on most important)`;
51
+ ## 🔍 Code Review
77
52
 
78
- // Config
79
- const MAX_DIFF_LINES = 10000;
80
- const MAX_INLINE_COMMENTS = 10;
81
- const CLIPROXY_URL = process.env.CLIPROXY_URL || 'http://localhost:8317';
82
- const MODEL = process.env.REVIEW_MODEL || 'gemini-claude-opus-4-5-thinking';
53
+ **Verdict**: [✅ Approve | ✅ Approve with suggestions | ⚠️ Request changes]
54
+
55
+ ### Summary
56
+ [1-2 sentences on what the PR does and if it's correct]
57
+
58
+ ### ✅ What's Good
59
+ - [Bullet points, 2-4 items max]
60
+
61
+ ### ⚠️ Issues Found
62
+ | File:Line | Issue | Severity |
63
+ |-----------|-------|----------|
64
+ | \`file.ts:123\` | Description | 🔴 High / 🟡 Medium / 🟢 Low |
65
+
66
+ (If no issues, write "None - LGTM")
67
+
68
+ ### 💡 Suggestions (Optional)
69
+ - [Only if truly valuable, max 2 items]
70
+
71
+ IMPORTANT: Output ONLY the markdown review. No JSON, no code blocks wrapping the review.`;
83
72
 
84
73
  // Fetch PR context
85
74
  async function getPRContext(prNumber: number, repo: string): Promise<PRContext> {
@@ -111,11 +100,14 @@ async function getPRContext(prNumber: number, repo: string): Promise<PRContext>
111
100
  }
112
101
 
113
102
  // Call Claude via CLIProxyAPI
114
- async function callClaude(context: PRContext): Promise<ReviewResult> {
103
+ async function callClaude(context: PRContext, repo: string): Promise<string> {
115
104
  const apiKey = process.env.CLIPROXY_API_KEY;
116
105
  if (!apiKey) throw new Error('CLIPROXY_API_KEY not set');
117
106
 
118
- const userMessage = `## Pull Request: ${context.title}
107
+ const userMessage = `REPO: ${repo}
108
+ PR NUMBER: ${context.number}
109
+
110
+ ## Pull Request: ${context.title}
119
111
 
120
112
  ### Description
121
113
  ${context.body || '(No description provided)'}
@@ -128,7 +120,7 @@ ${context.files.map((f) => `- ${f.path} (+${f.additions}/-${f.deletions})`).join
128
120
  ${context.diff}
129
121
  \`\`\`
130
122
 
131
- Please review this pull request and provide your assessment in the specified JSON format.`;
123
+ Review this PR following the guidelines. Refer to the project's CLAUDE.md and docs/ folder for conventions.`;
132
124
 
133
125
  const response = await fetch(`${CLIPROXY_URL}/v1/messages`, {
134
126
  method: 'POST',
@@ -139,7 +131,7 @@ Please review this pull request and provide your assessment in the specified JSO
139
131
  },
140
132
  body: JSON.stringify({
141
133
  model: MODEL,
142
- max_tokens: 8192,
134
+ max_tokens: 4096,
143
135
  system: CODE_REVIEWER_SYSTEM_PROMPT,
144
136
  messages: [{ role: 'user', content: userMessage }],
145
137
  }),
@@ -157,103 +149,21 @@ Please review this pull request and provide your assessment in the specified JSO
157
149
  throw new Error('Empty response from Claude');
158
150
  }
159
151
 
160
- // Parse JSON from response (may be wrapped in markdown code block)
161
- const jsonMatch = content.match(/```json\n?([\s\S]*?)\n?```/);
162
- const jsonStr = jsonMatch ? jsonMatch[1].trim() : content.trim();
163
-
164
- try {
165
- return JSON.parse(jsonStr) as ReviewResult;
166
- } catch {
167
- // Fallback: create basic review from raw text
168
- console.warn('[!] Failed to parse JSON, using fallback');
169
- return {
170
- summary: content.slice(0, 500),
171
- approvalStatus: 'COMMENT',
172
- comments: [],
173
- securityIssues: [],
174
- suggestions: [],
175
- };
176
- }
177
- }
178
-
179
- // Format inline comment with severity badge
180
- function formatComment(comment: ReviewComment): string {
181
- const badge: Record<string, string> = {
182
- critical: '[!] **Critical**',
183
- warning: '[~] **Warning**',
184
- suggestion: '[i] **Suggestion**',
185
- nitpick: '[ ] Nitpick',
186
- };
187
- return `${badge[comment.severity] || '[i]'}\n\n${comment.body}`;
152
+ return content;
188
153
  }
189
154
 
190
- // Post review to GitHub
191
- async function postReview(prNumber: number, repo: string, review: ReviewResult): Promise<void> {
192
- // Build review body
193
- let body = `## AI Code Review\n\n${review.summary}\n`;
194
-
195
- if (review.securityIssues.length > 0) {
196
- body += `\n### Security Issues\n${review.securityIssues.map((i) => `- ${i}`).join('\n')}\n`;
197
- }
198
-
199
- if (review.suggestions.length > 0) {
200
- body += `\n### Suggestions\n${review.suggestions.map((s) => `- ${s}`).join('\n')}\n`;
201
- }
202
-
203
- if (review.comments.length > 0) {
204
- body += `\n### Inline Comments\n${review.comments.length} comment(s) posted on specific lines.\n`;
205
- }
206
-
207
- body += '\n---\n*Automated review by CCS AGY Code Reviewer*';
208
-
209
- // Map approval status to gh flag
210
- const eventFlag: Record<string, string> = {
211
- APPROVE: '--approve',
212
- REQUEST_CHANGES: '--request-changes',
213
- COMMENT: '--comment',
214
- };
215
-
216
- // Post main review (try APPROVE/REQUEST_CHANGES, fallback to COMMENT if self-PR)
217
- const flag = eventFlag[review.approvalStatus] || '--comment';
218
- const reviewResult = await $`gh pr review ${prNumber} --repo ${repo} ${flag} --body ${body}`.nothrow();
219
-
220
- if (reviewResult.exitCode !== 0) {
221
- const stderr = reviewResult.stderr.toString();
222
- // GitHub doesn't allow self-approval or self-request-changes - fallback to comment
223
- if (stderr.includes('your own pull request')) {
224
- console.log('[i] Self-PR detected, falling back to COMMENT');
225
- await $`gh pr review ${prNumber} --repo ${repo} --comment --body ${body}`;
226
- } else {
227
- throw new Error(`Failed to post review: ${stderr}`);
228
- }
229
- }
230
-
231
- // Post inline comments via REST API
232
- for (const comment of review.comments.slice(0, MAX_INLINE_COMMENTS)) {
233
- try {
234
- const commentBody = formatComment(comment);
235
- // Get the PR head SHA for the comment
236
- const prInfo = await $`gh pr view ${prNumber} --repo ${repo} --json headRefOid --jq .headRefOid`.text();
237
- const commitId = prInfo.trim();
238
-
239
- await $`gh api repos/${repo}/pulls/${prNumber}/comments --method POST \
240
- -f body=${commentBody} \
241
- -f path=${comment.path} \
242
- -F line=${comment.line} \
243
- -f side=RIGHT \
244
- -f commit_id=${commitId}`;
245
- } catch (err) {
246
- console.error(`[!] Failed to post inline comment on ${comment.path}:${comment.line}`, err);
247
- }
248
- }
155
+ // Post review as PR comment
156
+ async function postReview(prNumber: number, repo: string, reviewContent: string): Promise<void> {
157
+ // Use gh pr comment to post the review
158
+ await $`gh pr comment ${prNumber} --repo ${repo} --body ${reviewContent}`;
249
159
  }
250
160
 
251
161
  // Check if already reviewed this PR (avoid spam)
252
162
  async function hasRecentReview(prNumber: number, repo: string): Promise<boolean> {
253
163
  try {
254
- const reviews =
255
- await $`gh api repos/${repo}/pulls/${prNumber}/reviews --jq '[.[] | select(.body | contains("AI Code Review"))] | length'`.text();
256
- return parseInt(reviews.trim(), 10) > 0;
164
+ const comments =
165
+ await $`gh api repos/${repo}/issues/${prNumber}/comments --jq '[.[] | select(.body | contains("🔍 Code Review"))] | length'`.text();
166
+ return parseInt(comments.trim(), 10) > 0;
257
167
  } catch {
258
168
  return false;
259
169
  }
@@ -291,13 +201,12 @@ async function main() {
291
201
 
292
202
  // 2. Call Claude
293
203
  console.log(`[i] Calling Claude (${MODEL}) for review...`);
294
- const review = await callClaude(context);
295
- console.log(`[i] Review complete: ${review.approvalStatus}`);
296
- console.log(`[i] Comments: ${review.comments.length}, Security issues: ${review.securityIssues.length}`);
204
+ const reviewContent = await callClaude(context, repo);
205
+ console.log('[i] Review generated');
297
206
 
298
- // 3. Post review
207
+ // 3. Post review as comment
299
208
  console.log('[i] Posting review to PR...');
300
- await postReview(prNumber, repo, review);
209
+ await postReview(prNumber, repo, reviewContent);
301
210
  console.log('[OK] Review posted successfully');
302
211
  } catch (error) {
303
212
  console.error('[X] Review failed:', error);