etymd 0.2.2 → 0.3.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 (38) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +145 -37
  3. package/dist/{approve-VC4V3OPW.js → approve-JEOEC2MV.js} +6 -4
  4. package/dist/audit-GYZ5YZ7U.js +12 -0
  5. package/dist/{brief-TZ45MNHD.js → brief-ZK7TXAC4.js} +6 -4
  6. package/dist/{chunk-MWIK6ML3.js → chunk-3NYDVB4Z.js} +123 -25
  7. package/dist/chunk-CRJHIXJH.js +5 -0
  8. package/dist/chunk-EF6BPBG6.js +22 -0
  9. package/dist/chunk-LT67FU2Y.js +59 -0
  10. package/dist/{chunk-BH37LELV.js → chunk-MKYPLNLH.js} +8 -5
  11. package/dist/{chunk-EUIHYD6F.js → chunk-MMPV67FW.js} +2 -2
  12. package/dist/{chunk-LVLZLCND.js → chunk-NFENQ3IK.js} +1 -1
  13. package/dist/{chunk-ANCCGYSH.js → chunk-NMZ3RHWW.js} +31 -59
  14. package/dist/{chunk-FZSIOCOZ.js → chunk-R74SJ7HS.js} +1 -1
  15. package/dist/{chunk-LBRNZZZF.js → chunk-TADKOW6P.js} +1 -99
  16. package/dist/{chunk-KMAJIQHC.js → chunk-V3DPLOOM.js} +7 -5
  17. package/dist/{chunk-KBF3SW3N.js → chunk-WWBF4Y7K.js} +1 -1
  18. package/dist/{chunk-IW6K5HLV.js → chunk-XTAX4YNT.js} +2 -2
  19. package/dist/chunk-YLNQQZ5F.js +102 -0
  20. package/dist/cli.js +50 -16
  21. package/dist/config-MX7OYI7F.js +4 -0
  22. package/dist/{context-GYCLR3TG.js → context-4DGXO7JR.js} +5 -3
  23. package/dist/doctor-C2RTL2QV.js +19 -0
  24. package/dist/{fleet-N3G5ZIGR.js → fleet-YZGR4UC2.js} +243 -10
  25. package/dist/gates-ILE4PXDA.js +272 -0
  26. package/dist/generate-IYCQETQB.js +4 -0
  27. package/dist/index.d.ts +113 -33
  28. package/dist/index.js +713 -257
  29. package/dist/{init-4CQOLFQG.js → init-OVLO4OBZ.js} +8 -5
  30. package/dist/ledger-TMIV45AA.js +5 -0
  31. package/dist/{scan-CYOOGJFG.js → scan-RAJUFTQR.js} +6 -4
  32. package/dist/scan-ZYZ7XDSJ.js +5 -0
  33. package/dist/screen-U7JMWZPS.js +134 -0
  34. package/package.json +2 -2
  35. package/dist/audit-LZJRJS5V.js +0 -9
  36. package/dist/doctor-6JDAHGDT.js +0 -16
  37. package/dist/gates-G5BCMRRV.js +0 -71
  38. package/dist/ledger-RHQJH5EA.js +0 -4
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
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';
2
+ import { applyFiles } from './chunk-EF6BPBG6.js';
3
+ import { planWorkflow } from './chunk-3NYDVB4Z.js';
4
+ import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-TADKOW6P.js';
5
+ import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-R74SJ7HS.js';
6
+ import { scanProject } from './chunk-MKYPLNLH.js';
7
+ import { VERSION } from './chunk-NFENQ3IK.js';
8
+ import { PACK_VERSION } from './chunk-CRJHIXJH.js';
9
+ import { git, readText } from './chunk-YLNQQZ5F.js';
7
10
  import { promises } from 'node:fs';
8
11
  import path from 'node:path';
9
12
  import { intro, spinner, select, confirm, outro, isCancel, cancel } from '@clack/prompts';
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ export { accept, dismiss, list } from './chunk-MMPV67FW.js';
3
+ import './chunk-WWBF4Y7K.js';
4
+ import './chunk-TADKOW6P.js';
5
+ import './chunk-YLNQQZ5F.js';
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
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';
2
+ import { print, renderFacts, theme } from './chunk-TADKOW6P.js';
3
+ import { writeCachedFacts } from './chunk-R74SJ7HS.js';
4
+ import { scanProject } from './chunk-MKYPLNLH.js';
5
+ import './chunk-NFENQ3IK.js';
6
+ import './chunk-CRJHIXJH.js';
7
+ import './chunk-YLNQQZ5F.js';
6
8
 
7
9
  // src/commands/scan.ts
8
10
  async function run(opts) {
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ export { scanProject } from './chunk-MKYPLNLH.js';
3
+ import './chunk-NFENQ3IK.js';
4
+ import './chunk-CRJHIXJH.js';
5
+ import './chunk-YLNQQZ5F.js';
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import { print, glyph, theme, section } from './chunk-TADKOW6P.js';
3
+ import { readText, git, pathExists } from './chunk-YLNQQZ5F.js';
4
+ import { promises } from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ var MACHINE_PATH_RE = /\/(?:Users|home)\/[A-Za-z0-9._-]+\//;
8
+ var ALLOW_MARKER = "allow-published-string";
9
+ var ALLOW_FILE = ".etymd-screen-allow";
10
+ var LEGACY_ALLOW_FILE = ".artifact-check-allow";
11
+ function compile(raw) {
12
+ return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
13
+ try {
14
+ return new RegExp(l, "i");
15
+ } catch {
16
+ return new RegExp(l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
17
+ }
18
+ });
19
+ }
20
+ function isSelfName(pattern, self) {
21
+ return pattern.source.toLowerCase() === self.toLowerCase();
22
+ }
23
+ function screenText(text, file, patterns, allow = []) {
24
+ const hits = [];
25
+ const lines = text.split("\n");
26
+ for (const [i, line] of lines.entries()) {
27
+ if (line.includes(ALLOW_MARKER)) continue;
28
+ if (allow.some((re) => re.test(line))) continue;
29
+ for (const re of patterns) {
30
+ if (re.test(line)) {
31
+ hits.push({ file, line: i + 1, text: line.trim().slice(0, 160), reason: String(re) });
32
+ break;
33
+ }
34
+ }
35
+ if (MACHINE_PATH_RE.test(line)) {
36
+ hits.push({
37
+ file,
38
+ line: i + 1,
39
+ text: line.trim().slice(0, 160),
40
+ reason: "absolute home path"
41
+ });
42
+ }
43
+ }
44
+ return hits;
45
+ }
46
+ async function walk(dir, out, depth = 0) {
47
+ if (depth > 12) return;
48
+ let entries;
49
+ try {
50
+ entries = await promises.readdir(dir, { withFileTypes: true });
51
+ } catch {
52
+ return;
53
+ }
54
+ for (const e of entries) {
55
+ if (e.name === ".git" || e.name === "node_modules") continue;
56
+ const abs = path.join(dir, e.name);
57
+ if (e.isDirectory()) await walk(abs, out, depth + 1);
58
+ else out.push(abs);
59
+ }
60
+ }
61
+ async function run(opts) {
62
+ const patternPath = opts.patterns ?? process.env.ETYMD_SCREEN_PATTERNS ?? process.env.PUBLIC_REPO_PATTERNS ?? path.join(process.env.HOME ?? "", ".config", "etymd", "screen-patterns");
63
+ const rawPatterns = await readText(patternPath);
64
+ if (rawPatterns === null) {
65
+ print(
66
+ ` ${glyph.partial} ${theme.dim(`no pattern file at ${patternPath} \u2014 nothing to screen against.`)}`
67
+ );
68
+ print(
69
+ ` ${theme.dim("Etymd ships no patterns by design: the strings worth screening for are themselves sensitive. Write one line per pattern (regex or literal, # for comments).")}`
70
+ );
71
+ return;
72
+ }
73
+ const patterns = compile(rawPatterns);
74
+ if (!patterns.length) {
75
+ print(` ${glyph.partial} ${theme.dim(`${patternPath} has no active patterns \u2014 skipping.`)}`);
76
+ return;
77
+ }
78
+ const allowRaw = await readText(path.join(opts.cwd, ALLOW_FILE)) ?? await readText(path.join(opts.cwd, LEGACY_ALLOW_FILE));
79
+ const allow = allowRaw ? compile(allowRaw) : [];
80
+ const self = path.basename(
81
+ await git(opts.cwd, ["rev-parse", "--show-toplevel"]) || path.resolve(opts.cwd)
82
+ );
83
+ const active = self ? patterns.filter((re) => !isSelfName(re, self)) : patterns;
84
+ const hits = [];
85
+ let scanned = 0;
86
+ if (opts.scope === "message") {
87
+ const file = opts.target;
88
+ if (!file) throw new Error("--message needs the message file path (the commit-msg hook's $1)");
89
+ const raw = await readText(file);
90
+ if (raw === null) return;
91
+ const body = raw.split("\n").filter((l) => !l.startsWith("#")).join("\n");
92
+ scanned = 1;
93
+ hits.push(...screenText(body, "commit message", active, allow));
94
+ } else if (opts.scope === "dir") {
95
+ const dir = opts.target;
96
+ if (!dir) throw new Error("--dir needs a directory");
97
+ const files = [];
98
+ await walk(dir, files);
99
+ for (const f of files) {
100
+ const raw = await readText(f);
101
+ if (raw === null) continue;
102
+ scanned++;
103
+ hits.push(...screenText(raw, path.relative(dir, f), active, allow));
104
+ }
105
+ } else {
106
+ const listing = opts.scope === "tree" ? await git(opts.cwd, ["ls-files"]) : await git(opts.cwd, ["diff", "--cached", "--name-only", "--diff-filter=ACM"]);
107
+ const files = (listing ?? "").split("\n").filter(Boolean);
108
+ for (const rel of files) {
109
+ if (rel === ALLOW_FILE || rel === LEGACY_ALLOW_FILE) continue;
110
+ const abs = path.join(opts.cwd, rel);
111
+ if (!await pathExists(abs)) continue;
112
+ const raw = await readText(abs);
113
+ if (raw === null) continue;
114
+ scanned++;
115
+ hits.push(...screenText(raw, rel, active, allow));
116
+ }
117
+ }
118
+ if (!hits.length) return;
119
+ section(`Content screen ${theme.dim(`\xB7 ${opts.scope} \xB7 ${scanned} file(s)`)}`);
120
+ for (const h of hits) {
121
+ print(` ${theme.warn(h.file)}${theme.dim(`:${h.line}`)} ${theme.dim(`(${h.reason})`)}`);
122
+ print(` ${h.text}`);
123
+ }
124
+ print("");
125
+ print(
126
+ ` ${theme.dim("Publishing exposes ALL history, so this content would be permanent the moment it is committed.")}`
127
+ );
128
+ print(
129
+ ` ${theme.dim(`Rewrite the line, mark a deliberate one with \`${ALLOW_MARKER}\`, or bypass with --no-verify.`)}`
130
+ );
131
+ if (!opts.advisory) process.exitCode = 1;
132
+ }
133
+
134
+ export { isSelfName, run, screenText };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "etymd",
3
- "version": "0.2.2",
3
+ "version": "0.3.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",
@@ -58,7 +58,7 @@
58
58
  "format:check": "prettier --check . && npm run guard:md",
59
59
  "guard:md": "! grep -rnF --include=*.md --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.git '\\*\\*' .",
60
60
  "prepare": "npm run build",
61
- "prepublishOnly": "npm run build",
61
+ "prepublishOnly": "npm run build && artifact-check",
62
62
  "changeset": "changeset",
63
63
  "version:packages": "changeset version",
64
64
  "release": "npm run build && changeset publish"
@@ -1,9 +0,0 @@
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,16 +0,0 @@
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,71 +0,0 @@
1
- #!/usr/bin/env node
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
- import { confirm, isCancel, cancel } from '@clack/prompts';
7
-
8
- async function run(opts) {
9
- const facts = await scanProject(opts.cwd);
10
- if (opts.ci) {
11
- section("CI review gate");
12
- print(
13
- ` ${glyph.partial} ${theme.dim("The advisory AI-review CI job (bring-your-own-key) ships in a later release \u2014 installing the free local gates below.")}`
14
- );
15
- }
16
- if (!facts.git.isRepo) {
17
- print(
18
- ` ${glyph.bad} ${theme.dim("Not a git repository \u2014 local hooks need git. Run `git init` first.")}`
19
- );
20
- return;
21
- }
22
- if (facts.hooks.source === "husky" || facts.hooks.source === "husky-legacy" || facts.hooks.source === "custom") {
23
- print(
24
- ` ${glyph.partial} ${theme.warn(`${facts.hooks.source} already manages hooks here.`)} ${theme.dim("git runs ONE hook path \u2014 installing .githooks + core.hooksPath takes over. Fold the existing hook logic in first.")}`
25
- );
26
- if (!opts.yes) {
27
- const go = await confirm({ message: "Proceed with .githooks anyway?", initialValue: false });
28
- if (isCancel(go) || !go) {
29
- cancel("No changes made.");
30
- return;
31
- }
32
- }
33
- }
34
- const files = await planWorkflow(opts.cwd, facts, { agents: false, gates: true });
35
- const gateFiles = files.filter((f) => f.executable);
36
- renderPlan(gateFiles);
37
- const overwrite = /* @__PURE__ */ new Set();
38
- for (const f of gateFiles) {
39
- if (!f.exists) continue;
40
- if (!f.differs) {
41
- overwrite.add(f.path);
42
- continue;
43
- }
44
- if (opts.yes) continue;
45
- const ow = await confirm({
46
- message: `${f.path} exists with different content (hand-edited?). Overwrite with the pack version?`,
47
- initialValue: false
48
- });
49
- if (isCancel(ow)) {
50
- cancel("No changes made.");
51
- return;
52
- }
53
- if (ow) overwrite.add(f.path);
54
- }
55
- if (!opts.yes) {
56
- const ok = await confirm({ message: "Install these git hooks and point git at .githooks?" });
57
- if (isCancel(ok) || !ok) {
58
- cancel("No changes made.");
59
- return;
60
- }
61
- }
62
- const result = await applyFiles(opts.cwd, gateFiles, overwrite);
63
- await git(opts.cwd, ["config", "core.hooksPath", ".githooks"]);
64
- section("Done");
65
- for (const w of result.written) print(` ${glyph.ok} ${theme.dim("wrote")} ${theme.info(w)}`);
66
- for (const sk of result.skipped)
67
- print(` ${glyph.bullet} ${theme.dim("kept (hand-edited)")} ${theme.dim(sk)}`);
68
- print(` ${glyph.ok} ${theme.dim("set")} ${theme.code("core.hooksPath = .githooks")}`);
69
- }
70
-
71
- export { run };
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- export { accept, dismiss, list } from './chunk-EUIHYD6F.js';
3
- import './chunk-KBF3SW3N.js';
4
- import './chunk-LBRNZZZF.js';