@spaceflow/review 0.82.0 → 1.0.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/dist/index.js CHANGED
@@ -3,34 +3,10 @@ import { access, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises
3
3
  import { basename, dirname, extname, isAbsolute, join, normalize, relative } from "path";
4
4
  import { homedir } from "os";
5
5
  import { execFileSync, execSync, spawn } from "child_process";
6
- import micromatch_0 from "micromatch";
6
+ import micromatch from "micromatch";
7
7
  import { existsSync } from "fs";
8
- var __webpack_modules__ = ({});
9
- // The module cache
10
- var __webpack_module_cache__ = {};
11
-
12
- // The require function
13
- function __webpack_require__(moduleId) {
14
-
15
- // Check if module is in cache
16
- var cachedModule = __webpack_module_cache__[moduleId];
17
- if (cachedModule !== undefined) {
18
- return cachedModule.exports;
19
- }
20
- // Create a new module (and put it into the cache)
21
- var module = (__webpack_module_cache__[moduleId] = {
22
- exports: {}
23
- });
24
- // Execute the module function
25
- __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
26
-
27
- // Return the exports of the module
28
- return module.exports;
29
-
30
- }
31
-
32
- // expose the modules object (__webpack_modules__)
33
- __webpack_require__.m = __webpack_modules__;
8
+ // The require scope
9
+ var __webpack_require__ = {};
34
10
 
35
11
  // webpack/runtime/define_property_getters
36
12
  (() => {
@@ -42,95 +18,9 @@ __webpack_require__.d = (exports, definition) => {
42
18
  }
43
19
  };
44
20
  })();
45
- // webpack/runtime/ensure_chunk
46
- (() => {
47
- __webpack_require__.f = {};
48
- // This file contains only the entry chunk.
49
- // The chunk loading function for additional chunks
50
- __webpack_require__.e = (chunkId) => {
51
- return Promise.all(
52
- Object.keys(__webpack_require__.f).reduce((promises, key) => {
53
- __webpack_require__.f[key](chunkId, promises);
54
- return promises;
55
- }, [])
56
- );
57
- };
58
- })();
59
- // webpack/runtime/get javascript chunk filename
60
- (() => {
61
- // This function allow to reference chunks
62
- __webpack_require__.u = (chunkId) => {
63
- // return url for filenames not based on template
64
-
65
- // return url for filenames based on template
66
- return "" + chunkId + ".js"
67
- }
68
- })();
69
21
  // webpack/runtime/has_own_property
70
22
  (() => {
71
23
  __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
72
- })();
73
- // webpack/runtime/public_path
74
- (() => {
75
- __webpack_require__.p = "";
76
- })();
77
- // webpack/runtime/module_chunk_loading
78
- (() => {
79
- // no BaseURI
80
- // object to store loaded and loading chunks
81
- // undefined = chunk not loaded, null = chunk preloaded/prefetched
82
- // [resolve, Promise] = chunk loading, 0 = chunk loaded
83
- var installedChunks = {"410": 0,};
84
- var installChunk = (data) => {
85
- var __rspack_esm_ids = data.__rspack_esm_ids;
86
- var __webpack_modules__ = data.__webpack_modules__;
87
- var __rspack_esm_runtime = data.__rspack_esm_runtime;
88
- // add "modules" to the modules object,
89
- // then flag all "ids" as loaded and fire callback
90
- var moduleId, chunkId, i = 0;
91
- for (moduleId in __webpack_modules__) {
92
- if (__webpack_require__.o(__webpack_modules__, moduleId)) {
93
- __webpack_require__.m[moduleId] = __webpack_modules__[moduleId];
94
- }
95
- }
96
- if (__rspack_esm_runtime) __rspack_esm_runtime(__webpack_require__);
97
- for (; i < __rspack_esm_ids.length; i++) {
98
- chunkId = __rspack_esm_ids[i];
99
- if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
100
- installedChunks[chunkId][0]();
101
- }
102
- installedChunks[__rspack_esm_ids[i]] = 0;
103
- }
104
-
105
- };
106
- __webpack_require__.f.j = function (chunkId, promises) {
107
- // import() chunk loading for javascript
108
- var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
109
- if (installedChunkData !== 0) { // 0 means "already installed".'
110
- // a Promise means "currently loading".
111
- if (installedChunkData) {
112
- promises.push(installedChunkData[1]);
113
- } else {
114
- if (true) {
115
- // setup Promise in chunk cache
116
- var promise = import("./" + __webpack_require__.u(chunkId)).then(installChunk, (e) => {
117
- if (installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;
118
- throw e;
119
- });
120
- var promise = Promise.race([promise, new Promise((resolve) => {
121
- installedChunkData = installedChunks[chunkId] = [resolve];
122
- })]);
123
- promises.push(installedChunkData[1] = promise);
124
- }
125
-
126
- }
127
- }
128
- }
129
- // no external install chunk
130
- // no on chunks loaded
131
- // no HMR
132
- // no HMR manifest
133
-
134
24
  })();
135
25
  var __webpack_exports__ = {};
136
26
 
@@ -262,14 +152,14 @@ const CODE_BLOCK_TYPES = [
262
152
  const fileStatus = STATUS_ALIAS[file.status?.toLowerCase() ?? ""] ?? "modified";
263
153
  if (!filename) return false;
264
154
  // 最终排除:命中排除模式的文件直接过滤掉
265
- if (negativeGlobs.length > 0 && micromatch_0.isMatch(filename, negativeGlobs, {
155
+ if (negativeGlobs.length > 0 && micromatch.isMatch(filename, negativeGlobs, {
266
156
  matchBase: true
267
157
  })) {
268
158
  console.log(`[filterFilesByIncludes] ${filename} excluded by negativeGlobs`);
269
159
  return false;
270
160
  }
271
161
  // 正向匹配:无前缀 glob
272
- if (plainGlobs.length > 0 && micromatch_0.isMatch(filename, plainGlobs, {
162
+ if (plainGlobs.length > 0 && micromatch.isMatch(filename, plainGlobs, {
273
163
  matchBase: true
274
164
  })) {
275
165
  console.log(`[filterFilesByIncludes] ${filename} matched plainGlobs`);
@@ -285,10 +175,10 @@ const CODE_BLOCK_TYPES = [
285
175
  const positiveGlobs = matchingStatusGlobs.filter((g)=>!g.startsWith("!"));
286
176
  const negativeStatusGlobs = matchingStatusGlobs.filter((g)=>g.startsWith("!")).map((g)=>g.slice(1));
287
177
  if (positiveGlobs.length > 0) {
288
- const matchesPositive = micromatch_0.isMatch(filename, positiveGlobs, {
178
+ const matchesPositive = micromatch.isMatch(filename, positiveGlobs, {
289
179
  matchBase: true
290
180
  });
291
- const matchesNegative = negativeStatusGlobs.length > 0 && micromatch_0.isMatch(filename, negativeStatusGlobs, {
181
+ const matchesNegative = negativeStatusGlobs.length > 0 && micromatch.isMatch(filename, negativeStatusGlobs, {
292
182
  matchBase: true
293
183
  });
294
184
  if (matchesPositive && !matchesNegative) {
@@ -308,6 +198,73 @@ const CODE_BLOCK_TYPES = [
308
198
  */ function extractGlobsFromIncludes(includes) {
309
199
  return includes.map((p)=>parseIncludePattern(p).glob).filter((g)=>g.length > 0);
310
200
  }
201
+ /**
202
+ * 检查单个文件是否匹配 includes 模式列表,支持 `status|glob` 前缀语法。
203
+ *
204
+ * - 当 `fileStatus` 未提供时,status 前缀的 includes 降级为纯 glob 匹配(向后兼容)
205
+ * - 当 `fileStatus` 提供时,完整支持 `added|`/`modified|`/`deleted|` 前缀语义
206
+ *
207
+ * 算法与 `filterFilesByIncludes` 一致,但针对单文件场景优化:
208
+ * 1. 排除模式(`!`) 优先过滤
209
+ * 2. 无前缀正向 glob 匹配
210
+ * 3. 有 status 前缀的 glob 按文件实际 status 过滤
211
+ *
212
+ * @param includes include 模式列表
213
+ * @param filename 待匹配的文件名
214
+ * @param fileStatus 文件变更状态(如 "added"/"modified"/"removed"),不提供时降级为纯 glob
215
+ * @returns 是否匹配
216
+ */ function matchIncludes(includes, filename, fileStatus) {
217
+ if (!includes || includes.length === 0) return true;
218
+ if (!filename) return false;
219
+ const parsed = includes.map(parseIncludePattern);
220
+ // 无 status 信息时降级为纯 glob 匹配(向后兼容)
221
+ if (!fileStatus) {
222
+ const globs = extractGlobsFromIncludes(includes);
223
+ if (globs.length === 0) return true;
224
+ return micromatch.isMatch(filename, globs, {
225
+ matchBase: true
226
+ });
227
+ }
228
+ const normalizedStatus = STATUS_ALIAS[fileStatus.toLowerCase()] ?? "modified";
229
+ // 排除模式(以 ! 开头),用于最终全局过滤
230
+ const negativeGlobs = parsed.filter((p)=>p.status === undefined && p.glob.startsWith("!")).map((p)=>p.glob.slice(1));
231
+ // 无前缀的正向 globs
232
+ const plainGlobs = parsed.filter((p)=>p.status === undefined && !p.glob.startsWith("!")).map((p)=>p.glob);
233
+ // 有 status 前缀的 patterns
234
+ const statusPatterns = parsed.filter((p)=>p.status !== undefined);
235
+ // 最终排除:命中排除模式的文件直接过滤掉
236
+ if (negativeGlobs.length > 0 && micromatch.isMatch(filename, negativeGlobs, {
237
+ matchBase: true
238
+ })) {
239
+ return false;
240
+ }
241
+ // 正向匹配:无前缀 glob
242
+ if (plainGlobs.length > 0 && micromatch.isMatch(filename, plainGlobs, {
243
+ matchBase: true
244
+ })) {
245
+ return true;
246
+ }
247
+ // 正向匹配:有 status 前缀的 glob,按文件实际 status 过滤
248
+ if (statusPatterns.length > 0) {
249
+ const matchingStatusGlobs = statusPatterns.filter(({ status })=>status === normalizedStatus).map(({ glob })=>glob);
250
+ if (matchingStatusGlobs.length > 0) {
251
+ const positiveGlobs = matchingStatusGlobs.filter((g)=>!g.startsWith("!"));
252
+ const negativeStatusGlobs = matchingStatusGlobs.filter((g)=>g.startsWith("!")).map((g)=>g.slice(1));
253
+ if (positiveGlobs.length > 0) {
254
+ const matchesPositive = micromatch.isMatch(filename, positiveGlobs, {
255
+ matchBase: true
256
+ });
257
+ const matchesNegative = negativeStatusGlobs.length > 0 && micromatch.isMatch(filename, negativeStatusGlobs, {
258
+ matchBase: true
259
+ });
260
+ if (matchesPositive && !matchesNegative) {
261
+ return true;
262
+ }
263
+ }
264
+ }
265
+ }
266
+ return false;
267
+ }
311
268
  /**
312
269
  * 从 whenModifiedCode 配置中解析代码结构过滤类型。
313
270
  * 只接受简单的类型名称,如 "function"、"class"、"interface"、"type"、"method"
@@ -324,6 +281,44 @@ const CODE_BLOCK_TYPES = [
324
281
  ];
325
282
  }
326
283
 
284
+ ;// CONCATENATED MODULE: ./src/prompt/specs-section.ts
285
+ /**
286
+ * 构建 specs 的 prompt 部分
287
+ */ function buildSpecsSection(specs) {
288
+ return specs.map((spec)=>{
289
+ const firstRule = spec.rules[0];
290
+ const rulesText = spec.rules.slice(1).map((rule)=>{
291
+ let text = `#### [${rule.id}] ${rule.title}\n`;
292
+ if (rule.description) {
293
+ text += `${rule.description}\n`;
294
+ }
295
+ if (rule.examples.length > 0) {
296
+ for (const example of rule.examples){
297
+ text += formatExample(example);
298
+ }
299
+ }
300
+ return text;
301
+ }).join("\n");
302
+ return `### ${firstRule.title}\n- 规范文件: ${spec.filename}\n- 适用扩展名: ${spec.extensions.join(", ")}\n\n${rulesText}`;
303
+ }).join("\n\n-------------------\n\n");
304
+ }
305
+ function formatExample(example) {
306
+ let text = "";
307
+ if (example.title) {
308
+ text += `##### ${example.title}\n`;
309
+ }
310
+ if (example.description) {
311
+ text += `${example.description}\n`;
312
+ }
313
+ for (const item of example.content){
314
+ text += formatContent(item);
315
+ }
316
+ return text;
317
+ }
318
+ function formatContent(item) {
319
+ return `###### ${item.type}${item.title ? `: ${item.title}` : ""}\n${item.description}\n`;
320
+ }
321
+
327
322
  ;// CONCATENATED MODULE: ./src/review-spec/review-spec.service.ts
328
323
 
329
324
 
@@ -838,7 +833,7 @@ class ReviewSpecService {
838
833
  const overrides = this.extractOverrides(ruleContent);
839
834
  // 提取描述:在第一个例子之前的文本
840
835
  let description = ruleContent;
841
- const firstExampleIndex = ruleContent.search(/(?:^|\n)###\s+(?:good|bad)/i);
836
+ const firstExampleIndex = ruleContent.search(/(?:^|\n)(?:####\s+(?:good|bad)|###\s+Example:)/i);
842
837
  if (firstExampleIndex !== -1) {
843
838
  description = ruleContent.slice(0, firstExampleIndex).trim();
844
839
  } else {
@@ -903,26 +898,53 @@ class ReviewSpecService {
903
898
  }
904
899
  extractExamples(content) {
905
900
  const examples = [];
906
- const sections = content.split(/(?:^|\n)###\s+/);
907
- for (const section of sections){
908
- const trimmedSection = section.trim();
909
- if (!trimmedSection) continue;
910
- let type = null;
911
- if (/^good\b/i.test(trimmedSection)) {
912
- type = "good";
913
- } else if (/^bad\b/i.test(trimmedSection)) {
914
- type = "bad";
915
- }
916
- if (!type) continue;
917
- const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
918
- let codeMatch;
919
- while((codeMatch = codeBlockRegex.exec(trimmedSection)) !== null){
920
- const lang = codeMatch[1] || "text";
921
- const code = codeMatch[2].trim();
901
+ // ### Example: 分组
902
+ const groupSections = content.split(/(?:^|\n)(?=###\s+Example:)/);
903
+ for (const groupSection of groupSections){
904
+ const trimmedGroup = groupSection.trim();
905
+ if (!trimmedGroup) continue;
906
+ // 提取分组描述,如 "### Example: 命名规则说明"
907
+ let exampleTitle = "";
908
+ let exampleDescription = "";
909
+ const groupMatch = trimmedGroup.match(/^###\s+Example\s*[::]\s*(.+)/i);
910
+ if (groupMatch) {
911
+ exampleTitle = "Example";
912
+ exampleDescription = groupMatch[1].trim();
913
+ }
914
+ // 在分组内按 #### 提取 Good/Bad
915
+ const ruleContents = [];
916
+ const sections = trimmedGroup.split(/(?:^|\n)####\s+/);
917
+ for (const section of sections){
918
+ const trimmedSection = section.trim();
919
+ if (!trimmedSection) continue;
920
+ let type = null;
921
+ let contentTitle = "";
922
+ if (/^good:/i.test(trimmedSection)) {
923
+ type = "good";
924
+ contentTitle = trimmedSection.match(/^good:\s*(.+)/i)?.[1]?.trim() ?? "";
925
+ } else if (/^bad:/i.test(trimmedSection)) {
926
+ type = "bad";
927
+ contentTitle = trimmedSection.match(/^bad:\s*(.+)/i)?.[1]?.trim() ?? "";
928
+ }
929
+ if (!type) continue;
930
+ // 提取代码块作为 description
931
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
932
+ let codeMatch;
933
+ const codeParts = [];
934
+ while((codeMatch = codeBlockRegex.exec(trimmedSection)) !== null){
935
+ codeParts.push(codeMatch[2].trim());
936
+ }
937
+ ruleContents.push({
938
+ title: contentTitle,
939
+ type,
940
+ description: codeParts.join("\n\n")
941
+ });
942
+ }
943
+ if (ruleContents.length > 0) {
922
944
  examples.push({
923
- lang,
924
- code,
925
- type
945
+ title: exampleTitle,
946
+ description: exampleDescription,
947
+ content: ruleContents
926
948
  });
927
949
  }
928
950
  }
@@ -959,7 +981,8 @@ class ReviewSpecService {
959
981
  * 根据 spec 的 includes 配置过滤 issues
960
982
  * 只保留文件名匹配对应 spec includes 模式的 issues
961
983
  * 如果 spec 没有 includes 配置,则保留该 spec 的所有 issues
962
- */ filterIssuesByIncludes(issues, specs) {
984
+ * 支持 `added|`/`modified|`/`deleted|` 前缀语法
985
+ */ filterIssuesByIncludes(issues, specs, changedFiles) {
963
986
  // 构建 spec filename -> includes 的映射
964
987
  const specIncludesMap = new Map();
965
988
  for (const spec of specs){
@@ -975,16 +998,9 @@ class ReviewSpecService {
975
998
  if (includes.length === 0) {
976
999
  return true;
977
1000
  }
978
- // 检查文件是否匹配 includes 模式(转换为纯 glob,避免 status| 前缀和 code-* 空串传入 micromatch)
979
- const globs = extractGlobsFromIncludes(includes);
980
- if (globs.length === 0) return true;
981
- const matches = micromatch_0.isMatch(issue.file, globs, {
982
- matchBase: true
983
- });
984
- if (!matches) {
985
- // console.log(` Issue [${issue.ruleId}] 在文件 ${issue.file} 不匹配 includes 模式,跳过`);
986
- }
987
- return matches;
1001
+ // 使用 matchIncludes 检查文件是否匹配 includes 模式(支持 status|glob 前缀)
1002
+ const fileStatus = changedFiles?.getStatus(issue.file);
1003
+ return matchIncludes(includes, issue.file, fileStatus);
988
1004
  });
989
1005
  }
990
1006
  /**
@@ -1017,7 +1033,7 @@ class ReviewSpecService {
1017
1033
  * @param specs - 已加载的 ReviewSpec 列表
1018
1034
  * @param verbose - 日志详细级别:1=基础统计,3=详细收集过程
1019
1035
  * @returns 过滤后的 issues 列表(排除了被 override 的规则产生的 issues)
1020
- */ filterIssuesByOverrides(issues, specs, verbose) {
1036
+ */ filterIssuesByOverrides(issues, specs, changedFiles, verbose) {
1021
1037
  // ========== 阶段1: 收集 spec -> overrides 的映射(保留作用域信息) ==========
1022
1038
  // 每个 override 需要记录其来源 spec 的 includes,用于作用域判断
1023
1039
  const scopedOverrides = [];
@@ -1073,12 +1089,9 @@ class ReviewSpecService {
1073
1089
  if (scoped.includes.length === 0) {
1074
1090
  return true;
1075
1091
  }
1076
- // 使用 micromatch 检查文件是否匹配 includes 模式(转换为纯 glob
1077
- const globs = extractGlobsFromIncludes(scoped.includes);
1078
- if (globs.length === 0) return true;
1079
- return issueFile && micromatch_0.isMatch(issueFile, globs, {
1080
- matchBase: true
1081
- });
1092
+ // 使用 matchIncludes 检查文件是否匹配 includes 模式(支持 status|glob 前缀)
1093
+ const fileStatus = changedFiles?.getStatus(issueFile);
1094
+ return matchIncludes(scoped.includes, issueFile, fileStatus);
1082
1095
  });
1083
1096
  if (matched) {
1084
1097
  skipped.push({
@@ -1177,23 +1190,7 @@ class ReviewSpecService {
1177
1190
  /**
1178
1191
  * 构建 specs 的 prompt 部分
1179
1192
  */ buildSpecsSection(specs) {
1180
- return specs.map((spec)=>{
1181
- const firstRule = spec.rules[0];
1182
- const rulesText = spec.rules.slice(1).map((rule)=>{
1183
- let text = `#### [${rule.id}] ${rule.title}\n`;
1184
- if (rule.description) {
1185
- text += `${rule.description}\n`;
1186
- }
1187
- if (rule.examples.length > 0) {
1188
- for (const example of rule.examples){
1189
- text += `##### ${example.type === "good" ? "推荐做法 (Good)" : "不推荐做法 (Bad)"}\n`;
1190
- text += `\`\`\`${example.lang}\n${example.code}\n\`\`\`\n`;
1191
- }
1192
- }
1193
- return text;
1194
- }).join("\n");
1195
- return `### ${firstRule.title}\n- 规范文件: ${spec.filename}\n- 适用扩展名: ${spec.extensions.join(", ")}\n\n${rulesText}`;
1196
- }).join("\n\n-------------------\n\n");
1193
+ return buildSpecsSection(specs);
1197
1194
  }
1198
1195
  /**
1199
1196
  * 根据 ruleId 查找规则定义
@@ -3263,6 +3260,15 @@ function generateIssueKey(issue) {
3263
3260
  map(fn) {
3264
3261
  return this._files.map(fn);
3265
3262
  }
3263
+ /**
3264
+ * 获取指定文件的变更状态
3265
+ */ getStatus(filename) {
3266
+ if (!filename) return undefined;
3267
+ for (const f of this._files){
3268
+ if (f.filename === filename) return f.status;
3269
+ }
3270
+ return undefined;
3271
+ }
3266
3272
  countByStatus() {
3267
3273
  let added = 0, modified = 0, deleted = 0;
3268
3274
  for (const f of this._files){
@@ -4753,7 +4759,6 @@ function checkMaxLinesPerFile(changedFiles, fileContents, rule, round, verbose)
4753
4759
 
4754
4760
 
4755
4761
 
4756
-
4757
4762
  class ReviewLlmProcessor {
4758
4763
  llmProxyService;
4759
4764
  reviewSpecService;
@@ -4786,7 +4791,8 @@ class ReviewLlmProcessor {
4786
4791
  * 根据文件过滤 specs,只返回与该文件匹配的规则
4787
4792
  * - 如果 spec 有 includes 配置,只有当文件名匹配 includes 模式时才包含该 spec
4788
4793
  * - 如果 spec 没有 includes 配置,则按扩展名匹配
4789
- */ filterSpecsForFile(specs, filename) {
4794
+ * - 支持 `added|`/`modified|`/`deleted|` 前缀语法,需传入 fileStatus
4795
+ */ filterSpecsForFile(specs, filename, fileStatus) {
4790
4796
  const ext = extname(filename).slice(1).toLowerCase();
4791
4797
  if (!ext) return [];
4792
4798
  return specs.filter((spec)=>{
@@ -4795,13 +4801,9 @@ class ReviewLlmProcessor {
4795
4801
  return false;
4796
4802
  }
4797
4803
  // 如果有 includes 配置,检查文件名是否匹配 includes 模式
4798
- // 需先提取纯 glob(去掉 added|/modified| 前缀,过滤 code-* 空串),避免 micromatch 报错
4804
+ // 使用 matchIncludes 支持 status|glob 前缀语法
4799
4805
  if (spec.includes.length > 0) {
4800
- const globs = extractGlobsFromIncludes(spec.includes);
4801
- if (globs.length === 0) return true;
4802
- return micromatch_0.isMatch(filename, globs, {
4803
- matchBase: true
4804
- });
4806
+ return matchIncludes(spec.includes, filename, fileStatus);
4805
4807
  }
4806
4808
  // 没有 includes 配置,扩展名匹配即可
4807
4809
  return true;
@@ -4833,7 +4835,7 @@ class ReviewLlmProcessor {
4833
4835
  const filePrompts = await Promise.all(fileDataList.filter((item)=>item !== null).map(async ({ filename, file, contentLines, commitsSection })=>{
4834
4836
  const fileDirectoryInfo = await this.getFileDirectoryInfo(filename);
4835
4837
  // 根据文件过滤 specs,只注入与当前文件匹配的规则
4836
- const fileSpecs = this.filterSpecsForFile(specs, filename);
4838
+ const fileSpecs = this.filterSpecsForFile(specs, filename, file.status);
4837
4839
  // 从全局 whenModifiedCode 配置中解析代码结构过滤类型
4838
4840
  const codeBlockTypes = whenModifiedCode ? extractCodeBlockTypes(whenModifiedCode) : [];
4839
4841
  // 构建带行号的内容:有 code-* 过滤时只输出匹配的代码块范围
@@ -5293,7 +5295,7 @@ class ReviewLlmProcessor {
5293
5295
  ({ commits, changedFiles } = await this.applyPreFilters(context, commits, changedFiles, isDirectFileMode));
5294
5296
  const headSha = prModel ? await prModel.getHeadSha() : context.headRef || "HEAD";
5295
5297
  const collectedFiles = ChangedFileCollection.from(changedFiles);
5296
- const fileContents = await this.getFileContents(context.owner, context.repo, collectedFiles.toArray(), commits, headSha, context.prNumber, isLocalMode, context.verbose);
5298
+ const fileContents = await this.getFileContents(context.owner, context.repo, collectedFiles.toArray(), commits, headSha, context.prNumber, isLocalMode, context.showAll, context.verbose);
5297
5299
  return {
5298
5300
  prModel,
5299
5301
  commits,
@@ -5435,18 +5437,20 @@ class ReviewLlmProcessor {
5435
5437
  * 2. --commits — 仅保留用户指定的 commit 及其涉及的文件
5436
5438
  * 3. --includes — glob 模式过滤文件和 commits(支持 status| 前缀语法)
5437
5439
  */ async applyPreFilters(context, commits, rawChangedFiles, isDirectFileMode) {
5438
- const { owner, repo, prNumber, verbose, includes, files, commits: filterCommits } = context;
5440
+ const { owner, repo, prNumber, verbose, includes, files, commits: filterCommits, showAll } = context;
5439
5441
  let changedFiles = ChangedFileCollection.from(rawChangedFiles);
5440
- // 0. 过滤掉 merge commit
5441
- {
5442
+ // 0. 过滤掉 merge commit(showAll=false 时启用)
5443
+ if (!showAll) {
5442
5444
  const before = commits.length;
5443
5445
  commits = commits.filter((c)=>{
5444
5446
  const message = c.commit?.message || "";
5445
- return !message.startsWith("Merge ");
5447
+ return !/^merge\b/i.test(message);
5446
5448
  });
5447
5449
  if (before !== commits.length && shouldLog(verbose, 1)) {
5448
5450
  console.log(` 跳过 Merge Commits: ${before} -> ${commits.length} 个`);
5449
5451
  }
5452
+ } else if (shouldLog(verbose, 2)) {
5453
+ console.log(` showAll=true,跳过 Merge Commit 过滤`);
5450
5454
  }
5451
5455
  // 1. 按指定的 files 过滤
5452
5456
  if (files && files.length > 0) {
@@ -5502,7 +5506,7 @@ class ReviewLlmProcessor {
5502
5506
  for (const commit of commits){
5503
5507
  if (!commit.sha) continue;
5504
5508
  const commitFiles = await this.issueFilter.getFilesForCommit(owner, repo, commit.sha, prNumber);
5505
- if (micromatch_0.some(commitFiles, globs)) {
5509
+ if (micromatch.some(commitFiles, globs)) {
5506
5510
  filteredCommits.push(commit);
5507
5511
  }
5508
5512
  }
@@ -5520,9 +5524,11 @@ class ReviewLlmProcessor {
5520
5524
  /**
5521
5525
  * 获取文件内容并构建行号到 commit hash 的映射
5522
5526
  * 返回 Map<filename, Array<[commitHash, lineCode]>>
5523
- */ async getFileContents(owner, repo, changedFiles, commits, ref, prNumber, isLocalMode, verbose) {
5527
+ */ async getFileContents(owner, repo, changedFiles, commits, ref, prNumber, isLocalMode, showAll, verbose) {
5524
5528
  const contents = new Map();
5525
5529
  const latestCommitHash = commits[commits.length - 1]?.sha?.slice(0, 7) || "+local+";
5530
+ const validCommitHashes = new Set(commits.map((c)=>c.sha?.slice(0, 7)).filter(Boolean));
5531
+ const shouldMaskUnknownChangedLines = !showAll && validCommitHashes.size > 0;
5526
5532
  if (shouldLog(verbose, 1)) {
5527
5533
  console.log(`📊 正在构建行号到变更的映射...`);
5528
5534
  }
@@ -5577,6 +5583,12 @@ class ReviewLlmProcessor {
5577
5583
  ];
5578
5584
  }
5579
5585
  const hash = blameMap?.get(lineNum) ?? latestCommitHash;
5586
+ if (shouldMaskUnknownChangedLines && !validCommitHashes.has(hash)) {
5587
+ return [
5588
+ "-------",
5589
+ line
5590
+ ];
5591
+ }
5580
5592
  return [
5581
5593
  hash,
5582
5594
  line
@@ -5868,7 +5880,7 @@ class ReviewService {
5868
5880
  */ filterNewIssues(issues, specs, opts) {
5869
5881
  const { commits, fileContents, changedFiles, isDirectFileMode, context } = opts;
5870
5882
  const { verbose } = context;
5871
- let filtered = this.reviewSpecService.filterIssuesByIncludes(issues, specs);
5883
+ let filtered = this.reviewSpecService.filterIssuesByIncludes(issues, specs, changedFiles);
5872
5884
  if (shouldLog(verbose, 1)) {
5873
5885
  console.log(` 应用 includes 过滤后: ${filtered.length} 个问题`);
5874
5886
  }
@@ -5876,7 +5888,7 @@ class ReviewService {
5876
5888
  if (shouldLog(verbose, 1)) {
5877
5889
  console.log(` 应用规则存在性过滤后: ${filtered.length} 个问题`);
5878
5890
  }
5879
- filtered = this.reviewSpecService.filterIssuesByOverrides(filtered, specs, verbose);
5891
+ filtered = this.reviewSpecService.filterIssuesByOverrides(filtered, specs, changedFiles, verbose);
5880
5892
  // 变更行过滤
5881
5893
  if (shouldLog(verbose, 3)) {
5882
5894
  console.log(` 🔍 变更行过滤条件检查:`);
@@ -6014,7 +6026,11 @@ class ReviewService {
6014
6026
  console.log(`📋 找到 ${resultModel.issues.length} 个历史问题`);
6015
6027
  }
6016
6028
  // 2. 获取 commits 并填充 author 信息
6017
- const commits = await prModel.getCommits();
6029
+ const allCommits = await prModel.getCommits();
6030
+ const commits = context.showAll ? allCommits : allCommits.filter((c)=>!/^merge\b/i.test(c.commit?.message || ""));
6031
+ if (allCommits.length !== commits.length && shouldLog(verbose, 1)) {
6032
+ console.log(` 跳过 Merge Commits: ${allCommits.length} -> ${commits.length} 个`);
6033
+ }
6018
6034
  resultModel.issues = await this.contextBuilder.fillIssueAuthors(resultModel.issues, commits, owner, repo, verbose);
6019
6035
  // 3. 同步已解决的评论状态
6020
6036
  await resultModel.syncResolved();
@@ -6026,7 +6042,7 @@ class ReviewService {
6026
6042
  const changedFiles = await prModel.getFiles();
6027
6043
  const headSha = await prModel.getHeadSha();
6028
6044
  const verifySpecs = await this.issueFilter.loadSpecs(context.specSources, verbose);
6029
- const verifyFileContents = await this.sourceResolver.getFileContents(owner, repo, changedFiles, commits, headSha, prNumber, false, verbose);
6045
+ const verifyFileContents = await this.sourceResolver.getFileContents(owner, repo, changedFiles, commits, headSha, prNumber, false, context.showAll, verbose);
6030
6046
  resultModel.issues = await this.issueFilter.verifyAndUpdateIssues(context, resultModel.issues, commits, {
6031
6047
  specs: verifySpecs,
6032
6048
  fileContents: verifyFileContents
@@ -6464,7 +6480,7 @@ class DeletionImpactService {
6464
6480
  const beforeCount = deletedBlocks.length;
6465
6481
  const globs = extractGlobsFromIncludes(context.includes);
6466
6482
  const filenames = deletedBlocks.map((b)=>b.file);
6467
- const matchedFilenames = micromatch_0(filenames, globs);
6483
+ const matchedFilenames = micromatch(filenames, globs);
6468
6484
  deletedBlocks = deletedBlocks.filter((b)=>matchedFilenames.includes(b.file));
6469
6485
  if (shouldLog(verbose, 1)) {
6470
6486
  console.log(` 🔍 Includes 过滤: ${beforeCount} -> ${deletedBlocks.length} 个删除块`);
@@ -7153,16 +7169,9 @@ const tools = [
7153
7169
  filename: filePath
7154
7170
  }
7155
7171
  ]));
7156
- const micromatchModule = await __webpack_require__.e(/* import() */ "551").then(__webpack_require__.bind(__webpack_require__, 946));
7157
- const micromatch = micromatchModule.default || micromatchModule;
7158
7172
  const rules = applicableSpecs.flatMap((spec)=>spec.rules.filter((rule)=>{
7159
7173
  const includes = rule.includes || spec.includes;
7160
- if (includes.length === 0) return true;
7161
- const globs = extractGlobsFromIncludes(includes);
7162
- if (globs.length === 0) return true;
7163
- return micromatch.isMatch(filePath, globs, {
7164
- matchBase: true
7165
- });
7174
+ return matchIncludes(includes, filePath);
7166
7175
  }).map((rule)=>({
7167
7176
  id: rule.id,
7168
7177
  title: rule.title,
@@ -7171,9 +7180,13 @@ const tools = [
7171
7180
  specFile: spec.filename,
7172
7181
  ...includeExamples && rule.examples.length > 0 ? {
7173
7182
  examples: rule.examples.map((ex)=>({
7174
- type: ex.type,
7175
- lang: ex.lang,
7176
- code: ex.code
7183
+ title: ex.title,
7184
+ description: ex.description,
7185
+ content: ex.content.map((c)=>({
7186
+ title: c.title,
7187
+ type: c.type,
7188
+ description: c.description
7189
+ }))
7177
7190
  }))
7178
7191
  } : {}
7179
7192
  })));
@@ -7212,9 +7225,13 @@ const tools = [
7212
7225
  includes: spec.includes,
7213
7226
  overrides: rule.overrides,
7214
7227
  examples: rule.examples.map((ex)=>({
7215
- type: ex.type,
7216
- lang: ex.lang,
7217
- code: ex.code
7228
+ title: ex.title,
7229
+ description: ex.description,
7230
+ content: ex.content.map((c)=>({
7231
+ title: c.title,
7232
+ type: c.type,
7233
+ description: c.description
7234
+ }))
7218
7235
  }))
7219
7236
  };
7220
7237
  }
@@ -7246,9 +7263,13 @@ const tools = [
7246
7263
  includes: spec.includes,
7247
7264
  ...includeExamples && rule.examples.length > 0 ? {
7248
7265
  examples: rule.examples.map((ex)=>({
7249
- type: ex.type,
7250
- lang: ex.lang,
7251
- code: ex.code
7266
+ title: ex.title,
7267
+ description: ex.description,
7268
+ content: ex.content.map((c)=>({
7269
+ title: c.title,
7270
+ type: c.type,
7271
+ description: c.description
7272
+ }))
7252
7273
  }))
7253
7274
  } : {
7254
7275
  hasExamples: rule.examples.length > 0