# Changelog

All notable changes to this project will be documented in this file.

## 0.38.0 — 2026-09-18

### 🎉 Features

- Give nmr plain output in pipes and CI logs (#838)

  - Stops `nmr` from printing emoji to a pipe or a CI log: When the output stream is not a terminal or `CI` is set, a verdict reads `PASS`, `FAIL`, `SKIP`, or `NOOP` and a command icon is omitted, so that a log can be searched for a verdict.
  - Adds `--output-style <auto|plain|rich>` to `nmr` and the `NMR_OUTPUT_STYLE` variable, each forcing either style; the flag outranks the variable, a value that names no style exits 1 with a usage error, and neither enters a check-cache key: A rich run and a plain run share a recorded pass.
  - Replaces several rich glyphs, taking the status markers from the set that release-kit and v11y-check print so that one status has one glyph across the three tools: a recalled pass shows ⏩ in place of ⏭️, a skipped empty override shows ⚪ in place of ⛔, a warning and the `--log` refusal show 🟠 in place of ⚠️ and 📭, the `--log` header shows 💾 in place of 📼, and `nmr-ensure-prepublish-hooks` reports ✅ and ❌ in place of ✓ and ✗.

### 🪦 Removed

- 🚨 **Breaking:** Rename the declinesArgs step declaration to shouldDeclineArguments (#822)

  - Makes a `.config/nmr.config.ts` that still declares `declinesArgs` on a composite script's `{ run, declinesArgs }` step fail to load, with an error that names `shouldDeclineArguments` as the replacement.
  - Renames the matching field of the `StepSpec` type exported by `@williamthorsen/nmr/config` and `@williamthorsen/nmr/scripts`.

  Migration: Rename `declinesArgs` to `shouldDeclineArguments` in each composite script step of `.config/nmr.config.ts`, and in any code that sets or reads the field on a `StepSpec`.

## 0.37.0 — 2026-09-15

### 🎉 Features

- Guard lefthook install in root prepare and report unguarded installs (#798)

  - Adds a `warn` check to the readyup kit of `@williamthorsen/nmr` that reports a root script run by `pnpm install`, such as `prepare` or `postinstall`, whose command calls `lefthook install` without `lefthook check-install`.
  - Includes in the check's suggested fix the guarded form `lefthook check-install || lefthook install`, which skips rewriting `.git/hooks` while `lefthook.yml` is unchanged since the last install, so that a repo using that form can run `pnpm install` without write access to `.git/hooks`, as inside the Claude Code sandbox, and under pnpm's default `verifyDepsBeforeRun` avoids a reinstall before every `pnpm exec`.

- Report misplaced test files and a missing guard from the readyup kit (#804)

  - Adds an `error` check to the `@williamthorsen/nmr` readyup kit that reports each `*.test.ts` or `*.test.tsx` file outside a `__tests__` directory, which no Vitest project collects and which therefore runs nowhere.
  - Adds a `warn` check that passes once a test file under `__tests__` imports `checkTestFileConventions` from `@williamthorsen/nmr/tests`, and whose fix shows the one-line guard test to add.
  - Makes the kit skip both test-file checks once it finds that guard, because the guard reports the same files and applies the repo's `exclude` list, which the kit cannot read.

### 🐛 Bug fixes

- Fail when a -F pattern selects no workspace (#785)

  - Fixes a run that reported success without doing anything. `nmr -F <pattern> <command>` printed nothing and exited 0 when the pattern matched no package. It now exits 1, names the rejected pattern, and explains the likely cause.
  - Covers three further selections that ran nothing and exited 0: an exclusion that leaves only the workspace root, such as `nmr -F '!./packages/*'`; `-R` in a workspace that declares no package; and an empty `-F ''`.

  Migration: Replace any `-F` pattern that names a directory with the package's manifest `name`. A filter that selects nothing now fails the run instead of passing in silence.

- Fix test runs failing on a dependency with a TypeScript source export (#793)

  - Fixes the issue that, with `resolveFromSource` at its default of `true`, every test in a file failed with `Stripping types is currently unsupported for files under node_modules` when the file's imports reached a dependency whose `exports` map declares a `source` condition pointing at TypeScript.
  - Limits the source resolution enabled by `resolveFromSource` to packages whose real path lies outside every `node_modules`, which covers workspace packages and packages linked into the repo from elsewhere.
  - Makes an import of a workspace package fail when that package's `source` condition names a missing file, instead of resolving to the build output without warning.
  - Makes conditions that a config adds to `resolve.conditions` or `ssr.resolve.conditions` extend Vite's defaults when `resolveFromSource` is `false`, instead of replacing them.

  Migration: Point each workspace package's `source` condition at a file that exists, or remove the condition. Build a workspace package before running a test that reaches it through an externalized dependency (one that Vitest loads through Node instead of transforming): Node resolves that import without the `source` condition, so it loads the package's build output.

- Prevent a recursive or multi-package run from hanging under pnpm 12 (#814)

  - Fixes a hang under pnpm 12: In a terminal, `nmr -R <command>`, a root command that runs in every package such as `nmr build`, and a `-F` pattern that selects several packages printed nothing and waited until Ctrl+C when a package command read keyboard input or changed the terminal's mode, as the spinner in `next build` does.
  - Gives no terminal input to each package of a `-R` run or a multi-package `-F` run, including a dependency-chain filter such as `-F 'app...'`, and keeps it for a `-F` filter that selects one package, which an interactive command such as `nmr -F <pkg> test:watch` needs.

### 🧪 Tests

- Run the descriptor-inheritance test on Linux and skip it in a sandbox (#803)

  - Stops `nmr ci` from failing in every Claude Code sandbox session on macOS by skipping the suite in `inherit-descriptor.tool.test.ts` when `SANDBOX_RUNTIME` is set, because the sandbox does not let `script(1)` open a pseudo-terminal.

### ⚙️ Tooling

- Upgrade eslint-config-typescript to v17 and adopt its ignore lists (#797)

  - Declares `vitest` as `catalog:` in every workspace that imports it, with the pnpm catalog pinning 4.1.11, because v17 no longer exempts `vitest` from `n/no-extraneous-import`.
  - Builds the global ignores in `eslint.config.ts` from the published `commonIgnores` and `toolIgnores`, which also exclude `pnpm-lock.yaml` and each `.readyup/manifest.json`, so the `eslint --fix` in `nmr lint` can no longer rewrite a kit manifest and make the next build's `rdy verify` fail.

### 📚 Documentation

- Trim the README and move reference detail into shipped topic docs (#800)

  - Deletes the default script registry tables and the CLI flag table, which `nmr --help` prints, and the migration sections, which `CHANGELOG.md` records, rather than moving them.
  - Adds `docs` to the files published with `@williamthorsen/nmr`, and changes the closing pointer in `agents/guidance/rulebooks/nmr.md` to name each topic's document under `node_modules/@williamthorsen/nmr/docs/` in place of `README.md`.

- Trim the release-kit README and move reference detail into shipped docs (#802)

  - Adds `docs` to the published `files` of `@williamthorsen/release-kit` and a `!docs/*.v*.md` exclusion to both it and `@williamthorsen/nmr`, because `pnpm pack` would otherwise include the release-notes previews that a local checkout holds.
  - Adds `--version` to the options that `release-kit --help` lists, and points the hints for the removed `releaseNotes.shouldCreateGithubRelease` field at `release-kit init` instead of the README.

## 0.36.0 — 2026-09-08

### 🎉 Features

- Report a package whose tarball would omit its changelog (#768)

  Adds ReadyUp checks to verify that every published package bundles its changelog files: the human-readable `CHANGELOG.md` and the machine-readable `changelog.json`.

- Reject a checkCache command name that matches no command (#778)

  Modifies `nmr` so that it rejects a `checkCache.extraCommands` or `checkCache.excludeCommands` name that matches no command, so a misspelt entry fails config load instead of silently having no effect.

  Modifies `nmr` so that it resolves names listed in either field against nmr's default scripts, the repo config's script registries, and each package's `package.json` scripts, so a name that only a package declares is accepted.

  Migration: Correct or remove any `checkCache.extraCommands` or `checkCache.excludeCommands` entry that names no command, and any entry that names a hook, which is rejected too. The error reports the field, the offending name, and the closest command where there is one.

- Report a bin target that is not a committed wrapper (#780)

  - Adds `every bin target is a committed wrapper` to nmr's readyup kit, which reports a workspace `bin` target naming a path under `dist/`, or one that git does not track, because pnpm links a package's bins before anything is built and never retries a failed link.
  - Adds `every bin wrapper's build-output target is covered by files`, which reports a wrapper loading build output that `files` omits, since that publishes a bin resolving to nothing.

  Migration: Replace a `bin` target naming a path under `dist/` with a committed wrapper under `bin/` that imports the build entry, commit the wrapper, and extend `files` to cover the output that the wrapper loads.

### 🐛 Bug fixes

- Keep global git ignore rules and attributes out of test subprocesses (#773)

  Fixes an issue where a test that spawns `git` read ignore rules and attributes from outside the repository, so a suite could pass or fail on whatever a developer or their machine happened to have configured. The setup file that `defineVitestConfig` loads by default now isolates both, alongside the git configuration already covered.

  Migration: Where a test needs its own `core.excludesFile` or `core.attributesFile`, pass it on the invocation as `git -c core.excludesFile=<path>`. An environment-injected key now outranks repository-local config.

### 🧪 Tests

- Read console spy output with listConsoleLines rather than by hand (#781)

  - Replaces the hand-rolled reads of a console spy's `mock.calls` in the `nmr`, `release-kit`, and `v11y-check` test suites with `listConsoleLines` from `@williamthorsen/toolbelt.vitest/candidate`.
  - Clears the `toolbelt.vitest/no-console-calls-read` recommendation that `rdy run --packages` reported.

## 0.35.0 — 2026-09-01

### 🎉 Features

- Expose tsconfigPaths as a first-class Vitest config flag (#751)

  Adds a `tsconfigPaths` option to `defineVitestConfig` and `defineRootVitestConfig` in `@williamthorsen/nmr/vitest`. It emits Vite's `resolve.tsconfigPaths`, so a test resolves an aliased specifier through the `paths` its own `tsconfig.json` declares, the way `tsc` does.

  The option defaults to `false` and requires Vite 8, which `@williamthorsen/nmr` now declares as an optional peer dependency at `>=8.0.0 <9`.

- Publish a test-file conventions check consuming repos can gate in CI (#753)

  Adds `checkTestFileConventions` at `@williamthorsen/nmr/tests`, which a consuming repo calls from a test of its own. It fails the run on any test file that names no isolation tier or sits outside `__tests__`. Both passed silently before.

  Adds `testCollectionExclude` to `defineVitestConfig` and `defineRootVitestConfig`. The same array of directory names goes to the check's `exclude`, so one declaration scopes both the sweep and what Vitest collects.

- Keep doc comments in published declaration files (#762)

  Changes the behavior of `nmr-compile` so that it preserves doc comments in `.d.ts` files.

  TypeScript's `removeComments` governs a whole emit, so a TS configuration that kept comments out of JavaScript also kept them out of declaration files. `nmr-compile` now emits from two programs: The first produces the `.js` under the package's own `removeComments`, and the second produces the declarations with comments.

- Report workspaces whose Vitest config bypasses the projects model (#763)

  Adds a check to nmr's ReadyUp kit to report every workspace that holds a Vite config without a Vitest config beside it, and widens the existing content check from the repo root to every workspace, so a Vitest config that does not build on `defineVitestConfig` is reported. The purpose of these checks is to ensure that tier-selecting test commands are supported.

  Also fixes an issue where the `no package re-exports the ancestor Vitest config` check incorrectly advised a consumer to delete a config.

### ♻️ Refactoring

- Upgrade to readyup v0.34 and exclude a private repo root from kit checks (#759)

  Upgrades `readyup` to v0.34, whose `discoverWorkspaces` now reports the repo root. `release-kit`'s `npm-auto-publish` kit filters that root out unless it is publishable, so a private root is no longer listed in the kit's report.

  Test suites for release-kit's ReadyUp kits have been refactored to use the patterns documented in v0.34.

- Upgrade eslint-config-typescript and mark the discards it now reports (#761)

  Upgrades `@williamthorsen/eslint-config-typescript` to v12 and fixes violations surfaced by the newly added rule warning against a disposable that is discarded or that is bound to a declaration that never releases it.

### ⚙️ Tooling

- Restrict node:fs scaffolding in test code to the temporary tree's API (#756)

  Adds an ESLint restriction that flags the `node:fs` and `node:fs/promises` calls that scaffold a temporary directory in test code instead of using the `TempTree` API. Also migrates `v11y-check`'s `node:fs/promises` scaffolding to that API.

## 0.34.0 — 2026-08-24

### 🎉 Features

- 🚨 **Breaking:** Make defineVitestConfig resolve from source and isolate git (#749)

  Adds two defaults to `defineVitestConfig` and `defineRootVitestConfig` in `@williamthorsen/nmr/vitest`: Workspace packages resolve through their `source` export condition, so a suite runs without a prior build; and every project loads a setup file that keeps the developer's git configuration out of the subprocesses a test spawns.

  Each is individually opt-out-able through the new `isolateGit` and `resolveFromSource` options. A consumer's own `resolve.conditions` and `setupFiles` compose alongside what the factory supplies.

  Migration: A workspace package declaring a `source` condition is resolved from source in tests that previously read its `dist`. A package that publishes only its build output takes a bespoke condition name instead, as `@williamthorsen/nmr-core` does with `nmr-source`.

### 🐛 Bug fixes

- Stop nmr upgrade from timing out against a proxying registry (#744)

  Fixes an issue where `nmr upgrade` timed out intermittently against a registry that proxies npm. Every `upgrade` script now calls taze with a 30-second request timeout in place of the 5-second default; a different request timeout specified by the caller is respected.

  The ReadyUp kit bundled with nmr now warns when a taze config sets a value that the taze CLI will not use.

### ♻️ Refactoring

- Upgrade ReadyUp and the toolbelt packages, adopting their new APIs (#748)

  Upgrades `readyup` and the `@williamthorsen/toolbelt.*` packages to their latest versions.

  The `readFile` utility from `readyup` replaces hand-rolled code in `nmr`.

### 🧪 Tests

- Scaffold the nmr unit-tier trees through the tree's write API (#738)

  Scaffolds the temporary trees of the ten unit-tier suites in `packages/nmr/src/__tests__/` through the tree's own write API, retiring the `node:fs` import from them. A helper that writes now takes the tree and a tree-relative path; one that only composes arguments for a production function keeps its directory string.

- Scaffold and inspect the tool-tier fixtures through the tree's own API (#739)

  Converts the tool-tier test suites under `packages/nmr/src/__tests__/` from bare `node:fs` scaffolding to the `TempTree` API of `@williamthorsen/toolbelt.filesystem`. Fixture directories, writes, removals, and in-tree reads now go through the tree. Each `node:fs` call that remains is one the tree's API cannot express.

- Scaffold the commands and helpers fixtures through the tree's own API (#742)

  Converts the test suites under `packages/nmr/src/commands/__tests__/` and `packages/nmr/src/helpers/__tests__/` from bare `node:fs` scaffolding to the `TempTree` API of `@williamthorsen/toolbelt.filesystem`. Fixture directories, writes, removals, links, and in-tree reads now go through the tree. One `node:fs` call remains because its listing cannot be expressed by the tree's API.

## 0.33.0 — 2026-08-20

### 🎉 Features

- Drop the coverage step from the `check:strict` gate (#727)

  Drops the coverage step from `check:strict` in both the workspace and root registries: The gate now runs `test` rather than `test:coverage`.

  Coverage is still available as `nmr test:coverage`, run as a separate step.

### 🧪 Tests

- Convert the shared temporary directories to file-scoped fixtures (#722)

  Converts the `nmr` suites whose temporary directory outlives a single test to take it as a file-scoped Vitest fixture built on `createTempTree`. A builder that runs a program before handing over its tree (a TypeScript compile or a child Vitest run) holds the tree in a `DisposableStack` and transfers ownership only past the last statement that can throw, which is what removes the directory when that run fails.

  Separately, `build.tool.test.ts` takes its per-test directories as a test-scoped fixture and registers its console silencers through `disposeOnTestFinished`.

## 0.32.2 — 2026-08-19

### 🐛 Bug fixes

- Recognize nmr behind a quoted environment assignment (#709)

  Fixes an issue where a `package.json` entry that re-invoked its own command from behind an environment assignment whose quoted value carried a space (e.g., `"build": "NODE_OPTIONS=\"--a --b\" nmr build"`) caused nmr to hang.

- Restore upgrade reporting for repos that pin exact versions (#720)

  Fixes an issue where `nmr upgrade` reported nothing in a repo that pins dependencies to exact versions, however far behind the tree was. nmr's shared upgrade policy now supplies both settings the upgrade tool needs to report a bare pin -- locked dependencies are included, and the range searched is `minor` -- and the default `upgrade` scripts no longer pass `--include-locked`, which went inert on its own in taze 20.0.1. The policy reaches a repo only through a `taze.config.ts` calling `defineConfig`, and nmr's ReadyUp kit now warns when a repo carries none.

  The `minor` range mode applies to every dependency, not only a pinned one: a `~` range is searched as `^`, so minor upgrades are proposed for it, and a `packageMode` entry other than `minor` drops its dependency from a default pass rather than narrowing it.

### 🧪 Tests

- Register fixture-repo cleanup against the test that asks for it (#715)

  Routes nmr's `buildRepo` fixture helper through `createTempTree`, registering each fixture directory's removal against the test that requested it. The module-level accumulator and the exported `removeFixtureDirs` have been removed.

- Convert the per-test temporary directories to test-scoped fixtures (#719)

  Converts the suites that create a temporary directory in `nmr`, `nmr-core`, and `release-kit` to take it as a Vitest fixture built on `createTempTree`, or as a `using` declaration where the directory lives and dies inside one test body. Each suite loses the mutable binding and the `beforeEach`/`afterEach` pair that carried the directory's lifetime, and a directory is created only for the tests that name one.

  Separately, `nmr lint:strict` now fails on a stale `eslint-disable` directive.

- Register temporary-directory disposal where a helper holds the binding (#721)

  Routes the temporary directory through `createTempTree` in the `nmr` and `release-kit` test suites whose describe-local helpers hold its binding, and registers its removal with `disposeOnTestFinished`. The hand-written `afterEach` teardown goes; the binding stays, so no suite helper changes signature.

## 0.32.1 — 2026-08-18

### 🐛 Bug fixes

- Reject a `package.json` entry that chains steps onto a self-reference (#707)

  Fixes an issue where a `package.json` script re-invoking its own command alongside other steps (such as `"build": "nmr build && rdy compile"`) silently lost those steps: nmr discarded the entry and ran its own `build` in its place, so `rdy compile` never ran. nmr now refuses to run the entry and reports the error.

  Separately, fixes an issue where the same chain written the other way round (e.g., `"build": "rdy compile && nmr build"`) was honored as an ordinary override, causing infinite recursion.

## 0.32.0 — 2026-08-17

### 🎉 Features

- Carry the test-tier convention in the ambient rulebook (#689)

  Adds the test-tier convention to `nmr.md`, the ambient rulebook `codeassembly sync` deploys to each consuming repo's agent guidance. The guidance describes the `<subject>[.<aspect>].<tier>.test.ts` grammar and the four tiers, and warns that tests outside a `__tests__` directory are ignored. Tests not explicitly attributed to a tier are flagged by the ReadyUp kit bundled with `nmr`.

  The previous guidance is removed from `AGENTS.md`.

- Close each bin's output with a statement of what the run came to (#691)

  Five nmr bins now end their output with a statement of the run's final result, rather than on whichever line the run happened to reach last, so the last line a reader sees carries the finding.

  Separately, `ensure-prepublish-hooks` prints its whole report, including failure messages, on stdout, to avoid splitting the report across two streams. Failure still results in a non-zero exit code.

- Reject a dead pnpm field in nmr's ReadyUp kit (#692)

  nmr's ReadyUp kit now checks for the presence of a `pnpm` field in any `package.json`. pnpm 11 no longer reads any key from that field, but taze (the engine underlying `nmr upgrade`) reads `pnpm.overrides`. To avoid confusion, the kit flags this as an error.

### 🐛 Bug fixes

- Fold the running nmr's fingerprint into the build cache key (#693)

  Fixes the issue that build output could be stale, but not reported to be stale, after an edit to `nmr-compile`'s own emit logic. The build cache key now folds a fingerprint of the `nmr` running the build alongside the TypeScript version it already carried. Any build keys generated against an `nmr` with a different fingerprint will be invalidated. Repos consuming `nmr` get the same behavior.

### ♻️ Refactoring

- Adopt silenceConsole across the test suites (#677)

  Adopts `silenceConsole` from `@williamthorsen/toolbelt.vitest` to replace all hand-rolled `vi.spyOn(console, …)` spies and reduce boilerplate across the repo's test suites. The function returns a disposable, allowing the caller to use `using` to enable restoration of the console method at the end of the test scope.

- Adopt `isError` and consolidate errno narrowing on a shared helper (#697)

  Replaces every hand-rolled `instanceof Error` narrowing with `hasErrnoCode`, a new predicate exported from `@williamthorsen/nmr-core`, or with `isError` from `@williamthorsen/toolbelt.errors`. Previously the errno question was answered by inline copies alongside two private helpers of differing shape; both helpers are removed.

  Separately, `eslint.config.ts` now bans `instanceof Error` in every position rather than in a ternary alone.

### 🧪 Tests

- Replace the stdio spies with captureStdio (#678)

  Replaces every `process.stdout` and `process.stderr` spy in the test suites with `captureStdio` from `@williamthorsen/toolbelt.testing` and deletes the obviated local capture helpers. `captureStdio` buffers writes rather than recording calls, so assertions that previously used `toHaveBeenCalledWith` instead check `stderr`/`stdout` strings and their chunk arrays.

  Separately, `unicorn/no-nonstandard-builtin-properties` is off for test files. Its hand-maintained symbol table omits `Symbol.dispose`, which a suite-scoped capture calls to restore the streams, and `schema: []` offers no way to extend the table.

## 0.31.0 — 2026-08-13

### 🎉 Features

- Report a verdict for every command nmr runs (#659)

  `nmr` now reports one line for every command it runs, naming the scope, command, outcome, and timing. The lines print in every verbosity, which makes `-q` the low-noise way to run rather than a silent one: a passing quiet run reports its verdicts and nothing else, so a pass, a cached skip, and a no-op override stay distinguishable.

  Verdicts nest. A composite reports alongside every command it expands into, while a `:pre`/`:post` hook leaf and a `--filter`/`--recursive` delegate report nothing, since each is already covered by the levels around it.

- Resolve a run's verbosity from repo config and agent-harness detection (#660)

  A verbosity preference for `nmr` can now be set in the repo's root config, and `nmr` now automatically switches to quiet mode when it detects a known agent harness. This makes four ways to set verbosity: the `-q` option, the `NMR_COMMAND_VERBOSITY` env var, config, and harness detection, consulted in that order and defaulting to `full` when none applies.

- 🚨 **Breaking:** Refine nmr upgrade to support catalogs (#661)

  Adds `nmr-report-catalog`, which names the dependencies a package takes from a pnpm catalog, their `catalog:` specifiers, and the monorepo root a covering pass runs from. The workspace `upgrade` default chains it ahead of the upgrade tool, and `nmr report-catalog` runs it alone. Because taze reads `pnpm-workspace.yaml` only when that file sits at its working directory, changes to the catalog are not attempted during a package-scoped upgrade.

  Separately, `nmr-report-overrides` now reports the `overrides` block in `pnpm-workspace.yaml` and fails on a `pnpm.overrides` block in the root `package.json`. Both `upgrade` and `root:upgrade` run the reporter ahead of taze. This avoids a scenario where taze would meaninglessly write to the latter block.

  Migration: Move the block to `overrides` in `pnpm-workspace.yaml`, quoting each version, or run `pnpx codemod run pnpm-v10-to-v11`. `engines` declares `pnpm: ">=11"`.

- Replay an excerpt of the run a skip recalls (#667)

  `nmr` can now show the result of the previous check when a check is skipped. When `nmr` runs a command, it stores the result, keyed by presentation environment. The result is deemed to be the last blank-line-delimited block written by the command. If the command is run again in the same presentation environment, the stored result is shown, labeled "replayed:" for clarity.

- Assemble a skipped composite's replay from its constituents' excerpts (#668)

  A skipped composite now replays what its constituents recorded, with each line attributed to the scope and command that produced it:

  ```console
  ⏭️ nmr: check: passed 35.7s ago on this tree, saved ~10s — replayed: nmr: fmt:check: All matched files use Prettier code style!; nmr: test: Test Files 52 passed (52) Tests 987 passed (987)
  ```

  Nesting collapses. A constituent that fans out across packages contributes a line from every package that recorded one; a constituent that recorded nothing is omitted.

  A new reserved variable, `NMR_RUN_ID`, carries a run's identity to every process below it. Every recorded excerpt names the run that last certified it. A composite summary includes saved results with the same status as a fresh result.

- Print a retained run with nmr --log (#669)

  Adds `nmr --log <command>`, which prints what the last recorded pass wrote instead of running the command again. The whole transcript, up to a limit of 256 KiB, is now saved for any passing command that is not writing straight to a terminal; `--log` prints it under a header giving the instant, the duration, and the command chain that succeeded. The log of a composite is the sum of its constituents' excerpts. If a change disqualifies the recording, `--log` instead reports the change.

- Emit one JSON object per command for machine consumers (#672)

  Adds `--json` to `nmr`, allowing the caller to request output in JSON format instead of prose. An env var carries the choice of `json` or `text` down a spawned chain. When the output is JSON, the consumer receives one JSON object per command; each object carries the command, scope, and outcome, and a recalled pass carries the recorded excerpts.

  The mode implies quiet. Stdout carries the objects alone (with no prose verdict line and no override notice); a failing command still displays its output on stderr.

- 🚨 **Breaking:** Bind trailing arguments to every step that accepts them (#675)

  Routes an argument passed to an `nmr` command to every step of that command that accepts it, in place of the last step alone. Thus, `nmr test <file>` therefore runs only that file's tests; previously, the root suite ran in full alongside the narrowed package runs. Steps by default accept passed arguments, but can be configured to ignore them. If no step of a command accepts the argument, `nmr` names the command and runs nothing.

  A command carrying an argument also reads the check-result cache nowhere in its chain, so it no longer skips part of its work from a pass recorded for an unnarrowed run.

  Migration: Existing configs need no change, since a bare string element keeps its present meaning. Two behaviors differ: A composite passes its arguments to every accepting step rather than to its final step alone; and `nmr typecheck <file>` at the repo root is refused, where it previously checked that file under default compiler options at the root and hunted for it in every package.

### 🐛 Bug fixes

- Derive the shelled-nmr remedy from the declaration site and reject a malformed package.json (#658)

  Fixes a shelled-nmr warning that named `.config/nmr.config.ts` as the remedy for every step it reported, including the `package.json` entries that tier cannot resolve. The line now names the relevant declaration and remedy. Shelled nmr can now be detected not only in a step's first token but anywhere in the step.

  Separately, a configuration mistake now reports as a plain message rather than a Node stack trace.

### ♻️ Refactoring

- Adopt toolbelt.errors for error-message extraction and cause-chaining (#652)

  Improves error reporting by adopting standardized ways of capturing and describing errors across the codebase.

### ⚙️ Tooling

- Upgrade the lint config and clear its violations (#671)

  Upgrades `@williamthorsen/eslint-config-typescript`, along with other config packages, and clears lint violations surfaced by the newly activated rules.

## 0.30.0 — 2026-08-10

### 🎉 Features

- 🚨 **Breaking:** Resolve a command to a step list and reject a script element carrying shell syntax (#648)

  A script defined in `.config/nmr.config.ts` as a list of nmr commands now holds one command name per entry, optionally preceded by nmr's own flags. An entry that carries a quoted argument or any other shell syntax previously ran as written and is now rejected when the config loads. Before upgrading, a repo relying on such an entry should define that command as a script of its own and reference it by name from the list.

- 🚨 **Breaking:** Sequence a command's steps in nmr rather than in a shell (#649)

  When a run in quiet mode fails, it now shows only the output of the command that failed instead of everything the run had produced up to that point. Quiet mode can now be set for a whole shell session with the `NMR_COMMAND_VERBOSITY` environment variable, which accepts `full` or `quiet`; `-q` still takes precedence for a single run, and an unrecognized value is reported as an error. Interrupting a run now stops the commands that would have followed it.

  Breaking: a `devBin` entry that maps `nmr` itself no longer takes effect inside a command that runs other nmr commands. Mapping any other tool (the documented use) is unaffected.

- 🚨 **Breaking:** Express the seven root defaults as steps (#650)

  Running `nmr build`, `nmr test`, `nmr test:all`, `nmr test:coverage`, `nmr test:tool`, `nmr test:unit`, or `nmr typecheck` from a monorepo root now passes over a package that does not define the command, instead of failing there. The first run of `test`, `test:coverage`, `test:tool`, `test:unit`, or `typecheck` after the upgrade no longer skips as already passed; skipping resumes on the run after.

  `nmr` now also warns when a command it is about to run invokes `nmr` again through a shell, where a failure reports the whole inner run's output instead of the failing command's. The warning catches only a direct `nmr` call, so a command that reaches `nmr` through another program goes unreported.

## 0.29.0 — 2026-08-09

### 🎉 Features

- Run the dependency audit before ci in prepush (#634)

  `nmr prepush` now runs the dependency audit before the code-quality gate, so a vulnerability surfaces within seconds instead of after a full build-and-check pass. A failing audit now stops the run before the code-quality gate.

### 🐛 Bug fixes

- Record no pass when build output changes while a check runs (#641)

  Fixes an issue where rebuilding a package in one terminal while `nmr prepush` ran in another could mark a check as passed against build output it never saw, causing a later run on the same tree to skip it. Because `nmr ci` builds during its own run, it now needs one further run before it can skip, and that run repeats only the build phase.

- Stream command output so a large writer is no longer killed (#642)

  Fixes an issue where a command that wrote more than a mebibyte of output under `nmr -q` was cut off and reported as failed while the tool itself kept running. When `nmr`'s output is piped or redirected to a file, a command's output now appears as it is produced instead of arriving all at once when the command exits. Piping `nmr` into a reader that stops early, such as `head`, now ends the run cleanly. A command killed by a signal now reports 128 plus the signal number rather than a bare 1.

## 0.28.0 — 2026-08-08

### 🎉 Features

- Stop reporting absence checks that pass (#610)

  A ReadyUp check that checks for a problem (such as an unwanted file or a stale config reference) now stays silent unless it finds one. The `nmr`, `release-kit`, and `v11y-check` ReadyUp kits previously printed a line for every such check on every run.

- Stop reporting non-issues in the readyup kits (#613)

  Modifies the `release-kit` and `nmr` ReadyUp kits to avoid verbose reporting when an initial check determines that further checks aren't applicable. In particular, a repo with no `release-kit` config file gets a single line in the `release-kit` report, and a repo that publishes nothing now bypasses the `npm-auto-publish` kit instead of failing it.

### 🐛 Bug fixes

- Fix prototype-key hole in nmr command runner (#624)

  Fixes an issue where running `nmr` with a command named for property inherited by JS objects (such as `constructor` or `toString`) failed with a type error instead of reporting the command as unknown.

  Also fixes all lint issues in the `nmr` package.

- Publish build output atomically so a failed build cannot destroy it (#631)

  Fixes an issue where a failed or still-running `nmr build` could leave a package with no build output at all, breaking the tooling that a subsequent build depends on. Separately, the error reported when a package's build output is missing now names the correct recovery command.

### ♻️ Refactoring

- Retire the deferred-lint-rule list and enforce its rules (#628)

  Errors that `release-kit` rethrows now carry the original error as their cause, previously available only as message text. Help text and documentation for `--with-release-notes` now use the same placeholder notation for its output paths.

  All violations of lint rules in the `release-kit` project are now cleared, and the list of deferred lint rules is retired, restoring associated rules to a max severity of "error" during strict linting.

### ⚙️ Tooling

- Migrate to the shared tsconfig baseline (#626)

  Adopts `@williamthorsen/tsconfig` as the standard TypeScript configuration for this repo, replacing the previous hand-maintained copy. Reading a property through an index signature now requires bracket notation or a type that declares the property. The `nmr` build now fails on a base TypeScript config it cannot resolve, rather than building without it, and it now resolves a base config named by package name alone.

## 0.27.0 — 2026-08-05

### 🎉 Features

- 🚨 **Breaking:** Lint the whole tree in one process, not one per workspace (#604)

  Linting of the entire monorepo now runs in a single call instead of via fan-out, eliminating the startup time previously incurred for each workspace.

  As a result of the change, `lint:pre` and `lint:post` hooks in a workspace are no longer triggered by a run from the monorepo root. They continue to be triggered by a run targeting the workspace itself.

### 🧪 Tests

- Clear the deferred vitest test rules and enforce them (#602)

  Fixes a subset of previously deferred lint violations and restores the severity of Vitest lint rules to "error" for purposes strict linting.

## 0.26.1 — 2026-08-04

### 🐛 Bug fixes

- Prevent unnecessary bypasses of the check-result cache (#595)

  Agents are now instructed never to bypass `nmr`'s check-result cache in order to force a supposed "real" run of a check that has already passed in the working tree, unless the output (and not only the result) of that check is needed. The "Re-run with --no-cache" hint, which was being treated as an instruction by agents, has been removed.

## 0.26.0 — 2026-08-04

### 🎉 Features

- Publish each package's readyup kit from the package that owns it (#590)

  ReadyUp checks for `@williamthorsen/nmr`, `@williamthorsen/release-kit`, and `v11y-check` are now run directly against these packages, guaranteeing a version-appropriate check, instead of against their host repo. The package must be installed as a direct dependency in the calling repo.

  The syntax is `rdy run --from npm:<package>`. `rdy run --packages` runs the kits contained in any packages listed in `.config/readyup.config.ts`. Requires `readyup` 0.23 or later.

- Ship agent guidance as CodeAssembly package content (#591)

  A repo that consumes `@williamthorsen/nmr` can now adopt `nmr`'s agent guidance by naming the package in `.agents/codeassembly.yaml`. `codeassembly sync` then delivers that guidance to every agent harness. Guidance updates are thus delivered directly with each package, eliminating the need to keep a guidance file in sync. The `nmr sync-agent-files` command has been removed.

  Migration guide: Delete `.agents/nmr/`, remove the `@nmr/AGENTS.md` include line, and remove all uses of `check:agent-files`.

## 0.25.0 — 2026-08-03

### 🎉 Features

- Mark a cache hit's time saving with a speed-boost icon (#579)

  Time saved due to `nmr`'s cache feature now earns a rocket emoji in the terminal output.
  Savings less than 1 second are no longer reported.

- Publish nmr's readyup kit with a test-tier conformance check (#580)

  `@williamthorsen/nmr` is now bundled with ReadyUp kits that check a repo's alignment with `nmr` requirements and recommendations.

  `rdy run --from npm:@williamthorsen/nmr` checks the repo against the kits without prior configuration. Once the package is added to the ReadyUp config, `rdy run --packages` will run the `nmr` kits along with those of any other registered package. Requires `readyup` v0.23 or greater.

## 0.24.0 — 2026-08-03

### 🎉 Features

- 🚨 **Breaking:** Retire integration label in favor of an isolation-tier test ladder (#552)

  Test groups are now named for what a test reaches rather than for how much of the codebase it covers: `integration` and `app` are replaced by `unit`, `tool`, `localhost`, and `remote`.

  `nmr test` now runs the `tool` group alongside `unit`, so the first run after upgrading may fail on tests that nothing was running before. Upgrading means renaming `*.int.test.ts` files onto the new groups and replacing `nmr test:integration` with `nmr test:tool`.

- Let Vitest config files compose shared options and target one tier (#556)

  Vitest settings can now be shared across config files: A repo declares its common options once, and a workspace can use its own config file to supplement or override them. Shared setup files run first. An option can now target a single test tier instead of all tiers, allowing per-tier timeouts to be set.

- 🚨 **Breaking:** Redefine ci as the code-quality gate and add prepush (#559)

  Repurposes the `nmr ci` command to align with its name; it now runs the code-quality checks that run in CI: `nmr build && nmr check:strict`. A new `nmr prepush` command, combining code-quality checks and the security audit, takes the place of `nmr ci`. nmr's readyup kit is adjusted to account for the changes.

- Exclude test scaffolding from builds and make the ignore set configurable (#564)

  The `build` command now ignores the directories typically used for test scaffolding, such as fixtures and mocks. The exclusion includes `test-utils/`, which is treated as the conventional name for a directory containing test utilities. A package can declare additional ignore patterns in its config file.

- Skip checks that already passed on the current working tree (#568)

  `nmr` commands now skip any pure check (types, lint, tests) that has already passed on an unchanged working tree; instead, success is reported immediately. `nmr --no-cache` causes the cache check to be skipped, and `NMR_NO_CACHE=1` does the same for a whole shell. Cached results are cleared by `nmr clean`. Which commands can skip is configurable at the monorepo root, and `NMR_DEBUG=1` reports why a run didn't skip.

### 🪦 Removed

- 🚨 **Breaking:** Remove the package root entry in favor of /config (#569)

  `defineConfig` is now imported from `@williamthorsen/nmr/config` instead of the bare package.

### 🐛 Bug fixes

- Stop excluding _.types.ts and mock_ files from coverage

  Coverage now measures modules matching `*.types.ts` and `mock*`, which the shared Vitest config previously excluded. A type module that grows a type guard, or a mock helper placed in `test-utils/` where helpers are meant to stay measured, was left out of the report with nothing saying so, and because exclusions concatenate, no consuming repo could add it back.

  What remains excluded names something that cannot hold runtime code by construction: a directory of test scaffolding, a barrel, and a declaration file. A pattern resting on a filename alone suppresses nothing it could claim to, since a file with no executable statements is absent from the report either way.

  A repo enforcing its own coverage thresholds may see a lower figure once these files enter the denominator.

### ♻️ Refactoring

- Share nmr-core's cache primitives with nmr-compile (#570)

  A package's writes to the build cache are now hidden from other packages until the write is complete. A failing build now reports its error in the same form as every other `nmr` command. `tsx` is no longer required to build a package.

## 0.23.0 — 2026-08-01

### 🎉 Features

- 🚨 **Breaking:** Close the shared Vitest config's path gaps (#546)

  Defines common patterns to determine which files are considered test files and which are subject to code coverage. Consumers that use `defineVitestConfig` benefit from these patterns if they follow naming and file-location conventions.

### 🐛 Bug fixes

- Stop rewriting documented commands inside Markdown fences (#549)

  Fixes an issue where `nmr fmt` silently rewrote shell commands documented in a repository's Markdown, leaving usage examples that no longer ran as written. Code fences inside Markdown are now left untouched in any language, withdrawing the fence formatting added in 0.22.0. Fence formatting can be reenabled on selected paths via the config.

## 0.22.0 — 2026-07-31

### 🎉 Features

- 🚨 **Breaking:** Ship shell and Dockerfile formatting from a shared nmr Prettier config (#540)

  Adds the ability to format shell scripts and Dockerfiles to `nmr fmt`. `@williamthorsen/nmr` now provides a common config for consistency across projects. Shell fences inside Markdown are now formatted as well. These features are absorbed into `nmr fmt` and `nmr fmt:check`; the `fmt:sh` and `fmt:all` commands are removed.

- 🚨 **Breaking:** Require the monorepo root in the shared root Vitest config (#541)

  Modifies the testing configuration so that root-level test runs always cover the repository containing the configuration. The shared root Vitest config now requires a `monorepoRoot` option specifying an absolute path to the repository's own root, in place of the optional search origin. A repository nested inside a parent monorepo fails instead of resolving silently to its parent.

## 0.21.0 — 2026-07-30

### 🎉 Features

- Add a shared Vitest config export (#526)

  Adds a shared Vitest configuration that can be used by repos that use `nmr`. Tests are now sorted into three named categories (unit, integration, and app), so that a run can target or skip a single category. A second variant excludes all tests in workspace packages, allowing the monorepo's own root-level tests to be run separately.

- 🚨 **Breaking:** Retire the Vitest config-file variants from nmr's defaults (#533)

  Packages no longer need extra Vitest config files to keep integration tests out of the default test run. Integration tests are now recognized by an `*.int.test.ts` suffix, and every package gets the same five commands: `test`, `test:all`, `test:coverage`, `test:integration`, and `test:watch`. The same five commands work from the monorepo root. A Vitest run that collects no test files now passes instead of failing. Consumers should migrate the Vitest config when upgrading.

### 🐛 Bug fixes

- Apply ignore rules from any working directory when formatting (#535)

  Fixes an issue where `nmr fmt` and `nmr fmt:check` did not apply a pattern from a package's `.gitignore` or `.prettierignore` when the run started at the monorepo root, so files a package excluded could fail a root-level format gate while passing a package-scoped one. Untracked files that git does not ignore are now formatted alongside tracked ones, and a run started outside a git repository now fails instead of reporting a clean pass over nothing. Trailing arguments to `nmr fmt` are git pathspecs rather than Prettier flags; one that matches no formattable file now fails instead of exiting silently.

### ⚙️ Tooling

- Adopt Vitest projects in this repo (#530)

  Simplifies test configurations so that every package exposes the same test commands, which select suites by test category: `nmr test` runs everything except integration tests, `nmr test:integration` runs only those, and `nmr test:all` runs both. `nmr test:integration` now succeeds in a package that has no integration tests instead of failing. release-kit's drift checks can now be run on their own, apart from unit and integration tests. A fresh clone now has to run `pnpm run bootstrap` before any test run, not just before `nmr` commands.

## 0.20.0 — 2026-07-28

### 🎉 Features

- 🚨 **Breaking:** Migrate to code-quality workflow v7 and retire the version-restatement apparatus (#503)

  Migrates the code-quality workflow from `@v6` to `@v7`, which no longer requires the Node version to be specified in two places, and removes the test that previously checked for drift. Also removed are the `nmr sync-pnpm-version` command and drift-related exported functions.

  Taking this upgrade requires migrating the repo's code-quality workflow to `@v7` and deleting its version-alignment test in the same change.

- 🚨 **Breaking:** Resolve workspace packages with pnpm's pattern semantics (#509)

  nmr now excludes packages matched by a `!`-prefixed pattern in `pnpm-workspace.yaml` from the packages it operates on. Deep globs such as `packages/**` now match nested packages, and running a command from inside a nested package targets that package rather than an enclosing ancestor. Exclusion patterns must be quoted to take effect. nmr now requires Node.js 24.16 or later.

- Report pnpm overrides when reviewing dependencies (#514)

  From the monorepo root, `nmr upgrade` now reports the repo's active pnpm dependency overrides alongside the upgrade report, making clear when a pin is the reason an expected upgrade is absent. Repos that wired up this override report themselves with an install-time hook can now remove that hook.

- Trim the bundled agent guidance to a cheatsheet (#516)

  Trims the agent guidance nmr installs into consuming repos from 100 lines to 10. Only the bare essentials are now injected into context. Agents are instructed to consult the package's README for details.

### 🐛 Bug fixes

- Anchor script execution to the resolved registry's directory (#517)

  Fixes an issue where `nmr --workspace-root`, run from inside a workspace package, either failed outright or silently acted on the wrong directory. Commands now behave the same regardless of the directory they are invoked from: root commands act on the monorepo root, and package commands on their own package. Two changes for existing users: `nmr --workspace-root clean` now sweeps every workspace package instead of cleaning only the current one, and relative paths passed to a script now resolve against the directory the script runs in.

## 0.19.0 — 2026-07-22

### 🎉 Features

- 🚨 **Breaking:** Replace the dependency commands with nmr upgrade (#491)

  Adds `nmr upgrade` as an ergonomic wrapper for displaying and applying available dependency upgrades. Taze is used under the hood to set a soaking period (customizable, defaulting to 7 days) before recently published dependencies will be accepted.

  Removes the four dependency commands it replaces: `outdated`, `outdated:latest`, `update`, and `update:latest`.

### 📦 Dependencies

- Upgrade dependencies and align the Node support policy (#483)

  All four published packages (`nmr`, `nmr-core`, `release-kit`, and `v11y-check`) now require Node.js 24 or later, up from Node 18.17. Separately, `nmr-compile` now rebuilds when the TypeScript version changes.

## 0.18.2 — 2026-07-18

### 🐛 Bug fixes

- Fail the build on aliased imports that escape the package source (#475)

  Fixes an issue where an aliased import that resolved to a target outside the package's own source (one Node could not reach at runtime) passed the build but produced published output that crashed with a module-not-found error. `nmr-compile` now fails the build and reports the unresolvable import and where it pointed, so the problem surfaces before publish instead of in production. Aliased imports that resolve outside the source but remain genuinely external and runnable continue to build and ship unchanged.

## 0.18.1 — 2026-07-14

### 🐛 Bug fixes

- Fix the stale build cache, the unshipped rimraf dependency, and the aborting root clean (#471)

  Fixes the following issues:
  - `nmr clean` failed with a missing-command error in any project that had not separately installed `rimraf`, a tool nmr neither declared nor shipped.
  - If run from a monorepo root, `nmr clean` cleaned one package and then aborted, leaving every other package untouched. Only repos that build nmr from source were affected.
  - If run from a monorepo root, `nmr clean` ignored a package's own `clean` script.
  - `nmr compile` skipped the build for a package whose output had been deleted, because the sources were unchanged. The package compiled to an empty output directory with no error and no warning, and could be published as an empty tarball.
  - `nmr compile` reported missing output and recompiled on every run for a package whose sources emit nothing, such as a source tree of only declaration files.

## 0.18.0 — 2026-07-13

### 🐛 Bug fixes

- Remove the attw check and the nmr-attw binary (#469)

  Fixes an issue where a repo using nmr's default scripts could not pass `check:strict` without installing `@arethetypeswrong/cli` and conforming to inflexible requirements of questionable value. The `attw` check is removed.

## 0.17.2 — 2026-07-06

### 🐛 Bug fixes

- Replace the raw attw script with an nmr-attw wrapper (#461)

  Fixes the issues that made the default `nmr attw` check fail across monorepos:

  - packages with no published API failed with a false `NoResolution` error;
  - the check left stray `.tgz` files in the working tree ;
  - output was a verbose per-package table; now output is terse on success, full diagnostics only on failure (`--verbose` to force full output);
  - a missing `@arethetypeswrong/cli` gave a raw command-not-found; now a hint is given.

## 0.17.1 — 2026-07-01

### 🐛 Bug fixes

- Relocate nmr-compile's build cache out of the published dist (#457)

  Fixes an issue where packages built with nmr-compile shipped an internal build-cache file inside their published npm tarball.

## 0.17.0 — 2026-07-01

### 🎉 Features

- 🚨 **Breaking:** Rebuild nmr-compile on a unified tsc emit (#455)

  `nmr` now compiles a package's JavaScript and type declarations in one step, rewriting every import form — static, re-export, dynamic `import()`, bare side-effect, and tsconfig `paths` aliases — to runnable `.js` in both outputs. It now requires TypeScript 5.7 or newer as a peer dependency.

## 0.16.0 — 2026-06-30

### 🎉 Features

- 🚨 **Breaking:** Rewrite parseArgs on node:util with typed error and exit API (#428)

  `@williamthorsen/nmr-core` gains a typed error API for CLI parsing: `parseArgsOrExit` (parse, or print a usage error and exit) and `ParseError` replace the per-command boilerplate, so every bundled CLI reports a flag mistake the same way. Breaking: the `translateParseError` export is removed and `parseArgs` now throws `ParseError`.

- 🚨 **Breaking:** Auto-activate integration test variant from config presence (#448)

  A package can now separate its integration tests from its standalone suite simply by including a `vitest.integration.config.ts` (alongside a `vitest.standalone.config.ts`). The `--int-test` flag that previously enabled this is removed — that config-file pairing is now the only way to activate the separation. In such a package, `test` and `test:coverage` run only the standalone suite and skip integration tests, while a new `test:all` runs both suites together. The separation now holds even when tests run across every package at once, so a full-workspace `test` run still keeps integration tests out of the default suite. Packages that previously hand-copied these test scripts no longer need to.

### 🐛 Bug fixes

- Prevent CLI output truncation when piped before exit (#446)

  Fixes an issue where large output from the `nmr` and `v11y` commands could be truncated when captured through a pipe — for example, by a CI job.

### ♻️ Refactoring

- Migrate ensure-prepublish-hooks to nmr-core parseArgs (#429)

  `ensure-prepublish-hooks` now parses its arguments with `nmr-core`'s shared `parseArgs` utilities instead of a hand-rolled `for`/`switch` loop, removing the last bespoke argument parser in the `nmr` package. Every `nmr` CLI now shares one argument-parsing and error-reporting path; as a side effect, stray positional arguments are ignored rather than rejected, while unknown flags and a missing `--command` value still exit non-zero.

- Route all CLI error reporting through a single stderr helper (#437)

  Consolidates error reporting across the CLIs so every command reports errors the same way, and guards against the previous inconsistency returning. The error messages and exit codes users see are unchanged.

- Normalize CLI error wording to the canonical Error: format (#439)

  Single-line error messages from the `nmr` and `release-kit` commands now print in one uniform shape, so the same class of failure reads the same way no matter which command produced it. Previously the wording varied, which made error output harder to scan and harder for scripts to match against. Richer output, such as multi-line validation reports and the stack trace from an unexpected crash, is unchanged.

## 0.15.0 — 2026-06-27

### 🎉 Features

- Centralize the per-package build as an nmr-compile bin (#419)

  Introduces `nmr-compile`, a single command shipped with `@williamthorsen/nmr` that compiles each workspace package and now backs the default build. Consuming repos can delete their own per-package build script and pick up future build fixes just by upgrading nmr. Repeated builds with unchanged source now reliably skip recompiling instead of occasionally rebuilding for no reason, and import aliases now resolve correctly in symlinked checkouts.

### 🐛 Bug fixes

- Write the build cache only after a successful compile (#421)

  Fixes an issue where a package compile that failed partway — from a crash, disk error, or transient build failure — could leave stale or incomplete output that the next build skipped over as unchanged. The next build now retries the failed compile, so recovering no longer requires a manual `nmr clean` or a source-file edit.

- Stop sync-agent-files --check failing on version-only bumps (#424)

  Fixes an issue where `nmr sync-agent-files --check` failed after an nmr upgrade even when the managed agent guidance was identical. The check now passes whenever the guidance content is current, even if the version has changed.

- Remove inapplicable bootstrap fallback from agent guidance (#425)

  Fixes an issue where the agent guidance bundled with nmr told coding agents to run a recovery step that exists only in the nmr repo itself.

### ♻️ Refactoring

- Sync workflow pnpm version via yaml document API (#412)

  `nmr sync-pnpm-version` now updates every pinned pnpm version in a workflow, not just the first, so a workflow that pins the version across multiple jobs no longer leaves later jobs on a stale version.

## 0.14.2 — 2026-06-16

### ♻️ Refactoring

- Migrate from js-yaml to yaml (#410)

  Replaces the `js-yaml` dependency with the `yaml` package for all YAML reading and writing, with no change to behavior or generated output. The swap trims the dependency footprint by dropping a direct dependency and its companion type-definitions package, since `yaml` ships its own types.

## 0.14.1 — 2026-05-19

### ♻️ Refactoring

- Restructure tests and align core package directory with package name (#405)

  Tests in every package are now typechecked alongside the code they cover, so type breakage in tests fails the build instead of slipping through. The `core` package's workspace directory is renamed to match its package name, so `nmr -F nmr-core ...` and `pnpm --filter nmr-core ...` now resolve where they previously failed.

### 🧪 Tests

- Eliminate subprocess-startup flakiness in CLI tests (#403)

  The `nmr` CLI test suite now passes deterministically and no longer leaks subprocess output to the vitest terminal. Production CLI behavior — output streaming, exit codes, error messages — is unchanged.

## 0.14.0 — 2026-05-10

### ⚙️ Tooling

- Expose workspace bins from any subdir and simplify nmr docs (#374)

  Workspace bins like `nmr`, `release-kit`, and `audit-deps` are now invocable from any subdirectory under the monorepo when direnv is active, replacing the previous root-only `.envrc` recipe. The nmr usage docs are restructured to present two unambiguous resolution modes — direnv (recommended) and `pnpm exec nmr` — and the repo's README now documents direnv as the recommended setup.

## 0.13.0 — 2026-05-04

### 🎉 Features

- Add :pre and :post hook conventions to nmr commands (#339)

  Adds `:pre` and `:post` hook conventions to nmr's command runner. Consumers can declare `X:pre` or `X:post` scripts that nmr runs automatically before and after script `X`. The hook commands are optional and ignored if missing. Hooks fire even when the main command is overridden, and direct invocations like `nmr build:pre` are treated as leaf operations rather than recursively cascading into `build:pre:pre` lookups.

- Filter `nmr --help` to nmr commands (#348)

  Restricts `nmr --help` to nmr commands only. Hook scripts (names ending in `:pre` or `:post`) are now hidden from help, and generic `package.json` lifecycle scripts (`prepare`, `postinstall`, `bootstrap`) no longer appear. When a `package.json` script overrides a built-in nmr command, the override is shown inline alongside the command name with a `*` marker, and a single `* Overridden by package.json` footnote is appended whenever any marker is rendered. Help also now reflects the active resolution context: invoked from a subpackage you see workspace-level overrides; invoked from the repo root (or with `-w`) you see root-level overrides.

### 🐛 Bug fixes

- Honor -w flag in composite-script step subprocesses (#346)

  Fixes an issue where invoking `nmr -w <command>` from inside a workspace package, with `<command>` resolving to a composite (multi-step) script defined at the root level, failed with `Unknown command` for any step that was reachable only via the root script registry. The `-w` flag is now propagated to every step's subprocess invocation, so composite scripts run end-to-end regardless of which directory they are invoked from.

### ♻️ Refactoring

- Read package version at runtime via shared helper (#338)

  Fixes an issue where running `audit-deps`, `nmr`, or `release-kit` from the locally built `dist/esm/` after a `git pull` could report a stale version. Each CLI now reads its version directly from its `package.json` at startup, so version reads stay in sync with the installed source without requiring a fresh `pnpm install` or rebuild.

## 0.12.0 — 2026-04-23

### 🎉 Features

- Add agent-facing AGENTS.md and sync-agent-files command (#263)

  Ships nmr-owned agent guidance alongside the `@williamthorsen/nmr` package so consuming repos stop hand-maintaining their own copies of the runner's invocation rules. A new `nmr sync-agent-files` command pulls that guidance into `.agents/nmr/AGENTS.md` in the consuming repo, stamped with the installed nmr version; a companion `--check` variant verifies the stamp against the installed version on every root `check:strict` run. Drift between installed nmr and the committed guidance now fails the quality gate automatically with a single actionable fix message, without per-consumer wiring.

- Rename check:fixable script to fix:check (#266)

  Renames the `check:fixable` convenience script to `fix:check` in both the workspace and root default script registries. The new name mirrors the existing `fmt` / `fmt:check` pattern, aligning the read-only variant with its mutating counterpart (`fix`) so the command's read-only semantics are recognizable from the name alone.

  The script's expansion (`fmt:check`, `lint:check`) is unchanged.

## 0.11.0 — 2026-04-17

### 🎉 Features

- Add `check:fixable` convenience script (#223)

  Adds a new `check:fixable` convenience script to the `nmr` default script registries, providing a read-only partner to the existing `fix` script. Running `nmr check:fixable` expands to `nmr fmt:check && nmr lint:check`, verifying that a tree is clean of auto-fixable violations without modifying any files.

- Show package name in override-script messages (#226)

  Adds the package directory name as a prefix to override-script log messages, making it easy to identify which package each message belongs to when running commands across multiple workspaces. Also introduces a distinct no-op (`:`) condition that logs a skip message and exits cleanly, separate from the existing empty-string case.

- Migrate nmr audit scripts to use audit-deps (#234)

  Migrates nmr's built-in `audit:dev` and `audit:prod` root scripts from calling `audit-ci` directly via `pnpm dlx` to delegating to the `audit-deps` CLI wrapper via `pnpm exec audit-deps`. Removes the now-obsolete `.config/audit-ci/` directory and its JSON5 config files, since `audit-deps` uses its own config at `.config/audit-deps.config.json`. Updates the nmr readyup kit to check for `@williamthorsen/audit-deps` installation instead of checking for legacy audit-ci directory placement.

## 0.10.0 — 2026-04-16

### 🎉 Features

- Decouple audit from CI quality gate and add audit workflow (#210)

  Dependency audit is now decoupled from the CI quality gate so that transient upstream CVEs no longer block the merging of unrelated code changes. Audit now runs in a dedicated workflow with non-blocking PR integration (acknowledgment checkbox) and a daily scheduled run that tracks results in a standing GitHub issue. A readyup kit is available to validate the new setup in consuming repos.

## 0.9.2 — 2026-04-15

### ⚙️ Tooling

- Enable automated publication to npm (#187)

  Prepares the repository for reliable tag-triggered npm publishing by adding missing package metadata, standardizing licensing, and introducing a readyup kit that validates publish readiness across all packages.

## 0.9.0 — 2026-04-04

### 🎉 Features

- Add --version flag to nmr and release-kit (#143)

  Adds `--version` / `-V` support to the `nmr` and `release-kit` CLIs, matching the existing `preflight` behavior. Moves the build-time version generation script to the shared `config/` directory so all three packages use a single `generateVersion.ts`.

- Add devBin config for source-repo binary substitution (#146)

  Adds a `devBin` config field to nmr that maps binary names to replacement commands, with relative paths resolved from the monorepo root. Documents `":"` as the recommended way to disable a script when `eslint-plugin-package-json/valid-scripts` forbids empty strings.

- Detect and report missing build output in bin wrappers (#152)

  Adds try/catch with `ERR_MODULE_NOT_FOUND` detection to all six bin wrappers across `nmr`, `preflight`, and `release-kit`. Previously, five of the six wrappers used bare `import()` calls that produced cryptic unhandled rejections when `dist/` was missing, and `preflight`'s existing try/catch gave no actionable guidance.

### 📚 Documentation

- Refine README to match preflight documentation standard (#137)

  Rewrites the nmr README to match the documentation standard established by the preflight README (#114). Restructures content to follow the cross-package convention (header → installation → quick start → concepts → CLI reference), adds comprehensive reference tables for CLI flags, `defineConfig` fields, and all built-in script registries (workspace and root), and introduces visual aids for context-aware resolution and three-tier override precedence.

## 0.5.0 — 2026-03-31

### 🎉 Features

- Add `fix` script to workspace and root registries (#106)

  Adds a `fix` composite script to both the workspace and root script registries in nmr. The script runs `lint` then `fmt` in sequence, providing a single command to auto-fix linting and formatting issues.

## 0.4.0 — 2026-03-30

### 🎉 Features

- Add default root scripts and split registry module (#96)

  Adds four new default root scripts to nmr (`fmt:sh`, `fmt:all`, `clean`, `root:check`) and split the monolithic `registries.ts` into a data-only `default-scripts.ts` and a composition-logic `resolve-scripts.ts`.

  Also fixes the `ci` and `check:strict` script ordering to run build before strict checks, and corrects stale test assertions.

## 0.3.0 — 2026-03-29

### 🎉 Features

- Resolve package.json scripts at root level and skip in recursive mode (#95)

  Pass `monorepoRoot` as `packageDir` when `isRoot` is true so root `package.json` scripts participate in the same override tier that workspace packages already use.

  Set `NMR_RUN_IF_PRESENT=1` in the `-R` codepath so child processes that can't resolve a command exit 0 (skip) instead of exit 1.

  Only log "Using override script" when the `package.json` script actually overrides a registry command.

  Remove the self-referencing `ci: "nmr ci"` script from root `package.json` and update the CI workflow to use `code-quality-pnpm-workflow.yaml@v4` with an explicit `check-command`.

## 0.2.0 — 2026-03-28

### 🎉 Features

- Add ensure-prepublish-hooks binary (#75)

  New binary that checks whether all publishable (non-private) workspace packages have a `prepublishOnly` script, and optionally adds one.

  - Check mode (default): reports each non-private package's `prepublishOnly` status, exits non-zero if any are missing.
  - Fix mode (`--fix`): inserts `prepublishOnly` into packages that lack it. Supports `--dry-run`.
  - Custom command (`--command`): overrides the default hook value (`npm run build`).

  Also adds `private` field extraction to the shared `PackageJson` interface.

### 📚 Documentation

- Document utility binaries

  Add README sections for the package's additional commands. Subcommands (report-overrides, sync-pnpm-version) are documented under "Additional subcommands" with nmr invocation syntax. The standalone ensure-prepublish-hooks utility is documented separately under "Standalone utilities".

  Also fix the executable bit on bin/ensure-prepublish-hooks.js to match the other bin entries.

## 0.1.1 — 2026-03-28

### 🎉 Features

- Extract nmr CLI from core package (#61)

  Extracts all nmr CLI code from `packages/core` into a new `packages/nmr` package (`@williamthorsen/nmr`). Core is reduced to an empty shared-library shell ready for cross-cutting utilities. All internal references are rewired and the full build/test pipeline passes.

  Scopes: core, nmr

### ♻️ Refactoring

- Extract helpers to reduce duplication in config and consistency modules (#62)

  Extracts two small helpers to consolidate structurally duplicated code in the nmr package. A new `getStringFromYamlFile` helper in `tests/helpers/` replaces the repeated YAML-read-parse-extract pattern in `consistency.ts`, and a private `validateScriptField` helper in `config.ts` replaces the duplicated script-record validation blocks.

<!-- Generated by release-kit. Do not edit this file. Use .meta/changelog-overrides.json to override entries. -->
