etymd 0.9.1 → 0.11.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 (39) hide show
  1. package/CHANGELOG.md +120 -14
  2. package/README.md +50 -2
  3. package/dist/{approve-HED3NCTE.js → approve-OUJ4UPGK.js} +5 -5
  4. package/dist/audit-A75FVXHD.js +12 -0
  5. package/dist/{brief-2CXAUDNN.js → brief-MYUESUMM.js} +7 -7
  6. package/dist/{chunk-ULM25GWG.js → chunk-2KVQBL4E.js} +1 -1
  7. package/dist/{chunk-V2SOHPXF.js → chunk-2VLNI3L2.js} +1 -1
  8. package/dist/{chunk-OAFYLBVG.js → chunk-3E2IPCRY.js} +3 -3
  9. package/dist/{chunk-IV3FYVTS.js → chunk-4VPBP6K6.js} +4 -3
  10. package/dist/{chunk-UN7IAYM6.js → chunk-5GQ52DK6.js} +5 -5
  11. package/dist/{chunk-D2RI3CCT.js → chunk-DWL2IKZH.js} +12 -3
  12. package/dist/{chunk-JSUYV26K.js → chunk-F75Q43BC.js} +4 -4
  13. package/dist/{chunk-EF6BPBG6.js → chunk-GWGKEPRX.js} +2 -2
  14. package/dist/{chunk-WENIJMVV.js → chunk-HRJJQCMT.js} +54 -18
  15. package/dist/{chunk-AQM4GYGN.js → chunk-J7XVOTZL.js} +1 -1
  16. package/dist/{chunk-C7LBUFNU.js → chunk-JHZ2BN4U.js} +3 -3
  17. package/dist/{chunk-KGVRZ6WC.js → chunk-JVLWM27I.js} +9 -9
  18. package/dist/{chunk-6DUDIVIC.js → chunk-LSZGCKIQ.js} +1 -1
  19. package/dist/cli.js +19 -19
  20. package/dist/config-XAH6PA5G.js +4 -0
  21. package/dist/{context-5ANOP2IJ.js → context-F63RSIBH.js} +4 -4
  22. package/dist/doctor-OZQTOUFR.js +19 -0
  23. package/dist/{fleet-DPJSZDEY.js → fleet-Z7W3QBEV.js} +18 -18
  24. package/dist/{gates-RR3V4PII.js → gates-47YTMMLZ.js} +10 -10
  25. package/dist/generate-PMCP37DC.js +6 -0
  26. package/dist/index.d.ts +9 -1
  27. package/dist/index.js +192 -147
  28. package/dist/{init-RC35RS5J.js → init-QLGIYFCX.js} +10 -10
  29. package/dist/ledger-4FOZ5HAB.js +5 -0
  30. package/dist/{scan-MBXSD5RO.js → scan-VWEXWM6G.js} +5 -5
  31. package/dist/scan-W7RHZVSF.js +5 -0
  32. package/dist/{screen-EHNK6ANK.js → screen-NSWNC7VQ.js} +125 -16
  33. package/package.json +4 -4
  34. package/dist/audit-Y35XVD6Z.js +0 -12
  35. package/dist/config-7WCVZCM6.js +0 -4
  36. package/dist/doctor-DXXQJ2L3.js +0 -19
  37. package/dist/generate-IPY5QIR5.js +0 -6
  38. package/dist/ledger-B2XGANTH.js +0 -5
  39. package/dist/scan-KX522VKV.js +0 -5
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { execFile } from 'node:child_process';
2
- import { promises } from 'node:fs';
3
- import { promisify } from 'node:util';
4
- import path from 'node:path';
5
- import { createHash } from 'node:crypto';
1
+ import { execFile } from 'child_process';
2
+ import { promises } from 'fs';
3
+ import path2 from 'path';
4
+ import { promisify } from 'util';
5
+ import { createHash } from 'crypto';
6
6
  import YAML from 'yaml';
7
- import os from 'node:os';
7
+ import os from 'os';
8
8
 
9
9
  var __defProp = Object.defineProperty;
10
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -20,7 +20,7 @@ var __export = (target, all) => {
20
20
  var PACK_VERSION;
21
21
  var init_version = __esm({
22
22
  "src/pack/version.ts"() {
23
- PACK_VERSION = "6";
23
+ PACK_VERSION = "8";
24
24
  }
25
25
  });
26
26
 
@@ -30,7 +30,7 @@ var init_package = __esm({
30
30
  "package.json"() {
31
31
  package_default = {
32
32
  name: "etymd",
33
- version: "0.9.1",
33
+ version: "0.11.0",
34
34
  description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules & skills against the actual repo, with drift caught over time and a regression ledger.",
35
35
  keywords: [
36
36
  "cli",
@@ -99,12 +99,12 @@ var init_package = __esm({
99
99
  yaml: "2.9.0"
100
100
  },
101
101
  devDependencies: {
102
- "@changesets/cli": "2.27.11",
102
+ "@changesets/cli": "2.31.1",
103
103
  "@types/node": "22.10.2",
104
104
  prettier: "3.4.2",
105
- tsup: "8.3.5",
105
+ tsup: "8.5.1",
106
106
  typescript: "5.7.2",
107
- vitest: "2.1.8"
107
+ vitest: "4.1.10"
108
108
  },
109
109
  publishConfig: {
110
110
  access: "public"
@@ -244,15 +244,15 @@ async function detectPackageManager(root, pkg) {
244
244
  ["bun.lockb", "bun"]
245
245
  ];
246
246
  for (const [file, pm] of lock) {
247
- if (await pathExists(path.join(root, file))) return pm;
247
+ if (await pathExists(path2.join(root, file))) return pm;
248
248
  }
249
249
  return "unknown";
250
250
  }
251
251
  async function detectWorkspace(root, pkg) {
252
- if (await pathExists(path.join(root, "nx.json"))) return { kind: "nx", packageGlobs: [] };
253
- if (await pathExists(path.join(root, "turbo.json"))) return { kind: "turbo", packageGlobs: [] };
254
- if (await pathExists(path.join(root, "lerna.json"))) return { kind: "lerna", packageGlobs: [] };
255
- const pnpmWs = await readText(path.join(root, "pnpm-workspace.yaml"));
252
+ if (await pathExists(path2.join(root, "nx.json"))) return { kind: "nx", packageGlobs: [] };
253
+ if (await pathExists(path2.join(root, "turbo.json"))) return { kind: "turbo", packageGlobs: [] };
254
+ if (await pathExists(path2.join(root, "lerna.json"))) return { kind: "lerna", packageGlobs: [] };
255
+ const pnpmWs = await readText(path2.join(root, "pnpm-workspace.yaml"));
256
256
  if (pnpmWs) {
257
257
  const globs = pnpmWs.split("\n").map((l) => l.match(/^\s*-\s*["']?([^"'#]+)["']?/)?.[1]?.trim()).filter((g) => Boolean(g));
258
258
  return { kind: "pnpm", packageGlobs: globs };
@@ -270,21 +270,21 @@ async function listWorkspacePackages(root, globs) {
270
270
  const found = [];
271
271
  const seen = /* @__PURE__ */ new Set();
272
272
  const addFromDir = async (dir) => {
273
- const pkgPath = path.join(dir, "package.json");
273
+ const pkgPath = path2.join(dir, "package.json");
274
274
  if (seen.has(pkgPath)) return;
275
275
  const pkg = await readJson(pkgPath);
276
276
  if (!pkg?.name) return;
277
277
  seen.add(pkgPath);
278
278
  found.push({
279
279
  name: pkg.name,
280
- dir: path.relative(root, dir) || ".",
280
+ dir: path2.relative(root, dir) || ".",
281
281
  private: Boolean(pkg.private),
282
282
  version: pkg.version
283
283
  });
284
284
  };
285
285
  for (const glob of globs) {
286
286
  const base = glob.replace(/\/\*\*?$/, "");
287
- const parent = path.join(root, base);
287
+ const parent = path2.join(root, base);
288
288
  if (!await isDirectory(parent)) continue;
289
289
  let entries;
290
290
  try {
@@ -294,7 +294,7 @@ async function listWorkspacePackages(root, globs) {
294
294
  }
295
295
  for (const entry of entries) {
296
296
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) continue;
297
- const child = path.join(parent, entry);
297
+ const child = path2.join(parent, entry);
298
298
  if (await isDirectory(child)) await addFromDir(child);
299
299
  }
300
300
  }
@@ -307,7 +307,7 @@ async function expandFileGlobs(root, globs, cap = 500) {
307
307
  if (out.size >= cap) return;
308
308
  let entries;
309
309
  try {
310
- entries = await promises.readdir(path.join(root, rel), { withFileTypes: true });
310
+ entries = await promises.readdir(path2.join(root, rel), { withFileTypes: true });
311
311
  } catch {
312
312
  return;
313
313
  }
@@ -334,7 +334,7 @@ async function expandFileGlobs(root, globs, cap = 500) {
334
334
  (r) => ![...roots].some((other) => other !== r && (other === "" || r.startsWith(other + "/")))
335
335
  );
336
336
  for (const r of minimal) {
337
- if (r && !await isDirectory(path.join(root, r))) continue;
337
+ if (r && !await isDirectory(path2.join(root, r))) continue;
338
338
  await walk(r);
339
339
  }
340
340
  return [...out].sort();
@@ -363,13 +363,13 @@ function detectFrameworks(pkg) {
363
363
  return out;
364
364
  }
365
365
  async function detectCi(root) {
366
- if (await pathExists(path.join(root, ".gitlab-ci.yml"))) {
366
+ if (await pathExists(path2.join(root, ".gitlab-ci.yml"))) {
367
367
  return { system: "gitlab", files: [".gitlab-ci.yml"] };
368
368
  }
369
- const workflows = path.join(root, ".github", "workflows");
369
+ const workflows = path2.join(root, ".github", "workflows");
370
370
  if (await isDirectory(workflows)) {
371
371
  try {
372
- const files = (await promises.readdir(workflows)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).map((f) => path.join(".github/workflows", f));
372
+ const files = (await promises.readdir(workflows)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).map((f) => path2.join(".github/workflows", f));
373
373
  if (files.length) return { system: "github", files };
374
374
  } catch {
375
375
  }
@@ -384,7 +384,7 @@ async function hasLintStagedConfig(root, pkg) {
384
384
  ".lintstagedrc.js",
385
385
  "lint-staged.config.js"
386
386
  ]) {
387
- if (await pathExists(path.join(root, f))) return true;
387
+ if (await pathExists(path2.join(root, f))) return true;
388
388
  }
389
389
  return false;
390
390
  }
@@ -392,50 +392,50 @@ async function detectHooks(root, hooksPath, pkg) {
392
392
  const lintStaged = await hasLintStagedConfig(root, pkg);
393
393
  if (hooksPath) {
394
394
  if (hooksPath.replace(/\/+$/, "") === ".husky/_") {
395
- const base2 = path.join(root, ".husky");
395
+ const base2 = path2.join(root, ".husky");
396
396
  return {
397
397
  source: "husky",
398
398
  dir: ".husky",
399
- preCommit: await pathExists(path.join(base2, "pre-commit")),
400
- prePush: await pathExists(path.join(base2, "pre-push")),
401
- commitMsg: await pathExists(path.join(base2, "commit-msg")),
399
+ preCommit: await pathExists(path2.join(base2, "pre-commit")),
400
+ prePush: await pathExists(path2.join(base2, "pre-push")),
401
+ commitMsg: await pathExists(path2.join(base2, "commit-msg")),
402
402
  lintStaged
403
403
  };
404
404
  }
405
- const base = path.join(root, hooksPath);
405
+ const base = path2.join(root, hooksPath);
406
406
  return {
407
407
  source: hooksPath === ".githooks" ? "githooks" : "custom",
408
408
  dir: hooksPath,
409
- preCommit: await pathExists(path.join(base, "pre-commit")),
410
- prePush: await pathExists(path.join(base, "pre-push")),
411
- commitMsg: await pathExists(path.join(base, "commit-msg")),
409
+ preCommit: await pathExists(path2.join(base, "pre-commit")),
410
+ prePush: await pathExists(path2.join(base, "pre-push")),
411
+ commitMsg: await pathExists(path2.join(base, "commit-msg")),
412
412
  lintStaged
413
413
  };
414
414
  }
415
- if (await isDirectory(path.join(root, ".githooks"))) {
416
- const base = path.join(root, ".githooks");
415
+ if (await isDirectory(path2.join(root, ".githooks"))) {
416
+ const base = path2.join(root, ".githooks");
417
417
  return {
418
418
  source: "githooks",
419
419
  dir: ".githooks",
420
- preCommit: await pathExists(path.join(base, "pre-commit")),
421
- prePush: await pathExists(path.join(base, "pre-push")),
422
- commitMsg: await pathExists(path.join(base, "commit-msg")),
420
+ preCommit: await pathExists(path2.join(base, "pre-commit")),
421
+ prePush: await pathExists(path2.join(base, "pre-push")),
422
+ commitMsg: await pathExists(path2.join(base, "commit-msg")),
423
423
  lintStaged
424
424
  };
425
425
  }
426
- if (await isDirectory(path.join(root, ".husky"))) {
427
- const base = path.join(root, ".husky");
426
+ if (await isDirectory(path2.join(root, ".husky"))) {
427
+ const base = path2.join(root, ".husky");
428
428
  return {
429
429
  source: "husky",
430
430
  dir: ".husky",
431
- preCommit: await pathExists(path.join(base, "pre-commit")),
432
- prePush: await pathExists(path.join(base, "pre-push")),
433
- commitMsg: await pathExists(path.join(base, "commit-msg")),
431
+ preCommit: await pathExists(path2.join(base, "pre-commit")),
432
+ prePush: await pathExists(path2.join(base, "pre-push")),
433
+ commitMsg: await pathExists(path2.join(base, "commit-msg")),
434
434
  lintStaged
435
435
  };
436
436
  }
437
437
  const legacyHooks = pkg?.husky?.hooks;
438
- const configText = legacyHooks ? null : await readText(path.join(root, "husky.config.js"));
438
+ const configText = legacyHooks ? null : await readText(path2.join(root, "husky.config.js"));
439
439
  if (legacyHooks || configText) {
440
440
  const hookNamed = (name) => legacyHooks ? Boolean(legacyHooks[name]) : Boolean(configText && configText.includes(name));
441
441
  return {
@@ -466,10 +466,10 @@ async function detectShellSurface(root, isRepo) {
466
466
  if (!listed) return { scripts: 0 };
467
467
  const files = listed.split("\0").filter(Boolean);
468
468
  const byExtension = files.filter((f) => SHELL_EXT.test(f));
469
- const candidates = files.filter((f) => !path.basename(f).includes("."));
469
+ const candidates = files.filter((f) => !path2.basename(f).includes("."));
470
470
  const SHEBANG_PROBE_CAP = 400;
471
471
  const probed = await Promise.all(
472
- candidates.slice(0, SHEBANG_PROBE_CAP).map(async (f) => SHELL_SHEBANG.test(await firstLine(path.join(root, f))))
472
+ candidates.slice(0, SHEBANG_PROBE_CAP).map(async (f) => SHELL_SHEBANG.test(await firstLine(path2.join(root, f))))
473
473
  );
474
474
  return { scripts: byExtension.length + probed.filter(Boolean).length };
475
475
  }
@@ -477,12 +477,12 @@ async function detectArtifacts(root) {
477
477
  const artifacts = await Promise.all(
478
478
  ARTIFACT_SPECS.map(async (spec) => ({
479
479
  ...spec,
480
- exists: await pathExists(path.join(root, spec.path))
480
+ exists: await pathExists(path2.join(root, spec.path))
481
481
  }))
482
482
  );
483
483
  let adrFiles = false;
484
484
  try {
485
- adrFiles = (await promises.readdir(path.join(root, "docs"))).some((f) => /^\d{4}-.+\.md$/.test(f));
485
+ adrFiles = (await promises.readdir(path2.join(root, "docs"))).some((f) => /^\d{4}-.+\.md$/.test(f));
486
486
  } catch {
487
487
  }
488
488
  artifacts.push({
@@ -511,7 +511,7 @@ async function walkTree(root) {
511
511
  for (const entry of entries) {
512
512
  if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
513
513
  if (entry.isDirectory()) {
514
- total += await countFiles(path.join(dir, entry.name));
514
+ total += await countFiles(path2.join(dir, entry.name));
515
515
  } else if (entry.isFile()) {
516
516
  total += 1;
517
517
  state.budget -= 1;
@@ -534,7 +534,7 @@ async function walkTree(root) {
534
534
  if (!entry.isDirectory()) continue;
535
535
  if (IGNORED_DIRS.has(entry.name)) continue;
536
536
  if (entry.name.startsWith(".") && !MEANINGFUL_DOT_DIRS.has(entry.name)) continue;
537
- dirs.push({ name: entry.name, files: await countFiles(path.join(root, entry.name)) });
537
+ dirs.push({ name: entry.name, files: await countFiles(path2.join(root, entry.name)) });
538
538
  }
539
539
  dirs.sort((a, b) => b.files - a.files);
540
540
  return { dirs, truncated: state.truncated };
@@ -782,8 +782,8 @@ async function collectFreshness(abs, isRepo, artifacts, upstreamRemote) {
782
782
  return facts;
783
783
  }
784
784
  async function scanProject(root, opts = {}) {
785
- const abs = path.resolve(root);
786
- const rootPkg = await readJson(path.join(abs, "package.json"));
785
+ const abs = path2.resolve(root);
786
+ const rootPkg = await readJson(path2.join(abs, "package.json"));
787
787
  const [packageManager, workspace, ci, artifacts, tree, isRepo] = await Promise.all([
788
788
  detectPackageManager(abs, rootPkg),
789
789
  detectWorkspace(abs, rootPkg),
@@ -810,7 +810,7 @@ async function scanProject(root, opts = {}) {
810
810
  packVersion: PACK_VERSION,
811
811
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
812
812
  root: abs,
813
- name: rootPkg?.name ?? path.basename(abs),
813
+ name: rootPkg?.name ?? path2.basename(abs),
814
814
  git: {
815
815
  isRepo,
816
816
  branch: branch ?? void 0,
@@ -847,13 +847,13 @@ var init_scan = __esm({
847
847
  }
848
848
  });
849
849
  function cacheFactsPath(root) {
850
- return path.join(root, CACHE_FACTS_FILE);
850
+ return path2.join(root, CACHE_FACTS_FILE);
851
851
  }
852
852
  function baselinePath(root) {
853
- return path.join(root, BASELINE_FILE);
853
+ return path2.join(root, BASELINE_FILE);
854
854
  }
855
855
  async function writeJsonFile(target, data) {
856
- await promises.mkdir(path.dirname(target), { recursive: true });
856
+ await promises.mkdir(path2.dirname(target), { recursive: true });
857
857
  await promises.writeFile(target, JSON.stringify(data, null, 2) + "\n", "utf8");
858
858
  return target;
859
859
  }
@@ -887,9 +887,9 @@ var init_facts = __esm({
887
887
  "src/core/facts.ts"() {
888
888
  init_util();
889
889
  ETYMD_DIR = ".etymd";
890
- CACHE_DIR = path.join(ETYMD_DIR, "cache");
891
- CACHE_FACTS_FILE = path.join(CACHE_DIR, "facts.json");
892
- BASELINE_FILE = path.join(ETYMD_DIR, "baseline.json");
890
+ CACHE_DIR = path2.join(ETYMD_DIR, "cache");
891
+ CACHE_FACTS_FILE = path2.join(CACHE_DIR, "facts.json");
892
+ BASELINE_FILE = path2.join(ETYMD_DIR, "baseline.json");
893
893
  }
894
894
  });
895
895
 
@@ -902,7 +902,7 @@ __export(config_exports, {
902
902
  readConfig: () => readConfig
903
903
  });
904
904
  function configPath(root) {
905
- return path.join(root, CONFIG_FILE);
905
+ return path2.join(root, CONFIG_FILE);
906
906
  }
907
907
  function readGlobList(value, key, problems) {
908
908
  if (value === void 0) return void 0;
@@ -961,6 +961,14 @@ async function readConfig(root) {
961
961
  }
962
962
  return gates.publishGate;
963
963
  })();
964
+ const commitFormat = (() => {
965
+ if (gates.commitFormat === void 0) return void 0;
966
+ if (typeof gates.commitFormat !== "boolean") {
967
+ problems.push(`${CONFIG_FILE}: \`gates.commitFormat\` must be true or false \u2014 ignored.`);
968
+ return void 0;
969
+ }
970
+ return gates.commitFormat;
971
+ })();
964
972
  return {
965
973
  present: true,
966
974
  problems,
@@ -982,6 +990,7 @@ async function readConfig(root) {
982
990
  commands: readGlobList(gates.commands, "gates.commands", problems) ?? DEFAULT_CONFIG.gates.commands,
983
991
  failOn: failOn ?? DEFAULT_CONFIG.gates.failOn,
984
992
  publishGate,
993
+ commitFormat,
985
994
  allowWriting: readGlobList(gates.allowWriting, "gates.allowWriting", problems) ?? DEFAULT_CONFIG.gates.allowWriting
986
995
  }
987
996
  }
@@ -992,7 +1001,7 @@ var init_config = __esm({
992
1001
  "src/core/config.ts"() {
993
1002
  init_facts();
994
1003
  init_util();
995
- CONFIG_FILE = path.join(ETYMD_DIR, "config.json");
1004
+ CONFIG_FILE = path2.join(ETYMD_DIR, "config.json");
996
1005
  DEFAULT_CONFIG = {
997
1006
  instructions: { include: [], exclude: [] },
998
1007
  context: { perFileWords: 4e3, totalWords: 8e3 },
@@ -1155,9 +1164,34 @@ fi
1155
1164
  exit 0
1156
1165
  `);
1157
1166
  }
1158
- function generateCommitMsgHook() {
1167
+ function commitFormatStep() {
1168
+ const types = COMMIT_TYPES.join("|");
1169
+ return `
1170
+ # Message format \u2014 <type>[(scope)][!]: <summary>. Needs nothing installed, so it always runs.
1171
+ subject=$(sed -e '/^#/d' -e '/^[[:space:]]*$/d' "$1" | head -1)
1172
+ case "$subject" in
1173
+ "Merge "*|"Revert "*|fixup!*|squash!*|amend!*) ;;
1174
+ *)
1175
+ if ! printf '%s' "$subject" | grep -qE '^(${types})(\\([a-z0-9._/-]+\\))?!?: .+'; then
1176
+ echo "\u2717 commit message: expected '<type>[(scope)][!]: <summary>'"
1177
+ echo " got: $subject"
1178
+ echo " types: ${COMMIT_TYPES.join(" ")}"
1179
+ echo " a '!' after the type or scope marks a breaking change"
1180
+ exit 1
1181
+ fi
1182
+ # Length is advice, not a block: the format is what tooling reads, the length is what a
1183
+ # person reads, and only one of the two can break anything.
1184
+ if [ "\${#subject}" -gt ${SUBJECT_ADVISORY_LENGTH} ]; then
1185
+ echo "\u203A note: subject is \${#subject} characters; ${SUBJECT_ADVISORY_LENGTH} or fewer reads better in git log"
1186
+ fi
1187
+ ;;
1188
+ esac`;
1189
+ }
1190
+ function generateCommitMsgHook(gates) {
1191
+ const format = gates?.commitFormat === true ? `${commitFormatStep()}
1192
+ ` : "";
1159
1193
  return stampGenerated(`#!/usr/bin/env sh
1160
- # etymd: content screen \u2014 the commit message itself.
1194
+ # etymd: the commit message itself \u2014 content screen, then format.
1161
1195
  #
1162
1196
  # The staged-content gate reads file bytes and never sees the message, yet a message is as
1163
1197
  # permanently published as any file. No-op where no checker is installed.
@@ -1167,7 +1201,7 @@ GATE="\${COMMIT_MSG_GATE:-$(command -v etymd || true)}"
1167
1201
  if [ -x "$GATE" ]; then
1168
1202
  "$GATE" screen --message "$1" || exit 1
1169
1203
  fi
1170
-
1204
+ ${format}
1171
1205
  ${localHookCall("commit-msg")}
1172
1206
 
1173
1207
  exit 0
@@ -1235,7 +1269,7 @@ ${auditStep}
1235
1269
  # with --no-verify and anything a rebase or merge brought in from elsewhere. Advisory here (it
1236
1270
  # never blocks the push): the blocking decision belongs at commit time, where the fix is cheap.
1237
1271
  ${CONTENT_GATE_RESOLUTION}
1238
- if [ -x "$GATE" ] && [ "\${CONTENT_GATE_PREPUSH:-1}" = "1" ]; then
1272
+ if [ -x "$GATE" ]; then
1239
1273
  "$GATE" screen --tree --advisory || true
1240
1274
  fi
1241
1275
 
@@ -1249,14 +1283,10 @@ function generateArtifactCheckScript() {
1249
1283
  # Wire it into the irreversible moment:
1250
1284
  # package.json \u2192 "prepublishOnly": "./scripts/artifact-check.sh"
1251
1285
  #
1252
- # No-op where no checker is installed. Bypass is deliberate and loud: ARTIFACT_CHECK_SKIP=1.
1286
+ # The artifact gate is the one check that sees what actually SHIPS \u2014 bypass with
1287
+ # .etymd-screen-allow entries (with provenance) if you must exempt a string.
1253
1288
  set -eu
1254
1289
 
1255
- if [ "\${ARTIFACT_CHECK_SKIP:-0}" = "1" ]; then
1256
- echo "\u203A artifact-check: SKIPPED by ARTIFACT_CHECK_SKIP=1"
1257
- exit 0
1258
- fi
1259
-
1260
1290
  ${CONTENT_GATE_RESOLUTION}
1261
1291
  [ -x "$GATE" ] || { echo "\u203A artifact-check: no checker installed \u2014 skipping."; exit 0; }
1262
1292
 
@@ -1274,12 +1304,26 @@ fi
1274
1304
  exit 0
1275
1305
  `);
1276
1306
  }
1277
- var GENERATION_MARKER_RE, CONTENT_GATE_RESOLUTION;
1307
+ var GENERATION_MARKER_RE, CONTENT_GATE_RESOLUTION, COMMIT_TYPES, SUBJECT_ADVISORY_LENGTH;
1278
1308
  var init_templates = __esm({
1279
1309
  "src/pack/templates.ts"() {
1280
1310
  init_version();
1281
1311
  GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
1282
- CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(command -v etymd || true)}"`;
1312
+ CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(if [ -x ./dist/cli.js ]; then echo ./dist/cli.js; else command -v etymd || true; fi)}"`;
1313
+ COMMIT_TYPES = [
1314
+ "feat",
1315
+ "fix",
1316
+ "docs",
1317
+ "style",
1318
+ "refactor",
1319
+ "perf",
1320
+ "test",
1321
+ "build",
1322
+ "ci",
1323
+ "chore",
1324
+ "revert"
1325
+ ];
1326
+ SUBJECT_ADVISORY_LENGTH = 72;
1283
1327
  }
1284
1328
  });
1285
1329
 
@@ -1322,7 +1366,7 @@ function deriveFailOn(facts, recorded) {
1322
1366
  async function planWorkflow(root, facts, opts) {
1323
1367
  const out = [];
1324
1368
  const add = async (rel, contents, label, executable = false) => {
1325
- const abs = path.join(root, rel);
1369
+ const abs = path2.join(root, rel);
1326
1370
  const exists = await pathExists(abs);
1327
1371
  const existing = exists ? await readText(abs) : null;
1328
1372
  const differs = exists ? existing !== contents : void 0;
@@ -1341,18 +1385,19 @@ async function planWorkflow(root, facts, opts) {
1341
1385
  await add("AGENTS.md", generateAgentsMd(facts), "Minimal operating contract (scaffold)");
1342
1386
  }
1343
1387
  if (opts.gates) {
1344
- const existingPrePush = await readText(path.join(root, ".githooks", "pre-push"));
1388
+ const existingPrePush = await readText(path2.join(root, ".githooks", "pre-push"));
1345
1389
  await add(".githooks/pre-commit", generatePreCommitHook(), "Process gate (pre-commit)", true);
1346
1390
  await add(
1347
1391
  ".githooks/commit-msg",
1348
- generateCommitMsgHook(),
1349
- "Content screen (commit message)",
1392
+ generateCommitMsgHook(opts.gateConfig),
1393
+ "Message gate (content screen, format)",
1350
1394
  true
1351
1395
  );
1352
1396
  const gateConfig = opts.gateConfig?.commands?.length ? opts.gateConfig : {
1353
1397
  commands: derivedCommands(facts, existingPrePush ?? void 0),
1354
1398
  failOn: opts.gateConfig?.failOn ?? DEFAULT_CONFIG.gates.failOn,
1355
1399
  publishGate: opts.gateConfig?.publishGate,
1400
+ commitFormat: opts.gateConfig?.commitFormat,
1356
1401
  allowWriting: opts.gateConfig?.allowWriting ?? []
1357
1402
  };
1358
1403
  await add(
@@ -1404,18 +1449,18 @@ function isAlwaysAppliedCursorRule(text) {
1404
1449
  async function measureContext(root, perFileWords = EXTRACTION_THRESHOLD) {
1405
1450
  const files = [];
1406
1451
  for (const spec of ALWAYS_LOADED) {
1407
- const abs = path.join(root, spec.path);
1452
+ const abs = path2.join(root, spec.path);
1408
1453
  const text = await readText(abs);
1409
1454
  if (text === null) continue;
1410
1455
  const words = wordCount(text);
1411
1456
  files.push({ path: spec.path, role: spec.role, words, approxTokens: approxTokens(words) });
1412
1457
  }
1413
- const rulesDir = path.join(root, ".cursor", "rules");
1458
+ const rulesDir = path2.join(root, ".cursor", "rules");
1414
1459
  if (await pathExists(rulesDir)) {
1415
1460
  try {
1416
1461
  for (const entry of await promises.readdir(rulesDir)) {
1417
1462
  if (!entry.endsWith(".mdc") && !entry.endsWith(".md")) continue;
1418
- const text = await readText(path.join(rulesDir, entry));
1463
+ const text = await readText(path2.join(rulesDir, entry));
1419
1464
  if (text === null || !isAlwaysAppliedCursorRule(text)) continue;
1420
1465
  const words = wordCount(text);
1421
1466
  files.push({
@@ -1448,8 +1493,8 @@ async function applyFiles(root, files, overwrite = /* @__PURE__ */ new Set()) {
1448
1493
  skipped.push(file.path);
1449
1494
  continue;
1450
1495
  }
1451
- const abs = path.join(root, file.path);
1452
- await promises.mkdir(path.dirname(abs), { recursive: true });
1496
+ const abs = path2.join(root, file.path);
1497
+ await promises.mkdir(path2.dirname(abs), { recursive: true });
1453
1498
  await promises.writeFile(abs, file.contents, "utf8");
1454
1499
  if (file.executable) await promises.chmod(abs, 493);
1455
1500
  written.push(file.path);
@@ -1750,14 +1795,14 @@ function parseGithubWorkflow(text, file, scripts) {
1750
1795
  return { jobs, parseErrors };
1751
1796
  }
1752
1797
  async function companionOf(root, hooksDir, hook, hookText, scripts) {
1753
- const rel = path.posix.join(hooksDir.split(path.sep).join("/"), `${hook}.local`);
1798
+ const rel = path2.posix.join(hooksDir.split(path2.sep).join("/"), `${hook}.local`);
1754
1799
  if (!hookText.includes(`${hook}.local`)) return null;
1755
- const abs = path.join(root, hooksDir, `${hook}.local`);
1800
+ const abs = path2.join(root, hooksDir, `${hook}.local`);
1756
1801
  const text = await readText(abs);
1757
1802
  if (text === null) return null;
1758
1803
  const [executable, hookExecutable] = await Promise.all([
1759
1804
  isExecutable(abs),
1760
- isExecutable(path.join(root, hooksDir, hook))
1805
+ isExecutable(path2.join(root, hooksDir, hook))
1761
1806
  ]);
1762
1807
  if (executable === false && hookExecutable === true) return { rel, tools: [], state: "inert" };
1763
1808
  return {
@@ -1774,7 +1819,7 @@ async function localHookTools(root, facts, scripts) {
1774
1819
  const inertCompanions = [];
1775
1820
  const readHook = async (name) => {
1776
1821
  if (!hooks.dir) return empty;
1777
- const text = await readText(path.join(root, hooks.dir, name));
1822
+ const text = await readText(path2.join(root, hooks.dir, name));
1778
1823
  if (!text) return empty;
1779
1824
  const tools = new Set(matchTools(text, scripts));
1780
1825
  const companion = await companionOf(root, hooks.dir, name, text, scripts);
@@ -1798,10 +1843,10 @@ async function localHookTools(root, facts, scripts) {
1798
1843
  ]);
1799
1844
  } else if (hooks.source === "husky-legacy") {
1800
1845
  const pkg = await readJson(
1801
- path.join(root, "package.json")
1846
+ path2.join(root, "package.json")
1802
1847
  );
1803
1848
  const legacy = pkg?.husky?.hooks ?? {};
1804
- const configText = await readText(path.join(root, "husky.config.js"));
1849
+ const configText = await readText(path2.join(root, "husky.config.js"));
1805
1850
  const forHook = (name) => {
1806
1851
  const cmd = legacy[name] ?? (configText?.includes(name) ? configText : void 0);
1807
1852
  return cmd ? matchTools(cmd, scripts) : [];
@@ -1812,7 +1857,7 @@ async function localHookTools(root, facts, scripts) {
1812
1857
  }
1813
1858
  let lintStaged = [];
1814
1859
  if (hooks.lintStaged) {
1815
- const pkg = await readJson(path.join(root, "package.json"));
1860
+ const pkg = await readJson(path2.join(root, "package.json"));
1816
1861
  const config = pkg?.["lint-staged"];
1817
1862
  const commands = [];
1818
1863
  const collect = (v) => {
@@ -1842,7 +1887,7 @@ async function localHookTools(root, facts, scripts) {
1842
1887
  };
1843
1888
  }
1844
1889
  async function detectThresholds(root, scripts, ciJobs) {
1845
- const sonarConfigured = await pathExists(path.join(root, "sonar-project.properties")) || ciJobs.some((j) => j.tools.includes("sonar"));
1890
+ const sonarConfigured = await pathExists(path2.join(root, "sonar-project.properties")) || ciJobs.some((j) => j.tools.includes("sonar"));
1846
1891
  let coverageThresholdLocal = false;
1847
1892
  for (const f of [
1848
1893
  "jest.config.js",
@@ -1854,7 +1899,7 @@ async function detectThresholds(root, scripts, ciJobs) {
1854
1899
  "vitest.config.js",
1855
1900
  "vitest.config.mts"
1856
1901
  ]) {
1857
- const text = await readText(path.join(root, f));
1902
+ const text = await readText(path2.join(root, f));
1858
1903
  if (text && /coverageThreshold|thresholds\s*:/.test(text)) {
1859
1904
  coverageThresholdLocal = true;
1860
1905
  break;
@@ -1862,7 +1907,7 @@ async function detectThresholds(root, scripts, ciJobs) {
1862
1907
  }
1863
1908
  if (!coverageThresholdLocal) {
1864
1909
  const pkg = await readJson(
1865
- path.join(root, "package.json")
1910
+ path2.join(root, "package.json")
1866
1911
  );
1867
1912
  coverageThresholdLocal = Boolean(pkg?.jest?.coverageThreshold);
1868
1913
  }
@@ -1875,7 +1920,7 @@ async function buildGateInventory(root, facts) {
1875
1920
  const inheritedIncludes = [];
1876
1921
  const parseErrors = [];
1877
1922
  if (facts.ci.system === "gitlab") {
1878
- const text = await readText(path.join(root, ".gitlab-ci.yml"));
1923
+ const text = await readText(path2.join(root, ".gitlab-ci.yml"));
1879
1924
  if (text) {
1880
1925
  const parsed = parseGitlabCi(text, ".gitlab-ci.yml", scripts);
1881
1926
  jobs.push(...parsed.jobs);
@@ -1883,7 +1928,7 @@ async function buildGateInventory(root, facts) {
1883
1928
  for (const inc of parsed.inheritedIncludes) {
1884
1929
  if (inc.startsWith("local: ")) {
1885
1930
  const rel = inc.slice("local: ".length).replace(/^\//, "");
1886
- const subText = await readText(path.join(root, rel));
1931
+ const subText = await readText(path2.join(root, rel));
1887
1932
  if (subText) {
1888
1933
  const sub = parseGitlabCi(subText, rel, scripts);
1889
1934
  jobs.push(...sub.jobs);
@@ -1899,7 +1944,7 @@ async function buildGateInventory(root, facts) {
1899
1944
  }
1900
1945
  } else if (facts.ci.system === "github") {
1901
1946
  for (const file of facts.ci.files) {
1902
- const text = await readText(path.join(root, file));
1947
+ const text = await readText(path2.join(root, file));
1903
1948
  if (!text) continue;
1904
1949
  const parsed = parseGithubWorkflow(text, file, scripts);
1905
1950
  jobs.push(...parsed.jobs);
@@ -1910,7 +1955,7 @@ async function buildGateInventory(root, facts) {
1910
1955
  localHookTools(root, facts, scripts),
1911
1956
  detectThresholds(root, scripts, jobs)
1912
1957
  ]);
1913
- const pkg = await readJson(path.join(root, "package.json"));
1958
+ const pkg = await readJson(path2.join(root, "package.json"));
1914
1959
  const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
1915
1960
  const commitlintDep = Boolean(deps["@commitlint/cli"] ?? deps["commitlint"]);
1916
1961
  return {
@@ -2332,7 +2377,7 @@ var stateFreshnessLens = {
2332
2377
  }
2333
2378
  }
2334
2379
  for (const a of stateArtifacts) {
2335
- const text = await readText(path.join(ctx.root, a.path));
2380
+ const text = await readText(path2.join(ctx.root, a.path));
2336
2381
  if (text === null) {
2337
2382
  disclosures.push(`${a.path} could not be read \u2014 unexamined, not clean.`);
2338
2383
  outOfScope.push(a.path);
@@ -2353,7 +2398,7 @@ var stateFreshnessLens = {
2353
2398
  }
2354
2399
  }
2355
2400
  for (const a of decisionArtifacts) {
2356
- const text = await readText(path.join(ctx.root, a.path));
2401
+ const text = await readText(path2.join(ctx.root, a.path));
2357
2402
  if (text === null) {
2358
2403
  disclosures.push(
2359
2404
  `${a.path} recognized as a decisions convention (directory) \u2014 age-exempt; per-entry format checks apply only to marker-carrying decisions files.`
@@ -2406,7 +2451,7 @@ init_util();
2406
2451
  async function listInstructionFiles(root, facts, scope) {
2407
2452
  const files = [];
2408
2453
  const add = async (rel) => {
2409
- const text = await readText(path.join(root, rel));
2454
+ const text = await readText(path2.join(root, rel));
2410
2455
  if (text !== null) files.push({ path: normalizeRelPath(rel), text });
2411
2456
  };
2412
2457
  const singleFileArtifacts = [
@@ -2422,21 +2467,21 @@ async function listInstructionFiles(root, facts, scope) {
2422
2467
  const artifact = facts.artifacts.find((a) => a.id === id);
2423
2468
  if (artifact?.exists) await add(artifact.path);
2424
2469
  }
2425
- const rulesDir = path.join(root, ".cursor", "rules");
2470
+ const rulesDir = path2.join(root, ".cursor", "rules");
2426
2471
  if (await isDirectory(rulesDir)) {
2427
2472
  try {
2428
2473
  for (const entry of await promises.readdir(rulesDir)) {
2429
2474
  if (entry.endsWith(".md") || entry.endsWith(".mdc"))
2430
- await add(path.join(".cursor/rules", entry));
2475
+ await add(path2.join(".cursor/rules", entry));
2431
2476
  }
2432
2477
  } catch {
2433
2478
  }
2434
2479
  }
2435
- const skillsDir = path.join(root, ".claude", "skills");
2480
+ const skillsDir = path2.join(root, ".claude", "skills");
2436
2481
  if (await isDirectory(skillsDir)) {
2437
2482
  try {
2438
2483
  for (const entry of await promises.readdir(skillsDir)) {
2439
- const skill = path.join(".claude/skills", entry, "SKILL.md");
2484
+ const skill = path2.join(".claude/skills", entry, "SKILL.md");
2440
2485
  await add(skill);
2441
2486
  }
2442
2487
  } catch {
@@ -2464,7 +2509,7 @@ async function listStateDocuments(root, facts) {
2464
2509
  const docs = [];
2465
2510
  for (const artifact of facts.artifacts) {
2466
2511
  if (artifact.kind !== "state" || !artifact.exists) continue;
2467
- const text = await readText(path.join(root, artifact.path));
2512
+ const text = await readText(path2.join(root, artifact.path));
2468
2513
  if (text !== null) docs.push({ path: normalizeRelPath(artifact.path), text });
2469
2514
  }
2470
2515
  return docs;
@@ -2748,28 +2793,28 @@ var instructionTruthLens = {
2748
2793
  const knownScripts = new Set(Object.keys(facts.commands.raw));
2749
2794
  for (const pkg of facts.packages) {
2750
2795
  const pkgJson = await readJson(
2751
- path.join(root, pkg.dir, "package.json")
2796
+ path2.join(root, pkg.dir, "package.json")
2752
2797
  );
2753
2798
  for (const key of Object.keys(pkgJson?.scripts ?? {})) knownScripts.add(key);
2754
2799
  }
2755
2800
  const pathResolves = async (claim) => {
2756
- const bases = [root, ...facts.packages.map((p) => path.join(root, p.dir))];
2801
+ const bases = [root, ...facts.packages.map((p) => path2.join(root, p.dir))];
2757
2802
  for (const base of bases) {
2758
- if (await pathExists(path.join(base, claim))) return true;
2759
- if (await pathExists(path.join(base, "src", claim))) return true;
2760
- if (await pathExists(path.join(base, "scripts", claim))) return true;
2803
+ if (await pathExists(path2.join(base, claim))) return true;
2804
+ if (await pathExists(path2.join(base, "src", claim))) return true;
2805
+ if (await pathExists(path2.join(base, "scripts", claim))) return true;
2761
2806
  }
2762
2807
  return false;
2763
2808
  };
2764
2809
  const binResolves = async (name) => {
2765
- const bases = [root, ...facts.packages.map((p) => path.join(root, p.dir))];
2810
+ const bases = [root, ...facts.packages.map((p) => path2.join(root, p.dir))];
2766
2811
  for (const base of bases) {
2767
- if (await pathExists(path.join(base, "node_modules", ".bin", name))) return true;
2812
+ if (await pathExists(path2.join(base, "node_modules", ".bin", name))) return true;
2768
2813
  }
2769
2814
  return false;
2770
2815
  };
2771
- const nodeModulesInstalled = await pathExists(path.join(root, "node_modules"));
2772
- const manifestExists = await pathExists(path.join(root, "package.json")) || facts.packages.length > 0;
2816
+ const nodeModulesInstalled = await pathExists(path2.join(root, "node_modules"));
2817
+ const manifestExists = await pathExists(path2.join(root, "package.json")) || facts.packages.length > 0;
2773
2818
  let totalFilteredSkipped = 0;
2774
2819
  let binaryResolved = 0;
2775
2820
  let unverifiableCommands = 0;
@@ -2861,7 +2906,7 @@ var instructionTruthLens = {
2861
2906
  }
2862
2907
  }
2863
2908
  for (const ref of extractDocRefs(file.text)) {
2864
- if (await pathExists(path.join(root, ref))) continue;
2909
+ if (await pathExists(path2.join(root, ref))) continue;
2865
2910
  findings.push(
2866
2911
  finding2({
2867
2912
  id: `${LENS_ID4}/dangling-ref:${file.path}:${ref}`,
@@ -2885,7 +2930,7 @@ var instructionTruthLens = {
2885
2930
  if (stateDocs.length) {
2886
2931
  for (const artifact of facts.artifacts) {
2887
2932
  if (artifact.kind !== "decisions" || !artifact.exists) continue;
2888
- const text = await readText(path.join(root, artifact.path));
2933
+ const text = await readText(path2.join(root, artifact.path));
2889
2934
  if (text === null) continue;
2890
2935
  const entries = parseDecisionEntries(text);
2891
2936
  if (!entries.length) continue;
@@ -3019,14 +3064,14 @@ function rankFindings(findings) {
3019
3064
  // src/engine/ledger.ts
3020
3065
  init_util();
3021
3066
  function ledgerPath(root) {
3022
- return path.join(root, ".etymd", "ledger.json");
3067
+ return path2.join(root, ".etymd", "ledger.json");
3023
3068
  }
3024
3069
  async function readLedger(root) {
3025
3070
  return await readJson(ledgerPath(root)) ?? { version: 1, entries: [] };
3026
3071
  }
3027
3072
  async function writeLedger(root, ledger) {
3028
3073
  const target = ledgerPath(root);
3029
- await promises.mkdir(path.dirname(target), { recursive: true });
3074
+ await promises.mkdir(path2.dirname(target), { recursive: true });
3030
3075
  await promises.writeFile(target, JSON.stringify(ledger, null, 2) + "\n", "utf8");
3031
3076
  }
3032
3077
  function belongsToAny(id, paths) {
@@ -3111,7 +3156,7 @@ var LENSES = [
3111
3156
  async function runAudit(root, opts = {}) {
3112
3157
  const facts = await scanProject(root, { upstreamRemote: opts.upstreamRemote });
3113
3158
  const ledgerRoot = opts.ledgerRoot ?? root;
3114
- if (!opts.readOnlyRoot && ((opts.persistLedger ?? true) || await pathExists(path.join(root, ETYMD_DIR)))) {
3159
+ if (!opts.readOnlyRoot && ((opts.persistLedger ?? true) || await pathExists(path2.join(root, ETYMD_DIR)))) {
3115
3160
  await writeCachedFacts(root, facts);
3116
3161
  }
3117
3162
  const baseline = await readBaseline(root);
@@ -3171,7 +3216,7 @@ function isFleetTrust(value) {
3171
3216
  }
3172
3217
  function expandTilde(p) {
3173
3218
  if (p === "~") return os.homedir();
3174
- if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
3219
+ if (p.startsWith("~/")) return path2.join(os.homedir(), p.slice(2));
3175
3220
  return p;
3176
3221
  }
3177
3222
  var REBUILD_RECIPE = 'rebuild it beside the manifest: {"machineProfile":"corp","dirs":{"<name>":"~/path/to/worktree"}}';
@@ -3206,7 +3251,7 @@ async function readLocal(localPath, problems) {
3206
3251
  if (!parsed) {
3207
3252
  problems.push({
3208
3253
  kind: "parse-error",
3209
- file: path.basename(localPath),
3254
+ file: path2.basename(localPath),
3210
3255
  detail: "must contain a JSON object \u2014 local mappings unusable"
3211
3256
  });
3212
3257
  return { present: true, data: {} };
@@ -3215,7 +3260,7 @@ async function readLocal(localPath, problems) {
3215
3260
  } catch (err) {
3216
3261
  problems.push({
3217
3262
  kind: "parse-error",
3218
- file: path.basename(localPath),
3263
+ file: path2.basename(localPath),
3219
3264
  detail: `not valid JSON (${err instanceof Error ? err.message : String(err)}) \u2014 local mappings unusable`
3220
3265
  });
3221
3266
  return { present: true, data: {} };
@@ -3283,13 +3328,13 @@ function loadRegistryEntries(projects, manifest, manifestFile) {
3283
3328
  } else {
3284
3329
  const dir = manifest.localDirs[name];
3285
3330
  if (!dir) {
3286
- entry.unresolved = `no \`dirs\` mapping for \`${name}\` in ${path.basename(manifest.localPath)}`;
3331
+ entry.unresolved = `no \`dirs\` mapping for \`${name}\` in ${path2.basename(manifest.localPath)}`;
3287
3332
  } else {
3288
- entry.resolvedRoot = path.resolve(manifest.dir, expandTilde(dir));
3333
+ entry.resolvedRoot = path2.resolve(manifest.dir, expandTilde(dir));
3289
3334
  }
3290
3335
  }
3291
3336
  } else if (entry.path) {
3292
- entry.resolvedRoot = path.resolve(manifest.root ?? manifest.dir, expandTilde(entry.path));
3337
+ entry.resolvedRoot = path2.resolve(manifest.root ?? manifest.dir, expandTilde(entry.path));
3293
3338
  } else {
3294
3339
  entry.unresolved = "personal entry declares no `path` \u2014 nothing to resolve";
3295
3340
  }
@@ -3298,7 +3343,7 @@ function loadRegistryEntries(projects, manifest, manifestFile) {
3298
3343
  if (corpNames.length && !manifest.localPresent) {
3299
3344
  manifest.problems.push({
3300
3345
  kind: "local-missing",
3301
- file: path.basename(manifest.localPath),
3346
+ file: path2.basename(manifest.localPath),
3302
3347
  detail: `${corpNames.length} corp/private entr${corpNames.length === 1 ? "y" : "ies"} (${corpNames.join(", ")}) cannot resolve without it \u2014 ${REBUILD_RECIPE}`
3303
3348
  });
3304
3349
  }
@@ -3327,19 +3372,19 @@ function loadCorpusEntries(sources, manifest, manifestFile) {
3327
3372
  };
3328
3373
  const localDir = manifest.localDirs[name];
3329
3374
  if (localDir) {
3330
- entry.resolvedRoot = path.resolve(manifest.dir, "..", expandTilde(localDir));
3375
+ entry.resolvedRoot = path2.resolve(manifest.dir, "..", expandTilde(localDir));
3331
3376
  } else if (entry.path) {
3332
- entry.resolvedRoot = path.resolve(manifest.dir, expandTilde(entry.path));
3377
+ entry.resolvedRoot = path2.resolve(manifest.dir, expandTilde(entry.path));
3333
3378
  } else {
3334
- entry.resolvedRoot = path.resolve(manifest.dir, "..", name);
3379
+ entry.resolvedRoot = path2.resolve(manifest.dir, "..", name);
3335
3380
  }
3336
3381
  manifest.entries.push(entry);
3337
3382
  }
3338
3383
  }
3339
3384
  async function loadFleetManifest(manifestPath) {
3340
- const abs = path.resolve(manifestPath);
3341
- const dir = path.dirname(abs);
3342
- const manifestFile = path.basename(abs);
3385
+ const abs = path2.resolve(manifestPath);
3386
+ const dir = path2.dirname(abs);
3387
+ const manifestFile = path2.basename(abs);
3343
3388
  const raw = await readText(abs);
3344
3389
  if (raw === null) {
3345
3390
  throw new Error(`cannot read fleet manifest \`${abs}\``);
@@ -3351,7 +3396,7 @@ async function loadFleetManifest(manifestPath) {
3351
3396
  corpHosts: [],
3352
3397
  labels: {},
3353
3398
  localDirs: {},
3354
- localPath: path.join(dir, "registry.local.json"),
3399
+ localPath: path2.join(dir, "registry.local.json"),
3355
3400
  localPresent: false,
3356
3401
  entries: [],
3357
3402
  problems: []
@@ -3384,7 +3429,7 @@ async function loadFleetManifest(manifestPath) {
3384
3429
  manifest.corpHosts = Array.isArray(local.data.corpHosts) ? local.data.corpHosts.filter((h) => typeof h === "string") : [];
3385
3430
  manifest.machineProfile = local.data.machineProfile === "corp" || local.data.machineProfile === "personal" ? local.data.machineProfile : void 0;
3386
3431
  const rootRaw = asString(local.data.root) ?? asString(parsed.root);
3387
- if (rootRaw) manifest.root = path.resolve(dir, expandTilde(rootRaw));
3432
+ if (rootRaw) manifest.root = path2.resolve(dir, expandTilde(rootRaw));
3388
3433
  const orientationRec = asRecord(parsed.orientation);
3389
3434
  if (orientationRec) {
3390
3435
  const orientationRoot = asString(orientationRec.root);
@@ -3406,7 +3451,7 @@ async function loadFleetManifest(manifestPath) {
3406
3451
  loadRegistryEntries(parsed.projects, manifest, manifestFile);
3407
3452
  } else if (Array.isArray(parsed.sources)) {
3408
3453
  manifest.shape = "corpus";
3409
- manifest.localPath = path.join(dir, "sources.local.json");
3454
+ manifest.localPath = path2.join(dir, "sources.local.json");
3410
3455
  const local = await readLocal(manifest.localPath, manifest.problems);
3411
3456
  manifest.localPresent = local.present;
3412
3457
  manifest.localDirs = asStringMap(local.data.dirs);
@@ -3462,9 +3507,9 @@ function finding3(id, tier, claim, evidence, why, action) {
3462
3507
  };
3463
3508
  }
3464
3509
  function corpPersistenceRoot(manifest, name) {
3465
- const corpDir = path.join(manifest.dir, "corp");
3466
- const root = path.resolve(corpDir, name);
3467
- if (path.dirname(root) !== corpDir || root === corpDir) {
3510
+ const corpDir = path2.join(manifest.dir, "corp");
3511
+ const root = path2.resolve(corpDir, name);
3512
+ if (path2.dirname(root) !== corpDir || root === corpDir) {
3468
3513
  throw new Error(
3469
3514
  `entry name ${JSON.stringify(name)} escapes the corp persistence zone \u2014 refusing to resolve a ledger root for it`
3470
3515
  );
@@ -3496,7 +3541,7 @@ async function realpathOr(p) {
3496
3541
  try {
3497
3542
  return await promises.realpath(p);
3498
3543
  } catch {
3499
- return path.resolve(p);
3544
+ return path2.resolve(p);
3500
3545
  }
3501
3546
  }
3502
3547
  function emailMatchesCorpHosts(email, hosts) {
@@ -3519,8 +3564,8 @@ var PROBLEM_TIER = {
3519
3564
  async function checkManifest(manifest) {
3520
3565
  const findings = [];
3521
3566
  const disclosures = [];
3522
- const manifestFile = path.basename(manifest.manifestPath);
3523
- const localFile = path.basename(manifest.localPath);
3567
+ const manifestFile = path2.basename(manifest.manifestPath);
3568
+ const localFile = path2.basename(manifest.localPath);
3524
3569
  for (const p of manifest.problems) {
3525
3570
  findings.push(
3526
3571
  finding3(
@@ -3672,7 +3717,7 @@ async function checkCorpWallArtifacts(entries, findings) {
3672
3717
  for (const entry of entries) {
3673
3718
  if (entry.profile !== "corp" || !entry.resolvedRoot) continue;
3674
3719
  for (const artifact of CORP_WALL_ARTIFACTS) {
3675
- if (await pathExists(path.join(entry.resolvedRoot, artifact))) {
3720
+ if (await pathExists(path2.join(entry.resolvedRoot, artifact))) {
3676
3721
  findings.push(
3677
3722
  finding3(
3678
3723
  `${FLEET_LENS}/corp-artifact-in-repo:${entry.name}:${artifact}`,
@@ -3706,9 +3751,9 @@ async function checkCoverage(manifest, findings, disclosures) {
3706
3751
  const dirents = await promises.readdir(manifest.root, { withFileTypes: true });
3707
3752
  for (const d of dirents) {
3708
3753
  if (!d.isDirectory() || d.name.startsWith(".")) continue;
3709
- const dir = path.join(manifest.root, d.name);
3754
+ const dir = path2.join(manifest.root, d.name);
3710
3755
  if (registered.has(await realpathOr(dir))) continue;
3711
- if (!await pathExists(path.join(dir, ".git"))) continue;
3756
+ if (!await pathExists(path2.join(dir, ".git"))) continue;
3712
3757
  const remotes = await git(dir, ["config", "--get-regexp", "^remote\\..*\\.url"]);
3713
3758
  if (!remotes) continue;
3714
3759
  const corpRemote = manifest.corpHosts.find((h) => remotes.includes(h));
@@ -3767,7 +3812,7 @@ async function checkHygieneNeedles(manifest, findings, disclosures) {
3767
3812
  ...new Set(
3768
3813
  [
3769
3814
  ...Object.values(manifest.labels),
3770
- ...Object.values(manifest.localDirs).map((d) => path.basename(expandNeedleDir(d))),
3815
+ ...Object.values(manifest.localDirs).map((d) => path2.basename(expandNeedleDir(d))),
3771
3816
  ...manifest.corpHosts
3772
3817
  ].filter((n) => n.length >= 3)
3773
3818
  )
@@ -3970,7 +4015,7 @@ async function sweepEntry(manifest, entry, opts) {
3970
4015
  }
3971
4016
  }
3972
4017
  const corp = entry.profile === "corp";
3973
- const optedIn = !corp && await pathExists(path.join(root, ETYMD_DIR));
4018
+ const optedIn = !corp && await pathExists(path2.join(root, ETYMD_DIR));
3974
4019
  const result = await runAudit(root, {
3975
4020
  kind: opts.kind,
3976
4021
  persistLedger: !corp && opts.persistLedgers === true && optedIn,
@@ -3988,7 +4033,7 @@ async function sweepEntry(manifest, entry, opts) {
3988
4033
  project.staleAfterDays = result.config.config.state.staleAfterDays;
3989
4034
  for (const [key, value] of Object.entries(entry.contract)) {
3990
4035
  if (key === "placement" || !value) continue;
3991
- const exists = await pathExists(path.join(root, value.replace(/#.*$/, "")));
4036
+ const exists = await pathExists(path2.join(root, value.replace(/#.*$/, "")));
3992
4037
  project.disclosures.push(
3993
4038
  `contract override \`${key}: ${value}\` \u2014 registered, existence-checked only (${exists ? "present" : "MISSING"})`
3994
4039
  );