pasika 0.10.30 → 0.10.32

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.
@@ -110,12 +110,11 @@ declare const tailwindRules: {
110
110
  "stylesheet-ordering": _eslint_css.CSSRuleDefinition;
111
111
  "css-variable-naming": _eslint_css.CSSRuleDefinition;
112
112
  "custom-utility-apply": _eslint_css.CSSRuleDefinition;
113
- "skin-utility": _eslint_css.CSSRuleDefinition;
113
+ "utility-mapping": _eslint_css.CSSRuleDefinition;
114
114
  "theme-variable-namespace": _eslint_css.CSSRuleDefinition;
115
115
  "css-entry-point": _eslint_css.CSSRuleDefinition;
116
116
  "global-stylesheet": _eslint_css.CSSRuleDefinition;
117
117
  "unused-utility": _eslint_css.CSSRuleDefinition;
118
- "single-use-utility": _eslint_css.CSSRuleDefinition;
119
118
  "global-selector-styling": _eslint_css.CSSRuleDefinition;
120
119
  };
121
120
 
@@ -162,12 +161,11 @@ declare const pasikaPlugin: {
162
161
  "stylesheet-ordering": _eslint_css.CSSRuleDefinition;
163
162
  "css-variable-naming": _eslint_css.CSSRuleDefinition;
164
163
  "custom-utility-apply": _eslint_css.CSSRuleDefinition;
165
- "skin-utility": _eslint_css.CSSRuleDefinition;
164
+ "utility-mapping": _eslint_css.CSSRuleDefinition;
166
165
  "theme-variable-namespace": _eslint_css.CSSRuleDefinition;
167
166
  "css-entry-point": _eslint_css.CSSRuleDefinition;
168
167
  "global-stylesheet": _eslint_css.CSSRuleDefinition;
169
168
  "unused-utility": _eslint_css.CSSRuleDefinition;
170
- "single-use-utility": _eslint_css.CSSRuleDefinition;
171
169
  "global-selector-styling": _eslint_css.CSSRuleDefinition;
172
170
  "doc-kind-suffix": _eslint_markdown.MarkdownRuleDefinition;
173
171
  "title-matches-file-name": _eslint_markdown.MarkdownRuleDefinition;
@@ -222,6 +220,7 @@ declare const pasikaPlugin: {
222
220
  "ui-state": eslint.Rule.RuleModule;
223
221
  "no-mixed-concerns": eslint.Rule.RuleModule;
224
222
  "no-arbitrary-tailwind": eslint.Rule.RuleModule;
223
+ "no-literal-inline-style": eslint.Rule.RuleModule;
225
224
  "unknown-utility": eslint.Rule.RuleModule;
226
225
  "enforce-cn-merge": {
227
226
  meta: {
@@ -255,7 +254,6 @@ declare const pasikaPlugin: {
255
254
  "locale-placement": eslint.Rule.RuleModule;
256
255
  "sole-state-owner": eslint.Rule.RuleModule;
257
256
  "locale-key-shape": eslint.Rule.RuleModule;
258
- "shared-style-dedup": eslint.Rule.RuleModule;
259
257
  "repeated-structure": eslint.Rule.RuleModule;
260
258
  };
261
259
  };
@@ -727,6 +727,74 @@ var noArbitraryTailwindRule = {
727
727
  }
728
728
  };
729
729
 
730
+ // eslint/rules/no-literal-inline-style.ts
731
+ function isStaticValue(value) {
732
+ if (value.type === "Literal") {
733
+ return typeof value.value === "string" || typeof value.value === "number" || typeof value.value === "boolean";
734
+ }
735
+ if (value.type === "TemplateLiteral") {
736
+ return value.expressions.length === 0;
737
+ }
738
+ if (value.type === "UnaryExpression" && (value.operator === "-" || value.operator === "+") && value.argument.type === "Literal") {
739
+ return true;
740
+ }
741
+ if (value.type === "ConditionalExpression") {
742
+ return isStaticValue(value.consequent) || isStaticValue(value.alternate);
743
+ }
744
+ if (value.type === "LogicalExpression") {
745
+ return isStaticValue(value.left) || isStaticValue(value.right);
746
+ }
747
+ return false;
748
+ }
749
+ function propertyName(property) {
750
+ if (!property.computed && property.key.type === "Identifier") return property.key.name;
751
+ if (property.key.type === "Literal" && typeof property.key.value === "string") return property.key.value;
752
+ return void 0;
753
+ }
754
+ function findStaticDeclaration(expression) {
755
+ for (const property of expression.properties) {
756
+ if (property.type !== "Property" || property.kind !== "init") continue;
757
+ const name = propertyName(property);
758
+ if (!name) continue;
759
+ if (isStaticValue(property.value)) return name;
760
+ }
761
+ return void 0;
762
+ }
763
+ function findStaticStyle(expression) {
764
+ if (expression.type === "ObjectExpression") return findStaticDeclaration(expression);
765
+ if (expression.type === "ConditionalExpression") {
766
+ return findStaticStyle(expression.consequent) ?? findStaticStyle(expression.alternate);
767
+ }
768
+ if (expression.type === "LogicalExpression") {
769
+ return findStaticStyle(expression.left) ?? findStaticStyle(expression.right);
770
+ }
771
+ return void 0;
772
+ }
773
+ var noLiteralInlineStyleRule = {
774
+ meta: {
775
+ schema: [],
776
+ type: "problem",
777
+ docs: {
778
+ description: "Disallow static CSS declarations in JSX style attributes."
779
+ }
780
+ },
781
+ create(context) {
782
+ return {
783
+ JSXAttribute(node) {
784
+ if (node.name?.name !== "style" || node.value?.type !== "JSXExpressionContainer") return;
785
+ const expression = node.value.expression;
786
+ if (!expression) return;
787
+ const name = findStaticStyle(expression);
788
+ if (!name) return;
789
+ context.report({
790
+ node,
791
+ message: `Static inline style "${name}" should use Tailwind. Keep inline styles for runtime-sourced values. See docs/next-tailwind-guide/rules/inline-style-rule.md`
792
+ });
793
+ }
794
+ };
795
+ }
796
+ };
797
+
730
798
  // eslint/rules/unknown-utility.ts
731
799
  import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
732
800
  import { createRequire as createRequire2 } from "module";
@@ -4807,67 +4875,6 @@ var localeKeyShapeRule = {
4807
4875
  }
4808
4876
  };
4809
4877
 
4810
- // eslint/rules/shared-style-dedup.ts
4811
- import path40 from "path";
4812
- import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
4813
- var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
4814
- var comboCache;
4815
- function combosFor(index) {
4816
- const files = [...index.modules.keys()].filter((file) => file.endsWith(".tsx") || file.endsWith(".jsx")).sort((left, right) => left.localeCompare(right));
4817
- let fingerprint2 = "";
4818
- for (const file of files) {
4819
- fingerprint2 += String(statSync3(file).mtimeMs);
4820
- }
4821
- if (comboCache?.sourceRoot === index.sourceRoot && comboCache.fingerprint === fingerprint2) {
4822
- return comboCache.combos;
4823
- }
4824
- const combos = /* @__PURE__ */ new Map();
4825
- for (const file of files) {
4826
- const text = readFileSync6(file, "utf8");
4827
- for (const match of text.matchAll(CLASS_NAME)) {
4828
- const classes = (match.groups?.classes ?? "").split(/\s+/).filter(Boolean);
4829
- if (classes.length < 2) continue;
4830
- const key = [...classes].sort().join(" ");
4831
- const users = combos.get(key) ?? /* @__PURE__ */ new Set();
4832
- users.add(file);
4833
- combos.set(key, users);
4834
- }
4835
- }
4836
- comboCache = { sourceRoot: index.sourceRoot, fingerprint: fingerprint2, combos };
4837
- return combos;
4838
- }
4839
- var sharedStyleDedupRule = {
4840
- meta: {
4841
- schema: [],
4842
- type: "problem",
4843
- docs: {
4844
- description: "Require a className combination used by two or more components to become a named *-skin utility."
4845
- }
4846
- },
4847
- create(context) {
4848
- const sourceRoot = sourceRootOf(context);
4849
- const file = path40.resolve(context.filename);
4850
- const segments = segmentsOf(file, sourceRoot);
4851
- if (segments.length === 0) return {};
4852
- const index = getProjectIndex(sourceRoot);
4853
- if (!index) return {};
4854
- const combos = combosFor(index);
4855
- const sharedHere = [...combos.entries()].filter(([, users]) => users.size >= 2 && users.has(file)).filter(([, users]) => [...users].sort((left, right) => left.localeCompare(right))[0] === file);
4856
- if (sharedHere.length === 0) return {};
4857
- return {
4858
- Program(node) {
4859
- for (const [combo, users] of sharedHere) {
4860
- context.report({
4861
- node,
4862
- loc: { line: 1, column: 0 },
4863
- message: `Class combination "${combo}" is used by ${String(users.size)} components and should change together; create a named *-skin Tailwind utility for it. See docs/next-tailwind-guide/rules/theme-and-utility-definition-rule.md`
4864
- });
4865
- }
4866
- }
4867
- };
4868
- }
4869
- };
4870
-
4871
4878
  // eslint/rules/repeated-structure.ts
4872
4879
  function nodeKind(node) {
4873
4880
  return node.type;
@@ -4936,6 +4943,11 @@ function isSubstantial(node) {
4936
4943
  function blockChildren(node) {
4937
4944
  return (node.children ?? []).filter(isJsxNode);
4938
4945
  }
4946
+ function structuralChildren(node) {
4947
+ return (node.children ?? []).filter(
4948
+ (child) => isJsxNode(child) || isJsxExpressionContainer(child)
4949
+ );
4950
+ }
4939
4951
  var repeatedStructureRule = {
4940
4952
  meta: {
4941
4953
  schema: [],
@@ -4955,6 +4967,10 @@ var repeatedStructureRule = {
4955
4967
  }
4956
4968
  };
4957
4969
  function checkChildren(node) {
4970
+ checkSubstantialBlocks(node);
4971
+ checkSmallElementExpressionPairs(node);
4972
+ }
4973
+ function checkSubstantialBlocks(node) {
4958
4974
  const children = blockChildren(node);
4959
4975
  const seen = /* @__PURE__ */ new Map();
4960
4976
  for (const child of children) {
@@ -4967,12 +4983,36 @@ var repeatedStructureRule = {
4967
4983
  if (group.length < 2) continue;
4968
4984
  const first = group[0];
4969
4985
  if (!first || !isSubstantial(first)) continue;
4970
- context.report({
4971
- node: first,
4972
- message: `The same arrangement of elements appears ${String(group.length)} times here; extract it as a named component. Different data or labels do not prevent extraction. See docs/next-codebase-guide/rules/repeated-structure-rule.md`
4973
- });
4986
+ reportRepeatedStructure(first, group.length);
4974
4987
  }
4975
4988
  }
4989
+ function checkSmallElementExpressionPairs(node) {
4990
+ const children = structuralChildren(node);
4991
+ const seen = /* @__PURE__ */ new Map();
4992
+ for (let index = 0; index < children.length - 1; index += 1) {
4993
+ const first = children[index];
4994
+ const second = children[index + 1];
4995
+ if (!first || !second || !isJsxNode(first) || !isJsxExpressionContainer(second) || isSubstantial(first)) {
4996
+ continue;
4997
+ }
4998
+ const sig = `${signature(first)}/{}`;
4999
+ const group = seen.get(sig) ?? [];
5000
+ group.push(first);
5001
+ seen.set(sig, group);
5002
+ }
5003
+ for (const [, group] of seen) {
5004
+ if (group.length < 2) continue;
5005
+ const first = group[0];
5006
+ if (!first) continue;
5007
+ reportRepeatedStructure(first, group.length);
5008
+ }
5009
+ }
5010
+ function reportRepeatedStructure(node, count) {
5011
+ context.report({
5012
+ node,
5013
+ message: `The same arrangement of elements appears ${String(count)} times here; extract it as a named component. Different data or labels do not prevent extraction. See docs/next-codebase-guide/rules/repeated-structure-rule.md`
5014
+ });
5015
+ }
4976
5016
  }
4977
5017
  };
4978
5018
 
@@ -5050,7 +5090,7 @@ var zodSchemaValidationRule = {
5050
5090
  };
5051
5091
 
5052
5092
  // eslint/rules/schema-casing.ts
5053
- import path41 from "path";
5093
+ import path40 from "path";
5054
5094
  function isCamelCase(name) {
5055
5095
  return /^[a-z][a-zA-Z0-9]*$/.test(name);
5056
5096
  }
@@ -5078,9 +5118,9 @@ var schemaCasingRule = {
5078
5118
  }
5079
5119
  },
5080
5120
  create(context) {
5081
- const filename = path41.resolve(context.filename);
5121
+ const filename = path40.resolve(context.filename);
5082
5122
  const sourceRoot = sourceRootOf(context);
5083
- if (!filename.startsWith(sourceRoot + path41.sep)) return {};
5123
+ if (!filename.startsWith(sourceRoot + path40.sep)) return {};
5084
5124
  let zodLocalName;
5085
5125
  return {
5086
5126
  ImportDeclaration(node) {
@@ -5112,7 +5152,7 @@ var schemaCasingRule = {
5112
5152
  };
5113
5153
 
5114
5154
  // eslint/rules/component-casing.ts
5115
- import path42 from "path";
5155
+ import path41 from "path";
5116
5156
  function isPascalCase5(name) {
5117
5157
  return /^[A-Z][A-Za-z0-9]*$/.test(name);
5118
5158
  }
@@ -5125,10 +5165,10 @@ var componentCasingRule = {
5125
5165
  }
5126
5166
  },
5127
5167
  create(context) {
5128
- const filename = path42.resolve(context.filename);
5168
+ const filename = path41.resolve(context.filename);
5129
5169
  const sourceRoot = sourceRootOf(context);
5130
- if (!filename.startsWith(sourceRoot + path42.sep)) return {};
5131
- if (path42.extname(filename) !== ".tsx") return {};
5170
+ if (!filename.startsWith(sourceRoot + path41.sep)) return {};
5171
+ if (path41.extname(filename) !== ".tsx") return {};
5132
5172
  return {
5133
5173
  Program(node) {
5134
5174
  for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
@@ -5145,7 +5185,7 @@ var componentCasingRule = {
5145
5185
  };
5146
5186
 
5147
5187
  // eslint/rules/source-under-src.ts
5148
- import path43 from "path";
5188
+ import path42 from "path";
5149
5189
  var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
5150
5190
  ".agents",
5151
5191
  ".cache",
@@ -5186,14 +5226,14 @@ var sourceUnderSrcRule = {
5186
5226
  }
5187
5227
  },
5188
5228
  create(context) {
5189
- const filename = path43.resolve(context.filename);
5229
+ const filename = path42.resolve(context.filename);
5190
5230
  if (!MODULE_EXTENSION.test(filename)) return {};
5191
- const relative = path43.relative(context.cwd, filename).replace(/\\/g, "/");
5231
+ const relative = path42.relative(context.cwd, filename).replace(/\\/g, "/");
5192
5232
  if (relative === "src" || relative.startsWith("src/")) return {};
5193
5233
  const topLevel = relative.split("/")[0] ?? "";
5194
5234
  if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
5195
5235
  if (!relative.includes("/")) {
5196
- const basename = path43.basename(filename);
5236
+ const basename = path42.basename(filename);
5197
5237
  if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
5198
5238
  }
5199
5239
  return {
@@ -5210,7 +5250,7 @@ var sourceUnderSrcRule = {
5210
5250
 
5211
5251
  // eslint/rules/zirka-baseline.ts
5212
5252
  import fs6 from "fs";
5213
- import path44 from "path";
5253
+ import path43 from "path";
5214
5254
  var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
5215
5255
  var PRETTIER_CONFIGS = [
5216
5256
  "prettier.config.mjs",
@@ -5229,10 +5269,10 @@ var zirkaBaselineRule = {
5229
5269
  }
5230
5270
  },
5231
5271
  create(context) {
5232
- const filename = path44.resolve(context.filename);
5233
- const basename = path44.basename(filename);
5272
+ const filename = path43.resolve(context.filename);
5273
+ const basename = path43.basename(filename);
5234
5274
  if (!ESLINT_CONFIG.test(basename)) return {};
5235
- const projectRoot = path44.dirname(filename);
5275
+ const projectRoot = path43.dirname(filename);
5236
5276
  const report3 = (message) => {
5237
5277
  context.report({
5238
5278
  node: context.sourceCode.ast,
@@ -5247,7 +5287,7 @@ var zirkaBaselineRule = {
5247
5287
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
5248
5288
  );
5249
5289
  }
5250
- const tsconfigPath = path44.join(projectRoot, "tsconfig.json");
5290
+ const tsconfigPath = path43.join(projectRoot, "tsconfig.json");
5251
5291
  if (!fs6.existsSync(tsconfigPath)) {
5252
5292
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
5253
5293
  } else {
@@ -5265,13 +5305,13 @@ var zirkaBaselineRule = {
5265
5305
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
5266
5306
  }
5267
5307
  }
5268
- const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs6.existsSync(path44.join(projectRoot, name)));
5308
+ const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs6.existsSync(path43.join(projectRoot, name)));
5269
5309
  if (!prettierConfigFile) {
5270
5310
  report3(
5271
5311
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
5272
5312
  );
5273
5313
  } else {
5274
- const content = fs6.readFileSync(path44.join(projectRoot, prettierConfigFile), "utf8");
5314
+ const content = fs6.readFileSync(path43.join(projectRoot, prettierConfigFile), "utf8");
5275
5315
  if (!content.includes("zirka")) {
5276
5316
  report3(
5277
5317
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -5291,6 +5331,9 @@ function referencesZirka(sourceCode) {
5291
5331
  return /require\(\s*["']zirka["']\s*\)/.test(sourceCode.getText());
5292
5332
  }
5293
5333
 
5334
+ // eslint/rules/documentation/helpers.ts
5335
+ import path44 from "path";
5336
+
5294
5337
  // constants/rfc2119.ts
5295
5338
  var RFC_2119_KEYWORDS = ["MUST NOT", "MUST", "SHOULD NOT", "SHOULD", "MAY"];
5296
5339
  var RFC_2119_PATTERN = new RegExp(`\\b(?<keyword>${RFC_2119_KEYWORDS.join("|")})\\b`);
@@ -5303,6 +5346,13 @@ function containsRfcKeyword(text) {
5303
5346
  function getFilename(context) {
5304
5347
  return context.filename;
5305
5348
  }
5349
+ function isAgentDocument(filename) {
5350
+ const base = path44.basename(filename);
5351
+ return base === "AGENTS.md" || base === "CLAUDE.md";
5352
+ }
5353
+ function isDocumentationTemplate(filename) {
5354
+ return path44.basename(path44.dirname(filename)) === "_templates";
5355
+ }
5306
5356
  function getTextContent(node) {
5307
5357
  if ("value" in node && typeof node.value === "string") return node.value;
5308
5358
  if ("children" in node) {
@@ -5336,7 +5386,7 @@ var docKindSuffixRule = {
5336
5386
  return {
5337
5387
  root(node) {
5338
5388
  const filename = getFilename(context);
5339
- if (!filename.endsWith(".md")) return;
5389
+ if (!filename.endsWith(".md") || isAgentDocument(filename) || isDocumentationTemplate(filename)) return;
5340
5390
  if (filename.endsWith("-guide.md") || filename.endsWith("-rule.md") || filename.endsWith("-reference.md") || filename.endsWith("-policy.md")) {
5341
5391
  return;
5342
5392
  }
@@ -5367,7 +5417,7 @@ var titleMatchesFileNameRule = {
5367
5417
  heading(node) {
5368
5418
  if (node.depth !== 1) return;
5369
5419
  const filename = getFilename(context);
5370
- if (!filename.endsWith(".md")) return;
5420
+ if (!filename.endsWith(".md") || isAgentDocument(filename) || isDocumentationTemplate(filename)) return;
5371
5421
  const title = getTextContent(node).trim();
5372
5422
  const expectedFileName = toExpectedFileName(title);
5373
5423
  const actualFileName = path45.basename(filename);
@@ -5647,12 +5697,16 @@ var rulePairedExamplesRule = {
5647
5697
  if (!filename.endsWith("-rule.md")) return;
5648
5698
  const exampleHeadings = [];
5649
5699
  for (const child of node.children) {
5650
- if (child.type === "heading" && child.depth === 2) {
5651
- const text = getTextContent(child).trim();
5652
- if (/^(?:Incorrect|Correct)\b/.test(text)) {
5653
- exampleHeadings.push({ line: getLine(child), text });
5654
- }
5700
+ if (child.type !== "heading" || child.depth !== 2) continue;
5701
+ const text = getTextContent(child).trim();
5702
+ if (/^(?:Incorrect|Correct)\b/.test(text)) {
5703
+ exampleHeadings.push({ line: getLine(child), text });
5704
+ continue;
5655
5705
  }
5706
+ context.report({
5707
+ node: child,
5708
+ message: `Rule level-2 heading must be an Incorrect/Correct example: ${text}`
5709
+ });
5656
5710
  }
5657
5711
  const incorrect = exampleHeadings.filter((h) => h.text.startsWith("Incorrect"));
5658
5712
  const correct = exampleHeadings.filter((h) => h.text.startsWith("Correct"));
@@ -5793,7 +5847,7 @@ var referenceBlockHeadingsRule = {
5793
5847
  return {
5794
5848
  root(node) {
5795
5849
  const filename = getFilename(context);
5796
- if (!filename.endsWith("-reference.md")) return;
5850
+ if (!filename.endsWith("-reference.md") || isDocumentationTemplate(filename)) return;
5797
5851
  const sectionHeadings = [];
5798
5852
  for (const child of node.children) {
5799
5853
  if (child.type === "heading" && child.depth >= 2) {
@@ -5867,7 +5921,7 @@ var supportDocumentPlacementRule = {
5867
5921
  return {
5868
5922
  root(node) {
5869
5923
  const filename = getFilename(context);
5870
- if (!filename.endsWith(".md")) return;
5924
+ if (!filename.endsWith(".md") || isDocumentationTemplate(filename)) return;
5871
5925
  let kind;
5872
5926
  if (filename.endsWith("-rule.md")) kind = "rule";
5873
5927
  if (filename.endsWith("-reference.md")) kind = "reference";
@@ -5895,6 +5949,7 @@ var noTemplatePromptRule = {
5895
5949
  }
5896
5950
  },
5897
5951
  create(context) {
5952
+ if (isDocumentationTemplate(getFilename(context))) return {};
5898
5953
  return {
5899
5954
  text(node) {
5900
5955
  if (TEMPLATE_PROMPT.test(node.value)) {
@@ -5912,7 +5967,7 @@ var noTemplatePromptRule = {
5912
5967
  import path48 from "path";
5913
5968
 
5914
5969
  // eslint/rules/documentation/project-index.ts
5915
- import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
5970
+ import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
5916
5971
  import path47 from "path";
5917
5972
  var KIND_BY_SUFFIX = [
5918
5973
  ["-rule.md", "rule"],
@@ -5923,14 +5978,14 @@ var KIND_BY_SUFFIX = [
5923
5978
  function listMarkdownFiles(dir) {
5924
5979
  return readdirSync3(dir).flatMap((entry) => {
5925
5980
  const entryPath = path47.join(dir, entry);
5926
- if (statSync4(entryPath).isDirectory()) {
5981
+ if (statSync3(entryPath).isDirectory()) {
5927
5982
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
5928
5983
  }
5929
5984
  return entry.endsWith(".md") ? [entryPath] : [];
5930
5985
  });
5931
5986
  }
5932
5987
  function extractTitle(filePath) {
5933
- const content = readFileSync7(filePath, "utf8");
5988
+ const content = readFileSync6(filePath, "utf8");
5934
5989
  const match = /^# (?<title>.+)$/m.exec(content);
5935
5990
  return match?.groups?.title?.trim() ?? "";
5936
5991
  }
@@ -5956,7 +6011,7 @@ function getProjectDocs(docsRoot) {
5956
6011
  function findDocsRoot(filePath) {
5957
6012
  let dir = path47.dirname(filePath);
5958
6013
  for (; ; ) {
5959
- if (path47.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
6014
+ if (path47.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
5960
6015
  return dir;
5961
6016
  }
5962
6017
  const parent = path47.dirname(dir);
@@ -6133,7 +6188,7 @@ var policySubjectHeadingsRule = {
6133
6188
  };
6134
6189
 
6135
6190
  // eslint/rules/documentation/guide-link-anchors.ts
6136
- import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
6191
+ import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
6137
6192
  import path49 from "path";
6138
6193
  function visitSteps2(node, check) {
6139
6194
  if (node.type === "list" && node.ordered) {
@@ -6164,7 +6219,7 @@ function documentAnchors(filePath) {
6164
6219
  anchorsByFile.set(filePath, anchors);
6165
6220
  let content;
6166
6221
  try {
6167
- content = readFileSync8(filePath, "utf8");
6222
+ content = readFileSync7(filePath, "utf8");
6168
6223
  } catch {
6169
6224
  return anchors;
6170
6225
  }
@@ -6263,7 +6318,7 @@ var noNestedHowToRule = {
6263
6318
  };
6264
6319
 
6265
6320
  // eslint/rules/documentation/glossary-term-linking.ts
6266
- import { readFileSync as readFileSync9 } from "fs";
6321
+ import { readFileSync as readFileSync8 } from "fs";
6267
6322
  import path50 from "path";
6268
6323
  function isGlossary(filePath) {
6269
6324
  return path50.basename(filePath).includes("glossary");
@@ -6284,7 +6339,7 @@ function extractTableTerms(content) {
6284
6339
  }
6285
6340
  function extractGlossaryTerms(filePath) {
6286
6341
  if (!isGlossary(filePath)) return [];
6287
- const content = readFileSync9(filePath, "utf8");
6342
+ const content = readFileSync8(filePath, "utf8");
6288
6343
  const tableTerms = extractTableTerms(content);
6289
6344
  if (tableTerms.length > 0) return tableTerms;
6290
6345
  const terms = [];
@@ -6298,7 +6353,7 @@ function extractGlossaryTerms(filePath) {
6298
6353
  function extractBlockHeadings(filePath) {
6299
6354
  const headings = [];
6300
6355
  let fenced = false;
6301
- for (const line of readFileSync9(filePath, "utf8").split("\n")) {
6356
+ for (const line of readFileSync8(filePath, "utf8").split("\n")) {
6302
6357
  if (/^\s*(?:```|~~~)/.test(line)) {
6303
6358
  fenced = !fenced;
6304
6359
  continue;
@@ -6608,6 +6663,11 @@ function preludeIdentifiers(node) {
6608
6663
  });
6609
6664
  return names;
6610
6665
  }
6666
+ function rawValue(node) {
6667
+ if (!node) return "";
6668
+ if (node.type === "Raw") return node.value;
6669
+ return "";
6670
+ }
6611
6671
  function rules(node) {
6612
6672
  const result = [];
6613
6673
  walkNodes(node, (current) => {
@@ -6955,39 +7015,40 @@ var customUtilityApplyRule = {
6955
7015
  }
6956
7016
  };
6957
7017
 
6958
- // eslint/rules/tailwind/skin-utility.ts
6959
- var skinUtilityRule = {
7018
+ // eslint/rules/tailwind/utility-mapping.ts
7019
+ function applyOperationCount(utility) {
7020
+ let count = 0;
7021
+ for (const apply of atrulesNamed(utility, "apply")) {
7022
+ const identifiers = preludeIdentifiers(apply);
7023
+ if (identifiers.length > 0) {
7024
+ count += identifiers.length;
7025
+ continue;
7026
+ }
7027
+ const prelude = "prelude" in apply ? apply.prelude : void 0;
7028
+ const raw = prelude ? rawValue(prelude) : "";
7029
+ const rawOperations = raw.trim().split(/\s+/).filter(Boolean);
7030
+ count += rawOperations.length > 0 ? rawOperations.length : 1;
7031
+ }
7032
+ return count;
7033
+ }
7034
+ var utilityMappingRule = {
6960
7035
  meta: {
6961
7036
  schema: [],
6962
7037
  type: "problem",
6963
7038
  docs: {
6964
- description: "Require reusable style combinations to become *-skin utilities."
7039
+ description: "Require custom utilities to map exactly one Tailwind styling operation."
6965
7040
  }
6966
7041
  },
6967
7042
  create(context) {
6968
7043
  return {
6969
7044
  "StyleSheet:exit"(node) {
6970
7045
  for (const utility of atrulesNamed(node, "utility")) {
6971
- const name = preludeIdentifiers(utility)[0];
6972
- const applied = new Set(atrulesNamed(utility, "apply").flatMap((apply) => preludeIdentifiers(apply)));
6973
- if (applied.size < 2 || name?.endsWith("-skin") === true) continue;
7046
+ const name = preludeIdentifiers(utility)[0] ?? "unknown";
7047
+ const operationCount = applyOperationCount(utility);
7048
+ if (operationCount === 1) continue;
6974
7049
  context.report({
6975
7050
  node: utility,
6976
- message: `Custom utility "${name ?? "unknown"}" combines ${String(applied.size)} styles; use the *-skin suffix.`
6977
- });
6978
- }
6979
- const combinations = /* @__PURE__ */ new Map();
6980
- for (const apply of atrulesNamed(node, "apply")) {
6981
- const classes = preludeIdentifiers(apply);
6982
- if (new Set(classes).size < 2) continue;
6983
- const key = [...classes].sort().join(" ");
6984
- combinations.set(key, (combinations.get(key) ?? 0) + 1);
6985
- }
6986
- for (const [combo, count] of combinations) {
6987
- if (count < 2) continue;
6988
- context.report({
6989
- node,
6990
- message: `Combination "${combo}" appears ${String(count)} times. Create a *-skin custom Tailwind utility for it.`
7051
+ message: `Custom utility "${name}" maps ${String(operationCount)} Tailwind operations; custom utilities must map exactly one operation. Reusable presentation belongs in a component.`
6991
7052
  });
6992
7053
  }
6993
7054
  }
@@ -7035,11 +7096,11 @@ var themeVariableNamespaceRule = {
7035
7096
  };
7036
7097
 
7037
7098
  // eslint/rules/tailwind/css-entry-point.ts
7038
- import { statSync as statSync6 } from "fs";
7099
+ import { statSync as statSync5 } from "fs";
7039
7100
  import path54 from "path";
7040
7101
 
7041
7102
  // eslint/rules/tailwind/source-files.ts
7042
- import { readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync5 } from "fs";
7103
+ import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
7043
7104
  import path52 from "path";
7044
7105
  var CSS_EXTENSIONS = [".css"];
7045
7106
  var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
@@ -7056,7 +7117,7 @@ function findFiles(dir, extensions) {
7056
7117
  const entryPath = path52.join(dir, entry);
7057
7118
  let stats;
7058
7119
  try {
7059
- stats = statSync5(entryPath);
7120
+ stats = statSync4(entryPath);
7060
7121
  } catch {
7061
7122
  return [];
7062
7123
  }
@@ -7070,7 +7131,7 @@ function cachedTextReader() {
7070
7131
  let text = texts.get(file);
7071
7132
  if (text === void 0) {
7072
7133
  try {
7073
- text = readFileSync10(file, "utf8");
7134
+ text = readFileSync9(file, "utf8");
7074
7135
  } catch {
7075
7136
  text = "";
7076
7137
  }
@@ -7149,7 +7210,7 @@ var cssEntryPointRule = {
7149
7210
  let cssFiles;
7150
7211
  let moduleFiles;
7151
7212
  try {
7152
- if (!statSync6(sourceRoot).isDirectory()) return {};
7213
+ if (!statSync5(sourceRoot).isDirectory()) return {};
7153
7214
  cssFiles = findFiles(sourceRoot, CSS_EXTENSIONS);
7154
7215
  moduleFiles = findFiles(sourceRoot, MODULE_EXTENSIONS3);
7155
7216
  } catch {
@@ -7237,7 +7298,7 @@ var globalStylesheetRule = {
7237
7298
  };
7238
7299
 
7239
7300
  // eslint/rules/tailwind/unused-utility.ts
7240
- import { statSync as statSync7 } from "fs";
7301
+ import { statSync as statSync6 } from "fs";
7241
7302
  function usagePattern(name) {
7242
7303
  return new RegExp(`(?<![\\w-])${escapeRegExp(name)}(?![\\w-])`);
7243
7304
  }
@@ -7253,7 +7314,7 @@ var unusedUtilityRule = {
7253
7314
  const sourceRoot = sourceRootOf(context);
7254
7315
  let files;
7255
7316
  try {
7256
- if (!statSync7(sourceRoot).isDirectory()) return {};
7317
+ if (!statSync6(sourceRoot).isDirectory()) return {};
7257
7318
  files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
7258
7319
  } catch {
7259
7320
  return {};
@@ -7278,48 +7339,6 @@ var unusedUtilityRule = {
7278
7339
  }
7279
7340
  };
7280
7341
 
7281
- // eslint/rules/tailwind/single-use-utility.ts
7282
- import { statSync as statSync8 } from "fs";
7283
- function usagePattern2(name) {
7284
- return new RegExp(`(?<![\\\\w-])${escapeRegExp(name)}(?![\\\\w-])`);
7285
- }
7286
- var singleUseUtilityRule = {
7287
- meta: {
7288
- schema: [],
7289
- type: "problem",
7290
- docs: {
7291
- description: "Disallow custom utilities referenced by only one source file."
7292
- }
7293
- },
7294
- create(context) {
7295
- const sourceRoot = sourceRootOf(context);
7296
- let files;
7297
- try {
7298
- if (!statSync8(sourceRoot).isDirectory()) return {};
7299
- files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
7300
- } catch {
7301
- return {};
7302
- }
7303
- const textOf = cachedTextReader();
7304
- return {
7305
- "StyleSheet:exit"(node) {
7306
- for (const utility of atrulesNamed(node, "utility")) {
7307
- const name = preludeIdentifiers(utility)[0];
7308
- if (!name) continue;
7309
- const pattern = usagePattern2(name);
7310
- const definition = `@utility ${name}`;
7311
- const consumers = files.filter((file) => pattern.test(textOf(file).replaceAll(definition, "")));
7312
- if (consumers.length !== 1) continue;
7313
- context.report({
7314
- node: utility,
7315
- message: `Custom utility "${name}" is used by only one source file; keep that styling as static Tailwind classes in its consumer.`
7316
- });
7317
- }
7318
- }
7319
- };
7320
- }
7321
- };
7322
-
7323
7342
  // eslint/rules/tailwind/global-selector-styling.ts
7324
7343
  function hasStyling(node) {
7325
7344
  for (const child of blockChildren2(node)) {
@@ -7361,12 +7380,11 @@ var tailwindRules = {
7361
7380
  "stylesheet-ordering": stylesheetOrderingRule,
7362
7381
  "css-variable-naming": cssVariableNamingRule,
7363
7382
  "custom-utility-apply": customUtilityApplyRule,
7364
- "skin-utility": skinUtilityRule,
7383
+ "utility-mapping": utilityMappingRule,
7365
7384
  "theme-variable-namespace": themeVariableNamespaceRule,
7366
7385
  "css-entry-point": cssEntryPointRule,
7367
7386
  "global-stylesheet": globalStylesheetRule,
7368
7387
  "unused-utility": unusedUtilityRule,
7369
- "single-use-utility": singleUseUtilityRule,
7370
7388
  "global-selector-styling": globalSelectorStylingRule
7371
7389
  };
7372
7390
 
@@ -7585,7 +7603,7 @@ var nextjsStackRule = {
7585
7603
  };
7586
7604
 
7587
7605
  // eslint/rules/package-json/vitest-coverage.ts
7588
- import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
7606
+ import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
7589
7607
  import path55 from "path";
7590
7608
  function memberName4(member) {
7591
7609
  return member.name.type === "String" ? member.name.value : member.name.name;
@@ -7663,7 +7681,7 @@ var vitestCoverageRule = {
7663
7681
  });
7664
7682
  return;
7665
7683
  }
7666
- const content = readFileSync11(path55.join(context.cwd, configName), "utf8");
7684
+ const content = readFileSync10(path55.join(context.cwd, configName), "utf8");
7667
7685
  for (const metric of THRESHOLD_METRICS) {
7668
7686
  if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
7669
7687
  context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
@@ -7709,7 +7727,7 @@ var nextjsPackageJsonRules = {
7709
7727
  };
7710
7728
 
7711
7729
  // eslint/rules/husky/husky-hook.ts
7712
- import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
7730
+ import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
7713
7731
  import path56 from "path";
7714
7732
  var VITEST_CONFIG_NAMES2 = [
7715
7733
  "vitest.config.ts",
@@ -7744,7 +7762,7 @@ var huskyHookRule = {
7744
7762
  });
7745
7763
  return;
7746
7764
  }
7747
- const content = readFileSync12(hookPath, "utf8");
7765
+ const content = readFileSync11(hookPath, "utf8");
7748
7766
  const scripts = root.members.find((member) => memberName5(member) === "scripts");
7749
7767
  const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
7750
7768
  const requireNamedScript = (name) => {
@@ -7806,7 +7824,7 @@ var huskyRules = {
7806
7824
  };
7807
7825
 
7808
7826
  // eslint/rules/vulyk/tracked-docs.ts
7809
- import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
7827
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "fs";
7810
7828
  import path57 from "path";
7811
7829
  var PASIKA_REPO = "Bredansky/pasika";
7812
7830
  var BASE_REQUIRED_TRACKED_DOCS = [
@@ -7849,7 +7867,7 @@ var trackedDocsRule = {
7849
7867
  });
7850
7868
  return;
7851
7869
  }
7852
- const config = readFileSync13(configPath, "utf8");
7870
+ const config = readFileSync12(configPath, "utf8");
7853
7871
  if (!config.includes(PASIKA_REPO)) {
7854
7872
  context.report({
7855
7873
  node,
@@ -7956,6 +7974,7 @@ var pasikaNextjsAppRules = {
7956
7974
  "ui-state": uiStateRule,
7957
7975
  "no-mixed-concerns": noMixedConcernsRule,
7958
7976
  "no-arbitrary-tailwind": noArbitraryTailwindRule,
7977
+ "no-literal-inline-style": noLiteralInlineStyleRule,
7959
7978
  "unknown-utility": unknownUtilityRule,
7960
7979
  "enforce-cn-merge": enforceCnMergeRule,
7961
7980
  "cn-helper": cnHelperRule,
@@ -7978,7 +7997,6 @@ var pasikaNextjsAppRules = {
7978
7997
  "locale-placement": localePlacementRule,
7979
7998
  "sole-state-owner": soleStateOwnerRule,
7980
7999
  "locale-key-shape": localeKeyShapeRule,
7981
- "shared-style-dedup": sharedStyleDedupRule,
7982
8000
  "repeated-structure": repeatedStructureRule
7983
8001
  };
7984
8002
  var pasikaPlugin = {
@@ -8075,10 +8093,6 @@ var zirkaConfig = {
8075
8093
  };
8076
8094
  var documentationConfig = {
8077
8095
  files: ["docs/**/*.md"],
8078
- // vulyk-generated agent files are not authored docs: with per-directory
8079
- // targets they can land under docs/ (e.g. docs/AGENTS.md) and must not be
8080
- // held to the documentation guide.
8081
- ignores: ["**/_*/**", "**/AGENTS.md", "**/CLAUDE.md"],
8082
8096
  plugins: {
8083
8097
  markdown,
8084
8098
  pasika: pasikaPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.10.30",
3
+ "version": "0.10.32",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",
@@ -88,6 +88,7 @@
88
88
  "tsx": "4.23.12",
89
89
  "typescript": "6.0.3",
90
90
  "vitest": "5.0.0",
91
+ "vulyk": "0.15.1",
91
92
  "zirka": "0.0.65",
92
93
  "zod": "4.4.3"
93
94
  },