etymd 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +120 -14
  2. package/README.md +50 -2
  3. package/dist/{approve-HED3NCTE.js → approve-OUJ4UPGK.js} +5 -5
  4. package/dist/audit-A75FVXHD.js +12 -0
  5. package/dist/{brief-2CXAUDNN.js → brief-MYUESUMM.js} +7 -7
  6. package/dist/{chunk-ULM25GWG.js → chunk-2KVQBL4E.js} +1 -1
  7. package/dist/{chunk-V2SOHPXF.js → chunk-2VLNI3L2.js} +1 -1
  8. package/dist/{chunk-OAFYLBVG.js → chunk-3E2IPCRY.js} +3 -3
  9. package/dist/{chunk-IV3FYVTS.js → chunk-4VPBP6K6.js} +4 -3
  10. package/dist/{chunk-UN7IAYM6.js → chunk-5GQ52DK6.js} +5 -5
  11. package/dist/{chunk-D2RI3CCT.js → chunk-DWL2IKZH.js} +12 -3
  12. package/dist/{chunk-JSUYV26K.js → chunk-F75Q43BC.js} +4 -4
  13. package/dist/{chunk-EF6BPBG6.js → chunk-GWGKEPRX.js} +2 -2
  14. package/dist/{chunk-WENIJMVV.js → chunk-HRJJQCMT.js} +54 -18
  15. package/dist/{chunk-AQM4GYGN.js → chunk-J7XVOTZL.js} +1 -1
  16. package/dist/{chunk-C7LBUFNU.js → chunk-JHZ2BN4U.js} +3 -3
  17. package/dist/{chunk-KGVRZ6WC.js → chunk-JVLWM27I.js} +9 -9
  18. package/dist/{chunk-6DUDIVIC.js → chunk-LSZGCKIQ.js} +1 -1
  19. package/dist/cli.js +19 -19
  20. package/dist/config-XAH6PA5G.js +4 -0
  21. package/dist/{context-5ANOP2IJ.js → context-F63RSIBH.js} +4 -4
  22. package/dist/doctor-OZQTOUFR.js +19 -0
  23. package/dist/{fleet-DPJSZDEY.js → fleet-Z7W3QBEV.js} +18 -18
  24. package/dist/{gates-RR3V4PII.js → gates-47YTMMLZ.js} +10 -10
  25. package/dist/generate-PMCP37DC.js +6 -0
  26. package/dist/index.d.ts +9 -1
  27. package/dist/index.js +192 -147
  28. package/dist/{init-RC35RS5J.js → init-QLGIYFCX.js} +10 -10
  29. package/dist/ledger-4FOZ5HAB.js +5 -0
  30. package/dist/{scan-MBXSD5RO.js → scan-VWEXWM6G.js} +5 -5
  31. package/dist/scan-W7RHZVSF.js +5 -0
  32. package/dist/{screen-EHNK6ANK.js → screen-NSWNC7VQ.js} +125 -16
  33. package/package.json +4 -4
  34. package/dist/audit-Y35XVD6Z.js +0 -12
  35. package/dist/config-7WCVZCM6.js +0 -4
  36. package/dist/doctor-DXXQJ2L3.js +0 -19
  37. package/dist/generate-IPY5QIR5.js +0 -6
  38. package/dist/ledger-B2XGANTH.js +0 -5
  39. package/dist/scan-KX522VKV.js +0 -5
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { applyFiles } from './chunk-EF6BPBG6.js';
3
- import { planWorkflow } from './chunk-WENIJMVV.js';
4
- import './chunk-D2RI3CCT.js';
2
+ import { applyFiles } from './chunk-GWGKEPRX.js';
3
+ import { planWorkflow } from './chunk-HRJJQCMT.js';
4
+ import './chunk-DWL2IKZH.js';
5
5
  import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-5BVKFJWM.js';
6
- import { scanProject } from './chunk-UN7IAYM6.js';
7
- import { VERSION } from './chunk-AQM4GYGN.js';
8
- import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-C7LBUFNU.js';
9
- import { PACK_VERSION } from './chunk-6DUDIVIC.js';
10
- import { git, readText } from './chunk-IV3FYVTS.js';
11
- import { promises } from 'node:fs';
12
- import path from 'node:path';
6
+ import { scanProject } from './chunk-5GQ52DK6.js';
7
+ import { VERSION } from './chunk-J7XVOTZL.js';
8
+ import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-JHZ2BN4U.js';
9
+ import { PACK_VERSION } from './chunk-LSZGCKIQ.js';
10
+ import { git, readText } from './chunk-4VPBP6K6.js';
11
+ import { promises } from 'fs';
12
+ import path from 'path';
13
13
  import { intro, spinner, select, confirm, outro, isCancel, cancel } from '@clack/prompts';
14
14
 
15
15
  function bail() {
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ export { accept, dismiss, list } from './chunk-2VLNI3L2.js';
3
+ import './chunk-3E2IPCRY.js';
4
+ import './chunk-5BVKFJWM.js';
5
+ import './chunk-4VPBP6K6.js';
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { print, renderFacts, theme } from './chunk-5BVKFJWM.js';
3
- import { scanProject } from './chunk-UN7IAYM6.js';
4
- import './chunk-AQM4GYGN.js';
5
- import { writeCachedFacts } from './chunk-C7LBUFNU.js';
6
- import './chunk-6DUDIVIC.js';
7
- import './chunk-IV3FYVTS.js';
3
+ import { scanProject } from './chunk-5GQ52DK6.js';
4
+ import './chunk-J7XVOTZL.js';
5
+ import { writeCachedFacts } from './chunk-JHZ2BN4U.js';
6
+ import './chunk-LSZGCKIQ.js';
7
+ import './chunk-4VPBP6K6.js';
8
8
 
9
9
  // src/commands/scan.ts
10
10
  async function run(opts) {
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ export { scanProject } from './chunk-5GQ52DK6.js';
3
+ import './chunk-J7XVOTZL.js';
4
+ import './chunk-LSZGCKIQ.js';
5
+ import './chunk-4VPBP6K6.js';
@@ -1,14 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
  import { print, glyph, theme, section } from './chunk-5BVKFJWM.js';
3
- import { readText, git, pathExists } from './chunk-IV3FYVTS.js';
4
- import { promises } from 'node:fs';
5
- import path from 'node:path';
3
+ import { git, readText, pathExists } from './chunk-4VPBP6K6.js';
4
+ import { promises } from 'fs';
5
+ import path from 'path';
6
6
 
7
7
  var MACHINE_PATH_RE = /\/(?:Users|home)\/[A-Za-z0-9._-]+\//;
8
8
  var ALLOW_MARKER = "allow-published-string";
9
9
  var ALLOW_FILE = ".etymd-screen-allow";
10
10
  var LEGACY_ALLOW_FILE = ".artifact-check-allow";
11
- function compile(raw) {
11
+ var LABELED_LINE = /^(pattern|reason|date|author)[ \t]+(.*)$/;
12
+ function compileAllow(raw) {
13
+ const entries = [];
14
+ let current = null;
15
+ const close = () => {
16
+ if (current) entries.push(current);
17
+ current = null;
18
+ };
19
+ for (const rawLine of raw.split("\n")) {
20
+ const l = rawLine.trim();
21
+ if (!l || l.startsWith("#")) continue;
22
+ const labeled = LABELED_LINE.exec(l);
23
+ if (labeled) {
24
+ const [, label = "", value = ""] = labeled;
25
+ if (label === "pattern") {
26
+ close();
27
+ current = makeAllowEntry(value);
28
+ } else if (current) {
29
+ if (label === "reason") current.reason = value.trim();
30
+ else if (label === "date") current.date = value.trim();
31
+ else current.author = value.trim();
32
+ }
33
+ } else {
34
+ close();
35
+ current = makeAllowEntry(l);
36
+ }
37
+ }
38
+ close();
39
+ return entries;
40
+ }
41
+ function makeAllowEntry(patternStr) {
42
+ try {
43
+ return { pattern: new RegExp(patternStr, "i"), isSelfName: false };
44
+ } catch {
45
+ return {
46
+ pattern: new RegExp(patternStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i"),
47
+ isSelfName: false
48
+ };
49
+ }
50
+ }
51
+ function compilePatterns(raw) {
12
52
  return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
13
53
  try {
14
54
  return new RegExp(l, "i");
@@ -17,15 +57,56 @@ function compile(raw) {
17
57
  }
18
58
  });
19
59
  }
20
- function isSelfName(pattern, self) {
21
- return pattern.source.toLowerCase() === self.toLowerCase();
60
+ var META = ".[]*^$+?(){}|";
61
+ function patternLiteral(source) {
62
+ let out = "";
63
+ let i = source.startsWith("\\b") ? 2 : 0;
64
+ for (; i < source.length; i++) {
65
+ const c = source.charAt(i);
66
+ if (c === "\\") {
67
+ const next = source[i + 1];
68
+ if (next === void 0) return null;
69
+ if (/[A-Za-z0-9]/.test(next)) return next === "b" && i + 2 === source.length ? out : null;
70
+ out += next;
71
+ i++;
72
+ continue;
73
+ }
74
+ if (META.includes(c)) return null;
75
+ out += c;
76
+ }
77
+ return out;
78
+ }
79
+ function isSelfName(pattern, selves) {
80
+ const literal = patternLiteral(pattern.source);
81
+ if (literal === null || literal === "") return false;
82
+ return selves.some((s) => s.toLowerCase() === literal.toLowerCase());
83
+ }
84
+ function bareName(raw) {
85
+ return raw.replace(/^@[^/]+\//, "").replace(/\.git$/, "");
86
+ }
87
+ async function selfIdentities(cwd) {
88
+ const names = /* @__PURE__ */ new Set();
89
+ const top = await git(cwd, ["rev-parse", "--show-toplevel"]);
90
+ names.add(path.basename(top || path.resolve(cwd)));
91
+ const pkg = await readText(path.join(top || cwd, "package.json"));
92
+ if (pkg) {
93
+ try {
94
+ const name = JSON.parse(pkg).name;
95
+ if (typeof name === "string" && name) names.add(bareName(name));
96
+ } catch {
97
+ }
98
+ }
99
+ const remote = await git(cwd, ["remote", "get-url", "origin"]);
100
+ if (remote) names.add(bareName(path.basename(remote.trim())));
101
+ names.delete("");
102
+ return [...names];
22
103
  }
23
104
  function screenText(text, file, patterns, allow = []) {
24
105
  const hits = [];
25
106
  const lines = text.split("\n");
26
107
  for (const [i, line] of lines.entries()) {
27
108
  if (line.includes(ALLOW_MARKER)) continue;
28
- if (allow.some((re) => re.test(line))) continue;
109
+ if (allow.some((entry) => entry.pattern.test(line))) continue;
29
110
  for (const re of patterns) {
30
111
  if (re.test(line)) {
31
112
  hits.push({ file, line: i + 1, text: line.trim().slice(0, 160), reason: String(re) });
@@ -81,17 +162,42 @@ async function run(opts) {
81
162
  );
82
163
  return;
83
164
  }
84
- const patterns = compile(rawPatterns);
165
+ const patterns = compilePatterns(rawPatterns);
85
166
  if (!patterns.length) {
86
167
  print(` ${glyph.partial} ${theme.dim(`${patternPath} has no active patterns \u2014 skipping.`)}`);
87
168
  return;
88
169
  }
89
- const allowRaw = await readText(path.join(opts.cwd, ALLOW_FILE)) ?? await readText(path.join(opts.cwd, LEGACY_ALLOW_FILE));
90
- const allow = allowRaw ? compile(allowRaw) : [];
91
- const self = path.basename(
92
- await git(opts.cwd, ["rev-parse", "--show-toplevel"]) || path.resolve(opts.cwd)
93
- );
94
- const active = self ? patterns.filter((re) => !isSelfName(re, self)) : patterns;
170
+ const usingLegacy = !await pathExists(path.join(opts.cwd, ALLOW_FILE)) && await pathExists(path.join(opts.cwd, LEGACY_ALLOW_FILE));
171
+ const allowRaw = usingLegacy ? await readText(path.join(opts.cwd, LEGACY_ALLOW_FILE)) : await readText(path.join(opts.cwd, ALLOW_FILE));
172
+ const allowEntries = allowRaw ? compileAllow(allowRaw) : [];
173
+ const selves = await selfIdentities(opts.cwd);
174
+ const validatedEntries = allowEntries.filter((entry) => {
175
+ if (entry.isSelfName) return true;
176
+ if (isSelfName(entry.pattern, selves)) {
177
+ entry.isSelfName = true;
178
+ return true;
179
+ }
180
+ if (!entry.reason || !entry.date || !entry.author) {
181
+ print(
182
+ ` ${glyph.partial} ${theme.warn(`Missing provenance for exemption pattern`)} ${theme.dim(String(entry.pattern))}`
183
+ );
184
+ print(
185
+ ` ${theme.dim("Format: a `pattern` line, then `reason`, `date`, `author` lines \u2014 a bare pattern line is a self-name exemption")}`
186
+ );
187
+ return false;
188
+ }
189
+ return true;
190
+ });
191
+ const allow = validatedEntries;
192
+ const active = selves.length ? patterns.filter((re) => !isSelfName(re, selves)) : patterns;
193
+ if (usingLegacy) {
194
+ print(
195
+ ` ${glyph.partial} ${theme.warn(`Using legacy ${LEGACY_ALLOW_FILE} file \u2014 rename to ${ALLOW_FILE} and add provenance`)}`
196
+ );
197
+ print(
198
+ ` ${theme.dim("New format: a `pattern` line, then `reason`, `date`, `author` lines per entry")}`
199
+ );
200
+ }
95
201
  const hits = [];
96
202
  let scanned = 0;
97
203
  let skippedBinary = 0;
@@ -135,12 +241,15 @@ async function run(opts) {
135
241
  hits.push(...screenText(raw, rel, active, allow));
136
242
  }
137
243
  }
138
- if (!hits.length) return;
139
244
  section(
140
245
  `Content screen ${theme.dim(
141
246
  `\xB7 ${opts.scope} \xB7 ${scanned} file(s)${skippedBinary ? ` \xB7 ${skippedBinary} binary skipped` : ""}`
142
247
  )}`
143
248
  );
249
+ if (!hits.length) {
250
+ print(` ${glyph.ok} ${theme.dim("clean")}`);
251
+ return;
252
+ }
144
253
  for (const h of hits) {
145
254
  print(` ${theme.warn(h.file)}${theme.dim(`:${h.line}`)} ${theme.dim(`(${h.reason})`)}`);
146
255
  print(` ${h.text}`);
@@ -155,4 +264,4 @@ async function run(opts) {
155
264
  if (!opts.advisory) process.exitCode = 1;
156
265
  }
157
266
 
158
- export { isSelfName, readScreenable, run, screenText };
267
+ export { compileAllow, isSelfName, patternLiteral, readScreenable, run, screenText, selfIdentities };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "etymd",
3
- "version": "0.9.1",
3
+ "version": "0.11.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",
@@ -69,12 +69,12 @@
69
69
  "yaml": "2.9.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@changesets/cli": "2.27.11",
72
+ "@changesets/cli": "2.31.1",
73
73
  "@types/node": "22.10.2",
74
74
  "prettier": "3.4.2",
75
- "tsup": "8.3.5",
75
+ "tsup": "8.5.1",
76
76
  "typescript": "5.7.2",
77
- "vitest": "2.1.8"
77
+ "vitest": "4.1.10"
78
78
  },
79
79
  "publishConfig": {
80
80
  "access": "public"
@@ -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';
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- export { CONFIG_FILE, DEFAULT_CONFIG, configPath, readConfig } from './chunk-D2RI3CCT.js';
3
- import './chunk-C7LBUFNU.js';
4
- import './chunk-IV3FYVTS.js';
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
- import { 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';
13
-
14
- // src/commands/doctor.ts
15
- async function run2(opts) {
16
- await run({ cwd: opts.cwd, json: opts.json, truth: true });
17
- }
18
-
19
- export { run2 as run };
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-WENIJMVV.js';
3
- import './chunk-D2RI3CCT.js';
4
- import './chunk-C7LBUFNU.js';
5
- import './chunk-6DUDIVIC.js';
6
- import './chunk-IV3FYVTS.js';
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- export { accept, dismiss, list } from './chunk-V2SOHPXF.js';
3
- import './chunk-OAFYLBVG.js';
4
- import './chunk-5BVKFJWM.js';
5
- import './chunk-IV3FYVTS.js';
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- export { scanProject } from './chunk-UN7IAYM6.js';
3
- import './chunk-AQM4GYGN.js';
4
- import './chunk-6DUDIVIC.js';
5
- import './chunk-IV3FYVTS.js';