frontend-guardian-core 3.14.1 → 3.20.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.
Files changed (108) hide show
  1. package/bin/fg-core.js +322 -16
  2. package/bin/fg-lsp.js +1 -1
  3. package/bin/fg-server.js +2 -2
  4. package/bin/watch-mode.js +155 -9
  5. package/dist/engine/rule-engine.d.ts +17 -1
  6. package/dist/engine/rule-engine.d.ts.map +1 -1
  7. package/dist/engine/rule-engine.js +158 -3
  8. package/dist/engine/rule-engine.js.map +1 -1
  9. package/dist/index.d.ts +21 -3
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +55 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/integrations/cypress.d.ts +9 -0
  14. package/dist/integrations/cypress.d.ts.map +1 -0
  15. package/dist/integrations/cypress.js +104 -0
  16. package/dist/integrations/cypress.js.map +1 -0
  17. package/dist/integrations/index.d.ts +6 -0
  18. package/dist/integrations/index.d.ts.map +1 -1
  19. package/dist/integrations/index.js +8 -1
  20. package/dist/integrations/index.js.map +1 -1
  21. package/dist/integrations/katalon.d.ts +9 -0
  22. package/dist/integrations/katalon.d.ts.map +1 -0
  23. package/dist/integrations/katalon.js +102 -0
  24. package/dist/integrations/katalon.js.map +1 -0
  25. package/dist/integrations/selenium.d.ts +9 -0
  26. package/dist/integrations/selenium.d.ts.map +1 -0
  27. package/dist/integrations/selenium.js +143 -0
  28. package/dist/integrations/selenium.js.map +1 -0
  29. package/dist/mcp/guidance.d.ts +1 -1
  30. package/dist/mcp/guidance.js +2 -2
  31. package/dist/mcp/mcp-server.js +1 -1
  32. package/dist/mcp/tools.d.ts.map +1 -1
  33. package/dist/mcp/tools.js +67 -10
  34. package/dist/mcp/tools.js.map +1 -1
  35. package/dist/mcp/types.d.ts +3 -0
  36. package/dist/mcp/types.d.ts.map +1 -1
  37. package/dist/rules/registry.d.ts +12 -1
  38. package/dist/rules/registry.d.ts.map +1 -1
  39. package/dist/rules/registry.js +26 -0
  40. package/dist/rules/registry.js.map +1 -1
  41. package/dist/scanners/backend-scanner.d.ts +9 -0
  42. package/dist/scanners/backend-scanner.d.ts.map +1 -0
  43. package/dist/scanners/backend-scanner.js +414 -0
  44. package/dist/scanners/backend-scanner.js.map +1 -0
  45. package/dist/scanners/css-scanner.d.ts +8 -0
  46. package/dist/scanners/css-scanner.d.ts.map +1 -0
  47. package/dist/scanners/css-scanner.js +185 -0
  48. package/dist/scanners/css-scanner.js.map +1 -0
  49. package/dist/scanners/data-scanner.d.ts +8 -0
  50. package/dist/scanners/data-scanner.d.ts.map +1 -0
  51. package/dist/scanners/data-scanner.js +377 -0
  52. package/dist/scanners/data-scanner.js.map +1 -0
  53. package/dist/types.d.ts +146 -2
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/utils/baseline.d.ts +1 -1
  56. package/dist/utils/baseline.d.ts.map +1 -1
  57. package/dist/utils/baseline.js +14 -2
  58. package/dist/utils/baseline.js.map +1 -1
  59. package/dist/utils/config-loader.d.ts.map +1 -1
  60. package/dist/utils/config-loader.js +14 -0
  61. package/dist/utils/config-loader.js.map +1 -1
  62. package/dist/utils/config-recommender.d.ts +38 -0
  63. package/dist/utils/config-recommender.d.ts.map +1 -0
  64. package/dist/utils/config-recommender.js +183 -0
  65. package/dist/utils/config-recommender.js.map +1 -0
  66. package/dist/utils/e2e-gap-detector.d.ts +2 -0
  67. package/dist/utils/e2e-gap-detector.d.ts.map +1 -1
  68. package/dist/utils/e2e-gap-detector.js +62 -16
  69. package/dist/utils/e2e-gap-detector.js.map +1 -1
  70. package/dist/utils/impact-graph.d.ts +70 -0
  71. package/dist/utils/impact-graph.d.ts.map +1 -0
  72. package/dist/utils/impact-graph.js +230 -0
  73. package/dist/utils/impact-graph.js.map +1 -0
  74. package/dist/utils/init-config.js +3 -1
  75. package/dist/utils/init-config.js.map +1 -1
  76. package/dist/utils/market-index.d.ts +27 -0
  77. package/dist/utils/market-index.d.ts.map +1 -0
  78. package/dist/utils/market-index.js +151 -0
  79. package/dist/utils/market-index.js.map +1 -0
  80. package/dist/utils/project-detector.js +4 -0
  81. package/dist/utils/project-detector.js.map +1 -1
  82. package/dist/utils/rule-compatibility.d.ts +15 -0
  83. package/dist/utils/rule-compatibility.d.ts.map +1 -0
  84. package/dist/utils/rule-compatibility.js +129 -0
  85. package/dist/utils/rule-compatibility.js.map +1 -0
  86. package/dist/utils/rule-doc-generator.d.ts +19 -0
  87. package/dist/utils/rule-doc-generator.d.ts.map +1 -0
  88. package/dist/utils/rule-doc-generator.js +143 -0
  89. package/dist/utils/rule-doc-generator.js.map +1 -0
  90. package/dist/utils/rule-hot-reload.d.ts +22 -0
  91. package/dist/utils/rule-hot-reload.d.ts.map +1 -0
  92. package/dist/utils/rule-hot-reload.js +59 -0
  93. package/dist/utils/rule-hot-reload.js.map +1 -0
  94. package/dist/utils/rule-scoring.d.ts +18 -0
  95. package/dist/utils/rule-scoring.d.ts.map +1 -0
  96. package/dist/utils/rule-scoring.js +173 -0
  97. package/dist/utils/rule-scoring.js.map +1 -0
  98. package/dist/utils/rule-template-generator.d.ts +16 -0
  99. package/dist/utils/rule-template-generator.d.ts.map +1 -0
  100. package/dist/utils/rule-template-generator.js +146 -0
  101. package/dist/utils/rule-template-generator.js.map +1 -0
  102. package/dist/utils/test-recommender.d.ts +5 -0
  103. package/dist/utils/test-recommender.d.ts.map +1 -1
  104. package/dist/utils/test-recommender.js +20 -1
  105. package/dist/utils/test-recommender.js.map +1 -1
  106. package/market/README.md +47 -0
  107. package/market/index.json +50 -0
  108. package/package.json +2 -1
package/bin/fg-core.js CHANGED
@@ -10,15 +10,21 @@ import pc from "picocolors";
10
10
  import {
11
11
  AIFixSuggester,
12
12
  a11yRules,
13
+ backendRules,
13
14
  BaselineManager,
14
15
  buildNotificationPayload,
16
+ checkRuleCompatibility,
15
17
  CodeownersParser,
16
18
  compareHistoryReports,
17
19
  complianceReportToMarkdown,
18
20
  componentRules,
21
+ computeRuleScores,
19
22
  createEngine,
20
23
  createPublisher,
21
24
  crossFileRules,
25
+ cssRules,
26
+ cypressIntegration,
27
+ dataRules,
22
28
  detectAIConfig,
23
29
  detectCIProvider,
24
30
  detectE2EGaps,
@@ -31,36 +37,53 @@ import {
31
37
  e2eRules,
32
38
  FileWatcher,
33
39
  formatAllAnnotations,
40
+ formatCompatibilityReport,
41
+ formatCompatibilityReportJson,
34
42
  formatE2EGapJson,
35
43
  formatE2EGapReport,
44
+ formatGeneratedDocs,
36
45
  formatHistoryCompare,
37
46
  formatHistoryCompareJson,
47
+ formatImpactGraph,
48
+ formatMarketIndex,
49
+ formatMarketIndexJson,
38
50
  formatMiniProgramJson,
51
+ formatScanProfile,
39
52
  formatMiniProgramReport,
40
53
  formatPageHealthJson,
54
+ formatRecommendedConfig,
41
55
  formatPageHealthReport,
56
+ formatRuleScores,
57
+ formatRuleScoresJson,
42
58
  formatWorkspaceJson,
43
59
  formatWorkspaceReport,
44
60
  generateCIConfig,
45
61
  generateComplianceReport,
46
62
  generateDashboard,
47
63
  generatePRComment,
64
+ generateRuleDocs,
65
+ generateRuleTemplate,
48
66
  generateSarif,
49
67
  HistoryReport,
50
68
  hooksRules,
51
69
  i18nRules,
52
70
  initConfig,
53
71
  installGitHooks,
72
+ isCompatibilityReportClean,
54
73
  isGitHubActions,
55
74
  isPlaywrightAvailable,
75
+ katalonIntegration,
76
+ loadMarketIndex,
56
77
  namingRules,
57
78
  ProjectIndexer,
58
79
  performanceRules,
59
80
  platformRules,
81
+ recommendConfig,
60
82
  playwrightIntegration,
61
83
  runFixBot,
62
84
  runMiniProgramTest,
63
85
  runPageHealthCheck,
86
+ seleniumIntegration,
64
87
  SmartCache,
65
88
  saveComplianceReport,
66
89
  scanWorkspace,
@@ -87,6 +110,9 @@ const MODULES = [
87
110
  "platform",
88
111
  "svelte",
89
112
  "e2e",
113
+ "css",
114
+ "data",
115
+ "backend",
90
116
  ];
91
117
 
92
118
  const MODULE_RULES = {
@@ -101,18 +127,21 @@ const MODULE_RULES = {
101
127
  platform: platformRules,
102
128
  svelte: svelteRules,
103
129
  e2e: e2eRules,
130
+ css: cssRules,
131
+ data: dataRules,
132
+ backend: backendRules,
104
133
  };
105
134
 
106
135
  function showHelp() {
107
136
  console.log(`
108
- Frontend Guardian Core v3.14.1
137
+ Frontend Guardian Core v3.20.0
109
138
 
110
139
  Usage:
111
140
  fg-core <project-dir> [options]
112
141
 
113
142
  Options:
114
143
  --scan 全量扫描(等价于 --module all)
115
- --module <name> 扫描模块: i18n | performance | a11y | security | naming | cross-file | component | hooks | platform | svelte | e2e | all
144
+ --module <name> 扫描模块: i18n | performance | a11y | security | naming | cross-file | component | hooks | platform | svelte | e2e | css | data | backend | all
116
145
  --severity <level> 最低严重级别: critical | warning | suggestion (默认: suggestion)
117
146
  --files <pattern> 仅扫描匹配的文件
118
147
  --exclude <pattern> 排除匹配的文件
@@ -129,6 +158,9 @@ Options:
129
158
  --output <file> 将扫描报告写入指定文件(Markdown 格式)
130
159
  --external 运行外部工具集成 (ESLint / TypeScript / Stylelint)
131
160
  --watch Watch 模式:文件变更自动增量扫描
161
+ --no-progress 禁用扫描进度条(v3.15.0)
162
+ --profile 输出扫描耗时分析:规则/文件耗时排名(v3.15.0)
163
+ --recommend-config 根据项目规模与框架自动推荐最优配置(v3.15.0)
132
164
  --no-cache 禁用智能缓存
133
165
  --config <file> 指定配置文件
134
166
  --install-hooks 安装 Git hook(默认 pre-commit,可用 --install-hooks-type 指定)
@@ -164,7 +196,8 @@ Options:
164
196
  --server <url> 扫描后上报到治理看板服务器
165
197
  --serve 扫描前启动本地看板服务(扫描完成后停止)
166
198
  --e2e-detect-gaps 检测 E2E 测试覆盖缺口(页面 + 接口)
167
- --e2e-run 运行 Playwright E2E 测试并输出治理报告
199
+ --e2e-run 运行 E2E 测试并输出治理报告
200
+ --e2e-tool <tool> E2E 工具: auto | playwright | cypress | selenium | katalon (默认: auto)(v3.16.0)
168
201
  --page-health 页面健康检查:遍历路由验证渲染、控制台错误、白屏
169
202
  --serve <cmd> 自动启动 dev server(配合 --page-health,如 "npm run dev")
170
203
  --port <n> dev server 端口(默认 5173,配合 --serve)
@@ -211,6 +244,22 @@ Options:
211
244
  --watch-index 监听文件变更并自动同步索引
212
245
  --index-status 查看项目索引状态
213
246
  --recommend-tests 智能测试推荐:基于变更影响分析推荐需要运行的测试(可配合 --staged/--diff/--auto-scope/--files)
247
+ --impact-graph 配合 --recommend-tests 输出变更影响图(默认 mermaid 格式)(v3.16.0)
248
+ --impact-graph-format <format> 影响图格式: json | mermaid | dot (默认: mermaid)(v3.16.0)
249
+ --create-rule <id> 生成规则模板文件(默认输出到 ./rules)(v3.17.0)
250
+ --create-rule-dir <dir> 规则模板输出目录(默认 ./rules)(v3.17.0)
251
+ --create-rule-category <cat> 规则分类(默认 security)(v3.17.0)
252
+ --create-rule-severity <sev> 严重级别: critical | warning | suggestion (默认: warning)(v3.17.0)
253
+ --create-rule-fix 生成的规则模板包含 fix 示例(v3.17.0)
254
+ --create-rule-lang <js|ts> 规则模板语言(默认 js)(v3.17.0)
255
+ --market-index 打印规则市场索引(v3.17.0)
256
+ --market-index-json 以 JSON 格式输出规则市场索引(v3.17.0)
257
+ --market-index-url <url> 覆盖默认市场索引 URL(v3.17.0)
258
+ --rule-scores 打印规则评分(基于本地扫描历史)(v3.17.0)
259
+ --rule-scores-json 以 JSON 格式输出规则评分(v3.17.0)
260
+ --generate-rule-docs 为所有规则生成 Markdown 文档(默认输出 ./docs/rules)(v3.18.0)
261
+ --generate-rule-docs-dir <dir> 指定规则文档输出目录(v3.18.0)
262
+ --check-rule-compat 检查当前启用规则集的兼容性/冲突(v3.18.0)
214
263
  --flaky-threshold-failure-rate <n> flaky 测试失败率阈值(默认 0.2)
215
264
  --flaky-threshold-flip-rate <n> flaky 测试状态翻转率阈值(默认 0.15)
216
265
  --flaky-min-runs <n> 参与 flaky 计算的最少历史运行次数(默认 3)
@@ -219,6 +268,8 @@ Options:
219
268
 
220
269
  Examples:
221
270
  fg-core ./my-project --module all
271
+ fg-core ./my-project --module backend
272
+ fg-core ./my-project --module backend --files "server/**/*.go"
222
273
  fg-core ./my-project --module i18n
223
274
  fg-core ./my-project --module i18n --severity warning --json
224
275
  fg-core ./my-project --module performance --files "src/**/*.tsx"
@@ -349,6 +400,30 @@ async function main() {
349
400
  flakyThresholdFailureRate: undefined,
350
401
  flakyThresholdFlipRate: undefined,
351
402
  flakyMinRuns: undefined,
403
+ // v3.16.0
404
+ impactGraph: false,
405
+ impactGraphFormat: "mermaid",
406
+ e2eTool: "auto",
407
+ // v3.17.0
408
+ createRule: undefined,
409
+ createRuleDir: "./rules",
410
+ createRuleCategory: "security",
411
+ createRuleSeverity: "warning",
412
+ createRuleFix: false,
413
+ createRuleLang: "js",
414
+ marketIndex: false,
415
+ marketIndexJson: false,
416
+ marketIndexUrl: undefined,
417
+ ruleScores: false,
418
+ ruleScoresJson: false,
419
+ // v3.18.0
420
+ generateRuleDocs: false,
421
+ generateRuleDocsDir: "./docs/rules",
422
+ checkRuleCompat: false,
423
+ // v3.15.0
424
+ profile: false,
425
+ showProgress: true,
426
+ recommendConfig: false,
352
427
  mcp: false,
353
428
  };
354
429
 
@@ -674,6 +749,65 @@ async function main() {
674
749
  case "--recommend-tests":
675
750
  options.recommendTests = true;
676
751
  break;
752
+ case "--impact-graph":
753
+ options.impactGraph = true;
754
+ break;
755
+ case "--impact-graph-format":
756
+ options.impactGraphFormat = args[++i];
757
+ break;
758
+ case "--e2e-tool":
759
+ options.e2eTool = args[++i];
760
+ break;
761
+ // v3.17.0
762
+ case "--create-rule": {
763
+ const next = args[i + 1];
764
+ if (next && !next.startsWith("-")) {
765
+ options.createRule = args[++i];
766
+ } else {
767
+ options.createRule = true;
768
+ }
769
+ break;
770
+ }
771
+ case "--create-rule-dir":
772
+ options.createRuleDir = args[++i];
773
+ break;
774
+ case "--create-rule-category":
775
+ options.createRuleCategory = args[++i];
776
+ break;
777
+ case "--create-rule-severity":
778
+ options.createRuleSeverity = args[++i];
779
+ break;
780
+ case "--create-rule-fix":
781
+ options.createRuleFix = true;
782
+ break;
783
+ case "--create-rule-lang":
784
+ options.createRuleLang = args[++i];
785
+ break;
786
+ case "--market-index":
787
+ options.marketIndex = true;
788
+ break;
789
+ case "--market-index-json":
790
+ options.marketIndexJson = true;
791
+ break;
792
+ case "--market-index-url":
793
+ options.marketIndexUrl = args[++i];
794
+ break;
795
+ case "--rule-scores":
796
+ options.ruleScores = true;
797
+ break;
798
+ case "--rule-scores-json":
799
+ options.ruleScoresJson = true;
800
+ break;
801
+ // v3.18.0
802
+ case "--generate-rule-docs":
803
+ options.generateRuleDocs = true;
804
+ break;
805
+ case "--generate-rule-docs-dir":
806
+ options.generateRuleDocsDir = args[++i];
807
+ break;
808
+ case "--check-rule-compat":
809
+ options.checkRuleCompat = true;
810
+ break;
677
811
  case "--flaky-threshold-failure-rate":
678
812
  options.flakyThresholdFailureRate = parseFloat(args[++i]);
679
813
  break;
@@ -686,6 +820,16 @@ async function main() {
686
820
  case "--mcp":
687
821
  options.mcp = true;
688
822
  break;
823
+ // v3.15.0
824
+ case "--profile":
825
+ options.profile = true;
826
+ break;
827
+ case "--no-progress":
828
+ options.showProgress = false;
829
+ break;
830
+ case "--recommend-config":
831
+ options.recommendConfig = true;
832
+ break;
689
833
  case "--help":
690
834
  case "-h":
691
835
  showHelp();
@@ -704,6 +848,102 @@ async function main() {
704
848
  return;
705
849
  }
706
850
 
851
+ // v3.15.0: 配置推荐
852
+ if (options.recommendConfig) {
853
+ const rec = recommendConfig(options.projectDir);
854
+ if (options.json) {
855
+ console.log(JSON.stringify(rec, null, 2));
856
+ } else {
857
+ console.log(formatRecommendedConfig(rec));
858
+ }
859
+ process.exit(0);
860
+ }
861
+
862
+ // v3.17.0: 生成规则模板
863
+ if (options.createRule) {
864
+ const ruleId = typeof options.createRule === "string" ? options.createRule : undefined;
865
+ if (!ruleId) {
866
+ console.log(pc.yellow("⚠️ 请提供规则 ID,例如: --create-rule no-console-log"));
867
+ process.exit(1);
868
+ }
869
+ try {
870
+ const result = generateRuleTemplate({
871
+ targetDir: resolve(options.projectDir, options.createRuleDir),
872
+ ruleId,
873
+ category: options.createRuleCategory,
874
+ severity: options.createRuleSeverity,
875
+ includeFix: options.createRuleFix,
876
+ language: options.createRuleLang,
877
+ });
878
+ console.log(pc.cyan("📝 规则模板已生成"));
879
+ console.log(pc.green(` ✅ 规则文件: ${result.rulePath}`));
880
+ console.log(pc.green(` ✅ 测试文件: ${result.testPath}`));
881
+ process.exit(0);
882
+ } catch (err) {
883
+ console.error(pc.red("❌ 生成规则模板失败:"), err instanceof Error ? err.message : String(err));
884
+ process.exit(1);
885
+ }
886
+ }
887
+
888
+ // v3.17.0: 规则市场索引
889
+ if (options.marketIndex || options.marketIndexJson) {
890
+ const market = await loadMarketIndex({
891
+ url: options.marketIndexUrl,
892
+ projectDir: options.projectDir,
893
+ });
894
+ if (options.marketIndexJson) {
895
+ console.log(formatMarketIndexJson(market));
896
+ } else {
897
+ console.log(formatMarketIndex(market));
898
+ }
899
+ process.exit(0);
900
+ }
901
+
902
+ // v3.17.0: 规则评分
903
+ if (options.ruleScores || options.ruleScoresJson) {
904
+ const summary = computeRuleScores(options.projectDir);
905
+ if (options.ruleScoresJson) {
906
+ console.log(formatRuleScoresJson(summary));
907
+ } else {
908
+ console.log(formatRuleScores(summary));
909
+ }
910
+ process.exit(0);
911
+ }
912
+
913
+ // v3.18.0: 规则文档生成
914
+ if (options.generateRuleDocs) {
915
+ try {
916
+ const engine = createEngine({ projectDir: options.projectDir, configFile: options.configFile });
917
+ const rules = engine.getRules ? engine.getRules() : Object.values(MODULE_RULES).flat();
918
+ const result = generateRuleDocs({
919
+ outputDir: resolve(options.projectDir, options.generateRuleDocsDir),
920
+ rules,
921
+ });
922
+ console.log(pc.cyan(formatGeneratedDocs(result)));
923
+ process.exit(0);
924
+ } catch (err) {
925
+ console.error(pc.red("❌ 生成规则文档失败:"), err instanceof Error ? err.message : String(err));
926
+ process.exit(1);
927
+ }
928
+ }
929
+
930
+ // v3.18.0: 规则兼容性检查
931
+ if (options.checkRuleCompat) {
932
+ try {
933
+ const engine = createEngine({ projectDir: options.projectDir, configFile: options.configFile });
934
+ const report = engine.checkRuleCompatibility();
935
+ if (options.json) {
936
+ console.log(formatCompatibilityReportJson(report));
937
+ } else {
938
+ console.log(formatCompatibilityReport(report));
939
+ }
940
+ process.exit(isCompatibilityReportClean(report) ? 0 : 2);
941
+ } catch (err) {
942
+ console.error(pc.red("❌ 规则兼容性检查失败:"), err instanceof Error ? err.message : String(err));
943
+ process.exit(1);
944
+ }
945
+ }
946
+
707
947
  // v3.6.0: E2E 测试覆盖缺口检测
708
948
  if (options.e2eDetectGaps) {
709
949
  const gapResult = detectE2EGaps({ projectDir: options.projectDir });
@@ -715,28 +955,49 @@ async function main() {
715
955
  process.exit(gapResult.uncoveredPages.length + gapResult.uncoveredApis.length > 0 ? 1 : 0);
716
956
  }
717
957
 
718
- // v3.6.1: 运行 Playwright E2E 测试
958
+ // v3.6.1 / v3.16.0: 运行 E2E 测试(支持 Playwright / Cypress / Selenium / Katalon)
719
959
  if (options.e2eRun) {
720
- if (!playwrightIntegration.isAvailable(options.projectDir)) {
721
- console.log(pc.yellow("⚠️ 未检测到 Playwright 配置(playwright.config.ts/js)或 playwright 包未安装"));
722
- console.log(pc.gray(" 请先安装 Playwright: npm install -D @playwright/test"));
723
- console.log(pc.gray(" 或初始化配置: npx playwright init"));
960
+ const tools = [
961
+ { name: "Playwright", integration: playwrightIntegration },
962
+ { name: "Cypress", integration: cypressIntegration },
963
+ { name: "Selenium", integration: seleniumIntegration },
964
+ { name: "Katalon", integration: katalonIntegration },
965
+ ];
966
+
967
+ let selected;
968
+ if (options.e2eTool === "auto") {
969
+ selected = tools.find((t) => t.integration.isAvailable(options.projectDir));
970
+ } else {
971
+ selected = tools.find((t) => t.name.toLowerCase() === options.e2eTool.toLowerCase());
972
+ if (selected && !selected.integration.isAvailable(options.projectDir)) {
973
+ console.log(pc.yellow(`⚠️ 未检测到 ${selected.name} 配置或依赖`));
974
+ process.exit(1);
975
+ }
976
+ }
977
+
978
+ if (!selected) {
979
+ if (options.e2eTool === "auto") {
980
+ console.log(pc.yellow("⚠️ 未检测到任何支持的 E2E 工具(Playwright / Cypress / Selenium / Katalon)"));
981
+ } else {
982
+ console.log(pc.yellow(`⚠️ 未知的 E2E 工具: ${options.e2eTool}`));
983
+ }
984
+ console.log(pc.gray(" 支持的工具: auto | playwright | cypress | selenium | katalon"));
724
985
  process.exit(1);
725
986
  }
726
987
 
727
- console.log(pc.cyan("🎭 正在运行 Playwright E2E 测试..."));
988
+ console.log(pc.cyan(`🎭 正在运行 ${selected.name} E2E 测试...`));
728
989
  console.log(pc.gray(" 这可能需要几分钟(取决于测试数量和浏览器启动时间)"));
729
990
  console.log("");
730
991
 
731
992
  const start = Date.now();
732
- const issues = playwrightIntegration.run(options.projectDir);
993
+ const issues = selected.integration.run(options.projectDir);
733
994
  const duration = Date.now() - start;
734
995
 
735
996
  if (options.json) {
736
997
  console.log(
737
998
  JSON.stringify(
738
999
  {
739
- tool: "Playwright",
1000
+ tool: selected.name,
740
1001
  total: issues.length,
741
1002
  duration,
742
1003
  issues,
@@ -746,12 +1007,12 @@ async function main() {
746
1007
  )
747
1008
  );
748
1009
  } else {
749
- console.log(pc.cyan(`📊 Playwright 测试结果`));
1010
+ console.log(pc.cyan(`📊 ${selected.name} 测试结果`));
750
1011
  console.log(pc.gray(` 耗时: ${duration}ms`));
751
1012
  if (issues.length === 0) {
752
1013
  console.log(pc.green(` ✅ 所有测试通过`));
753
1014
  } else {
754
- console.log(pc.red(` ❌ ${issues.length} 个测试失败`));
1015
+ console.log(pc.red(` ❌ ${issues.length} 个测试失败/异常`));
755
1016
  for (const issue of issues) {
756
1017
  const severityColor = issue.severity === "critical" ? pc.red : pc.yellow;
757
1018
  console.log(severityColor(` [${issue.severity.toUpperCase()}] ${issue.title}`));
@@ -978,7 +1239,7 @@ async function main() {
978
1239
  process.exit(0);
979
1240
  }
980
1241
 
981
- // v3.9.0: 智能测试推荐
1242
+ // v3.9.0 / v3.16.0: 智能测试推荐
982
1243
  if (options.recommendTests) {
983
1244
  const { recommendTests, formatRecommendations, formatRecommendationsJson } = await import("../dist/index.js");
984
1245
  const changedFiles = options.files?.map((f) => resolve(options.projectDir, f));
@@ -995,12 +1256,18 @@ async function main() {
995
1256
  flipRate: options.flakyThresholdFlipRate,
996
1257
  minRuns: options.flakyMinRuns,
997
1258
  },
1259
+ includeImpactGraph: options.impactGraph,
998
1260
  });
999
1261
 
1000
1262
  if (options.json) {
1001
1263
  console.log(JSON.stringify(formatRecommendationsJson(result), null, 2));
1002
1264
  } else {
1003
1265
  console.log(formatRecommendations(result));
1266
+ if (options.impactGraph && result.impactGraph) {
1267
+ console.log("");
1268
+ console.log(pc.cyan("🕸️ 变更影响图"));
1269
+ console.log(formatImpactGraph(result.impactGraph, options.impactGraphFormat));
1270
+ }
1004
1271
  }
1005
1272
 
1006
1273
  process.exit(result.uncoveredChanges.length > 0 ? 1 : 0);
@@ -1304,7 +1571,30 @@ async function main() {
1304
1571
  const scanFn = options.module === "all" ? runAllModules : runSingleModule;
1305
1572
  // v2.6.0: Watch 模式复用 SmartCache,实现缓存预热
1306
1573
  const cacheInstance = options.cache !== false ? new SmartCache(options.projectDir) : undefined;
1307
- await runWatchMode(options, scanFn, cacheInstance);
1574
+ // v3.17.0: 需要先创建 engine,以便 watch 模式热重载自定义规则
1575
+ const engine = createEngine({
1576
+ projectDir: options.projectDir,
1577
+ minSeverity: options.minSeverity,
1578
+ files: options.files,
1579
+ exclude: options.exclude,
1580
+ configFile: options.configFile,
1581
+ staged: options.staged,
1582
+ diffRange: options.diffRange,
1583
+ autoScope: options.autoScope,
1584
+ external: options.external,
1585
+ cache: options.cache,
1586
+ cacheInstance,
1587
+ dryRun: options.dryRun,
1588
+ interactive: options.interactive,
1589
+ skipLargeFilesThreshold: options.skipLargeFilesThreshold,
1590
+ strategy: options.strategy,
1591
+ profile: options.profile,
1592
+ showProgress: options.showProgress,
1593
+ });
1594
+ for (const rules of Object.values(MODULE_RULES)) {
1595
+ engine.registerAll(rules);
1596
+ }
1597
+ await runWatchMode(options, scanFn, cacheInstance, runSingleModule, engine);
1308
1598
  } else if (options.module === "all") {
1309
1599
  await runAllModules(options);
1310
1600
  } else {
@@ -1315,7 +1605,7 @@ async function main() {
1315
1605
  async function runAllModules(options, cacheInstance) {
1316
1606
  console.log(pc.cyan("🛡️ Frontend Guardian Core"));
1317
1607
  console.log(pc.gray(` Project: ${options.projectDir}`));
1318
- console.log(pc.gray(` Module: all (9 modules)`));
1608
+ console.log(pc.gray(` Module: all (${MODULES.length} modules)`));
1319
1609
  if (options.staged) {
1320
1610
  console.log(pc.gray(` Mode: staged (git cached only)`));
1321
1611
  } else if (options.diffRange) {
@@ -1341,6 +1631,9 @@ async function runAllModules(options, cacheInstance) {
1341
1631
  interactive: options.interactive,
1342
1632
  skipLargeFilesThreshold: options.skipLargeFilesThreshold,
1343
1633
  strategy: options.strategy,
1634
+ // v3.15.0
1635
+ profile: options.profile,
1636
+ showProgress: options.showProgress,
1344
1637
  });
1345
1638
 
1346
1639
  // 注册所有模块的规则
@@ -1393,6 +1686,11 @@ async function runAllModules(options, cacheInstance) {
1393
1686
  }
1394
1687
 
1395
1688
  allResults[mod] = result;
1689
+ // v3.15.0: 输出扫描耗时分析
1690
+ if (options.profile && !options.json && result.profile) {
1691
+ console.log(formatScanProfile(result.profile));
1692
+ console.log("");
1693
+ }
1396
1694
  totalCritical += result.issues.critical.length;
1397
1695
  totalWarning += result.issues.warning.length;
1398
1696
  totalSuggestion += result.issues.suggestion.length;
@@ -1947,6 +2245,9 @@ async function runSingleModule(options, cacheInstance) {
1947
2245
  interactive: options.interactive,
1948
2246
  skipLargeFilesThreshold: options.skipLargeFilesThreshold,
1949
2247
  strategy: options.strategy,
2248
+ // v3.15.0
2249
+ profile: options.profile,
2250
+ showProgress: options.showProgress,
1950
2251
  });
1951
2252
 
1952
2253
  // 注册规则
@@ -1978,6 +2279,11 @@ async function runSingleModule(options, cacheInstance) {
1978
2279
  }
1979
2280
 
1980
2281
  const { issues } = result;
2282
+ // v3.15.0: 输出扫描耗时分析
2283
+ if (options.profile && !options.json && result.profile) {
2284
+ console.log(formatScanProfile(result.profile));
2285
+ console.log("");
2286
+ }
1981
2287
  let totalCritical = issues.critical.length;
1982
2288
  let totalWarning = issues.warning.length;
1983
2289
  let totalSuggestion = issues.suggestion.length;
package/bin/fg-lsp.js CHANGED
@@ -27,7 +27,7 @@ for (let i = 0; i < args.length; i++) {
27
27
  } else if (arg === "--severity" || arg === "-s") {
28
28
  minSeverity = args[++i] ?? "suggestion";
29
29
  } else if (arg === "--help" || arg === "-h") {
30
- console.log(`Frontend Guardian LSP Server v3.14.1
30
+ console.log(`Frontend Guardian LSP Server v3.20.0
31
31
 
32
32
  Usage: fg-lsp [options]
33
33
 
package/bin/fg-server.js CHANGED
@@ -10,7 +10,7 @@ import { DashboardServer } from "../dist/index.js";
10
10
 
11
11
  function showHelp() {
12
12
  console.log(`
13
- Frontend Guardian Dashboard Server v3.14.1
13
+ Frontend Guardian Dashboard Server v3.20.0
14
14
 
15
15
  Usage:
16
16
  fg-server [options]
@@ -62,7 +62,7 @@ async function main() {
62
62
  }
63
63
 
64
64
  console.log(pc.cyan("Frontend Guardian Dashboard Server"));
65
- console.log(pc.gray(` Version: 3.14.0`));
65
+ console.log(pc.gray(` Version: 3.20.0`));
66
66
  console.log("");
67
67
 
68
68
  const server = new DashboardServer({