coderev-cli 1.0.23 → 1.0.25
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/README.md +185 -7
- package/package.json +2 -2
- package/src/bin.js +3 -0
- package/src/cli.js +118 -0
- package/src/models.js +160 -0
- package/src/models.test.js +80 -0
- package/src/reviewer.js +68 -15
package/README.md
CHANGED
|
@@ -27,9 +27,13 @@
|
|
|
27
27
|
- [coderev config(配置管理)](#coderev-config配置管理)
|
|
28
28
|
- [coderev init(初始化)](#coderev-init初始化)
|
|
29
29
|
- [coderev serve(GitHub App 自动审查)](#coderev-servegithub-app-自动审查)
|
|
30
|
+
- [coderev models(模型模板)🆕](#coderev-models模型模板)
|
|
31
|
+
- [coderev setup(模型配置)🆕](#coderev-setup模型配置)
|
|
32
|
+
- [coderev rules(规则市场)🆕](#coderev-rules规则市场)
|
|
30
33
|
- [配置详解](#配置详解)
|
|
31
34
|
- [平台集成](#平台集成)
|
|
32
35
|
- [CI/CD 集成](#cicd-集成)
|
|
36
|
+
- [VS Code 扩展 🆕](#vs-code-扩展)
|
|
33
37
|
- [FAQ / 常见问题](#faq--常见问题)
|
|
34
38
|
|
|
35
39
|
---
|
|
@@ -94,19 +98,25 @@ coderev init
|
|
|
94
98
|
|
|
95
99
|
会在当前目录创建 `.coderevrc.json` 配置文件。
|
|
96
100
|
|
|
97
|
-
### 第 2
|
|
101
|
+
### 第 2 步:设置模型和 API Key
|
|
98
102
|
|
|
99
|
-
coderev
|
|
103
|
+
coderev 内置了 11 个热门模型模板,只需选择模板 + 设置 API Key 即可:
|
|
100
104
|
|
|
101
105
|
```bash
|
|
102
|
-
#
|
|
103
|
-
|
|
106
|
+
# 查看所有可用模型
|
|
107
|
+
coderev models
|
|
104
108
|
|
|
105
|
-
#
|
|
106
|
-
|
|
109
|
+
# 选择 DeepSeek(推荐,性价比最高)
|
|
110
|
+
coderev setup --model deepseek
|
|
111
|
+
|
|
112
|
+
# 设置 API Key
|
|
113
|
+
# Windows PowerShell:
|
|
114
|
+
$env:DEEPSEEK_API_KEY="***"
|
|
115
|
+
# Linux / macOS:
|
|
116
|
+
export DEEPSEEK_API_KEY="***"
|
|
107
117
|
```
|
|
108
118
|
|
|
109
|
-
|
|
119
|
+
支持所有主流 AI 提供商(见 [coderev models](#coderev-models模型模板) 章节)。
|
|
110
120
|
|
|
111
121
|
### 第 3 步:运行审查
|
|
112
122
|
|
|
@@ -481,6 +491,125 @@ GITHUB_APP_ID=123456 GITHUB_APP_WEBHOOK_SECRET=mysecret coderev serve
|
|
|
481
491
|
|
|
482
492
|
---
|
|
483
493
|
|
|
494
|
+
### coderev models(模型模板)🆕 v1.0.24
|
|
495
|
+
|
|
496
|
+
**作用**:列出所有内置的 AI 模型模板及其配置。只需选择一个模板并设置对应的 API Key 即可使用。
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
coderev models
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**内置模板清单**:
|
|
503
|
+
|
|
504
|
+
| 模板 | 提供商 | 默认模型 | API Key 环境变量 | 说明 |
|
|
505
|
+
|------|--------|---------|-----------------|------|
|
|
506
|
+
| ⭐ `deepseek` | DeepSeek | deepseek-chat | `DEEPSEEK_API_KEY` | 国产高性价比,¥1/百万token |
|
|
507
|
+
| 🧠 `deepseek-r1` | DeepSeek | deepseek-reasoner | `DEEPSEEK_API_KEY` | 推理增强,适合复杂漏洞分析 |
|
|
508
|
+
| `openai` | OpenAI | gpt-4o | `OPENAI_API_KEY` | GPT-4o 多模态旗舰 |
|
|
509
|
+
| 🧠 `openai-o3` | OpenAI | o3-mini | `OPENAI_API_KEY` | 推理型,速度快 |
|
|
510
|
+
| `qwen` | 阿里云 | qwen-plus | `DASHSCOPE_API_KEY` | 中文能力强,¥0.8/百万token |
|
|
511
|
+
| ⭐ `qwen-coder` | 阿里云 | qwen-coder-plus | `DASHSCOPE_API_KEY` | 代码专精,¥2/百万token |
|
|
512
|
+
| `claude` | Anthropic | claude-sonnet-4-20250514 | `ANTHROPIC_API_KEY` | 代码理解深度最强 |
|
|
513
|
+
| `gemini` | Google | gemini-2.5-pro | `GEMINI_API_KEY` | 100万token上下文 |
|
|
514
|
+
| `zhipu` | 智谱 AI | glm-4-plus | `ZHIPU_API_KEY` | 国产强推理 |
|
|
515
|
+
| `moonshot` | 月之暗面 | moonshot-v1-8k | `MOONSHOT_API_KEY` | 长文本处理强 |
|
|
516
|
+
| `codestral` | Mistral | codestral-latest | `MISTRAL_API_KEY` | 专注代码生成与审查 |
|
|
517
|
+
|
|
518
|
+
> ⭐ = 推荐 | 🧠 = 推理增强型
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
### coderev setup(模型配置)🆕 v1.0.24
|
|
523
|
+
|
|
524
|
+
**作用**:一键配置 AI 模型,支持主模型、从模型(fallback)、以及不同 Agent 使用不同模型。
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
coderev setup [选项]
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**参数**:
|
|
531
|
+
|
|
532
|
+
| 参数 | 说明 | 示例 |
|
|
533
|
+
|------|------|------|
|
|
534
|
+
| `--model <模板名>` | 设置主模型 | `coderev setup --model deepseek` |
|
|
535
|
+
| `--fallback <模板名>` | 设置备用模型(主模型失败时自动切换) | `coderev setup --model deepseek --fallback qwen` |
|
|
536
|
+
| `--agent-security <模板名>` | 安全 Agent 专用模型 | `coderev setup --agent-security deepseek-r1` |
|
|
537
|
+
| `--agent-bugs <模板名>` | 缺陷检测 Agent 专用模型 | `coderev setup --agent-bugs qwen-coder` |
|
|
538
|
+
| `--agent-quality <模板名>` | 质量检查 Agent 专用模型 | `coderev setup --agent-quality gemini` |
|
|
539
|
+
| (无参数) | 查看当前配置 | `coderev setup` |
|
|
540
|
+
|
|
541
|
+
**使用示例**:
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
# 最简:选择 DeepSeek
|
|
545
|
+
coderev setup --model deepseek
|
|
546
|
+
|
|
547
|
+
# 主从模型:DeepSeek 挂了自动切到千问
|
|
548
|
+
coderev setup --model deepseek --fallback qwen
|
|
549
|
+
|
|
550
|
+
# 不同 Agent 不同模型
|
|
551
|
+
coderev setup --model deepseek --agent-security deepseek-r1 --agent-quality qwen
|
|
552
|
+
|
|
553
|
+
# 查看当前配置
|
|
554
|
+
coderev setup
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**主从模型回退**:主模型调用失败(超时/网络/API 错误)时,自动切到从模型,控制台打印 `↩ Falling back to xxx`。
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
### coderev rules(规则市场)🆕 v1.0.23
|
|
562
|
+
|
|
563
|
+
**作用**:访问 coderev SaaS 规则市场,搜索、安装、发布团队共享的审查规则包。
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
coderev rules <action> [选项]
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**子命令**:
|
|
570
|
+
|
|
571
|
+
| 动作 | 说明 | 示例 |
|
|
572
|
+
|------|------|------|
|
|
573
|
+
| `search [query]` | 搜索规则市场 | `coderev rules search react` |
|
|
574
|
+
| `install <name>` | 安装规则包 | `coderev rules install react-security` |
|
|
575
|
+
| `publish` | 发布本地规则到市场 | `coderev rules publish --name my-rules` |
|
|
576
|
+
| `list` | 查看已安装的规则包 | `coderev rules list` |
|
|
577
|
+
| `uninstall <name>` | 卸载规则包 | `coderev rules uninstall react-security` |
|
|
578
|
+
| `info <name>` | 查看规则包详情 | `coderev rules info react-security` |
|
|
579
|
+
|
|
580
|
+
**参数**:
|
|
581
|
+
|
|
582
|
+
| 参数 | 说明 |
|
|
583
|
+
|------|------|
|
|
584
|
+
| `-q, --query <text>` | 搜索查询 |
|
|
585
|
+
| `-n, --name <name>` | 规则包名称 |
|
|
586
|
+
| `--version <ver>` | 发布版本号(默认 1.0.0) |
|
|
587
|
+
| `--desc <text>` | 发布描述 |
|
|
588
|
+
| `--api-url <url>` | 自定义市场 API 地址 |
|
|
589
|
+
|
|
590
|
+
**使用示例**:
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# 搜索规则
|
|
594
|
+
coderev rules search react
|
|
595
|
+
|
|
596
|
+
# 安装规则包(自动合并到 .coderevrc.json)
|
|
597
|
+
coderev rules install react-security
|
|
598
|
+
|
|
599
|
+
# 查看已安装
|
|
600
|
+
coderev rules list
|
|
601
|
+
|
|
602
|
+
# 发布当地规则
|
|
603
|
+
coderev rules publish --name team-js-rules --desc "团队JS规范"
|
|
604
|
+
|
|
605
|
+
# 卸载
|
|
606
|
+
coderev rules uninstall react-security
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
安装后规则写入 `.coderevrc.json` 的 `rules.custom` 数组,带 `_source` 标记。
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
484
613
|
## 配置详解
|
|
485
614
|
|
|
486
615
|
### 配置加载顺序
|
|
@@ -815,6 +944,55 @@ git diff origin/main...HEAD | coderev review --ci --min-confidence 70
|
|
|
815
944
|
|
|
816
945
|
---
|
|
817
946
|
|
|
947
|
+
有关 GitHub Actions、GitLab CI、Jenkins 的 CI/CD 集成说明及模板,见 [docs/ci-cd.md](docs/ci-cd.md)。
|
|
948
|
+
|
|
949
|
+
一键生成对应 CI 模板:
|
|
950
|
+
|
|
951
|
+
```bash
|
|
952
|
+
# GitHub Actions 模板
|
|
953
|
+
coderev init --github-action
|
|
954
|
+
# → 生成 .github/workflows/coderev.yml
|
|
955
|
+
|
|
956
|
+
# GitLab CI 模板
|
|
957
|
+
coderev init --gitlab-ci
|
|
958
|
+
# → 生成 .gitlab-ci.yml
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
---
|
|
962
|
+
|
|
963
|
+
## VS Code 扩展 🆕 v1.0.22
|
|
964
|
+
|
|
965
|
+
coderev 自带 VS Code 扩展,安装后在编码时实时审查代码。
|
|
966
|
+
|
|
967
|
+
### 安装
|
|
968
|
+
|
|
969
|
+
```bash
|
|
970
|
+
# 从本地安装(源码已包含在 vscode/ 目录中)
|
|
971
|
+
cd vscode && npm install && npm run package
|
|
972
|
+
# 在 VS Code 中:Ctrl+Shift+P → Extensions: Install from VSIX...
|
|
973
|
+
# 选择 vscode/coderev-*.vsix
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
> 暂未上架 VS Code Marketplace,通过本地 VSIX 安装。
|
|
977
|
+
|
|
978
|
+
### 功能
|
|
979
|
+
|
|
980
|
+
| 功能 | 说明 |
|
|
981
|
+
|------|------|
|
|
982
|
+
| 工作区审查 | 右键项目目录 → `coderev: Review Workspace` |
|
|
983
|
+
| 单文件审查 | 打开文件 → `Ctrl+Shift+P` → `coderev: Review File` |
|
|
984
|
+
| 自动修复 | 审查发现问题后 → 一键调用 AI 修复 |
|
|
985
|
+
| 保存即审查 | 开启 `coderev.lintOnSave` → 每次保存文件自动审查 |
|
|
986
|
+
| Problems 面板集成 | 问题自动输出到 VS Code Problems 面板,点击跳转 |
|
|
987
|
+
|
|
988
|
+
### 扩展设置
|
|
989
|
+
|
|
990
|
+
- `coderev.enabled` — 是否启用自动检查(默认 true)
|
|
991
|
+
- `coderev.lintOnSave` — 保存时审查(默认 false)
|
|
992
|
+
- `coderev.minConfidence` — 最低置信度阈值(默认 60)
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
818
996
|
## FAQ / 常见问题
|
|
819
997
|
|
|
820
998
|
### Q:为什么审查结果为空?
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderev-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "Multi-agent AI code review for git -- parallel agents with confidence scoring",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"coderev": "src/
|
|
7
|
+
"coderev": "src/bin.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/cli.js",
|
package/src/bin.js
ADDED
package/src/cli.js
CHANGED
|
@@ -848,6 +848,124 @@ program
|
|
|
848
848
|
}
|
|
849
849
|
});
|
|
850
850
|
|
|
851
|
+
// ── Models ────────────────────────────────────────────────────────
|
|
852
|
+
program
|
|
853
|
+
.command('models')
|
|
854
|
+
.description('List available AI model templates')
|
|
855
|
+
.option('--json', 'Output as JSON')
|
|
856
|
+
.action((options) => {
|
|
857
|
+
const { listTemplates } = require('./models');
|
|
858
|
+
const templates = listTemplates();
|
|
859
|
+
|
|
860
|
+
if (options.json) {
|
|
861
|
+
console.log(JSON.stringify(templates, null, 2));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const tierIcons = { recommended: '⭐', reasoning: '🧠', standard: ' ' };
|
|
866
|
+
console.log(chalk.bold('\n📋 Available Model Templates\n'));
|
|
867
|
+
console.log(chalk.gray(' Usage: coderev setup --model <name> (set primary model)'));
|
|
868
|
+
console.log(chalk.gray(' coderev setup --model <name> --fallback <name> (set primary + fallback)'));
|
|
869
|
+
console.log(chalk.gray(' coderev setup --agent-security <name> --agent-quality <name>'));
|
|
870
|
+
console.log('');
|
|
871
|
+
|
|
872
|
+
for (const t of templates) {
|
|
873
|
+
const icon = tierIcons[t.tier] || ' ';
|
|
874
|
+
const providerTag = chalk.gray(`[${t.provider}]`);
|
|
875
|
+
console.log(` ${icon} ${chalk.green(t.name)} ${providerTag}`);
|
|
876
|
+
console.log(` ${t.desc}`);
|
|
877
|
+
console.log(chalk.gray(` API Key: ${t.apiKeyEnv}`));
|
|
878
|
+
console.log('');
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// ── Config ────────────────────────────────────────────────────────
|
|
883
|
+
program
|
|
884
|
+
.command('setup')
|
|
885
|
+
.description('Manage coderev model configuration')
|
|
886
|
+
.option('--model <name>', 'Set primary model template')
|
|
887
|
+
.option('--fallback <name>', 'Set fallback model template')
|
|
888
|
+
.option('--agent-security <name>', 'Model for security agent')
|
|
889
|
+
.option('--agent-bugs <name>', 'Model for bug detection agent')
|
|
890
|
+
.option('--agent-quality <name>', 'Model for quality agent')
|
|
891
|
+
.action((options) => {
|
|
892
|
+
const fs = require('fs');
|
|
893
|
+
const { resolveTemplate } = require('./models');
|
|
894
|
+
const configPath = path.join(process.cwd(), '.coderevrc.json');
|
|
895
|
+
|
|
896
|
+
let config = {};
|
|
897
|
+
if (fs.existsSync(configPath)) {
|
|
898
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (!config.ai) config.ai = {};
|
|
902
|
+
|
|
903
|
+
// Set primary model
|
|
904
|
+
if (options.model) {
|
|
905
|
+
try {
|
|
906
|
+
const resolved = resolveTemplate(options.model);
|
|
907
|
+
Object.assign(config.ai, resolved);
|
|
908
|
+
console.log(chalk.green(`✔ Primary model set to "${options.model}" (${resolved.model})`));
|
|
909
|
+
} catch (err) {
|
|
910
|
+
console.error(chalk.red(`✖ ${err.message}`));
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Set fallback model
|
|
916
|
+
if (options.fallback) {
|
|
917
|
+
try {
|
|
918
|
+
const resolved = resolveTemplate(options.fallback);
|
|
919
|
+
config.ai.fallback = {
|
|
920
|
+
enabled: true,
|
|
921
|
+
provider: resolved.provider,
|
|
922
|
+
baseURL: resolved.baseURL,
|
|
923
|
+
model: resolved.model,
|
|
924
|
+
temperature: resolved.temperature,
|
|
925
|
+
maxTokens: resolved.maxTokens,
|
|
926
|
+
};
|
|
927
|
+
console.log(chalk.green(`✔ Fallback model set to "${options.fallback}" (${resolved.model})`));
|
|
928
|
+
} catch (err) {
|
|
929
|
+
console.error(chalk.red(`✖ ${err.message}`));
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Set per-agent models
|
|
935
|
+
if (!config.ai.agents) config.ai.agents = {};
|
|
936
|
+
const agentOpts = [
|
|
937
|
+
['security', options.agentSecurity],
|
|
938
|
+
['bugs', options.agentBugs],
|
|
939
|
+
['quality', options.agentQuality],
|
|
940
|
+
];
|
|
941
|
+
for (const [agent, templateName] of agentOpts) {
|
|
942
|
+
if (templateName) {
|
|
943
|
+
try {
|
|
944
|
+
const resolved = resolveTemplate(templateName);
|
|
945
|
+
config.ai.agents[agent] = {
|
|
946
|
+
provider: resolved.provider,
|
|
947
|
+
baseURL: resolved.baseURL,
|
|
948
|
+
model: resolved.model,
|
|
949
|
+
temperature: resolved.temperature,
|
|
950
|
+
};
|
|
951
|
+
console.log(chalk.green(`✔ ${agent} agent model set to "${templateName}" (${resolved.model})`));
|
|
952
|
+
} catch (err) {
|
|
953
|
+
console.error(chalk.red(`✖ ${err.message}`));
|
|
954
|
+
process.exit(1);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
960
|
+
console.log(chalk.blue(` Config saved to ${configPath}`));
|
|
961
|
+
|
|
962
|
+
if (!options.model && !options.fallback && !options.agentSecurity && !options.agentBugs && !options.agentQuality) {
|
|
963
|
+
// No options: show current config
|
|
964
|
+
console.log(chalk.bold('\n📋 Current coderev config:\n'));
|
|
965
|
+
console.log(JSON.stringify(config, null, 2));
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
|
|
851
969
|
// ── Rules Marketplace ─────────────────────────────────────────────
|
|
852
970
|
program
|
|
853
971
|
.command('rules <action>')
|
package/src/models.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Templates — built-in hot model presets for coderev.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* coderev config --model deepseek # switch to DeepSeek template
|
|
6
|
+
* coderev config --model openai --fallback qwen # primary + fallback
|
|
7
|
+
* coderev config --agent-security deepseek --agent-quality qwen
|
|
8
|
+
* coderev models # list all templates
|
|
9
|
+
*
|
|
10
|
+
* Each template provides provider + baseURL + default model,
|
|
11
|
+
* user only needs to set the corresponding API key env var.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const BUILTIN_TEMPLATES = {
|
|
15
|
+
deepseek: {
|
|
16
|
+
provider: 'deepseek',
|
|
17
|
+
baseURL: 'https://api.deepseek.com',
|
|
18
|
+
model: 'deepseek-chat',
|
|
19
|
+
apiKeyEnv: 'DEEPSEEK_API_KEY',
|
|
20
|
+
desc: 'DeepSeek V3 — 国产高性价比,¥1/百万token',
|
|
21
|
+
tier: 'recommended',
|
|
22
|
+
},
|
|
23
|
+
'deepseek-r1': {
|
|
24
|
+
provider: 'deepseek',
|
|
25
|
+
baseURL: 'https://api.deepseek.com',
|
|
26
|
+
model: 'deepseek-reasoner',
|
|
27
|
+
apiKeyEnv: 'DEEPSEEK_API_KEY',
|
|
28
|
+
desc: 'DeepSeek R1 — 推理增强,适合复杂漏洞分析',
|
|
29
|
+
tier: 'reasoning',
|
|
30
|
+
},
|
|
31
|
+
openai: {
|
|
32
|
+
provider: 'openai',
|
|
33
|
+
baseURL: 'https://api.openai.com/v1',
|
|
34
|
+
model: 'gpt-4o',
|
|
35
|
+
apiKeyEnv: 'OPENAI_API_KEY',
|
|
36
|
+
desc: 'GPT-4o — OpenAI 多模态旗舰',
|
|
37
|
+
tier: 'standard',
|
|
38
|
+
},
|
|
39
|
+
'openai-o3': {
|
|
40
|
+
provider: 'openai',
|
|
41
|
+
baseURL: 'https://api.openai.com/v1',
|
|
42
|
+
model: 'o3-mini',
|
|
43
|
+
apiKeyEnv: 'OPENAI_API_KEY',
|
|
44
|
+
desc: 'OpenAI o3-mini — 推理型,速度快',
|
|
45
|
+
tier: 'reasoning',
|
|
46
|
+
},
|
|
47
|
+
qwen: {
|
|
48
|
+
provider: 'openai',
|
|
49
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
50
|
+
model: 'qwen-plus',
|
|
51
|
+
apiKeyEnv: 'DASHSCOPE_API_KEY',
|
|
52
|
+
desc: '通义千问 Qwen-Plus — 中文能力强,¥0.8/百万token',
|
|
53
|
+
tier: 'standard',
|
|
54
|
+
},
|
|
55
|
+
'qwen-coder': {
|
|
56
|
+
provider: 'openai',
|
|
57
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
58
|
+
model: 'qwen-coder-plus',
|
|
59
|
+
apiKeyEnv: 'DASHSCOPE_API_KEY',
|
|
60
|
+
desc: '通义千问 Coder — 代码专精,¥2/百万token',
|
|
61
|
+
tier: 'recommended',
|
|
62
|
+
},
|
|
63
|
+
claude: {
|
|
64
|
+
provider: 'openai',
|
|
65
|
+
baseURL: 'https://api.anthropic.com/v1',
|
|
66
|
+
model: 'claude-sonnet-4-20250514',
|
|
67
|
+
apiKeyEnv: 'ANTHROPIC_API_KEY',
|
|
68
|
+
desc: 'Claude Sonnet 4 — 代码理解深度最强',
|
|
69
|
+
tier: 'standard',
|
|
70
|
+
},
|
|
71
|
+
gemini: {
|
|
72
|
+
provider: 'openai',
|
|
73
|
+
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
74
|
+
model: 'gemini-2.5-pro',
|
|
75
|
+
apiKeyEnv: 'GEMINI_API_KEY',
|
|
76
|
+
desc: 'Gemini 2.5 Pro — Google,100万token上下文',
|
|
77
|
+
tier: 'standard',
|
|
78
|
+
},
|
|
79
|
+
zhipu: {
|
|
80
|
+
provider: 'openai',
|
|
81
|
+
baseURL: 'https://open.bigmodel.cn/api/paas/v4',
|
|
82
|
+
model: 'glm-4-plus',
|
|
83
|
+
apiKeyEnv: 'ZHIPU_API_KEY',
|
|
84
|
+
desc: '智谱 GLM-4-Plus — 国产强推理',
|
|
85
|
+
tier: 'standard',
|
|
86
|
+
},
|
|
87
|
+
moonshot: {
|
|
88
|
+
provider: 'openai',
|
|
89
|
+
baseURL: 'https://api.moonshot.cn/v1',
|
|
90
|
+
model: 'moonshot-v1-8k',
|
|
91
|
+
apiKeyEnv: 'MOONSHOT_API_KEY',
|
|
92
|
+
desc: '月之暗面 Kimi — 长文本处理强',
|
|
93
|
+
tier: 'standard',
|
|
94
|
+
},
|
|
95
|
+
codestral: {
|
|
96
|
+
provider: 'openai',
|
|
97
|
+
baseURL: 'https://api.mistral.ai/v1',
|
|
98
|
+
model: 'codestral-latest',
|
|
99
|
+
apiKeyEnv: 'MISTRAL_API_KEY',
|
|
100
|
+
desc: 'Mistral Codestral — 专注代码生成与审查',
|
|
101
|
+
tier: 'standard',
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a model template by name, with optional user overrides.
|
|
107
|
+
* Returns a full ai config block ready for use.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} templateName - Template name (e.g. 'deepseek', 'qwen')
|
|
110
|
+
* @param {object} [overrides] - User overrides (model, baseURL, provider, etc.)
|
|
111
|
+
* @returns {object} Resolved ai config
|
|
112
|
+
*/
|
|
113
|
+
function resolveTemplate(templateName, overrides = {}) {
|
|
114
|
+
const template = BUILTIN_TEMPLATES[templateName];
|
|
115
|
+
if (!template) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Unknown model template "${templateName}". ` +
|
|
118
|
+
`Available: ${Object.keys(BUILTIN_TEMPLATES).join(', ')}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
provider: overrides.provider || template.provider,
|
|
124
|
+
baseURL: overrides.baseURL || template.baseURL,
|
|
125
|
+
model: overrides.model || template.model,
|
|
126
|
+
apiKeyEnv: overrides.apiKeyEnv || template.apiKeyEnv,
|
|
127
|
+
temperature: overrides.temperature ?? 0.3,
|
|
128
|
+
maxTokens: overrides.maxTokens || 4096,
|
|
129
|
+
_template: templateName,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* List all built-in templates with key info.
|
|
135
|
+
* @returns {Array<{name: string, provider: string, model: string, apiKeyEnv: string, desc: string, tier: string}>}
|
|
136
|
+
*/
|
|
137
|
+
function listTemplates() {
|
|
138
|
+
return Object.entries(BUILTIN_TEMPLATES).map(([name, t]) => ({
|
|
139
|
+
name,
|
|
140
|
+
provider: t.provider,
|
|
141
|
+
model: t.model,
|
|
142
|
+
apiKeyEnv: t.apiKeyEnv,
|
|
143
|
+
desc: t.desc,
|
|
144
|
+
tier: t.tier,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get a single template by name. Returns null if not found.
|
|
150
|
+
*/
|
|
151
|
+
function getTemplate(name) {
|
|
152
|
+
return BUILTIN_TEMPLATES[name] || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
BUILTIN_TEMPLATES,
|
|
157
|
+
resolveTemplate,
|
|
158
|
+
listTemplates,
|
|
159
|
+
getTemplate,
|
|
160
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const { BUILTIN_TEMPLATES, resolveTemplate, listTemplates, getTemplate } = require('./models');
|
|
4
|
+
|
|
5
|
+
describe('models.js', () => {
|
|
6
|
+
it('should have all 11 built-in templates', () => {
|
|
7
|
+
const templates = listTemplates();
|
|
8
|
+
assert.strictEqual(templates.length, 11);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should resolve deepseek template correctly', () => {
|
|
12
|
+
const result = resolveTemplate('deepseek');
|
|
13
|
+
assert.strictEqual(result.provider, 'deepseek');
|
|
14
|
+
assert.strictEqual(result.model, 'deepseek-chat');
|
|
15
|
+
assert.strictEqual(result.baseURL, 'https://api.deepseek.com');
|
|
16
|
+
assert.strictEqual(result.apiKeyEnv, 'DEEPSEEK_API_KEY');
|
|
17
|
+
assert.strictEqual(result._template, 'deepseek');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should resolve qwen template with openai-compatible provider', () => {
|
|
21
|
+
const result = resolveTemplate('qwen');
|
|
22
|
+
assert.strictEqual(result.provider, 'openai');
|
|
23
|
+
assert.strictEqual(result.model, 'qwen-plus');
|
|
24
|
+
assert.ok(result.baseURL.includes('dashscope'));
|
|
25
|
+
assert.strictEqual(result.apiKeyEnv, 'DASHSCOPE_API_KEY');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should apply user overrides on template', () => {
|
|
29
|
+
const result = resolveTemplate('openai', {
|
|
30
|
+
model: 'gpt-4o-mini',
|
|
31
|
+
temperature: 0.1,
|
|
32
|
+
});
|
|
33
|
+
assert.strictEqual(result.provider, 'openai');
|
|
34
|
+
assert.strictEqual(result.model, 'gpt-4o-mini');
|
|
35
|
+
assert.strictEqual(result.temperature, 0.1);
|
|
36
|
+
assert.strictEqual(result._template, 'openai');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should throw on unknown template', () => {
|
|
40
|
+
assert.throws(() => resolveTemplate('nonexistent'), {
|
|
41
|
+
message: /Unknown model template/,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('getTemplate should return null for unknown', () => {
|
|
46
|
+
assert.strictEqual(getTemplate('nonexistent'), null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('getTemplate should return object for known', () => {
|
|
50
|
+
const t = getTemplate('deepseek');
|
|
51
|
+
assert.ok(t);
|
|
52
|
+
assert.strictEqual(t.model, 'deepseek-chat');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('listTemplates should return array with required fields', () => {
|
|
56
|
+
const templates = listTemplates();
|
|
57
|
+
for (const t of templates) {
|
|
58
|
+
assert.ok(t.name, `template should have name: ${JSON.stringify(t)}`);
|
|
59
|
+
assert.ok(t.provider, `template should have provider: ${JSON.stringify(t)}`);
|
|
60
|
+
assert.ok(t.model, `template should have model: ${JSON.stringify(t)}`);
|
|
61
|
+
assert.ok(t.apiKeyEnv, `template should have apiKeyEnv: ${JSON.stringify(t)}`);
|
|
62
|
+
assert.ok(t.desc, `template should have desc: ${JSON.stringify(t)}`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should have recommended and reasoning tiers', () => {
|
|
67
|
+
const templates = listTemplates();
|
|
68
|
+
const recommended = templates.filter(t => t.tier === 'recommended');
|
|
69
|
+
const reasoning = templates.filter(t => t.tier === 'reasoning');
|
|
70
|
+
assert.ok(recommended.length >= 2, 'should have at least 2 recommended templates');
|
|
71
|
+
assert.ok(reasoning.length >= 2, 'should have at least 2 reasoning templates');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('deepseek-r1 should be a reasoning model', () => {
|
|
75
|
+
const t = getTemplate('deepseek-r1');
|
|
76
|
+
assert.ok(t);
|
|
77
|
+
assert.strictEqual(t.tier, 'reasoning');
|
|
78
|
+
assert.strictEqual(t.model, 'deepseek-reasoner');
|
|
79
|
+
});
|
|
80
|
+
});
|
package/src/reviewer.js
CHANGED
|
@@ -186,7 +186,8 @@ async function runParallelAgents(apiKey, config, prompts) {
|
|
|
186
186
|
|
|
187
187
|
const tasks = prompts.map(async (p) => {
|
|
188
188
|
try {
|
|
189
|
-
|
|
189
|
+
// Pass agent name so callAI can select per-agent model config
|
|
190
|
+
const text = await callAI(apiKey, p.messages, config, p.role.name);
|
|
190
191
|
const parsed = parseReviewResponse(text);
|
|
191
192
|
return { name: p.role.name, success: true, ...parsed };
|
|
192
193
|
} catch (err) {
|
|
@@ -440,32 +441,84 @@ Important:
|
|
|
440
441
|
}
|
|
441
442
|
|
|
442
443
|
/**
|
|
443
|
-
* Call the AI provider
|
|
444
|
+
* Call the AI provider with support for:
|
|
445
|
+
* - Per-agent model selection (ai.agents[agentType])
|
|
446
|
+
* - Primary/fallback model switching (ai.fallback)
|
|
447
|
+
*
|
|
448
|
+
* @param {string} apiKey - API key
|
|
449
|
+
* @param {Array} messages - Chat messages
|
|
450
|
+
* @param {object} config - Full config object
|
|
451
|
+
* @param {string} [agentType] - Agent type for per-agent model selection ('security'|'bugs'|'quality')
|
|
452
|
+
* @returns {Promise<string>} AI response text
|
|
444
453
|
*/
|
|
445
|
-
async function callAI(apiKey, messages, config) {
|
|
454
|
+
async function callAI(apiKey, messages, config, agentType) {
|
|
446
455
|
const aiConfig = config.ai || {};
|
|
447
|
-
const provider = aiConfig.provider || 'openai';
|
|
448
456
|
|
|
449
|
-
|
|
457
|
+
// Resolve model config for this agent
|
|
458
|
+
let modelConfig = { ...aiConfig };
|
|
459
|
+
if (agentType && aiConfig.agents && aiConfig.agents[agentType]) {
|
|
460
|
+
const agentCfg = aiConfig.agents[agentType];
|
|
461
|
+
modelConfig = { ...aiConfig, ...agentCfg };
|
|
462
|
+
}
|
|
450
463
|
|
|
451
|
-
|
|
452
|
-
|
|
464
|
+
const provider = modelConfig.provider || 'openai';
|
|
465
|
+
|
|
466
|
+
// Determine base URL
|
|
467
|
+
let baseURL = modelConfig.baseURL;
|
|
453
468
|
if (!baseURL) {
|
|
454
469
|
if (provider === 'deepseek') baseURL = 'https://api.deepseek.com';
|
|
455
|
-
// openai and all others default to undefined (official endpoint)
|
|
456
470
|
}
|
|
457
471
|
|
|
458
|
-
const defaultModels = {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
472
|
+
const defaultModels = { openai: 'gpt-4o', deepseek: 'deepseek-chat' };
|
|
473
|
+
const model = modelConfig.model || defaultModels[provider] || provider;
|
|
474
|
+
|
|
475
|
+
// Try primary model first
|
|
476
|
+
try {
|
|
477
|
+
return await _doCallAI(apiKey, messages, {
|
|
478
|
+
model,
|
|
479
|
+
baseURL,
|
|
480
|
+
temperature: modelConfig.temperature ?? 0.3,
|
|
481
|
+
maxTokens: modelConfig.maxTokens || 4096,
|
|
482
|
+
});
|
|
483
|
+
} catch (err) {
|
|
484
|
+
const fallback = aiConfig.fallback;
|
|
485
|
+
const fallbackEnabled = fallback && fallback.enabled !== false;
|
|
486
|
+
|
|
487
|
+
if (!fallbackEnabled || !fallback.model) {
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
462
490
|
|
|
491
|
+
// Log fallback attempt (visible in --debug mode)
|
|
492
|
+
const chalk = require('chalk');
|
|
493
|
+
console.error(chalk.yellow(`⚠️ Primary model failed: ${err.message}`));
|
|
494
|
+
console.error(chalk.blue(`↩ Falling back to ${fallback.model}...`));
|
|
495
|
+
|
|
496
|
+
const fbProvider = fallback.provider || provider;
|
|
497
|
+
let fbBaseURL = fallback.baseURL;
|
|
498
|
+
if (!fbBaseURL) {
|
|
499
|
+
if (fbProvider === 'deepseek') fbBaseURL = 'https://api.deepseek.com';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return await _doCallAI(apiKey, messages, {
|
|
503
|
+
model: fallback.model,
|
|
504
|
+
baseURL: fbBaseURL,
|
|
505
|
+
temperature: fallback.temperature ?? 0.3,
|
|
506
|
+
maxTokens: fallback.maxTokens || 4096,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Internal: make a single AI call.
|
|
513
|
+
*/
|
|
514
|
+
async function _doCallAI(apiKey, messages, { model, baseURL, temperature, maxTokens }) {
|
|
515
|
+
const OpenAI = require('openai');
|
|
463
516
|
const client = new OpenAI({ apiKey, baseURL: baseURL || undefined });
|
|
464
517
|
|
|
465
518
|
const response = await client.chat.completions.create({
|
|
466
|
-
model
|
|
467
|
-
temperature
|
|
468
|
-
max_tokens:
|
|
519
|
+
model,
|
|
520
|
+
temperature,
|
|
521
|
+
max_tokens: maxTokens,
|
|
469
522
|
messages,
|
|
470
523
|
});
|
|
471
524
|
|