@cosyte/script-utils 0.1.2 → 0.1.4

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,36 @@
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.4
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
+ - b9b2a7c: Every gate is now a bin, so a repository with no `package.json` can run it through npx: `cosyte-no-emdash`, `cosyte-attw`, `cosyte-phi-scan`, `cosyte-internal-refs`, `cosyte-readme`, `cosyte-claude-card`, `cosyte-agent-notes` and `cosyte-catalog`, each with `--help`, and `script-utils`, the dispatcher npx runs by the package's name (`npx -y @cosyte/script-utils@0.1 no-emdash`). The dash gate also matches every form the marketing and consulting scanner caught, in any case: the characters, URL encoding, the backslash-u escape (plain, braced and eight-digit), the braced backslash-x escape, the CSS escape, the Unicode name, and the named, decimal and hexadecimal HTML entities with or without the semicolon and with leading zeros. It now reads every tracked file's name as well as its content, and answers `--help`. A tracked file or name carrying one of the newly matched forms is refused where 0.1.3 printed OK.
14
8
 
15
- ## [0.1.2] - Unreleased
9
+ ## 0.1.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 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.
14
+ - 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.
15
+ - 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.
16
+ - 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.
17
+ - 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.
18
+ - 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.
19
+ - 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.
20
+
21
+ ## Released before this file was generated
22
+
23
+ Every release section above this heading is written by
24
+ [Changesets](https://github.com/changesets/changesets) from the changesets in `.changeset/`, newest
25
+ release first. A release writes its own version heading, so nothing above this line is maintained by
26
+ hand: a change is recorded by adding a changeset rather than by editing this file.
27
+
28
+ The sections below were kept by hand, in the
29
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format, until 2026-11-07. A version heading
30
+ that still read "Unreleased" after its version reached npm now carries that date; no entry was
31
+ reworded.
32
+
33
+ ## [0.1.2] - 2026-11-07
16
34
 
17
35
  ### Changed
18
36
 
@@ -25,7 +43,7 @@ pre-alpha rule. See
25
43
  now refuses where 0.1.1 printed OK**: write a range with "to", and rewrite a pause with a period,
26
44
  a colon, a comma or parentheses.
27
45
 
28
- ## [0.1.1] - Unreleased
46
+ ## [0.1.1] - 2026-09-24
29
47
 
30
48
  ### Fixed
31
49
 
@@ -39,7 +57,7 @@ pre-alpha rule. See
39
57
  still read from the bytes git carries and a hit in them is printed before the refusal; the
40
58
  completeness rule still counts every read, and a root the walk reads is not refused.
41
59
 
42
- ## [0.1.0] - Unreleased
60
+ ## [0.1.0] - 2026-09-21
43
61
 
44
62
  ### Changed
45
63
 
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
@@ -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,135 @@ 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
+
250
+ ### The bins, for a repository with no package.json
251
+
252
+ Every gate is also a bin (D56), so a repository with no `package.json`, or none that depends on this
253
+ package, runs it through npx. `script-utils` is named after the package, so npx runs it by the
254
+ package's name and it dispatches to the gate named first:
255
+
256
+ ```sh
257
+ npx -y @cosyte/script-utils@0.1 no-emdash # the dispatcher: script-utils <command>
258
+ npx -y -p @cosyte/script-utils@0.1 cosyte-readme # or one gate's own bin
259
+ ```
260
+
261
+ | bin | `script-utils` command | the gate |
262
+ | ---------------------- | ---------------------- | --------------------------------------------- |
263
+ | `cosyte-no-emdash` | `no-emdash` | the em and en dash gate, over files and names |
264
+ | `cosyte-attw` | `attw` | the attw publish gate, for the package here |
265
+ | `cosyte-phi-scan` | `phi-scan` | the PHI commit-gate |
266
+ | `cosyte-internal-refs` | `internal-refs` | the internal-reference gate |
267
+ | `cosyte-readme` | `readme` | the README gate |
268
+ | `cosyte-claude-card` | `claude-card` | the CLAUDE.md card checker |
269
+ | `cosyte-agent-notes` | `agent-notes` | the agent-notes checker |
270
+ | `cosyte-catalog` | `catalog` | the catalog check |
271
+
272
+ Each runs over the working directory, takes the arguments its gate takes, exits with the gate's own
273
+ code, and prints what it does with `--help`. `script-utils` with no command prints the list and exits 2.
274
+
158
275
  ### The em-dash gate, which is a file rather than a subpath
159
276
 
160
277
  This package also ships `check-no-emdash.sh`, the one implementation of the em-dash gate for every
@@ -172,7 +289,7 @@ bash node_modules/@cosyte/script-utils/check-no-emdash.sh --self-id
172
289
 
173
290
  Exit 0 means the mode completed and found nothing banned. Exit 1 means anything else: a banned form
174
291
  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.
292
+ argument was not recognized. There is no third code. Hits and diagnostics go to stderr in every mode.
176
293
 
177
294
  `--self-id` prints the sha256 of the copy that is executing, so a repository can prove it is running
178
295
  these bytes rather than a fork of them. `--list-scanned` prints the NUL-separated, repo-root-relative
@@ -183,9 +300,101 @@ A repository narrows WHAT IS SCANNED, and only that, through a tracked file at i
183
300
  `scripts/check-no-emdash.exclude`: one entry per line, `#` comments and blank lines ignored, an entry
184
301
  ending in `/` covering every tracked path beneath it, and every entry anchored at the repository
185
302
  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 twelve
187
- banned forms are fixed in the gate. They are the em dash (U+2014) and the en dash (U+2013), each as
188
- the literal character, its URL encoding, its JS escape and three HTML entity spellings.
303
+ has outlived its subject is a hole nobody is looking at; an excluded path's name is left out with its
304
+ content. Nothing narrows WHAT IS MATCHED: the banned forms are fixed in the gate. They are the em
305
+ dash (U+2014) and the en dash (U+2013), in any case, each as the literal character, its URL encoding,
306
+ its backslash-u escape (plain, braced and eight-digit), its braced backslash-x escape, its CSS escape
307
+ (plain and zero-padded), its Unicode name escape (a backslash, `N` and the character's name in braces), and its named, decimal and hexadecimal
308
+ HTML entities, with or without the semicolon and with leading zeros. The default scan also reads
309
+ every tracked file's NAME, because a name is copy too.
310
+
311
+ ### The README gate
312
+
313
+ `@cosyte/script-utils/readme` checks a library's README against the house skeleton, and against a
314
+ size limit under npm's: the registry keeps only the first 65,536 characters of a README, and the
315
+ gate refuses one over 60,000. A library runs it from its package script, with no file of its own:
316
+
317
+ ```sh
318
+ # "readme:check": "node node_modules/@cosyte/script-utils/readme.js"
319
+ node node_modules/@cosyte/script-utils/readme.js # the package in this directory
320
+ node node_modules/@cosyte/script-utils/readme.js --dir <path> # another package directory
321
+ ```
322
+
323
+ The skeleton, in order: the shared lockup tile in a `<picture>` element with the alt text assets
324
+ declares for it; `# <package name>`; a one-line `>` tagline of at most 120 characters; the badge row
325
+ (npm version, CI, license and Node, one per line, derived from `package.json`); then the sections
326
+ `Why this exists`, `Status`, `Install`, `Usage`, `Support`, `Security`, `Contributing` and
327
+ `License`, in that order, none empty, with `License` last and linking the repository's `LICENSE` by
328
+ its absolute URL. A link to the package's page on docs.cosyte.com sits after `## Usage` begins and
329
+ before `## Support`. Other sections may sit between the required ones.
330
+
331
+ Exit 0 means the README follows the skeleton, 1 that it does not (each finding names its line and
332
+ its rule, from `README_RULES`), and 2 that the gate could not read the package: no README, no
333
+ `package.json`, or a manifest without the `@cosyte/*` name, the github.com/cosyte repository URL or
334
+ the `engines.node` the skeleton is derived from. Nothing is configurable, so no setting switches a
335
+ rule off.
336
+
337
+ ### The CLAUDE.md card checker
338
+
339
+ `@cosyte/script-utils/claude-card` checks the CLAUDE.md card every Cosyte repository keeps: what
340
+ must pass before a push, what a change can break that a gate would not show, and the rules that bind
341
+ there. The spec is `documentation/claude-card.md` in cosyte/config. A repository runs it with no file
342
+ of its own:
343
+
344
+ ```sh
345
+ node node_modules/@cosyte/script-utils/claude-card.js # CLAUDE.md in this directory
346
+ node node_modules/@cosyte/script-utils/claude-card.js --dir <path> # another repository
347
+ ```
348
+
349
+ The card opens with `# <name>` and a paragraph, then has exactly three level-2 sections in order,
350
+ `## Gate`, `## Hazard` and `## Rules` (a heading may go on after its word, as in
351
+ `## Rules that bind here`), none empty. It is at most 60 lines, every line at most 100 characters
352
+ with code included, and at most 5,120 bytes. When an AGENTS.md sits beside it, the card imports it on
353
+ a line of its own, `@AGENTS.md`. Exit 0 means the card follows the spec, 1 that it does not (each
354
+ finding names its line and rule, from `CARD_RULES`), and 2 that CLAUDE.md could not be read.
355
+
356
+ ### The catalog
357
+
358
+ `@cosyte/script-utils/catalog` is the one list of our libraries (ST13): the 8 standards (HL7 v2,
359
+ MLLP, FHIR, C-CDA, X12, NCPDP, ASTM and DICOM), the 6 built on them (terminology, transform, deid,
360
+ synth, dates and cli), and Pathways, our integration engine, in development and not on npm. Each
361
+ record is generated from the package's own `package.json` (name, description, release line,
362
+ engines, repository, docs and npm URLs) and carries its tier; the data ships as `catalog.json`
363
+ beside the module, and `CATALOG` is that file, frozen.
364
+
365
+ Each package.json description is that library's one canonical line (ST14), so every library runs
366
+ the check that its description is the catalog's, with no file of its own:
367
+
368
+ ```sh
369
+ node node_modules/@cosyte/script-utils/catalog.js # the package in this directory
370
+ node node_modules/@cosyte/script-utils/catalog.js --dir <path> # another package directory
371
+ ```
372
+
373
+ Exit 0 means the description is the catalog's, 1 that it is not (both values are printed), and 2
374
+ that the check could not run (no package.json, or a package the catalog does not list). A changed
375
+ description therefore goes through the catalog: regenerate it in cosyte/config
376
+ (`node scripts/catalog.mjs write`), release this package, then move the library to that release.
377
+
378
+ ### The agent-notes checker
379
+
380
+ `@cosyte/script-utils/agent-notes` checks a repository's agent notes (RD4): what does not fit on the
381
+ CLAUDE.md card lives in one file, `documentation/agent-notes.md`, and the card points into it in one
382
+ form, `documentation/agent-notes.md#<anchor>`. A repository runs it with no file of its own:
383
+
384
+ ```sh
385
+ node node_modules/@cosyte/script-utils/agent-notes.js # this repository
386
+ node node_modules/@cosyte/script-utils/agent-notes.js --dir <path> # another repository
387
+ ```
388
+
389
+ It reads every file `git ls-files` lists, skipping and counting a binary file or anything that is not
390
+ a regular file. Every pointer in any of them must name a heading of the notes (its GitHub slug) or an
391
+ explicit `<a id>` or `<a name>` target. In CLAUDE.md the pointer must take the one form, not a bare
392
+ backticked `#anchor` or `agent-notes.md#` without its directory. A `documentation/agent-notes/`
393
+ directory is a finding, and so is a notes file CLAUDE.md never points into, a blank one, an empty
394
+ section (a heading followed by a deeper one is a container, not empty), and a citation of the
395
+ umbrella's deleted ADR 0023 or its doc-budget hook. A repository with no notes and no pointers
396
+ passes. Exit 0 means clean, 1 a finding (file, line and rule from `AGENT_NOTES_RULES`), and 2 that
397
+ the check could not run.
189
398
 
190
399
  ## Overrides
191
400
 
@@ -207,7 +416,7 @@ four required, six defaulted, and every per-repository difference supplied by th
207
416
  will not accept is a configuration that SUBTRACTS. The canonical rule set, the self-test floor and
208
417
  every completeness refusal are not caller inputs, and an option that reaches for one of them is
209
418
  refused by name rather than ignored. So is an option the gate has never heard of: an ignored setting
210
- reads, from the caller's side, exactly like an honoured one.
419
+ reads, from the caller's side, exactly like an honored one.
211
420
 
212
421
  `runAttwGate` takes the narrowest bargain of the four, and deliberately. A caller supplies where it
213
422
  lives (`callerUrl`, required, no default) and may supply the arguments to forward; everything else
@@ -491,4 +700,4 @@ something. Never open an issue containing a real PHI value; describe the shape i
491
700
 
492
701
  ## License
493
702
 
494
- MIT, copyright Cosyte. See [LICENSE](https://github.com/cosyte/config/blob/main/LICENSE).
703
+ 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;