etymd 0.19.1 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/{approve-J6JU7MBX.js → approve-Z4NMEGDM.js} +3 -3
- package/dist/audit-4PGVS2TW.js +11 -0
- package/dist/{brief-NHHILXIH.js → brief-2N6X3CFR.js} +3 -3
- package/dist/{chunk-2DRA7S67.js → chunk-6FT7KSKU.js} +1 -1
- package/dist/{chunk-WKP72Z3J.js → chunk-DWPMANIC.js} +1 -1
- package/dist/{chunk-UMKC3OWQ.js → chunk-FG5XKFR2.js} +3 -3
- package/dist/{chunk-6DJMKZGK.js → chunk-OKHNNWOV.js} +32 -15
- package/dist/{chunk-7N5JDXQG.js → chunk-TZGRV7UY.js} +1 -1
- package/dist/{chunk-KQFEKOON.js → chunk-W6GGG4LG.js} +1 -1
- package/dist/{chunk-M7XDP6FT.js → chunk-WIBN7PBH.js} +4 -4
- package/dist/{chunk-GFUDKTC5.js → chunk-WYJFZ2MB.js} +2 -2
- package/dist/{chunk-PQISPOB2.js → chunk-X5GR2EJK.js} +73 -22
- package/dist/cli.js +16 -16
- package/dist/{doctor-264Q5PLY.js → doctor-7YG342YP.js} +6 -6
- package/dist/{fleet-VT625Q2J.js → fleet-XZVQLY53.js} +8 -8
- package/dist/{gates-YUMSTKUG.js → gates-633B6G2T.js} +5 -5
- package/dist/{generate-62GQSCKH.js → generate-S5KGZIIM.js} +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +104 -36
- package/dist/{init-G4XXFXVD.js → init-SL3HEB3H.js} +12 -5
- package/dist/{premise-RNMAKMVA.js → premise-62AXRQUK.js} +4 -4
- package/dist/{propose-35JXMEPH.js → propose-4UWLIFWJ.js} +6 -6
- package/dist/scan-M7STYIYQ.js +5 -0
- package/dist/{scan-PJH32H7C.js → scan-VEHQEEXY.js} +3 -3
- 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,18 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.19.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b46bbb9: Generated pre-push hooks check only the shell scripts each pushed commit changes, instead of
|
|
8
|
+
every script in every pushed commit's tree. A commit that changes the gate itself, its
|
|
9
|
+
classifier or a `.shellcheckrc` (deletions included) is still checked whole. So is the commit
|
|
10
|
+
that installs the gate, which reads a repo's existing scripts once. A commit with no shell
|
|
11
|
+
script says so. Pointer checks no longer accept an `@AGENTS.md` import inside a fenced code
|
|
12
|
+
block, an unreadable Claude Code version counts as too old instead of passing, and `init` warns
|
|
13
|
+
when a `CLAUDE.md` or `.claude/CLAUDE.md` hides `AGENTS.md`. Regenerate existing hooks with
|
|
14
|
+
`etymd gates --yes` to apply the change.
|
|
15
|
+
|
|
3
16
|
## 0.19.1
|
|
4
17
|
|
|
5
18
|
### 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-OKHNNWOV.js';
|
|
4
|
+
import { VERSION } from './chunk-W6GGG4LG.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-6FT7KSKU.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-WYJFZ2MB.js';
|
|
3
|
+
import './chunk-FG5XKFR2.js';
|
|
4
|
+
import './chunk-DWPMANIC.js';
|
|
5
|
+
import './chunk-3E2IPCRY.js';
|
|
6
|
+
import './chunk-HI7NWPRA.js';
|
|
7
|
+
import './chunk-OKHNNWOV.js';
|
|
8
|
+
import './chunk-W6GGG4LG.js';
|
|
9
|
+
import './chunk-P6ATKV2R.js';
|
|
10
|
+
import './chunk-6FT7KSKU.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-OKHNNWOV.js';
|
|
4
|
+
import './chunk-W6GGG4LG.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-6FT7KSKU.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
import { promises } from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { expandFileGlobs } from './chunk-
|
|
2
|
+
import { expandFileGlobs } from './chunk-OKHNNWOV.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,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-DWPMANIC.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-OKHNNWOV.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-6FT7KSKU.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 { VERSION } from './chunk-
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-W6GGG4LG.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-6FT7KSKU.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-X5GR2EJK.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,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runAudit } from './chunk-
|
|
2
|
+
import { runAudit } from './chunk-FG5XKFR2.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-OKHNNWOV.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-S5KGZIIM.js');
|
|
1010
|
+
const { scanProject } = await import('./scan-M7STYIYQ.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 { runAudit } from './chunk-
|
|
3
|
-
import { parseFailOnTier, meetsFailOn } from './chunk-
|
|
2
|
+
import { runAudit } from './chunk-FG5XKFR2.js';
|
|
3
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-DWPMANIC.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 { PACK_VERSION } from './chunk-
|
|
2
|
+
import { PACK_VERSION } from './chunk-6FT7KSKU.js';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
|
|
5
5
|
var GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
|
|
@@ -269,11 +269,11 @@ function generateShellDiscoveryScript() {
|
|
|
269
269
|
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
270
270
|
# fails, naming the path.
|
|
271
271
|
#
|
|
272
|
-
# The
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
272
|
+
# The match/error protocol: grep reports "no match" as 1 and a failure as 2 or more, and only
|
|
273
|
+
# the first is a verdict. Letting a failure fall through would pass a broken matcher as "not a
|
|
274
|
+
# shell script" \u2014 the exact silent coverage-shrink the fail-closed rules exist to prevent. The
|
|
275
|
+
# status is captured on the grep's own line (\`|| st=$?\`), so no command added later can come
|
|
276
|
+
# between the grep and the check and silently replace the status being read.
|
|
277
277
|
work=$1
|
|
278
278
|
shift
|
|
279
279
|
for file do
|
|
@@ -289,17 +289,22 @@ for file do
|
|
|
289
289
|
exit 1
|
|
290
290
|
}
|
|
291
291
|
head -n 1 "$work/head-bytes" > "$work/first-line" || exit 1
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
[ "
|
|
301
|
-
|
|
302
|
-
|
|
292
|
+
st=0
|
|
293
|
+
grep -qE "^#!.*[/ ](ba|da)?sh( |$)" "$work/first-line" || st=$?
|
|
294
|
+
case $st in
|
|
295
|
+
(0)
|
|
296
|
+
printf "./%s\\0" "$file" >> "$work/scripts" || exit 1
|
|
297
|
+
printf . >> "$work/count" || exit 1 ;;
|
|
298
|
+
(1)
|
|
299
|
+
st=0
|
|
300
|
+
grep -qE "^#!.*[/ ]zsh( |$)" "$work/first-line" || st=$?
|
|
301
|
+
case $st in
|
|
302
|
+
(0) printf . >> "$work/zsh-count" || exit 1 ;;
|
|
303
|
+
(1) ;;
|
|
304
|
+
(*) exit 1 ;;
|
|
305
|
+
esac ;;
|
|
306
|
+
(*) exit 1 ;;
|
|
307
|
+
esac
|
|
303
308
|
done
|
|
304
309
|
`);
|
|
305
310
|
}
|
|
@@ -369,7 +374,13 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
369
374
|
exit 1
|
|
370
375
|
}
|
|
371
376
|
shas=$(sort -u "$shellcheck_tmp/shas") || exit 1
|
|
372
|
-
[ -n "$shas" ]
|
|
377
|
+
if [ -n "$shas" ]; then
|
|
378
|
+
# Said up front: a first push from a fresh clone can carry the whole history, and a long
|
|
379
|
+
# silent hook reads as a hung one.
|
|
380
|
+
echo "\u203A shellcheck: $(printf '%s\\n' "$shas" | wc -l | tr -d ' ') commit(s) in the pushed range"
|
|
381
|
+
else
|
|
382
|
+
echo "\u203A shellcheck: no commit in the pushed refs (deletes only, or nothing on stdin) \u2014 nothing to check"
|
|
383
|
+
fi
|
|
373
384
|
# Resolved once, before any cd: the classifier runs inside each materialised tree, and a
|
|
374
385
|
# relative hook path would no longer point at it from there.
|
|
375
386
|
discover="$(cd "$(dirname "$0")" && pwd)/discover-shell-scripts.sh" || exit 1
|
|
@@ -395,10 +406,48 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
395
406
|
exit 1
|
|
396
407
|
fi
|
|
397
408
|
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
398
|
-
|
|
399
|
-
|
|
409
|
+
# Only the paths this commit adds, copies, modifies, renames or retypes against its first
|
|
410
|
+
# parent. A script the commit did not touch has its parent's bytes, and that parent was
|
|
411
|
+
# either gated when it reached the remote or sits in this range and is checked here; a
|
|
412
|
+
# merge diffed against its first parent brings in everything the other side added.
|
|
413
|
+
# Checking every script in every commit multiplied the cost by the range length, so a long
|
|
414
|
+
# push of a script-heavy repo ran long enough to look hung. Two changes alter the verdict
|
|
415
|
+
# on bytes nobody touched \u2014 the classifier deciding what is a script, and a .shellcheckrc
|
|
416
|
+
# deciding what is a finding \u2014 so a commit that touches either, deletion included (a
|
|
417
|
+
# removed .shellcheckrc re-enables every check it disabled), is checked whole. So is a
|
|
418
|
+
# commit that adds or changes this hook: a repo adopting the gate after the fact has
|
|
419
|
+
# scripts no gate ever read, and the commit installing it is where they get read once.
|
|
420
|
+
short=$(git rev-parse --short "$sha" 2>/dev/null || echo "$sha")
|
|
421
|
+
if git rev-parse -q --verify "\${sha}^1^{commit}" >/dev/null 2>&1; then
|
|
422
|
+
git diff-tree -r -z --name-only --no-commit-id "\${sha}^1" "$sha" > "$shellcheck_tmp/touched" \\
|
|
423
|
+
&& git diff-tree -r -z --name-only --no-commit-id --diff-filter=ACMRT "\${sha}^1" "$sha" > "$shellcheck_tmp/tracked"
|
|
424
|
+
else
|
|
425
|
+
git diff-tree -r -z --name-only --no-commit-id --root "$sha" > "$shellcheck_tmp/touched" \\
|
|
426
|
+
&& cp "$shellcheck_tmp/touched" "$shellcheck_tmp/tracked"
|
|
427
|
+
fi || {
|
|
428
|
+
echo "etymd: cannot enumerate the paths $short changes for shellcheck" >&2
|
|
400
429
|
exit 1
|
|
401
430
|
}
|
|
431
|
+
where="changed in $short"
|
|
432
|
+
# Through a file, not a pipe: a pipeline reports only grep's status, and a failing tr would
|
|
433
|
+
# hand grep nothing \u2014 a "no match" that silently takes the narrow path.
|
|
434
|
+
tr '\\000' '\\n' < "$shellcheck_tmp/touched" > "$shellcheck_tmp/touched-lines" || exit 1
|
|
435
|
+
# grep: 1 is "no match", a verdict; anything higher is the matcher failing. The status is
|
|
436
|
+
# taken on the grep's own line so nothing added later can come between them.
|
|
437
|
+
st=0
|
|
438
|
+
grep -qE '(^|/)(\\.shellcheckrc|discover-shell-scripts\\.sh|pre-push)$' "$shellcheck_tmp/touched-lines" || st=$?
|
|
439
|
+
case $st in
|
|
440
|
+
(0)
|
|
441
|
+
where="in $short (whole tree: the gate, its classifier or a .shellcheckrc changed)"
|
|
442
|
+
git ls-tree -r -z --name-only "$sha" > "$shellcheck_tmp/tracked" || {
|
|
443
|
+
echo "etymd: cannot enumerate the tree of $short for shellcheck" >&2
|
|
444
|
+
exit 1
|
|
445
|
+
} ;;
|
|
446
|
+
(1) ;;
|
|
447
|
+
(*)
|
|
448
|
+
echo "etymd: shell script discovery failed; cannot tell whether $short changes the gate, its classifier or a .shellcheckrc" >&2
|
|
449
|
+
exit 1 ;;
|
|
450
|
+
esac
|
|
402
451
|
( cd "$tree" && xargs -0 "$discover" "$shellcheck_tmp" ) < "$shellcheck_tmp/tracked" || {
|
|
403
452
|
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
404
453
|
exit 1
|
|
@@ -412,8 +461,10 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
412
461
|
if [ "$zsh_count" -gt 0 ]; then
|
|
413
462
|
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
414
463
|
fi
|
|
415
|
-
if [ "$count" -
|
|
416
|
-
echo "\u203A shellcheck
|
|
464
|
+
if [ "$count" -eq 0 ]; then
|
|
465
|
+
echo "\u203A shellcheck: no shell script $where \u2014 nothing to check there"
|
|
466
|
+
else
|
|
467
|
+
echo "\u203A shellcheck ($((count)) scripts $where, blocking at severity=warning)"
|
|
417
468
|
( cd "$tree" && xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" ) || {
|
|
418
469
|
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
419
470
|
exit 1
|
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-W6GGG4LG.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-4PGVS2TW.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-SL3HEB3H.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-Z4NMEGDM.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-VEHQEEXY.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-7YG342YP.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-2N6X3CFR.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-62AXRQUK.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-XZVQLY53.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-XZVQLY53.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-XZVQLY53.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-XZVQLY53.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-XZVQLY53.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-XZVQLY53.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-4UWLIFWJ.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-633B6G2T.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-WYJFZ2MB.js';
|
|
3
|
+
import './chunk-FG5XKFR2.js';
|
|
4
|
+
import './chunk-DWPMANIC.js';
|
|
5
5
|
import './chunk-3E2IPCRY.js';
|
|
6
6
|
import './chunk-HI7NWPRA.js';
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
7
|
+
import './chunk-OKHNNWOV.js';
|
|
8
|
+
import './chunk-W6GGG4LG.js';
|
|
9
9
|
import './chunk-P6ATKV2R.js';
|
|
10
|
-
import './chunk-
|
|
10
|
+
import './chunk-6FT7KSKU.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-WIBN7PBH.js';
|
|
4
|
+
import './chunk-FG5XKFR2.js';
|
|
5
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-DWPMANIC.js';
|
|
6
6
|
import './chunk-3E2IPCRY.js';
|
|
7
|
-
import { generateClaudePointerMd } from './chunk-
|
|
7
|
+
import { generateClaudePointerMd } from './chunk-X5GR2EJK.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-OKHNNWOV.js';
|
|
10
|
+
import './chunk-W6GGG4LG.js';
|
|
11
11
|
import './chunk-P6ATKV2R.js';
|
|
12
|
-
import './chunk-
|
|
12
|
+
import './chunk-6FT7KSKU.js';
|
|
13
13
|
import { readText, pathExists, git } from './chunk-4VPBP6K6.js';
|
|
14
14
|
import { promises } from 'fs';
|
|
15
15
|
import os from 'os';
|
|
@@ -240,7 +240,7 @@ async function add(opts) {
|
|
|
240
240
|
throw new Error(
|
|
241
241
|
`\`${name}\` has a CLAUDE.md that hides its AGENTS.md from Claude Code (${pointer.detail}). Add a full-line \`@AGENTS.md\` import to it, e.g.:
|
|
242
242
|
|
|
243
|
-
${generateClaudePointerMd().trimEnd()
|
|
243
|
+
${generateClaudePointerMd().trimEnd()}
|
|
244
244
|
|
|
245
245
|
\u2014 or symlink either file to the other, or delete the CLAUDE.md \u2014 then re-run \`etymd fleet add\`.`
|
|
246
246
|
);
|
|
@@ -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-TZGRV7UY.js';
|
|
4
|
+
import { runPrefix, isSafeGateCommand } from './chunk-X5GR2EJK.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-OKHNNWOV.js';
|
|
7
|
+
import './chunk-W6GGG4LG.js';
|
|
8
8
|
import { readConfig, CONFIG_FILE, configPath } from './chunk-P6ATKV2R.js';
|
|
9
|
-
import './chunk-
|
|
9
|
+
import './chunk-6FT7KSKU.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-TZGRV7UY.js';
|
|
3
|
+
import './chunk-X5GR2EJK.js';
|
|
4
4
|
import './chunk-P6ATKV2R.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-6FT7KSKU.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 = "16";
|
|
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 = "16";
|
|
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.2",
|
|
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",
|
|
@@ -501,23 +501,40 @@ async function detectArtifacts(root) {
|
|
|
501
501
|
});
|
|
502
502
|
return artifacts;
|
|
503
503
|
}
|
|
504
|
+
function withoutFencedCode(text) {
|
|
505
|
+
const kept = [];
|
|
506
|
+
let fence = null;
|
|
507
|
+
for (const line of text.split("\n")) {
|
|
508
|
+
if (fence === null) {
|
|
509
|
+
const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
510
|
+
if (open?.[1] && !(open[1][0] === "`" && open[2]?.includes("`"))) fence = open[1];
|
|
511
|
+
else kept.push(line);
|
|
512
|
+
} else {
|
|
513
|
+
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line)?.[1];
|
|
514
|
+
if (close && close[0] === fence[0] && close.length >= fence.length) fence = null;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return kept.join("\n");
|
|
518
|
+
}
|
|
504
519
|
function detectClaudeCodeVersion() {
|
|
505
520
|
const override = process.env.ETYMD_CLAUDE_VERSION;
|
|
506
521
|
if (override !== void 0) {
|
|
507
522
|
return Promise.resolve(override === "none" || override === "" ? null : override);
|
|
508
523
|
}
|
|
509
|
-
claudeVersionMemo ??= pExecFile2("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
524
|
+
claudeVersionMemo ??= pExecFile2("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?=\s|$)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
510
525
|
return claudeVersionMemo;
|
|
511
526
|
}
|
|
512
|
-
function
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
527
|
+
function mayPredate(a, b) {
|
|
528
|
+
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/.exec(v.trim());
|
|
529
|
+
const pa = parse(a);
|
|
530
|
+
const pb = parse(b);
|
|
531
|
+
if (!pa || !pb) return true;
|
|
532
|
+
for (let i = 1; i <= 3; i++) {
|
|
533
|
+
const x = Number(pa[i]);
|
|
534
|
+
const y = Number(pb[i]);
|
|
518
535
|
if (x !== y) return x < y;
|
|
519
536
|
}
|
|
520
|
-
return
|
|
537
|
+
return pa[4] !== void 0 && pb[4] === void 0;
|
|
521
538
|
}
|
|
522
539
|
async function checkClaudePointer(root, claudeVersion) {
|
|
523
540
|
const agentsAbs = path2.join(root, "AGENTS.md");
|
|
@@ -535,11 +552,11 @@ async function checkClaudePointer(root, claudeVersion) {
|
|
|
535
552
|
return { ok: true, via: "same-file" };
|
|
536
553
|
}
|
|
537
554
|
const rootClaude = await readText(claudeAbs);
|
|
538
|
-
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(rootClaude)) {
|
|
555
|
+
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(withoutFencedCode(rootClaude))) {
|
|
539
556
|
return { ok: true, via: "root-import" };
|
|
540
557
|
}
|
|
541
558
|
const dotClaude = await readText(path2.join(root, ".claude", "CLAUDE.md"));
|
|
542
|
-
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(dotClaude)) {
|
|
559
|
+
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(withoutFencedCode(dotClaude))) {
|
|
543
560
|
return { ok: true, via: "dotclaude-import" };
|
|
544
561
|
}
|
|
545
562
|
if (rootClaude !== null || dotClaude !== null) {
|
|
@@ -550,7 +567,7 @@ async function checkClaudePointer(root, claudeVersion) {
|
|
|
550
567
|
};
|
|
551
568
|
}
|
|
552
569
|
const version = await detectClaudeCodeVersion() ;
|
|
553
|
-
if (version !== null &&
|
|
570
|
+
if (version !== null && mayPredate(version, CLAUDE_AGENTS_FALLBACK_VERSION)) {
|
|
554
571
|
return {
|
|
555
572
|
ok: false,
|
|
556
573
|
kind: "old-reader",
|
|
@@ -779,8 +796,8 @@ var init_detect = __esm({
|
|
|
779
796
|
];
|
|
780
797
|
SHELL_SHEBANG = /^#!\s*\/\S*\b(?:ba|da|z)?sh\b|^#!\s*\/usr\/bin\/env\s+(?:ba|da|z)?sh\b/;
|
|
781
798
|
SHELL_EXT = /\.(?:sh|bash|zsh)$/;
|
|
782
|
-
ROOT_POINTER_IMPORT_RE =
|
|
783
|
-
DOT_CLAUDE_IMPORT_RE =
|
|
799
|
+
ROOT_POINTER_IMPORT_RE = /^ {0,3}@(\.\/)?AGENTS\.md[ \t]*$/m;
|
|
800
|
+
DOT_CLAUDE_IMPORT_RE = /^ {0,3}@\.\.\/AGENTS\.md[ \t]*$/m;
|
|
784
801
|
CLAUDE_AGENTS_FALLBACK_VERSION = "2.1.277";
|
|
785
802
|
pExecFile2 = promisify(execFile);
|
|
786
803
|
}
|
|
@@ -1331,11 +1348,11 @@ function generateShellDiscoveryScript() {
|
|
|
1331
1348
|
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
1332
1349
|
# fails, naming the path.
|
|
1333
1350
|
#
|
|
1334
|
-
# The
|
|
1335
|
-
#
|
|
1336
|
-
#
|
|
1337
|
-
#
|
|
1338
|
-
#
|
|
1351
|
+
# The match/error protocol: grep reports "no match" as 1 and a failure as 2 or more, and only
|
|
1352
|
+
# the first is a verdict. Letting a failure fall through would pass a broken matcher as "not a
|
|
1353
|
+
# shell script" \u2014 the exact silent coverage-shrink the fail-closed rules exist to prevent. The
|
|
1354
|
+
# status is captured on the grep's own line (\`|| st=$?\`), so no command added later can come
|
|
1355
|
+
# between the grep and the check and silently replace the status being read.
|
|
1339
1356
|
work=$1
|
|
1340
1357
|
shift
|
|
1341
1358
|
for file do
|
|
@@ -1351,17 +1368,22 @@ for file do
|
|
|
1351
1368
|
exit 1
|
|
1352
1369
|
}
|
|
1353
1370
|
head -n 1 "$work/head-bytes" > "$work/first-line" || exit 1
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
[ "
|
|
1363
|
-
|
|
1364
|
-
|
|
1371
|
+
st=0
|
|
1372
|
+
grep -qE "^#!.*[/ ](ba|da)?sh( |$)" "$work/first-line" || st=$?
|
|
1373
|
+
case $st in
|
|
1374
|
+
(0)
|
|
1375
|
+
printf "./%s\\0" "$file" >> "$work/scripts" || exit 1
|
|
1376
|
+
printf . >> "$work/count" || exit 1 ;;
|
|
1377
|
+
(1)
|
|
1378
|
+
st=0
|
|
1379
|
+
grep -qE "^#!.*[/ ]zsh( |$)" "$work/first-line" || st=$?
|
|
1380
|
+
case $st in
|
|
1381
|
+
(0) printf . >> "$work/zsh-count" || exit 1 ;;
|
|
1382
|
+
(1) ;;
|
|
1383
|
+
(*) exit 1 ;;
|
|
1384
|
+
esac ;;
|
|
1385
|
+
(*) exit 1 ;;
|
|
1386
|
+
esac
|
|
1365
1387
|
done
|
|
1366
1388
|
`);
|
|
1367
1389
|
}
|
|
@@ -1431,7 +1453,13 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
1431
1453
|
exit 1
|
|
1432
1454
|
}
|
|
1433
1455
|
shas=$(sort -u "$shellcheck_tmp/shas") || exit 1
|
|
1434
|
-
[ -n "$shas" ]
|
|
1456
|
+
if [ -n "$shas" ]; then
|
|
1457
|
+
# Said up front: a first push from a fresh clone can carry the whole history, and a long
|
|
1458
|
+
# silent hook reads as a hung one.
|
|
1459
|
+
echo "\u203A shellcheck: $(printf '%s\\n' "$shas" | wc -l | tr -d ' ') commit(s) in the pushed range"
|
|
1460
|
+
else
|
|
1461
|
+
echo "\u203A shellcheck: no commit in the pushed refs (deletes only, or nothing on stdin) \u2014 nothing to check"
|
|
1462
|
+
fi
|
|
1435
1463
|
# Resolved once, before any cd: the classifier runs inside each materialised tree, and a
|
|
1436
1464
|
# relative hook path would no longer point at it from there.
|
|
1437
1465
|
discover="$(cd "$(dirname "$0")" && pwd)/discover-shell-scripts.sh" || exit 1
|
|
@@ -1457,10 +1485,48 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
1457
1485
|
exit 1
|
|
1458
1486
|
fi
|
|
1459
1487
|
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
1460
|
-
|
|
1461
|
-
|
|
1488
|
+
# Only the paths this commit adds, copies, modifies, renames or retypes against its first
|
|
1489
|
+
# parent. A script the commit did not touch has its parent's bytes, and that parent was
|
|
1490
|
+
# either gated when it reached the remote or sits in this range and is checked here; a
|
|
1491
|
+
# merge diffed against its first parent brings in everything the other side added.
|
|
1492
|
+
# Checking every script in every commit multiplied the cost by the range length, so a long
|
|
1493
|
+
# push of a script-heavy repo ran long enough to look hung. Two changes alter the verdict
|
|
1494
|
+
# on bytes nobody touched \u2014 the classifier deciding what is a script, and a .shellcheckrc
|
|
1495
|
+
# deciding what is a finding \u2014 so a commit that touches either, deletion included (a
|
|
1496
|
+
# removed .shellcheckrc re-enables every check it disabled), is checked whole. So is a
|
|
1497
|
+
# commit that adds or changes this hook: a repo adopting the gate after the fact has
|
|
1498
|
+
# scripts no gate ever read, and the commit installing it is where they get read once.
|
|
1499
|
+
short=$(git rev-parse --short "$sha" 2>/dev/null || echo "$sha")
|
|
1500
|
+
if git rev-parse -q --verify "\${sha}^1^{commit}" >/dev/null 2>&1; then
|
|
1501
|
+
git diff-tree -r -z --name-only --no-commit-id "\${sha}^1" "$sha" > "$shellcheck_tmp/touched" \\
|
|
1502
|
+
&& git diff-tree -r -z --name-only --no-commit-id --diff-filter=ACMRT "\${sha}^1" "$sha" > "$shellcheck_tmp/tracked"
|
|
1503
|
+
else
|
|
1504
|
+
git diff-tree -r -z --name-only --no-commit-id --root "$sha" > "$shellcheck_tmp/touched" \\
|
|
1505
|
+
&& cp "$shellcheck_tmp/touched" "$shellcheck_tmp/tracked"
|
|
1506
|
+
fi || {
|
|
1507
|
+
echo "etymd: cannot enumerate the paths $short changes for shellcheck" >&2
|
|
1462
1508
|
exit 1
|
|
1463
1509
|
}
|
|
1510
|
+
where="changed in $short"
|
|
1511
|
+
# Through a file, not a pipe: a pipeline reports only grep's status, and a failing tr would
|
|
1512
|
+
# hand grep nothing \u2014 a "no match" that silently takes the narrow path.
|
|
1513
|
+
tr '\\000' '\\n' < "$shellcheck_tmp/touched" > "$shellcheck_tmp/touched-lines" || exit 1
|
|
1514
|
+
# grep: 1 is "no match", a verdict; anything higher is the matcher failing. The status is
|
|
1515
|
+
# taken on the grep's own line so nothing added later can come between them.
|
|
1516
|
+
st=0
|
|
1517
|
+
grep -qE '(^|/)(\\.shellcheckrc|discover-shell-scripts\\.sh|pre-push)$' "$shellcheck_tmp/touched-lines" || st=$?
|
|
1518
|
+
case $st in
|
|
1519
|
+
(0)
|
|
1520
|
+
where="in $short (whole tree: the gate, its classifier or a .shellcheckrc changed)"
|
|
1521
|
+
git ls-tree -r -z --name-only "$sha" > "$shellcheck_tmp/tracked" || {
|
|
1522
|
+
echo "etymd: cannot enumerate the tree of $short for shellcheck" >&2
|
|
1523
|
+
exit 1
|
|
1524
|
+
} ;;
|
|
1525
|
+
(1) ;;
|
|
1526
|
+
(*)
|
|
1527
|
+
echo "etymd: shell script discovery failed; cannot tell whether $short changes the gate, its classifier or a .shellcheckrc" >&2
|
|
1528
|
+
exit 1 ;;
|
|
1529
|
+
esac
|
|
1464
1530
|
( cd "$tree" && xargs -0 "$discover" "$shellcheck_tmp" ) < "$shellcheck_tmp/tracked" || {
|
|
1465
1531
|
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
1466
1532
|
exit 1
|
|
@@ -1474,8 +1540,10 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
1474
1540
|
if [ "$zsh_count" -gt 0 ]; then
|
|
1475
1541
|
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
1476
1542
|
fi
|
|
1477
|
-
if [ "$count" -
|
|
1478
|
-
echo "\u203A shellcheck
|
|
1543
|
+
if [ "$count" -eq 0 ]; then
|
|
1544
|
+
echo "\u203A shellcheck: no shell script $where \u2014 nothing to check there"
|
|
1545
|
+
else
|
|
1546
|
+
echo "\u203A shellcheck ($((count)) scripts $where, blocking at severity=warning)"
|
|
1479
1547
|
( cd "$tree" && xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" ) || {
|
|
1480
1548
|
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
1481
1549
|
exit 1
|
|
@@ -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-TZGRV7UY.js';
|
|
4
|
+
import './chunk-X5GR2EJK.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, checkClaudePointer } from './chunk-OKHNNWOV.js';
|
|
7
|
+
import { VERSION } from './chunk-W6GGG4LG.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-6FT7KSKU.js';
|
|
10
10
|
import { git, readText } from './chunk-4VPBP6K6.js';
|
|
11
11
|
import { promises } from 'fs';
|
|
12
12
|
import path from 'path';
|
|
@@ -102,6 +102,13 @@ async function run(opts) {
|
|
|
102
102
|
section("Onboarded");
|
|
103
103
|
for (const w of result.written) print(` ${glyph.ok} ${theme.dim("wrote")} ${theme.info(w)}`);
|
|
104
104
|
for (const sk of result.skipped) print(` ${glyph.bullet} ${theme.dim("kept")} ${theme.dim(sk)}`);
|
|
105
|
+
const pointer = await checkClaudePointer(opts.cwd);
|
|
106
|
+
if (!pointer.ok && pointer.kind === "no-import") {
|
|
107
|
+
const line = pointer.detail.includes(".claude/CLAUDE.md") ? "@../AGENTS.md" : "@AGENTS.md";
|
|
108
|
+
print(
|
|
109
|
+
` ${glyph.partial} ${theme.warn(pointer.detail)} ${theme.dim(`\u2014 Claude Code reads that file instead; add a full-line ${line} import`)}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
105
112
|
print(
|
|
106
113
|
` ${glyph.ok} ${theme.dim("baseline approved \u2192")} ${theme.info(".etymd/baseline.json")} ${theme.dim("(commit it \u2014 drift is measured against it)")}`
|
|
107
114
|
);
|
|
@@ -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-DWPMANIC.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-OKHNNWOV.js';
|
|
5
|
+
import './chunk-W6GGG4LG.js';
|
|
6
6
|
import { ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
7
|
-
import './chunk-
|
|
7
|
+
import './chunk-6FT7KSKU.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-WIBN7PBH.js';
|
|
3
|
+
import './chunk-FG5XKFR2.js';
|
|
4
|
+
import './chunk-DWPMANIC.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-OKHNNWOV.js';
|
|
8
|
+
import './chunk-W6GGG4LG.js';
|
|
9
9
|
import './chunk-P6ATKV2R.js';
|
|
10
|
-
import './chunk-
|
|
10
|
+
import './chunk-6FT7KSKU.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-OKHNNWOV.js';
|
|
4
|
+
import './chunk-W6GGG4LG.js';
|
|
5
5
|
import { writeCachedFacts } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-6FT7KSKU.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.2",
|
|
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-5N2TU2X7.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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';
|