@cosyte/script-utils 0.1.3 → 0.1.5
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 +12 -0
- package/README.md +32 -3
- package/bin/cosyte-agent-notes.js +23 -0
- package/bin/cosyte-attw.js +24 -0
- package/bin/cosyte-catalog.js +23 -0
- package/bin/cosyte-claude-card.js +23 -0
- package/bin/cosyte-internal-refs.js +23 -0
- package/bin/cosyte-no-emdash.js +18 -0
- package/bin/cosyte-phi-scan.js +26 -0
- package/bin/cosyte-readme.js +22 -0
- package/bin/script-utils.js +44 -0
- package/catalog.json +5 -5
- package/check-no-emdash.sh +69 -22
- package/internal-refs.js +32 -3
- package/package.json +15 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f5e1a1f: The internal-refs phase rule lets two kinds of ordinary prose through. `phase` as the second half of a compound word is no longer a hit (`connect-phase socket`, `two-phase state`, `read-phase timeout`), and neither is `phase` or `phases` followed by `where`, `are`, `was`, `were`, `during` or `at` (`All 5 phases are defined`, `Connection lifecycle phase where the error occurred`). A phase named beside those words is still a hit (`shipped in phase 2`, `Phase 3 was where it failed`, `Phase-L`), and both guards' passages are in the rule's self-test floor, so every run proves them. The catalog is regenerated from each library's current package.json: the terminology, deid and synth descriptions take the serial comma, the cli record carries cli's current description, and cli's engines are `>=22.0.0`.
|
|
8
|
+
|
|
9
|
+
## 0.1.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 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
|
+
|
|
3
15
|
## 0.1.3
|
|
4
16
|
|
|
5
17
|
### 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
|
|
279
|
-
banned forms are fixed in the gate. They are the em
|
|
280
|
-
the
|
|
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
|
+
}
|
package/catalog.json
CHANGED
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
{
|
|
117
117
|
"name": "@cosyte/terminology",
|
|
118
118
|
"slug": "terminology",
|
|
119
|
-
"description": "Terminology engine for US healthcare code systems in Node.js and TypeScript: FHIR $translate, $lookup, $validate-code and $expand over code systems, value sets and maps you supply.",
|
|
119
|
+
"description": "Terminology engine for US healthcare code systems in Node.js and TypeScript: FHIR $translate, $lookup, $validate-code, and $expand over code systems, value sets, and maps you supply.",
|
|
120
120
|
"line": "0.1",
|
|
121
121
|
"engines": {
|
|
122
122
|
"node": ">=22.0.0"
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
{
|
|
143
143
|
"name": "@cosyte/deid",
|
|
144
144
|
"slug": "deid",
|
|
145
|
-
"description": "Healthcare de-identification for Node.js and TypeScript: applies a HIPAA Safe Harbor policy to HL7 v2, C-CDA, FHIR R4, X12, NCPDP Telecom and DICOM, fails closed, and emits a value-free manifest.",
|
|
145
|
+
"description": "Healthcare de-identification for Node.js and TypeScript: applies a HIPAA Safe Harbor policy to HL7 v2, C-CDA, FHIR R4, X12, NCPDP Telecom, and DICOM, fails closed, and emits a value-free manifest.",
|
|
146
146
|
"line": "0.1",
|
|
147
147
|
"engines": {
|
|
148
148
|
"node": ">=22.0.0"
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
{
|
|
156
156
|
"name": "@cosyte/synth",
|
|
157
157
|
"slug": "synth",
|
|
158
|
-
"description": "Deterministic, seedable synthetic healthcare test fixtures for Node.js and TypeScript: HL7 v2, FHIR R4 and US Core, C-CDA, X12, NCPDP and ASTM, spec-clean by construction and never real PHI.",
|
|
158
|
+
"description": "Deterministic, seedable synthetic healthcare test fixtures for Node.js and TypeScript: HL7 v2, FHIR R4 and US Core, C-CDA, X12, NCPDP, and ASTM, spec-clean by construction and never real PHI.",
|
|
159
159
|
"line": "0.1",
|
|
160
160
|
"engines": {
|
|
161
161
|
"node": ">=22.0.0"
|
|
@@ -181,10 +181,10 @@
|
|
|
181
181
|
{
|
|
182
182
|
"name": "@cosyte/cli",
|
|
183
183
|
"slug": "cli",
|
|
184
|
-
"description": "The
|
|
184
|
+
"description": "The Cosyte command line for Node.js and TypeScript: one command over HL7 v2, FHIR R4, X12, ASTM, NCPDP, C-CDA, DICOM, and MLLP, with HL7 v2 to FHIR conversion from transform, code mapping from terminology, and the cosyte-mcp MCP server for agents. Value-free diagnostics and a documented exit-code contract.",
|
|
185
185
|
"line": "0.1",
|
|
186
186
|
"engines": {
|
|
187
|
-
"node": ">=22.0.0
|
|
187
|
+
"node": ">=22.0.0"
|
|
188
188
|
},
|
|
189
189
|
"repository": "https://github.com/cosyte/cli",
|
|
190
190
|
"docs": "https://docs.cosyte.com/cli",
|
package/check-no-emdash.sh
CHANGED
|
@@ -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
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
# entities
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
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 — –
|
|
237
|
+
# HTML decimal references — – leading zeros and a missing ; too
|
|
238
|
+
# HTML hex references — – 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: 𔃈 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])|�*821[12](?![0-9])|�*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
|
|
285
|
-
echo " to scan the text arriving on stdin; --list-scanned to write the
|
|
286
|
-
echo " default scan reads; --self-id to write the sha256 of the
|
|
287
|
-
echo " that is executing. Refusing to run rather
|
|
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" "$
|
|
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
|
|
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/internal-refs.js
CHANGED
|
@@ -230,10 +230,28 @@ const PHASE_NOT_CLINICAL =
|
|
|
230
230
|
"(?<!study )(?<!clinical )(?<!trial )(?<!acute )(?<!chronic )(?<!luteal )(?<!follicular )" +
|
|
231
231
|
"(?<!liquid )(?<!gas )";
|
|
232
232
|
|
|
233
|
-
/**
|
|
233
|
+
/**
|
|
234
|
+
* Lookbehind that drops `phase` as the second half of a compound word.
|
|
235
|
+
*
|
|
236
|
+
* `connect-phase socket`, `two-phase state` and `read-phase timeout` describe a protocol, not a
|
|
237
|
+
* roadmap: a letter and a hyphen in front of `phase` make it part of an adjective. Only the
|
|
238
|
+
* characters BEFORE the word are read, so `Phase-L` (the hyphen after it) is still a hit, and so is
|
|
239
|
+
* a phase after a digit and a hyphen.
|
|
240
|
+
*/
|
|
241
|
+
const PHASE_NOT_COMPOUND = "(?<![A-Za-z]-)";
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Lookahead that drops the clinical-study field-name tails, the trial roman numerals, and the
|
|
245
|
+
* words that make `phase` a noun in a sentence rather than the name of one.
|
|
246
|
+
*
|
|
247
|
+
* `All 5 phases are defined`, `the lifecycle phase where the error occurred`, `the phase was`, `the
|
|
248
|
+
* phases were`, `the phase during which` and `the phase at which` say what a phase is. A roadmap
|
|
249
|
+
* phase is named by what follows the word, and none of these words names one.
|
|
250
|
+
*/
|
|
234
251
|
const PHASE_NOT_FIELD =
|
|
235
252
|
"(?!of\\b|with\\b|in\\b|out\\b|the\\b|and\\b|is\\b|for\\b|to\\b|identifier\\b|start\\b|end\\b|" +
|
|
236
|
-
"evaluability\\b|number\\b|
|
|
253
|
+
"evaluability\\b|number\\b|where\\b|are\\b|was\\b|were\\b|during\\b|at\\b|" +
|
|
254
|
+
"(?:I{1,3}|IV)\\s+(?:trial|stud|clinical|oncolog))";
|
|
237
255
|
|
|
238
256
|
/**
|
|
239
257
|
* Imaging vocabulary that follows `slice` in reference material rather than in our jargon.
|
|
@@ -340,7 +358,8 @@ export function compileCanonicalRules(resolved) {
|
|
|
340
358
|
name: "phase or wave language",
|
|
341
359
|
patterns: [
|
|
342
360
|
new RegExp(
|
|
343
|
-
`\\b(?:roadmap phases?\\b[ ]?[A-Za-z0-9]*|${PHASE_NOT_CLINICAL}phases?[ -]
|
|
361
|
+
`\\b(?:roadmap phases?\\b[ ]?[A-Za-z0-9]*|${PHASE_NOT_CLINICAL}${PHASE_NOT_COMPOUND}phases?[ -]` +
|
|
362
|
+
`${PHASE_NOT_FIELD}` +
|
|
344
363
|
`[A-Za-z0-9]+[a-z]?\\b|wave \\d+\\b|the \\w+ and final phase\\b|` +
|
|
345
364
|
`documentation residual\\b|${ORDINAL} (?:slice|wave)\\b)`,
|
|
346
365
|
"gi",
|
|
@@ -350,12 +369,22 @@ export function compileCanonicalRules(resolved) {
|
|
|
350
369
|
"Phase 5b closes it (Phase W, Phase-L and the thirteenth slice landed earlier, in wave 2)",
|
|
351
370
|
// The plural, which ten of the eleven per-repository copies matched (`phases?[ -]`).
|
|
352
371
|
"Phases 1 to 6 shipped together",
|
|
372
|
+
// What the two guards must still catch: a phase named next to a compound word, and a
|
|
373
|
+
// phase named before the words that make a bare `phase` a noun.
|
|
374
|
+
"The connect-phase socket shipped in phase 2",
|
|
375
|
+
"Phase 3 was where the error occurred",
|
|
353
376
|
],
|
|
354
377
|
negatives: [
|
|
355
378
|
"CSP-1 Study Phase Identifier, CSP-2 Study Phase Start Date/Time, CSP-3 Study Phase End " +
|
|
356
379
|
"Date/Time, CSP-4 Study Phase Evaluability; a Phase III oncology trial and a Phase II " +
|
|
357
380
|
"study; the acute phase reactant; the adapter stays in phase with the source system " +
|
|
358
381
|
"and is out of phase",
|
|
382
|
+
// The compound-word guard: `phase` as the second half of an adjective.
|
|
383
|
+
"The connect-phase socket, the two-phase state and the read-phase timeout",
|
|
384
|
+
// The sentence guard: `phase` as a noun followed by what it is, not by its name.
|
|
385
|
+
"All 5 phases are defined; Connection lifecycle phase where the error occurred; the " +
|
|
386
|
+
"phase was open, the phases were closed, and a phase during which and a phase at which " +
|
|
387
|
+
"the socket failed",
|
|
359
388
|
],
|
|
360
389
|
},
|
|
361
390
|
{
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosyte/script-utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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
|
],
|
|
@@ -85,7 +98,7 @@
|
|
|
85
98
|
"access": "public"
|
|
86
99
|
},
|
|
87
100
|
"devDependencies": {
|
|
88
|
-
"vitest": "4.1.
|
|
101
|
+
"vitest": "4.1.12"
|
|
89
102
|
},
|
|
90
103
|
"scripts": {
|
|
91
104
|
"build": "node ../../scripts/pack-emdash-gate.mjs",
|