coderev-cli 1.0.21 → 1.0.23
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 +4 -0
- package/package.json +1 -1
- package/src/cli.js +171 -0
- package/src/rules-market.js +257 -0
- package/templates/github-action.yml +143 -0
package/README.md
CHANGED
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -640,6 +640,7 @@ program
|
|
|
640
640
|
.command('init')
|
|
641
641
|
.description('Create a default coderev config file')
|
|
642
642
|
.option('--gitlab-ci', 'Generate a .gitlab-ci.yml template for GitLab CI/CD review')
|
|
643
|
+
.option('--github-action', 'Generate a GitHub Actions workflow for PR review')
|
|
643
644
|
.action((options) => {
|
|
644
645
|
const fs = require('fs');
|
|
645
646
|
const path = require('path');
|
|
@@ -766,6 +767,64 @@ coderev-review:
|
|
|
766
767
|
console.log(chalk.blue(' 3. (Optional) Add GITLAB_TOKEN with api scope for MR comments'));
|
|
767
768
|
console.log(chalk.blue(' 4. Push to GitLab — coderev runs on every MR!'));
|
|
768
769
|
}
|
|
770
|
+
|
|
771
|
+
// --github-action: generate GitHub Actions workflow
|
|
772
|
+
if (options.githubAction) {
|
|
773
|
+
const gaDir = path.join(process.cwd(), '.github', 'workflows');
|
|
774
|
+
const gaPath = path.join(gaDir, 'coderev.yml');
|
|
775
|
+
if (fs.existsSync(gaPath)) {
|
|
776
|
+
console.log(chalk.yellow(`⚠ ${gaPath} already exists. Skipping.`));
|
|
777
|
+
} else {
|
|
778
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'github-action.yml');
|
|
779
|
+
if (fs.existsSync(templatePath)) {
|
|
780
|
+
fs.mkdirSync(gaDir, { recursive: true });
|
|
781
|
+
fs.copyFileSync(templatePath, gaPath);
|
|
782
|
+
console.log(chalk.green(`✔ GitHub Actions workflow created at ${gaPath}`));
|
|
783
|
+
} else {
|
|
784
|
+
// Fallback: write minimal workflow
|
|
785
|
+
const gaContent = `name: coderev AI Code Review
|
|
786
|
+
|
|
787
|
+
on:
|
|
788
|
+
pull_request:
|
|
789
|
+
types: [opened, synchronize, reopened]
|
|
790
|
+
|
|
791
|
+
permissions:
|
|
792
|
+
contents: read
|
|
793
|
+
pull-requests: write
|
|
794
|
+
|
|
795
|
+
jobs:
|
|
796
|
+
coderev:
|
|
797
|
+
runs-on: ubuntu-latest
|
|
798
|
+
timeout-minutes: 10
|
|
799
|
+
steps:
|
|
800
|
+
- uses: actions/checkout@v4
|
|
801
|
+
with:
|
|
802
|
+
fetch-depth: 0
|
|
803
|
+
- uses: actions/setup-node@v4
|
|
804
|
+
with:
|
|
805
|
+
node-version: 20
|
|
806
|
+
- run: npm install -g coderev-cli
|
|
807
|
+
- run: |
|
|
808
|
+
git diff \${{ github.event.pull_request.base.sha }}...\${{ github.event.pull_request.head.sha }} > /tmp/coderev-pr.diff
|
|
809
|
+
- env:
|
|
810
|
+
DEEPSEEK_API_KEY: \${{ secrets.DEEPSEEK_API_KEY }}
|
|
811
|
+
run: |
|
|
812
|
+
cat /tmp/coderev-pr.diff | coderev review --output markdown --ci --min-confidence 60 > /tmp/coderev-report.md
|
|
813
|
+
- uses: marocchino/sticky-pull-request-comment@v2
|
|
814
|
+
with:
|
|
815
|
+
header: coderev-review
|
|
816
|
+
path: /tmp/coderev-report.md
|
|
817
|
+
`;
|
|
818
|
+
fs.mkdirSync(gaDir, { recursive: true });
|
|
819
|
+
fs.writeFileSync(gaPath, gaContent);
|
|
820
|
+
console.log(chalk.green(`✔ GitHub Actions workflow created at ${gaPath}`));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
console.log(chalk.blue(' Next steps:'));
|
|
824
|
+
console.log(chalk.blue(' 1. Go to GitHub → Settings → Secrets and variables → Actions'));
|
|
825
|
+
console.log(chalk.blue(' 2. Add secret DEEPSEEK_API_KEY with your API key'));
|
|
826
|
+
console.log(chalk.blue(' 3. Push to GitHub — coderev reviews every PR!'));
|
|
827
|
+
}
|
|
769
828
|
});
|
|
770
829
|
|
|
771
830
|
// ── Serve (GitHub App) ────────────────────────────────────────────
|
|
@@ -789,6 +848,118 @@ program
|
|
|
789
848
|
}
|
|
790
849
|
});
|
|
791
850
|
|
|
851
|
+
// ── Rules Marketplace ─────────────────────────────────────────────
|
|
852
|
+
program
|
|
853
|
+
.command('rules <action>')
|
|
854
|
+
.description('Manage rule packs from the coderev marketplace')
|
|
855
|
+
.option('-q, --query <text>', 'Search query')
|
|
856
|
+
.option('-n, --name <name>', 'Rule pack name')
|
|
857
|
+
.option('--version <ver>', 'Version for publish', '1.0.0')
|
|
858
|
+
.option('--desc <text>', 'Description for publish')
|
|
859
|
+
.option('--api-url <url>', 'Marketplace API URL')
|
|
860
|
+
.action(async (action, options) => {
|
|
861
|
+
try {
|
|
862
|
+
const { searchRules, installRule, publishRules, listInstalled, uninstallRule, DEFAULT_API_URL } = require('./rules-market');
|
|
863
|
+
const apiUrl = options.apiUrl || process.env.CODEREV_MARKETPLACE_URL || DEFAULT_API_URL;
|
|
864
|
+
|
|
865
|
+
switch (action) {
|
|
866
|
+
case 'search': {
|
|
867
|
+
console.log(chalk.blue(`🔍 Searching marketplace for "${options.query || ''}"...`));
|
|
868
|
+
const results = await searchRules(options.query || '', apiUrl);
|
|
869
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
870
|
+
console.log(chalk.yellow('No rule packs found.'));
|
|
871
|
+
} else {
|
|
872
|
+
console.log(chalk.bold(`\nFound ${results.length} rule pack(s):\n`));
|
|
873
|
+
for (const pack of results) {
|
|
874
|
+
console.log(chalk.green(` 📦 ${pack.name}`) + chalk.gray(` v${pack.version}`));
|
|
875
|
+
console.log(` ${pack.description || '(no description)'}`);
|
|
876
|
+
console.log(chalk.gray(` ${pack.rules || 0} rules • ${pack.downloads || 0} downloads • by ${pack.author || 'unknown'}`));
|
|
877
|
+
console.log('');
|
|
878
|
+
}
|
|
879
|
+
console.log(chalk.blue(`Install: coderev rules install <name>`));
|
|
880
|
+
}
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
case 'install': {
|
|
885
|
+
const name = options.name || options.query;
|
|
886
|
+
if (!name) {
|
|
887
|
+
console.error(chalk.red('✖ Please specify a rule pack name: coderev rules install <name>'));
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
console.log(chalk.blue(`📥 Installing "${name}"...`));
|
|
891
|
+
const result = await installRule(name, apiUrl);
|
|
892
|
+
console.log(chalk.green(`✔ Installed ${result.name} v${result.version}`));
|
|
893
|
+
console.log(chalk.gray(` ${result.added}/${result.total} rules added to .coderevrc.json`));
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
case 'publish': {
|
|
898
|
+
console.log(chalk.blue('📤 Publishing rules to marketplace...'));
|
|
899
|
+
const result = await publishRules(apiUrl, {
|
|
900
|
+
name: options.name,
|
|
901
|
+
version: options.version,
|
|
902
|
+
description: options.desc,
|
|
903
|
+
});
|
|
904
|
+
console.log(chalk.green(`✔ Published "${result.name}" v${options.version} (${result.rules} rules)`));
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
case 'list': {
|
|
909
|
+
const result = listInstalled();
|
|
910
|
+
if (result.packs.length === 0) {
|
|
911
|
+
console.log(chalk.yellow(result.message));
|
|
912
|
+
} else {
|
|
913
|
+
console.log(chalk.bold(`\n📦 Installed rule packs (${result.packs.length}):\n`));
|
|
914
|
+
for (const pack of result.packs) {
|
|
915
|
+
console.log(chalk.green(` 📦 ${pack.name}`) + chalk.gray(` v${pack.version}`));
|
|
916
|
+
console.log(chalk.gray(` ${pack.rules} rules • installed ${new Date(pack.installedAt).toLocaleDateString()}`));
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
case 'uninstall': {
|
|
923
|
+
const name = options.name || options.query;
|
|
924
|
+
if (!name) {
|
|
925
|
+
console.error(chalk.red('✖ Please specify a rule pack name: coderev rules uninstall <name>'));
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}
|
|
928
|
+
const result = uninstallRule(name);
|
|
929
|
+
console.log(chalk.green(`✔ Uninstalled "${result.name}"`));
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
case 'info': {
|
|
934
|
+
const name = options.name || options.query;
|
|
935
|
+
if (!name) {
|
|
936
|
+
console.error(chalk.red('✖ Please specify a rule pack name: coderev rules info <name>'));
|
|
937
|
+
process.exit(1);
|
|
938
|
+
}
|
|
939
|
+
const pack = await apiRequest(apiUrl, `/rules/${encodeURIComponent(name)}`);
|
|
940
|
+
console.log(chalk.bold(`\n📦 ${pack.name}`) + chalk.gray(` v${pack.version}`));
|
|
941
|
+
console.log(` ${pack.description || '(no description)'}`);
|
|
942
|
+
console.log(chalk.gray(` ${pack.rules?.length || 0} rules • by ${pack.author || 'unknown'}`));
|
|
943
|
+
if (pack.rules && pack.rules.length > 0) {
|
|
944
|
+
console.log(chalk.bold('\n Rules:'));
|
|
945
|
+
for (const r of pack.rules) {
|
|
946
|
+
const sev = r.severity === 'error' ? chalk.red(r.severity) : r.severity === 'warning' ? chalk.yellow(r.severity) : chalk.gray(r.severity);
|
|
947
|
+
console.log(` - ${r.name} [${sev}] ${r.message || ''}`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
default:
|
|
954
|
+
console.error(chalk.red(`✖ Unknown action "${action}". Use: search | install | publish | list | uninstall | info`));
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
} catch (err) {
|
|
958
|
+
console.error(chalk.red(`✖ ${err.message}`));
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
792
963
|
program.parse(process.argv);
|
|
793
964
|
|
|
794
965
|
// ── Helpers ───────────────────────────────────────────────────
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules Market — SaaS cloud rule repository for coderev.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* coderev rules search <query> Search the rule marketplace
|
|
6
|
+
* coderev rules install <name> Install a rule pack from the marketplace
|
|
7
|
+
* coderev rules publish Publish local rules to the marketplace
|
|
8
|
+
* coderev rules list List installed rule packs
|
|
9
|
+
*
|
|
10
|
+
* API Base: configurable via .coderevrc.json → marketplace.apiUrl
|
|
11
|
+
* Default: https://rules.coderev.dev/api
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const https = require('https');
|
|
17
|
+
const http = require('http');
|
|
18
|
+
|
|
19
|
+
const DEFAULT_API_URL = 'https://rules.coderev.dev/api';
|
|
20
|
+
const MARKETPLACE_DIR = '.coderev-marketplace';
|
|
21
|
+
const INSTALLED_MANIFEST = 'installed.json';
|
|
22
|
+
|
|
23
|
+
// ── API Client ───────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Make an HTTP request to the marketplace API.
|
|
27
|
+
*/
|
|
28
|
+
function apiRequest(apiUrl, endpoint, method = 'GET', body = null) {
|
|
29
|
+
const url = new URL(`${apiUrl}${endpoint}`);
|
|
30
|
+
const isHttps = url.protocol === 'https:';
|
|
31
|
+
const transport = isHttps ? https : http;
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const options = {
|
|
35
|
+
hostname: url.hostname,
|
|
36
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
37
|
+
path: url.pathname + url.search,
|
|
38
|
+
method,
|
|
39
|
+
headers: {
|
|
40
|
+
'Accept': 'application/json',
|
|
41
|
+
'User-Agent': 'coderev-cli',
|
|
42
|
+
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
43
|
+
},
|
|
44
|
+
timeout: 15000,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const req = transport.request(options, (res) => {
|
|
48
|
+
let data = '';
|
|
49
|
+
res.on('data', (chunk) => (data += chunk));
|
|
50
|
+
res.on('end', () => {
|
|
51
|
+
try {
|
|
52
|
+
const json = JSON.parse(data);
|
|
53
|
+
if (res.statusCode >= 400) {
|
|
54
|
+
reject(new Error(json.error || `API error: ${res.statusCode}`));
|
|
55
|
+
} else {
|
|
56
|
+
resolve(json);
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
reject(new Error(`Invalid API response: ${data.slice(0, 200)}`));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
req.on('timeout', () => {
|
|
65
|
+
req.destroy();
|
|
66
|
+
reject(new Error('API request timed out'));
|
|
67
|
+
});
|
|
68
|
+
req.on('error', (err) => reject(new Error(`API connection failed: ${err.message}`)));
|
|
69
|
+
|
|
70
|
+
if (body) req.write(JSON.stringify(body));
|
|
71
|
+
req.end();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Marketplace Directory ────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function getMarketplaceDir() {
|
|
78
|
+
return path.join(process.cwd(), MARKETPLACE_DIR);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getInstalledManifest() {
|
|
82
|
+
const dir = getMarketplaceDir();
|
|
83
|
+
const manifestPath = path.join(dir, INSTALLED_MANIFEST);
|
|
84
|
+
if (!fs.existsSync(manifestPath)) return { packs: [] };
|
|
85
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function saveInstalledManifest(manifest) {
|
|
89
|
+
const dir = getMarketplaceDir();
|
|
90
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
fs.writeFileSync(path.join(dir, INSTALLED_MANIFEST), JSON.stringify(manifest, null, 2));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Search ───────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
async function searchRules(query, apiUrl) {
|
|
97
|
+
const endpoint = query
|
|
98
|
+
? `/rules?q=${encodeURIComponent(query)}`
|
|
99
|
+
: '/rules';
|
|
100
|
+
const result = await apiRequest(apiUrl, endpoint);
|
|
101
|
+
return result.rules || result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Install ──────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
async function installRule(packName, apiUrl) {
|
|
107
|
+
// Fetch rule pack from marketplace
|
|
108
|
+
const pack = await apiRequest(apiUrl, `/rules/${encodeURIComponent(packName)}`);
|
|
109
|
+
|
|
110
|
+
if (!pack || !pack.rules) {
|
|
111
|
+
throw new Error(`Rule pack "${packName}" not found or empty`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Merge into local .coderevrc.json
|
|
115
|
+
const configPath = path.join(process.cwd(), '.coderevrc.json');
|
|
116
|
+
let config = {};
|
|
117
|
+
if (fs.existsSync(configPath)) {
|
|
118
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Ensure custom rules array exists
|
|
122
|
+
if (!config.rules) config.rules = {};
|
|
123
|
+
if (!config.rules.custom) config.rules.custom = [];
|
|
124
|
+
|
|
125
|
+
// Add pack rules (skip duplicates by name)
|
|
126
|
+
let added = 0;
|
|
127
|
+
for (const rule of pack.rules) {
|
|
128
|
+
const exists = config.rules.custom.some(r => r.name === rule.name);
|
|
129
|
+
if (!exists) {
|
|
130
|
+
config.rules.custom.push({
|
|
131
|
+
...rule,
|
|
132
|
+
_source: packName,
|
|
133
|
+
_version: pack.version,
|
|
134
|
+
});
|
|
135
|
+
added++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
140
|
+
|
|
141
|
+
// Record installation
|
|
142
|
+
const manifest = getInstalledManifest();
|
|
143
|
+
const existing = manifest.packs.findIndex(p => p.name === packName);
|
|
144
|
+
if (existing >= 0) {
|
|
145
|
+
manifest.packs[existing] = {
|
|
146
|
+
name: packName,
|
|
147
|
+
version: pack.version,
|
|
148
|
+
rules: pack.rules.length,
|
|
149
|
+
installedAt: new Date().toISOString(),
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
manifest.packs.push({
|
|
153
|
+
name: packName,
|
|
154
|
+
version: pack.version,
|
|
155
|
+
rules: pack.rules.length,
|
|
156
|
+
installedAt: new Date().toISOString(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
saveInstalledManifest(manifest);
|
|
160
|
+
|
|
161
|
+
return { name: packName, version: pack.version, added, total: pack.rules.length };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Publish ───────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
async function publishRules(apiUrl, options = {}) {
|
|
167
|
+
const configPath = path.join(process.cwd(), '.coderevrc.json');
|
|
168
|
+
if (!fs.existsSync(configPath)) {
|
|
169
|
+
throw new Error('No .coderevrc.json found. Run `coderev init` first.');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
173
|
+
const rules = config.rules?.custom || [];
|
|
174
|
+
|
|
175
|
+
if (rules.length === 0) {
|
|
176
|
+
throw new Error('No custom rules found in .coderevrc.json');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const packName = options.name || path.basename(process.cwd());
|
|
180
|
+
const payload = {
|
|
181
|
+
name: packName,
|
|
182
|
+
version: options.version || '1.0.0',
|
|
183
|
+
description: options.description || `Rules from ${packName}`,
|
|
184
|
+
rules: rules.map(r => ({
|
|
185
|
+
name: r.name,
|
|
186
|
+
pattern: r.pattern,
|
|
187
|
+
severity: r.severity || 'warning',
|
|
188
|
+
message: r.message,
|
|
189
|
+
filePattern: r.filePattern,
|
|
190
|
+
category: r.category || 'style',
|
|
191
|
+
})),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = await apiRequest(apiUrl, '/rules', 'POST', payload);
|
|
195
|
+
|
|
196
|
+
return { name: packName, version: payload.version, rules: rules.length, published: true };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── List Installed ───────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
function listInstalled() {
|
|
202
|
+
const manifest = getInstalledManifest();
|
|
203
|
+
|
|
204
|
+
if (manifest.packs.length === 0) {
|
|
205
|
+
return { packs: [], message: 'No rule packs installed. Use `coderev rules search` to find rules.' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { packs: manifest.packs };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Uninstall ─────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
function uninstallRule(packName) {
|
|
214
|
+
const manifest = getInstalledManifest();
|
|
215
|
+
const idx = manifest.packs.findIndex(p => p.name === packName);
|
|
216
|
+
if (idx < 0) {
|
|
217
|
+
throw new Error(`Rule pack "${packName}" is not installed.`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
manifest.packs.splice(idx, 1);
|
|
221
|
+
saveInstalledManifest(manifest);
|
|
222
|
+
|
|
223
|
+
// Also remove from .coderevrc.json
|
|
224
|
+
const configPath = path.join(process.cwd(), '.coderevrc.json');
|
|
225
|
+
if (fs.existsSync(configPath)) {
|
|
226
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
227
|
+
if (config.rules?.custom) {
|
|
228
|
+
config.rules.custom = config.rules.custom.filter(r => r._source !== packName);
|
|
229
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { name: packName, removed: true };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Search Local ──────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
function searchLocalRules(query) {
|
|
239
|
+
const manifest = getInstalledManifest();
|
|
240
|
+
if (!query) return manifest.packs;
|
|
241
|
+
|
|
242
|
+
const q = query.toLowerCase();
|
|
243
|
+
return manifest.packs.filter(
|
|
244
|
+
p => p.name.toLowerCase().includes(q)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = {
|
|
249
|
+
searchRules,
|
|
250
|
+
installRule,
|
|
251
|
+
publishRules,
|
|
252
|
+
listInstalled,
|
|
253
|
+
uninstallRule,
|
|
254
|
+
searchLocalRules,
|
|
255
|
+
DEFAULT_API_URL,
|
|
256
|
+
getMarketplaceDir,
|
|
257
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# coderev — Multi-Agent AI Code Review for GitHub Actions
|
|
2
|
+
# Quick start:
|
|
3
|
+
# 1. Copy this file to .github/workflows/coderev.yml
|
|
4
|
+
# Or run: npx coderev-cli init --github-action
|
|
5
|
+
# 2. Set Secrets: DEEPSEEK_API_KEY in Settings → Secrets and variables → Actions
|
|
6
|
+
# 3. (Optional) Set GITHUB_TOKEN is auto-provided, no setup needed
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# - Review on every PR: default trigger
|
|
10
|
+
# - Block merging on issues: enable CODEREV_BLOCK or comment with coderev status
|
|
11
|
+
#
|
|
12
|
+
# Variables (set in workflow or as env):
|
|
13
|
+
# CODEREV_PROVIDER AI provider (default: deepseek)
|
|
14
|
+
# CODEREV_MODEL Model name (default: deepseek-chat)
|
|
15
|
+
# CODEREV_CONFIDENCE Minimum confidence 0-100 (default: 60)
|
|
16
|
+
# CODEREV_BLOCK Block PR on issues (default: false)
|
|
17
|
+
# CODEREV_BLAME Enable git blame context (default: false)
|
|
18
|
+
# CODEREV_MODE Review mode: comment | check (default: comment)
|
|
19
|
+
|
|
20
|
+
name: coderev AI Code Review
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
pull_request:
|
|
24
|
+
types: [opened, synchronize, reopened]
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
inputs:
|
|
27
|
+
ref:
|
|
28
|
+
description: 'Git ref to review (branch/commit)'
|
|
29
|
+
required: false
|
|
30
|
+
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
pull-requests: write
|
|
34
|
+
checks: write
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
coderev:
|
|
38
|
+
name: AI Code Review
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
timeout-minutes: 10
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout
|
|
44
|
+
uses: actions/checkout@v4
|
|
45
|
+
with:
|
|
46
|
+
fetch-depth: 0
|
|
47
|
+
|
|
48
|
+
- name: Setup Node.js
|
|
49
|
+
uses: actions/setup-node@v4
|
|
50
|
+
with:
|
|
51
|
+
node-version: 20
|
|
52
|
+
|
|
53
|
+
- name: Install coderev
|
|
54
|
+
run: npm install -g coderev-cli
|
|
55
|
+
|
|
56
|
+
- name: Generate diff
|
|
57
|
+
id: diff
|
|
58
|
+
run: |
|
|
59
|
+
if [ -n "${{ github.event.pull_request.base.sha }}" ]; then
|
|
60
|
+
git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} > /tmp/coderev-pr.diff
|
|
61
|
+
else
|
|
62
|
+
git diff HEAD~1 > /tmp/coderev-pr.diff
|
|
63
|
+
fi
|
|
64
|
+
echo "Diff size: $(wc -c < /tmp/coderev-pr.diff) bytes"
|
|
65
|
+
|
|
66
|
+
- name: Run coderev review
|
|
67
|
+
id: review
|
|
68
|
+
env:
|
|
69
|
+
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
|
70
|
+
CODEREV_PROVIDER: ${{ env.CODEREV_PROVIDER || 'deepseek' }}
|
|
71
|
+
CODEREV_MODEL: ${{ env.CODEREV_MODEL || 'deepseek-chat' }}
|
|
72
|
+
run: |
|
|
73
|
+
BLAME_FLAG=""
|
|
74
|
+
if [ "${{ env.CODEREV_BLAME || 'false' }}" = "true" ]; then
|
|
75
|
+
BLAME_FLAG="--blame"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
cat /tmp/coderev-pr.diff | coderev review \
|
|
79
|
+
--output markdown \
|
|
80
|
+
--ci $BLAME_FLAG \
|
|
81
|
+
--min-confidence ${{ env.CODEREV_CONFIDENCE || '60' }} \
|
|
82
|
+
> /tmp/coderev-report.md 2>/tmp/coderev-stderr.log
|
|
83
|
+
|
|
84
|
+
echo "Exit code: $?"
|
|
85
|
+
|
|
86
|
+
- name: Post review as PR comment
|
|
87
|
+
if: github.event_name == 'pull_request' && env.CODEREV_MODE != 'check'
|
|
88
|
+
uses: marocchino/sticky-pull-request-comment@v2
|
|
89
|
+
with:
|
|
90
|
+
header: coderev-review
|
|
91
|
+
path: /tmp/coderev-report.md
|
|
92
|
+
|
|
93
|
+
- name: Post review as GitHub Check
|
|
94
|
+
if: env.CODEREV_MODE == 'check'
|
|
95
|
+
uses: actions/github-script@v7
|
|
96
|
+
with:
|
|
97
|
+
script: |
|
|
98
|
+
const fs = require('fs');
|
|
99
|
+
const report = fs.readFileSync('/tmp/coderev-report.md', 'utf8');
|
|
100
|
+
|
|
101
|
+
// Parse score from report
|
|
102
|
+
const scoreMatch = report.match(/\*\*Score:\*\*\s*(\d+)\/100/);
|
|
103
|
+
const score = scoreMatch ? parseInt(scoreMatch[1]) : 0;
|
|
104
|
+
const conclusion = score >= 80 ? 'success' : score >= 50 ? 'neutral' : 'failure';
|
|
105
|
+
|
|
106
|
+
// Count issues
|
|
107
|
+
const issueMatches = report.match(/\*\*ERROR\*\*/g);
|
|
108
|
+
const warnMatches = report.match(/\*\*WARNING\*\*/g);
|
|
109
|
+
|
|
110
|
+
await github.rest.checks.create({
|
|
111
|
+
owner: context.repo.owner,
|
|
112
|
+
repo: context.repo.repo,
|
|
113
|
+
name: 'coderev AI Review',
|
|
114
|
+
head_sha: context.payload.pull_request?.head?.sha || context.sha,
|
|
115
|
+
status: 'completed',
|
|
116
|
+
conclusion: conclusion,
|
|
117
|
+
output: {
|
|
118
|
+
title: `Code Review: ${score}/100`,
|
|
119
|
+
summary: report.substring(0, 65535),
|
|
120
|
+
annotations: []
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
- name: Block on issues (optional)
|
|
125
|
+
if: env.CODEREV_BLOCK == 'true'
|
|
126
|
+
run: |
|
|
127
|
+
SCORE=$(grep -oP '\*\*Score:\*\*\s*\K\d+' /tmp/coderev-report.md || echo 0)
|
|
128
|
+
if [ "$SCORE" -lt 60 ]; then
|
|
129
|
+
echo "⛔ Score $SCORE/100 below threshold (60). Blocking PR."
|
|
130
|
+
cat /tmp/coderev-report.md
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
echo "✅ Score $SCORE/100 — passed."
|
|
134
|
+
|
|
135
|
+
- name: Upload review artifacts
|
|
136
|
+
if: always()
|
|
137
|
+
uses: actions/upload-artifact@v4
|
|
138
|
+
with:
|
|
139
|
+
name: coderev-report
|
|
140
|
+
path: |
|
|
141
|
+
/tmp/coderev-report.md
|
|
142
|
+
/tmp/coderev-stderr.log
|
|
143
|
+
retention-days: 7
|