@williamthorsen/nmr 0.25.0 → 0.26.0
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 +35 -38
- package/agents/README.md +24 -0
- package/{AGENTS.md → agents/guidance/rulebooks/nmr.md} +4 -2
- package/dist/esm/check-cache.js +0 -1
- package/dist/esm/default-scripts.js +1 -3
- package/package.json +7 -4
- 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.0" };
|
|
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,21 @@
|
|
|
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.0 (2026-08-04)
|
|
7
7
|
|
|
8
8
|
### 🎉 Features
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Publish each package's readyup kit from the package that owns it (#590)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
Savings less than 1 second are no longer reported.
|
|
12
|
+
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.
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
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.
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
- Ship agent guidance as CodeAssembly package content (#591)
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
Migration guide: Delete `.agents/nmr/`, remove the `@nmr/AGENTS.md` include line, and remove all uses of `check:agent-files`.
|
|
20
21
|
<!-- /section:release-notes -->
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
@@ -201,23 +202,23 @@ import { defineConfig } from '@williamthorsen/nmr/config';
|
|
|
201
202
|
|
|
202
203
|
export default defineConfig({
|
|
203
204
|
workspaceScripts: {
|
|
204
|
-
'build:pre': '
|
|
205
|
+
'build:pre': 'node scripts/generate-manifest.ts',
|
|
205
206
|
},
|
|
206
207
|
});
|
|
207
208
|
```
|
|
208
209
|
|
|
209
|
-
Attach a
|
|
210
|
+
Attach a step to one package's hook:
|
|
210
211
|
|
|
211
212
|
```jsonc
|
|
212
|
-
// packages/
|
|
213
|
+
// packages/my-package/package.json
|
|
213
214
|
{
|
|
214
215
|
"scripts": {
|
|
215
|
-
"
|
|
216
|
+
"upgrade:post": "nmr-report-overrides",
|
|
216
217
|
},
|
|
217
218
|
}
|
|
218
219
|
```
|
|
219
220
|
|
|
220
|
-
The second example calls the bin directly
|
|
221
|
+
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
222
|
|
|
222
223
|
## Check-result cache
|
|
223
224
|
|
|
@@ -237,7 +238,7 @@ The cache is a working-tree cache, not a build cache: it answers "has this exact
|
|
|
237
238
|
|
|
238
239
|
### What is cached
|
|
239
240
|
|
|
240
|
-
Cacheable by default: `check`, `check:
|
|
241
|
+
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
242
|
|
|
242
243
|
Everything else runs every time:
|
|
243
244
|
|
|
@@ -437,11 +438,10 @@ Neither is bound to a git hook. `prepush` is named for when you run it, not for
|
|
|
437
438
|
|
|
438
439
|
#### Check and quality
|
|
439
440
|
|
|
440
|
-
| Command
|
|
441
|
-
|
|
|
442
|
-
| `check`
|
|
443
|
-
| `check:
|
|
444
|
-
| `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage`, `check:agent-files` |
|
|
441
|
+
| Command | Runs |
|
|
442
|
+
| -------------- | -------------------------------------------------------- |
|
|
443
|
+
| `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
|
|
444
|
+
| `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
|
|
445
445
|
|
|
446
446
|
#### Fix
|
|
447
447
|
|
|
@@ -531,7 +531,6 @@ These scripts operate on root-level code only (not workspace packages):
|
|
|
531
531
|
| Command | Runs |
|
|
532
532
|
| ------------------ | ---------------------- |
|
|
533
533
|
| `report-overrides` | `nmr-report-overrides` |
|
|
534
|
-
| `sync-agent-files` | `nmr-sync-agent-files` |
|
|
535
534
|
|
|
536
535
|
## CLI reference
|
|
537
536
|
|
|
@@ -585,35 +584,33 @@ Report any active `pnpm.overrides` in the root `package.json`, reminding develop
|
|
|
585
584
|
nmr report-overrides
|
|
586
585
|
```
|
|
587
586
|
|
|
588
|
-
|
|
587
|
+
## Agent guidance
|
|
589
588
|
|
|
590
|
-
|
|
589
|
+
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
590
|
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
|
|
591
|
+
```yaml
|
|
592
|
+
packages:
|
|
593
|
+
use:
|
|
594
|
+
- '@williamthorsen/nmr'
|
|
595
595
|
```
|
|
596
596
|
|
|
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.
|
|
597
|
+
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
598
|
|
|
601
|
-
|
|
599
|
+
Wiring the sync to `postinstall` keeps it current without a hand-run command:
|
|
602
600
|
|
|
603
|
-
```
|
|
604
|
-
|
|
601
|
+
```json
|
|
602
|
+
{
|
|
603
|
+
"scripts": {
|
|
604
|
+
"postinstall": "codeassembly sync --warn-only"
|
|
605
|
+
}
|
|
606
|
+
}
|
|
605
607
|
```
|
|
606
608
|
|
|
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:
|
|
609
|
+
`--warn-only` reports a sync failure and exits 0; without it, a failed sync aborts `pnpm install`.
|
|
612
610
|
|
|
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.
|
|
611
|
+
### Migrating from `sync-agent-files`
|
|
615
612
|
|
|
616
|
-
|
|
613
|
+
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
614
|
|
|
618
615
|
## Standalone utilities
|
|
619
616
|
|
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,8 +1,10 @@
|
|
|
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.
|
package/dist/esm/check-cache.js
CHANGED
|
@@ -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.0",
|
|
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
|
],
|
|
@@ -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
|
-
}
|