etymd 0.9.1 → 0.11.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 +120 -14
- package/README.md +50 -2
- package/dist/{approve-HED3NCTE.js → approve-OUJ4UPGK.js} +5 -5
- package/dist/audit-A75FVXHD.js +12 -0
- package/dist/{brief-2CXAUDNN.js → brief-MYUESUMM.js} +7 -7
- package/dist/{chunk-ULM25GWG.js → chunk-2KVQBL4E.js} +1 -1
- package/dist/{chunk-V2SOHPXF.js → chunk-2VLNI3L2.js} +1 -1
- package/dist/{chunk-OAFYLBVG.js → chunk-3E2IPCRY.js} +3 -3
- package/dist/{chunk-IV3FYVTS.js → chunk-4VPBP6K6.js} +4 -3
- package/dist/{chunk-UN7IAYM6.js → chunk-5GQ52DK6.js} +5 -5
- package/dist/{chunk-D2RI3CCT.js → chunk-DWL2IKZH.js} +12 -3
- package/dist/{chunk-JSUYV26K.js → chunk-F75Q43BC.js} +4 -4
- package/dist/{chunk-EF6BPBG6.js → chunk-GWGKEPRX.js} +2 -2
- package/dist/{chunk-WENIJMVV.js → chunk-HRJJQCMT.js} +54 -18
- package/dist/{chunk-AQM4GYGN.js → chunk-J7XVOTZL.js} +1 -1
- package/dist/{chunk-C7LBUFNU.js → chunk-JHZ2BN4U.js} +3 -3
- package/dist/{chunk-KGVRZ6WC.js → chunk-JVLWM27I.js} +9 -9
- package/dist/{chunk-6DUDIVIC.js → chunk-LSZGCKIQ.js} +1 -1
- package/dist/cli.js +19 -19
- package/dist/config-XAH6PA5G.js +4 -0
- package/dist/{context-5ANOP2IJ.js → context-F63RSIBH.js} +4 -4
- package/dist/doctor-OZQTOUFR.js +19 -0
- package/dist/{fleet-DPJSZDEY.js → fleet-Z7W3QBEV.js} +18 -18
- package/dist/{gates-RR3V4PII.js → gates-47YTMMLZ.js} +10 -10
- package/dist/generate-PMCP37DC.js +6 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +192 -147
- package/dist/{init-RC35RS5J.js → init-QLGIYFCX.js} +10 -10
- package/dist/ledger-4FOZ5HAB.js +5 -0
- package/dist/{scan-MBXSD5RO.js → scan-VWEXWM6G.js} +5 -5
- package/dist/scan-W7RHZVSF.js +5 -0
- package/dist/{screen-EHNK6ANK.js → screen-NSWNC7VQ.js} +125 -16
- package/package.json +4 -4
- package/dist/audit-Y35XVD6Z.js +0 -12
- package/dist/config-7WCVZCM6.js +0 -4
- package/dist/doctor-DXXQJ2L3.js +0 -19
- package/dist/generate-IPY5QIR5.js +0 -6
- package/dist/ledger-B2XGANTH.js +0 -5
- package/dist/scan-KX522VKV.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,116 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1193d71: Content screen: allow-file entries carry provenance, and the env bypasses are gone
|
|
8
|
+
|
|
9
|
+
Every exemption is a hole in the gate, and a hole with no name on it lives forever — nobody can
|
|
10
|
+
ask "is this still needed?" about an entry nobody signed. Repo-level exceptions now live in
|
|
11
|
+
`.etymd-screen-allow` (the previous `.artifact-check-allow` is still honoured) as records of
|
|
12
|
+
labeled lines:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
pattern ^/Users/someone
|
|
16
|
+
reason test fixture for machine-path detector
|
|
17
|
+
date 2026-08-15
|
|
18
|
+
author owner
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The pattern is the rest of its line, verbatim — it may contain any character, `|` included,
|
|
22
|
+
without escaping. Delimiting a free-form field in-band would be an ambiguity in the format
|
|
23
|
+
itself, and every parser-side guard against it only makes the misparses you imagined loud while
|
|
24
|
+
leaving the rest silent; the line break is the one boundary the field cannot contain.
|
|
25
|
+
|
|
26
|
+
An entry naming the repo itself needs no provenance — a bare `^widget$` line is a complete
|
|
27
|
+
record, the exemption being exactly as wide as the name. Anything else missing a field is
|
|
28
|
+
reported and does not apply, so an unprovenanced file reads as noise to fix, not as silence to
|
|
29
|
+
trust.
|
|
30
|
+
|
|
31
|
+
The two environment bypasses the generated hooks carried — `CONTENT_GATE_PREPUSH` and
|
|
32
|
+
`ARTIFACT_CHECK_SKIP` — are removed. They were off-switches invisible in the tree: nothing in
|
|
33
|
+
the repo records that a gate was skipped, or why. The allow file is the one bypass path left,
|
|
34
|
+
and every entry in it says who exempted what, when, and for what reason.
|
|
35
|
+
|
|
36
|
+
Generated hooks now resolve their screener in a defined order: an explicit `CONTENT_GATE`, then
|
|
37
|
+
the repo's own `./dist/cli.js` when it builds one, then the `etymd` on PATH. The middle step is
|
|
38
|
+
the dogfood case — a repo developing the screener must gate on its own unreleased build, or its
|
|
39
|
+
hooks enforce the last published behaviour against a tree that has moved past it. Repos without
|
|
40
|
+
a `dist/` resolve exactly as before. Pack v8 — run `etymd gates` to pick it up.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 652a353: Dependency advisories, 2026-08 batch: vitest 2.1.8 → 4.1.10, @changesets/cli 2.27.11 → 2.31.1,
|
|
45
|
+
tsup 8.3.5 → 8.5.1, plus in-range transitive fixes (js-yaml, nanoid, postcss, tmp)
|
|
46
|
+
|
|
47
|
+
Clears the whole advisory list reachable in range, then the vitest 2 → 4 major the rest of it
|
|
48
|
+
needed — the deliberate migration, not an audit-chased one: all 260 tests pass on 4.1.10
|
|
49
|
+
unchanged, config needed nothing, and the esbuild/vite/vitest development-server exposure class
|
|
50
|
+
(file read and execution via a listening dev server) is closed rather than reasoned around.
|
|
51
|
+
|
|
52
|
+
What remains is one low: esbuild 0.27.3–0.28.0, arbitrary file read when running a development
|
|
53
|
+
server on Windows, reached only through tsup (which pins ^0.27.0). Forcing an override past
|
|
54
|
+
tsup's declared range is audit-chasing a code path this repo never enters — nothing in it
|
|
55
|
+
starts a listening dev server — so it waits for a tsup release that carries esbuild 0.28.
|
|
56
|
+
|
|
57
|
+
- 9521732: Content screen: a clean run reports itself, with the file count
|
|
58
|
+
|
|
59
|
+
A clean `etymd screen` printed nothing at all — the summary header, file count and binary-skip
|
|
60
|
+
count only appeared when there were findings. From inside a hook, where the exit code is the
|
|
61
|
+
only other signal, "silent" and "never looked" were indistinguishable, which is the exact
|
|
62
|
+
failure the binary-skip disclosure exists to prevent (0.9.1 claimed the count is always
|
|
63
|
+
reported; on a clean run it never was).
|
|
64
|
+
|
|
65
|
+
Every run now prints its summary line — scope, files scanned, binary skipped — and a clean run
|
|
66
|
+
says so. Hooks gain one line per successful commit; a silent screen remains possible only
|
|
67
|
+
where the screener is absent, which the tool already reports as inert rather than clean.
|
|
68
|
+
|
|
69
|
+
## 0.10.0
|
|
70
|
+
|
|
71
|
+
### Minor Changes
|
|
72
|
+
|
|
73
|
+
- 2efbd34: `etymd gates` can now check the commit subject against Conventional Commits, at the commit-msg
|
|
74
|
+
door the content screen already used. Pack v7 — run `etymd gates` to pick it up.
|
|
75
|
+
|
|
76
|
+
**Off unless you ask for it.** Set `gates.commitFormat: true` in `.etymd/config.json`; anything
|
|
77
|
+
else, including leaving the key out, writes exactly the hook you got before this release. It is
|
|
78
|
+
the one generated check that needs nothing installed and would therefore run for everyone who
|
|
79
|
+
clones your repo — every other check the pack writes is either derived from what your repo
|
|
80
|
+
already does, or inert without a checker you installed yourself. A commit convention is neither.
|
|
81
|
+
It is an opinion, and this tool does not hold opinions on your behalf.
|
|
82
|
+
|
|
83
|
+
Where you do turn it on, it is a format check and not a taste check: it asks whether a machine
|
|
84
|
+
can classify the subject and stops there. An over-long subject is advice and never blocks, and
|
|
85
|
+
the subjects git writes for you (merge, revert, fixup, squash, amend) are exempt.
|
|
86
|
+
|
|
87
|
+
### Patch Changes
|
|
88
|
+
|
|
89
|
+
- 5104394: Content screen: recognise a repo's own name however the pattern spells it
|
|
90
|
+
|
|
91
|
+
The self-name exemption compared a pattern's raw source to the repo's directory name. Once a
|
|
92
|
+
pattern file started listing names word-anchored — `\bwidget\b` rather than `widget` — no source
|
|
93
|
+
matched any name, and the exemption silently stopped applying: a repo began reporting its own
|
|
94
|
+
`package.json`, its own contract titles and its own storage keys as leaks, enough of them to bury
|
|
95
|
+
anything else in the report. Nothing failed; the code, its comment and its test all still read as
|
|
96
|
+
correct.
|
|
97
|
+
|
|
98
|
+
The comparison is now of meaning rather than spelling. A pattern is exempt when the single
|
|
99
|
+
string it matches is one of the names the repo can prove is its own, with word anchors accepted
|
|
100
|
+
because they change where a match may begin, never which string matches. Any pattern that can
|
|
101
|
+
match more than one string — a class, a quantifier, an alternation — is never exempt, so an
|
|
102
|
+
exemption stays exactly as wide as a name.
|
|
103
|
+
|
|
104
|
+
"Its own name" also widens from the worktree directory to the union of the directory, the
|
|
105
|
+
`name` in `package.json` (npm scope stripped) and the basename of the `origin` remote, since
|
|
106
|
+
those three routinely disagree. Each is read from the repo being screened, so a repo can only
|
|
107
|
+
ever exempt itself; where none can be read the union is empty and every pattern stays active.
|
|
108
|
+
|
|
3
109
|
## 0.9.1
|
|
4
110
|
|
|
5
111
|
### Patch Changes
|
|
6
112
|
|
|
7
|
-
-
|
|
113
|
+
- 91ddb97: Content screen: skip binary files, and say how many were skipped
|
|
8
114
|
|
|
9
115
|
Compressed bytes contain any short sequence eventually, so a short pattern
|
|
10
116
|
matches inside binary assets by accident and is reported as a "line" of
|
|
@@ -24,7 +130,7 @@
|
|
|
24
130
|
|
|
25
131
|
### Minor Changes
|
|
26
132
|
|
|
27
|
-
-
|
|
133
|
+
- 35dbeab: State documents are now checked for truth, not only for age: the instruction-truth lens runs its
|
|
28
134
|
command-claim and path-claim checks over detected state docs, and resolves their `D-NNN` decision
|
|
29
135
|
references against the repo's decisions file — a citation of an entry that was never written is a
|
|
30
136
|
gap finding. References naming another record ("peer D-050") and citations against directory
|
|
@@ -35,12 +141,12 @@
|
|
|
35
141
|
|
|
36
142
|
### Minor Changes
|
|
37
143
|
|
|
38
|
-
-
|
|
144
|
+
- bad1380: `etymd gates` no longer writes a gate that cannot fail. It derives whether any risk-tier finding is
|
|
39
145
|
reachable in the repo (a package manifest to contradict, a state document to fall behind); where
|
|
40
146
|
none is, the generated hook drops to `--fail-on gap` and the output says so and why. A `failOn`
|
|
41
147
|
recorded in `.etymd/config.json` is never adjusted, and `gates` now states which tier it wrote and
|
|
42
148
|
where that tier came from — the config key was previously unmentioned anywhere in its output.
|
|
43
|
-
-
|
|
149
|
+
- e873664: Declared-field checks are forward-only from the marker's position, not from the file. A decisions
|
|
44
150
|
file that declares required entry fields mid-life no longer demands them from the entries appended
|
|
45
151
|
above the marker — an append-only record cannot backfill them. The built-in `Scope:` check shares
|
|
46
152
|
the same gate; `Revisit:` keeps whole-file reach, since it fires only where the entry already wrote
|
|
@@ -50,7 +156,7 @@
|
|
|
50
156
|
|
|
51
157
|
### Minor Changes
|
|
52
158
|
|
|
53
|
-
-
|
|
159
|
+
- d27d635: Decisions records can require fields of their own, and etymd holds no opinion about which.
|
|
54
160
|
|
|
55
161
|
A decisions file already opts into per-entry format checks with a marker. It can now append field
|
|
56
162
|
names to it — `<!-- decisions-format: 1 fields=Owner,Rollback -->` — and every entry after the
|
|
@@ -86,7 +192,7 @@
|
|
|
86
192
|
|
|
87
193
|
### Minor Changes
|
|
88
194
|
|
|
89
|
-
-
|
|
195
|
+
- 47c5032: Local gates: the tool now reads and rewrites what it generated, instead of guessing about it.
|
|
90
196
|
|
|
91
197
|
Two defects in the local-gate machinery, both silent, both affecting every project using
|
|
92
198
|
`etymd gates`. Decision record:
|
|
@@ -164,7 +270,7 @@
|
|
|
164
270
|
|
|
165
271
|
### Minor Changes
|
|
166
272
|
|
|
167
|
-
-
|
|
273
|
+
- e265713: `fleet add --profile corp` now records the alias-to-directory mapping too, not just the entry.
|
|
168
274
|
|
|
169
275
|
A corp entry in the tracked manifest is deliberately alias-only — no path, no remote — which is
|
|
170
276
|
what keeps employer names out of a file that gets pushed. It also means the entry resolves to
|
|
@@ -185,7 +291,7 @@
|
|
|
185
291
|
without an entry is inert, while an entry without a mapping is a broken registration sitting in
|
|
186
292
|
the file that gets committed.
|
|
187
293
|
|
|
188
|
-
-
|
|
294
|
+
- e8ed2ba: `fleet add` no longer records a remote URL in the manifest.
|
|
189
295
|
|
|
190
296
|
The field was write-only: nothing in the tool ever read it back. It was persisted because it
|
|
191
297
|
happened to be derivable at registration time, and it stayed because no one asked what consumed
|
|
@@ -204,7 +310,7 @@
|
|
|
204
310
|
|
|
205
311
|
### Patch Changes
|
|
206
312
|
|
|
207
|
-
-
|
|
313
|
+
- 88492b5: Fix: `fleet add --profile corp` was silently ignored, registering employer repos as personal.
|
|
208
314
|
|
|
209
315
|
`fleet` declares its own `--profile` (the sweep filter), and commander hands a parent-declared
|
|
210
316
|
option the value even when it is typed after the subcommand. So `fleet add <dir> --profile corp`
|
|
@@ -225,7 +331,7 @@
|
|
|
225
331
|
|
|
226
332
|
### Patch Changes
|
|
227
333
|
|
|
228
|
-
-
|
|
334
|
+
- 821f144: Fix: the shell gate now actually prints the sub-warning findings it documented.
|
|
229
335
|
|
|
230
336
|
0.4.0 said "style and info print as advice" and did not do it — the generated hook ran only
|
|
231
337
|
`shellcheck -S warning` and discarded everything below that bar. The generated file made no such
|
|
@@ -246,7 +352,7 @@
|
|
|
246
352
|
|
|
247
353
|
### Minor Changes
|
|
248
354
|
|
|
249
|
-
-
|
|
355
|
+
- 59c506d: Gate the shell surface: `etymd gates` now installs a shellcheck step in repos that have one.
|
|
250
356
|
|
|
251
357
|
Package scripts are not the only executable surface a repo has, and in some repos they are not
|
|
252
358
|
the main one. A tools or infra repo can be entirely `bootstrap/*.sh` plus `.githooks/*` with no
|
|
@@ -363,7 +469,7 @@
|
|
|
363
469
|
|
|
364
470
|
### Patch Changes
|
|
365
471
|
|
|
366
|
-
-
|
|
472
|
+
- 7bc6cf5: Gate integrity: detect scripts regardless of how the package manager is invoked.
|
|
367
473
|
|
|
368
474
|
Script expansion guessed the script name positionally — the token straight after the
|
|
369
475
|
manager. That only holds for `npm run x`, `yarn x` and `pnpm x`. Every other live shape
|
|
@@ -392,7 +498,7 @@
|
|
|
392
498
|
|
|
393
499
|
### Patch Changes
|
|
394
500
|
|
|
395
|
-
-
|
|
501
|
+
- 983c0e5: Fleet sweep: recurring classes + `placement: "none"` honored.
|
|
396
502
|
|
|
397
503
|
- **Recurring classes** — the sweep report groups open findings by their engine-minted
|
|
398
504
|
class prefix and lists every class present in ≥2 projects (worst tier first). A class
|
|
@@ -410,7 +516,7 @@
|
|
|
410
516
|
|
|
411
517
|
### Minor Changes
|
|
412
518
|
|
|
413
|
-
-
|
|
519
|
+
- a336902: Fleet mode — the truth guard across your repositories (design record `docs/decisions/004-fleet-truth-guard.md`; registry + fleet `--json` schemas EXPERIMENTAL through 0.2.x).
|
|
414
520
|
|
|
415
521
|
- New `state-freshness` truth lens: state/decisions artifacts dated by git committer dates only (never mtime); staleness is relative, so a dormant repo's old state is current; state char budget against the ~10k session-hook truncation; marker-gated decisions format checks (`Scope:`, duplicate/out-of-order `D-NNN` ids, past `Revisit:` dates as due review debt); ADR conventions (`docs/adr/`, `docs/decisions/`, `NNNN-*.md`) recognized natively.
|
|
416
522
|
- New `etymd fleet` command family. The sweep runs a read-only audit per registered repo (`--manifest` required unless the cwd holds `registry.json` — no env var, no global pointer) and renders one line per project with a delta against `last.fleet.json`; detail only for new or risk findings. `fleet check` validates the manifest pair alone (dangling mappings, duplicate names, privacy leaks, machine paths). `fleet dismiss`/`fleet accept` resolve a project's finding from any cwd.
|
package/README.md
CHANGED
|
@@ -223,6 +223,7 @@ Every key is optional; omit the file entirely and the defaults below apply.
|
|
|
223
223
|
"commands": ["typecheck", "lint"], // pre-push steps, in order
|
|
224
224
|
"failOn": "risk", // audit tier that fails the push: risk | gap | polish
|
|
225
225
|
"publishGate": true, // screen the published artifact
|
|
226
|
+
"commitFormat": true, // check the commit subject — Conventional Commits, off unless set
|
|
226
227
|
"allowWriting": [], // commands allowed into a gate despite writing
|
|
227
228
|
// Why a value here is what it is. Each key mirrors the field it explains, so the
|
|
228
229
|
// note says what it refers to instead of sitting near it and hoping. Etymd keeps
|
|
@@ -337,8 +338,55 @@ packaged into a published release while every git-scoped check passes forever.
|
|
|
337
338
|
|
|
338
339
|
Every generated hook resolves the screener at run time and **no-ops when it is absent**, so the
|
|
339
340
|
same hook file is safe to commit to a public repo: it carries no patterns and imposes no policy
|
|
340
|
-
on anyone who clones it. A deliberate exception is marked inline with
|
|
341
|
-
visible in the diff
|
|
341
|
+
on anyone who clones it. A deliberate exception on a line you can edit is marked inline with
|
|
342
|
+
`allow-published-string`, visible in the diff.
|
|
343
|
+
|
|
344
|
+
Some exemptions cannot live on the line itself: a scanner's own source contains the strings it
|
|
345
|
+
screens for, its tests contain fixtures that must match, and a bundler strips comments so an
|
|
346
|
+
inline marker would not survive into the artifact. Those live in `.etymd-screen-allow` at the
|
|
347
|
+
repo root — one labeled line per field, so the pattern is never delimited:
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
pattern ^AcmeCorp|BetaCorp$
|
|
351
|
+
reason fixture proving the detector fires on either name
|
|
352
|
+
date 2026-08-15
|
|
353
|
+
author someone
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
A pattern may contain any character, including the `|` shown above, without escaping: a
|
|
357
|
+
free-form field delimited in-band is an ambiguity in the format itself, and no amount of
|
|
358
|
+
parser-side guarding fixes that class — labels move the boundary to the line break, which the
|
|
359
|
+
field cannot contain. An entry naming the repo itself needs no provenance — a bare `^widget$`
|
|
360
|
+
line is a complete record, the exemption being exactly as wide as the name. Anything else
|
|
361
|
+
missing a field is reported and does not apply: an exemption is a hole in the gate, and a hole
|
|
362
|
+
nobody signed cannot be audited later. The file is read from the repo being screened, never a
|
|
363
|
+
shared location, and it screens itself out (it necessarily contains every string it exempts).
|
|
364
|
+
|
|
365
|
+
### The commit subject, if you ask for it
|
|
366
|
+
|
|
367
|
+
Off unless you turn it on:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{ "gates": { "commitFormat": true } }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
in `.etymd/config.json`. Then the `commit-msg` hook also checks the subject against
|
|
374
|
+
[Conventional Commits](https://www.conventionalcommits.org) — `<type>[(scope)][!]: <summary>`,
|
|
375
|
+
with `feat fix docs style refactor perf test build ci chore revert` as the types. It is a format
|
|
376
|
+
check and not a taste check: it asks whether a machine can classify the line, and stops there.
|
|
377
|
+
An over-long subject is reported as advice and never blocks, and the subjects git writes for you
|
|
378
|
+
— merge, revert, fixup, squash, amend — are exempt, since gating those would ask you to rewrite
|
|
379
|
+
text you did not write.
|
|
380
|
+
|
|
381
|
+
It is the one generated check that needs nothing installed, and therefore the one that would run
|
|
382
|
+
for everyone who clones your repo. That is exactly why it is off by default. Every other check
|
|
383
|
+
the pack writes is either derived from what your repo already does or inert without a checker you
|
|
384
|
+
installed yourself; a commit convention is neither — it is an opinion, and this tool does not
|
|
385
|
+
hold opinions on your behalf.
|
|
386
|
+
|
|
387
|
+
Turn it on where the convention is already yours. A convention nobody gates does not get
|
|
388
|
+
abandoned in a decision you could point at: it erodes one hurried commit at a time, and by the
|
|
389
|
+
time the log reads as a mixture, every commit in it is already published.
|
|
342
390
|
|
|
343
391
|
Modeled on this repo's own workflow (Etymd guards its own instructions with Etymd — its CI runs
|
|
344
392
|
the same gate against its own freshly built CLI):
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
5
|
-
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-
|
|
6
|
-
import { PACK_VERSION } from './chunk-
|
|
7
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-5GQ52DK6.js';
|
|
4
|
+
import { VERSION } from './chunk-J7XVOTZL.js';
|
|
5
|
+
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-JHZ2BN4U.js';
|
|
6
|
+
import { PACK_VERSION } from './chunk-LSZGCKIQ.js';
|
|
7
|
+
import './chunk-4VPBP6K6.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/approve.ts
|
|
10
10
|
async function run(opts) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run } from './chunk-2KVQBL4E.js';
|
|
3
|
+
import './chunk-JVLWM27I.js';
|
|
4
|
+
import './chunk-F75Q43BC.js';
|
|
5
|
+
import './chunk-3E2IPCRY.js';
|
|
6
|
+
import './chunk-DWL2IKZH.js';
|
|
7
|
+
import './chunk-5BVKFJWM.js';
|
|
8
|
+
import './chunk-5GQ52DK6.js';
|
|
9
|
+
import './chunk-J7XVOTZL.js';
|
|
10
|
+
import './chunk-JHZ2BN4U.js';
|
|
11
|
+
import './chunk-LSZGCKIQ.js';
|
|
12
|
+
import './chunk-4VPBP6K6.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import { ETYMD_DIR } from './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import { promises } from '
|
|
9
|
-
import path from '
|
|
3
|
+
import { scanProject } from './chunk-5GQ52DK6.js';
|
|
4
|
+
import './chunk-J7XVOTZL.js';
|
|
5
|
+
import { ETYMD_DIR } from './chunk-JHZ2BN4U.js';
|
|
6
|
+
import './chunk-LSZGCKIQ.js';
|
|
7
|
+
import './chunk-4VPBP6K6.js';
|
|
8
|
+
import { promises } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
10
|
|
|
11
11
|
async function run(opts) {
|
|
12
12
|
const facts = await scanProject(opts.cwd);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-JVLWM27I.js';
|
|
3
3
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-5BVKFJWM.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readLedger, resolveEntry, writeLedger } from './chunk-
|
|
2
|
+
import { readLedger, resolveEntry, writeLedger } from './chunk-3E2IPCRY.js';
|
|
3
3
|
import { print, renderLedger, glyph, theme } from './chunk-5BVKFJWM.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/ledger.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readJson } from './chunk-
|
|
3
|
-
import { promises } from '
|
|
4
|
-
import path from '
|
|
2
|
+
import { readJson } from './chunk-4VPBP6K6.js';
|
|
3
|
+
import { promises } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
5
|
|
|
6
6
|
function ledgerPath(root) {
|
|
7
7
|
return path.join(root, ".etymd", "ledger.json");
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFile } from '
|
|
3
|
-
import { promises } from '
|
|
4
|
-
import
|
|
2
|
+
import { execFile } from 'child_process';
|
|
3
|
+
import { promises } from 'fs';
|
|
4
|
+
import 'path';
|
|
5
|
+
import { promisify } from 'util';
|
|
5
6
|
|
|
6
7
|
var pExecFile = promisify(execFile);
|
|
7
8
|
async function pathExists(p) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
4
|
-
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-
|
|
5
|
-
import path from '
|
|
6
|
-
import { promises } from '
|
|
2
|
+
import { VERSION } from './chunk-J7XVOTZL.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-LSZGCKIQ.js';
|
|
4
|
+
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-4VPBP6K6.js';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { promises } from 'fs';
|
|
7
7
|
|
|
8
8
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
9
9
|
"node_modules",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ETYMD_DIR } from './chunk-
|
|
3
|
-
import { pathExists, readText } from './chunk-
|
|
4
|
-
import path from '
|
|
2
|
+
import { ETYMD_DIR } from './chunk-JHZ2BN4U.js';
|
|
3
|
+
import { pathExists, readText } from './chunk-4VPBP6K6.js';
|
|
4
|
+
import path from 'path';
|
|
5
5
|
|
|
6
6
|
var CONFIG_FILE = path.join(ETYMD_DIR, "config.json");
|
|
7
7
|
var DEFAULT_CONFIG = {
|
|
@@ -70,6 +70,14 @@ async function readConfig(root) {
|
|
|
70
70
|
}
|
|
71
71
|
return gates.publishGate;
|
|
72
72
|
})();
|
|
73
|
+
const commitFormat = (() => {
|
|
74
|
+
if (gates.commitFormat === void 0) return void 0;
|
|
75
|
+
if (typeof gates.commitFormat !== "boolean") {
|
|
76
|
+
problems.push(`${CONFIG_FILE}: \`gates.commitFormat\` must be true or false \u2014 ignored.`);
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
return gates.commitFormat;
|
|
80
|
+
})();
|
|
73
81
|
return {
|
|
74
82
|
present: true,
|
|
75
83
|
problems,
|
|
@@ -91,6 +99,7 @@ async function readConfig(root) {
|
|
|
91
99
|
commands: readGlobList(gates.commands, "gates.commands", problems) ?? DEFAULT_CONFIG.gates.commands,
|
|
92
100
|
failOn: failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
93
101
|
publishGate,
|
|
102
|
+
commitFormat,
|
|
94
103
|
allowWriting: readGlobList(gates.allowWriting, "gates.allowWriting", problems) ?? DEFAULT_CONFIG.gates.allowWriting
|
|
95
104
|
}
|
|
96
105
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
3
|
-
import { readText, wordCount, approxTokens, pathExists } from './chunk-
|
|
4
|
-
import { promises } from '
|
|
5
|
-
import path from '
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-DWL2IKZH.js';
|
|
3
|
+
import { readText, wordCount, approxTokens, pathExists } from './chunk-4VPBP6K6.js';
|
|
4
|
+
import { promises } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
6
|
|
|
7
7
|
var ALWAYS_LOADED = [
|
|
8
8
|
{ path: "AGENTS.md", role: "operating contract" },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
4
|
-
import { readText, pathExists } from './chunk-
|
|
5
|
-
import path from '
|
|
6
|
-
import { createHash } from '
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-DWL2IKZH.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-LSZGCKIQ.js';
|
|
4
|
+
import { readText, pathExists } from './chunk-4VPBP6K6.js';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
7
|
|
|
8
8
|
var GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
|
|
9
9
|
function digestOf(body) {
|
|
@@ -132,7 +132,7 @@ ${[
|
|
|
132
132
|
"md"
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
|
-
var CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(command -v etymd || true)}"`;
|
|
135
|
+
var CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(if [ -x ./dist/cli.js ]; then echo ./dist/cli.js; else command -v etymd || true; fi)}"`;
|
|
136
136
|
function localHookCall(hook) {
|
|
137
137
|
return `# Repo-owned checks. This file is generated and will be overwritten; \`.githooks/${hook}.local\`
|
|
138
138
|
# is yours \u2014 etymd never reads, writes, or regenerates it. Put project-specific guards there.
|
|
@@ -161,9 +161,48 @@ fi
|
|
|
161
161
|
exit 0
|
|
162
162
|
`);
|
|
163
163
|
}
|
|
164
|
-
|
|
164
|
+
var COMMIT_TYPES = [
|
|
165
|
+
"feat",
|
|
166
|
+
"fix",
|
|
167
|
+
"docs",
|
|
168
|
+
"style",
|
|
169
|
+
"refactor",
|
|
170
|
+
"perf",
|
|
171
|
+
"test",
|
|
172
|
+
"build",
|
|
173
|
+
"ci",
|
|
174
|
+
"chore",
|
|
175
|
+
"revert"
|
|
176
|
+
];
|
|
177
|
+
var SUBJECT_ADVISORY_LENGTH = 72;
|
|
178
|
+
function commitFormatStep() {
|
|
179
|
+
const types = COMMIT_TYPES.join("|");
|
|
180
|
+
return `
|
|
181
|
+
# Message format \u2014 <type>[(scope)][!]: <summary>. Needs nothing installed, so it always runs.
|
|
182
|
+
subject=$(sed -e '/^#/d' -e '/^[[:space:]]*$/d' "$1" | head -1)
|
|
183
|
+
case "$subject" in
|
|
184
|
+
"Merge "*|"Revert "*|fixup!*|squash!*|amend!*) ;;
|
|
185
|
+
*)
|
|
186
|
+
if ! printf '%s' "$subject" | grep -qE '^(${types})(\\([a-z0-9._/-]+\\))?!?: .+'; then
|
|
187
|
+
echo "\u2717 commit message: expected '<type>[(scope)][!]: <summary>'"
|
|
188
|
+
echo " got: $subject"
|
|
189
|
+
echo " types: ${COMMIT_TYPES.join(" ")}"
|
|
190
|
+
echo " a '!' after the type or scope marks a breaking change"
|
|
191
|
+
exit 1
|
|
192
|
+
fi
|
|
193
|
+
# Length is advice, not a block: the format is what tooling reads, the length is what a
|
|
194
|
+
# person reads, and only one of the two can break anything.
|
|
195
|
+
if [ "\${#subject}" -gt ${SUBJECT_ADVISORY_LENGTH} ]; then
|
|
196
|
+
echo "\u203A note: subject is \${#subject} characters; ${SUBJECT_ADVISORY_LENGTH} or fewer reads better in git log"
|
|
197
|
+
fi
|
|
198
|
+
;;
|
|
199
|
+
esac`;
|
|
200
|
+
}
|
|
201
|
+
function generateCommitMsgHook(gates) {
|
|
202
|
+
const format = gates?.commitFormat === true ? `${commitFormatStep()}
|
|
203
|
+
` : "";
|
|
165
204
|
return stampGenerated(`#!/usr/bin/env sh
|
|
166
|
-
# etymd:
|
|
205
|
+
# etymd: the commit message itself \u2014 content screen, then format.
|
|
167
206
|
#
|
|
168
207
|
# The staged-content gate reads file bytes and never sees the message, yet a message is as
|
|
169
208
|
# permanently published as any file. No-op where no checker is installed.
|
|
@@ -173,7 +212,7 @@ GATE="\${COMMIT_MSG_GATE:-$(command -v etymd || true)}"
|
|
|
173
212
|
if [ -x "$GATE" ]; then
|
|
174
213
|
"$GATE" screen --message "$1" || exit 1
|
|
175
214
|
fi
|
|
176
|
-
|
|
215
|
+
${format}
|
|
177
216
|
${localHookCall("commit-msg")}
|
|
178
217
|
|
|
179
218
|
exit 0
|
|
@@ -241,7 +280,7 @@ ${auditStep}
|
|
|
241
280
|
# with --no-verify and anything a rebase or merge brought in from elsewhere. Advisory here (it
|
|
242
281
|
# never blocks the push): the blocking decision belongs at commit time, where the fix is cheap.
|
|
243
282
|
${CONTENT_GATE_RESOLUTION}
|
|
244
|
-
if [ -x "$GATE" ]
|
|
283
|
+
if [ -x "$GATE" ]; then
|
|
245
284
|
"$GATE" screen --tree --advisory || true
|
|
246
285
|
fi
|
|
247
286
|
|
|
@@ -255,14 +294,10 @@ function generateArtifactCheckScript() {
|
|
|
255
294
|
# Wire it into the irreversible moment:
|
|
256
295
|
# package.json \u2192 "prepublishOnly": "./scripts/artifact-check.sh"
|
|
257
296
|
#
|
|
258
|
-
#
|
|
297
|
+
# The artifact gate is the one check that sees what actually SHIPS \u2014 bypass with
|
|
298
|
+
# .etymd-screen-allow entries (with provenance) if you must exempt a string.
|
|
259
299
|
set -eu
|
|
260
300
|
|
|
261
|
-
if [ "\${ARTIFACT_CHECK_SKIP:-0}" = "1" ]; then
|
|
262
|
-
echo "\u203A artifact-check: SKIPPED by ARTIFACT_CHECK_SKIP=1"
|
|
263
|
-
exit 0
|
|
264
|
-
fi
|
|
265
|
-
|
|
266
301
|
${CONTENT_GATE_RESOLUTION}
|
|
267
302
|
[ -x "$GATE" ] || { echo "\u203A artifact-check: no checker installed \u2014 skipping."; exit 0; }
|
|
268
303
|
|
|
@@ -336,14 +371,15 @@ async function planWorkflow(root, facts, opts) {
|
|
|
336
371
|
await add(".githooks/pre-commit", generatePreCommitHook(), "Process gate (pre-commit)", true);
|
|
337
372
|
await add(
|
|
338
373
|
".githooks/commit-msg",
|
|
339
|
-
generateCommitMsgHook(),
|
|
340
|
-
"
|
|
374
|
+
generateCommitMsgHook(opts.gateConfig),
|
|
375
|
+
"Message gate (content screen, format)",
|
|
341
376
|
true
|
|
342
377
|
);
|
|
343
378
|
const gateConfig = opts.gateConfig?.commands?.length ? opts.gateConfig : {
|
|
344
379
|
commands: derivedCommands(facts, existingPrePush ?? void 0),
|
|
345
380
|
failOn: opts.gateConfig?.failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
346
381
|
publishGate: opts.gateConfig?.publishGate,
|
|
382
|
+
commitFormat: opts.gateConfig?.commitFormat,
|
|
347
383
|
allowWriting: opts.gateConfig?.allowWriting ?? []
|
|
348
384
|
};
|
|
349
385
|
await add(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readJson } from './chunk-
|
|
3
|
-
import { promises } from '
|
|
4
|
-
import path from '
|
|
2
|
+
import { readJson } from './chunk-4VPBP6K6.js';
|
|
3
|
+
import { promises } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
5
|
|
|
6
6
|
var ETYMD_DIR = ".etymd";
|
|
7
7
|
var CACHE_DIR = path.join(ETYMD_DIR, "cache");
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { measureContext } from './chunk-
|
|
3
|
-
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-
|
|
4
|
-
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-
|
|
5
|
-
import { scanProject, expandFileGlobs } from './chunk-
|
|
6
|
-
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-
|
|
7
|
-
import { PACK_VERSION } from './chunk-
|
|
8
|
-
import { pathExists, readJson, readText, isDirectory, matchesAnyGlob, git, normalizeRelPath, isCiEnvironment, isExecutable } from './chunk-
|
|
9
|
-
import path from '
|
|
2
|
+
import { measureContext } from './chunk-F75Q43BC.js';
|
|
3
|
+
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-3E2IPCRY.js';
|
|
4
|
+
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-DWL2IKZH.js';
|
|
5
|
+
import { scanProject, expandFileGlobs } from './chunk-5GQ52DK6.js';
|
|
6
|
+
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-JHZ2BN4U.js';
|
|
7
|
+
import { PACK_VERSION } from './chunk-LSZGCKIQ.js';
|
|
8
|
+
import { pathExists, readJson, readText, isDirectory, matchesAnyGlob, git, normalizeRelPath, isCiEnvironment, isExecutable } from './chunk-4VPBP6K6.js';
|
|
9
|
+
import path from 'path';
|
|
10
10
|
import YAML from 'yaml';
|
|
11
|
-
import { promises } from '
|
|
11
|
+
import { promises } from 'fs';
|
|
12
12
|
|
|
13
13
|
// src/engine/finding.ts
|
|
14
14
|
var TIER_ORDER = { risk: 0, gap: 1, polish: 2 };
|