@cosyte/script-utils 0.1.1 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -1,18 +1,43 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to `@cosyte/script-utils` are documented here, following
4
- [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions are managed with Changesets;
5
- this file is hand-maintained (Changesets' own changelog generation is disabled). The package is on the
6
- **`0.1.x`** line: its surface is settled, and bump types follow ordinary semver rather than a
7
- pre-alpha rule. See
8
- [ADR 0002](../../documentation/decisions/0002-the-0-1-0-version-line.md).
3
+ ## 0.1.3
9
4
 
10
- > Because the generator is disabled, **`[Unreleased]` is promoted to a version heading BY HAND**, in
11
- > the pull request that adds the changeset. Nothing does it automatically.
5
+ ### Patch Changes
12
6
 
13
- ## [Unreleased]
7
+ - f15075b: A new entry point, `@cosyte/script-utils/agent-notes`: the checker for a repository's agent notes, which replaces each library's own. What does not fit on the CLAUDE.md card lives in one file, `documentation/agent-notes.md`, and the card points into it in one form, `documentation/agent-notes.md#<anchor>`. The checker reads every tracked file and requires every pointer to name a heading of the notes (its GitHub slug) or an explicit `<a id>` or `<a name>` target; in CLAUDE.md it also refuses a bare backticked `#anchor` and `agent-notes.md#` without its directory. A notes directory, notes the card never points into, a blank notes file, an empty section and a citation of the umbrella's deleted ADR 0023 or its doc-budget hook are findings. Run it with `node node_modules/@cosyte/script-utils/agent-notes.js`; it exits 0 when clean, 1 with each finding's file, line and rule, and 2 when it cannot run.
8
+ - 7a0f98c: The `attw` gate in `@cosyte/script-utils` reads `npm pack --dry-run --json` in both of npm's shapes: the array npm 10 and 11 print, and the object keyed by package name that npm 12 prints. Before this, npm 12's object read as no tarball at all, and the gate refused every package ("npm described 0 tarball(s)"). It still requires exactly one entry whose name is the package's own, and in the object shape the key must match too; any other document is refused as before. The docblock no longer says every repo's `prepare` is the same one-liner: the parser template's `prepare` printed on stdout, inside the JSON this net reads, and twelve sibling repos still carry that line.
9
+ - 9d56322: A new entry point, `@cosyte/script-utils/catalog`: the one list of our libraries. It holds a record for each of the 14 libraries, the 8 standards (HL7 v2, MLLP, FHIR, C-CDA, X12, NCPDP, ASTM and DICOM) and the 6 built on them (terminology, transform, deid, synth, dates and cli), and one for Pathways, our integration engine, in development and not on npm. Each record is generated from the package's own package.json (name, description, release line, engines, repository, docs and npm URLs) and carries its tier. The data ships as `catalog.json` beside the module, and `CATALOG` is that file, frozen. Every library can run `node node_modules/@cosyte/script-utils/catalog.js` to check that its package.json description is the catalog's: it exits 0 when they agree, 1 with both values when they do not, and 2 when the package is not listed or package.json cannot be read.
10
+ - d0a2576: A new entry point, `@cosyte/script-utils/claude-card`: the checker for the CLAUDE.md card every Cosyte repository keeps. A card opens with `# <name>` and a paragraph, then has exactly three level-2 sections in order, Gate, Hazard and Rules (a heading may go on after its word, as in `## Rules that bind here`), none empty. It is at most 60 lines, every line at most 100 characters with code included, and at most 5,120 bytes, and when an AGENTS.md sits beside it the card imports it on a line of its own (`@AGENTS.md`). Run it with `node node_modules/@cosyte/script-utils/claude-card.js`; it exits 0 when the card follows the spec, 1 with each finding's line and rule, and 2 when CLAUDE.md cannot be read. `checkClaudeCard`, `runClaudeCardGate` and the limits it checks against are exported.
11
+ - a71747a: The attw, PHI-scan and internal-reference gates run from the command line, each with one per-repository file, as the dash gate already did. `node node_modules/@cosyte/script-utils/attw.js` runs the attw gate for the package in the working directory, and `scripts/attw.exclude` selects the profile (`node10` alone for `node16`, with `node16-cjs` for `esm-only`). `node node_modules/@cosyte/script-utils/phi-scan.js` runs the PHI engine over the whole repository with exit codes 0, 1 and 2, `--staged` reading everything but Markdown, and the literal tracked paths in `scripts/phi-scan.exclude` left out; it carries the cross-cutting floor only. `node node_modules/@cosyte/script-utils/internal-refs.js` reads the gate's axes from `scripts/internal-refs.json`, with exit codes 0, 1 and 2. The PHI engine's `--staged` route now passes `--ignore-submodules=none` to git, so a staged gitlink is refused even when the user's git config hides submodules from `git diff`. The internal-reference gate's ADR rule also matches an ADR path segment (`docs/adr/0001-scope.md`) and its phase rule the plural (`Phases 1 to 6`), as ten of the eleven per-repository copies did, and an extra rule's pattern may be a `{ source, flags }` object, so a rule read from JSON can be case-insensitive.
12
+ - bcdb7ea: The README's license line names the legal owner: "MIT, copyright Cosyte LLC." The READMEs of `@cosyte/eslint-config`, `@cosyte/process` and `@cosyte/script-utils` also use US spelling where they did not (license, behavior, generalize, recognized, honored). No code, configuration or type changes.
13
+ - e788688: A new entry point, `@cosyte/script-utils/readme`: the README gate for every `@cosyte/*` library. It checks the house skeleton (the shared lockup tile with the alt text assets declares for it, `# <package name>`, a one-line tagline, the badge row for npm version, CI, license and Node derived from `package.json`, the sections Why this exists, Status, Install, Usage, Support, Security, Contributing and License in that order with none empty, a link to the package's page on docs.cosyte.com between Usage and Support, and License last with an absolute link to the repository's LICENSE) and refuses a README over 60,000 characters, because npm keeps only the first 65,536. Run it with `node node_modules/@cosyte/script-utils/readme.js`; it exits 0 when the README follows the skeleton, 1 with each finding's line and rule, and 2 when it cannot read the package. `checkReadme`, `badgeRow`, `runReadmeGate` and the constants it checks against are exported.
14
14
 
15
- ## [0.1.1] - Unreleased
15
+ ## Released before this file was generated
16
+
17
+ Every release section above this heading is written by
18
+ [Changesets](https://github.com/changesets/changesets) from the changesets in `.changeset/`, newest
19
+ release first. A release writes its own version heading, so nothing above this line is maintained by
20
+ hand: a change is recorded by adding a changeset rather than by editing this file.
21
+
22
+ The sections below were kept by hand, in the
23
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format, until 2026-11-07. A version heading
24
+ that still read "Unreleased" after its version reached npm now carries that date; no entry was
25
+ reworded.
26
+
27
+ ## [0.1.2] - 2026-11-07
28
+
29
+ ### Changed
30
+
31
+ - **`check-no-emdash.sh` refuses the en dash (U+2013) as well as the em dash** (operator decision,
32
+ 2026-11-07). It matches the en dash in the same six forms it already matched for the em dash: the
33
+ literal character, its URL encoding, its JS escape and three HTML entity spellings. Its self-test
34
+ now also proves it matches a known en dash and passes a clean line (hyphens, a range written with
35
+ "to", the minus sign U+2212). Exit codes, modes and the exclusion declaration are unchanged; the
36
+ messages name both dashes. **A tracked file or a PR text carrying an en dash in any of those forms
37
+ now refuses where 0.1.1 printed OK**: write a range with "to", and rewrite a pause with a period,
38
+ a colon, a comma or parentheses.
39
+
40
+ ## [0.1.1] - 2026-09-24
16
41
 
17
42
  ### Fixed
18
43
 
@@ -26,7 +51,7 @@ pre-alpha rule. See
26
51
  still read from the bytes git carries and a hit in them is printed before the refusal; the
27
52
  completeness rule still counts every read, and a root the walk reads is not refused.
28
53
 
29
- ## [0.1.0] - Unreleased
54
+ ## [0.1.0] - 2026-09-21
30
55
 
31
56
  ### Changed
32
57
 
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Cosyte
3
+ Copyright (c) 2026 Cosyte LLC
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -20,7 +20,7 @@ Zero-dependency helpers for the repo-local gate scripts in @cosyte/\* repos.
20
20
 
21
21
  A gate script that exits 0 without having checked anything is worse than no gate, because the run
22
22
  conclusion is the only thing anyone reads. Two ways of producing one turned out to be universal
23
- across the cosyte repositories: an entrypoint guard written by hand that answers `false` for ordinary
23
+ across the Cosyte repositories: an entrypoint guard written by hand that answers `false` for ordinary
24
24
  invocations, and thirteen byte-distinct copies of one PHI scanner, each of which had to be fixed
25
25
  separately when an escape was found.
26
26
 
@@ -30,7 +30,7 @@ request and a version bump.
30
30
 
31
31
  ## Status
32
32
 
33
- `@cosyte/script-utils` is on the cosyte 0.1.x line: the public API is settled and bump types follow ordinary semver.
33
+ `@cosyte/script-utils` is on the Cosyte 0.1.x line: the public API is settled and bump types follow ordinary semver.
34
34
 
35
35
  Still moving: the `runPhiScan` option surface, which is where the per-repo axes below are still being
36
36
  argued about, and the cross-cutting detection floor, which gains patterns as escapes are found. A new
@@ -49,7 +49,7 @@ that a broken or hostile install cannot decide whether a release gate runs.
49
49
 
50
50
  ### If your gate runs before `pnpm install`, do not use the bare specifier
51
51
 
52
- Read this before adopting the package, because it is the one thing that does not generalise.
52
+ Read this before adopting the package, because it is the one thing that does not generalize.
53
53
 
54
54
  A bare `import ... from "@cosyte/script-utils"` resolves through `node_modules`, so it only works
55
55
  once the install has happened. `config`'s own gates run before their install and therefore import
@@ -65,8 +65,9 @@ So check when your gate runs, and pick accordingly:
65
65
  Installing the package does not, on its own, give a pre-install gate the property described above.
66
66
 
67
67
  Every entry point is published as source with no build step, so the relative-path route works for
68
- all three: the files are `index.js`, `phi-scan.js` and `internal-refs.js`, each beside its own
69
- hand-written `.d.ts`. A vendored copy is one of those files and nothing else.
68
+ each: the files are `index.js` and one `<entry>.js` per subpath, each beside its own hand-written
69
+ `.d.ts`. A vendored copy of a gate is that one file: its command-line form loads nothing beside it
70
+ unless node was pointed at a file of the same name.
70
71
 
71
72
  ## Usage
72
73
 
@@ -142,19 +143,110 @@ import { runAttwGate } from "@cosyte/script-utils/attw";
142
143
  typeof runAttwGate; // => "function"
143
144
  ```
144
145
 
146
+ The README gate is the fifth. It checks a library README against the house skeleton and npm's
147
+ size limit, and it reads everything package-specific from `package.json`:
148
+
149
+ ```ts runnable
150
+ import { checkReadme, README_CHARACTER_LIMIT } from "@cosyte/script-utils/readme";
151
+
152
+ const manifest = {
153
+ name: "@cosyte/demo",
154
+ repository: "https://github.com/cosyte/demo",
155
+ engines: { node: ">=22.14" },
156
+ };
157
+
158
+ // A README that is only a title: each finding names the rule it breaks and the line.
159
+ const findings = checkReadme("# @cosyte/demo\n", manifest);
160
+ findings.some((finding) => finding.rule === "tile"); // => true
161
+ README_CHARACTER_LIMIT; // => 60000
162
+ ```
163
+
164
+ The card checker is the sixth. It checks a repository's CLAUDE.md against the card spec: a title,
165
+ a paragraph, then `## Gate`, `## Hazard` and `## Rules`, in at most 60 lines of at most 100
166
+ characters and 5,120 bytes:
167
+
168
+ ```ts runnable
169
+ import { checkClaudeCard, CARD_MAX_LINES } from "@cosyte/script-utils/claude-card";
170
+
171
+ const card =
172
+ "# cosyte/demo\n\nOur demo.\n\n## Gate\n\npnpm test\n\n## Hazard\n\nNone.\n\n## Rules\n\n- One.\n";
173
+ checkClaudeCard(card); // => []
174
+ checkClaudeCard(card.replace("## Hazard", "## Notes")).length > 0; // => true
175
+ CARD_MAX_LINES; // => 60
176
+ ```
177
+
178
+ The catalog is the seventh: one record per library, generated from its package.json, with its
179
+ tier, and one for Pathways. A site reads the list from it, and a library checks its description
180
+ against it:
181
+
182
+ ```ts runnable
183
+ import { CATALOG, catalogEntry } from "@cosyte/script-utils/catalog";
184
+
185
+ CATALOG.libraries.length; // => 14
186
+ CATALOG.libraries.filter((library) => library.tier === "standard").length; // => 8
187
+ catalogEntry("@cosyte/hl7")?.docs; // => "https://docs.cosyte.com/hl7"
188
+ CATALOG.engine.displayName; // => "Pathways"
189
+ ```
190
+
191
+ The agent-notes checker is the eighth. It resolves every pointer into a repository's one notes
192
+ file against that file's headings, slugged the way GitHub slugs them:
193
+
194
+ ```ts runnable
195
+ import { checkAgentNotes, slugify } from "@cosyte/script-utils/agent-notes";
196
+
197
+ slugify("Why `attw` exits 0"); // => "why-attw-exits-0"
198
+
199
+ const claude = "# demo\n\nSee documentation/agent-notes.md#setup.\n";
200
+ const notes = "# Notes\n\n## Setup\n\nRun it.\n";
201
+ checkAgentNotes({ claude, notes, files: [] }); // => []
202
+ checkAgentNotes({ claude, notes: notes.replace("Setup", "Install"), files: [] })[0]?.rule; // => "dangling"
203
+ ```
204
+
145
205
  ## Entry points
146
206
 
147
- | entry point | what it is |
148
- | ------------------------------------ | ------------------------------------------------------------------------ |
149
- | `@cosyte/script-utils` | `isCliEntrypoint(moduleUrl)`, the entry-point guard |
150
- | `@cosyte/script-utils/phi-scan` | `runPhiScan(config)` and `exemptsMarkdown(relPath)`, the shared PHI gate |
151
- | `@cosyte/script-utils/internal-refs` | `runInternalRefsScan(config)`, the shared internal-reference gate |
152
- | `@cosyte/script-utils/attw` | `runAttwGate(options)`, the shared `attw` publish gate |
207
+ | entry point | what it is |
208
+ | ------------------------------------ | -------------------------------------------------------------------------------------------- |
209
+ | `@cosyte/script-utils` | `isCliEntrypoint(moduleUrl)`, the entry-point guard |
210
+ | `@cosyte/script-utils/phi-scan` | `runPhiScan(config)` and `exemptsMarkdown(relPath)`, the shared PHI gate |
211
+ | `@cosyte/script-utils/internal-refs` | `runInternalRefsScan(config)`, the shared internal-reference gate |
212
+ | `@cosyte/script-utils/attw` | `runAttwGate(options)`, the shared `attw` publish gate |
213
+ | `@cosyte/script-utils/readme` | `runReadmeGate(options)` and `checkReadme(text, manifest)`, the README gate |
214
+ | `@cosyte/script-utils/claude-card` | `runClaudeCardGate(options)` and `checkClaudeCard(text)`, the CLAUDE.md card checker |
215
+ | `@cosyte/script-utils/catalog` | `CATALOG`, `catalogEntry(name)` and `runCatalogCheck(options)`, the catalog of our libraries |
216
+ | `@cosyte/script-utils/agent-notes` | `runAgentNotesCheck(options)` and `checkAgentNotes(input)`, the agent-notes checker |
153
217
 
154
218
  They are separate subpaths because they are separately adoptable: a repo can take the entry-point
155
219
  guard without taking a position on PHI scanning, a repo can take the internal-reference gate without
156
220
  taking either, and importing one never loads another.
157
221
 
222
+ ### The four gates from the command line
223
+
224
+ Each shared gate runs from a repository's package script with no file of its own but one input
225
+ (ST34). The dash gate already did; the other three now do too:
226
+
227
+ | gate | command | the one per-repository file |
228
+ | -------------- | ----------------------------------------------------------- | --------------------------------- |
229
+ | em and en dash | `bash node_modules/@cosyte/script-utils/check-no-emdash.sh` | `scripts/check-no-emdash.exclude` |
230
+ | attw | `node node_modules/@cosyte/script-utils/attw.js` | `scripts/attw.exclude` |
231
+ | PHI scan | `node node_modules/@cosyte/script-utils/phi-scan.js` | `scripts/phi-scan.exclude` |
232
+ | internal refs | `node node_modules/@cosyte/script-utils/internal-refs.js` | `scripts/internal-refs.json` |
233
+
234
+ - **attw** runs for the package in the working directory, as the one-line `scripts/attw.mjs`
235
+ caller does. `scripts/attw.exclude` names the resolutions the package leaves out, one per line:
236
+ `node10` alone selects attw's `node16` profile, and `node10` with `node16-cjs` selects `esm-only`.
237
+ It selects a profile and nothing else; any other entry, a repeat, `node16-cjs` alone, or a
238
+ `--profile` argument beside the file is refused at exit 1.
239
+ - **PHI scan** runs the engine with the house settings: exit codes 0 clean, 1 hits, 2 refuse; the
240
+ whole repository as the scan root; `--staged` reading everything but Markdown; the allow-list at
241
+ `scripts/phi-allow-list.txt` and the override log at `phi-scan-overrides.md`. Each line of
242
+ `scripts/phi-scan.exclude` is one literal tracked path (`#` lines are comments); a directory, a
243
+ pattern, a repeat or a path git does not track is refused at exit 2. It carries the
244
+ cross-cutting floor only, so a repository whose standard needs field-level detectors keeps a
245
+ caller passing `detect` until the per-standard detectors ship here.
246
+ - **Internal refs** reads its axes from `scripts/internal-refs.json` (keys starting with `$` are
247
+ notes), with exit codes 0 clean, 1 hits, 2 the gate could not run. The file may not set
248
+ `exitCodes`, `repoRoot` or `write`, which the command-line form owns.
249
+
158
250
  ### The em-dash gate, which is a file rather than a subpath
159
251
 
160
252
  This package also ships `check-no-emdash.sh`, the one implementation of the em-dash gate for every
@@ -172,7 +264,7 @@ bash node_modules/@cosyte/script-utils/check-no-emdash.sh --self-id
172
264
 
173
265
  Exit 0 means the mode completed and found nothing banned. Exit 1 means anything else: a banned form
174
266
  was found, an input could not be read, an input was not scanned, a declaration was unusable, or an
175
- argument was not recognised. There is no third code. Hits and diagnostics go to stderr in every mode.
267
+ argument was not recognized. There is no third code. Hits and diagnostics go to stderr in every mode.
176
268
 
177
269
  `--self-id` prints the sha256 of the copy that is executing, so a repository can prove it is running
178
270
  these bytes rather than a fork of them. `--list-scanned` prints the NUL-separated, repo-root-relative
@@ -183,8 +275,97 @@ A repository narrows WHAT IS SCANNED, and only that, through a tracked file at i
183
275
  `scripts/check-no-emdash.exclude`: one entry per line, `#` comments and blank lines ignored, an entry
184
276
  ending in `/` covering every tracked path beneath it, and every entry anchored at the repository
185
277
  root. An entry that matches no tracked path is refused rather than ignored, because an exclusion that
186
- has outlived its subject is a hole nobody is looking at. Nothing narrows WHAT IS MATCHED: the six
187
- banned forms are fixed in the gate.
278
+ has outlived its subject is a hole nobody is looking at. Nothing narrows WHAT IS MATCHED: the twelve
279
+ banned forms are fixed in the gate. They are the em dash (U+2014) and the en dash (U+2013), each as
280
+ the literal character, its URL encoding, its JS escape and three HTML entity spellings.
281
+
282
+ ### The README gate
283
+
284
+ `@cosyte/script-utils/readme` checks a library's README against the house skeleton, and against a
285
+ size limit under npm's: the registry keeps only the first 65,536 characters of a README, and the
286
+ gate refuses one over 60,000. A library runs it from its package script, with no file of its own:
287
+
288
+ ```sh
289
+ # "readme:check": "node node_modules/@cosyte/script-utils/readme.js"
290
+ node node_modules/@cosyte/script-utils/readme.js # the package in this directory
291
+ node node_modules/@cosyte/script-utils/readme.js --dir <path> # another package directory
292
+ ```
293
+
294
+ The skeleton, in order: the shared lockup tile in a `<picture>` element with the alt text assets
295
+ declares for it; `# <package name>`; a one-line `>` tagline of at most 120 characters; the badge row
296
+ (npm version, CI, license and Node, one per line, derived from `package.json`); then the sections
297
+ `Why this exists`, `Status`, `Install`, `Usage`, `Support`, `Security`, `Contributing` and
298
+ `License`, in that order, none empty, with `License` last and linking the repository's `LICENSE` by
299
+ its absolute URL. A link to the package's page on docs.cosyte.com sits after `## Usage` begins and
300
+ before `## Support`. Other sections may sit between the required ones.
301
+
302
+ Exit 0 means the README follows the skeleton, 1 that it does not (each finding names its line and
303
+ its rule, from `README_RULES`), and 2 that the gate could not read the package: no README, no
304
+ `package.json`, or a manifest without the `@cosyte/*` name, the github.com/cosyte repository URL or
305
+ the `engines.node` the skeleton is derived from. Nothing is configurable, so no setting switches a
306
+ rule off.
307
+
308
+ ### The CLAUDE.md card checker
309
+
310
+ `@cosyte/script-utils/claude-card` checks the CLAUDE.md card every Cosyte repository keeps: what
311
+ must pass before a push, what a change can break that a gate would not show, and the rules that bind
312
+ there. The spec is `documentation/claude-card.md` in cosyte/config. A repository runs it with no file
313
+ of its own:
314
+
315
+ ```sh
316
+ node node_modules/@cosyte/script-utils/claude-card.js # CLAUDE.md in this directory
317
+ node node_modules/@cosyte/script-utils/claude-card.js --dir <path> # another repository
318
+ ```
319
+
320
+ The card opens with `# <name>` and a paragraph, then has exactly three level-2 sections in order,
321
+ `## Gate`, `## Hazard` and `## Rules` (a heading may go on after its word, as in
322
+ `## Rules that bind here`), none empty. It is at most 60 lines, every line at most 100 characters
323
+ with code included, and at most 5,120 bytes. When an AGENTS.md sits beside it, the card imports it on
324
+ a line of its own, `@AGENTS.md`. Exit 0 means the card follows the spec, 1 that it does not (each
325
+ finding names its line and rule, from `CARD_RULES`), and 2 that CLAUDE.md could not be read.
326
+
327
+ ### The catalog
328
+
329
+ `@cosyte/script-utils/catalog` is the one list of our libraries (ST13): the 8 standards (HL7 v2,
330
+ MLLP, FHIR, C-CDA, X12, NCPDP, ASTM and DICOM), the 6 built on them (terminology, transform, deid,
331
+ synth, dates and cli), and Pathways, our integration engine, in development and not on npm. Each
332
+ record is generated from the package's own `package.json` (name, description, release line,
333
+ engines, repository, docs and npm URLs) and carries its tier; the data ships as `catalog.json`
334
+ beside the module, and `CATALOG` is that file, frozen.
335
+
336
+ Each package.json description is that library's one canonical line (ST14), so every library runs
337
+ the check that its description is the catalog's, with no file of its own:
338
+
339
+ ```sh
340
+ node node_modules/@cosyte/script-utils/catalog.js # the package in this directory
341
+ node node_modules/@cosyte/script-utils/catalog.js --dir <path> # another package directory
342
+ ```
343
+
344
+ Exit 0 means the description is the catalog's, 1 that it is not (both values are printed), and 2
345
+ that the check could not run (no package.json, or a package the catalog does not list). A changed
346
+ description therefore goes through the catalog: regenerate it in cosyte/config
347
+ (`node scripts/catalog.mjs write`), release this package, then move the library to that release.
348
+
349
+ ### The agent-notes checker
350
+
351
+ `@cosyte/script-utils/agent-notes` checks a repository's agent notes (RD4): what does not fit on the
352
+ CLAUDE.md card lives in one file, `documentation/agent-notes.md`, and the card points into it in one
353
+ form, `documentation/agent-notes.md#<anchor>`. A repository runs it with no file of its own:
354
+
355
+ ```sh
356
+ node node_modules/@cosyte/script-utils/agent-notes.js # this repository
357
+ node node_modules/@cosyte/script-utils/agent-notes.js --dir <path> # another repository
358
+ ```
359
+
360
+ It reads every file `git ls-files` lists, skipping and counting a binary file or anything that is not
361
+ a regular file. Every pointer in any of them must name a heading of the notes (its GitHub slug) or an
362
+ explicit `<a id>` or `<a name>` target. In CLAUDE.md the pointer must take the one form, not a bare
363
+ backticked `#anchor` or `agent-notes.md#` without its directory. A `documentation/agent-notes/`
364
+ directory is a finding, and so is a notes file CLAUDE.md never points into, a blank one, an empty
365
+ section (a heading followed by a deeper one is a container, not empty), and a citation of the
366
+ umbrella's deleted ADR 0023 or its doc-budget hook. A repository with no notes and no pointers
367
+ passes. Exit 0 means clean, 1 a finding (file, line and rule from `AGENT_NOTES_RULES`), and 2 that
368
+ the check could not run.
188
369
 
189
370
  ## Overrides
190
371
 
@@ -206,7 +387,7 @@ four required, six defaulted, and every per-repository difference supplied by th
206
387
  will not accept is a configuration that SUBTRACTS. The canonical rule set, the self-test floor and
207
388
  every completeness refusal are not caller inputs, and an option that reaches for one of them is
208
389
  refused by name rather than ignored. So is an option the gate has never heard of: an ignored setting
209
- reads, from the caller's side, exactly like an honoured one.
390
+ reads, from the caller's side, exactly like an honored one.
210
391
 
211
392
  `runAttwGate` takes the narrowest bargain of the four, and deliberately. A caller supplies where it
212
393
  lives (`callerUrl`, required, no default) and may supply the arguments to forward; everything else
@@ -246,7 +427,7 @@ Is this module the file Node was pointed at, rather than one imported by somethi
246
427
 
247
428
  #### Why not compare the strings
248
429
 
249
- The obvious spelling is a one-liner, and several cosyte repos wrote it by hand:
430
+ The obvious spelling is a one-liner, and several Cosyte repos wrote it by hand:
250
431
 
251
432
  ```js
252
433
  // Do not do this.
@@ -490,4 +671,4 @@ something. Never open an issue containing a real PHI value; describe the shape i
490
671
 
491
672
  ## License
492
673
 
493
- MIT, copyright Cosyte. See [LICENSE](https://github.com/cosyte/config/blob/main/LICENSE).
674
+ MIT, copyright Cosyte LLC. See [LICENSE](https://github.com/cosyte/config/blob/main/LICENSE).
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Types for `@cosyte/script-utils/agent-notes`: the checker for a repository's agent notes, one
3
+ * file at `documentation/agent-notes.md` that CLAUDE.md points into as
4
+ * `documentation/agent-notes.md#<anchor>` (RD4).
5
+ */
6
+
7
+ /** The one place the notes live. */
8
+ export declare const NOTES_PATH: "documentation/agent-notes.md";
9
+
10
+ /** A rule id a finding can carry. */
11
+ export type AgentNotesRule =
12
+ | "layout"
13
+ | "pointer-form"
14
+ | "dangling"
15
+ | "notes-missing"
16
+ | "no-pointer"
17
+ | "blank"
18
+ | "empty-section"
19
+ | "stale-citation";
20
+
21
+ /** Every rule id, with what it requires. */
22
+ export declare const AGENT_NOTES_RULES: Readonly<Record<AgentNotesRule, string>>;
23
+
24
+ /** Raised when the check cannot run; the CLI turns it into exit 2. */
25
+ export declare class AgentNotesRefusal extends Error {}
26
+
27
+ /** GitHub's heading slug, as github-slugger writes it. */
28
+ export declare function slugify(text: string): string;
29
+
30
+ /** A slugger that suffixes repeats as GitHub does: the second `Setup` is `setup-1`. */
31
+ export declare function makeSlugger(): (text: string) => string;
32
+
33
+ /** One heading of the notes. */
34
+ export interface NotesHeading {
35
+ level: number;
36
+ text: string;
37
+ /** 0-based line. */
38
+ line: number;
39
+ slug: string;
40
+ /** Nothing under it, and not a container followed by a deeper heading. */
41
+ empty: boolean;
42
+ }
43
+
44
+ /**
45
+ * The anchors a notes file offers (heading slugs and explicit `<a id>` or `<a name>` targets) and
46
+ * its headings.
47
+ *
48
+ * @throws {AgentNotesRefusal} On an HTML comment that never closes.
49
+ */
50
+ export declare function readNotes(text: string): { targets: Set<string>; headings: NotesHeading[] };
51
+
52
+ /** One place the notes or a pointer depart from RD4. */
53
+ export interface AgentNotesFinding {
54
+ rule: AgentNotesRule;
55
+ file: string;
56
+ /** 1-based line, or 0 for the whole file. */
57
+ line: number;
58
+ message: string;
59
+ }
60
+
61
+ export interface AgentNotesInput {
62
+ /** CLAUDE.md's contents, or `null` when the repository has none. */
63
+ claude: string | null;
64
+ /** The notes' contents, or `null` when the repository has none. */
65
+ notes: string | null;
66
+ /** Every other tracked text file, scanned for pointers. */
67
+ files: { path: string; text: string }[];
68
+ /** Whether a `documentation/agent-notes/` directory is tracked. */
69
+ notesDirectory?: boolean;
70
+ /** Whether the repository has its own `documentation/decisions/0023-*`. */
71
+ localDecision0023?: boolean;
72
+ }
73
+
74
+ /**
75
+ * Check the notes and every pointer into them.
76
+ *
77
+ * @throws {AgentNotesRefusal} When the notes cannot be read to the end.
78
+ */
79
+ export declare function checkAgentNotes(input: AgentNotesInput): AgentNotesFinding[];
80
+
81
+ export interface AgentNotesCheckOptions {
82
+ /** The repository root. Default: the working directory. */
83
+ dir?: string;
84
+ /** CLI arguments; only `--dir <path>` is accepted. */
85
+ argv?: string[];
86
+ }
87
+
88
+ /**
89
+ * Run the checker over one repository's tracked files and return the exit code: 0 clean, 1 at
90
+ * least one finding, 2 the check could not run. It never exits the process.
91
+ */
92
+ export declare function runAgentNotesCheck(options?: AgentNotesCheckOptions): 0 | 1 | 2;