etymd 0.1.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 +48 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/approve-WM6OJHQG.js +37 -0
- package/dist/audit-OHOSWOWA.js +8 -0
- package/dist/brief-2MPNTITQ.js +94 -0
- package/dist/chunk-5D7WNJ6N.js +1148 -0
- package/dist/chunk-FXL4554F.js +176 -0
- package/dist/chunk-I566LDX7.js +120 -0
- package/dist/chunk-N64ZTBCW.js +105 -0
- package/dist/chunk-SMM2TW35.js +67 -0
- package/dist/chunk-TCWKIMCU.js +502 -0
- package/dist/chunk-U33XLT5I.js +9 -0
- package/dist/chunk-WVYKYPKS.js +332 -0
- package/dist/cli.js +104 -0
- package/dist/context-TT5H76LA.js +18 -0
- package/dist/doctor-NNRJTNIY.js +15 -0
- package/dist/gates-KPDEUC36.js +71 -0
- package/dist/index.d.ts +444 -0
- package/dist/index.js +2095 -0
- package/dist/init-V627UXKU.js +116 -0
- package/dist/ledger-YFTYCOIC.js +39 -0
- package/dist/scan-YL7JHLUA.js +22 -0
- package/package.json +83 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { planWorkflow, applyFiles } from './chunk-FXL4554F.js';
|
|
3
|
+
import { scanProject, PACK_VERSION } from './chunk-TCWKIMCU.js';
|
|
4
|
+
import { VERSION } from './chunk-U33XLT5I.js';
|
|
5
|
+
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-SMM2TW35.js';
|
|
6
|
+
import { theme, renderFacts, print, renderPlan, git, section, glyph, readText } from './chunk-WVYKYPKS.js';
|
|
7
|
+
import { promises } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { intro, spinner, select, confirm, outro, isCancel, cancel } from '@clack/prompts';
|
|
10
|
+
|
|
11
|
+
function bail() {
|
|
12
|
+
cancel("Onboarding cancelled \u2014 no changes made.");
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
function guard(value) {
|
|
16
|
+
if (isCancel(value)) bail();
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
async function ensureCacheIgnored(root) {
|
|
20
|
+
const target = path.join(root, ".gitignore");
|
|
21
|
+
const line = `${CACHE_DIR}/`;
|
|
22
|
+
const existing = await readText(target);
|
|
23
|
+
if (existing?.split("\n").some((l) => l.trim() === line || l.trim() === CACHE_DIR)) return false;
|
|
24
|
+
const next = existing ? `${existing.replace(/\n?$/, "\n")}${line}
|
|
25
|
+
` : `${line}
|
|
26
|
+
`;
|
|
27
|
+
await promises.writeFile(target, next, "utf8");
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
async function run(opts) {
|
|
31
|
+
intro(`${theme.brand("etymd")} ${theme.dim("\xB7 keep your agent instructions true")}`);
|
|
32
|
+
const s = spinner();
|
|
33
|
+
s.start("Reckoning the project");
|
|
34
|
+
const facts = await scanProject(opts.cwd);
|
|
35
|
+
await writeCachedFacts(opts.cwd, facts);
|
|
36
|
+
s.stop(`Reckoned ${theme.brand(facts.name)}`);
|
|
37
|
+
renderFacts(facts);
|
|
38
|
+
print();
|
|
39
|
+
const detectedProfile = deriveProfile(facts);
|
|
40
|
+
let profile = detectedProfile;
|
|
41
|
+
if (!opts.yes) {
|
|
42
|
+
profile = guard(
|
|
43
|
+
await select({
|
|
44
|
+
message: `Workflow profile ${theme.dim(`(detected: ${detectedProfile}, from recent commit authors)`)}`,
|
|
45
|
+
initialValue: detectedProfile,
|
|
46
|
+
options: [
|
|
47
|
+
{ value: "solo", label: "Solo", hint: "one developer" },
|
|
48
|
+
{ value: "team", label: "Team", hint: "several committers" }
|
|
49
|
+
]
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const hasContract = facts.artifacts.some((a) => a.id === "agents" && a.exists);
|
|
54
|
+
let scaffoldAgents = false;
|
|
55
|
+
if (!hasContract) {
|
|
56
|
+
scaffoldAgents = opts.yes ? true : guard(
|
|
57
|
+
await confirm({
|
|
58
|
+
message: "No AGENTS.md found \u2014 scaffold a minimal one from the reckoning?",
|
|
59
|
+
initialValue: true
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
let gates = false;
|
|
64
|
+
if (facts.git.isRepo && facts.hooks.source === "none") {
|
|
65
|
+
gates = opts.yes ? true : guard(
|
|
66
|
+
await confirm({
|
|
67
|
+
message: "No git hooks found \u2014 install the local gates (pre-commit + pre-push)?",
|
|
68
|
+
initialValue: true
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const files = await planWorkflow(opts.cwd, facts, { agents: scaffoldAgents, gates });
|
|
73
|
+
if (files.length) {
|
|
74
|
+
renderPlan(files);
|
|
75
|
+
if (!opts.yes) {
|
|
76
|
+
const go = guard(
|
|
77
|
+
await confirm({
|
|
78
|
+
message: `Write ${files.filter((f) => !f.exists).length} file(s) now?`,
|
|
79
|
+
initialValue: true
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
if (!go) bail();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const result = await applyFiles(opts.cwd, files);
|
|
86
|
+
if (gates && facts.git.isRepo) await git(opts.cwd, ["config", "core.hooksPath", ".githooks"]);
|
|
87
|
+
const approved = result.written.length ? await scanProject(opts.cwd) : facts;
|
|
88
|
+
await writeBaseline(opts.cwd, {
|
|
89
|
+
packVersion: PACK_VERSION,
|
|
90
|
+
etymdVersion: VERSION,
|
|
91
|
+
approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92
|
+
profile,
|
|
93
|
+
facts: approved
|
|
94
|
+
});
|
|
95
|
+
const ignored = facts.git.isRepo ? await ensureCacheIgnored(opts.cwd) : false;
|
|
96
|
+
section("Onboarded");
|
|
97
|
+
for (const w of result.written) print(` ${glyph.ok} ${theme.dim("wrote")} ${theme.info(w)}`);
|
|
98
|
+
for (const sk of result.skipped) print(` ${glyph.bullet} ${theme.dim("kept")} ${theme.dim(sk)}`);
|
|
99
|
+
print(
|
|
100
|
+
` ${glyph.ok} ${theme.dim("baseline approved \u2192")} ${theme.info(".etymd/baseline.json")} ${theme.dim("(commit it \u2014 drift is measured against it)")}`
|
|
101
|
+
);
|
|
102
|
+
if (ignored)
|
|
103
|
+
print(
|
|
104
|
+
` ${glyph.ok} ${theme.dim("gitignored the transient cache")} ${theme.info(CACHE_DIR + "/")}`
|
|
105
|
+
);
|
|
106
|
+
outro(
|
|
107
|
+
[
|
|
108
|
+
`${theme.ok("Truth guard armed.")} Next:`,
|
|
109
|
+
` 1. Commit ${theme.code(".etymd/baseline.json")}${scaffoldAgents ? ` and complete ${theme.code("AGENTS.md")} (or run ${theme.code("etymd brief")})` : ""}.`,
|
|
110
|
+
` 2. Run ${theme.code("etymd audit")} \u2014 it verifies every instruction claim against the repo.`,
|
|
111
|
+
` 3. Gate CI on it: ${theme.code("etymd audit --fail-on risk")}.`
|
|
112
|
+
].join("\n")
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { run };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readLedger, resolveEntry, writeLedger } from './chunk-N64ZTBCW.js';
|
|
3
|
+
import { print, renderLedger, glyph, theme } from './chunk-WVYKYPKS.js';
|
|
4
|
+
|
|
5
|
+
// src/commands/ledger.ts
|
|
6
|
+
async function list(opts) {
|
|
7
|
+
const ledger = await readLedger(opts.cwd);
|
|
8
|
+
if (opts.json) {
|
|
9
|
+
print(JSON.stringify(ledger, null, 2));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
renderLedger(ledger);
|
|
13
|
+
}
|
|
14
|
+
async function resolve(opts, status) {
|
|
15
|
+
if (status === "dismissed" && !opts.reason?.trim()) {
|
|
16
|
+
throw new Error('dismiss needs a reason: etymd dismiss <finding-id> --reason "\u2026"');
|
|
17
|
+
}
|
|
18
|
+
const ledger = await readLedger(opts.cwd);
|
|
19
|
+
const { ledger: next, entry } = resolveEntry(ledger, opts.id, status, opts.reason?.trim());
|
|
20
|
+
if (!entry) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`no finding \`${opts.id}\` in the ledger \u2014 run \`etymd audit\` first so it is recorded, then retry`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
await writeLedger(opts.cwd, next);
|
|
26
|
+
const verb = status === "dismissed" ? "dismissed" : "accepted";
|
|
27
|
+
print(
|
|
28
|
+
` ${glyph.ok} ${theme.dim(verb)} ${theme.info(entry.id)}${entry.reason ? theme.dim(` \u2014 \u201C${entry.reason}\u201D`) : ""}`
|
|
29
|
+
);
|
|
30
|
+
print(` ${theme.dim("hidden from future audits until it disappears (then counted resolved).")}`);
|
|
31
|
+
}
|
|
32
|
+
async function dismiss(opts) {
|
|
33
|
+
await resolve(opts, "dismissed");
|
|
34
|
+
}
|
|
35
|
+
async function accept(opts) {
|
|
36
|
+
await resolve(opts, "accepted");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { accept, dismiss, list };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { scanProject } from './chunk-TCWKIMCU.js';
|
|
3
|
+
import './chunk-U33XLT5I.js';
|
|
4
|
+
import { writeCachedFacts } from './chunk-SMM2TW35.js';
|
|
5
|
+
import { print, renderFacts, theme } from './chunk-WVYKYPKS.js';
|
|
6
|
+
|
|
7
|
+
// src/commands/scan.ts
|
|
8
|
+
async function run(opts) {
|
|
9
|
+
const facts = await scanProject(opts.cwd);
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
print(JSON.stringify(facts, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
renderFacts(facts);
|
|
15
|
+
if (opts.save !== false) {
|
|
16
|
+
const target = await writeCachedFacts(opts.cwd, facts);
|
|
17
|
+
print();
|
|
18
|
+
print(` ${theme.dim("cached reckoning \u2192")} ${theme.info(target)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { run };
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "etymd",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"ai",
|
|
8
|
+
"agents",
|
|
9
|
+
"agents-md",
|
|
10
|
+
"claude-md",
|
|
11
|
+
"instructions",
|
|
12
|
+
"lint",
|
|
13
|
+
"drift",
|
|
14
|
+
"context-engineering",
|
|
15
|
+
"truth",
|
|
16
|
+
"developer-tools"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Alkis Yuv",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/triartleet/etymd.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/triartleet/etymd#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/triartleet/etymd/issues"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"etymd": "./dist/cli.js"
|
|
31
|
+
},
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"CHANGELOG.md"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.17"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"start": "node dist/cli.js",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"format": "prettier --write . && npm run guard:md",
|
|
58
|
+
"format:check": "prettier --check . && npm run guard:md",
|
|
59
|
+
"guard:md": "! grep -rnF --include=*.md --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.git '\\*\\*' .",
|
|
60
|
+
"prepare": "npm run build",
|
|
61
|
+
"prepublishOnly": "npm run build",
|
|
62
|
+
"changeset": "changeset",
|
|
63
|
+
"version:packages": "changeset version",
|
|
64
|
+
"release": "npm run build && changeset publish"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@clack/prompts": "0.7.0",
|
|
68
|
+
"commander": "12.1.0",
|
|
69
|
+
"picocolors": "1.1.1",
|
|
70
|
+
"yaml": "2.7.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@changesets/cli": "2.27.11",
|
|
74
|
+
"@types/node": "22.10.2",
|
|
75
|
+
"prettier": "3.4.2",
|
|
76
|
+
"tsup": "8.3.5",
|
|
77
|
+
"typescript": "5.7.2",
|
|
78
|
+
"vitest": "2.1.8"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public"
|
|
82
|
+
}
|
|
83
|
+
}
|