proof-pr 0.1.15 → 0.1.16
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/README.md +5 -3
- package/dist/index.js +295 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ ProofPR 是给开源维护者和工程团队使用的 PR 证据门禁。它在
|
|
|
12
12
|
npx proof-pr@latest --version
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
当前应输出 `0.1.
|
|
15
|
+
当前应输出 `0.1.16`。
|
|
16
16
|
|
|
17
17
|
不知道用哪个功能时:
|
|
18
18
|
|
|
@@ -69,6 +69,8 @@ npx proof-pr@latest demo workflow --locale zh-CN
|
|
|
69
69
|
npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN --format html --output proofpr-report.html
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
HTML 报告支持筛选风险、搜索规则/文件/详情,并复制补证清单。
|
|
73
|
+
|
|
72
74
|
运行 benchmark:
|
|
73
75
|
|
|
74
76
|
```bash
|
|
@@ -78,7 +80,7 @@ npx proof-pr@latest benchmark --cases benchmarks/cases
|
|
|
78
80
|
## GitHub Action
|
|
79
81
|
|
|
80
82
|
```yaml
|
|
81
|
-
- uses: linsk27/proof-pr@v0.1.
|
|
83
|
+
- uses: linsk27/proof-pr@v0.1.16
|
|
82
84
|
with:
|
|
83
85
|
fail-on: high
|
|
84
86
|
comment: "true"
|
|
@@ -91,7 +93,7 @@ npx proof-pr@latest benchmark --cases benchmarks/cases
|
|
|
91
93
|
- 证据评分:0-100 分。
|
|
92
94
|
- Review 门禁:正常 review、重点 review、先补证据、风险处理前不要合并。
|
|
93
95
|
- Review 行动清单:维护者可直接执行的 checklist。
|
|
94
|
-
- 可选输出:GitHub annotations、SARIF、benchmark report
|
|
96
|
+
- 可选输出:GitHub annotations、SARIF、benchmark report、可筛选并可复制补证清单的独立 HTML 可视化报告;CLI 可用 `--output` 直接写文件。
|
|
95
97
|
|
|
96
98
|
## 常用预设
|
|
97
99
|
|
package/dist/index.js
CHANGED
|
@@ -23378,6 +23378,7 @@ function renderHtmlReport(result, locale = "en") {
|
|
|
23378
23378
|
const scoreGrade = formatEvidenceGrade(result.evidenceScore.grade, locale);
|
|
23379
23379
|
const findingsBySeverity = countFindingsBySeverity(result.findings);
|
|
23380
23380
|
const ruleCounts = countFindingsByRule(result.findings);
|
|
23381
|
+
const fixPrompt = renderContributorFixPrompt(result, locale);
|
|
23381
23382
|
const evidenceSignals = [
|
|
23382
23383
|
[labels.prDescription, locale === "zh-CN" ? translateDescriptionState(result.summary.pullRequestDescription) : result.summary.pullRequestDescription, result.summary.pullRequestDescription === "present"],
|
|
23383
23384
|
[labels.verification, yesNo(result.summary.verificationEvidence, locale), result.summary.verificationEvidence],
|
|
@@ -23549,6 +23550,69 @@ function renderHtmlReport(result, locale = "en") {
|
|
|
23549
23550
|
gap: 10px;
|
|
23550
23551
|
}
|
|
23551
23552
|
|
|
23553
|
+
.fix-panel {
|
|
23554
|
+
display: grid;
|
|
23555
|
+
gap: 12px;
|
|
23556
|
+
}
|
|
23557
|
+
|
|
23558
|
+
.fix-text {
|
|
23559
|
+
margin: 0;
|
|
23560
|
+
white-space: pre-wrap;
|
|
23561
|
+
border: 1px solid var(--line);
|
|
23562
|
+
border-radius: 8px;
|
|
23563
|
+
background: #fbfcfd;
|
|
23564
|
+
padding: 12px;
|
|
23565
|
+
color: var(--ink);
|
|
23566
|
+
overflow-x: auto;
|
|
23567
|
+
}
|
|
23568
|
+
|
|
23569
|
+
.copy-button, .filter-button {
|
|
23570
|
+
appearance: none;
|
|
23571
|
+
border: 1px solid var(--line);
|
|
23572
|
+
border-radius: 8px;
|
|
23573
|
+
background: var(--panel);
|
|
23574
|
+
color: var(--ink);
|
|
23575
|
+
font: inherit;
|
|
23576
|
+
font-size: 13px;
|
|
23577
|
+
padding: 8px 11px;
|
|
23578
|
+
cursor: pointer;
|
|
23579
|
+
}
|
|
23580
|
+
|
|
23581
|
+
.copy-button {
|
|
23582
|
+
justify-self: flex-start;
|
|
23583
|
+
border-color: #b8d5f4;
|
|
23584
|
+
background: var(--soft-blue);
|
|
23585
|
+
color: var(--blue);
|
|
23586
|
+
font-weight: 680;
|
|
23587
|
+
}
|
|
23588
|
+
|
|
23589
|
+
.filterbar {
|
|
23590
|
+
display: flex;
|
|
23591
|
+
flex-wrap: wrap;
|
|
23592
|
+
gap: 8px;
|
|
23593
|
+
margin-bottom: 12px;
|
|
23594
|
+
align-items: center;
|
|
23595
|
+
}
|
|
23596
|
+
|
|
23597
|
+
.filter-button.active {
|
|
23598
|
+
border-color: #b8d5f4;
|
|
23599
|
+
background: var(--soft-blue);
|
|
23600
|
+
color: var(--blue);
|
|
23601
|
+
font-weight: 680;
|
|
23602
|
+
}
|
|
23603
|
+
|
|
23604
|
+
.finding-search {
|
|
23605
|
+
min-width: min(320px, 100%);
|
|
23606
|
+
flex: 1 1 240px;
|
|
23607
|
+
border: 1px solid var(--line);
|
|
23608
|
+
border-radius: 8px;
|
|
23609
|
+
background: #fff;
|
|
23610
|
+
color: var(--ink);
|
|
23611
|
+
font: inherit;
|
|
23612
|
+
font-size: 13px;
|
|
23613
|
+
padding: 8px 11px;
|
|
23614
|
+
}
|
|
23615
|
+
|
|
23552
23616
|
.signal, .action, .focus, .deduction, .rule-row {
|
|
23553
23617
|
border: 1px solid var(--line);
|
|
23554
23618
|
border-radius: 8px;
|
|
@@ -23627,12 +23691,20 @@ function renderHtmlReport(result, locale = "en") {
|
|
|
23627
23691
|
background: #fff;
|
|
23628
23692
|
}
|
|
23629
23693
|
|
|
23694
|
+
.finding[hidden] { display: none; }
|
|
23695
|
+
|
|
23630
23696
|
.finding-head {
|
|
23631
23697
|
display: flex;
|
|
23632
23698
|
justify-content: space-between;
|
|
23633
23699
|
gap: 12px;
|
|
23634
23700
|
align-items: flex-start;
|
|
23635
23701
|
margin-bottom: 8px;
|
|
23702
|
+
cursor: pointer;
|
|
23703
|
+
list-style: none;
|
|
23704
|
+
}
|
|
23705
|
+
|
|
23706
|
+
.finding-head::-webkit-details-marker {
|
|
23707
|
+
display: none;
|
|
23636
23708
|
}
|
|
23637
23709
|
|
|
23638
23710
|
code {
|
|
@@ -23722,6 +23794,15 @@ function renderHtmlReport(result, locale = "en") {
|
|
|
23722
23794
|
</div>
|
|
23723
23795
|
</article>
|
|
23724
23796
|
|
|
23797
|
+
<article class="card full">
|
|
23798
|
+
<h2>${labels.quickFix}</h2>
|
|
23799
|
+
<div class="fix-panel">
|
|
23800
|
+
<p class="muted">${labels.quickFixHint}</p>
|
|
23801
|
+
<pre class="fix-text" id="proofpr-fix-prompt">${escapeHtml(fixPrompt)}</pre>
|
|
23802
|
+
<button class="copy-button" type="button" data-copy-target="proofpr-fix-prompt" data-label="${escapeHtml(labels.copyFix)}" data-copied="${escapeHtml(labels.copiedFix)}">${labels.copyFix}</button>
|
|
23803
|
+
</div>
|
|
23804
|
+
</article>
|
|
23805
|
+
|
|
23725
23806
|
<article class="card wide">
|
|
23726
23807
|
<h2>${labels.reviewPlan}</h2>
|
|
23727
23808
|
<div class="action-list">
|
|
@@ -23782,16 +23863,77 @@ function renderHtmlReport(result, locale = "en") {
|
|
|
23782
23863
|
|
|
23783
23864
|
<article class="card full">
|
|
23784
23865
|
<h2>${labels.findings}</h2>
|
|
23866
|
+
<div class="filterbar" aria-label="${labels.findingFilters}">
|
|
23867
|
+
${findingFilterButton(labels.allFindings, "all", result.findings.length, true)}
|
|
23868
|
+
${findingFilterButton(labels.high, "high", findingsBySeverity.high)}
|
|
23869
|
+
${findingFilterButton(labels.medium, "medium", findingsBySeverity.medium)}
|
|
23870
|
+
${findingFilterButton(labels.low, "low", findingsBySeverity.low)}
|
|
23871
|
+
${findingFilterButton(labels.info, "info", findingsBySeverity.info)}
|
|
23872
|
+
<input class="finding-search" id="proofpr-finding-search" type="search" placeholder="${escapeHtml(labels.searchFindings)}">
|
|
23873
|
+
</div>
|
|
23785
23874
|
<div class="finding-list">
|
|
23786
23875
|
${result.findings.length > 0
|
|
23787
23876
|
? result.findings.map((finding) => htmlFinding(finding, locale)).join("\n")
|
|
23788
23877
|
: `<div class="muted">${labels.noFindings}</div>`}
|
|
23878
|
+
<div class="muted" id="proofpr-empty-filter" hidden>${labels.noFilteredFindings}</div>
|
|
23789
23879
|
</div>
|
|
23790
23880
|
</article>
|
|
23791
23881
|
</section>
|
|
23792
23882
|
|
|
23793
23883
|
<p class="footer">${labels.footer}</p>
|
|
23794
23884
|
</main>
|
|
23885
|
+
<script>
|
|
23886
|
+
(() => {
|
|
23887
|
+
const buttons = Array.from(document.querySelectorAll("[data-filter-severity]"));
|
|
23888
|
+
const search = document.getElementById("proofpr-finding-search");
|
|
23889
|
+
const findings = Array.from(document.querySelectorAll("[data-finding]"));
|
|
23890
|
+
const empty = document.getElementById("proofpr-empty-filter");
|
|
23891
|
+
let activeSeverity = "all";
|
|
23892
|
+
|
|
23893
|
+
const applyFilters = () => {
|
|
23894
|
+
const query = (search?.value || "").trim().toLowerCase();
|
|
23895
|
+
let visible = 0;
|
|
23896
|
+
|
|
23897
|
+
for (const finding of findings) {
|
|
23898
|
+
const severity = finding.getAttribute("data-severity") || "";
|
|
23899
|
+
const haystack = finding.getAttribute("data-search") || "";
|
|
23900
|
+
const severityMatches = activeSeverity === "all" || severity === activeSeverity;
|
|
23901
|
+
const queryMatches = query === "" || haystack.includes(query);
|
|
23902
|
+
const show = severityMatches && queryMatches;
|
|
23903
|
+
finding.hidden = !show;
|
|
23904
|
+
if (show) visible += 1;
|
|
23905
|
+
}
|
|
23906
|
+
|
|
23907
|
+
if (empty) empty.hidden = visible !== 0 || findings.length === 0;
|
|
23908
|
+
};
|
|
23909
|
+
|
|
23910
|
+
for (const button of buttons) {
|
|
23911
|
+
button.addEventListener("click", () => {
|
|
23912
|
+
activeSeverity = button.getAttribute("data-filter-severity") || "all";
|
|
23913
|
+
for (const item of buttons) item.classList.toggle("active", item === button);
|
|
23914
|
+
applyFilters();
|
|
23915
|
+
});
|
|
23916
|
+
}
|
|
23917
|
+
|
|
23918
|
+
search?.addEventListener("input", applyFilters);
|
|
23919
|
+
|
|
23920
|
+
for (const button of Array.from(document.querySelectorAll("[data-copy-target]"))) {
|
|
23921
|
+
button.addEventListener("click", async () => {
|
|
23922
|
+
const target = document.getElementById(button.getAttribute("data-copy-target") || "");
|
|
23923
|
+
const text = target?.textContent || "";
|
|
23924
|
+
try {
|
|
23925
|
+
await navigator.clipboard.writeText(text);
|
|
23926
|
+
button.textContent = button.getAttribute("data-copied") || button.textContent;
|
|
23927
|
+
setTimeout(() => {
|
|
23928
|
+
button.textContent = button.getAttribute("data-label") || button.textContent;
|
|
23929
|
+
}, 1200);
|
|
23930
|
+
} catch {
|
|
23931
|
+
button.textContent = text;
|
|
23932
|
+
}
|
|
23933
|
+
});
|
|
23934
|
+
}
|
|
23935
|
+
})();
|
|
23936
|
+
</script>
|
|
23795
23937
|
</body>
|
|
23796
23938
|
</html>
|
|
23797
23939
|
`;
|
|
@@ -23844,9 +23986,14 @@ function renderEnglishMarkdownReport(result) {
|
|
|
23844
23986
|
REPORT_MARKER,
|
|
23845
23987
|
"# ProofPR Review",
|
|
23846
23988
|
"",
|
|
23847
|
-
|
|
23848
|
-
|
|
23849
|
-
|
|
23989
|
+
"## Summary",
|
|
23990
|
+
"",
|
|
23991
|
+
"| Item | Result |",
|
|
23992
|
+
"| --- | --- |",
|
|
23993
|
+
`| Risk | **${result.risk}** |`,
|
|
23994
|
+
`| Evidence score | **${result.evidenceScore.value}/100 (${formatEvidenceGrade(result.evidenceScore.grade, "en")})** |`,
|
|
23995
|
+
`| Review gate | **${formatReviewDecision(result.reviewDecision, "en")}** |`,
|
|
23996
|
+
`| Findings | ${result.findings.length} |`,
|
|
23850
23997
|
"",
|
|
23851
23998
|
"## Evidence",
|
|
23852
23999
|
"",
|
|
@@ -23864,6 +24011,7 @@ function renderEnglishMarkdownReport(result) {
|
|
|
23864
24011
|
""
|
|
23865
24012
|
];
|
|
23866
24013
|
appendEvidenceScoreSection(lines, result, "en");
|
|
24014
|
+
appendQuickFixSection(lines, result, "en");
|
|
23867
24015
|
appendReviewPlanSection(lines, result, "en");
|
|
23868
24016
|
if (result.findings.length === 0) {
|
|
23869
24017
|
lines.push("## Findings", "", "No review-risk findings detected by the enabled rules.", "");
|
|
@@ -23881,9 +24029,14 @@ function renderChineseMarkdownReport(result) {
|
|
|
23881
24029
|
REPORT_MARKER,
|
|
23882
24030
|
"# ProofPR 审查报告",
|
|
23883
24031
|
"",
|
|
23884
|
-
|
|
23885
|
-
|
|
23886
|
-
|
|
24032
|
+
"## 总览",
|
|
24033
|
+
"",
|
|
24034
|
+
"| 项目 | 结果 |",
|
|
24035
|
+
"| --- | --- |",
|
|
24036
|
+
`| 风险等级 | **${translateRisk(result.risk)}** |`,
|
|
24037
|
+
`| 证据评分 | **${result.evidenceScore.value}/100(${formatEvidenceGrade(result.evidenceScore.grade, "zh-CN")})** |`,
|
|
24038
|
+
`| Review 门禁 | **${formatReviewDecision(result.reviewDecision, "zh-CN")}** |`,
|
|
24039
|
+
`| 风险发现 | ${result.findings.length} |`,
|
|
23887
24040
|
"",
|
|
23888
24041
|
"## 证据概览",
|
|
23889
24042
|
"",
|
|
@@ -23901,6 +24054,7 @@ function renderChineseMarkdownReport(result) {
|
|
|
23901
24054
|
""
|
|
23902
24055
|
];
|
|
23903
24056
|
appendEvidenceScoreSection(lines, result, "zh-CN");
|
|
24057
|
+
appendQuickFixSection(lines, result, "zh-CN");
|
|
23904
24058
|
appendReviewPlanSection(lines, result, "zh-CN");
|
|
23905
24059
|
if (result.findings.length === 0) {
|
|
23906
24060
|
lines.push("## 风险发现", "", "启用的规则没有发现需要优先关注的 review 风险。", "");
|
|
@@ -23937,6 +24091,13 @@ function appendEvidenceScoreSection(lines, result, locale) {
|
|
|
23937
24091
|
}
|
|
23938
24092
|
lines.push("");
|
|
23939
24093
|
}
|
|
24094
|
+
function appendQuickFixSection(lines, result, locale) {
|
|
24095
|
+
lines.push(locale === "zh-CN" ? "## 可复制补证清单" : "## Copyable Fix Checklist", "");
|
|
24096
|
+
lines.push(locale === "zh-CN"
|
|
24097
|
+
? "贡献者可以直接复制下面内容补到 PR 描述里,维护者也可以把它作为 review 回复。"
|
|
24098
|
+
: "Contributors can paste this into the PR description; maintainers can also use it as a review reply.");
|
|
24099
|
+
lines.push("", "```md", renderContributorFixPrompt(result, locale), "```", "");
|
|
24100
|
+
}
|
|
23940
24101
|
function appendReviewPlanSection(lines, result, locale) {
|
|
23941
24102
|
lines.push(locale === "zh-CN" ? "## Review 行动清单" : "## Review Plan", "");
|
|
23942
24103
|
if (result.reviewPlan.actionItems.length > 0) {
|
|
@@ -23959,6 +24120,97 @@ function appendReviewPlanSection(lines, result, locale) {
|
|
|
23959
24120
|
}
|
|
23960
24121
|
lines.push("");
|
|
23961
24122
|
}
|
|
24123
|
+
function renderContributorFixPrompt(result, locale) {
|
|
24124
|
+
const missingEvidence = missingEvidenceLabels(result, locale);
|
|
24125
|
+
const actions = result.reviewPlan.actionItems.slice(0, 6);
|
|
24126
|
+
const focusFiles = result.reviewPlan.focusFiles.slice(0, 5);
|
|
24127
|
+
if (locale === "zh-CN") {
|
|
24128
|
+
const lines = [
|
|
24129
|
+
"请在这个 PR 描述中补充以下内容,方便维护者继续 review:",
|
|
24130
|
+
missingEvidence.length > 0 ? `ProofPR 当前最缺:${missingEvidence.join("、")}。` : "ProofPR 当前没有发现必须补充的证据项,可以保留关键验证记录。",
|
|
24131
|
+
"",
|
|
24132
|
+
"## 验证方式",
|
|
24133
|
+
"- 自动化测试:",
|
|
24134
|
+
"- 手动验证:",
|
|
24135
|
+
"- 未覆盖或不适用的部分:",
|
|
24136
|
+
"",
|
|
24137
|
+
"## 复现 / Before & After",
|
|
24138
|
+
"- 复现步骤或改动前状态:",
|
|
24139
|
+
"- 改动后结果:",
|
|
24140
|
+
"- 截图 / 录屏 / 日志链接:",
|
|
24141
|
+
"",
|
|
24142
|
+
"## 风险说明",
|
|
24143
|
+
"- 依赖 / CI / 权限 / MCP 变更原因:",
|
|
24144
|
+
"- 发布影响、迁移说明或回滚方案:"
|
|
24145
|
+
];
|
|
24146
|
+
if (actions.length > 0) {
|
|
24147
|
+
lines.push("", "## ProofPR 需要处理的点");
|
|
24148
|
+
for (const action of actions) {
|
|
24149
|
+
lines.push(`- ${translateReviewActionTitle(action.actionId, action.title)}:${translateReviewActionDetail(action.actionId, action.detail)}`);
|
|
24150
|
+
}
|
|
24151
|
+
}
|
|
24152
|
+
if (focusFiles.length > 0) {
|
|
24153
|
+
lines.push("", "## 重点文件");
|
|
24154
|
+
for (const file of focusFiles) {
|
|
24155
|
+
lines.push(`- ${file.path}:${translateFocusReason(file.reasonId, file.reason)}`);
|
|
24156
|
+
}
|
|
24157
|
+
}
|
|
24158
|
+
return lines.join("\n");
|
|
24159
|
+
}
|
|
24160
|
+
const lines = [
|
|
24161
|
+
"Please add the following context to this PR so maintainers can continue review:",
|
|
24162
|
+
missingEvidence.length > 0 ? `ProofPR is currently missing: ${missingEvidence.join(", ")}.` : "ProofPR did not find required missing evidence; keep the key verification notes visible.",
|
|
24163
|
+
"",
|
|
24164
|
+
"## Verification",
|
|
24165
|
+
"- Automated tests:",
|
|
24166
|
+
"- Manual verification:",
|
|
24167
|
+
"- Not covered or not applicable:",
|
|
24168
|
+
"",
|
|
24169
|
+
"## Reproduction / Before & After",
|
|
24170
|
+
"- Reproduction steps or previous state:",
|
|
24171
|
+
"- Result after this change:",
|
|
24172
|
+
"- Screenshot / recording / log link:",
|
|
24173
|
+
"",
|
|
24174
|
+
"## Risk Notes",
|
|
24175
|
+
"- Dependency / CI / permission / MCP rationale:",
|
|
24176
|
+
"- Release impact, migration notes, or rollback plan:"
|
|
24177
|
+
];
|
|
24178
|
+
if (actions.length > 0) {
|
|
24179
|
+
lines.push("", "## ProofPR Items To Resolve");
|
|
24180
|
+
for (const action of actions) {
|
|
24181
|
+
lines.push(`- ${action.title}: ${action.detail}`);
|
|
24182
|
+
}
|
|
24183
|
+
}
|
|
24184
|
+
if (focusFiles.length > 0) {
|
|
24185
|
+
lines.push("", "## Focus Files");
|
|
24186
|
+
for (const file of focusFiles) {
|
|
24187
|
+
lines.push(`- ${file.path}: ${file.reason}`);
|
|
24188
|
+
}
|
|
24189
|
+
}
|
|
24190
|
+
return lines.join("\n");
|
|
24191
|
+
}
|
|
24192
|
+
function missingEvidenceLabels(result, locale) {
|
|
24193
|
+
const labels = [];
|
|
24194
|
+
if (result.summary.pullRequestDescription !== "present") {
|
|
24195
|
+
labels.push(locale === "zh-CN" ? "清楚的 PR 描述" : "clear PR description");
|
|
24196
|
+
}
|
|
24197
|
+
if (!result.summary.verificationEvidence) {
|
|
24198
|
+
labels.push(locale === "zh-CN" ? "测试或手动验证" : "test or manual verification");
|
|
24199
|
+
}
|
|
24200
|
+
if (!result.summary.reproductionEvidence) {
|
|
24201
|
+
labels.push(locale === "zh-CN" ? "复现步骤或 before/after" : "reproduction steps or before/after context");
|
|
24202
|
+
}
|
|
24203
|
+
if (!result.summary.screenshotEvidence && result.findings.some((finding) => finding.ruleId.includes("screenshot"))) {
|
|
24204
|
+
labels.push(locale === "zh-CN" ? "截图或录屏" : "screenshot or recording");
|
|
24205
|
+
}
|
|
24206
|
+
if (!result.summary.changelogEvidence && result.findings.some((finding) => finding.ruleId.includes("dependency-major-upgrade"))) {
|
|
24207
|
+
labels.push(locale === "zh-CN" ? "changelog 或迁移说明" : "changelog or migration notes");
|
|
24208
|
+
}
|
|
24209
|
+
if (!result.summary.permissionRationaleEvidence && result.findings.some((finding) => finding.ruleId.includes("workflow"))) {
|
|
24210
|
+
labels.push(locale === "zh-CN" ? "权限变更理由" : "permission rationale");
|
|
24211
|
+
}
|
|
24212
|
+
return labels;
|
|
24213
|
+
}
|
|
23962
24214
|
function formatEnglishFinding(finding) {
|
|
23963
24215
|
const lines = [
|
|
23964
24216
|
`### ${finding.title}`,
|
|
@@ -24360,7 +24612,15 @@ function htmlLabels(locale) {
|
|
|
24360
24612
|
permissionRationale: "权限理由",
|
|
24361
24613
|
reviewPlan: "Review 行动清单",
|
|
24362
24614
|
noActions: "没有额外行动项。",
|
|
24615
|
+
quickFix: "一键补证建议",
|
|
24616
|
+
quickFixHint: "复制这段内容到 PR 描述或评论里,贡献者按空白项补齐即可。",
|
|
24617
|
+
copyFix: "复制补证清单",
|
|
24618
|
+
copiedFix: "已复制",
|
|
24363
24619
|
findingDistribution: "Finding 分布",
|
|
24620
|
+
findingFilters: "筛选风险发现",
|
|
24621
|
+
allFindings: "全部",
|
|
24622
|
+
searchFindings: "搜索规则、文件或详情",
|
|
24623
|
+
noFilteredFindings: "当前筛选条件下没有风险发现。",
|
|
24364
24624
|
high: "高",
|
|
24365
24625
|
medium: "中",
|
|
24366
24626
|
low: "低",
|
|
@@ -24408,7 +24668,15 @@ function htmlLabels(locale) {
|
|
|
24408
24668
|
permissionRationale: "Permission rationale",
|
|
24409
24669
|
reviewPlan: "Review plan",
|
|
24410
24670
|
noActions: "No additional action items.",
|
|
24671
|
+
quickFix: "One-click evidence fix",
|
|
24672
|
+
quickFixHint: "Copy this into the PR description or a review reply, then fill the blanks.",
|
|
24673
|
+
copyFix: "Copy checklist",
|
|
24674
|
+
copiedFix: "Copied",
|
|
24411
24675
|
findingDistribution: "Finding distribution",
|
|
24676
|
+
findingFilters: "Filter findings",
|
|
24677
|
+
allFindings: "All",
|
|
24678
|
+
searchFindings: "Search rules, files, or details",
|
|
24679
|
+
noFilteredFindings: "No findings match the current filter.",
|
|
24412
24680
|
high: "High",
|
|
24413
24681
|
medium: "Medium",
|
|
24414
24682
|
low: "Low",
|
|
@@ -24438,9 +24706,25 @@ function signalItem(name, state, ok) {
|
|
|
24438
24706
|
function severityItem(severity, value, label) {
|
|
24439
24707
|
return `<div class="severity ${severity === "high" ? "tone-high" : severity === "medium" ? "tone-medium" : severity === "low" ? "tone-low" : ""}"><strong>${value}</strong><span>${escapeHtml(label)}</span></div>`;
|
|
24440
24708
|
}
|
|
24709
|
+
function findingFilterButton(label, severity, count, active = false) {
|
|
24710
|
+
return `<button class="filter-button${active ? " active" : ""}" type="button" data-filter-severity="${escapeHtml(severity)}">${escapeHtml(label)} <span class="muted">${count}</span></button>`;
|
|
24711
|
+
}
|
|
24441
24712
|
function htmlFinding(finding, locale) {
|
|
24442
24713
|
const labels = htmlLabels(locale);
|
|
24443
24714
|
const translated = locale === "zh-CN" ? translateFinding(finding) : finding;
|
|
24715
|
+
const searchText = [
|
|
24716
|
+
finding.ruleId,
|
|
24717
|
+
finding.severity,
|
|
24718
|
+
finding.path,
|
|
24719
|
+
translated.title,
|
|
24720
|
+
translated.message,
|
|
24721
|
+
translated.recommendation,
|
|
24722
|
+
...(finding.evidence ?? [])
|
|
24723
|
+
]
|
|
24724
|
+
.filter(Boolean)
|
|
24725
|
+
.join(" ")
|
|
24726
|
+
.toLowerCase()
|
|
24727
|
+
.replace(/\s+/g, " ");
|
|
24444
24728
|
const evidence = finding.evidence && finding.evidence.length > 0
|
|
24445
24729
|
? `<ul class="evidence-list">${finding.evidence
|
|
24446
24730
|
.map((item) => `<li><code>${escapeHtml(locale === "zh-CN" ? translateEvidence(item) : item)}</code></li>`)
|
|
@@ -24452,19 +24736,19 @@ function htmlFinding(finding, locale) {
|
|
|
24452
24736
|
const recommendation = translated.recommendation
|
|
24453
24737
|
? `<div class="muted">${labels.recommendation}: ${escapeHtml(translated.recommendation)}</div>`
|
|
24454
24738
|
: "";
|
|
24455
|
-
return `<
|
|
24456
|
-
<
|
|
24739
|
+
return `<details class="finding" open data-finding data-severity="${escapeHtml(finding.severity)}" data-search="${escapeHtml(searchText)}">
|
|
24740
|
+
<summary class="finding-head">
|
|
24457
24741
|
<div>
|
|
24458
24742
|
<div class="finding-title">${escapeHtml(translated.title)}</div>
|
|
24459
24743
|
<div class="muted">${labels.rule}: <code>${escapeHtml(finding.ruleId)}</code></div>
|
|
24460
24744
|
</div>
|
|
24461
24745
|
<span class="pill ${finding.severity === "high" ? "tone-high" : finding.severity === "medium" ? "tone-medium" : "tone-low"}">${escapeHtml(locale === "zh-CN" ? translateSeverity(finding.severity) : finding.severity)}</span>
|
|
24462
|
-
</
|
|
24746
|
+
</summary>
|
|
24463
24747
|
${path}
|
|
24464
24748
|
<div class="muted">${labels.detail}: ${escapeHtml(translated.message)}</div>
|
|
24465
24749
|
${evidence}
|
|
24466
24750
|
${recommendation}
|
|
24467
|
-
</
|
|
24751
|
+
</details>`;
|
|
24468
24752
|
}
|
|
24469
24753
|
function countFindingsBySeverity(findings) {
|
|
24470
24754
|
return findings.reduce((counts, finding) => {
|
|
@@ -25728,7 +26012,7 @@ function dedupeFindings(findings) {
|
|
|
25728
26012
|
|
|
25729
26013
|
|
|
25730
26014
|
const execFileAsync = (0,external_node_util_namespaceObject.promisify)(external_node_child_process_.execFile);
|
|
25731
|
-
const CLI_VERSION = "0.1.
|
|
26015
|
+
const CLI_VERSION = "0.1.16";
|
|
25732
26016
|
const DEMO_CASES = [
|
|
25733
26017
|
{
|
|
25734
26018
|
id: "workflow",
|