etymd 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +141 -34
- package/dist/{approve-EJJDCNYV.js → approve-VC4V3OPW.js} +4 -4
- package/dist/audit-LZJRJS5V.js +9 -0
- package/dist/{brief-GFR3YOZT.js → brief-TZ45MNHD.js} +4 -4
- package/dist/{chunk-3LBIBVX3.js → chunk-ANCCGYSH.js} +2 -2
- package/dist/{chunk-WNMA2FYM.js → chunk-BH37LELV.js} +2 -2
- package/dist/{chunk-B4FYQE7S.js → chunk-EUIHYD6F.js} +2 -2
- package/dist/{chunk-DJ2DDFDK.js → chunk-FZSIOCOZ.js} +1 -1
- package/dist/{chunk-3WOORFOW.js → chunk-IW6K5HLV.js} +2 -2
- package/dist/{chunk-XW5XQN23.js → chunk-KBF3SW3N.js} +1 -1
- package/dist/{chunk-XGH2DTW4.js → chunk-KMAJIQHC.js} +69 -15
- package/dist/{chunk-ASKJFMSX.js → chunk-LBRNZZZF.js} +1 -1
- package/dist/{chunk-JQHGV74L.js → chunk-LVLZLCND.js} +1 -1
- package/dist/{chunk-WQXMJUIA.js → chunk-MWIK6ML3.js} +2 -2
- package/dist/cli.js +16 -16
- package/dist/{context-5HAWI2RD.js → context-GYCLR3TG.js} +3 -3
- package/dist/doctor-6JDAHGDT.js +16 -0
- package/dist/{fleet-2GXLNLJE.js → fleet-N3G5ZIGR.js} +41 -10
- package/dist/{gates-LD276XN5.js → gates-G5BCMRRV.js} +4 -4
- package/dist/index.d.ts +9 -0
- package/dist/index.js +90 -13
- package/dist/{init-WLYWEWRQ.js → init-4CQOLFQG.js} +5 -5
- package/dist/ledger-RHQJH5EA.js +4 -0
- package/dist/{scan-62KGD2V3.js → scan-CYOOGJFG.js} +4 -4
- package/package.json +1 -1
- package/dist/audit-ATHB6FHN.js +0 -9
- package/dist/doctor-WTYZSJ5X.js +0 -16
- package/dist/ledger-ENFHLNOG.js +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 80db483: Gate integrity: detect scripts regardless of how the package manager is invoked.
|
|
8
|
+
|
|
9
|
+
Script expansion guessed the script name positionally — the token straight after the
|
|
10
|
+
manager. That only holds for `npm run x`, `yarn x` and `pnpm x`. Every other live shape
|
|
11
|
+
expanded to nothing, so the tool was never detected and the gate read as absent:
|
|
12
|
+
|
|
13
|
+
| invocation | captured as the script name |
|
|
14
|
+
| ------------------------------------ | --------------------------- |
|
|
15
|
+
| `pnpm run typecheck` | `run` |
|
|
16
|
+
| `pnpm -s typecheck` | `-s` |
|
|
17
|
+
| `pnpm -r --if-present run typecheck` | `-r` |
|
|
18
|
+
| `pnpm --filter @scope/pkg test` | `--filter` |
|
|
19
|
+
|
|
20
|
+
A pre-push hook running `pnpm run typecheck` therefore reported as having no typecheck at
|
|
21
|
+
all, and `audit` said "type checking is enforced only in CI — no local hook runs it" while
|
|
22
|
+
the hook ran it on every push.
|
|
23
|
+
|
|
24
|
+
Expansion no longer guesses position — it cannot, since the position depends on each
|
|
25
|
+
manager's own flag grammar. It scans the invocation's tokens for a name that is a known
|
|
26
|
+
script, which needs no grammar at all. Package-manager built-ins are excluded so `npm ci`
|
|
27
|
+
is never read as "runs the `ci` script"; that direction matters, because expanding it would
|
|
28
|
+
claim a gate is covered by a line that only installs dependencies. `test` and `start` stay
|
|
29
|
+
recognised — those genuinely are script shortcuts. `exec`/`dlx` end the scan, since what
|
|
30
|
+
follows is a binary, and `npx` is only scanned for `run-s`/`run-p`/`npm-run-all`.
|
|
31
|
+
|
|
32
|
+
## 0.2.1
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- fdf6b6e: Fleet sweep: recurring classes + `placement: "none"` honored.
|
|
37
|
+
|
|
38
|
+
- **Recurring classes** — the sweep report groups open findings by their engine-minted
|
|
39
|
+
class prefix and lists every class present in ≥2 projects (worst tier first). A class
|
|
40
|
+
open in one repo is that repo's problem; the same class in four is a fleet lesson —
|
|
41
|
+
the report now asks "repo bug or fleet bug?" structurally, instead of relying on a
|
|
42
|
+
human to ask it at triage time. `--json` gains a `recurringClasses` array
|
|
43
|
+
(schema still EXPERIMENTAL through 0.2.x).
|
|
44
|
+
- **`placement: "none"` suppresses `no-contract`** — a registry entry declaring its
|
|
45
|
+
contract files legitimately absent no longer gets the absence re-reported every sweep.
|
|
46
|
+
The repo-local lens can't see the registry's declaration, so the fleet view honors it
|
|
47
|
+
(a standalone `etymd audit` in such a repo still reports it — correctly, since no
|
|
48
|
+
declaration is in scope there). Tier counts summarize the filtered list.
|
|
49
|
+
|
|
3
50
|
## 0.2.0
|
|
4
51
|
|
|
5
52
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -5,50 +5,107 @@
|
|
|
5
5
|
|
|
6
6
|
**Keep your agent instructions true.**
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
You wrote rules for your AI months ago. Since then a script got renamed, a folder moved, a habit
|
|
9
|
+
changed — and the AI still trusts every word. The file never complains when it goes stale; it just
|
|
10
|
+
keeps instructing, confidently, and you live with the results.
|
|
11
|
+
|
|
12
|
+
etymd reads those instruction files and checks every claim in them against your actual project.
|
|
13
|
+
|
|
14
|
+
One command, run in your project's folder (needs Node ≥ 18.17, nothing else): `npx etymd audit`.
|
|
15
|
+
Here it is on a small demo project whose AGENTS.md still tells the AI to run `npm run start` and
|
|
16
|
+
`npm run lint` and points at a `src/legacy/` folder — none of which exist any more:
|
|
11
17
|
|
|
12
18
|
```
|
|
13
|
-
$ etymd audit
|
|
19
|
+
$ npx etymd audit
|
|
20
|
+
|
|
21
|
+
RISK AGENTS.md tells agents to run `start` — no such script exists
|
|
22
|
+
evidence AGENTS.md: `npm run start` · package.json scripts (root + workspaces)
|
|
23
|
+
why An agent following this instruction runs a command that fails — or silently skips the check it was meant to run.
|
|
24
|
+
action Update the instruction to the current script name (or restore the script).
|
|
25
|
+
effort S · confidence high · instruction-truth · instruction-truth/stale-command:AGENTS.md:start
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
evidence AGENTS.md: `pnpm dev` · package.json scripts
|
|
17
|
-
action Update the instruction to the current script name.
|
|
27
|
+
...
|
|
18
28
|
|
|
19
|
-
GAP AGENTS.md references `src/legacy
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
GAP AGENTS.md references `src/legacy` — it does not exist in the repo
|
|
30
|
+
evidence AGENTS.md · missing: src/legacy
|
|
31
|
+
why Agents navigate by these references; a dead path wastes a lookup and erodes trust in the rest of the file.
|
|
32
|
+
action Fix or remove the reference.
|
|
33
|
+
effort S · confidence medium · instruction-truth · instruction-truth/stale-path:AGENTS.md:src/legacy
|
|
22
34
|
|
|
23
|
-
since last audit:
|
|
35
|
+
since last audit: 3 still open
|
|
24
36
|
```
|
|
25
37
|
|
|
38
|
+
Each entry names something that is no longer true, shows the evidence it found, and suggests the
|
|
39
|
+
smallest fix. And it remembers between runs: a problem you fixed — or looked at and deliberately
|
|
40
|
+
waved off — never nags you twice.
|
|
41
|
+
|
|
42
|
+
That's the whole deal. It works with zero configuration and never rewrites your files — the
|
|
43
|
+
memory it keeps between runs lives in one small folder of its own (`.etymd/`). Everything below
|
|
44
|
+
the line is reference — read it when you need it.
|
|
45
|
+
|
|
26
46
|
_From Greek **étymon** — a word's true, original sense (→ etymology) — clipped to **etym.** + the
|
|
27
47
|
**.md** family it guards._
|
|
28
48
|
|
|
49
|
+
---
|
|
50
|
+
|
|
29
51
|
## Why this exists
|
|
30
52
|
|
|
31
|
-
- Instruction files are
|
|
32
|
-
Gemini, …) read `AGENTS.md` natively
|
|
33
|
-
session.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
- **Truth is a property over time, not a point in time.** Instruction files are load-bearing now —
|
|
54
|
+
coding agents (Claude Code, Codex, Cursor, Copilot, Gemini, …) read `AGENTS.md` natively, and a
|
|
55
|
+
stale claim doesn't error, it silently misleads every session. Linters for these files check a
|
|
56
|
+
moment; etymd measures _drift_ against a committed _baseline_ and remembers findings in a
|
|
57
|
+
_ledger_, so fixed things stay fixed and a returning problem is named a _regression_ (all four
|
|
58
|
+
words defined just below). It runs when you invoke it — or when a hook or CI job you wire up
|
|
59
|
+
does.
|
|
38
60
|
- **Honesty is structural.** Every report declares what it could NOT see — CI jobs inherited from
|
|
39
|
-
unreadable org templates, server-side quality-gate thresholds, skipped
|
|
40
|
-
ever dressed as a fact.
|
|
61
|
+
unreadable org templates, server-side quality-gate thresholds, heuristics it skipped. No guess
|
|
62
|
+
is ever dressed as a fact.
|
|
63
|
+
- **Precision over recall.** A false "your file is lying" costs more trust than a missed lie, so
|
|
64
|
+
the checks filter aggressively — and every class of claim they skip is counted and disclosed,
|
|
65
|
+
never silently dropped.
|
|
66
|
+
|
|
67
|
+
## The words etymd uses
|
|
68
|
+
|
|
69
|
+
| The docs say | It means |
|
|
70
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| **finding** | One verified problem, ranked **RISK** (an agent acting on this does the wrong thing) → **GAP** (a dead reference or missing safeguard) → **POLISH** (worth tidying). Within a tier, cheapest fix first. |
|
|
72
|
+
| **claim** | Anything an instruction file asserts about the project that can be checked: a command it tells agents to run, a path it points at, a rule about tooling. |
|
|
73
|
+
| **lens** | One self-contained checker for one kind of truth (are the commands real? is the state doc current?). An audit is all lenses run together. |
|
|
74
|
+
| **baseline** | A snapshot of the repo's checkable facts that you approved and committed. Drift is measured against this — not against whatever yesterday's cache happened to hold. |
|
|
75
|
+
| **drift** | The distance between the baseline and the repo today: what existed at approval and is now gone, renamed, or moved. |
|
|
76
|
+
| **ledger** | The committed memory of findings — each one's status and history. A finding that was fixed and comes back is a **regression**, and the report names it as one rather than re-introducing it as new. |
|
|
77
|
+
| **dismiss vs accept** | Two deliberate ways to close a finding. _Dismiss_ = "not a real problem, here's why" — it never resurfaces unless it regresses. _Accept_ = "true, and we're living with it" — kept in the ledger, out of the report. |
|
|
78
|
+
| **gate** | A check that can actually fail a change: a git hook, a CI job. A job marked `allow_failure` is advisory, not a gate — a check that cannot fail anything is an opinion. |
|
|
79
|
+
| **disclosure** | The report's account of what it could not see or refused to guess about. Every report carries one; a clean result with no disclosures would be the exact dishonesty this tool exists to catch. |
|
|
80
|
+
| **fleet** | Your fleet of **repositories** — every repo you registered in one manifest, swept by one command. Not a fleet of AI agents. |
|
|
81
|
+
| **context economy** | The words your instruction files load into every single session, measured against a budget. Context is a cost you pay per conversation; leaner files are cheaper and better obeyed. |
|
|
82
|
+
|
|
83
|
+
### Things that surprise first-run users
|
|
84
|
+
|
|
85
|
+
- **"It missed an obvious stale command."** Without `node_modules` installed, command claims are
|
|
86
|
+
skipped — and the skip is disclosed in the report. A command might resolve to an installed
|
|
87
|
+
binary, and etymd would rather say "couldn't check" than accuse an honest file. Install
|
|
88
|
+
dependencies and run again.
|
|
89
|
+
- **`etymd audit` works without `etymd init`.** You only lose drift-over-time measurement — with
|
|
90
|
+
no committed baseline, there is nothing to measure drift against. Everything else runs.
|
|
91
|
+
- **`etymd init` never overwrites an existing `AGENTS.md`.** It scaffolds a minimal one only if
|
|
92
|
+
you have none. The feared overwrite path simply does not exist.
|
|
93
|
+
|
|
94
|
+
---
|
|
41
95
|
|
|
42
96
|
## Quick start
|
|
43
97
|
|
|
44
98
|
```bash
|
|
45
99
|
cd your-project
|
|
46
|
-
npx etymd init # approve the baseline (+ scaffold AGENTS.md only if you have none)
|
|
47
100
|
npx etymd audit # verify every instruction claim against the repo
|
|
101
|
+
npx etymd init # opt in to drift: approve the baseline (+ scaffold AGENTS.md only if you have none)
|
|
48
102
|
npx etymd audit --fail-on risk # the CI gate
|
|
49
103
|
```
|
|
50
104
|
|
|
51
|
-
|
|
105
|
+
`audit` needs no setup — without `init` you get the full findings report and lose only drift
|
|
106
|
+
measured against a committed baseline. `init` is that opt-in, not a prerequisite, and it never
|
|
107
|
+
overwrites an existing `AGENTS.md`. On npm since v0.1.0 — `npx etymd` just works. To wire the
|
|
108
|
+
gate into a pipeline, see [In CI](#in-ci).
|
|
52
109
|
|
|
53
110
|
## What it checks
|
|
54
111
|
|
|
@@ -122,6 +179,10 @@ personal and employer repos. See [the fleet manifest](#the-fleet-manifest-experi
|
|
|
122
179
|
The committed files are written to be publishable: the baseline records `"."` as its scan root, never
|
|
123
180
|
your absolute machine path. Only the gitignored cache keeps the real one.
|
|
124
181
|
|
|
182
|
+
Formatter interop: if your Prettier (or similar formatter) checks JSON, add `.etymd` to
|
|
183
|
+
`.prettierignore` — etymd writes its own JSON style, and a format gate fighting the ledger is
|
|
184
|
+
noise (this repo does exactly that).
|
|
185
|
+
|
|
125
186
|
### `.etymd/config.json` (optional)
|
|
126
187
|
|
|
127
188
|
Every key is optional; omit the file entirely and the defaults below apply.
|
|
@@ -145,16 +206,55 @@ Every key is optional; omit the file entirely and the defaults below apply.
|
|
|
145
206
|
Globs are repo-relative: `*` within a path segment, `**` across segments, `?` one character. A
|
|
146
207
|
pattern with no wildcard is a **path prefix**, so `.claude/skills` covers everything beneath it.
|
|
147
208
|
|
|
148
|
-
Narrowing an audit can hide findings, so etymd never lets it happen quietly
|
|
149
|
-
is counted and named in the lens disclosures**, and a config
|
|
150
|
-
disclosure rather than silently falling back to defaults.
|
|
209
|
+
Narrowing an audit can hide findings, so etymd never lets it happen quietly. Honesty is
|
|
210
|
+
structural: **every excluded file is counted and named in the lens disclosures**, and a config
|
|
211
|
+
that fails to parse is reported as a disclosure rather than silently falling back to defaults.
|
|
212
|
+
|
|
213
|
+
## In CI
|
|
214
|
+
|
|
215
|
+
The gate is one command:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npx etymd audit --no-ledger --fail-on risk
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Exit-code contract: without `--fail-on`, `audit` reports and exits 0 no matter what it found.
|
|
222
|
+
With `--fail-on <tier>` (`risk` | `gap` | `polish`) it exits non-zero when any finding at or
|
|
223
|
+
above that tier exists — so `--fail-on risk` blocks on risks only, `--fail-on polish` blocks on
|
|
224
|
+
everything. `--no-ledger` keeps the CI run read-only: the throwaway checkout is never written.
|
|
225
|
+
|
|
226
|
+
The ledger and baseline are not CI by-products — they are **committed, reviewable state**,
|
|
227
|
+
updated locally and read in CI. A dismissal (with its reason), an accepted finding, a baseline
|
|
228
|
+
refresh after an intentional restructure: each lands in `.etymd/` and shows up in the pull
|
|
229
|
+
request diff like any other change. Keep `.etymd` out of your formatter's reach (see
|
|
230
|
+
[the files etymd keeps](#the-files-etymd-keeps)).
|
|
231
|
+
|
|
232
|
+
A check that runs only in CI is itself a finding: the failure surfaces after the agent finished.
|
|
233
|
+
`etymd gates` installs the local pre-commit / pre-push mirror built from your own check scripts,
|
|
234
|
+
and the `gate-integrity` lens flags whatever still runs in CI alone.
|
|
235
|
+
|
|
236
|
+
Modeled on this repo's own workflow (etymd guards its own instructions with etymd — its CI runs
|
|
237
|
+
the same gate against its own freshly built CLI):
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
steps:
|
|
241
|
+
- uses: actions/checkout@v4
|
|
242
|
+
- uses: actions/setup-node@v4
|
|
243
|
+
with:
|
|
244
|
+
node-version: 20
|
|
245
|
+
cache: npm
|
|
246
|
+
- run: npm ci
|
|
247
|
+
- run: npx etymd audit --no-ledger --fail-on risk
|
|
248
|
+
```
|
|
151
249
|
|
|
152
250
|
## The fleet manifest (EXPERIMENTAL)
|
|
153
251
|
|
|
154
252
|
`etymd fleet` extends the one objective across every repository you work in — your fleet of
|
|
155
253
|
**repositories**, not a fleet of agents. The manifest, `registry.json`, is itself an
|
|
156
|
-
agent-context file: claims about your fleet (what exists, where, under which profile
|
|
157
|
-
|
|
254
|
+
agent-context file: claims about your fleet (what exists, where, under which **profile** — the
|
|
255
|
+
side of the wall an entry belongs to, `personal` or `corp`; the **wall** is the placement
|
|
256
|
+
boundary between personal and employer content that the sweep polices). It rots like any
|
|
257
|
+
AGENTS.md does, and `etymd fleet` keeps it true. Design record:
|
|
158
258
|
[`docs/design/004-fleet-truth-guard.md`](docs/design/004-fleet-truth-guard.md). Both the
|
|
159
259
|
registry schema and the fleet `--json` schema are **experimental through 0.2.x**.
|
|
160
260
|
|
|
@@ -194,7 +294,7 @@ Two files beside each other — the split is the privacy model:
|
|
|
194
294
|
|
|
195
295
|
```jsonc
|
|
196
296
|
{
|
|
197
|
-
"machineProfile": "corp", // "personal" resolves corp entries disclosed-absent
|
|
297
|
+
"machineProfile": "corp", // which profile this machine resolves; "personal" resolves corp entries disclosed-absent
|
|
198
298
|
"root": "~/projects", // optional per-machine root override
|
|
199
299
|
"dirs": { "c-one": "~/projects/real-corp-dir" },
|
|
200
300
|
"labels": { "c-one": "real-corp-dir" },
|
|
@@ -212,17 +312,24 @@ How the sweep behaves:
|
|
|
212
312
|
- **Deltas.** Each sweep compares against `last.fleet.json` stored beside the manifest and
|
|
213
313
|
renders `Δ +new −resolved` per project. Add `*.fleet.json` to the manifest repo's
|
|
214
314
|
`.gitignore` — sweep output is local-only and never tracked.
|
|
315
|
+
- **Recurring classes.** A finding class open in two or more projects renders as its own section
|
|
316
|
+
of class-fix candidates (worst tier first) — the sweep asking "repo bug or fleet bug?", a
|
|
317
|
+
fleet-level lesson no per-repo audit can see. The sweep only groups; the class vocabulary is
|
|
318
|
+
minted by the engine's lenses.
|
|
319
|
+
- **Declared absence is honored.** An entry whose contract declares `"placement": "none"` states
|
|
320
|
+
that instruction files are legitimately absent in that project — the sweep drops its
|
|
321
|
+
missing-contract finding instead of re-reporting a decision every run. Absence disclosed on
|
|
322
|
+
purpose is a state, not a gap.
|
|
215
323
|
- **Wall checks.** Corp contract files found inside a corp worktree, unregistered checkouts
|
|
216
324
|
under the fleet root whose remotes match `corpHosts`, tracked `/Users/` paths in the manifest
|
|
217
|
-
repo, private needles (labels, dir names, hosts)
|
|
218
|
-
corp-host commit emails on personal entries — each
|
|
219
|
-
is disclosed.
|
|
325
|
+
repo, private **needles** — the identifiers the local file holds (labels, dir names, hosts) —
|
|
326
|
+
inside `trust: "public-repo"` entries, and corp-host commit emails on personal entries — each
|
|
327
|
+
a risk finding; each check that cannot run is disclosed.
|
|
220
328
|
- **No global pointer.** `--manifest` is required unless the cwd holds `registry.json` — there
|
|
221
329
|
is deliberately no env var and no home-directory pointer.
|
|
222
330
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
noise (this repo does exactly that).
|
|
331
|
+
Formatter interop for the `.etymd` state the sweep resolves: same rule as everywhere — see
|
|
332
|
+
[the files etymd keeps](#the-files-etymd-keeps).
|
|
226
333
|
|
|
227
334
|
## Programmatic use
|
|
228
335
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { scanProject, PACK_VERSION } from './chunk-
|
|
3
|
-
import { VERSION } from './chunk-
|
|
4
|
-
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-
|
|
5
|
-
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-
|
|
2
|
+
import { scanProject, PACK_VERSION } from './chunk-BH37LELV.js';
|
|
3
|
+
import { VERSION } from './chunk-LVLZLCND.js';
|
|
4
|
+
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-FZSIOCOZ.js';
|
|
5
|
+
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-LBRNZZZF.js';
|
|
6
6
|
|
|
7
7
|
// src/commands/approve.ts
|
|
8
8
|
async function run(opts) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run } from './chunk-IW6K5HLV.js';
|
|
3
|
+
import './chunk-KMAJIQHC.js';
|
|
4
|
+
import './chunk-KBF3SW3N.js';
|
|
5
|
+
import './chunk-ANCCGYSH.js';
|
|
6
|
+
import './chunk-BH37LELV.js';
|
|
7
|
+
import './chunk-LVLZLCND.js';
|
|
8
|
+
import './chunk-FZSIOCOZ.js';
|
|
9
|
+
import './chunk-LBRNZZZF.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { scanProject } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { ETYMD_DIR } from './chunk-
|
|
5
|
-
import { print, theme } from './chunk-
|
|
2
|
+
import { scanProject } from './chunk-BH37LELV.js';
|
|
3
|
+
import './chunk-LVLZLCND.js';
|
|
4
|
+
import { ETYMD_DIR } from './chunk-FZSIOCOZ.js';
|
|
5
|
+
import { print, theme } from './chunk-LBRNZZZF.js';
|
|
6
6
|
import { promises } from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ETYMD_DIR } from './chunk-
|
|
3
|
-
import { pathExists, readText, wordCount, approxTokens } from './chunk-
|
|
2
|
+
import { ETYMD_DIR } from './chunk-FZSIOCOZ.js';
|
|
3
|
+
import { pathExists, readText, wordCount, approxTokens } from './chunk-LBRNZZZF.js';
|
|
4
4
|
import path2 from 'node:path';
|
|
5
5
|
import { promises } from 'node:fs';
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
3
|
-
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-LVLZLCND.js';
|
|
3
|
+
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-LBRNZZZF.js';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promises } from 'node:fs';
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readLedger, resolveEntry, writeLedger } from './chunk-
|
|
3
|
-
import { print, renderLedger, glyph, theme } from './chunk-
|
|
2
|
+
import { readLedger, resolveEntry, writeLedger } from './chunk-KBF3SW3N.js';
|
|
3
|
+
import { print, renderLedger, glyph, theme } from './chunk-LBRNZZZF.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/ledger.ts
|
|
6
6
|
async function list(opts) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
3
|
-
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-KMAJIQHC.js';
|
|
3
|
+
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-LBRNZZZF.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
6
6
|
async function run(opts) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-
|
|
3
|
-
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE, measureContext } from './chunk-
|
|
4
|
-
import { scanProject, PACK_VERSION, expandFileGlobs } from './chunk-
|
|
5
|
-
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-
|
|
6
|
-
import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath } from './chunk-
|
|
2
|
+
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-KBF3SW3N.js';
|
|
3
|
+
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE, measureContext } from './chunk-ANCCGYSH.js';
|
|
4
|
+
import { scanProject, PACK_VERSION, expandFileGlobs } from './chunk-BH37LELV.js';
|
|
5
|
+
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-FZSIOCOZ.js';
|
|
6
|
+
import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath } from './chunk-LBRNZZZF.js';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import YAML from 'yaml';
|
|
9
9
|
import { promises } from 'node:fs';
|
|
@@ -91,24 +91,78 @@ var TOOL_MATCHERS = [
|
|
|
91
91
|
["lint", /\beslint\b/],
|
|
92
92
|
["test", /\bjest\b|\bvitest\b|react-scripts test|\bnx\b[^&|;]*\btest\b/]
|
|
93
93
|
];
|
|
94
|
+
var PM_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
95
|
+
"run",
|
|
96
|
+
"run-script",
|
|
97
|
+
"install",
|
|
98
|
+
"i",
|
|
99
|
+
"ci",
|
|
100
|
+
"add",
|
|
101
|
+
"remove",
|
|
102
|
+
"rm",
|
|
103
|
+
"uninstall",
|
|
104
|
+
"un",
|
|
105
|
+
"update",
|
|
106
|
+
"up",
|
|
107
|
+
"upgrade",
|
|
108
|
+
"create",
|
|
109
|
+
"init",
|
|
110
|
+
"audit",
|
|
111
|
+
"publish",
|
|
112
|
+
"pack",
|
|
113
|
+
"link",
|
|
114
|
+
"unlink",
|
|
115
|
+
"why",
|
|
116
|
+
"list",
|
|
117
|
+
"ls",
|
|
118
|
+
"outdated",
|
|
119
|
+
"prune",
|
|
120
|
+
"dedupe",
|
|
121
|
+
"import",
|
|
122
|
+
"rebuild",
|
|
123
|
+
"root",
|
|
124
|
+
"config",
|
|
125
|
+
"cache",
|
|
126
|
+
"login",
|
|
127
|
+
"logout",
|
|
128
|
+
"whoami",
|
|
129
|
+
"version",
|
|
130
|
+
"workspace",
|
|
131
|
+
"workspaces",
|
|
132
|
+
"store",
|
|
133
|
+
"fund",
|
|
134
|
+
"deprecate",
|
|
135
|
+
"access",
|
|
136
|
+
"bin",
|
|
137
|
+
"env",
|
|
138
|
+
"help"
|
|
139
|
+
]);
|
|
140
|
+
var PM_BINARY_RUNNERS = /* @__PURE__ */ new Set(["exec", "dlx", "x"]);
|
|
141
|
+
var NPX_SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["run-s", "run-p", "npm-run-all"]);
|
|
94
142
|
function expandScriptRefs(command, scripts, depth = 2) {
|
|
95
143
|
if (depth <= 0) return command;
|
|
96
144
|
let expanded = command;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
145
|
+
const seen = /* @__PURE__ */ new Set();
|
|
146
|
+
const add = (name) => {
|
|
147
|
+
if (seen.has(name)) return;
|
|
148
|
+
seen.add(name);
|
|
101
149
|
const body = scripts[name];
|
|
102
150
|
if (body) expanded += `
|
|
103
151
|
${expandScriptRefs(body, scripts, depth - 1)}`;
|
|
152
|
+
};
|
|
153
|
+
for (const m of command.matchAll(/\b(yarn|pnpm|npm|bun|npx)\b([^&|;]*)/g)) {
|
|
154
|
+
const manager = m[1];
|
|
155
|
+
const tokens = (m[2] ?? "").split(/\s+/).map((t) => t.replace(/["']/g, "")).filter(Boolean);
|
|
156
|
+
if (manager === "npx" && !(tokens[0] && NPX_SCRIPT_RUNNERS.has(tokens[0]))) continue;
|
|
157
|
+
for (const token of tokens) {
|
|
158
|
+
if (token.startsWith("-")) continue;
|
|
159
|
+
if (PM_BINARY_RUNNERS.has(token)) break;
|
|
160
|
+
if (PM_SUBCOMMANDS.has(token) || NPX_SCRIPT_RUNNERS.has(token)) continue;
|
|
161
|
+
add(token);
|
|
162
|
+
}
|
|
104
163
|
}
|
|
105
164
|
if (/npm-run-all|run-s|run-p/.test(command)) {
|
|
106
|
-
for (const token of command.split(/\s+/))
|
|
107
|
-
const clean = token.replace(/["']/g, "");
|
|
108
|
-
const body = scripts[clean];
|
|
109
|
-
if (body) expanded += `
|
|
110
|
-
${expandScriptRefs(body, scripts, depth - 1)}`;
|
|
111
|
-
}
|
|
165
|
+
for (const token of command.split(/\s+/)) add(token.replace(/["']/g, ""));
|
|
112
166
|
}
|
|
113
167
|
return expanded;
|
|
114
168
|
}
|
|
@@ -356,4 +356,4 @@ function renderLedger(ledger) {
|
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
export { approxTokens, git, glyph, isCiEnvironment, isDirectory, matchesAnyGlob, normalizeRelPath, pathExists, print, readJson, readText, renderBaselineDrift, renderContext, renderFacts, renderFindings, renderFleetNotes, renderFleetRows, renderLedger, renderLedgerDiff, renderLensCoverage, renderPlan, section, theme, wordCount };
|
|
359
|
+
export { TIER_BADGE, approxTokens, git, glyph, isCiEnvironment, isDirectory, matchesAnyGlob, normalizeRelPath, pathExists, print, readJson, readText, renderBaselineDrift, renderContext, renderFacts, renderFindings, renderFleetNotes, renderFleetRows, renderLedger, renderLedgerDiff, renderLensCoverage, renderPlan, section, theme, wordCount };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { PACK_VERSION } from './chunk-
|
|
3
|
-
import { pathExists, readText } from './chunk-
|
|
2
|
+
import { PACK_VERSION } from './chunk-BH37LELV.js';
|
|
3
|
+
import { pathExists, readText } from './chunk-LBRNZZZF.js';
|
|
4
4
|
import { promises } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
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-LVLZLCND.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-LZJRJS5V.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-4CQOLFQG.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,49 +52,49 @@ 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-VC4V3OPW.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-CYOOGJFG.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-6JDAHGDT.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-GYCLR3TG.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-TZ45MNHD.js');
|
|
80
80
|
await run({ cwd: resolveCwd(cmd), human: opts.human });
|
|
81
81
|
})
|
|
82
82
|
);
|
|
83
83
|
program.command("ledger").description("List every tracked finding grouped by status (open \xB7 accepted \xB7 dismissed \xB7 \u2026)").option("--json", "print the raw ledger as JSON").action(
|
|
84
84
|
(opts, cmd) => action(async () => {
|
|
85
|
-
const { list } = await import('./ledger-
|
|
85
|
+
const { list } = await import('./ledger-RHQJH5EA.js');
|
|
86
86
|
await list({ cwd: resolveCwd(cmd), json: opts.json });
|
|
87
87
|
})
|
|
88
88
|
);
|
|
89
89
|
program.command("dismiss").argument("<finding-id>", "the finding id (from `etymd audit`) to dismiss").description("Dismiss a finding as a false positive / not applicable (hidden from future audits)").requiredOption("--reason <text>", "why it is dismissed \u2014 recorded so the decision survives").action(
|
|
90
90
|
(id, opts, cmd) => action(async () => {
|
|
91
|
-
const { dismiss } = await import('./ledger-
|
|
91
|
+
const { dismiss } = await import('./ledger-RHQJH5EA.js');
|
|
92
92
|
await dismiss({ cwd: resolveCwd(cmd), id, reason: opts.reason });
|
|
93
93
|
})
|
|
94
94
|
);
|
|
95
95
|
program.command("accept").argument("<finding-id>", "the finding id (from `etymd audit`) to accept").description("Accept a real finding as a known trade-off (hidden from future audits)").option("--reason <text>", "optional note on why the trade-off is accepted").action(
|
|
96
96
|
(id, opts, cmd) => action(async () => {
|
|
97
|
-
const { accept } = await import('./ledger-
|
|
97
|
+
const { accept } = await import('./ledger-RHQJH5EA.js');
|
|
98
98
|
await accept({ cwd: resolveCwd(cmd), id, reason: opts.reason });
|
|
99
99
|
})
|
|
100
100
|
);
|
|
@@ -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-N3G5ZIGR.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-N3G5ZIGR.js');
|
|
130
130
|
const opts = cmd.optsWithGlobals();
|
|
131
131
|
await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
|
|
132
132
|
})
|
|
@@ -135,7 +135,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
135
135
|
"Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
|
|
136
136
|
).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(
|
|
137
137
|
(name, id, _opts, cmd) => action(async () => {
|
|
138
|
-
const { dismiss } = await import('./fleet-
|
|
138
|
+
const { dismiss } = await import('./fleet-N3G5ZIGR.js');
|
|
139
139
|
const opts = cmd.optsWithGlobals();
|
|
140
140
|
await dismiss({
|
|
141
141
|
cwd: resolveCwd(cmd),
|
|
@@ -150,14 +150,14 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
150
150
|
"Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
|
|
151
151
|
).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(
|
|
152
152
|
(name, id, _opts, cmd) => action(async () => {
|
|
153
|
-
const { accept } = await import('./fleet-
|
|
153
|
+
const { accept } = await import('./fleet-N3G5ZIGR.js');
|
|
154
154
|
const opts = cmd.optsWithGlobals();
|
|
155
155
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
156
156
|
})
|
|
157
157
|
);
|
|
158
158
|
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(
|
|
159
159
|
(opts, cmd) => action(async () => {
|
|
160
|
-
const { run } = await import('./gates-
|
|
160
|
+
const { run } = await import('./gates-G5BCMRRV.js');
|
|
161
161
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
162
162
|
})
|
|
163
163
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readConfig, measureContext } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { print, renderContext } from './chunk-
|
|
2
|
+
import { readConfig, measureContext } from './chunk-ANCCGYSH.js';
|
|
3
|
+
import './chunk-FZSIOCOZ.js';
|
|
4
|
+
import { print, renderContext } from './chunk-LBRNZZZF.js';
|
|
5
5
|
|
|
6
6
|
// src/commands/context.ts
|
|
7
7
|
async function run(opts) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { run } from './chunk-IW6K5HLV.js';
|
|
3
|
+
import './chunk-KMAJIQHC.js';
|
|
4
|
+
import './chunk-KBF3SW3N.js';
|
|
5
|
+
import './chunk-ANCCGYSH.js';
|
|
6
|
+
import './chunk-BH37LELV.js';
|
|
7
|
+
import './chunk-LVLZLCND.js';
|
|
8
|
+
import './chunk-FZSIOCOZ.js';
|
|
9
|
+
import './chunk-LBRNZZZF.js';
|
|
10
|
+
|
|
11
|
+
// src/commands/doctor.ts
|
|
12
|
+
async function run2(opts) {
|
|
13
|
+
await run({ cwd: opts.cwd, json: opts.json, truth: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { run2 as run };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { dismiss, accept } from './chunk-
|
|
3
|
-
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-
|
|
4
|
-
import { readLedger } from './chunk-
|
|
5
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import { ETYMD_DIR } from './chunk-
|
|
9
|
-
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, glyph, pathExists, readText, isDirectory, git } from './chunk-
|
|
2
|
+
import { dismiss, accept } from './chunk-EUIHYD6F.js';
|
|
3
|
+
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-KMAJIQHC.js';
|
|
4
|
+
import { readLedger } from './chunk-KBF3SW3N.js';
|
|
5
|
+
import { DEFAULT_CONFIG } from './chunk-ANCCGYSH.js';
|
|
6
|
+
import './chunk-BH37LELV.js';
|
|
7
|
+
import './chunk-LVLZLCND.js';
|
|
8
|
+
import { ETYMD_DIR } from './chunk-FZSIOCOZ.js';
|
|
9
|
+
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph, pathExists, readText, isDirectory, git } from './chunk-LBRNZZZF.js';
|
|
10
10
|
import { promises } from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import os from 'node:os';
|
|
@@ -246,6 +246,25 @@ var pExecFile = promisify(execFile);
|
|
|
246
246
|
var FLEET_LENS = "fleet-manifest";
|
|
247
247
|
var FLEET_JSON_SCHEMA = "fleet-experimental-0.2";
|
|
248
248
|
var CORP_WALL_ARTIFACTS = ["PROJECT_CONTEXT.md", "DECISIONS.md"];
|
|
249
|
+
function recurringClasses(projects) {
|
|
250
|
+
const byClass = /* @__PURE__ */ new Map();
|
|
251
|
+
for (const p of projects) {
|
|
252
|
+
for (const f of p.findings) {
|
|
253
|
+
const classId = f.id.split(":")[0] ?? f.id;
|
|
254
|
+
const entry = byClass.get(classId);
|
|
255
|
+
if (entry) {
|
|
256
|
+
entry.projects.add(p.name);
|
|
257
|
+
if (TIER_ORDER.indexOf(f.tier) < TIER_ORDER.indexOf(entry.tier)) entry.tier = f.tier;
|
|
258
|
+
} else {
|
|
259
|
+
byClass.set(classId, { tier: f.tier, projects: /* @__PURE__ */ new Set([p.name]) });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return [...byClass.entries()].filter(([, v]) => v.projects.size >= 2).map(([classId, v]) => ({ classId, tier: v.tier, projects: [...v.projects].sort() })).sort(
|
|
264
|
+
(a, b) => TIER_ORDER.indexOf(a.tier) - TIER_ORDER.indexOf(b.tier) || b.projects.length - a.projects.length || a.classId.localeCompare(b.classId)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
var TIER_ORDER = ["risk", "gap", "polish"];
|
|
249
268
|
function finding(id, tier, claim, evidence, why, action) {
|
|
250
269
|
return {
|
|
251
270
|
id,
|
|
@@ -695,7 +714,10 @@ async function sweepEntry(manifest, entry, opts) {
|
|
|
695
714
|
upstreamRemote
|
|
696
715
|
});
|
|
697
716
|
project.findings = result.findings;
|
|
698
|
-
|
|
717
|
+
if (entry.contract.placement === "none") {
|
|
718
|
+
project.findings = project.findings.filter((f) => !f.id.endsWith("/no-contract"));
|
|
719
|
+
}
|
|
720
|
+
for (const f of project.findings) project.counts[f.tier] += 1;
|
|
699
721
|
project.stateAgeDays = stateAgeDays(result.facts);
|
|
700
722
|
project.staleAfterDays = result.config.config.state.staleAfterDays;
|
|
701
723
|
for (const [key, value] of Object.entries(entry.contract)) {
|
|
@@ -737,7 +759,8 @@ async function sweepFleet(manifest, opts = {}) {
|
|
|
737
759
|
wall: wall.findings,
|
|
738
760
|
wallDisclosures: wall.disclosures,
|
|
739
761
|
outOfScope: projects.filter((p) => !p.resolvedRoot || p.unresolved).map((p) => p.name),
|
|
740
|
-
problems: manifest.problems.map((p) => `${p.file}: ${p.detail}`)
|
|
762
|
+
problems: manifest.problems.map((p) => `${p.file}: ${p.detail}`),
|
|
763
|
+
recurringClasses: recurringClasses(projects)
|
|
741
764
|
};
|
|
742
765
|
}
|
|
743
766
|
async function fleetLedgerTarget(manifest, name) {
|
|
@@ -906,6 +929,14 @@ function renderSweep(result, previous, delta, wallNew) {
|
|
|
906
929
|
section(`New or risk findings ${theme.dim(`(${detail.length})`)}`);
|
|
907
930
|
renderFindings(detail);
|
|
908
931
|
}
|
|
932
|
+
if (result.recurringClasses.length) {
|
|
933
|
+
section(`Recurring classes ${theme.dim("(open in \u22652 projects \u2014 class-fix candidates)")}`);
|
|
934
|
+
for (const rc of result.recurringClasses) {
|
|
935
|
+
print(
|
|
936
|
+
` ${TIER_BADGE[rc.tier]} ${rc.classId} ${theme.dim(`\u2014 ${rc.projects.length}\xD7: ${rc.projects.join(", ")}`)}`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
909
940
|
section("Fleet wall");
|
|
910
941
|
if (!result.wall.length) print(` ${glyph.ok} ${theme.dim("clean")}`);
|
|
911
942
|
else
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { planWorkflow, applyFiles } from './chunk-
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import { section, print, glyph, theme, renderPlan, git } from './chunk-
|
|
2
|
+
import { planWorkflow, applyFiles } from './chunk-MWIK6ML3.js';
|
|
3
|
+
import { scanProject } from './chunk-BH37LELV.js';
|
|
4
|
+
import './chunk-LVLZLCND.js';
|
|
5
|
+
import { section, print, glyph, theme, renderPlan, git } from './chunk-LBRNZZZF.js';
|
|
6
6
|
import { confirm, isCancel, cancel } from '@clack/prompts';
|
|
7
7
|
|
|
8
8
|
async function run(opts) {
|
package/dist/index.d.ts
CHANGED
|
@@ -499,6 +499,15 @@ interface FleetSweepResult {
|
|
|
499
499
|
outOfScope: string[];
|
|
500
500
|
/** Manifest problems, verbatim — disclosed by every renderer, never defaulted away. */
|
|
501
501
|
problems: string[];
|
|
502
|
+
/** Defect classes open in ≥2 projects — the class-fix candidates (see recurringClasses()). */
|
|
503
|
+
recurringClasses: RecurringClass[];
|
|
504
|
+
}
|
|
505
|
+
interface RecurringClass {
|
|
506
|
+
/** The `<lens>/<class>` id prefix — minted by the engine's lenses, identical across repos. */
|
|
507
|
+
classId: string;
|
|
508
|
+
tier: FindingTier;
|
|
509
|
+
/** Project names (corp entries by alias) where the class is currently open. */
|
|
510
|
+
projects: string[];
|
|
502
511
|
}
|
|
503
512
|
/**
|
|
504
513
|
* Where a corp entry's audit state persists: beside the manifest, never in the worktree.
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var PACK_VERSION = "2";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "etymd",
|
|
16
|
-
version: "0.2.
|
|
16
|
+
version: "0.2.2"};
|
|
17
17
|
|
|
18
18
|
// src/version.ts
|
|
19
19
|
var VERSION = package_default.version;
|
|
@@ -1076,24 +1076,78 @@ var TOOL_MATCHERS = [
|
|
|
1076
1076
|
["lint", /\beslint\b/],
|
|
1077
1077
|
["test", /\bjest\b|\bvitest\b|react-scripts test|\bnx\b[^&|;]*\btest\b/]
|
|
1078
1078
|
];
|
|
1079
|
+
var PM_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
1080
|
+
"run",
|
|
1081
|
+
"run-script",
|
|
1082
|
+
"install",
|
|
1083
|
+
"i",
|
|
1084
|
+
"ci",
|
|
1085
|
+
"add",
|
|
1086
|
+
"remove",
|
|
1087
|
+
"rm",
|
|
1088
|
+
"uninstall",
|
|
1089
|
+
"un",
|
|
1090
|
+
"update",
|
|
1091
|
+
"up",
|
|
1092
|
+
"upgrade",
|
|
1093
|
+
"create",
|
|
1094
|
+
"init",
|
|
1095
|
+
"audit",
|
|
1096
|
+
"publish",
|
|
1097
|
+
"pack",
|
|
1098
|
+
"link",
|
|
1099
|
+
"unlink",
|
|
1100
|
+
"why",
|
|
1101
|
+
"list",
|
|
1102
|
+
"ls",
|
|
1103
|
+
"outdated",
|
|
1104
|
+
"prune",
|
|
1105
|
+
"dedupe",
|
|
1106
|
+
"import",
|
|
1107
|
+
"rebuild",
|
|
1108
|
+
"root",
|
|
1109
|
+
"config",
|
|
1110
|
+
"cache",
|
|
1111
|
+
"login",
|
|
1112
|
+
"logout",
|
|
1113
|
+
"whoami",
|
|
1114
|
+
"version",
|
|
1115
|
+
"workspace",
|
|
1116
|
+
"workspaces",
|
|
1117
|
+
"store",
|
|
1118
|
+
"fund",
|
|
1119
|
+
"deprecate",
|
|
1120
|
+
"access",
|
|
1121
|
+
"bin",
|
|
1122
|
+
"env",
|
|
1123
|
+
"help"
|
|
1124
|
+
]);
|
|
1125
|
+
var PM_BINARY_RUNNERS = /* @__PURE__ */ new Set(["exec", "dlx", "x"]);
|
|
1126
|
+
var NPX_SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["run-s", "run-p", "npm-run-all"]);
|
|
1079
1127
|
function expandScriptRefs(command, scripts, depth = 2) {
|
|
1080
1128
|
if (depth <= 0) return command;
|
|
1081
1129
|
let expanded = command;
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1130
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1131
|
+
const add = (name) => {
|
|
1132
|
+
if (seen.has(name)) return;
|
|
1133
|
+
seen.add(name);
|
|
1086
1134
|
const body = scripts[name];
|
|
1087
1135
|
if (body) expanded += `
|
|
1088
1136
|
${expandScriptRefs(body, scripts, depth - 1)}`;
|
|
1137
|
+
};
|
|
1138
|
+
for (const m of command.matchAll(/\b(yarn|pnpm|npm|bun|npx)\b([^&|;]*)/g)) {
|
|
1139
|
+
const manager = m[1];
|
|
1140
|
+
const tokens = (m[2] ?? "").split(/\s+/).map((t) => t.replace(/["']/g, "")).filter(Boolean);
|
|
1141
|
+
if (manager === "npx" && !(tokens[0] && NPX_SCRIPT_RUNNERS.has(tokens[0]))) continue;
|
|
1142
|
+
for (const token of tokens) {
|
|
1143
|
+
if (token.startsWith("-")) continue;
|
|
1144
|
+
if (PM_BINARY_RUNNERS.has(token)) break;
|
|
1145
|
+
if (PM_SUBCOMMANDS.has(token) || NPX_SCRIPT_RUNNERS.has(token)) continue;
|
|
1146
|
+
add(token);
|
|
1147
|
+
}
|
|
1089
1148
|
}
|
|
1090
1149
|
if (/npm-run-all|run-s|run-p/.test(command)) {
|
|
1091
|
-
for (const token of command.split(/\s+/))
|
|
1092
|
-
const clean = token.replace(/["']/g, "");
|
|
1093
|
-
const body = scripts[clean];
|
|
1094
|
-
if (body) expanded += `
|
|
1095
|
-
${expandScriptRefs(body, scripts, depth - 1)}`;
|
|
1096
|
-
}
|
|
1150
|
+
for (const token of command.split(/\s+/)) add(token.replace(/["']/g, ""));
|
|
1097
1151
|
}
|
|
1098
1152
|
return expanded;
|
|
1099
1153
|
}
|
|
@@ -2622,6 +2676,25 @@ var pExecFile2 = promisify(execFile);
|
|
|
2622
2676
|
var FLEET_LENS = "fleet-manifest";
|
|
2623
2677
|
var FLEET_JSON_SCHEMA = "fleet-experimental-0.2";
|
|
2624
2678
|
var CORP_WALL_ARTIFACTS = ["PROJECT_CONTEXT.md", "DECISIONS.md"];
|
|
2679
|
+
function recurringClasses(projects) {
|
|
2680
|
+
const byClass = /* @__PURE__ */ new Map();
|
|
2681
|
+
for (const p of projects) {
|
|
2682
|
+
for (const f of p.findings) {
|
|
2683
|
+
const classId = f.id.split(":")[0] ?? f.id;
|
|
2684
|
+
const entry = byClass.get(classId);
|
|
2685
|
+
if (entry) {
|
|
2686
|
+
entry.projects.add(p.name);
|
|
2687
|
+
if (TIER_ORDER2.indexOf(f.tier) < TIER_ORDER2.indexOf(entry.tier)) entry.tier = f.tier;
|
|
2688
|
+
} else {
|
|
2689
|
+
byClass.set(classId, { tier: f.tier, projects: /* @__PURE__ */ new Set([p.name]) });
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return [...byClass.entries()].filter(([, v]) => v.projects.size >= 2).map(([classId, v]) => ({ classId, tier: v.tier, projects: [...v.projects].sort() })).sort(
|
|
2694
|
+
(a, b) => TIER_ORDER2.indexOf(a.tier) - TIER_ORDER2.indexOf(b.tier) || b.projects.length - a.projects.length || a.classId.localeCompare(b.classId)
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
var TIER_ORDER2 = ["risk", "gap", "polish"];
|
|
2625
2698
|
function finding3(id, tier, claim, evidence, why, action) {
|
|
2626
2699
|
return {
|
|
2627
2700
|
id,
|
|
@@ -3071,7 +3144,10 @@ async function sweepEntry(manifest, entry, opts) {
|
|
|
3071
3144
|
upstreamRemote
|
|
3072
3145
|
});
|
|
3073
3146
|
project.findings = result.findings;
|
|
3074
|
-
|
|
3147
|
+
if (entry.contract.placement === "none") {
|
|
3148
|
+
project.findings = project.findings.filter((f) => !f.id.endsWith("/no-contract"));
|
|
3149
|
+
}
|
|
3150
|
+
for (const f of project.findings) project.counts[f.tier] += 1;
|
|
3075
3151
|
project.stateAgeDays = stateAgeDays(result.facts);
|
|
3076
3152
|
project.staleAfterDays = result.config.config.state.staleAfterDays;
|
|
3077
3153
|
for (const [key, value] of Object.entries(entry.contract)) {
|
|
@@ -3113,7 +3189,8 @@ async function sweepFleet(manifest, opts = {}) {
|
|
|
3113
3189
|
wall: wall.findings,
|
|
3114
3190
|
wallDisclosures: wall.disclosures,
|
|
3115
3191
|
outOfScope: projects.filter((p) => !p.resolvedRoot || p.unresolved).map((p) => p.name),
|
|
3116
|
-
problems: manifest.problems.map((p) => `${p.file}: ${p.detail}`)
|
|
3192
|
+
problems: manifest.problems.map((p) => `${p.file}: ${p.detail}`),
|
|
3193
|
+
recurringClasses: recurringClasses(projects)
|
|
3117
3194
|
};
|
|
3118
3195
|
}
|
|
3119
3196
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { planWorkflow, applyFiles } from './chunk-
|
|
3
|
-
import { scanProject, PACK_VERSION } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
5
|
-
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-
|
|
6
|
-
import { theme, renderFacts, print, renderPlan, git, section, glyph, readText } from './chunk-
|
|
2
|
+
import { planWorkflow, applyFiles } from './chunk-MWIK6ML3.js';
|
|
3
|
+
import { scanProject, PACK_VERSION } from './chunk-BH37LELV.js';
|
|
4
|
+
import { VERSION } from './chunk-LVLZLCND.js';
|
|
5
|
+
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-FZSIOCOZ.js';
|
|
6
|
+
import { theme, renderFacts, print, renderPlan, git, section, glyph, readText } from './chunk-LBRNZZZF.js';
|
|
7
7
|
import { promises } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { intro, spinner, select, confirm, outro, isCancel, cancel } from '@clack/prompts';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { scanProject } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { writeCachedFacts } from './chunk-
|
|
5
|
-
import { print, renderFacts, theme } from './chunk-
|
|
2
|
+
import { scanProject } from './chunk-BH37LELV.js';
|
|
3
|
+
import './chunk-LVLZLCND.js';
|
|
4
|
+
import { writeCachedFacts } from './chunk-FZSIOCOZ.js';
|
|
5
|
+
import { print, renderFacts, theme } from './chunk-LBRNZZZF.js';
|
|
6
6
|
|
|
7
7
|
// src/commands/scan.ts
|
|
8
8
|
async function run(opts) {
|
package/package.json
CHANGED
package/dist/audit-ATHB6FHN.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-3WOORFOW.js';
|
|
3
|
-
import './chunk-XGH2DTW4.js';
|
|
4
|
-
import './chunk-XW5XQN23.js';
|
|
5
|
-
import './chunk-3LBIBVX3.js';
|
|
6
|
-
import './chunk-WNMA2FYM.js';
|
|
7
|
-
import './chunk-JQHGV74L.js';
|
|
8
|
-
import './chunk-DJ2DDFDK.js';
|
|
9
|
-
import './chunk-ASKJFMSX.js';
|
package/dist/doctor-WTYZSJ5X.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-3WOORFOW.js';
|
|
3
|
-
import './chunk-XGH2DTW4.js';
|
|
4
|
-
import './chunk-XW5XQN23.js';
|
|
5
|
-
import './chunk-3LBIBVX3.js';
|
|
6
|
-
import './chunk-WNMA2FYM.js';
|
|
7
|
-
import './chunk-JQHGV74L.js';
|
|
8
|
-
import './chunk-DJ2DDFDK.js';
|
|
9
|
-
import './chunk-ASKJFMSX.js';
|
|
10
|
-
|
|
11
|
-
// src/commands/doctor.ts
|
|
12
|
-
async function run2(opts) {
|
|
13
|
-
await run({ cwd: opts.cwd, json: opts.json, truth: true });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export { run2 as run };
|
package/dist/ledger-ENFHLNOG.js
DELETED