proof-pr 0.1.13 → 0.1.15

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 (3) hide show
  1. package/README.md +18 -5
  2. package/dist/index.js +287 -12
  3. 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.13`。
15
+ 当前应输出 `0.1.15`。
16
16
 
17
17
  不知道用哪个功能时:
18
18
 
@@ -22,13 +22,26 @@ npx proof-pr@latest
22
22
  npx proof-pr@latest guide
23
23
  ```
24
24
 
25
+ 不接入仓库,先体验报告:
26
+
27
+ ```bash
28
+ npx proof-pr@latest demo workflow --locale zh-CN
29
+ npx proof-pr@latest demo --list
30
+ ```
31
+
25
32
  初始化配置和 GitHub Action:
26
33
 
27
34
  ```bash
28
35
  npx proof-pr@latest init
29
36
  ```
30
37
 
31
- 这个命令会生成 `.proofpr.yml` 和 `.github/workflows/proofpr.yml`,提交后打开 PR 即可看到报告。
38
+ 这个命令会生成 `.proofpr.yml`、`.github/workflows/proofpr.yml` 和 `.github/pull_request_template.md`,提交后打开 PR 即可看到报告。
39
+
40
+ 已接入仓库单独补 PR 模板:
41
+
42
+ ```bash
43
+ npx proof-pr@latest template
44
+ ```
32
45
 
33
46
  体检接入状态:
34
47
 
@@ -36,7 +49,7 @@ npx proof-pr@latest init
36
49
  npx proof-pr@latest doctor
37
50
  ```
38
51
 
39
- 这个命令会检查配置文件、workflow、Action 版本、PR 权限和本地 diff 是否可读。
52
+ 这个命令会检查配置文件、workflow、PR 模板、Action 版本、PR 权限和本地 diff 是否可读。
40
53
 
41
54
  本地扫描当前分支:
42
55
 
@@ -47,7 +60,7 @@ npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN
47
60
  扫描内置案例:
48
61
 
49
62
  ```bash
50
- npx proof-pr@latest scan --diff-file examples/cases/workflow-untrusted-checkout.diff --locale zh-CN
63
+ npx proof-pr@latest demo workflow --locale zh-CN
51
64
  ```
52
65
 
53
66
  生成独立 HTML 可视化报告:
@@ -65,7 +78,7 @@ npx proof-pr@latest benchmark --cases benchmarks/cases
65
78
  ## GitHub Action
66
79
 
67
80
  ```yaml
68
- - uses: linsk27/proof-pr@v0.1.13
81
+ - uses: linsk27/proof-pr@v0.1.15
69
82
  with:
70
83
  fail-on: high
71
84
  comment: "true"
package/dist/index.js CHANGED
@@ -25728,7 +25728,131 @@ function dedupeFindings(findings) {
25728
25728
 
25729
25729
 
25730
25730
  const execFileAsync = (0,external_node_util_namespaceObject.promisify)(external_node_child_process_.execFile);
25731
- const CLI_VERSION = "0.1.13";
25731
+ const CLI_VERSION = "0.1.15";
25732
+ const DEMO_CASES = [
25733
+ {
25734
+ id: "workflow",
25735
+ title: "高权限 workflow 运行不可信 PR 代码",
25736
+ description: "演示 pull_request_target 与 PR head checkout 组合风险。",
25737
+ diffText: `diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
25738
+ index 1111111..2222222 100644
25739
+ --- a/.github/workflows/pr.yml
25740
+ +++ b/.github/workflows/pr.yml
25741
+ @@ -1,7 +1,17 @@
25742
+ name: PR automation
25743
+ on:
25744
+ + pull_request_target:
25745
+ + types: [opened, synchronize]
25746
+ +
25747
+ jobs:
25748
+ test:
25749
+ runs-on: ubuntu-latest
25750
+ steps:
25751
+ - - uses: actions/checkout@v4
25752
+ + - uses: actions/checkout@v4
25753
+ + with:
25754
+ + repository: \${{ github.event.pull_request.head.repo.full_name }}
25755
+ + ref: \${{ github.event.pull_request.head.sha }}
25756
+ + - run: pnpm install
25757
+ + - run: pnpm test
25758
+ `
25759
+ },
25760
+ {
25761
+ id: "secret",
25762
+ title: "疑似 secret 被提交",
25763
+ description: "演示 .env、OpenAI key、数据库连接串等敏感内容会被拦截。",
25764
+ diffText: `diff --git a/.env b/.env
25765
+ new file mode 100644
25766
+ index 0000000..1111111
25767
+ --- /dev/null
25768
+ +++ b/.env
25769
+ @@ -0,0 +1,2 @@
25770
+ +OPENAI_API_KEY=sk-proj-examplevalueexamplevalue1234567890
25771
+ +DATABASE_URL=postgres://demo:super-secret-password@example.com:5432/app
25772
+ `
25773
+ },
25774
+ {
25775
+ id: "dependency",
25776
+ title: "依赖大版本升级",
25777
+ description: "演示依赖 major upgrade 需要 changelog、迁移说明和验证证据。",
25778
+ diffText: `diff --git a/package.json b/package.json
25779
+ index 1111111..2222222 100644
25780
+ --- a/package.json
25781
+ +++ b/package.json
25782
+ @@ -1,8 +1,8 @@
25783
+ {
25784
+ "dependencies": {
25785
+ - "react": "^18.2.0",
25786
+ + "react": "^19.0.0",
25787
+ "zod": "^3.25.1"
25788
+ },
25789
+ "devDependencies": {
25790
+ "typescript": "^5.9.3"
25791
+ }
25792
+ }
25793
+ `
25794
+ },
25795
+ {
25796
+ id: "mcp",
25797
+ title: "MCP 本地命令和凭据面",
25798
+ description: "演示 MCP / agent 配置中的 command、args、env 风险。",
25799
+ diffText: `diff --git a/.cursor/mcp.json b/.cursor/mcp.json
25800
+ new file mode 100644
25801
+ index 0000000..1111111
25802
+ --- /dev/null
25803
+ +++ b/.cursor/mcp.json
25804
+ @@ -0,0 +1,11 @@
25805
+ +{
25806
+ + "mcpServers": {
25807
+ + "local-admin": {
25808
+ + "command": "node",
25809
+ + "args": ["scripts/admin-server.js"],
25810
+ + "env": {
25811
+ + "API_TOKEN": "\${LOCAL_API_TOKEN}"
25812
+ + }
25813
+ + }
25814
+ + }
25815
+ +}
25816
+ `,
25817
+ config: { preset: "mcp-security" }
25818
+ },
25819
+ {
25820
+ id: "ui-evidence",
25821
+ title: "UI 改动缺少截图证据",
25822
+ description: "演示 Evidence Contract:组件改动必须提供截图和验证说明。",
25823
+ diffText: `diff --git a/src/components/Button.tsx b/src/components/Button.tsx
25824
+ index 1111111..2222222 100644
25825
+ --- a/src/components/Button.tsx
25826
+ +++ b/src/components/Button.tsx
25827
+ @@ -1,3 +1,7 @@
25828
+ export function Button() {
25829
+ - return <button>Save</button>;
25830
+ + return (
25831
+ + <button className="primary">
25832
+ + Save
25833
+ + </button>
25834
+ + );
25835
+ }
25836
+ `,
25837
+ config: {
25838
+ evidence: {
25839
+ contracts: [
25840
+ {
25841
+ id: "ui-screenshot",
25842
+ title: "UI changes need screenshots",
25843
+ paths: ["src/components/**"],
25844
+ requires: ["screenshot", "verification"],
25845
+ severity: "medium"
25846
+ }
25847
+ ]
25848
+ }
25849
+ },
25850
+ pullRequest: {
25851
+ title: "Update button styling",
25852
+ body: "This updates the primary button style and spacing so the layout is easier to scan."
25853
+ }
25854
+ }
25855
+ ];
25732
25856
  const build_program = new Command();
25733
25857
  build_program
25734
25858
  .name("proof-pr")
@@ -25745,6 +25869,7 @@ build_program
25745
25869
  .description("Check whether ProofPR is installed correctly in the current repository.")
25746
25870
  .option("--config <path>", "Path to .proofpr.yml.", ".proofpr.yml")
25747
25871
  .option("--workflow-path <path>", "Path to the GitHub Actions workflow.", ".github/workflows/proofpr.yml")
25872
+ .option("--pr-template-path <path>", "Path to the pull request template.", ".github/pull_request_template.md")
25748
25873
  .option("--base <ref>", "Base git ref used for local diff checks.", "origin/main")
25749
25874
  .option("--head <ref>", "Head git ref used for local diff checks.", "HEAD")
25750
25875
  .action(async (options) => {
@@ -25754,6 +25879,46 @@ build_program
25754
25879
  process.exitCode = 1;
25755
25880
  }
25756
25881
  });
25882
+ build_program
25883
+ .command("template")
25884
+ .description("Create a ProofPR-friendly pull request template.")
25885
+ .option("--output <path>", "Path to write the pull request template.", ".github/pull_request_template.md")
25886
+ .option("--force", "Overwrite the existing template.", false)
25887
+ .action(async (options) => {
25888
+ await writeIfMissing(options.output, renderPullRequestTemplate(), options.force);
25889
+ process.stdout.write(`ProofPR pull request template written to ${options.output}\n\nNext:\n1. Commit the template.\n2. Ask contributors to fill verification, reproduction, screenshot, changelog, and permission rationale sections when relevant.\n3. Run npx proof-pr@latest doctor to check setup.\n`);
25890
+ });
25891
+ build_program
25892
+ .command("demo")
25893
+ .description("Run a built-in ProofPR demo case without cloning this repository.")
25894
+ .argument("[case]", "Demo case id. Use --list to see available cases.", "workflow")
25895
+ .option("--list", "List built-in demo cases.", false)
25896
+ .option("--format <format>", "Output format: markdown, json, sarif, or html.", parseFormat, "markdown")
25897
+ .option("--output <path>", "Write demo report output to a file instead of stdout.")
25898
+ .option("--locale <locale>", "Report language: en or zh-CN.", "zh-CN")
25899
+ .action(async (caseId, options) => {
25900
+ if (options.list) {
25901
+ process.stdout.write(renderDemoList());
25902
+ return;
25903
+ }
25904
+ const demoCase = DEMO_CASES.find((item) => item.id === caseId);
25905
+ if (!demoCase) {
25906
+ throw new Error(`Unknown demo case "${caseId}". Run "proof-pr demo --list" to see available cases.`);
25907
+ }
25908
+ const result = scanDiff(demoCase.diffText, {
25909
+ config: demoCase.config,
25910
+ pullRequest: demoCase.pullRequest
25911
+ });
25912
+ const locale = parseLocale(options.locale, "zh-CN");
25913
+ const output = renderDemoOutput(demoCase, result, options.format, locale);
25914
+ if (options.output) {
25915
+ await writeOutput(options.output, `${output}\n`);
25916
+ process.stdout.write(`ProofPR demo ${options.format} report written to ${options.output}\n`);
25917
+ }
25918
+ else {
25919
+ process.stdout.write(`${output}\n`);
25920
+ }
25921
+ });
25757
25922
  build_program
25758
25923
  .command("scan", { isDefault: true })
25759
25924
  .description("Scan a git diff and print a ProofPR report.")
@@ -25796,13 +25961,26 @@ build_program
25796
25961
  .description("Create a starter .proofpr.yml and GitHub Actions workflow.")
25797
25962
  .option("--config-path <path>", "Path to write the ProofPR configuration file.", ".proofpr.yml")
25798
25963
  .option("--workflow-path <path>", "Path to write the GitHub Actions workflow.", ".github/workflows/proofpr.yml")
25964
+ .option("--no-pr-template", "Skip creating .github/pull_request_template.md.")
25965
+ .option("--pr-template-path <path>", "Path to write the pull request template.", ".github/pull_request_template.md")
25799
25966
  .option("--preset <preset>", `Config preset: ${listConfigPresets().join(", ")}.`, parsePresetOption, "open-source-maintainer")
25800
25967
  .option("--fail-on <level>", "Workflow failure threshold: low, medium, high, or never.", parseFailLevel, "high")
25801
25968
  .option("--force", "Overwrite existing files.", false)
25802
25969
  .action(async (options) => {
25803
25970
  await writeIfMissing(options.configPath, renderConfigTemplate(options.preset), options.force);
25804
25971
  await writeIfMissing(options.workflowPath, renderWorkflowTemplate(options.failOn), options.force);
25805
- process.stdout.write(`ProofPR initialized.\n\nCreated:\n- ${options.configPath}\n- ${options.workflowPath}\n\nNext:\n1. Commit these files.\n2. Open or update a pull request.\n3. Read the ProofPR comment or Actions summary.\n\nLocal check:\nnpx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN\n\nNeed another task?\nnpx proof-pr@latest guide\n`);
25972
+ const created = [options.configPath, options.workflowPath];
25973
+ const skipped = [];
25974
+ if (options.prTemplate) {
25975
+ const wroteTemplate = await writeIfMissingSoft(options.prTemplatePath, renderPullRequestTemplate(), options.force);
25976
+ if (wroteTemplate) {
25977
+ created.push(options.prTemplatePath);
25978
+ }
25979
+ else {
25980
+ skipped.push(`${options.prTemplatePath} already exists`);
25981
+ }
25982
+ }
25983
+ process.stdout.write(`ProofPR initialized.\n\nCreated:\n${created.map((item) => `- ${item}`).join("\n")}${skipped.length > 0 ? `\n\nSkipped:\n${skipped.map((item) => `- ${item}`).join("\n")}` : ""}\n\nNext:\n1. Commit these files.\n2. Open or update a pull request.\n3. Read the ProofPR comment or Actions summary.\n\nLocal check:\nnpx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN\n\nNeed another task?\nnpx proof-pr@latest guide\n`);
25806
25984
  });
25807
25985
  build_program
25808
25986
  .command("benchmark")
@@ -25849,33 +26027,40 @@ function renderGuide() {
25849
26027
 
25850
26028
  1. 接入 GitHub PR 自动检查
25851
26029
  npx proof-pr@latest init
25852
- 然后提交 .proofpr.yml 和 .github/workflows/proofpr.yml,打开 PR 后看评论和 Actions summary。
26030
+ 然后提交 .proofpr.yml、.github/workflows/proofpr.yml 和 .github/pull_request_template.md,打开 PR 后看评论和 Actions summary。
25853
26031
 
25854
26032
  2. 体检当前仓库接入状态
25855
26033
  npx proof-pr@latest doctor
25856
- 检查配置文件、workflow、Action 版本、PR 权限和本地 diff 是否正常。
26034
+ 检查配置文件、workflow、PR 模板、Action 版本、PR 权限和本地 diff 是否正常。
25857
26035
 
25858
- 3. 本地检查当前分支
26036
+ 3. 已接入仓库,单独补 PR 模板
26037
+ npx proof-pr@latest template
26038
+ 引导贡献者填写验证、复现、截图、changelog 和权限理由。
26039
+
26040
+ 4. 本地检查当前分支
25859
26041
  npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN
25860
26042
  适合在发 PR 前先看风险、证据评分和 Review 行动清单。
25861
26043
 
25862
- 4. 生成可分享 HTML 报告
26044
+ 5. 生成可分享 HTML 报告
25863
26045
  npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN --format html --output proofpr-report.html
25864
26046
  生成后用浏览器打开 proofpr-report.html。
25865
26047
 
25866
- 5. 生成 GitHub Code Scanning 的 SARIF
26048
+ 6. 生成 GitHub Code Scanning 的 SARIF
25867
26049
  npx proof-pr@latest scan --base origin/main --head HEAD --format sarif --output proofpr.sarif
25868
26050
  适合在 CI 里配合 github/codeql-action/upload-sarif 使用。
25869
26051
 
25870
- 6. 试跑内置风险案例
25871
- npx proof-pr@latest scan --diff-file examples/cases/workflow-untrusted-checkout.diff --locale zh-CN
25872
- 不需要改项目代码,也能快速看到 ProofPR 会抓什么风险。
26052
+ 7. 不接入仓库,先试跑内置案例
26053
+ npx proof-pr@latest demo workflow --locale zh-CN
26054
+ 不需要 clone 仓库或寻找 examples 文件,也能快速看到 ProofPR 会抓什么风险。
26055
+
26056
+ 8. 查看所有内置案例
26057
+ npx proof-pr@latest demo --list
25873
26058
 
25874
- 7. 验证规则样本是否仍然命中
26059
+ 9. 验证规则样本是否仍然命中
25875
26060
  npx proof-pr@latest benchmark --cases benchmarks/cases
25876
26061
  适合维护 ProofPR 规则或发版前回归。
25877
26062
 
25878
- 8. 调整审查强度
26063
+ 10. 调整审查强度
25879
26064
  打开 .proofpr.yml,把 preset 改成 security-strict、dependency-careful 或 mcp-security。
25880
26065
 
25881
26066
  结果在哪里看:
@@ -25883,6 +26068,37 @@ function renderGuide() {
25883
26068
  - 本地 CLI:终端输出;如果用了 --output,就看写出的 HTML / JSON / SARIF / Markdown 文件。
25884
26069
  `;
25885
26070
  }
26071
+ function renderDemoList() {
26072
+ const rows = DEMO_CASES.map((item) => `- ${item.id}: ${item.title}\n ${item.description}`).join("\n");
26073
+ return `ProofPR 内置案例
26074
+
26075
+ 用法:
26076
+ npx proof-pr@latest demo <case> --locale zh-CN
26077
+
26078
+ 可用案例:
26079
+ ${rows}
26080
+ `;
26081
+ }
26082
+ function renderDemoOutput(demoCase, result, format, locale) {
26083
+ if (format === "json") {
26084
+ return JSON.stringify({
26085
+ demo: {
26086
+ id: demoCase.id,
26087
+ title: demoCase.title,
26088
+ description: demoCase.description
26089
+ },
26090
+ result
26091
+ }, null, 2);
26092
+ }
26093
+ if (format === "markdown") {
26094
+ return `# ProofPR demo: ${demoCase.title}
26095
+
26096
+ ${demoCase.description}
26097
+
26098
+ ${renderMarkdownReport(result, locale)}`;
26099
+ }
26100
+ return renderOutput(result, format, locale);
26101
+ }
25886
26102
  async function runDoctor(options) {
25887
26103
  const checks = [];
25888
26104
  const nextSteps = new Set();
@@ -25924,6 +26140,7 @@ async function runDoctor(options) {
25924
26140
  checks.push({ level: "fail", title: `缺少 ${options.workflowPath}` });
25925
26141
  nextSteps.add("运行 npx proof-pr@latest init 生成 .github/workflows/proofpr.yml。");
25926
26142
  }
26143
+ await inspectPullRequestTemplate(options.prTemplatePath, checks, nextSteps);
25927
26144
  await inspectGitDiff(options, checks, nextSteps);
25928
26145
  if (nextSteps.size === 0) {
25929
26146
  nextSteps.add("当前接入状态正常;可以打开 PR,或运行 npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN 做本地自查。");
@@ -25969,6 +26186,29 @@ function inspectWorkflow(workflow, checks, nextSteps) {
25969
26186
  nextSteps.add("建议在 workflow permissions 中加入 contents: read。");
25970
26187
  }
25971
26188
  }
26189
+ async function inspectPullRequestTemplate(path, checks, nextSteps) {
26190
+ if (!(await pathExists(path))) {
26191
+ checks.push({ level: "warn", title: `缺少 ${path}` });
26192
+ nextSteps.add("运行 npx proof-pr@latest template 生成 PR 模板,引导贡献者补充验证、复现、截图和权限理由。");
26193
+ return;
26194
+ }
26195
+ const template = await (0,promises_namespaceObject.readFile)(path, "utf8");
26196
+ checks.push({ level: "pass", title: `${path} 已存在` });
26197
+ if (/验证|verification|test/i.test(template)) {
26198
+ checks.push({ level: "pass", title: "PR 模板会提示验证证据" });
26199
+ }
26200
+ else {
26201
+ checks.push({ level: "warn", title: "PR 模板没有明显的验证证据提示" });
26202
+ nextSteps.add("在 PR 模板中加入“验证方式”栏目,减少 ProofPR 报告里的证据不足。");
26203
+ }
26204
+ if (/复现|reproduction|before|after|截图|screenshot|权限|permission/i.test(template)) {
26205
+ checks.push({ level: "pass", title: "PR 模板覆盖复现、截图或权限理由提示" });
26206
+ }
26207
+ else {
26208
+ checks.push({ level: "warn", title: "PR 模板缺少复现、截图或权限理由提示" });
26209
+ nextSteps.add("在 PR 模板中加入复现、截图、changelog、权限理由等可选栏目。");
26210
+ }
26211
+ }
25972
26212
  async function inspectGitDiff(options, checks, nextSteps) {
25973
26213
  try {
25974
26214
  const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"]);
@@ -26055,6 +26295,14 @@ async function writeIfMissing(path, contents, force) {
26055
26295
  await (0,promises_namespaceObject.mkdir)((0,external_node_path_.dirname)(path), { recursive: true });
26056
26296
  await (0,promises_namespaceObject.writeFile)(path, contents, "utf8");
26057
26297
  }
26298
+ async function writeIfMissingSoft(path, contents, force) {
26299
+ if (!force && (await pathExists(path))) {
26300
+ return false;
26301
+ }
26302
+ await (0,promises_namespaceObject.mkdir)((0,external_node_path_.dirname)(path), { recursive: true });
26303
+ await (0,promises_namespaceObject.writeFile)(path, contents, "utf8");
26304
+ return true;
26305
+ }
26058
26306
  async function writeOutput(path, contents) {
26059
26307
  await (0,promises_namespaceObject.mkdir)((0,external_node_path_.dirname)(path), { recursive: true });
26060
26308
  await (0,promises_namespaceObject.writeFile)(path, contents, "utf8");
@@ -26102,6 +26350,33 @@ jobs:
26102
26350
  annotations: "true"
26103
26351
  `;
26104
26352
  }
26353
+ function renderPullRequestTemplate() {
26354
+ return `## 变更说明
26355
+
26356
+ 请说明这个 PR 为什么需要、改了什么、影响范围是什么。
26357
+
26358
+ ## 验证方式
26359
+
26360
+ - [ ] 已运行自动化测试:
26361
+ - [ ] 已完成手动验证:
26362
+ - [ ] 不需要测试,原因:
26363
+
26364
+ ## 复现 / Before & After
26365
+
26366
+ 如果是 bug fix,请写复现步骤、预期结果和实际结果。
26367
+ 如果是 UI 改动,请附 before/after 截图或录屏。
26368
+
26369
+ ## 依赖 / CI / 权限 / MCP 变更
26370
+
26371
+ 如果改了依赖、lockfile、GitHub Actions、MCP、环境变量或权限,请说明原因和安全影响。
26372
+
26373
+ ## 发布风险
26374
+
26375
+ - [ ] 无破坏性变更
26376
+ - [ ] 需要迁移说明 / changelog
26377
+ - [ ] 需要灰度或回滚方案
26378
+ `;
26379
+ }
26105
26380
  function renderOutput(result, format, locale) {
26106
26381
  if (format === "json") {
26107
26382
  return JSON.stringify(result, null, 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proof-pr",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "CLI for ProofPR, a maintainer-focused pull request evidence scanner.",
5
5
  "license": "MIT",
6
6
  "type": "module",