bounty-guard 0.2.0
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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/app/server.d.ts +23 -0
- package/dist/app/server.js +130 -0
- package/dist/app/server.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +41 -0
- package/dist/diff.js +191 -0
- package/dist/diff.js.map +1 -0
- package/dist/doctor.d.ts +25 -0
- package/dist/doctor.js +55 -0
- package/dist/doctor.js.map +1 -0
- package/dist/git-scan.d.ts +7 -0
- package/dist/git-scan.js +76 -0
- package/dist/git-scan.js.map +1 -0
- package/dist/github.d.ts +36 -0
- package/dist/github.js +145 -0
- package/dist/github.js.map +1 -0
- package/dist/glob.d.ts +8 -0
- package/dist/glob.js +53 -0
- package/dist/glob.js.map +1 -0
- package/dist/hooks.d.ts +14 -0
- package/dist/hooks.js +45 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +345 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/mock.d.ts +9 -0
- package/dist/llm/mock.js +14 -0
- package/dist/llm/mock.js.map +1 -0
- package/dist/llm/openai.d.ts +50 -0
- package/dist/llm/openai.js +193 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +20 -0
- package/dist/llm/provider.js +36 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +181 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/report.d.ts +34 -0
- package/dist/report.js +181 -0
- package/dist/report.js.map +1 -0
- package/dist/review.d.ts +22 -0
- package/dist/review.js +98 -0
- package/dist/review.js.map +1 -0
- package/dist/rules/index.d.ts +6 -0
- package/dist/rules/index.js +9 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/model.d.ts +24 -0
- package/dist/rules/model.js +2 -0
- package/dist/rules/model.js.map +1 -0
- package/dist/rules/python.d.ts +2 -0
- package/dist/rules/python.js +88 -0
- package/dist/rules/python.js.map +1 -0
- package/dist/rules/seeds.d.ts +2 -0
- package/dist/rules/seeds.js +256 -0
- package/dist/rules/seeds.js.map +1 -0
- package/dist/scanner.d.ts +24 -0
- package/dist/scanner.js +85 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const HOOK_MARKER = "\u7531 bounty-guard \u751F\u6210";
|
|
2
|
+
export interface PreCommitHookOptions {
|
|
3
|
+
/** 门禁等级(high | medium | low | info) */
|
|
4
|
+
failOn: string;
|
|
5
|
+
/** 只扫描已暂存变更(pre-commit 的标准姿势) */
|
|
6
|
+
staged: boolean;
|
|
7
|
+
/** 跳过开关的环境变量名,默认 BOUNTY_GUARD_SKIP */
|
|
8
|
+
skipEnv?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function renderPreCommitHook(options: PreCommitHookOptions): string;
|
|
11
|
+
/** 安装 pre-commit 钩子到指定 git 目录;已存在非本工具生成的钩子时拒绝(--force 可覆盖) */
|
|
12
|
+
export declare function installPreCommitHook(gitDir: string, options: PreCommitHookOptions, force?: boolean): string;
|
|
13
|
+
/** 移除由 bounty-guard 生成的钩子;不存在或不属于本工具时返回 false */
|
|
14
|
+
export declare function uninstallPreCommitHook(gitDir: string): boolean;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pre-commit 钩子形态:把 bounty-guard 装进开发者本地 git 提交流程。
|
|
3
|
+
* 生成的钩子提交前自动扫描未提交(或已暂存)变更,发现门禁级问题即
|
|
4
|
+
* 以非零码退出、阻断提交;单次跳过用 BOUNTY_GUARD_SKIP=1。
|
|
5
|
+
*/
|
|
6
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
export const HOOK_MARKER = '由 bounty-guard 生成';
|
|
9
|
+
export function renderPreCommitHook(options) {
|
|
10
|
+
const skipEnv = options.skipEnv ?? 'BOUNTY_GUARD_SKIP';
|
|
11
|
+
const stagedFlag = options.staged ? ' --staged' : '';
|
|
12
|
+
return [
|
|
13
|
+
'#!/bin/sh',
|
|
14
|
+
`# ${HOOK_MARKER}(bounty-guard init-hooks)—— 更新请重新执行 init-hooks --force`,
|
|
15
|
+
`[ -n "$${skipEnv}" ] && exit 0`,
|
|
16
|
+
`npx --no-install bounty-guard scan --git${stagedFlag} --fail-on ${options.failOn}`,
|
|
17
|
+
''
|
|
18
|
+
].join('\n');
|
|
19
|
+
}
|
|
20
|
+
/** 安装 pre-commit 钩子到指定 git 目录;已存在非本工具生成的钩子时拒绝(--force 可覆盖) */
|
|
21
|
+
export function installPreCommitHook(gitDir, options, force = false) {
|
|
22
|
+
const path = join(gitDir, 'hooks', 'pre-commit');
|
|
23
|
+
mkdirSync(join(gitDir, 'hooks'), { recursive: true }); // 全新临时仓库可能没有 hooks 目录
|
|
24
|
+
if (existsSync(path)) {
|
|
25
|
+
const existing = readFileSync(path, 'utf8');
|
|
26
|
+
if (!existing.includes(HOOK_MARKER) && !force) {
|
|
27
|
+
throw new Error(`已存在非 bounty-guard 生成的 pre-commit 钩子:${path}(确认覆盖请加 --force)`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
writeFileSync(path, renderPreCommitHook(options));
|
|
31
|
+
chmodSync(path, 0o755);
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
/** 移除由 bounty-guard 生成的钩子;不存在或不属于本工具时返回 false */
|
|
35
|
+
export function uninstallPreCommitHook(gitDir) {
|
|
36
|
+
const path = join(gitDir, 'hooks', 'pre-commit');
|
|
37
|
+
if (!existsSync(path))
|
|
38
|
+
return false;
|
|
39
|
+
const existing = readFileSync(path, 'utf8');
|
|
40
|
+
if (!existing.includes(HOOK_MARKER))
|
|
41
|
+
return false;
|
|
42
|
+
rmSync(path);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAChG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAW/C,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,mBAAmB,CAAC;IACvD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,OAAO;QACL,WAAW;QACX,KAAK,WAAW,wDAAwD;QACxE,UAAU,OAAO,eAAe;QAChC,2CAA2C,UAAU,cAAc,OAAO,CAAC,MAAM,EAAE;QACnF,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,OAA6B,EAC7B,KAAK,GAAG,KAAK;IAEb,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IACjD,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,sBAAsB;IAC7E,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,kBAAkB,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IACD,aAAa,CAAC,IAAI,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { loadConfig } from './config.js';
|
|
6
|
+
import { readEnv, startGhAppServer } from './app/server.js';
|
|
7
|
+
import { checkAi, checkConfig, checkConfigError, checkGit, checkNodeVersion, hasFailure, renderChecks } from './doctor.js';
|
|
8
|
+
import { parseDiff } from './diff.js';
|
|
9
|
+
import { collectGitChanges, runGit } from './git-scan.js';
|
|
10
|
+
import { fetchPrDiff, parseRepoSlug, resolvePrNumber, toAnnotations, upsertStickyComment, writeSummaryFile } from './github.js';
|
|
11
|
+
import { matchGlob } from './glob.js';
|
|
12
|
+
import { installPreCommitHook, uninstallPreCommitHook } from './hooks.js';
|
|
13
|
+
import { loadProvider } from './llm/provider.js';
|
|
14
|
+
import { startMcpServer } from './mcp/server.js';
|
|
15
|
+
import { renderMarkdownReport, renderReport, renderSarif, shouldFail } from './report.js';
|
|
16
|
+
import { reviewFindings } from './review.js';
|
|
17
|
+
import { scanDiff } from './scanner.js';
|
|
18
|
+
import { SEVERITIES } from './types.js';
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const VERSION = require('../package.json').version;
|
|
21
|
+
const USAGE_HINT = '请指定扫描来源:--git(扫描未提交变更)或 --diff <file>(扫描 diff 文件)';
|
|
22
|
+
const FORMATS = ['text', 'sarif', 'json'];
|
|
23
|
+
/** 用法类错误:输出友好提示并以退出码 2 结束 */
|
|
24
|
+
class UsageError extends Error {
|
|
25
|
+
}
|
|
26
|
+
/** 校验 --fail-on 取值;无值时回退配置文件 */
|
|
27
|
+
function resolveFailOn(config, value) {
|
|
28
|
+
if (!value)
|
|
29
|
+
return config.failOn;
|
|
30
|
+
if (!SEVERITIES.includes(value)) {
|
|
31
|
+
throw new UsageError(`--fail-on 取值无效:${value}(可选 ${SEVERITIES.join(' | ')})`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
/** 解析并扫描 diff,按需执行 LLM 复核(scan 与 pr-comment 共用)。
|
|
36
|
+
* upgradeOff:--ai 显式启用时把 provider=off 升级为 openai-compatible;
|
|
37
|
+
* 仅配置启用时保持用户配置(off 仍是纯规则)。 */
|
|
38
|
+
async function scanAndReview(diff, config, wantAi, upgradeOff) {
|
|
39
|
+
let findings = scanDiff(diff, {
|
|
40
|
+
ignore: config.ignore,
|
|
41
|
+
skipTests: !config.scanTests,
|
|
42
|
+
disabledRules: config.disabledRules
|
|
43
|
+
});
|
|
44
|
+
const scannable = diff.files.filter((f) => !f.isBinary && !matchGlob(f.path, config.ignore));
|
|
45
|
+
const addedLines = scannable.reduce((sum, f) => sum + f.hunks.reduce((n, h) => n + h.lines.filter((l) => l.type === 'add').length, 0), 0);
|
|
46
|
+
let review;
|
|
47
|
+
if (wantAi) {
|
|
48
|
+
const effective = upgradeOff && config.ai.provider === 'off'
|
|
49
|
+
? { ...config, ai: { ...config.ai, enabled: true, provider: 'openai-compatible' } }
|
|
50
|
+
: config;
|
|
51
|
+
const loaded = loadProvider(effective);
|
|
52
|
+
if (loaded.degraded) {
|
|
53
|
+
console.log(`ℹ️ ${loaded.reason}\n`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const outcome = await reviewFindings(findings, loaded.provider);
|
|
57
|
+
findings = outcome.findings;
|
|
58
|
+
review = {
|
|
59
|
+
provider: loaded.provider.name,
|
|
60
|
+
confirmed: outcome.findings.length,
|
|
61
|
+
filtered: outcome.filtered,
|
|
62
|
+
downgraded: outcome.downgraded,
|
|
63
|
+
unreviewed: outcome.unreviewed
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { findings, scannedFiles: scannable.length, addedLines, review };
|
|
68
|
+
}
|
|
69
|
+
const program = new Command();
|
|
70
|
+
program
|
|
71
|
+
.name('bounty-guard')
|
|
72
|
+
.description('AI 代码安全审查助手 —— 规则引擎初筛 + LLM 复核,守住每一行 diff')
|
|
73
|
+
.version(VERSION);
|
|
74
|
+
program
|
|
75
|
+
.command('scan')
|
|
76
|
+
.description('扫描代码变更中的安全问题(规则初筛;LLM 复核可选)')
|
|
77
|
+
.option('--git', '扫描当前仓库的未提交变更')
|
|
78
|
+
.option('--staged', '只扫描已暂存变更(需搭配 --git;pre-commit 场景)')
|
|
79
|
+
.option('--diff <file>', '从 unified diff 文件扫描')
|
|
80
|
+
.option('--ai', '启用 LLM 复核(无 API Key 时自动降级为纯规则模式)')
|
|
81
|
+
.option('--fail-on <severity>', '门禁等级:high | medium | low | info(覆盖配置文件)')
|
|
82
|
+
.option('-f, --format <fmt>', '输出格式:text | sarif | json(默认 text)')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
try {
|
|
85
|
+
if (!options.git && !options.diff)
|
|
86
|
+
throw new UsageError(USAGE_HINT);
|
|
87
|
+
if (options.git && options.diff)
|
|
88
|
+
throw new UsageError('两种扫描来源互斥,只能二选一');
|
|
89
|
+
if (options.staged && !options.git)
|
|
90
|
+
throw new UsageError('--staged 需要与 --git 搭配使用');
|
|
91
|
+
const config = loadConfig();
|
|
92
|
+
const failOn = resolveFailOn(config, options.failOn);
|
|
93
|
+
const cwd = process.cwd();
|
|
94
|
+
let diff;
|
|
95
|
+
let source;
|
|
96
|
+
try {
|
|
97
|
+
if (options.diff) {
|
|
98
|
+
diff = parseDiff(readFileSync(options.diff, 'utf8'));
|
|
99
|
+
source = `diff 文件 ${options.diff}`;
|
|
100
|
+
}
|
|
101
|
+
else if (options.staged) {
|
|
102
|
+
diff = parseDiff(await runGit(['diff', '--cached'], cwd));
|
|
103
|
+
source = 'git 已暂存变更';
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
diff = await collectGitChanges(cwd);
|
|
107
|
+
source = 'git 未提交变更';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
throw new UsageError(`获取扫描来源失败:${err instanceof Error ? err.message : String(err)}`);
|
|
112
|
+
}
|
|
113
|
+
const outcome = await scanAndReview(diff, config, Boolean(options.ai), Boolean(options.ai));
|
|
114
|
+
const format = options.format ?? 'text';
|
|
115
|
+
if (!FORMATS.includes(format)) {
|
|
116
|
+
throw new UsageError(`--format 取值无效:${format}(可选 ${FORMATS.join(' | ')})`);
|
|
117
|
+
}
|
|
118
|
+
const meta = {
|
|
119
|
+
source,
|
|
120
|
+
scannedFiles: outcome.scannedFiles,
|
|
121
|
+
addedLines: outcome.addedLines,
|
|
122
|
+
review: outcome.review
|
|
123
|
+
};
|
|
124
|
+
if (format === 'sarif') {
|
|
125
|
+
console.log(JSON.stringify(renderSarif(outcome.findings, meta), null, 2));
|
|
126
|
+
}
|
|
127
|
+
else if (format === 'json') {
|
|
128
|
+
console.log(JSON.stringify({ ...meta, findings: outcome.findings }, null, 2));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(renderReport(outcome.findings, meta));
|
|
132
|
+
}
|
|
133
|
+
process.exitCode = shouldFail(outcome.findings, failOn) ? 1 : 0;
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err instanceof UsageError) {
|
|
137
|
+
console.error(err.message);
|
|
138
|
+
process.exitCode = 2;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
program
|
|
145
|
+
.command('pr-comment')
|
|
146
|
+
.description('扫描 GitHub PR 并发布/更新粘性评论(供 Action 调用)')
|
|
147
|
+
.option('--pr <number>', 'PR 编号(缺省从 GITHUB_PR_NUMBER / GITHUB_REF 推断)')
|
|
148
|
+
.option('--repo <slug>', '仓库 owner/name(缺省 GITHUB_REPOSITORY)')
|
|
149
|
+
.option('--fail-on <severity>', '门禁等级:high | medium | low | info(覆盖配置文件)')
|
|
150
|
+
.option('--ai', '启用 LLM 复核(无 API Key 时自动降级为纯规则模式)')
|
|
151
|
+
.option('--annotations', '输出 Actions 告警标注(::error/::warning)')
|
|
152
|
+
.option('--summary', '把 Markdown 报告写入暂存文件,供 Action 步骤追加到 Job Summary')
|
|
153
|
+
.action(async (options) => {
|
|
154
|
+
try {
|
|
155
|
+
const token = process.env.GITHUB_TOKEN;
|
|
156
|
+
if (!token)
|
|
157
|
+
throw new UsageError('缺少 GITHUB_TOKEN 环境变量');
|
|
158
|
+
const repoInput = options.repo ?? process.env.GITHUB_REPOSITORY;
|
|
159
|
+
if (!repoInput)
|
|
160
|
+
throw new UsageError('无法确定仓库:用 --repo owner/name 或设置 GITHUB_REPOSITORY');
|
|
161
|
+
let repo;
|
|
162
|
+
try {
|
|
163
|
+
const { owner, name } = parseRepoSlug(repoInput);
|
|
164
|
+
repo = `${owner}/${name}`;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
throw new UsageError(err instanceof Error ? err.message : String(err));
|
|
168
|
+
}
|
|
169
|
+
const prNumber = resolvePrNumber(options.pr);
|
|
170
|
+
if (!prNumber)
|
|
171
|
+
throw new UsageError('无法确定 PR 编号:用 --pr <n> 或在 pull_request 事件中运行');
|
|
172
|
+
const config = loadConfig();
|
|
173
|
+
const failOn = resolveFailOn(config, options.failOn);
|
|
174
|
+
const ctx = { token, repo };
|
|
175
|
+
let diffText;
|
|
176
|
+
try {
|
|
177
|
+
diffText = await fetchPrDiff(ctx, prNumber);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
throw new UsageError(`读取 PR #${prNumber} diff 失败:${err instanceof Error ? err.message : String(err)}`);
|
|
181
|
+
}
|
|
182
|
+
const outcome = await scanAndReview(parseDiff(diffText), config, Boolean(options.ai), Boolean(options.ai));
|
|
183
|
+
const markdown = renderMarkdownReport(outcome.findings, {
|
|
184
|
+
source: `PR #${prNumber}(${repo})`,
|
|
185
|
+
scannedFiles: outcome.scannedFiles,
|
|
186
|
+
addedLines: outcome.addedLines,
|
|
187
|
+
review: outcome.review
|
|
188
|
+
});
|
|
189
|
+
let result;
|
|
190
|
+
try {
|
|
191
|
+
result = await upsertStickyComment(ctx, prNumber, markdown);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
195
|
+
if (/HTTP 403/.test(msg)) {
|
|
196
|
+
throw new UsageError(`发布评论失败:${msg}。常见原因:fork PR 的默认令牌为只读(可改用 PAT),或 workflow 缺少 pull-requests: write 权限`);
|
|
197
|
+
}
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
console.log(`✅ 已${result === 'created' ? '创建' : '更新'} PR #${prNumber} 的粘性评论`);
|
|
201
|
+
if (options.annotations)
|
|
202
|
+
for (const line of toAnnotations(outcome.findings))
|
|
203
|
+
console.log(line);
|
|
204
|
+
if (options.summary)
|
|
205
|
+
console.log(`📄 Job Summary 内容已写入 ${writeSummaryFile(markdown)}`);
|
|
206
|
+
process.exitCode = shouldFail(outcome.findings, failOn) ? 1 : 0;
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (err instanceof UsageError) {
|
|
210
|
+
console.error(err.message);
|
|
211
|
+
process.exitCode = 2;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
const aiProbe = async (baseUrl, apiKey) => {
|
|
218
|
+
try {
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
221
|
+
const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
222
|
+
const res = await fetch(new URL('models', base), {
|
|
223
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
224
|
+
signal: controller.signal
|
|
225
|
+
});
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
if (res.ok)
|
|
228
|
+
return { status: 'ok', detail: `模型服务连通(${base}models)` };
|
|
229
|
+
if (res.status === 401)
|
|
230
|
+
return { status: 'fail', detail: 'API Key 无效(401)' };
|
|
231
|
+
if (res.status === 404) {
|
|
232
|
+
return { status: 'warn', detail: '端点未提供 /models,无法自动验证——可跑一次 --ai 实测' };
|
|
233
|
+
}
|
|
234
|
+
return { status: 'warn', detail: `HTTP ${res.status}(不影响使用,仅无法自动验证)` };
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
return { status: 'fail', detail: `无法连接模型服务:${err instanceof Error ? err.message : String(err)}` };
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
program
|
|
241
|
+
.command('doctor')
|
|
242
|
+
.description('体检配置与环境(Node / Git / 配置文件 / AI 供应商连通性)')
|
|
243
|
+
.option('--json', '以 JSON 输出检查结果')
|
|
244
|
+
.action(async (options) => {
|
|
245
|
+
const checks = [];
|
|
246
|
+
checks.push(checkNodeVersion(process.versions.node));
|
|
247
|
+
let gitVersion = null;
|
|
248
|
+
let isRepo = false;
|
|
249
|
+
try {
|
|
250
|
+
const cwd = process.cwd();
|
|
251
|
+
gitVersion = (await runGit(['--version'], cwd)).trim().replace(/^git version /, '');
|
|
252
|
+
isRepo = (await runGit(['rev-parse', '--is-inside-work-tree'], cwd)).trim() === 'true';
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
gitVersion = null;
|
|
256
|
+
}
|
|
257
|
+
checks.push(checkGit(gitVersion, isRepo));
|
|
258
|
+
let config;
|
|
259
|
+
try {
|
|
260
|
+
config = loadConfig();
|
|
261
|
+
checks.push(checkConfig(config));
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
checks.push(checkConfigError(err));
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const loaded = loadProvider(config ?? loadConfig());
|
|
268
|
+
checks.push(await checkAi(loaded, aiProbe));
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
checks.push({
|
|
272
|
+
name: 'AI 复核',
|
|
273
|
+
status: 'warn',
|
|
274
|
+
detail: `跳过:${err instanceof Error ? err.message : String(err)}`
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (options.json) {
|
|
278
|
+
console.log(JSON.stringify({ ok: !hasFailure(checks), checks }, null, 2));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
console.log('bounty-guard doctor 体检报告\n');
|
|
282
|
+
console.log(renderChecks(checks));
|
|
283
|
+
console.log('');
|
|
284
|
+
console.log(hasFailure(checks) ? '✗ 存在需要处理的问题' : '✓ 环境就绪');
|
|
285
|
+
}
|
|
286
|
+
process.exitCode = hasFailure(checks) ? 1 : 0;
|
|
287
|
+
});
|
|
288
|
+
program
|
|
289
|
+
.command('init-hooks')
|
|
290
|
+
.description('在当前仓库安装 pre-commit 钩子(提交前自动扫描)')
|
|
291
|
+
.option('--fail-on <severity>', '门禁等级:high | medium | low | info(默认取配置文件)')
|
|
292
|
+
.option('--staged', '只扫描已暂存变更(pre-commit 标准姿势)')
|
|
293
|
+
.option('--force', '覆盖已存在的钩子文件')
|
|
294
|
+
.option('--uninstall', '移除由 bounty-guard 生成的钩子')
|
|
295
|
+
.action(async (options) => {
|
|
296
|
+
try {
|
|
297
|
+
const cwd = process.cwd();
|
|
298
|
+
let gitDir;
|
|
299
|
+
try {
|
|
300
|
+
gitDir = (await runGit(['rev-parse', '--absolute-git-dir'], cwd)).trim();
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
throw new UsageError('当前目录不在 git 仓库中,无法安装钩子');
|
|
304
|
+
}
|
|
305
|
+
if (options.uninstall) {
|
|
306
|
+
const removed = uninstallPreCommitHook(gitDir);
|
|
307
|
+
console.log(removed ? '✅ 已移除 bounty-guard 生成的 pre-commit 钩子' : 'ℹ️ 未发现 bounty-guard 生成的钩子');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const config = loadConfig();
|
|
311
|
+
const failOn = resolveFailOn(config, options.failOn);
|
|
312
|
+
const path = installPreCommitHook(gitDir, { failOn, staged: Boolean(options.staged) }, Boolean(options.force));
|
|
313
|
+
console.log(`✅ pre-commit 钩子已安装:${path}`);
|
|
314
|
+
console.log(` 提交前将自动扫描(--fail-on ${failOn}${options.staged ? ' --staged' : ''});临时跳过单次提交:BOUNTY_GUARD_SKIP=1 git commit ...`);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
if (err instanceof UsageError) {
|
|
318
|
+
console.error(err.message);
|
|
319
|
+
process.exitCode = 2;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
program
|
|
326
|
+
.command('mcp')
|
|
327
|
+
.description('以 MCP stdio 服务器运行(供 AI 编程助手作为工具接入)')
|
|
328
|
+
.action(() => {
|
|
329
|
+
// stdout 保留给协议消息;startMcpServer 内部日志全部走 stderr
|
|
330
|
+
startMcpServer();
|
|
331
|
+
});
|
|
332
|
+
program
|
|
333
|
+
.command('gh-app')
|
|
334
|
+
.description('以 GitHub App 自托管服务器运行(pull_request webhook 自动扫描评论)')
|
|
335
|
+
.action(() => {
|
|
336
|
+
try {
|
|
337
|
+
startGhAppServer(readEnv(), loadConfig());
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
341
|
+
process.exitCode = 2;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
program.parseAsync();
|
|
345
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LLMProvider, LLMReviewRequest, LLMReviewResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 测试与无 Key 环境的确定性 Provider。
|
|
4
|
+
* 行为契约:永远返回 confirmed + 固定格式解释,绝不抛异常。
|
|
5
|
+
*/
|
|
6
|
+
export declare class MockProvider implements LLMProvider {
|
|
7
|
+
readonly name = "mock";
|
|
8
|
+
review(request: LLMReviewRequest): Promise<LLMReviewResult>;
|
|
9
|
+
}
|
package/dist/llm/mock.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 测试与无 Key 环境的确定性 Provider。
|
|
3
|
+
* 行为契约:永远返回 confirmed + 固定格式解释,绝不抛异常。
|
|
4
|
+
*/
|
|
5
|
+
export class MockProvider {
|
|
6
|
+
name = 'mock';
|
|
7
|
+
async review(request) {
|
|
8
|
+
return {
|
|
9
|
+
verdict: 'confirmed',
|
|
10
|
+
explanation: `[mock] 规则 ${request.ruleId} 命中:${request.snippet.slice(0, 40)}`
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../../src/llm/mock.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,YAAY;IACd,IAAI,GAAG,MAAM,CAAC;IAEvB,KAAK,CAAC,MAAM,CAAC,OAAyB;QACpC,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,WAAW,EAAE,aAAa,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;SAC9E,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { LLMProvider, LLMReviewRequest, LLMReviewResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI 兼容适配器:逐条复核告警,走 chat/completions,要求 JSON 输出。
|
|
4
|
+
* 设计约束:
|
|
5
|
+
* - 只复核、不发明——输入永远是规则已命中的告警;
|
|
6
|
+
* - 严重度只接受下调(解析层直接丢弃上调建议,流水线再做双保险);
|
|
7
|
+
* - 单条复核失败不抛异常,返回 unsure 保留规则原判(优雅降级);
|
|
8
|
+
* - 兼容性:供应商不支持 response_format 时自动去掉该参数重试;
|
|
9
|
+
* HTTP 429/5xx 按指数退避(尊重 Retry-After),与解析失败分开处理。
|
|
10
|
+
*/
|
|
11
|
+
export interface OpenAIProviderOptions {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
/** 默认 https://api.openai.com/v1;DeepSeek/GLM 等换这里即可 */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
/** fetch 注入点(测试用);缺省用全局 fetch */
|
|
17
|
+
fetchImpl?: typeof fetch;
|
|
18
|
+
/** 单次请求超时(毫秒),默认 30 秒 */
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
/** JSON 解析 / HTTP 失败的重试次数,默认 1 */
|
|
21
|
+
retries?: number;
|
|
22
|
+
/** 重试的基础退避时长(毫秒),默认 1000;HTTP 429/5xx 按尝试次数递增 */
|
|
23
|
+
retryBaseDelayMs?: number;
|
|
24
|
+
/** 限制单条复核的输出长度(tokens),默认 500——控制复核成本 */
|
|
25
|
+
maxTokens?: number;
|
|
26
|
+
}
|
|
27
|
+
/** HTTP 层错误:携带状态码与 Retry-After,供重试策略决策 */
|
|
28
|
+
export declare class LLMHttpError extends Error {
|
|
29
|
+
readonly status: number;
|
|
30
|
+
readonly retryAfterMs: number | undefined;
|
|
31
|
+
constructor(status: number, retryAfterMs: number | undefined, detail: string);
|
|
32
|
+
}
|
|
33
|
+
export declare class OpenAICompatibleProvider implements LLMProvider {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
private readonly apiKey;
|
|
36
|
+
private readonly baseUrl;
|
|
37
|
+
private readonly model;
|
|
38
|
+
private readonly fetchImpl;
|
|
39
|
+
private readonly timeoutMs;
|
|
40
|
+
private readonly retries;
|
|
41
|
+
private readonly retryBaseDelayMs;
|
|
42
|
+
private readonly maxTokens;
|
|
43
|
+
constructor(options: OpenAIProviderOptions);
|
|
44
|
+
review(request: LLMReviewRequest): Promise<LLMReviewResult>;
|
|
45
|
+
/** 组装消息:组件级截断,控制单条复核的 Token 消耗 */
|
|
46
|
+
private buildMessages;
|
|
47
|
+
private chat;
|
|
48
|
+
/** 解析模型输出;严重度只保留「低于初判」的建议(上调与维持直接丢弃) */
|
|
49
|
+
private parseReview;
|
|
50
|
+
}
|