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