pasika 0.10.31 → 0.10.33

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
 
@@ -143,7 +142,7 @@ declare const vulykRules: {
143
142
  * One plugin object every preset block references. ESLint only permits a
144
143
  * plugin name to be redefined across configs when the value is the same
145
144
  * object reference, and several blocks share file scopes (`src/**`, the
146
- * manifest, `globals.css`), so a per-block plugin object would collide when
145
+ * manifest, `src/app/styles/globals.css`), so a per-block plugin object would collide when
147
146
  * the blocks merge.
148
147
  */
149
148
  declare const pasikaPlugin: {
@@ -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
  }
@@ -7169,12 +7197,14 @@ function buildStylesheetGraph(options) {
7169
7197
  }
7170
7198
 
7171
7199
  // eslint/rules/tailwind/css-entry-point.ts
7200
+ var APP_STYLES_DIR = path54.join("app", "styles");
7201
+ var GLOBAL_STYLESHEET_PATH = path54.join(APP_STYLES_DIR, "globals.css");
7172
7202
  var cssEntryPointRule = {
7173
7203
  meta: {
7174
7204
  schema: [],
7175
7205
  type: "problem",
7176
7206
  docs: {
7177
- description: "Require one global entry point; project CSS only in a stylesheet it imports directly."
7207
+ description: "Require the app/styles CSS root, one global entry point, and direct project-CSS imports."
7178
7208
  }
7179
7209
  },
7180
7210
  create(context) {
@@ -7182,7 +7212,7 @@ var cssEntryPointRule = {
7182
7212
  let cssFiles;
7183
7213
  let moduleFiles;
7184
7214
  try {
7185
- if (!statSync6(sourceRoot).isDirectory()) return {};
7215
+ if (!statSync5(sourceRoot).isDirectory()) return {};
7186
7216
  cssFiles = findFiles(sourceRoot, CSS_EXTENSIONS);
7187
7217
  moduleFiles = findFiles(sourceRoot, MODULE_EXTENSIONS3);
7188
7218
  } catch {
@@ -7195,6 +7225,7 @@ var cssEntryPointRule = {
7195
7225
  "StyleSheet:exit"(node) {
7196
7226
  if (globals.length === 0) return;
7197
7227
  const current = path54.normalize(path54.resolve(context.filename));
7228
+ const relative = path54.relative(sourceRoot, current);
7198
7229
  if (globals.includes(current)) {
7199
7230
  if (globals.length > 1) {
7200
7231
  context.report({
@@ -7203,6 +7234,13 @@ var cssEntryPointRule = {
7203
7234
  });
7204
7235
  return;
7205
7236
  }
7237
+ if (relative !== GLOBAL_STYLESHEET_PATH) {
7238
+ context.report({
7239
+ node,
7240
+ message: "The global stylesheet entry point must be src/app/styles/globals.css."
7241
+ });
7242
+ return;
7243
+ }
7206
7244
  const basename = path54.basename(current);
7207
7245
  const importCount = moduleFiles.filter((modulePath) => moduleImports(textOf(modulePath), basename)).length;
7208
7246
  if (importCount !== 1) {
@@ -7213,6 +7251,14 @@ var cssEntryPointRule = {
7213
7251
  }
7214
7252
  return;
7215
7253
  }
7254
+ const isInAppStyles = relative === APP_STYLES_DIR || relative.startsWith(`${APP_STYLES_DIR}${path54.sep}`);
7255
+ if (!isInAppStyles) {
7256
+ context.report({
7257
+ node,
7258
+ message: "Project stylesheets must live under src/app/styles/."
7259
+ });
7260
+ return;
7261
+ }
7216
7262
  const hasProjectCss = node.children.some((child) => {
7217
7263
  if (child.type === "Atrule" && child.name === "import") return false;
7218
7264
  if (child.type === "Comment") return false;
@@ -7270,7 +7316,7 @@ var globalStylesheetRule = {
7270
7316
  };
7271
7317
 
7272
7318
  // eslint/rules/tailwind/unused-utility.ts
7273
- import { statSync as statSync7 } from "fs";
7319
+ import { statSync as statSync6 } from "fs";
7274
7320
  function usagePattern(name) {
7275
7321
  return new RegExp(`(?<![\\w-])${escapeRegExp(name)}(?![\\w-])`);
7276
7322
  }
@@ -7286,7 +7332,7 @@ var unusedUtilityRule = {
7286
7332
  const sourceRoot = sourceRootOf(context);
7287
7333
  let files;
7288
7334
  try {
7289
- if (!statSync7(sourceRoot).isDirectory()) return {};
7335
+ if (!statSync6(sourceRoot).isDirectory()) return {};
7290
7336
  files = findFiles(sourceRoot, SOURCE_EXTENSIONS);
7291
7337
  } catch {
7292
7338
  return {};
@@ -7311,48 +7357,6 @@ var unusedUtilityRule = {
7311
7357
  }
7312
7358
  };
7313
7359
 
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
7360
  // eslint/rules/tailwind/global-selector-styling.ts
7357
7361
  function hasStyling(node) {
7358
7362
  for (const child of blockChildren2(node)) {
@@ -7394,12 +7398,11 @@ var tailwindRules = {
7394
7398
  "stylesheet-ordering": stylesheetOrderingRule,
7395
7399
  "css-variable-naming": cssVariableNamingRule,
7396
7400
  "custom-utility-apply": customUtilityApplyRule,
7397
- "skin-utility": skinUtilityRule,
7401
+ "utility-mapping": utilityMappingRule,
7398
7402
  "theme-variable-namespace": themeVariableNamespaceRule,
7399
7403
  "css-entry-point": cssEntryPointRule,
7400
7404
  "global-stylesheet": globalStylesheetRule,
7401
7405
  "unused-utility": unusedUtilityRule,
7402
- "single-use-utility": singleUseUtilityRule,
7403
7406
  "global-selector-styling": globalSelectorStylingRule
7404
7407
  };
7405
7408
 
@@ -7618,7 +7621,7 @@ var nextjsStackRule = {
7618
7621
  };
7619
7622
 
7620
7623
  // eslint/rules/package-json/vitest-coverage.ts
7621
- import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
7624
+ import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
7622
7625
  import path55 from "path";
7623
7626
  function memberName4(member) {
7624
7627
  return member.name.type === "String" ? member.name.value : member.name.name;
@@ -7696,7 +7699,7 @@ var vitestCoverageRule = {
7696
7699
  });
7697
7700
  return;
7698
7701
  }
7699
- const content = readFileSync11(path55.join(context.cwd, configName), "utf8");
7702
+ const content = readFileSync10(path55.join(context.cwd, configName), "utf8");
7700
7703
  for (const metric of THRESHOLD_METRICS) {
7701
7704
  if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
7702
7705
  context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
@@ -7742,7 +7745,7 @@ var nextjsPackageJsonRules = {
7742
7745
  };
7743
7746
 
7744
7747
  // eslint/rules/husky/husky-hook.ts
7745
- import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
7748
+ import { existsSync as existsSync6, readFileSync as readFileSync11 } from "fs";
7746
7749
  import path56 from "path";
7747
7750
  var VITEST_CONFIG_NAMES2 = [
7748
7751
  "vitest.config.ts",
@@ -7777,7 +7780,7 @@ var huskyHookRule = {
7777
7780
  });
7778
7781
  return;
7779
7782
  }
7780
- const content = readFileSync12(hookPath, "utf8");
7783
+ const content = readFileSync11(hookPath, "utf8");
7781
7784
  const scripts = root.members.find((member) => memberName5(member) === "scripts");
7782
7785
  const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName5) : []);
7783
7786
  const requireNamedScript = (name) => {
@@ -7839,7 +7842,7 @@ var huskyRules = {
7839
7842
  };
7840
7843
 
7841
7844
  // eslint/rules/vulyk/tracked-docs.ts
7842
- import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
7845
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "fs";
7843
7846
  import path57 from "path";
7844
7847
  var PASIKA_REPO = "Bredansky/pasika";
7845
7848
  var BASE_REQUIRED_TRACKED_DOCS = [
@@ -7882,7 +7885,7 @@ var trackedDocsRule = {
7882
7885
  });
7883
7886
  return;
7884
7887
  }
7885
- const config = readFileSync13(configPath, "utf8");
7888
+ const config = readFileSync12(configPath, "utf8");
7886
7889
  if (!config.includes(PASIKA_REPO)) {
7887
7890
  context.report({
7888
7891
  node,
@@ -7989,6 +7992,7 @@ var pasikaNextjsAppRules = {
7989
7992
  "ui-state": uiStateRule,
7990
7993
  "no-mixed-concerns": noMixedConcernsRule,
7991
7994
  "no-arbitrary-tailwind": noArbitraryTailwindRule,
7995
+ "no-literal-inline-style": noLiteralInlineStyleRule,
7992
7996
  "unknown-utility": unknownUtilityRule,
7993
7997
  "enforce-cn-merge": enforceCnMergeRule,
7994
7998
  "cn-helper": cnHelperRule,
@@ -8011,7 +8015,6 @@ var pasikaNextjsAppRules = {
8011
8015
  "locale-placement": localePlacementRule,
8012
8016
  "sole-state-owner": soleStateOwnerRule,
8013
8017
  "locale-key-shape": localeKeyShapeRule,
8014
- "shared-style-dedup": sharedStyleDedupRule,
8015
8018
  "repeated-structure": repeatedStructureRule
8016
8019
  };
8017
8020
  var pasikaPlugin = {
@@ -8064,7 +8067,7 @@ var pasikaNextjsAppConfig = {
8064
8067
  rules: Object.fromEntries(pasikaNextjsAppRuleIds.map((id) => [id, "error"]))
8065
8068
  };
8066
8069
  var tailwindStructureRules = {
8067
- files: ["src/**/globals.css"],
8070
+ files: ["src/app/styles/globals.css"],
8068
8071
  plugins: {
8069
8072
  css,
8070
8073
  pasika: pasikaPlugin
@@ -8108,10 +8111,6 @@ var zirkaConfig = {
8108
8111
  };
8109
8112
  var documentationConfig = {
8110
8113
  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
8114
  plugins: {
8116
8115
  markdown,
8117
8116
  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.33",
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
  },