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.
- package/CHANGELOG.md +42 -2
- package/README.md +145 -37
- package/dist/{approve-VC4V3OPW.js → approve-JEOEC2MV.js} +6 -4
- package/dist/audit-GYZ5YZ7U.js +12 -0
- package/dist/{brief-TZ45MNHD.js → brief-ZK7TXAC4.js} +6 -4
- package/dist/{chunk-MWIK6ML3.js → chunk-3NYDVB4Z.js} +123 -25
- package/dist/chunk-CRJHIXJH.js +5 -0
- package/dist/chunk-EF6BPBG6.js +22 -0
- package/dist/chunk-LT67FU2Y.js +59 -0
- package/dist/{chunk-BH37LELV.js → chunk-MKYPLNLH.js} +8 -5
- package/dist/{chunk-EUIHYD6F.js → chunk-MMPV67FW.js} +2 -2
- package/dist/{chunk-LVLZLCND.js → chunk-NFENQ3IK.js} +1 -1
- package/dist/{chunk-ANCCGYSH.js → chunk-NMZ3RHWW.js} +31 -59
- package/dist/{chunk-FZSIOCOZ.js → chunk-R74SJ7HS.js} +1 -1
- package/dist/{chunk-LBRNZZZF.js → chunk-TADKOW6P.js} +1 -99
- package/dist/{chunk-KMAJIQHC.js → chunk-V3DPLOOM.js} +7 -5
- package/dist/{chunk-KBF3SW3N.js → chunk-WWBF4Y7K.js} +1 -1
- package/dist/{chunk-IW6K5HLV.js → chunk-XTAX4YNT.js} +2 -2
- package/dist/chunk-YLNQQZ5F.js +102 -0
- package/dist/cli.js +50 -16
- package/dist/config-MX7OYI7F.js +4 -0
- package/dist/{context-GYCLR3TG.js → context-4DGXO7JR.js} +5 -3
- package/dist/doctor-C2RTL2QV.js +19 -0
- package/dist/{fleet-N3G5ZIGR.js → fleet-YZGR4UC2.js} +243 -10
- package/dist/gates-ILE4PXDA.js +272 -0
- package/dist/generate-IYCQETQB.js +4 -0
- package/dist/index.d.ts +113 -33
- package/dist/index.js +713 -257
- package/dist/{init-4CQOLFQG.js → init-OVLO4OBZ.js} +8 -5
- package/dist/ledger-TMIV45AA.js +5 -0
- package/dist/{scan-CYOOGJFG.js → scan-RAJUFTQR.js} +6 -4
- package/dist/scan-ZYZ7XDSJ.js +5 -0
- package/dist/screen-U7JMWZPS.js +134 -0
- package/package.json +2 -2
- package/dist/audit-LZJRJS5V.js +0 -9
- package/dist/doctor-6JDAHGDT.js +0 -16
- package/dist/gates-G5BCMRRV.js +0 -71
- package/dist/ledger-RHQJH5EA.js +0 -4
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-NMZ3RHWW.js';
|
|
3
|
+
import { readText, wordCount, approxTokens, pathExists } from './chunk-YLNQQZ5F.js';
|
|
4
|
+
import { promises } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
var ALWAYS_LOADED = [
|
|
8
|
+
{ path: "AGENTS.md", role: "operating contract" },
|
|
9
|
+
{ path: "PROJECT_CONTEXT.md", role: "ground-truth state" },
|
|
10
|
+
{ path: "CLAUDE.md", role: "Claude Code pointer" },
|
|
11
|
+
{ path: "GEMINI.md", role: "Gemini pointer" },
|
|
12
|
+
{ path: ".github/copilot-instructions.md", role: "Copilot instructions" },
|
|
13
|
+
{ path: ".cursorrules", role: "Cursor rules (legacy)" }
|
|
14
|
+
];
|
|
15
|
+
var EXTRACTION_THRESHOLD = DEFAULT_CONFIG.context.perFileWords;
|
|
16
|
+
function isAlwaysAppliedCursorRule(text) {
|
|
17
|
+
const fm = text.match(/^---\n([\s\S]*?)\n---/);
|
|
18
|
+
if (!fm) return true;
|
|
19
|
+
const frontmatter = fm[1] ?? "";
|
|
20
|
+
return /^\s*alwaysApply\s*:\s*true\s*$/m.test(frontmatter);
|
|
21
|
+
}
|
|
22
|
+
async function measureContext(root, perFileWords = EXTRACTION_THRESHOLD) {
|
|
23
|
+
const files = [];
|
|
24
|
+
for (const spec of ALWAYS_LOADED) {
|
|
25
|
+
const abs = path.join(root, spec.path);
|
|
26
|
+
const text = await readText(abs);
|
|
27
|
+
if (text === null) continue;
|
|
28
|
+
const words = wordCount(text);
|
|
29
|
+
files.push({ path: spec.path, role: spec.role, words, approxTokens: approxTokens(words) });
|
|
30
|
+
}
|
|
31
|
+
const rulesDir = path.join(root, ".cursor", "rules");
|
|
32
|
+
if (await pathExists(rulesDir)) {
|
|
33
|
+
try {
|
|
34
|
+
for (const entry of await promises.readdir(rulesDir)) {
|
|
35
|
+
if (!entry.endsWith(".mdc") && !entry.endsWith(".md")) continue;
|
|
36
|
+
const text = await readText(path.join(rulesDir, entry));
|
|
37
|
+
if (text === null || !isAlwaysAppliedCursorRule(text)) continue;
|
|
38
|
+
const words = wordCount(text);
|
|
39
|
+
files.push({
|
|
40
|
+
path: `.cursor/rules/${entry}`,
|
|
41
|
+
role: "Cursor rule (always applied)",
|
|
42
|
+
words,
|
|
43
|
+
approxTokens: approxTokens(words)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const totalWords = files.reduce((s, f) => s + f.words, 0);
|
|
50
|
+
return {
|
|
51
|
+
files: files.sort((a, b) => b.words - a.words),
|
|
52
|
+
totalWords,
|
|
53
|
+
totalApproxTokens: approxTokens(totalWords),
|
|
54
|
+
perFileWords,
|
|
55
|
+
extractionCandidates: files.filter((f) => f.words >= perFileWords)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { measureContext };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
3
|
-
import {
|
|
2
|
+
import { VERSION } from './chunk-NFENQ3IK.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-CRJHIXJH.js';
|
|
4
|
+
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-YLNQQZ5F.js';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { promises } from 'node:fs';
|
|
6
7
|
|
|
7
|
-
// src/pack/version.ts
|
|
8
|
-
var PACK_VERSION = "2";
|
|
9
8
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
10
9
|
"node_modules",
|
|
11
10
|
".git",
|
|
@@ -564,10 +563,14 @@ async function scanProject(root, opts = {}) {
|
|
|
564
563
|
commands: classifyCommands(rootPkg?.scripts),
|
|
565
564
|
ci,
|
|
566
565
|
hooks,
|
|
566
|
+
publishable: Boolean(rootPkg) && rootPkg?.private !== true,
|
|
567
|
+
// `engines.vscode` is what makes a package an extension, and extensions publish through
|
|
568
|
+
// vsce — which never runs prepublishOnly.
|
|
569
|
+
publishRoute: !rootPkg ? "none" : rootPkg.engines?.vscode ? "vscode" : "npm",
|
|
567
570
|
artifacts,
|
|
568
571
|
freshness,
|
|
569
572
|
tree
|
|
570
573
|
};
|
|
571
574
|
}
|
|
572
575
|
|
|
573
|
-
export {
|
|
576
|
+
export { expandFileGlobs, scanProject };
|
|
@@ -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-WWBF4Y7K.js';
|
|
3
|
+
import { print, renderLedger, glyph, theme } from './chunk-TADKOW6P.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/ledger.ts
|
|
6
6
|
async function list(opts) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ETYMD_DIR } from './chunk-
|
|
3
|
-
import { pathExists, readText
|
|
4
|
-
import
|
|
5
|
-
import { promises } from 'node:fs';
|
|
2
|
+
import { ETYMD_DIR } from './chunk-R74SJ7HS.js';
|
|
3
|
+
import { pathExists, readText } from './chunk-YLNQQZ5F.js';
|
|
4
|
+
import path from 'node:path';
|
|
6
5
|
|
|
7
|
-
var CONFIG_FILE =
|
|
6
|
+
var CONFIG_FILE = path.join(ETYMD_DIR, "config.json");
|
|
8
7
|
var DEFAULT_CONFIG = {
|
|
9
8
|
instructions: { include: [], exclude: [] },
|
|
10
9
|
context: { perFileWords: 4e3, totalWords: 8e3 },
|
|
11
|
-
state: { staleAfterDays: 30, maxChars: 9500 }
|
|
10
|
+
state: { staleAfterDays: 30, maxChars: 9500 },
|
|
11
|
+
gates: { commands: [], failOn: "risk", allowWriting: [] }
|
|
12
12
|
};
|
|
13
13
|
function configPath(root) {
|
|
14
|
-
return
|
|
14
|
+
return path.join(root, CONFIG_FILE);
|
|
15
15
|
}
|
|
16
16
|
function readGlobList(value, key, problems) {
|
|
17
17
|
if (value === void 0) return void 0;
|
|
@@ -53,6 +53,23 @@ async function readConfig(root) {
|
|
|
53
53
|
const instructions = obj.instructions ?? {};
|
|
54
54
|
const context = obj.context ?? {};
|
|
55
55
|
const state = obj.state ?? {};
|
|
56
|
+
const gates = obj.gates ?? {};
|
|
57
|
+
const failOn = (() => {
|
|
58
|
+
if (gates.failOn === void 0) return void 0;
|
|
59
|
+
if (typeof gates.failOn !== "string" || !["risk", "gap", "polish"].includes(gates.failOn.toLowerCase())) {
|
|
60
|
+
problems.push(`${CONFIG_FILE}: \`gates.failOn\` must be risk | gap | polish \u2014 ignored.`);
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
return gates.failOn.toLowerCase();
|
|
64
|
+
})();
|
|
65
|
+
const publishGate = (() => {
|
|
66
|
+
if (gates.publishGate === void 0) return void 0;
|
|
67
|
+
if (typeof gates.publishGate !== "boolean") {
|
|
68
|
+
problems.push(`${CONFIG_FILE}: \`gates.publishGate\` must be true or false \u2014 ignored.`);
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
return gates.publishGate;
|
|
72
|
+
})();
|
|
56
73
|
return {
|
|
57
74
|
present: true,
|
|
58
75
|
problems,
|
|
@@ -68,60 +85,15 @@ async function readConfig(root) {
|
|
|
68
85
|
state: {
|
|
69
86
|
staleAfterDays: readBudget(state.staleAfterDays, "state.staleAfterDays", problems) ?? DEFAULT_CONFIG.state.staleAfterDays,
|
|
70
87
|
maxChars: readBudget(state.maxChars, "state.maxChars", problems) ?? DEFAULT_CONFIG.state.maxChars
|
|
88
|
+
},
|
|
89
|
+
gates: {
|
|
90
|
+
commands: readGlobList(gates.commands, "gates.commands", problems) ?? DEFAULT_CONFIG.gates.commands,
|
|
91
|
+
failOn: failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
92
|
+
publishGate,
|
|
93
|
+
allowWriting: readGlobList(gates.allowWriting, "gates.allowWriting", problems) ?? DEFAULT_CONFIG.gates.allowWriting
|
|
71
94
|
}
|
|
72
95
|
}
|
|
73
96
|
};
|
|
74
97
|
}
|
|
75
|
-
var ALWAYS_LOADED = [
|
|
76
|
-
{ path: "AGENTS.md", role: "operating contract" },
|
|
77
|
-
{ path: "PROJECT_CONTEXT.md", role: "ground-truth state" },
|
|
78
|
-
{ path: "CLAUDE.md", role: "Claude Code pointer" },
|
|
79
|
-
{ path: "GEMINI.md", role: "Gemini pointer" },
|
|
80
|
-
{ path: ".github/copilot-instructions.md", role: "Copilot instructions" },
|
|
81
|
-
{ path: ".cursorrules", role: "Cursor rules (legacy)" }
|
|
82
|
-
];
|
|
83
|
-
var EXTRACTION_THRESHOLD = DEFAULT_CONFIG.context.perFileWords;
|
|
84
|
-
function isAlwaysAppliedCursorRule(text) {
|
|
85
|
-
const fm = text.match(/^---\n([\s\S]*?)\n---/);
|
|
86
|
-
if (!fm) return true;
|
|
87
|
-
const frontmatter = fm[1] ?? "";
|
|
88
|
-
return /^\s*alwaysApply\s*:\s*true\s*$/m.test(frontmatter);
|
|
89
|
-
}
|
|
90
|
-
async function measureContext(root, perFileWords = EXTRACTION_THRESHOLD) {
|
|
91
|
-
const files = [];
|
|
92
|
-
for (const spec of ALWAYS_LOADED) {
|
|
93
|
-
const abs = path2.join(root, spec.path);
|
|
94
|
-
const text = await readText(abs);
|
|
95
|
-
if (text === null) continue;
|
|
96
|
-
const words = wordCount(text);
|
|
97
|
-
files.push({ path: spec.path, role: spec.role, words, approxTokens: approxTokens(words) });
|
|
98
|
-
}
|
|
99
|
-
const rulesDir = path2.join(root, ".cursor", "rules");
|
|
100
|
-
if (await pathExists(rulesDir)) {
|
|
101
|
-
try {
|
|
102
|
-
for (const entry of await promises.readdir(rulesDir)) {
|
|
103
|
-
if (!entry.endsWith(".mdc") && !entry.endsWith(".md")) continue;
|
|
104
|
-
const text = await readText(path2.join(rulesDir, entry));
|
|
105
|
-
if (text === null || !isAlwaysAppliedCursorRule(text)) continue;
|
|
106
|
-
const words = wordCount(text);
|
|
107
|
-
files.push({
|
|
108
|
-
path: `.cursor/rules/${entry}`,
|
|
109
|
-
role: "Cursor rule (always applied)",
|
|
110
|
-
words,
|
|
111
|
-
approxTokens: approxTokens(words)
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
} catch {
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const totalWords = files.reduce((s, f) => s + f.words, 0);
|
|
118
|
-
return {
|
|
119
|
-
files: files.sort((a, b) => b.words - a.words),
|
|
120
|
-
totalWords,
|
|
121
|
-
totalApproxTokens: approxTokens(totalWords),
|
|
122
|
-
perFileWords,
|
|
123
|
-
extractionCandidates: files.filter((f) => f.words >= perFileWords)
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
98
|
|
|
127
|
-
export { CONFIG_FILE, DEFAULT_CONFIG,
|
|
99
|
+
export { CONFIG_FILE, DEFAULT_CONFIG, configPath, readConfig };
|
|
@@ -1,104 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promises } from 'node:fs';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
2
|
import pc from 'picocolors';
|
|
6
3
|
|
|
7
|
-
var pExecFile = promisify(execFile);
|
|
8
|
-
async function pathExists(p) {
|
|
9
|
-
try {
|
|
10
|
-
await promises.access(p);
|
|
11
|
-
return true;
|
|
12
|
-
} catch {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
async function readJson(p) {
|
|
17
|
-
try {
|
|
18
|
-
const raw = await promises.readFile(p, "utf8");
|
|
19
|
-
return JSON.parse(raw);
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async function readText(p) {
|
|
25
|
-
try {
|
|
26
|
-
return await promises.readFile(p, "utf8");
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async function isDirectory(p) {
|
|
32
|
-
try {
|
|
33
|
-
return (await promises.stat(p)).isDirectory();
|
|
34
|
-
} catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function git(root, args) {
|
|
39
|
-
try {
|
|
40
|
-
const { stdout } = await pExecFile("git", args, { cwd: root, timeout: 4e3 });
|
|
41
|
-
return stdout.trim();
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function wordCount(text) {
|
|
47
|
-
const m = text.trim().match(/\S+/g);
|
|
48
|
-
return m ? m.length : 0;
|
|
49
|
-
}
|
|
50
|
-
function approxTokens(words) {
|
|
51
|
-
return Math.round(words / 0.75);
|
|
52
|
-
}
|
|
53
|
-
var CI_ENV_VARS = [
|
|
54
|
-
"CI",
|
|
55
|
-
"GITHUB_ACTIONS",
|
|
56
|
-
"GITLAB_CI",
|
|
57
|
-
"BUILDKITE",
|
|
58
|
-
"CIRCLECI",
|
|
59
|
-
"JENKINS_URL",
|
|
60
|
-
"TF_BUILD"
|
|
61
|
-
];
|
|
62
|
-
function isCiEnvironment() {
|
|
63
|
-
return CI_ENV_VARS.some((v) => Boolean(process.env[v]));
|
|
64
|
-
}
|
|
65
|
-
function normalizeRelPath(p) {
|
|
66
|
-
return p.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
67
|
-
}
|
|
68
|
-
function globToRegExp(glob) {
|
|
69
|
-
let re = "";
|
|
70
|
-
for (let i = 0; i < glob.length; i++) {
|
|
71
|
-
const c = glob[i];
|
|
72
|
-
if (c === "*") {
|
|
73
|
-
if (glob[i + 1] === "*") {
|
|
74
|
-
i += 1;
|
|
75
|
-
if (glob[i + 1] === "/") {
|
|
76
|
-
i += 1;
|
|
77
|
-
re += "(?:.*/)?";
|
|
78
|
-
} else {
|
|
79
|
-
re += ".*";
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
re += "[^/]*";
|
|
83
|
-
}
|
|
84
|
-
} else if (c === "?") {
|
|
85
|
-
re += "[^/]";
|
|
86
|
-
} else {
|
|
87
|
-
re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return new RegExp(`^${re}$`);
|
|
91
|
-
}
|
|
92
|
-
function matchesGlob(p, glob) {
|
|
93
|
-
const target = normalizeRelPath(p);
|
|
94
|
-
const pattern = normalizeRelPath(glob);
|
|
95
|
-
if (!pattern) return false;
|
|
96
|
-
if (!/[*?]/.test(pattern)) return target === pattern || target.startsWith(pattern + "/");
|
|
97
|
-
return globToRegExp(pattern).test(target);
|
|
98
|
-
}
|
|
99
|
-
function matchesAnyGlob(p, globs) {
|
|
100
|
-
return globs.some((g) => matchesGlob(p, g));
|
|
101
|
-
}
|
|
102
4
|
var theme = {
|
|
103
5
|
brand: (s) => pc.magenta(pc.bold(s)),
|
|
104
6
|
heading: (s) => pc.bold(s),
|
|
@@ -356,4 +258,4 @@ function renderLedger(ledger) {
|
|
|
356
258
|
}
|
|
357
259
|
}
|
|
358
260
|
|
|
359
|
-
export { TIER_BADGE,
|
|
261
|
+
export { TIER_BADGE, glyph, print, renderBaselineDrift, renderContext, renderFacts, renderFindings, renderFleetNotes, renderFleetRows, renderLedger, renderLedgerDiff, renderLensCoverage, renderPlan, section, theme };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-
|
|
6
|
-
import {
|
|
2
|
+
import { measureContext } from './chunk-LT67FU2Y.js';
|
|
3
|
+
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-WWBF4Y7K.js';
|
|
4
|
+
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-NMZ3RHWW.js';
|
|
5
|
+
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-R74SJ7HS.js';
|
|
6
|
+
import { scanProject, expandFileGlobs } from './chunk-MKYPLNLH.js';
|
|
7
|
+
import { PACK_VERSION } from './chunk-CRJHIXJH.js';
|
|
8
|
+
import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath } from './chunk-YLNQQZ5F.js';
|
|
7
9
|
import path from 'node:path';
|
|
8
10
|
import YAML from 'yaml';
|
|
9
11
|
import { promises } from 'node:fs';
|
|
@@ -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-V3DPLOOM.js';
|
|
3
|
+
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-TADKOW6P.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
6
6
|
async function run(opts) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promises } from 'node:fs';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
var pExecFile = promisify(execFile);
|
|
7
|
+
async function pathExists(p) {
|
|
8
|
+
try {
|
|
9
|
+
await promises.access(p);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function readJson(p) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await promises.readFile(p, "utf8");
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function readText(p) {
|
|
24
|
+
try {
|
|
25
|
+
return await promises.readFile(p, "utf8");
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function isDirectory(p) {
|
|
31
|
+
try {
|
|
32
|
+
return (await promises.stat(p)).isDirectory();
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function git(root, args) {
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await pExecFile("git", args, { cwd: root, timeout: 4e3 });
|
|
40
|
+
return stdout.trim();
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function wordCount(text) {
|
|
46
|
+
const m = text.trim().match(/\S+/g);
|
|
47
|
+
return m ? m.length : 0;
|
|
48
|
+
}
|
|
49
|
+
function approxTokens(words) {
|
|
50
|
+
return Math.round(words / 0.75);
|
|
51
|
+
}
|
|
52
|
+
var CI_ENV_VARS = [
|
|
53
|
+
"CI",
|
|
54
|
+
"GITHUB_ACTIONS",
|
|
55
|
+
"GITLAB_CI",
|
|
56
|
+
"BUILDKITE",
|
|
57
|
+
"CIRCLECI",
|
|
58
|
+
"JENKINS_URL",
|
|
59
|
+
"TF_BUILD"
|
|
60
|
+
];
|
|
61
|
+
function isCiEnvironment() {
|
|
62
|
+
return CI_ENV_VARS.some((v) => Boolean(process.env[v]));
|
|
63
|
+
}
|
|
64
|
+
function normalizeRelPath(p) {
|
|
65
|
+
return p.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
66
|
+
}
|
|
67
|
+
function globToRegExp(glob) {
|
|
68
|
+
let re = "";
|
|
69
|
+
for (let i = 0; i < glob.length; i++) {
|
|
70
|
+
const c = glob[i];
|
|
71
|
+
if (c === "*") {
|
|
72
|
+
if (glob[i + 1] === "*") {
|
|
73
|
+
i += 1;
|
|
74
|
+
if (glob[i + 1] === "/") {
|
|
75
|
+
i += 1;
|
|
76
|
+
re += "(?:.*/)?";
|
|
77
|
+
} else {
|
|
78
|
+
re += ".*";
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
re += "[^/]*";
|
|
82
|
+
}
|
|
83
|
+
} else if (c === "?") {
|
|
84
|
+
re += "[^/]";
|
|
85
|
+
} else {
|
|
86
|
+
re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return new RegExp(`^${re}$`);
|
|
90
|
+
}
|
|
91
|
+
function matchesGlob(p, glob) {
|
|
92
|
+
const target = normalizeRelPath(p);
|
|
93
|
+
const pattern = normalizeRelPath(glob);
|
|
94
|
+
if (!pattern) return false;
|
|
95
|
+
if (!/[*?]/.test(pattern)) return target === pattern || target.startsWith(pattern + "/");
|
|
96
|
+
return globToRegExp(pattern).test(target);
|
|
97
|
+
}
|
|
98
|
+
function matchesAnyGlob(p, globs) {
|
|
99
|
+
return globs.some((g) => matchesGlob(p, g));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { approxTokens, git, isCiEnvironment, isDirectory, matchesAnyGlob, normalizeRelPath, pathExists, readJson, readText, wordCount };
|
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-NFENQ3IK.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-GYZ5YZ7U.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-OVLO4OBZ.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-JEOEC2MV.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-RAJUFTQR.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-C2RTL2QV.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-4DGXO7JR.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-ZK7TXAC4.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-TMIV45AA.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-TMIV45AA.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-TMIV45AA.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-YZGR4UC2.js');
|
|
115
115
|
await sweep({
|
|
116
116
|
cwd: resolveCwd(cmd),
|
|
117
117
|
manifest: opts.manifest,
|
|
@@ -126,16 +126,35 @@ 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-YZGR4UC2.js');
|
|
130
130
|
const opts = cmd.optsWithGlobals();
|
|
131
131
|
await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
|
|
132
132
|
})
|
|
133
133
|
);
|
|
134
|
+
fleet.command("add").argument("<dir>", "directory of the project to register").description("Register a project \u2014 refuses to write an entry missing a mandatory field").option("--name <name>", "registered name (defaults to the directory's basename)").option("--kind <kind>", "entry kind (defaults to a value derived from the scan)").option("--profile <profile>", "personal | corp (default: personal)").option(
|
|
135
|
+
"--trust <level>",
|
|
136
|
+
"public-repo | public-bound | private \u2014 mandatory for personal entries"
|
|
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
|
+
(dir, opts, cmd) => action(async () => {
|
|
139
|
+
const { add } = await import('./fleet-YZGR4UC2.js');
|
|
140
|
+
const shared = cmd.optsWithGlobals();
|
|
141
|
+
await add({
|
|
142
|
+
cwd: resolveCwd(cmd),
|
|
143
|
+
manifest: shared.manifest,
|
|
144
|
+
target: dir,
|
|
145
|
+
name: opts.name,
|
|
146
|
+
kind: opts.kind,
|
|
147
|
+
profile: opts.profile,
|
|
148
|
+
trust: opts.trust,
|
|
149
|
+
yes: opts.yes
|
|
150
|
+
});
|
|
151
|
+
})
|
|
152
|
+
);
|
|
134
153
|
fleet.command("dismiss").argument("<name>", "the registered project name").argument("<finding-id>", "the finding id (from `etymd fleet`) to dismiss").description(
|
|
135
154
|
"Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
|
|
136
155
|
).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
156
|
(name, id, _opts, cmd) => action(async () => {
|
|
138
|
-
const { dismiss } = await import('./fleet-
|
|
157
|
+
const { dismiss } = await import('./fleet-YZGR4UC2.js');
|
|
139
158
|
const opts = cmd.optsWithGlobals();
|
|
140
159
|
await dismiss({
|
|
141
160
|
cwd: resolveCwd(cmd),
|
|
@@ -150,14 +169,29 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
150
169
|
"Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
|
|
151
170
|
).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
171
|
(name, id, _opts, cmd) => action(async () => {
|
|
153
|
-
const { accept } = await import('./fleet-
|
|
172
|
+
const { accept } = await import('./fleet-YZGR4UC2.js');
|
|
154
173
|
const opts = cmd.optsWithGlobals();
|
|
155
174
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
156
175
|
})
|
|
157
176
|
);
|
|
177
|
+
program.command("screen").description(
|
|
178
|
+
"Content screen: check for text that must never be published. Bring your own patterns \u2014 etymd ships none."
|
|
179
|
+
).option("--staged", "screen what a commit is about to add (default)").option("--tree", "screen every tracked file \u2014 what is about to leave the machine").option("--message <file>", "screen a commit message (the commit-msg hook's $1)").option("--dir <dir>", "screen an unpacked build artifact \u2014 what actually ships").option("--patterns <file>", "pattern file (default: ~/.config/etymd/screen-patterns)").option("--advisory", "report without failing").action(
|
|
180
|
+
(opts, cmd) => action(async () => {
|
|
181
|
+
const { run } = await import('./screen-U7JMWZPS.js');
|
|
182
|
+
const scope = opts.message ? "message" : opts.dir ? "dir" : opts.tree ? "tree" : "staged";
|
|
183
|
+
await run({
|
|
184
|
+
cwd: resolveCwd(cmd),
|
|
185
|
+
scope,
|
|
186
|
+
target: opts.message ?? opts.dir,
|
|
187
|
+
patterns: opts.patterns,
|
|
188
|
+
advisory: opts.advisory
|
|
189
|
+
});
|
|
190
|
+
})
|
|
191
|
+
);
|
|
158
192
|
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
193
|
(opts, cmd) => action(async () => {
|
|
160
|
-
const { run } = await import('./gates-
|
|
194
|
+
const { run } = await import('./gates-ILE4PXDA.js');
|
|
161
195
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
162
196
|
})
|
|
163
197
|
);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { print, renderContext } from './chunk-
|
|
2
|
+
import { measureContext } from './chunk-LT67FU2Y.js';
|
|
3
|
+
import { readConfig } from './chunk-NMZ3RHWW.js';
|
|
4
|
+
import { print, renderContext } from './chunk-TADKOW6P.js';
|
|
5
|
+
import './chunk-R74SJ7HS.js';
|
|
6
|
+
import './chunk-YLNQQZ5F.js';
|
|
5
7
|
|
|
6
8
|
// src/commands/context.ts
|
|
7
9
|
async function run(opts) {
|