pasika 0.7.7 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -199,11 +199,14 @@ declare const pasikaPlugin: {
199
199
  "enforce-barrel-exports": eslint.Rule.RuleModule;
200
200
  "config-extraction": eslint.Rule.RuleModule;
201
201
  "value-extraction": eslint.Rule.RuleModule;
202
+ "constant-casing": eslint.Rule.RuleModule;
202
203
  "type-extraction": eslint.Rule.RuleModule;
203
204
  "zod-schema-validation": eslint.Rule.RuleModule;
205
+ "schema-casing": eslint.Rule.RuleModule;
204
206
  "source-under-src": eslint.Rule.RuleModule;
205
207
  "zirka-baseline": eslint.Rule.RuleModule;
206
208
  "component-placement": eslint.Rule.RuleModule;
209
+ "component-casing": eslint.Rule.RuleModule;
207
210
  "application-structure": eslint.Rule.RuleModule;
208
211
  "data-testid-case": eslint.Rule.RuleModule;
209
212
  "jsx-hygiene": eslint.Rule.RuleModule;
@@ -137,6 +137,30 @@ function parseComponentInfo(text, filename, options) {
137
137
  }
138
138
  return components;
139
139
  }
140
+ function findJsxReturningDeclarations(text, filename) {
141
+ const sourceFile = ts.createSourceFile(path.resolve(filename), text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
142
+ const results = [];
143
+ const record = (identifier, body) => {
144
+ if (isHookCallName(identifier.text) || !containsJsx(body)) return;
145
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(identifier.getStart(sourceFile));
146
+ results.push({ name: identifier.text, line: line + 1, column: character });
147
+ };
148
+ for (const statement of sourceFile.statements) {
149
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
150
+ record(statement.name, statement);
151
+ continue;
152
+ }
153
+ if (ts.isVariableStatement(statement)) {
154
+ for (const declaration of statement.declarationList.declarations) {
155
+ if (!ts.isIdentifier(declaration.name)) continue;
156
+ const initializer = declaration.initializer;
157
+ if (!initializer || !ts.isArrowFunction(initializer) && !ts.isFunctionExpression(initializer)) continue;
158
+ record(declaration.name, initializer);
159
+ }
160
+ }
161
+ }
162
+ return results;
163
+ }
140
164
  function jsxTagName(element) {
141
165
  const tagName2 = ts.isJsxElement(element) ? element.openingElement.tagName : element.tagName;
142
166
  return ts.isIdentifier(tagName2) ? tagName2.text : void 0;
@@ -2108,8 +2132,46 @@ var supportFolderShapeRule = {
2108
2132
  }
2109
2133
  };
2110
2134
 
2111
- // eslint/rules/import-through-index.ts
2135
+ // eslint/rules/constant-casing.ts
2112
2136
  import path17 from "path";
2137
+ var NEXTJS_ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2138
+ function isScreamingSnakeCase(name) {
2139
+ return /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/.test(name);
2140
+ }
2141
+ var constantCasingRule = {
2142
+ meta: {
2143
+ schema: [],
2144
+ type: "problem",
2145
+ docs: {
2146
+ description: "Require a module-level constant to be camelCase, unless a framework requires a specific name."
2147
+ }
2148
+ },
2149
+ create(context) {
2150
+ const filename = path17.resolve(context.filename);
2151
+ const sourceRoot = sourceRootOf(context);
2152
+ if (!filename.startsWith(sourceRoot + path17.sep)) return {};
2153
+ return {
2154
+ VariableDeclarator(node) {
2155
+ if (node.id.type !== "Identifier") return;
2156
+ const declaration = node.parent;
2157
+ if (declaration.type !== "VariableDeclaration" || declaration.kind !== "const") return;
2158
+ const container = declaration.parent;
2159
+ const isModuleLevel = container.type === "Program" || container.type === "ExportNamedDeclaration" && container.parent.type === "Program";
2160
+ if (!isModuleLevel) return;
2161
+ const { name } = node.id;
2162
+ if (!isScreamingSnakeCase(name)) return;
2163
+ if (NEXTJS_ROUTE_HANDLER_NAMES.has(name)) return;
2164
+ context.report({
2165
+ node,
2166
+ message: `${name} must be camelCase, unless a framework requires this exact name. See docs/next-codebase-guide/rules/constants-rule.md`
2167
+ });
2168
+ }
2169
+ };
2170
+ }
2171
+ };
2172
+
2173
+ // eslint/rules/import-through-index.ts
2174
+ import path18 from "path";
2113
2175
  var importThroughIndexRule = {
2114
2176
  meta: {
2115
2177
  schema: [],
@@ -2119,7 +2181,7 @@ var importThroughIndexRule = {
2119
2181
  }
2120
2182
  },
2121
2183
  create(context) {
2122
- const filename = path17.resolve(context.filename);
2184
+ const filename = path18.resolve(context.filename);
2123
2185
  const sourceRoot = sourceRootOf2(context, filename);
2124
2186
  return {
2125
2187
  Program(node) {
@@ -2131,7 +2193,7 @@ var importThroughIndexRule = {
2131
2193
  (segment) => ["constants", "types", "schemas"].includes(segment)
2132
2194
  );
2133
2195
  const supportFolder = supportFolderIndex >= 0 ? targetSegments[supportFolderIndex] : void 0;
2134
- if (!supportFolder || path17.basename(target).startsWith("index.")) continue;
2196
+ if (!supportFolder || path18.basename(target).startsWith("index.")) continue;
2135
2197
  const folderIndex = targetSegments.slice(0, supportFolderIndex + 1);
2136
2198
  const expected = `@/${folderIndex.join("/")}`;
2137
2199
  context.report({
@@ -2153,14 +2215,14 @@ function importSpecifiers(source) {
2153
2215
  return specifiers;
2154
2216
  }
2155
2217
  function sourceRootOf2(context, filename) {
2156
- const marker = `${path17.sep}src${path17.sep}`;
2218
+ const marker = `${path18.sep}src${path18.sep}`;
2157
2219
  const srcIndex = filename.lastIndexOf(marker);
2158
2220
  if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
2159
- return path17.resolve(context.cwd ?? process.cwd(), "src");
2221
+ return path18.resolve(context.cwd ?? process.cwd(), "src");
2160
2222
  }
2161
2223
 
2162
2224
  // eslint/rules/util-file-name.ts
2163
- import path18 from "path";
2225
+ import path19 from "path";
2164
2226
  function toKebabCase2(value) {
2165
2227
  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();
2166
2228
  }
@@ -2173,7 +2235,7 @@ var utilFileNameRule = {
2173
2235
  }
2174
2236
  },
2175
2237
  create(context) {
2176
- const filename = path18.resolve(context.filename);
2238
+ const filename = path19.resolve(context.filename);
2177
2239
  const segments = filename.replace(/\\/g, "/").split("/");
2178
2240
  if (!segments.includes("utils")) return {};
2179
2241
  let module;
@@ -2187,13 +2249,13 @@ var utilFileNameRule = {
2187
2249
  const functionName = functions[0]?.name;
2188
2250
  if (!functionName) return {};
2189
2251
  const expected = toKebabCase2(functionName);
2190
- const actual = path18.basename(filename, path18.extname(filename));
2252
+ const actual = path19.basename(filename, path19.extname(filename));
2191
2253
  if (!expected || actual === expected) return {};
2192
2254
  return {
2193
2255
  Program(node) {
2194
2256
  context.report({
2195
2257
  node,
2196
- message: `A utility file exporting ${functionName} must be named ${expected}.${path18.extname(filename).slice(1)}.`
2258
+ message: `A utility file exporting ${functionName} must be named ${expected}.${path19.extname(filename).slice(1)}.`
2197
2259
  });
2198
2260
  }
2199
2261
  };
@@ -2201,7 +2263,7 @@ var utilFileNameRule = {
2201
2263
  };
2202
2264
 
2203
2265
  // eslint/rules/no-util-barrel.ts
2204
- import path19 from "path";
2266
+ import path20 from "path";
2205
2267
  var noUtilBarrelRule = {
2206
2268
  meta: {
2207
2269
  schema: [],
@@ -2211,7 +2273,7 @@ var noUtilBarrelRule = {
2211
2273
  }
2212
2274
  },
2213
2275
  create(context) {
2214
- const filename = path19.resolve(context.filename);
2276
+ const filename = path20.resolve(context.filename);
2215
2277
  const sourceRoot = sourceRootOf3(context, filename);
2216
2278
  return {
2217
2279
  Program(node) {
@@ -2220,7 +2282,7 @@ var noUtilBarrelRule = {
2220
2282
  if (!target) continue;
2221
2283
  const segments = target.replace(/\\/g, "/").split("/");
2222
2284
  const utilsIndex = segments.lastIndexOf("utils");
2223
- if (utilsIndex < 0 || !path19.basename(target).startsWith("index.")) continue;
2285
+ if (utilsIndex < 0 || !path20.basename(target).startsWith("index.")) continue;
2224
2286
  context.report({
2225
2287
  node,
2226
2288
  message: `Import utilities directly instead of through "${specifier}". See docs/next-codebase-guide/rules/utilities-rule.md`
@@ -2240,10 +2302,10 @@ function importSpecifiers2(source) {
2240
2302
  return specifiers;
2241
2303
  }
2242
2304
  function sourceRootOf3(context, filename) {
2243
- const marker = `${path19.sep}src${path19.sep}`;
2305
+ const marker = `${path20.sep}src${path20.sep}`;
2244
2306
  const srcIndex = filename.lastIndexOf(marker);
2245
2307
  if (srcIndex >= 0) return filename.slice(0, srcIndex + marker.length - 1);
2246
- return path19.resolve(context.cwd ?? process.cwd(), "src");
2308
+ return path20.resolve(context.cwd ?? process.cwd(), "src");
2247
2309
  }
2248
2310
 
2249
2311
  // eslint/rules/jsx-hygiene.ts
@@ -2336,7 +2398,7 @@ var jsxHygieneRule = {
2336
2398
  };
2337
2399
 
2338
2400
  // eslint/rules/interactive-component.ts
2339
- import path20 from "path";
2401
+ import path21 from "path";
2340
2402
  var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
2341
2403
  "a",
2342
2404
  "button",
@@ -2491,8 +2553,8 @@ var interactiveComponentRule = {
2491
2553
  },
2492
2554
  create(context) {
2493
2555
  if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
2494
- const filename = path20.resolve(context.filename);
2495
- const base = path20.basename(filename, path20.extname(filename));
2556
+ const filename = path21.resolve(context.filename);
2557
+ const base = path21.basename(filename, path21.extname(filename));
2496
2558
  if (NEXT_ROUTING_FILES3.has(base)) return {};
2497
2559
  return {
2498
2560
  JSXElement(node) {
@@ -2743,12 +2805,12 @@ var cvaBooleanVariantsRule = {
2743
2805
  };
2744
2806
 
2745
2807
  // eslint/rules/cross-feature-import.ts
2746
- import path21 from "path";
2808
+ import path22 from "path";
2747
2809
  var FEATURES_SEGMENT = "features";
2748
2810
  function featureNameOf(resolvedPath, sourceRoot) {
2749
- const relative = path21.relative(sourceRoot, resolvedPath);
2811
+ const relative = path22.relative(sourceRoot, resolvedPath);
2750
2812
  if (relative.startsWith("..")) return void 0;
2751
- const segments = relative.split(path21.sep);
2813
+ const segments = relative.split(path22.sep);
2752
2814
  if (segments[0] !== FEATURES_SEGMENT || segments.length < 2) return void 0;
2753
2815
  return segments[1];
2754
2816
  }
@@ -2764,9 +2826,9 @@ var crossFeatureImportRule = {
2764
2826
  const filename = context.filename;
2765
2827
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2766
2828
  const sourceRoot = sourceRootOf(context);
2767
- const fileRelative = path21.relative(sourceRoot, filename);
2829
+ const fileRelative = path22.relative(sourceRoot, filename);
2768
2830
  if (fileRelative.startsWith("..")) return {};
2769
- const fileSegments = fileRelative.split(path21.sep);
2831
+ const fileSegments = fileRelative.split(path22.sep);
2770
2832
  const isInCompositions = fileSegments[0] === "compositions";
2771
2833
  const isInApp = fileSegments[0] === "app";
2772
2834
  const isConfig = fileSegments[0] === "config";
@@ -2780,9 +2842,9 @@ var crossFeatureImportRule = {
2780
2842
  if (typeof source.value !== "string") return;
2781
2843
  let resolved;
2782
2844
  if (source.value.startsWith("@/")) {
2783
- resolved = path21.resolve(sourceRoot, source.value.slice(2));
2845
+ resolved = path22.resolve(sourceRoot, source.value.slice(2));
2784
2846
  } else if (source.value.startsWith(".")) {
2785
- resolved = path21.resolve(path21.dirname(filename), source.value);
2847
+ resolved = path22.resolve(path22.dirname(filename), source.value);
2786
2848
  }
2787
2849
  if (!resolved) return;
2788
2850
  const feature = featureNameOf(resolved, sourceRoot);
@@ -2801,7 +2863,7 @@ var crossFeatureImportRule = {
2801
2863
  };
2802
2864
 
2803
2865
  // eslint/rules/pure-function-extract.ts
2804
- import path22 from "path";
2866
+ import path23 from "path";
2805
2867
  function isComponentLikeName(name) {
2806
2868
  return /^[A-Z]/.test(name);
2807
2869
  }
@@ -2830,9 +2892,9 @@ var pureFunctionExtractRule = {
2830
2892
  const filename = context.filename;
2831
2893
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2832
2894
  const sourceRoot = sourceRootOf(context);
2833
- const relative = path22.relative(sourceRoot, filename);
2895
+ const relative = path23.relative(sourceRoot, filename);
2834
2896
  if (relative.startsWith("..")) return {};
2835
- const segments = relative.split(path22.sep);
2897
+ const segments = relative.split(path23.sep);
2836
2898
  if (segments[0] === "utils") return {};
2837
2899
  if (segments[0] === "app") return {};
2838
2900
  const supportFolders = /* @__PURE__ */ new Set(["hooks", "types", "schemas", "constants", "utils"]);
@@ -2872,7 +2934,7 @@ var pureFunctionExtractRule = {
2872
2934
  };
2873
2935
 
2874
2936
  // eslint/rules/hook-complexity.ts
2875
- import path23 from "path";
2937
+ import path24 from "path";
2876
2938
  import ts4 from "typescript";
2877
2939
  var REACT_HOOKS = /* @__PURE__ */ new Set([
2878
2940
  "useState",
@@ -2925,9 +2987,9 @@ var hookComplexityRule = {
2925
2987
  create(context) {
2926
2988
  const filename = context.filename;
2927
2989
  const sourceRoot = sourceRootOf(context);
2928
- const relative = path23.relative(sourceRoot, filename);
2990
+ const relative = path24.relative(sourceRoot, filename);
2929
2991
  if (relative.startsWith("..")) return {};
2930
- const segments = relative.split(path23.sep);
2992
+ const segments = relative.split(path24.sep);
2931
2993
  const sourceText = context.sourceCode.text;
2932
2994
  function checkHook(node, name, body, exported) {
2933
2995
  if (!exported) return;
@@ -2969,9 +3031,9 @@ var hookComplexityRule = {
2969
3031
  };
2970
3032
 
2971
3033
  // eslint/rules/locale-dotted-path.ts
2972
- import path24 from "path";
3034
+ import path25 from "path";
2973
3035
  function isInLocalesDir(filename) {
2974
- const segments = path24.resolve(filename).split(path24.sep);
3036
+ const segments = path25.resolve(filename).split(path25.sep);
2975
3037
  const srcIdx = segments.lastIndexOf("src");
2976
3038
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
2977
3039
  }
@@ -3020,16 +3082,22 @@ var localeDottedPathRule = {
3020
3082
  };
3021
3083
 
3022
3084
  // eslint/rules/locales-location.ts
3023
- import path25 from "path";
3085
+ import path26 from "path";
3024
3086
  function isLocalesFile(filename) {
3025
- const segments = path25.resolve(filename).split(path25.sep);
3087
+ const segments = path26.resolve(filename).split(path26.sep);
3026
3088
  const srcIdx = segments.lastIndexOf("src");
3027
3089
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3028
3090
  }
3091
+ function isTestFile(filename) {
3092
+ return /\.(?:test|spec)\.[cm]?tsx?$/.test(filename);
3093
+ }
3029
3094
  var LOCALE_NAME_RE = /^[a-z][a-zA-Z0-9]*$/;
3030
3095
  function looksLikeLocaleKey(name) {
3031
3096
  return LOCALE_NAME_RE.test(name);
3032
3097
  }
3098
+ function looksLikeUserFacingString(value) {
3099
+ return typeof value === "string" && /^\p{Lu}/u.test(value);
3100
+ }
3033
3101
  var localesLocationRule = {
3034
3102
  meta: {
3035
3103
  schema: [],
@@ -3039,9 +3107,9 @@ var localesLocationRule = {
3039
3107
  }
3040
3108
  },
3041
3109
  create(context) {
3042
- if (isLocalesFile(context.filename)) return {};
3110
+ if (isLocalesFile(context.filename) || isTestFile(context.filename)) return {};
3043
3111
  const filename = context.filename;
3044
- const segments = path25.resolve(filename).split(path25.sep);
3112
+ const segments = path26.resolve(filename).split(path26.sep);
3045
3113
  const srcIdx = segments.lastIndexOf("src");
3046
3114
  if (srcIdx === -1) return {};
3047
3115
  const folder = segments[srcIdx + 1];
@@ -3049,8 +3117,10 @@ var localesLocationRule = {
3049
3117
  return {
3050
3118
  VariableDeclarator(node) {
3051
3119
  if (node.id.type === "Identifier" && node.init?.type === "ObjectExpression" && node.init.properties.length > 0 && looksLikeLocaleKey(node.id.name)) {
3052
- const hasStringValues = node.init.properties.some((p) => p.type === "Property" && p.value.type === "Literal");
3053
- if (hasStringValues) {
3120
+ const hasUserFacingStringValues = node.init.properties.some(
3121
+ (p) => p.type === "Property" && p.value.type === "Literal" && looksLikeUserFacingString(p.value.value)
3122
+ );
3123
+ if (hasUserFacingStringValues) {
3054
3124
  context.report({
3055
3125
  node,
3056
3126
  message: "User-facing strings must live in src/locales/, not inline in component files."
@@ -3063,7 +3133,7 @@ var localesLocationRule = {
3063
3133
  };
3064
3134
 
3065
3135
  // eslint/rules/hook-extraction.ts
3066
- import path26 from "path";
3136
+ import path27 from "path";
3067
3137
  var hookExtractionRule = {
3068
3138
  meta: {
3069
3139
  schema: [],
@@ -3074,7 +3144,7 @@ var hookExtractionRule = {
3074
3144
  },
3075
3145
  create(context) {
3076
3146
  const sourceRoot = sourceRootOf(context);
3077
- const file = path26.resolve(context.filename);
3147
+ const file = path27.resolve(context.filename);
3078
3148
  const segments = segmentsOf(file, sourceRoot);
3079
3149
  if (segments.length === 0) return {};
3080
3150
  const index = getProjectIndex(sourceRoot);
@@ -3100,7 +3170,7 @@ var hookExtractionRule = {
3100
3170
  };
3101
3171
 
3102
3172
  // eslint/rules/value-extraction.ts
3103
- import path27 from "path";
3173
+ import path28 from "path";
3104
3174
  var valueExtractionRule = {
3105
3175
  meta: {
3106
3176
  schema: [],
@@ -3111,7 +3181,7 @@ var valueExtractionRule = {
3111
3181
  },
3112
3182
  create(context) {
3113
3183
  const sourceRoot = sourceRootOf(context);
3114
- const file = path27.resolve(context.filename);
3184
+ const file = path28.resolve(context.filename);
3115
3185
  const segments = segmentsOf(file, sourceRoot);
3116
3186
  if (segments.length === 0 || segments[0] !== "app") return {};
3117
3187
  const index = getProjectIndex(sourceRoot);
@@ -3132,7 +3202,7 @@ var valueExtractionRule = {
3132
3202
  };
3133
3203
 
3134
3204
  // eslint/rules/config-extraction.ts
3135
- import path28 from "path";
3205
+ import path29 from "path";
3136
3206
  var configExtractionRule = {
3137
3207
  meta: {
3138
3208
  schema: [],
@@ -3143,7 +3213,7 @@ var configExtractionRule = {
3143
3213
  },
3144
3214
  create(context) {
3145
3215
  const sourceRoot = sourceRootOf(context);
3146
- const file = path28.resolve(context.filename);
3216
+ const file = path29.resolve(context.filename);
3147
3217
  const segments = segmentsOf(file, sourceRoot);
3148
3218
  if (segments.length < 3 || segments[0] !== "config") return {};
3149
3219
  if (SUPPORT_FOLDERS2.has(segments[2] ?? "")) return {};
@@ -3181,7 +3251,7 @@ var configExtractionRule = {
3181
3251
  };
3182
3252
 
3183
3253
  // eslint/rules/component-nesting.ts
3184
- import path29 from "path";
3254
+ import path30 from "path";
3185
3255
  var componentNestingRule = {
3186
3256
  meta: {
3187
3257
  schema: [],
@@ -3192,7 +3262,7 @@ var componentNestingRule = {
3192
3262
  },
3193
3263
  create(context) {
3194
3264
  const sourceRoot = sourceRootOf(context);
3195
- const file = path29.resolve(context.filename);
3265
+ const file = path30.resolve(context.filename);
3196
3266
  const segments = segmentsOf(file, sourceRoot);
3197
3267
  if (segments.length !== 4 || segments[0] !== "features") return {};
3198
3268
  const index = getProjectIndex(sourceRoot);
@@ -3225,7 +3295,7 @@ var componentNestingRule = {
3225
3295
  };
3226
3296
 
3227
3297
  // eslint/rules/stay-flat.ts
3228
- import path30 from "path";
3298
+ import path31 from "path";
3229
3299
  var stayFlatRule = {
3230
3300
  meta: {
3231
3301
  schema: [],
@@ -3236,7 +3306,7 @@ var stayFlatRule = {
3236
3306
  },
3237
3307
  create(context) {
3238
3308
  const sourceRoot = sourceRootOf(context);
3239
- const file = path30.resolve(context.filename);
3309
+ const file = path31.resolve(context.filename);
3240
3310
  const segments = segmentsOf(file, sourceRoot);
3241
3311
  if (segments.length !== 3 || segments[0] !== "features") return {};
3242
3312
  const index = getProjectIndex(sourceRoot);
@@ -3276,7 +3346,7 @@ var stayFlatRule = {
3276
3346
  };
3277
3347
 
3278
3348
  // eslint/rules/type-extraction.ts
3279
- import path31 from "path";
3349
+ import path32 from "path";
3280
3350
  var typeExtractionRule = {
3281
3351
  meta: {
3282
3352
  schema: [],
@@ -3287,7 +3357,7 @@ var typeExtractionRule = {
3287
3357
  },
3288
3358
  create(context) {
3289
3359
  const sourceRoot = sourceRootOf(context);
3290
- const file = path31.resolve(context.filename);
3360
+ const file = path32.resolve(context.filename);
3291
3361
  const segments = segmentsOf(file, sourceRoot);
3292
3362
  if (segments.length === 0) return {};
3293
3363
  const index = getProjectIndex(sourceRoot);
@@ -3333,7 +3403,7 @@ var typeExtractionRule = {
3333
3403
  };
3334
3404
 
3335
3405
  // eslint/rules/locale-placement.ts
3336
- import path32 from "path";
3406
+ import path33 from "path";
3337
3407
  import { readFileSync as readFileSync4 } from "fs";
3338
3408
  import ts5 from "typescript";
3339
3409
  var LOCALE_ACCESS = /\blocales\.(?<key>[A-Za-z_$][\w$]*)/g;
@@ -3381,7 +3451,7 @@ var localePlacementRule = {
3381
3451
  },
3382
3452
  create(context) {
3383
3453
  const sourceRoot = sourceRootOf(context);
3384
- const file = path32.resolve(context.filename);
3454
+ const file = path33.resolve(context.filename);
3385
3455
  const segments = segmentsOf(file, sourceRoot);
3386
3456
  if (segments.length === 0) return {};
3387
3457
  const index = getProjectIndex(sourceRoot);
@@ -3459,7 +3529,7 @@ var localePlacementRule = {
3459
3529
  };
3460
3530
 
3461
3531
  // eslint/rules/sole-state-owner.ts
3462
- import path33 from "path";
3532
+ import path34 from "path";
3463
3533
  import ts6 from "typescript";
3464
3534
  function findStateHooks(node) {
3465
3535
  const hooks = [];
@@ -3539,7 +3609,7 @@ var soleStateOwnerRule = {
3539
3609
  }
3540
3610
  },
3541
3611
  create(context) {
3542
- const filename = path33.resolve(context.filename);
3612
+ const filename = path34.resolve(context.filename);
3543
3613
  if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
3544
3614
  const text = context.sourceCode.text;
3545
3615
  const components = parseComponentInfo(text, filename);
@@ -3618,7 +3688,7 @@ function usesOutsideJsx(declaration, hook, children) {
3618
3688
  }
3619
3689
 
3620
3690
  // eslint/rules/locale-key-shape.ts
3621
- import path34 from "path";
3691
+ import path35 from "path";
3622
3692
  var MAX_KEY_LENGTH = 30;
3623
3693
  var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
3624
3694
  "Button",
@@ -3668,7 +3738,7 @@ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
3668
3738
  var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
3669
3739
  var ENGLISH = /^[A-Za-z0-9_]*$/;
3670
3740
  function isLocalesFile2(filename) {
3671
- const segments = path34.resolve(filename).split(path34.sep);
3741
+ const segments = path35.resolve(filename).split(path35.sep);
3672
3742
  const srcIdx = segments.lastIndexOf("src");
3673
3743
  return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
3674
3744
  }
@@ -3739,7 +3809,7 @@ var localeKeyShapeRule = {
3739
3809
  };
3740
3810
 
3741
3811
  // eslint/rules/shared-style-dedup.ts
3742
- import path35 from "path";
3812
+ import path36 from "path";
3743
3813
  import { readFileSync as readFileSync5, statSync as statSync3 } from "fs";
3744
3814
  var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
3745
3815
  var comboCache;
@@ -3777,7 +3847,7 @@ var sharedStyleDedupRule = {
3777
3847
  },
3778
3848
  create(context) {
3779
3849
  const sourceRoot = sourceRootOf(context);
3780
- const file = path35.resolve(context.filename);
3850
+ const file = path36.resolve(context.filename);
3781
3851
  const segments = segmentsOf(file, sourceRoot);
3782
3852
  if (segments.length === 0) return {};
3783
3853
  const index = getProjectIndex(sourceRoot);
@@ -3980,8 +4050,103 @@ var zodSchemaValidationRule = {
3980
4050
  }
3981
4051
  };
3982
4052
 
4053
+ // eslint/rules/schema-casing.ts
4054
+ import path37 from "path";
4055
+ function isCamelCase(name) {
4056
+ return /^[a-z][a-zA-Z0-9]*$/.test(name);
4057
+ }
4058
+ function rootIdentifierName(node) {
4059
+ let current = node;
4060
+ for (; ; ) {
4061
+ if (current.type === "Identifier") return current.name;
4062
+ if (current.type === "CallExpression") {
4063
+ current = current.callee;
4064
+ continue;
4065
+ }
4066
+ if (current.type === "MemberExpression") {
4067
+ current = current.object;
4068
+ continue;
4069
+ }
4070
+ return void 0;
4071
+ }
4072
+ }
4073
+ var schemaCasingRule = {
4074
+ meta: {
4075
+ schema: [],
4076
+ type: "problem",
4077
+ docs: {
4078
+ description: "Require a Zod schema constant to be named in camelCase."
4079
+ }
4080
+ },
4081
+ create(context) {
4082
+ const filename = path37.resolve(context.filename);
4083
+ const sourceRoot = sourceRootOf(context);
4084
+ if (!filename.startsWith(sourceRoot + path37.sep)) return {};
4085
+ let zodLocalName;
4086
+ return {
4087
+ ImportDeclaration(node) {
4088
+ if (node.source.value !== "zod") return;
4089
+ for (const specifier of node.specifiers) {
4090
+ if (specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "z") {
4091
+ zodLocalName = specifier.local.name;
4092
+ }
4093
+ }
4094
+ },
4095
+ VariableDeclarator(node) {
4096
+ if (!zodLocalName) return;
4097
+ if (node.id.type !== "Identifier") return;
4098
+ const declaration = node.parent;
4099
+ if (declaration.type !== "VariableDeclaration" || declaration.kind !== "const") return;
4100
+ const container = declaration.parent;
4101
+ const isModuleLevel = container.type === "Program" || container.type === "ExportNamedDeclaration" && container.parent.type === "Program";
4102
+ if (!isModuleLevel) return;
4103
+ if (!node.init || rootIdentifierName(node.init) !== zodLocalName) return;
4104
+ const { name } = node.id;
4105
+ if (isCamelCase(name)) return;
4106
+ context.report({
4107
+ node,
4108
+ message: `${name} must be camelCase. See docs/next-codebase-guide/rules/types-and-schemas-rule.md`
4109
+ });
4110
+ }
4111
+ };
4112
+ }
4113
+ };
4114
+
4115
+ // eslint/rules/component-casing.ts
4116
+ import path38 from "path";
4117
+ function isPascalCase6(name) {
4118
+ return /^[A-Z][A-Za-z0-9]*$/.test(name);
4119
+ }
4120
+ var componentCasingRule = {
4121
+ meta: {
4122
+ schema: [],
4123
+ type: "problem",
4124
+ docs: {
4125
+ description: "Require a JSX-returning function or const to be named in PascalCase."
4126
+ }
4127
+ },
4128
+ create(context) {
4129
+ const filename = path38.resolve(context.filename);
4130
+ const sourceRoot = sourceRootOf(context);
4131
+ if (!filename.startsWith(sourceRoot + path38.sep)) return {};
4132
+ if (path38.extname(filename) !== ".tsx") return {};
4133
+ return {
4134
+ Program(node) {
4135
+ for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
4136
+ if (isPascalCase6(declaration.name)) continue;
4137
+ context.report({
4138
+ node,
4139
+ loc: { line: declaration.line, column: declaration.column },
4140
+ message: `${declaration.name} must be PascalCase; only a PascalCase name resolves as a JSX component. See docs/next-codebase-guide/rules/smart-vs-dumb-component-rule.md`
4141
+ });
4142
+ }
4143
+ }
4144
+ };
4145
+ }
4146
+ };
4147
+
3983
4148
  // eslint/rules/source-under-src.ts
3984
- import path36 from "path";
4149
+ import path39 from "path";
3985
4150
  var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
3986
4151
  ".agents",
3987
4152
  ".cache",
@@ -4022,14 +4187,14 @@ var sourceUnderSrcRule = {
4022
4187
  }
4023
4188
  },
4024
4189
  create(context) {
4025
- const filename = path36.resolve(context.filename);
4190
+ const filename = path39.resolve(context.filename);
4026
4191
  if (!MODULE_EXTENSION.test(filename)) return {};
4027
- const relative = path36.relative(context.cwd, filename).replace(/\\/g, "/");
4192
+ const relative = path39.relative(context.cwd, filename).replace(/\\/g, "/");
4028
4193
  if (relative === "src" || relative.startsWith("src/")) return {};
4029
4194
  const topLevel = relative.split("/")[0] ?? "";
4030
4195
  if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
4031
4196
  if (!relative.includes("/")) {
4032
- const basename = path36.basename(filename);
4197
+ const basename = path39.basename(filename);
4033
4198
  if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
4034
4199
  }
4035
4200
  return {
@@ -4046,7 +4211,7 @@ var sourceUnderSrcRule = {
4046
4211
 
4047
4212
  // eslint/rules/zirka-baseline.ts
4048
4213
  import fs5 from "fs";
4049
- import path37 from "path";
4214
+ import path40 from "path";
4050
4215
  var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
4051
4216
  var PRETTIER_CONFIGS = [
4052
4217
  "prettier.config.mjs",
@@ -4065,10 +4230,10 @@ var zirkaBaselineRule = {
4065
4230
  }
4066
4231
  },
4067
4232
  create(context) {
4068
- const filename = path37.resolve(context.filename);
4069
- const basename = path37.basename(filename);
4233
+ const filename = path40.resolve(context.filename);
4234
+ const basename = path40.basename(filename);
4070
4235
  if (!ESLINT_CONFIG.test(basename)) return {};
4071
- const projectRoot = path37.dirname(filename);
4236
+ const projectRoot = path40.dirname(filename);
4072
4237
  const report3 = (message) => {
4073
4238
  context.report({
4074
4239
  node: context.sourceCode.ast,
@@ -4083,7 +4248,7 @@ var zirkaBaselineRule = {
4083
4248
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
4084
4249
  );
4085
4250
  }
4086
- const tsconfigPath = path37.join(projectRoot, "tsconfig.json");
4251
+ const tsconfigPath = path40.join(projectRoot, "tsconfig.json");
4087
4252
  if (!fs5.existsSync(tsconfigPath)) {
4088
4253
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
4089
4254
  } else {
@@ -4101,13 +4266,13 @@ var zirkaBaselineRule = {
4101
4266
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
4102
4267
  }
4103
4268
  }
4104
- const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path37.join(projectRoot, name)));
4269
+ const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs5.existsSync(path40.join(projectRoot, name)));
4105
4270
  if (!prettierConfigFile) {
4106
4271
  report3(
4107
4272
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
4108
4273
  );
4109
4274
  } else {
4110
- const content = fs5.readFileSync(path37.join(projectRoot, prettierConfigFile), "utf8");
4275
+ const content = fs5.readFileSync(path40.join(projectRoot, prettierConfigFile), "utf8");
4111
4276
  if (!content.includes("zirka")) {
4112
4277
  report3(
4113
4278
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -4177,7 +4342,7 @@ var docKindSuffixRule = {
4177
4342
  };
4178
4343
 
4179
4344
  // eslint/rules/documentation/title-matches-file-name.ts
4180
- import path38 from "path";
4345
+ import path41 from "path";
4181
4346
  function toExpectedFileName(title) {
4182
4347
  return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
4183
4348
  }
@@ -4197,7 +4362,7 @@ var titleMatchesFileNameRule = {
4197
4362
  if (!filename.endsWith(".md")) return;
4198
4363
  const title = getTextContent(node).trim();
4199
4364
  const expectedFileName = toExpectedFileName(title);
4200
- const actualFileName = path38.basename(filename);
4365
+ const actualFileName = path41.basename(filename);
4201
4366
  if (!title) {
4202
4367
  context.report({
4203
4368
  node,
@@ -4640,20 +4805,20 @@ var referenceBlockHeadingsRule = {
4640
4805
 
4641
4806
  // eslint/rules/documentation/support-document-placement.ts
4642
4807
  import { existsSync as existsSync2 } from "fs";
4643
- import path39 from "path";
4808
+ import path42 from "path";
4644
4809
  function checkPlacement(filename, kind) {
4645
- const parentFolder = path39.basename(path39.dirname(filename));
4810
+ const parentFolder = path42.basename(path42.dirname(filename));
4646
4811
  const expectedParent = `${kind}s`;
4647
4812
  if (parentFolder !== expectedParent) {
4648
4813
  return `${kind} lives in "${parentFolder}/" instead of "${expectedParent}/"`;
4649
4814
  }
4650
- const guideFolderPath = path39.dirname(path39.dirname(filename));
4651
- const guideFolder = path39.basename(guideFolderPath);
4815
+ const guideFolderPath = path42.dirname(path42.dirname(filename));
4816
+ const guideFolder = path42.basename(guideFolderPath);
4652
4817
  if (!guideFolder.endsWith("-guide")) {
4653
4818
  return `${kind} owner folder "${guideFolder}/" does not use the "*-guide/" suffix`;
4654
4819
  }
4655
4820
  const entryPoint = `${guideFolder}.md`;
4656
- if (!existsSync2(path39.join(guideFolderPath, entryPoint))) {
4821
+ if (!existsSync2(path42.join(guideFolderPath, entryPoint))) {
4657
4822
  return `${kind} owner folder "${guideFolder}/" has no "${entryPoint}" entry point`;
4658
4823
  }
4659
4824
  return void 0;
@@ -4712,11 +4877,11 @@ var noTemplatePromptRule = {
4712
4877
  };
4713
4878
 
4714
4879
  // eslint/rules/documentation/guide-folder-entry-point.ts
4715
- import path41 from "path";
4880
+ import path44 from "path";
4716
4881
 
4717
4882
  // eslint/rules/documentation/project-index.ts
4718
4883
  import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
4719
- import path40 from "path";
4884
+ import path43 from "path";
4720
4885
  var KIND_BY_SUFFIX = [
4721
4886
  ["-rule.md", "rule"],
4722
4887
  ["-guide.md", "guide"],
@@ -4725,7 +4890,7 @@ var KIND_BY_SUFFIX = [
4725
4890
  ];
4726
4891
  function listMarkdownFiles(dir) {
4727
4892
  return readdirSync3(dir).flatMap((entry) => {
4728
- const entryPath = path40.join(dir, entry);
4893
+ const entryPath = path43.join(dir, entry);
4729
4894
  if (statSync4(entryPath).isDirectory()) {
4730
4895
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
4731
4896
  }
@@ -4743,11 +4908,11 @@ function getProjectDocs(docsRoot) {
4743
4908
  if (cached) return cached;
4744
4909
  const files = listMarkdownFiles(docsRoot);
4745
4910
  const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
4746
- const fileName = path40.basename(filePath);
4911
+ const fileName = path43.basename(filePath);
4747
4912
  const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
4748
4913
  return {
4749
4914
  filePath,
4750
- doc: path40.relative(docsRoot, filePath).split(path40.sep).join("/"),
4915
+ doc: path43.relative(docsRoot, filePath).split(path43.sep).join("/"),
4751
4916
  fileName,
4752
4917
  kind,
4753
4918
  title: extractTitle(filePath)
@@ -4757,12 +4922,12 @@ function getProjectDocs(docsRoot) {
4757
4922
  return docs;
4758
4923
  }
4759
4924
  function findDocsRoot(filePath) {
4760
- let dir = path40.dirname(filePath);
4925
+ let dir = path43.dirname(filePath);
4761
4926
  for (; ; ) {
4762
- if (path40.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
4927
+ if (path43.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
4763
4928
  return dir;
4764
4929
  }
4765
- const parent = path40.dirname(dir);
4930
+ const parent = path43.dirname(dir);
4766
4931
  if (parent === dir) return void 0;
4767
4932
  dir = parent;
4768
4933
  }
@@ -4786,13 +4951,13 @@ var guideFolderEntryPointRule = {
4786
4951
  if (!docsRoot) return;
4787
4952
  const docs = getProjectDocs(docsRoot);
4788
4953
  const guideFolders = new Set(
4789
- docs.filter((doc) => ["rules", "references"].includes(path41.basename(path41.dirname(doc.filePath)))).map((doc) => path41.dirname(path41.dirname(doc.filePath))).filter((folder) => path41.resolve(folder) !== path41.resolve(docsRoot))
4954
+ docs.filter((doc) => ["rules", "references"].includes(path44.basename(path44.dirname(doc.filePath)))).map((doc) => path44.dirname(path44.dirname(doc.filePath))).filter((folder) => path44.resolve(folder) !== path44.resolve(docsRoot))
4790
4955
  );
4791
- const currentDir = path41.dirname(filename);
4956
+ const currentDir = path44.dirname(filename);
4792
4957
  if (guideFolders.has(currentDir)) {
4793
- const expectedEntryPoint = `${path41.basename(currentDir)}.md`;
4958
+ const expectedEntryPoint = `${path44.basename(currentDir)}.md`;
4794
4959
  const hasEntryPoint = docs.some(
4795
- (doc) => doc.kind === "guide" && path41.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
4960
+ (doc) => doc.kind === "guide" && path44.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
4796
4961
  );
4797
4962
  if (!hasEntryPoint) {
4798
4963
  context.report({
@@ -5017,7 +5182,7 @@ var noNestedHowToRule = {
5017
5182
 
5018
5183
  // eslint/rules/documentation/glossary-term-linking.ts
5019
5184
  import { readFileSync as readFileSync7 } from "fs";
5020
- import path42 from "path";
5185
+ import path45 from "path";
5021
5186
  function extractGlossaryTerms(filePath) {
5022
5187
  const content = readFileSync7(filePath, "utf8");
5023
5188
  const terms = [];
@@ -5071,9 +5236,9 @@ var glossaryTermLinkingRule = {
5071
5236
  const docsRoot = findDocsRoot(filename);
5072
5237
  if (!docsRoot) return;
5073
5238
  const docs = getProjectDocs(docsRoot);
5074
- const guideDir = path42.dirname(filename);
5239
+ const guideDir = path45.dirname(filename);
5075
5240
  const guideReferences = docs.filter(
5076
- (doc) => doc.kind === "reference" && path42.dirname(doc.filePath) === guideDir
5241
+ (doc) => doc.kind === "reference" && path45.dirname(doc.filePath) === guideDir
5077
5242
  );
5078
5243
  if (guideReferences.length === 0) return;
5079
5244
  const glossaryTerms = [];
@@ -5098,7 +5263,7 @@ var glossaryTermLinkingRule = {
5098
5263
 
5099
5264
  // eslint/rules/documentation/guide-mentions-documents.ts
5100
5265
  import { existsSync as existsSync3 } from "fs";
5101
- import path43 from "path";
5266
+ import path46 from "path";
5102
5267
  function visitSteps3(node, check) {
5103
5268
  if (node.type === "list" && node.ordered) {
5104
5269
  for (const child of node.children) check(child);
@@ -5131,12 +5296,12 @@ var guideMentionsDocumentsRule = {
5131
5296
  if (!filename.endsWith("-guide.md")) return;
5132
5297
  const docsRoot = findDocsRoot(filename);
5133
5298
  if (!docsRoot) return;
5134
- const guideDir = path43.dirname(filename);
5135
- if (path43.basename(filename, ".md") !== path43.basename(guideDir)) return;
5299
+ const guideDir = path46.dirname(filename);
5300
+ if (path46.basename(filename, ".md") !== path46.basename(guideDir)) return;
5136
5301
  const docs = getProjectDocs(docsRoot);
5137
5302
  const owned = docs.filter((doc) => {
5138
- const parent = path43.dirname(doc.filePath);
5139
- return parent === path43.join(guideDir, "rules") || parent === path43.join(guideDir, "references");
5303
+ const parent = path46.dirname(doc.filePath);
5304
+ return parent === path46.join(guideDir, "rules") || parent === path46.join(guideDir, "references");
5140
5305
  });
5141
5306
  const allLinks = [];
5142
5307
  collectMarkdownLinks(node, allLinks);
@@ -5163,7 +5328,7 @@ var guideMentionsDocumentsRule = {
5163
5328
  for (const link of allLinks) {
5164
5329
  const target = linkTarget(link.url);
5165
5330
  if (!target.endsWith(".md")) continue;
5166
- const resolved = path43.normalize(path43.join(guideDir, target));
5331
+ const resolved = path46.normalize(path46.join(guideDir, target));
5167
5332
  if (!existsSync3(resolved)) {
5168
5333
  context.report({
5169
5334
  node: link,
@@ -5713,11 +5878,11 @@ var themeVariableNamespaceRule = {
5713
5878
 
5714
5879
  // eslint/rules/tailwind/css-entry-point.ts
5715
5880
  import { statSync as statSync6 } from "fs";
5716
- import path46 from "path";
5881
+ import path49 from "path";
5717
5882
 
5718
5883
  // eslint/rules/tailwind/source-files.ts
5719
5884
  import { readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
5720
- import path44 from "path";
5885
+ import path47 from "path";
5721
5886
  var CSS_EXTENSIONS = [".css"];
5722
5887
  var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
5723
5888
  var SOURCE_EXTENSIONS = [...MODULE_EXTENSIONS3, ...CSS_EXTENSIONS];
@@ -5730,7 +5895,7 @@ function findFiles(dir, extensions) {
5730
5895
  }
5731
5896
  return entries.flatMap((entry) => {
5732
5897
  if (entry.startsWith(".") || entry === "node_modules") return [];
5733
- const entryPath = path44.join(dir, entry);
5898
+ const entryPath = path47.join(dir, entry);
5734
5899
  let stats;
5735
5900
  try {
5736
5901
  stats = statSync5(entryPath);
@@ -5738,7 +5903,7 @@ function findFiles(dir, extensions) {
5738
5903
  return [];
5739
5904
  }
5740
5905
  if (stats.isDirectory()) return findFiles(entryPath, extensions);
5741
- return extensions.includes(path44.extname(entry)) ? [entryPath] : [];
5906
+ return extensions.includes(path47.extname(entry)) ? [entryPath] : [];
5742
5907
  });
5743
5908
  }
5744
5909
  function cachedTextReader() {
@@ -5761,7 +5926,7 @@ function escapeRegExp(text) {
5761
5926
  }
5762
5927
 
5763
5928
  // eslint/rules/tailwind/stylesheet-graph.ts
5764
- import path45 from "path";
5929
+ import path48 from "path";
5765
5930
  function registersTailwind(text) {
5766
5931
  return /@import\s+(?:url\(\s*)?["']tailwindcss["']\s*\)?/i.test(text);
5767
5932
  }
@@ -5775,25 +5940,25 @@ function moduleImports(text, fileName) {
5775
5940
  return new RegExp(`(?:import|require)\\s*\\(?\\s*["'][^"']*${escaped}["']`, "i").test(text);
5776
5941
  }
5777
5942
  function resolveSpecifier2(fromFile, spec, sourceRoot) {
5778
- if (spec.startsWith("/")) return path45.resolve(spec);
5779
- if (spec.startsWith("./") || spec.startsWith("../")) return path45.resolve(path45.dirname(fromFile), spec);
5780
- if (spec.startsWith("@/")) return path45.resolve(sourceRoot, spec.slice(2));
5943
+ if (spec.startsWith("/")) return path48.resolve(spec);
5944
+ if (spec.startsWith("./") || spec.startsWith("../")) return path48.resolve(path48.dirname(fromFile), spec);
5945
+ if (spec.startsWith("@/")) return path48.resolve(sourceRoot, spec.slice(2));
5781
5946
  return void 0;
5782
5947
  }
5783
5948
  function buildStylesheetGraph(options) {
5784
5949
  const { cssFiles, sourceRoot, textOf } = options;
5785
- const cssSet = new Set(cssFiles.map((file) => path45.normalize(file)));
5950
+ const cssSet = new Set(cssFiles.map((file) => path48.normalize(file)));
5786
5951
  const globals = cssFiles.filter((file) => registersTailwind(textOf(file)));
5787
5952
  const reachable = /* @__PURE__ */ new Set();
5788
5953
  const queue = [...globals];
5789
- for (const global of globals) reachable.add(path45.normalize(global));
5954
+ for (const global of globals) reachable.add(path48.normalize(global));
5790
5955
  while (queue.length > 0) {
5791
5956
  const from = queue.shift();
5792
5957
  if (!from) continue;
5793
5958
  for (const spec of importedSpecifiers(textOf(from))) {
5794
5959
  const target = resolveSpecifier2(from, spec, sourceRoot);
5795
5960
  if (!target) continue;
5796
- const normalized = path45.normalize(target);
5961
+ const normalized = path48.normalize(target);
5797
5962
  if (cssSet.has(normalized) && !reachable.has(normalized)) {
5798
5963
  reachable.add(normalized);
5799
5964
  queue.push(normalized);
@@ -5805,7 +5970,7 @@ function buildStylesheetGraph(options) {
5805
5970
  for (const spec of importedSpecifiers(textOf(global))) {
5806
5971
  const target = resolveSpecifier2(global, spec, sourceRoot);
5807
5972
  if (!target) continue;
5808
- const normalized = path45.normalize(target);
5973
+ const normalized = path48.normalize(target);
5809
5974
  if (cssSet.has(normalized)) directChildren.add(normalized);
5810
5975
  }
5811
5976
  }
@@ -5838,7 +6003,7 @@ var cssEntryPointRule = {
5838
6003
  return {
5839
6004
  "StyleSheet:exit"(node) {
5840
6005
  if (globals.length === 0) return;
5841
- const current = path46.normalize(path46.resolve(context.filename));
6006
+ const current = path49.normalize(path49.resolve(context.filename));
5842
6007
  if (globals.includes(current)) {
5843
6008
  if (globals.length > 1) {
5844
6009
  context.report({
@@ -5847,7 +6012,7 @@ var cssEntryPointRule = {
5847
6012
  });
5848
6013
  return;
5849
6014
  }
5850
- const basename = path46.basename(current);
6015
+ const basename = path49.basename(current);
5851
6016
  const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
5852
6017
  if (importCount !== 1) {
5853
6018
  context.report({
@@ -6176,7 +6341,7 @@ var nextjsStackRule = {
6176
6341
 
6177
6342
  // eslint/rules/package-json/vitest-coverage.ts
6178
6343
  import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
6179
- import path47 from "path";
6344
+ import path50 from "path";
6180
6345
  function memberName4(member) {
6181
6346
  return member.name.type === "String" ? member.name.value : member.name.name;
6182
6347
  }
@@ -6245,7 +6410,7 @@ var vitestCoverageRule = {
6245
6410
  message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
6246
6411
  });
6247
6412
  }
6248
- const configName = VITEST_CONFIG_NAMES.find((name) => existsSync4(path47.join(context.cwd, name)));
6413
+ const configName = VITEST_CONFIG_NAMES.find((name) => existsSync4(path50.join(context.cwd, name)));
6249
6414
  if (!configName) {
6250
6415
  context.report({
6251
6416
  node,
@@ -6253,7 +6418,7 @@ var vitestCoverageRule = {
6253
6418
  });
6254
6419
  return;
6255
6420
  }
6256
- const content = readFileSync9(path47.join(context.cwd, configName), "utf8");
6421
+ const content = readFileSync9(path50.join(context.cwd, configName), "utf8");
6257
6422
  for (const metric of THRESHOLD_METRICS) {
6258
6423
  if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
6259
6424
  context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
@@ -6300,7 +6465,7 @@ var nextjsPackageJsonRules = {
6300
6465
 
6301
6466
  // eslint/rules/husky/husky-hook.ts
6302
6467
  import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
6303
- import path48 from "path";
6468
+ import path51 from "path";
6304
6469
  var VITEST_CONFIG_NAMES2 = [
6305
6470
  "vitest.config.ts",
6306
6471
  "vitest.config.mts",
@@ -6326,7 +6491,7 @@ var huskyHookRule = {
6326
6491
  const root = node.body;
6327
6492
  if (root.type !== "Object") return;
6328
6493
  if (!context.filename.endsWith("package.json")) return;
6329
- const hookPath = path48.join(context.cwd, ".husky", "pre-commit");
6494
+ const hookPath = path51.join(context.cwd, ".husky", "pre-commit");
6330
6495
  if (!existsSync5(hookPath)) {
6331
6496
  context.report({
6332
6497
  node,
@@ -6350,7 +6515,7 @@ var huskyHookRule = {
6350
6515
  }
6351
6516
  requireNamedScript("typecheck");
6352
6517
  requireNamedScript("test:unit:coverage");
6353
- const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync5(path48.join(context.cwd, name)));
6518
+ const vitestConfigName = VITEST_CONFIG_NAMES2.find((name) => existsSync5(path51.join(context.cwd, name)));
6354
6519
  if (vitestConfigName !== void 0) {
6355
6520
  const coverageIndex = content.indexOf("npm run test:unit:coverage");
6356
6521
  const localAddIndex = content.indexOf(`git add ${vitestConfigName}`);
@@ -6364,7 +6529,7 @@ var huskyHookRule = {
6364
6529
  if (!content.includes("libyear --limit-major-individual=1")) {
6365
6530
  context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
6366
6531
  }
6367
- const suppressionsPath = path48.join(context.cwd, "eslint-suppressions.json");
6532
+ const suppressionsPath = path51.join(context.cwd, "eslint-suppressions.json");
6368
6533
  if (existsSync5(suppressionsPath)) {
6369
6534
  requireNamedScript("lint:prune");
6370
6535
  const pruneIndex = content.indexOf("npm run lint:prune");
@@ -6437,7 +6602,7 @@ var vulykDependencyRule = {
6437
6602
 
6438
6603
  // eslint/rules/vulyk/vulyk-docs.ts
6439
6604
  import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
6440
- import path49 from "path";
6605
+ import path52 from "path";
6441
6606
  var PASIKA_REPO = "Bredansky/pasika";
6442
6607
  var BASE_REQUIRED_DOCS = [
6443
6608
  { name: "documentation-guide", path: "docs/documentation-guide" },
@@ -6470,8 +6635,8 @@ var vulykDocsRule = {
6470
6635
  if (!context.filename.endsWith("package.json")) return;
6471
6636
  const root = node.body;
6472
6637
  if (root.type !== "Object") return;
6473
- const projectRoot = path49.dirname(path49.resolve(context.filename));
6474
- const configPath = path49.join(projectRoot, "vulyk.config.ts");
6638
+ const projectRoot = path52.dirname(path52.resolve(context.filename));
6639
+ const configPath = path52.join(projectRoot, "vulyk.config.ts");
6475
6640
  if (!existsSync6(configPath)) {
6476
6641
  context.report({
6477
6642
  node,
@@ -6496,7 +6661,7 @@ var vulykDocsRule = {
6496
6661
  });
6497
6662
  }
6498
6663
  }
6499
- const agentsPath = path49.join(projectRoot, "AGENTS.md");
6664
+ const agentsPath = path52.join(projectRoot, "AGENTS.md");
6500
6665
  if (!existsSync6(agentsPath)) {
6501
6666
  context.report({
6502
6667
  node,
@@ -6528,12 +6693,15 @@ var pasikaNextjsAppRules = {
6528
6693
  "enforce-barrel-exports": enforceBarrelExportsRule,
6529
6694
  "config-extraction": configExtractionRule,
6530
6695
  "value-extraction": valueExtractionRule,
6696
+ "constant-casing": constantCasingRule,
6531
6697
  "type-extraction": typeExtractionRule,
6532
6698
  "zod-schema-validation": zodSchemaValidationRule,
6699
+ "schema-casing": schemaCasingRule,
6533
6700
  "source-under-src": sourceUnderSrcRule,
6534
6701
  "zirka-baseline": zirkaBaselineRule,
6535
6702
  // Next.js/React application rules.
6536
6703
  "component-placement": componentPlacementRule,
6704
+ "component-casing": componentCasingRule,
6537
6705
  "application-structure": applicationStructureRule,
6538
6706
  "data-testid-case": dataTestIdCaseRule,
6539
6707
  "jsx-hygiene": jsxHygieneRule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.7.7",
3
+ "version": "0.8.1",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",