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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { SEVERITY_RANK } from '../types.js';
|
|
2
|
+
const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
|
|
3
|
+
const DEFAULT_MODEL = 'gpt-4o-mini';
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
const DEFAULT_RETRIES = 1;
|
|
6
|
+
const DEFAULT_RETRY_BASE_DELAY_MS = 1_000;
|
|
7
|
+
const DEFAULT_MAX_TOKENS = 500;
|
|
8
|
+
const MAX_RETRY_DELAY_MS = 10_000;
|
|
9
|
+
const VERDICTS = ['confirmed', 'false-positive', 'unsure'];
|
|
10
|
+
const SEVERITIES = ['high', 'medium', 'low', 'info'];
|
|
11
|
+
/** 复核提示词:中文、只输出 JSON、严重度只允许下调 */
|
|
12
|
+
const SYSTEM_PROMPT = [
|
|
13
|
+
'你是代码安全告警的复核员。输入是一条静态规则命中的告警(规则 ID、初判严重度、命中代码行与上下文)。你的职责:',
|
|
14
|
+
'1. 判断该告警在当前上下文中是否为真实的安全问题;',
|
|
15
|
+
'2. 给出结论与一句话中文解释;确属问题时可附具体修复建议;',
|
|
16
|
+
'3. 严重度只能建议下调或维持,禁止上调;不确定时用 unsure,不要猜测。',
|
|
17
|
+
'判定倾向:宁可漏报不可误报——测试文件、注释、示例代码、占位符、纯静态字符串等非真实风险一律判 false-positive。',
|
|
18
|
+
'只输出一个 JSON 对象,不要输出任何其他内容,格式:',
|
|
19
|
+
'{"verdict":"confirmed|false-positive|unsure","severity":"high|medium|low|info","explanation":"一句话中文理由","fixSuggestion":"可选的中文修复建议"}',
|
|
20
|
+
'其中 verdict 必填;severity 仅在需要下调时给出;fixSuggestion 可选。'
|
|
21
|
+
].join('\n');
|
|
22
|
+
/** 截断上限:控制单条复核的 Token 消耗 */
|
|
23
|
+
const MAX_SNIPPET_CHARS = 400;
|
|
24
|
+
const MAX_CONTEXT_LINES = 6;
|
|
25
|
+
const MAX_CONTEXT_LINE_CHARS = 160;
|
|
26
|
+
/** HTTP 层错误:携带状态码与 Retry-After,供重试策略决策 */
|
|
27
|
+
export class LLMHttpError extends Error {
|
|
28
|
+
status;
|
|
29
|
+
retryAfterMs;
|
|
30
|
+
constructor(status, retryAfterMs, detail) {
|
|
31
|
+
super(`HTTP ${status}${detail ? `:${detail}` : ''}`);
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.retryAfterMs = retryAfterMs;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
37
|
+
export class OpenAICompatibleProvider {
|
|
38
|
+
name;
|
|
39
|
+
apiKey;
|
|
40
|
+
baseUrl;
|
|
41
|
+
model;
|
|
42
|
+
fetchImpl;
|
|
43
|
+
timeoutMs;
|
|
44
|
+
retries;
|
|
45
|
+
retryBaseDelayMs;
|
|
46
|
+
maxTokens;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.apiKey = options.apiKey;
|
|
49
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
50
|
+
this.model = options.model ?? DEFAULT_MODEL;
|
|
51
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
52
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
53
|
+
this.retries = options.retries ?? DEFAULT_RETRIES;
|
|
54
|
+
this.retryBaseDelayMs = options.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
|
|
55
|
+
this.maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
56
|
+
this.name = `openai-compatible:${this.model}`;
|
|
57
|
+
}
|
|
58
|
+
async review(request) {
|
|
59
|
+
const messages = this.buildMessages(request);
|
|
60
|
+
let lastError = '未知错误';
|
|
61
|
+
let lastDelayMs = 0;
|
|
62
|
+
let jsonMode = true;
|
|
63
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
64
|
+
if (attempt > 0 && lastDelayMs > 0)
|
|
65
|
+
await sleep(lastDelayMs);
|
|
66
|
+
try {
|
|
67
|
+
const content = await this.chat(messages, { jsonMode, retryNote: attempt > 0 });
|
|
68
|
+
const parsed = this.parseReview(content, request.severity);
|
|
69
|
+
if (parsed)
|
|
70
|
+
return parsed;
|
|
71
|
+
lastError = '输出无法解析为合法 JSON';
|
|
72
|
+
lastDelayMs = 0; // 解析类失败立即重试,无需退避
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
76
|
+
if (err instanceof LLMHttpError) {
|
|
77
|
+
// 供应商不支持 response_format(多见于 400):关闭 JSON 模式再试
|
|
78
|
+
if (err.status === 400 && jsonMode)
|
|
79
|
+
jsonMode = false;
|
|
80
|
+
lastDelayMs = Math.min(err.retryAfterMs ?? this.retryBaseDelayMs * (attempt + 1), MAX_RETRY_DELAY_MS);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
lastDelayMs = Math.min(this.retryBaseDelayMs * (attempt + 1), MAX_RETRY_DELAY_MS);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { verdict: 'unsure', explanation: `LLM 复核失败(${lastError}),保留规则原判` };
|
|
88
|
+
}
|
|
89
|
+
/** 组装消息:组件级截断,控制单条复核的 Token 消耗 */
|
|
90
|
+
buildMessages(request) {
|
|
91
|
+
const context = (request.context ?? [])
|
|
92
|
+
.slice(0, MAX_CONTEXT_LINES)
|
|
93
|
+
.map((line) => line.slice(0, MAX_CONTEXT_LINE_CHARS))
|
|
94
|
+
.join('\n');
|
|
95
|
+
const user = [
|
|
96
|
+
`规则 ID:${request.ruleId}`,
|
|
97
|
+
`规则初判严重度:${request.severity}`,
|
|
98
|
+
`语言:${request.language}`,
|
|
99
|
+
`命中行:${request.snippet.slice(0, MAX_SNIPPET_CHARS)}`,
|
|
100
|
+
'上下文:',
|
|
101
|
+
context || '(无)'
|
|
102
|
+
].join('\n');
|
|
103
|
+
return [
|
|
104
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
105
|
+
{ role: 'user', content: user }
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
async chat(messages, opts) {
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
let timer;
|
|
111
|
+
const timeout = new Promise((_, reject) => {
|
|
112
|
+
timer = setTimeout(() => {
|
|
113
|
+
controller.abort();
|
|
114
|
+
reject(new Error('请求超时'));
|
|
115
|
+
}, this.timeoutMs);
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
const body = {
|
|
119
|
+
model: this.model,
|
|
120
|
+
messages: opts.retryNote
|
|
121
|
+
? [...messages, { role: 'user', content: '你上一次的输出无法解析,请严格只输出一个 JSON 对象。' }]
|
|
122
|
+
: messages,
|
|
123
|
+
temperature: 0,
|
|
124
|
+
max_tokens: this.maxTokens
|
|
125
|
+
};
|
|
126
|
+
if (opts.jsonMode)
|
|
127
|
+
body.response_format = { type: 'json_object' };
|
|
128
|
+
const pending = this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(body),
|
|
135
|
+
signal: controller.signal
|
|
136
|
+
});
|
|
137
|
+
pending.catch(() => { }); // 防 timeout 赢得竞速后出现未处理拒绝
|
|
138
|
+
const response = (await Promise.race([pending, timeout]));
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
let detail = '';
|
|
141
|
+
try {
|
|
142
|
+
detail = (await response.text()).slice(0, 200);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
detail = '';
|
|
146
|
+
}
|
|
147
|
+
const retryAfter = Number(response.headers?.get?.('retry-after'));
|
|
148
|
+
throw new LLMHttpError(response.status, Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : undefined, detail);
|
|
149
|
+
}
|
|
150
|
+
const data = (await Promise.race([response.json(), timeout]));
|
|
151
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
if (timer)
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** 解析模型输出;严重度只保留「低于初判」的建议(上调与维持直接丢弃) */
|
|
159
|
+
parseReview(content, originalSeverity) {
|
|
160
|
+
const start = content.indexOf('{');
|
|
161
|
+
const end = content.lastIndexOf('}');
|
|
162
|
+
if (start === -1 || end <= start)
|
|
163
|
+
return null;
|
|
164
|
+
let obj;
|
|
165
|
+
try {
|
|
166
|
+
obj = JSON.parse(content.slice(start, end + 1));
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (typeof obj !== 'object' || obj === null)
|
|
172
|
+
return null;
|
|
173
|
+
const record = obj;
|
|
174
|
+
if (typeof record.verdict !== 'string' || !VERDICTS.includes(record.verdict))
|
|
175
|
+
return null;
|
|
176
|
+
const result = {
|
|
177
|
+
verdict: record.verdict,
|
|
178
|
+
explanation: typeof record.explanation === 'string' && record.explanation.trim()
|
|
179
|
+
? record.explanation.trim()
|
|
180
|
+
: '(模型未给出解释)'
|
|
181
|
+
};
|
|
182
|
+
if (typeof record.severity === 'string' &&
|
|
183
|
+
SEVERITIES.includes(record.severity) &&
|
|
184
|
+
SEVERITY_RANK[record.severity] < SEVERITY_RANK[originalSeverity]) {
|
|
185
|
+
result.severity = record.severity;
|
|
186
|
+
}
|
|
187
|
+
if (typeof record.fixSuggestion === 'string' && record.fixSuggestion.trim()) {
|
|
188
|
+
result.fixSuggestion = record.fixSuggestion.trim();
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/llm/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BountyConfig } from '../config.js';
|
|
2
|
+
import type { LLMProvider } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Provider 工厂。选择逻辑:
|
|
5
|
+
* - ai.provider=off 或未启用 → Mock(纯规则模式)
|
|
6
|
+
* - ai.provider=mock → Mock(显式走 Mock,可演练复核流水线)
|
|
7
|
+
* - ai.provider=openai-compatible → 读取环境变量 Key;无 Key 降级 Mock 并说明原因
|
|
8
|
+
*/
|
|
9
|
+
export interface LoadedProvider {
|
|
10
|
+
provider: LLMProvider;
|
|
11
|
+
/** mock=纯规则/显式 Mock;llm=真实复核 */
|
|
12
|
+
mode: 'mock' | 'llm';
|
|
13
|
+
/** true=想要复核但条件不满足而降级(CLI 会提示原因) */
|
|
14
|
+
degraded: boolean;
|
|
15
|
+
reason?: string;
|
|
16
|
+
/** mode=llm 时提供——供 doctor 探测连通性(不会出现在任何输出中) */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function loadProvider(config?: BountyConfig): LoadedProvider;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { MockProvider } from './mock.js';
|
|
3
|
+
import { OpenAICompatibleProvider } from './openai.js';
|
|
4
|
+
export function loadProvider(config = loadConfig()) {
|
|
5
|
+
const ai = config.ai;
|
|
6
|
+
if (!ai.enabled || ai.provider === 'off') {
|
|
7
|
+
return {
|
|
8
|
+
provider: new MockProvider(),
|
|
9
|
+
mode: 'mock',
|
|
10
|
+
degraded: true,
|
|
11
|
+
reason: 'LLM 复核未启用,当前为纯规则模式'
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (ai.provider === 'mock') {
|
|
15
|
+
return { provider: new MockProvider(), mode: 'mock', degraded: false };
|
|
16
|
+
}
|
|
17
|
+
const apiKey = process.env.BOUNTY_GUARD_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
return {
|
|
20
|
+
provider: new MockProvider(),
|
|
21
|
+
mode: 'mock',
|
|
22
|
+
degraded: true,
|
|
23
|
+
reason: '未检测到 API Key(设置 BOUNTY_GUARD_API_KEY 或 OPENAI_API_KEY),已降级为纯规则模式'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const baseUrl = ai.baseUrl ?? process.env.BOUNTY_GUARD_BASE_URL ?? process.env.OPENAI_BASE_URL ?? undefined;
|
|
27
|
+
const model = ai.model ?? process.env.BOUNTY_GUARD_MODEL ?? undefined;
|
|
28
|
+
return {
|
|
29
|
+
provider: new OpenAICompatibleProvider({ apiKey, baseUrl, model, maxTokens: ai.maxTokens }),
|
|
30
|
+
mode: 'llm',
|
|
31
|
+
degraded: false,
|
|
32
|
+
baseUrl: (baseUrl ?? 'https://api.openai.com/v1').replace(/\/+$/, ''),
|
|
33
|
+
apiKey
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAqBvD,MAAM,UAAU,YAAY,CAAC,SAAuB,UAAU,EAAE;IAC9D,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE,IAAI,YAAY,EAAE;YAC5B,IAAI,EAAE,MAAM;YACZ,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,oBAAoB;SAC7B,CAAC;IACJ,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO,EAAE,QAAQ,EAAE,IAAI,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC9E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,QAAQ,EAAE,IAAI,YAAY,EAAE;YAC5B,IAAI,EAAE,MAAM;YACZ,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,kEAAkE;SAC3E,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,SAAS,CAAC;IAC5G,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,SAAS,CAAC;IACtE,OAAO;QACL,QAAQ,EAAE,IAAI,wBAAwB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC;QAC3F,IAAI,EAAE,KAAK;QACX,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,CAAC,OAAO,IAAI,2BAA2B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACrE,MAAM;KACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** MCP 工具实现(可独立测试) */
|
|
2
|
+
export declare function callTool(name: string, args: Record<string, unknown>): Promise<{
|
|
3
|
+
text: string;
|
|
4
|
+
}>;
|
|
5
|
+
/** 处理一行 JSON-RPC 请求,返回响应行;无需响应(通知/非法输入)返回 null */
|
|
6
|
+
export declare function handleRpc(raw: string): Promise<string | null>;
|
|
7
|
+
/** 启动 stdio 服务器:stdout 只写协议消息,日志走 stderr */
|
|
8
|
+
export declare function startMcpServer(): void;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP(Model Context Protocol)stdio 服务器形态:
|
|
3
|
+
* 任何 AI 编程助手都可作为 MCP 客户端接入 bounty-guard,在写码瞬间调用扫描。
|
|
4
|
+
* 协议:stdin/stdout 上的换行分隔 JSON-RPC 2.0(零依赖实现)。
|
|
5
|
+
* 注意:stdout 只承载协议消息——一切日志一律走 stderr。
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { createInterface } from 'node:readline';
|
|
9
|
+
import { loadConfig } from '../config.js';
|
|
10
|
+
import { collectGitChanges, collectStagedChanges } from '../git-scan.js';
|
|
11
|
+
import { matchGlob } from '../glob.js';
|
|
12
|
+
import { loadProvider } from '../llm/provider.js';
|
|
13
|
+
import { renderReport } from '../report.js';
|
|
14
|
+
import { reviewFindings } from '../review.js';
|
|
15
|
+
import { scanDiff } from '../scanner.js';
|
|
16
|
+
import { ALL_RULES } from '../rules/index.js';
|
|
17
|
+
import { parseDiff } from '../diff.js';
|
|
18
|
+
const require2 = createRequire(import.meta.url);
|
|
19
|
+
const VERSION = require2('../../package.json').version;
|
|
20
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
21
|
+
function resp(id, result) {
|
|
22
|
+
return { jsonrpc: '2.0', id: id ?? null, result };
|
|
23
|
+
}
|
|
24
|
+
function errResp(id, code, message) {
|
|
25
|
+
return { jsonrpc: '2.0', id: id ?? null, error: { code, message } };
|
|
26
|
+
}
|
|
27
|
+
const TOOL_DEFS = [
|
|
28
|
+
{
|
|
29
|
+
name: 'scan_git',
|
|
30
|
+
description: '扫描指定目录的未提交/已暂存变更(只审新增行),返回中文安全报告与门禁结论',
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
cwd: { type: 'string', description: '项目目录,缺省为当前工作目录' },
|
|
35
|
+
staged: { type: 'boolean', description: '只扫描已暂存变更' },
|
|
36
|
+
ai: { type: 'boolean', description: '启用 LLM 复核(需已配置 Key)' }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'scan_diff',
|
|
42
|
+
description: '扫描一段 unified diff 文本,返回中文安全报告',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
diff: { type: 'string', description: 'unified diff 全文' },
|
|
47
|
+
ai: { type: 'boolean', description: '启用 LLM 复核' }
|
|
48
|
+
},
|
|
49
|
+
required: ['diff']
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'list_rules',
|
|
54
|
+
description: '列出全部内置安全规则(id、严重度、说明)',
|
|
55
|
+
inputSchema: { type: 'object', properties: {} }
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'doctor',
|
|
59
|
+
description: '查看当前目录的 bounty-guard 配置摘要',
|
|
60
|
+
inputSchema: { type: 'object', properties: { cwd: { type: 'string' } } }
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
/** 扫描并渲染为文本报告(MCP 内容载荷) */
|
|
64
|
+
async function scanToText(diff, cwd, wantAi, source) {
|
|
65
|
+
const config = loadConfig(cwd);
|
|
66
|
+
let findings = scanDiff(diff, {
|
|
67
|
+
ignore: config.ignore,
|
|
68
|
+
skipTests: !config.scanTests,
|
|
69
|
+
disabledRules: config.disabledRules
|
|
70
|
+
});
|
|
71
|
+
const scannable = diff.files.filter((f) => !f.isBinary && !matchGlob(f.path, config.ignore));
|
|
72
|
+
const addedLines = scannable.reduce((sum, f) => sum + f.hunks.reduce((n, h) => n + h.lines.filter((l) => l.type === 'add').length, 0), 0);
|
|
73
|
+
let review;
|
|
74
|
+
if (wantAi) {
|
|
75
|
+
const loaded = loadProvider(config);
|
|
76
|
+
if (!loaded.degraded) {
|
|
77
|
+
const outcome = await reviewFindings(findings, loaded.provider);
|
|
78
|
+
findings = outcome.findings;
|
|
79
|
+
review = {
|
|
80
|
+
provider: loaded.provider.name,
|
|
81
|
+
confirmed: outcome.findings.length,
|
|
82
|
+
filtered: outcome.filtered,
|
|
83
|
+
downgraded: outcome.downgraded,
|
|
84
|
+
unreviewed: outcome.unreviewed
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return renderReport(findings, {
|
|
89
|
+
source,
|
|
90
|
+
scannedFiles: scannable.length,
|
|
91
|
+
addedLines,
|
|
92
|
+
review
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** MCP 工具实现(可独立测试) */
|
|
96
|
+
export async function callTool(name, args) {
|
|
97
|
+
const cwd = typeof args.cwd === 'string' && args.cwd ? args.cwd : process.cwd();
|
|
98
|
+
if (name === 'scan_git') {
|
|
99
|
+
const diff = args.staged === true ? await collectStagedChanges(cwd) : await collectGitChanges(cwd);
|
|
100
|
+
return {
|
|
101
|
+
text: await scanToText(diff, cwd, args.ai === true, args.staged === true ? 'git 已暂存变更' : 'git 未提交变更')
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (name === 'scan_diff') {
|
|
105
|
+
if (typeof args.diff !== 'string' || args.diff.trim() === '') {
|
|
106
|
+
throw new Error('缺少 diff 参数(unified diff 文本)');
|
|
107
|
+
}
|
|
108
|
+
return { text: await scanToText(parseDiff(args.diff), cwd, args.ai === true, 'diff 文本') };
|
|
109
|
+
}
|
|
110
|
+
if (name === 'list_rules') {
|
|
111
|
+
const lines = ALL_RULES.map((r) => `- [${r.severity}] ${r.id}:${r.message}`);
|
|
112
|
+
return { text: `bounty-guard 共 ${ALL_RULES.length} 条规则:\n${lines.join('\n')}` };
|
|
113
|
+
}
|
|
114
|
+
if (name === 'doctor') {
|
|
115
|
+
const config = loadConfig(cwd);
|
|
116
|
+
return {
|
|
117
|
+
text: [
|
|
118
|
+
`Node:${process.versions.node}`,
|
|
119
|
+
`failOn:${config.failOn} · scanTests:${config.scanTests} · 禁用规则:${config.disabledRules?.length ?? 0} 条`,
|
|
120
|
+
`AI:${config.ai.enabled ? `已启用(${config.ai.provider})` : '未启用(纯规则模式)'}${config.ai.model ? ` · 模型 ${config.ai.model}` : ''}`
|
|
121
|
+
].join('\n')
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`未知工具:${name}`);
|
|
125
|
+
}
|
|
126
|
+
/** 处理一行 JSON-RPC 请求,返回响应行;无需响应(通知/非法输入)返回 null */
|
|
127
|
+
export async function handleRpc(raw) {
|
|
128
|
+
let req;
|
|
129
|
+
try {
|
|
130
|
+
req = JSON.parse(raw);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null; // 非法行静默忽略(协议健壮性)
|
|
134
|
+
}
|
|
135
|
+
if (typeof req.method !== 'string')
|
|
136
|
+
return null;
|
|
137
|
+
if (req.method === 'initialize') {
|
|
138
|
+
return JSON.stringify(resp(req.id ?? null, {
|
|
139
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
140
|
+
capabilities: { tools: { listChanged: false } },
|
|
141
|
+
serverInfo: { name: 'bounty-guard', version: VERSION }
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
if (req.method.startsWith('notifications/'))
|
|
145
|
+
return null;
|
|
146
|
+
if (req.method === 'tools/list')
|
|
147
|
+
return JSON.stringify(resp(req.id ?? null, { tools: TOOL_DEFS }));
|
|
148
|
+
if (req.method === 'tools/call') {
|
|
149
|
+
const params = req.params ?? {};
|
|
150
|
+
const name = typeof params.name === 'string' ? params.name : '';
|
|
151
|
+
const args = (params.arguments ?? {});
|
|
152
|
+
try {
|
|
153
|
+
const { text } = await callTool(name, args);
|
|
154
|
+
return JSON.stringify(resp(req.id ?? null, { content: [{ type: 'text', text }], isError: false }));
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
158
|
+
return JSON.stringify(resp(req.id ?? null, { content: [{ type: 'text', text: message }], isError: true }));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (req.id !== undefined && req.id !== null) {
|
|
162
|
+
return JSON.stringify(errResp(req.id, -32601, `未知方法:${req.method}`));
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/** 启动 stdio 服务器:stdout 只写协议消息,日志走 stderr */
|
|
167
|
+
export function startMcpServer() {
|
|
168
|
+
const rl = createInterface({ input: process.stdin });
|
|
169
|
+
rl.on('line', (line) => {
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
if (!trimmed)
|
|
172
|
+
return;
|
|
173
|
+
void handleRpc(trimmed).then((out) => {
|
|
174
|
+
if (out)
|
|
175
|
+
process.stdout.write(`${out}\n`);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
rl.on('close', () => process.exit(0));
|
|
179
|
+
process.stderr.write(`bounty-guard MCP 服务器已启动(stdio,协议 ${PROTOCOL_VERSION})\n`);
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAmB,MAAM,YAAY,CAAC;AAExD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD,MAAM,OAAO,GAAW,QAAQ,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC;AAE/D,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAgBtC,SAAS,IAAI,CAAC,EAAoB,EAAE,MAAe;IACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,OAAO,CAAC,EAAoB,EAAE,IAAY,EAAE,OAAe;IAClE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;AACtE,CAAC;AAED,MAAM,SAAS,GAAG;IAChB;QACE,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,uCAAuC;QACpD,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE;gBACtD,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE;gBACpD,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,qBAAqB,EAAE;aAC5D;SACF;KACF;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,+BAA+B;QAC5C,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;gBACxD,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE;aAClD;YACD,QAAQ,EAAE,CAAC,MAAM,CAAC;SACnB;KACF;IACD;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,uBAAuB;QACpC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,2BAA2B;QACxC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;KACzE;CACF,CAAC;AAEF,2BAA2B;AAC3B,KAAK,UAAU,UAAU,CAAC,IAAgB,EAAE,GAAW,EAAE,MAAe,EAAE,MAAc;IACtF,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE;QAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS,EAAE,CAAC,MAAM,CAAC,SAAS;QAC5B,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7F,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CACjC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EACjG,CAAC,CACF,CAAC;IACF,IAAI,MAAM,CAAC;IACX,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YAC5B,MAAM,GAAG;gBACP,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;gBAC9B,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAClC,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,UAAU,EAAE,OAAO,CAAC,UAAU;aAC/B,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,YAAY,CAAC,QAAQ,EAAE;QAC5B,MAAM;QACN,YAAY,EAAE,SAAS,CAAC,MAAM;QAC9B,UAAU;QACV,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,sBAAsB;AACtB,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,IAA6B;IACxE,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAChF,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACnG,OAAO;YACL,IAAI,EAAE,MAAM,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;SACtG,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5F,CAAC;IACD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7E,OAAO,EAAE,IAAI,EAAE,kBAAkB,SAAS,CAAC,MAAM,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO;YACL,IAAI,EAAE;gBACJ,QAAQ,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;gBAC/B,UAAU,MAAM,CAAC,MAAM,gBAAgB,MAAM,CAAC,SAAS,WAAW,MAAM,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,IAAI;gBACvG,MAAM,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;aAC5H,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,IAAI,GAAe,CAAC;IACpB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,iBAAiB;IAChC,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,GAAG,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC,SAAS,CACnB,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE;YACnB,eAAe,EAAE,gBAAgB;YACjC,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE;YAC/C,UAAU,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE;SACvD,CAAC,CACH,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IACzD,IAAI,GAAG,CAAC,MAAM,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACnG,IAAI,GAAG,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC;QACjE,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACrG,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,cAAc;IAC5B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACrD,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACnC,IAAI,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,gBAAgB,KAAK,CAAC,CAAC;AAClF,CAAC"}
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Finding, Severity } from './types.js';
|
|
2
|
+
/** 粘性评论的识别标记:更新而非新建全靠它 */
|
|
3
|
+
export declare const COMMENT_MARKER = "<!-- bounty-guard-report -->";
|
|
4
|
+
/** 各严重度的中文标签(终端、Markdown 与告警标注共用) */
|
|
5
|
+
export declare const LABELS: Record<Severity, string>;
|
|
6
|
+
/** CI 门禁:任一告警达到 failOn 档即应非零退出 */
|
|
7
|
+
export declare function shouldFail(findings: Finding[], failOn: Severity): boolean;
|
|
8
|
+
export interface ReportMeta {
|
|
9
|
+
/** 扫描来源描述,如「git 未提交变更」 */
|
|
10
|
+
source: string;
|
|
11
|
+
scannedFiles: number;
|
|
12
|
+
addedLines: number;
|
|
13
|
+
/** LLM 复核汇总(未开启复核时缺省) */
|
|
14
|
+
review?: {
|
|
15
|
+
provider: string;
|
|
16
|
+
confirmed: number;
|
|
17
|
+
filtered: number;
|
|
18
|
+
downgraded: number;
|
|
19
|
+
unreviewed?: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** 渲染中文终端报告,按文件分组 */
|
|
23
|
+
export declare function renderReport(findings: Finding[], meta: ReportMeta): string;
|
|
24
|
+
/** Markdown 版报告:PR 粘性评论与 Job Summary 共用 */
|
|
25
|
+
export declare function renderMarkdownReport(findings: Finding[], meta: ReportMeta): string;
|
|
26
|
+
/** SARIF 2.1.0 输出:可直接对接 GitHub code-scanning(actions/upload-sarif) */
|
|
27
|
+
export declare function renderSarif(findings: Finding[], meta: Pick<ReportMeta, 'source' | 'scannedFiles' | 'addedLines'>): Record<string, unknown>;
|
|
28
|
+
export interface MetricsRow {
|
|
29
|
+
pr: string;
|
|
30
|
+
addedLines: number;
|
|
31
|
+
findings: string[];
|
|
32
|
+
}
|
|
33
|
+
/** 周报表格:配合 scripts/weekly-report.ts 定时生成 docs/metrics.md */
|
|
34
|
+
export declare function renderMetricsTable(rows: MetricsRow[], date: string): string;
|