@williamthorsen/nmr 0.25.0 → 0.26.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/.readyup/kits/default.js +7 -19
- package/.readyup/manifest.json +1 -1
- package/README.md +32 -43
- package/agents/README.md +24 -0
- package/{AGENTS.md → agents/guidance/rulebooks/nmr.md} +5 -3
- package/dist/esm/check-cache.js +1 -2
- package/dist/esm/default-scripts.js +1 -3
- package/package.json +8 -5
- package/bin/nmr-sync-agent-files.js +0 -11
- package/dist/esm/cli-sync-agent-files.d.ts +0 -1
- package/dist/esm/cli-sync-agent-files.js +0 -29
- package/dist/esm/commands/sync-agent-files.d.ts +0 -13
- package/dist/esm/commands/sync-agent-files.js +0 -58
package/.readyup/kits/default.js
CHANGED
|
@@ -40,8 +40,7 @@ var workspaceScripts = {
|
|
|
40
40
|
"test:unit": "pnpm exec vitest --project unit",
|
|
41
41
|
"test:watch": `pnpm exec vitest ${GATE_PROJECTS} --watch`,
|
|
42
42
|
typecheck: "tsgo --noEmit",
|
|
43
|
-
// `--include-locked
|
|
44
|
-
// `savePrefix: ''`) has no dependency taze considers unlocked, so without it nothing is reported.
|
|
43
|
+
// Without `--include-locked`, nothing would be reported in a repo that pins exact version numbers.
|
|
45
44
|
upgrade: "nmr-taze --include-locked",
|
|
46
45
|
"view-coverage": "open coverage/index.html"
|
|
47
46
|
};
|
|
@@ -51,21 +50,17 @@ var rootScripts = {
|
|
|
51
50
|
"audit:prod": "pnpm exec v11y --prod",
|
|
52
51
|
build: "pnpm --recursive exec nmr build",
|
|
53
52
|
check: ["typecheck", "fmt:check", "lint:check", "test"],
|
|
54
|
-
"check:
|
|
55
|
-
|
|
56
|
-
// Excludes the audit, which has a workflow of its own.
|
|
53
|
+
"check:strict": ["typecheck", "fmt:check", "lint:strict", "test:coverage"],
|
|
54
|
+
// Excludes the audit, which in CI has a workflow of its own.
|
|
57
55
|
ci: ["build", "check:strict"],
|
|
58
56
|
clean: "nmr-clean",
|
|
59
57
|
fix: ["lint", "fmt"],
|
|
60
58
|
"fix:check": ["fmt:check", "lint:check"],
|
|
61
|
-
// Identical to the workspace entries: the bin scopes its own selection to the working directory, so
|
|
62
|
-
// neither registry needs the `sh -c '… "${@:-.}"' --` wrapper that gave the root form a default path.
|
|
63
59
|
fmt: "nmr-fmt --write",
|
|
64
60
|
"fmt:check": "nmr-fmt --check",
|
|
65
61
|
lint: "nmr root:lint && pnpm --recursive exec nmr lint",
|
|
66
62
|
"lint:check": "nmr root:lint:check && pnpm --recursive exec nmr lint:check",
|
|
67
63
|
"lint:strict": "nmr root:lint:strict && pnpm --recursive exec nmr lint:strict",
|
|
68
|
-
// Everything the remote runs: the code-quality gate plus the audit workflow.
|
|
69
64
|
prepush: ["ci", "audit"],
|
|
70
65
|
"report-overrides": "nmr-report-overrides",
|
|
71
66
|
"root:check": ["root:typecheck", "fmt:check", "root:lint:check", "root:test"],
|
|
@@ -78,23 +73,16 @@ var rootScripts = {
|
|
|
78
73
|
"root:test:unit": "vitest --config ./vitest.root.config.ts --project unit",
|
|
79
74
|
"root:typecheck": "tsgo --noEmit",
|
|
80
75
|
"root:upgrade": "nmr-taze --include-locked",
|
|
81
|
-
"sync-agent-files": "nmr-sync-agent-files",
|
|
82
76
|
test: "nmr root:test && pnpm --recursive exec nmr test",
|
|
83
77
|
"test:all": "nmr root:test:all && pnpm --recursive exec nmr test:all",
|
|
84
|
-
// Chains `root:test`, not a `root:test:coverage`: the root config reports no coverage of its own.
|
|
85
78
|
"test:coverage": "nmr root:test && pnpm --recursive exec nmr test:coverage",
|
|
86
79
|
"test:tool": "nmr root:test:tool && pnpm --recursive exec nmr test:tool",
|
|
87
80
|
"test:unit": "nmr root:test:unit && pnpm --recursive exec nmr test:unit",
|
|
88
|
-
//
|
|
89
|
-
//
|
|
81
|
+
// Bare `vitest` at the monorepo root resolves the root `vitest.config.ts`, covering the whole tree in one
|
|
82
|
+
// process; a chain like the others would never advance past its first watcher.
|
|
90
83
|
"test:watch": `vitest ${GATE_PROJECTS} --watch`,
|
|
91
84
|
typecheck: "nmr root:typecheck && pnpm --recursive exec nmr typecheck",
|
|
92
|
-
// A string rather than a composite
|
|
93
|
-
// composite, `nmr upgrade major` would hand `major` to the override report instead of the upgrade tool.
|
|
94
|
-
//
|
|
95
|
-
// Overrides print first, as context for the report that follows: a pinned transitive dependency is why
|
|
96
|
-
// an expected upgrade may be missing from it. taze handles pnpm workspaces natively, so this needs no
|
|
97
|
-
// `pnpm --recursive` fan-out: one process covers the root and every package.
|
|
85
|
+
// A string rather than a composite: Passthrough args attach to the chain's last command.
|
|
98
86
|
upgrade: "nmr-report-overrides && nmr-taze --include-locked --recursive"
|
|
99
87
|
};
|
|
100
88
|
|
|
@@ -375,7 +363,7 @@ function formatPaths(paths) {
|
|
|
375
363
|
${paths.map((path2) => ` ${path2}`).join("\n")}`;
|
|
376
364
|
}
|
|
377
365
|
function getMinVersion() {
|
|
378
|
-
const picked = { "version": "0.
|
|
366
|
+
const picked = { "version": "0.26.1" };
|
|
379
367
|
if (typeof picked.version !== "string") {
|
|
380
368
|
throw new TypeError("nmr/package.json: 'version' must be a string");
|
|
381
369
|
}
|
package/.readyup/manifest.json
CHANGED
package/README.md
CHANGED
|
@@ -3,20 +3,13 @@
|
|
|
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
5
|
<!-- section:release-notes -->
|
|
6
|
-
## Release notes — v0.
|
|
6
|
+
## Release notes — v0.26.1 (2026-08-04)
|
|
7
7
|
|
|
8
|
-
###
|
|
8
|
+
### 🐛 Bug fixes
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Prevent unnecessary bypasses of the check-result cache (#595)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
Savings less than 1 second are no longer reported.
|
|
14
|
-
|
|
15
|
-
- Publish nmr's readyup kit with a test-tier conformance check (#580)
|
|
16
|
-
|
|
17
|
-
`@williamthorsen/nmr` is now bundled with ReadyUp kits that check a repo's alignment with `nmr` requirements and recommendations.
|
|
18
|
-
|
|
19
|
-
`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.
|
|
12
|
+
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.
|
|
20
13
|
<!-- /section:release-notes -->
|
|
21
14
|
|
|
22
15
|
## Installation
|
|
@@ -201,23 +194,23 @@ import { defineConfig } from '@williamthorsen/nmr/config';
|
|
|
201
194
|
|
|
202
195
|
export default defineConfig({
|
|
203
196
|
workspaceScripts: {
|
|
204
|
-
'build:pre': '
|
|
197
|
+
'build:pre': 'node scripts/generate-manifest.ts',
|
|
205
198
|
},
|
|
206
199
|
});
|
|
207
200
|
```
|
|
208
201
|
|
|
209
|
-
Attach a
|
|
202
|
+
Attach a step to one package's hook:
|
|
210
203
|
|
|
211
204
|
```jsonc
|
|
212
|
-
// packages/
|
|
205
|
+
// packages/my-package/package.json
|
|
213
206
|
{
|
|
214
207
|
"scripts": {
|
|
215
|
-
"
|
|
208
|
+
"upgrade:post": "nmr-report-overrides",
|
|
216
209
|
},
|
|
217
210
|
}
|
|
218
211
|
```
|
|
219
212
|
|
|
220
|
-
The second example calls the bin directly
|
|
213
|
+
The second example calls the bin directly: `report-overrides` is a root-registry command, so `nmr report-overrides` from inside a package fails with `Unknown command`, while the bin runs from either scope.
|
|
221
214
|
|
|
222
215
|
## Check-result cache
|
|
223
216
|
|
|
@@ -228,7 +221,7 @@ $ nmr ci
|
|
|
228
221
|
# ... four minutes of build, typecheck, lint, and coverage ...
|
|
229
222
|
|
|
230
223
|
$ nmr ci
|
|
231
|
-
⏭️ ci: passed 2m ago on this tree (🚀 saved ~4m).
|
|
224
|
+
⏭️ ci: passed 2m ago on this tree (🚀 saved ~4m).
|
|
232
225
|
```
|
|
233
226
|
|
|
234
227
|
Composite commands expand into child `nmr` processes, so one green `nmr ci` records a pass for every cacheable command in the chain, at every scope it ran at. A later `nmr check`, `nmr typecheck`, or `nmr -F core test` on the same tree skips too. The skip line is suppressed by `-q`.
|
|
@@ -237,7 +230,7 @@ The cache is a working-tree cache, not a build cache: it answers "has this exact
|
|
|
237
230
|
|
|
238
231
|
### What is cached
|
|
239
232
|
|
|
240
|
-
Cacheable by default: `check`, `check:
|
|
233
|
+
Cacheable by default: `check`, `check:strict`, `ci`, `fix:check`, `fmt:check`, `lint:check`, `lint:strict`, `test`, `test:coverage`, `test:tool`, `test:unit`, `typecheck`, and the `root:` variants `root:check`, `root:lint:check`, `root:lint:strict`, `root:test`, `root:test:tool`, `root:test:unit`, and `root:typecheck`.
|
|
241
234
|
|
|
242
235
|
Everything else runs every time:
|
|
243
236
|
|
|
@@ -437,11 +430,10 @@ Neither is bound to a git hook. `prepush` is named for when you run it, not for
|
|
|
437
430
|
|
|
438
431
|
#### Check and quality
|
|
439
432
|
|
|
440
|
-
| Command
|
|
441
|
-
|
|
|
442
|
-
| `check`
|
|
443
|
-
| `check:
|
|
444
|
-
| `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage`, `check:agent-files` |
|
|
433
|
+
| Command | Runs |
|
|
434
|
+
| -------------- | -------------------------------------------------------- |
|
|
435
|
+
| `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
|
|
436
|
+
| `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
|
|
445
437
|
|
|
446
438
|
#### Fix
|
|
447
439
|
|
|
@@ -531,7 +523,6 @@ These scripts operate on root-level code only (not workspace packages):
|
|
|
531
523
|
| Command | Runs |
|
|
532
524
|
| ------------------ | ---------------------- |
|
|
533
525
|
| `report-overrides` | `nmr-report-overrides` |
|
|
534
|
-
| `sync-agent-files` | `nmr-sync-agent-files` |
|
|
535
526
|
|
|
536
527
|
## CLI reference
|
|
537
528
|
|
|
@@ -585,35 +576,33 @@ Report any active `pnpm.overrides` in the root `package.json`, reminding develop
|
|
|
585
576
|
nmr report-overrides
|
|
586
577
|
```
|
|
587
578
|
|
|
588
|
-
|
|
579
|
+
## Agent guidance
|
|
589
580
|
|
|
590
|
-
|
|
581
|
+
nmr ships the rules an agent needs in order to invoke it, as [CodeAssembly](https://github.com/williamthorsen/codeassembly) package content. Adopt it by naming the package in the consuming repo's `.agents/codeassembly.yaml`:
|
|
591
582
|
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
|
|
583
|
+
```yaml
|
|
584
|
+
packages:
|
|
585
|
+
use:
|
|
586
|
+
- '@williamthorsen/nmr'
|
|
595
587
|
```
|
|
596
588
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
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.
|
|
589
|
+
Then add `codeassembly` as a devDependency and run `codeassembly sync`. The guidance is injected into the machine-local guidance file each harness loads at launch; nothing is copied into the repo or committed there. It resolves from the installed package, so upgrading nmr updates it with no second step.
|
|
600
590
|
|
|
601
|
-
|
|
591
|
+
Wiring the sync to `postinstall` keeps it current without a hand-run command:
|
|
602
592
|
|
|
603
|
-
```
|
|
604
|
-
|
|
593
|
+
```json
|
|
594
|
+
{
|
|
595
|
+
"scripts": {
|
|
596
|
+
"postinstall": "codeassembly sync --warn-only"
|
|
597
|
+
}
|
|
598
|
+
}
|
|
605
599
|
```
|
|
606
600
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
The synced file is injected at launch in every consuming repo, so every line is paid for on every task whether or not it is relevant. It is a cheatsheet, not a manual.
|
|
610
|
-
|
|
611
|
-
A line earns a place in `AGENTS.md` only if both hold:
|
|
601
|
+
`--warn-only` reports a sync failure and exits 0; without it, a failed sync aborts `pnpm install`.
|
|
612
602
|
|
|
613
|
-
|
|
614
|
-
2. The obvious action goes wrong without it. Guidance that prevents a silent mistake earns its place; guidance that prevents a mistake the next error message would diagnose does not.
|
|
603
|
+
### Migrating from `sync-agent-files`
|
|
615
604
|
|
|
616
|
-
|
|
605
|
+
The `nmr sync-agent-files` command, its `nmr-sync-agent-files` bin, and the `check:agent-files` script were removed in favor of the above. After upgrading, delete the generated `.agents/nmr/` directory and the `@nmr/AGENTS.md` line it was exposed through, drop `check:agent-files` from any `check:strict` override that enumerates it, and remove any direct call to the bin.
|
|
617
606
|
|
|
618
607
|
## Standalone utilities
|
|
619
608
|
|
package/agents/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Authoring nmr's agent guidance
|
|
2
|
+
|
|
3
|
+
This directory is a CodeAssembly content root, declared by the `codeassembly.content` key in `packages/nmr/package.json`.
|
|
4
|
+
|
|
5
|
+
## The ambient cheatsheet
|
|
6
|
+
|
|
7
|
+
`guidance/rulebooks/nmr.md` carries `delivery: ambient`, so its body is injected at launch in every consuming repo. Every line is paid for on every task, whether or not it is relevant. It is a cheatsheet, not a manual.
|
|
8
|
+
|
|
9
|
+
A line earns a place in it only if both hold:
|
|
10
|
+
|
|
11
|
+
1. **It is absent from nmr's own output.** Bare `nmr` already prints the flags, every command, and the shell command each resolves to; failing checks already name their own fix. Repeating any of that costs launch tokens to say something the agent will be told anyway, at the moment it matters.
|
|
12
|
+
2. **The obvious action goes wrong without it.** Guidance that prevents a silent mistake earns its place; guidance that prevents a mistake the next error message would diagnose does not.
|
|
13
|
+
|
|
14
|
+
Everything else belongs in `packages/nmr/README.md`, reachable on demand at `node_modules/@williamthorsen/nmr/README.md`, with a pointer from the cheatsheet naming the topic so an agent knows to look. When adding a feature, document it there and extend that pointer rather than the cheatsheet.
|
|
15
|
+
|
|
16
|
+
## Constraints on rulebook bodies
|
|
17
|
+
|
|
18
|
+
Rulebook bodies carry no links to other rulebooks. CodeAssembly's renderer rewrites link targets under `skills/` and `scripts/` and rejects everything else, so a cross-reference names the deployed `consult-<slug>` in prose and declares the edge under `dependencies:`.
|
|
19
|
+
|
|
20
|
+
Anchor-only links are checked against the body they appear in, so a fragment naming a heading that lives in another document fails the run. Check `](#` before moving a section between bodies.
|
|
21
|
+
|
|
22
|
+
## Publishing
|
|
23
|
+
|
|
24
|
+
The directory is published because `files` in `packages/nmr/package.json` lists it. Dropping that entry fails nothing locally, because this repo resolves the guidance through a `workspace:*` self-link that `files` does not govern. `packaging.tool.test.ts` is what catches it.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
slug: nmr
|
|
3
|
+
description: Invocation rules for the nmr script runner in a pnpm monorepo.
|
|
4
|
+
delivery: ambient
|
|
3
5
|
---
|
|
4
6
|
|
|
5
|
-
# nmr: agent guidance
|
|
7
|
+
# nmr: agent guidance
|
|
6
8
|
|
|
7
9
|
- Use `nmr <command>`, not `pnpm run <command>`, for anything nmr provides. Run bare `nmr` to list every command and the shell command it resolves to; a wrong guess reports only `Unknown command`, with no list.
|
|
8
10
|
- Scope follows cwd, and bare `nmr` lists both registries: from the repo root a command covers root files and every workspace; from inside a package, that package alone. `nmr -F <pkg> <command>` targets one package from anywhere; `nmr root:<command>` targets root files alone, which isolates a failure to root code.
|
|
9
11
|
- Never `npx nmr`: inside a git worktree it can resolve a different nmr from outside the tree. Fall back to `pnpm exec nmr`.
|
|
10
12
|
- `nmr ci` runs what a code-quality workflow runs (build, then strict checks) and not the dependency audit, which belongs to a workflow of its own; `nmr prepush` runs both. Neither installs or binds a git hook.
|
|
11
|
-
- A check that already passed on the current working tree skips, printing `⏭️ <command>: passed …`.
|
|
13
|
+
- A check that already passed on the current working tree skips, printing `⏭️ <command>: passed …`. Any edit, addition, or deletion re-runs it, so a skip is a real pass and needs no confirming: never pass `--no-cache` to get a "real" run, which costs the command's full runtime to reach the same answer. Its one use is a file rather than an exit status -- a skipped command writes none, so a fresh `coverage/` needs `nmr --no-cache test:coverage`.
|
|
12
14
|
- The rest is in `node_modules/@williamthorsen/nmr/README.md`: pre/post hooks (every `nmr X` auto-wraps as `nmr X:pre && nmr X && nmr X:post`), script overrides and their `""`/`":"` skip values, `nmr upgrade` ceilings (report-only until `nmr upgrade --write`), the `nmr-compile` build (there is no repo-local build script), and the check-result cache's key, its `checkCache` config, and every condition that disables it.
|
package/dist/esm/check-cache.js
CHANGED
|
@@ -18,7 +18,6 @@ export const CURRENT_RUNTIME = {
|
|
|
18
18
|
export const DEBUG_ENV_VAR = 'NMR_DEBUG';
|
|
19
19
|
export const DEFAULT_CACHEABLE_COMMANDS = [
|
|
20
20
|
'check',
|
|
21
|
-
'check:agent-files',
|
|
22
21
|
'check:strict',
|
|
23
22
|
'ci',
|
|
24
23
|
'fix:check',
|
|
@@ -94,7 +93,7 @@ export function formatSkipLine(command, entry, now) {
|
|
|
94
93
|
const age = formatDuration(Math.max(0, now - Date.parse(entry.recordedAt)));
|
|
95
94
|
const saving = formatSaving(entry.durationMs);
|
|
96
95
|
const savingClause = saving === undefined ? '' : ` (${saving})`;
|
|
97
|
-
return `⏭️ ${command}: passed ${age} ago on this tree${savingClause}
|
|
96
|
+
return `⏭️ ${command}: passed ${age} ago on this tree${savingClause}.`;
|
|
98
97
|
}
|
|
99
98
|
export async function readBuildOutputState(monorepoRoot, config) {
|
|
100
99
|
const state = { missing: [], digests: {} };
|
|
@@ -28,8 +28,7 @@ export const rootScripts = {
|
|
|
28
28
|
'audit:prod': 'pnpm exec v11y --prod',
|
|
29
29
|
build: 'pnpm --recursive exec nmr build',
|
|
30
30
|
check: ['typecheck', 'fmt:check', 'lint:check', 'test'],
|
|
31
|
-
'check:
|
|
32
|
-
'check:strict': ['typecheck', 'fmt:check', 'lint:strict', 'test:coverage', 'check:agent-files'],
|
|
31
|
+
'check:strict': ['typecheck', 'fmt:check', 'lint:strict', 'test:coverage'],
|
|
33
32
|
ci: ['build', 'check:strict'],
|
|
34
33
|
clean: 'nmr-clean',
|
|
35
34
|
fix: ['lint', 'fmt'],
|
|
@@ -51,7 +50,6 @@ export const rootScripts = {
|
|
|
51
50
|
'root:test:unit': 'vitest --config ./vitest.root.config.ts --project unit',
|
|
52
51
|
'root:typecheck': 'tsgo --noEmit',
|
|
53
52
|
'root:upgrade': 'nmr-taze --include-locked',
|
|
54
|
-
'sync-agent-files': 'nmr-sync-agent-files',
|
|
55
53
|
test: 'nmr root:test && pnpm --recursive exec nmr test',
|
|
56
54
|
'test:all': 'nmr root:test:all && pnpm --recursive exec nmr test:all',
|
|
57
55
|
'test:coverage': 'nmr root:test && pnpm --recursive exec nmr test:coverage',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -54,13 +54,12 @@
|
|
|
54
54
|
"nmr-compile": "bin/nmr-compile.js",
|
|
55
55
|
"nmr-fmt": "bin/nmr-fmt.js",
|
|
56
56
|
"nmr-report-overrides": "bin/nmr-report-overrides.js",
|
|
57
|
-
"nmr-sync-agent-files": "bin/nmr-sync-agent-files.js",
|
|
58
57
|
"nmr-taze": "bin/nmr-taze.js"
|
|
59
58
|
},
|
|
60
59
|
"files": [
|
|
61
60
|
".readyup/kits/*.js",
|
|
62
61
|
".readyup/manifest.json",
|
|
63
|
-
"
|
|
62
|
+
"agents",
|
|
64
63
|
"bin",
|
|
65
64
|
"dist"
|
|
66
65
|
],
|
|
@@ -69,7 +68,7 @@
|
|
|
69
68
|
"prettier-plugin-sh": "0.19.0",
|
|
70
69
|
"taze": "19.16.0",
|
|
71
70
|
"yaml": "2.9.0",
|
|
72
|
-
"@williamthorsen/nmr-core": "0.9.
|
|
71
|
+
"@williamthorsen/nmr-core": "0.9.1"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
75
74
|
"prettier": "3.9.6",
|
|
@@ -96,7 +95,11 @@
|
|
|
96
95
|
"publishConfig": {
|
|
97
96
|
"access": "public"
|
|
98
97
|
},
|
|
98
|
+
"codeassembly": {
|
|
99
|
+
"content": "agents"
|
|
100
|
+
},
|
|
99
101
|
"scripts": {
|
|
100
|
-
"build:post": "
|
|
102
|
+
"build:post": "rdy compile",
|
|
103
|
+
"build:pre": "rdy verify"
|
|
101
104
|
}
|
|
102
105
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { check, sync } from "./commands/sync-agent-files.js";
|
|
3
|
-
import { findMonorepoRoot } from "./workspace.js";
|
|
4
|
-
const flagSchema = {
|
|
5
|
-
check: { long: '--check', type: 'boolean' },
|
|
6
|
-
};
|
|
7
|
-
const { flags } = parseArgsOrExit(process.argv.slice(2), flagSchema);
|
|
8
|
-
try {
|
|
9
|
-
runCommand(flags.check);
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
reportError(error instanceof Error ? error.message : String(error));
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
function runCommand(checkOnly) {
|
|
16
|
-
const monorepoRoot = findMonorepoRoot();
|
|
17
|
-
if (!checkOnly) {
|
|
18
|
-
const { path: destinationPath, packageSpecifier, changed } = sync(monorepoRoot);
|
|
19
|
-
console.info(changed ? `✓ Wrote ${destinationPath} (${packageSpecifier})` : `✓ ${destinationPath} already up to date`);
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
const result = check(monorepoRoot);
|
|
23
|
-
if (result.ok) {
|
|
24
|
-
console.info('✓ .agents/nmr/AGENTS.md is in sync');
|
|
25
|
-
process.exit(0);
|
|
26
|
-
}
|
|
27
|
-
process.stderr.write(`${result.reason}\n`);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare function check(destinationDir: string): CheckResult;
|
|
2
|
-
export interface SyncResult {
|
|
3
|
-
path: string;
|
|
4
|
-
packageSpecifier: string;
|
|
5
|
-
changed: boolean;
|
|
6
|
-
}
|
|
7
|
-
export declare function sync(destinationDir: string): SyncResult;
|
|
8
|
-
export type CheckResult = {
|
|
9
|
-
ok: true;
|
|
10
|
-
} | {
|
|
11
|
-
ok: false;
|
|
12
|
-
reason: string;
|
|
13
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { readPackageVersion } from '@williamthorsen/nmr-core';
|
|
5
|
-
const VERSION = readPackageVersion(import.meta.url);
|
|
6
|
-
const PACKAGE_NAME = '@williamthorsen/nmr';
|
|
7
|
-
const DESTINATION_RELATIVE_PATH = '.agents/nmr/AGENTS.md';
|
|
8
|
-
const SOURCE_FILENAME = 'AGENTS.md';
|
|
9
|
-
const FRONTMATTER_REGEX = /^---\n((?:.*\n)*?)---\n/;
|
|
10
|
-
export function check(destinationDir) {
|
|
11
|
-
const destinationPath = getDestinationPath(destinationDir);
|
|
12
|
-
if (!existsSync(destinationPath)) {
|
|
13
|
-
return {
|
|
14
|
-
ok: false,
|
|
15
|
-
reason: `${DESTINATION_RELATIVE_PATH} is missing. Run \`nmr sync-agent-files\`.`,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const expectedBody = stripFrontmatter(readFileSync(getSourcePath(), 'utf8'));
|
|
19
|
-
const foundBody = stripFrontmatter(readFileSync(destinationPath, 'utf8'));
|
|
20
|
-
if (foundBody !== expectedBody) {
|
|
21
|
-
return {
|
|
22
|
-
ok: false,
|
|
23
|
-
reason: `${DESTINATION_RELATIVE_PATH} content is out of date. Run \`nmr sync-agent-files\`.`,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return { ok: true };
|
|
27
|
-
}
|
|
28
|
-
export function sync(destinationDir) {
|
|
29
|
-
const body = stripFrontmatter(readFileSync(getSourcePath(), 'utf8'));
|
|
30
|
-
const packageSpecifier = buildPackageSpecifier();
|
|
31
|
-
const destinationPath = getDestinationPath(destinationDir);
|
|
32
|
-
if (existsSync(destinationPath) && stripFrontmatter(readFileSync(destinationPath, 'utf8')) === body) {
|
|
33
|
-
return { path: destinationPath, packageSpecifier, changed: false };
|
|
34
|
-
}
|
|
35
|
-
const output = `---\nsource: '${packageSpecifier}'\n---\n${body}`;
|
|
36
|
-
mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
37
|
-
writeFileSync(destinationPath, output, 'utf8');
|
|
38
|
-
return { path: destinationPath, packageSpecifier, changed: true };
|
|
39
|
-
}
|
|
40
|
-
function buildPackageSpecifier() {
|
|
41
|
-
return `${PACKAGE_NAME}@${VERSION}`;
|
|
42
|
-
}
|
|
43
|
-
function getDestinationPath(destinationDir) {
|
|
44
|
-
return path.join(destinationDir, DESTINATION_RELATIVE_PATH);
|
|
45
|
-
}
|
|
46
|
-
function getSourcePath() {
|
|
47
|
-
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
48
|
-
while (dir !== path.dirname(dir)) {
|
|
49
|
-
const candidate = path.join(dir, SOURCE_FILENAME);
|
|
50
|
-
if (existsSync(candidate))
|
|
51
|
-
return candidate;
|
|
52
|
-
dir = path.dirname(dir);
|
|
53
|
-
}
|
|
54
|
-
throw new Error(`Could not locate ${SOURCE_FILENAME} in any parent of ${fileURLToPath(import.meta.url)}`);
|
|
55
|
-
}
|
|
56
|
-
function stripFrontmatter(content) {
|
|
57
|
-
return content.replace(FRONTMATTER_REGEX, '');
|
|
58
|
-
}
|