@poleski/quality-tools
v0.2.1
Published
Portable TypeScript quality checks for project structure, complexity, mutation, and test health
Downloads
4,941
Readme
@poleski/quality-tools
Portable TypeScript quality checks for project structure, complexity, reachability, mutation, and test health.
The tools are intentionally project-agnostic. They discover packages from
pnpm-workspace.yaml or package.json#workspaces, accept explicit file/folder
targets, and read project-specific behavior from quality.config.json.
Install
pnpm add -D @poleski/quality-tools
pnpm exec quality-tools initBefore publish, use a local link from a host project:
pnpm add -D link:/absolute/path/to/quality-toolsQuick Start
pnpm exec quality-tools organize .
pnpm exec quality-tools acceptance compile "tests/acceptance/specs/**/*.feature" "tests/playwright/generated" --steps "tests/acceptance/steps.ts" --ir "build/acceptance/ir" --dry "build/acceptance/dry"
pnpm exec quality-tools boundaries . --strict
pnpm exec quality-tools reachability . --strict
pnpm exec quality-tools scrap ./tests
pnpm exec quality-tools crap ./src
pnpm exec quality-tools mutate .
pnpm exec quality-tools mutate ./src/parser.ts --force
pnpm exec quality-tools mutate -- --mutate ./src/parser.tsTargets can be the repo root, a package shorthand, a package root, a directory,
or a file. Package shorthand is resolved from workspace package names, not from
hardcoded folder names. The starter config uses src as a conventional default,
but the tools scope from the target path plus your configured include/exclude
globs.
Acceptance
The acceptance tool follows a small parser -> IR -> DRY check -> entrypoint generator pipeline:
pnpm exec quality-tools acceptance parse features/example.feature build/acceptance/ir/example.json
pnpm exec quality-tools acceptance dry-check build/acceptance/ir/example.json build/acceptance/dry/example.json
pnpm exec quality-tools acceptance generate build/acceptance/ir/example.json acceptance/generated/example.spec.ts --steps acceptance/steps.tsFor host projects with many specs, compile loops the same pipeline over a
glob, writes one JSON IR file per source spec, optionally writes advisory DRY
reports, and creates thin Playwright entrypoints that load the IR at runtime:
pnpm exec quality-tools acceptance compile "tests/acceptance/specs/**/*.feature" "tests/playwright/generated" --steps "tests/acceptance/steps.ts" --ir "build/acceptance/ir" --dry "build/acceptance/dry"The generated Playwright files delegate all behavior to the host step registry. DRY reports are advisory; they do not rewrite the source specs, IR, generated tests, or host bindings.
The checker treats exact duplicate assertions as actionable only when no setup or action step occurs between the repeated assertions. This keeps restoration checks such as toggling a graph-scope filter off and back on out of the major cleanup path while still flagging accidental duplicate lines.
Scenario Outline examples may include a case, name, title, or example
column to control the generated Playwright case suffix. Prefer case as the
first column when the remaining example values are detailed test data; this
keeps generated test names readable while preserving the full row in the runtime
example payload.
Reports
All quality-tool artifacts use one report root:
{
"reportsDir": "reports/quality-tools"
}Current outputs:
reportsDir/organize/*.jsonfor organize baselinesreportsDir/scrap/*.jsonfor scrap baselinesreportsDir/crap/<target>/coverage-final.jsonfor CRAP coverage inputreportsDir/mutation/mutation.jsonandmutation.htmlfrom StrykerreportsDir/mutation/<target>/mutation.jsoncopied per mutation target
CRAP reads the coverage report path you configure. The starter Vitest command
writes that coverage under reportsDir/crap/<target>/.
Config
Run quality-tools init to create a starter quality.config.json.
{
"reportsDir": "reports/quality-tools",
"defaults": {
"crap": {
"coverage": {
"command": "pnpm",
"args": [
"exec",
"vitest",
"run",
"--coverage",
"--coverage.reportsDirectory",
"{repoRoot}/{reportsDir}/crap/{reportKey}"
],
"coveragePath": "{repoRoot}/{reportsDir}/crap/{reportKey}/coverage-final.json"
},
"exclude": ["**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"]
},
"mutation": {
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["src/**/*.d.ts"]
},
"scrap": {
"include": ["tests/**/*.test.ts", "tests/**/*.test.tsx"],
"exclude": []
},
"boundaries": {
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["src/**/*.d.ts", "**/*.test.ts", "**/*.test.tsx"],
"entrypoints": [],
"layers": []
},
"organize": {
"lowInfoNames": {
"banned": ["utils", "helpers", "misc", "common", "shared", "_shared", "lib", "index"],
"discouraged": ["types", "constants", "config", "base", "core"]
}
}
},
"packages": {}
}Package-specific config lives under packages.<packageName>. The package name
is the unscoped manifest name by default, so @scope/parser is configured as
parser.
CRAP
CRAP calculates complexity plus coverage risk and fails when a function exceeds
the threshold. The default threshold is 8.
pnpm exec quality-tools crap ./src
pnpm exec quality-tools crap parser --threshold 10CRAP needs Istanbul coverage-final.json. Configure the command and report path
for your project. Put generated coverage under {reportsDir} when possible so
all artifacts stay together:
{
"defaults": {
"crap": {
"coverage": {
"command": "pnpm",
"args": [
"exec",
"vitest",
"run",
"--coverage",
"--coverage.reportsDirectory",
"{repoRoot}/{reportsDir}/crap/{reportKey}"
],
"coveragePath": "{repoRoot}/{reportsDir}/crap/{reportKey}/coverage-final.json"
}
}
},
"packages": {
"parser": {
"crap": {
"coverage": {
"command": "pnpm",
"args": [
"--filter",
"{packageJsonName}",
"exec",
"vitest",
"run",
"--coverage",
"--coverage.reportsDirectory",
"{repoRoot}/{reportsDir}/crap/{reportKey}"
],
"coveragePath": "{repoRoot}/{reportsDir}/crap/{reportKey}/coverage-final.json"
}
}
}
}
}Supported template values in CRAP coverage command fields:
{repoRoot}{packageRoot}{packageName}{packageJsonName}{reportKey}{target}or{targetPath}{reportsDir}
Mutation
Mutation runs Stryker against the selected source target. The CLI does not run typecheck first and does not assume any package is special.
pnpm exec quality-tools mutate .
pnpm exec quality-tools mutate parser
pnpm exec quality-tools mutate ./src/parser.ts --force
pnpm exec quality-tools mutate -- --mutate ./src/parser.tsmutate requires an explicit target. Use . for a repo-wide run, a package
shorthand for a workspace package, or a file/folder path for a narrower run.
Bare quality-tools mutate is intentionally invalid so host projects can own
their own all-package wrappers. --force is passed through to Stryker when you
need to ignore incremental mutation state.
The CLI passes Stryker:
- the Stryker config path
- scoped mutate globs from
quality.config.json - an incremental file path under
reportsDir/mutation/<target>/ QUALITY_TOOLS_REPORTS_DIR, so the bundled base config writes to the shared report root
Long mutation runs print a progress heartbeat once per minute. The bundled base config also accepts these environment knobs:
QUALITY_TOOLS_STRYKER_CONCURRENCY, default2QUALITY_TOOLS_STRYKER_MAX_TEST_RUNNER_REUSE, default0
Use a project Stryker config when you need custom Vitest wiring:
const base = require('@poleski/quality-tools/stryker.config.cjs');
module.exports = {
...base,
vitest: {
...base.vitest,
configFile: 'vitest.config.ts'
}
};Then reference it in quality.config.json:
{
"defaults": {
"mutation": {
"strykerConfig": "stryker.config.cjs",
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["src/**/*.d.ts"]
}
}
}If strykerConfig is omitted, the tool looks for stryker.config.cjs,
stryker.config.mjs, stryker.config.js, or stryker.conf.js in the repo
root, then falls back to the bundled base config.
Other Tools
pnpm exec quality-tools acceptance compile "tests/acceptance/specs/**/*.feature" "tests/playwright/generated" --steps "tests/acceptance/steps.ts" --ir "build/acceptance/ir" --dry "build/acceptance/dry"
pnpm exec quality-tools organize ./src
pnpm exec quality-tools boundaries parser --strict
pnpm exec quality-tools reachability parser --strict
pnpm exec quality-tools scrap ./tests --strictacceptancecompiles human-authored Gherkin.featurespecs into executable Playwright specs that import host-owned step bindings.organizechecks directory size, depth, naming, barrels, and cohesion. Use--write-baselineand--compare <path>for baseline workflows.boundarieschecks configured layers, entrypoints, dead surfaces, and dead ends.reachabilityreports dead surfaces and dead ends without boundary-layer failures.scrapreviews test files for split pressure, repeated setup, weak assertions, validation issues, and refactor recommendations.
Development
pnpm run test
pnpm run lint
pnpm run typecheck
pnpm run build