pasika 0.3.4 → 0.3.5

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/dist/cli/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  // cli/index.ts
4
4
  import { existsSync } from "fs";
5
- import path39 from "path";
5
+ import path41 from "path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // enforcement/coverage.ts
9
9
  import { readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync, statSync as statSync5 } from "fs";
10
- import path38 from "path";
10
+ import path40 from "path";
11
11
  import { z as z3 } from "zod";
12
12
 
13
13
  // eslint/pasika/rules/filename-case.ts
@@ -598,8 +598,8 @@ var enforceCvaVariantPropsRule = {
598
598
  if (node.typeAnnotation?.type !== "TSTypeLiteral") return;
599
599
  for (const member of node.typeAnnotation.members ?? []) {
600
600
  if (member.type !== "TSPropertySignature") continue;
601
- const keyName = member.key?.type === "Identifier" ? member.key.name : void 0;
602
- if (!keyName) continue;
601
+ const keyName2 = member.key?.type === "Identifier" ? member.key.name : void 0;
602
+ if (!keyName2) continue;
603
603
  const typeAnn = member.typeAnnotation?.typeAnnotation;
604
604
  if (typeAnn?.type !== "TSUnionType") continue;
605
605
  const allStringLiterals = (typeAnn.types ?? []).every(
@@ -607,10 +607,10 @@ var enforceCvaVariantPropsRule = {
607
607
  );
608
608
  if (allStringLiterals && (typeAnn.types?.length ?? 0) >= 2) {
609
609
  for (const variantNames of cvaDefinitions.values()) {
610
- if (variantNames.includes(keyName)) {
610
+ if (variantNames.includes(keyName2)) {
611
611
  context.report({
612
612
  node,
613
- message: `Variant prop "${keyName}" duplicates CVA variant values. Use VariantProps<typeof variants> instead of manually writing union types. See docs/styling-guide/rules/component-variant-rule.md`
613
+ message: `Variant prop "${keyName2}" duplicates CVA variant values. Use VariantProps<typeof variants> instead of manually writing union types. See docs/styling-guide/rules/component-variant-rule.md`
614
614
  });
615
615
  return;
616
616
  }
@@ -1346,21 +1346,27 @@ var dataTestIdCaseRule = {
1346
1346
  Program(node) {
1347
1347
  for (const component of components) {
1348
1348
  const root = findSimpleRoot(component, text, filename);
1349
- if (!root) continue;
1350
- const { value } = getTestId(root);
1351
- const expected = component.smart ? component.name : toKebabCase(component.name);
1352
- if (component.smart && value === void 0) {
1353
- context.report({
1354
- node,
1355
- message: `Smart component "${component.name}" with one outer DOM element must set data-testid="${expected}".`
1356
- });
1357
- continue;
1358
- }
1359
- if (value === void 0 || value === expected) continue;
1360
- if (component.smart ? !isPascalCase5(value) || value !== expected : !isKebabCase3(value)) {
1349
+ if (root) {
1350
+ const { value } = getTestId(root);
1351
+ const expected = component.smart ? component.name : toKebabCase(component.name);
1352
+ if (component.smart && value === void 0) {
1353
+ context.report({
1354
+ node,
1355
+ message: `Smart component "${component.name}" with one outer DOM element must set data-testid="${expected}".`
1356
+ });
1357
+ continue;
1358
+ }
1359
+ if (value === void 0 || value === expected) continue;
1360
+ if (component.smart ? !isPascalCase5(value) || value !== expected : !isKebabCase3(value)) {
1361
+ context.report({
1362
+ node,
1363
+ message: `data-testid for ${component.smart ? "smart" : "dumb"} component "${component.name}" must be ${component.smart ? "PascalCase" : "kebab-case"}: expected "${expected}".`
1364
+ });
1365
+ }
1366
+ } else if (component.smart) {
1361
1367
  context.report({
1362
1368
  node,
1363
- message: `data-testid for ${component.smart ? "smart" : "dumb"} component "${component.name}" must be ${component.smart ? "PascalCase" : "kebab-case"}: expected "${expected}".`
1369
+ message: `Smart component "${component.name}" has no single outer element; wrap its content in one outer element with data-testid="${component.name}" instead of rendering multiple roots. See docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md`
1364
1370
  });
1365
1371
  }
1366
1372
  }
@@ -1843,14 +1849,14 @@ var cvaAppearancePropsRule = {
1843
1849
  if (node.typeAnnotation?.type !== "TSTypeLiteral") return;
1844
1850
  for (const member of node.typeAnnotation.members ?? []) {
1845
1851
  if (member.type !== "TSPropertySignature") continue;
1846
- const keyName = member.key?.type === "Identifier" ? member.key.name : void 0;
1847
- if (!keyName || !VISUAL_OPTION_NAMES.has(keyName)) continue;
1852
+ const keyName2 = member.key?.type === "Identifier" ? member.key.name : void 0;
1853
+ if (!keyName2 || !VISUAL_OPTION_NAMES.has(keyName2)) continue;
1848
1854
  const typeAnnotation = member.typeAnnotation?.typeAnnotation ?? member.typeAnnotation;
1849
1855
  if (!typeAnnotation) continue;
1850
1856
  if (typeAnnotation.type === "TSUnionType" || typeAnnotation.type === "TSTypeLiteral") {
1851
1857
  context.report({
1852
1858
  node,
1853
- message: `Visual option prop "${keyName}" must be defined through a cva() call rather than a manual union type. See docs/styling-guide/rules/component-variant-rule.md`
1859
+ message: `Visual option prop "${keyName2}" must be defined through a cva() call rather than a manual union type. See docs/styling-guide/rules/component-variant-rule.md`
1854
1860
  });
1855
1861
  }
1856
1862
  }
@@ -1870,15 +1876,15 @@ function extractCvaInfo(node) {
1870
1876
  if (options?.type !== "ObjectExpression") return { variantNames, compoundVariants };
1871
1877
  for (const prop of options.properties) {
1872
1878
  if (prop.type !== "Property") continue;
1873
- const keyName = prop.key.type === "Identifier" ? prop.key.name : void 0;
1874
- if (keyName === "variants" && prop.value.type === "ObjectExpression") {
1879
+ const keyName2 = prop.key.type === "Identifier" ? prop.key.name : void 0;
1880
+ if (keyName2 === "variants" && prop.value.type === "ObjectExpression") {
1875
1881
  for (const vProp of prop.value.properties) {
1876
1882
  if (vProp.type !== "Property") continue;
1877
1883
  const vName = vProp.key.type === "Identifier" ? vProp.key.name : void 0;
1878
1884
  if (vName) variantNames.add(vName);
1879
1885
  }
1880
1886
  }
1881
- if (keyName === "compoundVariants" && prop.value.type === "ArrayExpression") {
1887
+ if (keyName2 === "compoundVariants" && prop.value.type === "ArrayExpression") {
1882
1888
  for (const entry of prop.value.elements) {
1883
1889
  if (entry?.type !== "ObjectExpression") continue;
1884
1890
  for (const cvProp of entry.properties) {
@@ -2649,8 +2655,279 @@ var localePlacementRule = {
2649
2655
  }
2650
2656
  };
2651
2657
 
2652
- // eslint/pasika/rules/shared-style-dedup.ts
2658
+ // eslint/pasika/rules/sole-state-owner.ts
2653
2659
  import path29 from "path";
2660
+ import ts6 from "typescript";
2661
+ function findStateHooks(node) {
2662
+ const hooks = [];
2663
+ const visit = (child) => {
2664
+ if (ts6.isCallExpression(child) && ts6.isIdentifier(child.expression) && child.expression.text === "useState") {
2665
+ if (ts6.isVariableDeclaration(child.parent)) {
2666
+ const { name } = child.parent;
2667
+ if (ts6.isArrayBindingPattern(name) && name.elements.length >= 2) {
2668
+ const value = name.elements[0];
2669
+ const updater = name.elements[1];
2670
+ if (value && updater && ts6.isBindingElement(value) && ts6.isBindingElement(updater)) {
2671
+ const valueName = value.name;
2672
+ const updaterName = updater.name;
2673
+ if (ts6.isIdentifier(valueName) && ts6.isIdentifier(updaterName)) {
2674
+ hooks.push({ value: valueName.text, updater: updaterName.text });
2675
+ }
2676
+ }
2677
+ }
2678
+ }
2679
+ }
2680
+ ts6.forEachChild(child, visit);
2681
+ };
2682
+ visit(node);
2683
+ return hooks;
2684
+ }
2685
+ function isHookUsage(node, hook) {
2686
+ if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression) && node.expression.text === hook.updater) {
2687
+ return "updater";
2688
+ }
2689
+ if (ts6.isIdentifier(node) && node.text === hook.value) {
2690
+ const parent = node.parent;
2691
+ if (ts6.isBindingElement(parent) || ts6.isPropertyAccessExpression(parent) || ts6.isShorthandPropertyAssignment(parent)) {
2692
+ return void 0;
2693
+ }
2694
+ return "value";
2695
+ }
2696
+ return void 0;
2697
+ }
2698
+ function topLevelJsxChildren(initial) {
2699
+ if (!initial) return void 0;
2700
+ let expression = initial;
2701
+ while (ts6.isParenthesizedExpression(expression)) expression = expression.expression;
2702
+ if (ts6.isJsxFragment(expression)) {
2703
+ const children = expression.children.filter(
2704
+ (c) => !ts6.isJsxText(c) && !ts6.isJsxSpreadAttribute(c)
2705
+ );
2706
+ return { root: expression, children };
2707
+ }
2708
+ if (ts6.isJsxElement(expression)) {
2709
+ const children = expression.children.filter(
2710
+ (c) => !ts6.isJsxText(c) && !ts6.isJsxSpreadAttribute(c)
2711
+ );
2712
+ return { root: expression, children };
2713
+ }
2714
+ return void 0;
2715
+ }
2716
+ function collectUsesIn(child, hook) {
2717
+ const positions = [];
2718
+ let count = 0;
2719
+ const visit = (node) => {
2720
+ if (isHookUsage(node, hook)) {
2721
+ positions.push(node);
2722
+ count += 1;
2723
+ }
2724
+ if (ts6.isFunctionDeclaration(node) || ts6.isClassDeclaration(node)) return;
2725
+ ts6.forEachChild(node, visit);
2726
+ };
2727
+ visit(child);
2728
+ return { positions, count };
2729
+ }
2730
+ var soleStateOwnerRule = {
2731
+ meta: {
2732
+ schema: [],
2733
+ type: "problem",
2734
+ docs: {
2735
+ description: "Require extracting a JSX part that is the sole owner of a useState hook as a named component."
2736
+ }
2737
+ },
2738
+ create(context) {
2739
+ const filename = path29.resolve(context.filename);
2740
+ if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) return {};
2741
+ const text = context.sourceCode.text;
2742
+ const components = parseComponentInfo(text, filename);
2743
+ return {
2744
+ Program(node) {
2745
+ for (const component of components) {
2746
+ const hooks = findStateHooks(component.declaration);
2747
+ for (const hook of hooks) {
2748
+ const result = analyzeSoleOwner(component.declaration, hook);
2749
+ if (!result) continue;
2750
+ const { run, totalTopLevel } = result;
2751
+ if (run === 0 || totalTopLevel === 0) continue;
2752
+ context.report({
2753
+ node,
2754
+ loc: { line: 1, column: 0 },
2755
+ message: `Component "${component.name}" uses state "${hook.value}" in ${String(run)} contiguous top-level JSX part${run === 1 ? "" : "s"}; extract that part into a named component that owns useState instead of reading it in the parent. See docs/code-organization-guide/rules/sole-state-owner-rule.md`
2756
+ });
2757
+ }
2758
+ }
2759
+ }
2760
+ };
2761
+ }
2762
+ };
2763
+ function analyzeSoleOwner(declaration, hook) {
2764
+ const body = ts6.isFunctionDeclaration(declaration) ? declaration.body : void 0;
2765
+ if (!body || !ts6.isBlock(body)) return void 0;
2766
+ const returns = [];
2767
+ const visit = (node) => {
2768
+ if (node !== body && (ts6.isFunctionLike(node) || ts6.isClassLike(node))) return;
2769
+ if (ts6.isReturnStatement(node)) returns.push(node);
2770
+ ts6.forEachChild(node, visit);
2771
+ };
2772
+ visit(body);
2773
+ if (returns.length !== 1) return void 0;
2774
+ const single = returns[0];
2775
+ if (!single?.expression || !ts6.isExpression(single.expression)) return void 0;
2776
+ const returnExpression = single.expression;
2777
+ const root = topLevelJsxChildren(returnExpression);
2778
+ if (!root || root.children.length === 0) return void 0;
2779
+ const totalTopLevel = root.children.length;
2780
+ const usagePerChild = root.children.map((child) => collectUsesIn(child, hook));
2781
+ const outsideJsx = usesOutsideJsx(declaration, hook, root.children);
2782
+ if (outsideJsx) return void 0;
2783
+ const usedLabels = usagePerChild.map((u) => u.count > 0);
2784
+ const firstUsed = usedLabels.findIndex((v) => v);
2785
+ const lastUsed = usedLabels.lastIndexOf(true);
2786
+ if (firstUsed === -1 || lastUsed === -1) return void 0;
2787
+ const windowHasUnused = usedLabels.slice(firstUsed, lastUsed + 1).some((v) => !v);
2788
+ if (windowHasUnused) return void 0;
2789
+ const run = lastUsed - firstUsed + 1;
2790
+ const hasOutside = firstUsed > 0 || lastUsed < totalTopLevel - 1;
2791
+ if (!hasOutside) return void 0;
2792
+ return { run, totalTopLevel };
2793
+ }
2794
+ function usesOutsideJsx(declaration, hook, children) {
2795
+ let outside = false;
2796
+ const visit = (node) => {
2797
+ if (outside) return;
2798
+ if (node !== declaration && (ts6.isFunctionDeclaration(node) || ts6.isClassDeclaration(node))) return;
2799
+ if (isHookUsage(node, hook)) {
2800
+ let current = node;
2801
+ let isInJsxChild = false;
2802
+ while (!ts6.isSourceFile(current)) {
2803
+ if (children.includes(current)) {
2804
+ isInJsxChild = true;
2805
+ break;
2806
+ }
2807
+ current = current.parent;
2808
+ }
2809
+ if (!isInJsxChild) outside = true;
2810
+ }
2811
+ ts6.forEachChild(node, visit);
2812
+ };
2813
+ visit(declaration);
2814
+ return outside;
2815
+ }
2816
+
2817
+ // eslint/pasika/rules/locale-key-shape.ts
2818
+ import path30 from "path";
2819
+ var MAX_KEY_LENGTH = 30;
2820
+ var ROLE_POSTFIXES = /* @__PURE__ */ new Set([
2821
+ "Button",
2822
+ "Link",
2823
+ "Dialog",
2824
+ "AlertDialog",
2825
+ "Tooltip",
2826
+ "Banner",
2827
+ "Alert",
2828
+ "Status",
2829
+ "Tab",
2830
+ "TabPanel",
2831
+ "Menu",
2832
+ "MenuItem",
2833
+ "MenuBar",
2834
+ "Checkbox",
2835
+ "Radio",
2836
+ "RadioGroup",
2837
+ "Switch",
2838
+ "ComboBox",
2839
+ "ListBox",
2840
+ "Option",
2841
+ "Search",
2842
+ "SearchBox",
2843
+ "Navigation",
2844
+ "ProgressBar",
2845
+ "Slider",
2846
+ "SpinButton",
2847
+ "TextBox",
2848
+ "Tree",
2849
+ "TreeItem",
2850
+ "TreeGrid",
2851
+ "Grid",
2852
+ "GridCell",
2853
+ "Feed",
2854
+ "Log",
2855
+ "Timer",
2856
+ "Marquee",
2857
+ "Region",
2858
+ "Main",
2859
+ "Complementary",
2860
+ "ContentInfo",
2861
+ "Form",
2862
+ "Toolbar",
2863
+ "ScrollBar"
2864
+ ]);
2865
+ var CAMEL_CASE = /^[a-z][a-zA-Z0-9]*$/;
2866
+ function isLocalesFile2(filename) {
2867
+ const segments = path30.resolve(filename).split(path30.sep);
2868
+ const srcIdx = segments.lastIndexOf("src");
2869
+ return srcIdx !== -1 && segments[srcIdx + 1] === "locales";
2870
+ }
2871
+ function keyName(key) {
2872
+ if (key.type === "Identifier") return key.name;
2873
+ if (key.type === "Literal" && typeof key.value === "string") return key.value;
2874
+ return void 0;
2875
+ }
2876
+ function reportFor(context, node, message) {
2877
+ context.report({ node, message });
2878
+ }
2879
+ function checkKey(context, node, name) {
2880
+ if (!CAMEL_CASE.test(name)) {
2881
+ reportFor(
2882
+ context,
2883
+ node,
2884
+ `Locale key "${name}" must be camelCase English based on the text. See docs/code-organization-guide/rules/locales-rule.md`
2885
+ );
2886
+ return;
2887
+ }
2888
+ if (name.length <= MAX_KEY_LENGTH) return;
2889
+ const hasRolePostfix = [...ROLE_POSTFIXES].some((role) => name.endsWith(role));
2890
+ if (hasRolePostfix) return;
2891
+ reportFor(
2892
+ context,
2893
+ node,
2894
+ `Locale key "${name}" is longer than ${String(MAX_KEY_LENGTH)} characters, so it must describe the message's purpose instead of the text; end it with an element role such as "Button", "Link", or "Dialog" (see WAI-ARIA roles). See docs/code-organization-guide/rules/locales-rule.md`
2895
+ );
2896
+ }
2897
+ var localeKeyShapeRule = {
2898
+ meta: {
2899
+ schema: [],
2900
+ type: "problem",
2901
+ docs: {
2902
+ description: "Require locale keys to be camelCase, and purpose-based element roles when long."
2903
+ }
2904
+ },
2905
+ create(context) {
2906
+ if (!isLocalesFile2(context.filename)) return {};
2907
+ function visitObject(node) {
2908
+ for (const property of node.properties) {
2909
+ if (property.type !== "Property") continue;
2910
+ const name = keyName(property.key);
2911
+ if (name === void 0) continue;
2912
+ if (property.value.type === "ObjectExpression") {
2913
+ visitObject(property.value);
2914
+ } else if (property.value.type === "Literal" && typeof property.value.value === "string") {
2915
+ checkKey(context, property, name);
2916
+ }
2917
+ }
2918
+ }
2919
+ return {
2920
+ VariableDeclarator(node) {
2921
+ if (node.id.type !== "Identifier" || node.id.name !== "locales") return;
2922
+ if (node.init?.type !== "ObjectExpression") return;
2923
+ visitObject(node.init);
2924
+ }
2925
+ };
2926
+ }
2927
+ };
2928
+
2929
+ // eslint/pasika/rules/shared-style-dedup.ts
2930
+ import path31 from "path";
2654
2931
  import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
2655
2932
  var CLASS_NAME = /className="(?<classes>[^"]+)"/g;
2656
2933
  var comboCache;
@@ -2691,8 +2968,8 @@ var sharedStyleDedupRule = {
2691
2968
  }
2692
2969
  },
2693
2970
  create(context) {
2694
- const sourceRoot2 = path29.resolve("src");
2695
- const file = path29.resolve(context.filename);
2971
+ const sourceRoot2 = path31.resolve("src");
2972
+ const file = path31.resolve(context.filename);
2696
2973
  const segments = segmentsOf(file, sourceRoot2);
2697
2974
  if (segments.length === 0) return {};
2698
2975
  const index = getProjectIndex(sourceRoot2);
@@ -2714,6 +2991,114 @@ var sharedStyleDedupRule = {
2714
2991
  }
2715
2992
  };
2716
2993
 
2994
+ // eslint/pasika/rules/repeated-structure.ts
2995
+ function nodeKind(node) {
2996
+ return node.type;
2997
+ }
2998
+ function isJsxNode(node) {
2999
+ const kind = nodeKind(node);
3000
+ return kind === "JSXElement" || kind === "JSXFragment";
3001
+ }
3002
+ function isJsxExpressionContainer(node) {
3003
+ return node.type === "JSXExpressionContainer";
3004
+ }
3005
+ function walkJsx(current, visit) {
3006
+ if (isJsxNode(current)) {
3007
+ visit(current);
3008
+ for (const child of current.children ?? []) walkJsx(child, visit);
3009
+ } else if (isJsxExpressionContainer(current)) {
3010
+ if (current.expression) walkJsx(current.expression, visit);
3011
+ } else if (current.type === "ArrowFunctionExpression") {
3012
+ walkJsx(current.body, visit);
3013
+ } else if (current.type === "CallExpression") {
3014
+ for (const argument of current.arguments) walkJsx(argument, visit);
3015
+ } else if (current.type === "ConditionalExpression") {
3016
+ walkJsx(current.consequent, visit);
3017
+ walkJsx(current.alternate, visit);
3018
+ } else if (current.type === "LogicalExpression") {
3019
+ walkJsx(current.left, visit);
3020
+ walkJsx(current.right, visit);
3021
+ }
3022
+ }
3023
+ function signature(node) {
3024
+ const parts = [];
3025
+ const visit = (current) => {
3026
+ const opening = nodeKind(current) === "JSXFragment" ? "<>" : current.openingElement?.name.name ?? "<>";
3027
+ parts.push(opening);
3028
+ if (nodeKind(current) === "JSXElement") {
3029
+ const attributes = (current.openingElement?.attributes ?? []).filter((attribute) => attribute.type === "JSXAttribute").map((attribute) => {
3030
+ const name = attribute.name?.name ?? "";
3031
+ if (name === "className" && attribute.value?.type === "Literal" && typeof attribute.value.value === "string") {
3032
+ return `class="${attribute.value.value.split(/\s+/).filter(Boolean).sort().join(" ")}"`;
3033
+ }
3034
+ return name;
3035
+ }).sort();
3036
+ parts.push(`[${attributes.join(",")}]`);
3037
+ }
3038
+ for (const child of current.children ?? []) {
3039
+ if (isJsxNode(child)) {
3040
+ visit(child);
3041
+ } else if (isJsxExpressionContainer(child)) {
3042
+ walkJsx(child, visit);
3043
+ parts.push("{}");
3044
+ } else {
3045
+ parts.push("{}");
3046
+ }
3047
+ }
3048
+ };
3049
+ visit(node);
3050
+ return parts.join("/");
3051
+ }
3052
+ function isSubstantial(node) {
3053
+ let count = 0;
3054
+ walkJsx(node, () => {
3055
+ count += 1;
3056
+ });
3057
+ return count >= 3;
3058
+ }
3059
+ function blockChildren(node) {
3060
+ return (node.children ?? []).filter(isJsxNode);
3061
+ }
3062
+ var repeatedStructureRule = {
3063
+ meta: {
3064
+ schema: [],
3065
+ type: "problem",
3066
+ docs: {
3067
+ description: "Require extracting a repeated block of elements as a named component."
3068
+ }
3069
+ },
3070
+ create(context) {
3071
+ if (!context.filename.endsWith(".tsx") && !context.filename.endsWith(".jsx")) return {};
3072
+ return {
3073
+ JSXElement(node) {
3074
+ checkChildren(node);
3075
+ },
3076
+ JSXFragment(node) {
3077
+ checkChildren(node);
3078
+ }
3079
+ };
3080
+ function checkChildren(node) {
3081
+ const children = blockChildren(node);
3082
+ const seen = /* @__PURE__ */ new Map();
3083
+ for (const child of children) {
3084
+ const sig = signature(child);
3085
+ const group = seen.get(sig) ?? [];
3086
+ group.push(child);
3087
+ seen.set(sig, group);
3088
+ }
3089
+ for (const [, group] of seen) {
3090
+ if (group.length < 2) continue;
3091
+ const first = group[0];
3092
+ if (!first || !isSubstantial(first)) continue;
3093
+ context.report({
3094
+ node: first,
3095
+ message: `The same arrangement of elements appears ${String(group.length)} times here; extract it as a named component. Different data or labels do not prevent extraction. See docs/code-organization-guide/rules/repeated-structure-rule.md`
3096
+ });
3097
+ }
3098
+ }
3099
+ }
3100
+ };
3101
+
2717
3102
  // eslint/pasika/rules/no-eslint-disable.ts
2718
3103
  var noEslintDisableRule = {
2719
3104
  meta: {
@@ -2813,7 +3198,7 @@ var zodSchemaValidationRule = {
2813
3198
  };
2814
3199
 
2815
3200
  // eslint/pasika/rules/source-under-src.ts
2816
- import path30 from "path";
3201
+ import path32 from "path";
2817
3202
  var NON_SOURCE_ROOT_DIRS = /* @__PURE__ */ new Set([
2818
3203
  ".agents",
2819
3204
  ".cache",
@@ -2854,14 +3239,14 @@ var sourceUnderSrcRule = {
2854
3239
  }
2855
3240
  },
2856
3241
  create(context) {
2857
- const filename = path30.resolve(context.filename);
3242
+ const filename = path32.resolve(context.filename);
2858
3243
  if (!MODULE_EXTENSION.test(filename)) return {};
2859
- const relative = path30.relative(process.cwd(), filename).replace(/\\/g, "/");
3244
+ const relative = path32.relative(process.cwd(), filename).replace(/\\/g, "/");
2860
3245
  if (relative === "src" || relative.startsWith("src/")) return {};
2861
3246
  const topLevel = relative.split("/")[0] ?? "";
2862
3247
  if (NON_SOURCE_ROOT_DIRS.has(topLevel)) return {};
2863
3248
  if (!relative.includes("/")) {
2864
- const basename = path30.basename(filename);
3249
+ const basename = path32.basename(filename);
2865
3250
  if (CONFIG_FILE.test(basename) || DECLARATION_FILE.test(basename) || basename.startsWith(".")) return {};
2866
3251
  }
2867
3252
  return {
@@ -2923,7 +3308,7 @@ var docKindSuffixRule = {
2923
3308
  };
2924
3309
 
2925
3310
  // eslint/pasika/rules/md/title-matches-file-name.ts
2926
- import path31 from "path";
3311
+ import path33 from "path";
2927
3312
  function toExpectedFileName(title) {
2928
3313
  return `${title.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}.md`;
2929
3314
  }
@@ -2943,7 +3328,7 @@ var titleMatchesFileNameRule = {
2943
3328
  if (!filename.endsWith(".md")) return;
2944
3329
  const title = getTextContent(node).trim();
2945
3330
  const expectedFileName = toExpectedFileName(title);
2946
- const actualFileName = path31.basename(filename);
3331
+ const actualFileName = path33.basename(filename);
2947
3332
  if (!title) {
2948
3333
  context.report({
2949
3334
  node,
@@ -3416,7 +3801,7 @@ var referenceBlockHeadingsRule = {
3416
3801
  };
3417
3802
 
3418
3803
  // eslint/pasika/rules/md/support-document-placement.ts
3419
- import path32 from "path";
3804
+ import path34 from "path";
3420
3805
  var supportDocumentPlacementRule = {
3421
3806
  meta: {
3422
3807
  type: "problem",
@@ -3430,7 +3815,7 @@ var supportDocumentPlacementRule = {
3430
3815
  root(node) {
3431
3816
  const filename = getFilename(context);
3432
3817
  if (!filename.endsWith(".md")) return;
3433
- const parentFolder = path32.basename(path32.dirname(filename));
3818
+ const parentFolder = path34.basename(path34.dirname(filename));
3434
3819
  if (filename.endsWith("-rule.md") && parentFolder !== "rules") {
3435
3820
  context.report({
3436
3821
  node,
@@ -3473,11 +3858,11 @@ var noTemplatePromptRule = {
3473
3858
  };
3474
3859
 
3475
3860
  // eslint/pasika/rules/md/guide-folder-entry-point.ts
3476
- import path34 from "path";
3861
+ import path36 from "path";
3477
3862
 
3478
3863
  // eslint/pasika/rules/md/project-index.ts
3479
3864
  import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
3480
- import path33 from "path";
3865
+ import path35 from "path";
3481
3866
  var KIND_BY_SUFFIX = [
3482
3867
  ["-rule.md", "rule"],
3483
3868
  ["-guide.md", "guide"],
@@ -3486,7 +3871,7 @@ var KIND_BY_SUFFIX = [
3486
3871
  ];
3487
3872
  function listMarkdownFiles(dir) {
3488
3873
  return readdirSync2(dir).flatMap((entry) => {
3489
- const entryPath = path33.join(dir, entry);
3874
+ const entryPath = path35.join(dir, entry);
3490
3875
  if (statSync3(entryPath).isDirectory()) {
3491
3876
  return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
3492
3877
  }
@@ -3504,11 +3889,11 @@ function getProjectDocs(docsRoot) {
3504
3889
  if (cached) return cached;
3505
3890
  const files = listMarkdownFiles(docsRoot);
3506
3891
  const docs = files.sort((a, b) => a.localeCompare(b)).map((filePath) => {
3507
- const fileName = path33.basename(filePath);
3892
+ const fileName = path35.basename(filePath);
3508
3893
  const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
3509
3894
  return {
3510
3895
  filePath,
3511
- doc: path33.relative(docsRoot, filePath).split(path33.sep).join("/"),
3896
+ doc: path35.relative(docsRoot, filePath).split(path35.sep).join("/"),
3512
3897
  fileName,
3513
3898
  kind,
3514
3899
  title: extractTitle(filePath)
@@ -3518,12 +3903,12 @@ function getProjectDocs(docsRoot) {
3518
3903
  return docs;
3519
3904
  }
3520
3905
  function findDocsRoot(filePath) {
3521
- let dir = path33.dirname(filePath);
3906
+ let dir = path35.dirname(filePath);
3522
3907
  for (; ; ) {
3523
- if (path33.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
3908
+ if (path35.basename(dir) === "docs" && statSync3(dir).isDirectory()) {
3524
3909
  return dir;
3525
3910
  }
3526
- const parent = path33.dirname(dir);
3911
+ const parent = path35.dirname(dir);
3527
3912
  if (parent === dir) return void 0;
3528
3913
  dir = parent;
3529
3914
  }
@@ -3547,13 +3932,13 @@ var guideFolderEntryPointRule = {
3547
3932
  if (!docsRoot) return;
3548
3933
  const docs = getProjectDocs(docsRoot);
3549
3934
  const guideFolders = new Set(
3550
- docs.filter((doc) => ["rules", "references"].includes(path34.basename(path34.dirname(doc.filePath)))).map((doc) => path34.dirname(path34.dirname(doc.filePath))).filter((folder) => path34.resolve(folder) !== path34.resolve(docsRoot))
3935
+ 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))
3551
3936
  );
3552
- const currentDir = path34.dirname(filename);
3937
+ const currentDir = path36.dirname(filename);
3553
3938
  if (guideFolders.has(currentDir)) {
3554
- const expectedEntryPoint = `${path34.basename(currentDir)}.md`;
3939
+ const expectedEntryPoint = `${path36.basename(currentDir)}.md`;
3555
3940
  const hasEntryPoint = docs.some(
3556
- (doc) => doc.kind === "guide" && path34.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
3941
+ (doc) => doc.kind === "guide" && path36.dirname(doc.filePath) === currentDir && doc.fileName === expectedEntryPoint
3557
3942
  );
3558
3943
  if (!hasEntryPoint) {
3559
3944
  context.report({
@@ -3610,6 +3995,39 @@ var rfcOnlyInBulletsRule = {
3610
3995
  }
3611
3996
  };
3612
3997
 
3998
+ // eslint/pasika/rules/md/rfc-keyword-in-every-bullet.ts
3999
+ function checkBullets(node, context) {
4000
+ if (node.type === "listItem" && !containsRfcKeyword(getTextContent(node))) {
4001
+ context.report({
4002
+ node,
4003
+ message: "bullet point contains no RFC 2119 keyword"
4004
+ });
4005
+ }
4006
+ if ("children" in node) {
4007
+ for (const child of node.children) {
4008
+ checkBullets(child, context);
4009
+ }
4010
+ }
4011
+ }
4012
+ var rfcKeywordInEveryBulletRule = {
4013
+ meta: {
4014
+ type: "problem",
4015
+ docs: {
4016
+ description: "Every bullet in a rule or policy document must contain an RFC 2119 keyword.",
4017
+ recommended: true
4018
+ }
4019
+ },
4020
+ create(context) {
4021
+ return {
4022
+ root(node) {
4023
+ const filename = getFilename(context);
4024
+ if (!filename.endsWith("-rule.md") && !filename.endsWith("-policy.md")) return;
4025
+ checkBullets(node, context);
4026
+ }
4027
+ };
4028
+ }
4029
+ };
4030
+
3613
4031
  // eslint/pasika/rules/md/policy-subject-headings.ts
3614
4032
  var policySubjectHeadingsRule = {
3615
4033
  meta: {
@@ -3745,7 +4163,7 @@ var noNestedHowToRule = {
3745
4163
 
3746
4164
  // eslint/pasika/rules/md/glossary-term-linking.ts
3747
4165
  import { readFileSync as readFileSync5 } from "fs";
3748
- import path35 from "path";
4166
+ import path37 from "path";
3749
4167
  function extractGlossaryTerms(filePath) {
3750
4168
  const content = readFileSync5(filePath, "utf8");
3751
4169
  const terms = [];
@@ -3799,9 +4217,9 @@ var glossaryTermLinkingRule = {
3799
4217
  const docsRoot = findDocsRoot(filename);
3800
4218
  if (!docsRoot) return;
3801
4219
  const docs = getProjectDocs(docsRoot);
3802
- const guideDir = path35.dirname(filename);
4220
+ const guideDir = path37.dirname(filename);
3803
4221
  const guideReferences = docs.filter(
3804
- (doc) => doc.kind === "reference" && path35.dirname(doc.filePath) === guideDir
4222
+ (doc) => doc.kind === "reference" && path37.dirname(doc.filePath) === guideDir
3805
4223
  );
3806
4224
  if (guideReferences.length === 0) return;
3807
4225
  const glossaryTerms = [];
@@ -3845,6 +4263,7 @@ var mdRules = {
3845
4263
  "no-template-prompt": noTemplatePromptRule,
3846
4264
  "guide-folder-entry-point": guideFolderEntryPointRule,
3847
4265
  "rfc-only-in-bullets": rfcOnlyInBulletsRule,
4266
+ "rfc-keyword-in-every-bullet": rfcKeywordInEveryBulletRule,
3848
4267
  "policy-subject-headings": policySubjectHeadingsRule,
3849
4268
  "guide-link-anchors": guideLinkAnchorsRule,
3850
4269
  "no-nested-how-to": noNestedHowToRule,
@@ -3874,7 +4293,7 @@ function atrules(node) {
3874
4293
  function atrulesNamed(node, name) {
3875
4294
  return atrules(node).filter((candidate) => candidate.type === "Atrule" && candidate.name === name);
3876
4295
  }
3877
- function blockChildren(node) {
4296
+ function blockChildren2(node) {
3878
4297
  if (!node) return [];
3879
4298
  if (!("block" in node) || !node.block) return [];
3880
4299
  return node.block.children;
@@ -3921,7 +4340,7 @@ var themeResetRule = {
3921
4340
  return {
3922
4341
  "StyleSheet:exit"(node) {
3923
4342
  const resetFound = atrulesNamed(node, "theme").some(
3924
- (theme) => blockChildren(theme).some((child) => {
4343
+ (theme) => blockChildren2(theme).some((child) => {
3925
4344
  if (child.type === "Declaration") return child.property.startsWith("--*");
3926
4345
  if (child.type === "Raw") return child.value.includes("--*: initial");
3927
4346
  return false;
@@ -3952,7 +4371,7 @@ var rootVariablesRule = {
3952
4371
  "StyleSheet:exit"(node) {
3953
4372
  const hasRootVars = rules(node).some((rule) => {
3954
4373
  if (!selectorNames(rule).includes("root")) return false;
3955
- return blockChildren(rule).some((child) => child.type === "Declaration");
4374
+ return blockChildren2(rule).some((child) => child.type === "Declaration");
3956
4375
  });
3957
4376
  if (!hasRootVars) {
3958
4377
  context.report({
@@ -3961,7 +4380,7 @@ var rootVariablesRule = {
3961
4380
  });
3962
4381
  }
3963
4382
  const referencingThemes = atrulesNamed(node, "theme").filter(
3964
- (theme) => blockChildren(theme).some((child) => {
4383
+ (theme) => blockChildren2(theme).some((child) => {
3965
4384
  if (child.type === "Raw") return child.value.includes("var(");
3966
4385
  if (child.type === "Declaration") return JSON.stringify(child.value).includes("var(");
3967
4386
  return false;
@@ -3995,9 +4414,9 @@ var applyUsageRule = {
3995
4414
  "StyleSheet:exit"(node) {
3996
4415
  const layers = node.children.filter((child) => child.type === "Atrule" && child.name === "layer");
3997
4416
  for (const layer of layers) {
3998
- for (const child of blockChildren(layer)) {
4417
+ for (const child of blockChildren2(layer)) {
3999
4418
  if (child.type !== "Rule") continue;
4000
- for (const declaration of blockChildren(child)) {
4419
+ for (const declaration of blockChildren2(child)) {
4001
4420
  if (declaration.type !== "Declaration") continue;
4002
4421
  context.report({
4003
4422
  node: declaration,
@@ -4027,10 +4446,10 @@ var baseLayerPairRule = {
4027
4446
  if (baseLayers.length === 0) return;
4028
4447
  const applied = [];
4029
4448
  for (const layer of baseLayers) {
4030
- for (const child of blockChildren(layer)) {
4449
+ for (const child of blockChildren2(layer)) {
4031
4450
  if (child.type !== "Rule") continue;
4032
4451
  if (!selectorNames(child).includes("body")) continue;
4033
- for (const declaration of blockChildren(child)) {
4452
+ for (const declaration of blockChildren2(child)) {
4034
4453
  if (declaration.type !== "Atrule" || declaration.name !== "apply") continue;
4035
4454
  applied.push(...preludeIdentifiers(declaration));
4036
4455
  }
@@ -4155,7 +4574,7 @@ var customUtilityApplyRule = {
4155
4574
  return {
4156
4575
  "StyleSheet:exit"(node) {
4157
4576
  for (const utility of atrulesNamed(node, "utility")) {
4158
- for (const child of blockChildren(utility)) {
4577
+ for (const child of blockChildren2(utility)) {
4159
4578
  if (child.type === "Atrule" && child.name === "apply") continue;
4160
4579
  if (child.type === "Declaration") {
4161
4580
  context.report({
@@ -4227,7 +4646,7 @@ var themeVariableNamespaceRule = {
4227
4646
  "StyleSheet:exit"(node) {
4228
4647
  const namespaces = /* @__PURE__ */ new Set();
4229
4648
  for (const theme of atrulesNamed(node, "theme")) {
4230
- for (const child of blockChildren(theme)) {
4649
+ for (const child of blockChildren2(theme)) {
4231
4650
  if (child.type !== "Declaration") continue;
4232
4651
  const segments = child.property.replace(/^--/, "").split("-");
4233
4652
  if (segments[0]) namespaces.add(segments[0]);
@@ -4406,7 +4825,10 @@ var pasikaRules = {
4406
4825
  "stay-flat": stayFlatRule,
4407
4826
  "type-extraction": typeExtractionRule,
4408
4827
  "locale-placement": localePlacementRule,
4828
+ "sole-state-owner": soleStateOwnerRule,
4829
+ "locale-key-shape": localeKeyShapeRule,
4409
4830
  "shared-style-dedup": sharedStyleDedupRule,
4831
+ "repeated-structure": repeatedStructureRule,
4410
4832
  "no-eslint-disable": noEslintDisableRule,
4411
4833
  "zod-schema-validation": zodSchemaValidationRule,
4412
4834
  "source-under-src": sourceUnderSrcRule
@@ -4435,7 +4857,7 @@ function hashRequirement(canonicalText) {
4435
4857
 
4436
4858
  // enforcement/parse-docs.ts
4437
4859
  import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
4438
- import path36 from "path";
4860
+ import path38 from "path";
4439
4861
  var KIND_BY_SUFFIX2 = [
4440
4862
  ["-rule.md", "rule"],
4441
4863
  ["-guide.md", "guide"],
@@ -4445,7 +4867,7 @@ var KIND_BY_SUFFIX2 = [
4445
4867
  var RFC_2119 = /\b(?<keyword>MUST NOT|MUST|SHOULD NOT|SHOULD|MAY)\b/;
4446
4868
  function listMarkdownFiles2(dir) {
4447
4869
  return readdirSync3(dir).flatMap((entry) => {
4448
- const entryPath = path36.join(dir, entry);
4870
+ const entryPath = path38.join(dir, entry);
4449
4871
  if (statSync4(entryPath).isDirectory()) {
4450
4872
  return entry.startsWith("_") ? [] : listMarkdownFiles2(entryPath);
4451
4873
  }
@@ -4471,7 +4893,7 @@ function parseDoc(filePath, docsRoot) {
4471
4893
  const sourceLines = body.split("\n");
4472
4894
  const prose = toProse(body, "$<content>");
4473
4895
  const proseWithoutCode = toProse(body, "");
4474
- const fileName = path36.basename(filePath);
4896
+ const fileName = path38.basename(filePath);
4475
4897
  const kind = KIND_BY_SUFFIX2.find(([suffix]) => fileName.endsWith(suffix))?.[1];
4476
4898
  const requirements = [];
4477
4899
  const steps = [];
@@ -4501,7 +4923,7 @@ function parseDoc(filePath, docsRoot) {
4501
4923
  const overview = prose.slice(1).find((line) => line.trim() !== "" && !line.startsWith("#"));
4502
4924
  return {
4503
4925
  filePath,
4504
- doc: path36.relative(docsRoot, filePath).split(path36.sep).join("/"),
4926
+ doc: path38.relative(docsRoot, filePath).split(path38.sep).join("/"),
4505
4927
  fileName,
4506
4928
  kind,
4507
4929
  title,
@@ -4521,7 +4943,7 @@ function parseDocs(docsRoot) {
4521
4943
 
4522
4944
  // enforcement/doctor.ts
4523
4945
  import fs4 from "fs";
4524
- import path37 from "path";
4946
+ import path39 from "path";
4525
4947
  import { z } from "zod";
4526
4948
  var doctorCheckRefs = [
4527
4949
  "pasika/pasika-installed",
@@ -4565,7 +4987,7 @@ function checkFrameworkPackages(pkg) {
4565
4987
  }
4566
4988
  function checkSourceRoot(cwd) {
4567
4989
  const findings = [];
4568
- const srcDir = path37.join(cwd, "src");
4990
+ const srcDir = path39.join(cwd, "src");
4569
4991
  if (!fs4.existsSync(srcDir)) {
4570
4992
  findings.push({
4571
4993
  check: "source-under-src",
@@ -4591,14 +5013,14 @@ function checkSourceRoot(cwd) {
4591
5013
  function findGlobalStylesheet(cwd) {
4592
5014
  const candidates = ["src/app/globals.css", "src/styles/globals.css", "src/globals.css"];
4593
5015
  for (const candidate of candidates) {
4594
- const full = path37.join(cwd, candidate);
5016
+ const full = path39.join(cwd, candidate);
4595
5017
  if (fs4.existsSync(full)) return full;
4596
5018
  }
4597
- const srcDir = path37.join(cwd, "src");
5019
+ const srcDir = path39.join(cwd, "src");
4598
5020
  if (!fs4.existsSync(srcDir)) return void 0;
4599
5021
  const walk = (dir) => {
4600
5022
  for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
4601
- const full = path37.join(dir, entry.name);
5023
+ const full = path39.join(dir, entry.name);
4602
5024
  if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".next") {
4603
5025
  const found = walk(full);
4604
5026
  if (found) return found;
@@ -4629,7 +5051,7 @@ function checkGlobalStylesheet(cwd) {
4629
5051
  function checkConfigBaseline(cwd) {
4630
5052
  const findings = [];
4631
5053
  const eslintConfigs = ["eslint.config.ts", "eslint.config.mjs", "eslint.config.js"];
4632
- const eslintConfig = eslintConfigs.find((name) => fs4.existsSync(path37.join(cwd, name)));
5054
+ const eslintConfig = eslintConfigs.find((name) => fs4.existsSync(path39.join(cwd, name)));
4633
5055
  if (!eslintConfig) {
4634
5056
  findings.push({
4635
5057
  check: "config-baseline",
@@ -4637,7 +5059,7 @@ function checkConfigBaseline(cwd) {
4637
5059
  severity: "error"
4638
5060
  });
4639
5061
  } else {
4640
- const content = fs4.readFileSync(path37.join(cwd, eslintConfig), "utf8");
5062
+ const content = fs4.readFileSync(path39.join(cwd, eslintConfig), "utf8");
4641
5063
  if (!content.includes("zirka")) {
4642
5064
  findings.push({
4643
5065
  check: "config-baseline",
@@ -4646,7 +5068,7 @@ function checkConfigBaseline(cwd) {
4646
5068
  });
4647
5069
  }
4648
5070
  }
4649
- if (!fs4.existsSync(path37.join(cwd, "tsconfig.json"))) {
5071
+ if (!fs4.existsSync(path39.join(cwd, "tsconfig.json"))) {
4650
5072
  findings.push({
4651
5073
  check: "config-baseline",
4652
5074
  message: "No tsconfig.json found. Create one extending the pasika baseline.",
@@ -4657,9 +5079,9 @@ function checkConfigBaseline(cwd) {
4657
5079
  }
4658
5080
  function checkManagedFiles(cwd) {
4659
5081
  const findings = [];
4660
- const vulykDir = path37.join(cwd, ".vulyk");
5082
+ const vulykDir = path39.join(cwd, ".vulyk");
4661
5083
  if (!fs4.existsSync(vulykDir)) return findings;
4662
- const manifestPath = path37.join(vulykDir, "manifest.json");
5084
+ const manifestPath = path39.join(vulykDir, "manifest.json");
4663
5085
  if (!fs4.existsSync(manifestPath)) return findings;
4664
5086
  try {
4665
5087
  const manifestEntrySchema = z.looseObject({
@@ -4671,7 +5093,7 @@ function checkManagedFiles(cwd) {
4671
5093
  for (const [, entry] of Object.entries(result.data)) {
4672
5094
  const targets = entry.targets ?? [];
4673
5095
  for (const target of targets) {
4674
- const targetPath = path37.join(cwd, target);
5096
+ const targetPath = path39.join(cwd, target);
4675
5097
  if (fs4.existsSync(targetPath)) {
4676
5098
  const stat = fs4.statSync(targetPath);
4677
5099
  const vulykStat = fs4.statSync(manifestPath);
@@ -4690,7 +5112,7 @@ function checkManagedFiles(cwd) {
4690
5112
  return findings;
4691
5113
  }
4692
5114
  function runDoctor(cwd) {
4693
- const pkgPath = path37.join(cwd, "package.json");
5115
+ const pkgPath = path39.join(cwd, "package.json");
4694
5116
  const pkg = readPackageJson(pkgPath);
4695
5117
  return [
4696
5118
  ...checkFrameworkPackages(pkg),
@@ -4738,7 +5160,7 @@ function collectTestTitles(rulesDir) {
4738
5160
  const titles = /* @__PURE__ */ new Set();
4739
5161
  const visit = (dir) => {
4740
5162
  for (const entry of readdirSync4(dir)) {
4741
- const entryPath = path38.join(dir, entry);
5163
+ const entryPath = path40.join(dir, entry);
4742
5164
  if (statSync5(entryPath).isDirectory()) {
4743
5165
  visit(entryPath);
4744
5166
  continue;
@@ -4921,12 +5343,12 @@ function json(data) {
4921
5343
  }
4922
5344
 
4923
5345
  // cli/index.ts
4924
- var REGISTRY_RELATIVE_PATH = path39.join("enforcement", "registry.json");
5346
+ var REGISTRY_RELATIVE_PATH = path41.join("enforcement", "registry.json");
4925
5347
  function findRegistryRoot(startDir) {
4926
- let dir = path39.resolve(startDir);
5348
+ let dir = path41.resolve(startDir);
4927
5349
  for (; ; ) {
4928
- if (existsSync(path39.join(dir, REGISTRY_RELATIVE_PATH))) return dir;
4929
- const parent = path39.dirname(dir);
5350
+ if (existsSync(path41.join(dir, REGISTRY_RELATIVE_PATH))) return dir;
5351
+ const parent = path41.dirname(dir);
4930
5352
  if (parent === dir) return void 0;
4931
5353
  dir = parent;
4932
5354
  }
@@ -4950,12 +5372,12 @@ program.command("coverage").description("Check that every documented requirement
4950
5372
  error(`No ${REGISTRY_RELATIVE_PATH} found in this directory or any parent.`);
4951
5373
  process.exit(1);
4952
5374
  }
4953
- const docsRoot = path39.join(root, "docs");
5375
+ const docsRoot = path41.join(root, "docs");
4954
5376
  if (!existsSync(docsRoot)) {
4955
5377
  error(`No documentation folder at ${docsRoot}. Run coverage inside the pasika repository.`);
4956
5378
  process.exit(1);
4957
5379
  }
4958
- const registryPath = path39.join(root, REGISTRY_RELATIVE_PATH);
5380
+ const registryPath = path41.join(root, REGISTRY_RELATIVE_PATH);
4959
5381
  if (options.classify !== void 0) {
4960
5382
  try {
4961
5383
  const result = classifyRequirement({
@@ -4974,7 +5396,7 @@ program.command("coverage").description("Check that every documented requirement
4974
5396
  const report3 = buildCoverageReport({
4975
5397
  docsRoot,
4976
5398
  registry: readRegistry(registryPath),
4977
- rulesDir: path39.join(root, "eslint", "pasika", "rules")
5399
+ rulesDir: path41.join(root, "eslint", "pasika", "rules")
4978
5400
  });
4979
5401
  if (options.json) {
4980
5402
  json(report3);