etymd 0.17.0 → 0.19.0
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 +57 -0
- package/README.md +10 -5
- package/dist/{approve-VN4EDBR5.js → approve-Y36ZO6IL.js} +3 -3
- package/dist/audit-RONSEYFS.js +11 -0
- package/dist/{brief-7MWP35UI.js → brief-ILB6RMSQ.js} +3 -3
- package/dist/{chunk-ANFNXFRI.js → chunk-4IESOWHQ.js} +2 -2
- package/dist/{chunk-446EZNAQ.js → chunk-DLHC7QON.js} +83 -10
- package/dist/{chunk-FNA4R5KT.js → chunk-EYUW3UJU.js} +59 -11
- package/dist/chunk-EZRK4MV3.js +109 -0
- package/dist/{chunk-PIJZUDDQ.js → chunk-HSRHOVII.js} +113 -121
- package/dist/{chunk-WKP7M2B3.js → chunk-KIPD3N77.js} +11 -6
- package/dist/{chunk-BUMOGHQA.js → chunk-L4KIIKXI.js} +1 -1
- package/dist/{chunk-YQZDYDAK.js → chunk-U7BCBJIH.js} +1 -1
- package/dist/{chunk-LVR2DPR7.js → chunk-ZCOIZVSM.js} +30 -3
- package/dist/cli.js +16 -16
- package/dist/{doctor-R4QDCAN5.js → doctor-EHRADIUZ.js} +6 -6
- package/dist/{fleet-WPYEVHVL.js → fleet-KFF3N2O7.js} +22 -6
- package/dist/{gates-3HRGYCYY.js → gates-UJ23THID.js} +5 -4
- package/dist/{generate-FSJQFQPV.js → generate-6IPTCLYG.js} +3 -2
- package/dist/index.d.ts +21 -3
- package/dist/index.js +301 -49
- package/dist/{init-6RULX5JZ.js → init-7HWVHZMF.js} +5 -4
- package/dist/{premise-GFWHSR3K.js → premise-U6JIB2QG.js} +9 -5
- package/dist/{propose-IYNOIWAO.js → propose-NZMEPZIL.js} +6 -6
- package/dist/{scan-LLWW4YOU.js → scan-DLKYG5BB.js} +3 -3
- package/dist/scan-V53PGAQ6.js +5 -0
- package/package.json +2 -2
- package/dist/audit-PHAK5HEW.js +0 -11
- package/dist/scan-5SC47FSM.js +0 -5
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 = "14";
|
|
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.
|
|
33
|
+
version: "0.19.0",
|
|
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",
|
|
@@ -104,7 +104,7 @@ var init_package = __esm({
|
|
|
104
104
|
prettier: "3.4.2",
|
|
105
105
|
tsup: "8.5.1",
|
|
106
106
|
typescript: "5.7.2",
|
|
107
|
-
vitest: "4.1.
|
|
107
|
+
vitest: "4.1.11"
|
|
108
108
|
},
|
|
109
109
|
publishConfig: {
|
|
110
110
|
access: "public"
|
|
@@ -388,10 +388,17 @@ async function hasLintStagedConfig(root, pkg) {
|
|
|
388
388
|
}
|
|
389
389
|
return false;
|
|
390
390
|
}
|
|
391
|
+
function normalizeHooksPath(root, raw) {
|
|
392
|
+
const abs = path2.resolve(root, raw);
|
|
393
|
+
const rel = path2.relative(root, abs);
|
|
394
|
+
const outside = path2.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path2.sep}`);
|
|
395
|
+
return outside ? abs : normalizeRelPath(rel) || ".";
|
|
396
|
+
}
|
|
391
397
|
async function detectHooks(root, hooksPath, pkg) {
|
|
392
398
|
const lintStaged = await hasLintStagedConfig(root, pkg);
|
|
393
399
|
if (hooksPath) {
|
|
394
|
-
|
|
400
|
+
const dir = normalizeHooksPath(root, hooksPath);
|
|
401
|
+
if (dir === ".husky/_") {
|
|
395
402
|
const base2 = path2.join(root, ".husky");
|
|
396
403
|
return {
|
|
397
404
|
source: "husky",
|
|
@@ -402,10 +409,10 @@ async function detectHooks(root, hooksPath, pkg) {
|
|
|
402
409
|
lintStaged
|
|
403
410
|
};
|
|
404
411
|
}
|
|
405
|
-
const base = path2.
|
|
412
|
+
const base = path2.resolve(root, dir);
|
|
406
413
|
return {
|
|
407
|
-
source:
|
|
408
|
-
dir
|
|
414
|
+
source: dir === ".githooks" ? "githooks" : "custom",
|
|
415
|
+
dir,
|
|
409
416
|
preCommit: await pathExists(path2.join(base, "pre-commit")),
|
|
410
417
|
prePush: await pathExists(path2.join(base, "pre-push")),
|
|
411
418
|
commitMsg: await pathExists(path2.join(base, "commit-msg")),
|
|
@@ -494,6 +501,64 @@ async function detectArtifacts(root) {
|
|
|
494
501
|
});
|
|
495
502
|
return artifacts;
|
|
496
503
|
}
|
|
504
|
+
function detectClaudeCodeVersion() {
|
|
505
|
+
const override = process.env.ETYMD_CLAUDE_VERSION;
|
|
506
|
+
if (override !== void 0) {
|
|
507
|
+
return Promise.resolve(override === "none" || override === "" ? null : override);
|
|
508
|
+
}
|
|
509
|
+
claudeVersionMemo ??= pExecFile2("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
510
|
+
return claudeVersionMemo;
|
|
511
|
+
}
|
|
512
|
+
function versionBefore(a, b) {
|
|
513
|
+
const pa = a.split(".").map(Number);
|
|
514
|
+
const pb = b.split(".").map(Number);
|
|
515
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
516
|
+
const x = pa[i] ?? 0;
|
|
517
|
+
const y = pb[i] ?? 0;
|
|
518
|
+
if (x !== y) return x < y;
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
async function checkClaudePointer(root, claudeVersion) {
|
|
523
|
+
const agentsAbs = path2.join(root, "AGENTS.md");
|
|
524
|
+
const claudeAbs = path2.join(root, "CLAUDE.md");
|
|
525
|
+
if (!await pathExists(agentsAbs)) return { ok: true, via: "no-agents" };
|
|
526
|
+
const statOrNone = async (p) => {
|
|
527
|
+
try {
|
|
528
|
+
return await promises.stat(p);
|
|
529
|
+
} catch {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const [agentsSt, claudeSt] = await Promise.all([statOrNone(agentsAbs), statOrNone(claudeAbs)]);
|
|
534
|
+
if (agentsSt && claudeSt && agentsSt.ino !== 0 && agentsSt.ino === claudeSt.ino && agentsSt.dev === claudeSt.dev) {
|
|
535
|
+
return { ok: true, via: "same-file" };
|
|
536
|
+
}
|
|
537
|
+
const rootClaude = await readText(claudeAbs);
|
|
538
|
+
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(rootClaude)) {
|
|
539
|
+
return { ok: true, via: "root-import" };
|
|
540
|
+
}
|
|
541
|
+
const dotClaude = await readText(path2.join(root, ".claude", "CLAUDE.md"));
|
|
542
|
+
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(dotClaude)) {
|
|
543
|
+
return { ok: true, via: "dotclaude-import" };
|
|
544
|
+
}
|
|
545
|
+
if (rootClaude !== null || dotClaude !== null) {
|
|
546
|
+
return {
|
|
547
|
+
ok: false,
|
|
548
|
+
kind: "no-import",
|
|
549
|
+
detail: rootClaude !== null ? "AGENTS.md and CLAUDE.md both present, but CLAUDE.md does not import it" : "AGENTS.md and .claude/CLAUDE.md both present, but .claude/CLAUDE.md does not import it"
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const version = await detectClaudeCodeVersion() ;
|
|
553
|
+
if (version !== null && versionBefore(version, CLAUDE_AGENTS_FALLBACK_VERSION)) {
|
|
554
|
+
return {
|
|
555
|
+
ok: false,
|
|
556
|
+
kind: "old-reader",
|
|
557
|
+
detail: `AGENTS.md present, no CLAUDE.md, and Claude Code ${version} predates the AGENTS.md fallback (${CLAUDE_AGENTS_FALLBACK_VERSION})`
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return { ok: true, via: "native-fallback" };
|
|
561
|
+
}
|
|
497
562
|
async function walkTree(root) {
|
|
498
563
|
const state = { budget: 2e4, truncated: false };
|
|
499
564
|
const countFiles = async (dir) => {
|
|
@@ -539,7 +604,7 @@ async function walkTree(root) {
|
|
|
539
604
|
dirs.sort((a, b) => b.files - a.files);
|
|
540
605
|
return { dirs, truncated: state.truncated };
|
|
541
606
|
}
|
|
542
|
-
var IGNORED_DIRS, MEANINGFUL_DOT_DIRS, isMeta, ROLE_LADDERS, FRAMEWORK_MARKERS, ARTIFACT_SPECS, SHELL_SHEBANG, SHELL_EXT;
|
|
607
|
+
var IGNORED_DIRS, MEANINGFUL_DOT_DIRS, isMeta, ROLE_LADDERS, FRAMEWORK_MARKERS, ARTIFACT_SPECS, SHELL_SHEBANG, SHELL_EXT, ROOT_POINTER_IMPORT_RE, DOT_CLAUDE_IMPORT_RE, CLAUDE_AGENTS_FALLBACK_VERSION, pExecFile2, claudeVersionMemo;
|
|
543
608
|
var init_detect = __esm({
|
|
544
609
|
"src/core/detect.ts"() {
|
|
545
610
|
init_util();
|
|
@@ -714,6 +779,10 @@ var init_detect = __esm({
|
|
|
714
779
|
];
|
|
715
780
|
SHELL_SHEBANG = /^#!\s*\/\S*\b(?:ba|da|z)?sh\b|^#!\s*\/usr\/bin\/env\s+(?:ba|da|z)?sh\b/;
|
|
716
781
|
SHELL_EXT = /\.(?:sh|bash|zsh)$/;
|
|
782
|
+
ROOT_POINTER_IMPORT_RE = /^\s*@(\.\/)?AGENTS\.md\s*$/m;
|
|
783
|
+
DOT_CLAUDE_IMPORT_RE = /^\s*@\.\.\/AGENTS\.md\s*$/m;
|
|
784
|
+
CLAUDE_AGENTS_FALLBACK_VERSION = "2.1.277";
|
|
785
|
+
pExecFile2 = promisify(execFile);
|
|
717
786
|
}
|
|
718
787
|
});
|
|
719
788
|
|
|
@@ -795,11 +864,12 @@ async function scanProject(root, opts = {}) {
|
|
|
795
864
|
const [branch, head, hooksPathRaw, authorsRaw, subjectsRaw] = isRepo ? await Promise.all([
|
|
796
865
|
git(abs, ["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
797
866
|
git(abs, ["rev-parse", "--short", "HEAD"]),
|
|
798
|
-
git
|
|
867
|
+
// `--type=path` expands a leading `~` the way git itself does when it runs the hooks.
|
|
868
|
+
git(abs, ["config", "--type=path", "--get", "core.hooksPath"]),
|
|
799
869
|
git(abs, ["log", "-200", "--format=%ae"]),
|
|
800
870
|
git(abs, ["log", "-50", "--format=%s"])
|
|
801
871
|
]) : [null, null, null, null, null];
|
|
802
|
-
const hooksPath = hooksPathRaw
|
|
872
|
+
const hooksPath = hooksPathRaw ? normalizeHooksPath(abs, hooksPathRaw) : void 0;
|
|
803
873
|
const hooks = await detectHooks(abs, hooksPath, rootPkg);
|
|
804
874
|
const shell = await detectShellSurface(abs, isRepo);
|
|
805
875
|
const freshness = await collectFreshness(abs, isRepo, artifacts, opts.upstreamRemote);
|
|
@@ -1132,6 +1202,19 @@ ${[
|
|
|
1132
1202
|
].filter(Boolean).join("\n") || "# add the project's key commands"}
|
|
1133
1203
|
\`\`\`
|
|
1134
1204
|
|
|
1205
|
+
`,
|
|
1206
|
+
"md"
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
function generateClaudePointerMd() {
|
|
1210
|
+
return stampGenerated(
|
|
1211
|
+
`# CLAUDE.md
|
|
1212
|
+
|
|
1213
|
+
Single source of truth for agent instructions lives in \`AGENTS.md\`. This pointer keeps Claude
|
|
1214
|
+
Code aligned with every other agent \u2014 edit \`AGENTS.md\`, not this file.
|
|
1215
|
+
|
|
1216
|
+
@AGENTS.md
|
|
1217
|
+
|
|
1135
1218
|
`,
|
|
1136
1219
|
"md"
|
|
1137
1220
|
);
|
|
@@ -1228,42 +1311,118 @@ ${localHookCall("commit-msg")}
|
|
|
1228
1311
|
exit 0
|
|
1229
1312
|
`);
|
|
1230
1313
|
}
|
|
1314
|
+
function generateShellDiscoveryScript() {
|
|
1315
|
+
return stampGenerated(`#!/usr/bin/env sh
|
|
1316
|
+
# etymd: shell script discovery for the pre-push shellcheck step. Arguments: the scratch
|
|
1317
|
+
# directory the hook created, then the tracked paths to classify (NUL-delimited on the hook's
|
|
1318
|
+
# side, positional here). Verdicts land in the scratch: scripts (NUL-delimited matches) and one
|
|
1319
|
+
# dot per decision into count / zsh-count / skip-count, tallied by the hook after the pipeline.
|
|
1320
|
+
#
|
|
1321
|
+
# A path with nothing readable behind it \u2014 a submodule entry, a dangling symlink, a file
|
|
1322
|
+
# deleted from the worktree while still tracked \u2014 cannot lie about its contents, so it is a
|
|
1323
|
+
# disclosed skip, never a block: an absent worktree file is routine dirty state. A regular file
|
|
1324
|
+
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
1325
|
+
# fails, naming the path.
|
|
1326
|
+
#
|
|
1327
|
+
# The two \`[ "$?" -eq 1 ]\` guards are the match/error protocol: grep reports "no match" as 1
|
|
1328
|
+
# and a failure as 2 or more, and only the first is a verdict. Dropping the guard would let a
|
|
1329
|
+
# failing matcher pass as "not a shell script" \u2014 the exact silent coverage-shrink the
|
|
1330
|
+
# fail-closed rules exist to prevent. The checker does not associate \`$?\` with the enclosing
|
|
1331
|
+
# if-condition, which is why this shape survives the pass it serves; keep it that way.
|
|
1332
|
+
work=$1
|
|
1333
|
+
shift
|
|
1334
|
+
for file do
|
|
1335
|
+
if [ ! -f "./$file" ]; then
|
|
1336
|
+
printf . >> "$work/skip-count" || exit 1
|
|
1337
|
+
continue
|
|
1338
|
+
fi
|
|
1339
|
+
# 4096 bytes bound the read \u2014 a binary with no newline would otherwise be copied whole
|
|
1340
|
+
# into the scratch on every push. The second head restores line-1-only semantics, so a
|
|
1341
|
+
# shebang embedded on a LATER line of a document cannot match the patterns below.
|
|
1342
|
+
head -c 4096 "./$file" > "$work/head-bytes" || {
|
|
1343
|
+
echo "etymd: cannot read tracked file for shellcheck: $file" >&2
|
|
1344
|
+
exit 1
|
|
1345
|
+
}
|
|
1346
|
+
head -n 1 "$work/head-bytes" > "$work/first-line" || exit 1
|
|
1347
|
+
if grep -qE "^#!.*[/ ](ba|da)?sh( |$)" "$work/first-line"; then
|
|
1348
|
+
printf "./%s\\0" "$file" >> "$work/scripts" || exit 1
|
|
1349
|
+
printf . >> "$work/count" || exit 1
|
|
1350
|
+
else
|
|
1351
|
+
[ "$?" -eq 1 ] || exit 1
|
|
1352
|
+
if grep -qE "^#!.*[/ ]zsh( |$)" "$work/first-line"; then
|
|
1353
|
+
printf . >> "$work/zsh-count" || exit 1
|
|
1354
|
+
else
|
|
1355
|
+
[ "$?" -eq 1 ] || exit 1
|
|
1356
|
+
fi
|
|
1357
|
+
fi
|
|
1358
|
+
done
|
|
1359
|
+
`);
|
|
1360
|
+
}
|
|
1231
1361
|
function shellcheckStep() {
|
|
1232
1362
|
return `
|
|
1233
1363
|
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
1234
|
-
# script added later is covered without regenerating this hook.
|
|
1364
|
+
# script added later is covered without regenerating this hook. The classifier is
|
|
1365
|
+
# discover-shell-scripts.sh beside this hook \u2014 tracked and shebanged like what it classifies,
|
|
1366
|
+
# so the scan it implements finds and checks it too. zsh is NOT in the checked set:
|
|
1235
1367
|
# the checker cannot parse it (SC1071 is a parser-level error no inline directive can silence),
|
|
1236
1368
|
# so checking it would fail every push on the parser, not on the script. Excluded \u2014 and said so
|
|
1237
1369
|
# at run time below, because a coverage hole that is silent is indistinguishable from coverage.
|
|
1370
|
+
# The same honesty splits the unreadable: a regular file that exists but cannot be read blocks
|
|
1371
|
+
# the push (coverage would otherwise silently shrink), while a tracked path with nothing readable
|
|
1372
|
+
# behind it \u2014 a submodule entry, a dangling symlink, a file deleted from the worktree while
|
|
1373
|
+
# still tracked \u2014 is counted and said so below as skipped, never a block.
|
|
1238
1374
|
#
|
|
1239
1375
|
# "the checker", not its name, on purpose: a comment whose first word is that name is read as
|
|
1240
1376
|
# a DIRECTIVE, and an unparseable directive is itself an error (SC1072/SC1073). A hook that
|
|
1241
1377
|
# explains why it skips a shell dialect must not break the checker while doing it.
|
|
1242
1378
|
if command -v shellcheck >/dev/null 2>&1; then
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1379
|
+
(
|
|
1380
|
+
# POSIX pipelines report only the LAST command's status, so discovery tallies progress in
|
|
1381
|
+
# files \u2014 one dot per decision, counted with wc -c after the pipeline \u2014 rather than
|
|
1382
|
+
# streaming through it: a pipeline that dies halfway cannot then pass as complete coverage,
|
|
1383
|
+
# and the same tallies carry the counts to the reporting below. The classifier writes the
|
|
1384
|
+
# tallies; this half only reads them.
|
|
1385
|
+
# NUL delimiters preserve filenames; positional arguments avoid xargs -I size limits and
|
|
1386
|
+
# interpreting filenames as shell code (never -I{}). The subshell confines cleanup to
|
|
1387
|
+
# this step. The classifier itself is the tracked, shebanged helper beside this hook, so
|
|
1388
|
+
# the discovery it performs finds and checks it too \u2014 the gate covers its own classifier.
|
|
1389
|
+
shellcheck_tmp=$(mktemp -d) || exit 1
|
|
1390
|
+
trap 'rm -rf "$shellcheck_tmp"' 0
|
|
1391
|
+
trap 'exit 1' 1 2 3 15
|
|
1392
|
+
git ls-files -z > "$shellcheck_tmp/tracked" || {
|
|
1393
|
+
echo "etymd: cannot enumerate tracked files for shellcheck" >&2
|
|
1256
1394
|
exit 1
|
|
1257
1395
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1396
|
+
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
1397
|
+
xargs -0 "$(dirname "$0")/discover-shell-scripts.sh" "$shellcheck_tmp" < "$shellcheck_tmp/tracked" || {
|
|
1398
|
+
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
1399
|
+
exit 1
|
|
1400
|
+
}
|
|
1401
|
+
count=$(wc -c < "$shellcheck_tmp/count") || exit 1
|
|
1402
|
+
zsh_count=$(wc -c < "$shellcheck_tmp/zsh-count") || exit 1
|
|
1403
|
+
skip_count=$(wc -c < "$shellcheck_tmp/skip-count") || exit 1
|
|
1404
|
+
if [ "$skip_count" -gt 0 ]; then
|
|
1405
|
+
echo "\u203A shellcheck: $((skip_count)) tracked path(s) with nothing readable behind them (submodule, dangling symlink, or deleted from the worktree) \u2014 not checked, not failed"
|
|
1265
1406
|
fi
|
|
1266
|
-
|
|
1407
|
+
if [ "$zsh_count" -gt 0 ]; then
|
|
1408
|
+
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
1409
|
+
fi
|
|
1410
|
+
if [ "$count" -gt 0 ]; then
|
|
1411
|
+
echo "\u203A shellcheck ($((count)) scripts, blocking at severity=warning)"
|
|
1412
|
+
xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" || {
|
|
1413
|
+
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
1414
|
+
exit 1
|
|
1415
|
+
}
|
|
1416
|
+
# Everything below the blocking bar, shown once the push is already cleared. Never affects
|
|
1417
|
+
# the exit code \u2014 advice that can fail a push is not advice.
|
|
1418
|
+
advice=$(xargs -0 shellcheck -S style -f gcc -- < "$shellcheck_tmp/scripts" 2>/dev/null \\
|
|
1419
|
+
| grep -v ': warning:\\|: error:' || true)
|
|
1420
|
+
if [ -n "$advice" ]; then
|
|
1421
|
+
echo " \xB7 style/info (not blocking):"
|
|
1422
|
+
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
1423
|
+
fi
|
|
1424
|
+
fi
|
|
1425
|
+
) || exit 1
|
|
1267
1426
|
else
|
|
1268
1427
|
echo "\u203A shellcheck skipped (not on PATH) \u2014 install it to gate this repo's shell scripts"
|
|
1269
1428
|
fi`;
|
|
@@ -1426,6 +1585,7 @@ async function planWorkflow(root, facts, opts) {
|
|
|
1426
1585
|
};
|
|
1427
1586
|
if (opts.agents) {
|
|
1428
1587
|
await add("AGENTS.md", generateAgentsMd(facts), "Minimal operating contract (scaffold)");
|
|
1588
|
+
await add("CLAUDE.md", generateClaudePointerMd(), "Claude Code pointer to AGENTS.md");
|
|
1429
1589
|
}
|
|
1430
1590
|
if (opts.gates) {
|
|
1431
1591
|
const existingPrePush = await readText(path2.join(root, ".githooks", "pre-push"));
|
|
@@ -1459,6 +1619,14 @@ async function planWorkflow(root, facts, opts) {
|
|
|
1459
1619
|
"Correctness gate (pre-push)",
|
|
1460
1620
|
true
|
|
1461
1621
|
);
|
|
1622
|
+
if (facts.shell?.scripts) {
|
|
1623
|
+
await add(
|
|
1624
|
+
".githooks/discover-shell-scripts.sh",
|
|
1625
|
+
generateShellDiscoveryScript(),
|
|
1626
|
+
"Shell discovery (pre-push shellcheck helper)",
|
|
1627
|
+
true
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1462
1630
|
if (opts.gateConfig?.publishGate ?? opts.publishGate ?? facts.publishable) {
|
|
1463
1631
|
await add(
|
|
1464
1632
|
"scripts/artifact-check.sh",
|
|
@@ -1893,7 +2061,7 @@ async function localHookTools(root, facts, scripts) {
|
|
|
1893
2061
|
const inertCompanions = [];
|
|
1894
2062
|
const readHook = async (name) => {
|
|
1895
2063
|
if (!hooks.dir) return empty;
|
|
1896
|
-
const text = await readText(path2.
|
|
2064
|
+
const text = await readText(path2.resolve(root, hooks.dir, name));
|
|
1897
2065
|
if (!text) return empty;
|
|
1898
2066
|
const tools = new Set(matchTools(text, scripts));
|
|
1899
2067
|
const companion = await companionOf(root, hooks.dir, name, text, scripts);
|
|
@@ -2042,7 +2210,7 @@ async function buildGateInventory(root, facts) {
|
|
|
2042
2210
|
|
|
2043
2211
|
// src/lenses/gate-integrity/screener.ts
|
|
2044
2212
|
init_util();
|
|
2045
|
-
var
|
|
2213
|
+
var pExecFile3 = promisify(execFile);
|
|
2046
2214
|
var HOOK_FILES = ["pre-commit", "pre-push", "commit-msg"];
|
|
2047
2215
|
var SCREEN_CALL_RE = /"\$GATE"\s+screen\b/;
|
|
2048
2216
|
var DEV_BUILD_ARM = "[ -x ./dist/cli.js ]";
|
|
@@ -2059,7 +2227,7 @@ async function probeScreener(root, facts) {
|
|
|
2059
2227
|
const doors = [];
|
|
2060
2228
|
let devBuildArm = false;
|
|
2061
2229
|
for (const name of HOOK_FILES) {
|
|
2062
|
-
const text = await readText(path2.
|
|
2230
|
+
const text = await readText(path2.resolve(root, dir, name));
|
|
2063
2231
|
if (!text || !SCREEN_CALL_RE.test(text)) continue;
|
|
2064
2232
|
doors.push(`${dir}/${name}`);
|
|
2065
2233
|
if (text.includes(DEV_BUILD_ARM)) devBuildArm = true;
|
|
@@ -2079,7 +2247,7 @@ async function probeScreener(root, facts) {
|
|
|
2079
2247
|
source = "path";
|
|
2080
2248
|
}
|
|
2081
2249
|
try {
|
|
2082
|
-
await
|
|
2250
|
+
await pExecFile3(runner, ["screen", "--help"], { cwd: root, timeout: 1e4 });
|
|
2083
2251
|
return { present: true, doors, runner, source, answersScreen: true };
|
|
2084
2252
|
} catch (err) {
|
|
2085
2253
|
const e = err;
|
|
@@ -2757,10 +2925,13 @@ function extractCommandClaims(text) {
|
|
|
2757
2925
|
return { scripts, filteredSkipped };
|
|
2758
2926
|
}
|
|
2759
2927
|
var PATH_TOKEN_RE = /^[A-Za-z0-9_.-]+(\/[A-Za-z0-9_.$-]+)+\/?$/;
|
|
2928
|
+
var HOSTNAME_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.([a-z]{2,})$/i;
|
|
2760
2929
|
var KNOWN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2761
2930
|
..."ts tsx cts mts js jsx cjs mjs json jsonc json5 md mdx mdc yml yaml toml ini cfg conf env sh bash zsh fish ps1 bat cmd css scss sass less html htm xml svg sql prisma graphql gql proto py rb rs go java kt kts swift c h cc cpp hpp cs php vue svelte astro txt log lock csv tsv png jpg jpeg gif webp ico avif woff woff2 ttf otf wasm map pem key crt tf tfvars example sample local snap ejs hbs pug".split(" ")
|
|
2762
2931
|
]);
|
|
2763
2932
|
var CREATION_CONTEXT_RE = /\b(?:creat(?:e|es|ed|ing)|generat(?:e|es|ed|ing)|scaffold(?:s|ed|ing)?|quarantin(?:e|es|ed|ing)|(?:writ(?:e|es|ten|ing)|output(?:s|ted)?|emit(?:s|ted|ting)?|sav(?:e|es|ed|ing)|mov(?:e|es|ed|ing)|copy|copi(?:es|ed))\s+(?:it\s+|them\s+)?(?:to|into)|new\s+(?:file|directory|folder)|will\s+(?:be\s+)?(?:created|generated|written)|add(?:s|ed|ing)?\s+(?:a|the)\s+new)\b/i;
|
|
2933
|
+
var FETCH_CONTEXT_RE = /\b(?:fetch(?:es|ed|ing)?|pull(?:s|ed|ing)?|clone(?:s|d)?|download(?:s|ed|ing)?)\b/i;
|
|
2934
|
+
var EXTERNAL_URL_RE = /https?:\/\/|\bwww\./i;
|
|
2764
2935
|
var PLACEHOLDER_SEGMENTS = /* @__PURE__ */ new Set(["placeholder", "foo", "bar", "baz", "qux"]);
|
|
2765
2936
|
var PLACEHOLDER_PREFIX_RE = /^(?:my|your)-/i;
|
|
2766
2937
|
function isPlaceholderClaim(token) {
|
|
@@ -2794,14 +2965,21 @@ ${line}`;
|
|
|
2794
2965
|
}
|
|
2795
2966
|
function extractPathClaims(text, opts = {}) {
|
|
2796
2967
|
const prospectiveOnly = /* @__PURE__ */ new Map();
|
|
2968
|
+
const fetchedOnly = /* @__PURE__ */ new Map();
|
|
2797
2969
|
const namespacedOnly = /* @__PURE__ */ new Map();
|
|
2798
2970
|
const placeholder = /* @__PURE__ */ new Set();
|
|
2971
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
2799
2972
|
for (const m of text.matchAll(/`([^`\n]+)`/g)) {
|
|
2800
2973
|
const token = m[1].trim();
|
|
2801
2974
|
if (token.includes(" ") || token.length > 120) continue;
|
|
2802
2975
|
if (token.startsWith("/") || token.startsWith("~") || token.startsWith("@") || token.startsWith("$"))
|
|
2803
2976
|
continue;
|
|
2804
2977
|
if (token.includes("://") || token.startsWith("www.")) continue;
|
|
2978
|
+
if (token.includes("/")) {
|
|
2979
|
+
const firstSegment = token.split("/")[0] ?? token;
|
|
2980
|
+
const hostSuffix = HOSTNAME_RE.exec(firstSegment)?.[1]?.toLowerCase();
|
|
2981
|
+
if (hostSuffix && !KNOWN_EXTENSIONS.has(hostSuffix)) continue;
|
|
2982
|
+
}
|
|
2805
2983
|
if (/[*?{}<>|]/.test(token)) continue;
|
|
2806
2984
|
if (token.includes("@")) continue;
|
|
2807
2985
|
if (!PATH_TOKEN_RE.test(token)) continue;
|
|
@@ -2814,8 +2992,12 @@ function extractPathClaims(text, opts = {}) {
|
|
|
2814
2992
|
placeholder.add(claim);
|
|
2815
2993
|
continue;
|
|
2816
2994
|
}
|
|
2817
|
-
|
|
2995
|
+
if (isDirClaim) dirs.add(claim);
|
|
2996
|
+
const context = claimContext(text, m.index ?? 0);
|
|
2997
|
+
const prospective2 = CREATION_CONTEXT_RE.test(context);
|
|
2818
2998
|
prospectiveOnly.set(claim, (prospectiveOnly.get(claim) ?? true) && prospective2);
|
|
2999
|
+
const fetched2 = FETCH_CONTEXT_RE.test(context) && EXTERNAL_URL_RE.test(context);
|
|
3000
|
+
fetchedOnly.set(claim, (fetchedOnly.get(claim) ?? true) && fetched2);
|
|
2819
3001
|
if (opts.namespaces) {
|
|
2820
3002
|
const nsLead = new RegExp(`(${NAMESPACE_IDENT}):[ \\t]*$`).exec(text.slice(0, m.index ?? 0));
|
|
2821
3003
|
const prefixed = isNamespace(nsLead?.[1]);
|
|
@@ -2824,13 +3006,15 @@ function extractPathClaims(text, opts = {}) {
|
|
|
2824
3006
|
}
|
|
2825
3007
|
const paths = [];
|
|
2826
3008
|
const prospective = [];
|
|
3009
|
+
const fetched = [];
|
|
2827
3010
|
const namespaced = [];
|
|
2828
3011
|
for (const [claim, only] of prospectiveOnly) {
|
|
2829
|
-
if (
|
|
3012
|
+
if (fetchedOnly.get(claim)) fetched.push(claim);
|
|
3013
|
+
else if (only) prospective.push(claim);
|
|
2830
3014
|
else if (namespacedOnly.get(claim)) namespaced.push(claim);
|
|
2831
3015
|
else paths.push(claim);
|
|
2832
3016
|
}
|
|
2833
|
-
return { paths, prospective, namespaced, placeholder: [...placeholder] };
|
|
3017
|
+
return { paths, dirs: [...dirs], prospective, fetched, namespaced, placeholder: [...placeholder] };
|
|
2834
3018
|
}
|
|
2835
3019
|
var LOCAL_REF_LEADINS = new Set(
|
|
2836
3020
|
"decision decisions entry entries ruling rulings record records ledger id ids item items see per in of on at by to as is was are were the a an and or but not with under over from via vs than after before since between through against latest newest earliest only also still now supersedes superseded superseding amends amended extends extended cites cited citing adds added adding wrote written writes locked locks closed closes opened opens resolves resolved reopened recorded number numbers".split(" ")
|
|
@@ -2902,12 +3086,14 @@ async function buildTruthEnv(root, facts) {
|
|
|
2902
3086
|
for (const key of Object.keys(pkgJson?.scripts ?? {})) knownScripts.add(key);
|
|
2903
3087
|
}
|
|
2904
3088
|
const bases = [root, ...facts.packages.map((p) => path2.join(root, p.dir))];
|
|
2905
|
-
const pathResolves = async (claim) => {
|
|
3089
|
+
const pathResolves = async (claim, fromDir) => {
|
|
2906
3090
|
for (const base of bases) {
|
|
2907
3091
|
if (await pathExists(path2.join(base, claim))) return true;
|
|
2908
3092
|
if (await pathExists(path2.join(base, "src", claim))) return true;
|
|
2909
3093
|
if (await pathExists(path2.join(base, "scripts", claim))) return true;
|
|
2910
3094
|
}
|
|
3095
|
+
if (fromDir && fromDir !== "." && await pathExists(path2.join(root, fromDir, claim)))
|
|
3096
|
+
return true;
|
|
2911
3097
|
return false;
|
|
2912
3098
|
};
|
|
2913
3099
|
const binResolves = async (name) => {
|
|
@@ -2937,6 +3123,7 @@ function emptyCounters() {
|
|
|
2937
3123
|
unverifiableCommands: 0,
|
|
2938
3124
|
gitignoredSkipped: 0,
|
|
2939
3125
|
prospectiveSkipped: 0,
|
|
3126
|
+
fetchedSkipped: 0,
|
|
2940
3127
|
placeholderSkipped: 0,
|
|
2941
3128
|
qualifiedRefsSkipped: 0,
|
|
2942
3129
|
unresolvableRefs: 0,
|
|
@@ -2979,19 +3166,48 @@ async function checkTextClaims(env, file, opts, counters) {
|
|
|
2979
3166
|
confidence: "high"
|
|
2980
3167
|
});
|
|
2981
3168
|
}
|
|
2982
|
-
const { paths, prospective, placeholder, namespaced } = extractPathClaims(
|
|
2983
|
-
|
|
2984
|
-
|
|
3169
|
+
const { paths, dirs, prospective, fetched, placeholder, namespaced } = extractPathClaims(
|
|
3170
|
+
file.text,
|
|
3171
|
+
{ namespaces: opts.treatNamespacedPrefixes }
|
|
3172
|
+
);
|
|
2985
3173
|
counters.prospectiveSkipped += prospective.length;
|
|
3174
|
+
counters.fetchedSkipped += fetched.length;
|
|
2986
3175
|
counters.placeholderSkipped += placeholder.length;
|
|
2987
3176
|
counters.namespacedSkipped += namespaced.length;
|
|
2988
|
-
const
|
|
3177
|
+
const fromDir = path2.dirname(file.path);
|
|
3178
|
+
const unresolved = [];
|
|
2989
3179
|
for (const claim of paths) {
|
|
2990
|
-
if (await env.pathResolves(claim
|
|
3180
|
+
if (await env.pathResolves(claim, fromDir))
|
|
3181
|
+
examined.push({ kind: "path", value: claim, exists: true });
|
|
3182
|
+
else unresolved.push(claim);
|
|
3183
|
+
}
|
|
3184
|
+
const proseBases = /* @__PURE__ */ new Set();
|
|
3185
|
+
for (const dirClaim of dirs) {
|
|
3186
|
+
if (await env.pathResolves(dirClaim, fromDir)) {
|
|
3187
|
+
proseBases.add(dirClaim);
|
|
3188
|
+
proseBases.add(path2.dirname(dirClaim));
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
const missing = [];
|
|
3192
|
+
for (const claim of unresolved) {
|
|
3193
|
+
let anchored = false;
|
|
3194
|
+
for (const base of proseBases) {
|
|
3195
|
+
if (await pathExists(path2.join(env.root, base, claim))) anchored = true;
|
|
3196
|
+
else if (fromDir !== "." && await pathExists(path2.join(env.root, fromDir, base, claim)))
|
|
3197
|
+
anchored = true;
|
|
3198
|
+
if (anchored) break;
|
|
3199
|
+
}
|
|
3200
|
+
if (anchored) examined.push({ kind: "path", value: claim, exists: true });
|
|
2991
3201
|
else missing.push(claim);
|
|
2992
3202
|
}
|
|
2993
3203
|
const ignoredOut = missing.length ? await git(env.root, ["check-ignore", ...missing]) : null;
|
|
2994
3204
|
const gitignored = new Set((ignoredOut ?? "").split("\n").filter(Boolean));
|
|
3205
|
+
const unproven = missing.filter((claim) => !gitignored.has(claim));
|
|
3206
|
+
if (unproven.length) {
|
|
3207
|
+
const dirOut = await git(env.root, ["check-ignore", ...unproven.map((claim) => `${claim}/`)]);
|
|
3208
|
+
for (const line of (dirOut ?? "").split("\n").filter(Boolean))
|
|
3209
|
+
gitignored.add(line.replace(/\/$/, ""));
|
|
3210
|
+
}
|
|
2995
3211
|
let pathFindings = 0;
|
|
2996
3212
|
for (const claim of missing) {
|
|
2997
3213
|
if (opts.rootedFirstSegments && !opts.rootedFirstSegments.has(claim.split("/")[0] ?? claim)) {
|
|
@@ -3279,6 +3495,11 @@ var instructionTruthLens = {
|
|
|
3279
3495
|
`${counters.prospectiveSkipped} path claim(s) sit in create-this prose (the file instructs generating them) \u2014 forward-looking, not stale; skipped, not flagged.`
|
|
3280
3496
|
);
|
|
3281
3497
|
}
|
|
3498
|
+
if (counters.fetchedSkipped) {
|
|
3499
|
+
disclosures.push(
|
|
3500
|
+
`${counters.fetchedSkipped} path claim(s) sit beside the URL they are fetched from \u2014 another tree's files, unverifiable here; skipped, not flagged.`
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3282
3503
|
if (counters.placeholderSkipped) {
|
|
3283
3504
|
disclosures.push(
|
|
3284
3505
|
`${counters.placeholderSkipped} path claim(s) are naming stand-ins (e.g. \`my-custom-skill\`) rather than real references; skipped, not flagged.`
|
|
@@ -3316,7 +3537,7 @@ var instructionTruthLens = {
|
|
|
3316
3537
|
);
|
|
3317
3538
|
}
|
|
3318
3539
|
disclosures.push(
|
|
3319
|
-
`Checked ${files.length} instruction file(s); commands resolved against root + ${facts.packages.length} workspace manifest(s) plus installed binaries; paths matched against root and
|
|
3540
|
+
`Checked ${files.length} instruction file(s); commands resolved against root + ${facts.packages.length} workspace manifest(s) plus installed binaries; paths matched against root, package roots, the claiming file's directory, and directories the same file names; existence judged on the working tree (gitignored-but-present is true, gitignored-and-absent unverifiable). Heuristics: workspace-filtered commands skipped (${counters.filteredSkipped}); tokens without a recognized extension treated as prose (a dir claim needs a trailing slash); schemeless hosts read as URLs; gitignored claims unverifiable; create-this, fetched-from-URL, and stand-in path claims skipped; absolute/globbed/placeholder tokens skipped; doc mentions inside \`~/\` home paths skipped; framework-pattern staleness not checked.`
|
|
3320
3541
|
);
|
|
3321
3542
|
return {
|
|
3322
3543
|
lens: LENS_ID4,
|
|
@@ -3747,6 +3968,7 @@ async function loadFleetManifest(manifestPath) {
|
|
|
3747
3968
|
|
|
3748
3969
|
// src/engine/fleet.ts
|
|
3749
3970
|
init_config();
|
|
3971
|
+
init_detect();
|
|
3750
3972
|
init_facts();
|
|
3751
3973
|
init_util();
|
|
3752
3974
|
|
|
@@ -3875,7 +4097,7 @@ function parseMilestones(text) {
|
|
|
3875
4097
|
}
|
|
3876
4098
|
|
|
3877
4099
|
// src/engine/fleet.ts
|
|
3878
|
-
var
|
|
4100
|
+
var pExecFile4 = promisify(execFile);
|
|
3879
4101
|
var FLEET_LENS = "fleet-manifest";
|
|
3880
4102
|
var FLEET_JSON_SCHEMA = "fleet-experimental-0.2";
|
|
3881
4103
|
var GUARDED_WALL_ARTIFACTS = ["PROJECT_CONTEXT.md", "DECISIONS.md"];
|
|
@@ -3930,7 +4152,7 @@ function stateBudgetsFor(entry) {
|
|
|
3930
4152
|
async function gitGrepFiles(root, needle, mode = "fixed") {
|
|
3931
4153
|
try {
|
|
3932
4154
|
const matchFlag = mode === "regex" ? "-E" : "-F";
|
|
3933
|
-
const { stdout } = await
|
|
4155
|
+
const { stdout } = await pExecFile4("git", ["grep", "-I", "-l", matchFlag, "-e", needle], {
|
|
3934
4156
|
cwd: root,
|
|
3935
4157
|
timeout: 8e3
|
|
3936
4158
|
});
|
|
@@ -4353,6 +4575,31 @@ async function checkGateDrift(manifest, findings, disclosures) {
|
|
|
4353
4575
|
}
|
|
4354
4576
|
}
|
|
4355
4577
|
}
|
|
4578
|
+
async function checkClaudePointers(manifest, findings) {
|
|
4579
|
+
for (const entry of manifest.entries) {
|
|
4580
|
+
const root = entry.resolvedRoot;
|
|
4581
|
+
if (!root || !await isDirectory(root)) continue;
|
|
4582
|
+
const check = await checkClaudePointer(root);
|
|
4583
|
+
if (check.ok) continue;
|
|
4584
|
+
findings.push(
|
|
4585
|
+
check.kind === "no-import" ? finding3(
|
|
4586
|
+
`${FLEET_LENS}/claude-pointer-missing:${entry.name}`,
|
|
4587
|
+
"risk",
|
|
4588
|
+
`\`${entry.name}\` has a CLAUDE.md that never imports its AGENTS.md`,
|
|
4589
|
+
[`${entry.name}: ${check.detail}`],
|
|
4590
|
+
"Claude Code reads CLAUDE.md when one exists and falls back to AGENTS.md only when none does. A CLAUDE.md without the import shadows AGENTS.md, so the contract looks universal from inside the repo while Claude Code never receives it.",
|
|
4591
|
+
"Add a full-line `@AGENTS.md` import to that CLAUDE.md, symlink either file to the other, or delete the CLAUDE.md if AGENTS.md is the whole contract."
|
|
4592
|
+
) : finding3(
|
|
4593
|
+
`${FLEET_LENS}/claude-pointer-missing:${entry.name}`,
|
|
4594
|
+
"gap",
|
|
4595
|
+
`\`${entry.name}\` relies on the AGENTS.md fallback, which the installed Claude Code predates`,
|
|
4596
|
+
[`${entry.name}: ${check.detail}`],
|
|
4597
|
+
`Claude Code reads AGENTS.md on its own only from ${CLAUDE_AGENTS_FALLBACK_VERSION}; older releases load CLAUDE.md alone, so this repo's contract is invisible to them.`,
|
|
4598
|
+
"Update Claude Code, or create a CLAUDE.md beside AGENTS.md whose only import line is `@AGENTS.md`."
|
|
4599
|
+
)
|
|
4600
|
+
);
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4356
4603
|
async function collectWallFindings(manifest) {
|
|
4357
4604
|
const findings = [];
|
|
4358
4605
|
const disclosures = [];
|
|
@@ -4362,6 +4609,7 @@ async function collectWallFindings(manifest) {
|
|
|
4362
4609
|
await checkHygieneNeedles(manifest, findings, disclosures);
|
|
4363
4610
|
await checkGuardedEmails(manifest, findings, disclosures);
|
|
4364
4611
|
await checkGateDrift(manifest, findings, disclosures);
|
|
4612
|
+
await checkClaudePointers(manifest, findings);
|
|
4365
4613
|
return { findings, disclosures };
|
|
4366
4614
|
}
|
|
4367
4615
|
function stateAgeDays(facts) {
|
|
@@ -4708,7 +4956,6 @@ var STOP_WORDS = new Set(
|
|
|
4708
4956
|
);
|
|
4709
4957
|
var WRAP_RE = /^([([{"']*)(.*?)([.,;:!?)\]}"']*)$/;
|
|
4710
4958
|
var TRAIL_RE = /[.,;:!?)\]}"']*$/;
|
|
4711
|
-
var HOSTNAME_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.([a-z]{2,})$/i;
|
|
4712
4959
|
function promoteBareTokens(text, ctx) {
|
|
4713
4960
|
const skips = {
|
|
4714
4961
|
bareInvocations: 0,
|
|
@@ -4896,6 +5143,11 @@ async function runPremise(opts) {
|
|
|
4896
5143
|
`${counters.prospectiveSkipped} path(s) sit in create-this prose (the task says to create them) \u2014 forward-looking, not missing; skipped.`
|
|
4897
5144
|
);
|
|
4898
5145
|
}
|
|
5146
|
+
if (counters.fetchedSkipped) {
|
|
5147
|
+
disclosures.push(
|
|
5148
|
+
`${counters.fetchedSkipped} path(s) are named beside the URL they are fetched from \u2014 another tree, not this repo; skipped, not flagged.`
|
|
5149
|
+
);
|
|
5150
|
+
}
|
|
4899
5151
|
if (counters.placeholderSkipped) {
|
|
4900
5152
|
disclosures.push(
|
|
4901
5153
|
`${counters.placeholderSkipped} path(s) are naming stand-ins (e.g. \`my-feature\`) rather than real references; skipped.`
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-GWGKEPRX.js';
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
3
|
+
import { planWorkflow } from './chunk-EZRK4MV3.js';
|
|
4
|
+
import './chunk-HSRHOVII.js';
|
|
4
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-HI7NWPRA.js';
|
|
5
|
-
import { scanProject } from './chunk-
|
|
6
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject } from './chunk-DLHC7QON.js';
|
|
7
|
+
import { VERSION } from './chunk-L4KIIKXI.js';
|
|
7
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-P6ATKV2R.js';
|
|
8
|
-
import { PACK_VERSION } from './chunk-
|
|
9
|
+
import { PACK_VERSION } from './chunk-U7BCBJIH.js';
|
|
9
10
|
import { git, readText } from './chunk-4VPBP6K6.js';
|
|
10
11
|
import { promises } from 'fs';
|
|
11
12
|
import path from 'path';
|