pasika 0.3.6 → 0.3.7

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Documentation, the lint rules derived from it, and the CLI that applies and diag
8
8
 
9
9
  ```text
10
10
  docs/
11
- agent-policy.md # agent-conduct requirements (Policy)
11
+ repository-policy.md # agent-conduct requirements (Policy)
12
12
  repository-policy.md # repo-wide code and documentation requirements (Policy)
13
13
  code-organization-guide/ # placement, extraction, module conventions
14
14
  documentation-guide/ # how documents themselves are written
@@ -38,7 +38,7 @@ Requirements are identified by a hash of their canonical text, not by a hand-wri
38
38
  "text": "A .tsx file that defines a component MUST contain exactly one component.",
39
39
  "hash": "b19fe3bd34",
40
40
  "ref": "pasika/no-mixed-concerns",
41
- "note": "counts exported components; a second component that is not exported is not detected"
41
+ "note": "counts exported components; a second component that is not exported is not detected",
42
42
  }
43
43
  ```
44
44
 
@@ -101,74 +101,74 @@ export default [pasikaConfig];
101
101
 
102
102
  ### TS/TSX rules
103
103
 
104
- | Rule | Enforces |
105
- | --- | --- |
106
- | `pasika/filename-case` | kebab-case for files that define no component |
107
- | `pasika/import-boundaries` | The shorter of the relative path and the `@/*` alias, and the layer boundaries |
108
- | `pasika/no-mixed-concerns` | One exported React component per `.tsx` file |
109
- | `pasika/no-arbitrary-tailwind` | No arbitrary `-[value]` classes, including inside `cn()` conditionals |
110
- | `pasika/enforce-cn-merge` | `cn()` instead of `+` or template literals; at most five classes per group |
111
- | `pasika/enforce-cva-variant-props` | `VariantProps<typeof …>` instead of hand-written unions |
112
- | `pasika/cva-appearance-props` | Visual option props (`size`, `variant`, …) declared through CVA appearance props |
113
- | `pasika/cva-boolean-variants` | Boolean appearance props placed on the CVA variant, not as standalone props |
114
- | `pasika/enforce-barrel-exports` | A nested `index.ts` re-exports only its component |
115
- | `pasika/component-placement` † | The folder a component's consumers imply |
116
- | `pasika/support-file-placement` † | The folder a hook, type, schema, constant, or utility belongs in |
117
- | `pasika/application-structure` | The path-based parts of the application structure and configuration rules |
118
- | `pasika/named-exports` | Named exports for application files (framework routing files may default-export) |
119
- | `pasika/data-testid-case` | `data-testid` casing where a rendered result carries one |
120
- | `pasika/support-folder-shape` | A `constants/`, `types/`, or `schemas/` folder defines exports directly in `index.ts` or in named-re-exported files |
121
- | `pasika/import-through-index` | An extracted constant, type, or schema is imported through its folder's `index.ts` |
122
- | `pasika/util-file-name` | A single-function utility file is named in the function's kebab-case form |
123
- | `pasika/no-util-barrel` | A utility is imported directly, without a barrel |
124
- | `pasika/jsx-hygiene` | Calculations and complex conditions stay out of JSX children and attributes |
125
- | `pasika/interactive-component` | Interactive elements are component boundaries when mixed with other content |
126
- | `pasika/ui-state` | Native or ARIA state expression and Tailwind state variants |
127
- | `pasika/cross-feature-import` | A component importing from two or more feature folders lives in a shared location |
128
- | `pasika/pure-function-extract` | Pure functions extracted to `utils/`, even with one consumer |
129
- | `pasika/hook-complexity` | Hook complexity limits (imperative categories per hook) |
130
- | `pasika/locale-dotted-path` | A namespaced locale is read through its full dotted path |
131
- | `pasika/locales-location` | Locales live in the named locales object |
132
- | `pasika/hook-extraction` † | A hook with two or more consumers is extracted to its own file |
133
- | `pasika/value-extraction` † | A value with cross-folder consumers is extracted |
134
- | `pasika/config-extraction` † | A type, schema, or utility used outside its config module is moved |
135
- | `pasika/component-nesting` † | A component is not nested solely because it has support files |
136
- | `pasika/stay-flat` † | A component stays flat until it has exclusive children |
137
- | `pasika/type-extraction` † | A type or schema with cross-folder consumers is extracted |
138
- | `pasika/locale-placement` † | Shared locales at the top level, single-feature locales namespaced |
139
- | `pasika/locale-key-shape` | camelCase locale keys; keys over 30 characters end in a WAI-ARIA element role |
140
- | `pasika/shared-style-dedup` † | A className combo used by two or more components becomes a named utility |
141
- | `pasika/repeated-structure` | A block of elements repeated two or more times is extracted as a named component |
142
- | `pasika/sole-state-owner` | A contiguous JSX part that is the sole consumer of a useState hook is extracted into a named component |
143
- | `pasika/no-eslint-disable` | No `eslint-disable` directives |
144
- | `pasika/zod-schema-validation` | Runtime validation through Zod schemas, not hand-written type guards |
145
- | `pasika/source-under-src` | Application source lives under `src/`, not in root-level folders |
104
+ | Rule | Enforces |
105
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
106
+ | `pasika/filename-case` | kebab-case for files that define no component |
107
+ | `pasika/import-boundaries` | The shorter of the relative path and the `@/*` alias, and the layer boundaries |
108
+ | `pasika/no-mixed-concerns` | One exported React component per `.tsx` file |
109
+ | `pasika/no-arbitrary-tailwind` | No arbitrary `-[value]` classes, including inside `cn()` conditionals |
110
+ | `pasika/enforce-cn-merge` | `cn()` instead of `+` or template literals; at most five classes per group |
111
+ | `pasika/enforce-cva-variant-props` | `VariantProps<typeof …>` instead of hand-written unions |
112
+ | `pasika/cva-appearance-props` | Visual option props (`size`, `variant`, …) declared through CVA appearance props |
113
+ | `pasika/cva-boolean-variants` | Boolean appearance props placed on the CVA variant, not as standalone props |
114
+ | `pasika/enforce-barrel-exports` | A nested `index.ts` re-exports only its component |
115
+ | `pasika/component-placement` † | The folder a component's consumers imply |
116
+ | `pasika/support-file-placement` † | The folder a hook, type, schema, constant, or utility belongs in |
117
+ | `pasika/application-structure` | The path-based parts of the application structure and configuration rules |
118
+ | `pasika/named-exports` | Named exports for application files (framework routing files may default-export) |
119
+ | `pasika/data-testid-case` | `data-testid` casing where a rendered result carries one |
120
+ | `pasika/support-folder-shape` | A `constants/`, `types/`, or `schemas/` folder defines exports directly in `index.ts` or in named-re-exported files |
121
+ | `pasika/import-through-index` | An extracted constant, type, or schema is imported through its folder's `index.ts` |
122
+ | `pasika/util-file-name` | A single-function utility file is named in the function's kebab-case form |
123
+ | `pasika/no-util-barrel` | A utility is imported directly, without a barrel |
124
+ | `pasika/jsx-hygiene` | Calculations and complex conditions stay out of JSX children and attributes |
125
+ | `pasika/interactive-component` | Interactive elements are component boundaries when mixed with other content |
126
+ | `pasika/ui-state` | Native or ARIA state expression and Tailwind state variants |
127
+ | `pasika/cross-feature-import` | A component importing from two or more feature folders lives in a shared location |
128
+ | `pasika/pure-function-extract` | Pure functions extracted to `utils/`, even with one consumer |
129
+ | `pasika/hook-complexity` | Hook complexity limits (imperative categories per hook) |
130
+ | `pasika/locale-dotted-path` | A namespaced locale is read through its full dotted path |
131
+ | `pasika/locales-location` | Locales live in the named locales object |
132
+ | `pasika/hook-extraction` † | A hook with two or more consumers is extracted to its own file |
133
+ | `pasika/value-extraction` † | A value with cross-folder consumers is extracted |
134
+ | `pasika/config-extraction` † | A type, schema, or utility used outside its config module is moved |
135
+ | `pasika/component-nesting` † | A component is not nested solely because it has support files |
136
+ | `pasika/stay-flat` † | A component stays flat until it has exclusive children |
137
+ | `pasika/type-extraction` † | A type or schema with cross-folder consumers is extracted |
138
+ | `pasika/locale-placement` † | Shared locales at the top level, single-feature locales namespaced |
139
+ | `pasika/locale-key-shape` | camelCase locale keys; keys over 30 characters end in a WAI-ARIA element role |
140
+ | `pasika/shared-style-dedup` † | A className combo used by two or more components becomes a named utility |
141
+ | `pasika/repeated-structure` | A block of elements repeated two or more times is extracted as a named component |
142
+ | `pasika/sole-state-owner` | A contiguous JSX part that is the sole consumer of a useState hook is extracted into a named component |
143
+ | `pasika/no-eslint-disable` | No `eslint-disable` directives |
144
+ | `pasika/zod-schema-validation` | Runtime validation through Zod schemas, not hand-written type guards |
145
+ | `pasika/source-under-src` | Application source lives under `src/`, not in root-level folders |
146
146
 
147
147
  ### CSS rules
148
148
 
149
149
  Applied to `src/**/globals.css` (and other stylesheets) through `@eslint/css` with tolerant Tailwind v4 parsing.
150
150
 
151
- | Rule | Enforces |
152
- | --- | --- |
153
- | `pasika/theme-reset` | A `--*: initial` theme reset is present |
154
- | `pasika/root-variables` | `:root` defines the CSS custom properties |
155
- | `pasika/apply-usage` | `@layer base` uses `@apply` for declarations |
156
- | `pasika/base-layer-pair` | The base layer applies `base-canvas` and `base-ink` |
157
- | `pasika/stylesheet-ordering` | Imports → `@custom-variant` → `:root` → `@theme` → `@utility` → `@layer base` |
158
- | `pasika/css-variable-naming` | Background vars named `--<role>-canvas`, text vars `--<role>-ink` |
159
- | `pasika/custom-utility-apply` | `@utility` blocks use `@apply` |
160
- | `pasika/surface-utility` | Repeated canvas+ink combos become a named surface utility |
161
- | `pasika/theme-variable-namespace` | Utility class groups share a namespace prefix |
162
- | `pasika/global-css-location` | Global CSS lives in the correct entry point |
151
+ | Rule | Enforces |
152
+ | --------------------------------- | ----------------------------------------------------------------------------- |
153
+ | `pasika/theme-reset` | A `--*: initial` theme reset is present |
154
+ | `pasika/root-variables` | `:root` defines the CSS custom properties |
155
+ | `pasika/apply-usage` | `@layer base` uses `@apply` for declarations |
156
+ | `pasika/base-layer-pair` | The base layer applies `base-canvas` and `base-ink` |
157
+ | `pasika/stylesheet-ordering` | Imports → `@custom-variant` → `:root` → `@theme` → `@utility` → `@layer base` |
158
+ | `pasika/css-variable-naming` | Background vars named `--<role>-canvas`, text vars `--<role>-ink` |
159
+ | `pasika/custom-utility-apply` | `@utility` blocks use `@apply` |
160
+ | `pasika/surface-utility` | Repeated canvas+ink combos become a named surface utility |
161
+ | `pasika/theme-variable-namespace` | Utility class groups share a namespace prefix |
162
+ | `pasika/global-css-location` | Global CSS lives in the correct entry point |
163
163
 
164
164
  ### JSON rules
165
165
 
166
166
  Applied to `package.json` through `@eslint/json`.
167
167
 
168
- | Rule | Enforces |
169
- | --- | --- |
170
- | `pasika/no-cache-flag` | Lint scripts don't pass `--cache` |
171
- | `pasika/no-vulyk-dependency` | `vulyk` is not in `dependencies` |
168
+ | Rule | Enforces |
169
+ | ---------------------------- | --------------------------------- |
170
+ | `pasika/no-cache-flag` | Lint scripts don't pass `--cache` |
171
+ | `pasika/no-vulyk-dependency` | `vulyk` is not in `dependencies` |
172
172
 
173
173
  ### Documentation rules
174
174
 
@@ -180,7 +180,7 @@ Run `pasika coverage --json` for the exact requirement each rule covers.
180
180
 
181
181
  Where a component, hook, value, type, or style belongs depends on which files use it, so the rules marked † index the whole `src/` tree instead of looking at one file. Two consequences:
182
182
 
183
- - **Do not pass `--cache`.** Move a file and the finding belongs to a *different* file, whose cache entry is unchanged — so ESLint would replay a stale verdict. `repository-policy.md` requires lint commands to run without it.
183
+ - **Do not pass `--cache`.** Move a file and the finding belongs to a _different_ file, whose cache entry is unchanged — so ESLint would replay a stale verdict. `repository-policy.md` requires lint commands to run without it.
184
184
  - The index is read from disk rather than from ESLint's file list, so a partial run such as `lint-staged` still judges against the true graph.
185
185
 
186
186
  All are inert in a repository with no `src/` tree.
@@ -112,10 +112,12 @@ declare const cssRules: {
112
112
  "surface-utility": _eslint_css.CSSRuleDefinition;
113
113
  "theme-variable-namespace": _eslint_css.CSSRuleDefinition;
114
114
  "global-css-location": _eslint_css.CSSRuleDefinition;
115
+ "global-stylesheet": _eslint_css.CSSRuleDefinition;
115
116
  };
116
117
 
117
118
  declare const jsonRules: {
118
119
  "no-vulyk-dependency": _eslint_json.JSONRuleDefinition;
120
+ "zirka-installed": _eslint_json.JSONRuleDefinition;
119
121
  };
120
122
 
121
123
  declare const pasikaRules: {
@@ -169,6 +171,7 @@ declare const pasikaRules: {
169
171
  "repeated-structure": eslint.Rule.RuleModule;
170
172
  "zod-schema-validation": eslint.Rule.RuleModule;
171
173
  "source-under-src": eslint.Rule.RuleModule;
174
+ "config-baseline": eslint.Rule.RuleModule;
172
175
  };
173
176
 
174
177
  /** Rule ids as they appear in configuration and in lint output. */
@@ -3143,7 +3143,7 @@ var zodSchemaValidationRule = {
3143
3143
  if (delegatesToSchema(node.body)) return;
3144
3144
  context.report({
3145
3145
  loc: node.loc ?? { line: 1, column: 0 },
3146
- message: "This hand-written type guard validates an unknown value ad hoc. Use a Zod schema instead: schema.safeParse(value) returns a discriminated result whose .success narrows the type. See docs/agent-policy.md"
3146
+ message: "This hand-written type guard validates an unknown value ad hoc. Use a Zod schema instead: schema.safeParse(value) returns a discriminated result whose .success narrows the type. See docs/repository-policy.md"
3147
3147
  });
3148
3148
  }
3149
3149
  return {
@@ -3180,8 +3180,8 @@ var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
3180
3180
  "coverage",
3181
3181
  "dist",
3182
3182
  "docs",
3183
- "enforcement",
3184
3183
  "eslint",
3184
+ "scripts",
3185
3185
  "node_modules",
3186
3186
  "public",
3187
3187
  "static",
@@ -3224,6 +3224,44 @@ var sourceUnderSrcRule = {
3224
3224
  }
3225
3225
  };
3226
3226
 
3227
+ // eslint/pasika/rules/config-baseline.ts
3228
+ import fs4 from "fs";
3229
+ import path33 from "path";
3230
+ var configBaselineRule = {
3231
+ meta: {
3232
+ schema: [],
3233
+ type: "problem",
3234
+ docs: {
3235
+ description: "Require eslint config to reference zirka and tsconfig.json to exist."
3236
+ }
3237
+ },
3238
+ create(context) {
3239
+ const filename = path33.resolve(context.filename);
3240
+ const basename = path33.basename(filename);
3241
+ if (!/^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/.test(basename)) return {};
3242
+ return {
3243
+ Program(node) {
3244
+ const content = context.sourceCode.getText();
3245
+ if (!content.includes("zirka")) {
3246
+ context.report({
3247
+ node,
3248
+ loc: { line: 1, column: 0 },
3249
+ message: "ESLint config must reference zirka. Use the pasika/zirka baseline configuration."
3250
+ });
3251
+ }
3252
+ const projectRoot = path33.dirname(filename);
3253
+ if (!fs4.existsSync(path33.join(projectRoot, "tsconfig.json"))) {
3254
+ context.report({
3255
+ node,
3256
+ loc: { line: 1, column: 0 },
3257
+ message: "No tsconfig.json found. Create one extending the pasika baseline."
3258
+ });
3259
+ }
3260
+ }
3261
+ };
3262
+ }
3263
+ };
3264
+
3227
3265
  // eslint/pasika/rules/md/helpers.ts
3228
3266
  var RFC_PATTERN = /\b(?<keyword>MUST NOT|MUST|SHALL NOT|SHALL|SHOULD NOT|SHOULD|MAY|RECOMMENDED|NOT RECOMMENDED|OPTIONAL|REQUIRED)\b/;
3229
3267
  function containsRfcKeyword(text) {
@@ -3271,7 +3309,7 @@ var docKindSuffixRule = {
3271
3309
  };
3272
3310
 
3273
3311
  // eslint/pasika/rules/md/title-matches-file-name.ts
3274
- import path33 from "path";
3312
+ import path34 from "path";
3275
3313
  function toExpectedFileName(title) {
3276
3314
  return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
3277
3315
  }
@@ -3291,7 +3329,7 @@ var titleMatchesFileNameRule = {
3291
3329
  if (!filename.endsWith(".md")) return;
3292
3330
  const title = getTextContent(node).trim();
3293
3331
  const expectedFileName = toExpectedFileName(title);
3294
- const actualFileName = path33.basename(filename);
3332
+ const actualFileName = path34.basename(filename);
3295
3333
  if (!title) {
3296
3334
  context.report({
3297
3335
  node,
@@ -3764,7 +3802,7 @@ var referenceBlockHeadingsRule = {
3764
3802
  };
3765
3803
 
3766
3804
  // eslint/pasika/rules/md/support-document-placement.ts
3767
- import path34 from "path";
3805
+ import path35 from "path";
3768
3806
  var supportDocumentPlacementRule = {
3769
3807
  meta: {
3770
3808
  type: "problem",
@@ -3778,7 +3816,7 @@ var supportDocumentPlacementRule = {
3778
3816
  root(node) {
3779
3817
  const filename = getFilename(context);
3780
3818
  if (!filename.endsWith(".md")) return;
3781
- const parentFolder = path34.basename(path34.dirname(filename));
3819
+ const parentFolder = path35.basename(path35.dirname(filename));
3782
3820
  if (filename.endsWith("-rule.md") && parentFolder !== "rules") {
3783
3821
  context.report({
3784
3822
  node,
@@ -3821,11 +3859,11 @@ var noTemplatePromptRule = {
3821
3859
  };
3822
3860
 
3823
3861
  // eslint/pasika/rules/md/guide-folder-entry-point.ts
3824
- import path36 from "path";
3862
+ import path37 from "path";
3825
3863
 
3826
3864
  // eslint/pasika/rules/md/project-index.ts
3827
3865
  import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
3828
- import path35 from "path";
3866
+ import path36 from "path";
3829
3867
  var KIND_BY_SUFFIX = [
3830
3868
  ["-rule.md", "rule"],
3831
3869
  ["-guide.md", "guide"],
@@ -3834,7 +3872,7 @@ var KIND_BY_SUFFIX = [
3834
3872
  ];
3835
3873
  function listMarkdownFiles(dir) {
3836
3874
  return readdirSync2(dir).flatMap((entry) => {
3837
- const entryPath = path35.join(dir, entry);
3875
+ const entryPath = path36.join(dir, entry);
3838
3876
  if (statSync3(entryPath).isDirectory()) {
3839
3877
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
3840
3878
  }
@@ -3852,11 +3890,11 @@ function getProjectDocs(docsRoot) {
3852
3890
  if (cached) return cached;
3853
3891
  const files = listMarkdownFiles(docsRoot);
3854
3892
  const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
3855
- const fileName = path35.basename(filePath);
3893
+ const fileName = path36.basename(filePath);
3856
3894
  const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
3857
3895
  return {
3858
3896
  filePath,
3859
- doc: path35.relative(docsRoot, filePath).split(path35.sep).join("/"),
3897
+ doc: path36.relative(docsRoot, filePath).split(path36.sep).join("/"),
3860
3898
  fileName,
3861
3899
  kind,
3862
3900
  title: extractTitle(filePath)
@@ -3866,12 +3904,12 @@ function getProjectDocs(docsRoot) {
3866
3904
  return docs;
3867
3905
  }
3868
3906
  function findDocsRoot(filePath) {
3869
- let dir = path35.dirname(filePath);
3907
+ let dir = path36.dirname(filePath);
3870
3908
  for (; ; ) {
3871
- if (path35.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
3909
+ if (path36.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
3872
3910
  return dir;
3873
3911
  }
3874
- const parent = path35.dirname(dir);
3912
+ const parent = path36.dirname(dir);
3875
3913
  if (parent === dir) return void 0;
3876
3914
  dir = parent;
3877
3915
  }
@@ -3895,13 +3933,13 @@ var guideFolderEntryPointRule = {
3895
3933
  if (!docsRoot) return;
3896
3934
  const docs = getProjectDocs(docsRoot);
3897
3935
  const guideFolders = new Set(
3898
- docs.filter((doc) => ["rules", "references"].includes(path36.basename(path36.dirname(doc.filePath)))).map((doc) => path36.dirname(path36.dirname(doc.filePath))).filter((folder) => path36.resolve(folder) !== path36.resolve(docsRoot))
3936
+ docs.filter((doc) => ["rules", "references"].includes(path37.basename(path37.dirname(doc.filePath)))).map((doc) => path37.dirname(path37.dirname(doc.filePath))).filter((folder) => path37.resolve(folder) !== path37.resolve(docsRoot))
3899
3937
  );
3900
- const currentDir = path36.dirname(filename);
3938
+ const currentDir = path37.dirname(filename);
3901
3939
  if (guideFolders.has(currentDir)) {
3902
- const expectedEntryPoint = `${path36.basename(currentDir)}.md`;
3940
+ const expectedEntryPoint = `${path37.basename(currentDir)}.md`;
3903
3941
  const hasEntryPoint = docs.some(
3904
- (doc) => doc.kind === "guide" && path36.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
3942
+ (doc) => doc.kind === "guide" && path37.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
3905
3943
  );
3906
3944
  if (!hasEntryPoint) {
3907
3945
  context.report({
@@ -4126,7 +4164,7 @@ var noNestedHowToRule = {
4126
4164
 
4127
4165
  // eslint/pasika/rules/md/glossary-term-linking.ts
4128
4166
  import { readFileSync as readFileSync5 } from "fs";
4129
- import path37 from "path";
4167
+ import path38 from "path";
4130
4168
  function extractGlossaryTerms(filePath) {
4131
4169
  const content = readFileSync5(filePath, "utf8");
4132
4170
  const terms = [];
@@ -4180,9 +4218,9 @@ var glossaryTermLinkingRule = {
4180
4218
  const docsRoot = findDocsRoot(filename);
4181
4219
  if (!docsRoot) return;
4182
4220
  const docs = getProjectDocs(docsRoot);
4183
- const guideDir = path37.dirname(filename);
4221
+ const guideDir = path38.dirname(filename);
4184
4222
  const guideReferences = docs.filter(
4185
- (doc) => doc.kind === "reference" && path37.dirname(doc.filePath) === guideDir
4223
+ (doc) => doc.kind === "reference" && path38.dirname(doc.filePath) === guideDir
4186
4224
  );
4187
4225
  if (guideReferences.length === 0) return;
4188
4226
  const glossaryTerms = [];
@@ -4665,6 +4703,39 @@ var globalCssLocationRule = {
4665
4703
  }
4666
4704
  };
4667
4705
 
4706
+ // eslint/pasika/rules/css/global-stylesheet.ts
4707
+ var globalStylesheetRule = {
4708
+ meta: {
4709
+ schema: [],
4710
+ type: "problem",
4711
+ docs: {
4712
+ description: "Require a global stylesheet entry point that registers Tailwind."
4713
+ }
4714
+ },
4715
+ create(context) {
4716
+ return {
4717
+ "StyleSheet:exit"(node) {
4718
+ const registersTailwind = node.children.some(
4719
+ (child) => child.type === "Atrule" && child.name === "import" && JSON.stringify(child.prelude).includes("tailwindcss")
4720
+ );
4721
+ if (!registersTailwind) {
4722
+ const hasProjectCss = node.children.some((child) => {
4723
+ if (child.type === "Atrule" && child.name === "import") return false;
4724
+ if (child.type === "Comment") return false;
4725
+ return true;
4726
+ });
4727
+ if (hasProjectCss) {
4728
+ context.report({
4729
+ node,
4730
+ message: 'Global stylesheet must register Tailwind with @import "tailwindcss".'
4731
+ });
4732
+ }
4733
+ }
4734
+ }
4735
+ };
4736
+ }
4737
+ };
4738
+
4668
4739
  // eslint/pasika/rules/css/index.ts
4669
4740
  var cssRules = {
4670
4741
  "theme-reset": themeResetRule,
@@ -4676,7 +4747,8 @@ var cssRules = {
4676
4747
  "custom-utility-apply": customUtilityApplyRule,
4677
4748
  "surface-utility": surfaceUtilityRule,
4678
4749
  "theme-variable-namespace": themeVariableNamespaceRule,
4679
- "global-css-location": globalCssLocationRule
4750
+ "global-css-location": globalCssLocationRule,
4751
+ "global-stylesheet": globalStylesheetRule
4680
4752
  };
4681
4753
 
4682
4754
  // eslint/pasika/rules/json/no-vulyk-dependency.ts
@@ -4712,9 +4784,46 @@ var noVulykDependencyRule = {
4712
4784
  }
4713
4785
  };
4714
4786
 
4787
+ // eslint/pasika/rules/json/zirka-installed.ts
4788
+ function memberName2(member) {
4789
+ return member.name.type === "String" ? member.name.value : member.name.name;
4790
+ }
4791
+ var zirkaInstalledRule = {
4792
+ meta: {
4793
+ schema: [],
4794
+ type: "problem",
4795
+ docs: {
4796
+ description: "Require zirka to be listed in package.json as a devDependency."
4797
+ }
4798
+ },
4799
+ create(context) {
4800
+ return {
4801
+ Document(node) {
4802
+ const root = node.body;
4803
+ if (root.type !== "Object") return;
4804
+ const allDeps = [];
4805
+ for (const key of ["dependencies", "devDependencies"]) {
4806
+ const section = root.members.find((member) => memberName2(member) === key);
4807
+ if (section?.value.type !== "Object") continue;
4808
+ for (const member of section.value.members) {
4809
+ allDeps.push(memberName2(member));
4810
+ }
4811
+ }
4812
+ if (!allDeps.includes("zirka")) {
4813
+ context.report({
4814
+ node,
4815
+ message: "zirka must be listed in package.json as a devDependency."
4816
+ });
4817
+ }
4818
+ }
4819
+ };
4820
+ }
4821
+ };
4822
+
4715
4823
  // eslint/pasika/rules/json/index.ts
4716
4824
  var jsonRules = {
4717
- "no-vulyk-dependency": noVulykDependencyRule
4825
+ "no-vulyk-dependency": noVulykDependencyRule,
4826
+ "zirka-installed": zirkaInstalledRule
4718
4827
  };
4719
4828
 
4720
4829
  // eslint/pasika/index.ts
@@ -4757,7 +4866,8 @@ var pasikaRules = {
4757
4866
  "shared-style-dedup": sharedStyleDedupRule,
4758
4867
  "repeated-structure": repeatedStructureRule,
4759
4868
  "zod-schema-validation": zodSchemaValidationRule,
4760
- "source-under-src": sourceUnderSrcRule
4869
+ "source-under-src": sourceUnderSrcRule,
4870
+ "config-baseline": configBaselineRule
4761
4871
  };
4762
4872
  var pasikaRuleIds = Object.keys(pasikaRules).map((name) => `pasika/${name}`);
4763
4873
  var pasikaMdRuleIds = Object.keys(mdRules).map((name) => `pasika/${name}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,30 +15,23 @@
15
15
  },
16
16
  "./package.json": "./package.json"
17
17
  },
18
- "bin": {
19
- "pasika": "dist/cli/index.js"
20
- },
21
18
  "files": [
22
19
  "dist"
23
20
  ],
24
21
  "scripts": {
25
22
  "build": "tsup",
26
- "coverage": "tsx cli/index.ts coverage",
23
+ "coverage": "tsx scripts/coverage.ts",
27
24
  "fix": "eslint . --fix",
28
25
  "lint": "eslint .",
29
26
  "prepack": "npm run build",
30
27
  "prepare": "husky",
31
- "test": "node --import tsx --test 'eslint/**/*.test.ts' 'enforcement/**/*.test.ts'",
28
+ "test": "node --import tsx --test 'eslint/**/*.test.ts' 'scripts/**/*.test.ts' 'utils/**/*.test.ts'",
32
29
  "typecheck": "tsc --noEmit"
33
30
  },
34
31
  "lint-staged": {
35
32
  "!(*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx)": "prettier --write --ignore-unknown",
36
33
  "*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx": "npx eslint --fix"
37
34
  },
38
- "dependencies": {
39
- "commander": "^12.1.0",
40
- "zod": "^4.4.3"
41
- },
42
35
  "devDependencies": {
43
36
  "@eslint/css": "^1.4.0",
44
37
  "@eslint/css-tree": "^4.0.5",
@@ -49,16 +42,15 @@
49
42
  "@types/mdast": "^4.0.4",
50
43
  "@types/node": "^26.4.0",
51
44
  "@typescript-eslint/parser": "^8.68.0",
52
- "@typescript-eslint/utils": "^8.68.0",
53
45
  "eslint": "^10.9.1",
54
46
  "husky": "^9.1.7",
55
- "jiti": "^2.7.0",
56
47
  "lint-staged": "^16.4.0",
57
48
  "prettier": "^3.8.1",
58
49
  "tsup": "^8.5.1",
59
50
  "tsx": "^4.23.12",
60
51
  "typescript": "^5.9.2",
61
- "zirka": "^0.0.42"
52
+ "zirka": "^0.0.42",
53
+ "zod": "^4.4.3"
62
54
  },
63
55
  "peerDependencies": {
64
56
  "typescript": ">=5.0.0"
@@ -1 +0,0 @@
1
- #!/usr/bin/env node