Mainfast Intact
When you merge main into a feature branch, a conflict resolved by taking ours, or a hand-merge that loses a line, reverts main's work without a trace. GitHub's PR diff never shows what the merge commit itself did. Mainfast Intact re-runs each merge and reports what it dropped.
What it catches
- dropped: a hunk main added (or removed) since the merge base that the merge commit no longer carries, with the main commit that introduced it.
- partially-dropped: part of such a hunk is gone.
- evil: the merge commit contains changes found in neither parent and outside any conflict.
- Lockfiles are checked by default: taking the branch's lockfile wholesale can undo a dependency bump, often a security fix.
- Dropped changes are matched by tokens, not lines, so a line that gained a trailing comment is fine but a value changed back is not.
- Default levels: dropped (main) is an error, partially-dropped and evil are warnings, dropping the branch's own change is a notice.
What you see
A real check run from a private test repository, where a merge commit dropped a change from main.
Install
name: Mainfast Intact
on:
pull_request:
push:
branches-ignore: [main]
permissions:
contents: read
checks: write
jobs:
intact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: mainfast/intact@v0git clone https://github.com/mainfast/mainfast
# from the cloned suite root:
npm ci && npm run build -w intact
ln -s "$PWD/intact/dist/cli.cjs" ~/.local/bin/mainfast
mainfast check # every merge commit in <main>..HEAD
mainfast check --range origin/main..feature
mainfast check --json --fail-on errorversion: 1
intact:
ignore: # gitignore-style globs
- "dist/"
severity: # first match wins: error, warning, notice, off
"docs/**": notice
"migrations/**": errorStep by step, with what to check after each part: installation manual. Everything else: the README.
Permissions
- Needs only GITHUB_TOKEN, with contents: read and checks: write.
- With checks: write it posts a check run with annotations. Without it (fork PRs) it writes the job summary and annotations instead.
- The Action reads .mainfast.yml from the base of the change, so a pull request cannot ignore its own dropped change. The CLI reads your working tree.
- The CLI needs Node 20+ and git. The Action unshallows a shallow checkout automatically; fetch-depth: 0 is faster.
- Reading a private shared config with extends needs a config-token input; GITHUB_TOKEN cannot read other private repositories.
Privacy
- It analyses git history on the runner (or your machine for the CLI) using git remerge-diff, merge-tree and blame.
- The Action calls only the GitHub API with your GITHUB_TOKEN. No code or findings are sent to a third party; with a licence key, only the key and the organisation id go to Polar to check it.
- The plan notice counts active contributors through the GitHub API with your token, once per run.
Limits
- Octopus merges (3+ parents) are reported as unsupported and skipped.
- It is a heuristic, not proof: token matching can miss a drop, and can flag a resolution that rewrote main's change in different words.
- Criss-cross merges measure from the first merge base, so expect extra notes there.
- Semantic fix-ups inside a merge commit, such as updating a renamed call site, are evil merges by definition and reported as warnings.
- Binary files are only checked for being reverted to the merge-base blob.
- Rename detection uses git's default 50% similarity. Paths containing newlines or tabs are not supported.
- Only the merge commit is judged; a later commit that changes main's line again is ordinary history.
More in Mainfast
Install Mainfast Intact
Prove that a merge commit did not silently drop changes from main.
