etymd 0.19.1 → 0.19.3
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 +19 -0
- package/dist/{approve-J6JU7MBX.js → approve-R24ZMJWB.js} +3 -3
- package/dist/audit-AZW4QGOE.js +11 -0
- package/dist/{brief-NHHILXIH.js → brief-HEJJU3GB.js} +3 -3
- package/dist/{chunk-UMKC3OWQ.js → chunk-5RRX2ONV.js} +3 -3
- package/dist/{chunk-GFUDKTC5.js → chunk-635GEE4V.js} +2 -2
- package/dist/{chunk-WKP72Z3J.js → chunk-7LRLA2XZ.js} +1 -1
- package/dist/{chunk-2DRA7S67.js → chunk-AXUYE7HH.js} +1 -1
- package/dist/{chunk-PQISPOB2.js → chunk-BXBSFDYL.js} +153 -64
- package/dist/{chunk-M7XDP6FT.js → chunk-IEHSNXR3.js} +4 -4
- package/dist/{chunk-KQFEKOON.js → chunk-VCEA7333.js} +1 -1
- package/dist/{chunk-6DJMKZGK.js → chunk-VVUEOA4V.js} +32 -15
- package/dist/{chunk-7N5JDXQG.js → chunk-XYJIDPKZ.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/{doctor-264Q5PLY.js → doctor-UGW5G5IZ.js} +6 -6
- package/dist/{fleet-VT625Q2J.js → fleet-Y22JBWLP.js} +8 -8
- package/dist/{gates-YUMSTKUG.js → gates-D6QPKN63.js} +5 -5
- package/dist/{generate-62GQSCKH.js → generate-5AF7V3DH.js} +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +184 -78
- package/dist/{init-G4XXFXVD.js → init-FNLRAYP4.js} +12 -5
- package/dist/{premise-RNMAKMVA.js → premise-FYOWXHUH.js} +4 -4
- package/dist/{propose-35JXMEPH.js → propose-5XOMTLW7.js} +6 -6
- package/dist/{scan-PJH32H7C.js → scan-QBPWNLNC.js} +3 -3
- package/dist/scan-WPXLF2J4.js +5 -0
- package/package.json +1 -1
- package/dist/audit-5N2TU2X7.js +0 -11
- package/dist/scan-F6OW4ANK.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.19.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bcd629d: Generated pre-push hooks read each pushed commit's scripts as raw blobs from git instead of checking the commit out. A checkout applied `.gitattributes` line-ending conversion, so under `eol=crlf` a script's shebang line ended in a carriage return and the script left the checked set without a message. The hook also no longer writes a commit's whole tree: one `git grep` pass finds the changed files with a `#!` line, and only those are read, which makes a whole-tree check faster than before. Symlink entries are now a disclosed skip, since a link's target is checked under its own path. The classifier refuses the old call shape, so a hand-edited pre-push kept across a regeneration stops the push and says to run `etymd gates`. Regenerate existing hooks with `etymd gates --yes`.
|
|
8
|
+
|
|
9
|
+
## 0.19.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- b46bbb9: Generated pre-push hooks check only the shell scripts each pushed commit changes, instead of
|
|
14
|
+
every script in every pushed commit's tree. A commit that changes the gate itself, its
|
|
15
|
+
classifier or a `.shellcheckrc` (deletions included) is still checked whole. So is the commit
|
|
16
|
+
that installs the gate, which reads a repo's existing scripts once. A commit with no shell
|
|
17
|
+
script says so. Pointer checks no longer accept an `@AGENTS.md` import inside a fenced code
|
|
18
|
+
block, an unreadable Claude Code version counts as too old instead of passing, and `init` warns
|
|
19
|
+
when a `CLAUDE.md` or `.claude/CLAUDE.md` hides `AGENTS.md`. Regenerate existing hooks with
|
|
20
|
+
`etymd gates --yes` to apply the change.
|
|
21
|
+
|
|
3
22
|
## 0.19.1
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-HI7NWPRA.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
3
|
+
import { scanProject } from './chunk-VVUEOA4V.js';
|
|
4
|
+
import { VERSION } from './chunk-VCEA7333.js';
|
|
5
5
|
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import { PACK_VERSION } from './chunk-
|
|
6
|
+
import { PACK_VERSION } from './chunk-AXUYE7HH.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/approve.ts
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run } from './chunk-635GEE4V.js';
|
|
3
|
+
import './chunk-5RRX2ONV.js';
|
|
4
|
+
import './chunk-7LRLA2XZ.js';
|
|
5
|
+
import './chunk-3E2IPCRY.js';
|
|
6
|
+
import './chunk-HI7NWPRA.js';
|
|
7
|
+
import './chunk-VVUEOA4V.js';
|
|
8
|
+
import './chunk-VCEA7333.js';
|
|
9
|
+
import './chunk-P6ATKV2R.js';
|
|
10
|
+
import './chunk-AXUYE7HH.js';
|
|
11
|
+
import './chunk-4VPBP6K6.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-HI7NWPRA.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-VVUEOA4V.js';
|
|
4
|
+
import './chunk-VCEA7333.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-AXUYE7HH.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
import { promises } from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { stateFreshnessLens, rankFindings, listInstructionFiles, buildTruthEnv, emptyCounters, packageManagerUsage, checkDocRefs, listStateDocuments, loadDecisionLedger, checkDecisionRefs, checkTextClaims } from './chunk-
|
|
2
|
+
import { stateFreshnessLens, rankFindings, listInstructionFiles, buildTruthEnv, emptyCounters, packageManagerUsage, checkDocRefs, listStateDocuments, loadDecisionLedger, checkDecisionRefs, checkTextClaims } from './chunk-7LRLA2XZ.js';
|
|
3
3
|
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-3E2IPCRY.js';
|
|
4
4
|
import { measureContext, contextFileLabel } from './chunk-HI7NWPRA.js';
|
|
5
|
-
import { scanProject } from './chunk-
|
|
5
|
+
import { scanProject } from './chunk-VVUEOA4V.js';
|
|
6
6
|
import { DEFAULT_CONFIG, ETYMD_DIR, writeCachedFacts, readBaseline, readConfig, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE, CONFIG_FILE } from './chunk-P6ATKV2R.js';
|
|
7
|
-
import { PACK_VERSION } from './chunk-
|
|
7
|
+
import { PACK_VERSION } from './chunk-AXUYE7HH.js';
|
|
8
8
|
import { pathExists, readText, readJson, isCiEnvironment, isExecutable } from './chunk-4VPBP6K6.js';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import YAML from 'yaml';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runAudit } from './chunk-
|
|
3
|
-
import { parseFailOnTier, meetsFailOn } from './chunk-
|
|
2
|
+
import { runAudit } from './chunk-5RRX2ONV.js';
|
|
3
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-7LRLA2XZ.js';
|
|
4
4
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-HI7NWPRA.js';
|
|
5
5
|
|
|
6
6
|
// src/commands/audit.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { expandFileGlobs } from './chunk-
|
|
2
|
+
import { expandFileGlobs } from './chunk-VVUEOA4V.js';
|
|
3
3
|
import { DEFAULT_CONFIG, CONFIG_FILE } from './chunk-P6ATKV2R.js';
|
|
4
4
|
import { readText, isDirectory, matchesAnyGlob, normalizeRelPath, readJson, pathExists, git } from './chunk-4VPBP6K6.js';
|
|
5
5
|
import path3 from 'path';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { PACK_VERSION } from './chunk-
|
|
2
|
+
import { PACK_VERSION } from './chunk-AXUYE7HH.js';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
|
|
5
5
|
var GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
|
|
@@ -257,49 +257,86 @@ exit 0
|
|
|
257
257
|
}
|
|
258
258
|
function generateShellDiscoveryScript() {
|
|
259
259
|
return stampGenerated(`#!/usr/bin/env sh
|
|
260
|
-
# etymd: shell script discovery for the pre-push shellcheck step.
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
260
|
+
# etymd: shell script discovery for the pre-push shellcheck step. Two calls per commit:
|
|
261
|
+
# discover-shell-scripts.sh --skips <scratch> <ls-tree record>...
|
|
262
|
+
# discover-shell-scripts.sh --commit <scratch> <tree> <commit> <commit>:<path>...
|
|
263
|
+
# The first counts the changed paths that are symlinks or submodule entries. The second reads
|
|
264
|
+
# the candidates \`git grep\` found with a line starting \`#!\`, and classifies each by its FIRST
|
|
265
|
+
# line. Verdicts land in the scratch: scripts (NUL-delimited matches) and one dot per decision
|
|
266
|
+
# into count / zsh-count / skip-count, tallied by the hook after the pipeline. Each script found
|
|
267
|
+
# is written into <tree> at its path, so the checker reads it there.
|
|
264
268
|
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
270
|
-
# fails, naming the path.
|
|
269
|
+
# Every byte comes from git's object store as the raw blob \u2014 \`git cat-file blob\`, which
|
|
270
|
+
# applies no eol, text or filter attribute. A checkout converts: under \`eol=crlf\` the shebang
|
|
271
|
+
# line ends in a carriage return, the match below fails, and the script leaves the checked set
|
|
272
|
+
# without a word. Only the entries handed in are read, never the whole commit.
|
|
271
273
|
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
274
|
+
# A symlink or submodule entry has no script bytes of its own \u2014 a link's target is a tracked
|
|
275
|
+
# path checked under its own name \u2014 so it is a disclosed skip, never a block. A candidate that
|
|
276
|
+
# cannot be read fails, naming the path: coverage would otherwise silently shrink.
|
|
277
|
+
#
|
|
278
|
+
# The match/error protocol: grep reports "no match" as 1 and a failure as 2 or more, and only
|
|
279
|
+
# the first is a verdict. Letting a failure fall through would pass a broken matcher as "not a
|
|
280
|
+
# shell script" \u2014 the exact silent coverage-shrink the fail-closed rules exist to prevent. The
|
|
281
|
+
# status is captured on the grep's own line (\`|| st=$?\`), so no command added later can come
|
|
282
|
+
# between the grep and the check and silently replace the status being read.
|
|
283
|
+
case \${1-} in
|
|
284
|
+
(--skips)
|
|
285
|
+
# Records only: a symlink or submodule entry is counted, everything else is left to the
|
|
286
|
+
# candidate pass. Builtins only \u2014 this loop sees every changed path.
|
|
287
|
+
work=$2
|
|
288
|
+
shift 2
|
|
289
|
+
for record do
|
|
290
|
+
case \${record%% *} in
|
|
291
|
+
(100644|100755) ;;
|
|
292
|
+
(*) printf . >> "$work/skip-count" || exit 1 ;;
|
|
293
|
+
esac
|
|
294
|
+
done
|
|
295
|
+
exit 0 ;;
|
|
296
|
+
(--commit)
|
|
297
|
+
[ $# -ge 4 ] || exit 1 ;;
|
|
298
|
+
(*)
|
|
299
|
+
echo "etymd: discover-shell-scripts.sh expects --commit or --skips; the pre-push beside it is older than this classifier \u2014 run 'etymd gates'" >&2
|
|
300
|
+
exit 1 ;;
|
|
301
|
+
esac
|
|
302
|
+
work=$2
|
|
303
|
+
tree=$3
|
|
304
|
+
sha=$4
|
|
305
|
+
shift 4
|
|
306
|
+
for entry do
|
|
307
|
+
file=\${entry#"$sha":}
|
|
308
|
+
# 4096 bytes bound the classifying read \u2014 a binary with no newline would otherwise be copied
|
|
309
|
+
# whole into the next step. The second head restores line-1-only semantics, so a shebang
|
|
310
|
+
# embedded on a LATER line of a document cannot match the patterns below.
|
|
311
|
+
git cat-file blob "$sha:$file" > "$work/blob" && head -c 4096 "$work/blob" > "$work/head-bytes" || {
|
|
288
312
|
echo "etymd: cannot read tracked file for shellcheck: $file" >&2
|
|
289
313
|
exit 1
|
|
290
314
|
}
|
|
291
315
|
head -n 1 "$work/head-bytes" > "$work/first-line" || exit 1
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
316
|
+
st=0
|
|
317
|
+
grep -qE "^#!.*[/ ](ba|da)?sh( |$)" "$work/first-line" || st=$?
|
|
318
|
+
case $st in
|
|
319
|
+
(0)
|
|
320
|
+
case $file in
|
|
321
|
+
(*/*) dir=\${file%/*} ;;
|
|
322
|
+
(*) dir=. ;;
|
|
323
|
+
esac
|
|
324
|
+
mkdir -p -- "$tree/$dir" && mv -- "$work/blob" "$tree/$file" || {
|
|
325
|
+
echo "etymd: cannot stage tracked file for shellcheck: $file" >&2
|
|
326
|
+
exit 1
|
|
327
|
+
}
|
|
328
|
+
printf "./%s\\0" "$file" >> "$work/scripts" || exit 1
|
|
329
|
+
printf . >> "$work/count" || exit 1 ;;
|
|
330
|
+
(1)
|
|
331
|
+
st=0
|
|
332
|
+
grep -qE "^#!.*[/ ]zsh( |$)" "$work/first-line" || st=$?
|
|
333
|
+
case $st in
|
|
334
|
+
(0) printf . >> "$work/zsh-count" || exit 1 ;;
|
|
335
|
+
(1) ;;
|
|
336
|
+
(*) exit 1 ;;
|
|
337
|
+
esac ;;
|
|
338
|
+
(*) exit 1 ;;
|
|
339
|
+
esac
|
|
303
340
|
done
|
|
304
341
|
`);
|
|
305
342
|
}
|
|
@@ -307,7 +344,7 @@ function shellcheckStep() {
|
|
|
307
344
|
return `
|
|
308
345
|
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
309
346
|
# script added later is covered without regenerating this hook. The commits BEING PUSHED are
|
|
310
|
-
# the bytes that ship, so each
|
|
347
|
+
# the bytes that ship, so each script is read as its raw blob from git's object store \u2014
|
|
311
348
|
# never the working tree (wrong in both directions: a fixed tree let an unfixed commit ship, and
|
|
312
349
|
# a dirty tree shared by several sessions blocked an unrelated push) and never the tip alone (a
|
|
313
350
|
# bad commit under a clean tip shipped while the gate read only the tip's fix). The classifier
|
|
@@ -369,37 +406,87 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
369
406
|
exit 1
|
|
370
407
|
}
|
|
371
408
|
shas=$(sort -u "$shellcheck_tmp/shas") || exit 1
|
|
372
|
-
[ -n "$shas" ]
|
|
373
|
-
|
|
409
|
+
if [ -n "$shas" ]; then
|
|
410
|
+
# Said up front: a first push from a fresh clone can carry the whole history, and a long
|
|
411
|
+
# silent hook reads as a hung one.
|
|
412
|
+
echo "\u203A shellcheck: $(printf '%s\\n' "$shas" | wc -l | tr -d ' ') commit(s) in the pushed range"
|
|
413
|
+
else
|
|
414
|
+
echo "\u203A shellcheck: no commit in the pushed refs (deletes only, or nothing on stdin) \u2014 nothing to check"
|
|
415
|
+
fi
|
|
416
|
+
# Resolved once, before any cd: the checker runs inside each commit's scratch tree, and a
|
|
374
417
|
# relative hook path would no longer point at it from there.
|
|
375
418
|
discover="$(cd "$(dirname "$0")" && pwd)/discover-shell-scripts.sh" || exit 1
|
|
376
419
|
for sha in $shas; do
|
|
377
|
-
# A fresh directory per commit
|
|
378
|
-
#
|
|
379
|
-
# every early exit.
|
|
420
|
+
# A fresh directory per commit holding only that commit's scripts, removed once it is
|
|
421
|
+
# checked; the subshell trap above still cleans the root on every early exit.
|
|
380
422
|
tree=$(mktemp -d "$shellcheck_tmp/commit.XXXXXX") || exit 1
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
# the checked set without a word. read-tree + checkout-index writes every tracked blob.
|
|
384
|
-
# Large-file pointers stay pointers \u2014 they are never shell, and a push must not need the
|
|
385
|
-
# network to be checked.
|
|
386
|
-
if ! git cat-file -e "\${sha}^{commit}" 2>/dev/null \\
|
|
387
|
-
|| ! GIT_INDEX_FILE="$shellcheck_tmp/index" git read-tree "$sha" 2>/dev/null \\
|
|
388
|
-
|| ! GIT_INDEX_FILE="$shellcheck_tmp/index" GIT_LFS_SKIP_SMUDGE=1 \\
|
|
389
|
-
git checkout-index -a -f --prefix="$tree/" 2>/dev/null; then
|
|
390
|
-
echo "\u2717 shellcheck: could not materialise $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") \u2014 refusing the push rather than certifying bytes this gate did not read" >&2
|
|
391
|
-
exit 1
|
|
392
|
-
fi
|
|
393
|
-
if [ -n "$(git ls-tree -r --name-only "$sha")" ] && [ -z "$(ls -A "$tree")" ]; then
|
|
394
|
-
echo "\u2717 shellcheck: $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") extracted to an empty tree \u2014 refusing the push" >&2
|
|
423
|
+
if ! git cat-file -e "\${sha}^{commit}" 2>/dev/null; then
|
|
424
|
+
echo "\u2717 shellcheck: cannot read commit $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") \u2014 refusing the push rather than certifying bytes this gate did not read" >&2
|
|
395
425
|
exit 1
|
|
396
426
|
fi
|
|
397
427
|
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
398
|
-
|
|
399
|
-
|
|
428
|
+
# Only the paths this commit adds, copies, modifies, renames or retypes against its first
|
|
429
|
+
# parent. A script the commit did not touch has its parent's bytes, and that parent was
|
|
430
|
+
# either gated when it reached the remote or sits in this range and is checked here; a
|
|
431
|
+
# merge diffed against its first parent brings in everything the other side added.
|
|
432
|
+
# Checking every script in every commit multiplied the cost by the range length, so a long
|
|
433
|
+
# push of a script-heavy repo ran long enough to look hung. Two changes alter the verdict
|
|
434
|
+
# on bytes nobody touched \u2014 the classifier deciding what is a script, and a .shellcheckrc
|
|
435
|
+
# deciding what is a finding \u2014 so a commit that touches either, deletion included (a
|
|
436
|
+
# removed .shellcheckrc re-enables every check it disabled), is checked whole. So is a
|
|
437
|
+
# commit that adds or changes this hook: a repo adopting the gate after the fact has
|
|
438
|
+
# scripts no gate ever read, and the commit installing it is where they get read once.
|
|
439
|
+
short=$(git rev-parse --short "$sha" 2>/dev/null || echo "$sha")
|
|
440
|
+
if git rev-parse -q --verify "\${sha}^1^{commit}" >/dev/null 2>&1; then
|
|
441
|
+
git diff-tree -r -z --name-only --no-commit-id "\${sha}^1" "$sha" > "$shellcheck_tmp/touched" \\
|
|
442
|
+
&& git diff-tree -r -z --name-only --no-commit-id --diff-filter=ACMRT "\${sha}^1" "$sha" > "$shellcheck_tmp/tracked"
|
|
443
|
+
else
|
|
444
|
+
git diff-tree -r -z --name-only --no-commit-id --root "$sha" > "$shellcheck_tmp/touched" \\
|
|
445
|
+
&& cp "$shellcheck_tmp/touched" "$shellcheck_tmp/tracked"
|
|
446
|
+
fi || {
|
|
447
|
+
echo "etymd: cannot enumerate the paths $short changes for shellcheck" >&2
|
|
400
448
|
exit 1
|
|
401
449
|
}
|
|
402
|
-
|
|
450
|
+
where="changed in $short"
|
|
451
|
+
# Through a file, not a pipe: a pipeline reports only grep's status, and a failing tr would
|
|
452
|
+
# hand grep nothing \u2014 a "no match" that silently takes the narrow path.
|
|
453
|
+
tr '\\000' '\\n' < "$shellcheck_tmp/touched" > "$shellcheck_tmp/touched-lines" || exit 1
|
|
454
|
+
# grep: 1 is "no match", a verdict; anything higher is the matcher failing. The status is
|
|
455
|
+
# taken on the grep's own line so nothing added later can come between them.
|
|
456
|
+
st=0
|
|
457
|
+
grep -qE '(^|/)(\\.shellcheckrc|discover-shell-scripts\\.sh|pre-push)$' "$shellcheck_tmp/touched-lines" || st=$?
|
|
458
|
+
case $st in
|
|
459
|
+
(0)
|
|
460
|
+
where="in $short (whole tree: the gate, its classifier or a .shellcheckrc changed)"
|
|
461
|
+
git ls-tree -r -z --name-only "$sha" > "$shellcheck_tmp/tracked" || {
|
|
462
|
+
echo "etymd: cannot enumerate the tree of $short for shellcheck" >&2
|
|
463
|
+
exit 1
|
|
464
|
+
} ;;
|
|
465
|
+
(1) ;;
|
|
466
|
+
(*)
|
|
467
|
+
echo "etymd: shell script discovery failed; cannot tell whether $short changes the gate, its classifier or a .shellcheckrc" >&2
|
|
468
|
+
exit 1 ;;
|
|
469
|
+
esac
|
|
470
|
+
# Only these paths are read, each as its raw blob \u2014 never a checkout of the commit, which
|
|
471
|
+
# would apply eol and filter attributes (a CRLF shebang left the checked set silently) and
|
|
472
|
+
# write the whole tree to check a handful of files. Never \`git archive\` either: it honours
|
|
473
|
+
# export-ignore, so a script so marked would leave the set without a word.
|
|
474
|
+
: > "$shellcheck_tmp/records" && : > "$shellcheck_tmp/candidates" || exit 1
|
|
475
|
+
if [ -s "$shellcheck_tmp/tracked" ]; then
|
|
476
|
+
# The candidates: changed blobs holding a line that starts \`#!\`, found in one pass over
|
|
477
|
+
# the raw blobs (git grep reads no eol or filter conversion). Only these are read one by
|
|
478
|
+
# one \u2014 a file with no such line cannot have a shebang first line. grep's "no match" is
|
|
479
|
+
# 1, a verdict; xargs would fold it into a failure, so the wrapper maps 1 to 0 and lets
|
|
480
|
+
# anything higher through as the failure it is.
|
|
481
|
+
xargs -0 git --literal-pathspecs ls-tree -z "$sha" -- < "$shellcheck_tmp/tracked" > "$shellcheck_tmp/records" \\
|
|
482
|
+
&& xargs -0 sh -c 'git --literal-pathspecs grep -z -l --full-name -e "^#!" "$0" -- "$@"; st=$?; [ "$st" -le 1 ]' "$sha" \\
|
|
483
|
+
< "$shellcheck_tmp/tracked" > "$shellcheck_tmp/candidates" || {
|
|
484
|
+
echo "etymd: shell script discovery failed; cannot list the tracked entries of $short" >&2
|
|
485
|
+
exit 1
|
|
486
|
+
}
|
|
487
|
+
fi
|
|
488
|
+
xargs -0 "$discover" --skips "$shellcheck_tmp" < "$shellcheck_tmp/records" \\
|
|
489
|
+
&& xargs -0 "$discover" --commit "$shellcheck_tmp" "$tree" "$sha" < "$shellcheck_tmp/candidates" || {
|
|
403
490
|
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
404
491
|
exit 1
|
|
405
492
|
}
|
|
@@ -407,13 +494,15 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
407
494
|
zsh_count=$(wc -c < "$shellcheck_tmp/zsh-count") || exit 1
|
|
408
495
|
skip_count=$(wc -c < "$shellcheck_tmp/skip-count") || exit 1
|
|
409
496
|
if [ "$skip_count" -gt 0 ]; then
|
|
410
|
-
echo "\u203A shellcheck: $((skip_count)) tracked path(s)
|
|
497
|
+
echo "\u203A shellcheck: $((skip_count)) tracked path(s) that are symlinks or submodule entries \u2014 no script bytes of their own (a link's target is checked under its own path); not checked, not failed"
|
|
411
498
|
fi
|
|
412
499
|
if [ "$zsh_count" -gt 0 ]; then
|
|
413
500
|
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
414
501
|
fi
|
|
415
|
-
if [ "$count" -
|
|
416
|
-
echo "\u203A shellcheck
|
|
502
|
+
if [ "$count" -eq 0 ]; then
|
|
503
|
+
echo "\u203A shellcheck: no shell script $where \u2014 nothing to check there"
|
|
504
|
+
else
|
|
505
|
+
echo "\u203A shellcheck ($((count)) scripts $where, blocking at severity=warning)"
|
|
417
506
|
( cd "$tree" && xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" ) || {
|
|
418
507
|
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
419
508
|
exit 1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runAudit } from './chunk-
|
|
2
|
+
import { runAudit } from './chunk-5RRX2ONV.js';
|
|
3
3
|
import { readLedger } from './chunk-3E2IPCRY.js';
|
|
4
|
-
import { checkClaudePointer, CLAUDE_AGENTS_FALLBACK_VERSION } from './chunk-
|
|
4
|
+
import { checkClaudePointer, CLAUDE_AGENTS_FALLBACK_VERSION } from './chunk-VVUEOA4V.js';
|
|
5
5
|
import { DEFAULT_CONFIG, ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
6
6
|
import { readText, isDirectory, git, pathExists } from './chunk-4VPBP6K6.js';
|
|
7
7
|
import os from 'os';
|
|
@@ -1006,8 +1006,8 @@ async function checkGuardedEmails(manifest, findings, disclosures) {
|
|
|
1006
1006
|
}
|
|
1007
1007
|
}
|
|
1008
1008
|
async function checkGateDrift(manifest, findings, disclosures) {
|
|
1009
|
-
const { planWorkflow } = await import('./generate-
|
|
1010
|
-
const { scanProject } = await import('./scan-
|
|
1009
|
+
const { planWorkflow } = await import('./generate-5AF7V3DH.js');
|
|
1010
|
+
const { scanProject } = await import('./scan-WPXLF2J4.js');
|
|
1011
1011
|
const { readConfig } = await import('./config-724Y3IOB.js');
|
|
1012
1012
|
for (const entry of manifest.entries) {
|
|
1013
1013
|
const root = entry.resolvedRoot;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-VCEA7333.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-AXUYE7HH.js';
|
|
4
4
|
import { normalizeRelPath, isDirectory, pathExists, readText, readJson, git, matchesAnyGlob } from './chunk-4VPBP6K6.js';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { execFile } from 'child_process';
|
|
@@ -447,8 +447,23 @@ async function detectArtifacts(root) {
|
|
|
447
447
|
});
|
|
448
448
|
return artifacts;
|
|
449
449
|
}
|
|
450
|
-
var ROOT_POINTER_IMPORT_RE =
|
|
451
|
-
var DOT_CLAUDE_IMPORT_RE =
|
|
450
|
+
var ROOT_POINTER_IMPORT_RE = /^ {0,3}@(\.\/)?AGENTS\.md[ \t]*$/m;
|
|
451
|
+
var DOT_CLAUDE_IMPORT_RE = /^ {0,3}@\.\.\/AGENTS\.md[ \t]*$/m;
|
|
452
|
+
function withoutFencedCode(text) {
|
|
453
|
+
const kept = [];
|
|
454
|
+
let fence = null;
|
|
455
|
+
for (const line of text.split("\n")) {
|
|
456
|
+
if (fence === null) {
|
|
457
|
+
const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
458
|
+
if (open?.[1] && !(open[1][0] === "`" && open[2]?.includes("`"))) fence = open[1];
|
|
459
|
+
else kept.push(line);
|
|
460
|
+
} else {
|
|
461
|
+
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line)?.[1];
|
|
462
|
+
if (close && close[0] === fence[0] && close.length >= fence.length) fence = null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return kept.join("\n");
|
|
466
|
+
}
|
|
452
467
|
var CLAUDE_AGENTS_FALLBACK_VERSION = "2.1.277";
|
|
453
468
|
var pExecFile = promisify(execFile);
|
|
454
469
|
var claudeVersionMemo;
|
|
@@ -457,18 +472,20 @@ function detectClaudeCodeVersion() {
|
|
|
457
472
|
if (override !== void 0) {
|
|
458
473
|
return Promise.resolve(override === "none" || override === "" ? null : override);
|
|
459
474
|
}
|
|
460
|
-
claudeVersionMemo ??= pExecFile("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
475
|
+
claudeVersionMemo ??= pExecFile("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?=\s|$)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
461
476
|
return claudeVersionMemo;
|
|
462
477
|
}
|
|
463
|
-
function
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
478
|
+
function mayPredate(a, b) {
|
|
479
|
+
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/.exec(v.trim());
|
|
480
|
+
const pa = parse(a);
|
|
481
|
+
const pb = parse(b);
|
|
482
|
+
if (!pa || !pb) return true;
|
|
483
|
+
for (let i = 1; i <= 3; i++) {
|
|
484
|
+
const x = Number(pa[i]);
|
|
485
|
+
const y = Number(pb[i]);
|
|
469
486
|
if (x !== y) return x < y;
|
|
470
487
|
}
|
|
471
|
-
return
|
|
488
|
+
return pa[4] !== void 0 && pb[4] === void 0;
|
|
472
489
|
}
|
|
473
490
|
async function checkClaudePointer(root, claudeVersion) {
|
|
474
491
|
const agentsAbs = path.join(root, "AGENTS.md");
|
|
@@ -486,11 +503,11 @@ async function checkClaudePointer(root, claudeVersion) {
|
|
|
486
503
|
return { ok: true, via: "same-file" };
|
|
487
504
|
}
|
|
488
505
|
const rootClaude = await readText(claudeAbs);
|
|
489
|
-
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(rootClaude)) {
|
|
506
|
+
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(withoutFencedCode(rootClaude))) {
|
|
490
507
|
return { ok: true, via: "root-import" };
|
|
491
508
|
}
|
|
492
509
|
const dotClaude = await readText(path.join(root, ".claude", "CLAUDE.md"));
|
|
493
|
-
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(dotClaude)) {
|
|
510
|
+
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(withoutFencedCode(dotClaude))) {
|
|
494
511
|
return { ok: true, via: "dotclaude-import" };
|
|
495
512
|
}
|
|
496
513
|
if (rootClaude !== null || dotClaude !== null) {
|
|
@@ -501,7 +518,7 @@ async function checkClaudePointer(root, claudeVersion) {
|
|
|
501
518
|
};
|
|
502
519
|
}
|
|
503
520
|
const version = claudeVersion === void 0 ? await detectClaudeCodeVersion() : claudeVersion;
|
|
504
|
-
if (version !== null &&
|
|
521
|
+
if (version !== null && mayPredate(version, CLAUDE_AGENTS_FALLBACK_VERSION)) {
|
|
505
522
|
return {
|
|
506
523
|
ok: false,
|
|
507
524
|
kind: "old-reader",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { isSafeGateCommand, generateAgentsMd, generateClaudePointerMd, isSelfBuildRepo, generatePreCommitHook, generateCommitMsgHook, generatePrePushHook, generateShellDiscoveryScript, generateArtifactCheckScript, fileOrigin } from './chunk-
|
|
2
|
+
import { isSafeGateCommand, generateAgentsMd, generateClaudePointerMd, isSelfBuildRepo, generatePreCommitHook, generateCommitMsgHook, generatePrePushHook, generateShellDiscoveryScript, generateArtifactCheckScript, fileOrigin } from './chunk-BXBSFDYL.js';
|
|
3
3
|
import { DEFAULT_CONFIG } from './chunk-P6ATKV2R.js';
|
|
4
4
|
import { readText, pathExists } from './chunk-4VPBP6K6.js';
|
|
5
5
|
import path from 'path';
|