pasika 0.10.31 → 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;
@@ -5083,7 +5090,7 @@ var zodSchemaValidationRule = {
5083
5090
  };
5084
5091
 
5085
5092
  // eslint/rules/schema-casing.ts
5086
- import path41 from "path";
5093
+ import path40 from "path";
5087
5094
  function isCamelCase(name) {
5088
5095
  return /^[a-z][a-zA-Z0-9]*$/.test(name);
5089
5096
  }
@@ -5111,9 +5118,9 @@ var schemaCasingRule = {
5111
5118
  }
5112
5119
  },
5113
5120
  create(context) {
5114
- const filename = path41.resolve(context.filename);
5121
+ const filename = path40.resolve(context.filename);
5115
5122
  const sourceRoot = sourceRootOf(context);
5116
- if (!filename.startsWith(sourceRoot + path41.sep)) return {};
5123
+ if (!filename.startsWith(sourceRoot + path40.sep)) return {};
5117
5124
  let zodLocalName;
5118
5125
  return {
5119
5126
  ImportDeclaration(node) {
@@ -5145,7 +5152,7 @@ var schemaCasingRule = {
5145
5152
  };
5146
5153
 
5147
5154
  // eslint/rules/component-casing.ts
5148
- import path42 from "path";
5155
+ import path41 from "path";
5149
5156
  function isPascalCase5(name) {
5150
5157
  return /^[A-Z][A-Za-z0-9]*$/.test(name);
5151
5158
  }
@@ -5158,10 +5165,10 @@ var componentCasingRule = {
5158
5165
  }
5159
5166
  },
5160
5167
  create(context) {
5161
- const filename = path42.resolve(context.filename);
5168
+ const filename = path41.resolve(context.filename);
5162
5169
  const sourceRoot = sourceRootOf(context);
5163
- if (!filename.startsWith(sourceRoot + path42.sep)) return {};
5164
- if (path42.extname(filename) !== ".tsx") return {};
5170
+ if (!filename.startsWith(sourceRoot + path41.sep)) return {};
5171
+ if (path41.extname(filename) !== ".tsx") return {};
5165
5172
  return {
5166
5173
  Program(node) {
5167
5174
  for (const declaration of findJsxReturningDeclarations(context.sourceCode.text, filename)) {
@@ -5178,7 +5185,7 @@ var componentCasingRule = {
5178
5185
  };
5179
5186
 
5180
5187
  // eslint/rules/source-under-src.ts
5181
- import path43 from "path";
5188
+ import path42 from "path";
5182
5189
  var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
5183
5190
  ".agents",
5184
5191
  ".cache",
@@ -5219,14 +5226,14 @@ var sourceUnderSrcRule = {
5219
5226
  }
5220
5227
  },
5221
5228
  create(context) {
5222
- const filename = path43.resolve(context.filename);
5229
+ const filename = path42.resolve(context.filename);
5223
5230
  if (!MODULE_EXTENSION.test(filename)) return {};
5224
- const relative = path43.relative(context.cwd, filename).replace(/\\/g, "/");
5231
+ const relative = path42.relative(context.cwd, filename).replace(/\\/g, "/");
5225
5232
  if (relative === "src" || relative.startsWith("src/")) return {};
5226
5233
  const topLevel = relative.split("/")[0] ?? "";
5227
5234
  if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
5228
5235
  if (!relative.includes("/")) {
5229
- const basename = path43.basename(filename);
5236
+ const basename = path42.basename(filename);
5230
5237
  if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
5231
5238
  }
5232
5239
  return {
@@ -5243,7 +5250,7 @@ var sourceUnderSrcRule = {
5243
5250
 
5244
5251
  // eslint/rules/zirka-baseline.ts
5245
5252
  import fs6 from "fs";
5246
- import path44 from "path";
5253
+ import path43 from "path";
5247
5254
  var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
5248
5255
  var PRETTIER_CONFIGS = [
5249
5256
  "prettier.config.mjs",
@@ -5262,10 +5269,10 @@ var zirkaBaselineRule = {
5262
5269
  }
5263
5270
  },
5264
5271
  create(context) {
5265
- const filename = path44.resolve(context.filename);
5266
- const basename = path44.basename(filename);
5272
+ const filename = path43.resolve(context.filename);
5273
+ const basename = path43.basename(filename);
5267
5274
  if (!ESLINT_CONFIG.test(basename)) return {};
5268
- const projectRoot = path44.dirname(filename);
5275
+ const projectRoot = path43.dirname(filename);
5269
5276
  const report3 = (message) => {
5270
5277
  context.report({
5271
5278
  node: context.sourceCode.ast,
@@ -5280,7 +5287,7 @@ var zirkaBaselineRule = {
5280
5287
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
5281
5288
  );
5282
5289
  }
5283
- const tsconfigPath = path44.join(projectRoot, "tsconfig.json");
5290
+ const tsconfigPath = path43.join(projectRoot, "tsconfig.json");
5284
5291
  if (!fs6.existsSync(tsconfigPath)) {
5285
5292
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
5286
5293
  } else {
@@ -5298,13 +5305,13 @@ var zirkaBaselineRule = {
5298
5305
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
5299
5306
  }
5300
5307
  }
5301
- 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)));
5302
5309
  if (!prettierConfigFile) {
5303
5310
  report3(
5304
5311
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
5305
5312
  );
5306
5313
  } else {
5307
- const content = fs6.readFileSync(path44.join(projectRoot, prettierConfigFile), "utf8");
5314
+ const content = fs6.readFileSync(path43.join(projectRoot, prettierConfigFile), "utf8");
5308
5315
  if (!content.includes("zirka")) {
5309
5316
  report3(
5310
5317
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -5324,6 +5331,9 @@ function referencesZirka(sourceCode) {
5324
5331
  return /require\(\s*["']zirka["']\s*\)/.test(sourceCode.getText());
5325
5332
  }
5326
5333
 
5334
+ // eslint/rules/documentation/helpers.ts
5335
+ import path44 from "path";
5336
+
5327
5337
  // constants/rfc2119.ts
5328
5338
  var RFC_2119_KEYWORDS = ["MUST NOT", "MUST", "SHOULD NOT", "SHOULD", "MAY"];
5329
5339
  var RFC_2119_PATTERN = new RegExp(`\\b(?<keyword>${RFC_2119_KEYWORDS.join("|")})\\b`);
@@ -5336,6 +5346,13 @@ function containsRfcKeyword(text) {
5336
5346
  function getFilename(context) {
5337
5347
  return context.filename;
5338
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
+ }
5339
5356
  function getTextContent(node) {
5340
5357
  if ("value" in node && typeof node.value === "string") return node.value;
5341
5358
  if ("children" in node) {
@@ -5369,7 +5386,7 @@ var docKindSuffixRule = {
5369
5386
  return {
5370
5387
  root(node) {
5371
5388
  const filename = getFilename(context);
5372
- if (!filename.endsWith(".md")) return;
5389
+ if (!filename.endsWith(".md") || isAgentDocument(filename) || isDocumentationTemplate(filename)) return;
5373
5390
  if (filename.endsWith("-guide.md") || filename.endsWith("-rule.md") || filename.endsWith("-reference.md") || filename.endsWith("-policy.md")) {
5374
5391
  return;
5375
5392
  }
@@ -5400,7 +5417,7 @@ var titleMatchesFileNameRule = {
5400
5417
  heading(node) {
5401
5418
  if (node.depth !== 1) return;
5402
5419
  const filename = getFilename(context);
5403
- if (!filename.endsWith(".md")) return;
5420
+ if (!filename.endsWith(".md") || isAgentDocument(filename) || isDocumentationTemplate(filename)) return;
5404
5421
  const title = getTextContent(node).trim();
5405
5422
  const expectedFileName = toExpectedFileName(title);
5406
5423
  const actualFileName = path45.basename(filename);
@@ -5680,12 +5697,16 @@ var rulePairedExamplesRule = {
5680
5697
  if (!filename.endsWith("-rule.md")) return;
5681
5698
  const exampleHeadings = [];
5682
5699
  for (const child of node.children) {
5683
- if (child.type === "heading" && child.depth === 2) {
5684
- const text = getTextContent(child).trim();
5685
- if (/^(?:Incorrect|Correct)\b/.test(text)) {
5686
- exampleHeadings.push({ line: getLine(child), text });
5687
- }
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;
5688
5705
  }
5706
+ context.report({
5707
+ node: child,
5708
+ message: `Rule level-2 heading must be an Incorrect/Correct example: ${text}`
5709
+ });
5689
5710
  }
5690
5711
  const incorrect = exampleHeadings.filter((h) => h.text.startsWith("Incorrect"));
5691
5712
  const correct = exampleHeadings.filter((h) => h.text.startsWith("Correct"));
@@ -5826,7 +5847,7 @@ var referenceBlockHeadingsRule = {
5826
5847
  return {
5827
5848
  root(node) {
5828
5849
  const filename = getFilename(context);
5829
- if (!filename.endsWith("-reference.md")) return;
5850
+ if (!filename.endsWith("-reference.md") || isDocumentationTemplate(filename)) return;
5830
5851
  const sectionHeadings = [];
5831
5852
  for (const child of node.children) {
5832
5853
  if (child.type === "heading" && child.depth >= 2) {
@@ -5900,7 +5921,7 @@ var supportDocumentPlacementRule = {
5900
5921
  return {
5901
5922
  root(node) {
5902
5923
  const filename = getFilename(context);
5903
- if (!filename.endsWith(".md")) return;
5924
+ if (!filename.endsWith(".md") || isDocumentationTemplate(filename)) return;
5904
5925
  let kind;
5905
5926
  if (filename.endsWith("-rule.md")) kind = "rule";
5906
5927
  if (filename.endsWith("-reference.md")) kind = "reference";
@@ -5928,6 +5949,7 @@ var noTemplatePromptRule = {
5928
5949
  }
5929
5950
  },
5930
5951
  create(context) {
5952
+ if (isDocumentationTemplate(getFilename(context))) return {};
5931
5953
  return {
5932
5954
  text(node) {
5933
5955
  if (TEMPLATE_PROMPT.test(node.value)) {
@@ -5945,7 +5967,7 @@ var noTemplatePromptRule = {
5945
5967
  import path48 from "path";
5946
5968
 
5947
5969
  // eslint/rules/documentation/project-index.ts
5948
- 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";
5949
5971
  import path47 from "path";
5950
5972
  var KIND_BY_SUFFIX = [
5951
5973
  ["-rule.md", "rule"],
@@ -5956,14 +5978,14 @@ var KIND_BY_SUFFIX = [
5956
5978
  function listMarkdownFiles(dir) {
5957
5979
  return readdirSync3(dir).flatMap((entry) => {
5958
5980
  const entryPath = path47.join(dir, entry);
5959
- if (statSync4(entryPath).isDirectory()) {
5981
+ if (statSync3(entryPath).isDirectory()) {
5960
5982
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
5961
5983
  }
5962
5984
  return entry.endsWith(".md") ? [entryPath] : [];
5963
5985
  });
5964
5986
  }
5965
5987
  function extractTitle(filePath) {
5966
- const content = readFileSync7(filePath, "utf8");
5988
+ const content = readFileSync6(filePath, "utf8");
5967
5989
  const match = /^# (?<title>.+)$/m.exec(content);
5968
5990
  return match?.groups?.title?.trim() ?? "";
5969
5991
  }
@@ -5989,7 +6011,7 @@ function getProjectDocs(docsRoot) {
5989
6011
  function findDocsRoot(filePath) {
5990
6012
  let dir = path47.dirname(filePath);
5991
6013
  for (; ; ) {
5992
- if (path47.basename(dir) === "docs" && statSync4(dir).isDirectory()) {
6014
+ if (path47.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
5993
6015
  return dir;
5994
6016
  }
5995
6017
  const parent = path47.dirname(dir);
@@ -6166,7 +6188,7 @@ var policySubjectHeadingsRule = {
6166
6188
  };
6167
6189
 
6168
6190
  // eslint/rules/documentation/guide-link-anchors.ts
6169
- import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
6191
+ import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
6170
6192
  import path49 from "path";
6171
6193
  function visitSteps2(node, check) {
6172
6194
  if (node.type === "list" && node.ordered) {
@@ -6197,7 +6219,7 @@ function documentAnchors(filePath) {
6197
6219
  anchorsByFile.set(filePath, anchors);
6198
6220
  let content;
6199
6221
  try {
6200
- content = readFileSync8(filePath, "utf8");
6222
+ content = readFileSync7(filePath, "utf8");
6201
6223
  } catch {
6202
6224
  return anchors;
6203
6225
  }
@@ -6296,7 +6318,7 @@ var noNestedHowToRule = {
6296
6318
  };
6297
6319
 
6298
6320
  // eslint/rules/documentation/glossary-term-linking.ts
6299
- import { readFileSync as readFileSync9 } from "fs";
6321
+ import { readFileSync as readFileSync8 } from "fs";
6300
6322
  import path50 from "path";
6301
6323
  function isGlossary(filePath) {
6302
6324
  return path50.basename(filePath).includes("glossary");
@@ -6317,7 +6339,7 @@ function extractTableTerms(content) {
6317
6339
  }
6318
6340
  function extractGlossaryTerms(filePath) {
6319
6341
  if (!isGlossary(filePath)) return [];
6320
- const content = readFileSync9(filePath, "utf8");
6342
+ const content = readFileSync8(filePath, "utf8");
6321
6343
  const tableTerms = extractTableTerms(content);
6322
6344
  if (tableTerms.length > 0) return tableTerms;
6323
6345
  const terms = [];
@@ -6331,7 +6353,7 @@ function extractGlossaryTerms(filePath) {
6331
6353
  function extractBlockHeadings(filePath) {
6332
6354
  const headings = [];
6333
6355
  let fenced = false;
6334
- for (const line of readFileSync9(filePath, "utf8").split("\n")) {
6356
+ for (const line of readFileSync8(filePath, "utf8").split("\n")) {
6335
6357
  if (/^\s*(?:```|~~~)/.test(line)) {
6336
6358
  fenced = !fenced;
6337
6359
  continue;
@@ -6641,6 +6663,11 @@ function preludeIdentifiers(node) {
6641
6663
  });
6642
6664
  return names;
6643
6665
  }
6666
+ function rawValue(node) {
6667
+ if (!node) return "";
6668
+ if (node.type === "Raw") return node.value;
6669
+ return "";
6670
+ }
6644
6671
  function rules(node) {
6645
6672
  const result = [];
6646
6673
  walkNodes(node, (current) => {
@@ -6988,39 +7015,40 @@ var customUtilityApplyRule = {
6988
7015
  }
6989
7016
  };
6990
7017
 
6991
- // eslint/rules/tailwind/skin-utility.ts
6992
- 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 = {
6993
7035
  meta: {
6994
7036
  schema: [],
6995
7037
  type: "problem",
6996
7038
  docs: {
6997
- description: "Require reusable style combinations to become *-skin utilities."
7039
+ description: "Require custom utilities to map exactly one Tailwind styling operation."
6998
7040
  }
6999
7041
  },
7000
7042
  create(context) {
7001
7043
  return {
7002
7044
  "StyleSheet:exit"(node) {
7003
7045
  for (const utility of atrulesNamed(node, "utility")) {
7004
- const name = preludeIdentifiers(utility)[0];
7005
- const applied = new Set(atrulesNamed(utility, "apply").flatMap((apply) => preludeIdentifiers(apply)));
7006
- 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;
7007
7049
  context.report({
7008
7050
  node: utility,
7009
- message: `Custom utility "${name ?? "unknown"}" combines ${String(applied.size)} styles; use the *-skin suffix.`
7010
- });
7011
- }
7012
- const combinations = /* @__PURE__ */ new Map();
7013
- for (const apply of atrulesNamed(node, "apply")) {
7014
- const classes = preludeIdentifiers(apply);
7015
- if (new Set(classes).size < 2) continue;
7016
- const key = [...classes].sort().join(" ");
7017
- combinations.set(key, (combinations.get(key) ?? 0) + 1);
7018
- }
7019
- for (const [combo, count] of combinations) {
7020
- if (count < 2) continue;
7021
- context.report({
7022
- node,
7023
- 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.`
7024
7052
  });
7025
7053
  }
7026
7054
  }
@@ -7068,11 +7096,11 @@ var themeVariableNamespaceRule = {
7068
7096
  };
7069
7097
 
7070
7098
  // eslint/rules/tailwind/css-entry-point.ts
7071
- import { statSync as statSync6 } from "fs";
7099
+ import { statSync as statSync5 } from "fs";
7072
7100
  import path54 from "path";
7073
7101
 
7074
7102
  // eslint/rules/tailwind/source-files.ts
7075
- 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";
7076
7104
  import path52 from "path";
7077
7105
  var CSS_EXTENSIONS = [".css"];
7078
7106
  var MODULE_EXTENSIONS3 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
@@ -7089,7 +7117,7 @@ function findFiles(dir, extensions) {
7089
7117
  const entryPath = path52.join(dir, entry);
7090
7118
  let stats;
7091
7119
  try {
7092
- stats = statSync5(entryPath);
7120
+ stats = statSync4(entryPath);
7093
7121
  } catch {
7094
7122
  return [];
7095
7123
  }
@@ -7103,7 +7131,7 @@ function cachedTextReader() {
7103
7131
  let text = texts.get(file);
7104
7132
  if (text === void 0) {
7105
7133
  try {
7106
- text = readFileSync10(file, "utf8");
7134
+ text = readFileSync9(file, "utf8");
7107
7135
  } catch {
7108
7136
  text = "";
7109
7137
  }
@@ -7182,7 +7210,7 @@ var cssEntryPointRule = {
7182
7210
  let cssFiles;
7183
7211
  let moduleFiles;
7184
7212
  try {
7185
- if (!statSync6(sourceRoot).isDirectory()) return {};
7213
+ if (!statSync5(sourceRoot).isDirectory()) return {};
7186
7214
  cssFiles = findFiles(sourceRoot, CSS_EXTENSIONS);
7187
7215
  moduleFiles = findFiles(sourceRoot, MODULE_EXTENSIONS3);
7188
7216
  } catch {
@@ -7270,7 +7298,7 @@ var globalStylesheetRule = {
7270
7298
  };
7271
7299
 
7272
7300
  // eslint/rules/tailwind/unused-utility.ts
7273
- import { statSync as statSync7 } from "fs";
7301
+ import { statSync as statSync6 } from "fs";
7274
7302
  function usagePattern(name) {
7275
7303
  return new RegExp(`(?<![\\w-])${escapeRegExp(name)}(?![\\w-])`);
7276
7304
  }
@@ -7286,7 +7314,7 @@ var unusedUtilityRule = {
7286
7314
  const sourceRoot = sourceRootOf(context);
7287
7315
  let files;
7288
7316
  try {
7289
- if (!statSync7(sourceRoot).isDirectory()) return {};
7317
+ if (!statSync6(sourceRoot).isDirectory()) return {};
7290
7318
  files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
7291
7319
  } catch {
7292
7320
  return {};
@@ -7311,48 +7339,6 @@ var unusedUtilityRule = {
7311
7339
  }
7312
7340
  };
7313
7341
 
7314
- // eslint/rules/tailwind/single-use-utility.ts
7315
- import { statSync as statSync8 } from "fs";
7316
- function usagePattern2(name) {
7317
- return new RegExp(`(?<![\\\\w-])${escapeRegExp(name)}(?![\\\\w-])`);
7318
- }
7319
- var singleUseUtilityRule = {
7320
- meta: {
7321
- schema: [],
7322
- type: "problem",
7323
- docs: {
7324
- description: "Disallow custom utilities referenced by only one source file."
7325
- }
7326
- },
7327
- create(context) {
7328
- const sourceRoot = sourceRootOf(context);
7329
- let files;
7330
- try {
7331
- if (!statSync8(sourceRoot).isDirectory()) return {};
7332
- files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
7333
- } catch {
7334
- return {};
7335
- }
7336
- const textOf = cachedTextReader();
7337
- return {
7338
- "StyleSheet:exit"(node) {
7339
- for (const utility of atrulesNamed(node, "utility")) {
7340
- const name = preludeIdentifiers(utility)[0];
7341
- if (!name) continue;
7342
- const pattern = usagePattern2(name);
7343
- const definition = `@utility ${name}`;
7344
- const consumers = files.filter((file) => pattern.test(textOf(file).replaceAll(definition, "")));
7345
- if (consumers.length !== 1) continue;
7346
- context.report({
7347
- node: utility,
7348
- message: `Custom utility "${name}" is used by only one source file; keep that styling as static Tailwind classes in its consumer.`
7349
- });
7350
- }
7351
- }
7352
- };
7353
- }
7354
- };
7355
-
7356
7342
  // eslint/rules/tailwind/global-selector-styling.ts
7357
7343
  function hasStyling(node) {
7358
7344
  for (const child of blockChildren2(node)) {
@@ -7394,12 +7380,11 @@ var tailwindRules = {
7394
7380
  "stylesheet-ordering": stylesheetOrderingRule,
7395
7381
  "css-variable-naming": cssVariableNamingRule,
7396
7382
  "custom-utility-apply": customUtilityApplyRule,
7397
- "skin-utility": skinUtilityRule,
7383
+ "utility-mapping": utilityMappingRule,
7398
7384
  "theme-variable-namespace": themeVariableNamespaceRule,
7399
7385
  "css-entry-point": cssEntryPointRule,
7400
7386
  "global-stylesheet": globalStylesheetRule,
7401
7387
  "unused-utility": unusedUtilityRule,
7402
- "single-use-utility": singleUseUtilityRule,
7403
7388
  "global-selector-styling": globalSelectorStylingRule
7404
7389
  };
7405
7390
 
@@ -7618,7 +7603,7 @@ var nextjsStackRule = {
7618
7603
  };
7619
7604
 
7620
7605
  // eslint/rules/package-json/vitest-coverage.ts
7621
- import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
7606
+ import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
7622
7607
  import path55 from "path";
7623
7608
  function memberName4(member) {
7624
7609
  return member.name.type === "String" ? member.name.value : member.name.name;
@@ -7696,7 +7681,7 @@ var vitestCoverageRule = {
7696
7681
  });
7697
7682
  return;
7698
7683
  }
7699
- const content = readFileSync11(path55.join(context.cwd, configName), "utf8");
7684
+ const content = readFileSync10(path55.join(context.cwd, configName), "utf8");
7700
7685
  for (const metric of THRESHOLD_METRICS) {
7701
7686
  if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
7702
7687
  context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
@@ -7742,7 +7727,7 @@ var nextjsPackageJsonRules = {
7742
7727
  };
7743
7728
 
7744
7729
  // eslint/rules/husky/husky-hook.ts
7745
- import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
7730
+ import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
7746
7731
  import path56 from "path";
7747
7732
  var VITEST_CONFIG_NAMES2 = [
7748
7733
  "vitest.config.ts",
@@ -7777,7 +7762,7 @@ var huskyHookRule = {
7777
7762
  });
7778
7763
  return;
7779
7764
  }
7780
- const content = readFileSync12(hookPath, "utf8");
7765
+ const content = readFileSync11(hookPath, "utf8");
7781
7766
  const scripts = root.members.find((member) => memberName5(member) === "scripts");
7782
7767
  const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
7783
7768
  const requireNamedScript = (name) => {
@@ -7839,7 +7824,7 @@ var huskyRules = {
7839
7824
  };
7840
7825
 
7841
7826
  // eslint/rules/vulyk/tracked-docs.ts
7842
- import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
7827
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "fs";
7843
7828
  import path57 from "path";
7844
7829
  var PASIKA_REPO = "Bredansky/pasika";
7845
7830
  var BASE_REQUIRED_TRACKED_DOCS = [
@@ -7882,7 +7867,7 @@ var trackedDocsRule = {
7882
7867
  });
7883
7868
  return;
7884
7869
  }
7885
- const config = readFileSync13(configPath, "utf8");
7870
+ const config = readFileSync12(configPath, "utf8");
7886
7871
  if (!config.includes(PASIKA_REPO)) {
7887
7872
  context.report({
7888
7873
  node,
@@ -7989,6 +7974,7 @@ var pasikaNextjsAppRules = {
7989
7974
  "ui-state": uiStateRule,
7990
7975
  "no-mixed-concerns": noMixedConcernsRule,
7991
7976
  "no-arbitrary-tailwind": noArbitraryTailwindRule,
7977
+ "no-literal-inline-style": noLiteralInlineStyleRule,
7992
7978
  "unknown-utility": unknownUtilityRule,
7993
7979
  "enforce-cn-merge": enforceCnMergeRule,
7994
7980
  "cn-helper": cnHelperRule,
@@ -8011,7 +7997,6 @@ var pasikaNextjsAppRules = {
8011
7997
  "locale-placement": localePlacementRule,
8012
7998
  "sole-state-owner": soleStateOwnerRule,
8013
7999
  "locale-key-shape": localeKeyShapeRule,
8014
- "shared-style-dedup": sharedStyleDedupRule,
8015
8000
  "repeated-structure": repeatedStructureRule
8016
8001
  };
8017
8002
  var pasikaPlugin = {
@@ -8108,10 +8093,6 @@ var zirkaConfig = {
8108
8093
  };
8109
8094
  var documentationConfig = {
8110
8095
  files: ["docs/**/*.md"],
8111
- // vulyk-generated agent files are not authored docs: with per-directory
8112
- // targets they can land under docs/ (e.g. docs/AGENTS.md) and must not be
8113
- // held to the documentation guide.
8114
- ignores: ["**/_*/**", "**/AGENTS.md", "**/CLAUDE.md"],
8115
8096
  plugins: {
8116
8097
  markdown,
8117
8098
  pasika: pasikaPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.10.31",
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
  },