frontend-guardian-core 3.7.6 → 3.8.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
@@ -6,75 +6,75 @@
6
6
 
7
7
  import { writeFileSync } from "node:fs";
8
8
  import { join } 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.8.0
111
111
 
112
112
  Usage:
113
113
  fg-core <project-dir> [options]
@@ -179,6 +179,7 @@ Options:
179
179
  --build-index 建立项目索引(预索引文件结构、符号、路由)
180
180
  --watch-index 监听文件变更并自动同步索引
181
181
  --index-status 查看项目索引状态
182
+ --mcp 启动 MCP Server(stdio,供 AI Agent 调用)
182
183
  --help, -h 显示帮助
183
184
 
184
185
  Examples:
@@ -267,6 +268,7 @@ async function main() {
267
268
  buildIndex: false,
268
269
  watchIndex: false,
269
270
  indexStatus: false,
271
+ mcp: false,
270
272
  };
271
273
 
272
274
  for (let i = 0; i < args.length; i++) {
@@ -476,6 +478,9 @@ async function main() {
476
478
  case "--index-status":
477
479
  options.indexStatus = true;
478
480
  break;
481
+ case "--mcp":
482
+ options.mcp = true;
483
+ break;
479
484
  case "--help":
480
485
  case "-h":
481
486
  showHelp();
@@ -483,6 +488,17 @@ async function main() {
483
488
  }
484
489
  }
485
490
 
491
+ // v3.8.0: 启动 MCP Server(AI Agent 集成)
492
+ if (options.mcp) {
493
+ const { runMCPServer } = await import("../dist/index.js");
494
+ runMCPServer({
495
+ projectDir: options.projectDir,
496
+ configFile: options.configFile,
497
+ minSeverity: options.minSeverity,
498
+ });
499
+ return;
500
+ }
501
+
486
502
  // v3.6.0: E2E 测试覆盖缺口检测
487
503
  if (options.e2eDetectGaps) {
488
504
  const gapResult = detectE2EGaps({ projectDir: options.projectDir });
@@ -512,12 +528,18 @@ async function main() {
512
528
  const duration = Date.now() - start;
513
529
 
514
530
  if (options.json) {
515
- console.log(JSON.stringify({
516
- tool: "Playwright",
517
- total: issues.length,
518
- duration,
519
- issues,
520
- }, null, 2));
531
+ console.log(
532
+ JSON.stringify(
533
+ {
534
+ tool: "Playwright",
535
+ total: issues.length,
536
+ duration,
537
+ issues,
538
+ },
539
+ null,
540
+ 2
541
+ )
542
+ );
521
543
  } else {
522
544
  console.log(pc.cyan(`📊 Playwright 测试结果`));
523
545
  console.log(pc.gray(` 耗时: ${duration}ms`));
@@ -553,7 +575,7 @@ async function main() {
553
575
 
554
576
  if (!options.baseUrl && !options.serveCommand) {
555
577
  console.log(pc.yellow("⚠️ 请指定 --base-url 或 --serve"));
556
- console.log(pc.gray(" 示例: fg-core . --page-health --serve \"npm run dev\" --port 5173"));
578
+ console.log(pc.gray(' 示例: fg-core . --page-health --serve "npm run dev" --port 5173'));
557
579
  console.log(pc.gray(" 示例: fg-core . --page-health --base-url http://localhost:3000"));
558
580
  process.exit(1);
559
581
  }
@@ -587,7 +609,8 @@ async function main() {
587
609
  if (result.issues.length > 0) {
588
610
  console.log(pc.cyan(`📋 发现 ${result.issues.length} 个问题:`));
589
611
  for (const issue of result.issues) {
590
- const color = issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue;
612
+ const color =
613
+ issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue;
591
614
  console.log(color(` [${issue.severity.toUpperCase()}] ${issue.title}`));
592
615
  console.log(pc.gray(` ${issue.description.split("\n")[0]}`));
593
616
  }
@@ -605,11 +628,7 @@ async function main() {
605
628
  if (!options.json) {
606
629
  console.log(pc.gray(`\n 正在上报到看板服务器: ${options.server}`));
607
630
  }
608
- const uploadResult = await uploadPageHealthResult(
609
- result,
610
- options.projectDir,
611
- dashboardConfig
612
- );
631
+ const uploadResult = await uploadPageHealthResult(result, options.projectDir, dashboardConfig);
613
632
  if (!options.json) {
614
633
  if (uploadResult.success) {
615
634
  console.log(pc.green(` ✅ 已上报到看板服务器`));
@@ -660,13 +679,7 @@ async function main() {
660
679
 
661
680
  const { globbySync } = require("globby");
662
681
  const patterns = ["**/*.{js,ts,jsx,tsx,vue}"];
663
- const exclude = [
664
- "**/node_modules/**",
665
- "**/dist/**",
666
- "**/build/**",
667
- "**/.git/**",
668
- "**/coverage/**",
669
- ];
682
+ const exclude = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/coverage/**"];
670
683
  const files = globbySync(patterns, {
671
684
  cwd: options.projectDir,
672
685
  ignore: exclude,
@@ -692,14 +705,20 @@ async function main() {
692
705
  projectDir: options.projectDir,
693
706
  onChange: (changed, deleted) => {
694
707
  if (changed.length > 0) {
695
- console.log(pc.cyan(`\n📝 变更: ${changed.map((f) => relative(options.projectDir, f)).join(", ")}`));
708
+ console.log(
709
+ pc.cyan(`\n📝 变更: ${changed.map((f) => relative(options.projectDir, f)).join(", ")}`)
710
+ );
696
711
  }
697
712
  if (deleted.length > 0) {
698
- console.log(pc.yellow(`\n🗑️ 删除: ${deleted.map((f) => relative(options.projectDir, f)).join(", ")}`));
713
+ console.log(
714
+ pc.yellow(`\n🗑️ 删除: ${deleted.map((f) => relative(options.projectDir, f)).join(", ")}`)
715
+ );
699
716
  }
700
717
  },
701
718
  onIndexUpdate: (stats) => {
702
- console.log(pc.gray(` 索引已更新 | 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`));
719
+ console.log(
720
+ pc.gray(` 索引已更新 | 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`)
721
+ );
703
722
  },
704
723
  onError: (err) => {
705
724
  console.error(pc.red(` 监听错误: ${err.message}`));
@@ -756,7 +775,9 @@ async function main() {
756
775
  console.log(pc.gray(" 使用 --init-config --force 覆盖(或使用 --init-config 直接覆盖)"));
757
776
  } else if (result.created) {
758
777
  console.log(pc.green(` ✅ 已创建: ${result.path}`));
759
- console.log(pc.gray(` 框架: ${meta.framework ?? "auto-detect"} | 组件库: ${meta.componentLib ?? "auto-detect"}`));
778
+ console.log(
779
+ pc.gray(` 框架: ${meta.framework ?? "auto-detect"} | 组件库: ${meta.componentLib ?? "auto-detect"}`)
780
+ );
760
781
  }
761
782
  process.exit(0);
762
783
  }
@@ -796,16 +817,28 @@ async function main() {
796
817
  if (filtered.length === 0) {
797
818
  console.log(pc.gray(" 暂无历史记录"));
798
819
  } else {
799
- console.log(` ${"时间".padEnd(20)} ${"模块".padEnd(15)} ${"C".padStart(4)} ${"W".padStart(4)} ${"S".padStart(4)}`);
820
+ console.log(
821
+ ` ${"时间".padEnd(20)} ${"模块".padEnd(15)} ${"C".padStart(4)} ${"W".padStart(4)} ${"S".padStart(4)}`
822
+ );
800
823
  console.log(pc.gray(" " + "-".repeat(50)));
801
824
  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" });
825
+ const time = new Date(r.timestamp).toLocaleString("zh-CN", {
826
+ month: "2-digit",
827
+ day: "2-digit",
828
+ hour: "2-digit",
829
+ minute: "2-digit",
830
+ });
803
831
  const c = pc.red(String(r.counts.critical).padStart(4));
804
832
  const w = pc.yellow(String(r.counts.warning).padStart(4));
805
833
  const s = pc.blue(String(r.counts.suggestion).padStart(4));
806
834
  console.log(` ${time.padEnd(20)} ${r.module.padEnd(15)} ${c} ${w} ${s}`);
807
835
  }
808
- console.log(pc.gray(` 共 ${reports.length} 条记录` + (options.historyModule ? `,已按模块 "${options.historyModule}" 过滤` : "")));
836
+ console.log(
837
+ pc.gray(
838
+ ` 共 ${reports.length} 条记录` +
839
+ (options.historyModule ? `,已按模块 "${options.historyModule}" 过滤` : "")
840
+ )
841
+ );
809
842
  }
810
843
  process.exit(0);
811
844
  }
@@ -840,9 +873,7 @@ async function main() {
840
873
  console.log(pc.yellow("⚠️ 暂无历史扫描记录,无法生成看板。请先运行 --save-report 生成历史数据。"));
841
874
  process.exit(1);
842
875
  }
843
- const fullReports = reportList
844
- .map((r) => hr.loadReport(r.filename))
845
- .filter(Boolean);
876
+ const fullReports = reportList.map((r) => hr.loadReport(r.filename)).filter(Boolean);
846
877
  const outputPath = generateDashboard(fullReports, { projectDir: options.projectDir });
847
878
  console.log(pc.cyan("📊 趋势看板已生成"));
848
879
  console.log(pc.green(` ✅ ${outputPath}`));
@@ -855,7 +886,11 @@ async function main() {
855
886
  if (options.monorepo) {
856
887
  const mono = detectMonorepo(options.projectDir);
857
888
  if (!mono.isMonorepo) {
858
- console.log(pc.yellow(`⚠️ 未检测到 monorepo workspace 配置(pnpm-workspace.yaml / lerna.json / nx.json / package.json workspaces)`));
889
+ console.log(
890
+ pc.yellow(
891
+ `⚠️ 未检测到 monorepo workspace 配置(pnpm-workspace.yaml / lerna.json / nx.json / package.json workspaces)`
892
+ )
893
+ );
859
894
  console.log(pc.gray(` 将在项目根目录执行常规扫描...`));
860
895
  if (options.module === "all") {
861
896
  await runAllModules(options);
@@ -906,13 +941,24 @@ async function main() {
906
941
  if (!pr.success || pr.result.total === 0) continue;
907
942
 
908
943
  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];
944
+ const allIssues = [
945
+ ...pr.result.issues.critical,
946
+ ...pr.result.issues.warning,
947
+ ...pr.result.issues.suggestion,
948
+ ];
910
949
  for (const issue of allIssues) {
911
- printIssue(issue, issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue);
950
+ printIssue(
951
+ issue,
952
+ issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue
953
+ );
912
954
  }
913
955
  }
914
956
 
915
- console.log(pc.gray(`\n⏱️ 总耗时: ${workspaceResult.summary.totalDuration}ms | 扫描 ${workspaceResult.summary.totalFilesScanned} 个文件`));
957
+ console.log(
958
+ pc.gray(
959
+ `\n⏱️ 总耗时: ${workspaceResult.summary.totalDuration}ms | 扫描 ${workspaceResult.summary.totalFilesScanned} 个文件`
960
+ )
961
+ );
916
962
 
917
963
  if (workspaceResult.summary.issuesBySeverity.critical > 0) {
918
964
  process.exit(1);
@@ -1118,9 +1164,7 @@ async function runAllModules(options, cacheInstance) {
1118
1164
  } else {
1119
1165
  if (!options.json) {
1120
1166
  console.log(
1121
- pc.yellow(
1122
- " ⚠️ 未检测到 Fix Bot 配置。请设置 FG_FIX_BOT_PROVIDER 和 FG_FIX_BOT_TOKEN 环境变量。"
1123
- )
1167
+ pc.yellow(" ⚠️ 未检测到 Fix Bot 配置。请设置 FG_FIX_BOT_PROVIDER 和 FG_FIX_BOT_TOKEN 环境变量。")
1124
1168
  );
1125
1169
  console.log("");
1126
1170
  }
@@ -1208,14 +1252,9 @@ async function runAllModules(options, cacheInstance) {
1208
1252
  // 构建发布器配置
1209
1253
  let pubConfig = detectPublisherConfig();
1210
1254
  if (!pubConfig && options.commentProvider) {
1211
- const token =
1212
- options.commentProvider === "github"
1213
- ? process.env.GITHUB_TOKEN
1214
- : process.env.GITLAB_TOKEN;
1255
+ const token = options.commentProvider === "github" ? process.env.GITHUB_TOKEN : process.env.GITLAB_TOKEN;
1215
1256
  const repo =
1216
- options.commentProvider === "github"
1217
- ? process.env.GITHUB_REPOSITORY
1218
- : process.env.CI_PROJECT_ID;
1257
+ options.commentProvider === "github" ? process.env.GITHUB_REPOSITORY : process.env.CI_PROJECT_ID;
1219
1258
  if (token && repo && options.prNumber) {
1220
1259
  pubConfig = {
1221
1260
  provider: options.commentProvider,
@@ -1231,9 +1270,7 @@ async function runAllModules(options, cacheInstance) {
1231
1270
  const publisher = createPublisher(pubConfig);
1232
1271
  const result = await publisher.publish(commentBody);
1233
1272
  if (result.success) {
1234
- console.log(
1235
- pc.cyan(`💬 PR/MR 评论已${result.action === "updated" ? "更新" : "发布"}`)
1236
- );
1273
+ console.log(pc.cyan(`💬 PR/MR 评论已${result.action === "updated" ? "更新" : "发布"}`));
1237
1274
  if (result.commentUrl) {
1238
1275
  console.log(pc.gray(` ${result.commentUrl}`));
1239
1276
  }
@@ -1254,7 +1291,7 @@ async function runAllModules(options, cacheInstance) {
1254
1291
  }
1255
1292
 
1256
1293
  // 生成并写入报告文件(--output)
1257
- let reportPath = options.output;
1294
+ const reportPath = options.output;
1258
1295
  if (reportPath) {
1259
1296
  const reportBody = generatePRComment(
1260
1297
  allResults,
@@ -1283,25 +1320,27 @@ async function runAllModules(options, cacheInstance) {
1283
1320
  if (options.upload) {
1284
1321
  const uploadConfig = detectUploadConfig();
1285
1322
  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
- })();
1323
+ const targetPath =
1324
+ reportPath ||
1325
+ (() => {
1326
+ const tmpPath = join(process.cwd(), `fg-report-${Date.now()}.md`);
1327
+ const reportBody = generatePRComment(
1328
+ allResults,
1329
+ {
1330
+ timestamp: new Date().toISOString(),
1331
+ duration: totalDuration,
1332
+ filesScanned: totalFilesScanned,
1333
+ },
1334
+ {
1335
+ external: externalResults.length > 0 ? externalResults : undefined,
1336
+ fixResult: fixResult
1337
+ ? { fixedCount: fixResult.fixedCount, filesModified: fixResult.filesModified }
1338
+ : null,
1339
+ }
1340
+ );
1341
+ writeFileSync(tmpPath, reportBody, "utf-8");
1342
+ return tmpPath;
1343
+ })();
1305
1344
  const uploadResult = await uploadReport(targetPath, uploadConfig);
1306
1345
  if (uploadResult.success) {
1307
1346
  console.log(pc.cyan("📤 报告已上传"));
@@ -1412,7 +1451,12 @@ async function runAllModules(options, cacheInstance) {
1412
1451
  if (suggestions.length > 0) {
1413
1452
  console.log(pc.cyan(` ✅ 生成 ${suggestions.length} 个 AI 修复建议`));
1414
1453
  for (const s of suggestions) {
1415
- const confidenceIcon = s.confidence === "high" ? pc.green("●") : s.confidence === "medium" ? pc.yellow("●") : pc.red("●");
1454
+ const confidenceIcon =
1455
+ s.confidence === "high"
1456
+ ? pc.green("●")
1457
+ : s.confidence === "medium"
1458
+ ? pc.yellow("●")
1459
+ : pc.red("●");
1416
1460
  console.log(pc.cyan(`\n 📄 ${s.issue.file}:${s.issue.line}`));
1417
1461
  console.log(pc.yellow(` [${s.issue.ruleId}] ${s.issue.title}`));
1418
1462
  console.log(pc.gray(` AI 置信度: ${confidenceIcon} ${s.confidence}`));
@@ -1470,18 +1514,18 @@ async function runAllModules(options, cacheInstance) {
1470
1514
  console.log(pc.gray(` ${icon} ${nr.channel}: ${nr.success ? "已发送" : nr.error}`));
1471
1515
  }
1472
1516
  } else {
1473
- console.log(pc.yellow("⚠️ 未配置通知渠道。请设置 FG_NOTIFY_FEISHU / FG_NOTIFY_DINGTALK / FG_NOTIFY_WECOM / FG_NOTIFY_SLACK 环境变量"));
1517
+ console.log(
1518
+ pc.yellow(
1519
+ "⚠️ 未配置通知渠道。请设置 FG_NOTIFY_FEISHU / FG_NOTIFY_DINGTALK / FG_NOTIFY_WECOM / FG_NOTIFY_SLACK 环境变量"
1520
+ )
1521
+ );
1474
1522
  }
1475
1523
  }
1476
1524
 
1477
1525
  // v3.5.0: 生成合规报告
1478
1526
  if (options.compliance) {
1479
1527
  const moduleResults = MODULES.map((m) => allResults[m]).filter(Boolean);
1480
- const report = generateComplianceReport(
1481
- moduleResults,
1482
- options.projectDir,
1483
- options.strategy
1484
- );
1528
+ const report = generateComplianceReport(moduleResults, options.projectDir, options.strategy);
1485
1529
  const markdown = complianceReportToMarkdown(report);
1486
1530
  saveComplianceReport(report, options.compliance);
1487
1531
  if (!options.json) {
@@ -1732,7 +1776,9 @@ async function runSingleModule(options, cacheInstance) {
1732
1776
  issues.warning = issues.warning.filter((i) => newKeySet.has(`${i.file}|${i.ruleId}|${i.line}`));
1733
1777
  issues.suggestion = issues.suggestion.filter((i) => newKeySet.has(`${i.file}|${i.ruleId}|${i.line}`));
1734
1778
  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;
1779
+ result.filesWithIssues = new Set(
1780
+ [...issues.critical, ...issues.warning, ...issues.suggestion].map((i) => i.file)
1781
+ ).size;
1736
1782
  allIssues = baselineResult.newIssues;
1737
1783
  }
1738
1784
 
@@ -1780,13 +1826,9 @@ async function runSingleModule(options, cacheInstance) {
1780
1826
  let pubConfig = detectPublisherConfig();
1781
1827
  if (!pubConfig && options.commentProvider) {
1782
1828
  const token =
1783
- options.commentProvider === "github"
1784
- ? process.env.GITHUB_TOKEN
1785
- : process.env.GITLAB_TOKEN;
1829
+ options.commentProvider === "github" ? process.env.GITHUB_TOKEN : process.env.GITLAB_TOKEN;
1786
1830
  const repo =
1787
- options.commentProvider === "github"
1788
- ? process.env.GITHUB_REPOSITORY
1789
- : process.env.CI_PROJECT_ID;
1831
+ options.commentProvider === "github" ? process.env.GITHUB_REPOSITORY : process.env.CI_PROJECT_ID;
1790
1832
  if (token && repo && options.prNumber) {
1791
1833
  pubConfig = {
1792
1834
  provider: options.commentProvider,
@@ -1802,9 +1844,7 @@ async function runSingleModule(options, cacheInstance) {
1802
1844
  const publisher = createPublisher(pubConfig);
1803
1845
  const pubResult = await publisher.publish(commentBody);
1804
1846
  if (pubResult.success) {
1805
- console.log(
1806
- pc.cyan(`💬 PR/MR 评论已${pubResult.action === "updated" ? "更新" : "发布"}`)
1807
- );
1847
+ console.log(pc.cyan(`💬 PR/MR 评论已${pubResult.action === "updated" ? "更新" : "发布"}`));
1808
1848
  if (pubResult.commentUrl) {
1809
1849
  console.log(pc.gray(` ${pubResult.commentUrl}`));
1810
1850
  }
@@ -1903,7 +1943,12 @@ async function runSingleModule(options, cacheInstance) {
1903
1943
  if (suggestions.length > 0) {
1904
1944
  console.log(pc.cyan(` ✅ 生成 ${suggestions.length} 个 AI 修复建议`));
1905
1945
  for (const s of suggestions) {
1906
- const confidenceIcon = s.confidence === "high" ? pc.green("●") : s.confidence === "medium" ? pc.yellow("●") : pc.red("●");
1946
+ const confidenceIcon =
1947
+ s.confidence === "high"
1948
+ ? pc.green("●")
1949
+ : s.confidence === "medium"
1950
+ ? pc.yellow("●")
1951
+ : pc.red("●");
1907
1952
  console.log(pc.cyan(`\n 📄 ${s.issue.file}:${s.issue.line}`));
1908
1953
  console.log(pc.yellow(` [${s.issue.ruleId}] ${s.issue.title}`));
1909
1954
  console.log(pc.gray(` AI 置信度: ${confidenceIcon} ${s.confidence}`));
@@ -1990,9 +2035,7 @@ async function applyBaselineToResults(allResults, externalResults, baselinePath,
1990
2035
  return baselineResult;
1991
2036
  }
1992
2037
 
1993
- const newKeySet = new Set(
1994
- baselineResult.newIssues.map((i) => `${i.file}|${i.ruleId}|${i.line}`)
1995
- );
2038
+ const newKeySet = new Set(baselineResult.newIssues.map((i) => `${i.file}|${i.ruleId}|${i.line}`));
1996
2039
 
1997
2040
  function isNew(issue) {
1998
2041
  return newKeySet.has(`${issue.file}|${issue.ruleId}|${issue.line}`);
@@ -2006,12 +2049,9 @@ async function applyBaselineToResults(allResults, externalResults, baselinePath,
2006
2049
  r.issues.warning = r.issues.warning.filter(isNew);
2007
2050
  r.issues.suggestion = r.issues.suggestion.filter(isNew);
2008
2051
  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;
2052
+ r.filesWithIssues = new Set(
2053
+ [...r.issues.critical, ...r.issues.warning, ...r.issues.suggestion].map((i) => i.file)
2054
+ ).size;
2015
2055
  }
2016
2056
 
2017
2057
  // 过滤外部工具 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.8.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.8.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.8.0`));
65
66
  console.log("");
66
67
 
67
68
  const server = new DashboardServer({
@@ -8,8 +8,8 @@
8
8
  * 4. 并行扫描多文件
9
9
  * 5. 支持增量扫描(git diff)
10
10
  */
11
- import type { Rule, Issue, Severity, ScanResult, FixPreview } from "../types.js";
12
11
  import type { ExternalTool, ExternalToolResult } from "../integrations/index.js";
12
+ import type { FixPreview, Issue, Rule, ScanResult, Severity } from "../types.js";
13
13
  import { SmartCache } from "./cache.js";
14
14
  export interface EngineOptions {
15
15
  /** 项目根目录 */
@@ -48,6 +48,8 @@ export interface EngineOptions {
48
48
  incrementalImportGraph?: boolean;
49
49
  /** v3.5.0: 扫描策略分级 strict | standard | loose */
50
50
  strategy?: "strict" | "standard" | "loose";
51
+ /** v3.8.0: 静默模式(禁止向 stdout 输出日志,供 MCP Server 等场景使用) */
52
+ silent?: boolean;
51
53
  }
52
54
  export declare class RuleEngine {
53
55
  private registry;
@@ -56,6 +58,9 @@ export declare class RuleEngine {
56
58
  private options;
57
59
  private cache?;
58
60
  private history;
61
+ /** v3.8.0: 静默模式下禁止 stdout 日志 */
62
+ private log;
63
+ private logError;
59
64
  constructor(options: EngineOptions);
60
65
  /** ── Phase 3: 配置驱动规则加载 ── */
61
66
  private loadConfigRules;