minutework 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/EXTERNAL_ALPHA.md +74 -0
  2. package/README.md +57 -0
  3. package/assets/claude-local/CLAUDE.md.template +45 -0
  4. package/assets/claude-local/bundle.json +22 -0
  5. package/assets/claude-local/skills/README.md +6 -0
  6. package/assets/claude-local/skills/app-pack-authoring.md +8 -0
  7. package/assets/claude-local/skills/event-bus.md +8 -0
  8. package/assets/claude-local/skills/ontology-mapping.md +8 -0
  9. package/assets/claude-local/skills/openclaw-skill-importer.md +7 -0
  10. package/assets/claude-local/skills/schema-engine.md +8 -0
  11. package/assets/claude-local/skills/secrets-runtime-bridge.md +9 -0
  12. package/assets/claude-local/skills/sidecar-generation.md +9 -0
  13. package/assets/templates/fastapi-sidecar/.env.example +8 -0
  14. package/assets/templates/fastapi-sidecar/README.md +77 -0
  15. package/assets/templates/fastapi-sidecar/poetry.lock +757 -0
  16. package/assets/templates/fastapi-sidecar/pyproject.toml +42 -0
  17. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/__init__.py +3 -0
  18. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/auth.py +70 -0
  19. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/__init__.py +3 -0
  20. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/client.py +71 -0
  21. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/logging_utils.py +25 -0
  22. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +85 -0
  23. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/receipts.py +24 -0
  24. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/settings.py +41 -0
  25. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/template_validation.py +26 -0
  26. package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/worker.py +33 -0
  27. package/assets/templates/fastapi-sidecar/template.json +43 -0
  28. package/assets/templates/fastapi-sidecar/template.schema.json +160 -0
  29. package/assets/templates/fastapi-sidecar/tests/conftest.py +36 -0
  30. package/assets/templates/fastapi-sidecar/tests/test_app.py +39 -0
  31. package/assets/templates/fastapi-sidecar/tests/test_auth.py +32 -0
  32. package/assets/templates/fastapi-sidecar/tests/test_bridge_client.py +31 -0
  33. package/assets/templates/fastapi-sidecar/tests/test_materialization.py +55 -0
  34. package/assets/templates/fastapi-sidecar/tests/test_template_contract.py +49 -0
  35. package/assets/templates/fastapi-sidecar/tests/test_worker.py +7 -0
  36. package/assets/templates/fastapi-sidecar/tools/template/validate_template.py +20 -0
  37. package/assets/templates/next-tenant-app/.env.example +8 -0
  38. package/assets/templates/next-tenant-app/.storybook/main.ts +19 -0
  39. package/assets/templates/next-tenant-app/.storybook/preview.tsx +38 -0
  40. package/assets/templates/next-tenant-app/README.md +115 -0
  41. package/assets/templates/next-tenant-app/components.json +21 -0
  42. package/assets/templates/next-tenant-app/eslint.config.mjs +41 -0
  43. package/assets/templates/next-tenant-app/next-env.d.ts +6 -0
  44. package/assets/templates/next-tenant-app/next.config.ts +8 -0
  45. package/assets/templates/next-tenant-app/package-lock.json +9682 -0
  46. package/assets/templates/next-tenant-app/package.json +59 -0
  47. package/assets/templates/next-tenant-app/pnpm-lock.yaml +6062 -0
  48. package/assets/templates/next-tenant-app/postcss.config.mjs +8 -0
  49. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +90 -0
  50. package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +78 -0
  51. package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +31 -0
  52. package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +16 -0
  53. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +79 -0
  54. package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +40 -0
  55. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +42 -0
  56. package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +29 -0
  57. package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +26 -0
  58. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +40 -0
  59. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +47 -0
  60. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +43 -0
  61. package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +45 -0
  62. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +83 -0
  63. package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +30 -0
  64. package/assets/templates/next-tenant-app/src/app/app/layout.tsx +20 -0
  65. package/assets/templates/next-tenant-app/src/app/app/page.test.ts +62 -0
  66. package/assets/templates/next-tenant-app/src/app/app/page.tsx +24 -0
  67. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +70 -0
  68. package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +57 -0
  69. package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +42 -0
  70. package/assets/templates/next-tenant-app/src/app/blog/page.tsx +37 -0
  71. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +70 -0
  72. package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +55 -0
  73. package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +42 -0
  74. package/assets/templates/next-tenant-app/src/app/docs/page.tsx +37 -0
  75. package/assets/templates/next-tenant-app/src/app/globals.css +70 -0
  76. package/assets/templates/next-tenant-app/src/app/layout.tsx +69 -0
  77. package/assets/templates/next-tenant-app/src/app/login/page.test.ts +55 -0
  78. package/assets/templates/next-tenant-app/src/app/login/page.tsx +33 -0
  79. package/assets/templates/next-tenant-app/src/app/page.test.ts +56 -0
  80. package/assets/templates/next-tenant-app/src/app/page.tsx +35 -0
  81. package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +55 -0
  82. package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +35 -0
  83. package/assets/templates/next-tenant-app/src/app/providers.tsx +25 -0
  84. package/assets/templates/next-tenant-app/src/app/robots.test.ts +20 -0
  85. package/assets/templates/next-tenant-app/src/app/robots.ts +18 -0
  86. package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +49 -0
  87. package/assets/templates/next-tenant-app/src/app/sitemap.ts +54 -0
  88. package/assets/templates/next-tenant-app/src/components/ui/button.tsx +59 -0
  89. package/assets/templates/next-tenant-app/src/components/ui/input.tsx +21 -0
  90. package/assets/templates/next-tenant-app/src/design-system/docs/governance.mdx +26 -0
  91. package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.stories.tsx +48 -0
  92. package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.tsx +26 -0
  93. package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.stories.tsx +26 -0
  94. package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.tsx +35 -0
  95. package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
  96. package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
  97. package/assets/templates/next-tenant-app/src/design-system/primitives/button.stories.tsx +37 -0
  98. package/assets/templates/next-tenant-app/src/design-system/primitives/button.ts +1 -0
  99. package/assets/templates/next-tenant-app/src/design-system/primitives/input.stories.tsx +26 -0
  100. package/assets/templates/next-tenant-app/src/design-system/primitives/input.ts +1 -0
  101. package/assets/templates/next-tenant-app/src/design-system/recipes/chrome.ts +28 -0
  102. package/assets/templates/next-tenant-app/src/design-system/tokens/foundation.css +31 -0
  103. package/assets/templates/next-tenant-app/src/design-system/tokens/index.css +3 -0
  104. package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.json +85 -0
  105. package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.ts +87 -0
  106. package/assets/templates/next-tenant-app/src/design-system/tokens/semantic.css +105 -0
  107. package/assets/templates/next-tenant-app/src/design-system/tokens/theme.css +59 -0
  108. package/assets/templates/next-tenant-app/src/design-system/tokens/tokens.stories.tsx +71 -0
  109. package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +198 -0
  110. package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +153 -0
  111. package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +342 -0
  112. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +66 -0
  113. package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +108 -0
  114. package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +111 -0
  115. package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +111 -0
  116. package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +624 -0
  117. package/assets/templates/next-tenant-app/src/lib/app-routes.test.ts +20 -0
  118. package/assets/templates/next-tenant-app/src/lib/app-routes.ts +59 -0
  119. package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +189 -0
  120. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +318 -0
  121. package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +232 -0
  122. package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +339 -0
  123. package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +5 -0
  124. package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +96 -0
  125. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +75 -0
  126. package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +25 -0
  127. package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +170 -0
  128. package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +661 -0
  129. package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +131 -0
  130. package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +34 -0
  131. package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +102 -0
  132. package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +87 -0
  133. package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +33 -0
  134. package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +108 -0
  135. package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +20 -0
  136. package/assets/templates/next-tenant-app/src/lib/public-site.ts +49 -0
  137. package/assets/templates/next-tenant-app/src/lib/theme-config.ts +10 -0
  138. package/assets/templates/next-tenant-app/src/lib/theme.tsx +159 -0
  139. package/assets/templates/next-tenant-app/src/lib/utils.ts +6 -0
  140. package/assets/templates/next-tenant-app/template.json +27 -0
  141. package/assets/templates/next-tenant-app/template.schema.json +160 -0
  142. package/assets/templates/next-tenant-app/test/server-only-stub.ts +1 -0
  143. package/assets/templates/next-tenant-app/tools/design-system/build-token-manifest.mjs +3 -0
  144. package/assets/templates/next-tenant-app/tools/design-system/check-imports.mjs +9 -0
  145. package/assets/templates/next-tenant-app/tools/design-system/check-stories.mjs +9 -0
  146. package/assets/templates/next-tenant-app/tools/design-system/check-values.mjs +9 -0
  147. package/assets/templates/next-tenant-app/tools/design-system/checks.mjs +238 -0
  148. package/assets/templates/next-tenant-app/tools/design-system/eslint-plugin-design-system.mjs +184 -0
  149. package/assets/templates/next-tenant-app/tools/design-system/playwright.config.mjs +34 -0
  150. package/assets/templates/next-tenant-app/tools/design-system/run-checks.mjs +22 -0
  151. package/assets/templates/next-tenant-app/tools/design-system/shared.mjs +166 -0
  152. package/assets/templates/next-tenant-app/tools/design-system/visual.spec.ts +41 -0
  153. package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +39 -0
  154. package/assets/templates/next-tenant-app/tools/template/validate-template.mjs +45 -0
  155. package/assets/templates/next-tenant-app/tsconfig.json +42 -0
  156. package/assets/templates/next-tenant-app/vitest.config.ts +25 -0
  157. package/bin/minutework.js +40 -0
  158. package/dist/auth.d.ts +59 -0
  159. package/dist/auth.js +338 -0
  160. package/dist/auth.js.map +1 -0
  161. package/dist/browser.d.ts +1 -0
  162. package/dist/browser.js +26 -0
  163. package/dist/browser.js.map +1 -0
  164. package/dist/cli.d.ts +2 -0
  165. package/dist/cli.js +5 -0
  166. package/dist/cli.js.map +1 -0
  167. package/dist/compile.d.ts +20 -0
  168. package/dist/compile.js +121 -0
  169. package/dist/compile.js.map +1 -0
  170. package/dist/config.d.ts +25 -0
  171. package/dist/config.js +102 -0
  172. package/dist/config.js.map +1 -0
  173. package/dist/deploy-state.d.ts +35 -0
  174. package/dist/deploy-state.js +30 -0
  175. package/dist/deploy-state.js.map +1 -0
  176. package/dist/deploy.d.ts +22 -0
  177. package/dist/deploy.js +308 -0
  178. package/dist/deploy.js.map +1 -0
  179. package/dist/developer-client.d.ts +88 -0
  180. package/dist/developer-client.js +78 -0
  181. package/dist/developer-client.js.map +1 -0
  182. package/dist/index.d.ts +27 -0
  183. package/dist/index.js +290 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/init.d.ts +22 -0
  186. package/dist/init.js +421 -0
  187. package/dist/init.js.map +1 -0
  188. package/dist/launcher.d.ts +1 -0
  189. package/dist/launcher.js +50 -0
  190. package/dist/launcher.js.map +1 -0
  191. package/dist/paths.d.ts +12 -0
  192. package/dist/paths.js +33 -0
  193. package/dist/paths.js.map +1 -0
  194. package/dist/sandbox.d.ts +30 -0
  195. package/dist/sandbox.js +852 -0
  196. package/dist/sandbox.js.map +1 -0
  197. package/dist/state.d.ts +46 -0
  198. package/dist/state.js +82 -0
  199. package/dist/state.js.map +1 -0
  200. package/dist/tokens.d.ts +14 -0
  201. package/dist/tokens.js +293 -0
  202. package/dist/tokens.js.map +1 -0
  203. package/package.json +43 -0
@@ -0,0 +1,159 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useState,
9
+ type ReactNode,
10
+ } from "react";
11
+
12
+ import {
13
+ isThemeMode,
14
+ SYSTEM_THEME_QUERY,
15
+ THEME_COOKIE_NAME,
16
+ THEME_STORAGE_KEY,
17
+ type ResolvedTheme,
18
+ type ThemeMode,
19
+ } from "@/lib/theme-config";
20
+
21
+ type ThemeContextValue = {
22
+ theme: ThemeMode;
23
+ resolvedTheme: ResolvedTheme;
24
+ setTheme: (theme: ThemeMode) => void;
25
+ };
26
+
27
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
28
+
29
+ function getSystemTheme(): ResolvedTheme {
30
+ return window.matchMedia(SYSTEM_THEME_QUERY).matches ? "dark" : "light";
31
+ }
32
+
33
+ function resolveTheme(theme: ThemeMode, systemTheme: ResolvedTheme): ResolvedTheme {
34
+ return theme === "system" ? systemTheme : theme;
35
+ }
36
+
37
+ function applyThemeToDocument(resolvedTheme: ResolvedTheme, attribute: string) {
38
+ const root = document.documentElement;
39
+
40
+ if (attribute === "class") {
41
+ root.classList.remove("light", "dark");
42
+ root.classList.add(resolvedTheme);
43
+ } else {
44
+ root.setAttribute(attribute, resolvedTheme);
45
+ }
46
+
47
+ root.style.colorScheme = resolvedTheme;
48
+ }
49
+
50
+ function disableTransitionsTemporarily() {
51
+ const style = document.createElement("style");
52
+ style.appendChild(
53
+ document.createTextNode(
54
+ "*{-webkit-transition:none !important;transition:none !important}",
55
+ ),
56
+ );
57
+ document.head.appendChild(style);
58
+
59
+ return () => {
60
+ window.getComputedStyle(document.body);
61
+ window.setTimeout(() => {
62
+ style.remove();
63
+ }, 0);
64
+ };
65
+ }
66
+
67
+ export function ThemeProvider({
68
+ children,
69
+ attribute = "class",
70
+ defaultTheme = "system",
71
+ enableSystem = true,
72
+ disableTransitionOnChange = false,
73
+ }: {
74
+ children: ReactNode;
75
+ attribute?: string;
76
+ defaultTheme?: ThemeMode;
77
+ enableSystem?: boolean;
78
+ disableTransitionOnChange?: boolean;
79
+ }) {
80
+ const [theme, setThemeState] = useState<ThemeMode>(() => {
81
+ if (typeof window === "undefined") {
82
+ return defaultTheme;
83
+ }
84
+
85
+ try {
86
+ const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
87
+ if (isThemeMode(storedTheme)) {
88
+ return storedTheme;
89
+ }
90
+ } catch {}
91
+
92
+ return defaultTheme;
93
+ });
94
+ const [systemTheme, setSystemTheme] = useState<ResolvedTheme>(() => {
95
+ if (typeof window === "undefined") {
96
+ return "light";
97
+ }
98
+
99
+ return getSystemTheme();
100
+ });
101
+ const resolvedTheme = resolveTheme(
102
+ theme,
103
+ enableSystem ? systemTheme : ("light" as ResolvedTheme),
104
+ );
105
+
106
+ useEffect(() => {
107
+ const mediaQuery = window.matchMedia(SYSTEM_THEME_QUERY);
108
+ const syncSystemTheme = () => {
109
+ setSystemTheme(mediaQuery.matches ? "dark" : "light");
110
+ };
111
+
112
+ syncSystemTheme();
113
+ mediaQuery.addEventListener("change", syncSystemTheme);
114
+
115
+ return () => {
116
+ mediaQuery.removeEventListener("change", syncSystemTheme);
117
+ };
118
+ }, []);
119
+
120
+ useEffect(() => {
121
+ const restoreTransitions = disableTransitionOnChange
122
+ ? disableTransitionsTemporarily()
123
+ : null;
124
+
125
+ applyThemeToDocument(resolvedTheme, attribute);
126
+
127
+ if (restoreTransitions) {
128
+ restoreTransitions();
129
+ }
130
+ }, [attribute, disableTransitionOnChange, resolvedTheme]);
131
+
132
+ const value = useMemo<ThemeContextValue>(
133
+ () => ({
134
+ theme,
135
+ resolvedTheme,
136
+ setTheme: (nextTheme) => {
137
+ setThemeState(nextTheme);
138
+
139
+ try {
140
+ window.localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
141
+ document.cookie = `${THEME_COOKIE_NAME}=${nextTheme}; path=/; max-age=31536000; SameSite=Lax`;
142
+ } catch {}
143
+ },
144
+ }),
145
+ [resolvedTheme, theme],
146
+ );
147
+
148
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
149
+ }
150
+
151
+ export function useTheme() {
152
+ const context = useContext(ThemeContext);
153
+
154
+ if (!context) {
155
+ throw new Error("useTheme must be used within ThemeProvider");
156
+ }
157
+
158
+ return context;
159
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "template_id": "next-tenant-app",
3
+ "template_kind": "sidecar_nextjs_private",
4
+ "template_profile": "platform_session_bff",
5
+ "template_bundle_ref": "runtime/builder/templates/next-tenant-app",
6
+ "template_version": "0.2.0",
7
+ "materialize": {
8
+ "destination": "app"
9
+ },
10
+ "builder_edit_mode": "workspace_copy_only",
11
+ "seed_source": "apps/mw-next-client",
12
+ "required_bootstrap_steps": [
13
+ "next_typegen",
14
+ "design_system_tokens"
15
+ ],
16
+ "runtime_contract_refs": [
17
+ "reference/mwv3-dj6-docs/auth_and_credential_contract.md",
18
+ "reference/mwv3-dj6-docs/published_web_property_contract.md",
19
+ "reference/mwv3-dj6-docs/runtime_app_pack_contract.md",
20
+ "reference/mwv3-dj6-docs/runtime_compute_isolation_and_sandboxing_contract.md"
21
+ ],
22
+ "example_features": {
23
+ "runtime_command_demo": {
24
+ "default_enabled": false
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,160 @@
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
+ ]
29
+ },
30
+ "template_profile": {
31
+ "type": "string",
32
+ "enum": [
33
+ "platform_session_bff",
34
+ "bridge_internal"
35
+ ]
36
+ },
37
+ "template_version": {
38
+ "type": "string",
39
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
40
+ },
41
+ "materialize": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": [
45
+ "destination"
46
+ ],
47
+ "properties": {
48
+ "destination": {
49
+ "type": "string",
50
+ "const": "app"
51
+ }
52
+ }
53
+ },
54
+ "builder_edit_mode": {
55
+ "type": "string",
56
+ "const": "workspace_copy_only"
57
+ },
58
+ "seed_source": {
59
+ "type": "string",
60
+ "minLength": 1
61
+ },
62
+ "template_bundle_ref": {
63
+ "type": "string",
64
+ "minLength": 1
65
+ },
66
+ "required_bootstrap_steps": {
67
+ "type": "array",
68
+ "minItems": 1,
69
+ "uniqueItems": true,
70
+ "items": {
71
+ "type": "string",
72
+ "enum": [
73
+ "next_typegen",
74
+ "design_system_tokens",
75
+ "python_env_sync",
76
+ "python_import_smoke"
77
+ ]
78
+ }
79
+ },
80
+ "runtime_contract_refs": {
81
+ "type": "array",
82
+ "items": {
83
+ "type": "string",
84
+ "minLength": 1
85
+ }
86
+ },
87
+ "sidecar_processes": {
88
+ "type": "array",
89
+ "minItems": 1,
90
+ "items": {
91
+ "$ref": "#/$defs/sidecarProcess"
92
+ }
93
+ },
94
+ "example_features": {
95
+ "type": "object",
96
+ "additionalProperties": {
97
+ "$ref": "#/$defs/exampleFeature"
98
+ }
99
+ }
100
+ },
101
+ "$defs": {
102
+ "exampleFeature": {
103
+ "type": "object",
104
+ "additionalProperties": false,
105
+ "required": [
106
+ "default_enabled"
107
+ ],
108
+ "properties": {
109
+ "default_enabled": {
110
+ "type": "boolean"
111
+ }
112
+ }
113
+ },
114
+ "sidecarProcess": {
115
+ "type": "object",
116
+ "additionalProperties": false,
117
+ "required": [
118
+ "name",
119
+ "process_type",
120
+ "entrypoint",
121
+ "composition_profiles"
122
+ ],
123
+ "properties": {
124
+ "name": {
125
+ "type": "string",
126
+ "minLength": 1
127
+ },
128
+ "process_type": {
129
+ "type": "string",
130
+ "enum": [
131
+ "fastapi",
132
+ "worker",
133
+ "cron"
134
+ ]
135
+ },
136
+ "entrypoint": {
137
+ "type": "string",
138
+ "minLength": 1
139
+ },
140
+ "health_check_path": {
141
+ "type": "string",
142
+ "minLength": 1
143
+ },
144
+ "composition_profiles": {
145
+ "type": "array",
146
+ "minItems": 1,
147
+ "uniqueItems": true,
148
+ "items": {
149
+ "type": "string",
150
+ "enum": [
151
+ "worker_only",
152
+ "internal_api_only",
153
+ "worker_plus_internal_api"
154
+ ]
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
@@ -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
+ }