etymd 0.1.0 → 0.2.1

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.
@@ -1,13 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-N64ZTBCW.js';
3
- import { DEFAULT_CONFIG, readConfig, CONFIG_FILE, measureContext } from './chunk-I566LDX7.js';
4
- import { scanProject, PACK_VERSION, expandFileGlobs } from './chunk-TCWKIMCU.js';
5
- import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-SMM2TW35.js';
6
- import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff, pathExists, readJson, git, isDirectory, matchesAnyGlob, readText, isCiEnvironment, normalizeRelPath } from './chunk-WVYKYPKS.js';
2
+ import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-KBF3SW3N.js';
3
+ import { DEFAULT_CONFIG, readConfig, CONFIG_FILE, measureContext } from './chunk-ANCCGYSH.js';
4
+ import { scanProject, PACK_VERSION, expandFileGlobs } from './chunk-E2Q7WFPN.js';
5
+ import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-FZSIOCOZ.js';
6
+ import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath } from './chunk-LBRNZZZF.js';
7
7
  import path from 'node:path';
8
8
  import YAML from 'yaml';
9
9
  import { promises } from 'node:fs';
10
10
 
11
+ // src/engine/finding.ts
12
+ var TIER_ORDER = { risk: 0, gap: 1, polish: 2 };
13
+ var EFFORT_ORDER = { S: 0, M: 1, L: 2 };
14
+ function parseFailOnTier(value) {
15
+ if (value === "risk" || value === "gap" || value === "polish") return value;
16
+ throw new Error(`--fail-on must be risk|gap|polish, got \`${value}\``);
17
+ }
18
+ function meetsFailOn(findings, failOn) {
19
+ const threshold = TIER_ORDER[failOn];
20
+ return findings.some((f) => TIER_ORDER[f.tier] <= threshold);
21
+ }
22
+ function rankFindings(findings) {
23
+ return [...findings].sort(
24
+ (a, b) => TIER_ORDER[a.tier] - TIER_ORDER[b.tier] || EFFORT_ORDER[a.effort] - EFFORT_ORDER[b.effort]
25
+ );
26
+ }
27
+
11
28
  // src/lenses/context-economy.ts
12
29
  var LENS_ID = "context-economy";
13
30
  DEFAULT_CONFIG.context.totalWords;
@@ -214,8 +231,8 @@ function parseGithubWorkflow(text, file, scripts) {
214
231
  const steps = Array.isArray(job.steps) ? job.steps : [];
215
232
  const tools = /* @__PURE__ */ new Set();
216
233
  for (const step of steps) {
217
- const run2 = step?.run;
218
- if (typeof run2 === "string") for (const t of matchTools(run2, scripts)) tools.add(t);
234
+ const run = step?.run;
235
+ if (typeof run === "string") for (const t of matchTools(run, scripts)) tools.add(t);
219
236
  }
220
237
  if (!tools.size) continue;
221
238
  jobs.push({
@@ -729,7 +746,13 @@ function packageManagerUsage(text) {
729
746
  }
730
747
  return counts;
731
748
  }
732
- var KNOWN_DOC_REFS = ["AGENTS.md", "CLAUDE.md", "PROJECT_CONTEXT.md", "GEMINI.md"];
749
+ var KNOWN_DOC_REFS = [
750
+ "AGENTS.md",
751
+ "CLAUDE.md",
752
+ "PROJECT_CONTEXT.md",
753
+ "DECISIONS.md",
754
+ "GEMINI.md"
755
+ ];
733
756
  function extractDocRefs(text) {
734
757
  return KNOWN_DOC_REFS.filter((name) => text.includes(name));
735
758
  }
@@ -1027,25 +1050,228 @@ var instructionTruthLens = {
1027
1050
  };
1028
1051
  }
1029
1052
  };
1030
-
1031
- // src/engine/finding.ts
1032
- var TIER_ORDER = { risk: 0, gap: 1, polish: 2 };
1033
- var EFFORT_ORDER = { S: 0, M: 1, L: 2 };
1034
- function rankFindings(findings) {
1035
- return [...findings].sort(
1036
- (a, b) => TIER_ORDER[a.tier] - TIER_ORDER[b.tier] || EFFORT_ORDER[a.effort] - EFFORT_ORDER[b.effort]
1037
- );
1053
+ var LENS_ID4 = "state-freshness";
1054
+ var DECISIONS_FORMAT_MARKER = "<!-- decisions-format: 1 -->";
1055
+ var MS_PER_DAY = 864e5;
1056
+ function parseDecisionEntries(text) {
1057
+ const headings = [...text.matchAll(/^## .*$/gm)];
1058
+ const entries = [];
1059
+ for (let i = 0; i < headings.length; i++) {
1060
+ const h = headings[i];
1061
+ const m = /^## (D-(\d+))\b/.exec(h[0]);
1062
+ if (!m) continue;
1063
+ const start = (h.index ?? 0) + h[0].length;
1064
+ const end = i + 1 < headings.length ? headings[i + 1].index : void 0;
1065
+ entries.push({ id: m[1], num: Number(m[2]), block: text.slice(start, end) });
1066
+ }
1067
+ return entries;
1038
1068
  }
1069
+ function checkIdSequence(file, entries) {
1070
+ const findings = [];
1071
+ const nextFree = Math.max(0, ...entries.map((e) => e.num)) + 1;
1072
+ const seen = /* @__PURE__ */ new Map();
1073
+ const duplicated = /* @__PURE__ */ new Set();
1074
+ let prev;
1075
+ for (const entry of entries) {
1076
+ if (seen.has(entry.num)) {
1077
+ if (!duplicated.has(entry.num)) {
1078
+ duplicated.add(entry.num);
1079
+ findings.push({
1080
+ id: `${LENS_ID4}/duplicate-id:${file}:${entry.id}`,
1081
+ lens: LENS_ID4,
1082
+ tier: "gap",
1083
+ claim: `${file} carries more than one ${entry.id} entry`,
1084
+ evidence: [`${file}: ${entry.id} appears twice`],
1085
+ why: "Two decisions under one id cannot be cited, superseded, or dismissed unambiguously \u2014 append races collide exactly here.",
1086
+ action: `Rename the later entry to D-${String(nextFree).padStart(3, "0")} (the next free id).`,
1087
+ effort: "S",
1088
+ confidence: "high"
1089
+ });
1090
+ }
1091
+ } else {
1092
+ seen.set(entry.num, entry);
1093
+ if (prev && entry.num < prev.num) {
1094
+ findings.push({
1095
+ id: `${LENS_ID4}/id-order:${file}:${entry.id}`,
1096
+ lens: LENS_ID4,
1097
+ tier: "gap",
1098
+ claim: `${file} lists ${entry.id} after ${prev.id} \u2014 ids out of append order`,
1099
+ evidence: [`${file}: ${prev.id} precedes ${entry.id}`],
1100
+ why: "An append-only record reads in id order; out-of-order ids make the newest decision hard to find and the next id hard to pick.",
1101
+ action: `Rename the out-of-order entry into sequence (next free id: D-${String(nextFree).padStart(3, "0")}).`,
1102
+ effort: "S",
1103
+ confidence: "high"
1104
+ });
1105
+ }
1106
+ prev = entry;
1107
+ }
1108
+ }
1109
+ return findings;
1110
+ }
1111
+ function checkFormatFields(file, entries, today) {
1112
+ const findings = [];
1113
+ for (const entry of entries) {
1114
+ if (!/Scope[\s*]*:/.test(entry.block)) {
1115
+ findings.push({
1116
+ id: `${LENS_ID4}/scope-missing:${file}:${entry.id}`,
1117
+ lens: LENS_ID4,
1118
+ tier: "gap",
1119
+ claim: `${file} ${entry.id} has no Scope: field`,
1120
+ evidence: [`${file}: ${entry.id}`],
1121
+ why: "A decision without a scope binds nobody \u2014 a reader cannot tell whether it covers the project they are working in.",
1122
+ action: "Add a Scope: line naming what the decision binds.",
1123
+ effort: "S",
1124
+ confidence: "high"
1125
+ });
1126
+ }
1127
+ const revisit = /Revisit[\s*]*:[\s*]*(\d{4}-\d{2}-\d{2})/.exec(entry.block);
1128
+ if (revisit && revisit[1] < today) {
1129
+ findings.push({
1130
+ id: `${LENS_ID4}/revisit-due:${file}:${entry.id}`,
1131
+ lens: LENS_ID4,
1132
+ tier: "gap",
1133
+ claim: `${file} ${entry.id} was due for revisit on ${revisit[1]}`,
1134
+ evidence: [`${file}: ${entry.id} Revisit: ${revisit[1]}`],
1135
+ why: "Review debt is due \u2014 a Revisit date is a promise to re-evaluate, and a past one silently hardens into policy.",
1136
+ action: "Re-evaluate the decision: supersede it or move the Revisit date.",
1137
+ effort: "S",
1138
+ confidence: "high"
1139
+ });
1140
+ }
1141
+ }
1142
+ return findings;
1143
+ }
1144
+ var stateFreshnessLens = {
1145
+ id: LENS_ID4,
1146
+ version: "1",
1147
+ title: "State freshness",
1148
+ kind: "truth",
1149
+ async run(ctx) {
1150
+ const budgets = ctx.config?.config.state ?? DEFAULT_CONFIG.state;
1151
+ const findings = [];
1152
+ const disclosures = [...ctx.config?.problems ?? []];
1153
+ const outOfScope = [];
1154
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1155
+ const stateArtifacts = ctx.facts.artifacts.filter((a) => a.kind === "state" && a.exists);
1156
+ const decisionArtifacts = ctx.facts.artifacts.filter((a) => a.kind === "decisions" && a.exists);
1157
+ if (stateArtifacts.length === 0 && decisionArtifacts.length === 0) {
1158
+ disclosures.push("No state or decisions artifacts detected \u2014 nothing to check.");
1159
+ }
1160
+ const freshness = ctx.facts.freshness;
1161
+ if (!freshness) {
1162
+ disclosures.push("Scan carried no freshness facts \u2014 staleness unchecked.");
1163
+ } else {
1164
+ for (const u of freshness.unverifiable) {
1165
+ disclosures.push(`Freshness of ${u.path} is unverifiable (${u.reason}) \u2014 not flagged.`);
1166
+ }
1167
+ for (const a of stateArtifacts) {
1168
+ const fact = freshness.artifacts.find((f) => f.artifactId === a.id);
1169
+ if (!fact || !freshness.repoLastCommit) continue;
1170
+ if (fact.dirty) {
1171
+ disclosures.push(
1172
+ `${a.path} has uncommitted changes \u2014 modified since its last commit; treated fresh-now, not flagged.`
1173
+ );
1174
+ continue;
1175
+ }
1176
+ if (!fact.commitsSince) continue;
1177
+ const gapDays = Math.floor(
1178
+ (Date.parse(freshness.repoLastCommit) - Date.parse(fact.lastCommit)) / MS_PER_DAY
1179
+ );
1180
+ if (gapDays <= budgets.staleAfterDays) continue;
1181
+ const escalated = gapDays > budgets.staleAfterDays * 3;
1182
+ findings.push({
1183
+ id: `${LENS_ID4}/stale-state:${a.path}`,
1184
+ lens: LENS_ID4,
1185
+ tier: escalated ? "risk" : "gap",
1186
+ claim: `${a.path} trails the repo by ${gapDays} days of commit traffic \u2014 it says "now" but the repo moved on`,
1187
+ evidence: [
1188
+ `${a.path} last commit: ${fact.lastCommit}`,
1189
+ `repo last commit: ${freshness.repoLastCommit}`
1190
+ ],
1191
+ why: escalated ? `Over three times the ${budgets.staleAfterDays}-day threshold while commits kept landing \u2014 every session starts from a picture of the project that is no longer true.` : `A state doc more than ${budgets.staleAfterDays} days behind continued commit traffic misleads every session that loads it.`,
1192
+ action: "Refresh the state doc (or record why it is still current).",
1193
+ effort: "S",
1194
+ confidence: "high"
1195
+ });
1196
+ }
1197
+ }
1198
+ for (const a of stateArtifacts) {
1199
+ const text = await readText(path.join(ctx.root, a.path));
1200
+ if (text === null) {
1201
+ disclosures.push(`${a.path} could not be read \u2014 unexamined, not clean.`);
1202
+ outOfScope.push(a.path);
1203
+ continue;
1204
+ }
1205
+ if (text.length > budgets.maxChars) {
1206
+ findings.push({
1207
+ id: `${LENS_ID4}/state-over-budget:${a.path}`,
1208
+ lens: LENS_ID4,
1209
+ tier: "gap",
1210
+ claim: `${a.path} is ${text.length} chars \u2014 over the ${budgets.maxChars}-char state budget`,
1211
+ evidence: [`${a.path}: ${text.length} chars`],
1212
+ why: "Session-injection hooks truncate state around 10,000 chars \u2014 an over-budget state doc gets cut mid-sentence, and every session pays its full weight before the task begins.",
1213
+ action: "Trim to budget; move overflow into decisions/docs and keep a pointer.",
1214
+ effort: "M",
1215
+ confidence: "high"
1216
+ });
1217
+ }
1218
+ }
1219
+ for (const a of decisionArtifacts) {
1220
+ const text = await readText(path.join(ctx.root, a.path));
1221
+ if (text === null) {
1222
+ disclosures.push(
1223
+ `${a.path} recognized as a decisions convention (directory) \u2014 age-exempt; per-entry format checks apply only to marker-carrying decisions files.`
1224
+ );
1225
+ continue;
1226
+ }
1227
+ const entries = parseDecisionEntries(text);
1228
+ findings.push(...checkIdSequence(a.path, entries));
1229
+ if (!text.includes(DECISIONS_FORMAT_MARKER)) {
1230
+ disclosures.push(
1231
+ `${a.path} carries no \`${DECISIONS_FORMAT_MARKER}\` marker \u2014 format checks skipped (forward-only, never retroactive); id-sequence checks still ran.`
1232
+ );
1233
+ outOfScope.push(a.path);
1234
+ continue;
1235
+ }
1236
+ findings.push(...checkFormatFields(a.path, entries, today));
1237
+ }
1238
+ disclosures.push(
1239
+ `Thresholds: staleAfterDays ${budgets.staleAfterDays} (3x escalates to risk), state budget ${budgets.maxChars} chars (${budgets.staleAfterDays === DEFAULT_CONFIG.state.staleAfterDays && budgets.maxChars === DEFAULT_CONFIG.state.maxChars ? `defaults \u2014 override under \`state\` in ${CONFIG_FILE}` : `set in ${CONFIG_FILE}`}). Decisions artifacts are exempt from age \u2014 old decisions are history, not defects.`
1240
+ );
1241
+ return {
1242
+ lens: LENS_ID4,
1243
+ version: "1",
1244
+ title: "State freshness",
1245
+ kind: "truth",
1246
+ status: "ran",
1247
+ disclosures,
1248
+ findings,
1249
+ ...outOfScope.length ? { outOfScope } : {}
1250
+ };
1251
+ }
1252
+ };
1039
1253
 
1040
1254
  // src/engine/run.ts
1041
- var LENSES = [instructionTruthLens, gateIntegrityLens, contextEconomyLens];
1255
+ var LENSES = [
1256
+ instructionTruthLens,
1257
+ stateFreshnessLens,
1258
+ gateIntegrityLens,
1259
+ contextEconomyLens
1260
+ ];
1042
1261
  async function runAudit(root, opts = {}) {
1043
- const facts = await scanProject(root);
1044
- if ((opts.persistLedger ?? true) || await pathExists(path.join(root, ETYMD_DIR))) {
1262
+ const facts = await scanProject(root, { upstreamRemote: opts.upstreamRemote });
1263
+ const ledgerRoot = opts.ledgerRoot ?? root;
1264
+ if (!opts.readOnlyRoot && ((opts.persistLedger ?? true) || await pathExists(path.join(root, ETYMD_DIR)))) {
1045
1265
  await writeCachedFacts(root, facts);
1046
1266
  }
1047
1267
  const baseline = await readBaseline(root);
1048
- const config = await readConfig(root);
1268
+ let config = await readConfig(root);
1269
+ if (opts.stateBudgets) {
1270
+ config = {
1271
+ ...config,
1272
+ config: { ...config.config, state: { ...config.config.state, ...opts.stateBudgets } }
1273
+ };
1274
+ }
1049
1275
  const profile = baseline?.profile ?? deriveProfile(facts);
1050
1276
  const selected = LENSES.filter(
1051
1277
  (lens) => (!opts.kind || lens.kind === opts.kind) && (!opts.lensIds || opts.lensIds.includes(lens.id))
@@ -1068,12 +1294,13 @@ async function runAudit(root, opts = {}) {
1068
1294
  }
1069
1295
  }
1070
1296
  const all = reports.flatMap((r) => r.findings);
1071
- const previous = await readLedger(root);
1297
+ const previous = await readLedger(ledgerRoot);
1072
1298
  const outOfScope = reports.flatMap((r) => r.outOfScope ?? []);
1073
1299
  const { ledger, diff } = reconcileLedger(previous, all, void 0, outOfScope);
1074
1300
  const fullRun = selected.length === LENSES.length;
1075
- if ((opts.persistLedger ?? true) && fullRun) {
1076
- await writeLedger(root, ledger);
1301
+ const ledgerWritable = !opts.readOnlyRoot || ledgerRoot !== root;
1302
+ if ((opts.persistLedger ?? true) && fullRun && ledgerWritable) {
1303
+ await writeLedger(ledgerRoot, ledger);
1077
1304
  }
1078
1305
  return {
1079
1306
  facts,
@@ -1087,62 +1314,4 @@ async function runAudit(root, opts = {}) {
1087
1314
  };
1088
1315
  }
1089
1316
 
1090
- // src/commands/audit.ts
1091
- var TIER_RANK = { risk: 0, gap: 1, polish: 2 };
1092
- async function run(opts) {
1093
- const result = await runAudit(opts.cwd, {
1094
- kind: opts.truth ? "truth" : void 0,
1095
- lensIds: opts.lens ? [opts.lens] : void 0,
1096
- persistLedger: !opts.noLedger
1097
- });
1098
- if (opts.failOn) {
1099
- const threshold = TIER_RANK[opts.failOn];
1100
- if (result.findings.some((f) => TIER_RANK[f.tier] <= threshold)) {
1101
- process.exitCode = 1;
1102
- }
1103
- }
1104
- if (opts.json) {
1105
- print(
1106
- JSON.stringify(
1107
- {
1108
- profile: result.profile,
1109
- baseline: result.baseline ? { packVersion: result.baseline.packVersion, approvedAt: result.baseline.approvedAt } : null,
1110
- reports: result.reports.map(({ findings: _findings, ...r }) => r),
1111
- findings: result.findings,
1112
- diff: {
1113
- new: result.diff.new.map((f) => f.id),
1114
- stillOpen: result.diff.stillOpen.map((f) => f.id),
1115
- regressed: result.diff.regressed.map((f) => f.id),
1116
- resolved: result.diff.resolved.map((e) => e.id),
1117
- dismissed: result.diff.dismissed.map((f) => f.id),
1118
- accepted: result.diff.accepted.map((f) => f.id)
1119
- }
1120
- },
1121
- null,
1122
- 2
1123
- )
1124
- );
1125
- return;
1126
- }
1127
- section(
1128
- `${opts.truth ? "Doctor \u2014 is this still true?" : "Audit"} ${theme.dim(`\xB7 ${result.facts.name} \xB7 ${result.profile} profile`)}`
1129
- );
1130
- if (!result.baseline && !opts.truth) {
1131
- print(
1132
- ` ${theme.dim("no committed baseline \u2014 run `etymd init` to approve one; drift checks are limited")}`
1133
- );
1134
- }
1135
- renderLensCoverage(result.reports);
1136
- section(`Findings ${theme.dim("(ranked: risk \u2192 gap \u2192 polish, cheapest first)")}`);
1137
- renderFindings(result.findings);
1138
- renderLedgerDiff(result.diff);
1139
- const top = result.findings[0];
1140
- if (top) {
1141
- print();
1142
- print(
1143
- ` ${theme.dim("next:")} ${theme.heading(top.claim)}${top.action ? theme.dim(` \u2014 ${top.action}`) : ""}`
1144
- );
1145
- }
1146
- }
1147
-
1148
- export { run };
1317
+ export { meetsFailOn, parseFailOnTier, runAudit };
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { VERSION } from './chunk-U33XLT5I.js';
2
+ import { VERSION } from './chunk-C4Z6NXDO.js';
3
3
  import path from 'node:path';
4
4
  import { Command } from 'commander';
5
5
  import pc from 'picocolors';
@@ -23,12 +23,15 @@ var program = new Command();
23
23
  program.name("etymd").description(
24
24
  "Keep your agent instructions true \u2014 verify AGENTS.md & friends against the actual repo, on a budget, with drift caught over time."
25
25
  ).version(VERSION, "-v, --version").option("--cwd <dir>", "run against a different project directory", process.cwd());
26
- program.command("audit").description("Verify every instruction claim against the repo: ranked findings + ledger diff").option("--json", "print the machine-stable audit result as JSON").option("--truth", "truth lenses only (the doctor subset)").option("--lens <id>", "run a single lens (instruction-truth | gate-integrity | context-economy)").option("--no-ledger", "read-only: do not persist the reconciled ledger").option(
26
+ program.command("audit").description("Verify every instruction claim against the repo: ranked findings + ledger diff").option("--json", "print the machine-stable audit result as JSON").option("--truth", "truth lenses only (the doctor subset)").option(
27
+ "--lens <id>",
28
+ "run a single lens (instruction-truth | state-freshness | gate-integrity | context-economy)"
29
+ ).option("--no-ledger", "read-only: do not persist the reconciled ledger").option(
27
30
  "--fail-on <tier>",
28
31
  "exit non-zero when findings at/above this tier exist (risk|gap|polish)"
29
32
  ).action(
30
33
  (opts, cmd) => action(async () => {
31
- const { run } = await import('./audit-OHOSWOWA.js');
34
+ const { run } = await import('./audit-YH74YZFQ.js');
32
35
  await run({
33
36
  cwd: resolveCwd(cmd),
34
37
  json: opts.json,
@@ -41,7 +44,7 @@ program.command("audit").description("Verify every instruction claim against the
41
44
  );
42
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(
43
46
  (opts, cmd) => action(async () => {
44
- const { run } = await import('./init-V627UXKU.js');
47
+ const { run } = await import('./init-ISEFZWPV.js');
45
48
  await run({ cwd: resolveCwd(cmd), yes: opts.yes });
46
49
  })
47
50
  );
@@ -49,55 +52,112 @@ program.command("approve").description(
49
52
  "Re-approve the committed baseline after intentional structural changes (non-interactive)"
50
53
  ).action(
51
54
  (_opts, cmd) => action(async () => {
52
- const { run } = await import('./approve-WM6OJHQG.js');
55
+ const { run } = await import('./approve-X3AW7KLD.js');
53
56
  await run({ cwd: resolveCwd(cmd) });
54
57
  })
55
58
  );
56
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(
57
60
  (opts, cmd) => action(async () => {
58
- const { run } = await import('./scan-YL7JHLUA.js');
61
+ const { run } = await import('./scan-ADZ2RKYY.js');
59
62
  await run({ cwd: resolveCwd(cmd), json: opts.json, save: opts.save });
60
63
  })
61
64
  );
62
65
  program.command("doctor").description('Alias for `audit --truth` \u2014 "are the recorded instructions still true?"').option("--json", "print findings as JSON").action(
63
66
  (opts, cmd) => action(async () => {
64
- const { run } = await import('./doctor-NNRJTNIY.js');
67
+ const { run } = await import('./doctor-EAA4IP4Y.js');
65
68
  await run({ cwd: resolveCwd(cmd), json: opts.json });
66
69
  })
67
70
  );
68
71
  program.command("context").description("Measure the always-loaded context footprint and flag extraction candidates").option("--json", "print the budget as JSON").action(
69
72
  (opts, cmd) => action(async () => {
70
- const { run } = await import('./context-TT5H76LA.js');
73
+ const { run } = await import('./context-GYCLR3TG.js');
71
74
  await run({ cwd: resolveCwd(cmd), json: opts.json });
72
75
  })
73
76
  );
74
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(
75
78
  (opts, cmd) => action(async () => {
76
- const { run } = await import('./brief-2MPNTITQ.js');
79
+ const { run } = await import('./brief-RT6KE4CN.js');
77
80
  await run({ cwd: resolveCwd(cmd), human: opts.human });
78
81
  })
79
82
  );
80
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(
81
84
  (opts, cmd) => action(async () => {
82
- const { list } = await import('./ledger-YFTYCOIC.js');
85
+ const { list } = await import('./ledger-RHQJH5EA.js');
83
86
  await list({ cwd: resolveCwd(cmd), json: opts.json });
84
87
  })
85
88
  );
86
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(
87
90
  (id, opts, cmd) => action(async () => {
88
- const { dismiss } = await import('./ledger-YFTYCOIC.js');
91
+ const { dismiss } = await import('./ledger-RHQJH5EA.js');
89
92
  await dismiss({ cwd: resolveCwd(cmd), id, reason: opts.reason });
90
93
  })
91
94
  );
92
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(
93
96
  (id, opts, cmd) => action(async () => {
94
- const { accept } = await import('./ledger-YFTYCOIC.js');
97
+ const { accept } = await import('./ledger-RHQJH5EA.js');
95
98
  await accept({ cwd: resolveCwd(cmd), id, reason: opts.reason });
96
99
  })
97
100
  );
101
+ var fleet = program.command("fleet").description(
102
+ "Sweep every project in a fleet manifest: per-repo audits + manifest-truth wall checks (schemas EXPERIMENTAL through 0.2.x)"
103
+ ).option(
104
+ "--manifest <file>",
105
+ "the fleet manifest (registry.json / legacy sources.json) \u2014 required unless the cwd holds registry.json"
106
+ ).option("--only <names...>", "sweep only these registered names").option("--profile <profile>", "sweep only entries with this profile (personal|corp)").option("--truth", "truth lenses only per repo (the doctor subset)").option(
107
+ "--persist-ledgers",
108
+ "persist per-repo ledgers \u2014 personal-profile entries that already carry .etymd only; corp worktrees are never written"
109
+ ).option("--json", "print the machine schema (EXPERIMENTAL through 0.2.x, local-only)").option(
110
+ "--fail-on <tier>",
111
+ "exit non-zero when findings at/above this tier exist (risk|gap|polish)"
112
+ ).action(
113
+ (opts, cmd) => action(async () => {
114
+ const { sweep } = await import('./fleet-IS6YWDDV.js');
115
+ await sweep({
116
+ cwd: resolveCwd(cmd),
117
+ manifest: opts.manifest,
118
+ only: opts.only,
119
+ profile: opts.profile,
120
+ truth: opts.truth,
121
+ persistLedgers: opts.persistLedgers,
122
+ json: opts.json,
123
+ failOn: opts.failOn
124
+ });
125
+ })
126
+ );
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
+ (_opts, cmd) => action(async () => {
129
+ const { check } = await import('./fleet-IS6YWDDV.js');
130
+ const opts = cmd.optsWithGlobals();
131
+ await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
132
+ })
133
+ );
134
+ fleet.command("dismiss").argument("<name>", "the registered project name").argument("<finding-id>", "the finding id (from `etymd fleet`) to dismiss").description(
135
+ "Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
136
+ ).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
+ (name, id, _opts, cmd) => action(async () => {
138
+ const { dismiss } = await import('./fleet-IS6YWDDV.js');
139
+ const opts = cmd.optsWithGlobals();
140
+ await dismiss({
141
+ cwd: resolveCwd(cmd),
142
+ manifest: opts.manifest,
143
+ name,
144
+ id,
145
+ reason: opts.reason
146
+ });
147
+ })
148
+ );
149
+ fleet.command("accept").argument("<name>", "the registered project name").argument("<finding-id>", "the finding id (from `etymd fleet`) to accept").description(
150
+ "Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
151
+ ).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
+ (name, id, _opts, cmd) => action(async () => {
153
+ const { accept } = await import('./fleet-IS6YWDDV.js');
154
+ const opts = cmd.optsWithGlobals();
155
+ await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
156
+ })
157
+ );
98
158
  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(
99
159
  (opts, cmd) => action(async () => {
100
- const { run } = await import('./gates-KPDEUC36.js');
160
+ const { run } = await import('./gates-3NPKF4IZ.js');
101
161
  await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
102
162
  })
103
163
  );
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { readConfig, measureContext } from './chunk-I566LDX7.js';
3
- import './chunk-SMM2TW35.js';
4
- import { print, renderContext } from './chunk-WVYKYPKS.js';
2
+ import { readConfig, measureContext } from './chunk-ANCCGYSH.js';
3
+ import './chunk-FZSIOCOZ.js';
4
+ import { print, renderContext } from './chunk-LBRNZZZF.js';
5
5
 
6
6
  // src/commands/context.ts
7
7
  async function run(opts) {
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './chunk-6W3J4PJ6.js';
3
+ import './chunk-SS7VGLJ5.js';
4
+ import './chunk-KBF3SW3N.js';
5
+ import './chunk-ANCCGYSH.js';
6
+ import './chunk-E2Q7WFPN.js';
7
+ import './chunk-C4Z6NXDO.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 };