@williamthorsen/nmr 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,41 @@
1
+ ---
2
+ source: '@williamthorsen/nmr@0.0.0-source'
3
+ ---
4
+
5
+ # nmr: agent guidance
6
+
7
+ This file is managed by `@williamthorsen/nmr`. Do not edit — re-run `pnpm exec nmr sync-agent-files` after an nmr upgrade to refresh it.
8
+
9
+ ## Discover scripts by running nmr
10
+
11
+ Run `nmr` with no command (from the monorepo root or any workspace package) to list every available script, including composite expansions and resolved shell commands. Check this before guessing a script name from another repo — the registry is authoritative.
12
+
13
+ ## Invocation rules
14
+
15
+ - Use `nmr <command>` for anything nmr provides. Do not use `pnpm run <command>`.
16
+ - Use `pnpm exec nmr`, not `npx nmr`. Inside git worktrees, `npx` can resolve a different nmr binary from outside the working tree.
17
+ - If `nmr` itself fails to run (fresh clone, missing build output), run `pnpm run bootstrap` from the repo root first.
18
+
19
+ ## Root vs. workspace context
20
+
21
+ nmr walks up to find `pnpm-workspace.yaml`, then decides which registry to use based on whether your cwd is inside a workspace package. The same command name (e.g. `build`, `test`, `check:strict`) often exists in both registries with different behavior — the root version typically delegates across all workspaces. Use `-w` to force the root registry from inside a package dir, and `-F <pkg>` to run a single package's script from anywhere.
22
+
23
+ ## Composite scripts
24
+
25
+ A script value shown in `nmr` output as `[a, b, c]` is a composite: it runs `nmr a && nmr b && nmr c`, stopping at the first failure. Composites can reference other composites.
26
+
27
+ ## `root:*` siblings
28
+
29
+ Some root scripts (e.g. `lint`, `typecheck`, `test`) expand to `nmr root:X && pnpm --recursive exec nmr X`. The `root:X` variant runs only against root-level files; the plain name runs everywhere. Use `root:X` directly when you want to isolate a failure to the root code.
30
+
31
+ ## Override behaviors
32
+
33
+ In `.config/nmr.config.ts` or a package's `package.json`, override values have special semantics:
34
+
35
+ - `""` (empty string) — skip the script with a "Skipping" message; exit 0.
36
+ - `":"` — no-op; exit 0. Prefer this over `""` if your repo enforces non-empty script values.
37
+ - Any other string — runs in place of the default.
38
+
39
+ ## Agent-file sync
40
+
41
+ The presence and version stamp of `.agents/nmr/AGENTS.md` is verified by `check:agent-files`, which is part of the default root `check:strict` composite. If it fails, run `nmr sync-agent-files`.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Context-aware script runner for PNPM monorepos. Ships an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
4
4
 
5
+ <!-- section:release-notes --><!-- /section:release-notes -->
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -133,11 +135,11 @@ These scripts are available out of the box. Repo-wide config (tier 2) and per-pa
133
135
  | ------------------ | -------------------------------------------------------- |
134
136
  | `build` | `compile`, `generate-typings` |
135
137
  | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
136
- | `check:fixable` | `fmt:check`, `lint:check` |
137
138
  | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
138
139
  | `clean` | `pnpm exec rimraf dist/*` |
139
140
  | `compile` | `tsx ../../config/build.ts` |
140
141
  | `fix` | `lint`, `fmt` |
142
+ | `fix:check` | `fmt:check`, `lint:check` |
141
143
  | `fmt` | `prettier --list-different --write .` |
142
144
  | `fmt:check` | `prettier --check .` |
143
145
  | `generate-typings` | `tsc --project tsconfig.generate-typings.json` |
@@ -173,11 +175,17 @@ Packages with a `vitest.integration.config.ts` file get different test commands.
173
175
 
174
176
  #### Check and quality
175
177
 
176
- | Command | Runs |
177
- | --------------- | -------------------------------------------------------- |
178
- | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
179
- | `check:fixable` | `fmt:check`, `lint:check` |
180
- | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
178
+ | Command | Runs |
179
+ | ------------------- | ----------------------------------------------------------------------------- |
180
+ | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
181
+ | `check:agent-files` | `nmr-sync-agent-files --check` |
182
+ | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage`, `check:agent-files` |
183
+
184
+ #### Fix
185
+
186
+ | Command | Runs |
187
+ | ----------- | ------------------------- |
188
+ | `fix:check` | `fmt:check`, `lint:check` |
181
189
 
182
190
  #### Test
183
191
 
@@ -245,6 +253,7 @@ These scripts operate on root-level code only (not workspace packages):
245
253
  | Command | Runs |
246
254
  | ------------------- | ----------------------- |
247
255
  | `report-overrides` | `nmr-report-overrides` |
256
+ | `sync-agent-files` | `nmr-sync-agent-files` |
248
257
  | `sync-pnpm-version` | `nmr-sync-pnpm-version` |
249
258
 
250
259
  ## CLI reference
@@ -296,6 +305,25 @@ Report any active `pnpm.overrides` in the root `package.json`. Useful as a `post
296
305
  nmr report-overrides
297
306
  ```
298
307
 
308
+ ### `sync-agent-files`
309
+
310
+ Sync the agent-facing guidance shipped with nmr into the consuming repo.
311
+
312
+ ```bash
313
+ nmr sync-agent-files # write .agents/nmr/AGENTS.md, stamped with the installed nmr version
314
+ nmr sync-agent-files --check # verify the stamp matches; exit 1 with a fix message if not
315
+ ```
316
+
317
+ Run `nmr sync-agent-files` once after upgrading nmr. The generated file is committed to the consuming repo; do not edit it by hand.
318
+
319
+ The default root `check:strict` composite includes `check:agent-files`, which runs `--check` automatically — so any CI pipeline already running `check:strict` catches drift without per-consumer wiring.
320
+
321
+ To expose the synced guidance to Claude Code sessions, add this include to the consuming repo's `.agents/PROJECT.md`:
322
+
323
+ ```markdown
324
+ @.agents/nmr/AGENTS.md
325
+ ```
326
+
299
327
  ### `sync-pnpm-version`
300
328
 
301
329
  Synchronize the pnpm version from the root `package.json` `packageManager` field into the GitHub `code-quality.yaml` workflow file.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ try {
3
+ await import('../dist/esm/cli-sync-agent-files.js');
4
+ } catch (error) {
5
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
6
+ process.stderr.write('nmr-sync-agent-files: build output not found — run `pnpm run build` first\n');
7
+ } else {
8
+ process.stderr.write(`nmr-sync-agent-files: failed to load: ${error.message}\n`);
9
+ }
10
+ process.exit(1);
11
+ }
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- c78bf7d8a9e046770c7c8de71f6bf1424bd0919ce2c557d53fc83d984a4528f8
1
+ ecd2594bf00be365cf1c1d58937d98631236e5a8841aade1fcaf6bb190cbf84b
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { check, sync } from "./commands/sync-agent-files.js";
2
+ import { findMonorepoRoot } from "./context.js";
3
+ function parseArgs(argv) {
4
+ const args = argv.slice(2);
5
+ if (args.length === 0) return { mode: "sync" };
6
+ if (args.length === 1 && args[0] === "--check") return { mode: "check" };
7
+ return { error: `Usage: nmr sync-agent-files [--check]` };
8
+ }
9
+ try {
10
+ const parsed = parseArgs(process.argv);
11
+ if ("error" in parsed) {
12
+ console.error(parsed.error);
13
+ process.exit(1);
14
+ }
15
+ const monorepoRoot = findMonorepoRoot();
16
+ if (parsed.mode === "sync") {
17
+ const { written, stamp } = sync(monorepoRoot);
18
+ console.info(`\u2713 Wrote ${written} (${stamp})`);
19
+ process.exit(0);
20
+ }
21
+ const result = check(monorepoRoot);
22
+ if (result.ok) {
23
+ console.info(`\u2713 .agents/nmr/AGENTS.md is in sync (${result.stamp})`);
24
+ process.exit(0);
25
+ }
26
+ console.error(result.reason);
27
+ process.exit(1);
28
+ } catch (error) {
29
+ console.error(error instanceof Error ? error.message : String(error));
30
+ process.exit(1);
31
+ }
@@ -0,0 +1,14 @@
1
+ export declare function parseSourceStamp(content: string): string | null;
2
+ export interface SyncResult {
3
+ written: string;
4
+ stamp: string;
5
+ }
6
+ export declare function sync(destinationDir: string): SyncResult;
7
+ export type CheckResult = {
8
+ ok: true;
9
+ stamp: string;
10
+ } | {
11
+ ok: false;
12
+ reason: string;
13
+ };
14
+ export declare function check(destinationDir: string): CheckResult;
@@ -0,0 +1,77 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { VERSION } from "../version.js";
5
+ const PACKAGE_NAME = "@williamthorsen/nmr";
6
+ const DESTINATION_RELATIVE_PATH = ".agents/nmr/AGENTS.md";
7
+ const SOURCE_FILENAME = "AGENTS.md";
8
+ function getSourcePath() {
9
+ let dir = path.dirname(fileURLToPath(import.meta.url));
10
+ while (dir !== path.dirname(dir)) {
11
+ const candidate = path.join(dir, SOURCE_FILENAME);
12
+ if (existsSync(candidate)) return candidate;
13
+ dir = path.dirname(dir);
14
+ }
15
+ throw new Error(`Could not locate ${SOURCE_FILENAME} in any parent of ${fileURLToPath(import.meta.url)}`);
16
+ }
17
+ function getDestinationPath(destinationDir) {
18
+ return path.join(destinationDir, DESTINATION_RELATIVE_PATH);
19
+ }
20
+ function currentSourceStamp() {
21
+ return `${PACKAGE_NAME}@${VERSION}`;
22
+ }
23
+ const FRONTMATTER_REGEX = /^---\n((?:.*\n)*?)---\n/;
24
+ function stripFrontmatter(content) {
25
+ return content.replace(FRONTMATTER_REGEX, "");
26
+ }
27
+ function parseSourceStamp(content) {
28
+ const frontmatterMatch = FRONTMATTER_REGEX.exec(content);
29
+ if (!frontmatterMatch) return null;
30
+ const frontmatterBody = frontmatterMatch[1] ?? "";
31
+ const sourceMatch = /^source:\s*['"]([^'"]+)['"]\s*$/m.exec(frontmatterBody);
32
+ return sourceMatch?.[1] ?? null;
33
+ }
34
+ function sync(destinationDir) {
35
+ const sourcePath = getSourcePath();
36
+ const sourceContent = readFileSync(sourcePath, "utf8");
37
+ const body = stripFrontmatter(sourceContent);
38
+ const stamp = currentSourceStamp();
39
+ const output = `---
40
+ source: '${stamp}'
41
+ ---
42
+ ${body}`;
43
+ const destinationPath = getDestinationPath(destinationDir);
44
+ mkdirSync(path.dirname(destinationPath), { recursive: true });
45
+ writeFileSync(destinationPath, output, "utf8");
46
+ return { written: destinationPath, stamp };
47
+ }
48
+ function check(destinationDir) {
49
+ const destinationPath = getDestinationPath(destinationDir);
50
+ const expected = currentSourceStamp();
51
+ if (!existsSync(destinationPath)) {
52
+ return {
53
+ ok: false,
54
+ reason: `${DESTINATION_RELATIVE_PATH} is missing. Run \`nmr sync-agent-files\`.`
55
+ };
56
+ }
57
+ const content = readFileSync(destinationPath, "utf8");
58
+ const found = parseSourceStamp(content);
59
+ if (found === null) {
60
+ return {
61
+ ok: false,
62
+ reason: `Cannot parse version stamp in ${DESTINATION_RELATIVE_PATH}. Run \`nmr sync-agent-files\`.`
63
+ };
64
+ }
65
+ if (found !== expected) {
66
+ return {
67
+ ok: false,
68
+ reason: `${DESTINATION_RELATIVE_PATH} is out of sync (file: ${found}, installed: ${expected}). Run \`nmr sync-agent-files\`.`
69
+ };
70
+ }
71
+ return { ok: true, stamp: expected };
72
+ }
73
+ export {
74
+ check,
75
+ parseSourceStamp,
76
+ sync
77
+ };
@@ -1,11 +1,11 @@
1
1
  const commonWorkspaceScripts = {
2
2
  build: ["compile", "generate-typings"],
3
3
  check: ["typecheck", "fmt:check", "lint:check", "test"],
4
- "check:fixable": ["fmt:check", "lint:check"],
5
4
  "check:strict": ["typecheck", "fmt:check", "lint:strict", "test:coverage"],
6
5
  clean: "pnpm exec rimraf dist/*",
7
6
  compile: "tsx ../../config/build.ts",
8
7
  fix: ["lint", "fmt"],
8
+ "fix:check": ["fmt:check", "lint:check"],
9
9
  fmt: "prettier --list-different --write .",
10
10
  "fmt:check": "prettier --check .",
11
11
  "generate-typings": "tsc --project tsconfig.generate-typings.json",
@@ -32,11 +32,12 @@ const rootScripts = {
32
32
  "audit:prod": "pnpm exec audit-deps --prod",
33
33
  build: "pnpm --recursive exec nmr build",
34
34
  check: ["typecheck", "fmt:check", "lint:check", "test"],
35
- "check:fixable": ["fmt:check", "lint:check"],
36
- "check:strict": ["typecheck", "fmt:check", "lint:strict", "test:coverage"],
35
+ "check:agent-files": "nmr-sync-agent-files --check",
36
+ "check:strict": ["typecheck", "fmt:check", "lint:strict", "test:coverage", "check:agent-files"],
37
37
  ci: ["build", "check:strict", "audit"],
38
38
  clean: "pnpm --recursive exec nmr clean",
39
39
  fix: ["lint", "fmt"],
40
+ "fix:check": ["fmt:check", "lint:check"],
40
41
  fmt: `sh -c 'prettier --list-different --write "\${@:-.}"' --`,
41
42
  "fmt:all": ["fmt", "fmt:sh"],
42
43
  "fmt:check": `sh -c 'prettier --check "\${@:-.}"' --`,
@@ -53,6 +54,7 @@ const rootScripts = {
53
54
  "root:lint:strict": "strict-lint --ignore-pattern 'packages/**' .",
54
55
  "root:test": "vitest --config ./vitest.root.config.ts",
55
56
  "root:typecheck": "tsgo --noEmit",
57
+ "sync-agent-files": "nmr-sync-agent-files",
56
58
  "sync-pnpm-version": "nmr-sync-pnpm-version",
57
59
  test: "nmr root:test && pnpm --recursive exec nmr test",
58
60
  "test:coverage": "nmr root:test && pnpm --recursive exec nmr test:coverage",
@@ -1 +1 @@
1
- export declare const VERSION = "0.11.0";
1
+ export declare const VERSION = "0.12.1";
@@ -1,4 +1,4 @@
1
- const VERSION = "0.11.0";
1
+ const VERSION = "0.12.1";
2
2
  export {
3
3
  VERSION
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/nmr",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "private": false,
5
5
  "description": "Context-aware script runner for PNPM monorepos",
6
6
  "keywords": [
@@ -30,16 +30,18 @@
30
30
  "ensure-prepublish-hooks": "bin/ensure-prepublish-hooks.js",
31
31
  "nmr": "bin/nmr.js",
32
32
  "nmr-report-overrides": "bin/nmr-report-overrides.js",
33
+ "nmr-sync-agent-files": "bin/nmr-sync-agent-files.js",
33
34
  "nmr-sync-pnpm-version": "bin/nmr-sync-pnpm-version.js"
34
35
  },
35
36
  "files": [
37
+ "AGENTS.md",
36
38
  "bin",
37
39
  "dist"
38
40
  ],
39
41
  "dependencies": {
40
42
  "jiti": "2.6.1",
41
43
  "js-yaml": "4.1.1",
42
- "zod": "4.3.6"
44
+ "zod": "4.4.1"
43
45
  },
44
46
  "publishConfig": {
45
47
  "access": "public"