@cosyte/script-utils 0.1.3 → 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,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
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.
8
+
3
9
  ## 0.1.3
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -247,6 +247,31 @@ Each shared gate runs from a repository's package script with no file of its own
247
247
  notes), with exit codes 0 clean, 1 hits, 2 the gate could not run. The file may not set
248
248
  `exitCodes`, `repoRoot` or `write`, which the command-line form owns.
249
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
+
250
275
  ### The em-dash gate, which is a file rather than a subpath
251
276
 
252
277
  This package also ships `check-no-emdash.sh`, the one implementation of the em-dash gate for every
@@ -275,9 +300,13 @@ A repository narrows WHAT IS SCANNED, and only that, through a tracked file at i
275
300
  `scripts/check-no-emdash.exclude`: one entry per line, `#` comments and blank lines ignored, an entry
276
301
  ending in `/` covering every tracked path beneath it, and every entry anchored at the repository
277
302
  root. An entry that matches no tracked path is refused rather than ignored, because an exclusion that
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.
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.
281
310
 
282
311
  ### The README gate
283
312
 
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-agent-notes: the agent-notes checker for the repository in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
+ // `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.
6
+
7
+ import { runAgentNotesCheck } from "../agent-notes.js";
8
+
9
+ const HELP = `cosyte-agent-notes: check a repository's agent notes and every pointer into them.
10
+
11
+ cosyte-agent-notes [--dir <repository root>]
12
+
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
+ `;
17
+
18
+ const args = process.argv.slice(2);
19
+ if (args.includes("--help") || args.includes("-h")) {
20
+ process.stdout.write(HELP);
21
+ } else {
22
+ process.exitCode = runAgentNotesCheck({ argv: args });
23
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-attw: the attw publish gate for the package in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
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.
6
+
7
+ import { runAttwCli } from "../attw.js";
8
+
9
+ const HELP = `cosyte-attw: run the attw publish gate for the package in the working directory.
10
+
11
+ cosyte-attw [--no-definitely-typed]
12
+
13
+ It runs this package's own node_modules/.bin/attw over the tarball npm would publish, with four
14
+ nets attw lacks. scripts/attw.exclude names the resolutions the package leaves out, one per line:
15
+ node10 selects attw's node16 profile, node10 with node16-cjs selects esm-only. Exit 0 every net
16
+ passed; attw's own status when attw judged the package; 1 a refusal.
17
+ `;
18
+
19
+ const args = process.argv.slice(2);
20
+ if (args.includes("--help") || args.includes("-h")) {
21
+ process.stdout.write(HELP);
22
+ } else {
23
+ process.exitCode = runAttwCli(args);
24
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-catalog: the catalog check for the library in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
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.
6
+
7
+ import { runCatalogCheck } from "../catalog.js";
8
+
9
+ const HELP = `cosyte-catalog: check that a library's package.json description is the catalog's.
10
+
11
+ cosyte-catalog [--dir <package directory>]
12
+
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.
16
+ `;
17
+
18
+ const args = process.argv.slice(2);
19
+ if (args.includes("--help") || args.includes("-h")) {
20
+ process.stdout.write(HELP);
21
+ } else {
22
+ process.exitCode = runCatalogCheck({ argv: args });
23
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-claude-card: the CLAUDE.md card checker for the repository in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
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.
6
+
7
+ import { runClaudeCardGate } from "../claude-card.js";
8
+
9
+ const HELP = `cosyte-claude-card: check a repository's CLAUDE.md against the card spec.
10
+
11
+ cosyte-claude-card [--dir <repository root>]
12
+
13
+ Gate, Hazard and Rules in order, at most 60 lines of at most 100 characters and 5,120 bytes, and an
14
+ @AGENTS.md import when an AGENTS.md sits beside it. Exit 0 the card follows the spec, 1 a finding,
15
+ 2 CLAUDE.md could not be read.
16
+ `;
17
+
18
+ const args = process.argv.slice(2);
19
+ if (args.includes("--help") || args.includes("-h")) {
20
+ process.stdout.write(HELP);
21
+ } else {
22
+ process.exitCode = runClaudeCardGate({ argv: args });
23
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-internal-refs: the internal-reference gate over the repository in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
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.
6
+
7
+ import { runInternalRefsCli } from "../internal-refs.js";
8
+
9
+ const HELP = `cosyte-internal-refs: run the internal-reference gate over the repository in the working directory.
10
+
11
+ cosyte-internal-refs [--config <path>]
12
+
13
+ It refuses internal bookkeeping on a public surface: item identifiers, phase and wave language, ADR
14
+ numbers, internal repo paths. The axes come from scripts/internal-refs.json (or --config). Exit 0
15
+ clean, 1 hits, 2 the gate could not run.
16
+ `;
17
+
18
+ const args = process.argv.slice(2);
19
+ if (args.includes("--help") || args.includes("-h")) {
20
+ process.stdout.write(HELP);
21
+ } else {
22
+ process.exitCode = runInternalRefsCli(args);
23
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-no-emdash: the em and en dash gate over the repository in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
+ // `npx -y -p @cosyte/script-utils cosyte-no-emdash`. The gate is the shell script beside this
5
+ // package's entry points; this runs it with bash, forwards every argument (`--help` included, which
6
+ // the gate answers itself) and exits with its code.
7
+
8
+ import { spawnSync } from "node:child_process";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const gate = fileURLToPath(new URL("../check-no-emdash.sh", import.meta.url));
12
+ const run = spawnSync("bash", [gate, ...process.argv.slice(2)], { stdio: "inherit" });
13
+ if (run.error) {
14
+ process.stderr.write(`cosyte-no-emdash: could not run bash ${gate}: ${run.error.message}\n`);
15
+ process.exitCode = 1;
16
+ } else {
17
+ process.exitCode = run.status ?? 1;
18
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-phi-scan: the PHI commit-gate over the repository in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
+ // `npx -y -p @cosyte/script-utils cosyte-phi-scan`. It forwards every argument to the gate and exits with
5
+ // the gate's own code; `--help` prints what it does and exits 0.
6
+
7
+ import { runPhiScanCli } from "../phi-scan.js";
8
+
9
+ const HELP = `cosyte-phi-scan: run the PHI commit-gate over the repository in the working directory.
10
+
11
+ cosyte-phi-scan scan the whole repository
12
+ cosyte-phi-scan --staged scan what is staged, Markdown excepted (the pre-commit route)
13
+ cosyte-phi-scan <path>... scan these paths
14
+
15
+ It carries the cross-cutting floor (a dashed SSN shape, an email at an undeclared domain) and
16
+ reports a place and a rule, never the matched value. scripts/phi-allow-list.txt declares synthetic
17
+ values, and scripts/phi-scan.exclude names literal tracked paths to leave out. Exit 0 clean, 1 hits,
18
+ 2 the scan could not account for something.
19
+ `;
20
+
21
+ const args = process.argv.slice(2);
22
+ if (args.includes("--help") || args.includes("-h")) {
23
+ process.stdout.write(HELP);
24
+ } else {
25
+ process.exitCode = runPhiScanCli(args);
26
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // cosyte-readme: the README gate for the library in the working directory.
3
+ // A bin of @cosyte/script-utils (D56), so a repository with no package.json can run it with
4
+ // `npx -y -p @cosyte/script-utils cosyte-readme`. It forwards every argument to the gate and exits with
5
+ // the gate's own code; `--help` prints what it does and exits 0.
6
+
7
+ import { runReadmeGate } from "../readme.js";
8
+
9
+ const HELP = `cosyte-readme: check a library README against the house skeleton and npm's size limit.
10
+
11
+ cosyte-readme [--dir <package directory>]
12
+
13
+ It reads README.md and package.json. Exit 0 the README follows the skeleton, 1 a finding (each
14
+ names its line and rule), 2 the package could not be read.
15
+ `;
16
+
17
+ const args = process.argv.slice(2);
18
+ if (args.includes("--help") || args.includes("-h")) {
19
+ process.stdout.write(HELP);
20
+ } else {
21
+ process.exitCode = runReadmeGate({ argv: args });
22
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ // script-utils: one entry for every gate in @cosyte/script-utils (D56). npx runs the bin named
3
+ // after the package, so `npx -y @cosyte/script-utils@<version> <command> [args]` reaches each gate
4
+ // from a repository with no package.json. `<command> --help` is the gate's own help.
5
+
6
+ import { spawnSync } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const COMMANDS = {
10
+ "no-emdash": ["cosyte-no-emdash", "the em and en dash gate, over tracked files and their names"],
11
+ attw: ["cosyte-attw", "the attw publish gate, for the package here"],
12
+ "phi-scan": ["cosyte-phi-scan", "the PHI commit-gate, over the repository here"],
13
+ "internal-refs": [
14
+ "cosyte-internal-refs",
15
+ "the internal-reference gate, over the repository here",
16
+ ],
17
+ readme: ["cosyte-readme", "the README gate, for the library here"],
18
+ "claude-card": ["cosyte-claude-card", "the CLAUDE.md card checker"],
19
+ "agent-notes": ["cosyte-agent-notes", "the agent-notes checker"],
20
+ catalog: ["cosyte-catalog", "the catalog check, for the library here"],
21
+ };
22
+
23
+ const HELP = `script-utils: run one of the @cosyte/script-utils gates over the working directory.
24
+
25
+ script-utils <command> [arguments]
26
+ script-utils <command> --help
27
+
28
+ ${Object.entries(COMMANDS)
29
+ .map(([command, [bin, what]]) => ` ${command.padEnd(14)} ${what} (also the bin ${bin})`)
30
+ .join("\n")}
31
+ `;
32
+
33
+ const [command, ...rest] = process.argv.slice(2);
34
+ if (command === undefined || command === "--help" || command === "-h") {
35
+ process.stdout.write(HELP);
36
+ process.exitCode = command === undefined ? 2 : 0;
37
+ } else if (!(command in COMMANDS)) {
38
+ process.stderr.write(`script-utils: ${JSON.stringify(command)} is not a command.\n\n${HELP}`);
39
+ process.exitCode = 2;
40
+ } else {
41
+ const bin = fileURLToPath(new URL(`./${COMMANDS[command][0]}.js`, import.meta.url));
42
+ const run = spawnSync(process.execPath, [bin, ...rest], { stdio: "inherit" });
43
+ process.exitCode = run.error ? 2 : (run.status ?? 2);
44
+ }
@@ -228,19 +228,32 @@ unset -f grep xargs sed awk 2>/dev/null || true
228
228
  # refuse_if_incomplete reads. That branch only refines wording, never the red.
229
229
  export LC_ALL=C.UTF-8
230
230
 
231
- # Matches U+2014 as the literal character and as its encodings: %E2%80%94 (URL), the
232
- # JS backslash-u escape, and the &mdash; / &#8212; / &#x2014; HTML entities. The
233
- # escape is not theoretical in this repo: it is what the parser-template manifest
234
- # carried, and a literal-only sweep passed straight over it. Matches U+2013 the same
235
- # way: %E2%80%93, the backslash-u escape, and the &ndash; / &#8211; / &#x2013; HTML
236
- # entities.
237
- #
238
- # THE MATCH VOCABULARY IS FROZEN at these twelve forms, six for each dash. A consumer
239
- # may narrow WHAT IS SCANNED through the declaration above; nothing may narrow WHAT IS
240
- # MATCHED, and widening it is a change every consuming repo inherits at once and is
241
- # specced as one. The en dash's six were that change (operator decision, 2026-11-07).
242
- PATTERN='\x{2014}|%E2%80%94|\\u2014|&mdash;|&#8212;|&#x2014;'
243
- PATTERN+='|\x{2013}|%E2%80%93|\\u2013|&ndash;|&#8211;|&#x2013;'
231
+ # What counts as a dash, for U+2014 and U+2013 alike. Case never matters, so `&MDASH;`
232
+ # and `%e2%80%94` count too (D56: the forms the marketing and consulting scanner caught,
233
+ # which this gate now matches so those repos lose nothing by moving to it):
234
+ #
235
+ # the characters U+2014 U+2013
236
+ # HTML named entities, ; optional &mdash; &ndash;
237
+ # HTML decimal references &#8212; &#8211; leading zeros and a missing ; too
238
+ # HTML hex references &#x2014; &#x2013; leading zeros and a missing ; too
239
+ # URL encoding of the UTF-8 bytes %E2%80%94 %E2%80%93
240
+ # backslash-u escapes (JS, JSON, Java, \u2014 \u{2014} \U00002014
241
+ # C#, Python, Rust, Swift)
242
+ # backslash-x escapes (PCRE, Perl) \x{2014}
243
+ # CSS escapes \2014 \002014
244
+ # Unicode names (Python, Perl) \N{EM DASH} \N{EN DASH}
245
+ #
246
+ # The lookaheads keep the near misses out: &#82120; and the CSS escape \20140 name other
247
+ # characters, and &mdashboard is not an entity. The escape forms are not theoretical: the
248
+ # parser-template manifest once carried a backslash-u em dash, and a literal-only sweep
249
+ # passed straight over it.
250
+ #
251
+ # A consumer may narrow WHAT IS SCANNED through the declaration above; nothing may narrow
252
+ # WHAT IS MATCHED, and widening it is a change every consuming repo inherits at once. The
253
+ # en dash's forms (operator decision, 2026-11-07) and these (D56) were such changes.
254
+ PATTERN='(?i)\x{2014}|\x{2013}|&[mn]dash(?![a-z])|&#0*821[12](?![0-9])|&#x0*201[34](?![0-9a-f])'
255
+ PATTERN+='|%E2%80%9[34]|\\u(?:0000)?201[34]|\\u\{0*201[34]\}|\\x\{0*201[34]\}'
256
+ PATTERN+='|\\0{0,2}201[34](?![0-9a-f])|\\N\{E[MN] DASH\}'
244
257
 
245
258
  # The declaration's path, relative to the top level of the repo under scan. Deliberately
246
259
  # NOT relative to this file: in a consuming repo this file lives under `node_modules/`
@@ -281,10 +294,11 @@ SCAN_MODE=files
281
294
 
282
295
  refuse_arguments() {
283
296
  echo "ERROR: check-no-emdash - $1" >&2
284
- echo " The modes are: (no argument) to scan every tracked file; --stdin LABEL" >&2
285
- echo " to scan the text arriving on stdin; --list-scanned to write the paths the" >&2
286
- echo " default scan reads; --self-id to write the sha256 of the implementation" >&2
287
- echo " that is executing. Refusing to run rather than guess which was meant." >&2
297
+ echo " The modes are: (no argument) to scan every tracked file and its name;" >&2
298
+ echo " --stdin LABEL to scan the text arriving on stdin; --list-scanned to write the" >&2
299
+ echo " paths the default scan reads; --self-id to write the sha256 of the" >&2
300
+ echo " implementation that is executing; --help for this. Refusing to run rather" >&2
301
+ echo " than guess which was meant." >&2
288
302
  exit 1
289
303
  }
290
304
 
@@ -324,6 +338,22 @@ case "${1:-}" in
324
338
  refuse_arguments "--self-id takes no further argument, and this was given: ${2}"
325
339
  fi
326
340
  ;;
341
+ --help | -h)
342
+ cat << 'USAGE'
343
+ check-no-emdash: refuse the em dash (U+2014) and the en dash (U+2013), as the characters and
344
+ in every encoded form, in the tracked files of the repository it runs in and in their names.
345
+
346
+ check-no-emdash.sh scan every tracked file and every tracked file name
347
+ check-no-emdash.sh --stdin LABEL scan the text on stdin (a PR title, body, commit message)
348
+ check-no-emdash.sh --list-scanned write the NUL-separated paths the default scan reads
349
+ check-no-emdash.sh --self-id write the sha256 of the implementation that is executing
350
+
351
+ A repository leaves paths out in scripts/check-no-emdash.exclude: one per line, an entry ending
352
+ in / covers everything beneath it, # starts a comment. Exit 0 means the mode completed and found
353
+ nothing banned; exit 1 means anything else.
354
+ USAGE
355
+ exit 0
356
+ ;;
327
357
  *)
328
358
  refuse_arguments "unrecognised argument: ${1}"
329
359
  ;;
@@ -341,6 +371,7 @@ GITERR=$(mktemp)
341
371
  FILELIST=$(mktemp)
342
372
  SCANLIST=$(mktemp)
343
373
  PLAINLIST=$(mktemp)
374
+ NAMELIST=$(mktemp)
344
375
  STDINBUF=$(mktemp)
345
376
  BINPROBE=$(mktemp)
346
377
  PROBEERR=$(mktemp)
@@ -353,8 +384,8 @@ PROBEERR=$(mktemp)
353
384
  # not lost with it.
354
385
  close_exit_vocabulary() {
355
386
  local code=$?
356
- rm -f "$ERRLOG" "$GITERR" "$FILELIST" "$SCANLIST" "$PLAINLIST" "$STDINBUF" "$BINPROBE" \
357
- "$PROBEERR"
387
+ rm -f "$ERRLOG" "$GITERR" "$FILELIST" "$SCANLIST" "$PLAINLIST" "$NAMELIST" "$STDINBUF" \
388
+ "$BINPROBE" "$PROBEERR"
358
389
  if [ "$code" -ne 0 ] && [ "$code" -ne 1 ]; then
359
390
  echo "ERROR: check-no-emdash - the run ended with status ${code}. This gate reports 0" >&2
360
391
  echo " for a mode that completed clean and 1 for everything else, so it is" >&2
@@ -816,6 +847,7 @@ esac
816
847
 
817
848
  : > "$SCANLIST"
818
849
  : > "$PLAINLIST"
850
+ : > "$NAMELIST"
819
851
  gitlinks=0
820
852
  scanned=0
821
853
  self_excluded=0
@@ -831,11 +863,16 @@ for i in "${!TRACKED[@]}"; do
831
863
 
832
864
  # What the repo declared it leaves out. Checked before the entry is opened, because an
833
865
  # excluded path is not read and its type on disk is therefore not this gate's business.
866
+ # It is not scanned at all, so its NAME is left out with its content.
834
867
  if is_excluded "$f"; then
835
868
  declared_excluded=$((declared_excluded + 1))
836
869
  continue
837
870
  fi
838
871
 
872
+ # A NAME IS COPY TOO (D56): every tracked path the declaration does not leave out has its
873
+ # name scanned, a gitlink's included, before any test of what is on disk.
874
+ printf '%s\0' "$f" >> "$NAMELIST"
875
+
839
876
  # `git ls-files` lists a submodule as a gitlink, which on disk is a REAL directory.
840
877
  # config has none today (checked: `git ls-files -s` lists no mode 160000 entry). The
841
878
  # skip is keyed on the INDEX MODE and never on the type on disk, because the two
@@ -886,12 +923,22 @@ if [ "$MODE" = list ]; then
886
923
  exit 0
887
924
  fi
888
925
 
926
+ # File names first: NUL-separated in, one per line out. `-a` because a name is text whatever
927
+ # bytes it holds, and the stderr capture refuses a name scan that could not complete.
928
+ NAME_HITS=$(grep -z -a -P -e "$PATTERN" < "$NAMELIST" 2>> "$ERRLOG" | tr '\0' '\n' || true)
929
+ refuse_if_incomplete
930
+
889
931
  HITS=$(xargs -0 -r grep -H -nP -e "$PATTERN" -- < "$SCANLIST" 2>> "$ERRLOG" || true)
890
932
 
891
933
  refuse_if_incomplete
892
934
 
935
+ if [ -n "$NAME_HITS" ] && [ -n "$HITS" ]; then
936
+ fail_with_hits "the tracked file names and the tracked files listed above" \
937
+ "$NAME_HITS"$'\n'"$HITS"
938
+ fi
939
+ [ -n "$NAME_HITS" ] && fail_with_hits "the tracked file names listed above" "$NAME_HITS"
893
940
  [ -n "$HITS" ] && fail_with_hits "the tracked files listed above" "$HITS"
894
941
 
895
- echo "check-no-emdash: OK (no em or en dashes in ${scanned} of ${#TRACKED[@]} tracked file(s);" \
896
- "${gitlinks} gitlink(s) skipped, ${self_excluded} excluded as the implementation under" \
897
- "scan, ${declared_excluded} excluded by ${DECLARATION})"
942
+ echo "check-no-emdash: OK (no em or en dashes in ${scanned} of ${#TRACKED[@]} tracked file(s)" \
943
+ "or their names; ${gitlinks} gitlink(s) skipped, ${self_excluded} excluded as the" \
944
+ "implementation under scan, ${declared_excluded} excluded by ${DECLARATION})"
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@cosyte/script-utils",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Zero-dependency helpers for the repo-local gate scripts in @cosyte/* repos.",
5
5
  "keywords": [
6
6
  "cli",
7
+ "bin",
7
8
  "entrypoint",
8
9
  "import-meta",
9
10
  "phi",
@@ -21,6 +22,17 @@
21
22
  "author": "Cosyte <hello@cosyte.com> (https://cosyte.com)",
22
23
  "type": "module",
23
24
  "main": "index.js",
25
+ "bin": {
26
+ "script-utils": "bin/script-utils.js",
27
+ "cosyte-no-emdash": "bin/cosyte-no-emdash.js",
28
+ "cosyte-attw": "bin/cosyte-attw.js",
29
+ "cosyte-phi-scan": "bin/cosyte-phi-scan.js",
30
+ "cosyte-internal-refs": "bin/cosyte-internal-refs.js",
31
+ "cosyte-readme": "bin/cosyte-readme.js",
32
+ "cosyte-claude-card": "bin/cosyte-claude-card.js",
33
+ "cosyte-agent-notes": "bin/cosyte-agent-notes.js",
34
+ "cosyte-catalog": "bin/cosyte-catalog.js"
35
+ },
24
36
  "types": "index.d.ts",
25
37
  "exports": {
26
38
  ".": {
@@ -75,6 +87,7 @@
75
87
  "agent-notes.js",
76
88
  "agent-notes.d.ts",
77
89
  "check-no-emdash.sh",
90
+ "bin",
78
91
  "README.md",
79
92
  "CHANGELOG.md"
80
93
  ],