etymd 0.10.0 → 0.12.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 +92 -0
- package/README.md +23 -2
- package/dist/{approve-6TMXJLOI.js → approve-PP2QJQ3Y.js} +5 -5
- package/dist/audit-BXHJUTMJ.js +12 -0
- package/dist/{brief-X64HK325.js → brief-GDLJEG6X.js} +7 -7
- package/dist/{chunk-V2SOHPXF.js → chunk-2VLNI3L2.js} +1 -1
- package/dist/{chunk-OAFYLBVG.js → chunk-3E2IPCRY.js} +3 -3
- package/dist/{chunk-IV3FYVTS.js → chunk-4VPBP6K6.js} +4 -3
- package/dist/{chunk-YVGC3E6U.js → chunk-DWL2IKZH.js} +3 -3
- package/dist/{chunk-IPOAD67U.js → chunk-F75Q43BC.js} +4 -4
- package/dist/{chunk-EF6BPBG6.js → chunk-GWGKEPRX.js} +2 -2
- package/dist/{chunk-R6CVJ5D7.js → chunk-HRJJQCMT.js} +9 -13
- package/dist/{chunk-C7LBUFNU.js → chunk-JHZ2BN4U.js} +3 -3
- package/dist/{chunk-OIYNP6Y6.js → chunk-LSZGCKIQ.js} +1 -1
- package/dist/{chunk-WTD2EIMN.js → chunk-MTVEJ33D.js} +5 -5
- package/dist/{chunk-KQMQJJZK.js → chunk-O3PHW4OP.js} +9 -9
- package/dist/{chunk-SGW7OPHZ.js → chunk-SWA4NQHB.js} +1 -1
- package/dist/{chunk-K34AVLDP.js → chunk-YFNRDXUY.js} +1 -1
- package/dist/cli.js +25 -21
- package/dist/config-XAH6PA5G.js +4 -0
- package/dist/{context-K6DZNWAV.js → context-F63RSIBH.js} +4 -4
- package/dist/doctor-7SLSUSVK.js +19 -0
- package/dist/{fleet-4PPNUIDJ.js → fleet-5NVOUIZD.js} +18 -18
- package/dist/{gates-Q7FMVUQP.js → gates-OS5V3P4C.js} +10 -10
- package/dist/generate-PMCP37DC.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +137 -141
- package/dist/{init-YYGRFIHQ.js → init-EKQ33WYS.js} +10 -10
- package/dist/ledger-4FOZ5HAB.js +5 -0
- package/dist/scan-N4BKVL5F.js +5 -0
- package/dist/{scan-VO776GM7.js → scan-XS4JPS7J.js} +5 -5
- package/dist/{screen-MOQP6XC7.js → screen-UF7VZ26C.js} +154 -20
- package/package.json +4 -4
- package/dist/audit-FGDX2AYS.js +0 -12
- package/dist/config-TPK5LYDM.js +0 -4
- package/dist/doctor-W3UZK7KS.js +0 -19
- package/dist/generate-NLHGTRZB.js +0 -6
- package/dist/ledger-B2XGANTH.js +0 -5
- package/dist/scan-PJSZGUG7.js +0 -5
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { applyFiles } from './chunk-
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
4
|
-
import './chunk-
|
|
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-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
8
|
-
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-
|
|
9
|
-
import { PACK_VERSION } from './chunk-
|
|
10
|
-
import { git, readText } from './chunk-
|
|
11
|
-
import { promises } from '
|
|
12
|
-
import path from '
|
|
6
|
+
import { scanProject } from './chunk-MTVEJ33D.js';
|
|
7
|
+
import { VERSION } from './chunk-YFNRDXUY.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() {
|
|
@@ -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-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import { writeCachedFacts } from './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-MTVEJ33D.js';
|
|
4
|
+
import './chunk-YFNRDXUY.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) {
|
|
@@ -1,21 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, glyph, theme, section } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { git, readText, pathExists } from './chunk-
|
|
4
|
-
import { promises } from '
|
|
5
|
-
import path from '
|
|
3
|
+
import { git, readText, pathExists } from './chunk-4VPBP6K6.js';
|
|
4
|
+
import { promises } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
6
|
|
|
7
|
+
var CLASS_DIRECTIVE = /^#[ \t]*class:[ \t]*(secret|vocabulary)\b/i;
|
|
7
8
|
var MACHINE_PATH_RE = /\/(?:Users|home)\/[A-Za-z0-9._-]+\//;
|
|
8
9
|
var ALLOW_MARKER = "allow-published-string";
|
|
9
10
|
var ALLOW_FILE = ".etymd-screen-allow";
|
|
10
11
|
var LEGACY_ALLOW_FILE = ".artifact-check-allow";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
var LABELED_LINE = /^(pattern|reason|date|author)[ \t]+(.*)$/;
|
|
13
|
+
function compileAllow(raw) {
|
|
14
|
+
const entries = [];
|
|
15
|
+
let current = null;
|
|
16
|
+
const close = () => {
|
|
17
|
+
if (current) entries.push(current);
|
|
18
|
+
current = null;
|
|
19
|
+
};
|
|
20
|
+
for (const rawLine of raw.split("\n")) {
|
|
21
|
+
const l = rawLine.trim();
|
|
22
|
+
if (!l || l.startsWith("#")) continue;
|
|
23
|
+
const labeled = LABELED_LINE.exec(l);
|
|
24
|
+
if (labeled) {
|
|
25
|
+
const [, label = "", value = ""] = labeled;
|
|
26
|
+
if (label === "pattern") {
|
|
27
|
+
close();
|
|
28
|
+
current = makeAllowEntry(value);
|
|
29
|
+
} else if (current) {
|
|
30
|
+
if (label === "reason") current.reason = value.trim();
|
|
31
|
+
else if (label === "date") current.date = value.trim();
|
|
32
|
+
else current.author = value.trim();
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
close();
|
|
36
|
+
current = makeAllowEntry(l);
|
|
17
37
|
}
|
|
18
|
-
}
|
|
38
|
+
}
|
|
39
|
+
close();
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
function makeAllowEntry(patternStr) {
|
|
43
|
+
try {
|
|
44
|
+
return { pattern: new RegExp(patternStr, "i"), isSelfName: false };
|
|
45
|
+
} catch {
|
|
46
|
+
return {
|
|
47
|
+
pattern: new RegExp(patternStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i"),
|
|
48
|
+
isSelfName: false
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function compileOnePattern(l) {
|
|
53
|
+
try {
|
|
54
|
+
return new RegExp(l, "i");
|
|
55
|
+
} catch {
|
|
56
|
+
return new RegExp(l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function compilePatterns(raw) {
|
|
60
|
+
const out = [];
|
|
61
|
+
let cls = "secret";
|
|
62
|
+
for (const rawLine of raw.split("\n")) {
|
|
63
|
+
const l = rawLine.trim();
|
|
64
|
+
if (!l) continue;
|
|
65
|
+
const dir = CLASS_DIRECTIVE.exec(l);
|
|
66
|
+
if (dir) {
|
|
67
|
+
cls = dir[1].toLowerCase();
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (l.startsWith("#")) continue;
|
|
71
|
+
out.push({ re: compileOnePattern(l), cls });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
19
74
|
}
|
|
20
75
|
var META = ".[]*^$+?(){}|";
|
|
21
76
|
function patternLiteral(source) {
|
|
@@ -61,13 +116,17 @@ async function selfIdentities(cwd) {
|
|
|
61
116
|
names.delete("");
|
|
62
117
|
return [...names];
|
|
63
118
|
}
|
|
64
|
-
function screenText(text, file, patterns, allow = []) {
|
|
119
|
+
function screenText(text, file, patterns, allow = [], skipVocabulary = false) {
|
|
120
|
+
const classed = patterns.map(
|
|
121
|
+
(p) => p instanceof RegExp ? { re: p, cls: "secret" } : p
|
|
122
|
+
);
|
|
123
|
+
const active = skipVocabulary ? classed.filter((p) => p.cls === "secret") : classed;
|
|
65
124
|
const hits = [];
|
|
66
125
|
const lines = text.split("\n");
|
|
67
126
|
for (const [i, line] of lines.entries()) {
|
|
68
127
|
if (line.includes(ALLOW_MARKER)) continue;
|
|
69
|
-
if (allow.some((
|
|
70
|
-
for (const re of
|
|
128
|
+
if (allow.some((entry) => entry.pattern.test(line))) continue;
|
|
129
|
+
for (const { re } of active) {
|
|
71
130
|
if (re.test(line)) {
|
|
72
131
|
hits.push({ file, line: i + 1, text: line.trim().slice(0, 160), reason: String(re) });
|
|
73
132
|
break;
|
|
@@ -84,6 +143,30 @@ function screenText(text, file, patterns, allow = []) {
|
|
|
84
143
|
}
|
|
85
144
|
return hits;
|
|
86
145
|
}
|
|
146
|
+
async function collectUpstreamBlobs(cwd, remote) {
|
|
147
|
+
const refsRaw = await git(cwd, ["for-each-ref", "--format=%(refname)", `refs/remotes/${remote}/`]);
|
|
148
|
+
const refs = (refsRaw ?? "").split("\n").filter(Boolean);
|
|
149
|
+
if (!refs.length) return null;
|
|
150
|
+
const blobs = /* @__PURE__ */ new Set();
|
|
151
|
+
for (const ref of refs) {
|
|
152
|
+
const tree = await git(cwd, ["ls-tree", "-r", ref]);
|
|
153
|
+
if (tree === null) continue;
|
|
154
|
+
for (const line of tree.split("\n")) {
|
|
155
|
+
const m = /^\d+ blob ([0-9a-f]+)\t/.exec(line);
|
|
156
|
+
if (m) blobs.add(m[1]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return blobs.size ? blobs : null;
|
|
160
|
+
}
|
|
161
|
+
async function stagedBlobShas(cwd) {
|
|
162
|
+
const raw = await git(cwd, ["ls-files", "-s"]);
|
|
163
|
+
const map = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const line of (raw ?? "").split("\n")) {
|
|
165
|
+
const m = /^\d+ ([0-9a-f]+) \d+\t(.+)$/.exec(line);
|
|
166
|
+
if (m) map.set(m[2], m[1]);
|
|
167
|
+
}
|
|
168
|
+
return map;
|
|
169
|
+
}
|
|
87
170
|
async function walk(dir, out, depth = 0) {
|
|
88
171
|
if (depth > 12) return;
|
|
89
172
|
let entries;
|
|
@@ -122,15 +205,60 @@ async function run(opts) {
|
|
|
122
205
|
);
|
|
123
206
|
return;
|
|
124
207
|
}
|
|
125
|
-
const patterns =
|
|
208
|
+
const patterns = compilePatterns(rawPatterns);
|
|
126
209
|
if (!patterns.length) {
|
|
127
210
|
print(` ${glyph.partial} ${theme.dim(`${patternPath} has no active patterns \u2014 skipping.`)}`);
|
|
128
211
|
return;
|
|
129
212
|
}
|
|
130
|
-
const
|
|
131
|
-
const
|
|
213
|
+
const usingLegacy = !await pathExists(path.join(opts.cwd, ALLOW_FILE)) && await pathExists(path.join(opts.cwd, LEGACY_ALLOW_FILE));
|
|
214
|
+
const allowRaw = usingLegacy ? await readText(path.join(opts.cwd, LEGACY_ALLOW_FILE)) : await readText(path.join(opts.cwd, ALLOW_FILE));
|
|
215
|
+
const allowEntries = allowRaw ? compileAllow(allowRaw) : [];
|
|
132
216
|
const selves = await selfIdentities(opts.cwd);
|
|
133
|
-
const
|
|
217
|
+
const validatedEntries = allowEntries.filter((entry) => {
|
|
218
|
+
if (entry.isSelfName) return true;
|
|
219
|
+
if (isSelfName(entry.pattern, selves)) {
|
|
220
|
+
entry.isSelfName = true;
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (!entry.reason || !entry.date || !entry.author) {
|
|
224
|
+
print(
|
|
225
|
+
` ${glyph.partial} ${theme.warn(`Missing provenance for exemption pattern`)} ${theme.dim(String(entry.pattern))}`
|
|
226
|
+
);
|
|
227
|
+
print(
|
|
228
|
+
` ${theme.dim("Format: a `pattern` line, then `reason`, `date`, `author` lines \u2014 a bare pattern line is a self-name exemption")}`
|
|
229
|
+
);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
});
|
|
234
|
+
const allow = validatedEntries;
|
|
235
|
+
const active = selves.length ? patterns.filter((p) => !isSelfName(p.re, selves)) : patterns;
|
|
236
|
+
const hasVocab = active.some((p) => p.cls === "vocabulary");
|
|
237
|
+
const upstreamRemote = hasVocab && (opts.scope === "staged" || opts.scope === "tree") ? opts.upstream ?? await git(opts.cwd, ["config", "--get", "etymd.upstream"]) ?? void 0 : void 0;
|
|
238
|
+
let upstreamBlobs = null;
|
|
239
|
+
let blobByPath = /* @__PURE__ */ new Map();
|
|
240
|
+
let upstreamNote = "";
|
|
241
|
+
if (upstreamRemote) {
|
|
242
|
+
upstreamBlobs = await collectUpstreamBlobs(opts.cwd, upstreamRemote);
|
|
243
|
+
if (upstreamBlobs === null) {
|
|
244
|
+
upstreamNote = `upstream ref '${upstreamRemote}' unavailable \u2014 vocabulary exemption off, screening every pattern`;
|
|
245
|
+
} else {
|
|
246
|
+
blobByPath = await stagedBlobShas(opts.cwd);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const isUpstreamOwned = (rel) => {
|
|
250
|
+
if (!upstreamBlobs) return false;
|
|
251
|
+
const sha = blobByPath.get(rel);
|
|
252
|
+
return !!sha && upstreamBlobs.has(sha);
|
|
253
|
+
};
|
|
254
|
+
if (usingLegacy) {
|
|
255
|
+
print(
|
|
256
|
+
` ${glyph.partial} ${theme.warn(`Using legacy ${LEGACY_ALLOW_FILE} file \u2014 rename to ${ALLOW_FILE} and add provenance`)}`
|
|
257
|
+
);
|
|
258
|
+
print(
|
|
259
|
+
` ${theme.dim("New format: a `pattern` line, then `reason`, `date`, `author` lines per entry")}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
134
262
|
const hits = [];
|
|
135
263
|
let scanned = 0;
|
|
136
264
|
let skippedBinary = 0;
|
|
@@ -171,15 +299,21 @@ async function run(opts) {
|
|
|
171
299
|
continue;
|
|
172
300
|
}
|
|
173
301
|
scanned++;
|
|
174
|
-
hits.push(...screenText(raw, rel, active, allow));
|
|
302
|
+
hits.push(...screenText(raw, rel, active, allow, isUpstreamOwned(rel)));
|
|
175
303
|
}
|
|
176
304
|
}
|
|
177
|
-
if (
|
|
305
|
+
if (upstreamNote) {
|
|
306
|
+
print(` ${glyph.partial} ${theme.warn(upstreamNote)}`);
|
|
307
|
+
}
|
|
178
308
|
section(
|
|
179
309
|
`Content screen ${theme.dim(
|
|
180
310
|
`\xB7 ${opts.scope} \xB7 ${scanned} file(s)${skippedBinary ? ` \xB7 ${skippedBinary} binary skipped` : ""}`
|
|
181
311
|
)}`
|
|
182
312
|
);
|
|
313
|
+
if (!hits.length) {
|
|
314
|
+
print(` ${glyph.ok} ${theme.dim("clean")}`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
183
317
|
for (const h of hits) {
|
|
184
318
|
print(` ${theme.warn(h.file)}${theme.dim(`:${h.line}`)} ${theme.dim(`(${h.reason})`)}`);
|
|
185
319
|
print(` ${h.text}`);
|
|
@@ -194,4 +328,4 @@ async function run(opts) {
|
|
|
194
328
|
if (!opts.advisory) process.exitCode = 1;
|
|
195
329
|
}
|
|
196
330
|
|
|
197
|
-
export { isSelfName, patternLiteral, readScreenable, run, screenText, selfIdentities };
|
|
331
|
+
export { collectUpstreamBlobs, compileAllow, compilePatterns, isSelfName, patternLiteral, readScreenable, run, screenText, selfIdentities, stagedBlobShas };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "etymd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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.
|
|
72
|
+
"@changesets/cli": "2.31.1",
|
|
73
73
|
"@types/node": "22.10.2",
|
|
74
74
|
"prettier": "3.4.2",
|
|
75
|
-
"tsup": "8.
|
|
75
|
+
"tsup": "8.5.1",
|
|
76
76
|
"typescript": "5.7.2",
|
|
77
|
-
"vitest": "
|
|
77
|
+
"vitest": "4.1.10"
|
|
78
78
|
},
|
|
79
79
|
"publishConfig": {
|
|
80
80
|
"access": "public"
|
package/dist/audit-FGDX2AYS.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-SGW7OPHZ.js';
|
|
3
|
-
import './chunk-KQMQJJZK.js';
|
|
4
|
-
import './chunk-IPOAD67U.js';
|
|
5
|
-
import './chunk-OAFYLBVG.js';
|
|
6
|
-
import './chunk-YVGC3E6U.js';
|
|
7
|
-
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-WTD2EIMN.js';
|
|
9
|
-
import './chunk-K34AVLDP.js';
|
|
10
|
-
import './chunk-C7LBUFNU.js';
|
|
11
|
-
import './chunk-OIYNP6Y6.js';
|
|
12
|
-
import './chunk-IV3FYVTS.js';
|
package/dist/config-TPK5LYDM.js
DELETED
package/dist/doctor-W3UZK7KS.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-SGW7OPHZ.js';
|
|
3
|
-
import './chunk-KQMQJJZK.js';
|
|
4
|
-
import './chunk-IPOAD67U.js';
|
|
5
|
-
import './chunk-OAFYLBVG.js';
|
|
6
|
-
import './chunk-YVGC3E6U.js';
|
|
7
|
-
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-WTD2EIMN.js';
|
|
9
|
-
import './chunk-K34AVLDP.js';
|
|
10
|
-
import './chunk-C7LBUFNU.js';
|
|
11
|
-
import './chunk-OIYNP6Y6.js';
|
|
12
|
-
import './chunk-IV3FYVTS.js';
|
|
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 };
|
package/dist/ledger-B2XGANTH.js
DELETED