etymd 0.3.2 → 0.4.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 +49 -0
- package/README.md +7 -7
- package/dist/{approve-P255DGOI.js → approve-DTYNGGXH.js} +3 -3
- package/dist/{audit-IGIUGKSQ.js → audit-UJU3336P.js} +5 -5
- package/dist/{brief-SFZ2DR65.js → brief-5UZLZ3CW.js} +3 -3
- package/dist/{chunk-CRJHIXJH.js → chunk-IGHES6AK.js} +1 -1
- package/dist/{chunk-B3SUTZVY.js → chunk-KIMFKLJB.js} +1 -1
- package/dist/{chunk-WUGBAW6Z.js → chunk-OWNWN2KF.js} +2 -2
- package/dist/{chunk-VFUYQLW7.js → chunk-OX3LUOZR.js} +36 -3
- package/dist/{chunk-7MOBQ4N5.js → chunk-RH2UAS25.js} +31 -2
- package/dist/{chunk-WOBHQQ6H.js → chunk-XER37MLS.js} +1 -1
- package/dist/cli.js +13 -13
- package/dist/{doctor-4LBUZHVD.js → doctor-W6DW3OK6.js} +5 -5
- package/dist/{fleet-Q6J5XIHH.js → fleet-77SSSFZO.js} +6 -6
- package/dist/{gates-T5J3H7WL.js → gates-LTM5PE7R.js} +7 -5
- package/dist/{generate-I75IEXXF.js → generate-CBMTNN7P.js} +2 -2
- package/dist/index.d.ts +18 -1
- package/dist/index.js +67 -5
- package/dist/{init-WYVDRQ2J.js → init-7GNB74BE.js} +4 -4
- package/dist/{scan-PZFF6LDA.js → scan-6YOKI3JW.js} +3 -3
- package/dist/scan-XZH57E4I.js +5 -0
- package/package.json +1 -1
- package/dist/scan-IY3LCBJ6.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 915ae78: Fix: the shell gate now actually prints the sub-warning findings it documented.
|
|
8
|
+
|
|
9
|
+
0.4.0 said "style and info print as advice" and did not do it — the generated hook ran only
|
|
10
|
+
`shellcheck -S warning` and discarded everything below that bar. The generated file made no such
|
|
11
|
+
claim, so no repo was misled about what its own gate does, but the promise was in the release
|
|
12
|
+
notes and not in the code.
|
|
13
|
+
|
|
14
|
+
The hook now runs a second, non-blocking pass after the blocking one and prints what it finds.
|
|
15
|
+
Its result is captured into a variable and explicitly tolerated with `|| true`, so it has no path
|
|
16
|
+
to the exit code: advice that can fail a push is not advice. The blocking bar is unchanged.
|
|
17
|
+
|
|
18
|
+
The test that was supposed to protect this pinned the wrong property — it asserted that style was
|
|
19
|
+
never MENTIONED, which the buggy version satisfied. It now asserts that the warning pass is the
|
|
20
|
+
only one wired to a failure branch, which is the property that actually matters.
|
|
21
|
+
|
|
22
|
+
Pack version 5.
|
|
23
|
+
|
|
24
|
+
## 0.4.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- 69d1aad: Gate the shell surface: `etymd gates` now installs a shellcheck step in repos that have one.
|
|
29
|
+
|
|
30
|
+
Package scripts are not the only executable surface a repo has, and in some repos they are not
|
|
31
|
+
the main one. A tools or infra repo can be entirely `bootstrap/*.sh` plus `.githooks/*` with no
|
|
32
|
+
`package.json` at all — and those repos were told "no correctness commands detected", which reads
|
|
33
|
+
as "nothing to check" when it means "nothing was looked at".
|
|
34
|
+
|
|
35
|
+
The scan now counts tracked files a shell executes (`facts.shell.scripts`), by extension and by
|
|
36
|
+
shebang, and the generated pre-push gains a shellcheck step when that count is non-zero. Three
|
|
37
|
+
properties, each one a lesson from a gate that failed:
|
|
38
|
+
|
|
39
|
+
- Scripts are re-discovered inside the hook at push time, by shebang over tracked files, never
|
|
40
|
+
baked in as a list. A generated list is correct the day it is written and silently wrong the
|
|
41
|
+
first time someone adds a script.
|
|
42
|
+
- A missing shellcheck binary is a loud skip that names the install command, never a quiet pass.
|
|
43
|
+
A check that goes silent when its tool is absent looks installed on every machine and is
|
|
44
|
+
installed on one.
|
|
45
|
+
- The blocking bar is severity `warning`; style and info print as advice. A gate with a high
|
|
46
|
+
false-positive rate does not make a repo careful, it teaches everyone the bypass flag — and
|
|
47
|
+
that flag is shared with the content screen, which must never be bypassed.
|
|
48
|
+
|
|
49
|
+
The install summary lists the step too, so accepting the plan shows what will actually run.
|
|
50
|
+
Pack version 4.
|
|
51
|
+
|
|
3
52
|
## 0.3.2
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Etymd
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
|
-
<img src="media/etymd-logo.png" width="520" alt="Etymd — a papyrus of written instructions, each line checked against the repository it describes">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/triartleet/etymd/main/media/etymd-logo.png" width="520" alt="Etymd — a papyrus of written instructions, each line checked against the repository it describes">
|
|
5
5
|
<p>
|
|
6
6
|
<a href="https://www.npmjs.com/package/etymd"><img src="https://img.shields.io/npm/v/etymd.svg?label=npm&color=cb3837" alt="npm version"></a>
|
|
7
7
|
<a href="https://github.com/triartleet/etymd/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/triartleet/etymd/ci.yml?branch=main&label=CI" alt="CI"></a>
|
|
8
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license"></a>
|
|
8
|
+
<a href="https://github.com/triartleet/etymd/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license"></a>
|
|
9
9
|
</p>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
@@ -335,7 +335,7 @@ agent-context file: claims about your fleet (what exists, where, under which **p
|
|
|
335
335
|
side of the wall an entry belongs to, `personal` or `corp`; the **wall** is the placement
|
|
336
336
|
boundary between personal and employer content that the sweep polices). It rots like any
|
|
337
337
|
AGENTS.md does, and `etymd fleet` keeps it true. Decision record:
|
|
338
|
-
[`docs/decisions/004-fleet-truth-guard.md`](docs/decisions/004-fleet-truth-guard.md). Both the
|
|
338
|
+
[`docs/decisions/004-fleet-truth-guard.md`](https://github.com/triartleet/etymd/blob/main/docs/decisions/004-fleet-truth-guard.md). Both the
|
|
339
339
|
registry schema and the fleet `--json` schema are **experimental through 0.2.x**.
|
|
340
340
|
|
|
341
341
|
Two files beside each other — the split is the privacy model:
|
|
@@ -346,7 +346,7 @@ Two files beside each other — the split is the privacy model:
|
|
|
346
346
|
{
|
|
347
347
|
"registryVersion": 1,
|
|
348
348
|
"root": "~/projects", // ~ expands on the consumer side — never a machine home
|
|
349
|
-
"orientation": { "root": "
|
|
349
|
+
"orientation": { "root": "notes" }, // optional: the entry every other entry is guided by
|
|
350
350
|
"projects": [
|
|
351
351
|
{
|
|
352
352
|
"name": "web-app",
|
|
@@ -451,7 +451,7 @@ console.log(audit.findings) // one schema: claim · evidence · why · action ·
|
|
|
451
451
|
## The corpus (how this is validated)
|
|
452
452
|
|
|
453
453
|
Etymd is developed against a corpus of real sibling repos rather than fixtures alone —
|
|
454
|
-
[`sources.json`](sources.json) lists them by shape. Every heuristic here exists because a real
|
|
454
|
+
[`sources.json`](https://github.com/triartleet/etymd/blob/main/sources.json) lists them by shape. Every heuristic here exists because a real
|
|
455
455
|
repo proved the previous one wrong, and each skip class in the truth lens is a false positive that
|
|
456
456
|
a corpus run caught.
|
|
457
457
|
|
|
@@ -469,10 +469,10 @@ Without it — on a fresh clone or in CI — those suites skip cleanly and the r
|
|
|
469
469
|
|
|
470
470
|
## Decision record & roadmap
|
|
471
471
|
|
|
472
|
-
[`docs/decisions/`](docs/decisions
|
|
472
|
+
[`docs/decisions/`](https://github.com/triartleet/etymd/tree/main/docs/decisions) — 001 founding · 002 foundation re-lock · **003 the truth-guard
|
|
473
473
|
pivot** (the current identity; includes the state-of-the-field investigation it rests on) ·
|
|
474
474
|
**004 fleet mode** (the truth guard across your repositories).
|
|
475
|
-
[`ROADMAP.md`](ROADMAP.md) — what's now / next / later, the pre-publish checklist, and the
|
|
475
|
+
[`ROADMAP.md`](https://github.com/triartleet/etymd/blob/main/ROADMAP.md) — what's now / next / later, the pre-publish checklist, and the
|
|
476
476
|
accepted heuristic trade-offs.
|
|
477
477
|
|
|
478
478
|
## License
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-TADKOW6P.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
3
|
+
import { scanProject } from './chunk-RH2UAS25.js';
|
|
4
|
+
import { VERSION } from './chunk-KIMFKLJB.js';
|
|
5
5
|
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-R74SJ7HS.js';
|
|
6
|
-
import { PACK_VERSION } from './chunk-
|
|
6
|
+
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
7
7
|
import './chunk-YLNQQZ5F.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/approve.ts
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { run } from './chunk-XER37MLS.js';
|
|
3
|
+
import './chunk-OWNWN2KF.js';
|
|
4
4
|
import './chunk-LT67FU2Y.js';
|
|
5
5
|
import './chunk-WWBF4Y7K.js';
|
|
6
6
|
import './chunk-NMZ3RHWW.js';
|
|
7
7
|
import './chunk-TADKOW6P.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-RH2UAS25.js';
|
|
9
|
+
import './chunk-KIMFKLJB.js';
|
|
10
10
|
import './chunk-R74SJ7HS.js';
|
|
11
|
-
import './chunk-
|
|
11
|
+
import './chunk-IGHES6AK.js';
|
|
12
12
|
import './chunk-YLNQQZ5F.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-TADKOW6P.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-RH2UAS25.js';
|
|
4
|
+
import './chunk-KIMFKLJB.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-R74SJ7HS.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-IGHES6AK.js';
|
|
7
7
|
import './chunk-YLNQQZ5F.js';
|
|
8
8
|
import { promises } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { measureContext } from './chunk-LT67FU2Y.js';
|
|
3
3
|
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-WWBF4Y7K.js';
|
|
4
4
|
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-NMZ3RHWW.js';
|
|
5
|
-
import { scanProject, expandFileGlobs } from './chunk-
|
|
5
|
+
import { scanProject, expandFileGlobs } from './chunk-RH2UAS25.js';
|
|
6
6
|
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-R74SJ7HS.js';
|
|
7
|
-
import { PACK_VERSION } from './chunk-
|
|
7
|
+
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
8
8
|
import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath } from './chunk-YLNQQZ5F.js';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import YAML from 'yaml';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { DEFAULT_CONFIG } from './chunk-NMZ3RHWW.js';
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
3
|
+
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
4
4
|
import { readText, pathExists } from './chunk-YLNQQZ5F.js';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
@@ -158,6 +158,33 @@ ${localHookCall("commit-msg")}
|
|
|
158
158
|
exit 0
|
|
159
159
|
`;
|
|
160
160
|
}
|
|
161
|
+
function shellcheckStep() {
|
|
162
|
+
return `
|
|
163
|
+
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
164
|
+
# script added later is covered without regenerating this hook.
|
|
165
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
166
|
+
scripts=$(git ls-files -z \\
|
|
167
|
+
| xargs -0 -I{} sh -c 'head -1 "{}" 2>/dev/null | grep -qE "^#!.*[/ ](ba|da|z)?sh( |$)" && echo "{}"' \\
|
|
168
|
+
| sort)
|
|
169
|
+
if [ -n "$scripts" ]; then
|
|
170
|
+
echo "\u203A shellcheck ($(printf '%s\\n' "$scripts" | wc -l | tr -d ' ') scripts, blocking at severity=warning)"
|
|
171
|
+
printf '%s\\n' "$scripts" | xargs shellcheck -S warning || {
|
|
172
|
+
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
173
|
+
exit 1
|
|
174
|
+
}
|
|
175
|
+
# Everything below the blocking bar, shown once the push is already cleared. Never affects
|
|
176
|
+
# the exit code \u2014 advice that can fail a push is not advice.
|
|
177
|
+
advice=$(printf '%s\\n' "$scripts" | xargs shellcheck -S style -f gcc 2>/dev/null \\
|
|
178
|
+
| grep -v ': warning:\\|: error:' || true)
|
|
179
|
+
if [ -n "$advice" ]; then
|
|
180
|
+
echo " \xB7 style/info (not blocking):"
|
|
181
|
+
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
182
|
+
fi
|
|
183
|
+
fi
|
|
184
|
+
else
|
|
185
|
+
echo "\u203A shellcheck skipped (not on PATH) \u2014 install it to gate this repo's shell scripts"
|
|
186
|
+
fi`;
|
|
187
|
+
}
|
|
161
188
|
function generatePrePushHook(facts, gates) {
|
|
162
189
|
const run = runPrefix(facts.packageManager);
|
|
163
190
|
const c = facts.commands;
|
|
@@ -166,8 +193,14 @@ function generatePrePushHook(facts, gates) {
|
|
|
166
193
|
const steps = candidates.filter(
|
|
167
194
|
(key) => Boolean(key) && (allowed.has(key) || isSafeGateCommand(c.raw[key]))
|
|
168
195
|
).map((key) => `${run} ${key}`);
|
|
196
|
+
const shellStep = facts.shell?.scripts ? shellcheckStep() : "";
|
|
169
197
|
const body = steps.length ? steps.map((s) => `echo "\u203A ${s}"
|
|
170
|
-
${s} || exit 1`).join("\n") :
|
|
198
|
+
${s} || exit 1`).join("\n") : shellStep ? (
|
|
199
|
+
// A repo whose executable surface is shell HAS a correctness command — it just is not in
|
|
200
|
+
// package.json. Claiming "none detected" beside a step that is about to run would be the
|
|
201
|
+
// tool contradicting itself.
|
|
202
|
+
`echo "\u203A no package scripts \u2014 shell is this repo's checkable surface"`
|
|
203
|
+
) : 'echo "etymd: no correctness commands detected \u2014 add format:check / typecheck / lint"';
|
|
171
204
|
const failOn = gates?.failOn ?? "risk";
|
|
172
205
|
const auditStep = `
|
|
173
206
|
if command -v etymd >/dev/null 2>&1; then
|
|
@@ -180,7 +213,7 @@ fi`;
|
|
|
180
213
|
# etymd: correctness gate. Mirrors CI cheapest-first; blocks the push on any failure.
|
|
181
214
|
|
|
182
215
|
${localHookCall("pre-push")}
|
|
183
|
-
${body}
|
|
216
|
+
${body}${shellStep}
|
|
184
217
|
${auditStep}
|
|
185
218
|
|
|
186
219
|
# Content screen, second pass \u2014 the WHOLE TREE rather than one diff. Catches anything committed
|
|
@@ -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-KIMFKLJB.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
4
4
|
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-YLNQQZ5F.js';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { promises } from 'node:fs';
|
|
@@ -390,6 +390,33 @@ var ARTIFACT_SPECS = [
|
|
|
390
390
|
kind: "map"
|
|
391
391
|
}
|
|
392
392
|
];
|
|
393
|
+
async function firstLine(file) {
|
|
394
|
+
let handle;
|
|
395
|
+
try {
|
|
396
|
+
handle = await promises.open(file, "r");
|
|
397
|
+
const { buffer, bytesRead } = await handle.read(Buffer.alloc(128), 0, 128, 0);
|
|
398
|
+
return buffer.subarray(0, bytesRead).toString("utf8").split("\n", 1)[0] ?? "";
|
|
399
|
+
} catch {
|
|
400
|
+
return "";
|
|
401
|
+
} finally {
|
|
402
|
+
await handle?.close();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
var SHELL_SHEBANG = /^#!\s*\/\S*\b(?:ba|da|z)?sh\b|^#!\s*\/usr\/bin\/env\s+(?:ba|da|z)?sh\b/;
|
|
406
|
+
var SHELL_EXT = /\.(?:sh|bash|zsh)$/;
|
|
407
|
+
async function detectShellSurface(root, isRepo) {
|
|
408
|
+
if (!isRepo) return { scripts: 0 };
|
|
409
|
+
const listed = await git(root, ["ls-files", "-z"]);
|
|
410
|
+
if (!listed) return { scripts: 0 };
|
|
411
|
+
const files = listed.split("\0").filter(Boolean);
|
|
412
|
+
const byExtension = files.filter((f) => SHELL_EXT.test(f));
|
|
413
|
+
const candidates = files.filter((f) => !path.basename(f).includes("."));
|
|
414
|
+
const SHEBANG_PROBE_CAP = 400;
|
|
415
|
+
const probed = await Promise.all(
|
|
416
|
+
candidates.slice(0, SHEBANG_PROBE_CAP).map(async (f) => SHELL_SHEBANG.test(await firstLine(path.join(root, f))))
|
|
417
|
+
);
|
|
418
|
+
return { scripts: byExtension.length + probed.filter(Boolean).length };
|
|
419
|
+
}
|
|
393
420
|
async function detectArtifacts(root) {
|
|
394
421
|
const artifacts = await Promise.all(
|
|
395
422
|
ARTIFACT_SPECS.map(async (spec) => ({
|
|
@@ -537,6 +564,7 @@ async function scanProject(root, opts = {}) {
|
|
|
537
564
|
]) : [null, null, null, null, null];
|
|
538
565
|
const hooksPath = hooksPathRaw ?? void 0;
|
|
539
566
|
const hooks = await detectHooks(abs, hooksPath, rootPkg);
|
|
567
|
+
const shell = await detectShellSurface(abs, isRepo);
|
|
540
568
|
const freshness = await collectFreshness(abs, isRepo, artifacts, opts.upstreamRemote);
|
|
541
569
|
const packages = workspace.packageGlobs.length ? await listWorkspacePackages(abs, workspace.packageGlobs) : [];
|
|
542
570
|
const recentAuthors = authorsRaw ? new Set(authorsRaw.split("\n").filter(Boolean)).size : void 0;
|
|
@@ -568,6 +596,7 @@ async function scanProject(root, opts = {}) {
|
|
|
568
596
|
// vsce — which never runs prepublishOnly.
|
|
569
597
|
publishRoute: !rootPkg ? "none" : rootPkg.engines?.vscode ? "vscode" : "npm",
|
|
570
598
|
artifacts,
|
|
599
|
+
shell,
|
|
571
600
|
freshness,
|
|
572
601
|
tree
|
|
573
602
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-OWNWN2KF.js';
|
|
3
3
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-TADKOW6P.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
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-KIMFKLJB.js';
|
|
3
3
|
import path from 'node: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-UJU3336P.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 missing").option("-y, --yes", "accept defaults without prompting (never overwrites)").action(
|
|
46
46
|
(opts, cmd) => action(async () => {
|
|
47
|
-
const { run } = await import('./init-
|
|
47
|
+
const { run } = await import('./init-7GNB74BE.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes });
|
|
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-DTYNGGXH.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-6YOKI3JW.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-W6DW3OK6.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-5UZLZ3CW.js');
|
|
80
80
|
await run({ cwd: resolveCwd(cmd), human: opts.human });
|
|
81
81
|
})
|
|
82
82
|
);
|
|
@@ -111,7 +111,7 @@ var fleet = program.command("fleet").description(
|
|
|
111
111
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
112
112
|
).action(
|
|
113
113
|
(opts, cmd) => action(async () => {
|
|
114
|
-
const { sweep } = await import('./fleet-
|
|
114
|
+
const { sweep } = await import('./fleet-77SSSFZO.js');
|
|
115
115
|
await sweep({
|
|
116
116
|
cwd: resolveCwd(cmd),
|
|
117
117
|
manifest: opts.manifest,
|
|
@@ -126,7 +126,7 @@ var fleet = program.command("fleet").description(
|
|
|
126
126
|
);
|
|
127
127
|
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(
|
|
128
128
|
(_opts, cmd) => action(async () => {
|
|
129
|
-
const { check } = await import('./fleet-
|
|
129
|
+
const { check } = await import('./fleet-77SSSFZO.js');
|
|
130
130
|
const opts = cmd.optsWithGlobals();
|
|
131
131
|
await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
|
|
132
132
|
})
|
|
@@ -136,7 +136,7 @@ fleet.command("add").argument("<dir>", "directory of the project to register").d
|
|
|
136
136
|
"public-repo | public-bound | private \u2014 mandatory for personal entries"
|
|
137
137
|
).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(
|
|
138
138
|
(dir, opts, cmd) => action(async () => {
|
|
139
|
-
const { add } = await import('./fleet-
|
|
139
|
+
const { add } = await import('./fleet-77SSSFZO.js');
|
|
140
140
|
const shared = cmd.optsWithGlobals();
|
|
141
141
|
await add({
|
|
142
142
|
cwd: resolveCwd(cmd),
|
|
@@ -154,7 +154,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
154
154
|
"Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
|
|
155
155
|
).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(
|
|
156
156
|
(name, id, _opts, cmd) => action(async () => {
|
|
157
|
-
const { dismiss } = await import('./fleet-
|
|
157
|
+
const { dismiss } = await import('./fleet-77SSSFZO.js');
|
|
158
158
|
const opts = cmd.optsWithGlobals();
|
|
159
159
|
await dismiss({
|
|
160
160
|
cwd: resolveCwd(cmd),
|
|
@@ -169,7 +169,7 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
169
169
|
"Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
|
|
170
170
|
).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(
|
|
171
171
|
(name, id, _opts, cmd) => action(async () => {
|
|
172
|
-
const { accept } = await import('./fleet-
|
|
172
|
+
const { accept } = await import('./fleet-77SSSFZO.js');
|
|
173
173
|
const opts = cmd.optsWithGlobals();
|
|
174
174
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
175
175
|
})
|
|
@@ -191,7 +191,7 @@ program.command("screen").description(
|
|
|
191
191
|
);
|
|
192
192
|
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(
|
|
193
193
|
(opts, cmd) => action(async () => {
|
|
194
|
-
const { run } = await import('./gates-
|
|
194
|
+
const { run } = await import('./gates-LTM5PE7R.js');
|
|
195
195
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
196
196
|
})
|
|
197
197
|
);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-XER37MLS.js';
|
|
3
|
+
import './chunk-OWNWN2KF.js';
|
|
4
4
|
import './chunk-LT67FU2Y.js';
|
|
5
5
|
import './chunk-WWBF4Y7K.js';
|
|
6
6
|
import './chunk-NMZ3RHWW.js';
|
|
7
7
|
import './chunk-TADKOW6P.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-RH2UAS25.js';
|
|
9
|
+
import './chunk-KIMFKLJB.js';
|
|
10
10
|
import './chunk-R74SJ7HS.js';
|
|
11
|
-
import './chunk-
|
|
11
|
+
import './chunk-IGHES6AK.js';
|
|
12
12
|
import './chunk-YLNQQZ5F.js';
|
|
13
13
|
|
|
14
14
|
// src/commands/doctor.ts
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dismiss, accept } from './chunk-MMPV67FW.js';
|
|
3
|
-
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-
|
|
3
|
+
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-OWNWN2KF.js';
|
|
4
4
|
import './chunk-LT67FU2Y.js';
|
|
5
5
|
import { readLedger } from './chunk-WWBF4Y7K.js';
|
|
6
6
|
import { DEFAULT_CONFIG } from './chunk-NMZ3RHWW.js';
|
|
7
7
|
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph } from './chunk-TADKOW6P.js';
|
|
8
|
-
import { scanProject } from './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import { scanProject } from './chunk-RH2UAS25.js';
|
|
9
|
+
import './chunk-KIMFKLJB.js';
|
|
10
10
|
import { ETYMD_DIR } from './chunk-R74SJ7HS.js';
|
|
11
|
-
import './chunk-
|
|
11
|
+
import './chunk-IGHES6AK.js';
|
|
12
12
|
import { readText, pathExists, git, isDirectory } from './chunk-YLNQQZ5F.js';
|
|
13
13
|
import { promises } from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
@@ -687,8 +687,8 @@ async function checkCorpEmails(manifest, findings, disclosures) {
|
|
|
687
687
|
}
|
|
688
688
|
}
|
|
689
689
|
async function checkGateDrift(manifest, findings, disclosures) {
|
|
690
|
-
const { planWorkflow } = await import('./generate-
|
|
691
|
-
const { scanProject: scanProject2 } = await import('./scan-
|
|
690
|
+
const { planWorkflow } = await import('./generate-CBMTNN7P.js');
|
|
691
|
+
const { scanProject: scanProject2 } = await import('./scan-XZH57E4I.js');
|
|
692
692
|
const { readConfig } = await import('./config-MX7OYI7F.js');
|
|
693
693
|
for (const entry of manifest.entries) {
|
|
694
694
|
const root = entry.resolvedRoot;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
3
|
-
import { derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-
|
|
3
|
+
import { derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-OX3LUOZR.js';
|
|
4
4
|
import { readConfig, CONFIG_FILE, configPath } from './chunk-NMZ3RHWW.js';
|
|
5
5
|
import { section, print, glyph, theme, renderPlan } from './chunk-TADKOW6P.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import { scanProject } from './chunk-RH2UAS25.js';
|
|
7
|
+
import './chunk-KIMFKLJB.js';
|
|
8
8
|
import './chunk-R74SJ7HS.js';
|
|
9
|
-
import './chunk-
|
|
9
|
+
import './chunk-IGHES6AK.js';
|
|
10
10
|
import { readText, git } from './chunk-YLNQQZ5F.js';
|
|
11
11
|
import { promises } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
@@ -57,7 +57,9 @@ function publishGateKey(route) {
|
|
|
57
57
|
}
|
|
58
58
|
function printGateSummary(gates, facts, source) {
|
|
59
59
|
const run2 = runPrefix(facts.packageManager);
|
|
60
|
-
const
|
|
60
|
+
const shellStep = facts.shell?.scripts ? `shellcheck (${facts.shell.scripts} script${facts.shell.scripts === 1 ? "" : "s"})` : "";
|
|
61
|
+
const listed = [...gates.commands.map((c) => `${run2} ${c}`), ...shellStep ? [shellStep] : []];
|
|
62
|
+
const cmds = listed.length ? listed.join(", ") : "none detected";
|
|
61
63
|
print(` ${glyph.bullet} ${theme.dim("pre-push runs")} ${theme.info(cmds)}`);
|
|
62
64
|
print(` ${glyph.bullet} ${theme.dim("audit fails on")} ${theme.info(gates.failOn)}`);
|
|
63
65
|
print(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { derivedCommands, planWorkflow } from './chunk-
|
|
2
|
+
export { derivedCommands, planWorkflow } from './chunk-OX3LUOZR.js';
|
|
3
3
|
import './chunk-NMZ3RHWW.js';
|
|
4
4
|
import './chunk-R74SJ7HS.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-IGHES6AK.js';
|
|
6
6
|
import './chunk-YLNQQZ5F.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,23 @@ interface ProjectFacts {
|
|
|
121
121
|
*/
|
|
122
122
|
publishRoute: "npm" | "vscode" | "none";
|
|
123
123
|
artifacts: DetectedArtifact[];
|
|
124
|
+
/**
|
|
125
|
+
* The repo's SHELL surface: tracked files that a shell executes.
|
|
126
|
+
*
|
|
127
|
+
* Package scripts are not the only executable surface a repo has, and in some repos they are
|
|
128
|
+
* not the main one — a tools or infra repo can be entirely `bootstrap/*.sh` plus `.githooks/*`
|
|
129
|
+
* with no `package.json` at all. Those are exactly the repos that report "no correctness
|
|
130
|
+
* commands detected", which reads as "nothing to check" when it means "nothing was looked at".
|
|
131
|
+
*
|
|
132
|
+
* Counted, not listed: the generated hook re-discovers scripts at push time by shebang, so it
|
|
133
|
+
* stays correct as the repo grows. This number only decides WHETHER the step is worth writing.
|
|
134
|
+
*
|
|
135
|
+
* Optional for the same reason as `freshness`: a facts file written by an older etymd predates
|
|
136
|
+
* the field, and absent must read as "not measured" rather than "measured as zero".
|
|
137
|
+
*/
|
|
138
|
+
shell?: {
|
|
139
|
+
scripts: number;
|
|
140
|
+
};
|
|
124
141
|
/** Optional: baselines approved by older versions predate this fact. */
|
|
125
142
|
freshness?: FreshnessFacts;
|
|
126
143
|
tree: {
|
|
@@ -752,7 +769,7 @@ declare const gateIntegrityLens: Lens;
|
|
|
752
769
|
* change meaning. Stamped into facts, baselines, and generated artifacts so drift against the
|
|
753
770
|
* pack is computable and `harvest` has something to diff.
|
|
754
771
|
*/
|
|
755
|
-
declare const PACK_VERSION = "
|
|
772
|
+
declare const PACK_VERSION = "5";
|
|
756
773
|
|
|
757
774
|
declare const VERSION: string;
|
|
758
775
|
declare const NAME: string;
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __export = (target, all) => {
|
|
|
19
19
|
var PACK_VERSION;
|
|
20
20
|
var init_version = __esm({
|
|
21
21
|
"src/pack/version.ts"() {
|
|
22
|
-
PACK_VERSION = "
|
|
22
|
+
PACK_VERSION = "5";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ var init_package = __esm({
|
|
|
29
29
|
"package.json"() {
|
|
30
30
|
package_default = {
|
|
31
31
|
name: "etymd",
|
|
32
|
-
version: "0.
|
|
32
|
+
version: "0.4.1",
|
|
33
33
|
description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules & skills against the actual repo, with drift caught over time and a regression ledger.",
|
|
34
34
|
keywords: [
|
|
35
35
|
"cli",
|
|
@@ -438,6 +438,31 @@ async function detectHooks(root, hooksPath, pkg) {
|
|
|
438
438
|
}
|
|
439
439
|
return { source: "none", preCommit: false, prePush: false, commitMsg: false, lintStaged };
|
|
440
440
|
}
|
|
441
|
+
async function firstLine(file) {
|
|
442
|
+
let handle;
|
|
443
|
+
try {
|
|
444
|
+
handle = await promises.open(file, "r");
|
|
445
|
+
const { buffer, bytesRead } = await handle.read(Buffer.alloc(128), 0, 128, 0);
|
|
446
|
+
return buffer.subarray(0, bytesRead).toString("utf8").split("\n", 1)[0] ?? "";
|
|
447
|
+
} catch {
|
|
448
|
+
return "";
|
|
449
|
+
} finally {
|
|
450
|
+
await handle?.close();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async function detectShellSurface(root, isRepo) {
|
|
454
|
+
if (!isRepo) return { scripts: 0 };
|
|
455
|
+
const listed = await git(root, ["ls-files", "-z"]);
|
|
456
|
+
if (!listed) return { scripts: 0 };
|
|
457
|
+
const files = listed.split("\0").filter(Boolean);
|
|
458
|
+
const byExtension = files.filter((f) => SHELL_EXT.test(f));
|
|
459
|
+
const candidates = files.filter((f) => !path.basename(f).includes("."));
|
|
460
|
+
const SHEBANG_PROBE_CAP = 400;
|
|
461
|
+
const probed = await Promise.all(
|
|
462
|
+
candidates.slice(0, SHEBANG_PROBE_CAP).map(async (f) => SHELL_SHEBANG.test(await firstLine(path.join(root, f))))
|
|
463
|
+
);
|
|
464
|
+
return { scripts: byExtension.length + probed.filter(Boolean).length };
|
|
465
|
+
}
|
|
441
466
|
async function detectArtifacts(root) {
|
|
442
467
|
const artifacts = await Promise.all(
|
|
443
468
|
ARTIFACT_SPECS.map(async (spec) => ({
|
|
@@ -504,7 +529,7 @@ async function walkTree(root) {
|
|
|
504
529
|
dirs.sort((a, b) => b.files - a.files);
|
|
505
530
|
return { dirs, truncated: state.truncated };
|
|
506
531
|
}
|
|
507
|
-
var IGNORED_DIRS, MEANINGFUL_DOT_DIRS, isMeta, ROLE_LADDERS, FRAMEWORK_MARKERS, ARTIFACT_SPECS;
|
|
532
|
+
var IGNORED_DIRS, MEANINGFUL_DOT_DIRS, isMeta, ROLE_LADDERS, FRAMEWORK_MARKERS, ARTIFACT_SPECS, SHELL_SHEBANG, SHELL_EXT;
|
|
508
533
|
var init_detect = __esm({
|
|
509
534
|
"src/core/detect.ts"() {
|
|
510
535
|
init_util();
|
|
@@ -677,6 +702,8 @@ var init_detect = __esm({
|
|
|
677
702
|
kind: "map"
|
|
678
703
|
}
|
|
679
704
|
];
|
|
705
|
+
SHELL_SHEBANG = /^#!\s*\/\S*\b(?:ba|da|z)?sh\b|^#!\s*\/usr\/bin\/env\s+(?:ba|da|z)?sh\b/;
|
|
706
|
+
SHELL_EXT = /\.(?:sh|bash|zsh)$/;
|
|
680
707
|
}
|
|
681
708
|
});
|
|
682
709
|
|
|
@@ -764,6 +791,7 @@ async function scanProject(root, opts = {}) {
|
|
|
764
791
|
]) : [null, null, null, null, null];
|
|
765
792
|
const hooksPath = hooksPathRaw ?? void 0;
|
|
766
793
|
const hooks = await detectHooks(abs, hooksPath, rootPkg);
|
|
794
|
+
const shell = await detectShellSurface(abs, isRepo);
|
|
767
795
|
const freshness = await collectFreshness(abs, isRepo, artifacts, opts.upstreamRemote);
|
|
768
796
|
const packages = workspace.packageGlobs.length ? await listWorkspacePackages(abs, workspace.packageGlobs) : [];
|
|
769
797
|
const recentAuthors = authorsRaw ? new Set(authorsRaw.split("\n").filter(Boolean)).size : void 0;
|
|
@@ -795,6 +823,7 @@ async function scanProject(root, opts = {}) {
|
|
|
795
823
|
// vsce — which never runs prepublishOnly.
|
|
796
824
|
publishRoute: !rootPkg ? "none" : rootPkg.engines?.vscode ? "vscode" : "npm",
|
|
797
825
|
artifacts,
|
|
826
|
+
shell,
|
|
798
827
|
freshness,
|
|
799
828
|
tree
|
|
800
829
|
};
|
|
@@ -1115,6 +1144,33 @@ ${localHookCall("commit-msg")}
|
|
|
1115
1144
|
exit 0
|
|
1116
1145
|
`;
|
|
1117
1146
|
}
|
|
1147
|
+
function shellcheckStep() {
|
|
1148
|
+
return `
|
|
1149
|
+
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
1150
|
+
# script added later is covered without regenerating this hook.
|
|
1151
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
1152
|
+
scripts=$(git ls-files -z \\
|
|
1153
|
+
| xargs -0 -I{} sh -c 'head -1 "{}" 2>/dev/null | grep -qE "^#!.*[/ ](ba|da|z)?sh( |$)" && echo "{}"' \\
|
|
1154
|
+
| sort)
|
|
1155
|
+
if [ -n "$scripts" ]; then
|
|
1156
|
+
echo "\u203A shellcheck ($(printf '%s\\n' "$scripts" | wc -l | tr -d ' ') scripts, blocking at severity=warning)"
|
|
1157
|
+
printf '%s\\n' "$scripts" | xargs shellcheck -S warning || {
|
|
1158
|
+
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
1159
|
+
exit 1
|
|
1160
|
+
}
|
|
1161
|
+
# Everything below the blocking bar, shown once the push is already cleared. Never affects
|
|
1162
|
+
# the exit code \u2014 advice that can fail a push is not advice.
|
|
1163
|
+
advice=$(printf '%s\\n' "$scripts" | xargs shellcheck -S style -f gcc 2>/dev/null \\
|
|
1164
|
+
| grep -v ': warning:\\|: error:' || true)
|
|
1165
|
+
if [ -n "$advice" ]; then
|
|
1166
|
+
echo " \xB7 style/info (not blocking):"
|
|
1167
|
+
printf '%s\\n' "$advice" | sed 's/^/ /'
|
|
1168
|
+
fi
|
|
1169
|
+
fi
|
|
1170
|
+
else
|
|
1171
|
+
echo "\u203A shellcheck skipped (not on PATH) \u2014 install it to gate this repo's shell scripts"
|
|
1172
|
+
fi`;
|
|
1173
|
+
}
|
|
1118
1174
|
function generatePrePushHook(facts, gates) {
|
|
1119
1175
|
const run = runPrefix(facts.packageManager);
|
|
1120
1176
|
const c = facts.commands;
|
|
@@ -1123,8 +1179,14 @@ function generatePrePushHook(facts, gates) {
|
|
|
1123
1179
|
const steps = candidates.filter(
|
|
1124
1180
|
(key) => Boolean(key) && (allowed.has(key) || isSafeGateCommand(c.raw[key]))
|
|
1125
1181
|
).map((key) => `${run} ${key}`);
|
|
1182
|
+
const shellStep = facts.shell?.scripts ? shellcheckStep() : "";
|
|
1126
1183
|
const body = steps.length ? steps.map((s) => `echo "\u203A ${s}"
|
|
1127
|
-
${s} || exit 1`).join("\n") :
|
|
1184
|
+
${s} || exit 1`).join("\n") : shellStep ? (
|
|
1185
|
+
// A repo whose executable surface is shell HAS a correctness command — it just is not in
|
|
1186
|
+
// package.json. Claiming "none detected" beside a step that is about to run would be the
|
|
1187
|
+
// tool contradicting itself.
|
|
1188
|
+
`echo "\u203A no package scripts \u2014 shell is this repo's checkable surface"`
|
|
1189
|
+
) : 'echo "etymd: no correctness commands detected \u2014 add format:check / typecheck / lint"';
|
|
1128
1190
|
const failOn = gates?.failOn ?? "risk";
|
|
1129
1191
|
const auditStep = `
|
|
1130
1192
|
if command -v etymd >/dev/null 2>&1; then
|
|
@@ -1137,7 +1199,7 @@ fi`;
|
|
|
1137
1199
|
# etymd: correctness gate. Mirrors CI cheapest-first; blocks the push on any failure.
|
|
1138
1200
|
|
|
1139
1201
|
${localHookCall("pre-push")}
|
|
1140
|
-
${body}
|
|
1202
|
+
${body}${shellStep}
|
|
1141
1203
|
${auditStep}
|
|
1142
1204
|
|
|
1143
1205
|
# Content screen, second pass \u2014 the WHOLE TREE rather than one diff. Catches anything committed
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
3
|
+
import { planWorkflow } from './chunk-OX3LUOZR.js';
|
|
4
4
|
import './chunk-NMZ3RHWW.js';
|
|
5
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-TADKOW6P.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject } from './chunk-RH2UAS25.js';
|
|
7
|
+
import { VERSION } from './chunk-KIMFKLJB.js';
|
|
8
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-R74SJ7HS.js';
|
|
9
|
-
import { PACK_VERSION } from './chunk-
|
|
9
|
+
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
10
10
|
import { git, readText } from './chunk-YLNQQZ5F.js';
|
|
11
11
|
import { promises } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, renderFacts, theme } from './chunk-TADKOW6P.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-RH2UAS25.js';
|
|
4
|
+
import './chunk-KIMFKLJB.js';
|
|
5
5
|
import { writeCachedFacts } from './chunk-R74SJ7HS.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-IGHES6AK.js';
|
|
7
7
|
import './chunk-YLNQQZ5F.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/scan.ts
|
package/package.json
CHANGED