etymd 0.4.1 → 0.5.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 +61 -0
- package/README.md +43 -20
- package/dist/{approve-DTYNGGXH.js → approve-ASWRO6OD.js} +2 -2
- package/dist/{audit-UJU3336P.js → audit-LPZOGOHE.js} +4 -4
- package/dist/{brief-5UZLZ3CW.js → brief-HWD7CILP.js} +2 -2
- package/dist/{chunk-RH2UAS25.js → chunk-4KOP36RD.js} +1 -1
- package/dist/{chunk-XER37MLS.js → chunk-CYSEGWIW.js} +1 -1
- package/dist/{chunk-KIMFKLJB.js → chunk-OJVQXB5Z.js} +1 -1
- package/dist/{chunk-OWNWN2KF.js → chunk-QJRPKZPA.js} +1 -1
- package/dist/cli.js +19 -14
- package/dist/{doctor-W6DW3OK6.js → doctor-MJ6RBZ2Q.js} +4 -4
- package/dist/{fleet-77SSSFZO.js → fleet-SAFXVN44.js} +57 -9
- package/dist/{gates-LTM5PE7R.js → gates-D5SBAVXB.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{init-7GNB74BE.js → init-AVE2GDJ6.js} +2 -2
- package/dist/{scan-6YOKI3JW.js → scan-5DSECI43.js} +2 -2
- package/dist/scan-ES7IOXOC.js +5 -0
- package/package.json +1 -1
- package/dist/scan-XZH57E4I.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e199ccc: `fleet add --profile corp` now records the alias-to-directory mapping too, not just the entry.
|
|
8
|
+
|
|
9
|
+
A corp entry in the tracked manifest is deliberately alias-only — no path, no remote — which is
|
|
10
|
+
what keeps employer names out of a file that gets pushed. It also means the entry resolves to
|
|
11
|
+
nothing on its own. Registering wrote only that half, so every corp registration ended as a
|
|
12
|
+
dangling entry that `fleet check` reported immediately and the user had to fix by hand, in the
|
|
13
|
+
one file the tool otherwise never asks anyone to hand-edit.
|
|
14
|
+
|
|
15
|
+
The mapping is written `~`-relative, so the local manifest stays portable between machines, and
|
|
16
|
+
merged into the existing document so hand-maintained entries survive.
|
|
17
|
+
|
|
18
|
+
Two refusals guard it, because this file is the one place real employer directory names are
|
|
19
|
+
written down. If the local manifest is not gitignored, registration refuses rather than creating
|
|
20
|
+
a file git would track — a leak the tool creates is worse than a registration it declines to
|
|
21
|
+
finish. If the file exists but is not valid JSON, it refuses rather than overwriting mappings
|
|
22
|
+
that cannot be re-derived.
|
|
23
|
+
|
|
24
|
+
The mapping is written before the tracked entry. The failure modes are not symmetric: a mapping
|
|
25
|
+
without an entry is inert, while an entry without a mapping is a broken registration sitting in
|
|
26
|
+
the file that gets committed.
|
|
27
|
+
|
|
28
|
+
- 9546979: `fleet add` no longer records a remote URL in the manifest.
|
|
29
|
+
|
|
30
|
+
The field was write-only: nothing in the tool ever read it back. It was persisted because it
|
|
31
|
+
happened to be derivable at registration time, and it stayed because no one asked what consumed
|
|
32
|
+
it.
|
|
33
|
+
|
|
34
|
+
It is also the field that turned a mis-profiled entry into a real disclosure. A raw remote URL
|
|
35
|
+
carries the host and the internal group path; `path` carries a bare directory name. Removing a
|
|
36
|
+
field no consumer reads retires that class outright, with no host-matching heuristic, and unlike
|
|
37
|
+
the corp-host guard it keeps working on a machine that has no local manifest to read corp hosts
|
|
38
|
+
from. The remote stays derivable from the checkout at any time, which is where it came from.
|
|
39
|
+
|
|
40
|
+
Existing entries that already carry a remote are left alone — nothing reads them, and rewriting
|
|
41
|
+
a hand-maintained manifest to remove inert keys would be a worse trade than leaving them.
|
|
42
|
+
|
|
43
|
+
## 0.4.2
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- 52ebca8: Fix: `fleet add --profile corp` was silently ignored, registering employer repos as personal.
|
|
48
|
+
|
|
49
|
+
`fleet` declares its own `--profile` (the sweep filter), and commander hands a parent-declared
|
|
50
|
+
option the value even when it is typed after the subcommand. So `fleet add <dir> --profile corp`
|
|
51
|
+
left the subcommand's own option undefined, the placement fell back to personal, and the
|
|
52
|
+
personal branch records `path` and the RAW `remote` — writing the employer host and its internal
|
|
53
|
+
group structure into a manifest that is tracked and pushed. That is precisely the disclosure the
|
|
54
|
+
alias convention exists to prevent. The same shadowing class was already fixed for `--manifest`
|
|
55
|
+
and `--json`; `--profile` was missed.
|
|
56
|
+
|
|
57
|
+
Two changes, because the flag alone is not enough. The CLI now reads the merged option view, so
|
|
58
|
+
the flag works. And `fleet add` refuses outright when the target's remote matches a corp host
|
|
59
|
+
declared in the local manifest while the profile is not corp — the manifest already knows which
|
|
60
|
+
hosts are the employer's and the remote has just been read, so the tool has every fact needed to
|
|
61
|
+
prevent this without anyone remembering a flag. It refuses rather than auto-correcting:
|
|
62
|
+
placement is the user's decision, but it may not be made by omission.
|
|
63
|
+
|
|
3
64
|
## 0.4.1
|
|
4
65
|
|
|
5
66
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -96,6 +96,10 @@ _From Greek **étymon** — a word's true, original sense (→ etymology) — cl
|
|
|
96
96
|
no committed baseline, there is nothing to measure drift against. Everything else runs.
|
|
97
97
|
- **`etymd init` never overwrites an existing `AGENTS.md`.** It scaffolds a minimal one only if
|
|
98
98
|
you have none. The feared overwrite path simply does not exist.
|
|
99
|
+
- **"The shellcheck step announced that it skipped."** That is the intended behaviour, not a
|
|
100
|
+
misconfiguration: where the binary is absent the hook says so and names the install command,
|
|
101
|
+
because a check that goes quiet when its tool is missing looks installed everywhere and is
|
|
102
|
+
installed nowhere.
|
|
99
103
|
|
|
100
104
|
---
|
|
101
105
|
|
|
@@ -153,24 +157,24 @@ personal and employer repos. See [the fleet manifest](#the-fleet-manifest-experi
|
|
|
153
157
|
|
|
154
158
|
## Commands
|
|
155
159
|
|
|
156
|
-
| Command | What it does
|
|
157
|
-
| -------------------------------- |
|
|
158
|
-
| `etymd audit` | Verify every claim; ranked findings (risk → gap → polish) + ledger diff. `--lens`, `--truth`, `--json`, `--no-ledger`, `--fail-on <tier>`.
|
|
159
|
-
| `etymd init` | Onboard: approve the committed baseline; scaffold a minimal AGENTS.md **only if missing**. Never overwrites.
|
|
160
|
-
| `etymd doctor` | Alias for `audit --truth`.
|
|
161
|
-
| `etymd context` | The economy view: per-file always-loaded footprint + extraction candidates.
|
|
162
|
-
| `etymd gates` | Install local git-hook gates (pre-commit / commit-msg / pre-push, plus a publish screen where something ships) built from your own check scripts.
|
|
163
|
-
| `etymd screen` | Content screen: find text that must never be published. Four scopes — `--staged`, `--message`, `--tree`, `--dir`. Bring your own patterns; etymd ships none.
|
|
164
|
-
| `etymd scan` | The deterministic reckoning behind everything. `--json`.
|
|
165
|
-
| `etymd brief` | A grounded briefing your in-repo agent completes to author the semantic layer.
|
|
166
|
-
| `etymd approve` | Refresh the committed baseline non-interactively after intentional structural changes.
|
|
167
|
-
| `etymd ledger` | The findings memory: every tracked finding with status and history.
|
|
168
|
-
| `etymd dismiss` | `dismiss <id> --reason <text>` — a dismissed finding never resurfaces without regressing.
|
|
169
|
-
| `etymd accept` | `accept <id>` — record a finding as accepted reality; visible in the ledger, out of the report.
|
|
170
|
-
| `etymd fleet` | Sweep every project in a fleet manifest: read-only per-repo audits + manifest/wall checks. `--manifest`, `--only`, `--profile`, `--truth`, `--persist-ledgers`, `--json`, `--fail-on`.
|
|
171
|
-
| `etymd fleet check` | Validate the manifest pair alone (no lenses): dangling mappings, duplicate names, privacy leaks, undeclared trust, machine paths. Non-zero exit on any finding.
|
|
172
|
-
| `etymd fleet add` | `add <dir>` — register a project: scans it, asks for what no scan can derive, and refuses to write an entry missing a mandatory field. `--name`, `--kind`, `--profile`, `--trust`, `-y`.
|
|
173
|
-
| `etymd fleet dismiss` / `accept` | `<name> <id>` — resolve a project's finding from any cwd; corp findings persist beside the manifest, never in the corp worktree.
|
|
160
|
+
| Command | What it does |
|
|
161
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| `etymd audit` | Verify every claim; ranked findings (risk → gap → polish) + ledger diff. `--lens`, `--truth`, `--json`, `--no-ledger`, `--fail-on <tier>`. |
|
|
163
|
+
| `etymd init` | Onboard: approve the committed baseline; scaffold a minimal AGENTS.md **only if missing**. Never overwrites. |
|
|
164
|
+
| `etymd doctor` | Alias for `audit --truth`. |
|
|
165
|
+
| `etymd context` | The economy view: per-file always-loaded footprint + extraction candidates. |
|
|
166
|
+
| `etymd gates` | Install local git-hook gates (pre-commit / commit-msg / pre-push, plus a publish screen where something ships) built from your own check scripts — and from the repo's shell surface, where it has one. |
|
|
167
|
+
| `etymd screen` | Content screen: find text that must never be published. Four scopes — `--staged`, `--message`, `--tree`, `--dir`. Bring your own patterns; etymd ships none. |
|
|
168
|
+
| `etymd scan` | The deterministic reckoning behind everything. `--json`. |
|
|
169
|
+
| `etymd brief` | A grounded briefing your in-repo agent completes to author the semantic layer. |
|
|
170
|
+
| `etymd approve` | Refresh the committed baseline non-interactively after intentional structural changes. |
|
|
171
|
+
| `etymd ledger` | The findings memory: every tracked finding with status and history. |
|
|
172
|
+
| `etymd dismiss` | `dismiss <id> --reason <text>` — a dismissed finding never resurfaces without regressing. |
|
|
173
|
+
| `etymd accept` | `accept <id>` — record a finding as accepted reality; visible in the ledger, out of the report. |
|
|
174
|
+
| `etymd fleet` | Sweep every project in a fleet manifest: read-only per-repo audits + manifest/wall checks. `--manifest`, `--only`, `--profile`, `--truth`, `--persist-ledgers`, `--json`, `--fail-on`. |
|
|
175
|
+
| `etymd fleet check` | Validate the manifest pair alone (no lenses): dangling mappings, duplicate names, privacy leaks, undeclared trust, machine paths. Non-zero exit on any finding. |
|
|
176
|
+
| `etymd fleet add` | `add <dir>` — register a project: scans it, asks for what no scan can derive, and refuses to write an entry missing a mandatory field. `--name`, `--kind`, `--profile`, `--trust`, `-y`. |
|
|
177
|
+
| `etymd fleet dismiss` / `accept` | `<name> <id>` — resolve a project's finding from any cwd; corp findings persist beside the manifest, never in the corp worktree. |
|
|
174
178
|
|
|
175
179
|
`--cwd <dir>` targets another directory. Read-only probing of any repo leaves **zero trace**
|
|
176
180
|
(`audit --no-ledger` writes nothing).
|
|
@@ -252,8 +256,9 @@ request diff like any other change. Keep `.etymd` out of your formatter's reach
|
|
|
252
256
|
[the files Etymd keeps](#the-files-etymd-keeps)).
|
|
253
257
|
|
|
254
258
|
A check that runs only in CI is itself a finding: the failure surfaces after the agent finished.
|
|
255
|
-
`etymd gates` installs the local pre-commit / pre-push mirror built from your own check scripts
|
|
256
|
-
and the
|
|
259
|
+
`etymd gates` installs the local pre-commit / pre-push mirror built from your own check scripts —
|
|
260
|
+
and from the repo's shell scripts, which usually have no check of their own — while the
|
|
261
|
+
`gate-integrity` lens flags whatever still runs in CI alone.
|
|
257
262
|
|
|
258
263
|
### Your own checks, beside the generated ones
|
|
259
264
|
|
|
@@ -282,6 +287,24 @@ was edited or went stale, never that you added a check of your own. Delete the c
|
|
|
282
287
|
checks stop running — that is what deleting a file means, and etymd does not police a file it
|
|
283
288
|
does not own.
|
|
284
289
|
|
|
290
|
+
### The shell surface
|
|
291
|
+
|
|
292
|
+
Package scripts are not the only executable surface a repo has, and in some repos they are not
|
|
293
|
+
the main one — a tools or infra repo can be entirely `bootstrap/*.sh` plus `.githooks/*` with no
|
|
294
|
+
`package.json` at all. Those repos were told "no correctness commands detected", which reads as
|
|
295
|
+
"nothing to check" when it means "nothing was looked at". Where a repo has tracked shell scripts,
|
|
296
|
+
the generated pre-push now carries a `shellcheck` step. Three properties are deliberate:
|
|
297
|
+
|
|
298
|
+
- **Scripts are re-discovered by shebang at push time**, never baked into the hook as a list. A
|
|
299
|
+
generated list is correct the day it is written and silently wrong the first time someone adds
|
|
300
|
+
a script.
|
|
301
|
+
- **A missing `shellcheck` binary is a loud skip that names the install command**, never a quiet
|
|
302
|
+
pass. A check that goes silent when its tool is absent looks installed on every machine and is
|
|
303
|
+
installed on one.
|
|
304
|
+
- **Only `warning` severity and above blocks**; style and info print as advice afterwards. A gate
|
|
305
|
+
with a high false-positive rate does not make a repo careful — it teaches everyone the bypass
|
|
306
|
+
flag, and that flag is shared with the checks that must never be bypassed.
|
|
307
|
+
|
|
285
308
|
### The content screen (`etymd screen`)
|
|
286
309
|
|
|
287
310
|
A separate question from "are the instructions true?": **does this repo carry text that must
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-TADKOW6P.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
3
|
+
import { scanProject } from './chunk-4KOP36RD.js';
|
|
4
|
+
import { VERSION } from './chunk-OJVQXB5Z.js';
|
|
5
5
|
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-R74SJ7HS.js';
|
|
6
6
|
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
7
7
|
import './chunk-YLNQQZ5F.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { run } from './chunk-CYSEGWIW.js';
|
|
3
|
+
import './chunk-QJRPKZPA.js';
|
|
4
4
|
import './chunk-LT67FU2Y.js';
|
|
5
5
|
import './chunk-WWBF4Y7K.js';
|
|
6
6
|
import './chunk-NMZ3RHWW.js';
|
|
7
7
|
import './chunk-TADKOW6P.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-4KOP36RD.js';
|
|
9
|
+
import './chunk-OJVQXB5Z.js';
|
|
10
10
|
import './chunk-R74SJ7HS.js';
|
|
11
11
|
import './chunk-IGHES6AK.js';
|
|
12
12
|
import './chunk-YLNQQZ5F.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-TADKOW6P.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-4KOP36RD.js';
|
|
4
|
+
import './chunk-OJVQXB5Z.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-R74SJ7HS.js';
|
|
6
6
|
import './chunk-IGHES6AK.js';
|
|
7
7
|
import './chunk-YLNQQZ5F.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-OJVQXB5Z.js';
|
|
3
3
|
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
4
4
|
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-YLNQQZ5F.js';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-QJRPKZPA.js';
|
|
3
3
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-TADKOW6P.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { measureContext } from './chunk-LT67FU2Y.js';
|
|
3
3
|
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-WWBF4Y7K.js';
|
|
4
4
|
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-NMZ3RHWW.js';
|
|
5
|
-
import { scanProject, expandFileGlobs } from './chunk-
|
|
5
|
+
import { scanProject, expandFileGlobs } from './chunk-4KOP36RD.js';
|
|
6
6
|
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-R74SJ7HS.js';
|
|
7
7
|
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
8
8
|
import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath } from './chunk-YLNQQZ5F.js';
|
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-OJVQXB5Z.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-LPZOGOHE.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-AVE2GDJ6.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,19 +52,19 @@ program.command("approve").description(
|
|
|
52
52
|
"Re-approve the committed baseline after intentional structural changes (non-interactive)"
|
|
53
53
|
).action(
|
|
54
54
|
(_opts, cmd) => action(async () => {
|
|
55
|
-
const { run } = await import('./approve-
|
|
55
|
+
const { run } = await import('./approve-ASWRO6OD.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-5DSECI43.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-MJ6RBZ2Q.js');
|
|
68
68
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
69
69
|
})
|
|
70
70
|
);
|
|
@@ -76,7 +76,7 @@ program.command("context").description("Measure the always-loaded context footpr
|
|
|
76
76
|
);
|
|
77
77
|
program.command("brief").description("Emit a grounded briefing for the in-repo agent to complete the semantic layer").option("--human", "write a human onboarding brief instead of the agent briefing").action(
|
|
78
78
|
(opts, cmd) => action(async () => {
|
|
79
|
-
const { run } = await import('./brief-
|
|
79
|
+
const { run } = await import('./brief-HWD7CILP.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-SAFXVN44.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-SAFXVN44.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-SAFXVN44.js');
|
|
140
140
|
const shared = cmd.optsWithGlobals();
|
|
141
141
|
await add({
|
|
142
142
|
cwd: resolveCwd(cmd),
|
|
@@ -144,7 +144,12 @@ fleet.command("add").argument("<dir>", "directory of the project to register").d
|
|
|
144
144
|
target: dir,
|
|
145
145
|
name: opts.name,
|
|
146
146
|
kind: opts.kind,
|
|
147
|
-
profile
|
|
147
|
+
// `fleet` declares its own --profile (the sweep filter), and commander hands a
|
|
148
|
+
// parent-declared option the value even when it is typed AFTER the subcommand — so
|
|
149
|
+
// `fleet add <dir> --profile corp` left opts.profile undefined and the flag silently
|
|
150
|
+
// did nothing, registering employer repos as personal. Reading the merged view is what
|
|
151
|
+
// makes the flag work; the corp-remote guard in add() is what makes forgetting it safe.
|
|
152
|
+
profile: opts.profile ?? shared.profile,
|
|
148
153
|
trust: opts.trust,
|
|
149
154
|
yes: opts.yes
|
|
150
155
|
});
|
|
@@ -154,7 +159,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
154
159
|
"Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
|
|
155
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(
|
|
156
161
|
(name, id, _opts, cmd) => action(async () => {
|
|
157
|
-
const { dismiss } = await import('./fleet-
|
|
162
|
+
const { dismiss } = await import('./fleet-SAFXVN44.js');
|
|
158
163
|
const opts = cmd.optsWithGlobals();
|
|
159
164
|
await dismiss({
|
|
160
165
|
cwd: resolveCwd(cmd),
|
|
@@ -169,7 +174,7 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
169
174
|
"Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
|
|
170
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(
|
|
171
176
|
(name, id, _opts, cmd) => action(async () => {
|
|
172
|
-
const { accept } = await import('./fleet-
|
|
177
|
+
const { accept } = await import('./fleet-SAFXVN44.js');
|
|
173
178
|
const opts = cmd.optsWithGlobals();
|
|
174
179
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
175
180
|
})
|
|
@@ -191,7 +196,7 @@ program.command("screen").description(
|
|
|
191
196
|
);
|
|
192
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(
|
|
193
198
|
(opts, cmd) => action(async () => {
|
|
194
|
-
const { run } = await import('./gates-
|
|
199
|
+
const { run } = await import('./gates-D5SBAVXB.js');
|
|
195
200
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
196
201
|
})
|
|
197
202
|
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-CYSEGWIW.js';
|
|
3
|
+
import './chunk-QJRPKZPA.js';
|
|
4
4
|
import './chunk-LT67FU2Y.js';
|
|
5
5
|
import './chunk-WWBF4Y7K.js';
|
|
6
6
|
import './chunk-NMZ3RHWW.js';
|
|
7
7
|
import './chunk-TADKOW6P.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-4KOP36RD.js';
|
|
9
|
+
import './chunk-OJVQXB5Z.js';
|
|
10
10
|
import './chunk-R74SJ7HS.js';
|
|
11
11
|
import './chunk-IGHES6AK.js';
|
|
12
12
|
import './chunk-YLNQQZ5F.js';
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dismiss, accept } from './chunk-MMPV67FW.js';
|
|
3
|
-
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-
|
|
3
|
+
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-QJRPKZPA.js';
|
|
4
4
|
import './chunk-LT67FU2Y.js';
|
|
5
5
|
import { readLedger } from './chunk-WWBF4Y7K.js';
|
|
6
6
|
import { DEFAULT_CONFIG } from './chunk-NMZ3RHWW.js';
|
|
7
7
|
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph } from './chunk-TADKOW6P.js';
|
|
8
|
-
import { scanProject } from './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import { scanProject } from './chunk-4KOP36RD.js';
|
|
9
|
+
import './chunk-OJVQXB5Z.js';
|
|
10
10
|
import { ETYMD_DIR } from './chunk-R74SJ7HS.js';
|
|
11
11
|
import './chunk-IGHES6AK.js';
|
|
12
12
|
import { readText, pathExists, git, isDirectory } from './chunk-YLNQQZ5F.js';
|
|
13
13
|
import { promises } from 'node:fs';
|
|
14
|
+
import os from 'node:os';
|
|
14
15
|
import path from 'node:path';
|
|
15
16
|
import { select, isCancel, cancel, text, confirm } from '@clack/prompts';
|
|
16
|
-
import os from 'node:os';
|
|
17
17
|
import { execFile } from 'node:child_process';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
|
|
@@ -688,7 +688,7 @@ async function checkCorpEmails(manifest, findings, disclosures) {
|
|
|
688
688
|
}
|
|
689
689
|
async function checkGateDrift(manifest, findings, disclosures) {
|
|
690
690
|
const { planWorkflow } = await import('./generate-CBMTNN7P.js');
|
|
691
|
-
const { scanProject: scanProject2 } = await import('./scan-
|
|
691
|
+
const { scanProject: scanProject2 } = await import('./scan-ES7IOXOC.js');
|
|
692
692
|
const { readConfig } = await import('./config-MX7OYI7F.js');
|
|
693
693
|
for (const entry of manifest.entries) {
|
|
694
694
|
const root = entry.resolvedRoot;
|
|
@@ -1077,6 +1077,35 @@ async function check(opts) {
|
|
|
1077
1077
|
renderFindings(findings);
|
|
1078
1078
|
renderFleetNotes([], disclosures);
|
|
1079
1079
|
}
|
|
1080
|
+
async function recordCorpMapping(manifest, name, absTarget) {
|
|
1081
|
+
const home = os.homedir();
|
|
1082
|
+
const value = absTarget === home || absTarget.startsWith(home + path.sep) ? `~${absTarget.slice(home.length)}` : absTarget;
|
|
1083
|
+
const insideRepo = await git(manifest.dir, ["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
1084
|
+
if (insideRepo) {
|
|
1085
|
+
const ignored = await git(manifest.dir, ["check-ignore", "-q", manifest.localPath]) !== null;
|
|
1086
|
+
if (!ignored) {
|
|
1087
|
+
throw new Error(
|
|
1088
|
+
`${path.basename(manifest.localPath)} is not gitignored \u2014 refusing to write employer directory names into a file git would track. Add it to .gitignore, then re-run.`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
const existing = await readText(manifest.localPath);
|
|
1093
|
+
let doc = {};
|
|
1094
|
+
if (existing) {
|
|
1095
|
+
try {
|
|
1096
|
+
doc = JSON.parse(existing);
|
|
1097
|
+
} catch {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
`${path.basename(manifest.localPath)} is not valid JSON \u2014 fix it before registering, so the mapping is not written over hand-maintained entries that cannot be re-derived.`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const dirs = doc.dirs ?? {};
|
|
1104
|
+
dirs[name] = value;
|
|
1105
|
+
doc.dirs = dirs;
|
|
1106
|
+
await promises.writeFile(manifest.localPath, JSON.stringify(doc, null, 2) + "\n", "utf8");
|
|
1107
|
+
return value;
|
|
1108
|
+
}
|
|
1080
1109
|
async function add(opts) {
|
|
1081
1110
|
const { manifest, autoNote } = await loadManifest(opts.cwd, opts.manifest);
|
|
1082
1111
|
if (autoNote) print(` ${theme.dim(`\u25E6 ${autoNote}`)}`);
|
|
@@ -1177,6 +1206,12 @@ async function add(opts) {
|
|
|
1177
1206
|
} else if (opts.trust) {
|
|
1178
1207
|
throw new Error("corp entries take no `trust` \u2014 `profile: corp` already implies it");
|
|
1179
1208
|
}
|
|
1209
|
+
const corpHost = remote ? manifest.corpHosts.find((h) => remote.includes(h)) : void 0;
|
|
1210
|
+
if (corpHost && profile !== "corp") {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`\`${name}\` has a remote on the corp host \`${corpHost}\`, but --profile is \`${profile}\`. A personal entry records its path and raw remote in the TRACKED manifest \u2014 register it with \`--profile corp\` (alias-only, machine-pinned), or move it out of the fleet root.`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1180
1215
|
const entry = profile === "corp" ? { name, kind, profile, private: true } : {
|
|
1181
1216
|
name,
|
|
1182
1217
|
kind,
|
|
@@ -1184,10 +1219,14 @@ async function add(opts) {
|
|
|
1184
1219
|
path: path.relative(manifest.root ?? manifest.dir, absTarget),
|
|
1185
1220
|
trust,
|
|
1186
1221
|
contract: {},
|
|
1187
|
-
links: {}
|
|
1188
|
-
// The
|
|
1189
|
-
//
|
|
1190
|
-
|
|
1222
|
+
links: {}
|
|
1223
|
+
// The remote is deliberately NOT recorded. Nothing reads it — it was persisted only
|
|
1224
|
+
// because it happened to be derivable — and it is the field that turns a mis-profiled
|
|
1225
|
+
// entry into a disclosure: a raw URL carries the host AND the internal group path,
|
|
1226
|
+
// while `path` carries a bare directory name. Removing a field no consumer reads
|
|
1227
|
+
// retires that whole class without any host-matching guess, and keeps working on a
|
|
1228
|
+
// machine that has no local manifest for the corp-host guard above to read.
|
|
1229
|
+
// It stays derivable at any time from the checkout itself, which is where it came from.
|
|
1191
1230
|
};
|
|
1192
1231
|
print(` ${glyph.bullet} ${theme.dim("entry")}`);
|
|
1193
1232
|
print(
|
|
@@ -1202,11 +1241,20 @@ async function add(opts) {
|
|
|
1202
1241
|
return;
|
|
1203
1242
|
}
|
|
1204
1243
|
}
|
|
1244
|
+
let mappedTo;
|
|
1245
|
+
if (profile === "corp") {
|
|
1246
|
+
mappedTo = await recordCorpMapping(manifest, name, absTarget);
|
|
1247
|
+
}
|
|
1205
1248
|
const doc = JSON.parse(raw);
|
|
1206
1249
|
if (!Array.isArray(doc.projects)) throw new Error("manifest has no `projects` array to append to");
|
|
1207
1250
|
doc.projects.push(entry);
|
|
1208
1251
|
await promises.writeFile(manifest.manifestPath, JSON.stringify(doc, null, 2) + "\n", "utf8");
|
|
1209
1252
|
print(` ${glyph.ok} ${theme.dim("registered")} ${theme.info(name)}`);
|
|
1253
|
+
if (mappedTo) {
|
|
1254
|
+
print(
|
|
1255
|
+
` ${glyph.ok} ${theme.dim("mapped")} ${theme.info(`${name} \u2192 ${mappedTo}`)} ${theme.dim(`in ${path.basename(manifest.localPath)} (gitignored)`)}`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1210
1258
|
print(` ${theme.dim(`verify with \`etymd fleet check --manifest ${manifest.manifestPath}\``)}`);
|
|
1211
1259
|
}
|
|
1212
1260
|
async function resolveFleetFinding(opts, status) {
|
|
@@ -3,8 +3,8 @@ import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
|
3
3
|
import { derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-OX3LUOZR.js';
|
|
4
4
|
import { readConfig, CONFIG_FILE, configPath } from './chunk-NMZ3RHWW.js';
|
|
5
5
|
import { section, print, glyph, theme, renderPlan } from './chunk-TADKOW6P.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import { scanProject } from './chunk-4KOP36RD.js';
|
|
7
|
+
import './chunk-OJVQXB5Z.js';
|
|
8
8
|
import './chunk-R74SJ7HS.js';
|
|
9
9
|
import './chunk-IGHES6AK.js';
|
|
10
10
|
import { readText, git } from './chunk-YLNQQZ5F.js';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ var init_package = __esm({
|
|
|
29
29
|
"package.json"() {
|
|
30
30
|
package_default = {
|
|
31
31
|
name: "etymd",
|
|
32
|
-
version: "0.
|
|
32
|
+
version: "0.5.0",
|
|
33
33
|
description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules & skills against the actual repo, with drift caught over time and a regression ledger.",
|
|
34
34
|
keywords: [
|
|
35
35
|
"cli",
|
|
@@ -3,8 +3,8 @@ import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
|
3
3
|
import { planWorkflow } from './chunk-OX3LUOZR.js';
|
|
4
4
|
import './chunk-NMZ3RHWW.js';
|
|
5
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-TADKOW6P.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject } from './chunk-4KOP36RD.js';
|
|
7
|
+
import { VERSION } from './chunk-OJVQXB5Z.js';
|
|
8
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-R74SJ7HS.js';
|
|
9
9
|
import { PACK_VERSION } from './chunk-IGHES6AK.js';
|
|
10
10
|
import { git, readText } from './chunk-YLNQQZ5F.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, renderFacts, theme } from './chunk-TADKOW6P.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-4KOP36RD.js';
|
|
4
|
+
import './chunk-OJVQXB5Z.js';
|
|
5
5
|
import { writeCachedFacts } from './chunk-R74SJ7HS.js';
|
|
6
6
|
import './chunk-IGHES6AK.js';
|
|
7
7
|
import './chunk-YLNQQZ5F.js';
|
package/package.json
CHANGED