pasika 0.9.0 → 0.10.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.
@@ -1079,13 +1079,13 @@ function classifyFunction(name, isTsx, hasJsx) {
1079
1079
  return "function";
1080
1080
  }
1081
1081
  function classifyValue(name, initializer, isTsx) {
1082
- const isFunctionLike = initializer !== void 0 && (ts2.isArrowFunction(initializer) || ts2.isFunctionExpression(initializer) || ts2.isFunctionDeclaration(initializer));
1082
+ const isFunctionLike2 = initializer !== void 0 && (ts2.isArrowFunction(initializer) || ts2.isFunctionExpression(initializer) || ts2.isFunctionDeclaration(initializer));
1083
1083
  if (isHookName(name)) return "hook";
1084
1084
  if (isSchemaName(name)) return "schema";
1085
- if (isTsx && isPascalCase3(name) && (initializer === void 0 || returnsJsx(initializer) || isFunctionLike)) {
1085
+ if (isTsx && isPascalCase3(name) && (initializer === void 0 || returnsJsx(initializer) || isFunctionLike2)) {
1086
1086
  return "component";
1087
1087
  }
1088
- if (isFunctionLike) return "function";
1088
+ if (isFunctionLike2) return "function";
1089
1089
  return "constant";
1090
1090
  }
1091
1091
  function parseModule(file) {
@@ -1121,7 +1121,11 @@ function parseModule(file) {
1121
1121
  }
1122
1122
  if (statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
1123
1123
  for (const element of statement.exportClause.elements) {
1124
- exports.push({ name: element.name.text, kind: "other", line: lineOf(sourceFile, element) });
1124
+ exports.push({
1125
+ name: element.name.text,
1126
+ kind: "other",
1127
+ line: lineOf(sourceFile, element)
1128
+ });
1125
1129
  }
1126
1130
  }
1127
1131
  continue;
@@ -1149,7 +1153,11 @@ function parseModule(file) {
1149
1153
  continue;
1150
1154
  }
1151
1155
  if (ts2.isTypeAliasDeclaration(statement) || ts2.isInterfaceDeclaration(statement)) {
1152
- exports.push({ name: statement.name.text, kind: "type", line: lineOf(sourceFile, statement) });
1156
+ exports.push({
1157
+ name: statement.name.text,
1158
+ kind: "type",
1159
+ line: lineOf(sourceFile, statement)
1160
+ });
1153
1161
  }
1154
1162
  }
1155
1163
  return { file: path6.resolve(file), imports, exports };
@@ -1382,41 +1390,81 @@ var enforceCnMergeRule = {
1382
1390
  };
1383
1391
 
1384
1392
  // eslint/rules/cn-helper.ts
1385
- function isIdentifier(node, name) {
1393
+ import path9 from "path";
1394
+ var HELPER = "cn";
1395
+ var DOC = "docs/pasika-adoption-guide/rules/cn-helper-rule.md";
1396
+ var ESLINT_CONFIG = /^eslint\.config\.(?:cjs|cts|js|mjs|mts|ts)$/;
1397
+ function isNamed(node, name) {
1386
1398
  return node?.type === "Identifier" && node.name === name;
1387
1399
  }
1400
+ function isCallTo(node, callee) {
1401
+ return node?.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === callee;
1402
+ }
1403
+ function isComposition(node) {
1404
+ if (!isCallTo(node, "twMerge")) return false;
1405
+ const first = node.arguments[0];
1406
+ return first !== void 0 && first.type !== "SpreadElement" && isCallTo(first, "clsx");
1407
+ }
1408
+ function returnedExpression(body) {
1409
+ if (!body) return void 0;
1410
+ if (body.type !== "BlockStatement") return body;
1411
+ for (const statement of body.body) {
1412
+ if (statement.type === "ReturnStatement") return statement.argument ?? void 0;
1413
+ }
1414
+ return void 0;
1415
+ }
1416
+ function sourceRootFor(context) {
1417
+ return path9.join(path9.dirname(path9.resolve(context.filename)), "src");
1418
+ }
1419
+ function definesHelper(context, name) {
1420
+ const index = getProjectIndex(sourceRootFor(context));
1421
+ if (!index) return true;
1422
+ for (const parsed of index.modules.values()) {
1423
+ if (parsed.exports.some((exported) => exported.name === name)) return true;
1424
+ }
1425
+ return false;
1426
+ }
1388
1427
  var cnHelperRule = {
1389
1428
  meta: {
1390
1429
  schema: [],
1391
1430
  type: "problem",
1392
1431
  docs: {
1393
- description: "Require the cn helper to merge clsx and tailwind-merge."
1432
+ description: `Require a repository to define a ${HELPER} helper that returns twMerge(clsx(...)).`
1394
1433
  }
1395
1434
  },
1396
1435
  create(context) {
1436
+ if (ESLINT_CONFIG.test(path9.basename(context.filename))) {
1437
+ return {
1438
+ Program() {
1439
+ if (definesHelper(context, HELPER)) return;
1440
+ context.report({
1441
+ node: context.sourceCode.ast,
1442
+ loc: { line: 1, column: 0 },
1443
+ message: `A repository must define a ${HELPER} helper. See ${DOC}`
1444
+ });
1445
+ }
1446
+ };
1447
+ }
1448
+ const check = (body, reportNode) => {
1449
+ if (isComposition(returnedExpression(body))) return;
1450
+ context.report({
1451
+ node: reportNode,
1452
+ message: `${HELPER} must return twMerge(clsx(...)). See ${DOC}`
1453
+ });
1454
+ };
1397
1455
  return {
1398
1456
  FunctionDeclaration(node) {
1399
- if (isIdentifier(node.id, "cn")) checkBody(context, node.body);
1457
+ if (isNamed(node.id, HELPER)) check(node.body, node);
1400
1458
  },
1401
1459
  VariableDeclarator(node) {
1402
- if (node.init?.type === "ArrowFunctionExpression" && isIdentifier(node.id, "cn")) {
1403
- checkBody(context, node.init.body);
1404
- }
1460
+ if (!isNamed(node.id.type === "Identifier" ? node.id : null, HELPER)) return;
1461
+ const init = node.init;
1462
+ if (init?.type !== "ArrowFunctionExpression" && init?.type !== "FunctionExpression") return;
1463
+ check(init.body, node);
1405
1464
  }
1406
1465
  };
1407
1466
  }
1408
1467
  };
1409
- function checkBody(context, body) {
1410
- const source = context.sourceCode.getText(body);
1411
- const usesClsx = /\bclsx\b/.test(source);
1412
- const usesTwMerge = /\btwMerge\b|\btailwind-merge\b/.test(source);
1413
- if (!usesClsx || !usesTwMerge) {
1414
- context.report({
1415
- node: body,
1416
- message: "cn must be built from clsx and tailwind-merge (e.g. cn = twMerge(clsx(inputs)))."
1417
- });
1418
- }
1419
- }
1420
1468
 
1421
1469
  // eslint/rules/enforce-cva-variant-props.ts
1422
1470
  var enforceCvaVariantPropsRule = {
@@ -1482,7 +1530,7 @@ var enforceCvaVariantPropsRule = {
1482
1530
  };
1483
1531
 
1484
1532
  // eslint/rules/enforce-barrel-exports.ts
1485
- import path9 from "path";
1533
+ import path10 from "path";
1486
1534
  import fs from "fs";
1487
1535
  function isPascalCase4(str) {
1488
1536
  return /^[A-Z][A-Za-z0-9]*$/.test(str);
@@ -1492,7 +1540,7 @@ function isKebabCase2(str) {
1492
1540
  }
1493
1541
  var SUPPORT_FOLDERS = /* @__PURE__ */ new Set(["types", "schemas", "hooks", "constants", "utils", "config", "locales"]);
1494
1542
  function parentComponentName(dirPath, folderName) {
1495
- const componentFile = path9.join(dirPath, `${folderName}.tsx`);
1543
+ const componentFile = path10.join(dirPath, `${folderName}.tsx`);
1496
1544
  if (!fs.existsSync(componentFile)) return void 0;
1497
1545
  try {
1498
1546
  const exports = parseModule(componentFile).exports;
@@ -1512,11 +1560,11 @@ var enforceBarrelExportsRule = {
1512
1560
  create(context) {
1513
1561
  const filename = context.filename;
1514
1562
  if (!filename) return {};
1515
- const baseName = path9.basename(filename);
1563
+ const baseName = path10.basename(filename);
1516
1564
  if (baseName !== "index.ts" && baseName !== "index.cts" && baseName !== "index.mts") return {};
1517
- const dirPath = path9.dirname(filename);
1518
- const folderName = path9.basename(dirPath);
1519
- const parentFolderName = path9.basename(path9.dirname(dirPath));
1565
+ const dirPath = path10.dirname(filename);
1566
+ const folderName = path10.basename(dirPath);
1567
+ const parentFolderName = path10.basename(path10.dirname(dirPath));
1520
1568
  if (SUPPORT_FOLDERS.has(folderName)) return {};
1521
1569
  if (!isPascalCase4(folderName) && !isKebabCase2(folderName)) return {};
1522
1570
  const parentName = parentComponentName(dirPath, folderName);
@@ -1555,14 +1603,14 @@ var enforceBarrelExportsRule = {
1555
1603
 
1556
1604
  // eslint/rules/component-placement.ts
1557
1605
  import fs2 from "fs";
1558
- import path11 from "path";
1606
+ import path12 from "path";
1559
1607
 
1560
1608
  // eslint/project/ccf.ts
1561
- import path10 from "path";
1609
+ import path11 from "path";
1562
1610
  var SUPPORT_FOLDERS2 = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
1563
1611
  function segmentsOf(file, sourceRoot) {
1564
- const relative = path10.relative(sourceRoot, file);
1565
- return relative.startsWith("..") ? [] : relative.split(path10.sep);
1612
+ const relative = path11.relative(sourceRoot, file);
1613
+ return relative.startsWith("..") ? [] : relative.split(path11.sep);
1566
1614
  }
1567
1615
  function folderSegmentsOf(file, sourceRoot) {
1568
1616
  return segmentsOf(file, sourceRoot).slice(0, -1);
@@ -1641,7 +1689,7 @@ function resolveSupportPlacement(supportFile, supportFolder, index) {
1641
1689
  }
1642
1690
  function describeConsumers(consumers, sourceRoot) {
1643
1691
  const shown = 3;
1644
- const names = consumers.map((consumer) => path10.relative(path10.dirname(sourceRoot), consumer).split(path10.sep).join("/")).sort((left, right) => left.localeCompare(right));
1692
+ const names = consumers.map((consumer) => path11.relative(path11.dirname(sourceRoot), consumer).split(path11.sep).join("/")).sort((left, right) => left.localeCompare(right));
1645
1693
  if (names.length <= shown) return names.join(", ");
1646
1694
  return `${names.slice(0, shown).join(", ")} and ${String(names.length - shown)} more`;
1647
1695
  }
@@ -1658,7 +1706,7 @@ function isNestedInside(expectedFolder, currentFolder, componentFile) {
1658
1706
  if (!sameFolder(currentFolder.slice(0, -1), expectedFolder)) return false;
1659
1707
  const folderName = currentFolder[currentFolder.length - 1];
1660
1708
  if (!folderName) return false;
1661
- return fs2.existsSync(path11.join(path11.dirname(componentFile), `${folderName}.tsx`));
1709
+ return fs2.existsSync(path12.join(path12.dirname(componentFile), `${folderName}.tsx`));
1662
1710
  }
1663
1711
  var componentPlacementRule = {
1664
1712
  meta: {
@@ -1674,7 +1722,7 @@ var componentPlacementRule = {
1674
1722
  const sourceRoot = sourceRootOf(context);
1675
1723
  const index = getProjectIndex(sourceRoot);
1676
1724
  if (!index) return {};
1677
- const componentFile = path11.resolve(filename);
1725
+ const componentFile = path12.resolve(filename);
1678
1726
  const segments = segmentsOf(componentFile, sourceRoot);
1679
1727
  if (segments.length === 0) return {};
1680
1728
  if (isUnderApp(segments) || isConfigModule(segments)) return {};
@@ -1708,7 +1756,7 @@ var componentPlacementRule = {
1708
1756
  };
1709
1757
 
1710
1758
  // eslint/rules/support-file-placement.ts
1711
- import path12 from "path";
1759
+ import path13 from "path";
1712
1760
  var CONFIG_OWNED_FOLDERS = /* @__PURE__ */ new Set(["types", "constants"]);
1713
1761
  var REASON_TEXT2 = {
1714
1762
  "app-consumer": "a file under src/app/ imports it, so it belongs to the app-wide support folder",
@@ -1728,7 +1776,7 @@ var supportFilePlacementRule = {
1728
1776
  },
1729
1777
  create(context) {
1730
1778
  const sourceRoot = sourceRootOf(context);
1731
- const supportFile = path12.resolve(context.filename);
1779
+ const supportFile = path13.resolve(context.filename);
1732
1780
  const currentFolder = folderSegmentsOf(supportFile, sourceRoot);
1733
1781
  const supportFolder = currentFolder[currentFolder.length - 1];
1734
1782
  if (supportFolder === void 0 || !SUPPORT_FOLDERS2.has(supportFolder)) return {};
@@ -1753,7 +1801,7 @@ var supportFilePlacementRule = {
1753
1801
 
1754
1802
  // eslint/rules/application-structure.ts
1755
1803
  import fs3 from "fs";
1756
- import path13 from "path";
1804
+ import path14 from "path";
1757
1805
  var MODULE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
1758
1806
  var ROUTING_FILES = /* @__PURE__ */ new Set([
1759
1807
  "default",
@@ -1791,7 +1839,7 @@ function report2(context, message) {
1791
1839
  };
1792
1840
  }
1793
1841
  function isCodeFile(filename) {
1794
- return MODULE_EXTENSIONS2.has(path13.extname(filename));
1842
+ return MODULE_EXTENSIONS2.has(path14.extname(filename));
1795
1843
  }
1796
1844
  function isRootSupportFolder(folder) {
1797
1845
  return SUPPORT_FOLDERS2.has(folder);
@@ -1812,7 +1860,7 @@ function expectedSupportFolder(kinds) {
1812
1860
  function configModuleRoot(filename, sourceRoot) {
1813
1861
  const segments = segmentsOf(filename, sourceRoot);
1814
1862
  if (segments[0] !== "config" || segments.length < 3) return void 0;
1815
- return path13.join(sourceRoot, "config", segments[1] ?? "");
1863
+ return path14.join(sourceRoot, "config", segments[1] ?? "");
1816
1864
  }
1817
1865
  function componentFolderStart(segments) {
1818
1866
  if (segments[0] === "features") return 2;
@@ -1825,12 +1873,12 @@ function componentFolderViolation(segments, sourceRoot) {
1825
1873
  for (let depth = segments.length - 2; depth >= start; depth -= 1) {
1826
1874
  const folder = segments[depth];
1827
1875
  if (!folder || SUPPORT_FOLDERS2.has(folder)) continue;
1828
- const folderPath = path13.join(sourceRoot, ...segments.slice(0, depth + 1));
1876
+ const folderPath = path14.join(sourceRoot, ...segments.slice(0, depth + 1));
1829
1877
  const label = `src/${segments.slice(0, depth + 1).join("/")}/`;
1830
- if (!fs3.existsSync(path13.join(folderPath, `${folder}.tsx`))) {
1878
+ if (!fs3.existsSync(path14.join(folderPath, `${folder}.tsx`))) {
1831
1879
  return `A folder that is not a support folder must be a component folder; add "${folder}.tsx" to ${label} or move its files into a support folder.`;
1832
1880
  }
1833
- if (!fs3.existsSync(path13.join(folderPath, "index.ts"))) {
1881
+ if (!fs3.existsSync(path14.join(folderPath, "index.ts"))) {
1834
1882
  return `A component folder must have an index.ts that named-re-exports its component; add index.ts to ${label}.`;
1835
1883
  }
1836
1884
  }
@@ -1845,7 +1893,7 @@ var applicationStructureRule = {
1845
1893
  }
1846
1894
  },
1847
1895
  create(context) {
1848
- const filename = path13.resolve(context.filename);
1896
+ const filename = path14.resolve(context.filename);
1849
1897
  const sourceRoot = sourceRootOf(context);
1850
1898
  const segments = segmentsOf(filename, sourceRoot);
1851
1899
  if (segments.length === 0) return {};
@@ -1876,43 +1924,43 @@ var applicationStructureRule = {
1876
1924
  );
1877
1925
  }
1878
1926
  const moduleRoot = configModuleRoot(filename, sourceRoot);
1879
- if (moduleRoot && !fs3.existsSync(path13.join(moduleRoot, "index.ts"))) {
1927
+ if (moduleRoot && !fs3.existsSync(path14.join(moduleRoot, "index.ts"))) {
1880
1928
  return report2(
1881
1929
  context,
1882
- `Add src/config/${path13.basename(moduleRoot)}/index.ts as the configuration module entry point.`
1930
+ `Add src/config/${path14.basename(moduleRoot)}/index.ts as the configuration module entry point.`
1883
1931
  );
1884
1932
  }
1885
- if (moduleRoot && segments.length === 3 && path13.basename(filename) !== "index.ts") {
1933
+ if (moduleRoot && segments.length === 3 && path14.basename(filename) !== "index.ts") {
1886
1934
  const kinds2 = exportedKinds(filename);
1887
1935
  const expected2 = expectedSupportFolder(kinds2);
1888
1936
  if (expected2 !== void 0) {
1889
1937
  return report2(
1890
1938
  context,
1891
- `Move this configuration support file into src/config/${path13.basename(moduleRoot)}/${expected2}/.`
1939
+ `Move this configuration support file into src/config/${path14.basename(moduleRoot)}/${expected2}/.`
1892
1940
  );
1893
1941
  }
1894
1942
  }
1895
1943
  }
1896
1944
  if (topLevel === "app" && isCodeFile(filename)) {
1897
- const basename = path13.basename(filename, path13.extname(filename));
1898
- const currentFolder2 = path13.basename(path13.dirname(filename));
1945
+ const basename = path14.basename(filename, path14.extname(filename));
1946
+ const currentFolder2 = path14.basename(path14.dirname(filename));
1899
1947
  if (SUPPORT_FOLDERS2.has(currentFolder2)) {
1900
1948
  return report2(
1901
1949
  context,
1902
1950
  "src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
1903
1951
  );
1904
1952
  }
1905
- if (!ROUTING_FILES.has(basename) && path13.extname(filename) !== ".css") {
1953
+ if (!ROUTING_FILES.has(basename) && path14.extname(filename) !== ".css") {
1906
1954
  return report2(
1907
1955
  context,
1908
1956
  "src/app/ may contain routing files and framework assets, but ordinary components and support files must live outside src/app/."
1909
1957
  );
1910
1958
  }
1911
- if (ROUTING_FILES.has(basename) || path13.extname(filename) === ".css") return {};
1959
+ if (ROUTING_FILES.has(basename) || path14.extname(filename) === ".css") return {};
1912
1960
  }
1913
- const currentFolder = path13.basename(path13.dirname(filename));
1961
+ const currentFolder = path14.basename(path14.dirname(filename));
1914
1962
  const kinds = exportedKinds(filename);
1915
- const isConfigModuleRoot = topLevel === "config" && segments.length === 3 && path13.basename(filename) === "index.ts";
1963
+ const isConfigModuleRoot = topLevel === "config" && segments.length === 3 && path14.basename(filename) === "index.ts";
1916
1964
  if (isConfigModuleRoot) return {};
1917
1965
  if (!SUPPORT_FOLDERS2.has(currentFolder)) {
1918
1966
  const expected2 = expectedSupportFolder(kinds);
@@ -1929,7 +1977,7 @@ var applicationStructureRule = {
1929
1977
  if (kinds.has("component")) {
1930
1978
  return report2(
1931
1979
  context,
1932
- `A support folder must not contain a component; move ${path13.basename(filename)} beside ${currentFolder}/.`
1980
+ `A support folder must not contain a component; move ${path14.basename(filename)} beside ${currentFolder}/.`
1933
1981
  );
1934
1982
  }
1935
1983
  const expected = expectedSupportFolder(kinds);
@@ -1946,7 +1994,7 @@ var applicationStructureRule = {
1946
1994
  };
1947
1995
 
1948
1996
  // eslint/rules/named-exports.ts
1949
- import path14 from "path";
1997
+ import path15 from "path";
1950
1998
  var FRAMEWORK_DEFAULT_EXPORT_FILES = /* @__PURE__ */ new Set([
1951
1999
  // App Router routing files
1952
2000
  "default",
@@ -1968,9 +2016,9 @@ var FRAMEWORK_DEFAULT_EXPORT_FILES = /* @__PURE__ */ new Set([
1968
2016
  "twitter-image"
1969
2017
  ]);
1970
2018
  function isFrameworkDefaultExportFile(filename) {
1971
- const normalized = filename.replaceAll(path14.sep, "/");
2019
+ const normalized = filename.replaceAll(path15.sep, "/");
1972
2020
  if (!normalized.includes("/src/app/")) return false;
1973
- const basename = path14.basename(filename, path14.extname(filename));
2021
+ const basename = path15.basename(filename, path15.extname(filename));
1974
2022
  return FRAMEWORK_DEFAULT_EXPORT_FILES.has(basename);
1975
2023
  }
1976
2024
  var namedExportsRule = {
@@ -1995,7 +2043,7 @@ var namedExportsRule = {
1995
2043
  };
1996
2044
 
1997
2045
  // eslint/rules/data-testid-case.ts
1998
- import path15 from "path";
2046
+ import path16 from "path";
1999
2047
  var NEXT_ROUTING_FILES2 = /* @__PURE__ */ new Set([
2000
2048
  "default",
2001
2049
  "error",
@@ -2028,9 +2076,9 @@ var dataTestIdCaseRule = {
2028
2076
  }
2029
2077
  },
2030
2078
  create(context) {
2031
- const filename = path15.resolve(context.filename);
2079
+ const filename = path16.resolve(context.filename);
2032
2080
  if (!filename.endsWith(".tsx")) return {};
2033
- const base = path15.basename(filename, path15.extname(filename));
2081
+ const base = path16.basename(filename, path16.extname(filename));
2034
2082
  if (NEXT_ROUTING_FILES2.has(base)) return {};
2035
2083
  const text = context.sourceCode.text;
2036
2084
  const components = parseComponentInfo(text, filename);
@@ -2072,7 +2120,7 @@ function toKebabCase(value) {
2072
2120
 
2073
2121
  // eslint/rules/support-folder-shape.ts
2074
2122
  import fs4 from "fs";
2075
- import path16 from "path";
2123
+ import path17 from "path";
2076
2124
  var SUPPORT_FOLDERS3 = /* @__PURE__ */ new Set(["constants", "types", "schemas"]);
2077
2125
  var INDEX_NAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx", "index.mts", "index.cts"]);
2078
2126
  var supportFolderShapeRule = {
@@ -2084,12 +2132,12 @@ var supportFolderShapeRule = {
2084
2132
  }
2085
2133
  },
2086
2134
  create(context) {
2087
- const filename = path16.resolve(context.filename);
2088
- const baseName = path16.basename(filename);
2135
+ const filename = path17.resolve(context.filename);
2136
+ const baseName = path17.basename(filename);
2089
2137
  if (!INDEX_NAMES.has(baseName)) return {};
2090
- const folder = path16.basename(path16.dirname(filename));
2138
+ const folder = path17.basename(path17.dirname(filename));
2091
2139
  if (!SUPPORT_FOLDERS3.has(folder)) return {};
2092
- const directory = path16.dirname(filename);
2140
+ const directory = path17.dirname(filename);
2093
2141
  let entries;
2094
2142
  try {
2095
2143
  entries = fs4.readdirSync(directory);
@@ -2106,7 +2154,7 @@ var supportFolderShapeRule = {
2106
2154
  const exportPattern = /export\s+(?:\{[^}]*\}|\*[^;]*)\s+from\s+["'](?<specifier>\.[^"']+)["']/g;
2107
2155
  for (const match of source.matchAll(exportPattern)) {
2108
2156
  const specifier = match.groups?.specifier;
2109
- if (specifier) exportedFiles.add(path16.basename(specifier));
2157
+ if (specifier) exportedFiles.add(path17.basename(specifier));
2110
2158
  }
2111
2159
  const hasAnyReExport = exportedFiles.size > 0;
2112
2160
  const guide = `docs/next-codebase-guide/rules/${folder === "constants" ? "constants" : "types-and-schemas"}-rule.md`;
@@ -2133,7 +2181,7 @@ var supportFolderShapeRule = {
2133
2181
  };
2134
2182
 
2135
2183
  // eslint/rules/constant-casing.ts
2136
- import path17 from "path";
2184
+ import path18 from "path";
2137
2185
  var NEXTJS_ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2138
2186
  function isScreamingSnakeCase(name) {
2139
2187
  return /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(name);
@@ -2147,9 +2195,9 @@ var constantCasingRule = {
2147
2195
  }
2148
2196
  },
2149
2197
  create(context) {
2150
- const filename = path17.resolve(context.filename);
2198
+ const filename = path18.resolve(context.filename);
2151
2199
  const sourceRoot = sourceRootOf(context);
2152
- if (!filename.startsWith(sourceRoot + path17.sep)) return {};
2200
+ if (!filename.startsWith(sourceRoot + path18.sep)) return {};
2153
2201
  return {
2154
2202
  VariableDeclarator(node) {
2155
2203
  if (node.id.type !== "Identifier") return;
@@ -2171,7 +2219,7 @@ var constantCasingRule = {
2171
2219
  };
2172
2220
 
2173
2221
  // eslint/rules/prefer-enum.ts
2174
- import path18 from "path";
2222
+ import path19 from "path";
2175
2223
  function isConstAssertion(node) {
2176
2224
  const typeAnnotation = node.typeAnnotation;
2177
2225
  return typeAnnotation?.type === "TSTypeReference" && typeAnnotation.typeName?.type === "Identifier" && typeAnnotation.typeName.name === "const";
@@ -2190,9 +2238,9 @@ var preferEnumRule = {
2190
2238
  }
2191
2239
  },
2192
2240
  create(context) {
2193
- const filename = path18.resolve(context.filename);
2241
+ const filename = path19.resolve(context.filename);
2194
2242
  const sourceRoot = sourceRootOf(context);
2195
- if (!filename.startsWith(sourceRoot + path18.sep)) return {};
2243
+ if (!filename.startsWith(sourceRoot + path19.sep)) return {};
2196
2244
  return {
2197
2245
  TSAsExpression(node) {
2198
2246
  if (!isConstAssertion(node)) return;
@@ -2210,7 +2258,7 @@ var preferEnumRule = {
2210
2258
  };
2211
2259
 
2212
2260
  // eslint/rules/import-through-index.ts
2213
- import path19 from "path";
2261
+ import path20 from "path";
2214
2262
  var importThroughIndexRule = {
2215
2263
  meta: {
2216
2264
  schema: [],
@@ -2220,7 +2268,7 @@ var importThroughIndexRule = {
2220
2268
  }
2221
2269
  },
2222
2270
  create(context) {
2223
- const filename = path19.resolve(context.filename);
2271
+ const filename = path20.resolve(context.filename);
2224
2272
  const sourceRoot = sourceRootOf2(context, filename);
2225
2273
  return {
2226
2274
  Program(node) {
@@ -2232,7 +2280,7 @@ var importThroughIndexRule = {
2232
2280
  (segment) => ["constants", "types", "schemas"].includes(segment)
2233
2281
  );
2234
2282
  const supportFolder = supportFolderIndex >= 0 ? targetSegments[supportFolderIndex] : void 0;
2235
- if (!supportFolder || path19.basename(target).startsWith("index.")) continue;
2283
+ if (!supportFolder || path20.basename(target).startsWith("index.")) continue;
2236
2284
  const folderIndex = targetSegments.slice(0, supportFolderIndex + 1);
2237
2285
  const expected = `@/${folderIndex.join("/")}`;
2238
2286
  context.report({
@@ -2254,14 +2302,14 @@ function importSpecifiers(source) {
2254
2302
  return specifiers;
2255
2303
  }
2256
2304
  function sourceRootOf2(context, filename) {
2257
- const marker = `${path19.sep}src${path19.sep}`;
2305
+ const marker = `${path20.sep}src${path20.sep}`;
2258
2306
  const srcIndex = filename.lastIndexOf(marker);
2259
2307
  if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
2260
- return path19.resolve(context.cwd ?? process.cwd(), "src");
2308
+ return path20.resolve(context.cwd ?? process.cwd(), "src");
2261
2309
  }
2262
2310
 
2263
2311
  // eslint/rules/util-file-name.ts
2264
- import path20 from "path";
2312
+ import path21 from "path";
2265
2313
  function toKebabCase2(value) {
2266
2314
  return value.replace(/(?<lower>[a-z0-9])(?<upper>[A-Z])/g, "$<lower>-$<upper>").replace(/(?<first>[A-Z])(?<rest>[A-Z][a-z])/g, "$<first>-$<rest>").toLowerCase();
2267
2315
  }
@@ -2274,7 +2322,7 @@ var utilFileNameRule = {
2274
2322
  }
2275
2323
  },
2276
2324
  create(context) {
2277
- const filename = path20.resolve(context.filename);
2325
+ const filename = path21.resolve(context.filename);
2278
2326
  const segments = filename.replace(/\\/g, "/").split("/");
2279
2327
  if (!segments.includes("utils")) return {};
2280
2328
  let module;
@@ -2288,13 +2336,13 @@ var utilFileNameRule = {
2288
2336
  const functionName = functions[0]?.name;
2289
2337
  if (!functionName) return {};
2290
2338
  const expected = toKebabCase2(functionName);
2291
- const actual = path20.basename(filename, path20.extname(filename));
2339
+ const actual = path21.basename(filename, path21.extname(filename));
2292
2340
  if (!expected || actual === expected) return {};
2293
2341
  return {
2294
2342
  Program(node) {
2295
2343
  context.report({
2296
2344
  node,
2297
- message: `A utility file exporting ${functionName} must be named ${expected}.${path20.extname(filename).slice(1)}.`
2345
+ message: `A utility file exporting ${functionName} must be named ${expected}.${path21.extname(filename).slice(1)}.`
2298
2346
  });
2299
2347
  }
2300
2348
  };
@@ -2302,7 +2350,7 @@ var utilFileNameRule = {
2302
2350
  };
2303
2351
 
2304
2352
  // eslint/rules/no-util-barrel.ts
2305
- import path21 from "path";
2353
+ import path22 from "path";
2306
2354
  var noUtilBarrelRule = {
2307
2355
  meta: {
2308
2356
  schema: [],
@@ -2312,7 +2360,7 @@ var noUtilBarrelRule = {
2312
2360
  }
2313
2361
  },
2314
2362
  create(context) {
2315
- const filename = path21.resolve(context.filename);
2363
+ const filename = path22.resolve(context.filename);
2316
2364
  const sourceRoot = sourceRootOf3(context, filename);
2317
2365
  return {
2318
2366
  Program(node) {
@@ -2321,7 +2369,7 @@ var noUtilBarrelRule = {
2321
2369
  if (!target) continue;
2322
2370
  const segments = target.replace(/\\/g, "/").split("/");
2323
2371
  const utilsIndex = segments.lastIndexOf("utils");
2324
- if (utilsIndex < 0 || !path21.basename(target).startsWith("index.")) continue;
2372
+ if (utilsIndex < 0 || !path22.basename(target).startsWith("index.")) continue;
2325
2373
  context.report({
2326
2374
  node,
2327
2375
  message: `Import utilities directly instead of through "${specifier}". See docs/next-codebase-guide/rules/utilities-rule.md`
@@ -2341,10 +2389,10 @@ function importSpecifiers2(source) {
2341
2389
  return specifiers;
2342
2390
  }
2343
2391
  function sourceRootOf3(context, filename) {
2344
- const marker = `${path21.sep}src${path21.sep}`;
2392
+ const marker = `${path22.sep}src${path22.sep}`;
2345
2393
  const srcIndex = filename.lastIndexOf(marker);
2346
2394
  if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
2347
- return path21.resolve(context.cwd ?? process.cwd(), "src");
2395
+ return path22.resolve(context.cwd ?? process.cwd(), "src");
2348
2396
  }
2349
2397
 
2350
2398
  // eslint/rules/jsx-hygiene.ts
@@ -2437,7 +2485,7 @@ var jsxHygieneRule = {
2437
2485
  };
2438
2486
 
2439
2487
  // eslint/rules/interactive-component.ts
2440
- import path22 from "path";
2488
+ import path23 from "path";
2441
2489
  var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
2442
2490
  "a",
2443
2491
  "button",
@@ -2592,8 +2640,8 @@ var interactiveComponentRule = {
2592
2640
  },
2593
2641
  create(context) {
2594
2642
  if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
2595
- const filename = path22.resolve(context.filename);
2596
- const base = path22.basename(filename, path22.extname(filename));
2643
+ const filename = path23.resolve(context.filename);
2644
+ const base = path23.basename(filename, path23.extname(filename));
2597
2645
  if (NEXT_ROUTING_FILES3.has(base)) return {};
2598
2646
  return {
2599
2647
  JSXElement(node) {
@@ -2844,12 +2892,12 @@ var cvaBooleanVariantsRule = {
2844
2892
  };
2845
2893
 
2846
2894
  // eslint/rules/cross-feature-import.ts
2847
- import path23 from "path";
2895
+ import path24 from "path";
2848
2896
  var FEATURES_SEGMENT = "features";
2849
2897
  function featureNameOf(resolvedPath, sourceRoot) {
2850
- const relative = path23.relative(sourceRoot, resolvedPath);
2898
+ const relative = path24.relative(sourceRoot, resolvedPath);
2851
2899
  if (relative.startsWith("..")) return void 0;
2852
- const segments = relative.split(path23.sep);
2900
+ const segments = relative.split(path24.sep);
2853
2901
  if (segments[0] !== FEATURES_SEGMENT || segments.length < 2) return void 0;
2854
2902
  return segments[1];
2855
2903
  }
@@ -2865,9 +2913,9 @@ var crossFeatureImportRule = {
2865
2913
  const filename = context.filename;
2866
2914
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2867
2915
  const sourceRoot = sourceRootOf(context);
2868
- const fileRelative = path23.relative(sourceRoot, filename);
2916
+ const fileRelative = path24.relative(sourceRoot, filename);
2869
2917
  if (fileRelative.startsWith("..")) return {};
2870
- const fileSegments = fileRelative.split(path23.sep);
2918
+ const fileSegments = fileRelative.split(path24.sep);
2871
2919
  const isInCompositions = fileSegments[0] === "compositions";
2872
2920
  const isInApp = fileSegments[0] === "app";
2873
2921
  const isConfig = fileSegments[0] === "config";
@@ -2881,9 +2929,9 @@ var crossFeatureImportRule = {
2881
2929
  if (typeof source.value !== "string") return;
2882
2930
  let resolved;
2883
2931
  if (source.value.startsWith("@/")) {
2884
- resolved = path23.resolve(sourceRoot, source.value.slice(2));
2932
+ resolved = path24.resolve(sourceRoot, source.value.slice(2));
2885
2933
  } else if (source.value.startsWith(".")) {
2886
- resolved = path23.resolve(path23.dirname(filename), source.value);
2934
+ resolved = path24.resolve(path24.dirname(filename), source.value);
2887
2935
  }
2888
2936
  if (!resolved) return;
2889
2937
  const feature = featureNameOf(resolved, sourceRoot);
@@ -2902,7 +2950,24 @@ var crossFeatureImportRule = {
2902
2950
  };
2903
2951
 
2904
2952
  // eslint/rules/pure-function-extract.ts
2905
- import path24 from "path";
2953
+ import path25 from "path";
2954
+ var ROUTE_HANDLER_EXPORT_NAMES = /* @__PURE__ */ new Set([
2955
+ "GET",
2956
+ "POST",
2957
+ "PUT",
2958
+ "PATCH",
2959
+ "DELETE",
2960
+ "HEAD",
2961
+ "OPTIONS",
2962
+ "dynamic",
2963
+ "dynamicParams",
2964
+ "revalidate",
2965
+ "fetchCache",
2966
+ "runtime",
2967
+ "preferredRegion",
2968
+ "maxDuration",
2969
+ "generateStaticParams"
2970
+ ]);
2906
2971
  function isComponentLikeName(name) {
2907
2972
  return /^[A-Z]/.test(name);
2908
2973
  }
@@ -2929,13 +2994,14 @@ var pureFunctionExtractRule = {
2929
2994
  },
2930
2995
  create(context) {
2931
2996
  const filename = context.filename;
2932
- if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2997
+ const isRouteFile = path25.basename(filename) === "route.ts";
2998
+ if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx") && !isRouteFile) return {};
2933
2999
  const sourceRoot = sourceRootOf(context);
2934
- const relative = path24.relative(sourceRoot, filename);
3000
+ const relative = path25.relative(sourceRoot, filename);
2935
3001
  if (relative.startsWith("..")) return {};
2936
- const segments = relative.split(path24.sep);
3002
+ const segments = relative.split(path25.sep);
2937
3003
  if (segments[0] === "utils") return {};
2938
- if (segments[0] === "app") return {};
3004
+ if (segments[0] === "app" && !isRouteFile) return {};
2939
3005
  const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
2940
3006
  if (segments.length >= 2 && supportFolders.has(segments[segments.length - 1] ?? "")) return {};
2941
3007
  function report3(node, name) {
@@ -2946,35 +3012,544 @@ var pureFunctionExtractRule = {
2946
3012
  }
2947
3013
  return {
2948
3014
  FunctionDeclaration(node) {
2949
- const exported = node.parent?.type === "ExportNamedDeclaration";
2950
- if (!exported) return;
2951
- const name = node.id?.name;
2952
- if (!name) return;
2953
- if (isComponentLikeName(name) || isHookName2(name)) return;
2954
- if (!node.body || hasHookUsage(node.body)) return;
2955
- report3(node, name);
3015
+ const exported = node.parent?.type === "ExportNamedDeclaration";
3016
+ const moduleLevel = exported || node.parent?.type === "Program";
3017
+ if (!moduleLevel) return;
3018
+ if (!isRouteFile && !exported) return;
3019
+ const name = node.id?.name;
3020
+ if (!name) return;
3021
+ if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
3022
+ if (isComponentLikeName(name) || isHookName2(name)) return;
3023
+ if (!node.body || hasHookUsage(node.body)) return;
3024
+ report3(node, name);
3025
+ },
3026
+ VariableDeclarator(node) {
3027
+ if (node.id.type !== "Identifier") return;
3028
+ const name = node.id.name;
3029
+ if (!name) return;
3030
+ const container = node.parent.parent;
3031
+ const exported = container?.type === "ExportNamedDeclaration";
3032
+ const moduleLevel = exported || container?.type === "Program";
3033
+ if (!moduleLevel) return;
3034
+ if (!isRouteFile && !exported) return;
3035
+ if (isRouteFile && ROUTE_HANDLER_EXPORT_NAMES.has(name)) return;
3036
+ if (isComponentLikeName(name) || isHookName2(name)) return;
3037
+ const init = node.init;
3038
+ if (!init || init.type !== "ArrowFunctionExpression" && init.type !== "FunctionExpression") {
3039
+ return;
3040
+ }
3041
+ if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
3042
+ report3(node, name);
3043
+ }
3044
+ };
3045
+ }
3046
+ };
3047
+
3048
+ // eslint/rules/root-support-placement.ts
3049
+ import path26 from "path";
3050
+ function isSupportFolder(value) {
3051
+ return value === "utils" || value === "types" || value === "schemas" || value === "constants";
3052
+ }
3053
+ var TYPES_AND_SCHEMAS_DOC = "docs/next-codebase-guide/rules/types-and-schemas-rule.md";
3054
+ var KIND_FOR_SUPPORT_FOLDER = {
3055
+ utils: "function",
3056
+ types: "type",
3057
+ schemas: "schema",
3058
+ constants: "constant"
3059
+ };
3060
+ var LABEL_FOR_SUPPORT_FOLDER = {
3061
+ utils: "Function",
3062
+ types: "Type",
3063
+ schemas: "Schema",
3064
+ constants: "Constant"
3065
+ };
3066
+ var DOC_FOR_SUPPORT_FOLDER = {
3067
+ utils: "docs/next-codebase-guide/rules/utilities-rule.md",
3068
+ types: TYPES_AND_SCHEMAS_DOC,
3069
+ schemas: TYPES_AND_SCHEMAS_DOC,
3070
+ constants: "docs/next-codebase-guide/rules/constants-rule.md"
3071
+ };
3072
+ var rootSupportPlacementRule = {
3073
+ meta: {
3074
+ schema: [],
3075
+ type: "problem",
3076
+ docs: {
3077
+ description: "Require a zero-consumer support-folder export to live under a feature folder, not root or elsewhere."
3078
+ }
3079
+ },
3080
+ create(context) {
3081
+ const sourceRoot = sourceRootOf(context);
3082
+ const file = path26.resolve(context.filename);
3083
+ const segments = segmentsOf(file, sourceRoot);
3084
+ const folderSegments = folderSegmentsOf(file, sourceRoot);
3085
+ const supportFolder = folderSegments[folderSegments.length - 1];
3086
+ if (!isSupportFolder(supportFolder)) return {};
3087
+ const expectedKind = KIND_FOR_SUPPORT_FOLDER[supportFolder];
3088
+ const doc = DOC_FOR_SUPPORT_FOLDER[supportFolder];
3089
+ const label = LABEL_FOR_SUPPORT_FOLDER[supportFolder];
3090
+ const isRoot = segments[0] === supportFolder;
3091
+ const isUnderFeature = segments[0] === "features";
3092
+ if (isUnderFeature) return {};
3093
+ const index = getProjectIndex(sourceRoot);
3094
+ if (!index) return {};
3095
+ const module = index.modules.get(file);
3096
+ if (!module) return {};
3097
+ const findings = [];
3098
+ for (const exp of module.exports) {
3099
+ if (exp.kind !== expectedKind) continue;
3100
+ const consumers = [...index.symbolConsumers.get(symbolKey(file, exp.name)) ?? []];
3101
+ if (consumers.length === 0) continue;
3102
+ const real = consumers.filter((consumer) => {
3103
+ const consumerSegments = segmentsOf(consumer, sourceRoot);
3104
+ return !isUnderApp(consumerSegments) && !isConfigModule(consumerSegments);
3105
+ });
3106
+ if (real.length > 0) continue;
3107
+ const where = isRoot ? `root src/${supportFolder}/` : `src/${folderSegments.join("/")}/`;
3108
+ findings.push({
3109
+ line: exp.line,
3110
+ message: `${label} "${exp.name}" has no consumer outside src/app/ or a configuration module, so it has not earned ${where}; move it into the feature it represents (src/features/<feature>/${supportFolder}/). If no existing feature applies, introduce a new feature folder. See ${doc}`
3111
+ });
3112
+ }
3113
+ if (findings.length === 0) return {};
3114
+ return {
3115
+ Program(node) {
3116
+ for (const finding of findings) {
3117
+ context.report({ node, loc: { line: finding.line, column: 0 }, message: finding.message });
3118
+ }
3119
+ }
3120
+ };
3121
+ }
3122
+ };
3123
+
3124
+ // eslint/rules/route-handler-shape.ts
3125
+ import path27 from "path";
3126
+ import ts4 from "typescript";
3127
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3128
+ var REQUIRED_BOUNDARY = "withResponse";
3129
+ function isFunctionLike(node) {
3130
+ return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
3131
+ }
3132
+ function findHandler(node) {
3133
+ if (isFunctionLike(node)) return node;
3134
+ if (node.type === "CallExpression") {
3135
+ for (const arg of node.arguments) {
3136
+ if (arg.type === "SpreadElement") continue;
3137
+ const found = findHandler(arg);
3138
+ if (found) return found;
3139
+ }
3140
+ }
3141
+ return void 0;
3142
+ }
3143
+ function isLoop(node) {
3144
+ return ts4.isForStatement(node) || ts4.isForOfStatement(node) || ts4.isForInStatement(node) || ts4.isWhileStatement(node) || ts4.isDoStatement(node);
3145
+ }
3146
+ function isFunctionBoundary(node) {
3147
+ return ts4.isFunctionExpression(node) || ts4.isArrowFunction(node) || ts4.isFunctionDeclaration(node);
3148
+ }
3149
+ function analyzeHandlerBody(body, sourceText) {
3150
+ const start = body.range?.[0] ?? 0;
3151
+ const end = body.range?.[1] ?? sourceText.length;
3152
+ const sourceFile = ts4.createSourceFile(
3153
+ "route.ts",
3154
+ sourceText.slice(start, end),
3155
+ ts4.ScriptTarget.Latest,
3156
+ true,
3157
+ ts4.ScriptKind.TS
3158
+ );
3159
+ const kinds = /* @__PURE__ */ new Set();
3160
+ const calledNames = /* @__PURE__ */ new Set();
3161
+ const visit = (node) => {
3162
+ if (ts4.isTryStatement(node)) kinds.add("try");
3163
+ if (isLoop(node)) kinds.add("loop");
3164
+ if (ts4.isIfStatement(node)) kinds.add("if");
3165
+ if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression)) calledNames.add(node.expression.text);
3166
+ if (isFunctionBoundary(node)) return;
3167
+ ts4.forEachChild(node, visit);
3168
+ };
3169
+ visit(sourceFile);
3170
+ return { kinds, calledNames };
3171
+ }
3172
+ var CONTROL_FLOW_MESSAGES = {
3173
+ try: "a try statement",
3174
+ loop: "a loop",
3175
+ if: "an if statement"
3176
+ };
3177
+ function isFunctionLikeInit(node) {
3178
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
3179
+ }
3180
+ function collectLocallyDeclaredNames(programBody) {
3181
+ const names = /* @__PURE__ */ new Set();
3182
+ for (const statement of programBody) {
3183
+ const declaration = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
3184
+ if (declaration.type === "FunctionDeclaration") {
3185
+ names.add(declaration.id.name);
3186
+ }
3187
+ if (declaration.type === "VariableDeclaration") {
3188
+ for (const declarator of declaration.declarations) {
3189
+ if (declarator.id.type === "Identifier" && isFunctionLikeInit(declarator.init)) {
3190
+ names.add(declarator.id.name);
3191
+ }
3192
+ }
3193
+ }
3194
+ }
3195
+ return names;
3196
+ }
3197
+ var routeHandlerShapeRule = {
3198
+ meta: {
3199
+ schema: [],
3200
+ type: "problem",
3201
+ docs: {
3202
+ description: "Require a route.ts handler to be wrapped in withResponse, with no try, loop, or if of its own, and every function it calls imported rather than declared in route.ts."
3203
+ }
3204
+ },
3205
+ create(context) {
3206
+ if (path27.basename(context.filename) !== "route.ts") return {};
3207
+ const sourceText = context.sourceCode.text;
3208
+ function reportUnwrapped(node, name) {
3209
+ context.report({
3210
+ node,
3211
+ message: `Handler "${name}" must be wrapped in withResponse. See docs/next-codebase-guide/rules/route-handler-rule.md`
3212
+ });
3213
+ }
3214
+ function checkExport(node, name, init) {
3215
+ if (init.type === "Identifier") return;
3216
+ const boundary = init.type === "CallExpression" && init.callee.type === "Identifier" ? init.callee.name : void 0;
3217
+ if (boundary !== REQUIRED_BOUNDARY) {
3218
+ reportUnwrapped(node, name);
3219
+ return;
3220
+ }
3221
+ const handler = findHandler(init);
3222
+ if (!handler?.body) return;
3223
+ const { kinds, calledNames } = analyzeHandlerBody(handler.body, sourceText);
3224
+ for (const kind of kinds) {
3225
+ context.report({
3226
+ node,
3227
+ message: `Handler "${name}" contains ${CONTROL_FLOW_MESSAGES[kind]} of its own; delegate that to a function it calls. See docs/next-codebase-guide/rules/route-handler-rule.md`
3228
+ });
3229
+ }
3230
+ const localNames = collectLocallyDeclaredNames(context.sourceCode.ast.body);
3231
+ for (const calledName of calledNames) {
3232
+ if (localNames.has(calledName)) {
3233
+ context.report({
3234
+ node,
3235
+ message: `Handler "${name}" calls "${calledName}", which is declared in route.ts instead of imported. See docs/next-codebase-guide/rules/route-handler-rule.md`
3236
+ });
3237
+ }
3238
+ }
3239
+ }
3240
+ return {
3241
+ FunctionDeclaration(node) {
3242
+ const exported = node.parent?.type === "ExportNamedDeclaration";
3243
+ const name = node.id?.name;
3244
+ if (!exported || !name || !HTTP_METHODS.has(name) || !node.body) return;
3245
+ reportUnwrapped(node, name);
3246
+ },
3247
+ VariableDeclarator(node) {
3248
+ if (node.id.type !== "Identifier") return;
3249
+ const name = node.id.name;
3250
+ const exported = node.parent.parent?.type === "ExportNamedDeclaration";
3251
+ if (!exported || !HTTP_METHODS.has(name) || !node.init) return;
3252
+ checkExport(node, name, node.init);
3253
+ }
3254
+ };
3255
+ }
3256
+ };
3257
+
3258
+ // eslint/rules/http-error-usage.ts
3259
+ import { readFileSync as readFileSync4 } from "fs";
3260
+ import path28 from "path";
3261
+ import ts5 from "typescript";
3262
+ var HTTP_ERROR = "HttpError";
3263
+ var ROUTE_FILE = "route.ts";
3264
+ var DOC2 = "docs/next-codebase-guide/rules/route-handler-rule.md";
3265
+ var HERITAGE_DEPTH = 5;
3266
+ var HANDS_ERROR_BACK = `A delegated module must throw the HttpError it reports a failure with, not return it. See ${DOC2}`;
3267
+ var delegatedModuleMessage = (name) => `A delegated module must report a failure by throwing an HttpError, not "${name}". See ${DOC2}`;
3268
+ function constructedErrorName(node) {
3269
+ if (node === void 0) return void 0;
3270
+ if (node.type === "NewExpression") {
3271
+ return node.callee.type === "Identifier" ? node.callee.name : void 0;
3272
+ }
3273
+ if (node.type === "AwaitExpression") return constructedErrorName(node.argument);
3274
+ if (node.type === "ConditionalExpression") {
3275
+ return constructedErrorName(node.consequent) ?? constructedErrorName(node.alternate);
3276
+ }
3277
+ if (node.type === "LogicalExpression") {
3278
+ return constructedErrorName(node.left) ?? constructedErrorName(node.right);
3279
+ }
3280
+ if (node.type !== "CallExpression") return void 0;
3281
+ for (const argument of node.arguments) {
3282
+ const found = constructedErrorName(argument);
3283
+ if (found) return found;
3284
+ }
3285
+ return void 0;
3286
+ }
3287
+ function parentClassOf(file, name) {
3288
+ let text;
3289
+ try {
3290
+ text = readFileSync4(file, "utf8");
3291
+ } catch {
3292
+ return void 0;
3293
+ }
3294
+ const sourceFile = ts5.createSourceFile(file, text, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TSX);
3295
+ for (const statement of sourceFile.statements) {
3296
+ if (!ts5.isClassDeclaration(statement) || statement.name?.text !== name) continue;
3297
+ const heritage = statement.heritageClauses?.find((clause) => clause.token === ts5.SyntaxKind.ExtendsKeyword);
3298
+ const parent = heritage?.types[0]?.expression;
3299
+ return parent && ts5.isIdentifier(parent) ? parent.text : void 0;
3300
+ }
3301
+ return void 0;
3302
+ }
3303
+ function importedFrom(file, name, sourceRoot, index) {
3304
+ const module = index.modules.get(path28.resolve(file));
3305
+ const moduleImport = module?.imports.find((entry) => entry.names.includes(name));
3306
+ if (!moduleImport) return void 0;
3307
+ return resolveSpecifier(file, moduleImport.specifier, sourceRoot);
3308
+ }
3309
+ function isHttpErrorClass(file, name, sourceRoot, index) {
3310
+ let currentFile = path28.resolve(file);
3311
+ let currentName = name;
3312
+ for (let depth = 0; depth <= HERITAGE_DEPTH; depth += 1) {
3313
+ if (currentName === HTTP_ERROR) return true;
3314
+ const parent = parentClassOf(currentFile, currentName);
3315
+ if (parent) {
3316
+ currentName = parent;
3317
+ continue;
3318
+ }
3319
+ const imported = importedFrom(currentFile, currentName, sourceRoot, index);
3320
+ if (!imported) return false;
3321
+ currentFile = imported;
3322
+ }
3323
+ return false;
3324
+ }
3325
+ var pipelineCache;
3326
+ function pipelineFiles(sourceRoot) {
3327
+ const index = getProjectIndex(sourceRoot);
3328
+ if (!index) return void 0;
3329
+ if (pipelineCache?.index === index) return pipelineCache.files;
3330
+ const files = /* @__PURE__ */ new Set();
3331
+ const queue = [...index.modules.keys()].filter((file) => path28.basename(file) === ROUTE_FILE);
3332
+ while (queue.length > 0) {
3333
+ const file = queue.pop();
3334
+ if (file === void 0 || files.has(file)) continue;
3335
+ files.add(file);
3336
+ for (const moduleImport of index.modules.get(file)?.imports ?? []) {
3337
+ const target = resolveSpecifier(file, moduleImport.specifier, index.sourceRoot);
3338
+ if (target && index.modules.has(target) && !files.has(target)) queue.push(target);
3339
+ }
3340
+ }
3341
+ pipelineCache = { index, files };
3342
+ return files;
3343
+ }
3344
+ var httpErrorUsageRule = {
3345
+ meta: {
3346
+ schema: [],
3347
+ type: "problem",
3348
+ docs: {
3349
+ description: "Require a delegated module to report a failure by throwing an HttpError, never another error type and never a returned value."
3350
+ }
3351
+ },
3352
+ create(context) {
3353
+ const sourceRoot = sourceRootOf(context);
3354
+ const file = path28.resolve(context.filename);
3355
+ let delegated;
3356
+ function inPipeline() {
3357
+ delegated ??= pipelineFiles(sourceRoot)?.has(file) ?? false;
3358
+ return delegated;
3359
+ }
3360
+ return {
3361
+ ReturnStatement(node) {
3362
+ if (node.argument === null) return;
3363
+ if (!inPipeline()) return;
3364
+ if (constructedErrorName(node.argument) !== HTTP_ERROR) return;
3365
+ context.report({ node, message: HANDS_ERROR_BACK });
3366
+ },
3367
+ ArrowFunctionExpression(node) {
3368
+ if (node.body.type === "BlockStatement") return;
3369
+ if (!inPipeline()) return;
3370
+ if (constructedErrorName(node.body) !== HTTP_ERROR) return;
3371
+ context.report({ node, message: HANDS_ERROR_BACK });
3372
+ },
3373
+ ThrowStatement(node) {
3374
+ const thrown = node.argument;
3375
+ if (thrown.type !== "NewExpression" || thrown.callee.type !== "Identifier") return;
3376
+ const name = thrown.callee.name;
3377
+ if (name === HTTP_ERROR) return;
3378
+ const index = getProjectIndex(sourceRoot);
3379
+ if (!index || !inPipeline()) return;
3380
+ if (isHttpErrorClass(file, name, sourceRoot, index)) return;
3381
+ context.report({ node, message: delegatedModuleMessage(name) });
3382
+ }
3383
+ };
3384
+ }
3385
+ };
3386
+
3387
+ // eslint/rules/with-response-helper.ts
3388
+ import path29 from "path";
3389
+ import ts6 from "typescript";
3390
+ var HELPER2 = "withResponse";
3391
+ var DOC3 = "docs/pasika-adoption-guide/rules/with-response-helper-rule.md";
3392
+ var ESLINT_CONFIG2 = /^eslint\.config\.(?:cjs|cts|js|mjs|mts|ts)$/;
3393
+ var BEAT_KEYS = [
3394
+ "awaitsHandler",
3395
+ "validatesWithSchema",
3396
+ "checksHttpError",
3397
+ "answersWithNullData",
3398
+ "usesErrorStatus",
3399
+ "rethrows"
3400
+ ];
3401
+ var BEAT_MESSAGES = {
3402
+ awaitsHandler: "must await the handler",
3403
+ validatesWithSchema: "must validate the handler's returned data through the response schema",
3404
+ checksHttpError: "must check the caught error with instanceof HttpError",
3405
+ answersWithNullData: "must answer a thrown HttpError with { data: null, message }",
3406
+ usesErrorStatus: "must answer at the caught error's status",
3407
+ rethrows: "must rethrow a caught error that is not an HttpError"
3408
+ };
3409
+ function isNamed2(node, name) {
3410
+ return node?.type === "Identifier" && node.name === name;
3411
+ }
3412
+ function sourceRootFor2(context) {
3413
+ return path29.join(path29.dirname(path29.resolve(context.filename)), "src");
3414
+ }
3415
+ function definesHelper2(context, name) {
3416
+ const index = getProjectIndex(sourceRootFor2(context));
3417
+ if (!index) return true;
3418
+ for (const parsed of index.modules.values()) {
3419
+ if (parsed.exports.some((exported) => exported.name === name)) return true;
3420
+ }
3421
+ return false;
3422
+ }
3423
+ function contains(node, check) {
3424
+ let found = false;
3425
+ const visit = (current) => {
3426
+ if (found) return;
3427
+ if (check(current)) {
3428
+ found = true;
3429
+ return;
3430
+ }
3431
+ ts6.forEachChild(current, visit);
3432
+ };
3433
+ visit(node);
3434
+ return found;
3435
+ }
3436
+ function findTryStatement(node) {
3437
+ let found;
3438
+ const visit = (current) => {
3439
+ if (found) return;
3440
+ if (ts6.isTryStatement(current)) {
3441
+ found = current;
3442
+ return;
3443
+ }
3444
+ ts6.forEachChild(current, visit);
3445
+ };
3446
+ visit(node);
3447
+ return found;
3448
+ }
3449
+ function awaitsHandlerCall(handlerName) {
3450
+ return (node) => ts6.isAwaitExpression(node) && ts6.isCallExpression(node.expression) && ts6.isIdentifier(node.expression.expression) && node.expression.expression.text === handlerName;
3451
+ }
3452
+ function parsesThroughSchema(schemaName) {
3453
+ return (node) => ts6.isCallExpression(node) && ts6.isPropertyAccessExpression(node.expression) && ts6.isIdentifier(node.expression.expression) && node.expression.expression.text === schemaName && (node.expression.name.text === "parse" || node.expression.name.text === "safeParse");
3454
+ }
3455
+ function checksHttpError(node) {
3456
+ return ts6.isBinaryExpression(node) && node.operatorToken.kind === ts6.SyntaxKind.InstanceOfKeyword && ts6.isIdentifier(node.right) && node.right.text === "HttpError";
3457
+ }
3458
+ function answersWithNullData(node) {
3459
+ if (!ts6.isCallExpression(node) || !ts6.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "json") {
3460
+ return false;
3461
+ }
3462
+ return node.arguments.some((argument) => {
3463
+ if (!ts6.isObjectLiteralExpression(argument)) return false;
3464
+ return argument.properties.some((property) => {
3465
+ if (!ts6.isPropertyAssignment(property)) return false;
3466
+ const name = ts6.isIdentifier(property.name) ? property.name.text : void 0;
3467
+ return name === "data" && property.initializer.kind === ts6.SyntaxKind.NullKeyword;
3468
+ });
3469
+ });
3470
+ }
3471
+ function readsErrorStatus(errorName) {
3472
+ return (node) => ts6.isPropertyAccessExpression(node) && node.name.text === "status" && ts6.isIdentifier(node.expression) && node.expression.text === errorName;
3473
+ }
3474
+ function collectBeats(text, schemaName, handlerName) {
3475
+ const beats = {
3476
+ awaitsHandler: false,
3477
+ validatesWithSchema: false,
3478
+ checksHttpError: false,
3479
+ answersWithNullData: false,
3480
+ usesErrorStatus: false,
3481
+ rethrows: false
3482
+ };
3483
+ const sourceFile = ts6.createSourceFile("with-response.ts", text, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TS);
3484
+ const tryStatement = findTryStatement(sourceFile);
3485
+ if (!tryStatement) return beats;
3486
+ beats.awaitsHandler = contains(tryStatement.tryBlock, awaitsHandlerCall(handlerName));
3487
+ beats.validatesWithSchema = contains(tryStatement.tryBlock, parsesThroughSchema(schemaName));
3488
+ const catchClause = tryStatement.catchClause;
3489
+ if (!catchClause) return beats;
3490
+ const errorName = catchClause.variableDeclaration?.name;
3491
+ const caughtName = errorName && ts6.isIdentifier(errorName) ? errorName.text : void 0;
3492
+ beats.checksHttpError = contains(catchClause.block, checksHttpError);
3493
+ beats.answersWithNullData = contains(catchClause.block, answersWithNullData);
3494
+ beats.usesErrorStatus = contains(catchClause.block, readsErrorStatus(caughtName));
3495
+ beats.rethrows = contains(catchClause.block, ts6.isThrowStatement);
3496
+ return beats;
3497
+ }
3498
+ function parameterNames(node) {
3499
+ if (node.type !== "FunctionDeclaration" && node.type !== "FunctionExpression" && node.type !== "ArrowFunctionExpression") {
3500
+ return [];
3501
+ }
3502
+ return node.params.map((parameter) => parameter.type === "Identifier" ? parameter.name : void 0);
3503
+ }
3504
+ var withResponseHelperRule = {
3505
+ meta: {
3506
+ schema: [],
3507
+ type: "problem",
3508
+ docs: {
3509
+ description: `Require a repository to define a ${HELPER2} helper that validates the handler's data and maps a thrown HttpError to a response.`
3510
+ }
3511
+ },
3512
+ create(context) {
3513
+ if (ESLINT_CONFIG2.test(path29.basename(context.filename))) {
3514
+ return {
3515
+ Program() {
3516
+ if (definesHelper2(context, HELPER2)) return;
3517
+ context.report({
3518
+ node: context.sourceCode.ast,
3519
+ loc: { line: 1, column: 0 },
3520
+ message: `A repository must define a ${HELPER2} helper. See ${DOC3}`
3521
+ });
3522
+ }
3523
+ };
3524
+ }
3525
+ const check = (node, parameters) => {
3526
+ const [start, end] = node.range ?? [0, context.sourceCode.text.length];
3527
+ const beats = collectBeats(context.sourceCode.text.slice(start, end), parameters[0], parameters[1]);
3528
+ for (const beat of BEAT_KEYS) {
3529
+ if (beats[beat]) continue;
3530
+ context.report({
3531
+ node,
3532
+ message: `${HELPER2} ${BEAT_MESSAGES[beat]}. See ${DOC3}`
3533
+ });
3534
+ }
3535
+ };
3536
+ return {
3537
+ FunctionDeclaration(node) {
3538
+ if (isNamed2(node.id, HELPER2)) check(node, parameterNames(node));
2956
3539
  },
2957
3540
  VariableDeclarator(node) {
2958
- if (node.id.type !== "Identifier") return;
2959
- const name = node.id.name;
2960
- if (!name) return;
2961
- const exported = node.parent.parent?.type === "ExportNamedDeclaration";
2962
- if (!exported) return;
2963
- if (isComponentLikeName(name) || isHookName2(name)) return;
3541
+ if (!isNamed2(node.id.type === "Identifier" ? node.id : null, HELPER2)) return;
2964
3542
  const init = node.init;
2965
- if (!init || init.type !== "ArrowFunctionExpression" && init.type !== "FunctionExpression") {
2966
- return;
2967
- }
2968
- if (init.body.type === "BlockStatement" && hasHookUsage(init.body)) return;
2969
- report3(node, name);
3543
+ if (init?.type !== "ArrowFunctionExpression" && init?.type !== "FunctionExpression") return;
3544
+ check(init, parameterNames(init));
2970
3545
  }
2971
3546
  };
2972
3547
  }
2973
3548
  };
2974
3549
 
2975
3550
  // eslint/rules/hook-complexity.ts
2976
- import path25 from "path";
2977
- import ts4 from "typescript";
3551
+ import path30 from "path";
3552
+ import ts7 from "typescript";
2978
3553
  var REACT_HOOKS = /* @__PURE__ */ new Set([
2979
3554
  "useState",
2980
3555
  "useEffect",
@@ -2992,28 +3567,67 @@ var REACT_HOOKS = /* @__PURE__ */ new Set([
2992
3567
  "useSyncExternalStore",
2993
3568
  "useInsertionEffect"
2994
3569
  ]);
3570
+ var SUBSCRIPTION_METHODS = /* @__PURE__ */ new Set(["on", "off", "addEventListener", "removeEventListener"]);
3571
+ var STORAGE_OBJECTS = /* @__PURE__ */ new Set(["localStorage", "sessionStorage", "indexedDB"]);
3572
+ var DOM_METHODS = /* @__PURE__ */ new Set(["focus", "blur", "scrollIntoView", "click"]);
3573
+ var DOM_PROPERTIES = /* @__PURE__ */ new Set(["classList"]);
3574
+ var DOM_CONSTRUCTORS = /* @__PURE__ */ new Set(["MutationObserver", "ResizeObserver", "IntersectionObserver"]);
3575
+ var LIFECYCLE_METHODS = /* @__PURE__ */ new Set(["load", "destroy", "dispose", "close", "cleanup", "unmount"]);
2995
3576
  function isHookName3(name) {
2996
3577
  return /^use[A-Z]/.test(name);
2997
3578
  }
2998
- function countImperativeCategories(body, sourceText) {
3579
+ function calledMethodName(node) {
3580
+ return ts7.isPropertyAccessExpression(node.expression) ? node.expression.name.text : void 0;
3581
+ }
3582
+ function calledOnObjectName(node) {
3583
+ if (!ts7.isPropertyAccessExpression(node.expression)) return void 0;
3584
+ const object = node.expression.expression;
3585
+ return ts7.isIdentifier(object) ? object.text : void 0;
3586
+ }
3587
+ function calledHookName(node) {
3588
+ return ts7.isCallExpression(node) && ts7.isIdentifier(node.expression) && REACT_HOOKS.has(node.expression.text) ? node.expression.text : void 0;
3589
+ }
3590
+ function sideEffectCategoryOf(node) {
3591
+ if (ts7.isAwaitExpression(node)) return "externalIO";
3592
+ if (ts7.isNewExpression(node) && ts7.isIdentifier(node.expression) && DOM_CONSTRUCTORS.has(node.expression.text)) {
3593
+ return "domManipulation";
3594
+ }
3595
+ if (ts7.isPropertyAccessExpression(node) && DOM_PROPERTIES.has(node.name.text)) {
3596
+ return "domManipulation";
3597
+ }
3598
+ if (ts7.isCallExpression(node)) {
3599
+ if (ts7.isIdentifier(node.expression) && node.expression.text === "fetch") return "externalIO";
3600
+ const method = calledMethodName(node);
3601
+ if (method && SUBSCRIPTION_METHODS.has(method)) return "subscription";
3602
+ if (method && LIFECYCLE_METHODS.has(method)) return "lifecycle";
3603
+ if (method && DOM_METHODS.has(method)) return "domManipulation";
3604
+ const object = calledOnObjectName(node);
3605
+ if (object && STORAGE_OBJECTS.has(object)) return "externalIO";
3606
+ }
3607
+ return void 0;
3608
+ }
3609
+ function computeExtractionScore(body, sourceText) {
2999
3610
  const start = body.range?.[0] ?? 0;
3000
3611
  const end = body.range?.[1] ?? sourceText.length;
3001
- const sourceFile = ts4.createSourceFile(
3612
+ const sourceFile = ts7.createSourceFile(
3002
3613
  "hook.ts",
3003
3614
  sourceText.slice(start, end),
3004
- ts4.ScriptTarget.Latest,
3615
+ ts7.ScriptTarget.Latest,
3005
3616
  true,
3006
- ts4.ScriptKind.TS
3617
+ ts7.ScriptKind.TS
3007
3618
  );
3008
- const categories = /* @__PURE__ */ new Set();
3619
+ const hookNames = /* @__PURE__ */ new Set();
3620
+ const sideEffectCategories = /* @__PURE__ */ new Set();
3009
3621
  const visit = (node) => {
3010
- if (ts4.isCallExpression(node) && ts4.isIdentifier(node.expression) && REACT_HOOKS.has(node.expression.text)) {
3011
- categories.add(node.expression.text);
3012
- }
3013
- ts4.forEachChild(node, visit);
3622
+ const hookName = calledHookName(node);
3623
+ if (hookName) hookNames.add(hookName);
3624
+ const sideEffect = sideEffectCategoryOf(node);
3625
+ if (sideEffect) sideEffectCategories.add(sideEffect);
3626
+ ts7.forEachChild(node, visit);
3014
3627
  };
3015
3628
  visit(sourceFile);
3016
- return categories.size;
3629
+ const hookDiversityPoint = hookNames.size >= 2 ? 1 : 0;
3630
+ return hookDiversityPoint + sideEffectCategories.size;
3017
3631
  }
3018
3632
  var hookComplexityRule = {
3019
3633
  meta: {
@@ -3026,26 +3640,26 @@ var hookComplexityRule = {
3026
3640
  create(context) {
3027
3641
  const filename = context.filename;
3028
3642
  const sourceRoot = sourceRootOf(context);
3029
- const relative = path25.relative(sourceRoot, filename);
3643
+ const relative = path30.relative(sourceRoot, filename);
3030
3644
  if (relative.startsWith("..")) return {};
3031
- const segments = relative.split(path25.sep);
3645
+ const segments = relative.split(path30.sep);
3032
3646
  const sourceText = context.sourceCode.text;
3033
3647
  function checkHook(node, name, body, exported) {
3034
3648
  if (!exported) return;
3035
3649
  if (!name || !isHookName3(name)) return;
3036
3650
  if (!body) return;
3037
- const imperativeCount = countImperativeCategories(body, sourceText);
3651
+ const score = computeExtractionScore(body, sourceText);
3038
3652
  const parentFolder = segments.length >= 2 ? segments[segments.length - 2] : void 0;
3039
3653
  const inSupportFolder = parentFolder === "hooks";
3040
- if (imperativeCount >= 2 && !inSupportFolder) {
3654
+ if (score >= 2 && !inSupportFolder) {
3041
3655
  context.report({
3042
3656
  node,
3043
- message: `Hook "${name}" has ${String(imperativeCount)} imperative categories and must be extracted to a hooks/ folder. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3657
+ message: `Hook "${name}" has an extraction score of ${String(score)} and must be extracted to a hooks/ folder. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3044
3658
  });
3045
- } else if (imperativeCount < 2 && inSupportFolder) {
3659
+ } else if (score < 2 && inSupportFolder) {
3046
3660
  context.report({
3047
3661
  node,
3048
- message: `Hook "${name}" has fewer than two imperative categories and must stay inline in its consumer file. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3662
+ message: `Hook "${name}" has an extraction score below two and must stay inline in its consumer file. See docs/next-codebase-guide/rules/hook-extraction-rule.md`
3049
3663
  });
3050
3664
  }
3051
3665
  }
@@ -3070,9 +3684,9 @@ var hookComplexityRule = {
3070
3684
  };
3071
3685
 
3072
3686
  // eslint/rules/locale-dotted-path.ts
3073
- import path26 from "path";
3687
+ import path31 from "path";
3074
3688
  function isInLocalesDir(filename) {
3075
- const segments = path26.resolve(filename).split(path26.sep);
3689
+ const segments = path31.resolve(filename).split(path31.sep);
3076
3690
  const srcIdx = segments.lastIndexOf("src");
3077
3691
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3078
3692
  }
@@ -3121,9 +3735,9 @@ var localeDottedPathRule = {
3121
3735
  };
3122
3736
 
3123
3737
  // eslint/rules/locales-location.ts
3124
- import path27 from "path";
3738
+ import path32 from "path";
3125
3739
  function isLocalesFile(filename) {
3126
- const segments = path27.resolve(filename).split(path27.sep);
3740
+ const segments = path32.resolve(filename).split(path32.sep);
3127
3741
  const srcIdx = segments.lastIndexOf("src");
3128
3742
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3129
3743
  }
@@ -3148,7 +3762,7 @@ var localesLocationRule = {
3148
3762
  create(context) {
3149
3763
  if (isLocalesFile(context.filename) || isTestFile(context.filename)) return {};
3150
3764
  const filename = context.filename;
3151
- const segments = path27.resolve(filename).split(path27.sep);
3765
+ const segments = path32.resolve(filename).split(path32.sep);
3152
3766
  const srcIdx = segments.lastIndexOf("src");
3153
3767
  if (srcIdx === -1) return {};
3154
3768
  const folder = segments[srcIdx + 1];
@@ -3172,7 +3786,7 @@ var localesLocationRule = {
3172
3786
  };
3173
3787
 
3174
3788
  // eslint/rules/hook-extraction.ts
3175
- import path28 from "path";
3789
+ import path33 from "path";
3176
3790
  var hookExtractionRule = {
3177
3791
  meta: {
3178
3792
  schema: [],
@@ -3183,7 +3797,7 @@ var hookExtractionRule = {
3183
3797
  },
3184
3798
  create(context) {
3185
3799
  const sourceRoot = sourceRootOf(context);
3186
- const file = path28.resolve(context.filename);
3800
+ const file = path33.resolve(context.filename);
3187
3801
  const segments = segmentsOf(file, sourceRoot);
3188
3802
  if (segments.length === 0) return {};
3189
3803
  const index = getProjectIndex(sourceRoot);
@@ -3209,7 +3823,7 @@ var hookExtractionRule = {
3209
3823
  };
3210
3824
 
3211
3825
  // eslint/rules/value-extraction.ts
3212
- import path29 from "path";
3826
+ import path34 from "path";
3213
3827
  var valueExtractionRule = {
3214
3828
  meta: {
3215
3829
  schema: [],
@@ -3220,7 +3834,7 @@ var valueExtractionRule = {
3220
3834
  },
3221
3835
  create(context) {
3222
3836
  const sourceRoot = sourceRootOf(context);
3223
- const file = path29.resolve(context.filename);
3837
+ const file = path34.resolve(context.filename);
3224
3838
  const segments = segmentsOf(file, sourceRoot);
3225
3839
  if (segments.length === 0 || segments[0] !== "app") return {};
3226
3840
  const index = getProjectIndex(sourceRoot);
@@ -3241,7 +3855,7 @@ var valueExtractionRule = {
3241
3855
  };
3242
3856
 
3243
3857
  // eslint/rules/config-extraction.ts
3244
- import path30 from "path";
3858
+ import path35 from "path";
3245
3859
  var configExtractionRule = {
3246
3860
  meta: {
3247
3861
  schema: [],
@@ -3252,7 +3866,7 @@ var configExtractionRule = {
3252
3866
  },
3253
3867
  create(context) {
3254
3868
  const sourceRoot = sourceRootOf(context);
3255
- const file = path30.resolve(context.filename);
3869
+ const file = path35.resolve(context.filename);
3256
3870
  const segments = segmentsOf(file, sourceRoot);
3257
3871
  if (segments.length < 3 || segments[0] !== "config") return {};
3258
3872
  if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
@@ -3290,7 +3904,7 @@ var configExtractionRule = {
3290
3904
  };
3291
3905
 
3292
3906
  // eslint/rules/component-nesting.ts
3293
- import path31 from "path";
3907
+ import path36 from "path";
3294
3908
  var componentNestingRule = {
3295
3909
  meta: {
3296
3910
  schema: [],
@@ -3301,7 +3915,7 @@ var componentNestingRule = {
3301
3915
  },
3302
3916
  create(context) {
3303
3917
  const sourceRoot = sourceRootOf(context);
3304
- const file = path31.resolve(context.filename);
3918
+ const file = path36.resolve(context.filename);
3305
3919
  const segments = segmentsOf(file, sourceRoot);
3306
3920
  if (segments.length !== 4 || segments[0] !== "features") return {};
3307
3921
  const index = getProjectIndex(sourceRoot);
@@ -3334,7 +3948,7 @@ var componentNestingRule = {
3334
3948
  };
3335
3949
 
3336
3950
  // eslint/rules/stay-flat.ts
3337
- import path32 from "path";
3951
+ import path37 from "path";
3338
3952
  var stayFlatRule = {
3339
3953
  meta: {
3340
3954
  schema: [],
@@ -3345,7 +3959,7 @@ var stayFlatRule = {
3345
3959
  },
3346
3960
  create(context) {
3347
3961
  const sourceRoot = sourceRootOf(context);
3348
- const file = path32.resolve(context.filename);
3962
+ const file = path37.resolve(context.filename);
3349
3963
  const segments = segmentsOf(file, sourceRoot);
3350
3964
  if (segments.length !== 3 || segments[0] !== "features") return {};
3351
3965
  const index = getProjectIndex(sourceRoot);
@@ -3385,7 +3999,7 @@ var stayFlatRule = {
3385
3999
  };
3386
4000
 
3387
4001
  // eslint/rules/type-extraction.ts
3388
- import path33 from "path";
4002
+ import path38 from "path";
3389
4003
  var typeExtractionRule = {
3390
4004
  meta: {
3391
4005
  schema: [],
@@ -3396,7 +4010,7 @@ var typeExtractionRule = {
3396
4010
  },
3397
4011
  create(context) {
3398
4012
  const sourceRoot = sourceRootOf(context);
3399
- const file = path33.resolve(context.filename);
4013
+ const file = path38.resolve(context.filename);
3400
4014
  const segments = segmentsOf(file, sourceRoot);
3401
4015
  if (segments.length === 0) return {};
3402
4016
  const index = getProjectIndex(sourceRoot);
@@ -3442,9 +4056,9 @@ var typeExtractionRule = {
3442
4056
  };
3443
4057
 
3444
4058
  // eslint/rules/locale-placement.ts
3445
- import path34 from "path";
3446
- import { readFileSync as readFileSync4 } from "fs";
3447
- import ts5 from "typescript";
4059
+ import path39 from "path";
4060
+ import { readFileSync as readFileSync5 } from "fs";
4061
+ import ts8 from "typescript";
3448
4062
  var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
3449
4063
  var camelCase = (name) => name.replace(/-[a-z]/g, (match) => match.slice(1).toUpperCase());
3450
4064
  var FORCED_TOP_LEVEL = /* @__PURE__ */ new Set(["app", "shared", "compositions", "config"]);
@@ -3452,26 +4066,26 @@ function forcesTopLevel(segments) {
3452
4066
  return FORCED_TOP_LEVEL.has(segments[0] ?? "") || SUPPORT_FOLDERS2.has(segments[0] ?? "");
3453
4067
  }
3454
4068
  function localePlacement(text) {
3455
- const sourceFile = ts5.createSourceFile("locales.ts", text, ts5.ScriptTarget.Latest, true, ts5.ScriptKind.TS);
4069
+ const sourceFile = ts8.createSourceFile("locales.ts", text, ts8.ScriptTarget.Latest, true, ts8.ScriptKind.TS);
3456
4070
  for (const statement of sourceFile.statements) {
3457
- if (!ts5.isVariableStatement(statement)) continue;
3458
- const isExported2 = (ts5.getModifiers(statement) ?? []).some(
3459
- (modifier) => modifier.kind === ts5.SyntaxKind.ExportKeyword
4071
+ if (!ts8.isVariableStatement(statement)) continue;
4072
+ const isExported2 = (ts8.getModifiers(statement) ?? []).some(
4073
+ (modifier) => modifier.kind === ts8.SyntaxKind.ExportKeyword
3460
4074
  );
3461
4075
  if (!isExported2) continue;
3462
4076
  for (const declaration of statement.declarationList.declarations) {
3463
- if (!ts5.isIdentifier(declaration.name) || declaration.name.text !== "locales") continue;
3464
- if (!declaration.initializer || !ts5.isObjectLiteralExpression(declaration.initializer)) continue;
4077
+ if (!ts8.isIdentifier(declaration.name) || declaration.name.text !== "locales") continue;
4078
+ if (!declaration.initializer || !ts8.isObjectLiteralExpression(declaration.initializer)) continue;
3465
4079
  const placement = /* @__PURE__ */ new Map();
3466
4080
  for (const property of declaration.initializer.properties) {
3467
- if (!ts5.isPropertyAssignment(property)) continue;
4081
+ if (!ts8.isPropertyAssignment(property)) continue;
3468
4082
  let name;
3469
- if (ts5.isIdentifier(property.name)) name = property.name.text;
3470
- else if (ts5.isStringLiteral(property.name)) name = property.name.text;
4083
+ if (ts8.isIdentifier(property.name)) name = property.name.text;
4084
+ else if (ts8.isStringLiteral(property.name)) name = property.name.text;
3471
4085
  if (name === void 0) continue;
3472
4086
  const line = sourceFile.getLineAndCharacterOfPosition(property.getStart(sourceFile)).line + 1;
3473
4087
  placement.set(name, {
3474
- kind: ts5.isObjectLiteralExpression(property.initializer) ? "nested" : "top",
4088
+ kind: ts8.isObjectLiteralExpression(property.initializer) ? "nested" : "top",
3475
4089
  line
3476
4090
  });
3477
4091
  }
@@ -3490,7 +4104,7 @@ var localePlacementRule = {
3490
4104
  },
3491
4105
  create(context) {
3492
4106
  const sourceRoot = sourceRootOf(context);
3493
- const file = path34.resolve(context.filename);
4107
+ const file = path39.resolve(context.filename);
3494
4108
  const segments = segmentsOf(file, sourceRoot);
3495
4109
  if (segments.length === 0) return {};
3496
4110
  const index = getProjectIndex(sourceRoot);
@@ -3500,7 +4114,7 @@ var localePlacementRule = {
3500
4114
  return candidateSegments.length === 2 && candidateSegments[0] === "locales" && candidateSegments[1]?.startsWith("index.");
3501
4115
  });
3502
4116
  if (!localesFile || file !== localesFile) return {};
3503
- const placement = localePlacement(readFileSync4(localesFile, "utf8"));
4117
+ const placement = localePlacement(readFileSync5(localesFile, "utf8"));
3504
4118
  if (!placement) return {};
3505
4119
  const keyReaders = /* @__PURE__ */ new Map();
3506
4120
  const keyFeatures = /* @__PURE__ */ new Map();
@@ -3512,7 +4126,7 @@ var localePlacementRule = {
3512
4126
  );
3513
4127
  if (!importsLocales) continue;
3514
4128
  const candidateSegments = segmentsOf(candidateFile, sourceRoot);
3515
- for (const match of readFileSync4(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
4129
+ for (const match of readFileSync5(candidateFile, "utf8").matchAll(LOCALE_ACCESS)) {
3516
4130
  const key = match.groups?.key;
3517
4131
  if (key === void 0) continue;
3518
4132
  const readers = keyReaders.get(key) ?? /* @__PURE__ */ new Set();
@@ -3568,39 +4182,39 @@ var localePlacementRule = {
3568
4182
  };
3569
4183
 
3570
4184
  // eslint/rules/sole-state-owner.ts
3571
- import path35 from "path";
3572
- import ts6 from "typescript";
4185
+ import path40 from "path";
4186
+ import ts9 from "typescript";
3573
4187
  function findStateHooks(node) {
3574
4188
  const hooks = [];
3575
4189
  const visit = (child) => {
3576
- if (ts6.isCallExpression(child) && ts6.isIdentifier(child.expression) && child.expression.text === "useState") {
3577
- if (ts6.isVariableDeclaration(child.parent)) {
4190
+ if (ts9.isCallExpression(child) && ts9.isIdentifier(child.expression) && child.expression.text === "useState") {
4191
+ if (ts9.isVariableDeclaration(child.parent)) {
3578
4192
  const { name } = child.parent;
3579
- if (ts6.isArrayBindingPattern(name) && name.elements.length >= 2) {
4193
+ if (ts9.isArrayBindingPattern(name) && name.elements.length >= 2) {
3580
4194
  const value = name.elements[0];
3581
4195
  const updater = name.elements[1];
3582
- if (value && updater && ts6.isBindingElement(value) && ts6.isBindingElement(updater)) {
4196
+ if (value && updater && ts9.isBindingElement(value) && ts9.isBindingElement(updater)) {
3583
4197
  const valueName = value.name;
3584
4198
  const updaterName = updater.name;
3585
- if (ts6.isIdentifier(valueName) && ts6.isIdentifier(updaterName)) {
4199
+ if (ts9.isIdentifier(valueName) && ts9.isIdentifier(updaterName)) {
3586
4200
  hooks.push({ value: valueName.text, updater: updaterName.text });
3587
4201
  }
3588
4202
  }
3589
4203
  }
3590
4204
  }
3591
4205
  }
3592
- ts6.forEachChild(child, visit);
4206
+ ts9.forEachChild(child, visit);
3593
4207
  };
3594
4208
  visit(node);
3595
4209
  return hooks;
3596
4210
  }
3597
4211
  function isHookUsage(node, hook) {
3598
- if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && node.expression.text === hook.updater) {
4212
+ if (ts9.isCallExpression(node) && ts9.isIdentifier(node.expression) && node.expression.text === hook.updater) {
3599
4213
  return "updater";
3600
4214
  }
3601
- if (ts6.isIdentifier(node) && node.text === hook.value) {
4215
+ if (ts9.isIdentifier(node) && node.text === hook.value) {
3602
4216
  const parent = node.parent;
3603
- if (ts6.isBindingElement(parent) || ts6.isPropertyAccessExpression(parent) || ts6.isShorthandPropertyAssignment(parent)) {
4217
+ if (ts9.isBindingElement(parent) || ts9.isPropertyAccessExpression(parent) || ts9.isShorthandPropertyAssignment(parent)) {
3604
4218
  return void 0;
3605
4219
  }
3606
4220
  return "value";
@@ -3610,16 +4224,16 @@ function isHookUsage(node, hook) {
3610
4224
  function topLevelJsxChildren(initial) {
3611
4225
  if (!initial) return void 0;
3612
4226
  let expression = initial;
3613
- while (ts6.isParenthesizedExpression(expression)) expression = expression.expression;
3614
- if (ts6.isJsxFragment(expression)) {
4227
+ while (ts9.isParenthesizedExpression(expression)) expression = expression.expression;
4228
+ if (ts9.isJsxFragment(expression)) {
3615
4229
  const children = expression.children.filter(
3616
- (c) => !ts6.isJsxText(c) && !ts6.isJsxSpreadAttribute(c)
4230
+ (c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
3617
4231
  );
3618
4232
  return { root: expression, children };
3619
4233
  }
3620
- if (ts6.isJsxElement(expression)) {
4234
+ if (ts9.isJsxElement(expression)) {
3621
4235
  const children = expression.children.filter(
3622
- (c) => !ts6.isJsxText(c) && !ts6.isJsxSpreadAttribute(c)
4236
+ (c) => !ts9.isJsxText(c) && !ts9.isJsxSpreadAttribute(c)
3623
4237
  );
3624
4238
  return { root: expression, children };
3625
4239
  }
@@ -3633,8 +4247,8 @@ function collectUsesIn(child, hook) {
3633
4247
  positions.push(node);
3634
4248
  count += 1;
3635
4249
  }
3636
- if (ts6.isFunctionDeclaration(node) || ts6.isClassDeclaration(node)) return;
3637
- ts6.forEachChild(node, visit);
4250
+ if (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node)) return;
4251
+ ts9.forEachChild(node, visit);
3638
4252
  };
3639
4253
  visit(child);
3640
4254
  return { positions, count };
@@ -3648,7 +4262,7 @@ var soleStateOwnerRule = {
3648
4262
  }
3649
4263
  },
3650
4264
  create(context) {
3651
- const filename = path35.resolve(context.filename);
4265
+ const filename = path40.resolve(context.filename);
3652
4266
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
3653
4267
  const text = context.sourceCode.text;
3654
4268
  const components = parseComponentInfo(text, filename);
@@ -3673,18 +4287,18 @@ var soleStateOwnerRule = {
3673
4287
  }
3674
4288
  };
3675
4289
  function analyzeSoleOwner(declaration, hook) {
3676
- const body = ts6.isFunctionDeclaration(declaration) ? declaration.body : void 0;
3677
- if (!body || !ts6.isBlock(body)) return void 0;
4290
+ const body = ts9.isFunctionDeclaration(declaration) ? declaration.body : void 0;
4291
+ if (!body || !ts9.isBlock(body)) return void 0;
3678
4292
  const returns = [];
3679
4293
  const visit = (node) => {
3680
- if (node !== body && (ts6.isFunctionLike(node) || ts6.isClassLike(node))) return;
3681
- if (ts6.isReturnStatement(node)) returns.push(node);
3682
- ts6.forEachChild(node, visit);
4294
+ if (node !== body && (ts9.isFunctionLike(node) || ts9.isClassLike(node))) return;
4295
+ if (ts9.isReturnStatement(node)) returns.push(node);
4296
+ ts9.forEachChild(node, visit);
3683
4297
  };
3684
4298
  visit(body);
3685
4299
  if (returns.length !== 1) return void 0;
3686
4300
  const single = returns[0];
3687
- if (!single?.expression || !ts6.isExpression(single.expression)) return void 0;
4301
+ if (!single?.expression || !ts9.isExpression(single.expression)) return void 0;
3688
4302
  const returnExpression = single.expression;
3689
4303
  const root = topLevelJsxChildren(returnExpression);
3690
4304
  if (!root || root.children.length === 0) return void 0;
@@ -3707,11 +4321,11 @@ function usesOutsideJsx(declaration, hook, children) {
3707
4321
  let outside = false;
3708
4322
  const visit = (node) => {
3709
4323
  if (outside) return;
3710
- if (node !== declaration && (ts6.isFunctionDeclaration(node) || ts6.isClassDeclaration(node))) return;
4324
+ if (node !== declaration && (ts9.isFunctionDeclaration(node) || ts9.isClassDeclaration(node))) return;
3711
4325
  if (isHookUsage(node, hook)) {
3712
4326
  let current = node;
3713
4327
  let isInJsxChild = false;
3714
- while (!ts6.isSourceFile(current)) {
4328
+ while (!ts9.isSourceFile(current)) {
3715
4329
  if (children.includes(current)) {
3716
4330
  isInJsxChild = true;
3717
4331
  break;
@@ -3720,14 +4334,14 @@ function usesOutsideJsx(declaration, hook, children) {
3720
4334
  }
3721
4335
  if (!isInJsxChild) outside = true;
3722
4336
  }
3723
- ts6.forEachChild(node, visit);
4337
+ ts9.forEachChild(node, visit);
3724
4338
  };
3725
4339
  visit(declaration);
3726
4340
  return outside;
3727
4341
  }
3728
4342
 
3729
4343
  // eslint/rules/locale-key-shape.ts
3730
- import path36 from "path";
4344
+ import path41 from "path";
3731
4345
  var MAX_KEY_LENGTH = 30;
3732
4346
  var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
3733
4347
  "Button",
@@ -3777,7 +4391,7 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
3777
4391
  var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
3778
4392
  var ENGLISH = /^[A-Za-z0-9_]*$/;
3779
4393
  function isLocalesFile2(filename) {
3780
- const segments = path36.resolve(filename).split(path36.sep);
4394
+ const segments = path41.resolve(filename).split(path41.sep);
3781
4395
  const srcIdx = segments.lastIndexOf("src");
3782
4396
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3783
4397
  }
@@ -3848,8 +4462,8 @@ var localeKeyShapeRule = {
3848
4462
  };
3849
4463
 
3850
4464
  // eslint/rules/shared-style-dedup.ts
3851
- import path37 from "path";
3852
- import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
4465
+ import path42 from "path";
4466
+ import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
3853
4467
  var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
3854
4468
  var comboCache;
3855
4469
  function combosFor(index) {
@@ -3863,7 +4477,7 @@ function combosFor(index) {
3863
4477
  }
3864
4478
  const combos = /* @__PURE__ */ new Map();
3865
4479
  for (const file of files) {
3866
- const text = readFileSync5(file, "utf8");
4480
+ const text = readFileSync6(file, "utf8");
3867
4481
  for (const match of text.matchAll(CLASS_NAME)) {
3868
4482
  const classes = (match.groups?.classes ?? "").split(/\s+/).filter(Boolean);
3869
4483
  if (classes.length < 2) continue;
@@ -3886,7 +4500,7 @@ var sharedStyleDedupRule = {
3886
4500
  },
3887
4501
  create(context) {
3888
4502
  const sourceRoot = sourceRootOf(context);
3889
- const file = path37.resolve(context.filename);
4503
+ const file = path42.resolve(context.filename);
3890
4504
  const segments = segmentsOf(file, sourceRoot);
3891
4505
  if (segments.length === 0) return {};
3892
4506
  const index = getProjectIndex(sourceRoot);
@@ -4090,7 +4704,7 @@ var zodSchemaValidationRule = {
4090
4704
  };
4091
4705
 
4092
4706
  // eslint/rules/schema-casing.ts
4093
- import path38 from "path";
4707
+ import path43 from "path";
4094
4708
  function isCamelCase(name) {
4095
4709
  return /^[a-z][a-zA-Z0-9]*$/.test(name);
4096
4710
  }
@@ -4118,9 +4732,9 @@ var schemaCasingRule = {
4118
4732
  }
4119
4733
  },
4120
4734
  create(context) {
4121
- const filename = path38.resolve(context.filename);
4735
+ const filename = path43.resolve(context.filename);
4122
4736
  const sourceRoot = sourceRootOf(context);
4123
- if (!filename.startsWith(sourceRoot + path38.sep)) return {};
4737
+ if (!filename.startsWith(sourceRoot + path43.sep)) return {};
4124
4738
  let zodLocalName;
4125
4739
  return {
4126
4740
  ImportDeclaration(node) {
@@ -4152,7 +4766,7 @@ var schemaCasingRule = {
4152
4766
  };
4153
4767
 
4154
4768
  // eslint/rules/component-casing.ts
4155
- import path39 from "path";
4769
+ import path44 from "path";
4156
4770
  function isPascalCase6(name) {
4157
4771
  return /^[A-Z][A-Za-z0-9]*$/.test(name);
4158
4772
  }
@@ -4165,10 +4779,10 @@ var componentCasingRule = {
4165
4779
  }
4166
4780
  },
4167
4781
  create(context) {
4168
- const filename = path39.resolve(context.filename);
4782
+ const filename = path44.resolve(context.filename);
4169
4783
  const sourceRoot = sourceRootOf(context);
4170
- if (!filename.startsWith(sourceRoot + path39.sep)) return {};
4171
- if (path39.extname(filename) !== ".tsx") return {};
4784
+ if (!filename.startsWith(sourceRoot + path44.sep)) return {};
4785
+ if (path44.extname(filename) !== ".tsx") return {};
4172
4786
  return {
4173
4787
  Program(node) {
4174
4788
  for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
@@ -4185,7 +4799,7 @@ var componentCasingRule = {
4185
4799
  };
4186
4800
 
4187
4801
  // eslint/rules/source-under-src.ts
4188
- import path40 from "path";
4802
+ import path45 from "path";
4189
4803
  var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
4190
4804
  ".agents",
4191
4805
  ".cache",
@@ -4226,14 +4840,14 @@ var sourceUnderSrcRule = {
4226
4840
  }
4227
4841
  },
4228
4842
  create(context) {
4229
- const filename = path40.resolve(context.filename);
4843
+ const filename = path45.resolve(context.filename);
4230
4844
  if (!MODULE_EXTENSION.test(filename)) return {};
4231
- const relative = path40.relative(context.cwd, filename).replace(/\\/g, "/");
4845
+ const relative = path45.relative(context.cwd, filename).replace(/\\/g, "/");
4232
4846
  if (relative === "src" || relative.startsWith("src/")) return {};
4233
4847
  const topLevel = relative.split("/")[0] ?? "";
4234
4848
  if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
4235
4849
  if (!relative.includes("/")) {
4236
- const basename = path40.basename(filename);
4850
+ const basename = path45.basename(filename);
4237
4851
  if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
4238
4852
  }
4239
4853
  return {
@@ -4250,8 +4864,8 @@ var sourceUnderSrcRule = {
4250
4864
 
4251
4865
  // eslint/rules/zirka-baseline.ts
4252
4866
  import fs5 from "fs";
4253
- import path41 from "path";
4254
- var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
4867
+ import path46 from "path";
4868
+ var ESLINT_CONFIG3 = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
4255
4869
  var PRETTIER_CONFIGS = [
4256
4870
  "prettier.config.mjs",
4257
4871
  "prettier.config.cjs",
@@ -4269,10 +4883,10 @@ var zirkaBaselineRule = {
4269
4883
  }
4270
4884
  },
4271
4885
  create(context) {
4272
- const filename = path41.resolve(context.filename);
4273
- const basename = path41.basename(filename);
4274
- if (!ESLINT_CONFIG.test(basename)) return {};
4275
- const projectRoot = path41.dirname(filename);
4886
+ const filename = path46.resolve(context.filename);
4887
+ const basename = path46.basename(filename);
4888
+ if (!ESLINT_CONFIG3.test(basename)) return {};
4889
+ const projectRoot = path46.dirname(filename);
4276
4890
  const report3 = (message) => {
4277
4891
  context.report({
4278
4892
  node: context.sourceCode.ast,
@@ -4287,7 +4901,7 @@ var zirkaBaselineRule = {
4287
4901
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
4288
4902
  );
4289
4903
  }
4290
- const tsconfigPath = path41.join(projectRoot, "tsconfig.json");
4904
+ const tsconfigPath = path46.join(projectRoot, "tsconfig.json");
4291
4905
  if (!fs5.existsSync(tsconfigPath)) {
4292
4906
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
4293
4907
  } else {
@@ -4305,13 +4919,13 @@ var zirkaBaselineRule = {
4305
4919
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
4306
4920
  }
4307
4921
  }
4308
- const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path41.join(projectRoot, name)));
4922
+ const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path46.join(projectRoot, name)));
4309
4923
  if (!prettierConfigFile) {
4310
4924
  report3(
4311
4925
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
4312
4926
  );
4313
4927
  } else {
4314
- const content = fs5.readFileSync(path41.join(projectRoot, prettierConfigFile), "utf8");
4928
+ const content = fs5.readFileSync(path46.join(projectRoot, prettierConfigFile), "utf8");
4315
4929
  if (!content.includes("zirka")) {
4316
4930
  report3(
4317
4931
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -4353,6 +4967,15 @@ function getTextContent(node) {
4353
4967
  function getLine(node) {
4354
4968
  return node.position?.start.line ?? 0;
4355
4969
  }
4970
+ function linkTarget(url) {
4971
+ return url.split("#")[0] ?? url;
4972
+ }
4973
+ function isDocLink(url) {
4974
+ return linkTarget(url).endsWith(".md");
4975
+ }
4976
+ function headingAnchor(text) {
4977
+ return text.trim().toLowerCase().replaceAll(/[^\p{L}\p{N}\s-]/gu, "").replaceAll(/\s+/g, "-");
4978
+ }
4356
4979
 
4357
4980
  // eslint/rules/documentation/doc-kind-suffix.ts
4358
4981
  var docKindSuffixRule = {
@@ -4381,7 +5004,7 @@ var docKindSuffixRule = {
4381
5004
  };
4382
5005
 
4383
5006
  // eslint/rules/documentation/title-matches-file-name.ts
4384
- import path42 from "path";
5007
+ import path47 from "path";
4385
5008
  function toExpectedFileName(title) {
4386
5009
  return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
4387
5010
  }
@@ -4401,7 +5024,7 @@ var titleMatchesFileNameRule = {
4401
5024
  if (!filename.endsWith(".md")) return;
4402
5025
  const title = getTextContent(node).trim();
4403
5026
  const expectedFileName = toExpectedFileName(title);
4404
- const actualFileName = path42.basename(filename);
5027
+ const actualFileName = path47.basename(filename);
4405
5028
  if (!title) {
4406
5029
  context.report({
4407
5030
  node,
@@ -4556,7 +5179,7 @@ var guideStepSingleSentenceRule = {
4556
5179
 
4557
5180
  // eslint/rules/documentation/guide-step-single-link.ts
4558
5181
  function countDocLinks(node) {
4559
- if (node.type === "link" && node.url.endsWith(".md")) return 1;
5182
+ if (node.type === "link" && isDocLink(node.url)) return 1;
4560
5183
  if ("children" in node) {
4561
5184
  return node.children.reduce((sum, child) => sum + countDocLinks(child), 0);
4562
5185
  }
@@ -4774,7 +5397,7 @@ var noCrossDocumentLinkRule = {
4774
5397
  const filename = getFilename(context);
4775
5398
  const kind = linkedKind(filename);
4776
5399
  if (!kind) return;
4777
- if (node.url.endsWith(".md")) {
5400
+ if (isDocLink(node.url)) {
4778
5401
  context.report({
4779
5402
  node,
4780
5403
  message: `${kind} links another document: ${node.url}`
@@ -4842,22 +5465,46 @@ var referenceBlockHeadingsRule = {
4842
5465
  }
4843
5466
  };
4844
5467
 
5468
+ // eslint/rules/documentation/reference-max-heading-depth.ts
5469
+ var referenceMaxHeadingDepthRule = {
5470
+ meta: {
5471
+ type: "problem",
5472
+ docs: {
5473
+ description: "Reference max heading depth rule.",
5474
+ recommended: true
5475
+ }
5476
+ },
5477
+ create(context) {
5478
+ return {
5479
+ heading(node) {
5480
+ const filename = getFilename(context);
5481
+ if (!filename.endsWith("-reference.md")) return;
5482
+ if (node.depth <= 2) return;
5483
+ context.report({
5484
+ node,
5485
+ message: "reference heading is deeper than level 2; flatten it into the section it would nest under"
5486
+ });
5487
+ }
5488
+ };
5489
+ }
5490
+ };
5491
+
4845
5492
  // eslint/rules/documentation/support-document-placement.ts
4846
5493
  import { existsSync as existsSync2 } from "fs";
4847
- import path43 from "path";
5494
+ import path48 from "path";
4848
5495
  function checkPlacement(filename, kind) {
4849
- const parentFolder = path43.basename(path43.dirname(filename));
5496
+ const parentFolder = path48.basename(path48.dirname(filename));
4850
5497
  const expectedParent = `${kind}s`;
4851
5498
  if (parentFolder !== expectedParent) {
4852
5499
  return `${kind} lives in "${parentFolder}/" instead of "${expectedParent}/"`;
4853
5500
  }
4854
- const guideFolderPath = path43.dirname(path43.dirname(filename));
4855
- const guideFolder = path43.basename(guideFolderPath);
5501
+ const guideFolderPath = path48.dirname(path48.dirname(filename));
5502
+ const guideFolder = path48.basename(guideFolderPath);
4856
5503
  if (!guideFolder.endsWith("-guide")) {
4857
5504
  return `${kind} owner folder "${guideFolder}/" does not use the "*-guide/" suffix`;
4858
5505
  }
4859
5506
  const entryPoint = `${guideFolder}.md`;
4860
- if (!existsSync2(path43.join(guideFolderPath, entryPoint))) {
5507
+ if (!existsSync2(path48.join(guideFolderPath, entryPoint))) {
4861
5508
  return `${kind} owner folder "${guideFolder}/" has no "${entryPoint}" entry point`;
4862
5509
  }
4863
5510
  return void 0;
@@ -4916,11 +5563,11 @@ var noTemplatePromptRule = {
4916
5563
  };
4917
5564
 
4918
5565
  // eslint/rules/documentation/guide-folder-entry-point.ts
4919
- import path45 from "path";
5566
+ import path50 from "path";
4920
5567
 
4921
5568
  // eslint/rules/documentation/project-index.ts
4922
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
4923
- import path44 from "path";
5569
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
5570
+ import path49 from "path";
4924
5571
  var KIND_BY_SUFFIX = [
4925
5572
  ["-rule.md", "rule"],
4926
5573
  ["-guide.md", "guide"],
@@ -4929,7 +5576,7 @@ var KIND_BY_SUFFIX = [
4929
5576
  ];
4930
5577
  function listMarkdownFiles(dir) {
4931
5578
  return readdirSync3(dir).flatMap((entry) => {
4932
- const entryPath = path44.join(dir, entry);
5579
+ const entryPath = path49.join(dir, entry);
4933
5580
  if (statSync4(entryPath).isDirectory()) {
4934
5581
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
4935
5582
  }
@@ -4937,7 +5584,7 @@ function listMarkdownFiles(dir) {
4937
5584
  });
4938
5585
  }
4939
5586
  function extractTitle(filePath) {
4940
- const content = readFileSync6(filePath, "utf8");
5587
+ const content = readFileSync7(filePath, "utf8");
4941
5588
  const match = /^# (?<title>.+)$/m.exec(content);
4942
5589
  return match?.groups?.title?.trim() ?? "";
4943
5590
  }
@@ -4947,11 +5594,11 @@ function getProjectDocs(docsRoot) {
4947
5594
  if (cached) return cached;
4948
5595
  const files = listMarkdownFiles(docsRoot);
4949
5596
  const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
4950
- const fileName = path44.basename(filePath);
5597
+ const fileName = path49.basename(filePath);
4951
5598
  const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
4952
5599
  return {
4953
5600
  filePath,
4954
- doc: path44.relative(docsRoot, filePath).split(path44.sep).join("/"),
5601
+ doc: path49.relative(docsRoot, filePath).split(path49.sep).join("/"),
4955
5602
  fileName,
4956
5603
  kind,
4957
5604
  title: extractTitle(filePath)
@@ -4961,12 +5608,12 @@ function getProjectDocs(docsRoot) {
4961
5608
  return docs;
4962
5609
  }
4963
5610
  function findDocsRoot(filePath) {
4964
- let dir = path44.dirname(filePath);
5611
+ let dir = path49.dirname(filePath);
4965
5612
  for (; ; ) {
4966
- if (path44.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
5613
+ if (path49.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
4967
5614
  return dir;
4968
5615
  }
4969
- const parent = path44.dirname(dir);
5616
+ const parent = path49.dirname(dir);
4970
5617
  if (parent === dir) return void 0;
4971
5618
  dir = parent;
4972
5619
  }
@@ -4990,13 +5637,13 @@ var guideFolderEntryPointRule = {
4990
5637
  if (!docsRoot) return;
4991
5638
  const docs = getProjectDocs(docsRoot);
4992
5639
  const guideFolders = new Set(
4993
- docs.filter((doc) => ["rules", "references"].includes(path45.basename(path45.dirname(doc.filePath)))).map((doc) => path45.dirname(path45.dirname(doc.filePath))).filter((folder) => path45.resolve(folder) !== path45.resolve(docsRoot))
5640
+ docs.filter((doc) => ["rules", "references"].includes(path50.basename(path50.dirname(doc.filePath)))).map((doc) => path50.dirname(path50.dirname(doc.filePath))).filter((folder) => path50.resolve(folder) !== path50.resolve(docsRoot))
4994
5641
  );
4995
- const currentDir = path45.dirname(filename);
5642
+ const currentDir = path50.dirname(filename);
4996
5643
  if (guideFolders.has(currentDir)) {
4997
- const expectedEntryPoint = `${path45.basename(currentDir)}.md`;
5644
+ const expectedEntryPoint = `${path50.basename(currentDir)}.md`;
4998
5645
  const hasEntryPoint = docs.some(
4999
- (doc) => doc.kind === "guide" && path45.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
5646
+ (doc) => doc.kind === "guide" && path50.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
5000
5647
  );
5001
5648
  if (!hasEntryPoint) {
5002
5649
  context.report({
@@ -5140,6 +5787,8 @@ var policySubjectHeadingsRule = {
5140
5787
  };
5141
5788
 
5142
5789
  // eslint/rules/documentation/guide-link-anchors.ts
5790
+ import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
5791
+ import path51 from "path";
5143
5792
  function visitSteps2(node, check) {
5144
5793
  if (node.type === "list" && node.ordered) {
5145
5794
  for (const child of node.children) check(child);
@@ -5149,17 +5798,52 @@ function visitSteps2(node, check) {
5149
5798
  }
5150
5799
  }
5151
5800
  function collectGuideLinks(node) {
5152
- if (node.type === "link" && node.url.endsWith("-guide.md")) return [node];
5801
+ if (node.type === "link" && linkTarget(node.url).endsWith("-guide.md")) return [node];
5153
5802
  if ("children" in node) {
5154
5803
  return node.children.flatMap(collectGuideLinks);
5155
5804
  }
5156
5805
  return [];
5157
5806
  }
5807
+ function collectLinks(node, out) {
5808
+ if (node.type === "link") out.push(node);
5809
+ if ("children" in node) {
5810
+ for (const child of node.children) collectLinks(child, out);
5811
+ }
5812
+ }
5813
+ var anchorsByFile = /* @__PURE__ */ new Map();
5814
+ function documentAnchors(filePath) {
5815
+ const cached = anchorsByFile.get(filePath);
5816
+ if (cached) return cached;
5817
+ const anchors = /* @__PURE__ */ new Set();
5818
+ anchorsByFile.set(filePath, anchors);
5819
+ let content;
5820
+ try {
5821
+ content = readFileSync8(filePath, "utf8");
5822
+ } catch {
5823
+ return anchors;
5824
+ }
5825
+ const seen = /* @__PURE__ */ new Map();
5826
+ let fenced = false;
5827
+ for (const line of content.split("\n")) {
5828
+ if (/^\s*(?:```|~~~)/.test(line)) {
5829
+ fenced = !fenced;
5830
+ continue;
5831
+ }
5832
+ if (fenced) continue;
5833
+ const heading = /^#{1,6}\s+(?<text>.+)$/.exec(line)?.groups?.text;
5834
+ if (!heading) continue;
5835
+ const base = headingAnchor(heading);
5836
+ const count = seen.get(base) ?? 0;
5837
+ seen.set(base, count + 1);
5838
+ anchors.add(count === 0 ? base : `${base}-${String(count)}`);
5839
+ }
5840
+ return anchors;
5841
+ }
5158
5842
  var guideLinkAnchorsRule = {
5159
5843
  meta: {
5160
5844
  type: "problem",
5161
5845
  docs: {
5162
- description: "A step that links another Guide must link directly to a How To section.",
5846
+ description: "A step that links another Guide must link directly to a How To section, and a link that carries an anchor must point at a heading the linked document has.",
5163
5847
  recommended: true
5164
5848
  }
5165
5849
  },
@@ -5178,6 +5862,19 @@ var guideLinkAnchorsRule = {
5178
5862
  }
5179
5863
  }
5180
5864
  });
5865
+ const links = [];
5866
+ collectLinks(node, links);
5867
+ for (const link of links) {
5868
+ const [target = "", fragment = ""] = link.url.split("#");
5869
+ if (!fragment || !isDocLink(link.url)) continue;
5870
+ const targetPath = path51.resolve(path51.dirname(filename), target);
5871
+ if (!existsSync3(targetPath)) continue;
5872
+ if (documentAnchors(targetPath).has(fragment.toLowerCase())) continue;
5873
+ context.report({
5874
+ node: link,
5875
+ message: `link ${link.url} points at a heading the document does not have`
5876
+ });
5877
+ }
5181
5878
  }
5182
5879
  };
5183
5880
  }
@@ -5220,41 +5917,82 @@ var noNestedHowToRule = {
5220
5917
  };
5221
5918
 
5222
5919
  // eslint/rules/documentation/glossary-term-linking.ts
5223
- import { readFileSync as readFileSync7 } from "fs";
5224
- import path46 from "path";
5920
+ import { readFileSync as readFileSync9 } from "fs";
5921
+ import path52 from "path";
5922
+ function isGlossary(filePath) {
5923
+ return path52.basename(filePath).includes("glossary");
5924
+ }
5925
+ function termNames(cell) {
5926
+ const abbreviation = /\((?<abbreviation>[^()]+)\)/.exec(cell)?.groups?.abbreviation?.trim();
5927
+ return abbreviation ? [cell, abbreviation] : [cell];
5928
+ }
5929
+ function extractTableTerms(content) {
5930
+ const terms = [];
5931
+ const rowPattern = /^\|(?<cell>[^|]*)\|/gm;
5932
+ for (const match of content.matchAll(rowPattern)) {
5933
+ const cell = match.groups?.cell?.trim() ?? "";
5934
+ if (!cell || /^:?-+:?$/.test(cell) || cell.toLowerCase() === "term") continue;
5935
+ terms.push({ term: cell, names: termNames(cell) });
5936
+ }
5937
+ return terms;
5938
+ }
5225
5939
  function extractGlossaryTerms(filePath) {
5226
- const content = readFileSync7(filePath, "utf8");
5940
+ if (!isGlossary(filePath)) return [];
5941
+ const content = readFileSync9(filePath, "utf8");
5942
+ const tableTerms = extractTableTerms(content);
5943
+ if (tableTerms.length > 0) return tableTerms;
5227
5944
  const terms = [];
5228
5945
  const headingPattern = /^## (?<term>.+)$/gm;
5229
- let match;
5230
- while ((match = headingPattern.exec(content)) !== null) {
5946
+ for (const match of content.matchAll(headingPattern)) {
5231
5947
  const term = match.groups?.term?.trim();
5232
- if (term) terms.push(term);
5948
+ if (term) terms.push({ term, names: termNames(term) });
5233
5949
  }
5234
5950
  return terms;
5235
5951
  }
5236
- function collectSteps(node) {
5237
- const texts = [];
5238
- const firstStepLinks = [];
5239
- if (node.type === "list" && node.ordered) {
5240
- for (const item of node.children) {
5241
- texts.push(getTextContent(item));
5242
- if (firstStepLinks.length === 0) {
5243
- collectDocLinks(item, firstStepLinks);
5244
- }
5952
+ function extractBlockHeadings(filePath) {
5953
+ const headings = [];
5954
+ let fenced = false;
5955
+ for (const line of readFileSync9(filePath, "utf8").split("\n")) {
5956
+ if (/^\s*(?:```|~~~)/.test(line)) {
5957
+ fenced = !fenced;
5958
+ continue;
5245
5959
  }
5960
+ if (fenced) continue;
5961
+ const heading = /^##\s+(?<text>.+)$/.exec(line)?.groups?.text;
5962
+ if (heading) headings.push(heading.trim());
5246
5963
  }
5247
- if ("children" in node) {
5248
- for (const child of node.children) {
5249
- const nested = collectSteps(child);
5250
- texts.push(...nested.texts);
5251
- if (firstStepLinks.length === 0) firstStepLinks.push(...nested.firstStepLinks);
5252
- }
5964
+ return headings;
5965
+ }
5966
+ function normalize(text) {
5967
+ return text.replaceAll("`", "").toLowerCase();
5968
+ }
5969
+ function collectSections(node) {
5970
+ const sections = [];
5971
+ const children = node.children;
5972
+ for (const [index, child] of children.entries()) {
5973
+ if (child.type !== "heading" || child.depth !== 2) continue;
5974
+ const title = getTextContent(child).trim();
5975
+ if (!/^How To \S/.test(title)) continue;
5976
+ const end = children.findIndex(
5977
+ (sibling, siblingIndex) => siblingIndex > index && sibling.type === "heading" && sibling.depth === 2
5978
+ );
5979
+ const section = { heading: child, title, stepTexts: [], firstStepLinks: [] };
5980
+ collectSteps(children.slice(index + 1, end === -1 ? children.length : end), section);
5981
+ sections.push(section);
5982
+ }
5983
+ return sections;
5984
+ }
5985
+ function collectSteps(content, section) {
5986
+ const lists = content.filter((node) => node.type === "list");
5987
+ const paragraphs = content.filter((node) => node.type === "paragraph");
5988
+ const steps = lists.length > 0 ? lists.flatMap((list) => list.children) : paragraphs.slice(content[0]?.type === "paragraph" ? 1 : 0);
5989
+ for (const step of steps) {
5990
+ section.stepTexts.push(getTextContent(step));
5991
+ if (section.stepTexts.length === 1) collectDocLinks(step, section.firstStepLinks);
5253
5992
  }
5254
- return { texts, firstStepLinks };
5255
5993
  }
5256
5994
  function collectDocLinks(node, out) {
5257
- if (node.type === "link" && node.url.endsWith(".md")) out.push(node.url);
5995
+ if (node.type === "link" && isDocLink(node.url)) out.push(node.url);
5258
5996
  if ("children" in node) {
5259
5997
  for (const child of node.children) collectDocLinks(child, out);
5260
5998
  }
@@ -5263,7 +6001,7 @@ var glossaryTermLinkingRule = {
5263
6001
  meta: {
5264
6002
  type: "problem",
5265
6003
  docs: {
5266
- description: "A Guide whose steps use glossary terms must link that Reference from its first step.",
6004
+ description: "A How To section whose steps use glossary terms must link that Reference from the first step, and every block of a shared glossary must be read by one of them.",
5267
6005
  recommended: true
5268
6006
  }
5269
6007
  },
@@ -5275,25 +6013,55 @@ var glossaryTermLinkingRule = {
5275
6013
  const docsRoot = findDocsRoot(filename);
5276
6014
  if (!docsRoot) return;
5277
6015
  const docs = getProjectDocs(docsRoot);
5278
- const guideDir = path46.dirname(filename);
6016
+ const guideDir = path52.dirname(filename);
6017
+ const referencesDir = path52.join(guideDir, "references");
5279
6018
  const guideReferences = docs.filter(
5280
- (doc) => doc.kind === "reference" && path46.dirname(doc.filePath) === guideDir
6019
+ (doc) => doc.kind === "reference" && path52.dirname(doc.filePath) === referencesDir
5281
6020
  );
5282
- if (guideReferences.length === 0) return;
6021
+ const glossaryReferences = guideReferences.filter((doc) => isGlossary(doc.filePath));
6022
+ if (glossaryReferences.length === 0) return;
5283
6023
  const glossaryTerms = [];
5284
- for (const ref of guideReferences) {
6024
+ for (const ref of glossaryReferences) {
5285
6025
  glossaryTerms.push(...extractGlossaryTerms(ref.filePath));
5286
6026
  }
5287
6027
  if (glossaryTerms.length === 0) return;
5288
- const { texts: stepTexts, firstStepLinks } = collectSteps(node);
5289
- const usedTerms = glossaryTerms.filter((term) => stepTexts.some((text) => text.includes(term)));
5290
- if (usedTerms.length === 0) return;
5291
- const hasRefLink = firstStepLinks.some((link) => guideReferences.some((ref) => link.includes(ref.fileName)));
5292
- if (!hasRefLink) {
5293
- context.report({
5294
- node,
5295
- message: `guide uses glossary terms (${usedTerms.join(", ")}) but first step does not link the reference`
5296
- });
6028
+ const sections = collectSections(node);
6029
+ for (const ref of glossaryReferences) {
6030
+ for (const heading of extractBlockHeadings(ref.filePath)) {
6031
+ const anchor = headingAnchor(heading);
6032
+ const anchored = sections.some(
6033
+ (section) => section.firstStepLinks.some((link) => {
6034
+ const fragment = link.split("#")[1] ?? "";
6035
+ if (fragment.toLowerCase() !== anchor) return false;
6036
+ return path52.resolve(guideDir, linkTarget(link)) === path52.resolve(ref.filePath);
6037
+ })
6038
+ );
6039
+ if (!anchored) {
6040
+ context.report({
6041
+ node,
6042
+ message: `shared glossary block "${heading}" is not read by any How To section's first step`
6043
+ });
6044
+ }
6045
+ }
6046
+ }
6047
+ for (const section of sections) {
6048
+ const normalizedStepTexts = section.stepTexts.map(normalize);
6049
+ const usedTerms = glossaryTerms.filter(
6050
+ (entry) => entry.names.some((name) => {
6051
+ const needle = normalize(name);
6052
+ return normalizedStepTexts.some((text) => text.includes(needle));
6053
+ })
6054
+ ).map((entry) => entry.term);
6055
+ if (usedTerms.length === 0) continue;
6056
+ const hasGlossaryLink = section.firstStepLinks.some(
6057
+ (link) => glossaryReferences.some((ref) => link.includes(ref.fileName))
6058
+ );
6059
+ if (!hasGlossaryLink) {
6060
+ context.report({
6061
+ node: section.heading,
6062
+ message: `guide section "${section.title}" uses glossary terms (${usedTerms.join(", ")}) but its first step does not link the glossary reference`
6063
+ });
6064
+ }
5297
6065
  }
5298
6066
  }
5299
6067
  };
@@ -5301,8 +6069,8 @@ var glossaryTermLinkingRule = {
5301
6069
  };
5302
6070
 
5303
6071
  // eslint/rules/documentation/guide-mentions-documents.ts
5304
- import { existsSync as existsSync3 } from "fs";
5305
- import path47 from "path";
6072
+ import { existsSync as existsSync4 } from "fs";
6073
+ import path53 from "path";
5306
6074
  function visitSteps3(node, check) {
5307
6075
  if (node.type === "list" && node.ordered) {
5308
6076
  for (const child of node.children) check(child);
@@ -5312,14 +6080,11 @@ function visitSteps3(node, check) {
5312
6080
  }
5313
6081
  }
5314
6082
  function collectMarkdownLinks(node, out) {
5315
- if (node.type === "link" && node.url.endsWith(".md")) out.push(node);
6083
+ if (node.type === "link" && isDocLink(node.url)) out.push(node);
5316
6084
  if ("children" in node) {
5317
6085
  for (const child of node.children) collectMarkdownLinks(child, out);
5318
6086
  }
5319
6087
  }
5320
- function linkTarget(url) {
5321
- return url.split("#")[0] ?? url;
5322
- }
5323
6088
  var guideMentionsDocumentsRule = {
5324
6089
  meta: {
5325
6090
  type: "problem",
@@ -5335,12 +6100,12 @@ var guideMentionsDocumentsRule = {
5335
6100
  if (!filename.endsWith("-guide.md")) return;
5336
6101
  const docsRoot = findDocsRoot(filename);
5337
6102
  if (!docsRoot) return;
5338
- const guideDir = path47.dirname(filename);
5339
- if (path47.basename(filename, ".md") !== path47.basename(guideDir)) return;
6103
+ const guideDir = path53.dirname(filename);
6104
+ if (path53.basename(filename, ".md") !== path53.basename(guideDir)) return;
5340
6105
  const docs = getProjectDocs(docsRoot);
5341
6106
  const owned = docs.filter((doc) => {
5342
- const parent = path47.dirname(doc.filePath);
5343
- return parent === path47.join(guideDir, "rules") || parent === path47.join(guideDir, "references");
6107
+ const parent = path53.dirname(doc.filePath);
6108
+ return parent === path53.join(guideDir, "rules") || parent === path53.join(guideDir, "references");
5344
6109
  });
5345
6110
  const allLinks = [];
5346
6111
  collectMarkdownLinks(node, allLinks);
@@ -5348,7 +6113,7 @@ var guideMentionsDocumentsRule = {
5348
6113
  visitSteps3(node, (item) => {
5349
6114
  collectMarkdownLinks(item, stepLinks);
5350
6115
  });
5351
- const mentionsOf = (links, fileName) => links.some((link) => link.url.split("/").pop() === fileName);
6116
+ const mentionsOf = (links, fileName) => links.some((link) => linkTarget(link.url).split("/").pop() === fileName);
5352
6117
  for (const doc of owned) {
5353
6118
  if (doc.kind === "rule") {
5354
6119
  if (!mentionsOf(stepLinks, doc.fileName)) {
@@ -5367,8 +6132,8 @@ var guideMentionsDocumentsRule = {
5367
6132
  for (const link of allLinks) {
5368
6133
  const target = linkTarget(link.url);
5369
6134
  if (!target.endsWith(".md")) continue;
5370
- const resolved = path47.normalize(path47.join(guideDir, target));
5371
- if (!existsSync3(resolved)) {
6135
+ const resolved = path53.normalize(path53.join(guideDir, target));
6136
+ if (!existsSync4(resolved)) {
5372
6137
  context.report({
5373
6138
  node: link,
5374
6139
  message: `Guide links a document that does not exist: ${link.url}`
@@ -5445,6 +6210,7 @@ var documentationRules = {
5445
6210
  "no-cross-document-link": noCrossDocumentLinkRule,
5446
6211
  "reference-no-rfc-vocabulary": referenceNoRfcVocabularyRule,
5447
6212
  "reference-block-headings": referenceBlockHeadingsRule,
6213
+ "reference-max-heading-depth": referenceMaxHeadingDepthRule,
5448
6214
  "support-document-placement": supportDocumentPlacementRule,
5449
6215
  "no-template-prompt": noTemplatePromptRule,
5450
6216
  "guide-folder-entry-point": guideFolderEntryPointRule,
@@ -5917,11 +6683,11 @@ var themeVariableNamespaceRule = {
5917
6683
 
5918
6684
  // eslint/rules/tailwind/css-entry-point.ts
5919
6685
  import { statSync as statSync6 } from "fs";
5920
- import path50 from "path";
6686
+ import path56 from "path";
5921
6687
 
5922
6688
  // eslint/rules/tailwind/source-files.ts
5923
- import { readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
5924
- import path48 from "path";
6689
+ import { readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
6690
+ import path54 from "path";
5925
6691
  var CSS_EXTENSIONS = [".css"];
5926
6692
  var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
5927
6693
  var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
@@ -5934,7 +6700,7 @@ function findFiles(dir, extensions) {
5934
6700
  }
5935
6701
  return entries.flatMap((entry) => {
5936
6702
  if (entry.startsWith(".") || entry === "node_modules") return [];
5937
- const entryPath = path48.join(dir, entry);
6703
+ const entryPath = path54.join(dir, entry);
5938
6704
  let stats;
5939
6705
  try {
5940
6706
  stats = statSync5(entryPath);
@@ -5942,7 +6708,7 @@ function findFiles(dir, extensions) {
5942
6708
  return [];
5943
6709
  }
5944
6710
  if (stats.isDirectory()) return findFiles(entryPath, extensions);
5945
- return extensions.includes(path48.extname(entry)) ? [entryPath] : [];
6711
+ return extensions.includes(path54.extname(entry)) ? [entryPath] : [];
5946
6712
  });
5947
6713
  }
5948
6714
  function cachedTextReader() {
@@ -5951,7 +6717,7 @@ function cachedTextReader() {
5951
6717
  let text = texts.get(file);
5952
6718
  if (text === void 0) {
5953
6719
  try {
5954
- text = readFileSync8(file, "utf8");
6720
+ text = readFileSync10(file, "utf8");
5955
6721
  } catch {
5956
6722
  text = "";
5957
6723
  }
@@ -5965,7 +6731,7 @@ function escapeRegExp(text) {
5965
6731
  }
5966
6732
 
5967
6733
  // eslint/rules/tailwind/stylesheet-graph.ts
5968
- import path49 from "path";
6734
+ import path55 from "path";
5969
6735
  function registersTailwind(text) {
5970
6736
  return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
5971
6737
  }
@@ -5979,25 +6745,25 @@ function moduleImports(text, fileName) {
5979
6745
  return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
5980
6746
  }
5981
6747
  function resolveSpecifier2(fromFile, spec, sourceRoot) {
5982
- if (spec.startsWith("/")) return path49.resolve(spec);
5983
- if (spec.startsWith("./") || spec.startsWith("../")) return path49.resolve(path49.dirname(fromFile), spec);
5984
- if (spec.startsWith("@/")) return path49.resolve(sourceRoot, spec.slice(2));
6748
+ if (spec.startsWith("/")) return path55.resolve(spec);
6749
+ if (spec.startsWith("./") || spec.startsWith("../")) return path55.resolve(path55.dirname(fromFile), spec);
6750
+ if (spec.startsWith("@/")) return path55.resolve(sourceRoot, spec.slice(2));
5985
6751
  return void 0;
5986
6752
  }
5987
6753
  function buildStylesheetGraph(options) {
5988
6754
  const { cssFiles, sourceRoot, textOf } = options;
5989
- const cssSet = new Set(cssFiles.map((file) => path49.normalize(file)));
6755
+ const cssSet = new Set(cssFiles.map((file) => path55.normalize(file)));
5990
6756
  const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
5991
6757
  const reachable = /* @__PURE__ */ new Set();
5992
6758
  const queue = [...globals];
5993
- for (const global of globals) reachable.add(path49.normalize(global));
6759
+ for (const global of globals) reachable.add(path55.normalize(global));
5994
6760
  while (queue.length > 0) {
5995
6761
  const from = queue.shift();
5996
6762
  if (!from) continue;
5997
6763
  for (const spec of importedSpecifiers(textOf(from))) {
5998
6764
  const target = resolveSpecifier2(from, spec, sourceRoot);
5999
6765
  if (!target) continue;
6000
- const normalized = path49.normalize(target);
6766
+ const normalized = path55.normalize(target);
6001
6767
  if (cssSet.has(normalized) && !reachable.has(normalized)) {
6002
6768
  reachable.add(normalized);
6003
6769
  queue.push(normalized);
@@ -6009,7 +6775,7 @@ function buildStylesheetGraph(options) {
6009
6775
  for (const spec of importedSpecifiers(textOf(global))) {
6010
6776
  const target = resolveSpecifier2(global, spec, sourceRoot);
6011
6777
  if (!target) continue;
6012
- const normalized = path49.normalize(target);
6778
+ const normalized = path55.normalize(target);
6013
6779
  if (cssSet.has(normalized)) directChildren.add(normalized);
6014
6780
  }
6015
6781
  }
@@ -6042,7 +6808,7 @@ var cssEntryPointRule = {
6042
6808
  return {
6043
6809
  "StyleSheet:exit"(node) {
6044
6810
  if (globals.length === 0) return;
6045
- const current = path50.normalize(path50.resolve(context.filename));
6811
+ const current = path56.normalize(path56.resolve(context.filename));
6046
6812
  if (globals.includes(current)) {
6047
6813
  if (globals.length > 1) {
6048
6814
  context.report({
@@ -6051,7 +6817,7 @@ var cssEntryPointRule = {
6051
6817
  });
6052
6818
  return;
6053
6819
  }
6054
- const basename = path50.basename(current);
6820
+ const basename = path56.basename(current);
6055
6821
  const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
6056
6822
  if (importCount !== 1) {
6057
6823
  context.report({
@@ -6379,8 +7145,8 @@ var nextjsStackRule = {
6379
7145
  };
6380
7146
 
6381
7147
  // eslint/rules/package-json/vitest-coverage.ts
6382
- import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
6383
- import path51 from "path";
7148
+ import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
7149
+ import path57 from "path";
6384
7150
  function memberName4(member) {
6385
7151
  return member.name.type === "String" ? member.name.value : member.name.name;
6386
7152
  }
@@ -6449,7 +7215,7 @@ var vitestCoverageRule = {
6449
7215
  message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
6450
7216
  });
6451
7217
  }
6452
- const configName = VITEST_CONFIG_NAMES.find((name) => existsSync4(path51.join(context.cwd, name)));
7218
+ const configName = VITEST_CONFIG_NAMES.find((name) => existsSync5(path57.join(context.cwd, name)));
6453
7219
  if (!configName) {
6454
7220
  context.report({
6455
7221
  node,
@@ -6457,7 +7223,7 @@ var vitestCoverageRule = {
6457
7223
  });
6458
7224
  return;
6459
7225
  }
6460
- const content = readFileSync9(path51.join(context.cwd, configName), "utf8");
7226
+ const content = readFileSync11(path57.join(context.cwd, configName), "utf8");
6461
7227
  for (const metric of THRESHOLD_METRICS) {
6462
7228
  if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
6463
7229
  context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
@@ -6503,8 +7269,8 @@ var nextjsPackageJsonRules = {
6503
7269
  };
6504
7270
 
6505
7271
  // eslint/rules/husky/husky-hook.ts
6506
- import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
6507
- import path52 from "path";
7272
+ import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
7273
+ import path58 from "path";
6508
7274
  var VITEST_CONFIG_NAMES2 = [
6509
7275
  "vitest.config.ts",
6510
7276
  "vitest.config.mts",
@@ -6530,15 +7296,15 @@ var huskyHookRule = {
6530
7296
  const root = node.body;
6531
7297
  if (root.type !== "Object") return;
6532
7298
  if (!context.filename.endsWith("package.json")) return;
6533
- const hookPath = path52.join(context.cwd, ".husky", "pre-commit");
6534
- if (!existsSync5(hookPath)) {
7299
+ const hookPath = path58.join(context.cwd, ".husky", "pre-commit");
7300
+ if (!existsSync6(hookPath)) {
6535
7301
  context.report({
6536
7302
  node,
6537
7303
  message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
6538
7304
  });
6539
7305
  return;
6540
7306
  }
6541
- const content = readFileSync10(hookPath, "utf8");
7307
+ const content = readFileSync12(hookPath, "utf8");
6542
7308
  const scripts = root.members.find((member) => memberName5(member) === "scripts");
6543
7309
  const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
6544
7310
  const requireNamedScript = (name) => {
@@ -6554,7 +7320,7 @@ var huskyHookRule = {
6554
7320
  }
6555
7321
  requireNamedScript("typecheck");
6556
7322
  requireNamedScript("test:unit:coverage");
6557
- const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync5(path52.join(context.cwd, name)));
7323
+ const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync6(path58.join(context.cwd, name)));
6558
7324
  if (vitestConfigName !== void 0) {
6559
7325
  const coverageIndex = content.indexOf("npm run test:unit:coverage");
6560
7326
  const localAddIndex = content.indexOf(`git add ${vitestConfigName}`);
@@ -6568,8 +7334,8 @@ var huskyHookRule = {
6568
7334
  if (!content.includes("libyear --limit-major-individual=1")) {
6569
7335
  context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
6570
7336
  }
6571
- const suppressionsPath = path52.join(context.cwd, "eslint-suppressions.json");
6572
- if (existsSync5(suppressionsPath)) {
7337
+ const suppressionsPath = path58.join(context.cwd, "eslint-suppressions.json");
7338
+ if (existsSync6(suppressionsPath)) {
6573
7339
  requireNamedScript("lint:prune");
6574
7340
  const pruneIndex = content.indexOf("npm run lint:prune");
6575
7341
  const localAddIndex = content.indexOf("git add eslint-suppressions.json");
@@ -6599,73 +7365,33 @@ var huskyRules = {
6599
7365
  "husky-hook": huskyHookRule
6600
7366
  };
6601
7367
 
6602
- // eslint/rules/vulyk/vulyk-dependency.ts
6603
- function memberName6(member) {
6604
- return member.name.type === "String" ? member.name.value : member.name.name;
6605
- }
6606
- function dependency(root, sectionName) {
6607
- if (root.type !== "Object") return void 0;
6608
- const section = root.members.find((member) => memberName6(member) === sectionName);
6609
- if (section?.value.type !== "Object") return void 0;
6610
- return section.value.members.find((member) => memberName6(member) === "vulyk");
6611
- }
6612
- var vulykDependencyRule = {
6613
- meta: {
6614
- schema: [],
6615
- type: "problem",
6616
- docs: {
6617
- description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
6618
- }
6619
- },
6620
- create(context) {
6621
- return {
6622
- Document(node) {
6623
- const runtimeDependency = dependency(node.body, "dependencies");
6624
- const developmentDependency = dependency(node.body, "devDependencies");
6625
- if (runtimeDependency) {
6626
- context.report({
6627
- node: runtimeDependency,
6628
- message: "vulyk must be listed in devDependencies, not dependencies."
6629
- });
6630
- }
6631
- if (!developmentDependency && !runtimeDependency) {
6632
- context.report({
6633
- node,
6634
- message: "vulyk must be listed in package.json as a devDependency."
6635
- });
6636
- }
6637
- }
6638
- };
6639
- }
6640
- };
6641
-
6642
- // eslint/rules/vulyk/vulyk-docs.ts
6643
- import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
6644
- import path53 from "path";
7368
+ // eslint/rules/vulyk/tracked-docs.ts
7369
+ import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
7370
+ import path59 from "path";
6645
7371
  var PASIKA_REPO = "Bredansky/pasika";
6646
- var BASE_REQUIRED_DOCS = [
7372
+ var BASE_REQUIRED_TRACKED_DOCS = [
6647
7373
  { name: "documentation-guide", path: "docs/documentation-guide" },
6648
7374
  { name: "pasika-adoption-guide", path: "docs/pasika-adoption-guide" },
6649
7375
  { name: "repository-policy", path: "docs/repository-policy.md" }
6650
7376
  ];
6651
- var NEXTJS_REQUIRED_DOCS = [
7377
+ var NEXTJS_REQUIRED_TRACKED_DOCS = [
6652
7378
  { name: "next-codebase-guide", path: "docs/next-codebase-guide" },
6653
7379
  { name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
6654
7380
  ];
6655
- function memberName7(member) {
7381
+ function memberName6(member) {
6656
7382
  return member.name.type === "String" ? member.name.value : member.name.name;
6657
7383
  }
6658
7384
  function hasDependency(root, name) {
6659
- const section = root.members.find((member) => memberName7(member) === "dependencies");
7385
+ const section = root.members.find((member) => memberName6(member) === "dependencies");
6660
7386
  if (section?.value.type !== "Object") return false;
6661
- return section.value.members.some((member) => memberName7(member) === name);
7387
+ return section.value.members.some((member) => memberName6(member) === name);
6662
7388
  }
6663
- var vulykDocsRule = {
7389
+ var trackedDocsRule = {
6664
7390
  meta: {
6665
7391
  schema: [],
6666
7392
  type: "problem",
6667
7393
  docs: {
6668
- description: "Require vulyk.config.ts to track the framework's required docs from pasika and the generated AGENTS.md."
7394
+ description: "Require vulyk.config.ts to track the framework's required tracked docs from pasika and the generated AGENTS.md."
6669
7395
  }
6670
7396
  },
6671
7397
  create(context) {
@@ -6674,34 +7400,34 @@ var vulykDocsRule = {
6674
7400
  if (!context.filename.endsWith("package.json")) return;
6675
7401
  const root = node.body;
6676
7402
  if (root.type !== "Object") return;
6677
- const projectRoot = path53.dirname(path53.resolve(context.filename));
6678
- const configPath = path53.join(projectRoot, "vulyk.config.ts");
6679
- if (!existsSync6(configPath)) {
7403
+ const projectRoot = path59.dirname(path59.resolve(context.filename));
7404
+ const configPath = path59.join(projectRoot, "vulyk.config.ts");
7405
+ if (!existsSync7(configPath)) {
6680
7406
  context.report({
6681
7407
  node,
6682
- message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's docs."
7408
+ message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's required tracked docs."
6683
7409
  });
6684
7410
  return;
6685
7411
  }
6686
- const config = readFileSync11(configPath, "utf8");
7412
+ const config = readFileSync13(configPath, "utf8");
6687
7413
  if (!config.includes(PASIKA_REPO)) {
6688
7414
  context.report({
6689
7415
  node,
6690
- message: "vulyk.config.ts must track the framework's docs from the pasika repository."
7416
+ message: "vulyk.config.ts must track the framework's required tracked docs from the pasika repository."
6691
7417
  });
6692
7418
  return;
6693
7419
  }
6694
- const requiredDocs = hasDependency(root, "next") ? [...BASE_REQUIRED_DOCS, ...NEXTJS_REQUIRED_DOCS] : BASE_REQUIRED_DOCS;
6695
- for (const doc of requiredDocs) {
6696
- if (!config.includes(doc.path)) {
7420
+ const requiredTrackedDocs = hasDependency(root, "next") ? [...BASE_REQUIRED_TRACKED_DOCS, ...NEXTJS_REQUIRED_TRACKED_DOCS] : BASE_REQUIRED_TRACKED_DOCS;
7421
+ for (const trackedDoc of requiredTrackedDocs) {
7422
+ if (!config.includes(trackedDoc.path)) {
6697
7423
  context.report({
6698
7424
  node,
6699
- message: `vulyk.config.ts must track the framework's ${doc.name} docs from pasika (${PASIKA_REPO}/${doc.path}).`
7425
+ message: `vulyk.config.ts must track the framework's ${trackedDoc.name} tracked docs from pasika (${PASIKA_REPO}/${trackedDoc.path}).`
6700
7426
  });
6701
7427
  }
6702
7428
  }
6703
- const agentsPath = path53.join(projectRoot, "AGENTS.md");
6704
- if (!existsSync6(agentsPath)) {
7429
+ const agentsPath = path59.join(projectRoot, "AGENTS.md");
7430
+ if (!existsSync7(agentsPath)) {
6705
7431
  context.report({
6706
7432
  node,
6707
7433
  message: "No AGENTS.md found. Run npx vulyk agents to generate the agent file that routes to the tracked docs."
@@ -6712,10 +7438,50 @@ var vulykDocsRule = {
6712
7438
  }
6713
7439
  };
6714
7440
 
7441
+ // eslint/rules/vulyk/vulyk-dependency.ts
7442
+ function memberName7(member) {
7443
+ return member.name.type === "String" ? member.name.value : member.name.name;
7444
+ }
7445
+ function dependency(root, sectionName) {
7446
+ if (root.type !== "Object") return void 0;
7447
+ const section = root.members.find((member) => memberName7(member) === sectionName);
7448
+ if (section?.value.type !== "Object") return void 0;
7449
+ return section.value.members.find((member) => memberName7(member) === "vulyk");
7450
+ }
7451
+ var vulykDependencyRule = {
7452
+ meta: {
7453
+ schema: [],
7454
+ type: "problem",
7455
+ docs: {
7456
+ description: "Require vulyk in devDependencies so its typed config and CLI use the pinned package."
7457
+ }
7458
+ },
7459
+ create(context) {
7460
+ return {
7461
+ Document(node) {
7462
+ const runtimeDependency = dependency(node.body, "dependencies");
7463
+ const developmentDependency = dependency(node.body, "devDependencies");
7464
+ if (runtimeDependency) {
7465
+ context.report({
7466
+ node: runtimeDependency,
7467
+ message: "vulyk must be listed in devDependencies, not dependencies."
7468
+ });
7469
+ }
7470
+ if (!developmentDependency && !runtimeDependency) {
7471
+ context.report({
7472
+ node,
7473
+ message: "vulyk must be listed in package.json as a devDependency."
7474
+ });
7475
+ }
7476
+ }
7477
+ };
7478
+ }
7479
+ };
7480
+
6715
7481
  // eslint/rules/vulyk/index.ts
6716
7482
  var vulykRules = {
6717
7483
  "vulyk-dependency": vulykDependencyRule,
6718
- "vulyk-docs": vulykDocsRule
7484
+ "tracked-docs": trackedDocsRule
6719
7485
  };
6720
7486
 
6721
7487
  // eslint/index.ts
@@ -6757,6 +7523,10 @@ var pasikaNextjsAppRules = {
6757
7523
  "cva-boolean-variants": cvaBooleanVariantsRule,
6758
7524
  "cross-feature-import": crossFeatureImportRule,
6759
7525
  "pure-function-extract": pureFunctionExtractRule,
7526
+ "root-support-placement": rootSupportPlacementRule,
7527
+ "route-handler-shape": routeHandlerShapeRule,
7528
+ "http-error-usage": httpErrorUsageRule,
7529
+ "with-response-helper": withResponseHelperRule,
6760
7530
  "hook-complexity": hookComplexityRule,
6761
7531
  "locale-dotted-path": localeDottedPathRule,
6762
7532
  "locales-location": localesLocationRule,
@@ -6861,6 +7631,11 @@ var zirkaConfig = {
6861
7631
  plugins: { pasika: pasikaPlugin },
6862
7632
  rules: { "pasika/zirka-baseline": "error" }
6863
7633
  };
7634
+ var nextjsHelperConfig = {
7635
+ files: ["eslint.config.{ts,mts,cts,js,mjs,cjs}"],
7636
+ plugins: { pasika: pasikaPlugin },
7637
+ rules: { "pasika/cn-helper": "error", "pasika/with-response-helper": "error" }
7638
+ };
6864
7639
  var documentationConfig = {
6865
7640
  files: ["docs/**/*.md"],
6866
7641
  // vulyk-generated agent files are not authored docs: with per-directory
@@ -6890,6 +7665,7 @@ var pasikaNextjsAppWithDiagnostic = (preset) => {
6890
7665
  var pasikaNextjsApp = pasikaNextjsAppWithDiagnostic([
6891
7666
  ...pasikaApp,
6892
7667
  pasikaNextjsAppPackageJsonConfig,
7668
+ nextjsHelperConfig,
6893
7669
  pasikaNextjsAppConfig,
6894
7670
  tailwindStructureRules,
6895
7671
  tailwindImportGraph