From 2a7b7d66610dbdeadf964d10cba753b86f2a0c39 Mon Sep 17 00:00:00 2001 From: sjh9714 <163989462+sjh9714@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:35:51 +0900 Subject: [PATCH] docs: lead with the one check nothing else does, and cut the install to one option The README opened with "AI coding tools open pull requests faster than anyone can read them", which is a claim about the world rather than about the reader. Then it told a made-up story lifted from the demo fixture, and the first thing it asked anyone to do was run a demo of a synthetic pull request. Nobody runs a demo of a tool they have not yet decided they need. What people who actually receive pull requests want is for the queue to shrink. peakoss/anti-slop has 751 stars for "automatically closes low-quality and AI slop PRs", written by a Coolify maintainer taking 120+ of them a month. MergeWarden refuses to close anything, on purpose. Rewriting the README does not close that gap and this commit does not claim to. What it does do is put first the one thing that appears to be uncontested: a pull request that edits the files instructing your coding agents. anti-slop's 34 rules do not cover it, and a search for repositories doing the same turned up nothing above 30 stars. Verified before writing the copy rather than after. A pull request titled "docs: tidy up the contributor notes", whose entire diff is one added line in CLAUDE.md, produces exactly the comment the README quotes: https://github.com/sjh9714/agent-gate-install-smoke-20260617/pull/22 That run also reports "Agent detected: no", so the check does not depend on the pull request being agent-authored. The 3.9% figure stays in. It is not a daily event and pretending otherwise would repeat the mistake this project has already made once. Install drops from four options to one. Three were doing nothing: github-token action.yml already defaults it to ${{ github.token }} mode: warn schema.ts:431 already defaults to "warn" fail-on-block run.ts:523 only fires on decision === "block", which warn mode never produces templates/mergewarden-observe.yml loses its `with:` block entirely for the same reason. comment's default is left alone: making `auto` the default would warn on any workflow granting only pull-requests: read, which is the trap fixed in #180. triage's token requirement moves above the command instead of below it, with a link to create one and the fact that no scopes are needed. The Chinese README gets the new opening and an explicit note that the rest of it is still the older structure. --- README.md | 226 ++++++++++-------------- README.zh-CN.md | 42 +++-- docs/getting-started.md | 16 +- docs/integrations/copilot.md | 3 - docs/integrations/cursor.md | 3 - packages/cli/test/documentation.test.ts | 11 +- templates/mergewarden-observe.yml | 4 - 7 files changed, 134 insertions(+), 171 deletions(-) diff --git a/README.md b/README.md index f7fef80..a6be367 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,49 @@ -# MergeWarden for AI PRs +# MergeWarden [![Release](https://img.shields.io/github/v/release/sjh9714/mergewarden?label=release)](https://github.com/sjh9714/mergewarden/releases) [![CI](https://github.com/sjh9714/mergewarden/actions/workflows/ci.yml/badge.svg)](https://github.com/sjh9714/mergewarden/actions/workflows/ci.yml) [![MergeWarden](https://github.com/sjh9714/mergewarden/actions/workflows/mergewarden.yml/badge.svg)](https://github.com/sjh9714/mergewarden/actions/workflows/mergewarden.yml) [![License](https://img.shields.io/github/license/sjh9714/mergewarden)](LICENSE) -**AI coding tools open pull requests faster than anyone can read them. MergeWarden -tells you which ones need a closer look, and why.** +**A pull request quietly edited your `CLAUDE.md`.** -## The situation it is built for +In the diff it reads as a documentation tidy-up. That file is what every coding +agent reads before it touches your repository, so the change outlives the pull +request and shows up in nothing anyone reviews afterwards. -An agent opens a pull request titled "Document the release process". The diff -touches four files. Three of them are documentation. The fourth is your release -workflow. +MergeWarden leaves one comment when that happens. **It closes nothing.** -Nothing in the title said that would happen, and in a busy week nobody notices. -MergeWarden notices, and leaves one comment saying so. +## What that actually looks like -It reports facts you could have checked by hand. It does not review your code, -grade the contributor, or merge anything. +This is a real pull request, titled "docs: tidy up the contributor notes", whose +whole diff is one added line in `CLAUDE.md`. -## See it work, without installing anything - -This runs a small example that ships inside the tool. No token, no repository, -no network: - -```bash -npx --yes mergewarden@0.10.1 demo ``` - -``` - Repository demo-org/demo-service - Pull request #482 "Document the release process" - Author an agent on branch codex/document-releasing - Declared allowed_paths: docs/** - Changed docs/releasing.md, .github/workflows/release.yml, AGENTS.md, package.json - MergeWarden: NEEDS REVIEW -ERROR contract/out-of-scope -Message: .github/workflows/release.yml changed outside the allowed contract scope. -Path: .github/workflows/release.yml -``` - -Now point it at a real pull request. Public repositories need no token: - -```bash -npx --yes mergewarden@0.10.1 scan owner/repository#123 -``` - -![The full mergewarden demo report scrolling past in a terminal](docs/assets/mergewarden-demo.gif) - -If a queue has built up, read the whole thing at once instead. This is real -output from a public repository: - -```bash -npx --yes mergewarden@0.10.1 triage owner/repository -``` - -``` -20 open pull request(s) read. 9 have something a maintainer checks by hand. - -#6941 update-unmanaged-certificates no description · template unused -#7227 add-tests no linked issue · oversized -#7790 feat/dedup-dynamic-upstreams no linked issue · template unused -#7290 natsort template unused -#7669 webtransport-reverse-proxy oversized -#7878 rfc9440-client-cert-placeholders oversized -#7912 feat/fastcgi-server-addr template unused -#7913 slowloris-idle-timeout no linked issue -#7922 fix/network-proxy-missing-host no linked issue - -Nothing was closed, labelled, or commented on. +Why: This file can change how AI agents behave in future PRs. (CLAUDE.md) +Next: Review the agent instruction/tooling change before merging. ``` -Every row is something you would have checked by hand, and you can confirm any -of them in a few seconds. The other eleven pull requests are not listed because -there was nothing to say about them. Nothing is written back to GitHub, and the -command needs no write access. - -**`triage` needs `GH_TOKEN` set, even for a public repository.** It reads every -open pull request, and GitHub allows 60 unauthenticated requests an hour, which -one queue uses up. Any personal access token with no scopes at all is enough, -since nothing here needs write access. Without one the command tells you so and -exits non-zero rather than reporting a half-read queue. - -`scan`, on a single public pull request, works without a token. - -There is deliberately no way to pass a token as a command-line flag, because -flags end up in shell history and CI logs. +[The pull request](https://github.com/sjh9714/agent-gate-install-smoke-20260617/pull/22) +and [the run that produced it](https://github.com/sjh9714/agent-gate-install-smoke-20260617/actions) +are public, so you can check the output against the diff yourself. -## What it looks for +It works whether or not an agent opened the pull request. That one says +`Agent detected: no`. -| It notices when | Which matters because | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------- | -| A pull request edits files outside what it said it would | An agent asked to update docs also changed your billing code | -| `AGENTS.md`, `CLAUDE.md`, `.mcp.json` or `.cursor/` change | These files instruct every future agent working in your repository | -| A workflow gains permissions it did not have before | It can now write where it could previously only read | -| Pull request text reaches an agent prompt | A stranger can put instructions in a title and have your automation follow them | -| An action, workflow or container is not pinned | Somebody else can change what runs in your CI without opening a pull request | -| An install script appears in a package manifest | That code runs during `npm install`, before any human reads it | -| Risky code changes with no matching test changes | Worth a look, not a verdict | +Watched by default: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `QWEN.md`, +`.mcp.json`, `.cursor/`, `.codex/`, `.gemini/`, +`.github/copilot-instructions.md`, `claude_desktop_config.json`. -Every finding names the rule, the file, and the evidence, and carries an ID you -can quote when you discuss or dismiss it. +**How often is this?** We scanned 2,204 merged agent-authored pull requests on +public GitHub and **3.9%** of them changed a file like this. That is not a daily +event. It is the kind of thing you want to hear about the once it happens rather +than find six weeks later. -## Run it on every pull request +## Install -Create `.github/workflows/mergewarden.yml`: +One file, `.github/workflows/mergewarden.yml`: ```yaml name: MergeWarden @@ -126,21 +62,18 @@ jobs: steps: - uses: sjh9714/mergewarden@v0.10.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - mode: warn - fail-on-block: false comment: auto ``` -That is the whole setup. No checkout step, no build, no configuration file, and -nothing installed into your repository. Until you add a `mergewarden.yml` of -your own, a sensible default policy applies. +That is the whole setup. There is no checkout step, no build, no config file, +and no token to create: the Action uses the one GitHub already gives the job. +Nothing is blocked from merging until you decide otherwise. -Most pull requests cross no boundary. Those pass silently, and the details go to -the Actions job summary rather than to a comment. You hear from MergeWarden when -something is worth hearing about. Push a fix and the existing comment rewrites -itself to `PASSED` instead of being deleted, so a stale review request cannot -outlive the problem it described. +`comment: auto` is the only option that does anything on a fresh install. It +means MergeWarden stays silent when there is nothing to say, and everything else +goes to the Actions job summary. When it does comment, pushing a fix rewrites +that same comment to `PASSED` instead of deleting it, so a stale review request +cannot outlive the problem it described. Pull requests from forks get a read-only token from GitHub, so they are never commented on. @@ -152,17 +85,40 @@ tag. MergeWarden does not publish or recommend a mutable `v0` tag. - uses: sjh9714/mergewarden@c32fb900b65708ea7c875b8ec4244c0983343970 ``` +## What else it checks + +Once it is installed, these come with it. Each one is a fact about the diff, not +a judgement about the contributor. + +| It also notices | Because | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------- | +| A workflow gained a permission it did not have on the base | It can now write where it could previously only read | +| `pull_request_target` combined with a head checkout | That hands your repository's secrets to whoever opened the pull request | +| An action, reusable workflow or container on a moving tag | Somebody else can change what runs in your CI without opening a pull request | +| An install or prepare script added to a package manifest | That code runs during `npm install`, before any human reads it | +| Pull request text reaching an agent prompt | A stranger can put instructions in a title and have your automation follow them | +| Files outside the scope a pull request declared for itself | An agent asked to update docs also changed your billing code | + +Every finding names the rule, the file, the evidence, and an ID you can quote +when you discuss or dismiss it. The +[configuration reference](docs/configuration.md) covers turning individual +checks up, down, or off. + ## What it will not do -It does not judge your code. It has no opinion on style, naming, or whether a -change deserves to be merged, and it does not infer anything from how the -description is written. +**It never closes, labels, or merges anything.** There is no `--close` flag, and +a test in this repository asserts there never will be one. A pull request closed +by a bot in error does not get reopened by the person who gave up on it. + +If what you want is for the pull requests to stop arriving, this is the wrong +tool and you should look at an auto-closing action instead. MergeWarden is for +deciding yourself, faster. -It never closes, labels, or merges anything. There is no `--close` flag, and a -test in this repository asserts there never will be one. A pull request that a -bot closes in error does not get reopened by the person who gave up on it. +It also does not judge your code. It has no opinion on style, naming, or whether +a change deserves to be merged, and it infers nothing from how the description is +written. -It is also careful about what it trusts: +On what it trusts: - It does not execute pull-request code, and never checks the branch out. - It reads your settings from the exact base commit of your branch, never from @@ -173,44 +129,46 @@ It is also careful about what it trusts: - If it cannot see everything it needs, it says so and fails rather than reporting a pass it cannot stand behind. -The [security model](docs/security-model.md) explains the boundaries and the -known limits. +The [security model](docs/security-model.md) has the boundaries and the known +limits. -## Turning it on gradually +## Reading a whole queue at once -Nothing is blocked on day one. With `mode: warn` and `fail-on-block: false`, -MergeWarden reports and does nothing else. A reasonable order: +If pull requests have piled up, `triage` reads all the open ones and lists only +those with something you would have checked by hand. -1. Run it in `warn` for a week and read what it tells you. -2. Raise individual checks to `error` once you agree with them. -3. Set `mode: block` and `fail-on-block: true`. -4. Require the MergeWarden check in branch protection. +**This one needs a token**, even on a public repository, because it makes one +request per pull request and GitHub allows 60 an hour without one. A +[personal access token](https://github.com/settings/personal-access-tokens/new) +with **no scopes selected** is enough, since nothing here writes. -Each of those is one line in [configuration](docs/configuration.md). +```bash +export GH_TOKEN=github_pat_... +npx --yes mergewarden@0.10.1 triage owner/repository +``` -Reports end with one of five verdicts, kept deliberately distinct: +``` +20 open pull request(s) read. 9 have something a maintainer checks by hand. -- `PASSED`: analysis finished, nothing active to report. -- `OBSERVED FINDINGS`: evidence recorded, decision unchanged. -- `NEEDS REVIEW`: a person should look. -- `BLOCKED`: policy rejected the change. -- `ANALYSIS INCOMPLETE`: something could not be read, so no verdict is claimed. +#6941 update-unmanaged-certificates no description · template unused +#7227 add-tests no linked issue · oversized +#7790 feat/dedup-dynamic-upstreams no linked issue · template unused + +Nothing was closed, labelled, or commented on. +``` -Worried about noise? On 46 merged human pull requests it stayed quiet on 44, and -both of the findings it did raise were correct -([how that was measured](docs/study/what-a-zero-config-install-reports.md)). If -it ever flags something your team already decided was fine, please open an issue -with the output. That is the most useful bug report this project can get. +Without a token it tells you what it could not read and exits non-zero, rather +than showing you a queue it only half saw. [More on triage](docs/triage.md). ## Documentation -[Getting started](docs/getting-started.md) walks through installing it and -reading your first report. The [documentation index](docs/README.md) has -everything else, including the [configuration reference](docs/configuration.md). +[Getting started](docs/getting-started.md) walks through the first week. The +[documentation index](docs/README.md) has the rest, including the +[configuration reference](docs/configuration.md). -If you run the agents yourself rather than reviewing their pull requests, the -same engine ships as an [MCP server](packages/mcp/README.md). It checks a change -against the scope you gave the agent before a pull request exists. +Running the agents yourself rather than reviewing their pull requests? The same +engine ships as an [MCP server](packages/mcp/README.md) that checks a change +against the scope you gave it, before a pull request exists. ## Contributing diff --git a/README.zh-CN.md b/README.zh-CN.md index 2f9b459..8016a6d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,25 +1,44 @@ -# MergeWarden:面向 AI PR 的变更管控门禁 +# MergeWarden [![Release](https://img.shields.io/github/v/release/sjh9714/mergewarden?label=release)](https://github.com/sjh9714/mergewarden/releases) [![CI](https://github.com/sjh9714/mergewarden/actions/workflows/ci.yml/badge.svg)](https://github.com/sjh9714/mergewarden/actions/workflows/ci.yml) [![MergeWarden](https://github.com/sjh9714/mergewarden/actions/workflows/mergewarden.yml/badge.svg)](https://github.com/sjh9714/mergewarden/actions/workflows/mergewarden.yml) [![License](https://img.shields.io/github/license/sjh9714/mergewarden)](LICENSE) -> **守在 AI 智能体与主分支之间的那道门。** - [English](README.md) · 简体中文 -编码智能体整天都在提 PR。MergeWarden 是一道变更管控门禁,用只有你的仓库才能定义的边界去检查每一个 PR: +**某个 PR 悄悄改了你的 `CLAUDE.md`。** + +在 diff 里它看着就是一次文档整理。可那个文件是每个编码智能体动你仓库之前都会读的东西, +所以这次改动会比这个 PR 活得更久,而且之后没有任何人会再去审它。 + +发生这种事时,MergeWarden 会留下一条评论。**它不会关闭任何 PR。** + +## 实际长这样 + +下面是一个真实的 PR,标题是「docs: tidy up the contributor notes」,整个 diff 只是在 +`CLAUDE.md` 里加了一行。 + +``` +MergeWarden: NEEDS REVIEW + +Why: This file can change how AI agents behave in future PRs. (CLAUDE.md) +Next: Review the agent instruction/tooling change before merging. +``` -- **这个 PR 有没有越出它自己声明的范围?** 智能体在 PR 正文的契约里声明打算改动的路径,超出范围的改动会被记为检查结果。 -- **它有没有动到智能体控制平面?** 对 `AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`.mcp.json`、`.cursor/**` 这类文件的改动,会左右此后每一个智能体 PR 的行为,理应经过人眼确认。 -- **它有没有把不可信文本接进智能体的提示词?** 从 PR 正文、标题或评论流向已登记的 agentic 工作流的新路径,会被追踪并标记出来。 +[这个 PR](https://github.com/sjh9714/agent-gate-install-smoke-20260617/pull/22) +是公开的,你可以自己把输出和 diff 对一遍。不管开 PR 的是不是智能体它都有效,那一条记录的是 +`Agent detected: no`。 -它同时也会捕捉工作流权限提升、未按 SHA 固定的供应链引用,以及有风险的软件包生命周期脚本。 +默认监视:`AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`QWEN.md`、`.mcp.json`、`.cursor/`、 +`.codex/`、`.gemini/`、`.github/copilot-instructions.md`、`claude_desktop_config.json`。 -它**不会**执行 PR 中的代码,**不会**从 PR 的 head 读取策略,运行时**不会**调用大模型。每一个判定都附带可在本地复现的确定性证据。 +**这种事多常见?** 我们扫了 GitHub 上 2,204 个已合并的智能体 PR,其中 **3.9%** 改动了 +这类文件。它不是每天都会发生的事,而是那种你希望在它发生的那一次就知道、而不是六周后才 +发现的事。 -MergeWarden 也用自己来把关自己的 PR —— 上面那枚 `MergeWarden` 徽章就是这个实时自检([我们如何 dogfooding](docs/demo-prs.md#dogfooding-mergewarden-gates-its-own-prs))。 +> 本页其余部分仍是旧版结构的翻译,内容属实但组织方式落后于 +> [英文 README](README.md)。以英文版为准。 ## 60 秒试一下 @@ -67,9 +86,6 @@ jobs: steps: - uses: sjh9714/mergewarden@v0.10.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - mode: warn - fail-on-block: false comment: auto ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 4a61a63..a4baa3b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -59,18 +59,18 @@ jobs: steps: - uses: sjh9714/mergewarden@v0.10.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - mode: warn - fail-on-block: false comment: auto ``` -There is no checkout step and no config file to write. MergeWarden reads the -pull request through the GitHub API, and until you add a `mergewarden.yml` of -your own it uses a built-in default policy. +There is no checkout step, no config file to write, and no token to create: the +Action uses the one GitHub already gives the job. MergeWarden reads the pull +request through the GitHub API, and until you add a `mergewarden.yml` of your +own it uses a built-in default policy. -`mode: warn` with `fail-on-block: false` means nothing is ever blocked from -merging. Leave it that way for a while. +`comment: auto` is the only option worth setting on a fresh install. The default +policy already runs in `warn` mode, which never blocks a merge, so +`mode: warn` and `fail-on-block: false` are what you would get anyway. Leave it +that way for a while. ## What the first pull request looks like diff --git a/docs/integrations/copilot.md b/docs/integrations/copilot.md index ac10470..e7cd31a 100644 --- a/docs/integrations/copilot.md +++ b/docs/integrations/copilot.md @@ -26,9 +26,6 @@ jobs: steps: - uses: sjh9714/mergewarden@c32fb900b65708ea7c875b8ec4244c0983343970 # v0.10.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - mode: warn - fail-on-block: false comment: auto ``` diff --git a/docs/integrations/cursor.md b/docs/integrations/cursor.md index 5e64b4d..ba49a40 100644 --- a/docs/integrations/cursor.md +++ b/docs/integrations/cursor.md @@ -26,9 +26,6 @@ jobs: steps: - uses: sjh9714/mergewarden@c32fb900b65708ea7c875b8ec4244c0983343970 # v0.10.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - mode: warn - fail-on-block: false comment: auto ``` diff --git a/packages/cli/test/documentation.test.ts b/packages/cli/test/documentation.test.ts index f2324b7..18dab48 100644 --- a/packages/cli/test/documentation.test.ts +++ b/packages/cli/test/documentation.test.ts @@ -70,13 +70,12 @@ describe("documentation contracts", () => { it("keeps the README concise and ordered around first value", async () => { const readme = await readFile(join(repoRoot, "README.md"), "utf8"); const headings = [ - "# MergeWarden for AI PRs", - "## The situation it is built for", - "## See it work, without installing anything", - "## What it looks for", - "## Run it on every pull request", + "# MergeWarden", + "## What that actually looks like", + "## Install", + "## What else it checks", "## What it will not do", - "## Turning it on gradually", + "## Reading a whole queue at once", "## Documentation", ]; let previous = -1; diff --git a/templates/mergewarden-observe.yml b/templates/mergewarden-observe.yml index 3bfea5a..7ed232a 100644 --- a/templates/mergewarden-observe.yml +++ b/templates/mergewarden-observe.yml @@ -13,7 +13,3 @@ jobs: runs-on: ubuntu-latest steps: - uses: sjh9714/mergewarden@v0.10.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - mode: warn - fail-on-block: false