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 +189 -149
- package/bin/fg-lsp.js +4 -3
- package/bin/fg-server.js +4 -3
- package/dist/engine/rule-engine.d.ts +6 -1
- package/dist/engine/rule-engine.d.ts.map +1 -1
- package/dist/engine/rule-engine.js +42 -33
- package/dist/engine/rule-engine.js.map +1 -1
- package/dist/index.d.ts +68 -66
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -137
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-server.d.ts +17 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +31 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/tools.d.ts +14 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +637 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +87 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/package.json +5 -4
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
|
-
|
|
11
|
-
i18nRules,
|
|
12
|
-
performanceRules,
|
|
11
|
+
AIFixSuggester,
|
|
13
12
|
a11yRules,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
BaselineManager,
|
|
14
|
+
buildNotificationPayload,
|
|
15
|
+
CodeownersParser,
|
|
16
|
+
compareHistoryReports,
|
|
17
|
+
complianceReportToMarkdown,
|
|
17
18
|
componentRules,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
40
|
+
formatPageHealthJson,
|
|
41
|
+
formatPageHealthReport,
|
|
42
|
+
formatWorkspaceJson,
|
|
43
|
+
formatWorkspaceReport,
|
|
44
|
+
generateCIConfig,
|
|
60
45
|
generateComplianceReport,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
60
|
+
performanceRules,
|
|
61
|
+
platformRules,
|
|
62
|
+
playwrightIntegration,
|
|
63
|
+
runFixBot,
|
|
70
64
|
runPageHealthCheck,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
|
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(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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", {
|
|
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(
|
|
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(
|
|
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 = [
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
?
|
|
1299
|
-
:
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
2011
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|