{"_id":"@bwawan/mutagen","name":"@bwawan/mutagen","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@bwawan/mutagen","version":"0.1.0","description":"Lightweight AST-based mutation testing for JavaScript","license":"MIT","author":{"name":"Brandon Correa"},"repository":{"type":"git","url":"git+https://github.com/brandoncorrea/mutagen.git"},"keywords":["mutation-testing","mutation","testing","vitest"],"type":"module","main":"src/index.js","bin":{"mutagen":"src/bin/mutagen.js"},"engines":{"node":">=20.11.0"},"exports":{".":"./src/index.js","./mutators":"./src/core/ast-mutators.js","./runners/vitest":"./src/runners/vitest.js"},"dependencies":{"@babel/parser":"^7.29.2","picomatch":"^4.0.4"},"scripts":{"test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","mutate":"node src/bin/mutagen.js --incremental --progress --parallel 8 --json reports/mutation/mutations.json","mutate:dry":"node src/bin/mutagen.js --all --dry-run"},"peerDependenciesMeta":{"vitest":{"optional":true}},"devDependencies":{"@vitest/coverage-v8":"^4.1.5","vitest":"^4.1.5"},"gitHead":"c13c777c780da61534f4a8cc77eeb17dd0eaef7f","_id":"@bwawan/mutagen@0.1.0","bugs":{"url":"https://github.com/brandoncorrea/mutagen/issues"},"homepage":"https://github.com/brandoncorrea/mutagen#readme","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-+mk90BZn+H63xctAdc3CWRAyYKxTyv5Soaq17qbTD+2GqkHdDX9xMuWg72Xux0Lg6HCak3VJEaY3HULD6DpAOA==","shasum":"5ccd97c15aa59a821bee0d3980ea27436c83c6ff","tarball":"https://registry.npmjs.org/@bwawan/mutagen/-/mutagen-0.1.0.tgz","fileCount":43,"unpackedSize":155206,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAXwOKLHcJhsCvrLmiDmWip+pudOLsjWXQrpssa1Y8uCAiBN5QE+yQ7wPG+Zi9TBzLt0i3vnm/UY4ybFMfhgdPKmZg=="}]},"_npmUser":{"name":"bwawan","email":"bwancor@gmail.com"},"directories":{},"maintainers":[{"name":"bwawan","email":"bwancor@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mutagen_0.1.0_1777006675888_0.6835327547376062"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-24T04:57:55.831Z","0.1.0":"2026-04-24T04:57:56.115Z","modified":"2026-04-24T04:57:56.264Z"},"maintainers":[{"name":"bwawan","email":"bwancor@gmail.com"}],"description":"Lightweight AST-based mutation testing for JavaScript","homepage":"https://github.com/brandoncorrea/mutagen#readme","keywords":["mutation-testing","mutation","testing","vitest"],"repository":{"type":"git","url":"git+https://github.com/brandoncorrea/mutagen.git"},"author":{"name":"Brandon Correa"},"bugs":{"url":"https://github.com/brandoncorrea/mutagen/issues"},"license":"MIT","readme":"# @bwawan/mutagen\n\nA lightweight mutation testing engine for JavaScript/TypeScript projects. AST-based mutations with automatic worktree isolation — original source files are never modified.\n\nRequires Node.js >= 20.11.0.\n\n```bash\nnpm install @bwawan/mutagen\n```\n\n## Quick start\n\n1. Create `mutagen.config.js` in your project root:\n\n```js\nimport { mutators, createVitestRunner } from '@bwawan/mutagen'\n\nexport default {\n  mutators: [...mutators.javascript],\n  include: ['src/**/*.js'],\n  exclude: ['**/*.test.js'],\n  createRunner: sourceFile => createVitestRunner(sourceFile)\n}\n```\n\n2. Run mutations:\n\n```bash\nnpx mutagen --all                   # All configured sources\nnpx mutagen src/foo.js              # Single file\nnpx mutagen --incremental           # Skip unchanged files\nnpx mutagen --all --parallel 4      # 4 parallel workers\nnpx mutagen --diff a.json b.json    # Compare two reports\n```\n\n## Agent usage\n\nMutagen is designed for agent consumption. Use `--quiet` and `--json` for machine-readable output:\n\n```bash\n# One-line summary to stderr, structured JSON to file\nnpx mutagen --all --quiet --json reports/mutation.json\n\n# Incremental: only re-test changed files, JSON report\nnpx mutagen --incremental --quiet --json reports/mutation.json\n\n# Only mutate files changed in git (pairs with --all or --incremental)\nnpx mutagen --all --changed --quiet --json reports/mutation.json\n\n# Retest: re-run only previously-surviving mutations from a report\nnpx mutagen --retest reports/mutation.json --quiet --json reports/retest.json\n\n# Fail if mutation score drops below 80%\nnpx mutagen --all --quiet --min-score 80\n\n# Compare reports for regressions (exit code 1 = regressions found)\nnpx mutagen --diff before.json after.json --json\n\n# JSON report to stdout (for piping)\nnpx mutagen --all --quiet --json - | jq '.survivors'\n\n# Only show surviving mutations on stdout (what to fix)\nnpx mutagen --all --survivors-only\n```\n\n### Exit codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | All mutations killed |\n| 1 | Surviving mutations or errors |\n\n### JSON report schema\n\nWhen `--json [path]` is used, a structured report is written:\n\n```json\n{\n  \"score\": 85.7,\n  \"total\": 14,\n  \"killed\": 12,\n  \"survived\": 2,\n  \"timedOut\": 0,\n  \"files\": {\n    \"src/foo.js\": {\n      \"score\": 100, \"killed\": 8, \"total\": 8,\n      \"mutants\": [\n        { \"id\": \"a1b2c3d4\", \"name\": \"=== → !==\", \"status\": \"killed\", \"line\": 10,\n          \"original\": \"if (a === b) {}\", \"mutated\": \"if (a !== b) {}\",\n          \"killedBy\": [\"tests/foo.test.js\"] }\n      ]\n    },\n    \"src/bar.js\": { \"score\": 66.7, \"killed\": 4, \"total\": 6, \"mutants\": [] }\n  },\n  \"survivors\": [\n    {\n      \"id\": \"e5f6a7b8\",\n      \"file\": \"src/bar.js\",\n      \"line\": 42,\n      \"name\": \"=== → !==\",\n      \"original\": \"if (a === b) {}\",\n      \"mutated\": \"if (a !== b) {}\",\n      \"coveredBy\": [\"tests/bar.test.js\"]\n    }\n  ],\n  \"deltas\": {\n    \"fixes\": [{ \"file\": \"src/bar.js\", \"line\": 10, \"name\": \"+ → -\" }],\n    \"regressions\": [],\n    \"rerunFiles\": [\"src/bar.js\"],\n    \"cachedFiles\": [\"src/foo.js\"]\n  }\n}\n```\n\nPer-file entries include a `mutants` array with every individual mutant (id, name, status, line, original, mutated, killedBy/coveredBy). Survivors are collected into the top-level `survivors` array for quick access. The `deltas` field is only present in incremental mode.\n\n## Config file\n\nThe `mutagen.config.js` default export is passed directly to `createManualRunner`:\n\n```js\nexport default {\n  mutators: [...],            // AST mutators (primary — see Mutator format)\n  include: ['src/**/*.js'],   // Glob patterns for source files\n  exclude: ['**/*.test.js'],  // Glob patterns to exclude (optional)\n  sources: ['src/foo.js'],    // Explicit source files (takes precedence over include/exclude)\n  cwd: process.cwd(),         // Base directory for glob resolution (default: cwd)\n  testSources: [],             // Explicit test files for incremental invalidation\n  testInclude: ['tests/**/*.test.js'],  // Glob patterns to discover test files\n  testExclude: [],             // Glob patterns to exclude from test discovery\n  createRunner: async (sourceFile) => runner,  // Test runner factory\n  reportDir: 'reports/mutation',               // Directory for JSON reports\n  reportFile: 'manual-report.json',            // Report filename\n  skipNodes: [],                 // AST node patterns to exclude from mutation (optional)\n  timeout: null                  // Default per-mutation timeout in ms\n}\n```\n\nUse `mutators` for AST-based mutations. The built-in `mutators.javascript` set covers all common JavaScript operators and constructs.\n\n`skipNodes` accepts AST node pattern objects. Any node matching a pattern is excluded from mutation along with all its children. Example: skip all `console.log` calls:\n\n```js\nskipNodes: [{ type: 'CallExpression', callee: { object: { name: 'console' } } }]\n```\n\n## CLI flags\n\n```\n<source>                        Mutate a single file\n<source> --line 42              Target a single line\n<source> --dry-run              List mutations without running\n<source> --json [path]          Structured JSON report (file path, or - for stdout)\n<source> --timeout 10000        10s timeout per mutation\n--all                           Batch all configured sources\n--all --dry-run                 Preview across all sources\n--incremental                   Hash-based caching, skip unchanged\n--incremental --json            Incremental + JSON report with deltas\n--parallel [N]                  Run mutations in parallel (default: 2 workers, max 32)\n--quiet                         Suppress verbose output, one-line summary to stderr\n--survivors-only                Only report surviving mutations\n--changed                       Only mutate files with uncommitted git changes\n--progress                      Show compact per-file dot notation progress on stderr\n--min-score N                   Exit 1 if mutation score is below N%\n--retest <report.json>          Re-run only previously-surviving mutations\n--diff <before> <after>         Compare two JSON report files\n--version, -v                   Print version number\n--help, -h                      Show usage information\n```\n\n`--json`, `--timeout`, `--parallel`, `--quiet`, `--progress`, `--survivors-only`, and `--changed` work across single-file, `--all`, and `--incremental` modes.\n\n## Programmatic API\n\n```js\nimport { createManualRunner, mutators, createVitestRunner } from '@bwawan/mutagen'\n\nconst runner = createManualRunner({\n  mutators: [...mutators.javascript],\n  include: ['src/**/*.js'],\n  createRunner: sourceFile => createVitestRunner(sourceFile)\n})\n\nrunner.main()\n```\n\nFor Jest projects:\n\n```js\nimport { createManualRunner, mutators, createJestRunner } from '@bwawan/mutagen'\n\nconst runner = createManualRunner({\n  mutators: [...mutators.javascript],\n  include: ['src/**/*.js'],\n  createRunner: sourceFile => createJestRunner(sourceFile, {\n    config: 'jest.config.js'\n  })\n})\n\nrunner.main()\n```\n\n## Runner interface\n\nThe `createRunner` callback receives a source file path and returns a runner object:\n\n```js\nasync function createRunner(sourceFile) {\n  return {\n    async run() { return { passed: true, killedBy: [] } },\n    async close() {}\n  }\n}\n```\n\nMutagen creates a temporary worktree (project copy) and writes mutations there. Original source files are never modified. The runner receives the worktree path as `sourceFile` and an `options.root` pointing to the worktree root.\n\n## Vitest runner\n\nBuilt-in adapter for Vitest with warm/cold fallback and module-graph-based test narrowing:\n\n```js\nimport { createVitestRunner } from '@bwawan/mutagen/runners/vitest'\n\ncreateVitestRunner(sourceFile)\n\n// With options\ncreateVitestRunner(sourceFile, {\n  config: 'frontend/vitest.config.js',\n  root: 'frontend',\n  testFile: 'tests/specific.test.js',\n  warm: true   // default: warm rerun, falls back to cold\n})\n```\n\n## Jest runner\n\nBuilt-in adapter for Jest (cold mode). Works with any Jest-compatible setup including `next/jest`:\n\n```js\nimport { createJestRunner } from '@bwawan/mutagen'\n\ncreateJestRunner(sourceFile)\n\n// With options\ncreateJestRunner(sourceFile, {\n  config: 'jest.config.js',\n  root: 'frontend'\n})\n```\n\n### Next.js projects (next/jest)\n\n```js\n// mutagen.config.js\nimport { mutators, createJestRunner } from '@bwawan/mutagen'\n\nexport default {\n  mutators: [...mutators.javascript],\n  include: ['src/**/*.js', 'src/**/*.tsx'],\n  exclude: ['**/*.test.*'],\n  createRunner: sourceFile => createJestRunner(sourceFile, {\n    config: 'jest.config.js'\n  })\n}\n```\n\n## Mutator format (AST)\n\nAST mutators target specific node types in the parsed syntax tree:\n\n```js\n{\n  name: '=== → !==',               // Human-readable name\n  types: ['BinaryExpression'],      // ESTree/Babel node types to visit\n  test(node, source, parent) {},    // Return true if this node should be mutated\n  mutate(node, source, parent) {}   // Return { start, end, replacement } or null\n}\n```\n\nThe built-in `mutators.javascript` set covers equality, logical, arithmetic, boolean, conditional, method, string, array, object, bitwise, update, unary, async, optional chaining, nullish coalescing, spread, void, throw, and property access operators.\n\nAST mutations are precise — they understand syntax structure, so they never accidentally mutate inside strings, comments, or JSX attributes.\n\n## Parallel execution\n\nThe `--parallel` flag runs mutations concurrently using an in-process worker pool. Each worker operates in its own worktree for crash-safe isolation.\n\n```bash\nnpx mutagen src/foo.js --parallel       # 2 workers (default)\nnpx mutagen --all --parallel 8          # 8 workers across all sources\nnpx mutagen --incremental --parallel 4  # Incremental + parallel\n```\n\n## Incremental mode\n\nIncremental mode tracks SHA-256 hashes of source and test files between runs. Only changed files (or files whose tests changed) are re-mutated. Cached results carry forward. The JSON report includes `deltas` showing fixes and regressions since the last run.\n\n```bash\nnpx mutagen --incremental --json reports/mutation.json\n```\n\n## Diff mode\n\nCompare two JSON reports to find regressions, improvements, and new/removed mutants:\n\n```bash\nnpx mutagen --diff before.json after.json\nnpx mutagen --diff before.json after.json --json   # Machine-readable diff\n```\n\nReturns exit code 1 if regressions are found.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-ad0e9ed23ebe70987642be23ebf99b07"}