boatman is a zero-dependency CLI that installs a smart quality gate with a ratchet mechanism into any JavaScript or TypeScript project. It detects your stack automatically, presents the viable options, and generates the necessary files: a standalone gate script, a CI workflow, and npm scripts — all tailored to your project.
A ratchet only moves in one direction. The quality gate works the same way:
- Baseline — You generate a
baseline.jsonthat records the current metrics of your project (coverage percentage, lint error count, duplication rate, vulnerability count). - Every PR/push — The gate compares current metrics against the baseline.
- Regression = blocked — If a metric got worse (e.g. coverage dropped, more lint errors), the CI job fails and the PR cannot be merged.
- Improvement = recorded — If metrics improved, the gate reports it. You run
quality:baselineto advance the ratchet to the new, better state.
The key insight: you can never silently regress. Every merge either holds the line or improves it.
npx boatman initOr target a specific directory:
npx boatman init --path ./my-projectAccept all defaults without interactive prompts:
npx boatman --yesPreview what would be generated without writing any files:
npx boatman --dry-run| Category | Supported |
|---|---|
| Language | JavaScript, TypeScript |
| Package manager | npm, pnpm, yarn, bun |
| Test runner | Vitest, Jest, Mocha |
| Coverage | @vitest/coverage-v8, @vitest/coverage-istanbul, jest |
| Linter | ESLint (any config format) |
| Mutation testing | Stryker (@stryker-mutator/core) |
| CI platform | GitHub Actions, GitLab CI |
| Check | What it tracks | Blocking? |
|---|---|---|
eslint |
Error and warning count | Warn only (non-blocking) |
coverage |
Lines, branches, functions percentage | Yes — drop blocks the PR |
duplication |
Code duplication percentage (via jscpd) | Yes — increase blocks the PR |
audit |
Critical and high npm vulnerabilities | Critical: zero-tolerance |
mutation |
Mutation score percentage (via Stryker) | Warn only (non-blocking) |
complexity |
Cyclomatic complexity & long functions | Warn only (non-blocking) |
pr-comment |
Posts a Markdown report as a PR comment | N/A (GitHub only) |
After running boatman init, the following files are created or updated in your project:
your-project/
├── scripts/
│ ├── quality-gate.mjs # Standalone gate script
│ └── count-lint.mjs # ESLint output counter (stdin helper)
├── .github/
│ └── workflows/
│ └── quality-gate.yml # GitHub Actions workflow (if selected)
├── .gitlab-ci.yml # GitLab CI config (if selected)
└── package.json # Scripts injected: quality:gate, quality:baseline, ...
| Script | What it does |
|---|---|
quality:gate |
Runs the gate in CI mode (Markdown output) |
quality:gate:local |
Runs the gate with human-readable terminal output |
quality:baseline |
Runs all tools and saves a new baseline.json |
duplication |
Runs jscpd and saves report (if selected) |
quality:mutation |
Runs Stryker mutation tests (if selected) |
count-lint.mjs is a helper that reads ESLint's JSON output from stdin and prints a human-readable summary. Useful for quick checks without opening the full JSON report:
npx eslint src --ext ts,tsx --format json | node scripts/count-lint.mjs
# ESLint: 3 errors, 12 warningsWhen the complexity check is enabled, boatman runs an ESLint scan with two specific rules applied on top of your existing config:
| Rule | Default threshold | Metric tracked |
|---|---|---|
complexity |
max 10 | complexity_violations |
max-lines-per-function |
max 50 lines | long_function_violations |
The scan writes its output to reports/complexity.json and the gate tracks the number of violations over time. Both metrics are non-blocking — they warn when the count increases but do not fail the build. This lets teams adopt the check incrementally: start tracking today, reduce the count gradually, and never let it grow again.
The check requires ESLint to be installed in the target project. It is available in the interactive installer only when ESLint is detected, and defaults to off (opt-in).
After installation, run the tools once to create your starting point:
npm run quality:baseline
# or: pnpm quality:baseline / yarn quality:baseline / bun run quality:baselineThis runs ESLint, your test suite with coverage, jscpd, npm audit, Stryker, and/or the complexity scan — then saves all metric values to baseline.json:
{
"generatedAt": "2026-05-07T10:00:00.000Z",
"commit": "a1b2c3d",
"project": "my-project",
"metrics": {
"eslint_errors": 0,
"coverage_lines": 84.2,
"duplicate_percent": 3.2,
"audit_critical": 0,
"mutation_score": 65.3,
"complexity_violations": 2,
"long_function_violations": 5
}
}git add baseline.json
git commit -m "chore: add quality gate baseline"The baseline is tracked in version control so every branch has the same reference point.
After a PR that genuinely improved metrics (e.g. added tests, fixed lint errors), update the baseline:
npm run quality:baseline
git add baseline.json
git commit -m "chore: advance quality baseline after coverage improvements"npm run quality:gate:localExample output:
Quality Gate — my-project
Improvements:
✔ Coverage lines %: 84.2 % (was 80.1 % +4.10 ↑)
✔ Mutation score: 65.3 % (was 60.0 % +5.30 ↑)
Unchanged:
─ ESLint errors: 0 errors
─ Duplication %: 3.2 %
Quality gate PASSED.
When running on GitHub Actions with the pr-comment check enabled, the gate posts a sticky comment on every PR:
## ✅ Quality Gate PASSED
**Project:** `my-project`
**Generated:** 2026-05-07T10:00:00.000Z
### Metrics
| Metric | Current | Baseline | Delta | Status |
|---------------------|----------|----------|---------|--------|
| ESLint errors | 0 errors | 0 errors | +0.00 → | ➡️ |
| Coverage lines % | 84.2 % | 80.1 % | +4.10 ↑ | ✅ |
| Coverage branches % | 78.5 % | 78.5 % | +0.00 → | ➡️ |
| Duplication % | 3.2 % | 3.5 % | -0.30 ↓ | ✅ |
| Mutation score | 65.3 % | 60.0 % | +5.30 ↑ | ✅ |
### ✅ Improvements Detected
- **Coverage lines %**: 84.2 % ↑ (was 80.1 %)
- **Duplication %**: 3.2 % (was 3.5 %)
- **Mutation score**: 65.3 % ↑ (was 60.0 %)
> Run `npm run quality:baseline` to update the baseline with these improvements.
---
*Generated by [boatman](https://github.com/rafaelvieiras/boatman)*
When a regression is detected:
## ❌ Quality Gate FAILED
### ❌ Blocking Regressions
- **Coverage lines %**: 72.3 % ← was 80.1 %
> Fix these regressions before merging, then run `npm run quality:baseline` if the new values are intentional.
boatman uses only Node.js built-ins (fs, path, readline, child_process). The generated gate script is also standalone — it does not import anything from boatman at runtime.
- Fork the repository
- Create your feature branch:
git checkout -b feat/my-feature - Make your changes and ensure the CLI still works:
node bin/boatman.mjs --dry-run - Commit your changes:
git commit -m "feat: add my feature" - Open a pull request
Bug reports and feature requests are welcome via GitHub Issues.
MIT — see LICENSE for details.
