@cosyte/script-utils 0.1.1 → 0.1.2

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
@@ -12,6 +12,19 @@ pre-alpha rule. See
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.1.2] - Unreleased
16
+
17
+ ### Changed
18
+
19
+ - **`check-no-emdash.sh` refuses the en dash (U+2013) as well as the em dash** (operator decision,
20
+ 2026-11-07). It matches the en dash in the same six forms it already matched for the em dash: the
21
+ literal character, its URL encoding, its JS escape and three HTML entity spellings. Its self-test
22
+ now also proves it matches a known en dash and passes a clean line (hyphens, a range written with
23
+ "to", the minus sign U+2212). Exit codes, modes and the exclusion declaration are unchanged; the
24
+ messages name both dashes. **A tracked file or a PR text carrying an en dash in any of those forms
25
+ now refuses where 0.1.1 printed OK**: write a range with "to", and rewrite a pause with a period,
26
+ a colon, a comma or parentheses.
27
+
15
28
  ## [0.1.1] - Unreleased
16
29
 
17
30
  ### Fixed
package/README.md CHANGED
@@ -20,7 +20,7 @@ Zero-dependency helpers for the repo-local gate scripts in @cosyte/\* repos.
20
20
 
21
21
  A gate script that exits 0 without having checked anything is worse than no gate, because the run
22
22
  conclusion is the only thing anyone reads. Two ways of producing one turned out to be universal
23
- across the cosyte repositories: an entrypoint guard written by hand that answers `false` for ordinary
23
+ across the Cosyte repositories: an entrypoint guard written by hand that answers `false` for ordinary
24
24
  invocations, and thirteen byte-distinct copies of one PHI scanner, each of which had to be fixed
25
25
  separately when an escape was found.
26
26
 
@@ -30,7 +30,7 @@ request and a version bump.
30
30
 
31
31
  ## Status
32
32
 
33
- `@cosyte/script-utils` is on the cosyte 0.1.x line: the public API is settled and bump types follow ordinary semver.
33
+ `@cosyte/script-utils` is on the Cosyte 0.1.x line: the public API is settled and bump types follow ordinary semver.
34
34
 
35
35
  Still moving: the `runPhiScan` option surface, which is where the per-repo axes below are still being
36
36
  argued about, and the cross-cutting detection floor, which gains patterns as escapes are found. A new
@@ -183,8 +183,9 @@ A repository narrows WHAT IS SCANNED, and only that, through a tracked file at i
183
183
  `scripts/check-no-emdash.exclude`: one entry per line, `#` comments and blank lines ignored, an entry
184
184
  ending in `/` covering every tracked path beneath it, and every entry anchored at the repository
185
185
  root. An entry that matches no tracked path is refused rather than ignored, because an exclusion that
186
- has outlived its subject is a hole nobody is looking at. Nothing narrows WHAT IS MATCHED: the six
187
- banned forms are fixed in the gate.
186
+ has outlived its subject is a hole nobody is looking at. Nothing narrows WHAT IS MATCHED: the twelve
187
+ banned forms are fixed in the gate. They are the em dash (U+2014) and the en dash (U+2013), each as
188
+ the literal character, its URL encoding, its JS escape and three HTML entity spellings.
188
189
 
189
190
  ## Overrides
190
191
 
@@ -246,7 +247,7 @@ Is this module the file Node was pointed at, rather than one imported by somethi
246
247
 
247
248
  #### Why not compare the strings
248
249
 
249
- The obvious spelling is a one-liner, and several cosyte repos wrote it by hand:
250
+ The obvious spelling is a one-liner, and several Cosyte repos wrote it by hand:
250
251
 
251
252
  ```js
252
253
  // Do not do this.
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env bash
2
2
  # scripts/check-no-emdash.sh
3
- # Brand rule (founder directive, 2026-07-24): cosyte never uses the em dash.
3
+ # Brand rule (founder directive, 2026-07-24): we never use the em dash.
4
4
  # The em dash (U+2014) reads as an AI tell, so it is banned outright across every
5
- # cosyte surface. Source of truth: `knowledgebase/06-brand/voice-and-tone.md`
6
- # ("No em dashes. Ever."), which names COMMIT MESSAGES explicitly.
5
+ # Cosyte surface. The en dash (U+2013) is banned with it (operator decision,
6
+ # 2026-11-07), and this gate refuses both. Source of truth:
7
+ # `knowledgebase/06-brand/voice-and-tone.md`, which names COMMIT MESSAGES explicitly.
7
8
  #
8
9
  # THIS FILE IS THE ONE IMPLEMENTATION OF THE EM-DASH GATE FOR THIS ESTATE, and config
9
10
  # owns it. Every other repo EXECUTES THESE EXACT BYTES and tracks no implementation of
@@ -57,8 +58,8 @@
57
58
  # over a tree that was cleared rather than green over a tree nobody looked at. A gate
58
59
  # without the sweep reds CI on arrival; a sweep without the gate lets it grow back.
59
60
  #
60
- # The fix is never to re-encode the character: rewrite the sentence with a period, a
61
- # colon, a comma, or parentheses.
61
+ # The fix is never to re-encode the character: write a range with "to", and rewrite a
62
+ # pause with a period, a colon, a comma, or parentheses.
62
63
  #
63
64
  # ---------------------------------------------------------------------------
64
65
  # THE SURFACE. Four modes, and the exit vocabulary is CLOSED AT TWO CODES.
@@ -146,7 +147,7 @@
146
147
  # hardcoded name would have excluded a consuming repo's `scripts/check-no-emdash.sh`
147
148
  # whether or not one existed, which is an exclusion granted to a file nobody reviewed.
148
149
  #
149
- # Nothing checks the checker, so keep this file free of the literal character: it
150
+ # Nothing checks the checker, so keep this file free of either literal character: it
150
151
  # matches by codepoint and by encoding and never spells one out.
151
152
  # ---------------------------------------------------------------------------
152
153
  # BINARY POSTURE. This repo scans EVERY tracked file with NO binary partition and
@@ -230,12 +231,16 @@ export LC_ALL=C.UTF-8
230
231
  # Matches U+2014 as the literal character and as its encodings: %E2%80%94 (URL), the
231
232
  # JS backslash-u escape, and the — / — / — HTML entities. The
232
233
  # escape is not theoretical in this repo: it is what the parser-template manifest
233
- # carried, and a literal-only sweep passed straight over it.
234
- #
235
- # THE MATCH VOCABULARY IS FROZEN at these six forms. A consumer may narrow WHAT IS
236
- # SCANNED through the declaration above; nothing may narrow WHAT IS MATCHED, and
237
- # widening it is a change every consuming repo inherits at once and is specced as one.
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 – / – / – 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).
238
242
  PATTERN='\x{2014}|%E2%80%94|\\u2014|—|—|—'
243
+ PATTERN+='|\x{2013}|%E2%80%93|\\u2013|–|–|–'
239
244
 
240
245
  # The declaration's path, relative to the top level of the repo under scan. Deliberately
241
246
  # NOT relative to this file: in a consuming repo this file lives under `node_modules/`
@@ -671,10 +676,11 @@ fi
671
676
 
672
677
  if [ "$MODE" = files ] || [ "$MODE" = stdin ]; then
673
678
  # SELF-TEST: prove the scanner can still MATCH what it is meant to catch before any
674
- # clean result is believed. `printf` emits U+2014 as its UTF-8 bytes, so this file
675
- # still never contains the literal character. Scope the claim honestly: this proves
676
- # the PATTERN compiles and matches, not that the scan reached every tracked file. The
677
- # probe below and the refusals further down cover the second half.
679
+ # clean result is believed, and that it leaves alone what the rule allows. `printf`
680
+ # emits U+2014, U+2013 and U+2212 as their UTF-8 bytes, so this file still never
681
+ # contains any of those characters. Scope the claim honestly: this proves the PATTERN
682
+ # compiles and matches, not that the scan reached every tracked file. The probe below
683
+ # and the refusals further down cover the second half.
678
684
  if ! printf 'a\xe2\x80\x94b\n' | grep -qP "$PATTERN"; then
679
685
  echo "ERROR: check-no-emdash - the scanner cannot match a known em dash." >&2
680
686
  echo " grep -P is unavailable, not in UTF-8 mode (LC_ALL=${LC_ALL}), or has" >&2
@@ -683,6 +689,22 @@ if [ "$MODE" = files ] || [ "$MODE" = stdin ]; then
683
689
  echo " Refusing to report a clean tree on a scanner that cannot see." >&2
684
690
  exit 1
685
691
  fi
692
+ if ! printf 'a\xe2\x80\x93b\n' | grep -qP "$PATTERN"; then
693
+ echo "ERROR: check-no-emdash - the scanner cannot match a known en dash." >&2
694
+ echo " grep -P is unavailable, not in UTF-8 mode (LC_ALL=${LC_ALL}), or has" >&2
695
+ echo " been forced to basic regular expressions, under which the alternations" >&2
696
+ echo " in PATTERN are literals and match nothing." >&2
697
+ echo " Refusing to report a clean tree on a scanner that cannot see." >&2
698
+ exit 1
699
+ fi
700
+ # The clean fixture: hyphens, a range written with "to", and the minus sign (U+2212),
701
+ # none of which the rule bans. A scanner that matched it would fail every tree, so its
702
+ # findings could not be told from noise.
703
+ if printf 'a-b, 2 to 3 tiers, \xe2\x88\x921\n' | grep -qP "$PATTERN"; then
704
+ echo "ERROR: check-no-emdash - the scanner matched clean text (hyphens and a minus sign)." >&2
705
+ echo " Refusing to report findings from a scanner that flags what the rule allows." >&2
706
+ exit 1
707
+ fi
686
708
 
687
709
  # SCANNER VISIBILITY PROBE: prove the scanner can still READ a file before believing a
688
710
  # word it says about one. The self-test above asserts what the tool MATCHES; this
@@ -708,7 +730,7 @@ if [ "$MODE" = files ] || [ "$MODE" = stdin ]; then
708
730
  echo " NUL byte and a seeded em dash: no hit on stdout, no diagnostic on stderr," >&2
709
731
  echo " exit 0. That is a scanner that cannot see its subject, and it is" >&2
710
732
  echo " indistinguishable from a clean tree." >&2
711
- echo " The known cause is a \`grep\` interposed with -I forced (the cosyte dev" >&2
733
+ echo " The known cause is a \`grep\` interposed with -I forced (the Cosyte dev" >&2
712
734
  echo " container ships one as a shell function; \`export -f grep\` reaches a child" >&2
713
735
  echo " script). Run this gate with a real GNU grep." >&2
714
736
  exit 1
@@ -719,9 +741,10 @@ fail_with_hits() {
719
741
  local what="$1" hits="$2"
720
742
  echo "$hits" >&2
721
743
  echo "" >&2
722
- echo "ERROR: check-no-emdash - em dash (U+2014, or an encoded form) found in ${what}." >&2
723
- echo " cosyte never uses em dashes (founder directive; 06-brand/voice-and-tone.md)." >&2
724
- echo " Rewrite with a period, colon, comma, or parentheses. Never re-encode it." >&2
744
+ echo "ERROR: check-no-emdash - em or en dash (U+2014 or U+2013, or an encoded form) found in ${what}." >&2
745
+ echo " We never use em or en dashes (founder directive; 06-brand/voice-and-tone.md)." >&2
746
+ echo " Write a range with \"to\"; rewrite a pause with a period, colon, comma, or" >&2
747
+ echo " parentheses. Never re-encode it." >&2
725
748
  exit 1
726
749
  }
727
750
 
@@ -738,7 +761,7 @@ refuse_if_incomplete() {
738
761
  if grep -qi 'binary file' "$ERRLOG"; then
739
762
  echo "ERROR: check-no-emdash - the input named above MATCHED the banned pattern, but" >&2
740
763
  echo " grep classifies it as binary, so the hit is reported without a line" >&2
741
- echo " number. Treat it as a real em dash." >&2
764
+ echo " number. Treat it as a real em or en dash." >&2
742
765
  if [ "$SCAN_MODE" = stdin ]; then
743
766
  echo " The text piped in is not valid UTF-8. Fix the encoding of whatever" >&2
744
767
  echo " produced it, then rewrite the sentence." >&2
@@ -749,7 +772,7 @@ refuse_if_incomplete() {
749
772
  echo " has ever tracked. If it is the latter, read the BINARY POSTURE note in" >&2
750
773
  echo " this script's header before changing anything." >&2
751
774
  fi
752
- echo " cosyte never uses em dashes (founder directive; 06-brand/voice-and-tone.md)." >&2
775
+ echo " We never use em or en dashes (founder directive; 06-brand/voice-and-tone.md)." >&2
753
776
  fi
754
777
  if grep -qiv 'binary file' "$ERRLOG"; then
755
778
  echo "ERROR: check-no-emdash - the scan reported errors, so it did not read all of" >&2
@@ -776,7 +799,7 @@ if [ "$MODE" = stdin ]; then
776
799
  HITS=$(grep -nP -e "$PATTERN" -- "$STDINBUF" 2>> "$ERRLOG" || true)
777
800
  refuse_if_incomplete
778
801
  [ -n "$HITS" ] && fail_with_hits "$LABEL" "$HITS"
779
- echo "check-no-emdash: OK (no em dashes in ${LABEL})"
802
+ echo "check-no-emdash: OK (no em or en dashes in ${LABEL})"
780
803
  exit 0
781
804
  fi
782
805
 
@@ -869,6 +892,6 @@ refuse_if_incomplete
869
892
 
870
893
  [ -n "$HITS" ] && fail_with_hits "the tracked files listed above" "$HITS"
871
894
 
872
- echo "check-no-emdash: OK (no em dashes in ${scanned} of ${#TRACKED[@]} tracked file(s);" \
895
+ echo "check-no-emdash: OK (no em or en dashes in ${scanned} of ${#TRACKED[@]} tracked file(s);" \
873
896
  "${gitlinks} gitlink(s) skipped, ${self_excluded} excluded as the implementation under" \
874
897
  "scan, ${declared_excluded} excluded by ${DECLARATION})"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosyte/script-utils",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Zero-dependency helpers for the repo-local gate scripts in @cosyte/* repos.",
5
5
  "keywords": [
6
6
  "cli",