@cosyte/script-utils 0.0.1 → 0.1.0
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 +174 -2
- package/README.md +423 -17
- package/attw.d.ts +36 -0
- package/attw.js +1679 -0
- package/check-no-emdash.sh +874 -0
- package/internal-refs.d.ts +223 -0
- package/internal-refs.js +1363 -0
- package/package.json +28 -1
- package/phi-scan.d.ts +271 -0
- package/phi-scan.js +1936 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,184 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@cosyte/script-utils` are documented here, following
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions are managed with Changesets;
|
|
5
|
-
this file is hand-maintained (Changesets' own changelog generation is disabled). The package
|
|
6
|
-
|
|
5
|
+
this file is hand-maintained (Changesets' own changelog generation is disabled). The package is on the
|
|
6
|
+
**`0.1.x`** line: its surface is settled, and bump types follow ordinary semver rather than a
|
|
7
|
+
pre-alpha rule. See
|
|
8
|
+
[ADR 0002](../../documentation/decisions/0002-the-0-1-0-version-line.md).
|
|
7
9
|
|
|
8
10
|
> Because the generator is disabled, **`[Unreleased]` is promoted to a version heading BY HAND**, in
|
|
9
11
|
> the pull request that adds the changeset. Nothing does it automatically.
|
|
10
12
|
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
## [0.1.0] - Unreleased
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **The package leaves the pre-alpha version ladder for the `0.1.x` line.** `isCliEntrypoint`
|
|
20
|
+
exports exactly what `0.0.2` exports. The phi-scan engine does NOT: see the entries below, which
|
|
21
|
+
are what this release actually carries on that subpath. The version policy itself is unchanged in
|
|
22
|
+
substance and the reasoning is in
|
|
23
|
+
[ADR 0002](../../documentation/decisions/0002-the-0-1-0-version-line.md). A consumer pinned at
|
|
24
|
+
`^0.0.2` does not resolve this release and has to widen its range once, which is recorded as break
|
|
25
|
+
candidate BC-2 in `documentation/release-0.1.0-audit.md`.
|
|
26
|
+
- **`phi-scan`: `all` mode refuses unless EVERY scan root yielded a file that was read**, naming the
|
|
27
|
+
starved roots. The completeness rule asks whether every target was read; nothing asked whether a
|
|
28
|
+
root produced a target at all, and one productive root made the whole run look productive.
|
|
29
|
+
Measured before the change: a caller supplying an `isWalkReadable` that admits nothing read no
|
|
30
|
+
file on either sweeping route and printed `OK: no hits` at the CLEAN code over a tracked file
|
|
31
|
+
carrying a live dashed identifier.
|
|
32
|
+
- **`phi-scan`: an UNTRACKED file removed mid-run is reported SKIPPED rather than refusing.** A
|
|
33
|
+
target the walk listed and that was gone by read time refused unconditionally, which made an
|
|
34
|
+
ordinary mid-run deletion look like an unaccounted-for file. Git carries no bytes at such a path,
|
|
35
|
+
so nothing the repository holds went unread. The exception is bounded: a tracked file, a
|
|
36
|
+
non-`ENOENT` failure, a path named on argv and a path that came BACK before the run ended all
|
|
37
|
+
still refuse.
|
|
38
|
+
- **`phi-scan`: a finding names a position and stops there.** A hit report prints the locus, the
|
|
39
|
+
locator inside it and the rule that fired, and no longer the token that matched:
|
|
40
|
+
`segment=(ssn) (dashed SSN pattern)` where it used to carry the identifier as well. stderr is a CI
|
|
41
|
+
log, and a diagnostic ABOUT a PHI leak that quotes the leak is a second copy of it somewhere with
|
|
42
|
+
no retention policy. `Hit.value` is now optional, a hit the engine reports never carries one, and
|
|
43
|
+
**a gate test asserting that the matched identifier reaches stderr asserts on the locator and the
|
|
44
|
+
rule instead.**
|
|
45
|
+
- **`phi-scan`: `all` mode reads the bytes git carries at EVERY tracked path.** `scanRoots` bounds
|
|
46
|
+
the WALK, which answers what is on disk under the roots you declared; the index answers what your
|
|
47
|
+
repository CARRIES, and a root list was narrowing that too. Measured on the first consumer to
|
|
48
|
+
adopt this engine: three real messages under an undeclared top-level directory were read by
|
|
49
|
+
neither route, and a tracked symbolic link outside every root was reached by neither. **A
|
|
50
|
+
repository whose roots are narrower than itself has more of it read after this release.** The two
|
|
51
|
+
READ filters are unchanged and still apply on both sweeping routes, so a tracked `.md` and an
|
|
52
|
+
`excludedPaths` entry mean what they meant. `--staged` is untouched.
|
|
53
|
+
- **`phi-scan`: the `--staged` route refuses what `isStagedReadable` admits, and nothing else.** Its
|
|
54
|
+
non-regular and unmerged refusals used to fire for any path under a scan root, so a repository
|
|
55
|
+
whose staged scope is narrower than its roots had commits blocked that its own gate had always let
|
|
56
|
+
through: what a commit is blocked on is a hook decision each repository takes for itself. **This
|
|
57
|
+
refuses LESS at the pre-commit hook.** A staged entry your staged scope declines is still refused
|
|
58
|
+
by `all` mode on both of its routes, so your own sweep catches it; widen `isStagedReadable` if you
|
|
59
|
+
want the hook to refuse it too. Recorded with its compensating control as N1 in
|
|
60
|
+
[ADR 0003](../../documentation/decisions/0003-the-phi-scan-engine-gap-settlements.md).
|
|
61
|
+
- **`phi-scan`: a hit in the bytes git carries says WHICH kind it is.** The single
|
|
62
|
+
`(as git carries it)` label becomes `(git index)` for a path whose working-tree copy the run never
|
|
63
|
+
read and `(git index; the working tree differs)` for one whose disk copy carries other bytes, with
|
|
64
|
+
a footer counting how many reported hits are in those bytes. The second kind reads clean in the
|
|
65
|
+
file a developer opens, so re-staging is part of its remedy and one label hid that. **A gate test
|
|
66
|
+
asserting the old label asserts one of the two new ones.**
|
|
67
|
+
- **`phi-scan`: an index-route refusal no longer discards the hits already in hand.** An unmerged
|
|
68
|
+
index entry or a tracked link refused the whole sweep before anything was read, so a consumer saw
|
|
69
|
+
a refusal with no indication that PHI had already been found. The walk sweeps first, prints what
|
|
70
|
+
it found, and the refusal follows at the same code.
|
|
71
|
+
- **`phi-scan`: a clean run that skipped a file says so on stdout**, as
|
|
72
|
+
`OK: no hits (N untracked file(s) skipped, see stderr)`. With nothing skipped the line is
|
|
73
|
+
byte-identical to what it was.
|
|
74
|
+
- **`phi-scan`: a scan root has one settled outcome per KIND.** A regular file is scanned as one
|
|
75
|
+
target, a symbolic link is refused rather than followed, and a directory that cannot be listed is
|
|
76
|
+
refused at your `refuse` code naming the path: that one used to escape `readdirSync` uncaught and
|
|
77
|
+
take node's own exit 1, the code this contract reserves for HITS FOUND. The kind stays DERIVED
|
|
78
|
+
from the filesystem rather than declared, and what that gives up is recorded as N2 in ADR 0003.
|
|
79
|
+
- **`phi-scan`: two refusals reworded because their remedies differ.** "git could not read this
|
|
80
|
+
repository's git index" and "the git index holds no entries" were one sentence. The starved-root
|
|
81
|
+
refusal says how many of how many roots went unobserved and names them.
|
|
82
|
+
- **`phi-scan`: which tier refuses `--allow-fixture` follows the MODE.** A bypass beside a positional
|
|
83
|
+
path names something the run does not enumerate; a lone bypass declares a path an `all`-mode run
|
|
84
|
+
would have read and is refused by the completeness rule. Both name the path, and neither reaches a
|
|
85
|
+
clean run, which is unchanged.
|
|
86
|
+
- The `README.md` in the tarball now opens its `## Status` section on the settled-line sentence
|
|
87
|
+
instead of the pre-alpha ladder one, so the policy text a consumer reads agrees with the version
|
|
88
|
+
printed beside it. `scripts/readme-check.mjs` grades that sentence against the release line the
|
|
89
|
+
pending changesets resolve to.
|
|
90
|
+
|
|
91
|
+
## [0.0.2] - 2026-08-11
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
|
|
95
|
+
- **`@cosyte/script-utils/phi-scan`: the shared machinery of the `@cosyte/*` PHI commit-gate, as a
|
|
96
|
+
parameterised engine.** `scripts/parser-template/` is a SCAFFOLD rather than a dependency, so
|
|
97
|
+
`scripts/phi-scan.ts` was COPIED into every parser repo. **Thirteen repos, thirteen byte-distinct
|
|
98
|
+
copies**, so a newly-found escape cost one pull request and one adversarial review PER REPO, and
|
|
99
|
+
three escape classes had already been paid for that way. `runPhiScan(config)` owns argument
|
|
100
|
+
parsing, the allow-list and override log, target enumeration on all three routes, the union of the
|
|
101
|
+
working-tree walk with the bytes git carries, content deduplication under git's own
|
|
102
|
+
`blob <len>\0` framing, the completeness rule, every refusal, and the cross-cutting SSN/email
|
|
103
|
+
floor.
|
|
104
|
+
|
|
105
|
+
**THE FIVE PER-REPO AXES ARE PARAMETERS, NOT FORKS**, and which ones are required is itself the
|
|
106
|
+
design. `exitCodes`, `scanRoots` and `isStagedReadable` are **REQUIRED**: the siblings genuinely
|
|
107
|
+
disagree on all three, and a default would be the porting mistake the gate exists to catch (at
|
|
108
|
+
least one sibling uses 2 where another uses 1). `excludedPaths`, `isWalkReadable` (the Markdown
|
|
109
|
+
read exemption) and `regularBlobModes` are **DEFAULTED**, so moving one of those boundaries is a
|
|
110
|
+
change to this package plus a version bump rather than an edit in thirteen repos. EOL
|
|
111
|
+
normalization has **no parameter at all** and stays machinery: the walk/index deduplication is by
|
|
112
|
+
CONTENT, so where a `text` attribute or `core.autocrlf` makes the index carry LF and the working
|
|
113
|
+
tree CRLF, BOTH forms are scanned.
|
|
114
|
+
|
|
115
|
+
**Per-standard field detection stays with the caller**, through `detect`. It is handed the
|
|
116
|
+
reported LOCUS rather than the target path, so a hit found in the bytes git carries cannot be
|
|
117
|
+
reported against a working-tree copy a developer would open and find clean. That used to be a
|
|
118
|
+
sentence in a comment; it is now the only path a caller can reach.
|
|
119
|
+
|
|
120
|
+
- **A scan root may name a regular FILE, and the kind is DERIVED from the filesystem rather than
|
|
121
|
+
declared**, which is what keeps the parameter a plain `string[]`. Measured against the thirteen
|
|
122
|
+
live copies: they declare roots in at least six different shapes, and one declares
|
|
123
|
+
`{ rel, shape: "directory" | "file" }` with a single file among them. An earlier draft fed every
|
|
124
|
+
root to `readdirSync`, so such a root threw `ENOTDIR`, uncaught, and the run took node's exit 1,
|
|
125
|
+
the code this contract reserves for HITS FOUND. **What deriving gives up is stated rather than
|
|
126
|
+
left to be found**: a declaration can notice a root is not the KIND it was meant to be and
|
|
127
|
+
derivation cannot. A root that is neither a file nor a directory is still refused, a root naming a
|
|
128
|
+
symbolic link is refused rather than followed, and a MISSING root is skipped, which is unchanged
|
|
129
|
+
from the copied scanners and is named as the one remaining silently-empty root state.
|
|
130
|
+
- **A scan root of `"."` means the whole repository**, with gitignored directories pruned during
|
|
131
|
+
descent and `.git` skipped by literal name, which is what makes a whole-repository root usable at
|
|
132
|
+
all rather than a walk through `node_modules`. Pruning is equivalent to filtering afterwards
|
|
133
|
+
because **`git check-ignore` is index-aware at directory granularity**, measured both ways on git
|
|
134
|
+
2.39.5: with nothing tracked underneath, a gitignored directory is reported ignored and pruned;
|
|
135
|
+
with one file force-added underneath it is reported NOT ignored, so the walk descends and still
|
|
136
|
+
reads it. An earlier draft justified this with the gitignore-pattern rule that a path under an
|
|
137
|
+
excluded directory cannot be re-included, which does not settle it, because the filter being
|
|
138
|
+
replaced asked `check-ignore` too.
|
|
139
|
+
|
|
140
|
+
### Changed
|
|
141
|
+
|
|
142
|
+
- **Two containments the first draft ASSERTED are now ENFORCED**, both falsified by this slice's
|
|
143
|
+
adversarial review and both reproduced against the pre-consolidation scanner too, so neither is a
|
|
144
|
+
regression this package introduced. What was introduced was the sentence claiming they held, on the
|
|
145
|
+
API contract thirteen migrations are about to be written against.
|
|
146
|
+
- **A staged path `isStagedReadable` admits that no scan root covers is REFUSED.** The type said
|
|
147
|
+
the `--staged` filter was "narrower than the root half by construction"; nothing constructed it,
|
|
148
|
+
because they are two independent keys. Measured with roots at `["src"]` and the filter at the
|
|
149
|
+
shared Markdown exemption: a STAGED mode-120000 entry under `test/fixtures/` was outside every
|
|
150
|
+
scan root, so the non-regular refusal never saw it, and the route enumerated it, READ it, handed
|
|
151
|
+
the link's TARGET PATH to the detector as if it were content, counted the scan complete and
|
|
152
|
+
printed `OK: no hits` at exit 0. Narrowing silently to the intersection would have been the wrong
|
|
153
|
+
repair: it hides a misconfiguration in the one place the gate blocks a commit.
|
|
154
|
+
- **A scan root is normalised the way every other path is, and one resolving outside the repository
|
|
155
|
+
is refused.** `"./src"` is a spelling the type documents as valid; it walked correctly while
|
|
156
|
+
`isUnderScanRoot` compared it against the normalized index path and never matched, emptying the
|
|
157
|
+
union, the index non-blob refusal and the unmerged refusal in silence. Measured: `["src"]`
|
|
158
|
+
refused a tracked mode-120000 entry at exit 2 while `["./src"]` reported clean at exit 0 over the
|
|
159
|
+
same repository.
|
|
160
|
+
- **The optional axes are shape-checked.** `excludedPaths: ["a"]`, a plausible reading of
|
|
161
|
+
"repo-relative paths", used to survive normalization, reach `.has(...)` inside enumeration, and
|
|
162
|
+
take node's exit 1 from there, which the exit contract reserves for HITS FOUND.
|
|
163
|
+
- **The floor's dashed-SSN branch now consults `allow.ids`, in either rendering.** This is a
|
|
164
|
+
correction rather than a port: with the whole-file `--allow-fixture` bypass closed, a detector
|
|
165
|
+
that consults nothing leaves a developer with a hit and **no remedy at all**, and a sibling
|
|
166
|
+
shipped a footer claiming the token allow-list was the only remedy that reaches a clean run while
|
|
167
|
+
that was false for exactly this branch. One `ID` entry now covers both the dashed and the undashed
|
|
168
|
+
rendering, so a repo does not have to guess which one a fixture uses.
|
|
169
|
+
- **A fatal partway through the sweep prints the hits already found BEFORE the refusal**, instead of
|
|
170
|
+
discarding them. Measured on a mutant that throws after the reads: the old ordering produced a
|
|
171
|
+
refusal with **zero** `HIT:` lines over a corpus containing a real hit. The refusal still wins the
|
|
172
|
+
exit code and the clean line is still unreachable from there, so nothing is reported as accounted
|
|
173
|
+
for that is not.
|
|
174
|
+
- **The hit footer is scoped to what the engine can know.** It says the cross-cutting floor consults
|
|
175
|
+
the allow-list and that a repo's own detectors are answerable that way **only if** they consult
|
|
176
|
+
it. The engine cannot see inside a caller's `detect`, so a wider claim would be one it has no
|
|
177
|
+
evidence for.
|
|
178
|
+
- **A detector that throws REFUSES the scan** rather than escaping to node's own exit code, which
|
|
179
|
+
this contract reserves for HITS FOUND. A misconfigured scanner throws a `TypeError` rather than
|
|
180
|
+
returning a code: at the point a required axis is missing, `exitCodes` is itself the thing that
|
|
181
|
+
was not supplied, so there is no trustworthy code to return.
|
|
182
|
+
|
|
11
183
|
## [0.0.1] - 2026-08-05
|
|
12
184
|
|
|
13
185
|
### Added
|