@cosyte/script-utils 0.1.4 → 0.1.6
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 +17 -0
- package/README.md +52 -12
- package/bin/cosyte-agent-notes.js +3 -2
- package/bin/cosyte-attw.js +3 -2
- package/bin/cosyte-catalog.js +8 -6
- package/bin/cosyte-claude-card.js +3 -2
- package/bin/cosyte-internal-refs.js +3 -2
- package/bin/cosyte-phi-scan.js +3 -2
- package/bin/cosyte-readme.js +3 -2
- package/bin/help.js +41 -0
- package/bin/script-utils.js +8 -3
- package/catalog.d.ts +10 -3
- package/catalog.js +44 -15
- package/catalog.json +5 -5
- package/check-no-emdash.sh +180 -15
- package/internal-refs.d.ts +31 -0
- package/internal-refs.js +493 -17
- package/package.json +2 -2
- package/phi-scan.d.ts +11 -0
- package/phi-scan.js +174 -23
package/check-no-emdash.sh
CHANGED
|
@@ -118,6 +118,22 @@
|
|
|
118
118
|
# * A repo with no such file declares no exclusions.
|
|
119
119
|
# * The declaration file is itself tracked and is SCANNED LIKE ANY OTHER TRACKED
|
|
120
120
|
# FILE. It is not self-excluding.
|
|
121
|
+
# * A BOUNDARY is the one entry that names part of a file rather than a whole one:
|
|
122
|
+
#
|
|
123
|
+
# CHANGELOG.md em-only below ## Released before this file was generated
|
|
124
|
+
# CHANGELOG.md unread below ## Released before this file was generated
|
|
125
|
+
#
|
|
126
|
+
# It is for a changelog whose dated archive predates the generated part and is kept
|
|
127
|
+
# verbatim. The path is ONE EXACT TRACKED PATH (no `/` prefix entry, no spaces), and
|
|
128
|
+
# the text after `below ` is a WHOLE LINE of that file, matched exactly. From the
|
|
129
|
+
# first line equal to it to the end of the file is the archive: `em-only` reads the
|
|
130
|
+
# archive for the em dash alone, in every form, so an en dash in an old range passes
|
|
131
|
+
# and an em dash still reds; `unread` does not read the archive at all. Everything
|
|
132
|
+
# above the boundary line is read for both dashes like any other file, and the file's
|
|
133
|
+
# NAME is scanned as always. A boundary line the file does not contain is a stale
|
|
134
|
+
# entry and the run exits 1, so a lost heading fails closed instead of widening what
|
|
135
|
+
# is left out. A path may carry one boundary, and a path the declaration also leaves
|
|
136
|
+
# out whole is refused, since it cannot be both.
|
|
121
137
|
#
|
|
122
138
|
# AN ENTRY IS THE LINE VERBATIM: nothing is trimmed off either end, because a path is
|
|
123
139
|
# bytes and trimming would invent a second spelling for one entry. A line carrying
|
|
@@ -255,6 +271,14 @@ PATTERN='(?i)\x{2014}|\x{2013}|&[mn]dash(?![a-z])|�*821[12](?![0-9])|�*201[
|
|
|
255
271
|
PATTERN+='|%E2%80%9[34]|\\u(?:0000)?201[34]|\\u\{0*201[34]\}|\\x\{0*201[34]\}'
|
|
256
272
|
PATTERN+='|\\0{0,2}201[34](?![0-9a-f])|\\N\{E[MN] DASH\}'
|
|
257
273
|
|
|
274
|
+
# The em dash's half of PATTERN, form for form, for the archive a declared `em-only`
|
|
275
|
+
# boundary reads. The self-test below holds it to matching the em dash and NOT the en
|
|
276
|
+
# dash, and test/check-no-emdash.test.ts holds every em form to it, so it cannot drift
|
|
277
|
+
# from PATTERN without a red.
|
|
278
|
+
EM_PATTERN='(?i)\x{2014}|&mdash(?![a-z])|�*8212(?![0-9])|�*2014(?![0-9a-f])'
|
|
279
|
+
EM_PATTERN+='|%E2%80%94|\\u(?:0000)?2014|\\u\{0*2014\}|\\x\{0*2014\}'
|
|
280
|
+
EM_PATTERN+='|\\0{0,2}2014(?![0-9a-f])|\\N\{EM DASH\}'
|
|
281
|
+
|
|
258
282
|
# The declaration's path, relative to the top level of the repo under scan. Deliberately
|
|
259
283
|
# NOT relative to this file: in a consuming repo this file lives under `node_modules/`
|
|
260
284
|
# and the declaration is the consumer's own tracked file.
|
|
@@ -314,6 +338,25 @@ if [ "$#" -gt 0 ] && [ "$1" = '--' ]; then
|
|
|
314
338
|
shift
|
|
315
339
|
fi
|
|
316
340
|
|
|
341
|
+
usage() {
|
|
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. A changelog archive is named by a boundary,
|
|
353
|
+
`PATH em-only below LINE` or `PATH unread below LINE`: from the first line of PATH equal to LINE to
|
|
354
|
+
the end, em-only reads for the em dash alone and unread reads nothing; above it, both dashes as
|
|
355
|
+
usual. Exit 0 means the mode completed and found nothing banned; exit 1 means anything else. Under
|
|
356
|
+
CI, --help exits 1: it checks nothing.
|
|
357
|
+
USAGE
|
|
358
|
+
}
|
|
359
|
+
|
|
317
360
|
case "${1:-}" in
|
|
318
361
|
'')
|
|
319
362
|
MODE=files
|
|
@@ -339,19 +382,17 @@ case "${1:-}" in
|
|
|
339
382
|
fi
|
|
340
383
|
;;
|
|
341
384
|
--help | -h)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
nothing banned; exit 1 means anything else.
|
|
354
|
-
USAGE
|
|
385
|
+
# Under CI the help is a refusal: it prints this text and checks nothing, so a CI step that
|
|
386
|
+
# runs it must not pass. `CI` set to anything but empty, `false` or `0` is how GitHub Actions
|
|
387
|
+
# and the other common runners announce themselves.
|
|
388
|
+
if [ -n "${CI:-}" ] && [ "${CI}" != false ] && [ "${CI}" != 0 ]; then
|
|
389
|
+
usage >&2
|
|
390
|
+
echo "ERROR: check-no-emdash - refusing --help under CI (CI=${CI}). It prints this text and" >&2
|
|
391
|
+
echo " checks nothing, so a CI step that runs it must not pass. Run the gate without" >&2
|
|
392
|
+
echo " --help." >&2
|
|
393
|
+
exit 1
|
|
394
|
+
fi
|
|
395
|
+
usage
|
|
355
396
|
exit 0
|
|
356
397
|
;;
|
|
357
398
|
*)
|
|
@@ -557,6 +598,13 @@ entry_matches_a_tracked_path() {
|
|
|
557
598
|
|
|
558
599
|
# ---- the exclusion declaration ---------------------------------------------------
|
|
559
600
|
EXCLUDES=()
|
|
601
|
+
# A declared boundary, one index per entry: the path, `em-only` or `unread`, the boundary
|
|
602
|
+
# line as written, and the number of the first line of the file equal to it.
|
|
603
|
+
BOUND_PATH=()
|
|
604
|
+
BOUND_MODE=()
|
|
605
|
+
BOUND_TEXT=()
|
|
606
|
+
BOUND_LINE=()
|
|
607
|
+
BOUNDARY_SHAPE='^([^ ]+) (em-only|unread) below (.+)$'
|
|
560
608
|
refuse_declaration() {
|
|
561
609
|
local line="$1" entry="$2" what="$3"
|
|
562
610
|
echo "ERROR: check-no-emdash - unusable entry in ${DECLARATION}, line ${line}: ${entry}" >&2
|
|
@@ -589,6 +637,45 @@ if is_tracked "$DECLARATION"; then
|
|
|
589
637
|
# The entry is the line VERBATIM. See the header: nothing is trimmed, because a path
|
|
590
638
|
# is bytes. The anchor rules are the whole of what makes an entry usable on its face.
|
|
591
639
|
entry=$line
|
|
640
|
+
|
|
641
|
+
# A BOUNDARY names part of one file. It is recognised by its shape and validated on its
|
|
642
|
+
# own terms, so nothing below it can mistake it for a path with spaces in it.
|
|
643
|
+
if [[ $entry =~ $BOUNDARY_SHAPE ]]; then
|
|
644
|
+
b_path=${BASH_REMATCH[1]}
|
|
645
|
+
b_mode=${BASH_REMATCH[2]}
|
|
646
|
+
b_text=${BASH_REMATCH[3]}
|
|
647
|
+
case $b_path in
|
|
648
|
+
/* | ./* | *..* | */)
|
|
649
|
+
refuse_declaration "$lineno" "$entry" \
|
|
650
|
+
"A boundary names ONE tracked file, anchored at the repository root: not absolute, not ./, no .., and not a directory."
|
|
651
|
+
;;
|
|
652
|
+
esac
|
|
653
|
+
if ! is_tracked "$b_path"; then
|
|
654
|
+
refuse_declaration "$lineno" "$entry" \
|
|
655
|
+
"No tracked file is spelled ${b_path}. A boundary that has outlived its file is a hole nobody is looking at."
|
|
656
|
+
fi
|
|
657
|
+
for i in "${!BOUND_PATH[@]}"; do
|
|
658
|
+
if [ "${BOUND_PATH[$i]}" = "$b_path" ]; then
|
|
659
|
+
refuse_declaration "$lineno" "$entry" \
|
|
660
|
+
"${b_path} already carries a boundary. A file has one archive, so it has one boundary."
|
|
661
|
+
fi
|
|
662
|
+
done
|
|
663
|
+
if [ ! -f "$b_path" ] || [ ! -r "$b_path" ]; then
|
|
664
|
+
refuse_declaration "$lineno" "$entry" \
|
|
665
|
+
"${b_path} is not a readable regular file, so its boundary line cannot be looked for."
|
|
666
|
+
fi
|
|
667
|
+
b_line=$(grep -n -m1 -xF -e "$b_text" -- "$b_path" 2> /dev/null || true)
|
|
668
|
+
b_line=${b_line%%:*}
|
|
669
|
+
if [ -z "$b_line" ]; then
|
|
670
|
+
refuse_declaration "$lineno" "$entry" \
|
|
671
|
+
"${b_path} has no line that is exactly: ${b_text}. A lost boundary fails closed rather than leaving out more than was declared: restore the line, or correct or delete the entry."
|
|
672
|
+
fi
|
|
673
|
+
BOUND_PATH+=("$b_path")
|
|
674
|
+
BOUND_MODE+=("$b_mode")
|
|
675
|
+
BOUND_TEXT+=("$b_text")
|
|
676
|
+
BOUND_LINE+=("$b_line")
|
|
677
|
+
continue
|
|
678
|
+
fi
|
|
592
679
|
case $entry in
|
|
593
680
|
/*)
|
|
594
681
|
refuse_declaration "$lineno" "$entry" \
|
|
@@ -648,6 +735,30 @@ is_excluded() {
|
|
|
648
735
|
return 1
|
|
649
736
|
}
|
|
650
737
|
|
|
738
|
+
# A path is either left out whole or read up to its boundary. Both at once would leave the
|
|
739
|
+
# reader unable to say which one the gate honoured, so it is refused, whichever line came
|
|
740
|
+
# first.
|
|
741
|
+
for i in "${!BOUND_PATH[@]}"; do
|
|
742
|
+
if is_excluded "${BOUND_PATH[$i]}"; then
|
|
743
|
+
echo "ERROR: check-no-emdash - ${DECLARATION} both leaves out ${BOUND_PATH[$i]} and" >&2
|
|
744
|
+
echo " declares a boundary in it. A file is either left out whole or read up to its" >&2
|
|
745
|
+
echo " boundary; delete one of the two entries." >&2
|
|
746
|
+
exit 1
|
|
747
|
+
fi
|
|
748
|
+
done
|
|
749
|
+
|
|
750
|
+
# The index of the boundary declared for a path, or nothing.
|
|
751
|
+
boundary_index() {
|
|
752
|
+
local p="$1" i
|
|
753
|
+
for i in "${!BOUND_PATH[@]}"; do
|
|
754
|
+
if [ "${BOUND_PATH[$i]}" = "$p" ]; then
|
|
755
|
+
printf '%s' "$i"
|
|
756
|
+
return 0
|
|
757
|
+
fi
|
|
758
|
+
done
|
|
759
|
+
return 1
|
|
760
|
+
}
|
|
761
|
+
|
|
651
762
|
# ---- --self-id: which bytes am I ------------------------------------------------
|
|
652
763
|
#
|
|
653
764
|
# Answered before the scanner assertions, because this mode reads no scanned content and
|
|
@@ -736,6 +847,20 @@ if [ "$MODE" = files ] || [ "$MODE" = stdin ]; then
|
|
|
736
847
|
echo " Refusing to report findings from a scanner that flags what the rule allows." >&2
|
|
737
848
|
exit 1
|
|
738
849
|
fi
|
|
850
|
+
# The archive reading a declared `em-only` boundary uses: it must still see the em dash,
|
|
851
|
+
# and it must NOT see the en dash, or `em-only` would mean something other than it says.
|
|
852
|
+
if [ "${#BOUND_PATH[@]}" -gt 0 ]; then
|
|
853
|
+
if ! printf 'a\xe2\x80\x94b\n' | grep -qP "$EM_PATTERN"; then
|
|
854
|
+
echo "ERROR: check-no-emdash - the archive scanner cannot match a known em dash." >&2
|
|
855
|
+
echo " Refusing to read a declared boundary's archive with a scanner that cannot see." >&2
|
|
856
|
+
exit 1
|
|
857
|
+
fi
|
|
858
|
+
if printf 'a\xe2\x80\x93b\n' | grep -qP "$EM_PATTERN"; then
|
|
859
|
+
echo "ERROR: check-no-emdash - the archive scanner matched an en dash, so it is not reading" >&2
|
|
860
|
+
echo " for the em dash alone. Refusing to report what an em-only boundary allows." >&2
|
|
861
|
+
exit 1
|
|
862
|
+
fi
|
|
863
|
+
fi
|
|
739
864
|
|
|
740
865
|
# SCANNER VISIBILITY PROBE: prove the scanner can still READ a file before believing a
|
|
741
866
|
# word it says about one. The self-test above asserts what the tool MATCHES; this
|
|
@@ -901,12 +1026,20 @@ for i in "${!TRACKED[@]}"; do
|
|
|
901
1026
|
exit 1
|
|
902
1027
|
fi
|
|
903
1028
|
|
|
1029
|
+
# A file with a declared boundary is read on its own below, in two halves. It is still
|
|
1030
|
+
# READ, so it is listed for --list-scanned and counted as scanned.
|
|
1031
|
+
if boundary_index "$f" > /dev/null; then
|
|
1032
|
+
printf '%s\0' "$f" >> "$PLAINLIST"
|
|
1033
|
+
scanned=$((scanned + 1))
|
|
1034
|
+
continue
|
|
1035
|
+
fi
|
|
1036
|
+
|
|
904
1037
|
printf './%s\0' "$f" >> "$SCANLIST"
|
|
905
1038
|
printf '%s\0' "$f" >> "$PLAINLIST"
|
|
906
1039
|
scanned=$((scanned + 1))
|
|
907
1040
|
done
|
|
908
1041
|
|
|
909
|
-
if [ ! -s "$SCANLIST" ]; then
|
|
1042
|
+
if [ ! -s "$SCANLIST" ] && [ "${#BOUND_PATH[@]}" -eq 0 ]; then
|
|
910
1043
|
echo "ERROR: check-no-emdash - no tracked files left to scan. Refusing to report" >&2
|
|
911
1044
|
echo " green from a scan that read nothing." >&2
|
|
912
1045
|
exit 1
|
|
@@ -932,6 +1065,38 @@ HITS=$(xargs -0 -r grep -H -nP -e "$PATTERN" -- < "$SCANLIST" 2>> "$ERRLOG" || t
|
|
|
932
1065
|
|
|
933
1066
|
refuse_if_incomplete
|
|
934
1067
|
|
|
1068
|
+
# The files with a declared boundary. Every hit is found with the full pattern first, so
|
|
1069
|
+
# the line numbers are the file's own; a hit above the boundary line stands, and a hit at
|
|
1070
|
+
# or below it stands only for `em-only` and only when the line holds an em dash.
|
|
1071
|
+
bounded_note=''
|
|
1072
|
+
for i in "${!BOUND_PATH[@]}"; do
|
|
1073
|
+
b_path=${BOUND_PATH[$i]}
|
|
1074
|
+
b_line=${BOUND_LINE[$i]}
|
|
1075
|
+
b_mode=${BOUND_MODE[$i]}
|
|
1076
|
+
b_hits=$(grep -H -nP -e "$PATTERN" -- "./${b_path}" 2>> "$ERRLOG" || true)
|
|
1077
|
+
refuse_if_incomplete
|
|
1078
|
+
while IFS= read -r hit; do
|
|
1079
|
+
[ -n "$hit" ] || continue
|
|
1080
|
+
n=${hit#"./${b_path}:"}
|
|
1081
|
+
n=${n%%:*}
|
|
1082
|
+
keep=0
|
|
1083
|
+
if [ "$n" -lt "$b_line" ]; then
|
|
1084
|
+
keep=1
|
|
1085
|
+
elif [ "$b_mode" = em-only ] && printf '%s\n' "${hit#"./${b_path}:${n}:"}" | grep -qP -e "$EM_PATTERN"; then
|
|
1086
|
+
keep=1
|
|
1087
|
+
fi
|
|
1088
|
+
if [ "$keep" -eq 1 ]; then
|
|
1089
|
+
HITS="${HITS:+${HITS}
|
|
1090
|
+
}${hit}"
|
|
1091
|
+
fi
|
|
1092
|
+
done <<< "$b_hits"
|
|
1093
|
+
if [ "$b_mode" = em-only ]; then
|
|
1094
|
+
bounded_note="${bounded_note}, ${b_path} from line ${b_line} read for the em dash only"
|
|
1095
|
+
else
|
|
1096
|
+
bounded_note="${bounded_note}, ${b_path} from line ${b_line} not read"
|
|
1097
|
+
fi
|
|
1098
|
+
done
|
|
1099
|
+
|
|
935
1100
|
if [ -n "$NAME_HITS" ] && [ -n "$HITS" ]; then
|
|
936
1101
|
fail_with_hits "the tracked file names and the tracked files listed above" \
|
|
937
1102
|
"$NAME_HITS"$'\n'"$HITS"
|
|
@@ -941,4 +1106,4 @@ fi
|
|
|
941
1106
|
|
|
942
1107
|
echo "check-no-emdash: OK (no em or en dashes in ${scanned} of ${#TRACKED[@]} tracked file(s)" \
|
|
943
1108
|
"or their names; ${gitlinks} gitlink(s) skipped, ${self_excluded} excluded as the" \
|
|
944
|
-
"implementation under scan, ${declared_excluded} excluded by ${DECLARATION})"
|
|
1109
|
+
"implementation under scan, ${declared_excluded} excluded by ${DECLARATION}${bounded_note})"
|
package/internal-refs.d.ts
CHANGED
|
@@ -68,6 +68,16 @@ export interface SourceDocCommentsAxis {
|
|
|
68
68
|
extraRules?: ExtraRule[];
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/** Whether the source string-literal pass runs, over what, and with which extra rules. */
|
|
72
|
+
export interface SourceStringLiteralsAxis {
|
|
73
|
+
/** @default false */
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
/** Tracked pathspecs holding the source whose string literals reach a consumer as messages. */
|
|
76
|
+
paths?: string[];
|
|
77
|
+
/** Detection that applies to string literals only. */
|
|
78
|
+
extraRules?: ExtraRule[];
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
export interface InternalRefsConfig {
|
|
72
82
|
/**
|
|
73
83
|
* AXIS 1, REQUIRED. The project and programme prefixes an identifier is keyed on, and an empty
|
|
@@ -113,6 +123,15 @@ export interface InternalRefsConfig {
|
|
|
113
123
|
*/
|
|
114
124
|
sourceDocComments?: SourceDocCommentsAxis;
|
|
115
125
|
|
|
126
|
+
/**
|
|
127
|
+
* DEFAULTED OFF. The source string-literal pass: double-quoted, single-quoted and template
|
|
128
|
+
* literals, decoded, with every comment skipped, because a string literal reaches a consumer as
|
|
129
|
+
* error and warning text and a comment does not. A NUL byte in source reads as a space.
|
|
130
|
+
*
|
|
131
|
+
* @default { enabled: false }
|
|
132
|
+
*/
|
|
133
|
+
sourceStringLiterals?: SourceStringLiteralsAxis;
|
|
134
|
+
|
|
116
135
|
/**
|
|
117
136
|
* AXIS 6, DEFAULTED. `clean` must be 0 and the other two must not be, which is checked: a mapping
|
|
118
137
|
* that put `hits` on 0 would turn every finding into a green run.
|
|
@@ -207,6 +226,18 @@ export declare function runSelfTests(rules: CompiledRule[]): string[];
|
|
|
207
226
|
/** Join a document the way markdown renders it, so a violation that straddles a wrap is visible. */
|
|
208
227
|
export declare function reflowParagraphs(text: string): string[];
|
|
209
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Extract the string literals of one JavaScript or TypeScript source file, decoded, with every
|
|
231
|
+
* comment skipped: one entry per literal, and one per source line holding two or more of them.
|
|
232
|
+
*/
|
|
233
|
+
export declare function extractStringLiterals(
|
|
234
|
+
text: string,
|
|
235
|
+
file: string,
|
|
236
|
+
): {
|
|
237
|
+
literals: { text: string; where: string }[];
|
|
238
|
+
lines: { text: string; where: string }[];
|
|
239
|
+
};
|
|
240
|
+
|
|
210
241
|
/** Extract doc-comment text from one source file, with each line's location beside it. */
|
|
211
242
|
export declare function extractDocComments(
|
|
212
243
|
text: string,
|