etymd 0.9.1 → 0.10.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 +54 -14
- package/README.md +27 -0
- package/dist/{approve-HED3NCTE.js → approve-6TMXJLOI.js} +3 -3
- package/dist/audit-FGDX2AYS.js +12 -0
- package/dist/{brief-2CXAUDNN.js → brief-X64HK325.js} +3 -3
- package/dist/{chunk-JSUYV26K.js → chunk-IPOAD67U.js} +1 -1
- package/dist/{chunk-AQM4GYGN.js → chunk-K34AVLDP.js} +1 -1
- package/dist/{chunk-KGVRZ6WC.js → chunk-KQMQJJZK.js} +4 -4
- package/dist/{chunk-6DUDIVIC.js → chunk-OIYNP6Y6.js} +1 -1
- package/dist/{chunk-WENIJMVV.js → chunk-R6CVJ5D7.js} +47 -7
- package/dist/{chunk-ULM25GWG.js → chunk-SGW7OPHZ.js} +1 -1
- package/dist/{chunk-UN7IAYM6.js → chunk-WTD2EIMN.js} +2 -2
- package/dist/{chunk-D2RI3CCT.js → chunk-YVGC3E6U.js} +9 -0
- package/dist/cli.js +15 -15
- package/dist/{config-7WCVZCM6.js → config-TPK5LYDM.js} +1 -1
- package/dist/{context-5ANOP2IJ.js → context-K6DZNWAV.js} +2 -2
- package/dist/{doctor-DXXQJ2L3.js → doctor-W3UZK7KS.js} +7 -7
- package/dist/{fleet-DPJSZDEY.js → fleet-4PPNUIDJ.js} +9 -9
- package/dist/{gates-RR3V4PII.js → gates-Q7FMVUQP.js} +5 -5
- package/dist/{generate-IPY5QIR5.js → generate-NLHGTRZB.js} +3 -3
- package/dist/index.d.ts +9 -1
- package/dist/index.js +57 -8
- package/dist/{init-RC35RS5J.js → init-YYGRFIHQ.js} +5 -5
- package/dist/scan-PJSZGUG7.js +5 -0
- package/dist/{scan-MBXSD5RO.js → scan-VO776GM7.js} +3 -3
- package/dist/{screen-EHNK6ANK.js → screen-MOQP6XC7.js} +47 -8
- package/package.json +1 -1
- package/dist/audit-Y35XVD6Z.js +0 -12
- package/dist/scan-KX522VKV.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2efbd34: `etymd gates` can now check the commit subject against Conventional Commits, at the commit-msg
|
|
8
|
+
door the content screen already used. Pack v7 — run `etymd gates` to pick it up.
|
|
9
|
+
|
|
10
|
+
**Off unless you ask for it.** Set `gates.commitFormat: true` in `.etymd/config.json`; anything
|
|
11
|
+
else, including leaving the key out, writes exactly the hook you got before this release. It is
|
|
12
|
+
the one generated check that needs nothing installed and would therefore run for everyone who
|
|
13
|
+
clones your repo — every other check the pack writes is either derived from what your repo
|
|
14
|
+
already does, or inert without a checker you installed yourself. A commit convention is neither.
|
|
15
|
+
It is an opinion, and this tool does not hold opinions on your behalf.
|
|
16
|
+
|
|
17
|
+
Where you do turn it on, it is a format check and not a taste check: it asks whether a machine
|
|
18
|
+
can classify the subject and stops there. An over-long subject is advice and never blocks, and
|
|
19
|
+
the subjects git writes for you (merge, revert, fixup, squash, amend) are exempt.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 5104394: Content screen: recognise a repo's own name however the pattern spells it
|
|
24
|
+
|
|
25
|
+
The self-name exemption compared a pattern's raw source to the repo's directory name. Once a
|
|
26
|
+
pattern file started listing names word-anchored — `\bwidget\b` rather than `widget` — no source
|
|
27
|
+
matched any name, and the exemption silently stopped applying: a repo began reporting its own
|
|
28
|
+
`package.json`, its own contract titles and its own storage keys as leaks, enough of them to bury
|
|
29
|
+
anything else in the report. Nothing failed; the code, its comment and its test all still read as
|
|
30
|
+
correct.
|
|
31
|
+
|
|
32
|
+
The comparison is now of meaning rather than spelling. A pattern is exempt when the single
|
|
33
|
+
string it matches is one of the names the repo can prove is its own, with word anchors accepted
|
|
34
|
+
because they change where a match may begin, never which string matches. Any pattern that can
|
|
35
|
+
match more than one string — a class, a quantifier, an alternation — is never exempt, so an
|
|
36
|
+
exemption stays exactly as wide as a name.
|
|
37
|
+
|
|
38
|
+
"Its own name" also widens from the worktree directory to the union of the directory, the
|
|
39
|
+
`name` in `package.json` (npm scope stripped) and the basename of the `origin` remote, since
|
|
40
|
+
those three routinely disagree. Each is read from the repo being screened, so a repo can only
|
|
41
|
+
ever exempt itself; where none can be read the union is empty and every pattern stays active.
|
|
42
|
+
|
|
3
43
|
## 0.9.1
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
|
6
46
|
|
|
7
|
-
-
|
|
47
|
+
- 91ddb97: Content screen: skip binary files, and say how many were skipped
|
|
8
48
|
|
|
9
49
|
Compressed bytes contain any short sequence eventually, so a short pattern
|
|
10
50
|
matches inside binary assets by accident and is reported as a "line" of
|
|
@@ -24,7 +64,7 @@
|
|
|
24
64
|
|
|
25
65
|
### Minor Changes
|
|
26
66
|
|
|
27
|
-
-
|
|
67
|
+
- 35dbeab: State documents are now checked for truth, not only for age: the instruction-truth lens runs its
|
|
28
68
|
command-claim and path-claim checks over detected state docs, and resolves their `D-NNN` decision
|
|
29
69
|
references against the repo's decisions file — a citation of an entry that was never written is a
|
|
30
70
|
gap finding. References naming another record ("peer D-050") and citations against directory
|
|
@@ -35,12 +75,12 @@
|
|
|
35
75
|
|
|
36
76
|
### Minor Changes
|
|
37
77
|
|
|
38
|
-
-
|
|
78
|
+
- bad1380: `etymd gates` no longer writes a gate that cannot fail. It derives whether any risk-tier finding is
|
|
39
79
|
reachable in the repo (a package manifest to contradict, a state document to fall behind); where
|
|
40
80
|
none is, the generated hook drops to `--fail-on gap` and the output says so and why. A `failOn`
|
|
41
81
|
recorded in `.etymd/config.json` is never adjusted, and `gates` now states which tier it wrote and
|
|
42
82
|
where that tier came from — the config key was previously unmentioned anywhere in its output.
|
|
43
|
-
-
|
|
83
|
+
- e873664: Declared-field checks are forward-only from the marker's position, not from the file. A decisions
|
|
44
84
|
file that declares required entry fields mid-life no longer demands them from the entries appended
|
|
45
85
|
above the marker — an append-only record cannot backfill them. The built-in `Scope:` check shares
|
|
46
86
|
the same gate; `Revisit:` keeps whole-file reach, since it fires only where the entry already wrote
|
|
@@ -50,7 +90,7 @@
|
|
|
50
90
|
|
|
51
91
|
### Minor Changes
|
|
52
92
|
|
|
53
|
-
-
|
|
93
|
+
- d27d635: Decisions records can require fields of their own, and etymd holds no opinion about which.
|
|
54
94
|
|
|
55
95
|
A decisions file already opts into per-entry format checks with a marker. It can now append field
|
|
56
96
|
names to it — `<!-- decisions-format: 1 fields=Owner,Rollback -->` — and every entry after the
|
|
@@ -86,7 +126,7 @@
|
|
|
86
126
|
|
|
87
127
|
### Minor Changes
|
|
88
128
|
|
|
89
|
-
-
|
|
129
|
+
- 47c5032: Local gates: the tool now reads and rewrites what it generated, instead of guessing about it.
|
|
90
130
|
|
|
91
131
|
Two defects in the local-gate machinery, both silent, both affecting every project using
|
|
92
132
|
`etymd gates`. Decision record:
|
|
@@ -164,7 +204,7 @@
|
|
|
164
204
|
|
|
165
205
|
### Minor Changes
|
|
166
206
|
|
|
167
|
-
-
|
|
207
|
+
- e265713: `fleet add --profile corp` now records the alias-to-directory mapping too, not just the entry.
|
|
168
208
|
|
|
169
209
|
A corp entry in the tracked manifest is deliberately alias-only — no path, no remote — which is
|
|
170
210
|
what keeps employer names out of a file that gets pushed. It also means the entry resolves to
|
|
@@ -185,7 +225,7 @@
|
|
|
185
225
|
without an entry is inert, while an entry without a mapping is a broken registration sitting in
|
|
186
226
|
the file that gets committed.
|
|
187
227
|
|
|
188
|
-
-
|
|
228
|
+
- e8ed2ba: `fleet add` no longer records a remote URL in the manifest.
|
|
189
229
|
|
|
190
230
|
The field was write-only: nothing in the tool ever read it back. It was persisted because it
|
|
191
231
|
happened to be derivable at registration time, and it stayed because no one asked what consumed
|
|
@@ -204,7 +244,7 @@
|
|
|
204
244
|
|
|
205
245
|
### Patch Changes
|
|
206
246
|
|
|
207
|
-
-
|
|
247
|
+
- 88492b5: Fix: `fleet add --profile corp` was silently ignored, registering employer repos as personal.
|
|
208
248
|
|
|
209
249
|
`fleet` declares its own `--profile` (the sweep filter), and commander hands a parent-declared
|
|
210
250
|
option the value even when it is typed after the subcommand. So `fleet add <dir> --profile corp`
|
|
@@ -225,7 +265,7 @@
|
|
|
225
265
|
|
|
226
266
|
### Patch Changes
|
|
227
267
|
|
|
228
|
-
-
|
|
268
|
+
- 821f144: Fix: the shell gate now actually prints the sub-warning findings it documented.
|
|
229
269
|
|
|
230
270
|
0.4.0 said "style and info print as advice" and did not do it — the generated hook ran only
|
|
231
271
|
`shellcheck -S warning` and discarded everything below that bar. The generated file made no such
|
|
@@ -246,7 +286,7 @@
|
|
|
246
286
|
|
|
247
287
|
### Minor Changes
|
|
248
288
|
|
|
249
|
-
-
|
|
289
|
+
- 59c506d: Gate the shell surface: `etymd gates` now installs a shellcheck step in repos that have one.
|
|
250
290
|
|
|
251
291
|
Package scripts are not the only executable surface a repo has, and in some repos they are not
|
|
252
292
|
the main one. A tools or infra repo can be entirely `bootstrap/*.sh` plus `.githooks/*` with no
|
|
@@ -363,7 +403,7 @@
|
|
|
363
403
|
|
|
364
404
|
### Patch Changes
|
|
365
405
|
|
|
366
|
-
-
|
|
406
|
+
- 7bc6cf5: Gate integrity: detect scripts regardless of how the package manager is invoked.
|
|
367
407
|
|
|
368
408
|
Script expansion guessed the script name positionally — the token straight after the
|
|
369
409
|
manager. That only holds for `npm run x`, `yarn x` and `pnpm x`. Every other live shape
|
|
@@ -392,7 +432,7 @@
|
|
|
392
432
|
|
|
393
433
|
### Patch Changes
|
|
394
434
|
|
|
395
|
-
-
|
|
435
|
+
- 983c0e5: Fleet sweep: recurring classes + `placement: "none"` honored.
|
|
396
436
|
|
|
397
437
|
- **Recurring classes** — the sweep report groups open findings by their engine-minted
|
|
398
438
|
class prefix and lists every class present in ≥2 projects (worst tier first). A class
|
|
@@ -410,7 +450,7 @@
|
|
|
410
450
|
|
|
411
451
|
### Minor Changes
|
|
412
452
|
|
|
413
|
-
-
|
|
453
|
+
- 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
454
|
|
|
415
455
|
- 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
456
|
- 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
|
|
@@ -340,6 +341,32 @@ same hook file is safe to commit to a public repo: it carries no patterns and im
|
|
|
340
341
|
on anyone who clones it. A deliberate exception is marked inline with `allow-published-string`,
|
|
341
342
|
visible in the diff rather than hidden in an allowlist.
|
|
342
343
|
|
|
344
|
+
### The commit subject, if you ask for it
|
|
345
|
+
|
|
346
|
+
Off unless you turn it on:
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{ "gates": { "commitFormat": true } }
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
in `.etymd/config.json`. Then the `commit-msg` hook also checks the subject against
|
|
353
|
+
[Conventional Commits](https://www.conventionalcommits.org) — `<type>[(scope)][!]: <summary>`,
|
|
354
|
+
with `feat fix docs style refactor perf test build ci chore revert` as the types. It is a format
|
|
355
|
+
check and not a taste check: it asks whether a machine can classify the line, and stops there.
|
|
356
|
+
An over-long subject is reported as advice and never blocks, and the subjects git writes for you
|
|
357
|
+
— merge, revert, fixup, squash, amend — are exempt, since gating those would ask you to rewrite
|
|
358
|
+
text you did not write.
|
|
359
|
+
|
|
360
|
+
It is the one generated check that needs nothing installed, and therefore the one that would run
|
|
361
|
+
for everyone who clones your repo. That is exactly why it is off by default. Every other check
|
|
362
|
+
the pack writes is either derived from what your repo already does or inert without a checker you
|
|
363
|
+
installed yourself; a commit convention is neither — it is an opinion, and this tool does not
|
|
364
|
+
hold opinions on your behalf.
|
|
365
|
+
|
|
366
|
+
Turn it on where the convention is already yours. A convention nobody gates does not get
|
|
367
|
+
abandoned in a decision you could point at: it erodes one hurried commit at a time, and by the
|
|
368
|
+
time the log reads as a mixture, every commit in it is already published.
|
|
369
|
+
|
|
343
370
|
Modeled on this repo's own workflow (Etymd guards its own instructions with Etymd — its CI runs
|
|
344
371
|
the same gate against its own freshly built CLI):
|
|
345
372
|
|
|
@@ -1,9 +1,9 @@
|
|
|
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-
|
|
3
|
+
import { scanProject } from './chunk-WTD2EIMN.js';
|
|
4
|
+
import { VERSION } from './chunk-K34AVLDP.js';
|
|
5
5
|
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-C7LBUFNU.js';
|
|
6
|
-
import { PACK_VERSION } from './chunk-
|
|
6
|
+
import { PACK_VERSION } from './chunk-OIYNP6Y6.js';
|
|
7
7
|
import './chunk-IV3FYVTS.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/approve.ts
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run } from './chunk-SGW7OPHZ.js';
|
|
3
|
+
import './chunk-KQMQJJZK.js';
|
|
4
|
+
import './chunk-IPOAD67U.js';
|
|
5
|
+
import './chunk-OAFYLBVG.js';
|
|
6
|
+
import './chunk-YVGC3E6U.js';
|
|
7
|
+
import './chunk-5BVKFJWM.js';
|
|
8
|
+
import './chunk-WTD2EIMN.js';
|
|
9
|
+
import './chunk-K34AVLDP.js';
|
|
10
|
+
import './chunk-C7LBUFNU.js';
|
|
11
|
+
import './chunk-OIYNP6Y6.js';
|
|
12
|
+
import './chunk-IV3FYVTS.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-WTD2EIMN.js';
|
|
4
|
+
import './chunk-K34AVLDP.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-C7LBUFNU.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-OIYNP6Y6.js';
|
|
7
7
|
import './chunk-IV3FYVTS.js';
|
|
8
8
|
import { promises } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-YVGC3E6U.js';
|
|
3
3
|
import { readText, wordCount, approxTokens, pathExists } from './chunk-IV3FYVTS.js';
|
|
4
4
|
import { promises } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { measureContext } from './chunk-
|
|
2
|
+
import { measureContext } from './chunk-IPOAD67U.js';
|
|
3
3
|
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-OAFYLBVG.js';
|
|
4
|
-
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-
|
|
5
|
-
import { scanProject, expandFileGlobs } from './chunk-
|
|
4
|
+
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-YVGC3E6U.js';
|
|
5
|
+
import { scanProject, expandFileGlobs } from './chunk-WTD2EIMN.js';
|
|
6
6
|
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-C7LBUFNU.js';
|
|
7
|
-
import { PACK_VERSION } from './chunk-
|
|
7
|
+
import { PACK_VERSION } from './chunk-OIYNP6Y6.js';
|
|
8
8
|
import { pathExists, readJson, readText, isDirectory, matchesAnyGlob, git, normalizeRelPath, isCiEnvironment, isExecutable } from './chunk-IV3FYVTS.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
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-YVGC3E6U.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-OIYNP6Y6.js';
|
|
4
4
|
import { readText, pathExists } from './chunk-IV3FYVTS.js';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
@@ -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
|
|
@@ -336,14 +375,15 @@ async function planWorkflow(root, facts, opts) {
|
|
|
336
375
|
await add(".githooks/pre-commit", generatePreCommitHook(), "Process gate (pre-commit)", true);
|
|
337
376
|
await add(
|
|
338
377
|
".githooks/commit-msg",
|
|
339
|
-
generateCommitMsgHook(),
|
|
340
|
-
"
|
|
378
|
+
generateCommitMsgHook(opts.gateConfig),
|
|
379
|
+
"Message gate (content screen, format)",
|
|
341
380
|
true
|
|
342
381
|
);
|
|
343
382
|
const gateConfig = opts.gateConfig?.commands?.length ? opts.gateConfig : {
|
|
344
383
|
commands: derivedCommands(facts, existingPrePush ?? void 0),
|
|
345
384
|
failOn: opts.gateConfig?.failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
346
385
|
publishGate: opts.gateConfig?.publishGate,
|
|
386
|
+
commitFormat: opts.gateConfig?.commitFormat,
|
|
347
387
|
allowWriting: opts.gateConfig?.allowWriting ?? []
|
|
348
388
|
};
|
|
349
389
|
await add(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-KQMQJJZK.js';
|
|
3
3
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-5BVKFJWM.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
@@ -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-K34AVLDP.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-OIYNP6Y6.js';
|
|
4
4
|
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-IV3FYVTS.js';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { promises } from 'node:fs';
|
|
@@ -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
|
}
|
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-K34AVLDP.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-FGDX2AYS.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-YYGRFIHQ.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,31 +52,31 @@ 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-6TMXJLOI.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-VO776GM7.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-W3UZK7KS.js');
|
|
68
68
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
69
69
|
})
|
|
70
70
|
);
|
|
71
71
|
program.command("context").description("Measure the always-loaded context footprint and flag extraction candidates").option("--json", "print the budget as JSON").action(
|
|
72
72
|
(opts, cmd) => action(async () => {
|
|
73
|
-
const { run } = await import('./context-
|
|
73
|
+
const { run } = await import('./context-K6DZNWAV.js');
|
|
74
74
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
75
75
|
})
|
|
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-X64HK325.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-4PPNUIDJ.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-4PPNUIDJ.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-4PPNUIDJ.js');
|
|
140
140
|
const shared = cmd.optsWithGlobals();
|
|
141
141
|
await add({
|
|
142
142
|
cwd: resolveCwd(cmd),
|
|
@@ -159,7 +159,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
159
159
|
"Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
|
|
160
160
|
).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(
|
|
161
161
|
(name, id, _opts, cmd) => action(async () => {
|
|
162
|
-
const { dismiss } = await import('./fleet-
|
|
162
|
+
const { dismiss } = await import('./fleet-4PPNUIDJ.js');
|
|
163
163
|
const opts = cmd.optsWithGlobals();
|
|
164
164
|
await dismiss({
|
|
165
165
|
cwd: resolveCwd(cmd),
|
|
@@ -174,7 +174,7 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
174
174
|
"Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
|
|
175
175
|
).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(
|
|
176
176
|
(name, id, _opts, cmd) => action(async () => {
|
|
177
|
-
const { accept } = await import('./fleet-
|
|
177
|
+
const { accept } = await import('./fleet-4PPNUIDJ.js');
|
|
178
178
|
const opts = cmd.optsWithGlobals();
|
|
179
179
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
180
180
|
})
|
|
@@ -183,7 +183,7 @@ program.command("screen").description(
|
|
|
183
183
|
"Content screen: check for text that must never be published. Bring your own patterns \u2014 etymd ships none."
|
|
184
184
|
).option("--staged", "screen what a commit is about to add (default)").option("--tree", "screen every tracked file \u2014 what is about to leave the machine").option("--message <file>", "screen a commit message (the commit-msg hook's $1)").option("--dir <dir>", "screen an unpacked build artifact \u2014 what actually ships").option("--patterns <file>", "pattern file (default: ~/.config/etymd/screen-patterns)").option("--advisory", "report without failing").action(
|
|
185
185
|
(opts, cmd) => action(async () => {
|
|
186
|
-
const { run } = await import('./screen-
|
|
186
|
+
const { run } = await import('./screen-MOQP6XC7.js');
|
|
187
187
|
const scope = opts.message ? "message" : opts.dir ? "dir" : opts.tree ? "tree" : "staged";
|
|
188
188
|
await run({
|
|
189
189
|
cwd: resolveCwd(cmd),
|
|
@@ -196,7 +196,7 @@ program.command("screen").description(
|
|
|
196
196
|
);
|
|
197
197
|
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(
|
|
198
198
|
(opts, cmd) => action(async () => {
|
|
199
|
-
const { run } = await import('./gates-
|
|
199
|
+
const { run } = await import('./gates-Q7FMVUQP.js');
|
|
200
200
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
201
201
|
})
|
|
202
202
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { measureContext } from './chunk-
|
|
3
|
-
import { readConfig } from './chunk-
|
|
2
|
+
import { measureContext } from './chunk-IPOAD67U.js';
|
|
3
|
+
import { readConfig } from './chunk-YVGC3E6U.js';
|
|
4
4
|
import { print, renderContext } from './chunk-5BVKFJWM.js';
|
|
5
5
|
import './chunk-C7LBUFNU.js';
|
|
6
6
|
import './chunk-IV3FYVTS.js';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-SGW7OPHZ.js';
|
|
3
|
+
import './chunk-KQMQJJZK.js';
|
|
4
|
+
import './chunk-IPOAD67U.js';
|
|
5
5
|
import './chunk-OAFYLBVG.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-YVGC3E6U.js';
|
|
7
7
|
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-WTD2EIMN.js';
|
|
9
|
+
import './chunk-K34AVLDP.js';
|
|
10
10
|
import './chunk-C7LBUFNU.js';
|
|
11
|
-
import './chunk-
|
|
11
|
+
import './chunk-OIYNP6Y6.js';
|
|
12
12
|
import './chunk-IV3FYVTS.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-V2SOHPXF.js';
|
|
3
|
-
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-KQMQJJZK.js';
|
|
4
|
+
import './chunk-IPOAD67U.js';
|
|
5
5
|
import { readLedger } from './chunk-OAFYLBVG.js';
|
|
6
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
6
|
+
import { DEFAULT_CONFIG } from './chunk-YVGC3E6U.js';
|
|
7
7
|
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph } from './chunk-5BVKFJWM.js';
|
|
8
|
-
import { scanProject } from './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import { scanProject } from './chunk-WTD2EIMN.js';
|
|
9
|
+
import './chunk-K34AVLDP.js';
|
|
10
10
|
import { ETYMD_DIR } from './chunk-C7LBUFNU.js';
|
|
11
|
-
import './chunk-
|
|
11
|
+
import './chunk-OIYNP6Y6.js';
|
|
12
12
|
import { readText, pathExists, git, isDirectory } from './chunk-IV3FYVTS.js';
|
|
13
13
|
import { promises } from 'node:fs';
|
|
14
14
|
import os from 'node:os';
|
|
@@ -687,9 +687,9 @@ 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-
|
|
692
|
-
const { readConfig } = await import('./config-
|
|
690
|
+
const { planWorkflow } = await import('./generate-NLHGTRZB.js');
|
|
691
|
+
const { scanProject: scanProject2 } = await import('./scan-PJSZGUG7.js');
|
|
692
|
+
const { readConfig } = await import('./config-TPK5LYDM.js');
|
|
693
693
|
for (const entry of manifest.entries) {
|
|
694
694
|
const root = entry.resolvedRoot;
|
|
695
695
|
if (!root || !await isDirectory(root)) continue;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
3
|
-
import { deriveFailOn, derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-
|
|
4
|
-
import { readConfig, CONFIG_FILE, configPath } from './chunk-
|
|
3
|
+
import { deriveFailOn, derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-R6CVJ5D7.js';
|
|
4
|
+
import { readConfig, CONFIG_FILE, configPath } from './chunk-YVGC3E6U.js';
|
|
5
5
|
import { section, print, glyph, theme, renderPlan } from './chunk-5BVKFJWM.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import { scanProject } from './chunk-WTD2EIMN.js';
|
|
7
|
+
import './chunk-K34AVLDP.js';
|
|
8
8
|
import './chunk-C7LBUFNU.js';
|
|
9
|
-
import './chunk-
|
|
9
|
+
import './chunk-OIYNP6Y6.js';
|
|
10
10
|
import { readText, git } from './chunk-IV3FYVTS.js';
|
|
11
11
|
import { promises } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-R6CVJ5D7.js';
|
|
3
|
+
import './chunk-YVGC3E6U.js';
|
|
4
4
|
import './chunk-C7LBUFNU.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-OIYNP6Y6.js';
|
|
6
6
|
import './chunk-IV3FYVTS.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -226,6 +226,14 @@ interface GateConfig {
|
|
|
226
226
|
failOn: string;
|
|
227
227
|
/** Emit the publish-time screen. Unset = derive from whether the repo publishes. */
|
|
228
228
|
publishGate?: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Check the commit subject against Conventional Commits. **Unset = off.** Every other check
|
|
231
|
+
* the pack generates is either derived from what the repo already does or inert without a
|
|
232
|
+
* checker the user installed themselves; this one is a convention, and a convention is an
|
|
233
|
+
* opinion. Etymd ships the door and never decides that a repo wants it: `true` here is the
|
|
234
|
+
* repo asking, and nothing else turns it on.
|
|
235
|
+
*/
|
|
236
|
+
commitFormat?: boolean;
|
|
229
237
|
/** Commands allowed into a gate despite looking like they write (an explicit override). */
|
|
230
238
|
allowWriting: string[];
|
|
231
239
|
}
|
|
@@ -804,7 +812,7 @@ declare const gateIntegrityLens: Lens;
|
|
|
804
812
|
* change meaning. Stamped into facts, baselines, and generated artifacts so drift against the
|
|
805
813
|
* pack is computable and `harvest` has something to diff.
|
|
806
814
|
*/
|
|
807
|
-
declare const PACK_VERSION = "
|
|
815
|
+
declare const PACK_VERSION = "7";
|
|
808
816
|
|
|
809
817
|
declare const VERSION: string;
|
|
810
818
|
declare const NAME: string;
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __export = (target, all) => {
|
|
|
20
20
|
var PACK_VERSION;
|
|
21
21
|
var init_version = __esm({
|
|
22
22
|
"src/pack/version.ts"() {
|
|
23
|
-
PACK_VERSION = "
|
|
23
|
+
PACK_VERSION = "7";
|
|
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.10.0",
|
|
34
34
|
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.",
|
|
35
35
|
keywords: [
|
|
36
36
|
"cli",
|
|
@@ -961,6 +961,14 @@ async function readConfig(root) {
|
|
|
961
961
|
}
|
|
962
962
|
return gates.publishGate;
|
|
963
963
|
})();
|
|
964
|
+
const commitFormat = (() => {
|
|
965
|
+
if (gates.commitFormat === void 0) return void 0;
|
|
966
|
+
if (typeof gates.commitFormat !== "boolean") {
|
|
967
|
+
problems.push(`${CONFIG_FILE}: \`gates.commitFormat\` must be true or false \u2014 ignored.`);
|
|
968
|
+
return void 0;
|
|
969
|
+
}
|
|
970
|
+
return gates.commitFormat;
|
|
971
|
+
})();
|
|
964
972
|
return {
|
|
965
973
|
present: true,
|
|
966
974
|
problems,
|
|
@@ -982,6 +990,7 @@ async function readConfig(root) {
|
|
|
982
990
|
commands: readGlobList(gates.commands, "gates.commands", problems) ?? DEFAULT_CONFIG.gates.commands,
|
|
983
991
|
failOn: failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
984
992
|
publishGate,
|
|
993
|
+
commitFormat,
|
|
985
994
|
allowWriting: readGlobList(gates.allowWriting, "gates.allowWriting", problems) ?? DEFAULT_CONFIG.gates.allowWriting
|
|
986
995
|
}
|
|
987
996
|
}
|
|
@@ -1155,9 +1164,34 @@ fi
|
|
|
1155
1164
|
exit 0
|
|
1156
1165
|
`);
|
|
1157
1166
|
}
|
|
1158
|
-
function
|
|
1167
|
+
function commitFormatStep() {
|
|
1168
|
+
const types = COMMIT_TYPES.join("|");
|
|
1169
|
+
return `
|
|
1170
|
+
# Message format \u2014 <type>[(scope)][!]: <summary>. Needs nothing installed, so it always runs.
|
|
1171
|
+
subject=$(sed -e '/^#/d' -e '/^[[:space:]]*$/d' "$1" | head -1)
|
|
1172
|
+
case "$subject" in
|
|
1173
|
+
"Merge "*|"Revert "*|fixup!*|squash!*|amend!*) ;;
|
|
1174
|
+
*)
|
|
1175
|
+
if ! printf '%s' "$subject" | grep -qE '^(${types})(\\([a-z0-9._/-]+\\))?!?: .+'; then
|
|
1176
|
+
echo "\u2717 commit message: expected '<type>[(scope)][!]: <summary>'"
|
|
1177
|
+
echo " got: $subject"
|
|
1178
|
+
echo " types: ${COMMIT_TYPES.join(" ")}"
|
|
1179
|
+
echo " a '!' after the type or scope marks a breaking change"
|
|
1180
|
+
exit 1
|
|
1181
|
+
fi
|
|
1182
|
+
# Length is advice, not a block: the format is what tooling reads, the length is what a
|
|
1183
|
+
# person reads, and only one of the two can break anything.
|
|
1184
|
+
if [ "\${#subject}" -gt ${SUBJECT_ADVISORY_LENGTH} ]; then
|
|
1185
|
+
echo "\u203A note: subject is \${#subject} characters; ${SUBJECT_ADVISORY_LENGTH} or fewer reads better in git log"
|
|
1186
|
+
fi
|
|
1187
|
+
;;
|
|
1188
|
+
esac`;
|
|
1189
|
+
}
|
|
1190
|
+
function generateCommitMsgHook(gates) {
|
|
1191
|
+
const format = gates?.commitFormat === true ? `${commitFormatStep()}
|
|
1192
|
+
` : "";
|
|
1159
1193
|
return stampGenerated(`#!/usr/bin/env sh
|
|
1160
|
-
# etymd:
|
|
1194
|
+
# etymd: the commit message itself \u2014 content screen, then format.
|
|
1161
1195
|
#
|
|
1162
1196
|
# The staged-content gate reads file bytes and never sees the message, yet a message is as
|
|
1163
1197
|
# permanently published as any file. No-op where no checker is installed.
|
|
@@ -1167,7 +1201,7 @@ GATE="\${COMMIT_MSG_GATE:-$(command -v etymd || true)}"
|
|
|
1167
1201
|
if [ -x "$GATE" ]; then
|
|
1168
1202
|
"$GATE" screen --message "$1" || exit 1
|
|
1169
1203
|
fi
|
|
1170
|
-
|
|
1204
|
+
${format}
|
|
1171
1205
|
${localHookCall("commit-msg")}
|
|
1172
1206
|
|
|
1173
1207
|
exit 0
|
|
@@ -1274,12 +1308,26 @@ fi
|
|
|
1274
1308
|
exit 0
|
|
1275
1309
|
`);
|
|
1276
1310
|
}
|
|
1277
|
-
var GENERATION_MARKER_RE, CONTENT_GATE_RESOLUTION;
|
|
1311
|
+
var GENERATION_MARKER_RE, CONTENT_GATE_RESOLUTION, COMMIT_TYPES, SUBJECT_ADVISORY_LENGTH;
|
|
1278
1312
|
var init_templates = __esm({
|
|
1279
1313
|
"src/pack/templates.ts"() {
|
|
1280
1314
|
init_version();
|
|
1281
1315
|
GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
|
|
1282
1316
|
CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(command -v etymd || true)}"`;
|
|
1317
|
+
COMMIT_TYPES = [
|
|
1318
|
+
"feat",
|
|
1319
|
+
"fix",
|
|
1320
|
+
"docs",
|
|
1321
|
+
"style",
|
|
1322
|
+
"refactor",
|
|
1323
|
+
"perf",
|
|
1324
|
+
"test",
|
|
1325
|
+
"build",
|
|
1326
|
+
"ci",
|
|
1327
|
+
"chore",
|
|
1328
|
+
"revert"
|
|
1329
|
+
];
|
|
1330
|
+
SUBJECT_ADVISORY_LENGTH = 72;
|
|
1283
1331
|
}
|
|
1284
1332
|
});
|
|
1285
1333
|
|
|
@@ -1345,14 +1393,15 @@ async function planWorkflow(root, facts, opts) {
|
|
|
1345
1393
|
await add(".githooks/pre-commit", generatePreCommitHook(), "Process gate (pre-commit)", true);
|
|
1346
1394
|
await add(
|
|
1347
1395
|
".githooks/commit-msg",
|
|
1348
|
-
generateCommitMsgHook(),
|
|
1349
|
-
"
|
|
1396
|
+
generateCommitMsgHook(opts.gateConfig),
|
|
1397
|
+
"Message gate (content screen, format)",
|
|
1350
1398
|
true
|
|
1351
1399
|
);
|
|
1352
1400
|
const gateConfig = opts.gateConfig?.commands?.length ? opts.gateConfig : {
|
|
1353
1401
|
commands: derivedCommands(facts, existingPrePush ?? void 0),
|
|
1354
1402
|
failOn: opts.gateConfig?.failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
1355
1403
|
publishGate: opts.gateConfig?.publishGate,
|
|
1404
|
+
commitFormat: opts.gateConfig?.commitFormat,
|
|
1356
1405
|
allowWriting: opts.gateConfig?.allowWriting ?? []
|
|
1357
1406
|
};
|
|
1358
1407
|
await add(
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { planWorkflow } from './chunk-R6CVJ5D7.js';
|
|
4
|
+
import './chunk-YVGC3E6U.js';
|
|
5
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-5BVKFJWM.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject } from './chunk-WTD2EIMN.js';
|
|
7
|
+
import { VERSION } from './chunk-K34AVLDP.js';
|
|
8
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-C7LBUFNU.js';
|
|
9
|
-
import { PACK_VERSION } from './chunk-
|
|
9
|
+
import { PACK_VERSION } from './chunk-OIYNP6Y6.js';
|
|
10
10
|
import { git, readText } from './chunk-IV3FYVTS.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-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-WTD2EIMN.js';
|
|
4
|
+
import './chunk-K34AVLDP.js';
|
|
5
5
|
import { writeCachedFacts } from './chunk-C7LBUFNU.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-OIYNP6Y6.js';
|
|
7
7
|
import './chunk-IV3FYVTS.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/scan.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, glyph, theme, section } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import {
|
|
3
|
+
import { git, readText, pathExists } from './chunk-IV3FYVTS.js';
|
|
4
4
|
import { promises } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
@@ -17,8 +17,49 @@ function compile(raw) {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
var META = ".[]*^$+?(){}|";
|
|
21
|
+
function patternLiteral(source) {
|
|
22
|
+
let out = "";
|
|
23
|
+
let i = source.startsWith("\\b") ? 2 : 0;
|
|
24
|
+
for (; i < source.length; i++) {
|
|
25
|
+
const c = source.charAt(i);
|
|
26
|
+
if (c === "\\") {
|
|
27
|
+
const next = source[i + 1];
|
|
28
|
+
if (next === void 0) return null;
|
|
29
|
+
if (/[A-Za-z0-9]/.test(next)) return next === "b" && i + 2 === source.length ? out : null;
|
|
30
|
+
out += next;
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (META.includes(c)) return null;
|
|
35
|
+
out += c;
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function isSelfName(pattern, selves) {
|
|
40
|
+
const literal = patternLiteral(pattern.source);
|
|
41
|
+
if (literal === null || literal === "") return false;
|
|
42
|
+
return selves.some((s) => s.toLowerCase() === literal.toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
function bareName(raw) {
|
|
45
|
+
return raw.replace(/^@[^/]+\//, "").replace(/\.git$/, "");
|
|
46
|
+
}
|
|
47
|
+
async function selfIdentities(cwd) {
|
|
48
|
+
const names = /* @__PURE__ */ new Set();
|
|
49
|
+
const top = await git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
50
|
+
names.add(path.basename(top || path.resolve(cwd)));
|
|
51
|
+
const pkg = await readText(path.join(top || cwd, "package.json"));
|
|
52
|
+
if (pkg) {
|
|
53
|
+
try {
|
|
54
|
+
const name = JSON.parse(pkg).name;
|
|
55
|
+
if (typeof name === "string" && name) names.add(bareName(name));
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const remote = await git(cwd, ["remote", "get-url", "origin"]);
|
|
60
|
+
if (remote) names.add(bareName(path.basename(remote.trim())));
|
|
61
|
+
names.delete("");
|
|
62
|
+
return [...names];
|
|
22
63
|
}
|
|
23
64
|
function screenText(text, file, patterns, allow = []) {
|
|
24
65
|
const hits = [];
|
|
@@ -88,10 +129,8 @@ async function run(opts) {
|
|
|
88
129
|
}
|
|
89
130
|
const allowRaw = await readText(path.join(opts.cwd, ALLOW_FILE)) ?? await readText(path.join(opts.cwd, LEGACY_ALLOW_FILE));
|
|
90
131
|
const allow = allowRaw ? compile(allowRaw) : [];
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
const active = self ? patterns.filter((re) => !isSelfName(re, self)) : patterns;
|
|
132
|
+
const selves = await selfIdentities(opts.cwd);
|
|
133
|
+
const active = selves.length ? patterns.filter((re) => !isSelfName(re, selves)) : patterns;
|
|
95
134
|
const hits = [];
|
|
96
135
|
let scanned = 0;
|
|
97
136
|
let skippedBinary = 0;
|
|
@@ -155,4 +194,4 @@ async function run(opts) {
|
|
|
155
194
|
if (!opts.advisory) process.exitCode = 1;
|
|
156
195
|
}
|
|
157
196
|
|
|
158
|
-
export { isSelfName, readScreenable, run, screenText };
|
|
197
|
+
export { isSelfName, patternLiteral, readScreenable, run, screenText, selfIdentities };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "etymd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Keep your agent instructions true — verify AGENTS.md, CLAUDE.md, rules & skills against the actual repo, with drift caught over time and a regression ledger.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
package/dist/audit-Y35XVD6Z.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-ULM25GWG.js';
|
|
3
|
-
import './chunk-KGVRZ6WC.js';
|
|
4
|
-
import './chunk-JSUYV26K.js';
|
|
5
|
-
import './chunk-OAFYLBVG.js';
|
|
6
|
-
import './chunk-D2RI3CCT.js';
|
|
7
|
-
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-UN7IAYM6.js';
|
|
9
|
-
import './chunk-AQM4GYGN.js';
|
|
10
|
-
import './chunk-C7LBUFNU.js';
|
|
11
|
-
import './chunk-6DUDIVIC.js';
|
|
12
|
-
import './chunk-IV3FYVTS.js';
|