@ggui-ai/ui-gen 0.27.0 → 0.29.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 (315) hide show
  1. package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
  2. package/dist/adapters/base.js +2 -27
  3. package/dist/adapters/base.js.map +1 -1
  4. package/dist/adapters/generation-dispatch.d.ts +14 -6
  5. package/dist/adapters/generation-dispatch.js +40 -19095
  6. package/dist/adapters/generation-dispatch.js.map +1 -1
  7. package/dist/adapters/index.d.ts +1 -1
  8. package/dist/adapters/index.js +25 -3561
  9. package/dist/adapters/index.js.map +1 -1
  10. package/dist/adapters/types.js +2 -9
  11. package/dist/adapters/types.js.map +1 -1
  12. package/dist/advanced/index.d.ts +13 -12
  13. package/dist/advanced/index.js +63 -21789
  14. package/dist/advanced/index.js.map +1 -1
  15. package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
  16. package/dist/blueprint-validator.js +6 -2301
  17. package/dist/blueprint-validator.js.map +1 -1
  18. package/dist/boilerplate.d.ts +3 -2
  19. package/dist/boilerplate.js +6 -1867
  20. package/dist/boilerplate.js.map +1 -1
  21. package/dist/check/index.d.ts +3 -2
  22. package/dist/check/index.js +6 -1923
  23. package/dist/check/index.js.map +1 -1
  24. package/dist/chunk-33B3BAWS.js +24 -0
  25. package/dist/chunk-33B3BAWS.js.map +1 -0
  26. package/dist/chunk-3HSGA6NI.js +591 -0
  27. package/dist/chunk-3HSGA6NI.js.map +1 -0
  28. package/dist/chunk-3NNNGNTO.js +1707 -0
  29. package/dist/chunk-3NNNGNTO.js.map +1 -0
  30. package/dist/chunk-3T2HMTON.js +79 -0
  31. package/dist/chunk-3T2HMTON.js.map +1 -0
  32. package/dist/chunk-3ZSSDV3L.js +123 -0
  33. package/dist/chunk-3ZSSDV3L.js.map +1 -0
  34. package/dist/chunk-4MBR5GA3.js +99 -0
  35. package/dist/chunk-4MBR5GA3.js.map +1 -0
  36. package/dist/chunk-56NJFD55.js +337 -0
  37. package/dist/chunk-56NJFD55.js.map +1 -0
  38. package/dist/chunk-5AGTY555.js +26 -0
  39. package/dist/chunk-5AGTY555.js.map +1 -0
  40. package/dist/chunk-5EJM7EGY.js +115 -0
  41. package/dist/chunk-5EJM7EGY.js.map +1 -0
  42. package/dist/chunk-5KXCQLLR.js +529 -0
  43. package/dist/chunk-5KXCQLLR.js.map +1 -0
  44. package/dist/chunk-5VBXHVEH.js +171 -0
  45. package/dist/chunk-5VBXHVEH.js.map +1 -0
  46. package/dist/chunk-6KDDEING.js +515 -0
  47. package/dist/chunk-6KDDEING.js.map +1 -0
  48. package/dist/chunk-6VDRWAP6.js +123 -0
  49. package/dist/chunk-6VDRWAP6.js.map +1 -0
  50. package/dist/chunk-732IBFEJ.js +2400 -0
  51. package/dist/chunk-732IBFEJ.js.map +1 -0
  52. package/dist/chunk-73AURS3I.js +46 -0
  53. package/dist/chunk-73AURS3I.js.map +1 -0
  54. package/dist/chunk-77H5SKLX.js +8 -0
  55. package/dist/chunk-77H5SKLX.js.map +1 -0
  56. package/dist/chunk-7QE3AHLE.js +35 -0
  57. package/dist/chunk-7QE3AHLE.js.map +1 -0
  58. package/dist/chunk-A375NNWN.js +11 -0
  59. package/dist/chunk-A375NNWN.js.map +1 -0
  60. package/dist/chunk-AOU33EFS.js +140 -0
  61. package/dist/chunk-AOU33EFS.js.map +1 -0
  62. package/dist/chunk-BA4QYA6E.js +461 -0
  63. package/dist/chunk-BA4QYA6E.js.map +1 -0
  64. package/dist/chunk-DBP764GL.js +3 -0
  65. package/dist/chunk-DBP764GL.js.map +1 -0
  66. package/dist/chunk-DUU3ZJDQ.js +41 -0
  67. package/dist/chunk-DUU3ZJDQ.js.map +1 -0
  68. package/dist/chunk-DXKRBXNU.js +30 -0
  69. package/dist/chunk-DXKRBXNU.js.map +1 -0
  70. package/dist/chunk-FB7OEEAH.js +1467 -0
  71. package/dist/chunk-FB7OEEAH.js.map +1 -0
  72. package/dist/chunk-FLRNMZV2.js +18 -0
  73. package/dist/chunk-FLRNMZV2.js.map +1 -0
  74. package/dist/chunk-FWJIMKFZ.js +2032 -0
  75. package/dist/chunk-FWJIMKFZ.js.map +1 -0
  76. package/dist/chunk-FXRHWYIQ.js +163 -0
  77. package/dist/chunk-FXRHWYIQ.js.map +1 -0
  78. package/dist/chunk-G3PMV62Z.js +33 -0
  79. package/dist/chunk-G3PMV62Z.js.map +1 -0
  80. package/dist/chunk-GAJI7Y3P.js +13 -0
  81. package/dist/chunk-GAJI7Y3P.js.map +1 -0
  82. package/dist/chunk-GYINWMPR.js +2052 -0
  83. package/dist/chunk-GYINWMPR.js.map +1 -0
  84. package/dist/chunk-HD6ABIK7.js +484 -0
  85. package/dist/chunk-HD6ABIK7.js.map +1 -0
  86. package/dist/chunk-HDQVHYO6.js +100 -0
  87. package/dist/chunk-HDQVHYO6.js.map +1 -0
  88. package/dist/chunk-IHUT3SIW.js +7 -0
  89. package/dist/chunk-IHUT3SIW.js.map +1 -0
  90. package/dist/chunk-JDH5EGNZ.js +126 -0
  91. package/dist/chunk-JDH5EGNZ.js.map +1 -0
  92. package/dist/chunk-JMD4RGSV.js +37 -0
  93. package/dist/chunk-JMD4RGSV.js.map +1 -0
  94. package/dist/chunk-KJWGZL5B.js +33 -0
  95. package/dist/chunk-KJWGZL5B.js.map +1 -0
  96. package/dist/chunk-MKMDQFZ7.js +86 -0
  97. package/dist/chunk-MKMDQFZ7.js.map +1 -0
  98. package/dist/chunk-NF6JUZWF.js +85 -0
  99. package/dist/chunk-NF6JUZWF.js.map +1 -0
  100. package/dist/chunk-O2YC6XDC.js +1933 -0
  101. package/dist/chunk-O2YC6XDC.js.map +1 -0
  102. package/dist/chunk-OVLH5ICV.js +29 -0
  103. package/dist/chunk-OVLH5ICV.js.map +1 -0
  104. package/dist/chunk-PBOYHGSD.js +106 -0
  105. package/dist/chunk-PBOYHGSD.js.map +1 -0
  106. package/dist/chunk-PJFH7QWA.js +81 -0
  107. package/dist/chunk-PJFH7QWA.js.map +1 -0
  108. package/dist/chunk-PUH4CXGZ.js +284 -0
  109. package/dist/chunk-PUH4CXGZ.js.map +1 -0
  110. package/dist/chunk-QLXXPGTV.js +18 -0
  111. package/dist/chunk-QLXXPGTV.js.map +1 -0
  112. package/dist/chunk-R5GIGPW2.js +2845 -0
  113. package/dist/chunk-R5GIGPW2.js.map +1 -0
  114. package/dist/chunk-RH4IUSTO.js +83 -0
  115. package/dist/chunk-RH4IUSTO.js.map +1 -0
  116. package/dist/chunk-S2GVXKKY.js +153 -0
  117. package/dist/chunk-S2GVXKKY.js.map +1 -0
  118. package/dist/chunk-TVHGKFJS.js +58 -0
  119. package/dist/chunk-TVHGKFJS.js.map +1 -0
  120. package/dist/chunk-U7GALWLG.js +66 -0
  121. package/dist/chunk-U7GALWLG.js.map +1 -0
  122. package/dist/chunk-VTC263LT.js +256 -0
  123. package/dist/chunk-VTC263LT.js.map +1 -0
  124. package/dist/chunk-WEOXFEVE.js +739 -0
  125. package/dist/chunk-WEOXFEVE.js.map +1 -0
  126. package/dist/chunk-WYHP46LT.js +28 -0
  127. package/dist/chunk-WYHP46LT.js.map +1 -0
  128. package/dist/chunk-XGLO5LGG.js +274 -0
  129. package/dist/chunk-XGLO5LGG.js.map +1 -0
  130. package/dist/chunk-XRBCRKA2.js +29 -0
  131. package/dist/chunk-XRBCRKA2.js.map +1 -0
  132. package/dist/chunk-XW7ADFFV.js +340 -0
  133. package/dist/chunk-XW7ADFFV.js.map +1 -0
  134. package/dist/chunk-YIQ42IUX.js +34 -0
  135. package/dist/chunk-YIQ42IUX.js.map +1 -0
  136. package/dist/chunk-YJZTUY7B.js +101 -0
  137. package/dist/chunk-YJZTUY7B.js.map +1 -0
  138. package/dist/chunk-Z3CXME3K.js +107 -0
  139. package/dist/chunk-Z3CXME3K.js.map +1 -0
  140. package/dist/classifier/index.js +2 -527
  141. package/dist/classifier/index.js.map +1 -1
  142. package/dist/client-JBPCXU7W.js +4 -0
  143. package/dist/client-JBPCXU7W.js.map +1 -0
  144. package/dist/compose.js +3 -367
  145. package/dist/compose.js.map +1 -1
  146. package/dist/cost-tracker-HHGQ4FTW.js +67 -0
  147. package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
  148. package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
  149. package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
  150. package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
  151. package/dist/dist-UALN6ZVB.js +543 -0
  152. package/dist/dist-UALN6ZVB.js.map +1 -0
  153. package/dist/dupe-break-X6K7QFIS.js +4 -0
  154. package/dist/dupe-break-X6K7QFIS.js.map +1 -0
  155. package/dist/evaluation/axis-checks/index.d.ts +5 -4
  156. package/dist/evaluation/axis-checks/index.js +6 -1818
  157. package/dist/evaluation/axis-checks/index.js.map +1 -1
  158. package/dist/evaluation/axis-checks/registry.d.ts +3 -2
  159. package/dist/evaluation/axis-checks/registry.js +3 -1748
  160. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  161. package/dist/evaluation/index.d.ts +49 -132
  162. package/dist/evaluation/index.js +302 -6368
  163. package/dist/evaluation/index.js.map +1 -1
  164. package/dist/evaluation/types.d.ts +3 -2
  165. package/dist/evaluation/types.js +2 -5
  166. package/dist/evaluation/types.js.map +1 -1
  167. package/dist/fragments/index.js +2 -338
  168. package/dist/fragments/index.js.map +1 -1
  169. package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
  170. package/dist/harness/check/runtime-render/index.d.ts +35 -15
  171. package/dist/harness/check/runtime-render/index.js +6 -2390
  172. package/dist/harness/check/runtime-render/index.js.map +1 -1
  173. package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
  174. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  175. package/dist/harness/index.d.ts +3 -2
  176. package/dist/harness/index.js +23 -5124
  177. package/dist/harness/index.js.map +1 -1
  178. package/dist/harness/llm-trace-sink.js +2 -24
  179. package/dist/harness/llm-trace-sink.js.map +1 -1
  180. package/dist/harness/prompts.d.ts +2 -2
  181. package/dist/harness/prompts.js +8 -324
  182. package/dist/harness/prompts.js.map +1 -1
  183. package/dist/harness/result-types.d.ts +18 -6
  184. package/dist/harness/result-types.js +2 -0
  185. package/dist/harness/result-types.js.map +1 -1
  186. package/dist/harness/runtime.d.ts +22 -5
  187. package/dist/harness/runtime.js +10 -2313
  188. package/dist/harness/runtime.js.map +1 -1
  189. package/dist/harness/types-public.d.ts +3 -2
  190. package/dist/harness/types-public.js +16 -2954
  191. package/dist/harness/types-public.js.map +1 -1
  192. package/dist/harness/validator-trace-sink.d.ts +3 -2
  193. package/dist/harness/validator-trace-sink.js +2 -26
  194. package/dist/harness/validator-trace-sink.js.map +1 -1
  195. package/dist/hash.js +2 -39
  196. package/dist/hash.js.map +1 -1
  197. package/dist/hashline-5OKREIRM.js +4 -0
  198. package/dist/hashline-5OKREIRM.js.map +1 -0
  199. package/dist/index.d.ts +51 -12
  200. package/dist/index.js +57 -21850
  201. package/dist/index.js.map +1 -1
  202. package/dist/llm-evaluator-PN3WFCS4.js +653 -0
  203. package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
  204. package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
  205. package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
  206. package/dist/patch.d.ts +3 -2
  207. package/dist/patch.js +2 -56
  208. package/dist/patch.js.map +1 -1
  209. package/dist/policy-VCYKGSAC.js +5 -0
  210. package/dist/policy-VCYKGSAC.js.map +1 -0
  211. package/dist/policy.js +2 -35
  212. package/dist/policy.js.map +1 -1
  213. package/dist/primitive-index-2OF6F4TZ.js +131 -0
  214. package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
  215. package/dist/primitive-slice-WTMBLYFJ.js +235 -0
  216. package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
  217. package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
  218. package/dist/provider-adapter-contract.js +10407 -14204
  219. package/dist/provider-adapter-contract.js.map +1 -1
  220. package/dist/provider-adapter.js +2 -44
  221. package/dist/provider-adapter.js.map +1 -1
  222. package/dist/providers/index.d.ts +89 -1
  223. package/dist/providers/index.js +115 -44
  224. package/dist/providers/index.js.map +1 -1
  225. package/dist/raw-C6KBH5I3.js +141 -0
  226. package/dist/raw-C6KBH5I3.js.map +1 -0
  227. package/dist/raw-FXSBXQTZ.js +7 -0
  228. package/dist/raw-FXSBXQTZ.js.map +1 -0
  229. package/dist/raw-MURDUPGK.js +8 -0
  230. package/dist/raw-MURDUPGK.js.map +1 -0
  231. package/dist/raw-T3GW3UVX.js +8 -0
  232. package/dist/raw-T3GW3UVX.js.map +1 -0
  233. package/dist/render-check-host-ZNIS6BJ5.js +5 -0
  234. package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
  235. package/dist/sdk-4JNSDNSR.js +6 -0
  236. package/dist/sdk-4JNSDNSR.js.map +1 -0
  237. package/dist/sdk-DIVJMHCX.js +7 -0
  238. package/dist/sdk-DIVJMHCX.js.map +1 -0
  239. package/dist/sdk-QRJOCRTS.js +7 -0
  240. package/dist/sdk-QRJOCRTS.js.map +1 -0
  241. package/dist/tag-balance-CTHCTRJ7.js +123 -0
  242. package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
  243. package/dist/tools/get-primitives-ts.d.ts +1 -1
  244. package/dist/tools/get-primitives-ts.js +3 -1
  245. package/dist/tools/get-primitives-ts.js.map +1 -1
  246. package/dist/tools/render-check-worker.js +3 -40
  247. package/dist/tools/render-check-worker.js.map +1 -1
  248. package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
  249. package/dist/validation/index.d.ts +2 -2
  250. package/dist/validation/index.js +3 -498
  251. package/dist/validation/index.js.map +1 -1
  252. package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
  253. package/dist/visual-evaluator-TGEXYQTP.js +12 -0
  254. package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
  255. package/dist/workflows.d.ts +3 -2
  256. package/dist/workflows.js +2 -151
  257. package/dist/workflows.js.map +1 -1
  258. package/package.json +21 -17
  259. package/src/adapters/claude/sdk.ts +4 -2
  260. package/src/adapters/claude/tool-bridge.ts +2 -1
  261. package/src/adapters/generation-dispatch.ts +9 -0
  262. package/src/adapters/openai/raw.ts +4 -0
  263. package/src/adapters/openai/sdk.ts +6 -1
  264. package/src/adapters/openai/tokens.ts +21 -7
  265. package/src/adapters/provider-router.ts +1 -0
  266. package/src/adapters/tool-bridge.ts +1 -1
  267. package/src/adapters/tools.ts +1 -1
  268. package/src/advanced/feedback.ts +1 -2
  269. package/src/advanced/generator.ts +4 -3
  270. package/src/boilerplate/free-design-prompt.ts +7 -0
  271. package/src/boilerplate/generate.ts +18 -5
  272. package/src/boilerplate/hard-sections.ts +8 -3
  273. package/src/boilerplate/react-hooks.ts +56 -0
  274. package/src/boilerplate/system-prompt.ts +12 -3
  275. package/src/check/run-tier0.ts +233 -8
  276. package/src/check/type-checker.ts +128 -16
  277. package/src/coding-agent/tools.ts +26 -3
  278. package/src/create-ui-generator.ts +4 -2
  279. package/src/design-mode.ts +4 -9
  280. package/src/evaluation/axis-checks/checks/state-merge.ts +3 -5
  281. package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
  282. package/src/evaluation/axis-checks/checks/tint.ts +197 -0
  283. package/src/evaluation/axis-checks/helpers.ts +246 -0
  284. package/src/evaluation/axis-checks/registry.ts +2 -0
  285. package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
  286. package/src/evaluation/criteria/bank.ts +1 -1
  287. package/src/evaluation/criteria/edge-instruments.ts +14 -0
  288. package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
  289. package/src/evaluation/criteria/edge-v2.ts +242 -0
  290. package/src/evaluation/criteria/resolve.ts +24 -5
  291. package/src/evaluation/evaluator.ts +2 -2
  292. package/src/evaluation/index.ts +3 -5
  293. package/src/evaluation/llm-evaluator.ts +2 -2
  294. package/src/evaluation/mcp-server.ts +0 -50
  295. package/src/evaluation/types-public.ts +6 -1
  296. package/src/evaluation/visual-evaluator.ts +162 -31
  297. package/src/harness/check/runtime-render/index.ts +28 -4
  298. package/src/harness/check/runtime-render/render-check.ts +20 -21
  299. package/src/harness/claude-agent-sdk-loader.ts +93 -0
  300. package/src/harness/claude-code-login-query.ts +1 -1
  301. package/src/harness/coding/assemble-result.ts +1 -0
  302. package/src/harness/coding/dupe-break.ts +70 -0
  303. package/src/harness/coding/generate-task-runner.ts +6 -1
  304. package/src/harness/coding/run-coding-turn.ts +59 -1
  305. package/src/harness/coding/run-eval-round.ts +9 -0
  306. package/src/harness/llm-router.ts +50 -2
  307. package/src/harness/result-types.ts +12 -0
  308. package/src/harness/runtime.ts +17 -0
  309. package/src/index.ts +10 -1
  310. package/src/providers/anthropic-tool-call.ts +263 -0
  311. package/src/providers/index.ts +13 -0
  312. package/src/tools/get-primitives-ts.ts +1 -1
  313. package/src/tools/render-check.ts +4 -4
  314. package/src/validation/primitives.ts +2 -1
  315. package/src/evaluation/loop.ts +0 -218
@@ -0,0 +1,2032 @@
1
+ import { isAllowedImport, describeAllowedImports } from './chunk-33B3BAWS.js';
2
+ import { jsonSchemaTypeToTs, BOILERPLATE_REACT_HOOKS, BOILERPLATE_REACT_IMPORT, taughtTokenManifest } from './chunk-Z3CXME3K.js';
3
+ import { priorityForIssue } from './chunk-PUH4CXGZ.js';
4
+ import { DEFAULT_DESIGN_MODE } from './chunk-3T2HMTON.js';
5
+ import ts4 from 'typescript';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { createRequire } from 'module';
9
+ import { TINT_STEPS } from '@ggui-ai/design';
10
+ import { Linter } from 'eslint';
11
+ import { listContractGadgets, HOOK_NAME_RE } from '@ggui-ai/protocol';
12
+ import { consumedTokenManifest } from '@ggui-ai/design/themes';
13
+
14
+ var HOOK_TO_KIND = {
15
+ useAction: "action",
16
+ useStream: "stream",
17
+ // `useGguiContext('slot')` is the client→agent observable-state
18
+ // hook. Treated as a wire call site for tier-0 preservation +
19
+ // undeclared detection because the runtime registers one
20
+ // React.Context per declared contextSpec slot at boot — referencing
21
+ // an undeclared slot throws synchronously at first paint.
22
+ useGguiContext: "context"
23
+ // Pre-rename component-side tool hooks are retired. The contract's
24
+ // `agentCapabilities.tools` catalog is agent-side declaration only —
25
+ // no component hook surface. Cross-refs surface via
26
+ // `actionSpec[*].nextStep` (already covered by the `action` kind)
27
+ // and `streamSpec[*].source.tool` (covered by `stream`).
28
+ };
29
+ var ALL_WIRE_HOOKS = Object.keys(HOOK_TO_KIND);
30
+ function extractWireCallSites(code) {
31
+ const sf = ts4.createSourceFile(
32
+ "component.tsx",
33
+ code,
34
+ ts4.ScriptTarget.Latest,
35
+ /*setParentNodes*/
36
+ true,
37
+ ts4.ScriptKind.TSX
38
+ );
39
+ const sites = [];
40
+ function visit(node) {
41
+ if (ts4.isCallExpression(node)) {
42
+ const callee = node.expression;
43
+ if (ts4.isIdentifier(callee)) {
44
+ const kind = HOOK_TO_KIND[callee.text];
45
+ if (kind) {
46
+ const firstArg = node.arguments[0];
47
+ if (firstArg && ts4.isStringLiteral(firstArg)) {
48
+ sites.push({ kind, name: firstArg.text });
49
+ }
50
+ }
51
+ }
52
+ }
53
+ ts4.forEachChild(node, visit);
54
+ }
55
+ visit(sf);
56
+ return sites;
57
+ }
58
+ function extractWireImports(code) {
59
+ const sf = ts4.createSourceFile(
60
+ "component.tsx",
61
+ code,
62
+ ts4.ScriptTarget.Latest,
63
+ /*setParentNodes*/
64
+ true,
65
+ ts4.ScriptKind.TSX
66
+ );
67
+ const imported = /* @__PURE__ */ new Set();
68
+ for (const stmt of sf.statements) {
69
+ if (!ts4.isImportDeclaration(stmt)) continue;
70
+ const moduleSpecifier = stmt.moduleSpecifier;
71
+ if (!ts4.isStringLiteral(moduleSpecifier)) continue;
72
+ if (moduleSpecifier.text !== "@ggui-ai/wire") continue;
73
+ const clause = stmt.importClause;
74
+ if (!clause) continue;
75
+ const bindings = clause.namedBindings;
76
+ if (!bindings) continue;
77
+ if (!ts4.isNamedImports(bindings)) continue;
78
+ for (const el of bindings.elements) {
79
+ imported.add(el.name.text);
80
+ }
81
+ }
82
+ return imported;
83
+ }
84
+ function collectExpectedWires(contract) {
85
+ const expected = [];
86
+ const actionsMap = contract.actionSpec ?? {};
87
+ for (const name of Object.keys(actionsMap)) {
88
+ expected.push({ kind: "action", name });
89
+ }
90
+ const streamsMap = contract.streamSpec ?? {};
91
+ for (const name of Object.keys(streamsMap)) {
92
+ expected.push({ kind: "stream", name });
93
+ }
94
+ const contextMap = contract.contextSpec ?? {};
95
+ for (const name of Object.keys(contextMap)) {
96
+ expected.push({ kind: "context", name });
97
+ }
98
+ return expected;
99
+ }
100
+ function checkWirePreservation(code, contract) {
101
+ const expected = collectExpectedWires(contract);
102
+ const actual = extractWireCallSites(code);
103
+ const actualKeys = new Set(actual.map((s) => `${s.kind}:${s.name}`));
104
+ const expectedKeys = new Set(expected.map((s) => `${s.kind}:${s.name}`));
105
+ const missing = expected.filter((s) => !actualKeys.has(`${s.kind}:${s.name}`));
106
+ const extra = actual.filter((s) => !expectedKeys.has(`${s.kind}:${s.name}`));
107
+ return { missing, extra };
108
+ }
109
+ function checkWireImports(code) {
110
+ const used = extractWireCallSites(code);
111
+ const imports = extractWireImports(code);
112
+ const usedHookSet = new Set(
113
+ used.map(
114
+ (s) => (
115
+ // Reverse the kind → hook-name lookup. Closed set of 5 —
116
+ // hardcoded to stay cheap and explicit.
117
+ {
118
+ action: "useAction",
119
+ stream: "useStream",
120
+ context: "useGguiContext"
121
+ }[s.kind]
122
+ )
123
+ )
124
+ );
125
+ const missing = [];
126
+ for (const hook of ALL_WIRE_HOOKS) {
127
+ if (!usedHookSet.has(hook)) continue;
128
+ if (imports.has(hook)) continue;
129
+ missing.push({ hook, kind: HOOK_TO_KIND[hook] });
130
+ }
131
+ return { missing };
132
+ }
133
+ function extractPropsInterface(code) {
134
+ const sf = ts4.createSourceFile("component.tsx", code, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
135
+ const props = [];
136
+ function visitMembers(members) {
137
+ for (const member of members) {
138
+ if (ts4.isPropertySignature(member) && member.name) {
139
+ const name = member.name.getText(sf);
140
+ const type = member.type ? member.type.getText(sf) : "unknown";
141
+ const optional = !!member.questionToken;
142
+ props.push({ name, type, optional });
143
+ }
144
+ }
145
+ }
146
+ function visit(node) {
147
+ if (ts4.isInterfaceDeclaration(node) && node.name.text === "Props") {
148
+ visitMembers(node.members);
149
+ return;
150
+ }
151
+ if (ts4.isTypeAliasDeclaration(node) && node.name.text === "Props") {
152
+ if (ts4.isTypeLiteralNode(node.type)) {
153
+ visitMembers(node.type.members);
154
+ }
155
+ return;
156
+ }
157
+ ts4.forEachChild(node, visit);
158
+ }
159
+ visit(sf);
160
+ return props.length > 0 ? props : null;
161
+ }
162
+ function validatePropsAgainstSchema(code, spec) {
163
+ const issues = [];
164
+ const extracted = extractPropsInterface(code);
165
+ if (!extracted) {
166
+ return [];
167
+ }
168
+ const extractedMap = new Map(extracted.map((p) => [p.name, p]));
169
+ for (const [propName, entry] of Object.entries(spec.properties)) {
170
+ const extractedProp = extractedMap.get(propName);
171
+ if (!extractedProp) {
172
+ if (entry.required) {
173
+ issues.push({
174
+ severity: "error",
175
+ field: propName,
176
+ message: `Props interface is missing required field '${propName}' from the data contract`,
177
+ fix: `Add \`${propName}${entry.required ? "" : "?"}: ${jsonSchemaTypeToTs(entry.schema)}\` to your Props interface`
178
+ });
179
+ } else {
180
+ issues.push({
181
+ severity: "warning",
182
+ field: propName,
183
+ message: `Props interface is missing optional field '${propName}' from the data contract`,
184
+ fix: `Consider adding \`${propName}?: ${jsonSchemaTypeToTs(entry.schema)}\` to your Props interface`
185
+ });
186
+ }
187
+ continue;
188
+ }
189
+ if (extractedProp.type !== "unknown") {
190
+ const expectedTsType = jsonSchemaTypeToTs(entry.schema);
191
+ if (!isTypeCompatible(extractedProp.type, expectedTsType, entry.schema)) {
192
+ issues.push({
193
+ severity: "warning",
194
+ field: propName,
195
+ message: `Field '${propName}' has type '${extractedProp.type}' but contract expects '${expectedTsType}'`,
196
+ fix: `Change \`${propName}\` type to \`${expectedTsType}\``
197
+ });
198
+ }
199
+ }
200
+ }
201
+ return issues;
202
+ }
203
+ function isTypeCompatible(tsType, expectedTsType, schema) {
204
+ const normalized = tsType.replace(/\s/g, "").toLowerCase();
205
+ switch (schema.type) {
206
+ case "string":
207
+ return normalized.includes("string");
208
+ case "number":
209
+ case "integer":
210
+ return normalized.includes("number");
211
+ case "boolean":
212
+ return normalized.includes("boolean");
213
+ case "array":
214
+ return normalized.includes("[]") || normalized.includes("array");
215
+ case "object":
216
+ return !["string", "number", "boolean"].some((t) => normalized === t);
217
+ default:
218
+ return true;
219
+ }
220
+ }
221
+ function propsSpecToTypeScript(spec, indent = 2) {
222
+ const pad = " ".repeat(indent);
223
+ const lines = [];
224
+ for (const [propName, entry] of Object.entries(spec.properties)) {
225
+ if (entry.description) {
226
+ lines.push(`${pad}/** ${entry.description} */`);
227
+ }
228
+ const optional = !entry.required;
229
+ const tsType = jsonSchemaTypeToTs(entry.schema);
230
+ const defaultStr = entry.default !== void 0 ? ` // default: ${JSON.stringify(entry.default)}` : "";
231
+ lines.push(`${pad}${propName}${optional ? "?" : ""}: ${tsType};${defaultStr}`);
232
+ if (entry.example !== void 0) {
233
+ const exampleStr = JSON.stringify(entry.example, null, 2).split("\n").map((l, i) => i === 0 ? `${pad}// Example: ${l}` : `${pad}// ${l}`).join("\n");
234
+ lines.push(exampleStr);
235
+ }
236
+ }
237
+ return `{
238
+ ${lines.join("\n")}
239
+ }`;
240
+ }
241
+ function validateStreamSpecConformance(code, spec) {
242
+ const issues = [];
243
+ const usesWireStream = code.includes("useStream");
244
+ if (!usesWireStream) {
245
+ issues.push({
246
+ severity: "error",
247
+ field: "__streamSpec__",
248
+ message: "Component does not handle stream events \u2014 use useStream() from the boilerplate wire hooks",
249
+ fix: "Use the useStream('eventName') hook from the boilerplate to receive real-time data from the agent"
250
+ });
251
+ return issues;
252
+ }
253
+ for (const [channelName, entry] of Object.entries(spec)) {
254
+ if (!code.includes(`'${channelName}'`) && !code.includes(`"${channelName}"`)) {
255
+ issues.push({
256
+ severity: "warning",
257
+ field: channelName,
258
+ message: `StreamSpec declares channel '${channelName}' but component doesn't reference it`,
259
+ fix: `Add useStream('${channelName}') to receive ${entry.description || channelName} events`
260
+ });
261
+ }
262
+ }
263
+ return issues;
264
+ }
265
+ function validateActionSpecConformance(code, spec) {
266
+ const issues = [];
267
+ for (const [actionId, entry] of Object.entries(spec)) {
268
+ const idReferenced = code.includes(`'${actionId}'`) || code.includes(`"${actionId}"`);
269
+ const labelReferenced = code.includes(entry.label);
270
+ const callbackName = `on${actionId.charAt(0).toUpperCase()}${actionId.slice(1)}`;
271
+ const callbackReferenced = code.includes(callbackName);
272
+ if (!idReferenced && !labelReferenced && !callbackReferenced) {
273
+ issues.push({
274
+ severity: "error",
275
+ field: actionId,
276
+ message: `ActionSpec declares action '${actionId}' ("${entry.label}") but component doesn't wire it`,
277
+ fix: `Use the useAction('${actionId}') hook from the boilerplate and wire it to a button or form submit`
278
+ });
279
+ }
280
+ }
281
+ return issues;
282
+ }
283
+ function validateAllContracts(code, contract) {
284
+ const issues = [];
285
+ if (contract.propsSpec) {
286
+ issues.push(...validatePropsAgainstSchema(code, contract.propsSpec));
287
+ }
288
+ if (contract.streamSpec) {
289
+ issues.push(...validateStreamSpecConformance(code, contract.streamSpec));
290
+ }
291
+ if (contract.actionSpec) {
292
+ issues.push(...validateActionSpecConformance(code, contract.actionSpec));
293
+ }
294
+ return issues;
295
+ }
296
+ function inferPropsSpecFromSampleData(data) {
297
+ const properties = {};
298
+ for (const [key, value] of Object.entries(data)) {
299
+ properties[key] = {
300
+ schema: inferJsonSchema(value),
301
+ required: true,
302
+ example: value
303
+ };
304
+ }
305
+ return { properties };
306
+ }
307
+ function inferJsonSchema(value) {
308
+ if (value === null || value === void 0) return { type: "null" };
309
+ if (typeof value === "string") return { type: "string" };
310
+ if (typeof value === "number") return { type: "number" };
311
+ if (typeof value === "boolean") return { type: "boolean" };
312
+ if (Array.isArray(value)) {
313
+ if (value.length === 0) return { type: "array" };
314
+ return {
315
+ type: "array",
316
+ items: inferJsonSchema(value[0])
317
+ };
318
+ }
319
+ if (typeof value === "object") {
320
+ const properties = {};
321
+ const required = [];
322
+ for (const [k, v] of Object.entries(value)) {
323
+ properties[k] = inferJsonSchema(v);
324
+ required.push(k);
325
+ }
326
+ return { type: "object", properties, required };
327
+ }
328
+ return { type: "string" };
329
+ }
330
+ var BLOCKING_CODES = /* @__PURE__ */ new Set([
331
+ 2304,
332
+ // Cannot find name
333
+ // 2307 (Cannot find module) is NOT blocking: a third-party gadget package
334
+ // with no `.d.ts` overlay can't be resolved by design, and forbidden imports
335
+ // are caught by runSelfChecks regex instead.
336
+ 2305,
337
+ // Module has no exported member
338
+ 2322,
339
+ // Type not assignable
340
+ 2820,
341
+ // Type not assignable — the "did you mean '…'?" form of 2322, for a near-miss literal (ggui#1849)
342
+ 2339,
343
+ // Property does not exist on type
344
+ 2741,
345
+ // Missing required property
346
+ 2769,
347
+ // No overload matches this call
348
+ 17004,
349
+ // Cannot use JSX unless '--jsx' flag
350
+ 18047,
351
+ // 'X' is possibly 'null' — causes runtime crash
352
+ 18048
353
+ // 'X' is possibly 'undefined' — causes runtime crash
354
+ ]);
355
+ var PROP_INTENT_ALIASES = { spacing: "gap", space: "gap", gutter: "gap", color: "tone" };
356
+ function unknownPropFix(component, prop, accepted, sourceHint, native = false) {
357
+ const meant = PROP_INTENT_ALIASES[prop];
358
+ const named = meant !== void 0 && accepted.includes(meant) ? ` For what \`${prop}\` does in other kits, \`<${component}>\` takes \`${meant}\`.` : "";
359
+ return `\`<${component}>\` takes no \`${prop}\` prop.${named} Its props: ${accepted.map((a) => `\`${a}\``).join(", ")}${native ? ", plus the native HTML attributes" : ""}. Use one of these or drop the prop.${sourceHint}`;
360
+ }
361
+ function acceptedPropsAt(program, file, pos) {
362
+ const find = (node2) => pos >= node2.getStart(file) && pos < node2.getEnd() ? ts4.forEachChild(node2, find) ?? node2 : void 0;
363
+ let node = find(file);
364
+ while (node !== void 0 && !ts4.isJsxOpeningElement(node) && !ts4.isJsxSelfClosingElement(node)) node = node.parent;
365
+ if (node === void 0) return void 0;
366
+ const checker = program.getTypeChecker();
367
+ const tagType = checker.getTypeAtLocation(node.tagName);
368
+ const signature = [...tagType.getCallSignatures(), ...tagType.getConstructSignatures()][0];
369
+ const param = signature?.getParameters()[0];
370
+ if (param === void 0) return void 0;
371
+ const propsType = checker.getTypeOfSymbolAtLocation(param, node);
372
+ const listed = checker.getPropertiesOfType(propsType).filter((p) => !["children", "key", "ref"].includes(p.getName()) && !p.getName().startsWith("aria-") && !p.getName().startsWith("data-"));
373
+ const fromReact = (p) => (p.getDeclarations() ?? []).every((d) => /[\\/]@types[\\/]react[\\/]/.test(d.getSourceFile().fileName));
374
+ const own = listed.filter((p) => !fromReact(p));
375
+ const accepted = own.map((p) => p.getName()).sort();
376
+ return accepted.length > 0 ? { component: node.tagName.getText(file), accepted, native: own.length < listed.length } : void 0;
377
+ }
378
+ function generateFix(code, message, sourceLine) {
379
+ const sourceHint = sourceLine && sourceLine.length > 0 && sourceLine.length <= 140 ? ` Offending line: \`${sourceLine}\`` : "";
380
+ if ((code === 2322 || code === 2339) && message && /onClick|onDoubleClick|onMouseEnter|onMouseLeave|onPress/.test(message)) {
381
+ return `This structural primitive has no event handlers of its own \u2014 add the trait as a PROP: as={Clickable} (then onClick works), imported from @ggui-ai/design. Do NOT wrap it in <Clickable>\u2026</Clickable>; as is a prop, not a wrapper element.${sourceHint}`;
382
+ }
383
+ if (code === 2339 && message && /Property '_[a-zA-Z]/.test(message)) {
384
+ return `You renamed a prop with a leading underscore to silence \`no-unused-vars\`, but the prop on \`Props\` doesn't have that prefix. Restore the original name; better, don't destructure props you won't render \u2014 access them via \`props.fieldName\` only when needed.${sourceHint}`;
385
+ }
386
+ const missing = code === 2304 && message !== void 0 ? /Cannot find name '([^']+)'/.exec(message)?.[1] : void 0;
387
+ if (missing !== void 0 && BOILERPLATE_REACT_HOOKS.some((h) => h === missing)) {
388
+ return `\`${missing}\` is a React hook, and the boilerplate's React import lists it: \`${BOILERPLATE_REACT_IMPORT}\` is a DO NOT EDIT line. This patch dropped or rewrote it. Restore that line exactly as the boilerplate wrote it, at the top of the file; do not import the hook a second way.${sourceHint}`;
389
+ }
390
+ if (code === 2304 && message && !message.includes("module") && !message.includes("import")) {
391
+ return `This name is not defined in scope. Either you destructured props (use \`props.fieldName\` directly instead) OR you removed a helper declaration in this patch but kept a JSX/expression reference to it. Read your full patch and either restore the declaration or remove the reference.${sourceHint}`;
392
+ }
393
+ const target = message === void 0 ? void 0 : /is not assignable to type '(.*)/.exec(message)?.[1];
394
+ if ((code === 2322 || code === 2820) && target !== void 0 && target.startsWith('"') && sourceLine && /(?<![\w-])surface=/.test(sourceLine) && !TINT_STEPS.some((s) => target.includes(`"${s}"`))) {
395
+ return `\`surface\` takes a layout slot only, one of the values the message lists. To colour by a data category (series, lanes, kinds, element groups), set \`tint\` instead, on the same Box or Card: ${TINT_STEPS.map((s) => `tint="${s}"`).join(", ")}. When the value comes from a map or a helper, type it as the tint union (${TINT_STEPS.map((s) => `'${s}'`).join(" | ")}), not \`string\`. Never cast it.${sourceHint}`;
396
+ }
397
+ if ((code === 2322 || code === 2769) && message && /unknown.*ReactNode|ReactNode.*unknown/.test(message)) {
398
+ return `This value has type 'unknown' and cannot be rendered in JSX. Cast it: String(value) or add a type annotation.${sourceHint}`;
399
+ }
400
+ switch (code) {
401
+ case 2307:
402
+ return `Only these imports are allowed: ${describeAllowedImports()}${sourceHint}`;
403
+ case 2322:
404
+ case 2820:
405
+ case 2769:
406
+ return `Type mismatch on this expression. Check the offending line below \u2014 the prop name in JSX is what TypeScript is rejecting; the message tells you the expected type.${sourceHint}`;
407
+ case 2339:
408
+ return `This prop doesn't exist on this component \u2014 check the available props on the component's interface (visible at the top of the file, or in the design-system reference).${sourceHint}`;
409
+ case 2305:
410
+ return `This name is not defined. Check your imports and variable declarations.${sourceHint}`;
411
+ case 2741:
412
+ return `A required prop is missing. Check the component's Props interface.${sourceHint}`;
413
+ case 18047:
414
+ case 18048:
415
+ return `This value might be null/undefined. Every dereference on the same nullable still needs \`?.\` \u2014 \`x?.foo && x?.bar\`, NOT \`x?.foo && x.bar\`. Or hoist: \`const v = x; if (!v) return null;\` then access \`v.foo\`/\`v.bar\` unguarded.${sourceHint}`;
416
+ default:
417
+ return `Review the TypeScript error and fix the type issue.${sourceHint}`;
418
+ }
419
+ }
420
+ var vfsCache = null;
421
+ var CSSTYPE_VFS_PATH = "node_modules/csstype/index.d.ts";
422
+ function parseAndStore(vfs, virtualPath, content) {
423
+ const sourceFile = ts4.createSourceFile(
424
+ virtualPath,
425
+ content,
426
+ ts4.ScriptTarget.ES2020,
427
+ true,
428
+ virtualPath.endsWith(".tsx") ? ts4.ScriptKind.TSX : ts4.ScriptKind.TS
429
+ );
430
+ vfs.set(virtualPath, { content, sourceFile });
431
+ }
432
+ function walkDir(dir, filter) {
433
+ const results = [];
434
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
435
+ const full = path.join(dir, entry.name);
436
+ if (entry.isDirectory()) {
437
+ results.push(...walkDir(full, filter));
438
+ } else if (filter(full)) {
439
+ results.push(full);
440
+ }
441
+ }
442
+ return results;
443
+ }
444
+ function findReactTypesDir() {
445
+ const lookup = createRequire(import.meta.url).resolve.paths("@types/react") ?? [];
446
+ for (const dir of lookup) {
447
+ const candidate = path.join(dir, "@types", "react");
448
+ if (fs.existsSync(path.join(candidate, "index.d.ts"))) return candidate;
449
+ }
450
+ return null;
451
+ }
452
+ function findCsstypeDir(reactDir) {
453
+ const lookup = createRequire(path.join(fs.realpathSync(reactDir), "index.d.ts")).resolve.paths("csstype") ?? [];
454
+ for (const dir of lookup) {
455
+ const candidate = path.join(dir, "csstype");
456
+ if (fs.existsSync(path.join(candidate, "index.d.ts"))) return candidate;
457
+ }
458
+ return null;
459
+ }
460
+ function findPackageDistDir(pkg) {
461
+ try {
462
+ const pkgJson = createRequire(import.meta.url).resolve(`${pkg}/package.json`);
463
+ const dist = path.join(path.dirname(pkgJson), "dist");
464
+ return fs.existsSync(dist) ? dist : null;
465
+ } catch {
466
+ return null;
467
+ }
468
+ }
469
+ async function loadVfs() {
470
+ if (vfsCache) return vfsCache;
471
+ const vfs = /* @__PURE__ */ new Map();
472
+ const require_ = createRequire(import.meta.url);
473
+ const tsDir = path.dirname(require_.resolve("typescript/lib/typescript.js"));
474
+ const libFiles = fs.readdirSync(tsDir).filter((f) => /^lib\..*\.d\.ts$/.test(f));
475
+ for (const file of libFiles) {
476
+ const content = fs.readFileSync(path.join(tsDir, file), "utf-8");
477
+ parseAndStore(vfs, file, content);
478
+ }
479
+ const reactDir = findReactTypesDir();
480
+ if (reactDir) {
481
+ const reactDts = fs.readdirSync(reactDir).filter((f) => f.endsWith(".d.ts"));
482
+ for (const file of reactDts) {
483
+ const content = fs.readFileSync(path.join(reactDir, file), "utf-8");
484
+ parseAndStore(vfs, `node_modules/@types/react/${file}`, content);
485
+ }
486
+ const csstypeDir = findCsstypeDir(reactDir);
487
+ if (csstypeDir) {
488
+ parseAndStore(vfs, CSSTYPE_VFS_PATH, fs.readFileSync(path.join(csstypeDir, "index.d.ts"), "utf-8"));
489
+ }
490
+ }
491
+ const designDist = findPackageDistDir("@ggui-ai/design");
492
+ if (designDist) {
493
+ const dtsFiles = walkDir(designDist, (f) => f.endsWith(".d.ts") && !f.endsWith(".d.ts.map"));
494
+ for (const file of dtsFiles) {
495
+ const rel = path.relative(designDist, file);
496
+ const content = fs.readFileSync(file, "utf-8");
497
+ parseAndStore(vfs, `node_modules/@ggui-ai/design/${rel}`, content);
498
+ }
499
+ }
500
+ const wireDist = findPackageDistDir("@ggui-ai/wire");
501
+ if (wireDist) {
502
+ const dtsFiles = walkDir(wireDist, (f) => f.endsWith(".d.ts") && !f.endsWith(".d.ts.map"));
503
+ for (const file of dtsFiles) {
504
+ const rel = path.relative(wireDist, file);
505
+ const content = fs.readFileSync(file, "utf-8");
506
+ parseAndStore(vfs, `node_modules/@ggui-ai/wire/${rel}`, content);
507
+ }
508
+ }
509
+ const clientLibsDist = findPackageDistDir("@ggui-ai/gadgets");
510
+ if (clientLibsDist) {
511
+ const dtsFiles = walkDir(
512
+ clientLibsDist,
513
+ (f) => f.endsWith(".d.ts") && !f.endsWith(".d.ts.map")
514
+ );
515
+ for (const file of dtsFiles) {
516
+ const rel = path.relative(clientLibsDist, file);
517
+ const content = fs.readFileSync(file, "utf-8");
518
+ parseAndStore(vfs, `node_modules/@ggui-ai/gadgets/${rel}`, content);
519
+ }
520
+ }
521
+ vfsCache = vfs;
522
+ return vfs;
523
+ }
524
+ function resolveRelativeInVfs(vfs, containingFile, importPath) {
525
+ const normalized = containingFile.startsWith("/") ? containingFile.slice(1) : containingFile;
526
+ const dir = path.posix.dirname(normalized);
527
+ const specifier = importPath.replace(/\.(?:m|c)?js$/, "");
528
+ const resolved2 = path.posix.normalize(`${dir}/${specifier}`);
529
+ const withDts = `${resolved2}.d.ts`;
530
+ if (vfs.has(withDts)) return withDts;
531
+ const withIndexDts = `${resolved2}/index.d.ts`;
532
+ if (vfs.has(withIndexDts)) return withIndexDts;
533
+ const withTs = `${resolved2}.ts`;
534
+ if (vfs.has(withTs)) return withTs;
535
+ if (vfs.has(resolved2)) return resolved2;
536
+ return void 0;
537
+ }
538
+ var COMPILER_OPTIONS = {
539
+ target: ts4.ScriptTarget.ES2020,
540
+ module: ts4.ModuleKind.ESNext,
541
+ // Classic React JSX mode. The synthetic prefix (SYNTHETIC_PREFIX)
542
+ // supplies `import React from 'react'` plus a `declare global` JSX
543
+ // namespace shim: `@types/react` v19 removed the *global* `JSX`
544
+ // namespace (it lives at `React.JSX` now), which classic mode needs
545
+ // for `JSX.IntrinsicElements` and the `key`/`ref` carve-out. Without
546
+ // the shim, `<div>` degrades to `any` and every typed component
547
+ // falsely rejects the intrinsic `key` prop.
548
+ jsx: ts4.JsxEmit.React,
549
+ moduleResolution: ts4.ModuleResolutionKind.Bundler,
550
+ strict: false,
551
+ strictNullChecks: true,
552
+ // Catch undefined.foo() errors that cause runtime crashes
553
+ noEmit: true,
554
+ skipLibCheck: true,
555
+ noImplicitAny: true,
556
+ types: [],
557
+ esModuleInterop: true
558
+ };
559
+ function normalizeVfsPath(f) {
560
+ if (f.startsWith("/")) {
561
+ return f.slice(1);
562
+ }
563
+ return null;
564
+ }
565
+ function resolveModuleName(vfs, name, containingFile) {
566
+ if (name === "react") {
567
+ return resolved("node_modules/@types/react/index.d.ts");
568
+ }
569
+ if (name === "react/jsx-runtime") {
570
+ return resolved("node_modules/@types/react/jsx-runtime.d.ts");
571
+ }
572
+ if (name === "react/jsx-dev-runtime") {
573
+ const p = "node_modules/@types/react/jsx-dev-runtime.d.ts";
574
+ if (vfs.has(p)) return resolved(p);
575
+ return resolved("node_modules/@types/react/jsx-runtime.d.ts");
576
+ }
577
+ const designPrefix = "@ggui-ai/design/";
578
+ if (name.startsWith(designPrefix)) {
579
+ const subpath = name.slice(designPrefix.length);
580
+ const indexPath = `node_modules/@ggui-ai/design/${subpath}/index.d.ts`;
581
+ if (vfs.has(indexPath)) return resolved(indexPath);
582
+ const directPath = `node_modules/@ggui-ai/design/${subpath}.d.ts`;
583
+ if (vfs.has(directPath)) return resolved(directPath);
584
+ }
585
+ if (name === "@ggui-ai/design") {
586
+ return resolved("node_modules/@ggui-ai/design/index.d.ts");
587
+ }
588
+ if (name === "@ggui-ai/wire") {
589
+ return resolved("node_modules/@ggui-ai/wire/index.d.ts");
590
+ }
591
+ if (name === "@ggui-ai/gadgets") {
592
+ return resolved("node_modules/@ggui-ai/gadgets/index.d.ts");
593
+ }
594
+ if (name.startsWith("./") || name.startsWith("../")) {
595
+ const resolvedPath = resolveRelativeInVfs(vfs, containingFile, name);
596
+ if (resolvedPath) return resolved(resolvedPath);
597
+ }
598
+ const bareIndex = `node_modules/${name}/index.d.ts`;
599
+ if (vfs.has(bareIndex)) return resolved(bareIndex);
600
+ return { resolvedModule: void 0 };
601
+ }
602
+ function createVfsHost(vfs, componentCode) {
603
+ const componentFile = ts4.createSourceFile(
604
+ "Component.tsx",
605
+ componentCode,
606
+ ts4.ScriptTarget.ES2020,
607
+ true,
608
+ ts4.ScriptKind.TSX
609
+ );
610
+ const host = {
611
+ getSourceFile(fileName) {
612
+ if (fileName === "Component.tsx") return componentFile;
613
+ const entry = vfs.get(fileName);
614
+ if (entry) return entry.sourceFile;
615
+ const normalized = normalizeVfsPath(fileName);
616
+ if (normalized) return vfs.get(normalized)?.sourceFile;
617
+ return void 0;
618
+ },
619
+ getDefaultLibFileName() {
620
+ return "lib.es2020.full.d.ts";
621
+ },
622
+ writeFile() {
623
+ },
624
+ getCurrentDirectory() {
625
+ return "/";
626
+ },
627
+ getCanonicalFileName(f) {
628
+ return f;
629
+ },
630
+ useCaseSensitiveFileNames() {
631
+ return true;
632
+ },
633
+ getNewLine() {
634
+ return "\n";
635
+ },
636
+ fileExists(f) {
637
+ if (f === "Component.tsx" || vfs.has(f)) return true;
638
+ const normalized = normalizeVfsPath(f);
639
+ if (normalized && vfs.has(normalized)) return true;
640
+ return false;
641
+ },
642
+ readFile(f) {
643
+ if (f === "Component.tsx") return componentCode;
644
+ const direct = vfs.get(f);
645
+ if (direct) return direct.content;
646
+ const normalized = normalizeVfsPath(f);
647
+ if (normalized) return vfs.get(normalized)?.content;
648
+ return void 0;
649
+ },
650
+ resolveModuleNameLiterals(moduleLiterals, containingFile) {
651
+ return moduleLiterals.map(
652
+ (literal) => resolveModuleName(vfs, literal.text, containingFile)
653
+ );
654
+ }
655
+ };
656
+ return host;
657
+ }
658
+ function resolved(resolvedFileName) {
659
+ return {
660
+ resolvedModule: {
661
+ resolvedFileName,
662
+ isExternalLibraryImport: true,
663
+ extension: ts4.Extension.Dts
664
+ }
665
+ };
666
+ }
667
+ async function typecheck(code, dtsMap) {
668
+ const vfs = await loadVfs();
669
+ const effectiveVfs = (() => {
670
+ const dtsEntries = dtsMap !== void 0 ? Object.entries(dtsMap) : [];
671
+ if (dtsEntries.length === 0) {
672
+ return vfs;
673
+ }
674
+ const overlay = new Map(vfs);
675
+ for (const [pkg, content] of dtsEntries) {
676
+ parseAndStore(overlay, `node_modules/${pkg}/index.d.ts`, content);
677
+ }
678
+ return overlay;
679
+ })();
680
+ const globalJsxShim = "declare global { namespace JSX { type ElementType = string | ((props: any) => any) | (new (props: any) => any); interface Element { type: any; props: any; key: string | number | null; } interface ElementClass { render(): any; } interface ElementAttributesProperty { props: object; } interface ElementChildrenAttribute { children: object; } interface IntrinsicAttributes { key?: string | number | bigint | null; } interface IntrinsicClassAttributes<T> { ref?: any; } interface IntrinsicElements { [elem: string]: any; } } }\n";
681
+ const SYNTHETIC_PREFIX = "import React from 'react';\n" + globalJsxShim;
682
+ const prefixedCode = SYNTHETIC_PREFIX + code;
683
+ const lineOffset = SYNTHETIC_PREFIX.split("\n").length - 1;
684
+ const host = createVfsHost(effectiveVfs, prefixedCode);
685
+ const program = ts4.createProgram(["Component.tsx"], COMPILER_OPTIONS, host);
686
+ const diagnostics = ts4.getPreEmitDiagnostics(program);
687
+ const errors = [];
688
+ const warnings = [];
689
+ for (const diag of diagnostics) {
690
+ if (diag.file && diag.file.fileName !== "Component.tsx") continue;
691
+ if (!diag.file) continue;
692
+ const diagLine = ts4.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;
693
+ if (diagLine < lineOffset) continue;
694
+ const rawLine = diag.file ? ts4.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1 : 0;
695
+ const line = Math.max(1, rawLine - lineOffset);
696
+ const message = ts4.flattenDiagnosticMessageText(diag.messageText, "\n");
697
+ const code_ = diag.code;
698
+ const sourceLines = diag.file.text.split("\n");
699
+ const sourceLine = rawLine >= 1 && rawLine <= sourceLines.length ? sourceLines[rawLine - 1]?.trim() ?? "" : "";
700
+ const unknownProp = code_ === 2322 ? /Property '([^']+)' does not exist on type '[^']*Props[^']*'/.exec(message)?.[1] : void 0;
701
+ const props = unknownProp !== void 0 ? acceptedPropsAt(program, diag.file, diag.start ?? 0) : void 0;
702
+ const hint = sourceLine.length > 0 && sourceLine.length <= 140 ? ` Offending line: \`${sourceLine}\`` : "";
703
+ const entry = {
704
+ code: code_,
705
+ line,
706
+ message,
707
+ fix: unknownProp !== void 0 && props !== void 0 ? unknownPropFix(props.component, unknownProp, props.accepted, hint, props.native) : generateFix(code_, message, sourceLine)
708
+ };
709
+ if (BLOCKING_CODES.has(code_)) {
710
+ errors.push(entry);
711
+ } else {
712
+ warnings.push(entry);
713
+ }
714
+ }
715
+ return { errors, warnings };
716
+ }
717
+ var linterInstance = null;
718
+ async function getLinter() {
719
+ if (!linterInstance) {
720
+ linterInstance = new Linter();
721
+ const tsParser = await import('@typescript-eslint/parser');
722
+ linterInstance.defineParser("@typescript-eslint/parser", tsParser);
723
+ const reactHooksPlugin = await import('eslint-plugin-react-hooks');
724
+ const hooksRules = reactHooksPlugin.default?.rules ?? reactHooksPlugin.rules;
725
+ for (const [name, rule] of Object.entries(hooksRules)) {
726
+ linterInstance.defineRule(`react-hooks/${name}`, rule);
727
+ }
728
+ const reactPlugin = await import('eslint-plugin-react');
729
+ const reactRules = reactPlugin.default?.rules ?? reactPlugin.rules;
730
+ for (const [name, rule] of Object.entries(reactRules)) {
731
+ linterInstance.defineRule(`react/${name}`, rule);
732
+ }
733
+ }
734
+ return linterInstance;
735
+ }
736
+ async function lintReactHooks(code) {
737
+ const linter = await getLinter();
738
+ let messages;
739
+ try {
740
+ const config = {
741
+ parser: "@typescript-eslint/parser",
742
+ parserOptions: {
743
+ ecmaVersion: 2022,
744
+ sourceType: "module",
745
+ ecmaFeatures: { jsx: true }
746
+ },
747
+ rules: {
748
+ "react-hooks/rules-of-hooks": 2,
749
+ "react-hooks/exhaustive-deps": 1,
750
+ // React rules
751
+ "react/jsx-no-undef": 2,
752
+ // undefined JSX components
753
+ "react/jsx-key": 1,
754
+ // missing key in lists
755
+ "react/no-direct-mutation-state": 2,
756
+ // direct state mutation
757
+ // An unused `const submit = useAction('submit')` is a
758
+ // dead contract wire. Narrow the rule to variable bindings we
759
+ // care about: skip function args (LLMs legitimately omit unused
760
+ // params), skip destructured `rest` collectors, respect a `_`-
761
+ // prefix escape hatch for intentionally-ignored declarations,
762
+ // and silence the caught-error slot (async error handling can
763
+ // leave `catch (err)` unused at a boundary).
764
+ "no-unused-vars": [2, {
765
+ vars: "all",
766
+ args: "none",
767
+ ignoreRestSiblings: true,
768
+ varsIgnorePattern: "^_",
769
+ caughtErrors: "none"
770
+ }]
771
+ },
772
+ settings: {
773
+ react: { version: "19.0" }
774
+ }
775
+ };
776
+ messages = linter.verify(code, config, { filename: "component.tsx" });
777
+ } catch {
778
+ return [];
779
+ }
780
+ const diagnostics = [];
781
+ const ADMITTED_RULES = (id) => id.startsWith("react-hooks/") || id.startsWith("react/") || id === "no-unused-vars";
782
+ for (const msg of messages) {
783
+ if (!msg.ruleId) continue;
784
+ if (!ADMITTED_RULES(msg.ruleId)) continue;
785
+ diagnostics.push({
786
+ rule: msg.ruleId,
787
+ line: msg.line,
788
+ message: msg.message,
789
+ fix: generateReactFix(msg.ruleId, msg.message),
790
+ severity: resolveSeverity(msg, code)
791
+ });
792
+ }
793
+ return diagnostics;
794
+ }
795
+ function resolveSeverity(msg, code) {
796
+ const base = msg.severity === 2 ? "error" : "warning";
797
+ if (msg.ruleId !== "no-unused-vars" || base !== "error") return base;
798
+ const name = msg.message.match(/'([^']+)'/)?.[1];
799
+ if (name === void 0) return "warning";
800
+ const safe = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
801
+ const directRe = new RegExp(
802
+ `\\bconst\\s+${safe}\\s*=\\s*use(Action|Stream|GguiContext)\\s*[<(]`
803
+ );
804
+ const destructuredRe = new RegExp(
805
+ `\\bconst\\s+\\[[^\\]]*\\b${safe}\\b[^\\]]*\\]\\s*=\\s*use(Action|Stream|GguiContext)\\s*[<(]`
806
+ );
807
+ return directRe.test(code) || destructuredRe.test(code) ? "error" : "warning";
808
+ }
809
+ function generateReactFix(ruleId, message) {
810
+ if (ruleId === "react-hooks/rules-of-hooks") {
811
+ if (message.includes("called conditionally")) {
812
+ return "Move this hook to the top level of the component, before any early returns or conditionals. Hooks must run in the same order every render.";
813
+ }
814
+ return "Hooks can only be called at the top level of a React function component or custom hook. Move it out of any conditional, loop, nested function, or callback.";
815
+ }
816
+ if (ruleId === "react-hooks/exhaustive-deps") {
817
+ return "Add the missing dependencies to the dependency array, or remove the array to run on every render.";
818
+ }
819
+ if (ruleId === "react/jsx-no-undef") {
820
+ const match = message.match(/'(\w+)'/);
821
+ const name = match?.[1] ?? "Component";
822
+ return `'${name}' is not defined. Import it from the design system or define it in the file.`;
823
+ }
824
+ if (ruleId === "react/jsx-key") {
825
+ return 'Add a unique "key" prop to each element rendered inside a .map() or iterator.';
826
+ }
827
+ if (ruleId === "react/no-direct-mutation-state") {
828
+ return "Do not mutate state directly. Use setState or the setter from useState instead.";
829
+ }
830
+ if (ruleId === "no-unused-vars") {
831
+ const match = message.match(/'([^']+)'/);
832
+ const name = match?.[1] ?? "variable";
833
+ const isLikelyWireBinding = /\bconst\s+[A-Za-z_$][\w$]*\s*=\s*use(Action|Stream)\s*\(/.test(message);
834
+ if (isLikelyWireBinding || /^(submit|cancel|search|progress|snapshot)$/.test(name)) {
835
+ return `'${name}' is declared but never used. If this binding came from a wire hook (useAction / useStream) or a clientCapabilities hook (useGeolocation / useCamera / etc. from @ggui-ai/gadgets), consume it somewhere in the component \u2014 render its value in JSX, bind it to a callback prop, or use it in an effect. A contract-declared hook without consumption is a dead wire.`;
836
+ }
837
+ return `'${name}' is declared but never used. Remove the declaration, or consume it somewhere in the component. Prefix with '_' (e.g. '_${name}') to mark it intentionally unused.`;
838
+ }
839
+ return "Fix the React violation.";
840
+ }
841
+ function stripComments(code) {
842
+ const scanner = ts4.createScanner(
843
+ ts4.ScriptTarget.Latest,
844
+ /* skipTrivia */
845
+ false,
846
+ ts4.LanguageVariant.JSX,
847
+ code
848
+ );
849
+ let out = "";
850
+ for (let token = scanner.scan(); token !== ts4.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
851
+ const text = scanner.getTokenText();
852
+ out += token === ts4.SyntaxKind.SingleLineCommentTrivia || token === ts4.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
853
+ }
854
+ return out;
855
+ }
856
+ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode = DEFAULT_DESIGN_MODE) {
857
+ const issues = [];
858
+ if (actionBindings.length === 0) return issues;
859
+ const bindings = new Set(actionBindings);
860
+ const anyElementIsInteractive = designMode === "free";
861
+ const TRAIT_HOST_TAGS = /* @__PURE__ */ new Set(["Card", "Box", "Stack", "Row"]);
862
+ const TRAITS_THAT_FIRE = /* @__PURE__ */ new Set(["Clickable", "Pressable"]);
863
+ const INTERACTIVE_DESCENDANTS = /* @__PURE__ */ new Set([
864
+ "Button",
865
+ "Checkbox",
866
+ "Input",
867
+ "Toggle",
868
+ "Slider",
869
+ "RadioGroup",
870
+ "Select",
871
+ "TextArea",
872
+ "Link"
873
+ ]);
874
+ const HANDLER_ATTRS = ["onClick", "onChange", "onPress", "onSelect"];
875
+ const sf = ts4.createSourceFile(
876
+ "source.tsx",
877
+ sourceCode,
878
+ ts4.ScriptTarget.Latest,
879
+ /* setParentNodes */
880
+ true,
881
+ ts4.ScriptKind.TSX
882
+ );
883
+ function tagNameText(node) {
884
+ return ts4.isIdentifier(node.tagName) ? node.tagName.text : void 0;
885
+ }
886
+ function attrExpression(node, attrName) {
887
+ for (const attr of node.attributes.properties) {
888
+ if (ts4.isJsxAttribute(attr) && ts4.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts4.isJsxExpression(attr.initializer)) {
889
+ return attr.initializer.expression;
890
+ }
891
+ }
892
+ return void 0;
893
+ }
894
+ function isTraitHostWithFiringAs(node) {
895
+ const tag = tagNameText(node);
896
+ if (tag === void 0) return false;
897
+ if (anyElementIsInteractive) return true;
898
+ if (!TRAIT_HOST_TAGS.has(tag)) return false;
899
+ const asExpr = attrExpression(node, "as");
900
+ return asExpr !== void 0 && ts4.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
901
+ }
902
+ function extractCalleeName(expr) {
903
+ if (expr === void 0) return void 0;
904
+ if (ts4.isIdentifier(expr)) return expr.text;
905
+ if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) {
906
+ const body = expr.body;
907
+ if (ts4.isCallExpression(body) && ts4.isIdentifier(body.expression)) {
908
+ return body.expression.text;
909
+ }
910
+ if (ts4.isBlock(body)) {
911
+ for (const stmt of body.statements) {
912
+ if (ts4.isExpressionStatement(stmt) && ts4.isCallExpression(stmt.expression) && ts4.isIdentifier(stmt.expression.expression)) {
913
+ return stmt.expression.expression.text;
914
+ }
915
+ }
916
+ }
917
+ }
918
+ return void 0;
919
+ }
920
+ function findMatchingInteractiveDescendant(outer, expectedBinding) {
921
+ let found;
922
+ function walk(node) {
923
+ if (found !== void 0) return;
924
+ const opening = ts4.isJsxElement(node) ? node.openingElement : ts4.isJsxSelfClosingElement(node) ? node : void 0;
925
+ if (opening !== void 0) {
926
+ const tag = tagNameText(opening);
927
+ if (tag !== void 0 && (anyElementIsInteractive || INTERACTIVE_DESCENDANTS.has(tag))) {
928
+ for (const attrName of HANDLER_ATTRS) {
929
+ const callee = extractCalleeName(attrExpression(opening, attrName));
930
+ if (callee === expectedBinding) {
931
+ found = {
932
+ tag,
933
+ line: sf.getLineAndCharacterOfPosition(opening.getStart()).line + 1
934
+ };
935
+ return;
936
+ }
937
+ }
938
+ }
939
+ }
940
+ ts4.forEachChild(node, walk);
941
+ }
942
+ ts4.forEachChild(outer, walk);
943
+ return found;
944
+ }
945
+ function visit(node) {
946
+ if (ts4.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
947
+ const outerCallee = extractCalleeName(attrExpression(node.openingElement, "onClick")) ?? extractCalleeName(attrExpression(node.openingElement, "onPress"));
948
+ if (outerCallee !== void 0 && bindings.has(outerCallee)) {
949
+ const match = findMatchingInteractiveDescendant(node, outerCallee);
950
+ if (match !== void 0) {
951
+ const outerTag = tagNameText(node.openingElement);
952
+ const outerLine = sf.getLineAndCharacterOfPosition(node.openingElement.getStart()).line + 1;
953
+ const outerLabel = anyElementIsInteractive ? `<${outerTag}>` : `<${outerTag} as={...}>`;
954
+ issues.push({
955
+ tier: 0,
956
+ result: "fail",
957
+ category: "interactivity",
958
+ subcategory: "double-wired-action:certain",
959
+ severity: "critical",
960
+ description: `Nested-interactive double-wire: outer ${outerLabel} at line ${outerLine} and inner <${match.tag}> at line ${match.line} both dispatch the same useAction binding '${outerCallee}'. One user click on the inner control fires its handler AND bubbles to the outer handler \u2014 '${outerCallee}' dispatches TWICE, the action runs back-to-back, and a toggle-style action silently reverts the user's change.`,
961
+ fix: anyElementIsInteractive ? `Pick ONE gesture surface for '${outerCallee}': either remove the onClick from the outer <${outerTag}> and let the inner <${match.tag}> own the gesture, OR drop the inner <${match.tag}>'s handler and let the outer <${outerTag}> own it. Never wire both to the same useAction binding \u2014 stopPropagation is not a fix.` : `Pick ONE surface for the gesture: either drop \`as={...}\` + onClick on the outer <${outerTag}> and let the inner <${match.tag}> own the gesture, OR remove the inner <${match.tag}> and let the outer <${outerTag} as={...}> own it. Don't wire both to the same useAction binding.`,
962
+ line: outerLine
963
+ });
964
+ }
965
+ }
966
+ }
967
+ ts4.forEachChild(node, visit);
968
+ }
969
+ visit(sf);
970
+ return issues;
971
+ }
972
+ var MANIFEST_TOKENS = new Set(consumedTokenManifest);
973
+ var RETIRED_TOKEN_SUCCESSORS = /* @__PURE__ */ new Map([
974
+ ["--ggui-color-surface", "--ggui-color-container"],
975
+ ["--ggui-color-onSurface", "--ggui-color-onContainer"],
976
+ ["--ggui-color-surfaceVariant", "--ggui-color-sunken"],
977
+ ["--ggui-color-onSurfaceVariant", "--ggui-color-onSunken"]
978
+ ]);
979
+ function suggestManifestTokens(name) {
980
+ const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
981
+ if (successor !== void 0) return [successor];
982
+ const segments = name.split("-").filter((segment) => segment.length > 0);
983
+ const own = name.endsWith("-") ? segments.length : segments.length - 1;
984
+ for (let n = own; n >= 2; n--) {
985
+ const prefix = `--${segments.slice(0, n).join("-")}-`;
986
+ const family = taughtTokenManifest.filter((token) => token.startsWith(prefix));
987
+ if (family.length > 0) return wordHitsFirst(name, rolesFirst(family));
988
+ }
989
+ const words = new Set(
990
+ segments.slice(1).filter((segment) => segment.length >= 4).map((segment) => segment.toLowerCase())
991
+ );
992
+ return rolesFirst(
993
+ taughtTokenManifest.filter(
994
+ (token) => token.split("-").some((segment) => words.has(segment.toLowerCase()))
995
+ )
996
+ );
997
+ }
998
+ function wordHitsFirst(name, family) {
999
+ const word = name.slice(name.lastIndexOf("-") + 1).toLowerCase();
1000
+ if (word.length < 4) return family;
1001
+ const hit = (token) => token.slice(token.lastIndexOf("-") + 1).toLowerCase().includes(word);
1002
+ return [...family.filter(hit), ...family.filter((token) => !hit(token))];
1003
+ }
1004
+ function rolesFirst(tokens) {
1005
+ const isRole = (token) => !/^\d+$/.test(token.slice(token.lastIndexOf("-") + 1));
1006
+ return [...tokens.filter(isRole), ...tokens.filter((token) => !isRole(token))];
1007
+ }
1008
+ function offManifestHint(name) {
1009
+ const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
1010
+ if (successor !== void 0) {
1011
+ return `"${name}" was retired (ggui#987); the current name is var(${successor}). `;
1012
+ }
1013
+ const near = suggestManifestTokens(name);
1014
+ if (near.length === 0) return "";
1015
+ const shown = near.slice(0, 8).map((token) => `var(${token})`).join(", ");
1016
+ const more = near.length > 8 ? ` (and ${near.length - 8} more in that family)` : "";
1017
+ return `Nearest names in the manifest: ${shown}${more}. `;
1018
+ }
1019
+ var TOKEN_REF_RE = /var\(\s*(--ggui-[a-zA-Z0-9-]+)/g;
1020
+ var HOOK_NAME_FOR = {
1021
+ action: "useAction",
1022
+ stream: "useStream",
1023
+ context: "useGguiContext"
1024
+ };
1025
+ var CONTRACT_FIELD_FOR = {
1026
+ action: "actionSpec",
1027
+ stream: "streamSpec",
1028
+ context: "contextSpec"
1029
+ };
1030
+ function isGadgetExportImported(source, pkg, exportName) {
1031
+ const esc = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1032
+ const re = new RegExp(
1033
+ `import\\s*(?:[A-Za-z_$][\\w$]*\\s*,\\s*)?\\{([^}]*)\\}\\s*from\\s*['"]${esc}['"]`,
1034
+ "g"
1035
+ );
1036
+ let m;
1037
+ while ((m = re.exec(source)) !== null) {
1038
+ for (const raw of m[1].split(",")) {
1039
+ if (raw.trim().split(/\s+as\s+/)[0]?.trim() === exportName) return true;
1040
+ }
1041
+ }
1042
+ return false;
1043
+ }
1044
+ function detectComponentNeverReturns(sourceCode) {
1045
+ const sf = ts4.createSourceFile(
1046
+ "source.tsx",
1047
+ sourceCode,
1048
+ ts4.ScriptTarget.Latest,
1049
+ /* setParentNodes */
1050
+ true,
1051
+ ts4.ScriptKind.TSX
1052
+ );
1053
+ const isFunctionLike = (n) => ts4.isFunctionDeclaration(n) || ts4.isFunctionExpression(n) || ts4.isArrowFunction(n) || ts4.isMethodDeclaration(n) || ts4.isClassDeclaration(n) || ts4.isClassExpression(n);
1054
+ function resolveTopLevel(name) {
1055
+ for (const st of sf.statements) {
1056
+ if (ts4.isFunctionDeclaration(st) && st.name?.text === name) return st;
1057
+ if (ts4.isVariableStatement(st)) {
1058
+ for (const decl of st.declarationList.declarations) {
1059
+ if (ts4.isIdentifier(decl.name) && decl.name.text === name && decl.initializer !== void 0 && (ts4.isArrowFunction(decl.initializer) || ts4.isFunctionExpression(decl.initializer))) {
1060
+ return decl.initializer;
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+ return void 0;
1066
+ }
1067
+ let component;
1068
+ for (const st of sf.statements) {
1069
+ if (ts4.isFunctionDeclaration(st) && st.modifiers?.some((m) => m.kind === ts4.SyntaxKind.DefaultKeyword)) {
1070
+ component = st;
1071
+ break;
1072
+ }
1073
+ if (ts4.isExportAssignment(st) && st.isExportEquals !== true) {
1074
+ const expr = st.expression;
1075
+ if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) component = expr;
1076
+ else if (ts4.isIdentifier(expr)) component = resolveTopLevel(expr.text);
1077
+ break;
1078
+ }
1079
+ }
1080
+ if (component === void 0) return null;
1081
+ const body = component.body;
1082
+ if (body === void 0 || !ts4.isBlock(body)) return null;
1083
+ let returnsValue = false;
1084
+ let jsxStatementLine;
1085
+ const visit = (node) => {
1086
+ if (returnsValue) return;
1087
+ if (ts4.isReturnStatement(node)) {
1088
+ if (node.expression !== void 0) returnsValue = true;
1089
+ return;
1090
+ }
1091
+ if (jsxStatementLine === void 0 && ts4.isExpressionStatement(node) && (ts4.isJsxElement(node.expression) || ts4.isJsxFragment(node.expression) || ts4.isJsxSelfClosingElement(node.expression))) {
1092
+ jsxStatementLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
1093
+ }
1094
+ node.forEachChild((child) => {
1095
+ if (!isFunctionLike(child)) visit(child);
1096
+ });
1097
+ };
1098
+ for (const st of body.statements) if (!isFunctionLike(st)) visit(st);
1099
+ return returnsValue ? null : { jsxStatementLine };
1100
+ }
1101
+ function detectBandEdgeLost(sourceCode) {
1102
+ const sf = ts4.createSourceFile(
1103
+ "source.tsx",
1104
+ sourceCode,
1105
+ ts4.ScriptTarget.Latest,
1106
+ /* setParentNodes */
1107
+ true,
1108
+ ts4.ScriptKind.TSX
1109
+ );
1110
+ const unwrap = (e) => ts4.isParenthesizedExpression(e) ? unwrap(e.expression) : e;
1111
+ const isEl = (n) => ts4.isJsxElement(n) || ts4.isJsxSelfClosingElement(n);
1112
+ const opening = (el) => ts4.isJsxElement(el) ? el.openingElement : el;
1113
+ const tag = (el) => opening(el).tagName.getText(sf);
1114
+ const attr = (el, name) => opening(el).attributes.properties.find((a) => ts4.isJsxAttribute(a) && a.name.getText(sf) === name);
1115
+ const stringAttr = (el, name) => {
1116
+ const a = attr(el, name);
1117
+ if (a?.initializer === void 0) return void 0;
1118
+ if (ts4.isStringLiteral(a.initializer)) return a.initializer.text;
1119
+ if (ts4.isJsxExpression(a.initializer) && a.initializer.expression !== void 0 && ts4.isStringLiteral(a.initializer.expression)) {
1120
+ return a.initializer.expression.text;
1121
+ }
1122
+ return void 0;
1123
+ };
1124
+ const flagOn = (el, name) => {
1125
+ const a = attr(el, name);
1126
+ if (a === void 0) return false;
1127
+ if (a.initializer === void 0) return true;
1128
+ return !(ts4.isJsxExpression(a.initializer) && a.initializer.expression?.kind === ts4.SyntaxKind.FalseKeyword);
1129
+ };
1130
+ const branches = (x) => {
1131
+ const e = unwrap(x);
1132
+ if (isEl(e)) return [e];
1133
+ const guard = ts4.isBinaryExpression(e) && (e.operatorToken.kind === ts4.SyntaxKind.AmpersandAmpersandToken || e.operatorToken.kind === ts4.SyntaxKind.BarBarToken);
1134
+ if (guard) return branches(e.right);
1135
+ if (ts4.isConditionalExpression(e)) return [...branches(e.whenTrue), ...branches(e.whenFalse)];
1136
+ return [];
1137
+ };
1138
+ const slots = (el) => {
1139
+ if (!ts4.isJsxElement(el)) return [];
1140
+ const out = [];
1141
+ for (const n of el.children) {
1142
+ if (isEl(n)) out.push({ alts: [n], conditional: false });
1143
+ else if (ts4.isJsxExpression(n) && n.expression !== void 0) {
1144
+ const alts = branches(n.expression);
1145
+ if (alts.length > 0) out.push({ alts, conditional: true });
1146
+ }
1147
+ }
1148
+ return out;
1149
+ };
1150
+ const lineOf = (el) => sf.getLineAndCharacterOfPosition(el.getStart(sf)).line + 1;
1151
+ const styleKeys = (el) => {
1152
+ const style = attr(el, "style")?.initializer;
1153
+ if (style === void 0 || !ts4.isJsxExpression(style) || style.expression === void 0) return [];
1154
+ const obj = unwrap(style.expression);
1155
+ if (!ts4.isObjectLiteralExpression(obj)) return [];
1156
+ return obj.properties.filter(ts4.isPropertyAssignment).map((pr) => pr.name.getText(sf).replace(/['"]/g, ""));
1157
+ };
1158
+ const paints = (el) => {
1159
+ const surface = stringAttr(el, "surface");
1160
+ if (surface !== void 0 && surface !== "transparent") return true;
1161
+ if (attr(el, "tint") !== void 0) return true;
1162
+ return styleKeys(el).some((k) => k === "background" || k === "backgroundColor");
1163
+ };
1164
+ const hasOwnEdge = (el) => {
1165
+ if (attr(el, "radius") !== void 0 && stringAttr(el, "radius") !== "none") return true;
1166
+ return styleKeys(el).some((k) => k === "borderRadius" || k === "boxShadow" || /^border(Top|Bottom|Left|Right)?(Width|Style|Color)?$/.test(k));
1167
+ };
1168
+ const isHeroBand = (el) => tag(el) === "Box" && stringAttr(el, "surface") === "hero" && !hasOwnEdge(el);
1169
+ const isHeroCard = (el) => tag(el) === "Card" && stringAttr(el, "surface") === "hero" && !flagOn(el, "bleed");
1170
+ const isPlainWrapper = (el) => ["Stack", "Box", "Container", "div"].includes(tag(el)) && !paints(el) && !(tag(el) === "Stack" && stringAttr(el, "direction") === "horizontal");
1171
+ const byName = /* @__PURE__ */ new Map();
1172
+ let component;
1173
+ for (const st of sf.statements) {
1174
+ if (ts4.isFunctionDeclaration(st)) {
1175
+ if (st.name !== void 0) byName.set(st.name.text, st);
1176
+ if (st.modifiers?.some((m) => m.kind === ts4.SyntaxKind.DefaultKeyword)) component = st;
1177
+ } else if (ts4.isVariableStatement(st)) {
1178
+ for (const d of st.declarationList.declarations) {
1179
+ const init = d.initializer === void 0 ? void 0 : unwrap(d.initializer);
1180
+ if (ts4.isIdentifier(d.name) && init !== void 0 && (ts4.isArrowFunction(init) || ts4.isFunctionExpression(init))) byName.set(d.name.text, init);
1181
+ }
1182
+ }
1183
+ }
1184
+ for (const st of sf.statements) {
1185
+ if (component !== void 0 || !ts4.isExportAssignment(st) || st.isExportEquals === true) continue;
1186
+ const e = unwrap(st.expression);
1187
+ if (ts4.isIdentifier(e)) component = byName.get(e.text);
1188
+ else if (ts4.isArrowFunction(e) || ts4.isFunctionExpression(e)) component = e;
1189
+ }
1190
+ if (component?.body === void 0) return null;
1191
+ let root;
1192
+ if (!ts4.isBlock(component.body)) {
1193
+ const e = unwrap(component.body);
1194
+ if (isEl(e)) root = e;
1195
+ } else {
1196
+ for (const st of component.body.statements) {
1197
+ if (ts4.isReturnStatement(st) && st.expression !== void 0) {
1198
+ const e = unwrap(st.expression);
1199
+ if (isEl(e)) root = e;
1200
+ }
1201
+ }
1202
+ }
1203
+ if (root === void 0) return null;
1204
+ const kids = slots(root);
1205
+ const first = kids[0];
1206
+ if (first === void 0) return null;
1207
+ if (!first.conditional && first.alts.some(isHeroBand)) return null;
1208
+ const heroCard = kids.length > 1 ? first.alts.find(isHeroCard) : void 0;
1209
+ if (heroCard !== void 0) return { shape: "hero-card-first", line: lineOf(heroCard), conditional: first.conditional };
1210
+ for (let i = 1; i < kids.length; i++) {
1211
+ const k = kids[i];
1212
+ const band = k.alts.find((a) => isHeroBand(a) && !flagOn(a, "bleed"));
1213
+ if (band !== void 0) {
1214
+ return { shape: "hero-not-first", line: lineOf(band), conditional: k.conditional || kids.slice(0, i).every((s) => s.conditional) };
1215
+ }
1216
+ }
1217
+ const opener = first.alts.find(isPlainWrapper);
1218
+ const inner = opener === void 0 ? void 0 : slots(opener)[0];
1219
+ const wrapped = inner?.alts.find(isHeroBand);
1220
+ if (wrapped !== void 0 && inner !== void 0) {
1221
+ return { shape: "hero-not-first", line: lineOf(wrapped), conditional: first.conditional || inner.conditional };
1222
+ }
1223
+ return null;
1224
+ }
1225
+ var KIT_CONTROLS = ["Button", "Input", "Select", "TextArea", "Checkbox", "RadioGroup", "Toggle", "SearchField"];
1226
+ function kitControlTags(source) {
1227
+ const out = [];
1228
+ const open = new RegExp(`<(${KIT_CONTROLS.join("|")})\\b`, "g");
1229
+ for (const m of source.matchAll(open)) {
1230
+ const start = (m.index ?? 0) + m[0].length;
1231
+ let depth = 0;
1232
+ let quote = null;
1233
+ let i = start;
1234
+ for (; i < source.length; i += 1) {
1235
+ const c = source[i];
1236
+ if (quote !== null) {
1237
+ if (c === quote && source[i - 1] !== "\\") quote = null;
1238
+ } else if (c === '"' || c === "'" || c === "`") quote = c;
1239
+ else if (c === "{") depth += 1;
1240
+ else if (c === "}") depth -= 1;
1241
+ else if (c === ">" && depth === 0) break;
1242
+ }
1243
+ out.push({ name: m[1] ?? "", attrs: source.slice(start, i), line: source.slice(0, m.index ?? 0).split("\n").length });
1244
+ }
1245
+ return out;
1246
+ }
1247
+ var CONTROL_BORDER_KEY = /\b(border(?:Top|Right|Bottom|Left)?(?:Color|Width|Style)?)\s*:/;
1248
+ var CONTROL_COLOR_KEY = /\b(color|background|backgroundColor)\s*:/;
1249
+ function inlineStyleKey(attrs, source, key) {
1250
+ const style = attrs.match(/\bstyle=\{\{([\s\S]*)\}\}/);
1251
+ if (style !== null) {
1252
+ const hit2 = (style[1] ?? "").match(key);
1253
+ if (hit2 !== null) return hit2[1] ?? null;
1254
+ }
1255
+ const named = attrs.match(/\bstyle=\{\s*([A-Za-z_$][\w$]*)\s*\}/);
1256
+ if (named === null) return null;
1257
+ const decl = source.match(new RegExp(`\\b(?:const|let)\\s+${named[1]}\\b[^=]*=\\s*\\{([^{}]*)\\}`));
1258
+ const hit = decl !== null ? (decl[1] ?? "").match(key) : null;
1259
+ return hit !== null ? hit[1] ?? null : null;
1260
+ }
1261
+ async function runTier0Checks(sourceCode, contract, buildErrors, gadgetTypes, designMode = DEFAULT_DESIGN_MODE) {
1262
+ const issues = [];
1263
+ const lines = sourceCode.split("\n");
1264
+ const enforceDesignVocabulary = designMode === "constrained";
1265
+ if (buildErrors && buildErrors.length > 0) {
1266
+ const errorDetail = buildErrors.map((e) => {
1267
+ const match = e.match(/<stdin>:(\d+):\d+: ERROR: (.+)/);
1268
+ return match ? `Line ${match[1]}: ${match[2]}` : e.slice(0, 200);
1269
+ }).join("\n");
1270
+ issues.push({
1271
+ tier: 0,
1272
+ result: "fail",
1273
+ category: "compile",
1274
+ subcategory: "build-error",
1275
+ severity: "critical",
1276
+ description: `Component failed to compile:
1277
+ ${errorDetail}`,
1278
+ fix: "Fix the JSX/TypeScript syntax errors listed above"
1279
+ });
1280
+ }
1281
+ const assetEscapedLines = /* @__PURE__ */ new Set();
1282
+ const jsxOpenRegex = /<([A-Z][A-Za-z0-9]*)\b([^>]*)>/gs;
1283
+ for (const match of sourceCode.matchAll(jsxOpenRegex)) {
1284
+ const tagAttrs = match[2];
1285
+ const assetColorAttr = tagAttrs.match(/\bassetColor\s*=\s*(?:["']([^"']*)["']|\{[^}]*\})/);
1286
+ if (!assetColorAttr) continue;
1287
+ const assetSemanticAttr = tagAttrs.match(/\bassetSemantic\s*=\s*["']([^"']*)["']/);
1288
+ const startLine = sourceCode.slice(0, match.index ?? 0).split("\n").length;
1289
+ const tagLineCount = match[0].split("\n").length;
1290
+ if (/var\(\s*--ggui-/.test(assetColorAttr[0])) {
1291
+ issues.push({
1292
+ tier: 0,
1293
+ result: "fail",
1294
+ category: "tokens",
1295
+ subcategory: "asset-color-theme-token",
1296
+ severity: "critical",
1297
+ description: "Box `assetColor` carries a theme token (`var(--ggui-\u2026)`) \u2014 the brand-color escape is only for an exact external brand hex, and the theme already has its own doors.",
1298
+ fix: 'To colour by a data category, use `tint="tint-1"` \u2026 `tint="tint-8"` (one step per category, in the data\'s order). For a theme-tracking fill, use `surface="..."`. Drop `assetColor` and `assetSemantic`.',
1299
+ line: startLine
1300
+ });
1301
+ continue;
1302
+ }
1303
+ if (!assetSemanticAttr || assetSemanticAttr[1].length === 0) {
1304
+ issues.push({
1305
+ tier: 0,
1306
+ result: "fail",
1307
+ category: "tokens",
1308
+ subcategory: "asset-color-pair",
1309
+ severity: "critical",
1310
+ description: "Box `assetColor` set without a non-empty `assetSemantic` \u2014 the typed brand-color escape requires both. `assetSemantic` is a human-readable label that documents why this color bypasses the theme.",
1311
+ fix: 'Pair the `assetColor` with a non-empty `assetSemantic` literal, e.g. `<Box assetColor="#635BFF" assetSemantic="stripe-brand-purple">`. The escape is only for an exact external brand hex: to colour by a data category use `tint="tint-1"` \u2026 `tint="tint-8"`, and use `surface="..."` whenever the color SHOULD track the operator\'s theme.',
1312
+ line: startLine
1313
+ });
1314
+ continue;
1315
+ }
1316
+ for (let l = 0; l < tagLineCount; l++) {
1317
+ assetEscapedLines.add(startLine + l);
1318
+ }
1319
+ }
1320
+ const allowedGadgetPackages = new Set(
1321
+ Object.keys(contract?.clientCapabilities?.gadgets ?? {})
1322
+ );
1323
+ for (let i = 0; i < lines.length; i++) {
1324
+ const line = lines[i];
1325
+ const lineNum = i + 1;
1326
+ if (/\beval\s*\(/.test(line)) {
1327
+ issues.push({
1328
+ tier: 0,
1329
+ result: "fail",
1330
+ category: "security",
1331
+ subcategory: "eval",
1332
+ severity: "critical",
1333
+ description: "eval() is forbidden",
1334
+ fix: "Remove eval() call entirely",
1335
+ line: lineNum
1336
+ });
1337
+ }
1338
+ if (/\bfetch\s*\(/.test(line)) {
1339
+ issues.push({
1340
+ tier: 0,
1341
+ result: "fail",
1342
+ category: "security",
1343
+ subcategory: "fetch",
1344
+ severity: "critical",
1345
+ description: "fetch() is forbidden \u2014 use props for data",
1346
+ fix: "Remove fetch() and pass data via props",
1347
+ line: lineNum
1348
+ });
1349
+ }
1350
+ const trimmedLine = line.trim();
1351
+ if (trimmedLine.startsWith("//") || trimmedLine.startsWith("*")) continue;
1352
+ const importMatch = trimmedLine.match(/import\s+.*from\s+['"]([^'"]+)['"]/);
1353
+ if (importMatch) {
1354
+ const pkg = importMatch[1];
1355
+ if (!isAllowedImport(pkg, allowedGadgetPackages)) {
1356
+ issues.push({
1357
+ tier: 0,
1358
+ result: "fail",
1359
+ category: "imports",
1360
+ subcategory: "disallowed-package",
1361
+ severity: "critical",
1362
+ description: `Import from "${pkg}" is not allowed`,
1363
+ fix: `Only import from: ${describeAllowedImports()}`,
1364
+ line: lineNum
1365
+ });
1366
+ }
1367
+ }
1368
+ const literalFallbackMatch = line.match(
1369
+ /var\(\s*(--ggui-[a-zA-Z0-9-]+)\s*,\s*([^)]+)\)/
1370
+ );
1371
+ const literalFallback = literalFallbackMatch !== null && !literalFallbackMatch[2].trim().startsWith("var(") ? { token: literalFallbackMatch[1], literal: literalFallbackMatch[2].trim() } : null;
1372
+ if (literalFallback !== null && !assetEscapedLines.has(lineNum)) {
1373
+ issues.push({
1374
+ tier: 0,
1375
+ result: "fail",
1376
+ category: "tokens",
1377
+ subcategory: "token-fallback",
1378
+ severity: "critical",
1379
+ description: `Literal fallback "${literalFallback.literal}" inside a token reference \u2014 the runtime injects every token, and the fallback paints the wrong value exactly when the theme matters most.`,
1380
+ fix: `Drop the fallback: write var(${literalFallback.token}) bare. If the token might not exist, that is a vocabulary problem to fix by choosing a token from the design-system docs, never by a literal.`,
1381
+ line: lineNum
1382
+ });
1383
+ }
1384
+ const hexMatch = line.match(/#[0-9a-fA-F]{3,8}\b/);
1385
+ if (hexMatch && !assetEscapedLines.has(lineNum)) {
1386
+ if (literalFallback !== null && literalFallback.literal.includes(hexMatch[0])) ; else if (!line.includes("// fallback")) {
1387
+ issues.push({
1388
+ tier: 0,
1389
+ result: "fail",
1390
+ category: "tokens",
1391
+ subcategory: "hex-color",
1392
+ severity: "critical",
1393
+ description: `Hardcoded color "${hexMatch[0]}" breaks theme switching \u2014 use design tokens.`,
1394
+ fix: `Replace with a primitive variant (Button variant="primary"|Badge variant="success"|...) OR a bare token reference like var(--ggui-color-primary-500) \u2014 never a literal fallback. Hardcoded colors mean the operator's theme has no effect on this surface.`,
1395
+ line: lineNum
1396
+ });
1397
+ }
1398
+ }
1399
+ const colorFnMatch = line.match(/\b(rgba?|hsla?)\s*\(/);
1400
+ if (colorFnMatch && !line.includes("var(--ggui-") && !line.includes("// fallback") && !line.includes("$value") && !assetEscapedLines.has(lineNum)) {
1401
+ issues.push({
1402
+ tier: 0,
1403
+ result: "fail",
1404
+ category: "tokens",
1405
+ subcategory: "hardcoded-color-fn",
1406
+ severity: "critical",
1407
+ description: `Hardcoded ${colorFnMatch[1]}() breaks theme switching \u2014 use design tokens.`,
1408
+ fix: "Replace with a primitive variant OR a layering role: var(--ggui-color-container), var(--ggui-color-onContainer), var(--ggui-color-outline), etc.",
1409
+ line: lineNum
1410
+ });
1411
+ }
1412
+ const offManifest = /* @__PURE__ */ new Set();
1413
+ for (const m of line.matchAll(TOKEN_REF_RE)) {
1414
+ const name = m[1];
1415
+ if (!MANIFEST_TOKENS.has(name)) offManifest.add(name);
1416
+ }
1417
+ for (const name of offManifest) {
1418
+ issues.push({
1419
+ tier: 0,
1420
+ result: "fail",
1421
+ category: "tokens",
1422
+ subcategory: "off-manifest-token",
1423
+ severity: "critical",
1424
+ description: `Token "${name}" is not in the design system's token manifest \u2014 no theme defines it, so it renders unset.`,
1425
+ fix: `${offManifestHint(name)}Choose a token that exists: the closed vocabulary is the manifest the design-system docs list (colors: var(--ggui-color-primary-500), var(--ggui-color-onContainer), \u2026; spacing: var(--ggui-spacing-4); type: var(--ggui-font-size-sm)). Never invent a name and never add a literal fallback.`,
1426
+ line: lineNum
1427
+ });
1428
+ }
1429
+ const rawSpacingMatch = enforceDesignVocabulary ? line.match(
1430
+ /\b(gap|padding|paddingX|paddingY|margin|radius)\s*=\s*["'][\d.]+(?:px|rem|em)["']/
1431
+ ) : null;
1432
+ if (rawSpacingMatch) {
1433
+ issues.push({
1434
+ tier: 0,
1435
+ result: "fail",
1436
+ category: "tokens",
1437
+ subcategory: "raw-spacing",
1438
+ severity: "critical",
1439
+ description: `\`${rawSpacingMatch[1]}\` uses a raw CSS length \u2014 bypasses the design spacing/radius scale.`,
1440
+ fix: `Use a scale name: ${rawSpacingMatch[1]}="xs|sm|md|lg|xl" (spacing also has none|2xl; radius none|sm|md|lg|xl). For an exact off-scale pixel value pass a number \u2014 ${rawSpacingMatch[1]}={12}.`,
1441
+ line: lineNum
1442
+ });
1443
+ }
1444
+ const namedColorMatch = line.match(
1445
+ /(?:^|[^a-zA-Z-])(?:color|background|backgroundColor|borderColor)\s*:\s*['"]([a-z][a-zA-Z]+)['"]/
1446
+ );
1447
+ if (namedColorMatch) {
1448
+ const named = namedColorMatch[1].toLowerCase();
1449
+ const allowed = /* @__PURE__ */ new Set([
1450
+ "inherit",
1451
+ "currentcolor",
1452
+ "transparent",
1453
+ "unset",
1454
+ "initial",
1455
+ "revert",
1456
+ "none"
1457
+ ]);
1458
+ const namedCssColors = /* @__PURE__ */ new Set([
1459
+ "red",
1460
+ "green",
1461
+ "blue",
1462
+ "yellow",
1463
+ "orange",
1464
+ "purple",
1465
+ "pink",
1466
+ "cyan",
1467
+ "magenta",
1468
+ "lime",
1469
+ "teal",
1470
+ "navy",
1471
+ "maroon",
1472
+ "olive",
1473
+ "aqua",
1474
+ "fuchsia",
1475
+ "silver",
1476
+ "gray",
1477
+ "grey",
1478
+ "white",
1479
+ "black",
1480
+ "brown",
1481
+ "gold",
1482
+ "indigo",
1483
+ "violet",
1484
+ "crimson",
1485
+ "tomato",
1486
+ "coral",
1487
+ "salmon",
1488
+ "orchid",
1489
+ "plum",
1490
+ "tan",
1491
+ "khaki",
1492
+ "beige",
1493
+ "ivory",
1494
+ "lavender",
1495
+ "mint",
1496
+ "turquoise",
1497
+ "azure",
1498
+ "royalblue",
1499
+ "darkblue",
1500
+ "lightblue",
1501
+ "skyblue",
1502
+ "steelblue",
1503
+ "darkred",
1504
+ "lightgreen",
1505
+ "darkgreen",
1506
+ "forestgreen",
1507
+ "darkorange",
1508
+ "lightgray",
1509
+ "lightgrey",
1510
+ "darkgray",
1511
+ "darkgrey",
1512
+ "hotpink",
1513
+ "deeppink",
1514
+ "lightpink",
1515
+ "lightyellow"
1516
+ ]);
1517
+ if (!allowed.has(named) && namedCssColors.has(named)) {
1518
+ issues.push({
1519
+ tier: 0,
1520
+ result: "fail",
1521
+ category: "tokens",
1522
+ subcategory: "named-color",
1523
+ severity: "critical",
1524
+ description: `Hardcoded CSS named color "${named}" breaks theme switching \u2014 use design tokens.`,
1525
+ fix: `Replace with a typed slot (Text tone="muted" / Box surface="accent" / Badge variant="success") OR a semantic token: var(--ggui-color-onSunken), var(--ggui-color-primary-500), etc. The keyword "${named}" maps to a fixed RGB; the operator's theme has no effect on it.`,
1526
+ line: lineNum
1527
+ });
1528
+ }
1529
+ }
1530
+ const pxMatch = enforceDesignVocabulary ? line.match(/(?:padding|margin|gap|borderRadius)\s*:\s*['"]?\d+px/) : null;
1531
+ if (pxMatch && !line.includes("var(--ggui-")) {
1532
+ issues.push({
1533
+ tier: 0,
1534
+ result: "warn",
1535
+ category: "tokens",
1536
+ subcategory: "raw-pixels",
1537
+ description: "Raw pixel value in spacing \u2014 must use design tokens",
1538
+ fix: 'Replace with a bare var(--ggui-spacing-*) token \u2014 no literal fallback (the runtime injects every token) \u2014 or the scale name on the primitive prop (gap="md", padding="lg")',
1539
+ line: lineNum
1540
+ });
1541
+ }
1542
+ const numericSpacingMatch = enforceDesignVocabulary ? line.match(/\b(padding|paddingX|paddingY|gap|margin)\s*=\s*\{?\s*(\d+)\s*\}?/) : null;
1543
+ if (numericSpacingMatch && !line.includes("var(--ggui-")) {
1544
+ issues.push({
1545
+ tier: 0,
1546
+ result: "warn",
1547
+ category: "tokens",
1548
+ subcategory: "numeric-spacing-prop",
1549
+ description: `Numeric spacing prop ${numericSpacingMatch[1]}={${numericSpacingMatch[2]}} \u2014 use design tokens instead`,
1550
+ fix: `Replace with ${numericSpacingMatch[1]}="var(--ggui-spacing-*)"`,
1551
+ line: lineNum
1552
+ });
1553
+ }
1554
+ }
1555
+ const clickablePattern = /<(?:Card|Box|Stack|Row)\s[^>]*onClick/;
1556
+ for (let i = 0; enforceDesignVocabulary && i < lines.length; i++) {
1557
+ if (clickablePattern.test(lines[i]) && !lines[i].includes("as={Clickable}") && !lines[i].includes("as={Pressable}")) {
1558
+ issues.push({
1559
+ tier: 0,
1560
+ result: "warn",
1561
+ category: "contract",
1562
+ subcategory: "clickable-wrapper",
1563
+ description: `onClick on a structural primitive without as={Clickable} \u2014 the bare primitive has no click or keyboard handling`,
1564
+ fix: `Add as={Clickable} to the element, e.g., <Card as={Clickable} onClick={handler}>`,
1565
+ line: i + 1
1566
+ });
1567
+ }
1568
+ }
1569
+ const bindingRe = designMode === "free" ? /(?:const|let)\s+(\w+)\s*=\s*useAction\s*(?:<[^>]*>)?\s*\(/g : /(?:const|let)\s+(\w+)\s*=\s*useAction\s*\(/g;
1570
+ const actionBindings = [...sourceCode.matchAll(bindingRe)].map((m) => m[1]).filter((name) => typeof name === "string");
1571
+ for (const binding of actionBindings) {
1572
+ const callSites = (sourceCode.match(new RegExp(`\\b${binding}\\s*\\(`, "g")) ?? []).length;
1573
+ if (callSites >= 2) {
1574
+ issues.push({
1575
+ tier: 0,
1576
+ result: "warn",
1577
+ category: "interactivity",
1578
+ subcategory: "double-wired-action",
1579
+ description: `Action '${binding}' is dispatched from ${callSites} call sites \u2014 if an interactive element nests inside another, one gesture fires the action twice (the inner gesture bubbles to the outer handler).`,
1580
+ fix: `Wire each useAction callback to exactly ONE interactive surface; never nest interactive elements (e.g. a Checkbox inside a Card as={Clickable}).`
1581
+ });
1582
+ }
1583
+ }
1584
+ if (actionBindings.length > 0) {
1585
+ issues.push(
1586
+ ...detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode)
1587
+ );
1588
+ }
1589
+ if (/\buseRender\s*\(/.test(sourceCode)) {
1590
+ const suppressionRe = /(?<![\w-])disabled\s*=\s*\{([^}]*)\}/gs;
1591
+ for (const m of sourceCode.matchAll(suppressionRe)) {
1592
+ const expr = m[1];
1593
+ if (!/\bisConnected\b/.test(expr)) continue;
1594
+ const attrLine = sourceCode.slice(0, m.index ?? 0).split("\n").length;
1595
+ issues.push({
1596
+ tier: 0,
1597
+ result: "fail",
1598
+ category: "interactivity",
1599
+ subcategory: "connection-suppression",
1600
+ severity: "critical",
1601
+ description: `\`disabled\` is bound to the connection state (\`${expr.trim().replace(/\s+/g, " ")}\`) \u2014 that strips the click handler, and the click is how the runtime detects that the host can relay actions again. The control stays dead after recovery.`,
1602
+ fix: "Leave the control enabled \u2014 never bind `disabled` to the connection state. The runtime presents the dead zone itself (a standing notice, then a per-gesture cue) and the attempt is how it detects recovery; a suppressed control stays dead after the host recovers.",
1603
+ line: attrLine
1604
+ });
1605
+ }
1606
+ }
1607
+ const propsInterfaceMatch = sourceCode.match(/interface Props\s*\{([^}]+)\}/);
1608
+ if (propsInterfaceMatch) {
1609
+ const propsBody = propsInterfaceMatch[1];
1610
+ const optionalProps = [...propsBody.matchAll(/(\w+)\?:/g)].map((m) => m[1]);
1611
+ for (const prop of optionalProps) {
1612
+ const accessPattern = new RegExp(`props\\.${prop}[.\\[]`, "g");
1613
+ const guardPattern = new RegExp(`props\\.${prop}(\\?[.[]|\\s*&&|\\s*\\?\\?)`, "g");
1614
+ const accesses = (sourceCode.match(accessPattern) || []).length;
1615
+ const guards = (sourceCode.match(guardPattern) || []).length;
1616
+ if (accesses > 0 && guards === 0) {
1617
+ issues.push({
1618
+ tier: 0,
1619
+ result: "warn",
1620
+ category: "crash",
1621
+ subcategory: `optional-prop:${prop}`,
1622
+ description: `Optional prop 'props.${prop}' accessed without null guard \u2014 will crash if undefined`,
1623
+ fix: `Use props.${prop}?.field or props.${prop} ?? fallback or {props.${prop} && ...}`
1624
+ });
1625
+ }
1626
+ }
1627
+ }
1628
+ const ALIGN_VALUES = /* @__PURE__ */ new Set(["start", "center", "end", "stretch"]);
1629
+ const JUSTIFY_VALUES = /* @__PURE__ */ new Set(["start", "center", "end", "between", "around", "evenly"]);
1630
+ const tagRegex = /<(Stack|Row)\b([^>]*)>/gs;
1631
+ for (const tag of enforceDesignVocabulary ? sourceCode.matchAll(tagRegex) : []) {
1632
+ const tagName = tag[1];
1633
+ const attrs = tag[2];
1634
+ const tagLine = sourceCode.slice(0, tag.index).split("\n").length;
1635
+ if (/\bpadding=/.test(attrs)) {
1636
+ issues.push({
1637
+ tier: 0,
1638
+ result: "fail",
1639
+ category: "types",
1640
+ subcategory: "stack-row-padding",
1641
+ description: `<${tagName}> does not accept a 'padding' prop`,
1642
+ fix: `Wrap the children in <Box padding="..."> or use the parent <Card padding="...">`,
1643
+ line: tagLine
1644
+ });
1645
+ }
1646
+ const alignAttr = attrs.match(/\balign=["']([^"']+)["']/);
1647
+ if (alignAttr && !ALIGN_VALUES.has(alignAttr[1])) {
1648
+ issues.push({
1649
+ tier: 0,
1650
+ result: "fail",
1651
+ category: "types",
1652
+ subcategory: "stack-row-align",
1653
+ description: `align="${alignAttr[1]}" is invalid on <${tagName}>`,
1654
+ fix: `Use align="start" | "center" | "end" | "stretch" (NOT flex-start/flex-end/space-between)`,
1655
+ line: tagLine
1656
+ });
1657
+ }
1658
+ const justifyAttr = attrs.match(/\bjustify=["']([^"']+)["']/);
1659
+ if (justifyAttr && !JUSTIFY_VALUES.has(justifyAttr[1])) {
1660
+ issues.push({
1661
+ tier: 0,
1662
+ result: "fail",
1663
+ category: "types",
1664
+ subcategory: "stack-row-justify",
1665
+ description: `justify="${justifyAttr[1]}" is invalid on <${tagName}>`,
1666
+ fix: `Use justify="start" | "center" | "end" | "between" | "around" | "evenly"`,
1667
+ line: tagLine
1668
+ });
1669
+ }
1670
+ }
1671
+ for (const tag of enforceDesignVocabulary ? kitControlTags(sourceCode) : []) {
1672
+ const key = inlineStyleKey(tag.attrs, sourceCode, CONTROL_BORDER_KEY);
1673
+ if (key === null) continue;
1674
+ issues.push({
1675
+ tier: 0,
1676
+ result: "fail",
1677
+ category: "tokens",
1678
+ subcategory: "control-inline-border",
1679
+ severity: "critical",
1680
+ description: `control-inline-border: <${tag.name}> sets an inline \`${key}\` \u2014 a kit control draws its own boundary, and an inline border paints over it`,
1681
+ fix: `Remove the border from <${tag.name}>'s style (keep borderRadius if you need it). An outline chip is <Button variant="outline"> exactly as it comes; a control-like edge on your own element uses var(--ggui-color-controlOutline).`,
1682
+ line: tag.line
1683
+ });
1684
+ }
1685
+ for (const tag of enforceDesignVocabulary ? kitControlTags(sourceCode) : []) {
1686
+ const key = inlineStyleKey(tag.attrs, sourceCode, CONTROL_COLOR_KEY);
1687
+ if (key === null) continue;
1688
+ issues.push({
1689
+ tier: 0,
1690
+ result: "fail",
1691
+ category: "tokens",
1692
+ subcategory: "control-inline-color",
1693
+ severity: "critical",
1694
+ description: `control-inline-color: <${tag.name}> sets an inline \`${key}\` \u2014 a kit control's variant carries its ink and fill, and an inline one paints over it`,
1695
+ fix: `Remove \`${key}\` from <${tag.name}>'s style and pick the variant that carries the look (e.g. <Button variant="primary"> for the main action, variant="outline" for a chip). Colour your own element, not the kit's control.`,
1696
+ line: tag.line
1697
+ });
1698
+ }
1699
+ const bandEdge = enforceDesignVocabulary ? detectBandEdgeLost(sourceCode) : null;
1700
+ if (bandEdge !== null) {
1701
+ issues.push({
1702
+ tier: 0,
1703
+ result: "warn",
1704
+ category: "layout",
1705
+ subcategory: `band-edge-lost:${bandEdge.shape}`,
1706
+ severity: "major",
1707
+ description: bandEdge.shape === "hero-card-first" ? `band-edge-lost: a surface="hero" Card opens the card among other children; a Card is a tile and keeps the card's inset (read from the source${bandEdge.conditional ? "; it renders conditionally" : ""})` : `band-edge-lost: a surface="hero" band that is not the root's first child floats inside the card's inset (read from the source${bandEdge.conditional ? "; it, or everything before it, renders conditionally, and it was read where it renders" : ""})`,
1708
+ fix: bandEdge.shape === "hero-card-first" ? 'If this is the opening band, make it <Box surface="hero" bleed> (a Box, no radius, shadow or border of its own). A hero tile belongs among other tiles, in a grid or a row, not opening the card.' : `Make the opening band the root's FIRST child as <Box surface="hero" bleed> (not wrapped in a Stack, not after other content). A band is a Box with no radius, shadow or border of its own; a hero Card is a tile and stays where it is. Put a conditional notice after the band, not before it.`,
1709
+ line: bandEdge.line
1710
+ });
1711
+ }
1712
+ if (!sourceCode.includes("interface Props") && !sourceCode.includes("type Props")) {
1713
+ issues.push({
1714
+ tier: 0,
1715
+ result: "fail",
1716
+ category: "types",
1717
+ subcategory: "props-interface",
1718
+ severity: "critical",
1719
+ description: "No Props interface found \u2014 data is likely hardcoded",
1720
+ fix: "Add interface Props { ... } with typed fields and default values in the function signature"
1721
+ });
1722
+ }
1723
+ if (!sourceCode.includes("export default function")) {
1724
+ issues.push({
1725
+ tier: 0,
1726
+ result: "fail",
1727
+ category: "compile",
1728
+ subcategory: "default-export",
1729
+ severity: "critical",
1730
+ description: "Missing default export function",
1731
+ fix: "Add export default function Component(props: Props) { ... }"
1732
+ });
1733
+ }
1734
+ const neverReturns = detectComponentNeverReturns(sourceCode);
1735
+ if (neverReturns !== null) {
1736
+ issues.push({
1737
+ tier: 0,
1738
+ result: "fail",
1739
+ category: "compile",
1740
+ subcategory: "renders-nothing",
1741
+ severity: "critical",
1742
+ description: neverReturns.jsxStatementLine === void 0 ? "The component never returns its tree \u2014 it renders a blank frame" : `The component never returns its tree: the JSX at line ${neverReturns.jsxStatementLine} sits at statement position, so it is built and thrown away and the frame renders blank`,
1743
+ fix: "Wrap the tree in the return the boilerplate gave you: `return (` before the root element and `);` after it.",
1744
+ ...neverReturns.jsxStatementLine === void 0 ? {} : { line: neverReturns.jsxStatementLine }
1745
+ });
1746
+ }
1747
+ if (contract) {
1748
+ try {
1749
+ const contractIssues = validateAllContracts(sourceCode, contract);
1750
+ for (const ci of contractIssues) {
1751
+ issues.push({
1752
+ tier: 0,
1753
+ result: ci.severity === "error" ? "fail" : "warn",
1754
+ category: "contract",
1755
+ subcategory: ci.field,
1756
+ severity: ci.severity === "error" ? "critical" : "major",
1757
+ description: ci.message,
1758
+ fix: ci.fix
1759
+ });
1760
+ }
1761
+ } catch {
1762
+ }
1763
+ const fnBody = sourceCode.slice(sourceCode.indexOf("export default function"));
1764
+ if (contract.actionSpec) {
1765
+ for (const actionName of Object.keys(contract.actionSpec)) {
1766
+ if (!fnBody.includes(actionName)) {
1767
+ issues.push({
1768
+ tier: 0,
1769
+ result: "warn",
1770
+ category: "contract",
1771
+ subcategory: `action:${actionName}`,
1772
+ description: `Action hook '${actionName}' from contract is declared but never used in the component`,
1773
+ fix: `Wire ${actionName}() to a Button onClick, form onSubmit, or other user interaction`
1774
+ });
1775
+ }
1776
+ }
1777
+ }
1778
+ if (contract.streamSpec) {
1779
+ for (const channelName of Object.keys(contract.streamSpec)) {
1780
+ if (!fnBody.includes(channelName)) {
1781
+ issues.push({
1782
+ tier: 0,
1783
+ result: "warn",
1784
+ category: "contract",
1785
+ subcategory: `stream:${channelName}`,
1786
+ description: `Stream hook '${channelName}' from contract is declared but never rendered in the component`,
1787
+ fix: `Render ${channelName}.latest data in the JSX (with null guard: ${channelName}.latest && ...)`
1788
+ });
1789
+ }
1790
+ }
1791
+ }
1792
+ if (contract.clientCapabilities) {
1793
+ for (const use of listContractGadgets(contract)) {
1794
+ if (!fnBody.includes(use.name)) {
1795
+ issues.push({
1796
+ tier: 0,
1797
+ result: "warn",
1798
+ category: "contract",
1799
+ subcategory: `clientCapability:${use.name}`,
1800
+ description: `Client capability '${use.name}' from contract is declared but the export is never used`,
1801
+ fix: HOOK_NAME_RE.test(use.name) ? `Import \`${use.name}\` from \`${use.package}\` and call it inside the component \u2014 bind the return value and surface its \`.value\` / \`.status\` in JSX.` : `Import \`${use.name}\` from \`${use.package}\` and render it as a JSX element (\`<${use.name} \u2026 />\`).`
1802
+ });
1803
+ }
1804
+ }
1805
+ }
1806
+ try {
1807
+ const report = checkWirePreservation(sourceCode, contract);
1808
+ for (const site of report.missing) {
1809
+ const hook = HOOK_NAME_FOR[site.kind];
1810
+ const fix = site.kind === "context" ? `Restore \`const [${site.name}, set${site.name.charAt(0).toUpperCase() + site.name.slice(1)}] = ${hook}('${site.name}')\` at the top of the component body. The boilerplate auto-emits this destructure for every declared contextSpec slot \u2014 do not delete it.` : `Restore \`const ${site.name} = ${hook}('${site.name}')\` at the top of the component body and consume the returned binding (in JSX, a callback, or an effect).`;
1811
+ issues.push({
1812
+ tier: 0,
1813
+ result: "fail",
1814
+ category: "contract",
1815
+ subcategory: `wire_preservation:${site.kind}:${site.name}`,
1816
+ severity: "critical",
1817
+ description: `Contract declares ${site.kind} '${site.name}' but no ${hook}('${site.name}') call exists in the component. The boilerplate placed this hook for you \u2014 do not delete it.`,
1818
+ fix
1819
+ });
1820
+ }
1821
+ } catch {
1822
+ }
1823
+ if (contract.propsSpec) {
1824
+ const propsProperties = contract.propsSpec.properties ?? {};
1825
+ for (const propName of Object.keys(propsProperties)) {
1826
+ if (!fnBody.includes(`props.${propName}`) && !fnBody.includes(`{props.${propName}}`)) {
1827
+ issues.push({
1828
+ tier: 0,
1829
+ result: "warn",
1830
+ category: "contract",
1831
+ subcategory: `prop:${propName}`,
1832
+ description: `Props field '${propName}' from contract is never rendered \u2014 data is wasted`,
1833
+ fix: `Render props.${propName} in the JSX (e.g., <Text>{props.${propName}}</Text>)`
1834
+ });
1835
+ }
1836
+ }
1837
+ }
1838
+ }
1839
+ try {
1840
+ const contractsForExtraCheck = contract ?? {};
1841
+ const extras = checkWirePreservation(sourceCode, contractsForExtraCheck).extra;
1842
+ for (const site of extras) {
1843
+ const hook = HOOK_NAME_FOR[site.kind];
1844
+ const field = CONTRACT_FIELD_FOR[site.kind];
1845
+ issues.push({
1846
+ tier: 0,
1847
+ result: "fail",
1848
+ category: "contract",
1849
+ subcategory: `wire_undeclared:${site.kind}:${site.name}`,
1850
+ severity: "critical",
1851
+ description: `Component calls ${hook}('${site.name}') but the contract does not declare ${site.kind} '${site.name}'. Every wire reference in the generated code MUST correspond to a declared entry on the agent-authored contract \u2014 the runtime mounts/registers wire surfaces from the contract, not from the code. ` + (contract === void 0 ? "No contract authored at all \u2014 describing the contract in the prompt text is NOT enough; the agent MUST pass a structured `contract` field on the ggui_render call." : ""),
1852
+ fix: `Either (a) declare \`${field}.${site.name}\` on the \`contract\` input so the runtime registers the surface for this UI, or (b) remove the ${hook}('${site.name}') call from the component if it isn't part of the intended wire surface.`
1853
+ });
1854
+ }
1855
+ } catch {
1856
+ }
1857
+ try {
1858
+ const importReport = checkWireImports(sourceCode);
1859
+ for (const miss of importReport.missing) {
1860
+ issues.push({
1861
+ tier: 0,
1862
+ result: "fail",
1863
+ category: "imports",
1864
+ subcategory: `wire_import_missing:${miss.hook}`,
1865
+ severity: "critical",
1866
+ description: `Component calls ${miss.hook}(...) but does not import it from '@ggui-ai/wire'. Without the import, rewriteImports has no specifier to attach the data-URL shim to, so the hook is undeclared at browser eval time and the component crashes on mount with \`ReferenceError: ${miss.hook} is not defined\`.`,
1867
+ fix: `Add \`import { ${miss.hook} } from '@ggui-ai/wire';\` at the top of the file alongside the other imports.`
1868
+ });
1869
+ }
1870
+ } catch {
1871
+ }
1872
+ {
1873
+ const requireRe = /require\s*\(\s*['"](@[^'"]+)['"]\s*\)/g;
1874
+ const seen = /* @__PURE__ */ new Set();
1875
+ let m;
1876
+ while ((m = requireRe.exec(sourceCode)) !== null) {
1877
+ const pkg = m[1];
1878
+ if (pkg === void 0 || seen.has(pkg)) continue;
1879
+ seen.add(pkg);
1880
+ issues.push({
1881
+ tier: 0,
1882
+ result: "fail",
1883
+ category: "imports",
1884
+ subcategory: `require_disallowed:${pkg}`,
1885
+ severity: "critical",
1886
+ description: `Component uses \`require('${pkg}')\` \u2014 CommonJS require is not available in the iframe's browser ESM runtime, and the import-rewriter only attaches data-URL shims to STATIC import specifiers. The component will fail to mount with \`ReferenceError: require is not defined\` at the first call.`,
1887
+ fix: `Replace with a top-level static \`import { \u2026 } from '${pkg}'\` at the top of the file. The boilerplate emits this import for you when the contract declares a matching \`clientCapabilities.gadgets[*]\` entry \u2014 restore the line instead of inlining a require() call.`
1888
+ });
1889
+ }
1890
+ }
1891
+ {
1892
+ const gadgetScanSource = stripComments(sourceCode);
1893
+ for (const use of contract ? listContractGadgets(contract) : []) {
1894
+ const exportName = use.name;
1895
+ if (isGadgetExportImported(gadgetScanSource, use.package, exportName)) {
1896
+ continue;
1897
+ }
1898
+ const isComponent = !HOOK_NAME_RE.test(exportName);
1899
+ const kind = isComponent ? "component" : "hook";
1900
+ const bindingName = exportName.length > 3 ? exportName.charAt(3).toLowerCase() + exportName.slice(4) : exportName;
1901
+ const fix = isComponent ? `The component \`${exportName}\` is REAL and CORRECT \u2014 do NOT remove it. Restore the gadget plumbing in 2 steps:
1902
+ (1) Keep \`import { ${exportName} } from '${use.package}';\` at the top of the file.
1903
+ (2) RENDER \`<${exportName} \u2026 />\` as a JSX element in the tree you return \u2014 pass its props from the contract. Do NOT call it like a hook.
1904
+ Import \`${exportName}\` ONLY from '${use.package}' \u2014 that is the package it is registered under. If you remove the import again, this check will fail again \u2014 \`${exportName}\` is not optional.` : `The hook \`${exportName}\` is REAL and CORRECT \u2014 do NOT remove it. Restore the gadget plumbing in 2 steps:
1905
+ (1) Keep \`import { ${exportName} } from '${use.package}';\` at the top of the file.
1906
+ (2) CALL \`${exportName}(...)\` inside the component body and render its return value. Example:
1907
+ \`const ${bindingName} = ${exportName}({ /* props from contract */ });\`
1908
+ \`return <div>{/* render ${bindingName} */}</div>;\`
1909
+ Import \`${exportName}\` ONLY from '${use.package}' \u2014 that is the package it is registered under. If you remove the import again, this check will fail again \u2014 \`${exportName}\` is not optional.`;
1910
+ issues.push({
1911
+ tier: 0,
1912
+ result: "fail",
1913
+ category: "imports",
1914
+ subcategory: `gadget_preservation:${exportName}`,
1915
+ severity: "critical",
1916
+ description: `Contract declares \`clientCapabilities.gadgets['${use.package}']['${exportName}']\` (a ${kind}) but the component does not import \`${exportName}\` from \`${use.package}\`. The boilerplate emits \`import { ${exportName} } from '${use.package}';\` \u2014 keep it. Without the import the iframe runtime cannot resolve the ${kind} and the registered gadget is unreachable.`,
1917
+ fix
1918
+ });
1919
+ }
1920
+ }
1921
+ const [typeResult, reactResult] = await Promise.all([
1922
+ typecheck(sourceCode, gadgetTypes).catch((err) => {
1923
+ console.warn("[runTier0Checks] TypeChecker failed:", err instanceof Error ? err.message : String(err));
1924
+ return null;
1925
+ }),
1926
+ lintReactHooks(sourceCode).catch((err) => {
1927
+ console.warn("[runTier0Checks] React linter failed:", err instanceof Error ? err.message : String(err));
1928
+ return [];
1929
+ })
1930
+ ]);
1931
+ if (typeResult) {
1932
+ for (const error of typeResult.errors) {
1933
+ issues.push({
1934
+ tier: 0,
1935
+ result: "fail",
1936
+ category: "types",
1937
+ subcategory: `ts${error.code}`,
1938
+ description: error.message,
1939
+ fix: error.fix,
1940
+ line: error.line
1941
+ });
1942
+ }
1943
+ for (const warning of typeResult.warnings) {
1944
+ issues.push({
1945
+ tier: 0,
1946
+ result: "warn",
1947
+ category: "types",
1948
+ subcategory: `ts${warning.code}`,
1949
+ description: warning.message,
1950
+ fix: warning.fix,
1951
+ line: warning.line
1952
+ });
1953
+ }
1954
+ }
1955
+ for (const diag of reactResult) {
1956
+ issues.push({
1957
+ tier: 0,
1958
+ result: diag.severity === "error" ? "fail" : "warn",
1959
+ category: "types",
1960
+ subcategory: diag.rule,
1961
+ description: diag.message,
1962
+ fix: diag.fix,
1963
+ line: diag.line
1964
+ });
1965
+ }
1966
+ for (const issue of issues) {
1967
+ if (!issue.priority) issue.priority = priorityForIssue(issue.category);
1968
+ }
1969
+ return issues;
1970
+ }
1971
+ async function runTier0(sourceCode, contract, designMode = DEFAULT_DESIGN_MODE) {
1972
+ const issues = await runTier0Checks(sourceCode, contract, void 0, void 0, designMode);
1973
+ const pass = [];
1974
+ const failedCategories = new Set(issues.map((i) => i.category));
1975
+ const tier0Categories = ["compile", "security", "contract", "types", "imports", "tokens"];
1976
+ for (const cat of tier0Categories) {
1977
+ if (!failedCategories.has(cat)) {
1978
+ pass.push(cat);
1979
+ }
1980
+ }
1981
+ return { issues, pass };
1982
+ }
1983
+
1984
+ // src/check/sample-props.ts
1985
+ function generateSampleProps(spec) {
1986
+ const props = {};
1987
+ for (const [name, entry] of Object.entries(spec.properties)) {
1988
+ if (entry.example !== void 0) {
1989
+ props[name] = entry.example;
1990
+ continue;
1991
+ }
1992
+ if (entry.default !== void 0) {
1993
+ props[name] = entry.default;
1994
+ continue;
1995
+ }
1996
+ props[name] = synthesizeFromSchema(entry.schema);
1997
+ }
1998
+ return props;
1999
+ }
2000
+ function synthesizeFromSchema(schema) {
2001
+ const declared = Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") : schema.type;
2002
+ switch (declared) {
2003
+ case "string":
2004
+ return "sample";
2005
+ case "number":
2006
+ case "integer":
2007
+ return 0;
2008
+ case "boolean":
2009
+ return false;
2010
+ case "null":
2011
+ return null;
2012
+ case "array": {
2013
+ if (schema.items) return [synthesizeFromSchema(schema.items) ?? null];
2014
+ return [];
2015
+ }
2016
+ case "object": {
2017
+ const obj = {};
2018
+ if (schema.properties) {
2019
+ for (const [k, v] of Object.entries(schema.properties)) {
2020
+ obj[k] = synthesizeFromSchema(v);
2021
+ }
2022
+ }
2023
+ return obj;
2024
+ }
2025
+ default:
2026
+ return void 0;
2027
+ }
2028
+ }
2029
+
2030
+ export { checkWireImports, checkWirePreservation, collectExpectedWires, extractPropsInterface, extractWireCallSites, extractWireImports, generateSampleProps, inferPropsSpecFromSampleData, lintReactHooks, propsSpecToTypeScript, runTier0, runTier0Checks, typecheck, validateActionSpecConformance, validateAllContracts, validatePropsAgainstSchema, validateStreamSpecConformance };
2031
+ //# sourceMappingURL=chunk-FWJIMKFZ.js.map
2032
+ //# sourceMappingURL=chunk-FWJIMKFZ.js.map