minutework 0.1.39 → 0.1.41

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 (166) hide show
  1. package/EXTERNAL_ALPHA.md +17 -1
  2. package/README.md +21 -1
  3. package/assets/claude-local/skills/README.md +6 -0
  4. package/assets/claude-local/skills/ai-capability-defaults/SKILL.md +3 -0
  5. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +15 -0
  6. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +25 -9
  7. package/assets/claude-local/skills/integration-broker-and-connectors/SKILL.md +122 -0
  8. package/assets/claude-local/skills/layering-and-import-modes/SKILL.md +4 -0
  9. package/assets/claude-local/skills/project-overview-and-strategy/SKILL.md +6 -0
  10. package/assets/claude-local/skills/shell-architecture/SKILL.md +15 -0
  11. package/assets/claude-local/skills/vuilder-public-site-authoring/SKILL.md +10 -4
  12. package/assets/claude-local/skills/vuilder-workspace-architecture/SKILL.md +78 -0
  13. package/assets/templates/vuilder-public-site/.env.example +11 -0
  14. package/assets/templates/vuilder-public-site/README.md +15 -0
  15. package/assets/templates/vuilder-public-site/eslint.config.mjs +6 -0
  16. package/assets/templates/vuilder-public-site/next-env.d.ts +4 -0
  17. package/assets/templates/vuilder-public-site/next.config.mjs +28 -0
  18. package/assets/templates/vuilder-public-site/package.json +39 -0
  19. package/assets/templates/vuilder-public-site/postcss.config.mjs +5 -0
  20. package/assets/templates/vuilder-public-site/src/app/api/onboarding/start/route.ts +19 -0
  21. package/assets/templates/vuilder-public-site/src/app/blog/[slug]/page.tsx +25 -0
  22. package/assets/templates/vuilder-public-site/src/app/blog/page.tsx +26 -0
  23. package/assets/templates/vuilder-public-site/src/app/globals.css +103 -0
  24. package/assets/templates/vuilder-public-site/src/app/layout.tsx +18 -0
  25. package/assets/templates/vuilder-public-site/src/app/onboarding/[[...step]]/page.tsx +39 -0
  26. package/assets/templates/vuilder-public-site/src/app/page.tsx +43 -0
  27. package/assets/templates/vuilder-public-site/src/lib/env.server.ts +31 -0
  28. package/assets/templates/vuilder-public-site/src/lib/platform.server.ts +47 -0
  29. package/assets/templates/vuilder-public-site/src/lib/public-dj.server.ts +86 -0
  30. package/assets/templates/vuilder-public-site/src/lib/public-dj.test.ts +8 -0
  31. package/assets/templates/vuilder-public-site/src/lib/routes.test.ts +13 -0
  32. package/assets/templates/vuilder-public-site/src/lib/routes.ts +12 -0
  33. package/assets/templates/vuilder-public-site/template.json +21 -0
  34. package/assets/templates/vuilder-public-site/tools/template/validate-template.mjs +44 -0
  35. package/assets/templates/vuilder-public-site/tsconfig.json +23 -0
  36. package/assets/templates/vuilder-public-site/vitest.config.ts +13 -0
  37. package/assets/templates/vuilder-shell/.env.example +8 -0
  38. package/assets/templates/vuilder-shell/.storybook/main.ts +19 -0
  39. package/assets/templates/vuilder-shell/.storybook/preview.tsx +38 -0
  40. package/assets/templates/vuilder-shell/README.md +49 -0
  41. package/assets/templates/vuilder-shell/components.json +21 -0
  42. package/assets/templates/vuilder-shell/eslint.config.mjs +41 -0
  43. package/assets/templates/vuilder-shell/next-env.d.ts +6 -0
  44. package/assets/templates/vuilder-shell/next.config.mjs +33 -0
  45. package/assets/templates/vuilder-shell/package.json +61 -0
  46. package/assets/templates/vuilder-shell/postcss.config.mjs +8 -0
  47. package/assets/templates/vuilder-shell/public/.gitkeep +1 -0
  48. package/assets/templates/vuilder-shell/src/app/api/auth/accept-shell-session/route.test.ts +105 -0
  49. package/assets/templates/vuilder-shell/src/app/api/auth/accept-shell-session/route.ts +63 -0
  50. package/assets/templates/vuilder-shell/src/app/api/auth/logout/route.test.ts +63 -0
  51. package/assets/templates/vuilder-shell/src/app/api/auth/logout/route.ts +24 -0
  52. package/assets/templates/vuilder-shell/src/app/api/auth/session/route.test.ts +70 -0
  53. package/assets/templates/vuilder-shell/src/app/api/auth/session/route.ts +27 -0
  54. package/assets/templates/vuilder-shell/src/app/app/layout.tsx +17 -0
  55. package/assets/templates/vuilder-shell/src/app/app/page.tsx +30 -0
  56. package/assets/templates/vuilder-shell/src/app/blog/[slug]/page.tsx +15 -0
  57. package/assets/templates/vuilder-shell/src/app/blog/page.tsx +15 -0
  58. package/assets/templates/vuilder-shell/src/app/docs/[...slug]/page.tsx +15 -0
  59. package/assets/templates/vuilder-shell/src/app/docs/page.tsx +15 -0
  60. package/assets/templates/vuilder-shell/src/app/globals.css +70 -0
  61. package/assets/templates/vuilder-shell/src/app/layout.tsx +69 -0
  62. package/assets/templates/vuilder-shell/src/app/login/route.test.ts +33 -0
  63. package/assets/templates/vuilder-shell/src/app/login/route.ts +21 -0
  64. package/assets/templates/vuilder-shell/src/app/page.tsx +16 -0
  65. package/assets/templates/vuilder-shell/src/app/pricing/page.tsx +15 -0
  66. package/assets/templates/vuilder-shell/src/app/providers.tsx +25 -0
  67. package/assets/templates/vuilder-shell/src/app/robots.ts +21 -0
  68. package/assets/templates/vuilder-shell/src/app/sitemap.ts +33 -0
  69. package/assets/templates/vuilder-shell/src/app/w/[workspace_slug]/connect/page.tsx +31 -0
  70. package/assets/templates/vuilder-shell/src/app/w/[workspace_slug]/page.tsx +54 -0
  71. package/assets/templates/vuilder-shell/src/components/ui/button.tsx +59 -0
  72. package/assets/templates/vuilder-shell/src/components/ui/input.tsx +21 -0
  73. package/assets/templates/vuilder-shell/src/design-system/docs/governance.mdx +26 -0
  74. package/assets/templates/vuilder-shell/src/design-system/patterns/panel-frame.stories.tsx +48 -0
  75. package/assets/templates/vuilder-shell/src/design-system/patterns/panel-frame.tsx +26 -0
  76. package/assets/templates/vuilder-shell/src/design-system/patterns/status-badge.stories.tsx +26 -0
  77. package/assets/templates/vuilder-shell/src/design-system/patterns/status-badge.tsx +35 -0
  78. package/assets/templates/vuilder-shell/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
  79. package/assets/templates/vuilder-shell/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
  80. package/assets/templates/vuilder-shell/src/design-system/primitives/button.stories.tsx +37 -0
  81. package/assets/templates/vuilder-shell/src/design-system/primitives/button.ts +1 -0
  82. package/assets/templates/vuilder-shell/src/design-system/primitives/input.stories.tsx +26 -0
  83. package/assets/templates/vuilder-shell/src/design-system/primitives/input.ts +1 -0
  84. package/assets/templates/vuilder-shell/src/design-system/recipes/chrome.ts +28 -0
  85. package/assets/templates/vuilder-shell/src/design-system/tokens/foundation.css +31 -0
  86. package/assets/templates/vuilder-shell/src/design-system/tokens/index.css +3 -0
  87. package/assets/templates/vuilder-shell/src/design-system/tokens/manifest.json +85 -0
  88. package/assets/templates/vuilder-shell/src/design-system/tokens/manifest.ts +87 -0
  89. package/assets/templates/vuilder-shell/src/design-system/tokens/semantic.css +105 -0
  90. package/assets/templates/vuilder-shell/src/design-system/tokens/theme.css +59 -0
  91. package/assets/templates/vuilder-shell/src/design-system/tokens/tokens.stories.tsx +71 -0
  92. package/assets/templates/vuilder-shell/src/features/dashboard/components/tenant-dashboard.tsx +134 -0
  93. package/assets/templates/vuilder-shell/src/features/public-shell/components/static-public-page.tsx +58 -0
  94. package/assets/templates/vuilder-shell/src/features/shell/components/authenticated-app-layout-shell.tsx +84 -0
  95. package/assets/templates/vuilder-shell/src/features/shell/components/private-app-shell.tsx +22 -0
  96. package/assets/templates/vuilder-shell/src/features/vuilder-shell/components/vuilder-connect-screen.tsx +89 -0
  97. package/assets/templates/vuilder-shell/src/features/vuilder-shell/components/vuilder-workspace-screen.tsx +49 -0
  98. package/assets/templates/vuilder-shell/src/lib/app-routes.test.ts +37 -0
  99. package/assets/templates/vuilder-shell/src/lib/app-routes.ts +86 -0
  100. package/assets/templates/vuilder-shell/src/lib/auth-routes.server.test.ts +26 -0
  101. package/assets/templates/vuilder-shell/src/lib/auth-routes.server.ts +53 -0
  102. package/assets/templates/vuilder-shell/src/lib/http/same-origin.test.ts +23 -0
  103. package/assets/templates/vuilder-shell/src/lib/http/same-origin.ts +18 -0
  104. package/assets/templates/vuilder-shell/src/lib/platform/client.server.test.ts +201 -0
  105. package/assets/templates/vuilder-shell/src/lib/platform/client.server.ts +540 -0
  106. package/assets/templates/vuilder-shell/src/lib/platform/contracts.ts +190 -0
  107. package/assets/templates/vuilder-shell/src/lib/platform/endpoints.server.ts +29 -0
  108. package/assets/templates/vuilder-shell/src/lib/platform/env.server.ts +82 -0
  109. package/assets/templates/vuilder-shell/src/lib/platform/route-response.ts +33 -0
  110. package/assets/templates/vuilder-shell/src/lib/platform/session.server.ts +145 -0
  111. package/assets/templates/vuilder-shell/src/lib/public-site.test.ts +20 -0
  112. package/assets/templates/vuilder-shell/src/lib/public-site.ts +48 -0
  113. package/assets/templates/vuilder-shell/src/lib/theme-config.ts +10 -0
  114. package/assets/templates/vuilder-shell/src/lib/theme.tsx +159 -0
  115. package/assets/templates/vuilder-shell/src/lib/utils.ts +6 -0
  116. package/assets/templates/vuilder-shell/template.json +28 -0
  117. package/assets/templates/vuilder-shell/template.schema.json +171 -0
  118. package/assets/templates/vuilder-shell/test/server-only-stub.ts +1 -0
  119. package/assets/templates/vuilder-shell/tools/design-system/build-token-manifest.mjs +3 -0
  120. package/assets/templates/vuilder-shell/tools/design-system/check-imports.mjs +9 -0
  121. package/assets/templates/vuilder-shell/tools/design-system/check-stories.mjs +9 -0
  122. package/assets/templates/vuilder-shell/tools/design-system/check-values.mjs +9 -0
  123. package/assets/templates/vuilder-shell/tools/design-system/checks.mjs +238 -0
  124. package/assets/templates/vuilder-shell/tools/design-system/eslint-plugin-design-system.mjs +184 -0
  125. package/assets/templates/vuilder-shell/tools/design-system/playwright.config.mjs +34 -0
  126. package/assets/templates/vuilder-shell/tools/design-system/run-checks.mjs +22 -0
  127. package/assets/templates/vuilder-shell/tools/design-system/shared.mjs +166 -0
  128. package/assets/templates/vuilder-shell/tools/design-system/visual.spec.ts +41 -0
  129. package/assets/templates/vuilder-shell/tools/template/validate-route-contract.mjs +373 -0
  130. package/assets/templates/vuilder-shell/tools/template/validate-template.mjs +45 -0
  131. package/assets/templates/vuilder-shell/tools/template/with-public-site-fixture.mjs +45 -0
  132. package/assets/templates/vuilder-shell/tsconfig.json +42 -0
  133. package/assets/templates/vuilder-shell/vitest.config.ts +23 -0
  134. package/dist/auth.js +66 -14
  135. package/dist/auth.js.map +1 -1
  136. package/dist/deploy-state.d.ts +1 -0
  137. package/dist/deploy-state.js.map +1 -1
  138. package/dist/deploy.js +18 -4
  139. package/dist/deploy.js.map +1 -1
  140. package/dist/developer-client.d.ts +1 -1
  141. package/dist/index.js +12 -2
  142. package/dist/index.js.map +1 -1
  143. package/dist/init-prompt.js +21 -13
  144. package/dist/init-prompt.js.map +1 -1
  145. package/dist/init.d.ts +3 -1
  146. package/dist/init.js +103 -12
  147. package/dist/init.js.map +1 -1
  148. package/dist/orchestrator-context.js +17 -5
  149. package/dist/orchestrator-context.js.map +1 -1
  150. package/dist/orchestrator-state.d.ts +2 -2
  151. package/dist/orchestrator-state.js.map +1 -1
  152. package/dist/publish.js +12 -2
  153. package/dist/publish.js.map +1 -1
  154. package/dist/state.d.ts +2 -0
  155. package/dist/state.js +9 -0
  156. package/dist/state.js.map +1 -1
  157. package/package.json +2 -2
  158. package/vendor/workspace-mcp/context.d.ts +3 -1
  159. package/vendor/workspace-mcp/context.js +134 -21
  160. package/vendor/workspace-mcp/context.js.map +1 -1
  161. package/vendor/workspace-mcp/types.d.ts +72 -7
  162. package/vendor/workspace-mcp/types.js +8 -4
  163. package/vendor/workspace-mcp/types.js.map +1 -1
  164. package/assets/templates/fastapi-sidecar/poetry.lock +0 -757
  165. package/assets/templates/next-tenant-app/package-lock.json +0 -9682
  166. package/assets/templates/next-tenant-app/pnpm-lock.yaml +0 -6062
@@ -0,0 +1,171 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://minutework.dev/schemas/runtime/builder/template.schema.json",
4
+ "title": "Runtime Builder Template Manifest",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "template_id",
9
+ "template_kind",
10
+ "template_profile",
11
+ "template_version",
12
+ "materialize",
13
+ "builder_edit_mode",
14
+ "seed_source",
15
+ "required_bootstrap_steps",
16
+ "example_features"
17
+ ],
18
+ "properties": {
19
+ "template_id": {
20
+ "type": "string",
21
+ "minLength": 1
22
+ },
23
+ "template_kind": {
24
+ "type": "string",
25
+ "enum": [
26
+ "sidecar_nextjs_private",
27
+ "sidecar_fastapi_internal",
28
+ "combined_web",
29
+ "public_site",
30
+ "vuilder_shell"
31
+ ]
32
+ },
33
+ "template_profile": {
34
+ "type": "string",
35
+ "enum": [
36
+ "platform_session_bff",
37
+ "vuilder_shell_token",
38
+ "tenant_web_auth_sdk",
39
+ "bridge_internal",
40
+ "public_dj_cms"
41
+ ]
42
+ },
43
+ "template_version": {
44
+ "type": "string",
45
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
46
+ },
47
+ "materialize": {
48
+ "type": "object",
49
+ "additionalProperties": false,
50
+ "required": [
51
+ "destination"
52
+ ],
53
+ "properties": {
54
+ "destination": {
55
+ "type": "string",
56
+ "enum": [
57
+ "app",
58
+ "sidecar",
59
+ "vuilder-app",
60
+ "vuilder-shell"
61
+ ]
62
+ }
63
+ }
64
+ },
65
+ "builder_edit_mode": {
66
+ "type": "string",
67
+ "const": "workspace_copy_only"
68
+ },
69
+ "seed_source": {
70
+ "type": "string",
71
+ "minLength": 1
72
+ },
73
+ "template_bundle_ref": {
74
+ "type": "string",
75
+ "minLength": 1
76
+ },
77
+ "required_bootstrap_steps": {
78
+ "type": "array",
79
+ "minItems": 1,
80
+ "uniqueItems": true,
81
+ "items": {
82
+ "type": "string",
83
+ "enum": [
84
+ "next_typegen",
85
+ "design_system_tokens",
86
+ "python_env_sync",
87
+ "python_import_smoke"
88
+ ]
89
+ }
90
+ },
91
+ "runtime_contract_refs": {
92
+ "type": "array",
93
+ "items": {
94
+ "type": "string",
95
+ "minLength": 1
96
+ }
97
+ },
98
+ "sidecar_processes": {
99
+ "type": "array",
100
+ "minItems": 1,
101
+ "items": {
102
+ "$ref": "#/$defs/sidecarProcess"
103
+ }
104
+ },
105
+ "example_features": {
106
+ "type": "object",
107
+ "additionalProperties": {
108
+ "$ref": "#/$defs/exampleFeature"
109
+ }
110
+ }
111
+ },
112
+ "$defs": {
113
+ "exampleFeature": {
114
+ "type": "object",
115
+ "additionalProperties": false,
116
+ "required": [
117
+ "default_enabled"
118
+ ],
119
+ "properties": {
120
+ "default_enabled": {
121
+ "type": "boolean"
122
+ }
123
+ }
124
+ },
125
+ "sidecarProcess": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": [
129
+ "name",
130
+ "process_type",
131
+ "entrypoint",
132
+ "composition_profiles"
133
+ ],
134
+ "properties": {
135
+ "name": {
136
+ "type": "string",
137
+ "minLength": 1
138
+ },
139
+ "process_type": {
140
+ "type": "string",
141
+ "enum": [
142
+ "fastapi",
143
+ "worker",
144
+ "cron"
145
+ ]
146
+ },
147
+ "entrypoint": {
148
+ "type": "string",
149
+ "minLength": 1
150
+ },
151
+ "health_check_path": {
152
+ "type": "string",
153
+ "minLength": 1
154
+ },
155
+ "composition_profiles": {
156
+ "type": "array",
157
+ "minItems": 1,
158
+ "uniqueItems": true,
159
+ "items": {
160
+ "type": "string",
161
+ "enum": [
162
+ "worker_only",
163
+ "internal_api_only",
164
+ "worker_plus_internal_api"
165
+ ]
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,3 @@
1
+ import { buildTokenManifest } from "./checks.mjs";
2
+
3
+ buildTokenManifest();
@@ -0,0 +1,9 @@
1
+ import { checkImports } from "./checks.mjs";
2
+ import { parseCliArgs, resolveTargetFiles } from "./shared.mjs";
3
+
4
+ const options = parseCliArgs();
5
+ const violations = checkImports(resolveTargetFiles(options));
6
+
7
+ if (violations.length > 0) {
8
+ process.exit(1);
9
+ }
@@ -0,0 +1,9 @@
1
+ import { checkStories } from "./checks.mjs";
2
+ import { parseCliArgs, resolveTargetFiles } from "./shared.mjs";
3
+
4
+ const options = parseCliArgs();
5
+ const violations = checkStories(resolveTargetFiles(options), options);
6
+
7
+ if (violations.length > 0) {
8
+ process.exit(1);
9
+ }
@@ -0,0 +1,9 @@
1
+ import { checkValues } from "./checks.mjs";
2
+ import { parseCliArgs, resolveTargetFiles } from "./shared.mjs";
3
+
4
+ const options = parseCliArgs();
5
+ const violations = checkValues(resolveTargetFiles(options));
6
+
7
+ if (violations.length > 0) {
8
+ process.exit(1);
9
+ }
@@ -0,0 +1,238 @@
1
+ import { readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+
3
+ import {
4
+ absolutePath,
5
+ arbitraryVisualUtilityPattern,
6
+ isFeatureSurfaceFile,
7
+ isGovernedVisualFile,
8
+ isLegacyUiImport,
9
+ isTokenFile,
10
+ literalStyleVisualPattern,
11
+ matchesPattern,
12
+ normalizePath,
13
+ rawColorPattern,
14
+ } from "./shared.mjs";
15
+
16
+ function readFile(filePath) {
17
+ return readFileSync(absolutePath(filePath), "utf8");
18
+ }
19
+
20
+ function ensureTrailingNewline(value) {
21
+ return value.endsWith("\n") ? value : `${value}\n`;
22
+ }
23
+
24
+ function reportViolations(title, violations) {
25
+ if (violations.length === 0) {
26
+ return;
27
+ }
28
+
29
+ console.error(`\n${title}`);
30
+ for (const violation of violations) {
31
+ console.error(`- ${violation}`);
32
+ }
33
+ }
34
+
35
+ function parseCssVariables(filePath) {
36
+ const content = readFile(filePath);
37
+ const matches = [...content.matchAll(/--([\w-]+):\s*([^;]+);/g)];
38
+
39
+ return Object.fromEntries(matches.map((match) => [match[1], match[2].trim()]));
40
+ }
41
+
42
+ function fileExists(filePath) {
43
+ try {
44
+ readFileSync(filePath);
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ export function buildTokenManifest() {
52
+ const manifest = {
53
+ foundation: parseCssVariables("src/design-system/tokens/foundation.css"),
54
+ semantic: parseCssVariables("src/design-system/tokens/semantic.css"),
55
+ };
56
+
57
+ const jsonOutputPath = absolutePath("src/design-system/tokens/manifest.json");
58
+ const tsOutputPath = absolutePath("src/design-system/tokens/manifest.ts");
59
+ const serializedManifest = JSON.stringify(manifest, null, 2);
60
+
61
+ writeFileSync(jsonOutputPath, ensureTrailingNewline(serializedManifest));
62
+ writeFileSync(
63
+ tsOutputPath,
64
+ ensureTrailingNewline(
65
+ `export const tokenManifest = ${serializedManifest} as const\n\nexport default tokenManifest`,
66
+ ),
67
+ );
68
+
69
+ return manifest;
70
+ }
71
+
72
+ export function checkImports(targetFiles) {
73
+ const violations = [];
74
+ const importPattern =
75
+ /(?:import|export)\s[\s\S]*?from\s+['"]([^'"]+)['"]|import\(\s*['"]([^'"]+)['"]\s*\)/g;
76
+
77
+ for (const filePath of targetFiles) {
78
+ if (!isFeatureSurfaceFile(filePath)) {
79
+ continue;
80
+ }
81
+
82
+ const content = readFile(filePath);
83
+ const matches = [...content.matchAll(importPattern)];
84
+
85
+ for (const match of matches) {
86
+ const specifier = match[1] ?? match[2];
87
+ if (!specifier) {
88
+ continue;
89
+ }
90
+
91
+ if (isLegacyUiImport(specifier)) {
92
+ violations.push(
93
+ `${normalizePath(filePath)} imports ${specifier}; use @/design-system/primitives/* instead.`,
94
+ );
95
+ }
96
+
97
+ if (specifier.startsWith("@radix-ui/")) {
98
+ violations.push(
99
+ `${normalizePath(filePath)} imports ${specifier}; Radix primitives are only allowed inside src/design-system.`,
100
+ );
101
+ }
102
+
103
+ if (specifier === "class-variance-authority") {
104
+ violations.push(
105
+ `${normalizePath(filePath)} imports class-variance-authority; recipes belong in src/design-system/recipes.`,
106
+ );
107
+ }
108
+
109
+ if (
110
+ specifier === "@/design-system/tokens" ||
111
+ (specifier.startsWith("@/design-system/tokens/") &&
112
+ specifier !== "@/design-system/tokens/manifest")
113
+ ) {
114
+ violations.push(
115
+ `${normalizePath(filePath)} imports ${specifier}; feature code must consume semantic classes or @/design-system/tokens/manifest only.`,
116
+ );
117
+ }
118
+ }
119
+ }
120
+
121
+ reportViolations("Design-system import violations:", violations);
122
+ return violations;
123
+ }
124
+
125
+ export function checkValues(targetFiles) {
126
+ const violations = [];
127
+
128
+ for (const filePath of targetFiles) {
129
+ if (!isGovernedVisualFile(filePath) || isTokenFile(filePath)) {
130
+ continue;
131
+ }
132
+
133
+ const content = readFile(filePath);
134
+
135
+ if (matchesPattern(rawColorPattern, content)) {
136
+ violations.push(
137
+ `${normalizePath(filePath)} contains raw color literals; move those values into src/design-system/tokens.`,
138
+ );
139
+ }
140
+
141
+ if (matchesPattern(arbitraryVisualUtilityPattern, content)) {
142
+ violations.push(
143
+ `${normalizePath(filePath)} contains arbitrary visual utilities such as bg-[...] or text-[...]; use tokens or recipes instead.`,
144
+ );
145
+ }
146
+
147
+ if (matchesPattern(literalStyleVisualPattern, content)) {
148
+ violations.push(
149
+ `${normalizePath(filePath)} contains inline style color values; use CSS variables or semantic classes instead.`,
150
+ );
151
+ }
152
+ }
153
+
154
+ reportViolations("Design-system value violations:", violations);
155
+ return violations;
156
+ }
157
+
158
+ export function checkStories(targetFiles, { staged = false } = {}) {
159
+ const publicDirectories = [
160
+ "src/design-system/primitives",
161
+ "src/design-system/patterns",
162
+ ];
163
+ const storySensitivePaths = new Set(
164
+ targetFiles.filter((filePath) =>
165
+ publicDirectories.some((directory) => filePath.startsWith(directory)),
166
+ ),
167
+ );
168
+
169
+ if (staged && storySensitivePaths.size === 0) {
170
+ return [];
171
+ }
172
+
173
+ const violations = [];
174
+ const componentFiles = [];
175
+ const storyFiles = [];
176
+
177
+ for (const directory of publicDirectories) {
178
+ const stack = [absolutePath(directory)];
179
+
180
+ while (stack.length > 0) {
181
+ const currentDirectory = stack.pop();
182
+ if (!currentDirectory) {
183
+ continue;
184
+ }
185
+
186
+ const directoryEntries = readdirSync(currentDirectory, {
187
+ withFileTypes: true,
188
+ });
189
+
190
+ for (const entry of directoryEntries) {
191
+ const absoluteEntryPath = `${currentDirectory}/${entry.name}`;
192
+ if (entry.isDirectory()) {
193
+ stack.push(absoluteEntryPath);
194
+ continue;
195
+ }
196
+
197
+ const normalizedPath = normalizePath(absoluteEntryPath);
198
+ if (!normalizedPath.endsWith(".ts") && !normalizedPath.endsWith(".tsx")) {
199
+ continue;
200
+ }
201
+
202
+ if (normalizedPath.includes(".stories.")) {
203
+ storyFiles.push(normalizedPath);
204
+ continue;
205
+ }
206
+
207
+ componentFiles.push(normalizedPath);
208
+ }
209
+ }
210
+ }
211
+
212
+ for (const componentFile of componentFiles) {
213
+ const tsxStoryPath = absolutePath(componentFile.replace(/\.(ts|tsx)$/, ".stories.tsx"));
214
+ const mdxStoryPath = absolutePath(componentFile.replace(/\.(ts|tsx)$/, ".stories.mdx"));
215
+
216
+ if (!fileExists(tsxStoryPath) && !fileExists(mdxStoryPath)) {
217
+ violations.push(
218
+ `${componentFile} is missing a colocated .stories.tsx or .stories.mdx file.`,
219
+ );
220
+ }
221
+ }
222
+
223
+ for (const storyFile of storyFiles) {
224
+ const componentTsPath = absolutePath(storyFile.replace(/\.stories\.(tsx|mdx)$/, ".ts"));
225
+ const componentTsxPath = absolutePath(
226
+ storyFile.replace(/\.stories\.(tsx|mdx)$/, ".tsx"),
227
+ );
228
+
229
+ if (!fileExists(componentTsPath) && !fileExists(componentTsxPath)) {
230
+ violations.push(
231
+ `${storyFile} is orphaned; add the matching component file or remove the story.`,
232
+ );
233
+ }
234
+ }
235
+
236
+ reportViolations("Design-system story coverage violations:", violations);
237
+ return violations;
238
+ }
@@ -0,0 +1,184 @@
1
+ import {
2
+ arbitraryVisualUtilityPattern,
3
+ isFeatureSurfaceFile,
4
+ matchesPattern,
5
+ rawColorPattern,
6
+ } from "./shared.mjs";
7
+
8
+ function filenameOf(context) {
9
+ return context.filename ?? context.getFilename();
10
+ }
11
+
12
+ function stringValueFromNode(node) {
13
+ if (!node) {
14
+ return null;
15
+ }
16
+
17
+ if (node.type === "Literal" && typeof node.value === "string") {
18
+ return node.value;
19
+ }
20
+
21
+ if (node.type === "TemplateElement") {
22
+ return node.value.raw;
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ function reportIfPatternMatches(context, node, pattern, message) {
29
+ const value = stringValueFromNode(node);
30
+ if (!value || !matchesPattern(pattern, value)) {
31
+ return;
32
+ }
33
+
34
+ context.report({
35
+ node,
36
+ message,
37
+ });
38
+ }
39
+
40
+ const noDesignSystemBypassImports = {
41
+ meta: {
42
+ type: "problem",
43
+ schema: [],
44
+ },
45
+ create(context) {
46
+ return {
47
+ ImportDeclaration(node) {
48
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
49
+ return;
50
+ }
51
+
52
+ const source = node.source.value;
53
+ if (typeof source !== "string") {
54
+ return;
55
+ }
56
+
57
+ if (source.startsWith("@/components/ui/")) {
58
+ context.report({
59
+ node,
60
+ message:
61
+ "Use @/design-system/primitives/* instead of importing from @/components/ui/* in feature code.",
62
+ });
63
+ }
64
+
65
+ if (source.startsWith("@radix-ui/")) {
66
+ context.report({
67
+ node,
68
+ message:
69
+ "Radix imports are only allowed inside src/design-system; feature code must import public design-system primitives.",
70
+ });
71
+ }
72
+
73
+ if (
74
+ source === "@/design-system/tokens" ||
75
+ (source.startsWith("@/design-system/tokens/") &&
76
+ source !== "@/design-system/tokens/manifest")
77
+ ) {
78
+ context.report({
79
+ node,
80
+ message:
81
+ "Feature code cannot import raw token modules directly; use semantic classes or @/design-system/tokens/manifest.",
82
+ });
83
+ }
84
+ },
85
+ };
86
+ },
87
+ };
88
+
89
+ const noCvaOutsideDesignSystem = {
90
+ meta: {
91
+ type: "problem",
92
+ schema: [],
93
+ },
94
+ create(context) {
95
+ return {
96
+ ImportDeclaration(node) {
97
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
98
+ return;
99
+ }
100
+
101
+ if (node.source.value === "class-variance-authority") {
102
+ context.report({
103
+ node,
104
+ message:
105
+ "Feature code cannot import class-variance-authority directly; use recipes from src/design-system/recipes.",
106
+ });
107
+ }
108
+ },
109
+ };
110
+ },
111
+ };
112
+
113
+ const noRawDesignValues = {
114
+ meta: {
115
+ type: "problem",
116
+ schema: [],
117
+ },
118
+ create(context) {
119
+ return {
120
+ Literal(node) {
121
+ reportIfPatternMatches(
122
+ context,
123
+ node,
124
+ rawColorPattern,
125
+ "Raw color literals are forbidden outside src/design-system/tokens.",
126
+ );
127
+ },
128
+ TemplateElement(node) {
129
+ reportIfPatternMatches(
130
+ context,
131
+ node,
132
+ rawColorPattern,
133
+ "Raw color literals are forbidden outside src/design-system/tokens.",
134
+ );
135
+ },
136
+ };
137
+ },
138
+ };
139
+
140
+ const noBespokeVisualStacksInFeatures = {
141
+ meta: {
142
+ type: "problem",
143
+ schema: [],
144
+ },
145
+ create(context) {
146
+ return {
147
+ Literal(node) {
148
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
149
+ return;
150
+ }
151
+
152
+ reportIfPatternMatches(
153
+ context,
154
+ node,
155
+ arbitraryVisualUtilityPattern,
156
+ "Feature code cannot use arbitrary visual Tailwind utilities; use tokens or design-system recipes instead.",
157
+ );
158
+ },
159
+ TemplateElement(node) {
160
+ if (!isFeatureSurfaceFile(filenameOf(context))) {
161
+ return;
162
+ }
163
+
164
+ reportIfPatternMatches(
165
+ context,
166
+ node,
167
+ arbitraryVisualUtilityPattern,
168
+ "Feature code cannot use arbitrary visual Tailwind utilities; use tokens or design-system recipes instead.",
169
+ );
170
+ },
171
+ };
172
+ },
173
+ };
174
+
175
+ const designSystemPlugin = {
176
+ rules: {
177
+ "no-design-system-bypass-imports": noDesignSystemBypassImports,
178
+ "no-cva-outside-design-system": noCvaOutsideDesignSystem,
179
+ "no-raw-design-values": noRawDesignValues,
180
+ "no-bespoke-visual-stacks-in-features": noBespokeVisualStacksInFeatures,
181
+ },
182
+ };
183
+
184
+ export default designSystemPlugin;
@@ -0,0 +1,34 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { defineConfig, devices } from "@playwright/test";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const repoRoot = path.resolve(__dirname, "../..");
8
+
9
+ export default defineConfig({
10
+ testDir: __dirname,
11
+ testMatch: "visual.spec.ts",
12
+ fullyParallel: true,
13
+ retries: 0,
14
+ reporter: "line",
15
+ snapshotPathTemplate: "{testDir}/__screenshots__/{testFilePath}/{arg}{ext}",
16
+ use: {
17
+ baseURL: "http://127.0.0.1:6007",
18
+ trace: "on-first-retry",
19
+ },
20
+ webServer: {
21
+ command: "pnpm exec http-server ./storybook-static -p 6007 -s",
22
+ cwd: repoRoot,
23
+ url: "http://127.0.0.1:6007",
24
+ reuseExistingServer: !process.env.CI,
25
+ },
26
+ projects: [
27
+ {
28
+ name: "chromium",
29
+ use: {
30
+ ...devices["Desktop Chrome"],
31
+ },
32
+ },
33
+ ],
34
+ });
@@ -0,0 +1,22 @@
1
+ import {
2
+ buildTokenManifest,
3
+ checkImports,
4
+ checkStories,
5
+ checkValues,
6
+ } from "./checks.mjs";
7
+ import { parseCliArgs, resolveTargetFiles } from "./shared.mjs";
8
+
9
+ const options = parseCliArgs();
10
+ const targetFiles = resolveTargetFiles(options);
11
+
12
+ buildTokenManifest();
13
+
14
+ const violations = [
15
+ ...checkImports(targetFiles),
16
+ ...checkValues(targetFiles),
17
+ ...checkStories(targetFiles, options),
18
+ ];
19
+
20
+ if (violations.length > 0) {
21
+ process.exit(1);
22
+ }