@agimon-ai/vibe-lint-plugin-doom-web 0.0.1-alpha.35

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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  3. package/dist/configs/patterns.cjs +2 -0
  4. package/dist/configs/patterns.cjs.map +1 -0
  5. package/dist/configs/patterns.d.cts +11 -0
  6. package/dist/configs/patterns.d.cts.map +1 -0
  7. package/dist/configs/patterns.d.mts +11 -0
  8. package/dist/configs/patterns.d.mts.map +1 -0
  9. package/dist/configs/patterns.mjs +2 -0
  10. package/dist/configs/patterns.mjs.map +1 -0
  11. package/dist/configs/recommended.cjs +2 -0
  12. package/dist/configs/recommended.cjs.map +1 -0
  13. package/dist/configs/recommended.d.cts +6 -0
  14. package/dist/configs/recommended.d.cts.map +1 -0
  15. package/dist/configs/recommended.d.mts +6 -0
  16. package/dist/configs/recommended.d.mts.map +1 -0
  17. package/dist/configs/recommended.mjs +2 -0
  18. package/dist/configs/recommended.mjs.map +1 -0
  19. package/dist/index.cjs +1 -0
  20. package/dist/index.d.cts +12 -0
  21. package/dist/index.d.mts +12 -0
  22. package/dist/index.mjs +1 -0
  23. package/dist/plugin.cjs +2 -0
  24. package/dist/plugin.cjs.map +1 -0
  25. package/dist/plugin.d.cts +6 -0
  26. package/dist/plugin.d.cts.map +1 -0
  27. package/dist/plugin.d.mts +6 -0
  28. package/dist/plugin.d.mts.map +1 -0
  29. package/dist/plugin.mjs +2 -0
  30. package/dist/plugin.mjs.map +1 -0
  31. package/dist/rules/componentLibrary.cjs +2 -0
  32. package/dist/rules/componentLibrary.cjs.map +1 -0
  33. package/dist/rules/componentLibrary.d.cts +8 -0
  34. package/dist/rules/componentLibrary.d.cts.map +1 -0
  35. package/dist/rules/componentLibrary.d.mts +8 -0
  36. package/dist/rules/componentLibrary.d.mts.map +1 -0
  37. package/dist/rules/componentLibrary.mjs +2 -0
  38. package/dist/rules/componentLibrary.mjs.map +1 -0
  39. package/dist/rules/conventions.cjs +2 -0
  40. package/dist/rules/conventions.cjs.map +1 -0
  41. package/dist/rules/conventions.d.cts +6 -0
  42. package/dist/rules/conventions.d.cts.map +1 -0
  43. package/dist/rules/conventions.d.mts +6 -0
  44. package/dist/rules/conventions.d.mts.map +1 -0
  45. package/dist/rules/conventions.mjs +2 -0
  46. package/dist/rules/conventions.mjs.map +1 -0
  47. package/dist/rules/features.cjs +2 -0
  48. package/dist/rules/features.cjs.map +1 -0
  49. package/dist/rules/features.d.cts +6 -0
  50. package/dist/rules/features.d.cts.map +1 -0
  51. package/dist/rules/features.d.mts +6 -0
  52. package/dist/rules/features.d.mts.map +1 -0
  53. package/dist/rules/features.mjs +2 -0
  54. package/dist/rules/features.mjs.map +1 -0
  55. package/dist/rules/index.cjs +1 -0
  56. package/dist/rules/index.d.cts +8 -0
  57. package/dist/rules/index.d.mts +8 -0
  58. package/dist/rules/index.mjs +1 -0
  59. package/dist/rules/layers.cjs +2 -0
  60. package/dist/rules/layers.cjs.map +1 -0
  61. package/dist/rules/layers.d.cts +6 -0
  62. package/dist/rules/layers.d.cts.map +1 -0
  63. package/dist/rules/layers.d.mts +6 -0
  64. package/dist/rules/layers.d.mts.map +1 -0
  65. package/dist/rules/layers.mjs +2 -0
  66. package/dist/rules/layers.mjs.map +1 -0
  67. package/dist/rules/moduleGraph.cjs +2 -0
  68. package/dist/rules/moduleGraph.cjs.map +1 -0
  69. package/dist/rules/moduleGraph.mjs +2 -0
  70. package/dist/rules/moduleGraph.mjs.map +1 -0
  71. package/dist/rules/primitives.cjs +2 -0
  72. package/dist/rules/primitives.cjs.map +1 -0
  73. package/dist/rules/primitives.d.cts +6 -0
  74. package/dist/rules/primitives.d.cts.map +1 -0
  75. package/dist/rules/primitives.d.mts +6 -0
  76. package/dist/rules/primitives.d.mts.map +1 -0
  77. package/dist/rules/primitives.mjs +2 -0
  78. package/dist/rules/primitives.mjs.map +1 -0
  79. package/dist/rules/registry.cjs +2 -0
  80. package/dist/rules/registry.cjs.map +1 -0
  81. package/dist/rules/registry.d.cts +6 -0
  82. package/dist/rules/registry.d.cts.map +1 -0
  83. package/dist/rules/registry.d.mts +6 -0
  84. package/dist/rules/registry.d.mts.map +1 -0
  85. package/dist/rules/registry.mjs +2 -0
  86. package/dist/rules/registry.mjs.map +1 -0
  87. package/dist/rules/theming.cjs +2 -0
  88. package/dist/rules/theming.cjs.map +1 -0
  89. package/dist/rules/theming.d.cts +6 -0
  90. package/dist/rules/theming.d.cts.map +1 -0
  91. package/dist/rules/theming.d.mts +6 -0
  92. package/dist/rules/theming.d.mts.map +1 -0
  93. package/dist/rules/theming.mjs +2 -0
  94. package/dist/rules/theming.mjs.map +1 -0
  95. package/package.json +64 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
@@ -0,0 +1,2 @@
1
+ const e={"doom-web-types":{description:`Framework-neutral contracts both sides share: request and response payloads, domain entities, and option shapes. Imports nothing else in the package, which is what makes it the one root the browser and the server may both read.`,includes:[`src/types/**`]},"doom-web-services":{description:`Server-side domain policy over the types layer. No HTTP framework, no filesystem, no browser API, so every rule stays testable without a running server.`,includes:[`src/services/**`]},"doom-web-adapters":{description:`Server-side integration with the outside world: HTTP handlers, static asset serving, filesystem and process access, and third-party clients. An adapter satisfies a contract declared in src/types and delegates behavior to src/services.`,includes:[`src/adapters/**`]},"doom-web-bin":{description:`Executable entrypoints declared in package.json bin. Reads argv and the environment, assembles the service and adapter graph, and starts the server. Behavior belongs one layer down.`,includes:[`src/bin/**`]},"doom-web-exports":{description:`The package's only public surface. Pure re-exports, one file per package.json exports subpath, mirroring the subpath tree.`,includes:[`src/exports/**`]},"doom-web-client-lib":{description:`Browser helpers with no React state and no server import: formatting, parsing, typed fetch wrappers, and query client setup. The lowest client layer above types.`,includes:[`src/web/lib/**`]},"doom-web-client-stores":{description:`Client state containers, one <topic>Store.ts per store. A store owns state and the actions that change it; it renders nothing and imports no component.`,includes:[`src/web/stores/**`]},"doom-web-client-components":{description:`Presentation shared by every feature, one PascalCase .tsx file per component. Driven by props and stores, with no feature-specific branching and no data fetching of its own.`,includes:[`src/web/components/**`]},"doom-web-client-features":{description:`One directory per user-facing capability, holding its own components, hooks, and state. A feature is self-contained: code two features need moves down into components, lib, or stores rather than across to a sibling.`,includes:[`src/web/features/**`]},"doom-web-client-routes":{description:`TanStack Router modules where the file tree is the URL tree, so the router owns the spelling (__root.tsx, dot and kebab segments). A route binds a URL to a feature and holds no feature logic.`,includes:[`src/web/routes/**`]},"doom-web-client-app":{description:`The browser composition root: providers, router instantiation, global styles, and the mount call. The one client layer that may reach every other.`,includes:[`src/web/app/**`]},"doom-web-client-entry":{description:`The bundler entry: the HTML document, the mount script, global stylesheet, and ambient client type declarations. Holds wiring only, never behavior worth testing, because nothing can import it.`,includes:[`src/web/index.html`,`src/web/main.tsx`,`src/web/vite-env.d.ts`,`src/web/styles/**`]},"doom-components-types":{description:`The theme contract: the token names a theme must fill, the config shape, and the CSS custom property each token is published as. Imports nothing else, which is what lets both the components and the theme runtime read it.`,includes:[`src/types/**`]},"doom-components-lib":{description:"Pure helpers shared by every component, `cn` above all. No React state, no theme value, no DOM access beyond what a helper is handed.",includes:[`src/lib/**`]},"doom-components-theme":{description:`The theme runtime: the shipped configs, the validator that refuses a malformed one, the derivation of tinted and accented tokens from a palette, and the call that writes them onto the document. It renders nothing, so a host may apply a theme before the first paint.`,includes:[`src/theme/**`,`themes/**`]},"doom-components-icons":{description:`The curated icon set, re-exported from one module so every surface draws from the same family and no consumer needs an icon dependency of its own.`,includes:[`src/icons/**`]},"doom-components-components":{description:`The shared primitives, one PascalCase .tsx file per component. Props-driven and theme-agnostic: colours come from token classes, never from a theme value the component read, so a theme swap recolours a rendered tree without re-rendering it.`,includes:[`src/components/**`]},"doom-components-styles":{description:`The token stylesheet a host imports after Tailwind: the :root defaults, the @theme mapping that turns each token into a utility, the semantic aliases, and the base styles.`,includes:[`styles/**`]},"doom-web-tests":{description:`Unit, integration, and package-contract verification.`,includes:[`tests/**`]},"doom-web-metadata":{description:`Publishable package, documentation, and build configuration.`,includes:[`package.json`,`project.json`,`tsconfig.json`,`vite.config.ts`,`tsdown.config.ts`,`vitest.config.ts`,`playwright.config.ts`,`vibe-lint.config.yaml`,`README.md`,`LICENSE`,`llms.txt`,`.oxlintrc.json`]}};exports.patterns=e;
2
+ //# sourceMappingURL=patterns.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.cjs","names":[],"sources":["../../src/configs/patterns.ts"],"sourcesContent":["import type { PatternDefinition } from '@agimon-ai/vibe-lint';\n\n/**\n * Design-pattern context surfaced before a DoomPi web package file is edited.\n * A web package ships a browser bundle and a server from one source tree, so\n * each folder states which of the two it belongs to and what it may reach.\n */\nexport const patterns: Record<string, PatternDefinition> = {\n 'doom-web-types': {\n description:\n 'Framework-neutral contracts both sides share: request and response payloads, domain entities, and option shapes. Imports nothing else in the package, which is what makes it the one root the browser and the server may both read.',\n includes: ['src/types/**'],\n },\n 'doom-web-services': {\n description:\n 'Server-side domain policy over the types layer. No HTTP framework, no filesystem, no browser API, so every rule stays testable without a running server.',\n includes: ['src/services/**'],\n },\n 'doom-web-adapters': {\n description:\n 'Server-side integration with the outside world: HTTP handlers, static asset serving, filesystem and process access, and third-party clients. An adapter satisfies a contract declared in src/types and delegates behavior to src/services.',\n includes: ['src/adapters/**'],\n },\n 'doom-web-bin': {\n description:\n 'Executable entrypoints declared in package.json bin. Reads argv and the environment, assembles the service and adapter graph, and starts the server. Behavior belongs one layer down.',\n includes: ['src/bin/**'],\n },\n 'doom-web-exports': {\n description:\n \"The package's only public surface. Pure re-exports, one file per package.json exports subpath, mirroring the subpath tree.\",\n includes: ['src/exports/**'],\n },\n 'doom-web-client-lib': {\n description:\n 'Browser helpers with no React state and no server import: formatting, parsing, typed fetch wrappers, and query client setup. The lowest client layer above types.',\n includes: ['src/web/lib/**'],\n },\n 'doom-web-client-stores': {\n description:\n 'Client state containers, one <topic>Store.ts per store. A store owns state and the actions that change it; it renders nothing and imports no component.',\n includes: ['src/web/stores/**'],\n },\n 'doom-web-client-components': {\n description:\n 'Presentation shared by every feature, one PascalCase .tsx file per component. Driven by props and stores, with no feature-specific branching and no data fetching of its own.',\n includes: ['src/web/components/**'],\n },\n 'doom-web-client-features': {\n description:\n 'One directory per user-facing capability, holding its own components, hooks, and state. A feature is self-contained: code two features need moves down into components, lib, or stores rather than across to a sibling.',\n includes: ['src/web/features/**'],\n },\n 'doom-web-client-routes': {\n description:\n 'TanStack Router modules where the file tree is the URL tree, so the router owns the spelling (__root.tsx, dot and kebab segments). A route binds a URL to a feature and holds no feature logic.',\n includes: ['src/web/routes/**'],\n },\n 'doom-web-client-app': {\n description:\n 'The browser composition root: providers, router instantiation, global styles, and the mount call. The one client layer that may reach every other.',\n includes: ['src/web/app/**'],\n },\n 'doom-web-client-entry': {\n description:\n 'The bundler entry: the HTML document, the mount script, global stylesheet, and ambient client type declarations. Holds wiring only, never behavior worth testing, because nothing can import it.',\n includes: ['src/web/index.html', 'src/web/main.tsx', 'src/web/vite-env.d.ts', 'src/web/styles/**'],\n },\n 'doom-components-types': {\n description:\n 'The theme contract: the token names a theme must fill, the config shape, and the CSS custom property each token is published as. Imports nothing else, which is what lets both the components and the theme runtime read it.',\n includes: ['src/types/**'],\n },\n 'doom-components-lib': {\n description:\n 'Pure helpers shared by every component, `cn` above all. No React state, no theme value, no DOM access beyond what a helper is handed.',\n includes: ['src/lib/**'],\n },\n 'doom-components-theme': {\n description:\n 'The theme runtime: the shipped configs, the validator that refuses a malformed one, the derivation of tinted and accented tokens from a palette, and the call that writes them onto the document. It renders nothing, so a host may apply a theme before the first paint.',\n includes: ['src/theme/**', 'themes/**'],\n },\n 'doom-components-icons': {\n description:\n 'The curated icon set, re-exported from one module so every surface draws from the same family and no consumer needs an icon dependency of its own.',\n includes: ['src/icons/**'],\n },\n 'doom-components-components': {\n description:\n 'The shared primitives, one PascalCase .tsx file per component. Props-driven and theme-agnostic: colours come from token classes, never from a theme value the component read, so a theme swap recolours a rendered tree without re-rendering it.',\n includes: ['src/components/**'],\n },\n 'doom-components-styles': {\n description:\n 'The token stylesheet a host imports after Tailwind: the :root defaults, the @theme mapping that turns each token into a utility, the semantic aliases, and the base styles.',\n includes: ['styles/**'],\n },\n 'doom-web-tests': {\n description: 'Unit, integration, and package-contract verification.',\n includes: ['tests/**'],\n },\n 'doom-web-metadata': {\n description: 'Publishable package, documentation, and build configuration.',\n includes: [\n 'package.json',\n 'project.json',\n 'tsconfig.json',\n 'vite.config.ts',\n 'tsdown.config.ts',\n 'vitest.config.ts',\n 'playwright.config.ts',\n 'vibe-lint.config.yaml',\n 'README.md',\n 'LICENSE',\n 'llms.txt',\n '.oxlintrc.json',\n ],\n },\n};\n"],"mappings":"AAOA,MAAa,EAA8C,CACzD,iBAAkB,CAChB,YACE,sOACF,SAAU,CAAC,cAAc,CAC3B,EACA,oBAAqB,CACnB,YACE,2JACF,SAAU,CAAC,iBAAiB,CAC9B,EACA,oBAAqB,CACnB,YACE,6OACF,SAAU,CAAC,iBAAiB,CAC9B,EACA,eAAgB,CACd,YACE,wLACF,SAAU,CAAC,YAAY,CACzB,EACA,mBAAoB,CAClB,YACE,6HACF,SAAU,CAAC,gBAAgB,CAC7B,EACA,sBAAuB,CACrB,YACE,oKACF,SAAU,CAAC,gBAAgB,CAC7B,EACA,yBAA0B,CACxB,YACE,0JACF,SAAU,CAAC,mBAAmB,CAChC,EACA,6BAA8B,CAC5B,YACE,gLACF,SAAU,CAAC,uBAAuB,CACpC,EACA,2BAA4B,CAC1B,YACE,0NACF,SAAU,CAAC,qBAAqB,CAClC,EACA,yBAA0B,CACxB,YACE,kMACF,SAAU,CAAC,mBAAmB,CAChC,EACA,sBAAuB,CACrB,YACE,qJACF,SAAU,CAAC,gBAAgB,CAC7B,EACA,wBAAyB,CACvB,YACE,mMACF,SAAU,CAAC,qBAAsB,mBAAoB,wBAAyB,mBAAmB,CACnG,EACA,wBAAyB,CACvB,YACE,+NACF,SAAU,CAAC,cAAc,CAC3B,EACA,sBAAuB,CACrB,YACE,wIACF,SAAU,CAAC,YAAY,CACzB,EACA,wBAAyB,CACvB,YACE,4QACF,SAAU,CAAC,eAAgB,WAAW,CACxC,EACA,wBAAyB,CACvB,YACE,qJACF,SAAU,CAAC,cAAc,CAC3B,EACA,6BAA8B,CAC5B,YACE,mPACF,SAAU,CAAC,mBAAmB,CAChC,EACA,yBAA0B,CACxB,YACE,8KACF,SAAU,CAAC,WAAW,CACxB,EACA,iBAAkB,CAChB,YAAa,wDACb,SAAU,CAAC,UAAU,CACvB,EACA,oBAAqB,CACnB,YAAa,+DACb,SAAU,CACR,eACA,eACA,gBACA,iBACA,mBACA,mBACA,uBACA,wBACA,YACA,UACA,WACA,gBACF,CACF,CACF"}
@@ -0,0 +1,11 @@
1
+ import { PatternDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/configs/patterns.d.ts
3
+ /**
4
+ * Design-pattern context surfaced before a DoomPi web package file is edited.
5
+ * A web package ships a browser bundle and a server from one source tree, so
6
+ * each folder states which of the two it belongs to and what it may reach.
7
+ */
8
+ declare const patterns: Record<string, PatternDefinition>;
9
+ //#endregion
10
+ export { patterns };
11
+ //# sourceMappingURL=patterns.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.cts","names":[],"sources":["../../src/configs/patterns.ts"],"mappings":";;;;;;;cAOa,UAAU,eAAe"}
@@ -0,0 +1,11 @@
1
+ import { PatternDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/configs/patterns.d.ts
3
+ /**
4
+ * Design-pattern context surfaced before a DoomPi web package file is edited.
5
+ * A web package ships a browser bundle and a server from one source tree, so
6
+ * each folder states which of the two it belongs to and what it may reach.
7
+ */
8
+ declare const patterns: Record<string, PatternDefinition>;
9
+ //#endregion
10
+ export { patterns };
11
+ //# sourceMappingURL=patterns.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.mts","names":[],"sources":["../../src/configs/patterns.ts"],"mappings":";;;;;;;cAOa,UAAU,eAAe"}
@@ -0,0 +1,2 @@
1
+ const e={"doom-web-types":{description:`Framework-neutral contracts both sides share: request and response payloads, domain entities, and option shapes. Imports nothing else in the package, which is what makes it the one root the browser and the server may both read.`,includes:[`src/types/**`]},"doom-web-services":{description:`Server-side domain policy over the types layer. No HTTP framework, no filesystem, no browser API, so every rule stays testable without a running server.`,includes:[`src/services/**`]},"doom-web-adapters":{description:`Server-side integration with the outside world: HTTP handlers, static asset serving, filesystem and process access, and third-party clients. An adapter satisfies a contract declared in src/types and delegates behavior to src/services.`,includes:[`src/adapters/**`]},"doom-web-bin":{description:`Executable entrypoints declared in package.json bin. Reads argv and the environment, assembles the service and adapter graph, and starts the server. Behavior belongs one layer down.`,includes:[`src/bin/**`]},"doom-web-exports":{description:`The package's only public surface. Pure re-exports, one file per package.json exports subpath, mirroring the subpath tree.`,includes:[`src/exports/**`]},"doom-web-client-lib":{description:`Browser helpers with no React state and no server import: formatting, parsing, typed fetch wrappers, and query client setup. The lowest client layer above types.`,includes:[`src/web/lib/**`]},"doom-web-client-stores":{description:`Client state containers, one <topic>Store.ts per store. A store owns state and the actions that change it; it renders nothing and imports no component.`,includes:[`src/web/stores/**`]},"doom-web-client-components":{description:`Presentation shared by every feature, one PascalCase .tsx file per component. Driven by props and stores, with no feature-specific branching and no data fetching of its own.`,includes:[`src/web/components/**`]},"doom-web-client-features":{description:`One directory per user-facing capability, holding its own components, hooks, and state. A feature is self-contained: code two features need moves down into components, lib, or stores rather than across to a sibling.`,includes:[`src/web/features/**`]},"doom-web-client-routes":{description:`TanStack Router modules where the file tree is the URL tree, so the router owns the spelling (__root.tsx, dot and kebab segments). A route binds a URL to a feature and holds no feature logic.`,includes:[`src/web/routes/**`]},"doom-web-client-app":{description:`The browser composition root: providers, router instantiation, global styles, and the mount call. The one client layer that may reach every other.`,includes:[`src/web/app/**`]},"doom-web-client-entry":{description:`The bundler entry: the HTML document, the mount script, global stylesheet, and ambient client type declarations. Holds wiring only, never behavior worth testing, because nothing can import it.`,includes:[`src/web/index.html`,`src/web/main.tsx`,`src/web/vite-env.d.ts`,`src/web/styles/**`]},"doom-components-types":{description:`The theme contract: the token names a theme must fill, the config shape, and the CSS custom property each token is published as. Imports nothing else, which is what lets both the components and the theme runtime read it.`,includes:[`src/types/**`]},"doom-components-lib":{description:"Pure helpers shared by every component, `cn` above all. No React state, no theme value, no DOM access beyond what a helper is handed.",includes:[`src/lib/**`]},"doom-components-theme":{description:`The theme runtime: the shipped configs, the validator that refuses a malformed one, the derivation of tinted and accented tokens from a palette, and the call that writes them onto the document. It renders nothing, so a host may apply a theme before the first paint.`,includes:[`src/theme/**`,`themes/**`]},"doom-components-icons":{description:`The curated icon set, re-exported from one module so every surface draws from the same family and no consumer needs an icon dependency of its own.`,includes:[`src/icons/**`]},"doom-components-components":{description:`The shared primitives, one PascalCase .tsx file per component. Props-driven and theme-agnostic: colours come from token classes, never from a theme value the component read, so a theme swap recolours a rendered tree without re-rendering it.`,includes:[`src/components/**`]},"doom-components-styles":{description:`The token stylesheet a host imports after Tailwind: the :root defaults, the @theme mapping that turns each token into a utility, the semantic aliases, and the base styles.`,includes:[`styles/**`]},"doom-web-tests":{description:`Unit, integration, and package-contract verification.`,includes:[`tests/**`]},"doom-web-metadata":{description:`Publishable package, documentation, and build configuration.`,includes:[`package.json`,`project.json`,`tsconfig.json`,`vite.config.ts`,`tsdown.config.ts`,`vitest.config.ts`,`playwright.config.ts`,`vibe-lint.config.yaml`,`README.md`,`LICENSE`,`llms.txt`,`.oxlintrc.json`]}};export{e as patterns};
2
+ //# sourceMappingURL=patterns.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.mjs","names":[],"sources":["../../src/configs/patterns.ts"],"sourcesContent":["import type { PatternDefinition } from '@agimon-ai/vibe-lint';\n\n/**\n * Design-pattern context surfaced before a DoomPi web package file is edited.\n * A web package ships a browser bundle and a server from one source tree, so\n * each folder states which of the two it belongs to and what it may reach.\n */\nexport const patterns: Record<string, PatternDefinition> = {\n 'doom-web-types': {\n description:\n 'Framework-neutral contracts both sides share: request and response payloads, domain entities, and option shapes. Imports nothing else in the package, which is what makes it the one root the browser and the server may both read.',\n includes: ['src/types/**'],\n },\n 'doom-web-services': {\n description:\n 'Server-side domain policy over the types layer. No HTTP framework, no filesystem, no browser API, so every rule stays testable without a running server.',\n includes: ['src/services/**'],\n },\n 'doom-web-adapters': {\n description:\n 'Server-side integration with the outside world: HTTP handlers, static asset serving, filesystem and process access, and third-party clients. An adapter satisfies a contract declared in src/types and delegates behavior to src/services.',\n includes: ['src/adapters/**'],\n },\n 'doom-web-bin': {\n description:\n 'Executable entrypoints declared in package.json bin. Reads argv and the environment, assembles the service and adapter graph, and starts the server. Behavior belongs one layer down.',\n includes: ['src/bin/**'],\n },\n 'doom-web-exports': {\n description:\n \"The package's only public surface. Pure re-exports, one file per package.json exports subpath, mirroring the subpath tree.\",\n includes: ['src/exports/**'],\n },\n 'doom-web-client-lib': {\n description:\n 'Browser helpers with no React state and no server import: formatting, parsing, typed fetch wrappers, and query client setup. The lowest client layer above types.',\n includes: ['src/web/lib/**'],\n },\n 'doom-web-client-stores': {\n description:\n 'Client state containers, one <topic>Store.ts per store. A store owns state and the actions that change it; it renders nothing and imports no component.',\n includes: ['src/web/stores/**'],\n },\n 'doom-web-client-components': {\n description:\n 'Presentation shared by every feature, one PascalCase .tsx file per component. Driven by props and stores, with no feature-specific branching and no data fetching of its own.',\n includes: ['src/web/components/**'],\n },\n 'doom-web-client-features': {\n description:\n 'One directory per user-facing capability, holding its own components, hooks, and state. A feature is self-contained: code two features need moves down into components, lib, or stores rather than across to a sibling.',\n includes: ['src/web/features/**'],\n },\n 'doom-web-client-routes': {\n description:\n 'TanStack Router modules where the file tree is the URL tree, so the router owns the spelling (__root.tsx, dot and kebab segments). A route binds a URL to a feature and holds no feature logic.',\n includes: ['src/web/routes/**'],\n },\n 'doom-web-client-app': {\n description:\n 'The browser composition root: providers, router instantiation, global styles, and the mount call. The one client layer that may reach every other.',\n includes: ['src/web/app/**'],\n },\n 'doom-web-client-entry': {\n description:\n 'The bundler entry: the HTML document, the mount script, global stylesheet, and ambient client type declarations. Holds wiring only, never behavior worth testing, because nothing can import it.',\n includes: ['src/web/index.html', 'src/web/main.tsx', 'src/web/vite-env.d.ts', 'src/web/styles/**'],\n },\n 'doom-components-types': {\n description:\n 'The theme contract: the token names a theme must fill, the config shape, and the CSS custom property each token is published as. Imports nothing else, which is what lets both the components and the theme runtime read it.',\n includes: ['src/types/**'],\n },\n 'doom-components-lib': {\n description:\n 'Pure helpers shared by every component, `cn` above all. No React state, no theme value, no DOM access beyond what a helper is handed.',\n includes: ['src/lib/**'],\n },\n 'doom-components-theme': {\n description:\n 'The theme runtime: the shipped configs, the validator that refuses a malformed one, the derivation of tinted and accented tokens from a palette, and the call that writes them onto the document. It renders nothing, so a host may apply a theme before the first paint.',\n includes: ['src/theme/**', 'themes/**'],\n },\n 'doom-components-icons': {\n description:\n 'The curated icon set, re-exported from one module so every surface draws from the same family and no consumer needs an icon dependency of its own.',\n includes: ['src/icons/**'],\n },\n 'doom-components-components': {\n description:\n 'The shared primitives, one PascalCase .tsx file per component. Props-driven and theme-agnostic: colours come from token classes, never from a theme value the component read, so a theme swap recolours a rendered tree without re-rendering it.',\n includes: ['src/components/**'],\n },\n 'doom-components-styles': {\n description:\n 'The token stylesheet a host imports after Tailwind: the :root defaults, the @theme mapping that turns each token into a utility, the semantic aliases, and the base styles.',\n includes: ['styles/**'],\n },\n 'doom-web-tests': {\n description: 'Unit, integration, and package-contract verification.',\n includes: ['tests/**'],\n },\n 'doom-web-metadata': {\n description: 'Publishable package, documentation, and build configuration.',\n includes: [\n 'package.json',\n 'project.json',\n 'tsconfig.json',\n 'vite.config.ts',\n 'tsdown.config.ts',\n 'vitest.config.ts',\n 'playwright.config.ts',\n 'vibe-lint.config.yaml',\n 'README.md',\n 'LICENSE',\n 'llms.txt',\n '.oxlintrc.json',\n ],\n },\n};\n"],"mappings":"AAOA,MAAa,EAA8C,CACzD,iBAAkB,CAChB,YACE,sOACF,SAAU,CAAC,cAAc,CAC3B,EACA,oBAAqB,CACnB,YACE,2JACF,SAAU,CAAC,iBAAiB,CAC9B,EACA,oBAAqB,CACnB,YACE,6OACF,SAAU,CAAC,iBAAiB,CAC9B,EACA,eAAgB,CACd,YACE,wLACF,SAAU,CAAC,YAAY,CACzB,EACA,mBAAoB,CAClB,YACE,6HACF,SAAU,CAAC,gBAAgB,CAC7B,EACA,sBAAuB,CACrB,YACE,oKACF,SAAU,CAAC,gBAAgB,CAC7B,EACA,yBAA0B,CACxB,YACE,0JACF,SAAU,CAAC,mBAAmB,CAChC,EACA,6BAA8B,CAC5B,YACE,gLACF,SAAU,CAAC,uBAAuB,CACpC,EACA,2BAA4B,CAC1B,YACE,0NACF,SAAU,CAAC,qBAAqB,CAClC,EACA,yBAA0B,CACxB,YACE,kMACF,SAAU,CAAC,mBAAmB,CAChC,EACA,sBAAuB,CACrB,YACE,qJACF,SAAU,CAAC,gBAAgB,CAC7B,EACA,wBAAyB,CACvB,YACE,mMACF,SAAU,CAAC,qBAAsB,mBAAoB,wBAAyB,mBAAmB,CACnG,EACA,wBAAyB,CACvB,YACE,+NACF,SAAU,CAAC,cAAc,CAC3B,EACA,sBAAuB,CACrB,YACE,wIACF,SAAU,CAAC,YAAY,CACzB,EACA,wBAAyB,CACvB,YACE,4QACF,SAAU,CAAC,eAAgB,WAAW,CACxC,EACA,wBAAyB,CACvB,YACE,qJACF,SAAU,CAAC,cAAc,CAC3B,EACA,6BAA8B,CAC5B,YACE,mPACF,SAAU,CAAC,mBAAmB,CAChC,EACA,yBAA0B,CACxB,YACE,8KACF,SAAU,CAAC,WAAW,CACxB,EACA,iBAAkB,CAChB,YAAa,wDACb,SAAU,CAAC,UAAU,CACvB,EACA,oBAAqB,CACnB,YAAa,+DACb,SAAU,CACR,eACA,eACA,gBACA,iBACA,mBACA,mBACA,uBACA,wBACA,YACA,UACA,WACA,gBACF,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ const e={rules:{"doom-components-layer-boundary":`error`,"doom-web-layer-boundary":`error`,"no-cross-feature-import":`error`,"no-raw-theme-color":`error`,"prefer-shared-primitive":`warn`,"web-file-naming":`error`}};exports.recommended=e;
2
+ //# sourceMappingURL=recommended.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.cjs","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["import type { PluginConfigPreset, Severity } from '@agimon-ai/vibe-lint';\n\nconst rules: Record<string, Severity> = {\n 'doom-components-layer-boundary': 'error',\n 'doom-web-layer-boundary': 'error',\n 'no-cross-feature-import': 'error',\n 'no-raw-theme-color': 'error',\n // Warn until every surface has been migrated; the sweep would otherwise\n // block the very change that clears it.\n 'prefer-shared-primitive': 'warn',\n 'web-file-naming': 'error',\n};\n\nexport const recommended: PluginConfigPreset = { rules };\n"],"mappings":"AAaA,MAAa,EAAkC,CAAE,OAV/C,iCAAkC,QAClC,0BAA2B,QAC3B,0BAA2B,QAC3B,qBAAsB,QAGtB,0BAA2B,OAC3B,kBAAmB,OAG4B,CAAM"}
@@ -0,0 +1,6 @@
1
+ import { PluginConfigPreset } from "@agimon-ai/vibe-lint";
2
+ //#region src/configs/recommended.d.ts
3
+ declare const recommended: PluginConfigPreset;
4
+ //#endregion
5
+ export { recommended };
6
+ //# sourceMappingURL=recommended.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.d.cts","names":[],"sources":["../../src/configs/recommended.ts"],"mappings":";;cAaa,aAAa"}
@@ -0,0 +1,6 @@
1
+ import { PluginConfigPreset } from "@agimon-ai/vibe-lint";
2
+ //#region src/configs/recommended.d.ts
3
+ declare const recommended: PluginConfigPreset;
4
+ //#endregion
5
+ export { recommended };
6
+ //# sourceMappingURL=recommended.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.d.mts","names":[],"sources":["../../src/configs/recommended.ts"],"mappings":";;cAaa,aAAa"}
@@ -0,0 +1,2 @@
1
+ const e={rules:{"doom-components-layer-boundary":`error`,"doom-web-layer-boundary":`error`,"no-cross-feature-import":`error`,"no-raw-theme-color":`error`,"prefer-shared-primitive":`warn`,"web-file-naming":`error`}};export{e as recommended};
2
+ //# sourceMappingURL=recommended.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.mjs","names":[],"sources":["../../src/configs/recommended.ts"],"sourcesContent":["import type { PluginConfigPreset, Severity } from '@agimon-ai/vibe-lint';\n\nconst rules: Record<string, Severity> = {\n 'doom-components-layer-boundary': 'error',\n 'doom-web-layer-boundary': 'error',\n 'no-cross-feature-import': 'error',\n 'no-raw-theme-color': 'error',\n // Warn until every surface has been migrated; the sweep would otherwise\n // block the very change that clears it.\n 'prefer-shared-primitive': 'warn',\n 'web-file-naming': 'error',\n};\n\nexport const recommended: PluginConfigPreset = { rules };\n"],"mappings":"AAaA,MAAa,EAAkC,CAAE,OAV/C,iCAAkC,QAClC,0BAA2B,QAC3B,0BAA2B,QAC3B,qBAAsB,QAGtB,0BAA2B,OAC3B,kBAAmB,OAG4B,CAAM"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./configs/patterns.cjs"),t=require("./configs/recommended.cjs"),n=require("./rules/componentLibrary.cjs"),r=require("./rules/conventions.cjs"),i=require("./rules/features.cjs"),a=require("./rules/primitives.cjs"),o=require("./rules/layers.cjs"),s=require("./rules/theming.cjs"),c=require("./rules/registry.cjs"),l=require("./plugin.cjs");require("./rules/index.cjs"),exports.default=l.doomWebPlugin,exports.doomComponentsLayerBoundary=n.doomComponentsLayerBoundary,exports.doomWebLayerBoundary=o.doomWebLayerBoundary,exports.doomWebPlugin=l.doomWebPlugin,exports.isComponentLibrary=n.isComponentLibrary,exports.noCrossFeatureImport=i.noCrossFeatureImport,exports.noRawThemeColor=s.noRawThemeColor,exports.patterns=e.patterns,exports.preferSharedPrimitive=a.preferSharedPrimitive,exports.recommended=t.recommended,exports.rules=c.rules,exports.webFileNaming=r.webFileNaming;
@@ -0,0 +1,12 @@
1
+ import { patterns } from "./configs/patterns.cjs";
2
+ import { recommended } from "./configs/recommended.cjs";
3
+ import { doomWebPlugin } from "./plugin.cjs";
4
+ import { doomComponentsLayerBoundary, isComponentLibrary } from "./rules/componentLibrary.cjs";
5
+ import { webFileNaming } from "./rules/conventions.cjs";
6
+ import { noCrossFeatureImport } from "./rules/features.cjs";
7
+ import { doomWebLayerBoundary } from "./rules/layers.cjs";
8
+ import { preferSharedPrimitive } from "./rules/primitives.cjs";
9
+ import { rules } from "./rules/registry.cjs";
10
+ import { noRawThemeColor } from "./rules/theming.cjs";
11
+ import "./rules/index.cjs";
12
+ export { doomWebPlugin as default, doomComponentsLayerBoundary, doomWebLayerBoundary, doomWebPlugin, isComponentLibrary, noCrossFeatureImport, noRawThemeColor, patterns, preferSharedPrimitive, recommended, rules, webFileNaming };
@@ -0,0 +1,12 @@
1
+ import { patterns } from "./configs/patterns.mjs";
2
+ import { recommended } from "./configs/recommended.mjs";
3
+ import { doomWebPlugin } from "./plugin.mjs";
4
+ import { doomComponentsLayerBoundary, isComponentLibrary } from "./rules/componentLibrary.mjs";
5
+ import { webFileNaming } from "./rules/conventions.mjs";
6
+ import { noCrossFeatureImport } from "./rules/features.mjs";
7
+ import { doomWebLayerBoundary } from "./rules/layers.mjs";
8
+ import { preferSharedPrimitive } from "./rules/primitives.mjs";
9
+ import { rules } from "./rules/registry.mjs";
10
+ import { noRawThemeColor } from "./rules/theming.mjs";
11
+ import "./rules/index.mjs";
12
+ export { doomWebPlugin as default, doomComponentsLayerBoundary, doomWebLayerBoundary, doomWebPlugin, isComponentLibrary, noCrossFeatureImport, noRawThemeColor, patterns, preferSharedPrimitive, recommended, rules, webFileNaming };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{patterns as e}from"./configs/patterns.mjs";import{recommended as t}from"./configs/recommended.mjs";import{doomComponentsLayerBoundary as n,isComponentLibrary as r}from"./rules/componentLibrary.mjs";import{webFileNaming as i}from"./rules/conventions.mjs";import{noCrossFeatureImport as a}from"./rules/features.mjs";import{preferSharedPrimitive as o}from"./rules/primitives.mjs";import{doomWebLayerBoundary as s}from"./rules/layers.mjs";import{noRawThemeColor as c}from"./rules/theming.mjs";import{rules as l}from"./rules/registry.mjs";import{doomWebPlugin as u}from"./plugin.mjs";import"./rules/index.mjs";export{u as default,n as doomComponentsLayerBoundary,s as doomWebLayerBoundary,u as doomWebPlugin,r as isComponentLibrary,a as noCrossFeatureImport,c as noRawThemeColor,e as patterns,o as preferSharedPrimitive,t as recommended,l as rules,i as webFileNaming};
@@ -0,0 +1,2 @@
1
+ const e=require("./configs/patterns.cjs"),t=require("./configs/recommended.cjs"),n={name:`doom-web`,rules:require("./rules/registry.cjs").rules,patterns:e.patterns,configs:{recommended:t.recommended}};exports.doomWebPlugin=n;
2
+ //# sourceMappingURL=plugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["import type { VibeLintPlugin } from '@agimon-ai/vibe-lint';\nimport { patterns } from './configs/patterns.js';\nimport { recommended } from './configs/recommended.js';\nimport { rules } from './rules/registry.js';\n\nexport const doomWebPlugin: VibeLintPlugin = {\n name: 'doom-web',\n rules,\n patterns,\n configs: { recommended },\n};\n"],"mappings":"iFAKa,EAAgC,CAC3C,KAAM,WACN,oCAAA,CAAA,CAAA,MACA,SAAA,EAAA,SACA,QAAS,CAAE,YAAA,EAAA,WAAY,CACzB"}
@@ -0,0 +1,6 @@
1
+ import { VibeLintPlugin } from "@agimon-ai/vibe-lint";
2
+ //#region src/plugin.d.ts
3
+ declare const doomWebPlugin: VibeLintPlugin;
4
+ //#endregion
5
+ export { doomWebPlugin };
6
+ //# sourceMappingURL=plugin.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/plugin.ts"],"mappings":";;cAKa,eAAe"}
@@ -0,0 +1,6 @@
1
+ import { VibeLintPlugin } from "@agimon-ai/vibe-lint";
2
+ //#region src/plugin.d.ts
3
+ declare const doomWebPlugin: VibeLintPlugin;
4
+ //#endregion
5
+ export { doomWebPlugin };
6
+ //# sourceMappingURL=plugin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;cAKa,eAAe"}
@@ -0,0 +1,2 @@
1
+ import{patterns as e}from"./configs/patterns.mjs";import{recommended as t}from"./configs/recommended.mjs";import{rules as n}from"./rules/registry.mjs";const r={name:`doom-web`,rules:n,patterns:e,configs:{recommended:t}};export{r as doomWebPlugin};
2
+ //# sourceMappingURL=plugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["import type { VibeLintPlugin } from '@agimon-ai/vibe-lint';\nimport { patterns } from './configs/patterns.js';\nimport { recommended } from './configs/recommended.js';\nimport { rules } from './rules/registry.js';\n\nexport const doomWebPlugin: VibeLintPlugin = {\n name: 'doom-web',\n rules,\n patterns,\n configs: { recommended },\n};\n"],"mappings":"uJAKA,MAAa,EAAgC,CAC3C,KAAM,WACN,QACA,WACA,QAAS,CAAE,aAAY,CACzB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./moduleGraph.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);const i={types:[`types`],lib:[`lib`,`types`],icons:[`icons`],theme:[`theme`,`types`],components:[`components`,`icons`,`lib`,`types`],exports:[`components`,`exports`,`icons`,`lib`,`theme`,`types`]};function a(e){return!n.existsSync(r.join(e,`src`,`web`))&&n.existsSync(r.join(e,`src`,`components`))}function o(e,n){let r=t.projectPath(e,n);if(r===null)return;let i=r.split(`/`);return i[0]===`src`&&i.length>1?i[1]:void 0}const s={preflight:!0,rule:`A shared web component library points inward, and a component never imports the theme runtime`,rationale:`A component library is imported by every surface that renders, so a wrong edge inside it is a wrong edge everywhere. The one worth naming is components reaching the theme: the moment a component reads a colour as a value it renders one theme, and swapping a theme means re-rendering the tree instead of rewriting a custom property. Keep colours in CSS variables and components stay theme-agnostic, testable without a theme, and cheap to restyle.`,check(e,n){if(!a(n))return null;let s=o(e,n),c=s===void 0?void 0:i[s];if(c===void 0)return null;let l=t.readSource(e);if(!l)return null;let u=new Set;for(let i of t.collectSpecifiers(l)){if(!i.startsWith(`.`))continue;let t=o(r.resolve(r.dirname(e),i),n);t===void 0||c.includes(t)||u.add(t===`theme`&&s===`components`?`src/components may not import src/theme ('${i}'). A component takes its colours from the theme's CSS custom properties, so it renders correctly under any theme without reading one. Use a token class such as bg-doom-panel, or take the value as a prop.`:`src/${s} may not import src/${t} ('${i}'). In a component library types and lib are the base, icons and theme sit beside them, components build on components, icons and lib, and exports may reach everything. Move the shared code down to a layer both sides may read.`)}return u.size>0?[...u].join(` `):null}};exports.doomComponentsLayerBoundary=s,exports.isComponentLibrary=a;
2
+ //# sourceMappingURL=componentLibrary.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentLibrary.cjs","names":["fs","path","projectPath","readSource","collectSpecifiers"],"sources":["../../src/rules/componentLibrary.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { collectSpecifiers, projectPath, readSource } from './moduleGraph.js';\n\n/**\n * What each layer of a shared web component library may import.\n *\n * Not a linear order, because one edge a linear order would allow is the one\n * that matters most: a component must not import the theme runtime. Colour\n * reaches a component through CSS custom properties, never through a value it\n * read, which is what lets a theme swap recolour a rendered tree without\n * re-rendering it and what keeps every component usable before a theme loads.\n */\nconst ALLOWED: Readonly<Record<string, readonly string[] | undefined>> = {\n types: ['types'],\n lib: ['lib', 'types'],\n icons: ['icons'],\n theme: ['theme', 'types'],\n components: ['components', 'icons', 'lib', 'types'],\n exports: ['components', 'exports', 'icons', 'lib', 'theme', 'types'],\n};\n\nconst LAYER_SUMMARY =\n 'types and lib are the base, icons and theme sit beside them, components build on components, icons and lib, and exports may reach everything';\n\n/** A component library has no src/web: its whole tree is the browser bundle. */\nexport function isComponentLibrary(configRoot: string): boolean {\n return (\n !fs.existsSync(path.join(configRoot, 'src', 'web')) && fs.existsSync(path.join(configRoot, 'src', 'components'))\n );\n}\n\n/** The layer a path belongs to, or undefined when it is outside src. */\nfunction layerOf(filePath: string, configRoot: string): string | undefined {\n const relativePath = projectPath(filePath, configRoot);\n if (relativePath === null) return undefined;\n const parts = relativePath.split('/');\n return parts[0] === 'src' && parts.length > 1 ? parts[1] : undefined;\n}\n\nexport const doomComponentsLayerBoundary: RuleDefinition = {\n preflight: true,\n rule: 'A shared web component library points inward, and a component never imports the theme runtime',\n rationale:\n 'A component library is imported by every surface that renders, so a wrong edge inside it is a wrong edge everywhere. The one worth naming is components reaching the theme: the moment a component reads a colour as a value it renders one theme, and swapping a theme means re-rendering the tree instead of rewriting a custom property. Keep colours in CSS variables and components stay theme-agnostic, testable without a theme, and cheap to restyle.',\n check(filePath, configRoot) {\n if (!isComponentLibrary(configRoot)) return null;\n const layer = layerOf(filePath, configRoot);\n const allowed = layer === undefined ? undefined : ALLOWED[layer];\n if (allowed === undefined) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const messages = new Set<string>();\n for (const specifier of collectSpecifiers(sourceFile)) {\n if (!specifier.startsWith('.')) continue;\n const target = layerOf(path.resolve(path.dirname(filePath), specifier), configRoot);\n if (target === undefined || allowed.includes(target)) continue;\n messages.add(\n target === 'theme' && layer === 'components'\n ? `src/components may not import src/theme ('${specifier}'). A component takes its colours from the theme's CSS custom properties, so it renders correctly under any theme without reading one. Use a token class such as bg-doom-panel, or take the value as a prop.`\n : `src/${layer} may not import src/${target} ('${specifier}'). In a component library ${LAYER_SUMMARY}. Move the shared code down to a layer both sides may read.`,\n );\n }\n return messages.size > 0 ? [...messages].join(' ') : null;\n },\n};\n"],"mappings":"0KAcA,MAAM,EAAmE,CACvE,MAAO,CAAC,OAAO,EACf,IAAK,CAAC,MAAO,OAAO,EACpB,MAAO,CAAC,OAAO,EACf,MAAO,CAAC,QAAS,OAAO,EACxB,WAAY,CAAC,aAAc,QAAS,MAAO,OAAO,EAClD,QAAS,CAAC,aAAc,UAAW,QAAS,MAAO,QAAS,OAAO,CACrE,EAMA,SAAgB,EAAmB,EAA6B,CAC9D,MACE,CAACA,EAAG,WAAWC,EAAK,KAAK,EAAY,MAAO,KAAK,CAAC,GAAKD,EAAG,WAAWC,EAAK,KAAK,EAAY,MAAO,YAAY,CAAC,CAEnH,CAGA,SAAS,EAAQ,EAAkB,EAAwC,CACzE,IAAM,EAAeC,EAAAA,YAAY,EAAU,CAAU,EACrD,GAAI,IAAiB,KAAM,OAC3B,IAAM,EAAQ,EAAa,MAAM,GAAG,EACpC,OAAO,EAAM,KAAO,OAAS,EAAM,OAAS,EAAI,EAAM,GAAK,IAAA,EAC7D,CAEA,MAAa,EAA8C,CACzD,UAAW,GACX,KAAM,gGACN,UACE,gcACF,MAAM,EAAU,EAAY,CAC1B,GAAI,CAAC,EAAmB,CAAU,EAAG,OAAO,KAC5C,IAAM,EAAQ,EAAQ,EAAU,CAAU,EACpC,EAAU,IAAU,IAAA,GAAY,IAAA,GAAY,EAAQ,GAC1D,GAAI,IAAY,IAAA,GAAW,OAAO,KAClC,IAAM,EAAaC,EAAAA,WAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAaC,EAAAA,kBAAkB,CAAU,EAAG,CACrD,GAAI,CAAC,EAAU,WAAW,GAAG,EAAG,SAChC,IAAM,EAAS,EAAQH,EAAK,QAAQA,EAAK,QAAQ,CAAQ,EAAG,CAAS,EAAG,CAAU,EAC9E,IAAW,IAAA,IAAa,EAAQ,SAAS,CAAM,GACnD,EAAS,IACP,IAAW,SAAW,IAAU,aAC5B,6CAA6C,EAAU,8MACvD,OAAO,EAAM,sBAAsB,EAAO,KAAK,EAAU,mOAC/D,CACF,CACA,OAAO,EAAS,KAAO,EAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAG,EAAI,IACvD,CACF"}
@@ -0,0 +1,8 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/componentLibrary.d.ts
3
+ /** A component library has no src/web: its whole tree is the browser bundle. */
4
+ declare function isComponentLibrary(configRoot: string): boolean;
5
+ declare const doomComponentsLayerBoundary: RuleDefinition;
6
+ //#endregion
7
+ export { doomComponentsLayerBoundary, isComponentLibrary };
8
+ //# sourceMappingURL=componentLibrary.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentLibrary.d.cts","names":[],"sources":["../../src/rules/componentLibrary.ts"],"mappings":";;;iBA2BgB,mBAAmB;cActB,6BAA6B"}
@@ -0,0 +1,8 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/componentLibrary.d.ts
3
+ /** A component library has no src/web: its whole tree is the browser bundle. */
4
+ declare function isComponentLibrary(configRoot: string): boolean;
5
+ declare const doomComponentsLayerBoundary: RuleDefinition;
6
+ //#endregion
7
+ export { doomComponentsLayerBoundary, isComponentLibrary };
8
+ //# sourceMappingURL=componentLibrary.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentLibrary.d.mts","names":[],"sources":["../../src/rules/componentLibrary.ts"],"mappings":";;;iBA2BgB,mBAAmB;cActB,6BAA6B"}
@@ -0,0 +1,2 @@
1
+ import{collectSpecifiers as e,projectPath as t,readSource as n}from"./moduleGraph.mjs";import*as r from"node:fs";import*as i from"node:path";const a={types:[`types`],lib:[`lib`,`types`],icons:[`icons`],theme:[`theme`,`types`],components:[`components`,`icons`,`lib`,`types`],exports:[`components`,`exports`,`icons`,`lib`,`theme`,`types`]};function o(e){return!r.existsSync(i.join(e,`src`,`web`))&&r.existsSync(i.join(e,`src`,`components`))}function s(e,n){let r=t(e,n);if(r===null)return;let i=r.split(`/`);return i[0]===`src`&&i.length>1?i[1]:void 0}const c={preflight:!0,rule:`A shared web component library points inward, and a component never imports the theme runtime`,rationale:`A component library is imported by every surface that renders, so a wrong edge inside it is a wrong edge everywhere. The one worth naming is components reaching the theme: the moment a component reads a colour as a value it renders one theme, and swapping a theme means re-rendering the tree instead of rewriting a custom property. Keep colours in CSS variables and components stay theme-agnostic, testable without a theme, and cheap to restyle.`,check(t,r){if(!o(r))return null;let c=s(t,r),l=c===void 0?void 0:a[c];if(l===void 0)return null;let u=n(t);if(!u)return null;let d=new Set;for(let n of e(u)){if(!n.startsWith(`.`))continue;let e=s(i.resolve(i.dirname(t),n),r);e===void 0||l.includes(e)||d.add(e===`theme`&&c===`components`?`src/components may not import src/theme ('${n}'). A component takes its colours from the theme's CSS custom properties, so it renders correctly under any theme without reading one. Use a token class such as bg-doom-panel, or take the value as a prop.`:`src/${c} may not import src/${e} ('${n}'). In a component library types and lib are the base, icons and theme sit beside them, components build on components, icons and lib, and exports may reach everything. Move the shared code down to a layer both sides may read.`)}return d.size>0?[...d].join(` `):null}};export{c as doomComponentsLayerBoundary,o as isComponentLibrary};
2
+ //# sourceMappingURL=componentLibrary.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentLibrary.mjs","names":[],"sources":["../../src/rules/componentLibrary.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { collectSpecifiers, projectPath, readSource } from './moduleGraph.js';\n\n/**\n * What each layer of a shared web component library may import.\n *\n * Not a linear order, because one edge a linear order would allow is the one\n * that matters most: a component must not import the theme runtime. Colour\n * reaches a component through CSS custom properties, never through a value it\n * read, which is what lets a theme swap recolour a rendered tree without\n * re-rendering it and what keeps every component usable before a theme loads.\n */\nconst ALLOWED: Readonly<Record<string, readonly string[] | undefined>> = {\n types: ['types'],\n lib: ['lib', 'types'],\n icons: ['icons'],\n theme: ['theme', 'types'],\n components: ['components', 'icons', 'lib', 'types'],\n exports: ['components', 'exports', 'icons', 'lib', 'theme', 'types'],\n};\n\nconst LAYER_SUMMARY =\n 'types and lib are the base, icons and theme sit beside them, components build on components, icons and lib, and exports may reach everything';\n\n/** A component library has no src/web: its whole tree is the browser bundle. */\nexport function isComponentLibrary(configRoot: string): boolean {\n return (\n !fs.existsSync(path.join(configRoot, 'src', 'web')) && fs.existsSync(path.join(configRoot, 'src', 'components'))\n );\n}\n\n/** The layer a path belongs to, or undefined when it is outside src. */\nfunction layerOf(filePath: string, configRoot: string): string | undefined {\n const relativePath = projectPath(filePath, configRoot);\n if (relativePath === null) return undefined;\n const parts = relativePath.split('/');\n return parts[0] === 'src' && parts.length > 1 ? parts[1] : undefined;\n}\n\nexport const doomComponentsLayerBoundary: RuleDefinition = {\n preflight: true,\n rule: 'A shared web component library points inward, and a component never imports the theme runtime',\n rationale:\n 'A component library is imported by every surface that renders, so a wrong edge inside it is a wrong edge everywhere. The one worth naming is components reaching the theme: the moment a component reads a colour as a value it renders one theme, and swapping a theme means re-rendering the tree instead of rewriting a custom property. Keep colours in CSS variables and components stay theme-agnostic, testable without a theme, and cheap to restyle.',\n check(filePath, configRoot) {\n if (!isComponentLibrary(configRoot)) return null;\n const layer = layerOf(filePath, configRoot);\n const allowed = layer === undefined ? undefined : ALLOWED[layer];\n if (allowed === undefined) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const messages = new Set<string>();\n for (const specifier of collectSpecifiers(sourceFile)) {\n if (!specifier.startsWith('.')) continue;\n const target = layerOf(path.resolve(path.dirname(filePath), specifier), configRoot);\n if (target === undefined || allowed.includes(target)) continue;\n messages.add(\n target === 'theme' && layer === 'components'\n ? `src/components may not import src/theme ('${specifier}'). A component takes its colours from the theme's CSS custom properties, so it renders correctly under any theme without reading one. Use a token class such as bg-doom-panel, or take the value as a prop.`\n : `src/${layer} may not import src/${target} ('${specifier}'). In a component library ${LAYER_SUMMARY}. Move the shared code down to a layer both sides may read.`,\n );\n }\n return messages.size > 0 ? [...messages].join(' ') : null;\n },\n};\n"],"mappings":"6IAcA,MAAM,EAAmE,CACvE,MAAO,CAAC,OAAO,EACf,IAAK,CAAC,MAAO,OAAO,EACpB,MAAO,CAAC,OAAO,EACf,MAAO,CAAC,QAAS,OAAO,EACxB,WAAY,CAAC,aAAc,QAAS,MAAO,OAAO,EAClD,QAAS,CAAC,aAAc,UAAW,QAAS,MAAO,QAAS,OAAO,CACrE,EAMA,SAAgB,EAAmB,EAA6B,CAC9D,MACE,CAAC,EAAG,WAAW,EAAK,KAAK,EAAY,MAAO,KAAK,CAAC,GAAK,EAAG,WAAW,EAAK,KAAK,EAAY,MAAO,YAAY,CAAC,CAEnH,CAGA,SAAS,EAAQ,EAAkB,EAAwC,CACzE,IAAM,EAAe,EAAY,EAAU,CAAU,EACrD,GAAI,IAAiB,KAAM,OAC3B,IAAM,EAAQ,EAAa,MAAM,GAAG,EACpC,OAAO,EAAM,KAAO,OAAS,EAAM,OAAS,EAAI,EAAM,GAAK,IAAA,EAC7D,CAEA,MAAa,EAA8C,CACzD,UAAW,GACX,KAAM,gGACN,UACE,gcACF,MAAM,EAAU,EAAY,CAC1B,GAAI,CAAC,EAAmB,CAAU,EAAG,OAAO,KAC5C,IAAM,EAAQ,EAAQ,EAAU,CAAU,EACpC,EAAU,IAAU,IAAA,GAAY,IAAA,GAAY,EAAQ,GAC1D,GAAI,IAAY,IAAA,GAAW,OAAO,KAClC,IAAM,EAAa,EAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAa,EAAkB,CAAU,EAAG,CACrD,GAAI,CAAC,EAAU,WAAW,GAAG,EAAG,SAChC,IAAM,EAAS,EAAQ,EAAK,QAAQ,EAAK,QAAQ,CAAQ,EAAG,CAAS,EAAG,CAAU,EAC9E,IAAW,IAAA,IAAa,EAAQ,SAAS,CAAM,GACnD,EAAS,IACP,IAAW,SAAW,IAAU,aAC5B,6CAA6C,EAAU,8MACvD,OAAO,EAAM,sBAAsB,EAAO,KAAK,EAAU,mOAC/D,CACF,CACA,OAAO,EAAS,KAAO,EAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAG,EAAI,IACvD,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./moduleGraph.cjs");let n=require("node:path");n=e.__toESM(n,1);const r=/^[A-Z][A-Za-z0-9]*$/,i=/^use[A-Z0-9][A-Za-z0-9]*$/,a=/^use[-_A-Z0-9]/,o={preflight:!0,rule:`Browser files are named after what they export: PascalCase components, use* hooks, <topic>Store stores`,rationale:`A client tree is read by filename far more often than by content, in a file list, a stack trace, and an import statement. When the name states the kind, a reader knows whether a module renders, holds state, or wraps behavior before opening it, and a search for a component finds one spelling instead of three. Router files are exempt because their names are the URL, not the export.`,check(e,o){let s=t.projectPath(e,o);if(!s?.startsWith(`src/web/`))return null;let c=s.split(`/`),l=c[c.length-1],u=n.extname(l);if(u!==`.ts`&&u!==`.tsx`||l===`index.ts`||l===`index.tsx`||c[2]===`routes`)return null;let d=l.slice(0,-u.length);if(d.includes(`.`))return null;let f=c[c.length-2];return f===`hooks`||a.test(d)?i.test(d)?null:`${s} is not a camelCase use* name. A hook file is named after the hook it exports, for example useChatSession.ts.`:f===`stores`?d.endsWith(`Store`)&&u===`.ts`?null:`${s} does not end in Store.ts. A file under stores/ is named after the store it exports, for example chatStore.ts.`:f===`components`&&u===`.tsx`?r.test(d)?null:`${s} is not PascalCase. A .tsx file under components/ is named after the component it exports, for example ChatPanel.tsx.`:null}};exports.webFileNaming=o;
2
+ //# sourceMappingURL=conventions.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.cjs","names":["projectPath","path"],"sources":["../../src/rules/conventions.ts"],"sourcesContent":["import * as path from 'node:path';\nimport type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { projectPath } from './moduleGraph.js';\n\nconst COMPONENT_NAME = /^[A-Z][A-Za-z0-9]*$/;\nconst HOOK_NAME = /^use[A-Z0-9][A-Za-z0-9]*$/;\n/** `useChat` and `use-chat` read as hooks; `userProfile` does not. */\nconst HOOK_LIKE = /^use[-_A-Z0-9]/;\n\nexport const webFileNaming: RuleDefinition = {\n preflight: true,\n rule: 'Browser files are named after what they export: PascalCase components, use* hooks, <topic>Store stores',\n rationale:\n 'A client tree is read by filename far more often than by content, in a file list, a stack trace, and an import statement. When the name states the kind, a reader knows whether a module renders, holds state, or wraps behavior before opening it, and a search for a component finds one spelling instead of three. Router files are exempt because their names are the URL, not the export.',\n check(filePath, configRoot) {\n const relativePath = projectPath(filePath, configRoot);\n if (!relativePath?.startsWith('src/web/')) return null;\n\n const parts = relativePath.split('/');\n const fileName = parts[parts.length - 1];\n const extension = path.extname(fileName);\n if (extension !== '.ts' && extension !== '.tsx') return null;\n if (fileName === 'index.ts' || fileName === 'index.tsx') return null;\n // src/web/routes names the URL tree, so TanStack owns the spelling there.\n if (parts[2] === 'routes') return null;\n\n const stem = fileName.slice(0, -extension.length);\n // A dotted stem is a variant of another file (Panel.test.tsx, Panel.stories.tsx).\n if (stem.includes('.')) return null;\n const directory = parts[parts.length - 2];\n\n if (directory === 'hooks' || HOOK_LIKE.test(stem)) {\n return HOOK_NAME.test(stem)\n ? null\n : `${relativePath} is not a camelCase use* name. A hook file is named after the hook it exports, for example useChatSession.ts.`;\n }\n if (directory === 'stores') {\n return stem.endsWith('Store') && extension === '.ts'\n ? null\n : `${relativePath} does not end in Store.ts. A file under stores/ is named after the store it exports, for example chatStore.ts.`;\n }\n if (directory === 'components' && extension === '.tsx') {\n return COMPONENT_NAME.test(stem)\n ? null\n : `${relativePath} is not PascalCase. A .tsx file under components/ is named after the component it exports, for example ChatPanel.tsx.`;\n }\n return null;\n },\n};\n"],"mappings":"gIAIA,MAAM,EAAiB,sBACjB,EAAY,4BAEZ,EAAY,iBAEL,EAAgC,CAC3C,UAAW,GACX,KAAM,yGACN,UACE,iYACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAeA,EAAAA,YAAY,EAAU,CAAU,EACrD,GAAI,CAAC,GAAc,WAAW,UAAU,EAAG,OAAO,KAElD,IAAM,EAAQ,EAAa,MAAM,GAAG,EAC9B,EAAW,EAAM,EAAM,OAAS,GAChC,EAAYC,EAAK,QAAQ,CAAQ,EAIvC,GAHI,IAAc,OAAS,IAAc,QACrC,IAAa,YAAc,IAAa,aAExC,EAAM,KAAO,SAAU,OAAO,KAElC,IAAM,EAAO,EAAS,MAAM,EAAG,CAAC,EAAU,MAAM,EAEhD,GAAI,EAAK,SAAS,GAAG,EAAG,OAAO,KAC/B,IAAM,EAAY,EAAM,EAAM,OAAS,GAiBvC,OAfI,IAAc,SAAW,EAAU,KAAK,CAAI,EACvC,EAAU,KAAK,CAAI,EACtB,KACA,GAAG,EAAa,+GAElB,IAAc,SACT,EAAK,SAAS,OAAO,GAAK,IAAc,MAC3C,KACA,GAAG,EAAa,gHAElB,IAAc,cAAgB,IAAc,OACvC,EAAe,KAAK,CAAI,EAC3B,KACA,GAAG,EAAa,uHAEf,IACT,CACF"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/conventions.d.ts
3
+ declare const webFileNaming: RuleDefinition;
4
+ //#endregion
5
+ export { webFileNaming };
6
+ //# sourceMappingURL=conventions.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.d.cts","names":[],"sources":["../../src/rules/conventions.ts"],"mappings":";;cASa,eAAe"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/conventions.d.ts
3
+ declare const webFileNaming: RuleDefinition;
4
+ //#endregion
5
+ export { webFileNaming };
6
+ //# sourceMappingURL=conventions.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.d.mts","names":[],"sources":["../../src/rules/conventions.ts"],"mappings":";;cASa,eAAe"}
@@ -0,0 +1,2 @@
1
+ import{projectPath as e}from"./moduleGraph.mjs";import*as t from"node:path";const n=/^[A-Z][A-Za-z0-9]*$/,r=/^use[A-Z0-9][A-Za-z0-9]*$/,i=/^use[-_A-Z0-9]/,a={preflight:!0,rule:`Browser files are named after what they export: PascalCase components, use* hooks, <topic>Store stores`,rationale:`A client tree is read by filename far more often than by content, in a file list, a stack trace, and an import statement. When the name states the kind, a reader knows whether a module renders, holds state, or wraps behavior before opening it, and a search for a component finds one spelling instead of three. Router files are exempt because their names are the URL, not the export.`,check(a,o){let s=e(a,o);if(!s?.startsWith(`src/web/`))return null;let c=s.split(`/`),l=c[c.length-1],u=t.extname(l);if(u!==`.ts`&&u!==`.tsx`||l===`index.ts`||l===`index.tsx`||c[2]===`routes`)return null;let d=l.slice(0,-u.length);if(d.includes(`.`))return null;let f=c[c.length-2];return f===`hooks`||i.test(d)?r.test(d)?null:`${s} is not a camelCase use* name. A hook file is named after the hook it exports, for example useChatSession.ts.`:f===`stores`?d.endsWith(`Store`)&&u===`.ts`?null:`${s} does not end in Store.ts. A file under stores/ is named after the store it exports, for example chatStore.ts.`:f===`components`&&u===`.tsx`?n.test(d)?null:`${s} is not PascalCase. A .tsx file under components/ is named after the component it exports, for example ChatPanel.tsx.`:null}};export{a as webFileNaming};
2
+ //# sourceMappingURL=conventions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.mjs","names":[],"sources":["../../src/rules/conventions.ts"],"sourcesContent":["import * as path from 'node:path';\nimport type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { projectPath } from './moduleGraph.js';\n\nconst COMPONENT_NAME = /^[A-Z][A-Za-z0-9]*$/;\nconst HOOK_NAME = /^use[A-Z0-9][A-Za-z0-9]*$/;\n/** `useChat` and `use-chat` read as hooks; `userProfile` does not. */\nconst HOOK_LIKE = /^use[-_A-Z0-9]/;\n\nexport const webFileNaming: RuleDefinition = {\n preflight: true,\n rule: 'Browser files are named after what they export: PascalCase components, use* hooks, <topic>Store stores',\n rationale:\n 'A client tree is read by filename far more often than by content, in a file list, a stack trace, and an import statement. When the name states the kind, a reader knows whether a module renders, holds state, or wraps behavior before opening it, and a search for a component finds one spelling instead of three. Router files are exempt because their names are the URL, not the export.',\n check(filePath, configRoot) {\n const relativePath = projectPath(filePath, configRoot);\n if (!relativePath?.startsWith('src/web/')) return null;\n\n const parts = relativePath.split('/');\n const fileName = parts[parts.length - 1];\n const extension = path.extname(fileName);\n if (extension !== '.ts' && extension !== '.tsx') return null;\n if (fileName === 'index.ts' || fileName === 'index.tsx') return null;\n // src/web/routes names the URL tree, so TanStack owns the spelling there.\n if (parts[2] === 'routes') return null;\n\n const stem = fileName.slice(0, -extension.length);\n // A dotted stem is a variant of another file (Panel.test.tsx, Panel.stories.tsx).\n if (stem.includes('.')) return null;\n const directory = parts[parts.length - 2];\n\n if (directory === 'hooks' || HOOK_LIKE.test(stem)) {\n return HOOK_NAME.test(stem)\n ? null\n : `${relativePath} is not a camelCase use* name. A hook file is named after the hook it exports, for example useChatSession.ts.`;\n }\n if (directory === 'stores') {\n return stem.endsWith('Store') && extension === '.ts'\n ? null\n : `${relativePath} does not end in Store.ts. A file under stores/ is named after the store it exports, for example chatStore.ts.`;\n }\n if (directory === 'components' && extension === '.tsx') {\n return COMPONENT_NAME.test(stem)\n ? null\n : `${relativePath} is not PascalCase. A .tsx file under components/ is named after the component it exports, for example ChatPanel.tsx.`;\n }\n return null;\n },\n};\n"],"mappings":"4EAIA,MAAM,EAAiB,sBACjB,EAAY,4BAEZ,EAAY,iBAEL,EAAgC,CAC3C,UAAW,GACX,KAAM,yGACN,UACE,iYACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAe,EAAY,EAAU,CAAU,EACrD,GAAI,CAAC,GAAc,WAAW,UAAU,EAAG,OAAO,KAElD,IAAM,EAAQ,EAAa,MAAM,GAAG,EAC9B,EAAW,EAAM,EAAM,OAAS,GAChC,EAAY,EAAK,QAAQ,CAAQ,EAIvC,GAHI,IAAc,OAAS,IAAc,QACrC,IAAa,YAAc,IAAa,aAExC,EAAM,KAAO,SAAU,OAAO,KAElC,IAAM,EAAO,EAAS,MAAM,EAAG,CAAC,EAAU,MAAM,EAEhD,GAAI,EAAK,SAAS,GAAG,EAAG,OAAO,KAC/B,IAAM,EAAY,EAAM,EAAM,OAAS,GAiBvC,OAfI,IAAc,SAAW,EAAU,KAAK,CAAI,EACvC,EAAU,KAAK,CAAI,EACtB,KACA,GAAG,EAAa,+GAElB,IAAc,SACT,EAAK,SAAS,OAAO,GAAK,IAAc,MAC3C,KACA,GAAG,EAAa,gHAElB,IAAc,cAAgB,IAAc,OACvC,EAAe,KAAK,CAAI,EAC3B,KACA,GAAG,EAAa,uHAEf,IACT,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./moduleGraph.cjs"),t={preflight:!0,rule:`A feature under src/web/features never imports a sibling feature`,rationale:`A feature is the unit a team owns, ships, and removes. One import into a sibling makes that untrue: the two now share a release, a regression surface, and a reviewer, and the next import is easier to justify than the first. What both features need is not feature code, it is shared code that has not been named yet. Lift it down a layer and each feature keeps a single direction of dependency.`,check(t,n){let r=e.locate(t,n);if(r?.feature===void 0)return null;let i=e.readSource(t);if(!i)return null;let a=new Set;for(let o of e.collectSpecifiers(i)){let i=e.relativeTarget(t,o,n);i?.feature!==void 0&&i.feature!==r.feature&&a.add(`src/web/features/${r.feature} may not import src/web/features/${i.feature} ('${o}'). Features stay independent. Lift the shared code into src/web/components, src/web/lib or src/web/stores and import it from both features.`)}return a.size>0?[...a].join(` `):null}};exports.noCrossFeatureImport=t;
2
+ //# sourceMappingURL=features.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.cjs","names":["locate","readSource","collectSpecifiers","relativeTarget"],"sources":["../../src/rules/features.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { collectSpecifiers, locate, readSource, relativeTarget } from './moduleGraph.js';\n\nexport const noCrossFeatureImport: RuleDefinition = {\n preflight: true,\n rule: 'A feature under src/web/features never imports a sibling feature',\n rationale:\n 'A feature is the unit a team owns, ships, and removes. One import into a sibling makes that untrue: the two now share a release, a regression surface, and a reviewer, and the next import is easier to justify than the first. What both features need is not feature code, it is shared code that has not been named yet. Lift it down a layer and each feature keeps a single direction of dependency.',\n check(filePath, configRoot) {\n const source = locate(filePath, configRoot);\n if (source?.feature === undefined) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const messages = new Set<string>();\n for (const specifier of collectSpecifiers(sourceFile)) {\n const target = relativeTarget(filePath, specifier, configRoot);\n if (target?.feature === undefined || target.feature === source.feature) continue;\n messages.add(\n `src/web/features/${source.feature} may not import src/web/features/${target.feature} ('${specifier}'). Features stay independent. Lift the shared code into src/web/components, src/web/lib or src/web/stores and import it from both features.`,\n );\n }\n\n return messages.size > 0 ? [...messages].join(' ') : null;\n },\n};\n"],"mappings":"qCAGa,EAAuC,CAClD,UAAW,GACX,KAAM,mEACN,UACE,4YACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAASA,EAAAA,OAAO,EAAU,CAAU,EAC1C,GAAI,GAAQ,UAAY,IAAA,GAAW,OAAO,KAC1C,IAAM,EAAaC,EAAAA,WAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAaC,EAAAA,kBAAkB,CAAU,EAAG,CACrD,IAAM,EAASC,EAAAA,eAAe,EAAU,EAAW,CAAU,EACzD,GAAQ,UAAY,IAAA,IAAa,EAAO,UAAY,EAAO,SAC/D,EAAS,IACP,oBAAoB,EAAO,QAAQ,mCAAmC,EAAO,QAAQ,KAAK,EAAU,6IACtG,CACF,CAEA,OAAO,EAAS,KAAO,EAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAG,EAAI,IACvD,CACF"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/features.d.ts
3
+ declare const noCrossFeatureImport: RuleDefinition;
4
+ //#endregion
5
+ export { noCrossFeatureImport };
6
+ //# sourceMappingURL=features.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.d.cts","names":[],"sources":["../../src/rules/features.ts"],"mappings":";;cAGa,sBAAsB"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/features.d.ts
3
+ declare const noCrossFeatureImport: RuleDefinition;
4
+ //#endregion
5
+ export { noCrossFeatureImport };
6
+ //# sourceMappingURL=features.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.d.mts","names":[],"sources":["../../src/rules/features.ts"],"mappings":";;cAGa,sBAAsB"}
@@ -0,0 +1,2 @@
1
+ import{collectSpecifiers as e,locate as t,readSource as n,relativeTarget as r}from"./moduleGraph.mjs";const i={preflight:!0,rule:`A feature under src/web/features never imports a sibling feature`,rationale:`A feature is the unit a team owns, ships, and removes. One import into a sibling makes that untrue: the two now share a release, a regression surface, and a reviewer, and the next import is easier to justify than the first. What both features need is not feature code, it is shared code that has not been named yet. Lift it down a layer and each feature keeps a single direction of dependency.`,check(i,a){let o=t(i,a);if(o?.feature===void 0)return null;let s=n(i);if(!s)return null;let c=new Set;for(let t of e(s)){let e=r(i,t,a);e?.feature!==void 0&&e.feature!==o.feature&&c.add(`src/web/features/${o.feature} may not import src/web/features/${e.feature} ('${t}'). Features stay independent. Lift the shared code into src/web/components, src/web/lib or src/web/stores and import it from both features.`)}return c.size>0?[...c].join(` `):null}};export{i as noCrossFeatureImport};
2
+ //# sourceMappingURL=features.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.mjs","names":[],"sources":["../../src/rules/features.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { collectSpecifiers, locate, readSource, relativeTarget } from './moduleGraph.js';\n\nexport const noCrossFeatureImport: RuleDefinition = {\n preflight: true,\n rule: 'A feature under src/web/features never imports a sibling feature',\n rationale:\n 'A feature is the unit a team owns, ships, and removes. One import into a sibling makes that untrue: the two now share a release, a regression surface, and a reviewer, and the next import is easier to justify than the first. What both features need is not feature code, it is shared code that has not been named yet. Lift it down a layer and each feature keeps a single direction of dependency.',\n check(filePath, configRoot) {\n const source = locate(filePath, configRoot);\n if (source?.feature === undefined) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const messages = new Set<string>();\n for (const specifier of collectSpecifiers(sourceFile)) {\n const target = relativeTarget(filePath, specifier, configRoot);\n if (target?.feature === undefined || target.feature === source.feature) continue;\n messages.add(\n `src/web/features/${source.feature} may not import src/web/features/${target.feature} ('${specifier}'). Features stay independent. Lift the shared code into src/web/components, src/web/lib or src/web/stores and import it from both features.`,\n );\n }\n\n return messages.size > 0 ? [...messages].join(' ') : null;\n },\n};\n"],"mappings":"sGAGA,MAAa,EAAuC,CAClD,UAAW,GACX,KAAM,mEACN,UACE,4YACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAS,EAAO,EAAU,CAAU,EAC1C,GAAI,GAAQ,UAAY,IAAA,GAAW,OAAO,KAC1C,IAAM,EAAa,EAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAa,EAAkB,CAAU,EAAG,CACrD,IAAM,EAAS,EAAe,EAAU,EAAW,CAAU,EACzD,GAAQ,UAAY,IAAA,IAAa,EAAO,UAAY,EAAO,SAC/D,EAAS,IACP,oBAAoB,EAAO,QAAQ,mCAAmC,EAAO,QAAQ,KAAK,EAAU,6IACtG,CACF,CAEA,OAAO,EAAS,KAAO,EAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAG,EAAI,IACvD,CACF"}
@@ -0,0 +1 @@
1
+ const e=require("./componentLibrary.cjs"),t=require("./conventions.cjs"),n=require("./features.cjs"),r=require("./primitives.cjs"),i=require("./layers.cjs"),a=require("./theming.cjs"),o=require("./registry.cjs");exports.doomComponentsLayerBoundary=e.doomComponentsLayerBoundary,exports.doomWebLayerBoundary=i.doomWebLayerBoundary,exports.isComponentLibrary=e.isComponentLibrary,exports.noCrossFeatureImport=n.noCrossFeatureImport,exports.noRawThemeColor=a.noRawThemeColor,exports.preferSharedPrimitive=r.preferSharedPrimitive,exports.rules=o.rules,exports.webFileNaming=t.webFileNaming;
@@ -0,0 +1,8 @@
1
+ import { doomComponentsLayerBoundary, isComponentLibrary } from "./componentLibrary.cjs";
2
+ import { webFileNaming } from "./conventions.cjs";
3
+ import { noCrossFeatureImport } from "./features.cjs";
4
+ import { doomWebLayerBoundary } from "./layers.cjs";
5
+ import { preferSharedPrimitive } from "./primitives.cjs";
6
+ import { rules } from "./registry.cjs";
7
+ import { noRawThemeColor } from "./theming.cjs";
8
+ export { doomComponentsLayerBoundary, doomWebLayerBoundary, isComponentLibrary, noCrossFeatureImport, noRawThemeColor, preferSharedPrimitive, rules, webFileNaming };
@@ -0,0 +1,8 @@
1
+ import { doomComponentsLayerBoundary, isComponentLibrary } from "./componentLibrary.mjs";
2
+ import { webFileNaming } from "./conventions.mjs";
3
+ import { noCrossFeatureImport } from "./features.mjs";
4
+ import { doomWebLayerBoundary } from "./layers.mjs";
5
+ import { preferSharedPrimitive } from "./primitives.mjs";
6
+ import { rules } from "./registry.mjs";
7
+ import { noRawThemeColor } from "./theming.mjs";
8
+ export { doomComponentsLayerBoundary, doomWebLayerBoundary, isComponentLibrary, noCrossFeatureImport, noRawThemeColor, preferSharedPrimitive, rules, webFileNaming };
@@ -0,0 +1 @@
1
+ import{doomComponentsLayerBoundary as e,isComponentLibrary as t}from"./componentLibrary.mjs";import{webFileNaming as n}from"./conventions.mjs";import{noCrossFeatureImport as r}from"./features.mjs";import{preferSharedPrimitive as i}from"./primitives.mjs";import{doomWebLayerBoundary as a}from"./layers.mjs";import{noRawThemeColor as o}from"./theming.mjs";import{rules as s}from"./registry.mjs";export{e as doomComponentsLayerBoundary,a as doomWebLayerBoundary,t as isComponentLibrary,r as noCrossFeatureImport,o as noRawThemeColor,i as preferSharedPrimitive,s as rules,n as webFileNaming};
@@ -0,0 +1,2 @@
1
+ const e=require("./moduleGraph.cjs"),t={types:0,lib:1,stores:2,components:3,features:4,routes:5,app:6},n={types:0,services:1,adapters:2,bin:3,exports:4},r=new Set([`services`,`adapters`,`bin`,`exports`]),i=new Set([`services`,`adapters`,`bin`]);function a(e){return e.side===`client`?e.layer?`src/web/${e.layer}`:`src/web`:e.layer?`src/${e.layer}`:`src`}function o(e,o,s){let c=`${a(e)} may not import ${a(o)} ('${s}')`;if(e.side===`client`&&o.side===`server`)return r.has(o.layer??``)?`${c}. Browser code may not reach server code. Call the server over HTTP or RPC and keep the shared contract in src/types.`:void 0;if(e.side===`server`&&o.side===`client`)return i.has(e.layer??``)?`${c}. Server code may not reach the browser bundle. Keep the shared contract in src/types and let the client import it from there.`:void 0;let l=e.side===`client`?t:n,u=e.layer===void 0?void 0:l[e.layer],d=o.layer===void 0?void 0:l[o.layer];if(!(u===void 0||d===void 0||d<=u))return e.side===`client`?`${c}. The client layer order is types, lib, stores, components, features, routes, app and no layer may import a higher one. Move the shared code down to a lower layer, or hand it in as a prop, a store value, or a route parameter.`:`${c}. The server layer order is types, services, adapters, bin, exports and no layer may import a higher one. Declare the contract in src/types and let the higher layer inject the implementation.`}const s={preflight:!0,rule:`Web layers point inward and the browser bundle never meets the server outside src/types`,rationale:`A web package ships two programs from one source tree. Left unseparated they merge: a component reaches a service for one field, the bundler follows it, and server credentials, filesystem access, and node builtins land in the browser. Direction inside each tree matters for the same reason: when a component imports a feature, the reusable half now depends on the specific one and neither can be moved or deleted alone. Keep the shared vocabulary in src/types, where both sides can read it without dragging an implementation across.`,check(t,n){let r=e.locate(t,n);if(!r)return null;let i=e.readSource(t);if(!i)return null;let a=new Set;for(let s of e.collectSpecifiers(i)){let i=e.relativeTarget(t,s,n);if(!i)continue;let c=o(r,i,s);c&&a.add(c)}return a.size>0?[...a].join(` `):null}};exports.doomWebLayerBoundary=s;
2
+ //# sourceMappingURL=layers.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.cjs","names":["locate","readSource","collectSpecifiers","relativeTarget"],"sources":["../../src/rules/layers.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { type WebLocation, collectSpecifiers, locate, readSource, relativeTarget } from './moduleGraph.js';\n\nconst CLIENT_LAYER_ORDER: Readonly<Record<string, number | undefined>> = {\n types: 0,\n lib: 1,\n stores: 2,\n components: 3,\n features: 4,\n routes: 5,\n app: 6,\n};\nconst SERVER_LAYER_ORDER: Readonly<Record<string, number | undefined>> = {\n types: 0,\n services: 1,\n adapters: 2,\n bin: 3,\n exports: 4,\n};\n\nconst CLIENT_ORDER_LABEL = 'types, lib, stores, components, features, routes, app';\nconst SERVER_ORDER_LABEL = 'types, services, adapters, bin, exports';\n\n/** src/types is the shared contract, so it is the one server root the bundle may read. */\nconst SERVER_ROOTS_CLOSED_TO_CLIENT = new Set(['services', 'adapters', 'bin', 'exports']);\nconst SERVER_ROOTS_CLOSED_TO_WEB = new Set(['services', 'adapters', 'bin']);\n\nfunction label(location: WebLocation): string {\n if (location.side === 'client') return location.layer ? `src/web/${location.layer}` : 'src/web';\n return location.layer ? `src/${location.layer}` : 'src';\n}\n\nfunction violation(source: WebLocation, target: WebLocation, specifier: string): string | undefined {\n const pair = `${label(source)} may not import ${label(target)} ('${specifier}')`;\n\n if (source.side === 'client' && target.side === 'server') {\n return SERVER_ROOTS_CLOSED_TO_CLIENT.has(target.layer ?? '')\n ? `${pair}. Browser code may not reach server code. Call the server over HTTP or RPC and keep the shared contract in src/types.`\n : undefined;\n }\n if (source.side === 'server' && target.side === 'client') {\n return SERVER_ROOTS_CLOSED_TO_WEB.has(source.layer ?? '')\n ? `${pair}. Server code may not reach the browser bundle. Keep the shared contract in src/types and let the client import it from there.`\n : undefined;\n }\n\n const order = source.side === 'client' ? CLIENT_LAYER_ORDER : SERVER_LAYER_ORDER;\n const from = source.layer === undefined ? undefined : order[source.layer];\n const to = target.layer === undefined ? undefined : order[target.layer];\n if (from === undefined || to === undefined || to <= from) return undefined;\n\n return source.side === 'client'\n ? `${pair}. The client layer order is ${CLIENT_ORDER_LABEL} and no layer may import a higher one. Move the shared code down to a lower layer, or hand it in as a prop, a store value, or a route parameter.`\n : `${pair}. The server layer order is ${SERVER_ORDER_LABEL} and no layer may import a higher one. Declare the contract in src/types and let the higher layer inject the implementation.`;\n}\n\nexport const doomWebLayerBoundary: RuleDefinition = {\n preflight: true,\n rule: 'Web layers point inward and the browser bundle never meets the server outside src/types',\n rationale:\n 'A web package ships two programs from one source tree. Left unseparated they merge: a component reaches a service for one field, the bundler follows it, and server credentials, filesystem access, and node builtins land in the browser. Direction inside each tree matters for the same reason: when a component imports a feature, the reusable half now depends on the specific one and neither can be moved or deleted alone. Keep the shared vocabulary in src/types, where both sides can read it without dragging an implementation across.',\n check(filePath, configRoot) {\n const source = locate(filePath, configRoot);\n if (!source) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const messages = new Set<string>();\n for (const specifier of collectSpecifiers(sourceFile)) {\n const target = relativeTarget(filePath, specifier, configRoot);\n if (!target) continue;\n const message = violation(source, target, specifier);\n if (message) messages.add(message);\n }\n\n return messages.size > 0 ? [...messages].join(' ') : null;\n },\n};\n"],"mappings":"qCAGM,EAAmE,CACvE,MAAO,EACP,IAAK,EACL,OAAQ,EACR,WAAY,EACZ,SAAU,EACV,OAAQ,EACR,IAAK,CACP,EACM,EAAmE,CACvE,MAAO,EACP,SAAU,EACV,SAAU,EACV,IAAK,EACL,QAAS,CACX,EAMM,EAAgC,IAAI,IAAI,CAAC,WAAY,WAAY,MAAO,SAAS,CAAC,EAClF,EAA6B,IAAI,IAAI,CAAC,WAAY,WAAY,KAAK,CAAC,EAE1E,SAAS,EAAM,EAA+B,CAE5C,OADI,EAAS,OAAS,SAAiB,EAAS,MAAQ,WAAW,EAAS,QAAU,UAC/E,EAAS,MAAQ,OAAO,EAAS,QAAU,KACpD,CAEA,SAAS,EAAU,EAAqB,EAAqB,EAAuC,CAClG,IAAM,EAAO,GAAG,EAAM,CAAM,EAAE,kBAAkB,EAAM,CAAM,EAAE,KAAK,EAAU,IAE7E,GAAI,EAAO,OAAS,UAAY,EAAO,OAAS,SAC9C,OAAO,EAA8B,IAAI,EAAO,OAAS,EAAE,EACvD,GAAG,EAAK,uHACR,IAAA,GAEN,GAAI,EAAO,OAAS,UAAY,EAAO,OAAS,SAC9C,OAAO,EAA2B,IAAI,EAAO,OAAS,EAAE,EACpD,GAAG,EAAK,gIACR,IAAA,GAGN,IAAM,EAAQ,EAAO,OAAS,SAAW,EAAqB,EACxD,EAAO,EAAO,QAAU,IAAA,GAAY,IAAA,GAAY,EAAM,EAAO,OAC7D,EAAK,EAAO,QAAU,IAAA,GAAY,IAAA,GAAY,EAAM,EAAO,OAC7D,SAAS,IAAA,IAAa,IAAO,IAAA,IAAa,GAAM,GAEpD,OAAO,EAAO,OAAS,SACnB,GAAG,EAAK,mOACR,GAAG,EAAK,gMACd,CAEA,MAAa,EAAuC,CAClD,UAAW,GACX,KAAM,0FACN,UACE,uhBACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAASA,EAAAA,OAAO,EAAU,CAAU,EAC1C,GAAI,CAAC,EAAQ,OAAO,KACpB,IAAM,EAAaC,EAAAA,WAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAaC,EAAAA,kBAAkB,CAAU,EAAG,CACrD,IAAM,EAASC,EAAAA,eAAe,EAAU,EAAW,CAAU,EAC7D,GAAI,CAAC,EAAQ,SACb,IAAM,EAAU,EAAU,EAAQ,EAAQ,CAAS,EAC/C,GAAS,EAAS,IAAI,CAAO,CACnC,CAEA,OAAO,EAAS,KAAO,EAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAG,EAAI,IACvD,CACF"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/layers.d.ts
3
+ declare const doomWebLayerBoundary: RuleDefinition;
4
+ //#endregion
5
+ export { doomWebLayerBoundary };
6
+ //# sourceMappingURL=layers.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.cts","names":[],"sources":["../../src/rules/layers.ts"],"mappings":";;cAwDa,sBAAsB"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/layers.d.ts
3
+ declare const doomWebLayerBoundary: RuleDefinition;
4
+ //#endregion
5
+ export { doomWebLayerBoundary };
6
+ //# sourceMappingURL=layers.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.mts","names":[],"sources":["../../src/rules/layers.ts"],"mappings":";;cAwDa,sBAAsB"}
@@ -0,0 +1,2 @@
1
+ import{collectSpecifiers as e,locate as t,readSource as n,relativeTarget as r}from"./moduleGraph.mjs";const i={types:0,lib:1,stores:2,components:3,features:4,routes:5,app:6},a={types:0,services:1,adapters:2,bin:3,exports:4},o=new Set([`services`,`adapters`,`bin`,`exports`]),s=new Set([`services`,`adapters`,`bin`]);function c(e){return e.side===`client`?e.layer?`src/web/${e.layer}`:`src/web`:e.layer?`src/${e.layer}`:`src`}function l(e,t,n){let r=`${c(e)} may not import ${c(t)} ('${n}')`;if(e.side===`client`&&t.side===`server`)return o.has(t.layer??``)?`${r}. Browser code may not reach server code. Call the server over HTTP or RPC and keep the shared contract in src/types.`:void 0;if(e.side===`server`&&t.side===`client`)return s.has(e.layer??``)?`${r}. Server code may not reach the browser bundle. Keep the shared contract in src/types and let the client import it from there.`:void 0;let l=e.side===`client`?i:a,u=e.layer===void 0?void 0:l[e.layer],d=t.layer===void 0?void 0:l[t.layer];if(!(u===void 0||d===void 0||d<=u))return e.side===`client`?`${r}. The client layer order is types, lib, stores, components, features, routes, app and no layer may import a higher one. Move the shared code down to a lower layer, or hand it in as a prop, a store value, or a route parameter.`:`${r}. The server layer order is types, services, adapters, bin, exports and no layer may import a higher one. Declare the contract in src/types and let the higher layer inject the implementation.`}const u={preflight:!0,rule:`Web layers point inward and the browser bundle never meets the server outside src/types`,rationale:`A web package ships two programs from one source tree. Left unseparated they merge: a component reaches a service for one field, the bundler follows it, and server credentials, filesystem access, and node builtins land in the browser. Direction inside each tree matters for the same reason: when a component imports a feature, the reusable half now depends on the specific one and neither can be moved or deleted alone. Keep the shared vocabulary in src/types, where both sides can read it without dragging an implementation across.`,check(i,a){let o=t(i,a);if(!o)return null;let s=n(i);if(!s)return null;let c=new Set;for(let t of e(s)){let e=r(i,t,a);if(!e)continue;let n=l(o,e,t);n&&c.add(n)}return c.size>0?[...c].join(` `):null}};export{u as doomWebLayerBoundary};
2
+ //# sourceMappingURL=layers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.mjs","names":[],"sources":["../../src/rules/layers.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { type WebLocation, collectSpecifiers, locate, readSource, relativeTarget } from './moduleGraph.js';\n\nconst CLIENT_LAYER_ORDER: Readonly<Record<string, number | undefined>> = {\n types: 0,\n lib: 1,\n stores: 2,\n components: 3,\n features: 4,\n routes: 5,\n app: 6,\n};\nconst SERVER_LAYER_ORDER: Readonly<Record<string, number | undefined>> = {\n types: 0,\n services: 1,\n adapters: 2,\n bin: 3,\n exports: 4,\n};\n\nconst CLIENT_ORDER_LABEL = 'types, lib, stores, components, features, routes, app';\nconst SERVER_ORDER_LABEL = 'types, services, adapters, bin, exports';\n\n/** src/types is the shared contract, so it is the one server root the bundle may read. */\nconst SERVER_ROOTS_CLOSED_TO_CLIENT = new Set(['services', 'adapters', 'bin', 'exports']);\nconst SERVER_ROOTS_CLOSED_TO_WEB = new Set(['services', 'adapters', 'bin']);\n\nfunction label(location: WebLocation): string {\n if (location.side === 'client') return location.layer ? `src/web/${location.layer}` : 'src/web';\n return location.layer ? `src/${location.layer}` : 'src';\n}\n\nfunction violation(source: WebLocation, target: WebLocation, specifier: string): string | undefined {\n const pair = `${label(source)} may not import ${label(target)} ('${specifier}')`;\n\n if (source.side === 'client' && target.side === 'server') {\n return SERVER_ROOTS_CLOSED_TO_CLIENT.has(target.layer ?? '')\n ? `${pair}. Browser code may not reach server code. Call the server over HTTP or RPC and keep the shared contract in src/types.`\n : undefined;\n }\n if (source.side === 'server' && target.side === 'client') {\n return SERVER_ROOTS_CLOSED_TO_WEB.has(source.layer ?? '')\n ? `${pair}. Server code may not reach the browser bundle. Keep the shared contract in src/types and let the client import it from there.`\n : undefined;\n }\n\n const order = source.side === 'client' ? CLIENT_LAYER_ORDER : SERVER_LAYER_ORDER;\n const from = source.layer === undefined ? undefined : order[source.layer];\n const to = target.layer === undefined ? undefined : order[target.layer];\n if (from === undefined || to === undefined || to <= from) return undefined;\n\n return source.side === 'client'\n ? `${pair}. The client layer order is ${CLIENT_ORDER_LABEL} and no layer may import a higher one. Move the shared code down to a lower layer, or hand it in as a prop, a store value, or a route parameter.`\n : `${pair}. The server layer order is ${SERVER_ORDER_LABEL} and no layer may import a higher one. Declare the contract in src/types and let the higher layer inject the implementation.`;\n}\n\nexport const doomWebLayerBoundary: RuleDefinition = {\n preflight: true,\n rule: 'Web layers point inward and the browser bundle never meets the server outside src/types',\n rationale:\n 'A web package ships two programs from one source tree. Left unseparated they merge: a component reaches a service for one field, the bundler follows it, and server credentials, filesystem access, and node builtins land in the browser. Direction inside each tree matters for the same reason: when a component imports a feature, the reusable half now depends on the specific one and neither can be moved or deleted alone. Keep the shared vocabulary in src/types, where both sides can read it without dragging an implementation across.',\n check(filePath, configRoot) {\n const source = locate(filePath, configRoot);\n if (!source) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const messages = new Set<string>();\n for (const specifier of collectSpecifiers(sourceFile)) {\n const target = relativeTarget(filePath, specifier, configRoot);\n if (!target) continue;\n const message = violation(source, target, specifier);\n if (message) messages.add(message);\n }\n\n return messages.size > 0 ? [...messages].join(' ') : null;\n },\n};\n"],"mappings":"sGAGA,MAAM,EAAmE,CACvE,MAAO,EACP,IAAK,EACL,OAAQ,EACR,WAAY,EACZ,SAAU,EACV,OAAQ,EACR,IAAK,CACP,EACM,EAAmE,CACvE,MAAO,EACP,SAAU,EACV,SAAU,EACV,IAAK,EACL,QAAS,CACX,EAMM,EAAgC,IAAI,IAAI,CAAC,WAAY,WAAY,MAAO,SAAS,CAAC,EAClF,EAA6B,IAAI,IAAI,CAAC,WAAY,WAAY,KAAK,CAAC,EAE1E,SAAS,EAAM,EAA+B,CAE5C,OADI,EAAS,OAAS,SAAiB,EAAS,MAAQ,WAAW,EAAS,QAAU,UAC/E,EAAS,MAAQ,OAAO,EAAS,QAAU,KACpD,CAEA,SAAS,EAAU,EAAqB,EAAqB,EAAuC,CAClG,IAAM,EAAO,GAAG,EAAM,CAAM,EAAE,kBAAkB,EAAM,CAAM,EAAE,KAAK,EAAU,IAE7E,GAAI,EAAO,OAAS,UAAY,EAAO,OAAS,SAC9C,OAAO,EAA8B,IAAI,EAAO,OAAS,EAAE,EACvD,GAAG,EAAK,uHACR,IAAA,GAEN,GAAI,EAAO,OAAS,UAAY,EAAO,OAAS,SAC9C,OAAO,EAA2B,IAAI,EAAO,OAAS,EAAE,EACpD,GAAG,EAAK,gIACR,IAAA,GAGN,IAAM,EAAQ,EAAO,OAAS,SAAW,EAAqB,EACxD,EAAO,EAAO,QAAU,IAAA,GAAY,IAAA,GAAY,EAAM,EAAO,OAC7D,EAAK,EAAO,QAAU,IAAA,GAAY,IAAA,GAAY,EAAM,EAAO,OAC7D,SAAS,IAAA,IAAa,IAAO,IAAA,IAAa,GAAM,GAEpD,OAAO,EAAO,OAAS,SACnB,GAAG,EAAK,mOACR,GAAG,EAAK,gMACd,CAEA,MAAa,EAAuC,CAClD,UAAW,GACX,KAAM,0FACN,UACE,uhBACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAS,EAAO,EAAU,CAAU,EAC1C,GAAI,CAAC,EAAQ,OAAO,KACpB,IAAM,EAAa,EAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAa,EAAkB,CAAU,EAAG,CACrD,IAAM,EAAS,EAAe,EAAU,EAAW,CAAU,EAC7D,GAAI,CAAC,EAAQ,SACb,IAAM,EAAU,EAAU,EAAQ,EAAQ,CAAS,EAC/C,GAAS,EAAS,IAAI,CAAO,CACnC,CAEA,OAAO,EAAS,KAAO,EAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAG,EAAI,IACvD,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("typescript");r=e.__toESM(r,1);const i=new Set([`.ts`,`.tsx`,`.mts`,`.cts`]);function a(e,t){let r=n.resolve(t),i=n.resolve(e);return i!==r&&!i.startsWith(`${r}${n.sep}`)?null:n.relative(r,i).split(n.sep).join(`/`)}function o(e,t){let n=a(e,t);if(!n)return null;let r=n.split(`/`);return r[0]!==`src`||r.length<2?null:r[1]===`web`?{side:`client`,layer:r[2],feature:r[2]===`features`?r[3]:void 0}:{side:`server`,layer:r[1],feature:void 0}}function s(e,t,r){return t.startsWith(`.`)?o(n.resolve(n.dirname(e),t),r):null}function c(e){return!t.existsSync(e)||!i.has(n.extname(e))?null:r.default.createSourceFile(e,t.readFileSync(e,`utf8`),r.default.ScriptTarget.Latest,!0)}function l(e){let t=[],n=e=>{(r.default.isImportDeclaration(e)||r.default.isExportDeclaration(e))&&e.moduleSpecifier&&r.default.isStringLiteralLike(e.moduleSpecifier)&&t.push(e.moduleSpecifier.text),r.default.isCallExpression(e)&&e.expression.kind===r.default.SyntaxKind.ImportKeyword&&e.arguments.length===1&&r.default.isStringLiteralLike(e.arguments[0])&&t.push(e.arguments[0].text),r.default.forEachChild(e,n)};return n(e),t}exports.collectSpecifiers=l,exports.locate=o,exports.projectPath=a,exports.readSource=c,exports.relativeTarget=s;
2
+ //# sourceMappingURL=moduleGraph.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moduleGraph.cjs","names":["path","fs","ts"],"sources":["../../src/rules/moduleGraph.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport ts from 'typescript';\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);\n\nexport type WebSide = 'client' | 'server';\n\nexport interface WebLocation {\n side: WebSide;\n layer: string | undefined;\n feature: string | undefined;\n}\n\nexport function projectPath(filePath: string, configRoot: string): string | null {\n const root = path.resolve(configRoot);\n const target = path.resolve(filePath);\n if (target !== root && !target.startsWith(`${root}${path.sep}`)) return null;\n return path.relative(root, target).split(path.sep).join('/');\n}\n\n/**\n * A web package has two source trees under one src: the browser bundle in\n * src/web and the server in the remaining roots. Which tree a path belongs to\n * decides both the layer vocabulary and whether the two may see each other.\n */\nexport function locate(filePath: string, configRoot: string): WebLocation | null {\n const relativePath = projectPath(filePath, configRoot);\n if (!relativePath) return null;\n const parts = relativePath.split('/');\n if (parts[0] !== 'src' || parts.length < 2) return null;\n if (parts[1] !== 'web') return { side: 'server', layer: parts[1], feature: undefined };\n return { side: 'client', layer: parts[2], feature: parts[2] === 'features' ? parts[3] : undefined };\n}\n\nexport function relativeTarget(filePath: string, specifier: string, configRoot: string): WebLocation | null {\n if (!specifier.startsWith('.')) return null;\n return locate(path.resolve(path.dirname(filePath), specifier), configRoot);\n}\n\nexport function readSource(filePath: string): ts.SourceFile | null {\n if (!fs.existsSync(filePath) || !SOURCE_EXTENSIONS.has(path.extname(filePath))) return null;\n return ts.createSourceFile(filePath, fs.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest, true);\n}\n\nexport function collectSpecifiers(sourceFile: ts.SourceFile): string[] {\n const specifiers: string[] = [];\n const visit = (node: ts.Node): void => {\n if (\n (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&\n node.moduleSpecifier &&\n ts.isStringLiteralLike(node.moduleSpecifier)\n ) {\n specifiers.push(node.moduleSpecifier.text);\n }\n if (\n ts.isCallExpression(node) &&\n node.expression.kind === ts.SyntaxKind.ImportKeyword &&\n node.arguments.length === 1 &&\n ts.isStringLiteralLike(node.arguments[0])\n ) {\n specifiers.push(node.arguments[0].text);\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return specifiers;\n}\n"],"mappings":"wLAIA,MAAM,EAAoB,IAAI,IAAI,CAAC,MAAO,OAAQ,OAAQ,MAAM,CAAC,EAUjE,SAAgB,EAAY,EAAkB,EAAmC,CAC/E,IAAM,EAAOA,EAAK,QAAQ,CAAU,EAC9B,EAASA,EAAK,QAAQ,CAAQ,EAEpC,OADI,IAAW,GAAQ,CAAC,EAAO,WAAW,GAAG,IAAOA,EAAK,KAAK,EAAU,KACjEA,EAAK,SAAS,EAAM,CAAM,CAAC,CAAC,MAAMA,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAC7D,CAOA,SAAgB,EAAO,EAAkB,EAAwC,CAC/E,IAAM,EAAe,EAAY,EAAU,CAAU,EACrD,GAAI,CAAC,EAAc,OAAO,KAC1B,IAAM,EAAQ,EAAa,MAAM,GAAG,EAGpC,OAFI,EAAM,KAAO,OAAS,EAAM,OAAS,EAAU,KAC/C,EAAM,KAAO,MACV,CAAE,KAAM,SAAU,MAAO,EAAM,GAAI,QAAS,EAAM,KAAO,WAAa,EAAM,GAAK,IAAA,EAAU,EADnE,CAAE,KAAM,SAAU,MAAO,EAAM,GAAI,QAAS,IAAA,EAAU,CAEvF,CAEA,SAAgB,EAAe,EAAkB,EAAmB,EAAwC,CAE1G,OADK,EAAU,WAAW,GAAG,EACtB,EAAOA,EAAK,QAAQA,EAAK,QAAQ,CAAQ,EAAG,CAAS,EAAG,CAAU,EADlC,IAEzC,CAEA,SAAgB,EAAW,EAAwC,CAEjE,MADI,CAACC,EAAG,WAAW,CAAQ,GAAK,CAAC,EAAkB,IAAID,EAAK,QAAQ,CAAQ,CAAC,EAAU,KAChFE,EAAAA,QAAG,iBAAiB,EAAUD,EAAG,aAAa,EAAU,MAAM,EAAGC,EAAAA,QAAG,aAAa,OAAQ,EAAI,CACtG,CAEA,SAAgB,EAAkB,EAAqC,CACrE,IAAM,EAAuB,CAAC,EACxB,EAAS,GAAwB,EAElCA,EAAAA,QAAG,oBAAoB,CAAI,GAAKA,EAAAA,QAAG,oBAAoB,CAAI,IAC5D,EAAK,iBACLA,EAAAA,QAAG,oBAAoB,EAAK,eAAe,GAE3C,EAAW,KAAK,EAAK,gBAAgB,IAAI,EAGzCA,EAAAA,QAAG,iBAAiB,CAAI,GACxB,EAAK,WAAW,OAASA,EAAAA,QAAG,WAAW,eACvC,EAAK,UAAU,SAAW,GAC1BA,EAAAA,QAAG,oBAAoB,EAAK,UAAU,EAAE,GAExC,EAAW,KAAK,EAAK,UAAU,EAAE,CAAC,IAAI,EAExC,EAAA,QAAG,aAAa,EAAM,CAAK,CAC7B,EAEA,OADA,EAAM,CAAU,EACT,CACT"}
@@ -0,0 +1,2 @@
1
+ import*as e from"node:fs";import*as t from"node:path";import n from"typescript";const r=new Set([`.ts`,`.tsx`,`.mts`,`.cts`]);function i(e,n){let r=t.resolve(n),i=t.resolve(e);return i!==r&&!i.startsWith(`${r}${t.sep}`)?null:t.relative(r,i).split(t.sep).join(`/`)}function a(e,t){let n=i(e,t);if(!n)return null;let r=n.split(`/`);return r[0]!==`src`||r.length<2?null:r[1]===`web`?{side:`client`,layer:r[2],feature:r[2]===`features`?r[3]:void 0}:{side:`server`,layer:r[1],feature:void 0}}function o(e,n,r){return n.startsWith(`.`)?a(t.resolve(t.dirname(e),n),r):null}function s(i){return!e.existsSync(i)||!r.has(t.extname(i))?null:n.createSourceFile(i,e.readFileSync(i,`utf8`),n.ScriptTarget.Latest,!0)}function c(e){let t=[],r=e=>{(n.isImportDeclaration(e)||n.isExportDeclaration(e))&&e.moduleSpecifier&&n.isStringLiteralLike(e.moduleSpecifier)&&t.push(e.moduleSpecifier.text),n.isCallExpression(e)&&e.expression.kind===n.SyntaxKind.ImportKeyword&&e.arguments.length===1&&n.isStringLiteralLike(e.arguments[0])&&t.push(e.arguments[0].text),n.forEachChild(e,r)};return r(e),t}export{c as collectSpecifiers,a as locate,i as projectPath,s as readSource,o as relativeTarget};
2
+ //# sourceMappingURL=moduleGraph.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moduleGraph.mjs","names":[],"sources":["../../src/rules/moduleGraph.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport ts from 'typescript';\n\nconst SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);\n\nexport type WebSide = 'client' | 'server';\n\nexport interface WebLocation {\n side: WebSide;\n layer: string | undefined;\n feature: string | undefined;\n}\n\nexport function projectPath(filePath: string, configRoot: string): string | null {\n const root = path.resolve(configRoot);\n const target = path.resolve(filePath);\n if (target !== root && !target.startsWith(`${root}${path.sep}`)) return null;\n return path.relative(root, target).split(path.sep).join('/');\n}\n\n/**\n * A web package has two source trees under one src: the browser bundle in\n * src/web and the server in the remaining roots. Which tree a path belongs to\n * decides both the layer vocabulary and whether the two may see each other.\n */\nexport function locate(filePath: string, configRoot: string): WebLocation | null {\n const relativePath = projectPath(filePath, configRoot);\n if (!relativePath) return null;\n const parts = relativePath.split('/');\n if (parts[0] !== 'src' || parts.length < 2) return null;\n if (parts[1] !== 'web') return { side: 'server', layer: parts[1], feature: undefined };\n return { side: 'client', layer: parts[2], feature: parts[2] === 'features' ? parts[3] : undefined };\n}\n\nexport function relativeTarget(filePath: string, specifier: string, configRoot: string): WebLocation | null {\n if (!specifier.startsWith('.')) return null;\n return locate(path.resolve(path.dirname(filePath), specifier), configRoot);\n}\n\nexport function readSource(filePath: string): ts.SourceFile | null {\n if (!fs.existsSync(filePath) || !SOURCE_EXTENSIONS.has(path.extname(filePath))) return null;\n return ts.createSourceFile(filePath, fs.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest, true);\n}\n\nexport function collectSpecifiers(sourceFile: ts.SourceFile): string[] {\n const specifiers: string[] = [];\n const visit = (node: ts.Node): void => {\n if (\n (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&\n node.moduleSpecifier &&\n ts.isStringLiteralLike(node.moduleSpecifier)\n ) {\n specifiers.push(node.moduleSpecifier.text);\n }\n if (\n ts.isCallExpression(node) &&\n node.expression.kind === ts.SyntaxKind.ImportKeyword &&\n node.arguments.length === 1 &&\n ts.isStringLiteralLike(node.arguments[0])\n ) {\n specifiers.push(node.arguments[0].text);\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return specifiers;\n}\n"],"mappings":"gFAIA,MAAM,EAAoB,IAAI,IAAI,CAAC,MAAO,OAAQ,OAAQ,MAAM,CAAC,EAUjE,SAAgB,EAAY,EAAkB,EAAmC,CAC/E,IAAM,EAAO,EAAK,QAAQ,CAAU,EAC9B,EAAS,EAAK,QAAQ,CAAQ,EAEpC,OADI,IAAW,GAAQ,CAAC,EAAO,WAAW,GAAG,IAAO,EAAK,KAAK,EAAU,KACjE,EAAK,SAAS,EAAM,CAAM,CAAC,CAAC,MAAM,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAC7D,CAOA,SAAgB,EAAO,EAAkB,EAAwC,CAC/E,IAAM,EAAe,EAAY,EAAU,CAAU,EACrD,GAAI,CAAC,EAAc,OAAO,KAC1B,IAAM,EAAQ,EAAa,MAAM,GAAG,EAGpC,OAFI,EAAM,KAAO,OAAS,EAAM,OAAS,EAAU,KAC/C,EAAM,KAAO,MACV,CAAE,KAAM,SAAU,MAAO,EAAM,GAAI,QAAS,EAAM,KAAO,WAAa,EAAM,GAAK,IAAA,EAAU,EADnE,CAAE,KAAM,SAAU,MAAO,EAAM,GAAI,QAAS,IAAA,EAAU,CAEvF,CAEA,SAAgB,EAAe,EAAkB,EAAmB,EAAwC,CAE1G,OADK,EAAU,WAAW,GAAG,EACtB,EAAO,EAAK,QAAQ,EAAK,QAAQ,CAAQ,EAAG,CAAS,EAAG,CAAU,EADlC,IAEzC,CAEA,SAAgB,EAAW,EAAwC,CAEjE,MADI,CAAC,EAAG,WAAW,CAAQ,GAAK,CAAC,EAAkB,IAAI,EAAK,QAAQ,CAAQ,CAAC,EAAU,KAChF,EAAG,iBAAiB,EAAU,EAAG,aAAa,EAAU,MAAM,EAAG,EAAG,aAAa,OAAQ,EAAI,CACtG,CAEA,SAAgB,EAAkB,EAAqC,CACrE,IAAM,EAAuB,CAAC,EACxB,EAAS,GAAwB,EAElC,EAAG,oBAAoB,CAAI,GAAK,EAAG,oBAAoB,CAAI,IAC5D,EAAK,iBACL,EAAG,oBAAoB,EAAK,eAAe,GAE3C,EAAW,KAAK,EAAK,gBAAgB,IAAI,EAGzC,EAAG,iBAAiB,CAAI,GACxB,EAAK,WAAW,OAAS,EAAG,WAAW,eACvC,EAAK,UAAU,SAAW,GAC1B,EAAG,oBAAoB,EAAK,UAAU,EAAE,GAExC,EAAW,KAAK,EAAK,UAAU,EAAE,CAAC,IAAI,EAExC,EAAG,aAAa,EAAM,CAAK,CAC7B,EAEA,OADA,EAAM,CAAU,EACT,CACT"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./moduleGraph.cjs"),n=require("./componentLibrary.cjs");let r=require("typescript");r=e.__toESM(r,1);const i={button:`Button`,input:`Input`,textarea:`Textarea`,select:`Select`},a=/prefer-shared-primitive:\s*ignore/;function o(e){return e.startsWith(`src/web/`)||e.startsWith(`web/`)}function s(e){return e.startsWith(`tests/`)||e.includes(`.test.`)||e.includes(`.spec.`)}function c(e){let t=e.tagName;return r.default.isIdentifier(t)?t.text:void 0}function l(e,t){return e.attributes.properties.some(e=>r.default.isJsxAttribute(e)&&r.default.isIdentifier(e.name)&&e.name.text===t)}function u(e){let t=r.default.isJsxOpeningElement(e)?e.parent.parent:e.parent;for(let e=t;e!==void 0;e=e.parent){if(r.default.isJsxElement(e))return l(e.openingElement,`asChild`);if(!r.default.isJsxFragment(e)&&!r.default.isJsxExpression(e))return!1}return!1}const d={preflight:!0,rule:`Browser code reaches for the shared primitive rather than restyling a raw element`,rationale:`The component library exists so a button looks and behaves the same everywhere: one focus ring, one disabled state, one set of tokens. A raw <button className="…"> renders something that only resembles a Button, and the resemblance decays. It is also invisible in review, which is how the same chip came to be spelled ten different ways across the packages. Radix asChild is the escape hatch when a primitive must lend its styling to another element, and the rule stands down there.`,check(e,l){let d=t.projectPath(e,l);if(d===null||!o(d)||s(d)||n.isComponentLibrary(l))return null;let f=t.readSource(e);if(!f||a.test(f.getFullText()))return null;let p=new Map,m=e=>{if(r.default.isJsxOpeningElement(e)||r.default.isJsxSelfClosingElement(e)){let t=c(e),n=t===void 0?void 0:i[t];t!==void 0&&n!==void 0&&!u(e)&&p.set(t,n),t!==void 0&&!i[t]&&e.attributes.properties.some(e=>r.default.isJsxAttribute(e)&&r.default.isIdentifier(e.name)&&e.name.text===`role`&&e.initializer!==void 0&&r.default.isStringLiteral(e.initializer)&&e.initializer.text===`option`)&&p.set(`${t} role="option"`,`OptionRow`)}r.default.forEachChild(e,m)};return m(f),p.size===0?null:`${d} renders ${[...p].map(([e,t])=>`<${e}> (use ${t})`).join(`, `)} directly. Import the primitive from @agimon-ai/doompi-web-components instead, or wrap the element in the primitive with asChild when it must stay this element. A row that genuinely has no primitive can opt out with a "prefer-shared-primitive: ignore" comment naming why.`}};exports.preferSharedPrimitive=d;
2
+ //# sourceMappingURL=primitives.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.cjs","names":["ts","projectPath","isComponentLibrary","readSource"],"sources":["../../src/rules/primitives.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport ts from 'typescript';\nimport { isComponentLibrary } from './componentLibrary.js';\nimport { projectPath, readSource } from './moduleGraph.js';\n\n/** The interactive elements the shared library already owns, and what to reach for instead. */\nconst OWNED_ELEMENTS: Readonly<Record<string, string>> = {\n button: 'Button',\n input: 'Input',\n textarea: 'Textarea',\n select: 'Select',\n};\n\n/** A row of a listbox is an option, and OptionRow is the primitive for it. */\nconst OPTION_HINT = 'OptionRow';\n\nconst IGNORE_MARKER = /prefer-shared-primitive:\\s*ignore/;\n\n/** Browser source: the cockpit's client tree or a plugin's web/ tree. */\nfunction isBrowserSource(relativePath: string): boolean {\n return relativePath.startsWith('src/web/') || relativePath.startsWith('web/');\n}\n\nfunction isTest(relativePath: string): boolean {\n return relativePath.startsWith('tests/') || relativePath.includes('.test.') || relativePath.includes('.spec.');\n}\n\nfunction tagNameOf(node: ts.JsxOpeningLikeElement): string | undefined {\n const name = node.tagName;\n return ts.isIdentifier(name) ? name.text : undefined;\n}\n\nfunction hasAttribute(node: ts.JsxOpeningLikeElement, attribute: string): boolean {\n return node.attributes.properties.some(\n (property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && property.name.text === attribute,\n );\n}\n\n/**\n * Radix lends a primitive's behaviour to whatever element it wraps, so the\n * element under an `asChild` parent is that primitive's rendering, not a\n * bypass of it.\n */\nfunction underAsChild(node: ts.JsxOpeningLikeElement): boolean {\n // An opening tag's parent is its own JsxElement; step past it so the search\n // starts at the element that encloses this one.\n const start = ts.isJsxOpeningElement(node) ? node.parent.parent : node.parent;\n for (let parent: ts.Node | undefined = start; parent !== undefined; parent = parent.parent) {\n if (ts.isJsxElement(parent)) return hasAttribute(parent.openingElement, 'asChild');\n // Only the immediately enclosing element lends its slot; a fragment or an\n // expression container between the two is transparent, anything else is not.\n if (!ts.isJsxFragment(parent) && !ts.isJsxExpression(parent)) return false;\n }\n return false;\n}\n\nexport const preferSharedPrimitive: RuleDefinition = {\n preflight: true,\n rule: 'Browser code reaches for the shared primitive rather than restyling a raw element',\n rationale:\n 'The component library exists so a button looks and behaves the same everywhere: one focus ring, one disabled state, one set of tokens. A raw <button className=\"…\"> renders something that only resembles a Button, and the resemblance decays. It is also invisible in review, which is how the same chip came to be spelled ten different ways across the packages. Radix asChild is the escape hatch when a primitive must lend its styling to another element, and the rule stands down there.',\n check(filePath, configRoot) {\n const relativePath = projectPath(filePath, configRoot);\n if (relativePath === null || !isBrowserSource(relativePath) || isTest(relativePath)) return null;\n // The library is where the primitives are built, so it owns the raw elements.\n if (isComponentLibrary(configRoot)) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n if (IGNORE_MARKER.test(sourceFile.getFullText())) return null;\n\n const offenders = new Map<string, string>();\n const visit = (node: ts.Node): void => {\n if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {\n const tag = tagNameOf(node);\n const replacement = tag === undefined ? undefined : OWNED_ELEMENTS[tag];\n if (tag !== undefined && replacement !== undefined && !underAsChild(node)) {\n offenders.set(tag, replacement);\n }\n // A hand-rolled listbox row: it claims the role without the shape.\n if (\n tag !== undefined &&\n !OWNED_ELEMENTS[tag] &&\n node.attributes.properties.some(\n (property) =>\n ts.isJsxAttribute(property) &&\n ts.isIdentifier(property.name) &&\n property.name.text === 'role' &&\n property.initializer !== undefined &&\n ts.isStringLiteral(property.initializer) &&\n property.initializer.text === 'option',\n )\n ) {\n offenders.set(`${tag} role=\"option\"`, OPTION_HINT);\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n if (offenders.size === 0) return null;\n\n const listed = [...offenders].map(([tag, replacement]) => `<${tag}> (use ${replacement})`).join(', ');\n return `${relativePath} renders ${listed} directly. Import the primitive from @agimon-ai/doompi-web-components instead, or wrap the element in the primitive with asChild when it must stay this element. A row that genuinely has no primitive can opt out with a \"prefer-shared-primitive: ignore\" comment naming why.`;\n },\n};\n"],"mappings":"qKAMA,MAAM,EAAmD,CACvD,OAAQ,SACR,MAAO,QACP,SAAU,WACV,OAAQ,QACV,EAKM,EAAgB,oCAGtB,SAAS,EAAgB,EAA+B,CACtD,OAAO,EAAa,WAAW,UAAU,GAAK,EAAa,WAAW,MAAM,CAC9E,CAEA,SAAS,EAAO,EAA+B,CAC7C,OAAO,EAAa,WAAW,QAAQ,GAAK,EAAa,SAAS,QAAQ,GAAK,EAAa,SAAS,QAAQ,CAC/G,CAEA,SAAS,EAAU,EAAoD,CACrE,IAAM,EAAO,EAAK,QAClB,OAAOA,EAAAA,QAAG,aAAa,CAAI,EAAI,EAAK,KAAO,IAAA,EAC7C,CAEA,SAAS,EAAa,EAAgC,EAA4B,CAChF,OAAO,EAAK,WAAW,WAAW,KAC/B,GAAaA,EAAAA,QAAG,eAAe,CAAQ,GAAKA,EAAAA,QAAG,aAAa,EAAS,IAAI,GAAK,EAAS,KAAK,OAAS,CACxG,CACF,CAOA,SAAS,EAAa,EAAyC,CAG7D,IAAM,EAAQA,EAAAA,QAAG,oBAAoB,CAAI,EAAI,EAAK,OAAO,OAAS,EAAK,OACvE,IAAK,IAAI,EAA8B,EAAO,IAAW,IAAA,GAAW,EAAS,EAAO,OAAQ,CAC1F,GAAIA,EAAAA,QAAG,aAAa,CAAM,EAAG,OAAO,EAAa,EAAO,eAAgB,SAAS,EAGjF,GAAI,CAACA,EAAAA,QAAG,cAAc,CAAM,GAAK,CAACA,EAAAA,QAAG,gBAAgB,CAAM,EAAG,MAAO,EACvE,CACA,MAAO,EACT,CAEA,MAAa,EAAwC,CACnD,UAAW,GACX,KAAM,oFACN,UACE,qeACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAeC,EAAAA,YAAY,EAAU,CAAU,EAGrD,GAFI,IAAiB,MAAQ,CAAC,EAAgB,CAAY,GAAK,EAAO,CAAY,GAE9EC,EAAAA,mBAAmB,CAAU,EAAG,OAAO,KAC3C,IAAM,EAAaC,EAAAA,WAAW,CAAQ,EAEtC,GADI,CAAC,GACD,EAAc,KAAK,EAAW,YAAY,CAAC,EAAG,OAAO,KAEzD,IAAM,EAAY,IAAI,IAChB,EAAS,GAAwB,CACrC,GAAIH,EAAAA,QAAG,oBAAoB,CAAI,GAAKA,EAAAA,QAAG,wBAAwB,CAAI,EAAG,CACpE,IAAM,EAAM,EAAU,CAAI,EACpB,EAAc,IAAQ,IAAA,GAAY,IAAA,GAAY,EAAe,GAC/D,IAAQ,IAAA,IAAa,IAAgB,IAAA,IAAa,CAAC,EAAa,CAAI,GACtE,EAAU,IAAI,EAAK,CAAW,EAI9B,IAAQ,IAAA,IACR,CAAC,EAAe,IAChB,EAAK,WAAW,WAAW,KACxB,GACCA,EAAAA,QAAG,eAAe,CAAQ,GAC1BA,EAAAA,QAAG,aAAa,EAAS,IAAI,GAC7B,EAAS,KAAK,OAAS,QACvB,EAAS,cAAgB,IAAA,IACzBA,EAAAA,QAAG,gBAAgB,EAAS,WAAW,GACvC,EAAS,YAAY,OAAS,QAClC,GAEA,EAAU,IAAI,GAAG,EAAI,gBAAiB,WAAW,CAErD,CACA,EAAA,QAAG,aAAa,EAAM,CAAK,CAC7B,EAKA,OAJA,EAAM,CAAU,EACZ,EAAU,OAAS,EAAU,KAG1B,GAAG,EAAa,WADR,CAAC,GAAG,CAAS,CAAC,CAAC,KAAK,CAAC,EAAK,KAAiB,IAAI,EAAI,SAAS,EAAY,EAAE,CAAC,CAAC,KAAK,IACzD,EAAE,gRAC3C,CACF"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/primitives.d.ts
3
+ declare const preferSharedPrimitive: RuleDefinition;
4
+ //#endregion
5
+ export { preferSharedPrimitive };
6
+ //# sourceMappingURL=primitives.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.d.cts","names":[],"sources":["../../src/rules/primitives.ts"],"mappings":";;cAwDa,uBAAuB"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/primitives.d.ts
3
+ declare const preferSharedPrimitive: RuleDefinition;
4
+ //#endregion
5
+ export { preferSharedPrimitive };
6
+ //# sourceMappingURL=primitives.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.d.mts","names":[],"sources":["../../src/rules/primitives.ts"],"mappings":";;cAwDa,uBAAuB"}
@@ -0,0 +1,2 @@
1
+ import{projectPath as e,readSource as t}from"./moduleGraph.mjs";import{isComponentLibrary as n}from"./componentLibrary.mjs";import r from"typescript";const i={button:`Button`,input:`Input`,textarea:`Textarea`,select:`Select`},a=/prefer-shared-primitive:\s*ignore/;function o(e){return e.startsWith(`src/web/`)||e.startsWith(`web/`)}function s(e){return e.startsWith(`tests/`)||e.includes(`.test.`)||e.includes(`.spec.`)}function c(e){let t=e.tagName;return r.isIdentifier(t)?t.text:void 0}function l(e,t){return e.attributes.properties.some(e=>r.isJsxAttribute(e)&&r.isIdentifier(e.name)&&e.name.text===t)}function u(e){let t=r.isJsxOpeningElement(e)?e.parent.parent:e.parent;for(let e=t;e!==void 0;e=e.parent){if(r.isJsxElement(e))return l(e.openingElement,`asChild`);if(!r.isJsxFragment(e)&&!r.isJsxExpression(e))return!1}return!1}const d={preflight:!0,rule:`Browser code reaches for the shared primitive rather than restyling a raw element`,rationale:`The component library exists so a button looks and behaves the same everywhere: one focus ring, one disabled state, one set of tokens. A raw <button className="…"> renders something that only resembles a Button, and the resemblance decays. It is also invisible in review, which is how the same chip came to be spelled ten different ways across the packages. Radix asChild is the escape hatch when a primitive must lend its styling to another element, and the rule stands down there.`,check(l,d){let f=e(l,d);if(f===null||!o(f)||s(f)||n(d))return null;let p=t(l);if(!p||a.test(p.getFullText()))return null;let m=new Map,h=e=>{if(r.isJsxOpeningElement(e)||r.isJsxSelfClosingElement(e)){let t=c(e),n=t===void 0?void 0:i[t];t!==void 0&&n!==void 0&&!u(e)&&m.set(t,n),t!==void 0&&!i[t]&&e.attributes.properties.some(e=>r.isJsxAttribute(e)&&r.isIdentifier(e.name)&&e.name.text===`role`&&e.initializer!==void 0&&r.isStringLiteral(e.initializer)&&e.initializer.text===`option`)&&m.set(`${t} role="option"`,`OptionRow`)}r.forEachChild(e,h)};return h(p),m.size===0?null:`${f} renders ${[...m].map(([e,t])=>`<${e}> (use ${t})`).join(`, `)} directly. Import the primitive from @agimon-ai/doompi-web-components instead, or wrap the element in the primitive with asChild when it must stay this element. A row that genuinely has no primitive can opt out with a "prefer-shared-primitive: ignore" comment naming why.`}};export{d as preferSharedPrimitive};
2
+ //# sourceMappingURL=primitives.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.mjs","names":[],"sources":["../../src/rules/primitives.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport ts from 'typescript';\nimport { isComponentLibrary } from './componentLibrary.js';\nimport { projectPath, readSource } from './moduleGraph.js';\n\n/** The interactive elements the shared library already owns, and what to reach for instead. */\nconst OWNED_ELEMENTS: Readonly<Record<string, string>> = {\n button: 'Button',\n input: 'Input',\n textarea: 'Textarea',\n select: 'Select',\n};\n\n/** A row of a listbox is an option, and OptionRow is the primitive for it. */\nconst OPTION_HINT = 'OptionRow';\n\nconst IGNORE_MARKER = /prefer-shared-primitive:\\s*ignore/;\n\n/** Browser source: the cockpit's client tree or a plugin's web/ tree. */\nfunction isBrowserSource(relativePath: string): boolean {\n return relativePath.startsWith('src/web/') || relativePath.startsWith('web/');\n}\n\nfunction isTest(relativePath: string): boolean {\n return relativePath.startsWith('tests/') || relativePath.includes('.test.') || relativePath.includes('.spec.');\n}\n\nfunction tagNameOf(node: ts.JsxOpeningLikeElement): string | undefined {\n const name = node.tagName;\n return ts.isIdentifier(name) ? name.text : undefined;\n}\n\nfunction hasAttribute(node: ts.JsxOpeningLikeElement, attribute: string): boolean {\n return node.attributes.properties.some(\n (property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && property.name.text === attribute,\n );\n}\n\n/**\n * Radix lends a primitive's behaviour to whatever element it wraps, so the\n * element under an `asChild` parent is that primitive's rendering, not a\n * bypass of it.\n */\nfunction underAsChild(node: ts.JsxOpeningLikeElement): boolean {\n // An opening tag's parent is its own JsxElement; step past it so the search\n // starts at the element that encloses this one.\n const start = ts.isJsxOpeningElement(node) ? node.parent.parent : node.parent;\n for (let parent: ts.Node | undefined = start; parent !== undefined; parent = parent.parent) {\n if (ts.isJsxElement(parent)) return hasAttribute(parent.openingElement, 'asChild');\n // Only the immediately enclosing element lends its slot; a fragment or an\n // expression container between the two is transparent, anything else is not.\n if (!ts.isJsxFragment(parent) && !ts.isJsxExpression(parent)) return false;\n }\n return false;\n}\n\nexport const preferSharedPrimitive: RuleDefinition = {\n preflight: true,\n rule: 'Browser code reaches for the shared primitive rather than restyling a raw element',\n rationale:\n 'The component library exists so a button looks and behaves the same everywhere: one focus ring, one disabled state, one set of tokens. A raw <button className=\"…\"> renders something that only resembles a Button, and the resemblance decays. It is also invisible in review, which is how the same chip came to be spelled ten different ways across the packages. Radix asChild is the escape hatch when a primitive must lend its styling to another element, and the rule stands down there.',\n check(filePath, configRoot) {\n const relativePath = projectPath(filePath, configRoot);\n if (relativePath === null || !isBrowserSource(relativePath) || isTest(relativePath)) return null;\n // The library is where the primitives are built, so it owns the raw elements.\n if (isComponentLibrary(configRoot)) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n if (IGNORE_MARKER.test(sourceFile.getFullText())) return null;\n\n const offenders = new Map<string, string>();\n const visit = (node: ts.Node): void => {\n if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {\n const tag = tagNameOf(node);\n const replacement = tag === undefined ? undefined : OWNED_ELEMENTS[tag];\n if (tag !== undefined && replacement !== undefined && !underAsChild(node)) {\n offenders.set(tag, replacement);\n }\n // A hand-rolled listbox row: it claims the role without the shape.\n if (\n tag !== undefined &&\n !OWNED_ELEMENTS[tag] &&\n node.attributes.properties.some(\n (property) =>\n ts.isJsxAttribute(property) &&\n ts.isIdentifier(property.name) &&\n property.name.text === 'role' &&\n property.initializer !== undefined &&\n ts.isStringLiteral(property.initializer) &&\n property.initializer.text === 'option',\n )\n ) {\n offenders.set(`${tag} role=\"option\"`, OPTION_HINT);\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n if (offenders.size === 0) return null;\n\n const listed = [...offenders].map(([tag, replacement]) => `<${tag}> (use ${replacement})`).join(', ');\n return `${relativePath} renders ${listed} directly. Import the primitive from @agimon-ai/doompi-web-components instead, or wrap the element in the primitive with asChild when it must stay this element. A row that genuinely has no primitive can opt out with a \"prefer-shared-primitive: ignore\" comment naming why.`;\n },\n};\n"],"mappings":"sJAMA,MAAM,EAAmD,CACvD,OAAQ,SACR,MAAO,QACP,SAAU,WACV,OAAQ,QACV,EAKM,EAAgB,oCAGtB,SAAS,EAAgB,EAA+B,CACtD,OAAO,EAAa,WAAW,UAAU,GAAK,EAAa,WAAW,MAAM,CAC9E,CAEA,SAAS,EAAO,EAA+B,CAC7C,OAAO,EAAa,WAAW,QAAQ,GAAK,EAAa,SAAS,QAAQ,GAAK,EAAa,SAAS,QAAQ,CAC/G,CAEA,SAAS,EAAU,EAAoD,CACrE,IAAM,EAAO,EAAK,QAClB,OAAO,EAAG,aAAa,CAAI,EAAI,EAAK,KAAO,IAAA,EAC7C,CAEA,SAAS,EAAa,EAAgC,EAA4B,CAChF,OAAO,EAAK,WAAW,WAAW,KAC/B,GAAa,EAAG,eAAe,CAAQ,GAAK,EAAG,aAAa,EAAS,IAAI,GAAK,EAAS,KAAK,OAAS,CACxG,CACF,CAOA,SAAS,EAAa,EAAyC,CAG7D,IAAM,EAAQ,EAAG,oBAAoB,CAAI,EAAI,EAAK,OAAO,OAAS,EAAK,OACvE,IAAK,IAAI,EAA8B,EAAO,IAAW,IAAA,GAAW,EAAS,EAAO,OAAQ,CAC1F,GAAI,EAAG,aAAa,CAAM,EAAG,OAAO,EAAa,EAAO,eAAgB,SAAS,EAGjF,GAAI,CAAC,EAAG,cAAc,CAAM,GAAK,CAAC,EAAG,gBAAgB,CAAM,EAAG,MAAO,EACvE,CACA,MAAO,EACT,CAEA,MAAa,EAAwC,CACnD,UAAW,GACX,KAAM,oFACN,UACE,qeACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAe,EAAY,EAAU,CAAU,EAGrD,GAFI,IAAiB,MAAQ,CAAC,EAAgB,CAAY,GAAK,EAAO,CAAY,GAE9E,EAAmB,CAAU,EAAG,OAAO,KAC3C,IAAM,EAAa,EAAW,CAAQ,EAEtC,GADI,CAAC,GACD,EAAc,KAAK,EAAW,YAAY,CAAC,EAAG,OAAO,KAEzD,IAAM,EAAY,IAAI,IAChB,EAAS,GAAwB,CACrC,GAAI,EAAG,oBAAoB,CAAI,GAAK,EAAG,wBAAwB,CAAI,EAAG,CACpE,IAAM,EAAM,EAAU,CAAI,EACpB,EAAc,IAAQ,IAAA,GAAY,IAAA,GAAY,EAAe,GAC/D,IAAQ,IAAA,IAAa,IAAgB,IAAA,IAAa,CAAC,EAAa,CAAI,GACtE,EAAU,IAAI,EAAK,CAAW,EAI9B,IAAQ,IAAA,IACR,CAAC,EAAe,IAChB,EAAK,WAAW,WAAW,KACxB,GACC,EAAG,eAAe,CAAQ,GAC1B,EAAG,aAAa,EAAS,IAAI,GAC7B,EAAS,KAAK,OAAS,QACvB,EAAS,cAAgB,IAAA,IACzB,EAAG,gBAAgB,EAAS,WAAW,GACvC,EAAS,YAAY,OAAS,QAClC,GAEA,EAAU,IAAI,GAAG,EAAI,gBAAiB,WAAW,CAErD,CACA,EAAG,aAAa,EAAM,CAAK,CAC7B,EAKA,OAJA,EAAM,CAAU,EACZ,EAAU,OAAS,EAAU,KAG1B,GAAG,EAAa,WADR,CAAC,GAAG,CAAS,CAAC,CAAC,KAAK,CAAC,EAAK,KAAiB,IAAI,EAAI,SAAS,EAAY,EAAE,CAAC,CAAC,KAAK,IACzD,EAAE,gRAC3C,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./componentLibrary.cjs"),t=require("./conventions.cjs"),n=require("./features.cjs"),r=require("./primitives.cjs"),i=require("./layers.cjs"),a=require("./theming.cjs"),o={"doom-components-layer-boundary":e.doomComponentsLayerBoundary,"doom-web-layer-boundary":i.doomWebLayerBoundary,"no-cross-feature-import":n.noCrossFeatureImport,"no-raw-theme-color":a.noRawThemeColor,"prefer-shared-primitive":r.preferSharedPrimitive,"web-file-naming":t.webFileNaming};exports.rules=o;
2
+ //# sourceMappingURL=registry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.cjs","names":["doomComponentsLayerBoundary","doomWebLayerBoundary","noCrossFeatureImport","noRawThemeColor","preferSharedPrimitive","webFileNaming"],"sources":["../../src/rules/registry.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { doomComponentsLayerBoundary } from './componentLibrary.js';\nimport { webFileNaming } from './conventions.js';\nimport { noCrossFeatureImport } from './features.js';\nimport { preferSharedPrimitive } from './primitives.js';\nimport { doomWebLayerBoundary } from './layers.js';\nimport { noRawThemeColor } from './theming.js';\n\nexport const rules: Record<string, RuleDefinition> = {\n 'doom-components-layer-boundary': doomComponentsLayerBoundary,\n 'doom-web-layer-boundary': doomWebLayerBoundary,\n 'no-cross-feature-import': noCrossFeatureImport,\n 'no-raw-theme-color': noRawThemeColor,\n 'prefer-shared-primitive': preferSharedPrimitive,\n 'web-file-naming': webFileNaming,\n};\n"],"mappings":"wLAQa,EAAwC,CACnD,iCAAkCA,EAAAA,4BAClC,0BAA2BC,EAAAA,qBAC3B,0BAA2BC,EAAAA,qBAC3B,qBAAsBC,EAAAA,gBACtB,0BAA2BC,EAAAA,sBAC3B,kBAAmBC,EAAAA,aACrB"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/registry.d.ts
3
+ declare const rules: Record<string, RuleDefinition>;
4
+ //#endregion
5
+ export { rules };
6
+ //# sourceMappingURL=registry.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.cts","names":[],"sources":["../../src/rules/registry.ts"],"mappings":";;cAQa,OAAO,eAAe"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/registry.d.ts
3
+ declare const rules: Record<string, RuleDefinition>;
4
+ //#endregion
5
+ export { rules };
6
+ //# sourceMappingURL=registry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.mts","names":[],"sources":["../../src/rules/registry.ts"],"mappings":";;cAQa,OAAO,eAAe"}
@@ -0,0 +1,2 @@
1
+ import{doomComponentsLayerBoundary as e}from"./componentLibrary.mjs";import{webFileNaming as t}from"./conventions.mjs";import{noCrossFeatureImport as n}from"./features.mjs";import{preferSharedPrimitive as r}from"./primitives.mjs";import{doomWebLayerBoundary as i}from"./layers.mjs";import{noRawThemeColor as a}from"./theming.mjs";const o={"doom-components-layer-boundary":e,"doom-web-layer-boundary":i,"no-cross-feature-import":n,"no-raw-theme-color":a,"prefer-shared-primitive":r,"web-file-naming":t};export{o as rules};
2
+ //# sourceMappingURL=registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.mjs","names":[],"sources":["../../src/rules/registry.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport { doomComponentsLayerBoundary } from './componentLibrary.js';\nimport { webFileNaming } from './conventions.js';\nimport { noCrossFeatureImport } from './features.js';\nimport { preferSharedPrimitive } from './primitives.js';\nimport { doomWebLayerBoundary } from './layers.js';\nimport { noRawThemeColor } from './theming.js';\n\nexport const rules: Record<string, RuleDefinition> = {\n 'doom-components-layer-boundary': doomComponentsLayerBoundary,\n 'doom-web-layer-boundary': doomWebLayerBoundary,\n 'no-cross-feature-import': noCrossFeatureImport,\n 'no-raw-theme-color': noRawThemeColor,\n 'prefer-shared-primitive': preferSharedPrimitive,\n 'web-file-naming': webFileNaming,\n};\n"],"mappings":"0UAQA,MAAa,EAAwC,CACnD,iCAAkC,EAClC,0BAA2B,EAC3B,0BAA2B,EAC3B,qBAAsB,EACtB,0BAA2B,EAC3B,kBAAmB,CACrB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./moduleGraph.cjs");let n=require("typescript");n=e.__toESM(n,1);const r=/-\[#[0-9a-f]{3,8}\]/i,i=/^\s*(?:#[0-9a-f]{3,8}|(?:rgb|rgba|hsl|hsla)\([^)]*\))\s*$/i;function a(e){return e.startsWith(`src/theme/`)||e.startsWith(`themes/`)||e.startsWith(`tests/`)||e.includes(`.test.`)||e.includes(`.spec.`)}function o(e){return e.startsWith(`src/web/`)||e.startsWith(`web/`)?!0:e.startsWith(`src/components/`)||e.startsWith(`src/lib/`)}const s={preflight:!0,rule:`Browser code names a theme token, never a colour literal`,rationale:`A theme is only a theme if every colour on the page comes from it. One literal hex is invisible in review and then permanent: it survives a theme switch, so a dark-tuned tint sits inside a light surface and the reader assumes the theme is broken rather than that one card opted out. Naming a token also names the intent, and a reviewer can tell a warning tint from a brand blue without opening a colour picker.`,check(e,s){let c=t.projectPath(e,s);if(c===null||a(c)||!o(c))return null;let l=t.readSource(e);if(!l)return null;let u=new Set,d=e=>{if(n.default.isStringLiteralLike(e)){let t=e.text;(r.test(t)||i.test(t))&&u.add(t.trim().slice(0,60))}n.default.forEachChild(e,d)};return d(l),u.size===0?null:`${c} hardcodes ${[...u].map(e=>`'${e}'`).join(`, `)}. Browser code names a theme token instead: a Tailwind class such as bg-doom-panel, text-doom-hi, border-doom-edge-red, or bg-doom-tint-yellow, or the CSS custom property var(--doom-blue). If the palette has no token for what you mean, add one to the theme contract rather than spelling the colour here.`}};exports.noRawThemeColor=s;
2
+ //# sourceMappingURL=theming.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theming.cjs","names":["projectPath","readSource","ts"],"sources":["../../src/rules/theming.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport ts from 'typescript';\nimport { projectPath, readSource } from './moduleGraph.js';\n\n/** A Tailwind arbitrary colour: the `bg-[#312A1C]` shape, in any utility. */\nconst ARBITRARY_COLOR = /-\\[#[0-9a-f]{3,8}\\]/i;\n/** A string that is nothing but a colour: '#282c34', 'rgb(1 2 3)', 'hsl(...)'. */\nconst BARE_COLOR = /^\\s*(?:#[0-9a-f]{3,8}|(?:rgb|rgba|hsl|hsla)\\([^)]*\\))\\s*$/i;\n\n/**\n * Where colours are the subject rather than a choice: the theme configs\n * themselves, the modules that parse and derive them, and the tests that\n * assert on them.\n */\nfunction declaresColors(relativePath: string): boolean {\n return (\n relativePath.startsWith('src/theme/') ||\n relativePath.startsWith('themes/') ||\n relativePath.startsWith('tests/') ||\n relativePath.includes('.test.') ||\n relativePath.includes('.spec.')\n );\n}\n\n/** Browser source: the cockpit's client tree, a plugin's web/ tree, or a component library's src. */\nfunction isBrowserSource(relativePath: string): boolean {\n if (relativePath.startsWith('src/web/')) return true;\n if (relativePath.startsWith('web/')) return true;\n return relativePath.startsWith('src/components/') || relativePath.startsWith('src/lib/');\n}\n\nexport const noRawThemeColor: RuleDefinition = {\n preflight: true,\n rule: 'Browser code names a theme token, never a colour literal',\n rationale:\n 'A theme is only a theme if every colour on the page comes from it. One literal hex is invisible in review and then permanent: it survives a theme switch, so a dark-tuned tint sits inside a light surface and the reader assumes the theme is broken rather than that one card opted out. Naming a token also names the intent, and a reviewer can tell a warning tint from a brand blue without opening a colour picker.',\n check(filePath, configRoot) {\n const relativePath = projectPath(filePath, configRoot);\n if (relativePath === null || declaresColors(relativePath) || !isBrowserSource(relativePath)) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const offenders = new Set<string>();\n const visit = (node: ts.Node): void => {\n if (ts.isStringLiteralLike(node)) {\n const text = node.text;\n if (ARBITRARY_COLOR.test(text) || BARE_COLOR.test(text)) offenders.add(text.trim().slice(0, 60));\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n if (offenders.size === 0) return null;\n\n return `${relativePath} hardcodes ${[...offenders].map((value) => `'${value}'`).join(', ')}. Browser code names a theme token instead: a Tailwind class such as bg-doom-panel, text-doom-hi, border-doom-edge-red, or bg-doom-tint-yellow, or the CSS custom property var(--doom-blue). If the palette has no token for what you mean, add one to the theme contract rather than spelling the colour here.`;\n },\n};\n"],"mappings":"iIAKA,MAAM,EAAkB,uBAElB,EAAa,6DAOnB,SAAS,EAAe,EAA+B,CACrD,OACE,EAAa,WAAW,YAAY,GACpC,EAAa,WAAW,SAAS,GACjC,EAAa,WAAW,QAAQ,GAChC,EAAa,SAAS,QAAQ,GAC9B,EAAa,SAAS,QAAQ,CAElC,CAGA,SAAS,EAAgB,EAA+B,CAGtD,OAFI,EAAa,WAAW,UAAU,GAClC,EAAa,WAAW,MAAM,EAAU,GACrC,EAAa,WAAW,iBAAiB,GAAK,EAAa,WAAW,UAAU,CACzF,CAEA,MAAa,EAAkC,CAC7C,UAAW,GACX,KAAM,2DACN,UACE,6ZACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAeA,EAAAA,YAAY,EAAU,CAAU,EACrD,GAAI,IAAiB,MAAQ,EAAe,CAAY,GAAK,CAAC,EAAgB,CAAY,EAAG,OAAO,KACpG,IAAM,EAAaC,EAAAA,WAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAY,IAAI,IAChB,EAAS,GAAwB,CACrC,GAAIC,EAAAA,QAAG,oBAAoB,CAAI,EAAG,CAChC,IAAM,EAAO,EAAK,MACd,EAAgB,KAAK,CAAI,GAAK,EAAW,KAAK,CAAI,IAAG,EAAU,IAAI,EAAK,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,CAAC,CACjG,CACA,EAAA,QAAG,aAAa,EAAM,CAAK,CAC7B,EAIA,OAHA,EAAM,CAAU,EACZ,EAAU,OAAS,EAAU,KAE1B,GAAG,EAAa,aAAa,CAAC,GAAG,CAAS,CAAC,CAAC,IAAK,GAAU,IAAI,EAAM,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,gTAC7F,CACF"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/theming.d.ts
3
+ declare const noRawThemeColor: RuleDefinition;
4
+ //#endregion
5
+ export { noRawThemeColor };
6
+ //# sourceMappingURL=theming.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theming.d.cts","names":[],"sources":["../../src/rules/theming.ts"],"mappings":";;cA+Ba,iBAAiB"}
@@ -0,0 +1,6 @@
1
+ import { RuleDefinition } from "@agimon-ai/vibe-lint";
2
+ //#region src/rules/theming.d.ts
3
+ declare const noRawThemeColor: RuleDefinition;
4
+ //#endregion
5
+ export { noRawThemeColor };
6
+ //# sourceMappingURL=theming.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theming.d.mts","names":[],"sources":["../../src/rules/theming.ts"],"mappings":";;cA+Ba,iBAAiB"}
@@ -0,0 +1,2 @@
1
+ import{projectPath as e,readSource as t}from"./moduleGraph.mjs";import n from"typescript";const r=/-\[#[0-9a-f]{3,8}\]/i,i=/^\s*(?:#[0-9a-f]{3,8}|(?:rgb|rgba|hsl|hsla)\([^)]*\))\s*$/i;function a(e){return e.startsWith(`src/theme/`)||e.startsWith(`themes/`)||e.startsWith(`tests/`)||e.includes(`.test.`)||e.includes(`.spec.`)}function o(e){return e.startsWith(`src/web/`)||e.startsWith(`web/`)?!0:e.startsWith(`src/components/`)||e.startsWith(`src/lib/`)}const s={preflight:!0,rule:`Browser code names a theme token, never a colour literal`,rationale:`A theme is only a theme if every colour on the page comes from it. One literal hex is invisible in review and then permanent: it survives a theme switch, so a dark-tuned tint sits inside a light surface and the reader assumes the theme is broken rather than that one card opted out. Naming a token also names the intent, and a reviewer can tell a warning tint from a brand blue without opening a colour picker.`,check(s,c){let l=e(s,c);if(l===null||a(l)||!o(l))return null;let u=t(s);if(!u)return null;let d=new Set,f=e=>{if(n.isStringLiteralLike(e)){let t=e.text;(r.test(t)||i.test(t))&&d.add(t.trim().slice(0,60))}n.forEachChild(e,f)};return f(u),d.size===0?null:`${l} hardcodes ${[...d].map(e=>`'${e}'`).join(`, `)}. Browser code names a theme token instead: a Tailwind class such as bg-doom-panel, text-doom-hi, border-doom-edge-red, or bg-doom-tint-yellow, or the CSS custom property var(--doom-blue). If the palette has no token for what you mean, add one to the theme contract rather than spelling the colour here.`}};export{s as noRawThemeColor};
2
+ //# sourceMappingURL=theming.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theming.mjs","names":[],"sources":["../../src/rules/theming.ts"],"sourcesContent":["import type { RuleDefinition } from '@agimon-ai/vibe-lint';\nimport ts from 'typescript';\nimport { projectPath, readSource } from './moduleGraph.js';\n\n/** A Tailwind arbitrary colour: the `bg-[#312A1C]` shape, in any utility. */\nconst ARBITRARY_COLOR = /-\\[#[0-9a-f]{3,8}\\]/i;\n/** A string that is nothing but a colour: '#282c34', 'rgb(1 2 3)', 'hsl(...)'. */\nconst BARE_COLOR = /^\\s*(?:#[0-9a-f]{3,8}|(?:rgb|rgba|hsl|hsla)\\([^)]*\\))\\s*$/i;\n\n/**\n * Where colours are the subject rather than a choice: the theme configs\n * themselves, the modules that parse and derive them, and the tests that\n * assert on them.\n */\nfunction declaresColors(relativePath: string): boolean {\n return (\n relativePath.startsWith('src/theme/') ||\n relativePath.startsWith('themes/') ||\n relativePath.startsWith('tests/') ||\n relativePath.includes('.test.') ||\n relativePath.includes('.spec.')\n );\n}\n\n/** Browser source: the cockpit's client tree, a plugin's web/ tree, or a component library's src. */\nfunction isBrowserSource(relativePath: string): boolean {\n if (relativePath.startsWith('src/web/')) return true;\n if (relativePath.startsWith('web/')) return true;\n return relativePath.startsWith('src/components/') || relativePath.startsWith('src/lib/');\n}\n\nexport const noRawThemeColor: RuleDefinition = {\n preflight: true,\n rule: 'Browser code names a theme token, never a colour literal',\n rationale:\n 'A theme is only a theme if every colour on the page comes from it. One literal hex is invisible in review and then permanent: it survives a theme switch, so a dark-tuned tint sits inside a light surface and the reader assumes the theme is broken rather than that one card opted out. Naming a token also names the intent, and a reviewer can tell a warning tint from a brand blue without opening a colour picker.',\n check(filePath, configRoot) {\n const relativePath = projectPath(filePath, configRoot);\n if (relativePath === null || declaresColors(relativePath) || !isBrowserSource(relativePath)) return null;\n const sourceFile = readSource(filePath);\n if (!sourceFile) return null;\n\n const offenders = new Set<string>();\n const visit = (node: ts.Node): void => {\n if (ts.isStringLiteralLike(node)) {\n const text = node.text;\n if (ARBITRARY_COLOR.test(text) || BARE_COLOR.test(text)) offenders.add(text.trim().slice(0, 60));\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n if (offenders.size === 0) return null;\n\n return `${relativePath} hardcodes ${[...offenders].map((value) => `'${value}'`).join(', ')}. Browser code names a theme token instead: a Tailwind class such as bg-doom-panel, text-doom-hi, border-doom-edge-red, or bg-doom-tint-yellow, or the CSS custom property var(--doom-blue). If the palette has no token for what you mean, add one to the theme contract rather than spelling the colour here.`;\n },\n};\n"],"mappings":"0FAKA,MAAM,EAAkB,uBAElB,EAAa,6DAOnB,SAAS,EAAe,EAA+B,CACrD,OACE,EAAa,WAAW,YAAY,GACpC,EAAa,WAAW,SAAS,GACjC,EAAa,WAAW,QAAQ,GAChC,EAAa,SAAS,QAAQ,GAC9B,EAAa,SAAS,QAAQ,CAElC,CAGA,SAAS,EAAgB,EAA+B,CAGtD,OAFI,EAAa,WAAW,UAAU,GAClC,EAAa,WAAW,MAAM,EAAU,GACrC,EAAa,WAAW,iBAAiB,GAAK,EAAa,WAAW,UAAU,CACzF,CAEA,MAAa,EAAkC,CAC7C,UAAW,GACX,KAAM,2DACN,UACE,6ZACF,MAAM,EAAU,EAAY,CAC1B,IAAM,EAAe,EAAY,EAAU,CAAU,EACrD,GAAI,IAAiB,MAAQ,EAAe,CAAY,GAAK,CAAC,EAAgB,CAAY,EAAG,OAAO,KACpG,IAAM,EAAa,EAAW,CAAQ,EACtC,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAY,IAAI,IAChB,EAAS,GAAwB,CACrC,GAAI,EAAG,oBAAoB,CAAI,EAAG,CAChC,IAAM,EAAO,EAAK,MACd,EAAgB,KAAK,CAAI,GAAK,EAAW,KAAK,CAAI,IAAG,EAAU,IAAI,EAAK,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,CAAC,CACjG,CACA,EAAG,aAAa,EAAM,CAAK,CAC7B,EAIA,OAHA,EAAM,CAAU,EACZ,EAAU,OAAS,EAAU,KAE1B,GAAG,EAAa,aAAa,CAAC,GAAG,CAAS,CAAC,CAAC,IAAK,GAAU,IAAI,EAAM,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,gTAC7F,CACF"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@agimon-ai/vibe-lint-plugin-doom-web",
3
+ "version": "0.0.1-alpha.35",
4
+ "description": "Deterministic Vibe-Lint rules for DoomPi web packages",
5
+ "keywords": [
6
+ "doompi",
7
+ "lint",
8
+ "typescript",
9
+ "vibe-lint",
10
+ "web"
11
+ ],
12
+ "homepage": "https://agimon.ai",
13
+ "bugs": {
14
+ "url": "https://github.com/AgiFlow/doompi/issues"
15
+ },
16
+ "license": "MIT",
17
+ "author": "Vuong Ngo",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/AgiFlow/doompi.git",
21
+ "directory": "packages/tooling/vibe-lint-plugin-doom-web"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md"
26
+ ],
27
+ "type": "module",
28
+ "main": "./dist/index.cjs",
29
+ "types": "./dist/index.d.mts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.mts",
33
+ "import": "./dist/index.mjs",
34
+ "require": "./dist/index.cjs"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "typescript": "6.0.3"
43
+ },
44
+ "devDependencies": {
45
+ "@agimon-ai/vibe-lint": "0.0.1-alpha.29",
46
+ "@types/node": "26.2.0",
47
+ "@vitest/coverage-v8": "4.1.11",
48
+ "tsdown": "0.22.14",
49
+ "vitest": "4.1.11"
50
+ },
51
+ "peerDependencies": {
52
+ "@agimon-ai/vibe-lint": "0.0.1-alpha.29"
53
+ },
54
+ "engines": {
55
+ "node": ">=22.19.0"
56
+ },
57
+ "scripts": {
58
+ "build": "tsdown",
59
+ "test": "vitest --run",
60
+ "typecheck": "tsc --noEmit",
61
+ "lint": "oxlint . && oxfmt . --check",
62
+ "fixcode": "oxlint . --fix && oxfmt ."
63
+ }
64
+ }