enigma-cli 1.63.6 → 1.64.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.
Files changed (32) hide show
  1. package/assets/memory/AGENTS.md +1 -1
  2. package/assets/memory/CLAUDE.md +1 -1
  3. package/assets/skills/anti-overengineering-policy/skill.json +1 -1
  4. package/assets/skills/anti-overengineering-review/skill.json +1 -1
  5. package/assets/skills/backend-policy/SKILL.md +14 -1
  6. package/assets/skills/backend-policy/skill.json +5 -5
  7. package/assets/skills/ci-policy/skill.json +1 -1
  8. package/assets/skills/ciphera-style-policy/skill.json +1 -1
  9. package/assets/skills/code-review-policy/skill.json +1 -1
  10. package/assets/skills/core-engineering-policy/skill.json +1 -1
  11. package/assets/skills/database-expert/skill.json +1 -1
  12. package/assets/skills/debugging-policy/skill.json +1 -1
  13. package/assets/skills/dependency-policy/skill.json +1 -1
  14. package/assets/skills/email-policy/skill.json +1 -1
  15. package/assets/skills/frontend-design/skill.json +1 -1
  16. package/assets/skills/frontend-policy/SKILL.md +4 -1
  17. package/assets/skills/frontend-policy/skill.json +4 -4
  18. package/assets/skills/git-policy/skill.json +1 -1
  19. package/assets/skills/logo-sourcing-policy/skill.json +1 -1
  20. package/assets/skills/security-policy/SKILL.md +2 -0
  21. package/assets/skills/security-policy/skill.json +4 -4
  22. package/assets/skills/skill-creator/skill.json +1 -1
  23. package/assets/skills/task-completion-policy/skill.json +1 -1
  24. package/assets/skills/technical-writing-policy/skill.json +1 -1
  25. package/assets/skills/testing-policy/SKILL.md +1 -0
  26. package/assets/skills/testing-policy/skill.json +4 -4
  27. package/assets/skills/validation-policy/skill.json +1 -1
  28. package/bin/checksums.json +4 -4
  29. package/dist/guard.js +0 -0
  30. package/dist/guardrails.js +194 -24
  31. package/dist/post-edit.js +241 -68
  32. package/package.json +1 -1
package/dist/post-edit.js CHANGED
@@ -573,6 +573,44 @@ var init_trim = __esm({
573
573
  }
574
574
  });
575
575
 
576
+ // src/private-terms.ts
577
+ import { join as join5 } from "path";
578
+ import { homedir as homedir2 } from "os";
579
+ import { readFileSync as readFileSync4 } from "fs";
580
+ function readPrivateTerms() {
581
+ try {
582
+ const raw = JSON.parse(readFileSync4(join5(process.env.ENIGMA_CONFIG_HOME || homedir2(), ".enigma-guard.json"), "utf8"));
583
+ if (!Array.isArray(raw.privateTerms)) return [];
584
+ return raw.privateTerms.filter((t) => typeof t === "string" && t.trim().length >= 3);
585
+ } catch {
586
+ return [];
587
+ }
588
+ }
589
+ function privateTermMatcher(terms) {
590
+ const anyCase = (ch) => {
591
+ const lo = ch.toLowerCase(), up = ch.toUpperCase();
592
+ return lo === up ? ch.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&") : `[${lo}${up}]`;
593
+ };
594
+ const parts = terms.map((t) => t.trim().split(/[\s_-]+/).filter(Boolean).map((w) => [...w].map(anyCase).join("")).join("[\\s_-]*")).filter(Boolean);
595
+ if (!parts.length) return null;
596
+ return new RegExp(`(?:(?<![\\p{L}\\p{N}])|(?<=\\p{Ll})(?=\\p{Lu}))(?:${parts.join("|")})(?!\\p{Ll})`, "u");
597
+ }
598
+ function hasPrivateTerm(line, re) {
599
+ return re.test(line.replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, " "));
600
+ }
601
+ function privateTermLines(text, re) {
602
+ const out = [];
603
+ text.split("\n").forEach((line, i) => {
604
+ if (hasPrivateTerm(line, re)) out.push(i + 1);
605
+ });
606
+ return out;
607
+ }
608
+ var init_private_terms = __esm({
609
+ "src/private-terms.ts"() {
610
+ "use strict";
611
+ }
612
+ });
613
+
576
614
  // src/guardrails.ts
577
615
  var guardrails_exports = {};
578
616
  __export(guardrails_exports, {
@@ -617,9 +655,11 @@ __export(guardrails_exports, {
617
655
  pageAwaitWithoutBoundary: () => pageAwaitWithoutBoundary,
618
656
  pathJoinedFromInput: () => pathJoinedFromInput,
619
657
  poweredByBuzzword: () => poweredByBuzzword,
658
+ privateTermLeak: () => privateTermLeak,
620
659
  purpleGradient: () => purpleGradient,
621
660
  queryInLoop: () => queryInLoop,
622
661
  radixContentWithoutPortal: () => radixContentWithoutPortal,
662
+ rawImgOverComponent: () => rawImgOverComponent,
623
663
  readLedger: () => readLedger,
624
664
  readReplyLedger: () => readReplyLedger,
625
665
  recordFindings: () => recordFindings,
@@ -654,11 +694,11 @@ __export(guardrails_exports, {
654
694
  viewBlankedWhileLoading: () => viewBlankedWhileLoading,
655
695
  wideNamedImports: () => wideNamedImports
656
696
  });
657
- import { homedir as homedir2 } from "os";
697
+ import { homedir as homedir3 } from "os";
658
698
  import { fileURLToPath as fileURLToPath2 } from "url";
659
699
  import { execFileSync as execFileSync2 } from "child_process";
660
- import { dirname as dirname2, join as join5, resolve as resolve2, sep as sep2 } from "path";
661
- import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync3, statSync as statSync2, existsSync as existsSync4 } from "fs";
700
+ import { dirname as dirname2, join as join6, resolve as resolve2, sep as sep2 } from "path";
701
+ import { appendFileSync, mkdirSync as mkdirSync2, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync3, statSync as statSync2, existsSync as existsSync4 } from "fs";
662
702
  function newPasswordAffordanceOnSignIn(content) {
663
703
  if (!CURRENT_PASSWORD.test(content) || NEW_PASSWORD.test(content)) return [];
664
704
  const out = [];
@@ -676,7 +716,7 @@ function applyFixes(file, findings, stage2 = "edit") {
676
716
  if (!fixable.length) return { fixed: [], remaining: findings };
677
717
  let content;
678
718
  try {
679
- content = readFileSync4(file, "utf8");
719
+ content = readFileSync5(file, "utf8");
680
720
  } catch {
681
721
  return { fixed: [], remaining: findings };
682
722
  }
@@ -1002,6 +1042,63 @@ function nativeSelectOverPrimitive(content) {
1002
1042
  }
1003
1043
  return [];
1004
1044
  }
1045
+ function projectImageComponent(root) {
1046
+ const cached = imageComponentCache.get(root);
1047
+ if (cached !== void 0) return cached;
1048
+ let deps = {};
1049
+ try {
1050
+ deps = JSON.parse(readFileSync5(join6(root, "package.json"), "utf8")).dependencies ?? {};
1051
+ } catch {
1052
+ }
1053
+ let found = null;
1054
+ if ("@enigmax/primitives" in deps) found = "`Image` from @enigmax/primitives (`enigma add image`)";
1055
+ else if ("next" in deps) found = "`Image` from next/image";
1056
+ else if ("astro" in deps) found = "`Image` from astro:assets";
1057
+ else if ("@nuxt/image" in deps) found = "`NuxtImg` from @nuxt/image";
1058
+ else {
1059
+ for (const dir of LOCAL_IMAGE_COMPONENTS) {
1060
+ let names = [];
1061
+ try {
1062
+ names = readdirSync2(join6(root, dir));
1063
+ } catch {
1064
+ continue;
1065
+ }
1066
+ const hit = names.find((n) => /^(?:image|img|picture)\.(?:tsx|jsx|vue|svelte|astro)$/i.test(n));
1067
+ if (hit) {
1068
+ found = `the project's own \`${dir}/${hit}\``;
1069
+ break;
1070
+ }
1071
+ }
1072
+ }
1073
+ imageComponentCache.set(root, found);
1074
+ return found;
1075
+ }
1076
+ function imgElementAt(lines, start) {
1077
+ const parts = [];
1078
+ for (let i = start; i < lines.length && i < start + 30; i++) {
1079
+ const text = i === start ? lines[i].slice(lines[i].search(RAW_IMG)) : lines[i];
1080
+ parts.push(text);
1081
+ if (/(?<!=)>/.test(text)) break;
1082
+ }
1083
+ return parts.join("\n");
1084
+ }
1085
+ function rawImgOverComponent(content, file) {
1086
+ const norm = file.replace(/\\/g, "/");
1087
+ if (!RAW_IMG.test(content) || IMAGE_COMPONENT_FILE.test(norm) || DEFINES_IMAGE.test(content)) return [];
1088
+ const root = findProjectRoot(file);
1089
+ const component = root ? projectImageComponent(root) : null;
1090
+ if (!component) return [];
1091
+ const lines = content.split("\n");
1092
+ const hits = [];
1093
+ for (let i = 0; i < lines.length; i++) {
1094
+ const line = lines[i];
1095
+ if (!RAW_IMG.test(line) || COMMENT_LINE.test(line) || allowedAt(lines, i, ALLOW_RAW_IMG)) continue;
1096
+ const element = imgElementAt(lines, i);
1097
+ if (DECORATIVE_IMG.test(element) || ALLOW_RAW_IMG.test(element)) continue;
1098
+ hits.push({ line: i + 1, detail: `this project already has ${component}` });
1099
+ }
1100
+ return hits;
1101
+ }
1005
1102
  function handRolledToast(content) {
1006
1103
  if (TOAST_MITIGATED.test(content)) return [];
1007
1104
  if (!TOAST_QUEUE.test(content) || !TOAST_TIMER.test(content)) return [];
@@ -1082,11 +1179,11 @@ function nearestTsconfig(file) {
1082
1179
  return cached;
1083
1180
  }
1084
1181
  seen.push(dir);
1085
- const candidate = join5(dir, "tsconfig.json");
1182
+ const candidate = join6(dir, "tsconfig.json");
1086
1183
  if (existsSync4(candidate)) {
1087
1184
  let found = null;
1088
1185
  try {
1089
- found = { dir, text: readFileSync4(candidate, "utf8") };
1186
+ found = { dir, text: readFileSync5(candidate, "utf8") };
1090
1187
  } catch {
1091
1188
  found = null;
1092
1189
  }
@@ -1149,14 +1246,14 @@ function deepRelativeImports(content, file) {
1149
1246
  function missingPathAlias(content, file) {
1150
1247
  if (/["'](?:paths|extends)["']\s*:/.test(content)) return [];
1151
1248
  const dir = dirname2(resolve2(file));
1152
- const src = ["src", "app", "lib"].find((d) => existsSync4(join5(dir, d)));
1249
+ const src = ["src", "app", "lib"].find((d) => existsSync4(join6(dir, d)));
1153
1250
  if (!src) return [];
1154
1251
  const anchor = content.split("\n").findIndex((l) => /["']compilerOptions["']/.test(l));
1155
1252
  return [{ line: anchor === -1 ? 1 : anchor + 1, detail: `no alias for ./${src}` }];
1156
1253
  }
1157
1254
  function operatorHome() {
1158
1255
  const env = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
1159
- return env?.trim() || homedir2();
1256
+ return env?.trim() || homedir3();
1160
1257
  }
1161
1258
  function operatorHomePathLeak(content) {
1162
1259
  const re = operatorHomeRegex("i");
@@ -1168,6 +1265,14 @@ function operatorHomePathLeak(content) {
1168
1265
  }
1169
1266
  return out;
1170
1267
  }
1268
+ function privateTermLeak(content) {
1269
+ const terms = readPrivateTerms();
1270
+ const key = terms.join("\n");
1271
+ if (privateTermCache?.key !== key) privateTermCache = { key, re: privateTermMatcher(terms) };
1272
+ const re = privateTermCache.re;
1273
+ if (re === null) return [];
1274
+ return privateTermLines(content, re).map((line) => ({ line, detail: "a private term from your list" }));
1275
+ }
1171
1276
  function redactOperatorHome(text) {
1172
1277
  const re = operatorHomeRegex("gi");
1173
1278
  return re === null ? text : text.replace(re, "~");
@@ -1540,7 +1645,7 @@ function wideNamedImports(content, max) {
1540
1645
  }
1541
1646
  function readPkgDeps(root) {
1542
1647
  try {
1543
- const pkg = JSON.parse(readFileSync4(join5(root, "package.json"), "utf8"));
1648
+ const pkg = JSON.parse(readFileSync5(join6(root, "package.json"), "utf8"));
1544
1649
  return { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.optionalDependencies, ...pkg.peerDependencies };
1545
1650
  } catch {
1546
1651
  return null;
@@ -1561,7 +1666,7 @@ function globRe(glob, ignoreCase = false) {
1561
1666
  return re;
1562
1667
  }
1563
1668
  function guardrailsConfigPath() {
1564
- return process.env.ENIGMA_GUARDRAILS_CONFIG || join5(homedir2(), ".enigma-guardrails.json");
1669
+ return process.env.ENIGMA_GUARDRAILS_CONFIG || join6(homedir3(), ".enigma-guardrails.json");
1565
1670
  }
1566
1671
  function isValidRule(r) {
1567
1672
  const x = r;
@@ -1575,7 +1680,7 @@ function loadRules() {
1575
1680
  let disabled = [];
1576
1681
  let custom = [];
1577
1682
  try {
1578
- const raw = JSON.parse(readFileSync4(guardrailsConfigPath(), "utf8"));
1683
+ const raw = JSON.parse(readFileSync5(guardrailsConfigPath(), "utf8"));
1579
1684
  if (Array.isArray(raw.disabled)) disabled = raw.disabled.filter((s) => typeof s === "string");
1580
1685
  if (Array.isArray(raw.rules)) custom = raw.rules.filter(isValidRule);
1581
1686
  } catch {
@@ -1586,10 +1691,10 @@ function loadRules() {
1586
1691
  function findProjectRoot(file) {
1587
1692
  let dir = dirname2(resolve2(file));
1588
1693
  for (let i = 0; i < 40; i++) {
1589
- const isProj = existsSync4(join5(dir, "package.json")) || existsSync4(join5(dir, ".enigma.json"));
1694
+ const isProj = existsSync4(join6(dir, "package.json")) || existsSync4(join6(dir, ".enigma.json"));
1590
1695
  let hasGit = false;
1591
1696
  try {
1592
- hasGit = statSync2(join5(dir, ".git")).isDirectory();
1697
+ hasGit = statSync2(join6(dir, ".git")).isDirectory();
1593
1698
  } catch {
1594
1699
  }
1595
1700
  if (isProj || hasGit) return dir;
@@ -1655,7 +1760,7 @@ function formatFindings(findings) {
1655
1760
  function checkPath(file, stage2 = "edit") {
1656
1761
  let content;
1657
1762
  try {
1658
- content = readFileSync4(file, "utf8");
1763
+ content = readFileSync5(file, "utf8");
1659
1764
  } catch {
1660
1765
  return [];
1661
1766
  }
@@ -1693,7 +1798,7 @@ function repairableDiffFindings(file) {
1693
1798
  if (!changed) return [];
1694
1799
  let content;
1695
1800
  try {
1696
- content = readFileSync4(file, "utf8");
1801
+ content = readFileSync5(file, "utf8");
1697
1802
  } catch {
1698
1803
  return [];
1699
1804
  }
@@ -1707,7 +1812,7 @@ function repairableDiffFindings(file) {
1707
1812
  function runGuardrailsHook(payload) {
1708
1813
  let file;
1709
1814
  try {
1710
- file = JSON.parse(payload ?? readFileSync4(0, "utf8"))?.tool_input?.file_path;
1815
+ file = JSON.parse(payload ?? readFileSync5(0, "utf8"))?.tool_input?.file_path;
1711
1816
  } catch {
1712
1817
  }
1713
1818
  if (!file || typeof file !== "string") return 0;
@@ -2031,16 +2136,16 @@ function pathJoinedFromInput(content, file) {
2031
2136
  if (!python && /^\s*["']use client["']/m.test(content)) return [];
2032
2137
  if (PATH_CONTAINED.test(content)) return [];
2033
2138
  const input = python ? REQUEST_INPUT_PY : REQUEST_INPUT_JS;
2034
- const join10 = python ? PATH_JOIN_PY : PATH_JOIN_JS;
2139
+ const join11 = python ? PATH_JOIN_PY : PATH_JOIN_JS;
2035
2140
  const lines = content.split("\n");
2036
2141
  const names = namesFromInput(lines, input);
2037
2142
  const out = [];
2038
2143
  for (let i = 0; i < lines.length; i++) {
2039
2144
  const text = lines[i];
2040
2145
  if (auditSkipLine(text)) continue;
2041
- join10.lastIndex = 0;
2146
+ join11.lastIndex = 0;
2042
2147
  let m;
2043
- while (m = join10.exec(text)) {
2148
+ while (m = join11.exec(text)) {
2044
2149
  const parts = (m[1] ?? m[2] ?? "").split(",").slice(1).map((s) => s.trim());
2045
2150
  if (parts.some((p) => input.test(p) || names.has(p) || names.has(p.replace(/^`\$\{(\w+)\}.*`$/, "$1")))) {
2046
2151
  out.push({ line: i + 1, detail: text.trim().slice(0, 120) });
@@ -2359,7 +2464,7 @@ function sparklesIcon(content) {
2359
2464
  return out;
2360
2465
  }
2361
2466
  function ledgerPath() {
2362
- return process.env.ENIGMA_GUARDRAILS_LOG || join5(homedir2(), ".enigma", "guardrail-log.jsonl");
2467
+ return process.env.ENIGMA_GUARDRAILS_LOG || join6(homedir3(), ".enigma", "guardrail-log.jsonl");
2363
2468
  }
2364
2469
  function ledgerKey(rule, outcome, file, line) {
2365
2470
  return JSON.stringify([rule, outcome, file, line ?? null]);
@@ -2395,7 +2500,7 @@ function recordFindings(findings, outcome, stage2 = "edit") {
2395
2500
  } catch {
2396
2501
  }
2397
2502
  if (size > LEDGER_MAX_BYTES) {
2398
- const kept = rotateLedger(readFileSync4(path, "utf8"));
2503
+ const kept = rotateLedger(readFileSync5(path, "utf8"));
2399
2504
  writeFileSync3(path, kept.length ? `${kept.join("\n")}
2400
2505
  ` : "");
2401
2506
  }
@@ -2430,7 +2535,7 @@ function rotateLedger(text) {
2430
2535
  function eachLedgerEntry(sinceDays, visit) {
2431
2536
  let text;
2432
2537
  try {
2433
- text = readFileSync4(ledgerPath(), "utf8");
2538
+ text = readFileSync5(ledgerPath(), "utf8");
2434
2539
  } catch {
2435
2540
  return;
2436
2541
  }
@@ -2500,7 +2605,7 @@ function runGuardrailsScan(all) {
2500
2605
  for (const file of files) {
2501
2606
  let content;
2502
2607
  try {
2503
- content = readFileSync4(file, "utf8");
2608
+ content = readFileSync5(file, "utf8");
2504
2609
  } catch {
2505
2610
  continue;
2506
2611
  }
@@ -2529,10 +2634,11 @@ enigma-guardrails: BLOCKED - ${r.blocks.length} convention violation(s):`);
2529
2634
  console.log(`enigma-guardrails: ${r.count} ${all ? "tracked" : "staged"} file(s) checked, no blocking violations.`);
2530
2635
  return 0;
2531
2636
  }
2532
- var COMMENT_LINE, SKELETON_GUARD_SRC, SKELETON_SIGNAL_SRC, INJECTION_EXCLUDES, WORKFLOW_FILES, UI_CODE_FILES, UI_STYLE_FILES, TELL_EXCLUDES, DB_SCRIPT_EXCLUDES, BUILTIN_RULES, PROJECT_CHECKS, CURRENT_PASSWORD, NEW_PASSWORD, NEW_PASSWORD_PROP, NOT_A_JSX_PROP, FILE_CHECKS, FIXERS, SPAWNERS, MUTATING_REQUEST, ENTITY_WRITE, OPTIMISTIC_SIGNAL, ALLOW_SERVER_FIRST, RESULT_BINDING, BLOCK_LOOKBACK, BLOCK_LOOKAHEAD, REVALIDATE_AFTER, PROMISE_FANOUT, FANOUT_LITERAL, FANOUT_MAPPED, FANOUT_OVER_OBJECT, EXPENSIVE_PER_ITEM, CONCURRENCY_BOUND, FANOUT_WINDOW, TEXTAREA, TEXTAREA_LOWER, TEXTAREA_UPPER, TEXTAREA_FIXED, TEXTAREA_AUTOSIZE, VIEW_GUARD, LOADING_NAME, LOADING_VALUE, VIEW_GUARD_LOADING, VIEW_CHROME_HEADING, VIEW_CHROME_TEXT, VIEW_BODY_LOOKAHEAD, ALLOW_VIEW_SKELETON, SKELETON_GUARD, SKELETON_SIGNAL, REMOTE_COLLECTION_BINDINGS, LIST_BOUNDED, ALLOW_UNBOUNDED_LIST, MENU_HANDLER, MENU_POINT, MENU_STATE, MENU_MITIGATED, SELECT_RANGE_KEY, SELECT_ADD_KEY, SELECT_STATE, SELECT_MITIGATED, COLOR_MATHS, COLOR_DRAG, COLOR_MEASURE, COLOR_STATE, COLOR_MITIGATED, LIGHTBOX_IMAGE, LIGHTBOX_WHEEL, LIGHTBOX_ZOOM, LIGHTBOX_DIALOG, LIGHTBOX_MITIGATED, VIDEO_ELEMENT, VIDEO_DRIVEN, VIDEO_TIMELINE, VIDEO_CONTROLS, VIDEO_MITIGATED, TWOFA_SUBJECT, TWOFA_ENROLL, TWOFA_PASSWORD, TWOFA_MITIGATED, TWOFA_WINDOW, LISTBOX_ROLE, LISTBOX_OPEN, LISTBOX_OPTIONS, LISTBOX_MITIGATED, NATIVE_SELECT, NATIVE_SELECT_MAPPED, NATIVE_SELECT_TRIGGER, NATIVE_SELECT_MITIGATED, TOAST_QUEUE, TOAST_TIMER, TOAST_MITIGATED, PALETTE_SHORTCUT, PALETTE_MODIFIER, PALETTE_OPEN, PALETTE_FILTER, PALETTE_MITIGATED, CLIP_ONE_LINE, DYNAMIC_CHILD, VALUE_REACHABLE, WRAPPER_LOOKBACK, ALLOW_CLIPPED_VALUE, CLIPPED_SIMPLE_VALUE, NOT_TEXT_BINDING, ASYNC_ROUTE, SERVER_DATA_AWAIT, STREAM_BOUNDARY, ALLOW_BLOCKING_PAGE, LOADING_BOUNDARY_FILE, SPECIFIER, MODULE_EXT, JS_EXT, tsconfigCache, NAMED_IMPORT, GENERIC_ACCOUNTS, MINIFIED_LINE, CALL_LEAD_JS, CALL_LEAD_PY, CONSTANT_EXPR, SQL_DML, SQL_VALUE_POSITION, SQL_PLACEHOLDER_BUILDER, SQL_SAFE_EXPR, SQL_CALL_JS, SQL_CALL_PY, SHELL_SAFE_EXPR, INSECURE_TLS_JS, INSECURE_TLS_PY, PY_HTTP_CLIENT, LOOPBACK_HINT, TLS_OPERATOR_TOGGLE, CORS_ANY_ORIGIN_JS, CORS_CREDENTIALS_JS, CORS_ANY_ORIGIN_PY, CORS_CREDENTIALS_PY, CORS_WINDOW, PYYAML_IMPORT, PICKLE_FROM_OUTSIDE, RAW_HTML_MARKDOWN, OPT_IN_HTML_MARKDOWN, MARKDOWN_HTML_ON, HTML_SANITIZER, HTML_SINK, RADIX_OVERLAY_MODULE, RADIX_UMBRELLA_PARTS, INTERNAL_MODULE, globCache, DIFF_HUNK, REQUEST_INPUT_JS, REQUEST_INPUT_PY, SSH_HOST_KEY_OFF, ACTION_USES, FULL_SHA, IMAGE_DIGEST, PRIVILEGED_TRIGGER, PR_HEAD_REF, CHECKOUT_SITE, SAME_REPO_GUARD, UNTRUSTED_EVENT_TEXT, SCRIPT_KEY, BLOCK_SCALAR, OPERAND_BEFORE, OPERAND_AFTER, LITERAL_OPERAND, CONFIGURED_SECRET, PRESENTED_CREDENTIAL, COMPUTED_MAC, SECRET_NAMED, NOT_SECRET_NAMED, REMEMBERED_COPY, SLASH_PREFIX_CHECK, DESTINATION_NAME, GENERIC_PARAM_NAME, NAVIGATION_SINK, DESTINATION_HELPER, SIGN_IN_FILE, RESOLVED_ORIGIN_CHECK, COMPLETE_PREFIX_CHECK, REDIRECT_SINK_JS, REDIRECT_SINK_PY, REDIRECT_VALIDATED, OUTBOUND_JS, OUTBOUND_PY, SSRF_GUARDED, DNS_RESOLUTION, PRIVATE_ADDRESS_CHECK, OUTBOUND_CALL, CONNECTION_PINNED, RESPONSE_TYPE_CALL_JS, RESPONSE_TYPE_KEY_JS, RESPONSE_OPENER_JS, RESPONSE_TYPE_KWARG_PY, RESPONSE_OPENER_PY, STORED_FILE_TYPE, RESPONSE_SANDBOXED, PATH_JOIN_JS, PATH_JOIN_PY, PATH_CONTAINED, TW_HUE, TW_STOP, HEX_COLOR, ALLOW_BRAND_GRADIENT, ALLOW_GRADIENT_TEXT, SHIMMER, TW_LEFT_STRIPE, TW_ROUNDED_LEFT, ALLOW_ACCENT_STRIPE, EMOJI, EMOJI_LEAD, EMOJI_ICON, EMOJI_COMMIT_SAMPLE, TERMINAL_MARKUP, ALLOW_EMOJI_ICON, COPY_PROP, POWERED_BY_BUZZWORD, ALLOW_POWERED_BY, CHAT_RESIDUE, CHAT_LINK, ALLOW_CHAT_RESIDUE, AI_VOCABULARY, SEAMLESS_LITERAL, ALLOW_AI_VOCABULARY, REQUEST_SERVING, READ_BOUND, ALLOW_UNBOUNDED_READ, ALLOW_SELECT_STAR, ORM_READ_CALL, ROW_LOOP, ALLOW_QUERY_IN_LOOP, FIXED_CANVAS_FILE, TW_DECORATION, EMAIL_MARKUP, TW_WIDTH_GUARD, ALLOW_FIXED_WIDTH, TW_VIEWPORT_HEIGHT, DYNAMIC_VIEWPORT, ALLOW_VIEWPORT_HEIGHT, SPARKLES_IMPORT, SPARKLES_NAME, ALLOW_SPARKLES, LEDGER_MAX_BYTES, LEDGER_KEEP, LEDGER_KEEP_REPLY, grEntry, isGrEntry;
2637
+ var COMMENT_LINE, SKELETON_GUARD_SRC, SKELETON_SIGNAL_SRC, INJECTION_EXCLUDES, WORKFLOW_FILES, UI_CODE_FILES, UI_STYLE_FILES, TELL_EXCLUDES, DB_SCRIPT_EXCLUDES, BUILTIN_RULES, PROJECT_CHECKS, CURRENT_PASSWORD, NEW_PASSWORD, NEW_PASSWORD_PROP, NOT_A_JSX_PROP, FILE_CHECKS, FIXERS, SPAWNERS, MUTATING_REQUEST, ENTITY_WRITE, OPTIMISTIC_SIGNAL, ALLOW_SERVER_FIRST, RESULT_BINDING, BLOCK_LOOKBACK, BLOCK_LOOKAHEAD, REVALIDATE_AFTER, PROMISE_FANOUT, FANOUT_LITERAL, FANOUT_MAPPED, FANOUT_OVER_OBJECT, EXPENSIVE_PER_ITEM, CONCURRENCY_BOUND, FANOUT_WINDOW, TEXTAREA, TEXTAREA_LOWER, TEXTAREA_UPPER, TEXTAREA_FIXED, TEXTAREA_AUTOSIZE, VIEW_GUARD, LOADING_NAME, LOADING_VALUE, VIEW_GUARD_LOADING, VIEW_CHROME_HEADING, VIEW_CHROME_TEXT, VIEW_BODY_LOOKAHEAD, ALLOW_VIEW_SKELETON, SKELETON_GUARD, SKELETON_SIGNAL, REMOTE_COLLECTION_BINDINGS, LIST_BOUNDED, ALLOW_UNBOUNDED_LIST, MENU_HANDLER, MENU_POINT, MENU_STATE, MENU_MITIGATED, SELECT_RANGE_KEY, SELECT_ADD_KEY, SELECT_STATE, SELECT_MITIGATED, COLOR_MATHS, COLOR_DRAG, COLOR_MEASURE, COLOR_STATE, COLOR_MITIGATED, LIGHTBOX_IMAGE, LIGHTBOX_WHEEL, LIGHTBOX_ZOOM, LIGHTBOX_DIALOG, LIGHTBOX_MITIGATED, VIDEO_ELEMENT, VIDEO_DRIVEN, VIDEO_TIMELINE, VIDEO_CONTROLS, VIDEO_MITIGATED, TWOFA_SUBJECT, TWOFA_ENROLL, TWOFA_PASSWORD, TWOFA_MITIGATED, TWOFA_WINDOW, LISTBOX_ROLE, LISTBOX_OPEN, LISTBOX_OPTIONS, LISTBOX_MITIGATED, NATIVE_SELECT, NATIVE_SELECT_MAPPED, NATIVE_SELECT_TRIGGER, NATIVE_SELECT_MITIGATED, RAW_IMG, ALLOW_RAW_IMG, DECORATIVE_IMG, IMAGE_COMPONENT_FILE, DEFINES_IMAGE, LOCAL_IMAGE_COMPONENTS, imageComponentCache, TOAST_QUEUE, TOAST_TIMER, TOAST_MITIGATED, PALETTE_SHORTCUT, PALETTE_MODIFIER, PALETTE_OPEN, PALETTE_FILTER, PALETTE_MITIGATED, CLIP_ONE_LINE, DYNAMIC_CHILD, VALUE_REACHABLE, WRAPPER_LOOKBACK, ALLOW_CLIPPED_VALUE, CLIPPED_SIMPLE_VALUE, NOT_TEXT_BINDING, ASYNC_ROUTE, SERVER_DATA_AWAIT, STREAM_BOUNDARY, ALLOW_BLOCKING_PAGE, LOADING_BOUNDARY_FILE, SPECIFIER, MODULE_EXT, JS_EXT, tsconfigCache, NAMED_IMPORT, GENERIC_ACCOUNTS, privateTermCache, MINIFIED_LINE, CALL_LEAD_JS, CALL_LEAD_PY, CONSTANT_EXPR, SQL_DML, SQL_VALUE_POSITION, SQL_PLACEHOLDER_BUILDER, SQL_SAFE_EXPR, SQL_CALL_JS, SQL_CALL_PY, SHELL_SAFE_EXPR, INSECURE_TLS_JS, INSECURE_TLS_PY, PY_HTTP_CLIENT, LOOPBACK_HINT, TLS_OPERATOR_TOGGLE, CORS_ANY_ORIGIN_JS, CORS_CREDENTIALS_JS, CORS_ANY_ORIGIN_PY, CORS_CREDENTIALS_PY, CORS_WINDOW, PYYAML_IMPORT, PICKLE_FROM_OUTSIDE, RAW_HTML_MARKDOWN, OPT_IN_HTML_MARKDOWN, MARKDOWN_HTML_ON, HTML_SANITIZER, HTML_SINK, RADIX_OVERLAY_MODULE, RADIX_UMBRELLA_PARTS, INTERNAL_MODULE, globCache, DIFF_HUNK, REQUEST_INPUT_JS, REQUEST_INPUT_PY, SSH_HOST_KEY_OFF, ACTION_USES, FULL_SHA, IMAGE_DIGEST, PRIVILEGED_TRIGGER, PR_HEAD_REF, CHECKOUT_SITE, SAME_REPO_GUARD, UNTRUSTED_EVENT_TEXT, SCRIPT_KEY, BLOCK_SCALAR, OPERAND_BEFORE, OPERAND_AFTER, LITERAL_OPERAND, CONFIGURED_SECRET, PRESENTED_CREDENTIAL, COMPUTED_MAC, SECRET_NAMED, NOT_SECRET_NAMED, REMEMBERED_COPY, SLASH_PREFIX_CHECK, DESTINATION_NAME, GENERIC_PARAM_NAME, NAVIGATION_SINK, DESTINATION_HELPER, SIGN_IN_FILE, RESOLVED_ORIGIN_CHECK, COMPLETE_PREFIX_CHECK, REDIRECT_SINK_JS, REDIRECT_SINK_PY, REDIRECT_VALIDATED, OUTBOUND_JS, OUTBOUND_PY, SSRF_GUARDED, DNS_RESOLUTION, PRIVATE_ADDRESS_CHECK, OUTBOUND_CALL, CONNECTION_PINNED, RESPONSE_TYPE_CALL_JS, RESPONSE_TYPE_KEY_JS, RESPONSE_OPENER_JS, RESPONSE_TYPE_KWARG_PY, RESPONSE_OPENER_PY, STORED_FILE_TYPE, RESPONSE_SANDBOXED, PATH_JOIN_JS, PATH_JOIN_PY, PATH_CONTAINED, TW_HUE, TW_STOP, HEX_COLOR, ALLOW_BRAND_GRADIENT, ALLOW_GRADIENT_TEXT, SHIMMER, TW_LEFT_STRIPE, TW_ROUNDED_LEFT, ALLOW_ACCENT_STRIPE, EMOJI, EMOJI_LEAD, EMOJI_ICON, EMOJI_COMMIT_SAMPLE, TERMINAL_MARKUP, ALLOW_EMOJI_ICON, COPY_PROP, POWERED_BY_BUZZWORD, ALLOW_POWERED_BY, CHAT_RESIDUE, CHAT_LINK, ALLOW_CHAT_RESIDUE, AI_VOCABULARY, SEAMLESS_LITERAL, ALLOW_AI_VOCABULARY, REQUEST_SERVING, READ_BOUND, ALLOW_UNBOUNDED_READ, ALLOW_SELECT_STAR, ORM_READ_CALL, ROW_LOOP, ALLOW_QUERY_IN_LOOP, FIXED_CANVAS_FILE, TW_DECORATION, EMAIL_MARKUP, TW_WIDTH_GUARD, ALLOW_FIXED_WIDTH, TW_VIEWPORT_HEIGHT, DYNAMIC_VIEWPORT, ALLOW_VIEWPORT_HEIGHT, SPARKLES_IMPORT, SPARKLES_NAME, ALLOW_SPARKLES, LEDGER_MAX_BYTES, LEDGER_KEEP, LEDGER_KEEP_REPLY, grEntry, isGrEntry;
2533
2638
  var init_guardrails = __esm({
2534
2639
  "src/guardrails.ts"() {
2535
2640
  "use strict";
2641
+ init_private_terms();
2536
2642
  COMMENT_LINE = /^\s*(\/\/|#|\*|--|<!--|\{?\/\*)/;
2537
2643
  SKELETON_GUARD_SRC = "\\bif\\s*\\(\\s*(isLoading|isPending|isFetching|loading|pending)\\s*\\)\\s*return\\s+(null\\b|<\\s*\\w*(Spinner|Loader|Loading|CircularProgress)\\b)";
2538
2644
  SKELETON_SIGNAL_SRC = "skeleton|animate-pulse|shimmer|Suspense|ContentLoader|content-loader|<\\s*Placeholder";
@@ -3937,6 +4043,35 @@ var init_guardrails = __esm({
3937
4043
  severity: "block",
3938
4044
  skill: "frontend-policy"
3939
4045
  },
4046
+ {
4047
+ id: "fe-raw-img-over-component",
4048
+ label: "A project with an image component uses it",
4049
+ files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro"],
4050
+ excludeFiles: [
4051
+ "*.test.*",
4052
+ "*.spec.*",
4053
+ "**/tests/**",
4054
+ "**/__tests__/**",
4055
+ "**/fixtures/**",
4056
+ "*.stories.*",
4057
+ "**/dist/**",
4058
+ "**/build/**",
4059
+ "**/node_modules/**",
4060
+ "**/vendor/**",
4061
+ "**/emails/**",
4062
+ "dist/**",
4063
+ "build/**",
4064
+ "node_modules/**",
4065
+ "vendor/**",
4066
+ "emails/**"
4067
+ ],
4068
+ scope: "file",
4069
+ stage: "diff",
4070
+ fileCheck: "fe-raw-img-over-component",
4071
+ message: "A raw <img> in a project that already has an image component. Every content image goes through ONE component, so loading, sizing, alt text, lazy decoding and the lightbox stay the same everywhere and a fix lands once; an <img> per page is a second, drifting copy of it. Use the component named here. Mark the line `enigma:allow-raw-img` where the element must stay raw - an email template, a tracking pixel, or markup the component cannot render (frontend-policy).",
4072
+ severity: "block",
4073
+ skill: "frontend-policy"
4074
+ },
3940
4075
  {
3941
4076
  id: "fe-toast-hand-rolled",
3942
4077
  label: "Toasts come from the primitive",
@@ -4747,6 +4882,35 @@ var init_guardrails = __esm({
4747
4882
  severity: "block",
4748
4883
  skill: "security-policy"
4749
4884
  },
4885
+ {
4886
+ id: "sec-private-term",
4887
+ label: "Real people's data stays out of the repository",
4888
+ files: ["**"],
4889
+ excludeFiles: [
4890
+ "**/node_modules/**",
4891
+ "**/dist/**",
4892
+ "**/build/**",
4893
+ "**/vendor/**",
4894
+ "**/.next/**",
4895
+ "node_modules/**",
4896
+ "dist/**",
4897
+ "build/**",
4898
+ "vendor/**",
4899
+ "*.lock",
4900
+ "*.lockb",
4901
+ "package-lock.json",
4902
+ "*.min.js",
4903
+ "*.map"
4904
+ ],
4905
+ scope: "file",
4906
+ // DIFF stage like sec-operator-env-leak: it fires on the line being added, the moment
4907
+ // before the value reaches the history for good, and never on an old file it did not write.
4908
+ stage: "diff",
4909
+ fileCheck: "sec-private-term",
4910
+ message: "This line carries one of your private terms - the real name, handle, server or community name of you or of the people you serve (the list in ~/.enigma-guard.json, `enigma config guard-private-terms`). Real data never goes into a tracked file, and tests, fixtures, examples and docs use ANONYMOUS data: invent the values (`Player One`, `alice@example.test`, `Test Server`) and, when a real log or payload shape matters, reproduce the shape with invented values instead of pasting the capture. Being shown a real value to work with is permission to use it, never to publish it (security-policy, testing-policy).",
4911
+ severity: "block",
4912
+ skill: "security-policy"
4913
+ },
4750
4914
  // The injection class. Every one but sec-unsafe-deserialization is DIFF stage for the reason the
4751
4915
  // stage exists: each has a measured backlog in real repositories (the counts are in guardrails.md),
4752
4916
  // so at the edit stage it would block unrelated edits to a file that already carried the shape.
@@ -5216,7 +5380,7 @@ var init_guardrails = __esm({
5216
5380
  "ts-relational-no-prisma": (root) => {
5217
5381
  const pkg = readPkgDeps(root);
5218
5382
  if (!pkg) return false;
5219
- const hasTs = "typescript" in pkg || existsSync4(join5(root, "tsconfig.json"));
5383
+ const hasTs = "typescript" in pkg || existsSync4(join6(root, "tsconfig.json"));
5220
5384
  if (!hasTs) return false;
5221
5385
  const relational = ["typeorm", "sequelize", "knex", "drizzle-orm", "pg", "mysql", "mysql2", "better-sqlite3", "@mikro-orm/core"];
5222
5386
  if (!relational.some((d) => d in pkg)) return false;
@@ -5241,6 +5405,7 @@ var init_guardrails = __esm({
5241
5405
  "ts-alias-deep-relative": (content, file) => deepRelativeImports(content, file),
5242
5406
  "ts-alias-paths": (content, file) => missingPathAlias(content, file),
5243
5407
  "sec-operator-env-leak": (content) => operatorHomePathLeak(content),
5408
+ "sec-private-term": (content) => privateTermLeak(content),
5244
5409
  "fe-new-password-affordance-on-signin": (content) => newPasswordAffordanceOnSignIn(content),
5245
5410
  "fe-context-menu-hand-rolled": (content) => handRolledContextMenu(content),
5246
5411
  "fe-selection-hand-rolled": (content) => handRolledSelection(content),
@@ -5250,6 +5415,7 @@ var init_guardrails = __esm({
5250
5415
  "sec-2fa-reauth-prompt": (content) => twoFactorPasswordPrompt(content),
5251
5416
  "fe-select-hand-rolled": (content) => handRolledSelect(content),
5252
5417
  "fe-native-select-over-primitive": (content) => nativeSelectOverPrimitive(content),
5418
+ "fe-raw-img-over-component": (content, file) => rawImgOverComponent(content, file),
5253
5419
  "fe-toast-hand-rolled": (content) => handRolledToast(content),
5254
5420
  "fe-palette-hand-rolled": (content) => handRolledPalette(content),
5255
5421
  "sec-sql-built-from-values": (content, file) => sqlBuiltFromValues(content, file),
@@ -5387,6 +5553,13 @@ var init_guardrails = __esm({
5387
5553
  NATIVE_SELECT_MAPPED = /<option\b|\.map\(/;
5388
5554
  NATIVE_SELECT_TRIGGER = /@enigmax\/primitives/;
5389
5555
  NATIVE_SELECT_MITIGATED = /enigma:allow-native-select|SelectRoot|useSelectContext/;
5556
+ RAW_IMG = /<img\b/;
5557
+ ALLOW_RAW_IMG = /enigma:allow-raw-img/;
5558
+ DECORATIVE_IMG = /alt=(?:""|''|\{["']{2}\})|\.svg\b|\b(?:logo|icon|flag|favicon|sprite)/i;
5559
+ IMAGE_COMPONENT_FILE = /(?:^|\/)(?:image|img|picture)\.(?:tsx|jsx|vue|svelte|astro)$/i;
5560
+ DEFINES_IMAGE = /\b(?:function|const|class)\s+(?:Image|Img|Picture)\b/;
5561
+ LOCAL_IMAGE_COMPONENTS = ["src/components", "components", "src/components/ui", "components/ui", "src/lib/components", "app/components"];
5562
+ imageComponentCache = /* @__PURE__ */ new Map();
5390
5563
  TOAST_QUEUE = /\bset(?:Toasts?|Snackbars?)\b|\btoasts\.(?:map|filter|slice)\b|\btoastQueue\b/;
5391
5564
  TOAST_TIMER = /setTimeout\(/;
5392
5565
  TOAST_MITIGATED = /@enigmax\/primitives|createNotifications|useNotifications|<Toaster|ToastT\b|ToasterProps\b|["']sonner["']|react-hot-toast|react-toastify|notistack|@radix-ui|@mui\/material|@chakra-ui|@mantine|antd|enigma:allow-hand-rolled-toast/;
@@ -5588,10 +5761,10 @@ var init_governor = __esm({
5588
5761
 
5589
5762
  // src/repo-keyed-file.ts
5590
5763
  import { dirname as dirname3 } from "path";
5591
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, rmSync, writeFileSync as writeFileSync4 } from "fs";
5764
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync, rmSync, writeFileSync as writeFileSync4 } from "fs";
5592
5765
  function readRepoKeyedFile(path, version) {
5593
5766
  try {
5594
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
5767
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
5595
5768
  if (!parsed || parsed.version !== version || typeof parsed.repos !== "object" || parsed.repos === null) return {};
5596
5769
  return parsed.repos;
5597
5770
  } catch {
@@ -6031,8 +6204,8 @@ var init_codegraph_extract_docs = __esm({
6031
6204
 
6032
6205
  // src/codegraph-extract.ts
6033
6206
  import { execFileSync as execFileSync4 } from "child_process";
6034
- import { extname as extname2, join as join6, relative as relative2, resolve as resolve3 } from "path";
6035
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
6207
+ import { extname as extname2, join as join7, relative as relative2, resolve as resolve3 } from "path";
6208
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
6036
6209
  function isCodeLang(lang) {
6037
6210
  return !DOC_LANGS.has(lang);
6038
6211
  }
@@ -6349,7 +6522,7 @@ function gitList(root, args) {
6349
6522
  function gitSourceFiles(root) {
6350
6523
  const out = gitList(root, ["--cached", "--others", "--exclude-standard"]);
6351
6524
  if (out === null) return null;
6352
- const nested = existsSync5(join6(root, ".gitmodules")) ? gitList(root, ["--recurse-submodules"]) : null;
6525
+ const nested = existsSync5(join7(root, ".gitmodules")) ? gitList(root, ["--recurse-submodules"]) : null;
6353
6526
  const code = [];
6354
6527
  const docs = [];
6355
6528
  const seen = /* @__PURE__ */ new Set();
@@ -6359,7 +6532,7 @@ function gitSourceFiles(root) {
6359
6532
  seen.add(rel);
6360
6533
  if (!LANG_BY_EXT[extname2(rel).toLowerCase()]) continue;
6361
6534
  if (rel.split("/").some((seg) => seg.startsWith(".") || IGNORE_DIRS.has(seg))) continue;
6362
- (isDocPath(rel) ? docs : code).push(join6(root, rel));
6535
+ (isDocPath(rel) ? docs : code).push(join7(root, rel));
6363
6536
  }
6364
6537
  return [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
6365
6538
  }
@@ -6380,11 +6553,11 @@ function walk(root) {
6380
6553
  for (const e of entries) {
6381
6554
  if (code.length >= MAX_FILES || ++visited >= MAX_ENTRIES) break;
6382
6555
  if (e.isDirectory()) {
6383
- if (!e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) queue.push(join6(queue[head], e.name));
6556
+ if (!e.name.startsWith(".") && !IGNORE_DIRS.has(e.name)) queue.push(join7(queue[head], e.name));
6384
6557
  continue;
6385
6558
  }
6386
6559
  if (!e.isFile() || !LANG_BY_EXT[extname2(e.name).toLowerCase()]) continue;
6387
- (isDocPath(e.name) ? docs : code).push(join6(queue[head], e.name));
6560
+ (isDocPath(e.name) ? docs : code).push(join7(queue[head], e.name));
6388
6561
  }
6389
6562
  }
6390
6563
  const ordered = [...code, ...pickDocs(root, docs, MAX_FILES - code.length)];
@@ -6414,7 +6587,7 @@ function scanFiles(rootDir) {
6414
6587
  }
6415
6588
  let content;
6416
6589
  try {
6417
- content = readFileSync6(abs, "utf8");
6590
+ content = readFileSync7(abs, "utf8");
6418
6591
  } catch {
6419
6592
  continue;
6420
6593
  }
@@ -6540,25 +6713,25 @@ __export(codegraph_exports, {
6540
6713
  scanFiles: () => scanFiles,
6541
6714
  searchGraph: () => searchGraph
6542
6715
  });
6543
- import { homedir as homedir3 } from "os";
6716
+ import { homedir as homedir4 } from "os";
6544
6717
  import { createHash as createHash2 } from "crypto";
6545
- import { basename as basename2, join as join7, resolve as resolve4 } from "path";
6546
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
6718
+ import { basename as basename2, join as join8, resolve as resolve4 } from "path";
6719
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
6547
6720
  function storeDir() {
6548
- return process.env.ENIGMA_CODEGRAPH_DIR || join7(homedir3(), ".enigma", "codegraph");
6721
+ return process.env.ENIGMA_CODEGRAPH_DIR || join8(homedir4(), ".enigma", "codegraph");
6549
6722
  }
6550
6723
  function projectsFile() {
6551
- return join7(storeDir(), "projects.json");
6724
+ return join8(storeDir(), "projects.json");
6552
6725
  }
6553
6726
  function graphFile(id) {
6554
- return join7(storeDir(), `${id}.json`);
6727
+ return join8(storeDir(), `${id}.json`);
6555
6728
  }
6556
6729
  function bodyIndexFile(id) {
6557
- return join7(storeDir(), `${id}.bodies.json`);
6730
+ return join8(storeDir(), `${id}.bodies.json`);
6558
6731
  }
6559
6732
  function readJsonSafe(file, fallback) {
6560
6733
  try {
6561
- return existsSync6(file) ? JSON.parse(readFileSync7(file, "utf8")) : fallback;
6734
+ return existsSync6(file) ? JSON.parse(readFileSync8(file, "utf8")) : fallback;
6562
6735
  } catch {
6563
6736
  return fallback;
6564
6737
  }
@@ -6587,7 +6760,7 @@ function orders(all, byLang) {
6587
6760
  }
6588
6761
  function goModulePath(root) {
6589
6762
  try {
6590
- return /^\s*module\s+(\S+)/m.exec(readFileSync7(join7(root, "go.mod"), "utf8"))?.[1] ?? null;
6763
+ return /^\s*module\s+(\S+)/m.exec(readFileSync8(join8(root, "go.mod"), "utf8"))?.[1] ?? null;
6591
6764
  } catch {
6592
6765
  return null;
6593
6766
  }
@@ -6605,7 +6778,7 @@ function createImportResolver(root, known) {
6605
6778
  return null;
6606
6779
  };
6607
6780
  const relative4 = (fromRel, spec, lang) => {
6608
- const abs = resolve4(join7(root, fromRel), "..", spec);
6781
+ const abs = resolve4(join8(root, fromRel), "..", spec);
6609
6782
  return verifyModule(relPath(root, abs), known, lang);
6610
6783
  };
6611
6784
  return (fromRel, lang, spec) => {
@@ -6782,7 +6955,7 @@ function buildEdges(root, files, importEdges, resolveImport) {
6782
6955
  for (const f of files) {
6783
6956
  let content = "";
6784
6957
  try {
6785
- content = readFileSync7(join7(root, f.path), "utf8");
6958
+ content = readFileSync8(join8(root, f.path), "utf8");
6786
6959
  } catch {
6787
6960
  continue;
6788
6961
  }
@@ -6849,7 +7022,7 @@ function topTokens(text, limit) {
6849
7022
  return [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit);
6850
7023
  }
6851
7024
  function isManagedPath(dir) {
6852
- const managed = resolve4(join7(enigmaHome(), ".enigma"));
7025
+ const managed = resolve4(join8(enigmaHome(), ".enigma"));
6853
7026
  return dir === managed || dir.startsWith(managed + (dir.includes("\\") ? "\\" : "/"));
6854
7027
  }
6855
7028
  function indexProject(rootDir) {
@@ -6918,14 +7091,14 @@ function covering(all, dir) {
6918
7091
  }
6919
7092
  function projectRootOf(dir) {
6920
7093
  const start = resolve4(dir);
6921
- if (ROOT_MARKERS.some((marker) => existsSync6(join7(start, marker)))) return start;
6922
- const home = resolve4(homedir3());
7094
+ if (ROOT_MARKERS.some((marker) => existsSync6(join8(start, marker)))) return start;
7095
+ const home = resolve4(homedir4());
6923
7096
  let current = start;
6924
7097
  for (let up = 0; up < MAX_ROOT_WALK; up++) {
6925
7098
  const parent = resolve4(current, "..");
6926
7099
  if (parent === current || parent === home) break;
6927
7100
  current = parent;
6928
- if (existsSync6(join7(current, ".git"))) return current;
7101
+ if (existsSync6(join8(current, ".git"))) return current;
6929
7102
  }
6930
7103
  return start;
6931
7104
  }
@@ -7127,8 +7300,8 @@ __export(codegraph_query_exports, {
7127
7300
  isCleanDrift: () => isCleanDrift,
7128
7301
  resolveSymbolNodes: () => resolveSymbolNodes
7129
7302
  });
7130
- import { join as join8 } from "path";
7131
- import { readFileSync as readFileSync8 } from "fs";
7303
+ import { join as join9 } from "path";
7304
+ import { readFileSync as readFileSync9 } from "fs";
7132
7305
  function load(opts) {
7133
7306
  const graph = loadFreshGraph(opts.project, opts.refresh ?? true);
7134
7307
  if (!graph) return null;
@@ -7156,7 +7329,7 @@ function savingsFor(nodes, paths) {
7156
7329
  }
7157
7330
  function sliceSpan(root, path, from, to) {
7158
7331
  try {
7159
- const lines = readFileSync8(join8(root, path), "utf8").split("\n");
7332
+ const lines = readFileSync9(join9(root, path), "utf8").split("\n");
7160
7333
  const start = Math.max(1, from);
7161
7334
  const end = Math.min(lines.length, to);
7162
7335
  const slice = lines.slice(start - 1, end);
@@ -7510,7 +7683,7 @@ function codeGraphGrep(pattern, opts = {}) {
7510
7683
  for (const file of files) {
7511
7684
  let text;
7512
7685
  try {
7513
- text = readFileSync8(join8(loaded.graph.root, file.path), "utf8");
7686
+ text = readFileSync9(join9(loaded.graph.root, file.path), "utf8");
7514
7687
  } catch {
7515
7688
  truncatedFiles++;
7516
7689
  continue;
@@ -8065,10 +8238,10 @@ var codegraph_hook_exports = {};
8065
8238
  __export(codegraph_hook_exports, {
8066
8239
  runCodeGraphHook: () => runCodeGraphHook
8067
8240
  });
8068
- import { homedir as homedir4 } from "os";
8241
+ import { homedir as homedir5 } from "os";
8069
8242
  import { spawn } from "child_process";
8070
- import { basename as basename3, join as join9, relative as relative3, resolve as resolve5 } from "path";
8071
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync9, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
8243
+ import { basename as basename3, join as join10, relative as relative3, resolve as resolve5 } from "path";
8244
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync10, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
8072
8245
  function parsePayload(raw) {
8073
8246
  try {
8074
8247
  return JSON.parse(raw);
@@ -8083,15 +8256,15 @@ function emit(event, additionalContext) {
8083
8256
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext } }));
8084
8257
  }
8085
8258
  function stateDir() {
8086
- return join9(process.env.ENIGMA_CODEGRAPH_DIR || join9(homedir4(), ".enigma", "codegraph"), "sessions");
8259
+ return join10(process.env.ENIGMA_CODEGRAPH_DIR || join10(homedir5(), ".enigma", "codegraph"), "sessions");
8087
8260
  }
8088
8261
  function stateFile(dir, sessionId) {
8089
8262
  const key = `${dir}|${sessionId}`.replace(/[^A-Za-z0-9]/g, "").slice(-40) || "default";
8090
- return join9(stateDir(), `${key}.json`);
8263
+ return join10(stateDir(), `${key}.json`);
8091
8264
  }
8092
8265
  function readState(dir, sessionId) {
8093
8266
  try {
8094
- const parsed = JSON.parse(readFileSync9(stateFile(dir, sessionId), "utf8"));
8267
+ const parsed = JSON.parse(readFileSync10(stateFile(dir, sessionId), "utf8"));
8095
8268
  return { injected: Array.isArray(parsed.injected) ? parsed.injected : [], dirty: parsed.dirty === true };
8096
8269
  } catch {
8097
8270
  return { injected: [], dirty: false };
@@ -8113,8 +8286,8 @@ function projectRoot(cg, project, fallback) {
8113
8286
  }
8114
8287
  function writeStatuslineSnapshot(root, symbols, stale) {
8115
8288
  try {
8116
- const dir = process.env.ENIGMA_CODEGRAPH_DIR || join9(homedir4(), ".enigma", "codegraph");
8117
- const target = join9(dir, "statusline.json");
8289
+ const dir = process.env.ENIGMA_CODEGRAPH_DIR || join10(homedir5(), ".enigma", "codegraph");
8290
+ const target = join10(dir, "statusline.json");
8118
8291
  const repos = readRepoKeyedFile(target, STATUSLINE_SNAPSHOT_VERSION);
8119
8292
  repos[root] = { root, symbols, stale, at: Date.now() };
8120
8293
  writeRepoKeyedFile(target, STATUSLINE_SNAPSHOT_VERSION, repos, (s) => s.at);
@@ -8158,7 +8331,7 @@ function coveringProject(cg, dir) {
8158
8331
  }
8159
8332
  function indexMarkerFile(dir) {
8160
8333
  const key = dir.replace(/[^A-Za-z0-9]/g, "").slice(-40) || "default";
8161
- return join9(stateDir(), `indexing-${key}`);
8334
+ return join10(stateDir(), `indexing-${key}`);
8162
8335
  }
8163
8336
  function claimIndex(dir) {
8164
8337
  const file = indexMarkerFile(dir);
@@ -8325,20 +8498,20 @@ async function runLintStep(payload) {
8325
8498
  }
8326
8499
  const file = input?.file_path;
8327
8500
  if (!file || !LINTABLE.test(file)) return 0;
8328
- const { join: join10 } = await import("path");
8329
- const { homedir: homedir5 } = await import("os");
8501
+ const { join: join11 } = await import("path");
8502
+ const { homedir: homedir6 } = await import("os");
8330
8503
  const { createRequire } = await import("module");
8331
- const { readFileSync: readFileSync10, writeFileSync: writeFileSync7 } = await import("fs");
8332
- const dir = process.env.ENIGMA_LINT_DIR || join10(homedir5(), ".enigma", "linter");
8504
+ const { readFileSync: readFileSync11, writeFileSync: writeFileSync7 } = await import("fs");
8505
+ const dir = process.env.ENIGMA_LINT_DIR || join11(homedir6(), ".enigma", "linter");
8333
8506
  let linter;
8334
8507
  try {
8335
- linter = createRequire(join10(dir, "noop.js"))("@enigmax/linter");
8508
+ linter = createRequire(join11(dir, "noop.js"))("@enigmax/linter");
8336
8509
  } catch {
8337
8510
  return 0;
8338
8511
  }
8339
8512
  let text;
8340
8513
  try {
8341
- text = readFileSync10(file, "utf8");
8514
+ text = readFileSync11(file, "utf8");
8342
8515
  } catch {
8343
8516
  return 0;
8344
8517
  }