pasika 0.5.18 → 0.6.0

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
@@ -231,7 +231,7 @@ Many of the TS/TSX rules call the TypeScript compiler API directly at lint time,
231
231
  ```bash
232
232
  npm run lint
233
233
  npm run typecheck
234
- npm run test
234
+ npm run test:unit
235
235
  npm run coverage
236
236
  npm run build
237
237
  npm run dogfood -- ../some/repo # requires a build; see Dogfooding above
@@ -119,6 +119,8 @@ declare const tailwindRules: {
119
119
  declare const repoPackageJsonRules: {
120
120
  "no-vulyk-dependency": _eslint_json.JSONRuleDefinition;
121
121
  "exact-version": _eslint_json.JSONRuleDefinition;
122
+ "lint-setup": _eslint_json.JSONRuleDefinition;
123
+ "vitest-coverage": _eslint_json.JSONRuleDefinition;
122
124
  };
123
125
  /** Package.json rules specific to a Next.js (or React) application. */
124
126
  declare const nextjsPackageJsonRules: {
@@ -147,6 +149,8 @@ declare const pasikaPlugin: {
147
149
  "nextjs-stack": _eslint_json.JSONRuleDefinition;
148
150
  "no-vulyk-dependency": _eslint_json.JSONRuleDefinition;
149
151
  "exact-version": _eslint_json.JSONRuleDefinition;
152
+ "lint-setup": _eslint_json.JSONRuleDefinition;
153
+ "vitest-coverage": _eslint_json.JSONRuleDefinition;
150
154
  "theme-reset": _eslint_css.CSSRuleDefinition;
151
155
  "root-variables": _eslint_css.CSSRuleDefinition;
152
156
  "apply-usage": _eslint_css.CSSRuleDefinition;
@@ -5786,10 +5786,108 @@ var exactVersionRule = {
5786
5786
  }
5787
5787
  };
5788
5788
 
5789
- // eslint/rules/package-json/no-vulyk-dependency.ts
5789
+ // eslint/rules/package-json/lint-setup.ts
5790
5790
  function memberName2(member) {
5791
5791
  return member.name.type === "String" ? member.name.value : member.name.name;
5792
5792
  }
5793
+ function memberValue2(member) {
5794
+ return member?.value.type === "String" ? member.value.value : void 0;
5795
+ }
5796
+ function stringValues(value) {
5797
+ if (value.type === "String") return [value.value];
5798
+ if (value.type !== "Array") return [];
5799
+ return value.elements.flatMap((element) => element.value.type === "String" ? [element.value.value] : []);
5800
+ }
5801
+ var SOURCE_GLOB_PATTERN = /(?:^|[^a-z])(?:[cm]?[jt]sx?)(?:[^a-z]|$)/i;
5802
+ var DIRECT_ESLINT_PATTERN = /(?:^|&&|\|\||;)\s*(?:npx\s+)?eslint(?:\s|$)/;
5803
+ var REPOSITORY_ARGUMENT_PATTERN = /(?:^|\s)\.(?=\s|$)/;
5804
+ var PRETTIER_PATTERN = /\bprettier\b/;
5805
+ var CHECK_FLAG_PATTERN = /(?:^|\s)(?:--check|-c)(?:\s|$)/;
5806
+ function runsEslintDirectly(command) {
5807
+ return DIRECT_ESLINT_PATTERN.test(command);
5808
+ }
5809
+ function runsPrettier(command) {
5810
+ return PRETTIER_PATTERN.test(command);
5811
+ }
5812
+ function runsNamedScript(command, name) {
5813
+ return command.includes(`npm run ${name}`);
5814
+ }
5815
+ var lintSetupRule = {
5816
+ meta: {
5817
+ schema: [],
5818
+ type: "problem",
5819
+ docs: {
5820
+ description: "Require full-repository lint/format scripts and argument-free staged-file scripts that lint-staged runs by name."
5821
+ }
5822
+ },
5823
+ create(context) {
5824
+ return {
5825
+ Document(node) {
5826
+ const root = node.body;
5827
+ if (root.type !== "Object") return;
5828
+ const scripts = root.members.find((member) => memberName2(member) === "scripts");
5829
+ const scriptMembers = scripts?.value.type === "Object" ? scripts.value.members : [];
5830
+ const findScript = (name) => scriptMembers.find((member) => memberName2(member) === name);
5831
+ const lint = findScript("lint");
5832
+ const lintCommand = memberValue2(lint);
5833
+ if (lintCommand === void 0 || !runsEslintDirectly(lintCommand) || !REPOSITORY_ARGUMENT_PATTERN.test(lintCommand)) {
5834
+ context.report({
5835
+ node: lint ?? node,
5836
+ message: 'package.json must declare a "lint" script that runs ESLint across the repository (e.g. "eslint .").'
5837
+ });
5838
+ }
5839
+ const format = findScript("format");
5840
+ const formatCommand = memberValue2(format);
5841
+ if (formatCommand === void 0 || !runsPrettier(formatCommand) || !CHECK_FLAG_PATTERN.test(formatCommand) || !REPOSITORY_ARGUMENT_PATTERN.test(formatCommand)) {
5842
+ context.report({
5843
+ node: format ?? node,
5844
+ message: 'package.json must declare a "format" script that runs prettier --check across the repository.'
5845
+ });
5846
+ }
5847
+ const lintStaged = findScript("lint:staged");
5848
+ const lintStagedCommand = memberValue2(lintStaged);
5849
+ if (lintStagedCommand === void 0 || !runsEslintDirectly(lintStagedCommand) || REPOSITORY_ARGUMENT_PATTERN.test(lintStagedCommand)) {
5850
+ context.report({
5851
+ node: lintStaged ?? node,
5852
+ message: 'package.json must declare a "lint:staged" script that runs ESLint with no repository-wide argument (e.g. "eslint --fix").'
5853
+ });
5854
+ }
5855
+ const formatStaged = findScript("format:staged");
5856
+ const formatStagedCommand = memberValue2(formatStaged);
5857
+ if (formatStagedCommand === void 0 || !runsPrettier(formatStagedCommand) || REPOSITORY_ARGUMENT_PATTERN.test(formatStagedCommand)) {
5858
+ context.report({
5859
+ node: formatStaged ?? node,
5860
+ message: 'package.json must declare a "format:staged" script that runs prettier with no repository-wide argument (e.g. "prettier --write").'
5861
+ });
5862
+ }
5863
+ const lintStagedConfig = root.members.find((member) => memberName2(member) === "lint-staged");
5864
+ const runsStagedScript = (name) => lintStagedConfig?.value.type === "Object" && lintStagedConfig.value.members.some(
5865
+ (member) => stringValues(member.value).some((command) => runsNamedScript(command, name))
5866
+ );
5867
+ const hasStagedLintEntry = lintStagedConfig?.value.type === "Object" && lintStagedConfig.value.members.some(
5868
+ (member) => SOURCE_GLOB_PATTERN.test(memberName2(member)) && stringValues(member.value).some((command) => runsNamedScript(command, "lint:staged"))
5869
+ );
5870
+ if (!hasStagedLintEntry) {
5871
+ context.report({
5872
+ node: lintStagedConfig ?? node,
5873
+ message: 'package.json lint-staged must run "npm run lint:staged --" for staged JavaScript or TypeScript files.'
5874
+ });
5875
+ }
5876
+ if (!runsStagedScript("format:staged")) {
5877
+ context.report({
5878
+ node: lintStagedConfig ?? node,
5879
+ message: 'package.json lint-staged must run "npm run format:staged --" for staged files ESLint does not already format.'
5880
+ });
5881
+ }
5882
+ }
5883
+ };
5884
+ }
5885
+ };
5886
+
5887
+ // eslint/rules/package-json/no-vulyk-dependency.ts
5888
+ function memberName3(member) {
5889
+ return member.name.type === "String" ? member.name.value : member.name.name;
5890
+ }
5793
5891
  var noVulykDependencyRule = {
5794
5892
  meta: {
5795
5893
  schema: [],
@@ -5804,9 +5902,9 @@ var noVulykDependencyRule = {
5804
5902
  const root = node.body;
5805
5903
  if (root.type !== "Object") return;
5806
5904
  for (const key of ["dependencies", "devDependencies"]) {
5807
- const section = root.members.find((member) => memberName2(member) === key);
5905
+ const section = root.members.find((member) => memberName3(member) === key);
5808
5906
  if (section?.value.type !== "Object") continue;
5809
- const vulyk = section.value.members.find((member) => memberName2(member) === "vulyk");
5907
+ const vulyk = section.value.members.find((member) => memberName3(member) === "vulyk");
5810
5908
  if (vulyk) {
5811
5909
  context.report({
5812
5910
  node: vulyk,
@@ -5836,15 +5934,19 @@ var NEXTJS_STACK_DEV_DEPENDENCIES = [
5836
5934
  "prettier",
5837
5935
  "husky",
5838
5936
  "lint-staged",
5839
- "zirka"
5937
+ "zirka",
5938
+ "jsdom",
5939
+ "@vitejs/plugin-react",
5940
+ "@testing-library/react",
5941
+ "@testing-library/dom"
5840
5942
  ];
5841
- function memberName3(member) {
5943
+ function memberName4(member) {
5842
5944
  return member.name.type === "String" ? member.name.value : member.name.name;
5843
5945
  }
5844
5946
  function sectionPackages(root, section) {
5845
- const member = root.type === "Object" ? root.members.find((entry) => memberName3(entry) === section) : void 0;
5947
+ const member = root.type === "Object" ? root.members.find((entry) => memberName4(entry) === section) : void 0;
5846
5948
  if (member?.value.type !== "Object") return /* @__PURE__ */ new Set();
5847
- return new Set(member.value.members.map(memberName3));
5949
+ return new Set(member.value.members.map(memberName4));
5848
5950
  }
5849
5951
  var nextjsStackRule = {
5850
5952
  meta: {
@@ -5880,19 +5982,151 @@ var nextjsStackRule = {
5880
5982
  }
5881
5983
  };
5882
5984
 
5985
+ // eslint/rules/package-json/vitest-coverage.ts
5986
+ import { existsSync as existsSync2, readFileSync as readFileSync9 } from "fs";
5987
+ import path47 from "path";
5988
+ function memberName5(member) {
5989
+ return member.name.type === "String" ? member.name.value : member.name.name;
5990
+ }
5991
+ function memberValue3(member) {
5992
+ return member?.value.type === "String" ? member.value.value : void 0;
5993
+ }
5994
+ function stringValues2(value) {
5995
+ if (value.type === "String") return [value.value];
5996
+ if (value.type !== "Array") return [];
5997
+ return value.elements.flatMap((element) => element.value.type === "String" ? [element.value.value] : []);
5998
+ }
5999
+ var VITEST_CONFIG_NAMES = [
6000
+ "vitest.config.ts",
6001
+ "vitest.config.mts",
6002
+ "vitest.config.cts",
6003
+ "vitest.config.js",
6004
+ "vitest.config.mjs",
6005
+ "vitest.config.cjs"
6006
+ ];
6007
+ var THRESHOLD_METRICS = ["lines", "functions", "branches", "statements"];
6008
+ var SOURCE_GLOB_PATTERN2 = /(?:^|[^a-z])(?:[cm]?[jt]sx?)(?:[^a-z]|$)/i;
6009
+ var COVERAGE_FLAG_PATTERN = /(?:^|\s)--coverage(?:[=\s]|$)/;
6010
+ var RELATED_PATTERN = /\brelated\b/;
6011
+ var AUTO_UPDATE_PATTERN = /autoUpdate\s*:\s*true/;
6012
+ var CHANGED_FLAG_PATTERN = /--coverage\.changed\b/;
6013
+ var PER_FILE_FLAG_PATTERN = /--coverage\.thresholds\.perFile\b/;
6014
+ var AUTO_UPDATE_DISABLED_FLAG_PATTERN = /--coverage\.thresholds\.autoUpdate=false\b/;
6015
+ function hasEightyOrAboveFlag(command, metric) {
6016
+ return new RegExp(`--coverage\\.thresholds\\.${metric}=(?:8\\d|9\\d|100)\\b`).test(command);
6017
+ }
6018
+ var vitestCoverageRule = {
6019
+ meta: {
6020
+ schema: [],
6021
+ type: "problem",
6022
+ docs: {
6023
+ description: "Require Vitest unit-test scripts, the V8 provider, a rising coverage threshold, and an 80% floor on staged files via lint-staged."
6024
+ }
6025
+ },
6026
+ create(context) {
6027
+ return {
6028
+ Document(node) {
6029
+ const root = node.body;
6030
+ if (root.type !== "Object") return;
6031
+ const devDependencies = root.members.find((member) => memberName5(member) === "devDependencies");
6032
+ const devDependencyNames = new Set(
6033
+ devDependencies?.value.type === "Object" ? devDependencies.value.members.map(memberName5) : []
6034
+ );
6035
+ if (!devDependencyNames.has("vitest")) {
6036
+ context.report({ node, message: "vitest must be listed in package.json as a devDependency." });
6037
+ }
6038
+ if (!devDependencyNames.has("@vitest/coverage-v8")) {
6039
+ context.report({
6040
+ node,
6041
+ message: "@vitest/coverage-v8 must be listed in package.json as a devDependency."
6042
+ });
6043
+ }
6044
+ const scripts = root.members.find((member) => memberName5(member) === "scripts");
6045
+ const scriptMembers = scripts?.value.type === "Object" ? scripts.value.members : [];
6046
+ const unit = scriptMembers.find((member) => memberName5(member) === "test:unit");
6047
+ const unitCommand = memberValue3(unit);
6048
+ if (unitCommand === void 0 || !/\bvitest\b/.test(unitCommand) || COVERAGE_FLAG_PATTERN.test(unitCommand)) {
6049
+ context.report({
6050
+ node: unit ?? node,
6051
+ message: 'package.json must declare a "test:unit" script that runs Vitest without coverage.'
6052
+ });
6053
+ }
6054
+ const coverage = scriptMembers.find((member) => memberName5(member) === "test:unit:coverage");
6055
+ const coverageCommand = memberValue3(coverage);
6056
+ if (coverageCommand === void 0 || !/\bvitest\b/.test(coverageCommand) || !COVERAGE_FLAG_PATTERN.test(coverageCommand)) {
6057
+ context.report({
6058
+ node: coverage ?? node,
6059
+ message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
6060
+ });
6061
+ }
6062
+ const configName = VITEST_CONFIG_NAMES.find((name) => existsSync2(path47.join(context.cwd, name)));
6063
+ if (!configName) {
6064
+ context.report({
6065
+ node,
6066
+ message: "No vitest config found. Create one with a coverage threshold above zero for lines, functions, branches, and statements."
6067
+ });
6068
+ return;
6069
+ }
6070
+ const content = readFileSync9(path47.join(context.cwd, configName), "utf8");
6071
+ for (const metric of THRESHOLD_METRICS) {
6072
+ if (!new RegExp(`\\b${metric}\\s*:\\s*[1-9]\\d*`).test(content)) {
6073
+ context.report({ node, message: `${configName} must set a coverage threshold above zero for ${metric}.` });
6074
+ }
6075
+ }
6076
+ if (!AUTO_UPDATE_PATTERN.test(content)) {
6077
+ context.report({
6078
+ node,
6079
+ message: `${configName} must set coverage.thresholds.autoUpdate to true.`
6080
+ });
6081
+ }
6082
+ const changedCoverage = scriptMembers.find((member) => memberName5(member) === "test:unit:coverage:staged");
6083
+ const changedCoverageCommand = memberValue3(changedCoverage);
6084
+ if (changedCoverageCommand === void 0 || !/\bvitest\b/.test(changedCoverageCommand) || !RELATED_PATTERN.test(changedCoverageCommand) || !COVERAGE_FLAG_PATTERN.test(changedCoverageCommand)) {
6085
+ context.report({
6086
+ node: changedCoverage ?? node,
6087
+ message: 'package.json must declare a "test:unit:coverage:staged" script that runs vitest related with coverage.'
6088
+ });
6089
+ }
6090
+ const lintStaged = root.members.find((member) => memberName5(member) === "lint-staged");
6091
+ const hasStagedChangedCoverage = lintStaged?.value.type === "Object" && lintStaged.value.members.some(
6092
+ (member) => SOURCE_GLOB_PATTERN2.test(memberName5(member)) && stringValues2(member.value).some((command) => command.includes("npm run test:unit:coverage:staged"))
6093
+ );
6094
+ if (!hasStagedChangedCoverage) {
6095
+ context.report({
6096
+ node: lintStaged ?? node,
6097
+ message: 'package.json lint-staged must run "npm run test:unit:coverage:staged" for staged JavaScript or TypeScript files.'
6098
+ });
6099
+ }
6100
+ const changedCoverageCommandText = changedCoverageCommand ?? "";
6101
+ const hasEightyFloor = THRESHOLD_METRICS.every(
6102
+ (metric) => hasEightyOrAboveFlag(changedCoverageCommandText, metric)
6103
+ );
6104
+ if (!CHANGED_FLAG_PATTERN.test(changedCoverageCommandText) || !PER_FILE_FLAG_PATTERN.test(changedCoverageCommandText) || !hasEightyFloor || !AUTO_UPDATE_DISABLED_FLAG_PATTERN.test(changedCoverageCommandText)) {
6105
+ context.report({
6106
+ node: changedCoverage ?? node,
6107
+ message: 'package.json "test:unit:coverage:staged" script must pass --coverage.changed, a --coverage.thresholds.perFile of at least 80 for lines, functions, branches, and statements, and --coverage.thresholds.autoUpdate=false.'
6108
+ });
6109
+ }
6110
+ }
6111
+ };
6112
+ }
6113
+ };
6114
+
5883
6115
  // eslint/rules/package-json/index.ts
5884
6116
  var repoPackageJsonRules = {
5885
6117
  "no-vulyk-dependency": noVulykDependencyRule,
5886
- "exact-version": exactVersionRule
6118
+ "exact-version": exactVersionRule,
6119
+ "lint-setup": lintSetupRule,
6120
+ "vitest-coverage": vitestCoverageRule
5887
6121
  };
5888
6122
  var nextjsPackageJsonRules = {
5889
6123
  "nextjs-stack": nextjsStackRule
5890
6124
  };
5891
6125
 
5892
6126
  // eslint/rules/husky/husky-hook.ts
5893
- import { existsSync as existsSync2, readFileSync as readFileSync9 } from "fs";
5894
- import path47 from "path";
5895
- function memberName4(member) {
6127
+ import { existsSync as existsSync3, readFileSync as readFileSync10 } from "fs";
6128
+ import path48 from "path";
6129
+ function memberName6(member) {
5896
6130
  return member.name.type === "String" ? member.name.value : member.name.name;
5897
6131
  }
5898
6132
  var huskyHookRule = {
@@ -5900,7 +6134,7 @@ var huskyHookRule = {
5900
6134
  schema: [],
5901
6135
  type: "problem",
5902
6136
  docs: {
5903
- description: "Require a pre-commit hook that runs lint-staged, typecheck, and the libyear drift check."
6137
+ description: "Require a pre-commit hook that runs lint-staged and the libyear drift check directly, and the typecheck and suppression-file ratchet through named package.json scripts."
5904
6138
  }
5905
6139
  },
5906
6140
  create(context) {
@@ -5908,29 +6142,39 @@ var huskyHookRule = {
5908
6142
  Document(node) {
5909
6143
  const root = node.body;
5910
6144
  if (root.type !== "Object") return;
5911
- const scriptName = context.filename;
5912
- if (!scriptName.endsWith("package.json")) return;
5913
- const hookPath = path47.join(context.cwd, ".husky", "pre-commit");
5914
- if (!existsSync2(hookPath)) {
6145
+ if (!context.filename.endsWith("package.json")) return;
6146
+ const hookPath = path48.join(context.cwd, ".husky", "pre-commit");
6147
+ if (!existsSync3(hookPath)) {
5915
6148
  context.report({
5916
6149
  node,
5917
6150
  message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
5918
6151
  });
5919
6152
  return;
5920
6153
  }
5921
- const content = readFileSync9(hookPath, "utf8");
6154
+ const content = readFileSync10(hookPath, "utf8");
6155
+ const scripts = root.members.find((member) => memberName6(member) === "scripts");
6156
+ const scriptNames = new Set(scripts?.value.type === "Object" ? scripts.value.members.map(memberName6) : []);
6157
+ const requireNamedScript = (name) => {
6158
+ if (!scriptNames.has(name)) {
6159
+ context.report({ node, message: `package.json must declare a "${name}" script.` });
6160
+ }
6161
+ if (!content.includes(`npm run ${name}`)) {
6162
+ context.report({ node, message: `.husky/pre-commit must run npm run ${name}.` });
6163
+ }
6164
+ };
5922
6165
  if (!content.includes("lint-staged")) {
5923
6166
  context.report({ node, message: ".husky/pre-commit must run lint-staged." });
5924
6167
  }
5925
- if (!content.includes("typecheck")) {
5926
- context.report({ node, message: ".husky/pre-commit must run npm run typecheck." });
5927
- }
6168
+ requireNamedScript("typecheck");
5928
6169
  if (!content.includes("libyear --limit-major-individual=1")) {
5929
6170
  context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
5930
6171
  }
5931
- const scripts = root.members.find((member) => memberName4(member) === "scripts");
6172
+ const suppressionsPath = path48.join(context.cwd, "eslint-suppressions.json");
6173
+ if (existsSync3(suppressionsPath)) {
6174
+ requireNamedScript("lint:prune");
6175
+ }
5932
6176
  if (scripts?.value.type === "Object") {
5933
- const prepare = scripts.value.members.find((member) => memberName4(member) === "prepare");
6177
+ const prepare = scripts.value.members.find((member) => memberName6(member) === "prepare");
5934
6178
  if (prepare?.value.type === "String" && !prepare.value.value.includes("husky")) {
5935
6179
  context.report({
5936
6180
  node: prepare,
@@ -5949,8 +6193,8 @@ var huskyRules = {
5949
6193
  };
5950
6194
 
5951
6195
  // eslint/rules/vulyk/vulyk-docs.ts
5952
- import { existsSync as existsSync3, readFileSync as readFileSync10 } from "fs";
5953
- import path48 from "path";
6196
+ import { existsSync as existsSync4, readFileSync as readFileSync11 } from "fs";
6197
+ import path49 from "path";
5954
6198
  var PASIKA_REPO = "Bredansky/pasika";
5955
6199
  var BASE_REQUIRED_DOCS = [
5956
6200
  { name: "documentation-guide", path: "docs/documentation-guide" },
@@ -5961,13 +6205,13 @@ var NEXTJS_REQUIRED_DOCS = [
5961
6205
  { name: "next-codebase-guide", path: "docs/next-codebase-guide" },
5962
6206
  { name: "next-tailwind-guide", path: "docs/next-tailwind-guide" }
5963
6207
  ];
5964
- function memberName5(member) {
6208
+ function memberName7(member) {
5965
6209
  return member.name.type === "String" ? member.name.value : member.name.name;
5966
6210
  }
5967
6211
  function hasDependency(root, name) {
5968
- const section = root.members.find((member) => memberName5(member) === "dependencies");
6212
+ const section = root.members.find((member) => memberName7(member) === "dependencies");
5969
6213
  if (section?.value.type !== "Object") return false;
5970
- return section.value.members.some((member) => memberName5(member) === name);
6214
+ return section.value.members.some((member) => memberName7(member) === name);
5971
6215
  }
5972
6216
  var vulykDocsRule = {
5973
6217
  meta: {
@@ -5983,16 +6227,16 @@ var vulykDocsRule = {
5983
6227
  if (!context.filename.endsWith("package.json")) return;
5984
6228
  const root = node.body;
5985
6229
  if (root.type !== "Object") return;
5986
- const projectRoot = path48.dirname(path48.resolve(context.filename));
5987
- const configPath = path48.join(projectRoot, "vulyk.config.ts");
5988
- if (!existsSync3(configPath)) {
6230
+ const projectRoot = path49.dirname(path49.resolve(context.filename));
6231
+ const configPath = path49.join(projectRoot, "vulyk.config.ts");
6232
+ if (!existsSync4(configPath)) {
5989
6233
  context.report({
5990
6234
  node,
5991
6235
  message: "No vulyk.config.ts found. Run npx vulyk@latest init to create one that tracks the framework's docs."
5992
6236
  });
5993
6237
  return;
5994
6238
  }
5995
- const config = readFileSync10(configPath, "utf8");
6239
+ const config = readFileSync11(configPath, "utf8");
5996
6240
  if (!config.includes(PASIKA_REPO)) {
5997
6241
  context.report({
5998
6242
  node,
@@ -6009,8 +6253,8 @@ var vulykDocsRule = {
6009
6253
  });
6010
6254
  }
6011
6255
  }
6012
- const agentsPath = path48.join(projectRoot, "AGENTS.md");
6013
- if (!existsSync3(agentsPath)) {
6256
+ const agentsPath = path49.join(projectRoot, "AGENTS.md");
6257
+ if (!existsSync4(agentsPath)) {
6014
6258
  context.report({
6015
6259
  node,
6016
6260
  message: "No AGENTS.md found. Run npx vulyk@latest agents to generate the agent file that routes to the tracked docs."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.5.18",
3
+ "version": "0.6.0",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,15 +22,23 @@
22
22
  "coverage": "tsx scripts/coverage.ts",
23
23
  "dogfood": "tsx scripts/dogfood.ts",
24
24
  "fix": "eslint . --fix",
25
+ "format": "prettier --check . --ignore-unknown",
26
+ "format:staged": "prettier --write --ignore-unknown",
25
27
  "lint": "eslint .",
28
+ "lint:staged": "eslint --fix",
26
29
  "prepack": "npm run build",
27
30
  "prepare": "husky",
28
- "test": "node --import tsx --test 'eslint/**/*.test.ts' 'scripts/**/*.test.ts' 'utils/**/*.test.ts'",
31
+ "test:unit": "vitest run",
32
+ "test:unit:coverage": "vitest run --coverage",
33
+ "test:unit:coverage:staged": "vitest related --run --coverage --coverage.changed --coverage.thresholds.perFile --coverage.thresholds.lines=80 --coverage.thresholds.functions=80 --coverage.thresholds.branches=80 --coverage.thresholds.statements=80 --coverage.thresholds.autoUpdate=false",
29
34
  "typecheck": "tsc --noEmit"
30
35
  },
31
36
  "lint-staged": {
32
- "!(*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx)": "prettier --write --ignore-unknown",
33
- "*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx": "npx eslint --fix"
37
+ "!(*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx)": "npm run format:staged --",
38
+ "*.ts|*.mts|*.cts|*.tsx|*.js|*.cjs|*.mjs|*.jsx": [
39
+ "npm run lint:staged --",
40
+ "npm run test:unit:coverage:staged --"
41
+ ]
34
42
  },
35
43
  "dependencies": {
36
44
  "@typescript-eslint/parser": "8.68.0",
@@ -45,6 +53,7 @@
45
53
  "@types/estree": "1.0.9",
46
54
  "@types/mdast": "4.0.4",
47
55
  "@types/node": "26.4.0",
56
+ "@vitest/coverage-v8": "5.0.0",
48
57
  "eslint": "10.9.1",
49
58
  "husky": "9.1.7",
50
59
  "lint-staged": "17.4.1",
@@ -52,6 +61,7 @@
52
61
  "tsup": "8.5.1",
53
62
  "tsx": "4.23.12",
54
63
  "typescript": "6.0.3",
64
+ "vitest": "5.0.0",
55
65
  "zirka": "0.0.48",
56
66
  "zod": "4.4.3"
57
67
  },