@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 +41 -0
- package/README.md +34 -6
- package/bin/nmr-sync-agent-files.js +11 -0
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli-sync-agent-files.d.ts +1 -0
- package/dist/esm/cli-sync-agent-files.js +31 -0
- package/dist/esm/commands/sync-agent-files.d.ts +14 -0
- package/dist/esm/commands/sync-agent-files.js +77 -0
- package/dist/esm/default-scripts.js +5 -3
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -2
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
|
|
177
|
-
|
|
|
178
|
-
| `check`
|
|
179
|
-
| `check:
|
|
180
|
-
| `check:strict`
|
|
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
|
-
|
|
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:
|
|
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",
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.12.1";
|
package/dist/esm/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "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.
|
|
44
|
+
"zod": "4.4.1"
|
|
43
45
|
},
|
|
44
46
|
"publishConfig": {
|
|
45
47
|
"access": "public"
|