@cosyte/script-utils 0.1.5 → 0.1.7

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,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 9d0d69f: Deprecated: the agent-notes checker, `@cosyte/script-utils/agent-notes` and its bin `cosyte-agent-notes`. The agent notes, `documentation/agent-notes.md`, were removed from every Cosyte repository (D62), so remove the package script or CI step that runs it. Until a minor release removes the entry point and the bin, a repository with no notes file (not tracked, or deleted from the working tree) passes: the checker exits 0 with one line, `agent notes: OK, no agent notes (retired, D62)`, where it used to fail on a pointer left in the card, a source comment or a changelog. A repository that still has the file is checked as before, and a failing run ends with a line saying the notes are retired. `--help`, the `script-utils` command list, the declarations and the README say it is deprecated.
8
+ - 9d0d69f: The CLAUDE.md card checker no longer sends detail to `documentation/agent-notes.md`, which was removed from every repository (D62). A card over 5,120 bytes, or with a level-2 heading other than Gate, Hazard and Rules, is told to move the detail to where it is used: a test, a comment at the code it guards, an ADR, the README or docs, or a runbook. What the checker checks, and its exit codes, are unchanged.
9
+
10
+ ## 0.1.6
11
+
12
+ ### Patch Changes
13
+
14
+ - 239ac9a: The catalog check a library runs is now advisory. `cosyte-catalog` (and `catalog.js`) still compares the package.json description with the catalog this release carries, but a disagreement, or a package the catalog does not list, prints both values as a note and exits 0: a library that changes its description is no longer held red until the next script-utils release. `--strict` (or `{ strict: true }`) restores the gate, 1 for a disagreement and 2 for an unlisted package; 2 still means the check could not run. Keeping the catalog fresh moves to cosyte/config, which compares it with every library's latest release on npm (its description, release line and engines range) nightly and before each config release, and regenerates it from there.
15
+ - 4e30735: The dash gate reads a changelog's dated archive the way the repositories that keep one already do. A line in `scripts/check-no-emdash.exclude` of the form `CHANGELOG.md em-only below ## Released before this file was generated` reads the file from that exact line to its end for the em dash alone, in every form, so an en dash in an old range passes while an em dash still reds; `CHANGELOG.md unread below <line>` does not read the archive at all. Everything above the line is read for both dashes as before, the file's name is still scanned, and a boundary line the file no longer carries is refused rather than ignored. The test suite now spells out every encoded form terminology's own scanner carried (the braced escape, lower-case URL encoding, entities with no semicolon or with leading zeros, and the upper-case X), all of which 0.1.4 already refused. The internal-refs slice rule no longer counts `the` or `each` as a determiner: a fixed-width reader's field slice (`the field slices`, `the slice holding the concept code`) is the reader's vocabulary. `this`, `that`, `another`, `previous`, `next`, `final` and `current` still red, so `the final slice` and `the next slice` are still hits, but a bare `the slice` and `each slice` no longer are.
16
+ - 409ef6c: Under CI, every gate's `--help` and `-h` now exit non-zero instead of 0: the bins exit 2 and the dash gate exits 1, after printing the help to stderr with a line saying why. Help checks nothing, so a package script such as `"attw": "cosyte-attw --help"` used to pass a required check having run no gate. CI is read the way the common runners set it: `CI` set to anything but an empty string, `false` or `0`. At a terminal `--help` prints to stdout and exits 0 as before.
17
+ - 91f8d76: The internal-refs canonical rules take what generalizes from dicom's own gate. An umbrella spec id (`S0228-github-profile-ci-drift`, never a HCPCS code such as `S9083`) is an identifier. The phase rule also catches the adjective forms (`in subsequent phases`, `a later wave`, `waves 2 and 3`) while letting the clinical senses through (`a later phase of the disease`), and a citation of the roadmap (`tracked by the roadmap`). The ADR rule reads the plural, `#` and two-digit forms (`ADRs 0015`, `ADR #0018`, `ADR 23`). The jargon rule catches process commentary (`a later PR`, `this changeset`, `out of scope for this change`) but not `this spec`, `this session` or `this port`, which are reader words in a parser, and lets an image slice in its volume through. The repo-path rule catches the planning files a session writes (`CONTEXT.md`, `REQUIREMENTS.md`, `PITFALLS.md`), the meta-repo or umbrella repo named as a place, and its `work/` and `cards/` directories, and a `decisions/NNNN-` path wherever it sits. Measured on the pinned libraries, this adds 12 hits, all of them real (in hl7, x12 and ncpdp), and removes none. dicom's decision-number, issue-number, `WORD-N` and bare section rules stay dicom's: each collides with reference material another library publishes.
18
+ - d75579c: The internal-refs gate gains a source string-literal pass, `sourceStringLiterals: { enabled, paths, extraRules }`, off by default like the doc-comment pass. It reads the double-quoted, single-quoted and template literals in the tracked source under `paths`, decoded, plus the literals on each line joined, because a string literal reaches a consumer as error and warning text. It never reads a comment, so an item identifier in a `//` note or a `/* */` block stays allowed, and it skips regular-expression literals. In both source passes a NUL byte now reads as a space instead of refusing the file: a raw separator inside a literal is legitimate source, and a space keeps the word boundary beside it. The public-surface pass still refuses a NUL-bearing target.
19
+ - d30eca0: The PHI engine reads a JavaScript or TypeScript source as the document its string literals spell, as well as its bytes. A literal can spell a dashed identifier's separators as `-`, `\u{2D}` or `\x2D`, and the raw floor read straight past it. Each `.ts`, `.js`, `.mts`, `.cts`, `.mjs`, `.cjs`, `.tsx` and `.jsx` target is now also decoded (an escaped backslash first, so a doubled backslash stays text), and the floor and the caller's `detect` run over that view too, so a field-level detector can parse a record whose separators are escapes. The decoded view adds only what the raw view did not report, and says `escape-encoded in a source literal` on the hit. It is a view, not a parser, so it also decodes a `String.raw` template or a comment quoting an escape; an `ID <value>` declaration in the allow-list clears such a hit. `decodeSourceLiterals` and `isSourceLiteralContainer` are exported.
20
+
3
21
  ## 0.1.5
4
22
 
5
23
  ### Patch Changes
package/README.md CHANGED
@@ -188,20 +188,6 @@ catalogEntry("@cosyte/hl7")?.docs; // => "https://docs.cosyte.com/hl7"
188
188
  CATALOG.engine.displayName; // => "Pathways"
189
189
  ```
190
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
-
205
191
  ## Entry points
206
192
 
207
193
  | entry point | what it is |
@@ -213,7 +199,7 @@ checkAgentNotes({ claude, notes: notes.replace("Setup", "Install"), files: [] })
213
199
  | `@cosyte/script-utils/readme` | `runReadmeGate(options)` and `checkReadme(text, manifest)`, the README gate |
214
200
  | `@cosyte/script-utils/claude-card` | `runClaudeCardGate(options)` and `checkClaudeCard(text)`, the CLAUDE.md card checker |
215
201
  | `@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 |
202
+ | `@cosyte/script-utils/agent-notes` | deprecated (D62): `runAgentNotesCheck(options)` and `checkAgentNotes(input)` |
217
203
 
218
204
  They are separate subpaths because they are separately adoptable: a repo can take the entry-point
219
205
  guard without taking a position on PHI scanning, a repo can take the internal-reference gate without
@@ -258,19 +244,22 @@ npx -y @cosyte/script-utils@0.1 no-emdash # the dispatcher: script-util
258
244
  npx -y -p @cosyte/script-utils@0.1 cosyte-readme # or one gate's own bin
259
245
  ```
260
246
 
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 |
247
+ | bin | `script-utils` command | the gate |
248
+ | ---------------------- | ---------------------- | ------------------------------------------------- |
249
+ | `cosyte-no-emdash` | `no-emdash` | the em and en dash gate, over files and names |
250
+ | `cosyte-attw` | `attw` | the attw publish gate, for the package here |
251
+ | `cosyte-phi-scan` | `phi-scan` | the PHI commit-gate |
252
+ | `cosyte-internal-refs` | `internal-refs` | the internal-reference gate |
253
+ | `cosyte-readme` | `readme` | the README gate |
254
+ | `cosyte-claude-card` | `claude-card` | the CLAUDE.md card checker |
255
+ | `cosyte-agent-notes` | `agent-notes` | deprecated (D62): the retired agent-notes checker |
256
+ | `cosyte-catalog` | `catalog` | the catalog check |
271
257
 
272
258
  Each runs over the working directory, takes the arguments its gate takes, exits with the gate's own
273
259
  code, and prints what it does with `--help`. `script-utils` with no command prints the list and exits 2.
260
+ Under CI (`CI` set to anything but empty, `false` or `0`, as GitHub Actions sets it) `--help` and
261
+ `-h` print to stderr and exit non-zero (2, or 1 for the dash gate, whose only codes are 0 and 1): help
262
+ checks nothing, so a package script that ends in `--help` must not pass a required check.
274
263
 
275
264
  ### The em-dash gate, which is a file rather than a subpath
276
265
 
@@ -308,6 +297,16 @@ its backslash-u escape (plain, braced and eight-digit), its braced backslash-x e
308
297
  HTML entities, with or without the semicolon and with leading zeros. The default scan also reads
309
298
  every tracked file's NAME, because a name is copy too.
310
299
 
300
+ A changelog whose dated archive predates the generated part names that archive with a boundary line
301
+ in the same file, `CHANGELOG.md em-only below ## Released before this file was generated` or
302
+ `CHANGELOG.md unread below ## Released before this file was generated`. The text after `below` is a
303
+ whole line of that file, matched exactly; from it to the end of the file is the archive. `em-only`
304
+ reads the archive for the em dash alone, in every form, so an en dash in an old range passes and an em
305
+ dash still fails; `unread` does not read the archive. Above the line the file is read for both dashes
306
+ like any other, and its name is scanned as always. A boundary line the file no longer carries is
307
+ refused, so a lost heading fails closed, and a file cannot be both left out whole and given a
308
+ boundary.
309
+
311
310
  ### The README gate
312
311
 
313
312
  `@cosyte/script-utils/readme` checks a library's README against the house skeleton, and against a
@@ -362,40 +361,56 @@ record is generated from the package's own `package.json` (name, description, re
362
361
  engines, repository, docs and npm URLs) and carries its tier; the data ships as `catalog.json`
363
362
  beside the module, and `CATALOG` is that file, frozen.
364
363
 
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:
364
+ Each package.json description is that library's one canonical line (ST14). Two sides check it, and
365
+ each checks one thing:
366
+
367
+ - **cosyte/config keeps the catalog fresh.** `node scripts/catalog.mjs check --npm` compares the
368
+ committed catalog with every library's latest release on npm. It runs nightly and before each
369
+ config release, and fails when a library has released a description, release line or engines
370
+ range the catalog does not carry; `write --npm` regenerates it. npm keeps 255 characters of a
371
+ description, so a longer one is fresh when npm holds its first 255, and the run notes it.
372
+ - **A library can check its own line, advisory.** With no file of its own:
367
373
 
368
374
  ```sh
369
375
  node node_modules/@cosyte/script-utils/catalog.js # the package in this directory
370
376
  node node_modules/@cosyte/script-utils/catalog.js --dir <path> # another package directory
377
+ node node_modules/@cosyte/script-utils/catalog.js --strict # a disagreement fails
371
378
  ```
372
379
 
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.
380
+ It compares the package.json description with the catalog this release of `@cosyte/script-utils`
381
+ carries. A disagreement, or a package the catalog does not list, prints both values as a note and
382
+ exits 0: a library that changes its description is not held red until the next script-utils
383
+ release, because config's check against npm picks the change up once the library is released. Exit
384
+ 2 means the check could not run (no package.json, or an argument it does not accept). `--strict`
385
+ restores the gate: 1 for a disagreement, 2 for an unlisted package.
386
+
387
+ ### The agent-notes checker, deprecated
388
+
389
+ `@cosyte/script-utils/agent-notes` and its bin, `cosyte-agent-notes`, are deprecated. They check a
390
+ repository's agent notes, `documentation/agent-notes.md`, and every
391
+ `documentation/agent-notes.md#<anchor>` pointer into them. On 2026-11-08 the notes were removed from
392
+ every Cosyte repository (D62): a CLAUDE.md card stands on its own, and what does not fit on it goes
393
+ where it is used, in a test, a comment at the code it guards, an ADR, the README or docs, or a
394
+ runbook (`documentation/claude-card.md` in cosyte/config). Remove the package script or CI step that
395
+ runs the checker.
396
+
397
+ Until a minor release removes them, the entry point and the bin keep a repository that still runs
398
+ the checker green while its notes are removed. With no `documentation/agent-notes.md` (not tracked,
399
+ or deleted from the working tree) the checker exits 0 with one line,
400
+ `agent notes: OK, no agent notes (retired, D62)`, whatever still points at the file. A repository
401
+ that still has the file is checked as before: exit 0 clean, 1 a finding (file, line and rule from
402
+ `AGENT_NOTES_RULES`, then a line saying the notes are retired), and 2 that the check could not run.
377
403
 
378
- ### The agent-notes checker
404
+ ```ts runnable
405
+ import { checkAgentNotes, NOTES_PATH } from "@cosyte/script-utils/agent-notes";
379
406
 
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:
407
+ // The file it checks, which no repository keeps any more.
408
+ NOTES_PATH; // => "documentation/agent-notes.md"
383
409
 
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
410
+ // A card with no notes and nothing pointing at them has no findings.
411
+ checkAgentNotes({ claude: "# demo\n\nOur demo.\n", notes: null, files: [] }); // => []
387
412
  ```
388
413
 
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.
398
-
399
414
  ## Overrides
400
415
 
401
416
  `isCliEntrypoint` has no options and cannot be overridden. It answers one question and its
@@ -528,6 +543,13 @@ every `scanRoots` entry yielded a file the WALK read, naming the starved roots);
528
543
  cross-cutting detection floor that finds a dashed SSN shape and an email at an undeclared domain over
529
544
  every file, always.
530
545
 
546
+ **A JavaScript or TypeScript source is read twice**: as its bytes, and as the document its string
547
+ literals spell, with `\uXXXX`, `\u{...}`, `\xHH` and the single-character escapes decoded (an
548
+ escaped backslash first, so a doubled backslash before `u002D` stays text). The floor and your
549
+ `detect` run over both views, and the decoded view adds only what the raw view did not report, with
550
+ `, escape-encoded in a source literal` on its reason. It is a view, not a parser, so it also decodes a
551
+ `String.raw` template or a comment quoting an escape; an `ID <value>` declaration clears such a hit.
552
+
531
553
  **The RULES, local to your repo, never moved and never shipped in this package.**
532
554
  `scripts/phi-allow-list.txt` (the positive declaration that a token is synthetic, tagged `NAME`,
533
555
  `DOB`, `ADDR`, `ID`, `EMAILDOMAIN`); `phi-scan-overrides.md` (the `--allow-fixture` audit log, and
@@ -593,7 +615,7 @@ still refuses such an entry on both of its routes.
593
615
  `@cosyte/script-utils/internal-refs` keeps our own bookkeeping off every surface a consumer reads:
594
616
  item identifiers, phase and wave language, ADR numbers, internal repository paths and traceability
595
617
  markers, on the pages, the npm metadata and, optionally, the doc comments that compile into shipped
596
- type declarations.
618
+ type declarations and the string literals a consumer reads as error and warning text.
597
619
 
598
620
  ```ts
599
621
  import { runInternalRefsScan } from "@cosyte/script-utils/internal-refs";
@@ -605,6 +627,7 @@ process.exit(
605
627
  surfacePaths: ["README.md", "TRADEMARKS.md", "LICENSE", "docs-content"],
606
628
  accountedTarballFiles: ["CHANGELOG.md", "dist"],
607
629
  sourceDocComments: { enabled: true, paths: ["src/*.ts", "src/**/*.ts"] },
630
+ sourceStringLiterals: { enabled: true, paths: ["src/*.ts", "src/**/*.ts"] },
608
631
  }),
609
632
  );
610
633
  ```
@@ -632,14 +655,23 @@ gets wrong, and a default would be one repository's answer imposed on every othe
632
655
  | 3 Public surface | `surfacePaths` | **Required**, empty refused, every entry must be tracked. |
633
656
  | 4 Tarball accounting | `accountedTarballFiles` | **Required.** The `files` entries already accounted for. |
634
657
  | 5 Source doc comments | `sourceDocComments` | Defaulted OFF. Enable where the build copies doc text verbatim. |
635
- | 6 Exit codes | `exitCodes` | Defaulted `{ clean: 0, hits: 1, refuse: 1 }`. `clean` must stay 0. |
636
- | 7 Added detection | `extraRules` | Defaulted `[]`. Each added rule brings its own samples. |
637
- | 8 Added self-test material | `extraSamples` | Defaulted `{}`. Additive only. |
638
- | 9 Repository under test | `repoRoot` | Defaulted to the working directory, anchored at its git top level. |
639
- | 10 Report destination | `write` | Defaulted to the process streams. |
658
+ | 6 Source string literals | `sourceStringLiterals` | Defaulted OFF. Enable where messages reach a consumer. No comments. |
659
+ | 7 Exit codes | `exitCodes` | Defaulted `{ clean: 0, hits: 1, refuse: 1 }`. `clean` must stay 0. |
660
+ | 8 Added detection | `extraRules` | Defaulted `[]`. Each added rule brings its own samples. |
661
+ | 9 Added self-test material | `extraSamples` | Defaulted `{}`. Additive only. |
662
+ | 10 Repository under test | `repoRoot` | Defaulted to the working directory, anchored at its git top level. |
663
+ | 11 Report destination | `write` | Defaulted to the process streams. |
640
664
 
641
665
  `CONFIG_AXES` is the same table as data, so a consumer can enumerate it rather than transcribe it.
642
666
 
667
+ The string-literal pass reads double-quoted, single-quoted and template literals (a multi-line
668
+ template whole, a `${...}` as a space), decodes their escapes, and also reads the literals on one
669
+ source line joined, so a phrase split across `"a " + "b"` is seen. It never reads a comment: a `//`
670
+ line, a trailing `//` and a `/* */` block are skipped, so an item identifier in a maintainer's note
671
+ stays allowed. A regular-expression literal is skipped too. In both source passes a NUL byte reads as
672
+ a space instead of refusing the file, since a raw separator inside a literal is legitimate source;
673
+ the clean line counts them.
674
+
643
675
  #### Two things worth knowing before you adopt this one
644
676
 
645
677
  **The prefix set has to be extended by hand, and that is the cheaper mistake.** A new programme
package/agent-notes.d.ts CHANGED
@@ -2,12 +2,24 @@
2
2
  * Types for `@cosyte/script-utils/agent-notes`: the checker for a repository's agent notes, one
3
3
  * file at `documentation/agent-notes.md` that CLAUDE.md points into as
4
4
  * `documentation/agent-notes.md#<anchor>` (RD4).
5
+ *
6
+ * Deprecated (D62): the agent notes were removed from every Cosyte repository. A repository with no
7
+ * notes file passes `runAgentNotesCheck` with one line, and one that still has the file is checked
8
+ * as before. This entry point stays until a minor release removes it.
5
9
  */
6
10
 
7
- /** The one place the notes live. */
11
+ /**
12
+ * The one place the notes live.
13
+ *
14
+ * @deprecated Retired with the agent notes (D62).
15
+ */
8
16
  export declare const NOTES_PATH: "documentation/agent-notes.md";
9
17
 
10
- /** A rule id a finding can carry. */
18
+ /**
19
+ * A rule id a finding can carry.
20
+ *
21
+ * @deprecated Retired with the agent notes (D62).
22
+ */
11
23
  export type AgentNotesRule =
12
24
  | "layout"
13
25
  | "pointer-form"
@@ -18,19 +30,39 @@ export type AgentNotesRule =
18
30
  | "empty-section"
19
31
  | "stale-citation";
20
32
 
21
- /** Every rule id, with what it requires. */
33
+ /**
34
+ * Every rule id, with what it requires.
35
+ *
36
+ * @deprecated Retired with the agent notes (D62).
37
+ */
22
38
  export declare const AGENT_NOTES_RULES: Readonly<Record<AgentNotesRule, string>>;
23
39
 
24
- /** Raised when the check cannot run; the CLI turns it into exit 2. */
40
+ /**
41
+ * Raised when the check cannot run; the CLI turns it into exit 2.
42
+ *
43
+ * @deprecated Retired with the agent notes (D62).
44
+ */
25
45
  export declare class AgentNotesRefusal extends Error {}
26
46
 
27
- /** GitHub's heading slug, as github-slugger writes it. */
47
+ /**
48
+ * GitHub's heading slug, as github-slugger writes it.
49
+ *
50
+ * @deprecated Retired with the agent notes (D62).
51
+ */
28
52
  export declare function slugify(text: string): string;
29
53
 
30
- /** A slugger that suffixes repeats as GitHub does: the second `Setup` is `setup-1`. */
54
+ /**
55
+ * A slugger that suffixes repeats as GitHub does: the second `Setup` is `setup-1`.
56
+ *
57
+ * @deprecated Retired with the agent notes (D62).
58
+ */
31
59
  export declare function makeSlugger(): (text: string) => string;
32
60
 
33
- /** One heading of the notes. */
61
+ /**
62
+ * One heading of the notes.
63
+ *
64
+ * @deprecated Retired with the agent notes (D62).
65
+ */
34
66
  export interface NotesHeading {
35
67
  level: number;
36
68
  text: string;
@@ -46,10 +78,15 @@ export interface NotesHeading {
46
78
  * its headings.
47
79
  *
48
80
  * @throws {AgentNotesRefusal} On an HTML comment that never closes.
81
+ * @deprecated Retired with the agent notes (D62).
49
82
  */
50
83
  export declare function readNotes(text: string): { targets: Set<string>; headings: NotesHeading[] };
51
84
 
52
- /** One place the notes or a pointer depart from RD4. */
85
+ /**
86
+ * One place the notes or a pointer depart from RD4.
87
+ *
88
+ * @deprecated Retired with the agent notes (D62).
89
+ */
53
90
  export interface AgentNotesFinding {
54
91
  rule: AgentNotesRule;
55
92
  file: string;
@@ -58,6 +95,11 @@ export interface AgentNotesFinding {
58
95
  message: string;
59
96
  }
60
97
 
98
+ /**
99
+ * What `checkAgentNotes` reads.
100
+ *
101
+ * @deprecated Retired with the agent notes (D62).
102
+ */
61
103
  export interface AgentNotesInput {
62
104
  /** CLAUDE.md's contents, or `null` when the repository has none. */
63
105
  claude: string | null;
@@ -75,9 +117,15 @@ export interface AgentNotesInput {
75
117
  * Check the notes and every pointer into them.
76
118
  *
77
119
  * @throws {AgentNotesRefusal} When the notes cannot be read to the end.
120
+ * @deprecated Retired with the agent notes (D62).
78
121
  */
79
122
  export declare function checkAgentNotes(input: AgentNotesInput): AgentNotesFinding[];
80
123
 
124
+ /**
125
+ * What `runAgentNotesCheck` takes.
126
+ *
127
+ * @deprecated Retired with the agent notes (D62).
128
+ */
81
129
  export interface AgentNotesCheckOptions {
82
130
  /** The repository root. Default: the working directory. */
83
131
  dir?: string;
@@ -87,6 +135,9 @@ export interface AgentNotesCheckOptions {
87
135
 
88
136
  /**
89
137
  * 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.
138
+ * least one finding, 2 the check could not run. A repository with no notes file is clean: it gets
139
+ * one line, `agent notes: OK, no agent notes (retired, D62)`. It never exits the process.
140
+ *
141
+ * @deprecated Retired with the agent notes (D62).
91
142
  */
92
143
  export declare function runAgentNotesCheck(options?: AgentNotesCheckOptions): 0 | 1 | 2;
package/agent-notes.js CHANGED
@@ -1,10 +1,22 @@
1
1
  // @cosyte/script-utils/agent-notes
2
2
  //
3
- // THE AGENT-NOTES CHECKER (RD4). A repository's CLAUDE.md card stays short, and what does not fit in
4
- // it lives in ONE file, `documentation/agent-notes.md`, which the card points into with ONE form:
5
- // `documentation/agent-notes.md#<anchor>`. This checker replaces the eight per-repository checkers
6
- // (mllp, ccda, ncpdp, astm, terminology, transform, deid and cli), keeping every rule they shared and
7
- // resolving the ones they disagreed on; the README section "The agent-notes checker" lists both.
3
+ // DEPRECATED (D62, 2026-11-08). The agent notes were removed from every Cosyte repository, and the
4
+ // card spec (documentation/claude-card.md in cosyte/config) has no pointer form any more: a card
5
+ // stands on its own. This checker stays only so that a repository still running it keeps passing
6
+ // while its notes are removed:
7
+ //
8
+ // - with no notes file (not tracked, or deleted from the working tree) it exits 0 with one line,
9
+ // `agent notes: OK, no agent notes (retired, D62)`, whatever still points at the file;
10
+ // - with one, it checks the notes and every pointer into them as it always has (below).
11
+ //
12
+ // Removing it (this file, its declarations, the `./agent-notes` export and the `cosyte-agent-notes`
13
+ // bin) is a breaking change, so it waits for a minor release, which the operator approves (D56).
14
+ //
15
+ // THE AGENT-NOTES CHECKER (RD4, as it stood before D62). A repository's CLAUDE.md card stays short,
16
+ // and what does not fit in it lives in ONE file, `documentation/agent-notes.md`, which the card
17
+ // points into with ONE form: `documentation/agent-notes.md#<anchor>`. This checker replaced the eight
18
+ // per-repository checkers (mllp, ccda, ncpdp, astm, terminology, transform, deid and cli), keeping
19
+ // every rule they shared and resolving the ones they disagreed on.
8
20
  //
9
21
  // THE RULES, each one id a finding names:
10
22
  //
@@ -40,6 +52,9 @@ import { isCliEntrypoint } from "./index.js";
40
52
  /** The one place the notes live. */
41
53
  export const NOTES_PATH = "documentation/agent-notes.md";
42
54
 
55
+ /** What a repository with no notes file is told, and passes on (D62). */
56
+ const RETIRED = "no agent notes (retired, D62)";
57
+
43
58
  /** The directory layout the one-file rule retires. */
44
59
  const NOTES_DIRECTORY = "documentation/agent-notes/";
45
60
 
@@ -406,8 +421,8 @@ export function checkAgentNotes(input) {
406
421
  return findings;
407
422
  }
408
423
 
409
- /** The tracked files of a repository: regular files read, everything else counted. */
410
- function trackedFiles(dir) {
424
+ /** The tracked paths of a repository, each with whether git records it as a regular file. */
425
+ function listTracked(dir) {
411
426
  let listing;
412
427
  try {
413
428
  listing = execFileSync("git", ["ls-files", "-z", "-s"], {
@@ -419,14 +434,24 @@ function trackedFiles(dir) {
419
434
  } catch (err) {
420
435
  throw new AgentNotesRefusal(`could not list the tracked files of ${dir}: ${err.message}`);
421
436
  }
422
- const files = [];
423
- const skipped = [];
437
+ const entries = [];
424
438
  for (const record of listing.split("\0")) {
425
439
  if (record === "") continue;
426
- const tab = record.indexOf("\t");
427
440
  const mode = record.slice(0, 6);
428
- const path = record.slice(tab + 1);
429
- if (mode !== "100644" && mode !== "100755") {
441
+ entries.push({
442
+ path: record.slice(record.indexOf("\t") + 1),
443
+ regular: mode === "100644" || mode === "100755",
444
+ });
445
+ }
446
+ return entries;
447
+ }
448
+
449
+ /** The tracked files of a repository: regular files read, everything else counted. */
450
+ function readTracked(dir, entries) {
451
+ const files = [];
452
+ const skipped = [];
453
+ for (const { path, regular } of entries) {
454
+ if (!regular) {
430
455
  skipped.push(path);
431
456
  continue;
432
457
  }
@@ -469,10 +494,20 @@ export function runAgentNotesCheck(options = {}) {
469
494
  }
470
495
  dir = resolve(dir);
471
496
  try {
472
- const { files, skipped } = trackedFiles(dir);
497
+ const entries = listTracked(dir);
498
+ // Retired (D62): with no notes file there is nothing to check, so nothing else is read, and a
499
+ // pointer left in the card, a source comment or a changelog is not a finding.
500
+ const hasNotes =
501
+ entries.some((entry) => entry.path === NOTES_PATH && entry.regular) &&
502
+ existsSync(join(dir, NOTES_PATH));
503
+ const { files, skipped } = hasNotes ? readTracked(dir, entries) : { files: [], skipped: [] };
473
504
  const byPath = new Map(files.map((file) => [file.path, file.text]));
474
505
  const claude = byPath.get("CLAUDE.md") ?? null;
475
506
  const notes = byPath.get(NOTES_PATH) ?? null;
507
+ if (notes === null) {
508
+ process.stdout.write(`agent notes: OK, ${RETIRED}\n`);
509
+ return 0;
510
+ }
476
511
  const others = files.filter((f) => f.path !== "CLAUDE.md" && f.path !== NOTES_PATH);
477
512
  const decisions = join(dir, "documentation", "decisions");
478
513
  const findings = checkAgentNotes({
@@ -495,7 +530,9 @@ export function runAgentNotesCheck(options = {}) {
495
530
  findings
496
531
  .map((f) => ` ${f.file}${f.line > 0 ? `:${f.line}` : ""} [${f.rule}] ${f.message}`)
497
532
  .join("\n") +
498
- "\n",
533
+ `\nagent notes: the notes are retired (D62). Rather than fix these, delete ${NOTES_PATH}, ` +
534
+ `which passes this check, and move what still binds to where it is used ` +
535
+ `(documentation/claude-card.md in cosyte/config).\n`,
499
536
  );
500
537
  return 1;
501
538
  } catch (err) {
@@ -1,23 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  // cosyte-agent-notes: the agent-notes checker for the repository in the working directory.
3
+ // Deprecated (D62): the agent notes were removed from every repository, so it passes a repository
4
+ // with no notes file, and stays only until a minor release removes it.
3
5
  // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
6
  // `npx -y -p @cosyte/script-utils cosyte-agent-notes`. It forwards every argument to the gate and exits with
5
- // the gate's own code; `--help` prints what it does and exits 0.
7
+ // the gate's own code; `--help` prints what it does and exits 0, or 2 under CI.
6
8
 
7
9
  import { runAgentNotesCheck } from "../agent-notes.js";
10
+ import { answerHelp } from "./help.js";
8
11
 
9
- const HELP = `cosyte-agent-notes: check a repository's agent notes and every pointer into them.
12
+ const HELP = `cosyte-agent-notes: deprecated (D62). Remove the step that runs it.
10
13
 
11
14
  cosyte-agent-notes [--dir <repository root>]
12
15
 
13
- The notes are one file, documentation/agent-notes.md, and CLAUDE.md points into it as
14
- documentation/agent-notes.md#<anchor>. Every pointer in a tracked file must name a heading or an
15
- explicit anchor. Exit 0 clean, 1 a finding, 2 the check could not run.
16
+ The agent notes, documentation/agent-notes.md, were removed from every Cosyte repository on
17
+ 2026-11-08 (D62). A CLAUDE.md card stands on its own, and what does not fit on it goes where it is
18
+ used: a test, a comment at the code it guards, an ADR, the README or docs, or a runbook
19
+ (documentation/claude-card.md in cosyte/config).
20
+
21
+ Until a minor release removes this bin, a repository with no documentation/agent-notes.md passes
22
+ with one line, "no agent notes (retired, D62)", whatever still points at the file. One that still
23
+ has the file is checked as before: every documentation/agent-notes.md#<anchor> pointer in a tracked
24
+ file must name a heading or an explicit anchor. Exit 0 clean, 1 a finding, 2 the check could not
25
+ run.
16
26
  `;
17
27
 
18
28
  const args = process.argv.slice(2);
19
29
  if (args.includes("--help") || args.includes("-h")) {
20
- process.stdout.write(HELP);
30
+ process.exitCode = answerHelp("cosyte-agent-notes", HELP);
21
31
  } else {
22
32
  process.exitCode = runAgentNotesCheck({ argv: args });
23
33
  }
@@ -2,9 +2,10 @@
2
2
  // cosyte-attw: the attw publish gate for the package in the working directory.
3
3
  // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
4
  // `npx -y -p @cosyte/script-utils cosyte-attw`. It forwards every argument to the gate and exits with
5
- // the gate's own code; `--help` prints what it does and exits 0.
5
+ // the gate's own code; `--help` prints what it does and exits 0, or 2 under CI.
6
6
 
7
7
  import { runAttwCli } from "../attw.js";
8
+ import { answerHelp } from "./help.js";
8
9
 
9
10
  const HELP = `cosyte-attw: run the attw publish gate for the package in the working directory.
10
11
 
@@ -18,7 +19,7 @@ passed; attw's own status when attw judged the package; 1 a refusal.
18
19
 
19
20
  const args = process.argv.slice(2);
20
21
  if (args.includes("--help") || args.includes("-h")) {
21
- process.stdout.write(HELP);
22
+ process.exitCode = answerHelp("cosyte-attw", HELP);
22
23
  } else {
23
24
  process.exitCode = runAttwCli(args);
24
25
  }
@@ -2,22 +2,24 @@
2
2
  // cosyte-catalog: the catalog check for the library in the working directory.
3
3
  // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
4
  // `npx -y -p @cosyte/script-utils cosyte-catalog`. It forwards every argument to the gate and exits with
5
- // the gate's own code; `--help` prints what it does and exits 0.
5
+ // the gate's own code; `--help` prints what it does and exits 0, or 2 under CI.
6
6
 
7
7
  import { runCatalogCheck } from "../catalog.js";
8
+ import { answerHelp } from "./help.js";
8
9
 
9
10
  const HELP = `cosyte-catalog: check that a library's package.json description is the catalog's.
10
11
 
11
- cosyte-catalog [--dir <package directory>]
12
+ cosyte-catalog [--dir <package directory>] [--strict]
12
13
 
13
- The catalog is the one list of our libraries, generated in cosyte/config from each package.json.
14
- Exit 0 the description is the catalog's, 1 it is not (both values printed), 2 the package is not
15
- listed or package.json cannot be read.
14
+ The catalog is the one list of our libraries, generated in cosyte/config, which keeps it fresh
15
+ against npm. This check is advisory: exit 0 when the description is the catalog's, and 0 with both
16
+ values printed when it is not or the package is unlisted; 2 when package.json cannot be read. With
17
+ --strict a disagreement exits 1 and an unlisted package 2.
16
18
  `;
17
19
 
18
20
  const args = process.argv.slice(2);
19
21
  if (args.includes("--help") || args.includes("-h")) {
20
- process.stdout.write(HELP);
22
+ process.exitCode = answerHelp("cosyte-catalog", HELP);
21
23
  } else {
22
24
  process.exitCode = runCatalogCheck({ argv: args });
23
25
  }
@@ -2,9 +2,10 @@
2
2
  // cosyte-claude-card: the CLAUDE.md card checker for the repository in the working directory.
3
3
  // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
4
  // `npx -y -p @cosyte/script-utils cosyte-claude-card`. It forwards every argument to the gate and exits with
5
- // the gate's own code; `--help` prints what it does and exits 0.
5
+ // the gate's own code; `--help` prints what it does and exits 0, or 2 under CI.
6
6
 
7
7
  import { runClaudeCardGate } from "../claude-card.js";
8
+ import { answerHelp } from "./help.js";
8
9
 
9
10
  const HELP = `cosyte-claude-card: check a repository's CLAUDE.md against the card spec.
10
11
 
@@ -17,7 +18,7 @@ Gate, Hazard and Rules in order, at most 60 lines of at most 100 characters and
17
18
 
18
19
  const args = process.argv.slice(2);
19
20
  if (args.includes("--help") || args.includes("-h")) {
20
- process.stdout.write(HELP);
21
+ process.exitCode = answerHelp("cosyte-claude-card", HELP);
21
22
  } else {
22
23
  process.exitCode = runClaudeCardGate({ argv: args });
23
24
  }
@@ -2,9 +2,10 @@
2
2
  // cosyte-internal-refs: the internal-reference gate over the repository in the working directory.
3
3
  // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
4
  // `npx -y -p @cosyte/script-utils cosyte-internal-refs`. It forwards every argument to the gate and exits with
5
- // the gate's own code; `--help` prints what it does and exits 0.
5
+ // the gate's own code; `--help` prints what it does and exits 0, or 2 under CI.
6
6
 
7
7
  import { runInternalRefsCli } from "../internal-refs.js";
8
+ import { answerHelp } from "./help.js";
8
9
 
9
10
  const HELP = `cosyte-internal-refs: run the internal-reference gate over the repository in the working directory.
10
11
 
@@ -17,7 +18,7 @@ clean, 1 hits, 2 the gate could not run.
17
18
 
18
19
  const args = process.argv.slice(2);
19
20
  if (args.includes("--help") || args.includes("-h")) {
20
- process.stdout.write(HELP);
21
+ process.exitCode = answerHelp("cosyte-internal-refs", HELP);
21
22
  } else {
22
23
  process.exitCode = runInternalRefsCli(args);
23
24
  }