etymd 0.19.0 → 0.19.1
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 +11 -0
- package/dist/{approve-Y36ZO6IL.js → approve-J6JU7MBX.js} +3 -3
- package/dist/audit-5N2TU2X7.js +11 -0
- package/dist/{brief-ILB6RMSQ.js → brief-NHHILXIH.js} +3 -3
- package/dist/{chunk-U7BCBJIH.js → chunk-2DRA7S67.js} +1 -1
- package/dist/{chunk-DLHC7QON.js → chunk-6DJMKZGK.js} +2 -2
- package/dist/{chunk-EZRK4MV3.js → chunk-7N5JDXQG.js} +1 -1
- package/dist/{chunk-4IESOWHQ.js → chunk-GFUDKTC5.js} +2 -2
- package/dist/{chunk-L4KIIKXI.js → chunk-KQFEKOON.js} +1 -1
- package/dist/{chunk-ZCOIZVSM.js → chunk-M7XDP6FT.js} +4 -4
- package/dist/{chunk-HSRHOVII.js → chunk-PQISPOB2.js} +113 -44
- package/dist/{chunk-KIPD3N77.js → chunk-UMKC3OWQ.js} +3 -3
- package/dist/{chunk-EYUW3UJU.js → chunk-WKP72Z3J.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/{doctor-EHRADIUZ.js → doctor-264Q5PLY.js} +6 -6
- package/dist/{fleet-KFF3N2O7.js → fleet-VT625Q2J.js} +7 -7
- package/dist/{gates-UJ23THID.js → gates-YUMSTKUG.js} +5 -5
- package/dist/{generate-6IPTCLYG.js → generate-62GQSCKH.js} +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +114 -45
- package/dist/{init-7HWVHZMF.js → init-G4XXFXVD.js} +5 -5
- package/dist/{premise-U6JIB2QG.js → premise-RNMAKMVA.js} +4 -4
- package/dist/{propose-NZMEPZIL.js → propose-35JXMEPH.js} +6 -6
- package/dist/scan-F6OW4ANK.js +5 -0
- package/dist/{scan-DLKYG5BB.js → scan-PJH32H7C.js} +3 -3
- package/package.json +1 -1
- package/dist/audit-RONSEYFS.js +0 -11
- package/dist/scan-V53PGAQ6.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.19.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51ecbc9: Generated pre-push hooks shellcheck every commit in the pushed range, each materialised from
|
|
8
|
+
git's object store, instead of reading the working tree. A push whose tip is clean but whose
|
|
9
|
+
earlier commits carry a broken script is now refused; the working tree no longer substitutes
|
|
10
|
+
for the pushed bytes; a push that creates a branch checks every commit no remote already has;
|
|
11
|
+
and a commit that cannot be materialised refuses the push rather than passing as unread.
|
|
12
|
+
Regenerate existing hooks with `etymd gates --yes` to apply the correction.
|
|
13
|
+
|
|
3
14
|
## 0.19.0
|
|
4
15
|
|
|
5
16
|
### Minor 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-6DJMKZGK.js';
|
|
4
|
+
import { VERSION } from './chunk-KQFEKOON.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-2DRA7S67.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-GFUDKTC5.js';
|
|
3
|
+
import './chunk-UMKC3OWQ.js';
|
|
4
|
+
import './chunk-WKP72Z3J.js';
|
|
5
|
+
import './chunk-3E2IPCRY.js';
|
|
6
|
+
import './chunk-HI7NWPRA.js';
|
|
7
|
+
import './chunk-6DJMKZGK.js';
|
|
8
|
+
import './chunk-KQFEKOON.js';
|
|
9
|
+
import './chunk-P6ATKV2R.js';
|
|
10
|
+
import './chunk-2DRA7S67.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-6DJMKZGK.js';
|
|
4
|
+
import './chunk-KQFEKOON.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-2DRA7S67.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
import { promises } from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -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-KQFEKOON.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-2DRA7S67.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';
|
|
@@ -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-PQISPOB2.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';
|
|
@@ -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-UMKC3OWQ.js';
|
|
3
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-WKP72Z3J.js';
|
|
4
4
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-HI7NWPRA.js';
|
|
5
5
|
|
|
6
6
|
// src/commands/audit.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runAudit } from './chunk-
|
|
2
|
+
import { runAudit } from './chunk-UMKC3OWQ.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-6DJMKZGK.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-62GQSCKH.js');
|
|
1010
|
+
const { scanProject } = await import('./scan-F6OW4ANK.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,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { PACK_VERSION } from './chunk-
|
|
2
|
+
import { PACK_VERSION } from './chunk-2DRA7S67.js';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
|
|
5
5
|
var GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
|
|
@@ -161,15 +161,21 @@ function contentScreenCall(opts) {
|
|
|
161
161
|
`${indent}fi`
|
|
162
162
|
].join("\n");
|
|
163
163
|
}
|
|
164
|
-
function localHookCall(hook) {
|
|
164
|
+
function localHookCall(hook, feedRefs = false) {
|
|
165
|
+
const refsCapture = feedRefs ? `# git hands the pushed refs to pre-push ONCE, on stdin \u2014 one line per ref:
|
|
166
|
+
# "<local ref> <local sha> <remote ref> <remote sha>". The shell gate below reads them too, so
|
|
167
|
+
# they are captured here and fed to each.
|
|
168
|
+
refs=$(cat)
|
|
169
|
+
` : "";
|
|
170
|
+
const call = feedRefs ? `printf '%s\\n' "$refs" | "$LOCAL" "$@" || exit 1` : `"$LOCAL" "$@" || exit 1`;
|
|
165
171
|
return `# Repo-owned checks. This file is generated and will be overwritten; \`.githooks/${hook}.local\`
|
|
166
172
|
# is yours \u2014 etymd never reads, writes, or regenerates it. Put project-specific guards there.
|
|
167
173
|
# A guard running tests that build fixture repositories should scrub git's exported GIT_* names
|
|
168
174
|
# first \u2014 a child git inherits them and ignores its cwd, so the suite would hit the real repo:
|
|
169
175
|
# env $(env | grep -o '^GIT_[A-Za-z0-9_]*' | sed 's/^/-u /') <your command>
|
|
170
176
|
LOCAL="$(dirname "$0")/${hook}.local"
|
|
171
|
-
if [ -x "$LOCAL" ]; then
|
|
172
|
-
|
|
177
|
+
${refsCapture}if [ -x "$LOCAL" ]; then
|
|
178
|
+
${call}
|
|
173
179
|
fi`;
|
|
174
180
|
}
|
|
175
181
|
function generatePreCommitHook(selfBuild = false) {
|
|
@@ -178,10 +184,10 @@ function generatePreCommitHook(selfBuild = false) {
|
|
|
178
184
|
|
|
179
185
|
${localHookCall("pre-commit")}
|
|
180
186
|
|
|
181
|
-
# Content screen \u2014 staged file bytes. Refuses to commit environment,
|
|
182
|
-
#
|
|
183
|
-
# are machine-local by design, so this is a NO-OP wherever no checker is installed:
|
|
184
|
-
# commit anywhere, active only where you opted in.
|
|
187
|
+
# Content screen \u2014 staged file bytes. Refuses to commit detail about your environment, work
|
|
188
|
+
# or identity into a repo whose history is (or could become) public. The checker and its
|
|
189
|
+
# patterns are machine-local by design, so this is a NO-OP wherever no checker is installed:
|
|
190
|
+
# safe to commit anywhere, active only where you opted in.
|
|
185
191
|
#
|
|
186
192
|
# Bypass, with a reason: git commit --no-verify
|
|
187
193
|
${contentGateResolution(selfBuild)}
|
|
@@ -256,9 +262,10 @@ function generateShellDiscoveryScript() {
|
|
|
256
262
|
# side, positional here). Verdicts land in the scratch: scripts (NUL-delimited matches) and one
|
|
257
263
|
# dot per decision into count / zsh-count / skip-count, tallied by the hook after the pipeline.
|
|
258
264
|
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
265
|
+
# The hook runs it from inside a commit materialised from git's object store, so paths resolve
|
|
266
|
+
# against that tree, never the working tree. A path with nothing readable behind it \u2014 a
|
|
267
|
+
# submodule entry, a dangling symlink \u2014 cannot lie about its contents, so it is a disclosed
|
|
268
|
+
# skip, never a block. A regular file
|
|
262
269
|
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
263
270
|
# fails, naming the path.
|
|
264
271
|
#
|
|
@@ -299,16 +306,20 @@ done
|
|
|
299
306
|
function shellcheckStep() {
|
|
300
307
|
return `
|
|
301
308
|
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
302
|
-
# script added later is covered without regenerating this hook. The
|
|
303
|
-
#
|
|
309
|
+
# script added later is covered without regenerating this hook. The commits BEING PUSHED are
|
|
310
|
+
# the bytes that ship, so each one is materialised from git's object store and checked there \u2014
|
|
311
|
+
# never the working tree (wrong in both directions: a fixed tree let an unfixed commit ship, and
|
|
312
|
+
# a dirty tree shared by several sessions blocked an unrelated push) and never the tip alone (a
|
|
313
|
+
# bad commit under a clean tip shipped while the gate read only the tip's fix). The classifier
|
|
314
|
+
# is discover-shell-scripts.sh beside this hook \u2014 tracked and shebanged like what it classifies,
|
|
304
315
|
# so the scan it implements finds and checks it too. zsh is NOT in the checked set:
|
|
305
316
|
# the checker cannot parse it (SC1071 is a parser-level error no inline directive can silence),
|
|
306
317
|
# so checking it would fail every push on the parser, not on the script. Excluded \u2014 and said so
|
|
307
318
|
# at run time below, because a coverage hole that is silent is indistinguishable from coverage.
|
|
308
319
|
# The same honesty splits the unreadable: a regular file that exists but cannot be read blocks
|
|
309
320
|
# the push (coverage would otherwise silently shrink), while a tracked path with nothing readable
|
|
310
|
-
# behind it \u2014 a submodule entry, a dangling symlink
|
|
311
|
-
#
|
|
321
|
+
# behind it \u2014 a submodule entry, a dangling symlink \u2014 is counted and said so below as skipped,
|
|
322
|
+
# never a block.
|
|
312
323
|
#
|
|
313
324
|
# "the checker", not its name, on purpose: a comment whose first word is that name is read as
|
|
314
325
|
# a DIRECTIVE, and an unparseable directive is itself an error (SC1072/SC1073). A hook that
|
|
@@ -327,39 +338,97 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
327
338
|
shellcheck_tmp=$(mktemp -d) || exit 1
|
|
328
339
|
trap 'rm -rf "$shellcheck_tmp"' 0
|
|
329
340
|
trap 'exit 1' 1 2 3 15
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
341
|
+
# Every commit in each pushed range, never the tip alone: pushing two commits \u2014 a bad
|
|
342
|
+
# script, then its fix \u2014 passed a tip-only read while the bad commit landed on the remote.
|
|
343
|
+
# An all-zero local sha is a delete (nothing to check). An all-zero REMOTE sha is a new
|
|
344
|
+
# branch: everything no remote already has is being pushed, so the range is the local sha
|
|
345
|
+
# minus every remote-tracking ref \u2014 commits a remote already received were gated when they
|
|
346
|
+
# landed there, and the residue is exactly this push's new commits. A remote sha this clone
|
|
347
|
+
# has never seen (the remote moved on since the last fetch) cannot bound a range, so it takes
|
|
348
|
+
# the same new-branch rule instead of refusing a push the gate could have read. Enumeration
|
|
349
|
+
# failure refuses the push: a range the gate could not list is a range it did not read.
|
|
350
|
+
# (pattern) with both parens: bash 3.2 (macOS /bin/sh) cannot parse an unbalanced )
|
|
351
|
+
# in a case pattern.
|
|
352
|
+
: > "$shellcheck_tmp/shas" || exit 1
|
|
353
|
+
printf '%s\\n' "$refs" | while read -r _lref lsha _rref rsha; do
|
|
354
|
+
case "$lsha" in
|
|
355
|
+
(*[!0]*) ;;
|
|
356
|
+
(*) continue ;;
|
|
357
|
+
esac
|
|
358
|
+
case "$rsha" in
|
|
359
|
+
(*[!0]*)
|
|
360
|
+
if git cat-file -e "\${rsha}^{commit}" 2>/dev/null; then
|
|
361
|
+
git rev-list "$rsha..$lsha"
|
|
362
|
+
else
|
|
363
|
+
git rev-list "$lsha" --not --remotes
|
|
364
|
+
fi ;;
|
|
365
|
+
(*) git rev-list "$lsha" --not --remotes ;;
|
|
366
|
+
esac >> "$shellcheck_tmp/shas" || exit 1
|
|
367
|
+
done || {
|
|
368
|
+
echo "etymd: could not enumerate the commits being pushed for shellcheck" >&2
|
|
337
369
|
exit 1
|
|
338
370
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
shas=$(sort -u "$shellcheck_tmp/shas") || exit 1
|
|
372
|
+
[ -n "$shas" ] || echo "\u203A shellcheck: no commit in the pushed refs (deletes only, or nothing on stdin) \u2014 nothing to check"
|
|
373
|
+
# Resolved once, before any cd: the classifier runs inside each materialised tree, and a
|
|
374
|
+
# relative hook path would no longer point at it from there.
|
|
375
|
+
discover="$(cd "$(dirname "$0")" && pwd)/discover-shell-scripts.sh" || exit 1
|
|
376
|
+
for sha in $shas; do
|
|
377
|
+
# A fresh directory per commit, removed once that commit is checked so a long push does
|
|
378
|
+
# not pile up one full tree per commit; the subshell trap above still cleans the root on
|
|
379
|
+
# every early exit.
|
|
380
|
+
tree=$(mktemp -d "$shellcheck_tmp/commit.XXXXXX") || exit 1
|
|
381
|
+
# Checked out through a scratch index, never \`git archive\`: archive honours the
|
|
382
|
+
# export-ignore and export-subst attributes, so a script marked export-ignore would leave
|
|
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
|
|
395
|
+
exit 1
|
|
396
|
+
fi
|
|
397
|
+
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
398
|
+
git ls-tree -r -z --name-only "$sha" > "$shellcheck_tmp/tracked" || {
|
|
399
|
+
echo "etymd: cannot enumerate the tree of $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") for shellcheck" >&2
|
|
400
|
+
exit 1
|
|
401
|
+
}
|
|
402
|
+
( cd "$tree" && xargs -0 "$discover" "$shellcheck_tmp" ) < "$shellcheck_tmp/tracked" || {
|
|
403
|
+
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
352
404
|
exit 1
|
|
353
405
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
echo " \xB7 style/info (not blocking):"
|
|
360
|
-
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
406
|
+
count=$(wc -c < "$shellcheck_tmp/count") || exit 1
|
|
407
|
+
zsh_count=$(wc -c < "$shellcheck_tmp/zsh-count") || exit 1
|
|
408
|
+
skip_count=$(wc -c < "$shellcheck_tmp/skip-count") || exit 1
|
|
409
|
+
if [ "$skip_count" -gt 0 ]; then
|
|
410
|
+
echo "\u203A shellcheck: $((skip_count)) tracked path(s) with nothing readable behind them (submodule or dangling symlink) \u2014 not checked, not failed"
|
|
361
411
|
fi
|
|
362
|
-
|
|
412
|
+
if [ "$zsh_count" -gt 0 ]; then
|
|
413
|
+
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
414
|
+
fi
|
|
415
|
+
if [ "$count" -gt 0 ]; then
|
|
416
|
+
echo "\u203A shellcheck ($((count)) scripts in $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha"), blocking at severity=warning)"
|
|
417
|
+
( cd "$tree" && xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" ) || {
|
|
418
|
+
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
419
|
+
exit 1
|
|
420
|
+
}
|
|
421
|
+
# Everything below the blocking bar, shown once the push is already cleared. Never affects
|
|
422
|
+
# the exit code \u2014 advice that can fail a push is not advice.
|
|
423
|
+
advice=$( ( cd "$tree" && xargs -0 shellcheck -S style -f gcc -- < "$shellcheck_tmp/scripts" 2>/dev/null ) \\
|
|
424
|
+
| grep -v ': warning:\\|: error:' || true)
|
|
425
|
+
if [ -n "$advice" ]; then
|
|
426
|
+
echo " \xB7 style/info (not blocking):"
|
|
427
|
+
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
428
|
+
fi
|
|
429
|
+
fi
|
|
430
|
+
rm -rf "$tree" || exit 1
|
|
431
|
+
done
|
|
363
432
|
) || exit 1
|
|
364
433
|
else
|
|
365
434
|
echo "\u203A shellcheck skipped (not on PATH) \u2014 install it to gate this repo's shell scripts"
|
|
@@ -401,7 +470,7 @@ fi`;
|
|
|
401
470
|
return stampGenerated(`#!/usr/bin/env sh
|
|
402
471
|
# etymd: correctness gate. Mirrors CI cheapest-first; blocks the push on any failure.
|
|
403
472
|
|
|
404
|
-
${localHookCall("pre-push")}${runner}
|
|
473
|
+
${localHookCall("pre-push", true)}${runner}
|
|
405
474
|
${body}${shellStep}
|
|
406
475
|
${auditStep}
|
|
407
476
|
|
|
@@ -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-WKP72Z3J.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-6DJMKZGK.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-2DRA7S67.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,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { expandFileGlobs } from './chunk-
|
|
2
|
+
import { expandFileGlobs } from './chunk-6DJMKZGK.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';
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-KQFEKOON.js';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import pc from 'picocolors';
|
|
@@ -31,7 +31,7 @@ program.command("audit").description("Verify every instruction claim against the
|
|
|
31
31
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
32
32
|
).action(
|
|
33
33
|
(opts, cmd) => action(async () => {
|
|
34
|
-
const { run } = await import('./audit-
|
|
34
|
+
const { run } = await import('./audit-5N2TU2X7.js');
|
|
35
35
|
await run({
|
|
36
36
|
cwd: resolveCwd(cmd),
|
|
37
37
|
json: opts.json,
|
|
@@ -44,7 +44,7 @@ program.command("audit").description("Verify every instruction claim against the
|
|
|
44
44
|
);
|
|
45
45
|
program.command("init").description("Onboard the truth guard: approve the baseline; scaffold AGENTS.md only if asked").option("-y, --yes", "accept defaults without prompting (never overwrites)").option("--with-agents", "also scaffold a minimal AGENTS.md where none exists (off by default)").action(
|
|
46
46
|
(opts, cmd) => action(async () => {
|
|
47
|
-
const { run } = await import('./init-
|
|
47
|
+
const { run } = await import('./init-G4XXFXVD.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes, withAgents: opts.withAgents });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,19 +52,19 @@ program.command("approve").description(
|
|
|
52
52
|
"Re-approve the committed baseline after intentional structural changes (non-interactive)"
|
|
53
53
|
).action(
|
|
54
54
|
(_opts, cmd) => action(async () => {
|
|
55
|
-
const { run } = await import('./approve-
|
|
55
|
+
const { run } = await import('./approve-J6JU7MBX.js');
|
|
56
56
|
await run({ cwd: resolveCwd(cmd) });
|
|
57
57
|
})
|
|
58
58
|
);
|
|
59
59
|
program.command("scan").description("Deterministically reckon the project into a facts index").option("--json", "print the raw facts as JSON").option("--no-save", "do not write the .etymd cache").action(
|
|
60
60
|
(opts, cmd) => action(async () => {
|
|
61
|
-
const { run } = await import('./scan-
|
|
61
|
+
const { run } = await import('./scan-PJH32H7C.js');
|
|
62
62
|
await run({ cwd: resolveCwd(cmd), json: opts.json, save: opts.save });
|
|
63
63
|
})
|
|
64
64
|
);
|
|
65
65
|
program.command("doctor").description('Alias for `audit --truth` \u2014 "are the recorded instructions still true?"').option("--json", "print findings as JSON").action(
|
|
66
66
|
(opts, cmd) => action(async () => {
|
|
67
|
-
const { run } = await import('./doctor-
|
|
67
|
+
const { run } = await import('./doctor-264Q5PLY.js');
|
|
68
68
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
69
69
|
})
|
|
70
70
|
);
|
|
@@ -76,7 +76,7 @@ program.command("context").description("Measure the always-loaded context footpr
|
|
|
76
76
|
);
|
|
77
77
|
program.command("brief").description("Emit a grounded briefing for the in-repo agent to complete the semantic layer").option("--human", "write a human onboarding brief instead of the agent briefing").action(
|
|
78
78
|
(opts, cmd) => action(async () => {
|
|
79
|
-
const { run } = await import('./brief-
|
|
79
|
+
const { run } = await import('./brief-NHHILXIH.js');
|
|
80
80
|
await run({ cwd: resolveCwd(cmd), human: opts.human });
|
|
81
81
|
})
|
|
82
82
|
);
|
|
@@ -90,7 +90,7 @@ program.command("premise").argument("[task]", "the task you are about to hand an
|
|
|
90
90
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
91
91
|
).action(
|
|
92
92
|
(task, opts, cmd) => action(async () => {
|
|
93
|
-
const { run } = await import('./premise-
|
|
93
|
+
const { run } = await import('./premise-RNMAKMVA.js');
|
|
94
94
|
await run({
|
|
95
95
|
cwd: resolveCwd(cmd),
|
|
96
96
|
task,
|
|
@@ -132,7 +132,7 @@ var fleet = program.command("fleet").description(
|
|
|
132
132
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
133
133
|
).action(
|
|
134
134
|
(opts, cmd) => action(async () => {
|
|
135
|
-
const { sweep } = await import('./fleet-
|
|
135
|
+
const { sweep } = await import('./fleet-VT625Q2J.js');
|
|
136
136
|
await sweep({
|
|
137
137
|
cwd: resolveCwd(cmd),
|
|
138
138
|
manifest: opts.manifest,
|
|
@@ -147,7 +147,7 @@ var fleet = program.command("fleet").description(
|
|
|
147
147
|
);
|
|
148
148
|
fleet.command("check").description("Validate the manifest pair only \u2014 no lenses: dangling mappings, duplicates, leaks").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").option("--json", "print the findings as JSON (EXPERIMENTAL through 0.2.x)").action(
|
|
149
149
|
(_opts, cmd) => action(async () => {
|
|
150
|
-
const { check } = await import('./fleet-
|
|
150
|
+
const { check } = await import('./fleet-VT625Q2J.js');
|
|
151
151
|
const opts = cmd.optsWithGlobals();
|
|
152
152
|
await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
|
|
153
153
|
})
|
|
@@ -156,7 +156,7 @@ fleet.command("board").description(
|
|
|
156
156
|
"Render the fleet board: every project's milestones (contract key `milestones`) plus a ranked initiatives table"
|
|
157
157
|
).option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").option("--initiatives <file>", "the hand-edited initiatives table (rank | id | initiative | \u2026)").option("--out <file>", "write the Markdown board here instead of printing it").option("--json", "print the board as JSON (EXPERIMENTAL)").action(
|
|
158
158
|
(opts, cmd) => action(async () => {
|
|
159
|
-
const { board } = await import('./fleet-
|
|
159
|
+
const { board } = await import('./fleet-VT625Q2J.js');
|
|
160
160
|
const shared = cmd.optsWithGlobals();
|
|
161
161
|
await board({
|
|
162
162
|
cwd: resolveCwd(cmd),
|
|
@@ -172,7 +172,7 @@ fleet.command("add").argument("<dir>", "directory of the project to register").d
|
|
|
172
172
|
"public-repo | public-bound | private \u2014 mandatory for personal entries"
|
|
173
173
|
).option("-y, --yes", "skip prompts; every mandatory value must be passed as a flag").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
174
174
|
(dir, opts, cmd) => action(async () => {
|
|
175
|
-
const { add } = await import('./fleet-
|
|
175
|
+
const { add } = await import('./fleet-VT625Q2J.js');
|
|
176
176
|
const shared = cmd.optsWithGlobals();
|
|
177
177
|
await add({
|
|
178
178
|
cwd: resolveCwd(cmd),
|
|
@@ -195,7 +195,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
195
195
|
"Dismiss a project's finding from any cwd \u2014 guarded ledgers persist beside the manifest"
|
|
196
196
|
).requiredOption("--reason <text>", "why it is dismissed \u2014 recorded so the decision survives").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
197
197
|
(name, id, _opts, cmd) => action(async () => {
|
|
198
|
-
const { dismiss } = await import('./fleet-
|
|
198
|
+
const { dismiss } = await import('./fleet-VT625Q2J.js');
|
|
199
199
|
const opts = cmd.optsWithGlobals();
|
|
200
200
|
await dismiss({
|
|
201
201
|
cwd: resolveCwd(cmd),
|
|
@@ -210,7 +210,7 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
210
210
|
"Accept a project's finding as a known trade-off \u2014 guarded ledgers persist beside the manifest"
|
|
211
211
|
).option("--reason <text>", "optional note on why the trade-off is accepted").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
212
212
|
(name, id, _opts, cmd) => action(async () => {
|
|
213
|
-
const { accept } = await import('./fleet-
|
|
213
|
+
const { accept } = await import('./fleet-VT625Q2J.js');
|
|
214
214
|
const opts = cmd.optsWithGlobals();
|
|
215
215
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
216
216
|
})
|
|
@@ -225,7 +225,7 @@ program.command("propose").description(
|
|
|
225
225
|
"the fleet manifest \u2014 runs a read-only sweep now (no delta baseline move)"
|
|
226
226
|
).option("--from <file>", "read a stored `etymd fleet --json` output instead of sweeping").option("--json", "print the proposal/1 records as JSON (EXPERIMENTAL)").action(
|
|
227
227
|
(opts, cmd) => action(async () => {
|
|
228
|
-
const { run } = await import('./propose-
|
|
228
|
+
const { run } = await import('./propose-35JXMEPH.js');
|
|
229
229
|
await run({
|
|
230
230
|
cwd: resolveCwd(cmd),
|
|
231
231
|
manifest: opts.manifest,
|
|
@@ -256,7 +256,7 @@ program.command("screen").description(
|
|
|
256
256
|
);
|
|
257
257
|
program.command("gates").description("Install the local git-hook gates (process \u2192 pre-commit, correctness \u2192 pre-push)").option("--ci", "note about the CI review gate (ships later; local gates install now)").option("-y, --yes", "skip prompts; never overwrites a hand-edited hook").action(
|
|
258
258
|
(opts, cmd) => action(async () => {
|
|
259
|
-
const { run } = await import('./gates-
|
|
259
|
+
const { run } = await import('./gates-YUMSTKUG.js');
|
|
260
260
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
261
261
|
})
|
|
262
262
|
);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-GFUDKTC5.js';
|
|
3
|
+
import './chunk-UMKC3OWQ.js';
|
|
4
|
+
import './chunk-WKP72Z3J.js';
|
|
5
5
|
import './chunk-3E2IPCRY.js';
|
|
6
6
|
import './chunk-HI7NWPRA.js';
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
7
|
+
import './chunk-6DJMKZGK.js';
|
|
8
|
+
import './chunk-KQFEKOON.js';
|
|
9
9
|
import './chunk-P6ATKV2R.js';
|
|
10
|
-
import './chunk-
|
|
10
|
+
import './chunk-2DRA7S67.js';
|
|
11
11
|
import './chunk-4VPBP6K6.js';
|
|
12
12
|
|
|
13
13
|
// src/commands/doctor.ts
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dismiss, accept } from './chunk-HOR4M6EC.js';
|
|
3
|
-
import { sweepFleet, FLEET_JSON_SCHEMA, checkManifest, FLEET_TRUST_VALUES, isFleetTrust, MILESTONES_FILE, parseMilestones, parseInitiatives, BOARD_JSON_SCHEMA, renderBoard, loadFleetManifest, fleetLedgerTarget, ensureFindingRecorded } from './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import { parseFailOnTier, meetsFailOn } from './chunk-
|
|
3
|
+
import { sweepFleet, FLEET_JSON_SCHEMA, checkManifest, FLEET_TRUST_VALUES, isFleetTrust, MILESTONES_FILE, parseMilestones, parseInitiatives, BOARD_JSON_SCHEMA, renderBoard, loadFleetManifest, fleetLedgerTarget, ensureFindingRecorded } from './chunk-M7XDP6FT.js';
|
|
4
|
+
import './chunk-UMKC3OWQ.js';
|
|
5
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-WKP72Z3J.js';
|
|
6
6
|
import './chunk-3E2IPCRY.js';
|
|
7
|
-
import { generateClaudePointerMd } from './chunk-
|
|
7
|
+
import { generateClaudePointerMd } from './chunk-PQISPOB2.js';
|
|
8
8
|
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph } from './chunk-HI7NWPRA.js';
|
|
9
|
-
import { checkClaudePointer, scanProject } from './chunk-
|
|
10
|
-
import './chunk-
|
|
9
|
+
import { checkClaudePointer, scanProject } from './chunk-6DJMKZGK.js';
|
|
10
|
+
import './chunk-KQFEKOON.js';
|
|
11
11
|
import './chunk-P6ATKV2R.js';
|
|
12
|
-
import './chunk-
|
|
12
|
+
import './chunk-2DRA7S67.js';
|
|
13
13
|
import { readText, pathExists, git } from './chunk-4VPBP6K6.js';
|
|
14
14
|
import { promises } from 'fs';
|
|
15
15
|
import os from 'os';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-GWGKEPRX.js';
|
|
3
|
-
import { deriveFailOn, derivedCommands, planWorkflow } from './chunk-
|
|
4
|
-
import { runPrefix, isSafeGateCommand } from './chunk-
|
|
3
|
+
import { deriveFailOn, derivedCommands, planWorkflow } from './chunk-7N5JDXQG.js';
|
|
4
|
+
import { runPrefix, isSafeGateCommand } from './chunk-PQISPOB2.js';
|
|
5
5
|
import { section, print, glyph, theme, renderPlan } from './chunk-HI7NWPRA.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import { scanProject } from './chunk-6DJMKZGK.js';
|
|
7
|
+
import './chunk-KQFEKOON.js';
|
|
8
8
|
import { readConfig, CONFIG_FILE, configPath } from './chunk-P6ATKV2R.js';
|
|
9
|
-
import './chunk-
|
|
9
|
+
import './chunk-2DRA7S67.js';
|
|
10
10
|
import { readText, git } from './chunk-4VPBP6K6.js';
|
|
11
11
|
import { promises } from 'fs';
|
|
12
12
|
import path from 'path';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-7N5JDXQG.js';
|
|
3
|
+
import './chunk-PQISPOB2.js';
|
|
4
4
|
import './chunk-P6ATKV2R.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-2DRA7S67.js';
|
|
6
6
|
import './chunk-4VPBP6K6.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1127,7 +1127,7 @@ declare const gateIntegrityLens: Lens;
|
|
|
1127
1127
|
* Gaps in the sequence are deliberate: a number stays retired even when the change that took it
|
|
1128
1128
|
* never shipped, so a version cited in a baseline or a record means exactly one pack, ever.
|
|
1129
1129
|
*/
|
|
1130
|
-
declare const PACK_VERSION = "
|
|
1130
|
+
declare const PACK_VERSION = "15";
|
|
1131
1131
|
|
|
1132
1132
|
declare const VERSION: string;
|
|
1133
1133
|
declare const NAME: string;
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __export = (target, all) => {
|
|
|
20
20
|
var PACK_VERSION;
|
|
21
21
|
var init_version = __esm({
|
|
22
22
|
"src/pack/version.ts"() {
|
|
23
|
-
PACK_VERSION = "
|
|
23
|
+
PACK_VERSION = "15";
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -30,7 +30,7 @@ var init_package = __esm({
|
|
|
30
30
|
"package.json"() {
|
|
31
31
|
package_default = {
|
|
32
32
|
name: "etymd",
|
|
33
|
-
version: "0.19.
|
|
33
|
+
version: "0.19.1",
|
|
34
34
|
description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules, skills, and the task you hand an agent against the actual repo, with drift caught over time and a regression ledger.",
|
|
35
35
|
keywords: [
|
|
36
36
|
"cli",
|
|
@@ -1237,15 +1237,21 @@ function contentScreenCall(opts) {
|
|
|
1237
1237
|
`${indent}fi`
|
|
1238
1238
|
].join("\n");
|
|
1239
1239
|
}
|
|
1240
|
-
function localHookCall(hook) {
|
|
1240
|
+
function localHookCall(hook, feedRefs = false) {
|
|
1241
|
+
const refsCapture = feedRefs ? `# git hands the pushed refs to pre-push ONCE, on stdin \u2014 one line per ref:
|
|
1242
|
+
# "<local ref> <local sha> <remote ref> <remote sha>". The shell gate below reads them too, so
|
|
1243
|
+
# they are captured here and fed to each.
|
|
1244
|
+
refs=$(cat)
|
|
1245
|
+
` : "";
|
|
1246
|
+
const call = feedRefs ? `printf '%s\\n' "$refs" | "$LOCAL" "$@" || exit 1` : `"$LOCAL" "$@" || exit 1`;
|
|
1241
1247
|
return `# Repo-owned checks. This file is generated and will be overwritten; \`.githooks/${hook}.local\`
|
|
1242
1248
|
# is yours \u2014 etymd never reads, writes, or regenerates it. Put project-specific guards there.
|
|
1243
1249
|
# A guard running tests that build fixture repositories should scrub git's exported GIT_* names
|
|
1244
1250
|
# first \u2014 a child git inherits them and ignores its cwd, so the suite would hit the real repo:
|
|
1245
1251
|
# env $(env | grep -o '^GIT_[A-Za-z0-9_]*' | sed 's/^/-u /') <your command>
|
|
1246
1252
|
LOCAL="$(dirname "$0")/${hook}.local"
|
|
1247
|
-
if [ -x "$LOCAL" ]; then
|
|
1248
|
-
|
|
1253
|
+
${refsCapture}if [ -x "$LOCAL" ]; then
|
|
1254
|
+
${call}
|
|
1249
1255
|
fi`;
|
|
1250
1256
|
}
|
|
1251
1257
|
function generatePreCommitHook(selfBuild = false) {
|
|
@@ -1254,10 +1260,10 @@ function generatePreCommitHook(selfBuild = false) {
|
|
|
1254
1260
|
|
|
1255
1261
|
${localHookCall("pre-commit")}
|
|
1256
1262
|
|
|
1257
|
-
# Content screen \u2014 staged file bytes. Refuses to commit environment,
|
|
1258
|
-
#
|
|
1259
|
-
# are machine-local by design, so this is a NO-OP wherever no checker is installed:
|
|
1260
|
-
# commit anywhere, active only where you opted in.
|
|
1263
|
+
# Content screen \u2014 staged file bytes. Refuses to commit detail about your environment, work
|
|
1264
|
+
# or identity into a repo whose history is (or could become) public. The checker and its
|
|
1265
|
+
# patterns are machine-local by design, so this is a NO-OP wherever no checker is installed:
|
|
1266
|
+
# safe to commit anywhere, active only where you opted in.
|
|
1261
1267
|
#
|
|
1262
1268
|
# Bypass, with a reason: git commit --no-verify
|
|
1263
1269
|
${contentGateResolution(selfBuild)}
|
|
@@ -1318,9 +1324,10 @@ function generateShellDiscoveryScript() {
|
|
|
1318
1324
|
# side, positional here). Verdicts land in the scratch: scripts (NUL-delimited matches) and one
|
|
1319
1325
|
# dot per decision into count / zsh-count / skip-count, tallied by the hook after the pipeline.
|
|
1320
1326
|
#
|
|
1321
|
-
#
|
|
1322
|
-
#
|
|
1323
|
-
#
|
|
1327
|
+
# The hook runs it from inside a commit materialised from git's object store, so paths resolve
|
|
1328
|
+
# against that tree, never the working tree. A path with nothing readable behind it \u2014 a
|
|
1329
|
+
# submodule entry, a dangling symlink \u2014 cannot lie about its contents, so it is a disclosed
|
|
1330
|
+
# skip, never a block. A regular file
|
|
1324
1331
|
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
1325
1332
|
# fails, naming the path.
|
|
1326
1333
|
#
|
|
@@ -1361,16 +1368,20 @@ done
|
|
|
1361
1368
|
function shellcheckStep() {
|
|
1362
1369
|
return `
|
|
1363
1370
|
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
1364
|
-
# script added later is covered without regenerating this hook. The
|
|
1365
|
-
#
|
|
1371
|
+
# script added later is covered without regenerating this hook. The commits BEING PUSHED are
|
|
1372
|
+
# the bytes that ship, so each one is materialised from git's object store and checked there \u2014
|
|
1373
|
+
# never the working tree (wrong in both directions: a fixed tree let an unfixed commit ship, and
|
|
1374
|
+
# a dirty tree shared by several sessions blocked an unrelated push) and never the tip alone (a
|
|
1375
|
+
# bad commit under a clean tip shipped while the gate read only the tip's fix). The classifier
|
|
1376
|
+
# is discover-shell-scripts.sh beside this hook \u2014 tracked and shebanged like what it classifies,
|
|
1366
1377
|
# so the scan it implements finds and checks it too. zsh is NOT in the checked set:
|
|
1367
1378
|
# the checker cannot parse it (SC1071 is a parser-level error no inline directive can silence),
|
|
1368
1379
|
# so checking it would fail every push on the parser, not on the script. Excluded \u2014 and said so
|
|
1369
1380
|
# at run time below, because a coverage hole that is silent is indistinguishable from coverage.
|
|
1370
1381
|
# The same honesty splits the unreadable: a regular file that exists but cannot be read blocks
|
|
1371
1382
|
# the push (coverage would otherwise silently shrink), while a tracked path with nothing readable
|
|
1372
|
-
# behind it \u2014 a submodule entry, a dangling symlink
|
|
1373
|
-
#
|
|
1383
|
+
# behind it \u2014 a submodule entry, a dangling symlink \u2014 is counted and said so below as skipped,
|
|
1384
|
+
# never a block.
|
|
1374
1385
|
#
|
|
1375
1386
|
# "the checker", not its name, on purpose: a comment whose first word is that name is read as
|
|
1376
1387
|
# a DIRECTIVE, and an unparseable directive is itself an error (SC1072/SC1073). A hook that
|
|
@@ -1389,39 +1400,97 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
1389
1400
|
shellcheck_tmp=$(mktemp -d) || exit 1
|
|
1390
1401
|
trap 'rm -rf "$shellcheck_tmp"' 0
|
|
1391
1402
|
trap 'exit 1' 1 2 3 15
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1403
|
+
# Every commit in each pushed range, never the tip alone: pushing two commits \u2014 a bad
|
|
1404
|
+
# script, then its fix \u2014 passed a tip-only read while the bad commit landed on the remote.
|
|
1405
|
+
# An all-zero local sha is a delete (nothing to check). An all-zero REMOTE sha is a new
|
|
1406
|
+
# branch: everything no remote already has is being pushed, so the range is the local sha
|
|
1407
|
+
# minus every remote-tracking ref \u2014 commits a remote already received were gated when they
|
|
1408
|
+
# landed there, and the residue is exactly this push's new commits. A remote sha this clone
|
|
1409
|
+
# has never seen (the remote moved on since the last fetch) cannot bound a range, so it takes
|
|
1410
|
+
# the same new-branch rule instead of refusing a push the gate could have read. Enumeration
|
|
1411
|
+
# failure refuses the push: a range the gate could not list is a range it did not read.
|
|
1412
|
+
# (pattern) with both parens: bash 3.2 (macOS /bin/sh) cannot parse an unbalanced )
|
|
1413
|
+
# in a case pattern.
|
|
1414
|
+
: > "$shellcheck_tmp/shas" || exit 1
|
|
1415
|
+
printf '%s\\n' "$refs" | while read -r _lref lsha _rref rsha; do
|
|
1416
|
+
case "$lsha" in
|
|
1417
|
+
(*[!0]*) ;;
|
|
1418
|
+
(*) continue ;;
|
|
1419
|
+
esac
|
|
1420
|
+
case "$rsha" in
|
|
1421
|
+
(*[!0]*)
|
|
1422
|
+
if git cat-file -e "\${rsha}^{commit}" 2>/dev/null; then
|
|
1423
|
+
git rev-list "$rsha..$lsha"
|
|
1424
|
+
else
|
|
1425
|
+
git rev-list "$lsha" --not --remotes
|
|
1426
|
+
fi ;;
|
|
1427
|
+
(*) git rev-list "$lsha" --not --remotes ;;
|
|
1428
|
+
esac >> "$shellcheck_tmp/shas" || exit 1
|
|
1429
|
+
done || {
|
|
1430
|
+
echo "etymd: could not enumerate the commits being pushed for shellcheck" >&2
|
|
1399
1431
|
exit 1
|
|
1400
1432
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1433
|
+
shas=$(sort -u "$shellcheck_tmp/shas") || exit 1
|
|
1434
|
+
[ -n "$shas" ] || echo "\u203A shellcheck: no commit in the pushed refs (deletes only, or nothing on stdin) \u2014 nothing to check"
|
|
1435
|
+
# Resolved once, before any cd: the classifier runs inside each materialised tree, and a
|
|
1436
|
+
# relative hook path would no longer point at it from there.
|
|
1437
|
+
discover="$(cd "$(dirname "$0")" && pwd)/discover-shell-scripts.sh" || exit 1
|
|
1438
|
+
for sha in $shas; do
|
|
1439
|
+
# A fresh directory per commit, removed once that commit is checked so a long push does
|
|
1440
|
+
# not pile up one full tree per commit; the subshell trap above still cleans the root on
|
|
1441
|
+
# every early exit.
|
|
1442
|
+
tree=$(mktemp -d "$shellcheck_tmp/commit.XXXXXX") || exit 1
|
|
1443
|
+
# Checked out through a scratch index, never \`git archive\`: archive honours the
|
|
1444
|
+
# export-ignore and export-subst attributes, so a script marked export-ignore would leave
|
|
1445
|
+
# the checked set without a word. read-tree + checkout-index writes every tracked blob.
|
|
1446
|
+
# Large-file pointers stay pointers \u2014 they are never shell, and a push must not need the
|
|
1447
|
+
# network to be checked.
|
|
1448
|
+
if ! git cat-file -e "\${sha}^{commit}" 2>/dev/null \\
|
|
1449
|
+
|| ! GIT_INDEX_FILE="$shellcheck_tmp/index" git read-tree "$sha" 2>/dev/null \\
|
|
1450
|
+
|| ! GIT_INDEX_FILE="$shellcheck_tmp/index" GIT_LFS_SKIP_SMUDGE=1 \\
|
|
1451
|
+
git checkout-index -a -f --prefix="$tree/" 2>/dev/null; then
|
|
1452
|
+
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
|
|
1453
|
+
exit 1
|
|
1454
|
+
fi
|
|
1455
|
+
if [ -n "$(git ls-tree -r --name-only "$sha")" ] && [ -z "$(ls -A "$tree")" ]; then
|
|
1456
|
+
echo "\u2717 shellcheck: $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") extracted to an empty tree \u2014 refusing the push" >&2
|
|
1457
|
+
exit 1
|
|
1458
|
+
fi
|
|
1459
|
+
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
1460
|
+
git ls-tree -r -z --name-only "$sha" > "$shellcheck_tmp/tracked" || {
|
|
1461
|
+
echo "etymd: cannot enumerate the tree of $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") for shellcheck" >&2
|
|
1462
|
+
exit 1
|
|
1463
|
+
}
|
|
1464
|
+
( cd "$tree" && xargs -0 "$discover" "$shellcheck_tmp" ) < "$shellcheck_tmp/tracked" || {
|
|
1465
|
+
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
1414
1466
|
exit 1
|
|
1415
1467
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
echo " \xB7 style/info (not blocking):"
|
|
1422
|
-
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
1468
|
+
count=$(wc -c < "$shellcheck_tmp/count") || exit 1
|
|
1469
|
+
zsh_count=$(wc -c < "$shellcheck_tmp/zsh-count") || exit 1
|
|
1470
|
+
skip_count=$(wc -c < "$shellcheck_tmp/skip-count") || exit 1
|
|
1471
|
+
if [ "$skip_count" -gt 0 ]; then
|
|
1472
|
+
echo "\u203A shellcheck: $((skip_count)) tracked path(s) with nothing readable behind them (submodule or dangling symlink) \u2014 not checked, not failed"
|
|
1423
1473
|
fi
|
|
1424
|
-
|
|
1474
|
+
if [ "$zsh_count" -gt 0 ]; then
|
|
1475
|
+
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
1476
|
+
fi
|
|
1477
|
+
if [ "$count" -gt 0 ]; then
|
|
1478
|
+
echo "\u203A shellcheck ($((count)) scripts in $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha"), blocking at severity=warning)"
|
|
1479
|
+
( cd "$tree" && xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" ) || {
|
|
1480
|
+
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
1481
|
+
exit 1
|
|
1482
|
+
}
|
|
1483
|
+
# Everything below the blocking bar, shown once the push is already cleared. Never affects
|
|
1484
|
+
# the exit code \u2014 advice that can fail a push is not advice.
|
|
1485
|
+
advice=$( ( cd "$tree" && xargs -0 shellcheck -S style -f gcc -- < "$shellcheck_tmp/scripts" 2>/dev/null ) \\
|
|
1486
|
+
| grep -v ': warning:\\|: error:' || true)
|
|
1487
|
+
if [ -n "$advice" ]; then
|
|
1488
|
+
echo " \xB7 style/info (not blocking):"
|
|
1489
|
+
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
1490
|
+
fi
|
|
1491
|
+
fi
|
|
1492
|
+
rm -rf "$tree" || exit 1
|
|
1493
|
+
done
|
|
1425
1494
|
) || exit 1
|
|
1426
1495
|
else
|
|
1427
1496
|
echo "\u203A shellcheck skipped (not on PATH) \u2014 install it to gate this repo's shell scripts"
|
|
@@ -1455,7 +1524,7 @@ fi`;
|
|
|
1455
1524
|
return stampGenerated(`#!/usr/bin/env sh
|
|
1456
1525
|
# etymd: correctness gate. Mirrors CI cheapest-first; blocks the push on any failure.
|
|
1457
1526
|
|
|
1458
|
-
${localHookCall("pre-push")}${runner}
|
|
1527
|
+
${localHookCall("pre-push", true)}${runner}
|
|
1459
1528
|
${body}${shellStep}
|
|
1460
1529
|
${auditStep}
|
|
1461
1530
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-GWGKEPRX.js';
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { planWorkflow } from './chunk-7N5JDXQG.js';
|
|
4
|
+
import './chunk-PQISPOB2.js';
|
|
5
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-HI7NWPRA.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject } from './chunk-6DJMKZGK.js';
|
|
7
|
+
import { VERSION } from './chunk-KQFEKOON.js';
|
|
8
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-P6ATKV2R.js';
|
|
9
|
-
import { PACK_VERSION } from './chunk-
|
|
9
|
+
import { PACK_VERSION } from './chunk-2DRA7S67.js';
|
|
10
10
|
import { git, readText } from './chunk-4VPBP6K6.js';
|
|
11
11
|
import { promises } from 'fs';
|
|
12
12
|
import path from 'path';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, meetsFailOn, buildTruthEnv, emptyCounters, checkTextClaims, checkDocRefs, loadDecisionLedger, checkDecisionRefs, rankFindings, PATH_TOKEN_RE, HOSTNAME_RE, KNOWN_EXTENSIONS, NAMESPACE_IDENT, NAMESPACE_STOP } from './chunk-
|
|
2
|
+
import { parseFailOnTier, meetsFailOn, buildTruthEnv, emptyCounters, checkTextClaims, checkDocRefs, loadDecisionLedger, checkDecisionRefs, rankFindings, PATH_TOKEN_RE, HOSTNAME_RE, KNOWN_EXTENSIONS, NAMESPACE_IDENT, NAMESPACE_STOP } from './chunk-WKP72Z3J.js';
|
|
3
3
|
import { print, section, theme, renderFindings } from './chunk-HI7NWPRA.js';
|
|
4
|
-
import { scanProject } from './chunk-
|
|
5
|
-
import './chunk-
|
|
4
|
+
import { scanProject } from './chunk-6DJMKZGK.js';
|
|
5
|
+
import './chunk-KQFEKOON.js';
|
|
6
6
|
import { ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
7
|
-
import './chunk-
|
|
7
|
+
import './chunk-2DRA7S67.js';
|
|
8
8
|
import { readText, pathExists } from './chunk-4VPBP6K6.js';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { promises } from 'fs';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { sweepFleet, loadFleetManifest, FLEET_JSON_SCHEMA } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
2
|
+
import { sweepFleet, loadFleetManifest, FLEET_JSON_SCHEMA } from './chunk-M7XDP6FT.js';
|
|
3
|
+
import './chunk-UMKC3OWQ.js';
|
|
4
|
+
import './chunk-WKP72Z3J.js';
|
|
5
5
|
import './chunk-3E2IPCRY.js';
|
|
6
6
|
import { print, section, theme, renderFleetNotes } from './chunk-HI7NWPRA.js';
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
7
|
+
import './chunk-6DJMKZGK.js';
|
|
8
|
+
import './chunk-KQFEKOON.js';
|
|
9
9
|
import './chunk-P6ATKV2R.js';
|
|
10
|
-
import './chunk-
|
|
10
|
+
import './chunk-2DRA7S67.js';
|
|
11
11
|
import { readText } from './chunk-4VPBP6K6.js';
|
|
12
12
|
import path from 'path';
|
|
13
13
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, renderFacts, theme } from './chunk-HI7NWPRA.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-6DJMKZGK.js';
|
|
4
|
+
import './chunk-KQFEKOON.js';
|
|
5
5
|
import { writeCachedFacts } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-2DRA7S67.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/scan.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "etymd",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Keep your agent instructions true — verify AGENTS.md, CLAUDE.md, rules, skills, and the task you hand an agent against the actual repo, with drift caught over time and a regression ledger.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
package/dist/audit-RONSEYFS.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-4IESOWHQ.js';
|
|
3
|
-
import './chunk-KIPD3N77.js';
|
|
4
|
-
import './chunk-EYUW3UJU.js';
|
|
5
|
-
import './chunk-3E2IPCRY.js';
|
|
6
|
-
import './chunk-HI7NWPRA.js';
|
|
7
|
-
import './chunk-DLHC7QON.js';
|
|
8
|
-
import './chunk-L4KIIKXI.js';
|
|
9
|
-
import './chunk-P6ATKV2R.js';
|
|
10
|
-
import './chunk-U7BCBJIH.js';
|
|
11
|
-
import './chunk-4VPBP6K6.js';
|