frontend-guardian-core 3.7.6 → 3.9.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/bin/fg-core.js CHANGED
@@ -5,76 +5,76 @@
5
5
  */
6
6
 
7
7
  import { writeFileSync } from "node:fs";
8
- import { join } from "node:path";
8
+ import { join, resolve } from "node:path";
9
+ import pc from "picocolors";
9
10
  import {
10
- createEngine,
11
- i18nRules,
12
- performanceRules,
11
+ AIFixSuggester,
13
12
  a11yRules,
14
- securityRules,
15
- namingRules,
16
- crossFileRules,
13
+ BaselineManager,
14
+ buildNotificationPayload,
15
+ CodeownersParser,
16
+ compareHistoryReports,
17
+ complianceReportToMarkdown,
17
18
  componentRules,
18
- hooksRules,
19
- platformRules,
20
- svelteRules,
21
- e2eRules,
22
- detectE2EGaps,
23
- formatE2EGapReport,
24
- formatE2EGapJson,
25
- installGitHooks,
26
- uninstallGitHooks,
27
- hasGitHook,
28
- generateCIConfig,
19
+ createEngine,
20
+ createPublisher,
21
+ crossFileRules,
22
+ detectAIConfig,
29
23
  detectCIProvider,
30
- generateSarif,
31
- formatAllAnnotations,
32
- isGitHubActions,
33
- writeJobSummary,
34
- BaselineManager,
35
- initConfig,
24
+ detectDashboardConfig,
25
+ detectE2EGaps,
26
+ detectFixBotConfig,
27
+ detectMonorepo,
28
+ detectNotificationConfig,
36
29
  detectProjectMeta,
37
- generatePRComment,
38
30
  detectPublisherConfig,
39
- createPublisher,
40
- uploadReport,
41
31
  detectUploadConfig,
42
- SmartCache,
43
- runFixBot,
44
- detectFixBotConfig,
45
- HistoryReport,
46
- generateDashboard,
47
- detectMonorepo,
48
- scanWorkspace,
49
- formatWorkspaceReport,
50
- formatWorkspaceJson,
51
- AIFixSuggester,
52
- detectAIConfig,
53
- compareHistoryReports,
32
+ e2eRules,
33
+ FileWatcher,
34
+ findRouteFiles,
35
+ formatAllAnnotations,
36
+ formatE2EGapJson,
37
+ formatE2EGapReport,
54
38
  formatHistoryCompare,
55
39
  formatHistoryCompareJson,
56
- CodeownersParser,
57
- sendNotifications,
58
- detectNotificationConfig,
59
- buildNotificationPayload,
40
+ formatPageHealthJson,
41
+ formatPageHealthReport,
42
+ formatWorkspaceJson,
43
+ formatWorkspaceReport,
44
+ generateCIConfig,
60
45
  generateComplianceReport,
61
- complianceReportToMarkdown,
62
- saveComplianceReport,
63
- uploadToDashboardServer,
64
- detectDashboardConfig,
65
- playwrightIntegration,
46
+ generateDashboard,
47
+ generatePRComment,
48
+ generateSarif,
49
+ HistoryReport,
50
+ hasGitHook,
51
+ hooksRules,
52
+ i18nRules,
53
+ initConfig,
54
+ installGitHooks,
55
+ isGitHubActions,
56
+ isPlaywrightAvailable,
57
+ namingRules,
66
58
  ProjectIndexer,
67
- FileWatcher,
68
59
  parseAllRoutes,
69
- findRouteFiles,
60
+ performanceRules,
61
+ platformRules,
62
+ playwrightIntegration,
63
+ runFixBot,
70
64
  runPageHealthCheck,
71
- isPlaywrightAvailable,
72
- formatPageHealthReport,
73
- formatPageHealthJson,
74
- uploadPageHealthResult,
65
+ SmartCache,
66
+ saveComplianceReport,
67
+ scanWorkspace,
68
+ securityRules,
69
+ sendNotifications,
70
+ svelteRules,
75
71
  toScanResult,
72
+ uninstallGitHooks,
73
+ uploadPageHealthResult,
74
+ uploadReport,
75
+ uploadToDashboardServer,
76
+ writeJobSummary,
76
77
  } from "../dist/index.js";
77
- import pc from "picocolors";
78
78
  import { runWatchMode } from "./watch-mode.js";
79
79
 
80
80
  const MODULES = [
@@ -107,7 +107,7 @@ const MODULE_RULES = {
107
107
 
108
108
  function showHelp() {
109
109
  console.log(`
110
- Frontend Guardian Core v3.7.6
110
+ Frontend Guardian Core v3.9.0
111
111
 
112
112
  Usage:
113
113
  fg-core <project-dir> [options]
@@ -179,6 +179,8 @@ Options:
179
179
  --build-index 建立项目索引(预索引文件结构、符号、路由)
180
180
  --watch-index 监听文件变更并自动同步索引
181
181
  --index-status 查看项目索引状态
182
+ --recommend-tests 智能测试推荐:基于变更影响分析推荐需要运行的测试(可配合 --staged/--diff/--auto-scope/--files)
183
+ --mcp 启动 MCP Server(stdio,供 AI Agent 调用)
182
184
  --help, -h 显示帮助
183
185
 
184
186
  Examples:
@@ -267,6 +269,8 @@ async function main() {
267
269
  buildIndex: false,
268
270
  watchIndex: false,
269
271
  indexStatus: false,
272
+ recommendTests: false,
273
+ mcp: false,
270
274
  };
271
275
 
272
276
  for (let i = 0; i < args.length; i++) {
@@ -476,6 +480,12 @@ async function main() {
476
480
  case "--index-status":
477
481
  options.indexStatus = true;
478
482
  break;
483
+ case "--recommend-tests":
484
+ options.recommendTests = true;
485
+ break;
486
+ case "--mcp":
487
+ options.mcp = true;
488
+ break;
479
489
  case "--help":
480
490
  case "-h":
481
491
  showHelp();
@@ -483,6 +493,17 @@ async function main() {
483
493
  }
484
494
  }
485
495
 
496
+ // v3.8.0: 启动 MCP Server(AI Agent 集成)
497
+ if (options.mcp) {
498
+ const { runMCPServer } = await import("../dist/index.js");
499
+ runMCPServer({
500
+ projectDir: options.projectDir,
501
+ configFile: options.configFile,
502
+ minSeverity: options.minSeverity,
503
+ });
504
+ return;
505
+ }
506
+
486
507
  // v3.6.0: E2E 测试覆盖缺口检测
487
508
  if (options.e2eDetectGaps) {
488
509
  const gapResult = detectE2EGaps({ projectDir: options.projectDir });
@@ -512,12 +533,18 @@ async function main() {
512
533
  const duration = Date.now() - start;
513
534
 
514
535
  if (options.json) {
515
- console.log(JSON.stringify({
516
- tool: "Playwright",
517
- total: issues.length,
518
- duration,
519
- issues,
520
- }, null, 2));
536
+ console.log(
537
+ JSON.stringify(
538
+ {
539
+ tool: "Playwright",
540
+ total: issues.length,
541
+ duration,
542
+ issues,
543
+ },
544
+ null,
545
+ 2
546
+ )
547
+ );
521
548
  } else {
522
549
  console.log(pc.cyan(`📊 Playwright 测试结果`));
523
550
  console.log(pc.gray(` 耗时: ${duration}ms`));
@@ -553,7 +580,7 @@ async function main() {
553
580
 
554
581
  if (!options.baseUrl && !options.serveCommand) {
555
582
  console.log(pc.yellow("⚠️ 请指定 --base-url 或 --serve"));
556
- console.log(pc.gray(" 示例: fg-core . --page-health --serve \"npm run dev\" --port 5173"));
583
+ console.log(pc.gray(' 示例: fg-core . --page-health --serve "npm run dev" --port 5173'));
557
584
  console.log(pc.gray(" 示例: fg-core . --page-health --base-url http://localhost:3000"));
558
585
  process.exit(1);
559
586
  }
@@ -587,7 +614,8 @@ async function main() {
587
614
  if (result.issues.length > 0) {
588
615
  console.log(pc.cyan(`📋 发现 ${result.issues.length} 个问题:`));
589
616
  for (const issue of result.issues) {
590
- const color = issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue;
617
+ const color =
618
+ issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue;
591
619
  console.log(color(` [${issue.severity.toUpperCase()}] ${issue.title}`));
592
620
  console.log(pc.gray(` ${issue.description.split("\n")[0]}`));
593
621
  }
@@ -605,11 +633,7 @@ async function main() {
605
633
  if (!options.json) {
606
634
  console.log(pc.gray(`\n 正在上报到看板服务器: ${options.server}`));
607
635
  }
608
- const uploadResult = await uploadPageHealthResult(
609
- result,
610
- options.projectDir,
611
- dashboardConfig
612
- );
636
+ const uploadResult = await uploadPageHealthResult(result, options.projectDir, dashboardConfig);
613
637
  if (!options.json) {
614
638
  if (uploadResult.success) {
615
639
  console.log(pc.green(` ✅ 已上报到看板服务器`));
@@ -653,6 +677,29 @@ async function main() {
653
677
  process.exit(0);
654
678
  }
655
679
 
680
+ // v3.9.0: 智能测试推荐
681
+ if (options.recommendTests) {
682
+ const { recommendTests, formatRecommendations, formatRecommendationsJson } = await import("../dist/index.js");
683
+ const changedFiles = options.files?.map((f) => resolve(options.projectDir, f));
684
+
685
+ const result = await recommendTests({
686
+ projectDir: options.projectDir,
687
+ changedFiles,
688
+ staged: options.staged,
689
+ diffRange: options.diffRange,
690
+ autoScope: options.autoScope,
691
+ minPriority: 1,
692
+ });
693
+
694
+ if (options.json) {
695
+ console.log(JSON.stringify(formatRecommendationsJson(result), null, 2));
696
+ } else {
697
+ console.log(formatRecommendations(result));
698
+ }
699
+
700
+ process.exit(result.uncoveredChanges.length > 0 ? 1 : 0);
701
+ }
702
+
656
703
  // v3.7.0: 建立项目索引
657
704
  if (options.buildIndex) {
658
705
  console.log(pc.cyan("📦 正在建立项目索引..."));
@@ -660,13 +707,7 @@ async function main() {
660
707
 
661
708
  const { globbySync } = require("globby");
662
709
  const patterns = ["**/*.{js,ts,jsx,tsx,vue}"];
663
- const exclude = [
664
- "**/node_modules/**",
665
- "**/dist/**",
666
- "**/build/**",
667
- "**/.git/**",
668
- "**/coverage/**",
669
- ];
710
+ const exclude = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/coverage/**"];
670
711
  const files = globbySync(patterns, {
671
712
  cwd: options.projectDir,
672
713
  ignore: exclude,
@@ -692,14 +733,20 @@ async function main() {
692
733
  projectDir: options.projectDir,
693
734
  onChange: (changed, deleted) => {
694
735
  if (changed.length > 0) {
695
- console.log(pc.cyan(`\n📝 变更: ${changed.map((f) => relative(options.projectDir, f)).join(", ")}`));
736
+ console.log(
737
+ pc.cyan(`\n📝 变更: ${changed.map((f) => relative(options.projectDir, f)).join(", ")}`)
738
+ );
696
739
  }
697
740
  if (deleted.length > 0) {
698
- console.log(pc.yellow(`\n🗑️ 删除: ${deleted.map((f) => relative(options.projectDir, f)).join(", ")}`));
741
+ console.log(
742
+ pc.yellow(`\n🗑️ 删除: ${deleted.map((f) => relative(options.projectDir, f)).join(", ")}`)
743
+ );
699
744
  }
700
745
  },
701
746
  onIndexUpdate: (stats) => {
702
- console.log(pc.gray(` 索引已更新 | 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`));
747
+ console.log(
748
+ pc.gray(` 索引已更新 | 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`)
749
+ );
703
750
  },
704
751
  onError: (err) => {
705
752
  console.error(pc.red(` 监听错误: ${err.message}`));
@@ -756,7 +803,9 @@ async function main() {
756
803
  console.log(pc.gray(" 使用 --init-config --force 覆盖(或使用 --init-config 直接覆盖)"));
757
804
  } else if (result.created) {
758
805
  console.log(pc.green(` ✅ 已创建: ${result.path}`));
759
- console.log(pc.gray(` 框架: ${meta.framework ?? "auto-detect"} | 组件库: ${meta.componentLib ?? "auto-detect"}`));
806
+ console.log(
807
+ pc.gray(` 框架: ${meta.framework ?? "auto-detect"} | 组件库: ${meta.componentLib ?? "auto-detect"}`)
808
+ );
760
809
  }
761
810
  process.exit(0);
762
811
  }
@@ -796,16 +845,28 @@ async function main() {
796
845
  if (filtered.length === 0) {
797
846
  console.log(pc.gray(" 暂无历史记录"));
798
847
  } else {
799
- console.log(` ${"时间".padEnd(20)} ${"模块".padEnd(15)} ${"C".padStart(4)} ${"W".padStart(4)} ${"S".padStart(4)}`);
848
+ console.log(
849
+ ` ${"时间".padEnd(20)} ${"模块".padEnd(15)} ${"C".padStart(4)} ${"W".padStart(4)} ${"S".padStart(4)}`
850
+ );
800
851
  console.log(pc.gray(" " + "-".repeat(50)));
801
852
  for (const r of filtered) {
802
- const time = new Date(r.timestamp).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
853
+ const time = new Date(r.timestamp).toLocaleString("zh-CN", {
854
+ month: "2-digit",
855
+ day: "2-digit",
856
+ hour: "2-digit",
857
+ minute: "2-digit",
858
+ });
803
859
  const c = pc.red(String(r.counts.critical).padStart(4));
804
860
  const w = pc.yellow(String(r.counts.warning).padStart(4));
805
861
  const s = pc.blue(String(r.counts.suggestion).padStart(4));
806
862
  console.log(` ${time.padEnd(20)} ${r.module.padEnd(15)} ${c} ${w} ${s}`);
807
863
  }
808
- console.log(pc.gray(` 共 ${reports.length} 条记录` + (options.historyModule ? `,已按模块 "${options.historyModule}" 过滤` : "")));
864
+ console.log(
865
+ pc.gray(
866
+ ` 共 ${reports.length} 条记录` +
867
+ (options.historyModule ? `,已按模块 "${options.historyModule}" 过滤` : "")
868
+ )
869
+ );
809
870
  }
810
871
  process.exit(0);
811
872
  }
@@ -840,9 +901,7 @@ async function main() {
840
901
  console.log(pc.yellow("⚠️ 暂无历史扫描记录,无法生成看板。请先运行 --save-report 生成历史数据。"));
841
902
  process.exit(1);
842
903
  }
843
- const fullReports = reportList
844
- .map((r) => hr.loadReport(r.filename))
845
- .filter(Boolean);
904
+ const fullReports = reportList.map((r) => hr.loadReport(r.filename)).filter(Boolean);
846
905
  const outputPath = generateDashboard(fullReports, { projectDir: options.projectDir });
847
906
  console.log(pc.cyan("📊 趋势看板已生成"));
848
907
  console.log(pc.green(` ✅ ${outputPath}`));
@@ -855,7 +914,11 @@ async function main() {
855
914
  if (options.monorepo) {
856
915
  const mono = detectMonorepo(options.projectDir);
857
916
  if (!mono.isMonorepo) {
858
- console.log(pc.yellow(`⚠️ 未检测到 monorepo workspace 配置(pnpm-workspace.yaml / lerna.json / nx.json / package.json workspaces)`));
917
+ console.log(
918
+ pc.yellow(
919
+ `⚠️ 未检测到 monorepo workspace 配置(pnpm-workspace.yaml / lerna.json / nx.json / package.json workspaces)`
920
+ )
921
+ );
859
922
  console.log(pc.gray(` 将在项目根目录执行常规扫描...`));
860
923
  if (options.module === "all") {
861
924
  await runAllModules(options);
@@ -906,13 +969,24 @@ async function main() {
906
969
  if (!pr.success || pr.result.total === 0) continue;
907
970
 
908
971
  console.log(pc.cyan(`\n📦 ${pr.package.name} (${pr.package.path})`));
909
- const allIssues = [...pr.result.issues.critical, ...pr.result.issues.warning, ...pr.result.issues.suggestion];
972
+ const allIssues = [
973
+ ...pr.result.issues.critical,
974
+ ...pr.result.issues.warning,
975
+ ...pr.result.issues.suggestion,
976
+ ];
910
977
  for (const issue of allIssues) {
911
- printIssue(issue, issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue);
978
+ printIssue(
979
+ issue,
980
+ issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue
981
+ );
912
982
  }
913
983
  }
914
984
 
915
- console.log(pc.gray(`\n⏱️ 总耗时: ${workspaceResult.summary.totalDuration}ms | 扫描 ${workspaceResult.summary.totalFilesScanned} 个文件`));
985
+ console.log(
986
+ pc.gray(
987
+ `\n⏱️ 总耗时: ${workspaceResult.summary.totalDuration}ms | 扫描 ${workspaceResult.summary.totalFilesScanned} 个文件`
988
+ )
989
+ );
916
990
 
917
991
  if (workspaceResult.summary.issuesBySeverity.critical > 0) {
918
992
  process.exit(1);
@@ -1118,9 +1192,7 @@ async function runAllModules(options, cacheInstance) {
1118
1192
  } else {
1119
1193
  if (!options.json) {
1120
1194
  console.log(
1121
- pc.yellow(
1122
- " ⚠️ 未检测到 Fix Bot 配置。请设置 FG_FIX_BOT_PROVIDER 和 FG_FIX_BOT_TOKEN 环境变量。"
1123
- )
1195
+ pc.yellow(" ⚠️ 未检测到 Fix Bot 配置。请设置 FG_FIX_BOT_PROVIDER 和 FG_FIX_BOT_TOKEN 环境变量。")
1124
1196
  );
1125
1197
  console.log("");
1126
1198
  }
@@ -1208,14 +1280,9 @@ async function runAllModules(options, cacheInstance) {
1208
1280
  // 构建发布器配置
1209
1281
  let pubConfig = detectPublisherConfig();
1210
1282
  if (!pubConfig && options.commentProvider) {
1211
- const token =
1212
- options.commentProvider === "github"
1213
- ? process.env.GITHUB_TOKEN
1214
- : process.env.GITLAB_TOKEN;
1283
+ const token = options.commentProvider === "github" ? process.env.GITHUB_TOKEN : process.env.GITLAB_TOKEN;
1215
1284
  const repo =
1216
- options.commentProvider === "github"
1217
- ? process.env.GITHUB_REPOSITORY
1218
- : process.env.CI_PROJECT_ID;
1285
+ options.commentProvider === "github" ? process.env.GITHUB_REPOSITORY : process.env.CI_PROJECT_ID;
1219
1286
  if (token && repo && options.prNumber) {
1220
1287
  pubConfig = {
1221
1288
  provider: options.commentProvider,
@@ -1231,9 +1298,7 @@ async function runAllModules(options, cacheInstance) {
1231
1298
  const publisher = createPublisher(pubConfig);
1232
1299
  const result = await publisher.publish(commentBody);
1233
1300
  if (result.success) {
1234
- console.log(
1235
- pc.cyan(`💬 PR/MR 评论已${result.action === "updated" ? "更新" : "发布"}`)
1236
- );
1301
+ console.log(pc.cyan(`💬 PR/MR 评论已${result.action === "updated" ? "更新" : "发布"}`));
1237
1302
  if (result.commentUrl) {
1238
1303
  console.log(pc.gray(` ${result.commentUrl}`));
1239
1304
  }
@@ -1254,7 +1319,7 @@ async function runAllModules(options, cacheInstance) {
1254
1319
  }
1255
1320
 
1256
1321
  // 生成并写入报告文件(--output)
1257
- let reportPath = options.output;
1322
+ const reportPath = options.output;
1258
1323
  if (reportPath) {
1259
1324
  const reportBody = generatePRComment(
1260
1325
  allResults,
@@ -1283,25 +1348,27 @@ async function runAllModules(options, cacheInstance) {
1283
1348
  if (options.upload) {
1284
1349
  const uploadConfig = detectUploadConfig();
1285
1350
  if (uploadConfig) {
1286
- const targetPath = reportPath || (() => {
1287
- const tmpPath = join(process.cwd(), `fg-report-${Date.now()}.md`);
1288
- const reportBody = generatePRComment(
1289
- allResults,
1290
- {
1291
- timestamp: new Date().toISOString(),
1292
- duration: totalDuration,
1293
- filesScanned: totalFilesScanned,
1294
- },
1295
- {
1296
- external: externalResults.length > 0 ? externalResults : undefined,
1297
- fixResult: fixResult
1298
- ? { fixedCount: fixResult.fixedCount, filesModified: fixResult.filesModified }
1299
- : null,
1300
- }
1301
- );
1302
- writeFileSync(tmpPath, reportBody, "utf-8");
1303
- return tmpPath;
1304
- })();
1351
+ const targetPath =
1352
+ reportPath ||
1353
+ (() => {
1354
+ const tmpPath = join(process.cwd(), `fg-report-${Date.now()}.md`);
1355
+ const reportBody = generatePRComment(
1356
+ allResults,
1357
+ {
1358
+ timestamp: new Date().toISOString(),
1359
+ duration: totalDuration,
1360
+ filesScanned: totalFilesScanned,
1361
+ },
1362
+ {
1363
+ external: externalResults.length > 0 ? externalResults : undefined,
1364
+ fixResult: fixResult
1365
+ ? { fixedCount: fixResult.fixedCount, filesModified: fixResult.filesModified }
1366
+ : null,
1367
+ }
1368
+ );
1369
+ writeFileSync(tmpPath, reportBody, "utf-8");
1370
+ return tmpPath;
1371
+ })();
1305
1372
  const uploadResult = await uploadReport(targetPath, uploadConfig);
1306
1373
  if (uploadResult.success) {
1307
1374
  console.log(pc.cyan("📤 报告已上传"));
@@ -1412,7 +1479,12 @@ async function runAllModules(options, cacheInstance) {
1412
1479
  if (suggestions.length > 0) {
1413
1480
  console.log(pc.cyan(` ✅ 生成 ${suggestions.length} 个 AI 修复建议`));
1414
1481
  for (const s of suggestions) {
1415
- const confidenceIcon = s.confidence === "high" ? pc.green("●") : s.confidence === "medium" ? pc.yellow("●") : pc.red("●");
1482
+ const confidenceIcon =
1483
+ s.confidence === "high"
1484
+ ? pc.green("●")
1485
+ : s.confidence === "medium"
1486
+ ? pc.yellow("●")
1487
+ : pc.red("●");
1416
1488
  console.log(pc.cyan(`\n 📄 ${s.issue.file}:${s.issue.line}`));
1417
1489
  console.log(pc.yellow(` [${s.issue.ruleId}] ${s.issue.title}`));
1418
1490
  console.log(pc.gray(` AI 置信度: ${confidenceIcon} ${s.confidence}`));
@@ -1470,18 +1542,18 @@ async function runAllModules(options, cacheInstance) {
1470
1542
  console.log(pc.gray(` ${icon} ${nr.channel}: ${nr.success ? "已发送" : nr.error}`));
1471
1543
  }
1472
1544
  } else {
1473
- console.log(pc.yellow("⚠️ 未配置通知渠道。请设置 FG_NOTIFY_FEISHU / FG_NOTIFY_DINGTALK / FG_NOTIFY_WECOM / FG_NOTIFY_SLACK 环境变量"));
1545
+ console.log(
1546
+ pc.yellow(
1547
+ "⚠️ 未配置通知渠道。请设置 FG_NOTIFY_FEISHU / FG_NOTIFY_DINGTALK / FG_NOTIFY_WECOM / FG_NOTIFY_SLACK 环境变量"
1548
+ )
1549
+ );
1474
1550
  }
1475
1551
  }
1476
1552
 
1477
1553
  // v3.5.0: 生成合规报告
1478
1554
  if (options.compliance) {
1479
1555
  const moduleResults = MODULES.map((m) => allResults[m]).filter(Boolean);
1480
- const report = generateComplianceReport(
1481
- moduleResults,
1482
- options.projectDir,
1483
- options.strategy
1484
- );
1556
+ const report = generateComplianceReport(moduleResults, options.projectDir, options.strategy);
1485
1557
  const markdown = complianceReportToMarkdown(report);
1486
1558
  saveComplianceReport(report, options.compliance);
1487
1559
  if (!options.json) {
@@ -1732,7 +1804,9 @@ async function runSingleModule(options, cacheInstance) {
1732
1804
  issues.warning = issues.warning.filter((i) => newKeySet.has(`${i.file}|${i.ruleId}|${i.line}`));
1733
1805
  issues.suggestion = issues.suggestion.filter((i) => newKeySet.has(`${i.file}|${i.ruleId}|${i.line}`));
1734
1806
  result.total = issues.critical.length + issues.warning.length + issues.suggestion.length;
1735
- result.filesWithIssues = new Set([...issues.critical, ...issues.warning, ...issues.suggestion].map((i) => i.file)).size;
1807
+ result.filesWithIssues = new Set(
1808
+ [...issues.critical, ...issues.warning, ...issues.suggestion].map((i) => i.file)
1809
+ ).size;
1736
1810
  allIssues = baselineResult.newIssues;
1737
1811
  }
1738
1812
 
@@ -1780,13 +1854,9 @@ async function runSingleModule(options, cacheInstance) {
1780
1854
  let pubConfig = detectPublisherConfig();
1781
1855
  if (!pubConfig && options.commentProvider) {
1782
1856
  const token =
1783
- options.commentProvider === "github"
1784
- ? process.env.GITHUB_TOKEN
1785
- : process.env.GITLAB_TOKEN;
1857
+ options.commentProvider === "github" ? process.env.GITHUB_TOKEN : process.env.GITLAB_TOKEN;
1786
1858
  const repo =
1787
- options.commentProvider === "github"
1788
- ? process.env.GITHUB_REPOSITORY
1789
- : process.env.CI_PROJECT_ID;
1859
+ options.commentProvider === "github" ? process.env.GITHUB_REPOSITORY : process.env.CI_PROJECT_ID;
1790
1860
  if (token && repo && options.prNumber) {
1791
1861
  pubConfig = {
1792
1862
  provider: options.commentProvider,
@@ -1802,9 +1872,7 @@ async function runSingleModule(options, cacheInstance) {
1802
1872
  const publisher = createPublisher(pubConfig);
1803
1873
  const pubResult = await publisher.publish(commentBody);
1804
1874
  if (pubResult.success) {
1805
- console.log(
1806
- pc.cyan(`💬 PR/MR 评论已${pubResult.action === "updated" ? "更新" : "发布"}`)
1807
- );
1875
+ console.log(pc.cyan(`💬 PR/MR 评论已${pubResult.action === "updated" ? "更新" : "发布"}`));
1808
1876
  if (pubResult.commentUrl) {
1809
1877
  console.log(pc.gray(` ${pubResult.commentUrl}`));
1810
1878
  }
@@ -1903,7 +1971,12 @@ async function runSingleModule(options, cacheInstance) {
1903
1971
  if (suggestions.length > 0) {
1904
1972
  console.log(pc.cyan(` ✅ 生成 ${suggestions.length} 个 AI 修复建议`));
1905
1973
  for (const s of suggestions) {
1906
- const confidenceIcon = s.confidence === "high" ? pc.green("●") : s.confidence === "medium" ? pc.yellow("●") : pc.red("●");
1974
+ const confidenceIcon =
1975
+ s.confidence === "high"
1976
+ ? pc.green("●")
1977
+ : s.confidence === "medium"
1978
+ ? pc.yellow("●")
1979
+ : pc.red("●");
1907
1980
  console.log(pc.cyan(`\n 📄 ${s.issue.file}:${s.issue.line}`));
1908
1981
  console.log(pc.yellow(` [${s.issue.ruleId}] ${s.issue.title}`));
1909
1982
  console.log(pc.gray(` AI 置信度: ${confidenceIcon} ${s.confidence}`));
@@ -1990,9 +2063,7 @@ async function applyBaselineToResults(allResults, externalResults, baselinePath,
1990
2063
  return baselineResult;
1991
2064
  }
1992
2065
 
1993
- const newKeySet = new Set(
1994
- baselineResult.newIssues.map((i) => `${i.file}|${i.ruleId}|${i.line}`)
1995
- );
2066
+ const newKeySet = new Set(baselineResult.newIssues.map((i) => `${i.file}|${i.ruleId}|${i.line}`));
1996
2067
 
1997
2068
  function isNew(issue) {
1998
2069
  return newKeySet.has(`${issue.file}|${issue.ruleId}|${issue.line}`);
@@ -2006,12 +2077,9 @@ async function applyBaselineToResults(allResults, externalResults, baselinePath,
2006
2077
  r.issues.warning = r.issues.warning.filter(isNew);
2007
2078
  r.issues.suggestion = r.issues.suggestion.filter(isNew);
2008
2079
  r.total = r.issues.critical.length + r.issues.warning.length + r.issues.suggestion.length;
2009
- r.filesWithIssues =
2010
- new Set([
2011
- ...r.issues.critical,
2012
- ...r.issues.warning,
2013
- ...r.issues.suggestion,
2014
- ].map((i) => i.file)).size;
2080
+ r.filesWithIssues = new Set(
2081
+ [...r.issues.critical, ...r.issues.warning, ...r.issues.suggestion].map((i) => i.file)
2082
+ ).size;
2015
2083
  }
2016
2084
 
2017
2085
  // 过滤外部工具 issues
package/bin/fg-lsp.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  /**
3
4
  * Frontend Guardian LSP Server
4
5
  * Usage: fg-lsp --stdio [--project-dir <dir>] [--config <file>]
@@ -6,9 +7,9 @@
6
7
  * v3.3.0: Language Server Protocol 实现,为 IDE 提供实时诊断和快速修复。
7
8
  */
8
9
 
9
- import { runLSPServer } from "../dist/ide/lsp-server.js";
10
- import { resolve } from "node:path";
11
10
  import { existsSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { runLSPServer } from "../dist/ide/lsp-server.js";
12
13
 
13
14
  const args = process.argv.slice(2);
14
15
 
@@ -26,7 +27,7 @@ for (let i = 0; i < args.length; i++) {
26
27
  } else if (arg === "--severity" || arg === "-s") {
27
28
  minSeverity = args[++i] ?? "suggestion";
28
29
  } else if (arg === "--help" || arg === "-h") {
29
- console.log(`Frontend Guardian LSP Server v3.7.6
30
+ console.log(`Frontend Guardian LSP Server v3.9.0
30
31
 
31
32
  Usage: fg-lsp [options]
32
33
 
package/bin/fg-server.js CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  /**
3
4
  * Frontend Guardian Dashboard Server CLI
4
5
  * Usage: fg-server [options]
5
6
  */
6
7
 
7
- import { DashboardServer } from "../dist/index.js";
8
8
  import pc from "picocolors";
9
+ import { DashboardServer } from "../dist/index.js";
9
10
 
10
11
  function showHelp() {
11
12
  console.log(`
12
- Frontend Guardian Dashboard Server v3.7.6
13
+ Frontend Guardian Dashboard Server v3.9.0
13
14
 
14
15
  Usage:
15
16
  fg-server [options]
@@ -61,7 +62,7 @@ async function main() {
61
62
  }
62
63
 
63
64
  console.log(pc.cyan("Frontend Guardian Dashboard Server"));
64
- console.log(pc.gray(` Version: 3.7.5`));
65
+ console.log(pc.gray(` Version: 3.9.0`));
65
66
  console.log("");
66
67
 
67
68
  const server = new DashboardServer({