@autoview/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +407 -0
  3. package/lib/AutoViewAgent.d.ts +109 -0
  4. package/lib/AutoViewAgent.js +123 -0
  5. package/lib/AutoViewAgent.js.map +1 -0
  6. package/lib/agent/emitMcpServer.d.ts +15 -0
  7. package/lib/agent/emitMcpServer.js +157 -0
  8. package/lib/agent/emitMcpServer.js.map +1 -0
  9. package/lib/agent/emitReport.d.ts +14 -0
  10. package/lib/agent/emitReport.js +85 -0
  11. package/lib/agent/emitReport.js.map +1 -0
  12. package/lib/agent/toolSurface.d.ts +130 -0
  13. package/lib/agent/toolSurface.js +342 -0
  14. package/lib/agent/toolSurface.js.map +1 -0
  15. package/lib/agent/verifyAgentTasks.d.ts +87 -0
  16. package/lib/agent/verifyAgentTasks.js +126 -0
  17. package/lib/agent/verifyAgentTasks.js.map +1 -0
  18. package/lib/cli/main.d.ts +2 -0
  19. package/lib/cli/main.js +295 -0
  20. package/lib/cli/main.js.map +1 -0
  21. package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
  22. package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
  23. package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
  24. package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
  25. package/lib/constants/AutoViewFrontendTemplate.js +46 -0
  26. package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
  27. package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
  28. package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
  29. package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
  30. package/lib/context/IAutoViewAgentContext.d.ts +60 -0
  31. package/lib/context/IAutoViewAgentContext.js +3 -0
  32. package/lib/context/IAutoViewAgentContext.js.map +1 -0
  33. package/lib/fromSwagger.d.ts +53 -0
  34. package/lib/fromSwagger.js +513 -0
  35. package/lib/fromSwagger.js.map +1 -0
  36. package/lib/generateDeterministic.d.ts +26 -0
  37. package/lib/generateDeterministic.js +75 -0
  38. package/lib/generateDeterministic.js.map +1 -0
  39. package/lib/index.d.ts +15 -0
  40. package/lib/index.js +41 -0
  41. package/lib/index.js.map +1 -0
  42. package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
  43. package/lib/orchestrate/orchestrateAutoView.js +491 -0
  44. package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
  45. package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
  46. package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
  47. package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
  48. package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
  49. package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
  50. package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
  51. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
  52. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
  53. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
  54. package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
  55. package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
  56. package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
  57. package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
  58. package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
  59. package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
  60. package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
  61. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
  62. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
  63. package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
  64. package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
  65. package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
  66. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
  67. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
  68. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
  69. package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
  70. package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
  71. package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
  72. package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
  73. package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
  74. package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
  75. package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
  76. package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
  77. package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
  78. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
  79. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
  80. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
  81. package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
  82. package/lib/orchestrate/utils/HistoryMessage.js +25 -0
  83. package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
  84. package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
  85. package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
  86. package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
  87. package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
  88. package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
  89. package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
  90. package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
  91. package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
  92. package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
  93. package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
  94. package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
  95. package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
  96. package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
  97. package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
  98. package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
  99. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
  100. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
  101. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
  102. package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
  103. package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
  104. package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
  105. package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
  106. package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
  107. package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
  108. package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
  109. package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
  110. package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
  111. package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
  112. package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
  113. package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
  114. package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
  115. package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
  116. package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
  117. package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
  118. package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
  119. package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
  120. package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
  121. package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
  122. package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
  123. package/lib/preview/renderPreview.d.ts +22 -0
  124. package/lib/preview/renderPreview.js +198 -0
  125. package/lib/preview/renderPreview.js.map +1 -0
  126. package/lib/typings/compiler.d.ts +39 -0
  127. package/lib/typings/compiler.js +3 -0
  128. package/lib/typings/compiler.js.map +1 -0
  129. package/lib/typings/events.d.ts +106 -0
  130. package/lib/typings/events.js +3 -0
  131. package/lib/typings/events.js.map +1 -0
  132. package/lib/typings/index.d.ts +10 -0
  133. package/lib/typings/index.js +27 -0
  134. package/lib/typings/index.js.map +1 -0
  135. package/lib/typings/misc.d.ts +78 -0
  136. package/lib/typings/misc.js +3 -0
  137. package/lib/typings/misc.js.map +1 -0
  138. package/lib/utils/ArrayUtil.d.ts +8 -0
  139. package/lib/utils/ArrayUtil.js +30 -0
  140. package/lib/utils/ArrayUtil.js.map +1 -0
  141. package/lib/utils/StringUtil.d.ts +11 -0
  142. package/lib/utils/StringUtil.js +28 -0
  143. package/lib/utils/StringUtil.js.map +1 -0
  144. package/lib/utils/classifyEndpoints.d.ts +62 -0
  145. package/lib/utils/classifyEndpoints.js +216 -0
  146. package/lib/utils/classifyEndpoints.js.map +1 -0
  147. package/lib/utils/endpointFilter.d.ts +26 -0
  148. package/lib/utils/endpointFilter.js +0 -0
  149. package/lib/utils/endpointFilter.js.map +1 -0
  150. package/lib/utils/extractFields.d.ts +85 -0
  151. package/lib/utils/extractFields.js +231 -0
  152. package/lib/utils/extractFields.js.map +1 -0
  153. package/lib/utils/index.d.ts +13 -0
  154. package/lib/utils/index.js +30 -0
  155. package/lib/utils/index.js.map +1 -0
  156. package/lib/utils/normalizeForNestia.d.ts +34 -0
  157. package/lib/utils/normalizeForNestia.js +133 -0
  158. package/lib/utils/normalizeForNestia.js.map +1 -0
  159. package/lib/utils/resourcePlan.d.ts +39 -0
  160. package/lib/utils/resourcePlan.js +95 -0
  161. package/lib/utils/resourcePlan.js.map +1 -0
  162. package/lib/utils/sliceDocument.d.ts +17 -0
  163. package/lib/utils/sliceDocument.js +114 -0
  164. package/lib/utils/sliceDocument.js.map +1 -0
  165. package/lib/utils/toEndpoints.d.ts +90 -0
  166. package/lib/utils/toEndpoints.js +227 -0
  167. package/lib/utils/toEndpoints.js.map +1 -0
  168. package/lib/verify/runWorkflows.d.ts +25 -0
  169. package/lib/verify/runWorkflows.js +366 -0
  170. package/lib/verify/runWorkflows.js.map +1 -0
  171. package/lib/verify/workflows.d.ts +53 -0
  172. package/lib/verify/workflows.js +107 -0
  173. package/lib/verify/workflows.js.map +1 -0
  174. package/package.json +82 -0
  175. package/prompts/AUTOVIEW_RENDER.md +398 -0
  176. package/prompts/AUTOVIEW_REVIEW.md +60 -0
  177. package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
  178. package/src/AutoViewAgent.ts +222 -0
  179. package/src/agent/emitMcpServer.integration.test.ts +168 -0
  180. package/src/agent/emitMcpServer.test.ts +51 -0
  181. package/src/agent/emitMcpServer.ts +178 -0
  182. package/src/agent/emitReport.ts +117 -0
  183. package/src/agent/toolSurface.test.ts +243 -0
  184. package/src/agent/toolSurface.ts +501 -0
  185. package/src/agent/verifyAgentTasks.test.ts +106 -0
  186. package/src/agent/verifyAgentTasks.ts +171 -0
  187. package/src/cli/main.ts +363 -0
  188. package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
  189. package/src/constants/AutoViewFrontendTemplate.ts +42 -0
  190. package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
  191. package/src/context/IAutoViewAgentContext.ts +84 -0
  192. package/src/fromSwagger.test.ts +269 -0
  193. package/src/fromSwagger.ts +500 -0
  194. package/src/generateDeterministic.test.ts +39 -0
  195. package/src/generateDeterministic.ts +77 -0
  196. package/src/index.ts +30 -0
  197. package/src/orchestrate/orchestrateAutoView.ts +590 -0
  198. package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
  199. package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
  200. package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
  201. package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
  202. package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
  203. package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
  204. package/src/orchestrate/renderNavTs.test.ts +74 -0
  205. package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
  206. package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
  207. package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
  208. package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
  209. package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
  210. package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
  211. package/src/orchestrate/utils/HistoryMessage.ts +41 -0
  212. package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
  213. package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
  214. package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
  215. package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
  216. package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
  217. package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
  218. package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
  219. package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
  220. package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
  221. package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
  222. package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
  223. package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
  224. package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
  225. package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
  226. package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
  227. package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
  228. package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
  229. package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
  230. package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
  231. package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
  232. package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
  233. package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
  234. package/src/preview/renderPreview.ts +273 -0
  235. package/src/typings/compiler.ts +47 -0
  236. package/src/typings/events.ts +155 -0
  237. package/src/typings/index.ts +10 -0
  238. package/src/typings/misc.ts +93 -0
  239. package/src/utils/ArrayUtil.ts +16 -0
  240. package/src/utils/StringUtil.ts +29 -0
  241. package/src/utils/classifyEndpoints.test.ts +86 -0
  242. package/src/utils/classifyEndpoints.ts +291 -0
  243. package/src/utils/endpointFilter.test.ts +50 -0
  244. package/src/utils/endpointFilter.ts +0 -0
  245. package/src/utils/extractFields.test.ts +82 -0
  246. package/src/utils/extractFields.ts +306 -0
  247. package/src/utils/index.ts +13 -0
  248. package/src/utils/normalizeForNestia.test.ts +93 -0
  249. package/src/utils/normalizeForNestia.ts +139 -0
  250. package/src/utils/resourcePlan.test.ts +104 -0
  251. package/src/utils/resourcePlan.ts +180 -0
  252. package/src/utils/sliceDocument.test.ts +85 -0
  253. package/src/utils/sliceDocument.ts +119 -0
  254. package/src/utils/toEndpoints.test.ts +251 -0
  255. package/src/utils/toEndpoints.ts +343 -0
  256. package/src/verify/runWorkflows.ts +403 -0
  257. package/src/verify/workflows.test.ts +117 -0
  258. package/src/verify/workflows.ts +154 -0
  259. package/template/CLAUDE.md +140 -0
  260. package/template/Dockerfile +31 -0
  261. package/template/PROMPT.md +80 -0
  262. package/template/SANDBOX.md +70 -0
  263. package/template/app/api/health/route.ts +10 -0
  264. package/template/app/globals.css +97 -0
  265. package/template/app/layout.tsx +30 -0
  266. package/template/app/page.tsx +19 -0
  267. package/template/components/AppShell.tsx +114 -0
  268. package/template/components/auto/CatalogGrid.tsx +159 -0
  269. package/template/components/auto/ConfirmButton.tsx +67 -0
  270. package/template/components/auto/EmbeddedCollection.tsx +144 -0
  271. package/template/components/auto/ResourceDashboard.tsx +104 -0
  272. package/template/components/auto/ResourceDetail.tsx +93 -0
  273. package/template/components/auto/ResourceForm.tsx +235 -0
  274. package/template/components/auto/ResourceIcon.tsx +88 -0
  275. package/template/components/auto/ResourceLanding.tsx +155 -0
  276. package/template/components/auto/ResourceTable.tsx +223 -0
  277. package/template/components/auto/formatValue.tsx +186 -0
  278. package/template/components/auto/types.ts +42 -0
  279. package/template/components/ui/badge.tsx +40 -0
  280. package/template/components/ui/button.tsx +57 -0
  281. package/template/components/ui/card.tsx +86 -0
  282. package/template/components/ui/dialog.tsx +119 -0
  283. package/template/components/ui/input.tsx +23 -0
  284. package/template/components/ui/label.tsx +24 -0
  285. package/template/components/ui/pagination.tsx +117 -0
  286. package/template/components/ui/select.tsx +92 -0
  287. package/template/components/ui/sheet.tsx +135 -0
  288. package/template/components/ui/skeleton.tsx +15 -0
  289. package/template/components/ui/table.tsx +120 -0
  290. package/template/components/ui/tabs.tsx +55 -0
  291. package/template/lib/utils.ts +35 -0
  292. package/template/next.config.mjs +52 -0
  293. package/template/package.json +46 -0
  294. package/template/postcss.config.js +6 -0
  295. package/template/scripts/start-shopping-backend.sh +56 -0
  296. package/template/tailwind.config.ts +96 -0
  297. package/template/tsconfig.json +29 -0
@@ -0,0 +1,1117 @@
1
+ import { OpenApi } from "@typia/interface";
2
+ import { IAutoViewEndpoint, toEndpoints } from "../utils";
3
+ import { IAgenticaController, IAgenticaHistoryJson } from "@agentica/core";
4
+ import {
5
+ AutoBeAutoViewRenderPageEvent,
6
+ } from "../typings";
7
+ import { IPointer, Singleton } from "tstl";
8
+ import ts from "typescript";
9
+ import typia, { ILlmApplication } from "typia";
10
+ import { v7 } from "uuid";
11
+
12
+ import { AutoViewSystemPromptConstant } from "../constants/AutoViewSystemPromptConstant";
13
+ import { IAutoViewAgentContext } from "../context/IAutoViewAgentContext";
14
+ import { IAutoViewProductPlan } from "./structures/IAutoViewProductPlan";
15
+ import { IAutoViewRenderApplication } from "./structures/IAutoViewRenderApplication";
16
+ import { IAutoViewSdkMap } from "./structures/IAutoViewSdkMap";
17
+ import { HistoryMessage } from "./utils/HistoryMessage";
18
+ import {
19
+ describeEndpointPropsShape,
20
+ describeRequestBodyHint,
21
+ } from "./utils/describeEndpointPropsShape";
22
+ import { describeEndpointRequestBodyShape } from "./utils/describeEndpointRequestBodyShape";
23
+ import { describeEndpointResponseShape } from "./utils/describeEndpointResponseShape";
24
+ import { executeCachedBatch } from "./utils/executeCachedBatch";
25
+
26
+ // Five tries (one initial + four retries). The earlier limit of two
27
+ // retries silently dropped landing pages with multiple orthogonal
28
+ // defects — TC39 mix, unclosed JSX, regex escapes — because the model
29
+ // only managed to fix the first issue per attempt. Five tries gives
30
+ // the retry loop enough budget to converge on common LLM mistakes
31
+ // without ballooning per-page latency past ~30s.
32
+ const MAX_PARSE_RETRIES = 4;
33
+
34
+ /**
35
+ * Phase 4 of the AutoView agent — Render.
36
+ *
37
+ * Generates one TSX page per screen in {@link IAutoViewProductPlan} via parallel
38
+ * LLM calls. Each call returns the complete page source; the orchestrator runs
39
+ * it through TypeScript's parser and, on syntax failure, re-invokes the model
40
+ * with the diagnostic. Pages that still fail after {@link MAX_PARSE_RETRIES}
41
+ * retries are replaced with an inline error placeholder so the wider project
42
+ * keeps compiling.
43
+ *
44
+ * Returns a `path → content` patch keyed by the screen file path
45
+ * (`app/.../page.tsx`) that the orchestrator merges into the scaffold output
46
+ * before the `autoViewComplete` event.
47
+ */
48
+ export interface IAutoViewRenderOutput {
49
+ /** `app/.../page.tsx` → TSX source for every rendered screen. */
50
+ files: Record<string, string>;
51
+
52
+ /** Per-screen render metadata, consumed by the Review phase. */
53
+ screens: IAutoViewRenderOutput.IScreenReport[];
54
+ }
55
+
56
+ export namespace IAutoViewRenderOutput {
57
+ export interface IScreenReport {
58
+ pagePath: string;
59
+ uiPattern: string;
60
+ actor: string;
61
+ rationale: string;
62
+ attempts: number;
63
+ ok: boolean;
64
+ /**
65
+ * Parser diagnostic when `ok === false`. Empty string when the page
66
+ * rendered cleanly.
67
+ */
68
+ diagnostic: string;
69
+ }
70
+ }
71
+
72
+ export async function orchestrateAutoViewRender(
73
+ ctx: IAutoViewAgentContext,
74
+ props: {
75
+ document: OpenApi.IDocument;
76
+ sdkMap: IAutoViewSdkMap;
77
+ productPlan: IAutoViewProductPlan;
78
+ designTheme: string;
79
+ step: number;
80
+ },
81
+ ): Promise<IAutoViewRenderOutput> {
82
+ const total = props.productPlan.screens.length;
83
+ const completed = { value: 0 };
84
+ const files: Record<string, string> = {};
85
+ const screens: IAutoViewRenderOutput.IScreenReport[] = [];
86
+ // Pre-compute the list of frontend routes the plan declared so the
87
+ // render prompt can teach the LLM which `<Link href=...>` targets
88
+ // actually exist. Without this the model routinely linked to SDK
89
+ // accessor paths (`/shoppings/customers/systematic/channels`) that
90
+ // 404 the moment the user clicks them.
91
+ const allScreenPaths = props.productPlan.screens.map((s) => s.path);
92
+
93
+ const semaphoreCap =
94
+ typeof ctx.vendor.semaphore === "number"
95
+ ? ctx.vendor.semaphore
96
+ : ctx.vendor.semaphore !== undefined
97
+ ? ctx.vendor.semaphore.max()
98
+ : 8;
99
+ await executeCachedBatch(
100
+ semaphoreCap,
101
+ props.productPlan.screens.map((screen) => async (promptCacheKey) => {
102
+ const counter = new Singleton(() => ++completed.value);
103
+ const result = await renderOneScreen(ctx, {
104
+ screen,
105
+ document: props.document,
106
+ sdkMap: props.sdkMap,
107
+ designTheme: props.designTheme,
108
+ promptCacheKey,
109
+ allScreenPaths,
110
+ });
111
+ const filePath = screenPathToFile(screen.path);
112
+ files[filePath] = result.tsx;
113
+ // Preserve the LLM's final raw attempt next to the wiki when the
114
+ // page fell back to the placeholder. Without this the operator
115
+ // (and future render-prompt iterations) cannot see what the model
116
+ // actually emitted — only the placeholder text — so the same
117
+ // class of defect keeps recurring across archives.
118
+ if (!result.ok && result.lastAttemptTsx.length > 0) {
119
+ const slug =
120
+ screen.path.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") ||
121
+ "root";
122
+ files[`wiki/render-failed/${slug}.tsx`] = result.lastAttemptTsx;
123
+ }
124
+ screens.push({
125
+ pagePath: screen.path,
126
+ uiPattern: screen.uiPattern as string,
127
+ actor: screen.actor,
128
+ rationale: result.rationale,
129
+ attempts: result.attempts,
130
+ ok: result.ok,
131
+ diagnostic: result.diagnostic,
132
+ });
133
+
134
+ ctx.dispatch({
135
+ type: "autoViewRenderPage",
136
+ id: v7(),
137
+ created_at: new Date().toISOString(),
138
+ step: props.step,
139
+ total,
140
+ completed: counter.get(),
141
+ pagePath: screen.path,
142
+ uiPattern: screen.uiPattern as string,
143
+ attempts: result.attempts,
144
+ ok: result.ok,
145
+ tokenUsage: result.tokenUsage,
146
+ metric: result.metric,
147
+ } satisfies AutoBeAutoViewRenderPageEvent);
148
+ }),
149
+ );
150
+
151
+ return { files, screens };
152
+ }
153
+
154
+ /**
155
+ * Re-render a single screen after the typecheck phase reported diagnostics on
156
+ * its file. Reuses the parser-driven retry loop inside the per-screen render —
157
+ * the first attempt uses {@link buildTypecheckRetryHistories} (which surfaces
158
+ * the diagnostics + previous TSX), and any subsequent retries fall back to
159
+ * {@link buildRetryHistories} so the model still benefits from the deterministic
160
+ * `??`/`||` auto-fix and the syntax retry budget.
161
+ *
162
+ * Returns `null` when the model could not produce parser-valid TSX after the
163
+ * full retry budget; the caller should leave the existing (broken) file in
164
+ * place and report the failure so the operator still gets some output.
165
+ */
166
+ export async function rerenderScreenForTypecheck(
167
+ ctx: IAutoViewAgentContext,
168
+ args: {
169
+ screen: IAutoViewProductPlan.IScreen;
170
+ document: OpenApi.IDocument;
171
+ sdkMap: IAutoViewSdkMap;
172
+ designTheme: string;
173
+ promptCacheKey: string;
174
+ allScreenPaths: readonly string[];
175
+ previousTsx: string;
176
+ typecheckErrors: ITypecheckErrorLine[];
177
+ },
178
+ ): Promise<{ tsx: string; ok: boolean; diagnostic: string } | null> {
179
+ const relevantOperations = toEndpoints(args.document).filter((op) =>
180
+ args.screen.endpoints.includes((op.accessor ?? []).join(".")),
181
+ );
182
+
183
+ let previousTsx = args.previousTsx;
184
+ let lastParseError: string | null = null;
185
+ let attempt = 0;
186
+ for (; attempt <= MAX_PARSE_RETRIES; attempt++) {
187
+ const pointer: IPointer<IAutoViewRenderApplication.IProps | null> = {
188
+ value: null,
189
+ };
190
+ const histories =
191
+ attempt === 0
192
+ ? buildTypecheckRetryHistories({
193
+ ...args,
194
+ relevantOperations,
195
+ previousTsx,
196
+ typecheckErrors: args.typecheckErrors,
197
+ })
198
+ : buildRetryHistories({
199
+ ...args,
200
+ relevantOperations,
201
+ lastTsx: previousTsx,
202
+ lastError: lastParseError ?? "",
203
+ });
204
+ const userMessage =
205
+ attempt === 0
206
+ ? `Fix the typecheck errors on ${args.screen.path}. Re-emit the entire page TSX with the corrections. Call \`renderPage\` once.`
207
+ : `The corrected attempt failed TypeScript parsing: ${lastParseError}. Regenerate the entire page TSX with the fix applied.`;
208
+
209
+ try {
210
+ await ctx.conversate({
211
+ source: "autoViewRenderPage",
212
+ target: args.screen.path,
213
+ controller: createController({
214
+ build: (next) => {
215
+ pointer.value = next;
216
+ },
217
+ }),
218
+ enforceFunctionCall: true,
219
+ promptCacheKey: args.promptCacheKey,
220
+ histories,
221
+ userMessage,
222
+ });
223
+ } catch (error) {
224
+ // Vendor-side failure during the typecheck-driven retry. Don't let
225
+ // it reject the whole retry batch — record and retry, falling
226
+ // through to `null` if the budget runs out so the caller keeps the
227
+ // previously-rendered (typecheck-broken) file instead of aborting
228
+ // the entire run.
229
+ lastParseError = error instanceof Error ? error.message : String(error);
230
+ continue;
231
+ }
232
+ if (pointer.value === null) {
233
+ // Model refused to call renderPage on this attempt — escalate to
234
+ // the parser-retry pathway with the previous TSX still in scope.
235
+ lastParseError = "model did not call renderPage";
236
+ continue;
237
+ }
238
+ const candidate =
239
+ autoWrapNullishCoalescingMix(pointer.value.tsx) ?? pointer.value.tsx;
240
+ const parseError = findSyntaxError(candidate);
241
+ if (parseError === null) {
242
+ return {
243
+ tsx: candidate,
244
+ ok: true,
245
+ diagnostic: "",
246
+ };
247
+ }
248
+ previousTsx = candidate;
249
+ lastParseError = parseError;
250
+ }
251
+ return null;
252
+ }
253
+
254
+ /* -------------------------------------------------------------------------- */
255
+ /* per-screen render with parser-driven retry */
256
+ /* -------------------------------------------------------------------------- */
257
+
258
+ interface IRenderResult {
259
+ tsx: string;
260
+ rationale: string;
261
+ attempts: number;
262
+ ok: boolean;
263
+ /** Parser diagnostic when `ok === false`; empty string otherwise. */
264
+ diagnostic: string;
265
+ /**
266
+ * The TSX the LLM emitted on the final attempt — preserved even when the page
267
+ * fell back to the placeholder so the operator can inspect what the model
268
+ * kept trying. Empty string when `ok === true` (the shipped `tsx` is already
269
+ * the LLM's last attempt).
270
+ */
271
+ lastAttemptTsx: string;
272
+ tokenUsage: AutoBeAutoViewRenderPageEvent["tokenUsage"];
273
+ metric: AutoBeAutoViewRenderPageEvent["metric"];
274
+ }
275
+
276
+ async function renderOneScreen(
277
+ ctx: IAutoViewAgentContext,
278
+ args: {
279
+ screen: IAutoViewProductPlan.IScreen;
280
+ document: OpenApi.IDocument;
281
+ sdkMap: IAutoViewSdkMap;
282
+ designTheme: string;
283
+ promptCacheKey: string;
284
+ allScreenPaths: readonly string[];
285
+ },
286
+ ): Promise<IRenderResult> {
287
+ // Restrict the SDK context to the schemas the screen actually
288
+ // composes; the full document would blow the prompt budget on
289
+ // larger projects.
290
+ const relevantOperations = toEndpoints(args.document).filter((op) =>
291
+ args.screen.endpoints.includes((op.accessor ?? []).join(".")),
292
+ );
293
+
294
+ let lastTsx: string | null = null;
295
+ let lastError: string | null = null;
296
+ let lastRationale: string = "";
297
+ let attempts = 0;
298
+ let lastTokenUsage: AutoBeAutoViewRenderPageEvent["tokenUsage"] | null = null;
299
+ let lastMetric: AutoBeAutoViewRenderPageEvent["metric"] | null = null;
300
+
301
+ for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt++) {
302
+ attempts = attempt + 1;
303
+ const pointer: IPointer<IAutoViewRenderApplication.IProps | null> = {
304
+ value: null,
305
+ };
306
+ const histories =
307
+ attempt === 0
308
+ ? buildInitialHistories({ ...args, relevantOperations })
309
+ : buildRetryHistories({
310
+ ...args,
311
+ relevantOperations,
312
+ lastTsx: lastTsx ?? "",
313
+ lastError: lastError ?? "",
314
+ });
315
+ const userMessage =
316
+ attempt === 0
317
+ ? `Render the page for ${args.screen.path} (${args.screen.uiPattern}). Call \`renderPage\` once.`
318
+ : `The previous attempt failed TypeScript parsing: ${lastError}. Regenerate the entire page TSX with the fix applied. Pay attention to: escaping \`/\` inside regex literals as \`\\/\`, wrapping mixed \`??\` and \`||\` with parentheses, and closing every \`{\` opened inside JSX.`;
319
+
320
+ try {
321
+ const { metric, tokenUsage } = await ctx.conversate({
322
+ source: "autoViewRenderPage",
323
+ target: args.screen.path,
324
+ controller: createController({
325
+ build: (next) => {
326
+ pointer.value = next;
327
+ },
328
+ }),
329
+ enforceFunctionCall: true,
330
+ promptCacheKey: args.promptCacheKey,
331
+ histories,
332
+ userMessage,
333
+ });
334
+ lastTokenUsage = tokenUsage;
335
+ lastMetric = metric;
336
+ } catch (error) {
337
+ // Vendor-side failure (connection drop / undici `terminated`, 5xx,
338
+ // rate limit). On larger SDKs the per-screen render fan-out keeps
339
+ // dozens of LLM calls in flight and one dropped socket would
340
+ // otherwise reject the whole `executeCachedBatch` and abort every
341
+ // other screen mid-flight. Treat it like a failed attempt: record
342
+ // the reason, let the retry loop try again, and fall back to the
343
+ // placeholder if the budget runs out — the project still ships.
344
+ lastError =
345
+ error instanceof Error
346
+ ? `vendor error: ${error.message}`
347
+ : `vendor error: ${String(error)}`;
348
+ continue;
349
+ }
350
+
351
+ if (pointer.value === null) {
352
+ // No function call. Treat as fatal — break to the placeholder.
353
+ break;
354
+ }
355
+ // Deterministic auto-fix: LLMs reliably re-emit `a ?? b || c` /
356
+ // `a || b ?? c` even with hard rules in the prompt. Walk the AST,
357
+ // parenthesize every nullish-coalescing branch nested inside a
358
+ // logical-or / logical-and (and vice versa), and re-check. This
359
+ // costs zero extra LLM calls and turns a guaranteed placeholder
360
+ // into a working page.
361
+ const candidate =
362
+ autoWrapNullishCoalescingMix(pointer.value.tsx) ?? pointer.value.tsx;
363
+ lastTsx = candidate;
364
+ lastRationale = pointer.value.rationale;
365
+ const parseError = findSyntaxError(candidate);
366
+ if (parseError === null) {
367
+ return {
368
+ tsx: candidate,
369
+ rationale: lastRationale,
370
+ attempts,
371
+ ok: true,
372
+ diagnostic: "",
373
+ lastAttemptTsx: "",
374
+ tokenUsage: lastTokenUsage!,
375
+ metric: lastMetric!,
376
+ };
377
+ }
378
+ lastError = parseError;
379
+ }
380
+
381
+ // Exhausted retries — ship a placeholder so the rest of the project
382
+ // still compiles. Surface the reason inline.
383
+ return {
384
+ tsx: buildPlaceholderPage(args.screen, lastError ?? "no rendering"),
385
+ rationale: lastRationale,
386
+ attempts,
387
+ ok: false,
388
+ diagnostic: lastError ?? "model did not call renderPage",
389
+ lastAttemptTsx: lastTsx ?? "",
390
+ tokenUsage: lastTokenUsage ?? zeroTokenUsage(),
391
+ metric: lastMetric ?? zeroMetric(),
392
+ };
393
+ }
394
+
395
+ function zeroTokenUsage(): AutoBeAutoViewRenderPageEvent["tokenUsage"] {
396
+ return {
397
+ total: 0,
398
+ input: { total: 0, cached: 0 },
399
+ output: {
400
+ total: 0,
401
+ reasoning: 0,
402
+ accepted_prediction: 0,
403
+ rejected_prediction: 0,
404
+ },
405
+ };
406
+ }
407
+
408
+ function zeroMetric(): AutoBeAutoViewRenderPageEvent["metric"] {
409
+ return {
410
+ attempt: 0,
411
+ success: 0,
412
+ invalidJson: 0,
413
+ validationFailure: 0,
414
+ consent: 0,
415
+ };
416
+ }
417
+
418
+ /* -------------------------------------------------------------------------- */
419
+ /* prompt history builders */
420
+ /* -------------------------------------------------------------------------- */
421
+
422
+ type ConversateHistory =
423
+ | IAgenticaHistoryJson.ISystemMessage
424
+ | IAgenticaHistoryJson.IAssistantMessage;
425
+
426
+ function buildInitialHistories(args: {
427
+ screen: IAutoViewProductPlan.IScreen;
428
+ sdkMap: IAutoViewSdkMap;
429
+ designTheme: string;
430
+ document: OpenApi.IDocument;
431
+ relevantOperations: IAutoViewEndpoint[];
432
+ allScreenPaths: readonly string[];
433
+ }): ConversateHistory[] {
434
+ // Deterministic per-endpoint `props` shape. The LLM has repeatedly
435
+ // flattened body fields into the top level of `props` (e.g.
436
+ // `index(connection, { limit: 24 })` instead of
437
+ // `index(connection, { body: { limit: 24 } })`), which makes the
438
+ // simulator return a masked 400 and the page renders the empty
439
+ // state. Pre-computing the exact shape kills that whole class of
440
+ // defect at zero LLM cost. Parameterless endpoints render as
441
+ // `(connection)` because the Nestia SDK omits the second arg
442
+ // entirely when there are no path params or body — emitting
443
+ // `(connection, {})` here teaches the LLM a signature that fails
444
+ // `npm run typecheck` with "Expected 1 arguments, but got 2".
445
+ const propsSignatures = args.relevantOperations
446
+ .map((op) => {
447
+ const accessor = (op.accessor ?? []).join(".");
448
+ const propsShape = describeEndpointPropsShape(op);
449
+ const signature =
450
+ propsShape === "{}"
451
+ ? `api.functional.${accessor}(connection)`
452
+ : `api.functional.${accessor}(connection, ${propsShape})`;
453
+ return `- \`${signature}\``;
454
+ })
455
+ .join("\n");
456
+
457
+ // Per-endpoint required-fields hint + sample body literal. Stops the
458
+ // model from sending `{ body: {} }` to endpoints with required fields
459
+ // (cart create, etc.) — which used to come back as a typia validation
460
+ // 400 the user sees as "Unable to load …" with no actionable signal.
461
+ const bodyHints = args.relevantOperations
462
+ .map((op) => {
463
+ const hint = describeRequestBodyHint(op, args.document);
464
+ if (hint.length === 0) return null;
465
+ const accessor = (op.accessor ?? []).join(".");
466
+ return `- \`${accessor}\` → ${hint}`;
467
+ })
468
+ .filter((line): line is string => line !== null)
469
+ .join("\n");
470
+
471
+ // Per-endpoint return-type shape, fully expanded one or two levels. The LLM
472
+ // has the raw OpenAPI JSON below, but routinely invents plausible-sounding
473
+ // properties (`auth.member.email` on an `IAuthorized` whose real shape is
474
+ // `{ id, created_at, token }`). Spelling out the actual fields up front
475
+ // anchors the render against the SDK return type instead of the model's
476
+ // domain priors.
477
+ const responseShapes = args.relevantOperations
478
+ .map((op) => {
479
+ const accessor = (op.accessor ?? []).join(".");
480
+ const shape = describeEndpointResponseShape(op, args.document);
481
+ return `### \`api.functional.${accessor}\` → Promise<${shape}>`;
482
+ })
483
+ .join("\n\n");
484
+
485
+ // Symmetric input-side expansion. The one-line `bodyHints` block above shows
486
+ // required keys + a sample literal but stops short of nested union / array
487
+ // shapes, which is exactly where the LLM hallucinates (sort grammars as
488
+ // `{ field, direction }[]` instead of `("created_at.asc" | ...)[]`, or
489
+ // invented top-level `title` fields on search request types). Surfacing the
490
+ // full body shape closes that gap without changing any other prompt
491
+ // mechanics.
492
+ const requestShapes = args.relevantOperations
493
+ .map((op) => {
494
+ const shape = describeEndpointRequestBodyShape(op, args.document);
495
+ if (shape === null) return null;
496
+ const accessor = (op.accessor ?? []).join(".");
497
+ return `### \`api.functional.${accessor}\` body shape\n\n\`\`\`ts\n${shape}\n\`\`\``;
498
+ })
499
+ .filter((line): line is string => line !== null)
500
+ .join("\n\n");
501
+
502
+ return [
503
+ HistoryMessage.system(AutoViewSystemPromptConstant.AUTOVIEW_RENDER),
504
+ HistoryMessage.assistant`
505
+ Here is the screen to render and everything you need to compose
506
+ it.
507
+
508
+ ## Screen
509
+
510
+ \`\`\`json
511
+ ${JSON.stringify(args.screen, null, 2)}
512
+ \`\`\`
513
+
514
+ ## Allowed internal route paths (use only these in \`<Link href=...>\` / \`router.push()\`)
515
+
516
+ ${args.allScreenPaths.map((p) => `- \`${p}\``).join("\n")}
517
+
518
+ ## SDK call signatures (use these \`props\` shapes verbatim)
519
+
520
+ ${propsSignatures}
521
+
522
+ ## Body required-fields hint (start each \`body: { ... }\` with at least these keys)
523
+
524
+ ${bodyHints.length > 0 ? bodyHints : "(no endpoints with a request body)"}
525
+
526
+ ## SDK return type shapes (use these exact field names + nesting — do not invent properties)
527
+
528
+ ${responseShapes.length > 0 ? responseShapes : "(no endpoints with a response body)"}
529
+
530
+ ## SDK request body shapes (use these exact field names + nesting — do not invent properties)
531
+
532
+ ${requestShapes.length > 0 ? requestShapes : "(no endpoints with a request body)"}
533
+
534
+ ## SDK endpoints used by this screen (full operation payloads)
535
+
536
+ \`\`\`json
537
+ ${JSON.stringify(args.relevantOperations, null, 2)}
538
+ \`\`\`
539
+
540
+ ## Design theme
541
+
542
+ ${args.designTheme.length > 0 ? args.designTheme : "(none — prototype-first, content-first defaults)"}
543
+
544
+ ## Actor context (from the SDK map)
545
+
546
+ \`\`\`json
547
+ ${JSON.stringify(
548
+ args.sdkMap.actors.find((a) => a.name === args.screen.actor) ?? null,
549
+ null,
550
+ 2,
551
+ )}
552
+ \`\`\`
553
+ `,
554
+ ];
555
+ }
556
+
557
+ function buildRetryHistories(args: {
558
+ screen: IAutoViewProductPlan.IScreen;
559
+ sdkMap: IAutoViewSdkMap;
560
+ designTheme: string;
561
+ document: OpenApi.IDocument;
562
+ relevantOperations: IAutoViewEndpoint[];
563
+ allScreenPaths: readonly string[];
564
+ lastTsx: string;
565
+ lastError: string;
566
+ }): ConversateHistory[] {
567
+ return [
568
+ ...buildInitialHistories(args),
569
+ HistoryMessage.assistant`
570
+ My previous attempt failed TypeScript syntax check.
571
+
572
+ ## Previous TSX
573
+
574
+ \`\`\`tsx
575
+ ${args.lastTsx}
576
+ \`\`\`
577
+
578
+ ## Syntax error reported by the compiler
579
+
580
+ \`\`\`
581
+ ${args.lastError}
582
+ \`\`\`
583
+ `,
584
+ ];
585
+ }
586
+
587
+ /**
588
+ * History builder for the typecheck-driven retry pass. Differs from
589
+ * {@link buildRetryHistories} in that it lists every `tsc --noEmit` diagnostic
590
+ * on this file (not a single parser error) and explicitly anchors the LLM to
591
+ * the SDK-shape sections from the initial history — the typical failure mode is
592
+ * the model inventing property names that look plausible against domain priors
593
+ * instead of reading the schema we already spelled out.
594
+ */
595
+ function buildTypecheckRetryHistories(args: {
596
+ screen: IAutoViewProductPlan.IScreen;
597
+ sdkMap: IAutoViewSdkMap;
598
+ designTheme: string;
599
+ document: OpenApi.IDocument;
600
+ relevantOperations: IAutoViewEndpoint[];
601
+ allScreenPaths: readonly string[];
602
+ previousTsx: string;
603
+ typecheckErrors: ITypecheckErrorLine[];
604
+ }): ConversateHistory[] {
605
+ const lines = args.typecheckErrors
606
+ .map((e) => `- L${e.line}:${e.column} \`${e.code}\` — ${e.message}`)
607
+ .join("\n");
608
+ return [
609
+ ...buildInitialHistories(args),
610
+ HistoryMessage.assistant`
611
+ My previous attempt compiled cleanly but failed \`tsc --noEmit\` against
612
+ the assembled project. The errors all anchor on the SDK boundary — they
613
+ are caused by accessing properties, calling functions, or passing values
614
+ whose shape does not match what the SDK actually exposes.
615
+
616
+ ## Previous TSX
617
+
618
+ \`\`\`tsx
619
+ ${args.previousTsx}
620
+ \`\`\`
621
+
622
+ ## TypeScript errors on this file
623
+
624
+ ${lines}
625
+
626
+ Re-emit the entire page TSX with these errors fixed. Use only field
627
+ names and call signatures that match the "SDK return type shapes" and
628
+ "SDK request body shapes" sections above — do not invent properties,
629
+ do not assume snake_case when the schema uses camelCase, and do not add
630
+ a second argument to a parameterless endpoint.
631
+ `,
632
+ ];
633
+ }
634
+
635
+ /**
636
+ * Tsc diagnostic line carried into the typecheck retry prompt. Mirrors the
637
+ * shape {@link FrontendTypecheckSession} produces but only the fields the prompt
638
+ * actually needs, so the orchestrator can adapt other compilers in the future
639
+ * without touching this history builder.
640
+ */
641
+ export interface ITypecheckErrorLine {
642
+ line: number;
643
+ column: number;
644
+ code: string;
645
+ message: string;
646
+ }
647
+
648
+ /**
649
+ * History builder for the Phase 6 runtime-driven retry pass. The Playwright
650
+ * audit captures console errors, uncaught page exceptions, and navigation
651
+ * failures that the parser + typecheck gates cannot see — typically
652
+ * `obj?.array.method(...)` undefined-access on simulator data, react render
653
+ * crashes against missing optional fields, or hydration mismatches. Surfacing
654
+ * the captured messages back into the render prompt lets the LLM rewrite the
655
+ * page against the actual failure mode instead of guessing.
656
+ */
657
+ function buildRuntimeRetryHistories(args: {
658
+ screen: IAutoViewProductPlan.IScreen;
659
+ sdkMap: IAutoViewSdkMap;
660
+ designTheme: string;
661
+ document: OpenApi.IDocument;
662
+ relevantOperations: IAutoViewEndpoint[];
663
+ allScreenPaths: readonly string[];
664
+ previousTsx: string;
665
+ runtimeErrors: IRuntimeErrorLine[];
666
+ }): ConversateHistory[] {
667
+ const lines = args.runtimeErrors
668
+ .map((e) => `- \`${e.type}\` — ${e.message}`)
669
+ .join("\n");
670
+ return [
671
+ ...buildInitialHistories(args),
672
+ HistoryMessage.assistant`
673
+ My previous attempt typechecked cleanly but crashed at runtime when
674
+ Playwright loaded it under \`next dev\` (simulator mode). The errors
675
+ below are the live console / uncaught exception / navigation
676
+ diagnostics — typescript could not see them because the access pattern
677
+ uses optional chaining that short-circuits past the unsafe call site.
678
+
679
+ ## Previous TSX
680
+
681
+ \`\`\`tsx
682
+ ${args.previousTsx}
683
+ \`\`\`
684
+
685
+ ## Runtime errors on this page
686
+
687
+ ${lines}
688
+
689
+ Re-emit the entire page TSX with these errors fixed. Common causes:
690
+ - \`obj?.array.method(...)\` — fix by writing \`(obj?.array ?? []).method(...)\`
691
+ or \`obj?.array?.method(...)\` so the short-circuit covers the call.
692
+ - Property access on a value that can be \`null\`/\`undefined\` in
693
+ simulator data — narrow with explicit guards before reading.
694
+ - Components that crash on empty arrays — render an empty state when
695
+ the SDK returns \`[]\`.
696
+ Use only field names and shapes from the "SDK return type shapes" and
697
+ "SDK request body shapes" sections above. Do not invent properties.
698
+ `,
699
+ ];
700
+ }
701
+
702
+ /**
703
+ * Runtime diagnostic carried into the audit retry prompt. Subset of
704
+ * `IRuntimeAuditDiagnostic` from `auditFrontendRuntime` — the prompt only needs
705
+ * the type label and the message.
706
+ */
707
+ export interface IRuntimeErrorLine {
708
+ type: "console" | "pageerror" | "navigation";
709
+ message: string;
710
+ }
711
+
712
+ /**
713
+ * Re-render a single screen after the Phase 6 runtime audit reported broken
714
+ * navigation / console / pageerror diagnostics on its page. Identical
715
+ * retry-loop shape to {@link rerenderScreenForTypecheck}: the first attempt uses
716
+ * {@link buildRuntimeRetryHistories} (which surfaces the live runtime errors +
717
+ * previous TSX) and any subsequent retries fall back to
718
+ * {@link buildRetryHistories} so the parser-driven safety net still applies.
719
+ *
720
+ * Returns `null` when the model could not produce parser-valid TSX after the
721
+ * full retry budget; the caller should leave the existing (broken) file in
722
+ * place.
723
+ */
724
+ export async function rerenderScreenForRuntime(
725
+ ctx: IAutoViewAgentContext,
726
+ args: {
727
+ screen: IAutoViewProductPlan.IScreen;
728
+ document: OpenApi.IDocument;
729
+ sdkMap: IAutoViewSdkMap;
730
+ designTheme: string;
731
+ promptCacheKey: string;
732
+ allScreenPaths: readonly string[];
733
+ previousTsx: string;
734
+ runtimeErrors: IRuntimeErrorLine[];
735
+ },
736
+ ): Promise<{ tsx: string; ok: boolean; diagnostic: string } | null> {
737
+ const relevantOperations = toEndpoints(args.document).filter((op) =>
738
+ args.screen.endpoints.includes((op.accessor ?? []).join(".")),
739
+ );
740
+
741
+ let previousTsx = args.previousTsx;
742
+ let lastParseError: string | null = null;
743
+ for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt++) {
744
+ const pointer: IPointer<IAutoViewRenderApplication.IProps | null> = {
745
+ value: null,
746
+ };
747
+ const histories =
748
+ attempt === 0
749
+ ? buildRuntimeRetryHistories({
750
+ ...args,
751
+ relevantOperations,
752
+ previousTsx,
753
+ runtimeErrors: args.runtimeErrors,
754
+ })
755
+ : buildRetryHistories({
756
+ ...args,
757
+ relevantOperations,
758
+ lastTsx: previousTsx,
759
+ lastError: lastParseError ?? "",
760
+ });
761
+ const userMessage =
762
+ attempt === 0
763
+ ? `Fix the runtime errors on ${args.screen.path}. Re-emit the entire page TSX with the corrections. Call \`renderPage\` once.`
764
+ : `The corrected attempt failed TypeScript parsing: ${lastParseError}. Regenerate the entire page TSX with the fix applied.`;
765
+
766
+ try {
767
+ await ctx.conversate({
768
+ source: "autoViewRenderPage",
769
+ target: args.screen.path,
770
+ controller: createController({
771
+ build: (next) => {
772
+ pointer.value = next;
773
+ },
774
+ }),
775
+ enforceFunctionCall: true,
776
+ promptCacheKey: args.promptCacheKey,
777
+ histories,
778
+ userMessage,
779
+ });
780
+ } catch (error) {
781
+ // Vendor-side failure during the runtime-driven retry. Same policy
782
+ // as the typecheck retry: record, retry, and fall through to `null`
783
+ // so the caller keeps the previous file rather than aborting.
784
+ lastParseError = error instanceof Error ? error.message : String(error);
785
+ continue;
786
+ }
787
+ if (pointer.value === null) {
788
+ lastParseError = "model did not call renderPage";
789
+ continue;
790
+ }
791
+ const candidate =
792
+ autoWrapNullishCoalescingMix(pointer.value.tsx) ?? pointer.value.tsx;
793
+ const parseError = findSyntaxError(candidate);
794
+ if (parseError === null) {
795
+ return { tsx: candidate, ok: true, diagnostic: "" };
796
+ }
797
+ previousTsx = candidate;
798
+ lastParseError = parseError;
799
+ }
800
+ return null;
801
+ }
802
+
803
+ /* -------------------------------------------------------------------------- */
804
+ /* syntax validation */
805
+ /* -------------------------------------------------------------------------- */
806
+
807
+ /**
808
+ * Run the rendered TSX through TypeScript's tolerant parser plus a focused
809
+ * regex for `??` / `||` mixing — the model's most common grammar error that the
810
+ * parser flags only post-parse.
811
+ */
812
+ /**
813
+ * Walk the TypeScript AST and parenthesize every `??` branch that sits directly
814
+ * inside a `||` / `&&` expression, and vice versa. TC39 forbids mixing the two
815
+ * without explicit parens, and even with the prompt rule the model keeps
816
+ * re-emitting `a ?? b || c`. Doing it deterministically after the LLM call
817
+ * costs no extra inference and avoids the retry budget being burned on
818
+ * grammar-only defects.
819
+ *
820
+ * Returns the rewritten source when at least one fix landed, `null` when
821
+ * nothing needed changing or the rewrite failed validation (so the caller can
822
+ * fall back to the original text).
823
+ */
824
+ const NULLISH_MIX_REGEX =
825
+ /\?\?[^()?|]{0,400}\|\||\|\|[^()|?]{0,400}\?\?|\?\?[^()?&]{0,400}&&|&&[^()&?]{0,400}\?\?/;
826
+
827
+ /**
828
+ * Last-resort textual rewrite for the cases the AST visitor below cannot reach
829
+ * (parser bails out at the diagnostic, printer optimizes the parens away,
830
+ * etc.). Iteratively wraps every `??`-pair adjacent to a `||` or `&&` token.
831
+ * Pattern recognizes identifiers, dotted property access, subscript access,
832
+ * function calls with no nested parens, and template literals — enough to
833
+ * handle the LLM's typical landing-page expressions.
834
+ *
835
+ * Conservative on purpose: complex expressions where the operands cross parens
836
+ * or another operator are left alone, so the retry loop still has the original
837
+ * code to repair from.
838
+ */
839
+ function regexWrapNullishMix(code: string): string {
840
+ // An "atom" the wrapper is comfortable picking up as one of the
841
+ // `??` operands. Covers:
842
+ // - identifiers and dotted chains (`obj.foo.bar`)
843
+ // - optional chains (`obj?.foo?.bar`)
844
+ // - subscript access (`arr[i]`, single-level)
845
+ // - balanced function calls (`fn(...)`, single nesting level)
846
+ // - type-cast wrappers (`(value as Type)` and `(value as Type).prop`)
847
+ // - string / number / boolean / null literals
848
+ // - empty / shallow array / object literals (`[]`, `{}`)
849
+ // Anything more exotic falls back to the AST visitor or the retry
850
+ // budget — but this covers ~95% of LLM-generated landing-page code.
851
+ const ID = String.raw`[A-Za-z_$][\w$]*`;
852
+ const CAST = String.raw`\(\s*${ID}(?:\.${ID})*\s+as\s+[A-Za-z_$][\w$<>.,\s\[\]?|&]*?\s*\)`;
853
+ const LITERAL = String.raw`"[^"\n]{0,80}"|'[^'\n]{0,80}'|\d+(?:\.\d+)?|true|false|null|undefined|\[\s*\]|\{\s*\}`;
854
+ const HEAD = String.raw`(?:${LITERAL}|${CAST}|${ID})`;
855
+ const TAIL = String.raw`(?:\??\.${ID}|\[[^\[\]\n]{1,80}\]|\([^()\n]{0,200}\))*`;
856
+ const ATOM = `${HEAD}${TAIL}`;
857
+ const pairs: Array<[RegExp, string]> = [
858
+ [
859
+ new RegExp(
860
+ `(?<![\\w(])(${ATOM}\\s*\\?\\?\\s*${ATOM})(\\s*(?:\\|\\||&&))`,
861
+ "g",
862
+ ),
863
+ "($1)$2",
864
+ ],
865
+ [
866
+ new RegExp(
867
+ `((?:\\|\\||&&)\\s*)(${ATOM}\\s*\\?\\?\\s*${ATOM})(?![\\w)])`,
868
+ "g",
869
+ ),
870
+ "$1($2)",
871
+ ],
872
+ ];
873
+
874
+ let prev = "";
875
+ let curr = code;
876
+ let iterations = 0;
877
+ while (prev !== curr && iterations < 10) {
878
+ prev = curr;
879
+ for (const [re, replacement] of pairs) {
880
+ curr = curr.replace(re, replacement);
881
+ }
882
+ iterations++;
883
+ }
884
+ return curr;
885
+ }
886
+
887
+ function autoWrapNullishCoalescingMix(code: string): string | null {
888
+ const source = ts.createSourceFile(
889
+ "Page.tsx",
890
+ code,
891
+ ts.ScriptTarget.ES2022,
892
+ /*setParentNodes*/ true,
893
+ ts.ScriptKind.TSX,
894
+ );
895
+
896
+ let changed = false;
897
+ const isNullish = (n: ts.Node): boolean =>
898
+ ts.isBinaryExpression(n) &&
899
+ n.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken;
900
+ const isLogical = (n: ts.Node): boolean =>
901
+ ts.isBinaryExpression(n) &&
902
+ (n.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
903
+ n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken);
904
+
905
+ const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
906
+ const visit: ts.Visitor = (node) => {
907
+ const visited = ts.visitEachChild(node, visit, context);
908
+ if (!ts.isBinaryExpression(visited)) return visited;
909
+
910
+ const wrap = (child: ts.Expression): ts.Expression => {
911
+ changed = true;
912
+ return ts.factory.createParenthesizedExpression(child);
913
+ };
914
+
915
+ const op = visited.operatorToken.kind;
916
+ if (
917
+ op === ts.SyntaxKind.BarBarToken ||
918
+ op === ts.SyntaxKind.AmpersandAmpersandToken
919
+ ) {
920
+ return ts.factory.updateBinaryExpression(
921
+ visited,
922
+ isNullish(visited.left) ? wrap(visited.left) : visited.left,
923
+ visited.operatorToken,
924
+ isNullish(visited.right) ? wrap(visited.right) : visited.right,
925
+ );
926
+ }
927
+ if (op === ts.SyntaxKind.QuestionQuestionToken) {
928
+ return ts.factory.updateBinaryExpression(
929
+ visited,
930
+ isLogical(visited.left) ? wrap(visited.left) : visited.left,
931
+ visited.operatorToken,
932
+ isLogical(visited.right) ? wrap(visited.right) : visited.right,
933
+ );
934
+ }
935
+ return visited;
936
+ };
937
+ return (root) => ts.visitNode(root, visit) as ts.SourceFile;
938
+ };
939
+
940
+ const result = ts.transform(source, [transformer]);
941
+ const printer = ts.createPrinter({
942
+ newLine: ts.NewLineKind.LineFeed,
943
+ removeComments: false,
944
+ });
945
+ let printed = changed ? printer.printFile(result.transformed[0]) : code;
946
+ result.dispose();
947
+
948
+ // Fallback for cases the AST visitor cannot reach (TS parser bails
949
+ // out at the diagnostic, the printer optimizes the parens away,
950
+ // etc.). Iteratively wrap remaining `?? ||` / `?? &&` pairs with a
951
+ // textual rewrite limited to atomic operands. Brute force but
952
+ // reliable for the LLM's typical landing-page expressions.
953
+ if (NULLISH_MIX_REGEX.test(printed)) {
954
+ const swept = regexWrapNullishMix(printed);
955
+ if (swept !== printed) {
956
+ printed = swept;
957
+ changed = true;
958
+ }
959
+ }
960
+
961
+ if (!changed) return null;
962
+
963
+ // Safety net: only return the rewrite when it actually clears the
964
+ // grammar-error regex. If both AST and the textual fallback gave up,
965
+ // leave the original alone so the retry loop still has a shot.
966
+ if (NULLISH_MIX_REGEX.test(printed)) {
967
+ return null;
968
+ }
969
+ return printed;
970
+ }
971
+
972
+ function findSyntaxError(code: string): string | null {
973
+ const source = ts.createSourceFile(
974
+ "Page.tsx",
975
+ code,
976
+ ts.ScriptTarget.ES2022,
977
+ /*setParentNodes*/ false,
978
+ ts.ScriptKind.TSX,
979
+ );
980
+ const diagnostics: readonly ts.Diagnostic[] =
981
+ (source as unknown as { parseDiagnostics?: ts.Diagnostic[] })
982
+ .parseDiagnostics ?? [];
983
+ if (diagnostics.length > 0) {
984
+ const first = diagnostics[0]!;
985
+ const message =
986
+ typeof first.messageText === "string"
987
+ ? first.messageText
988
+ : first.messageText.messageText;
989
+ if (first.start !== undefined) {
990
+ const { line, character } = source.getLineAndCharacterOfPosition(
991
+ first.start,
992
+ );
993
+ // Line + column alone is not enough for the model to fix the
994
+ // error — it cannot see the source, so it tends to repeat the
995
+ // same defect. Include a small slice of the surrounding code so
996
+ // the retry attempt has concrete context.
997
+ const lines = code.split("\n");
998
+ const start = Math.max(0, line - 2);
999
+ const end = Math.min(lines.length, line + 3);
1000
+ const snippet = lines
1001
+ .slice(start, end)
1002
+ .map((text, idx) => {
1003
+ const lineNo = start + idx + 1;
1004
+ const marker = lineNo === line + 1 ? ">" : " ";
1005
+ return `${marker} ${lineNo.toString().padStart(4, " ")} | ${text}`;
1006
+ })
1007
+ .join("\n");
1008
+ return `Line ${line + 1}, column ${character + 1}: ${message}\n\n${snippet}`;
1009
+ }
1010
+ return message;
1011
+ }
1012
+ if (NULLISH_MIX_REGEX.test(code)) {
1013
+ return "Mixing `??` with `||` / `&&` is a TC39 grammar error — wrap the nullish-coalescing branch in parentheses (e.g. `(a ?? b) || c`).";
1014
+ }
1015
+ return null;
1016
+ }
1017
+
1018
+ /* -------------------------------------------------------------------------- */
1019
+ /* path mapping + placeholder fallback */
1020
+ /* -------------------------------------------------------------------------- */
1021
+
1022
+ /**
1023
+ * Map a planner-emitted screen path (`/`, `/cart`, `/todos/[id]/edit`) to the
1024
+ * Next.js app-router file the scaffold writes for it. Exposed so the
1025
+ * typecheck-driven retry loop can look up which screen owns a given broken file
1026
+ * and re-render it directly.
1027
+ */
1028
+ export function screenPathToFile(routePath: string): string {
1029
+ const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
1030
+ if (trimmed.length === 0) return "app/page.tsx";
1031
+ return `app/${trimmed}/page.tsx`;
1032
+ }
1033
+
1034
+ /**
1035
+ * Inverse of {@link screenPathToFile}. Returns `null` when the path does not
1036
+ * match the `app/.../page.tsx` shape the scaffold writes — e.g. for shared
1037
+ * components or wiki files, which are not screen-owned and therefore cannot be
1038
+ * fixed by re-rendering a single screen.
1039
+ */
1040
+ export function fileToScreenPath(filePath: string): string | null {
1041
+ if (filePath === "app/page.tsx") return "/";
1042
+ const match = /^app\/(.+)\/page\.tsx$/.exec(filePath);
1043
+ if (match === null) return null;
1044
+ return `/${match[1]}`;
1045
+ }
1046
+
1047
+ function buildPlaceholderPage(
1048
+ screen: IAutoViewProductPlan.IScreen,
1049
+ reason: string,
1050
+ ): string {
1051
+ const title = JSON.stringify(screen.title);
1052
+ const purpose = JSON.stringify(screen.purpose);
1053
+ const pattern = JSON.stringify(screen.uiPattern);
1054
+ const actor = JSON.stringify(screen.actor);
1055
+ const endpointsArrayLit = JSON.stringify(screen.endpoints);
1056
+ const reasonLit = JSON.stringify(reason);
1057
+ return `"use client";
1058
+
1059
+ // AutoView Render: this page failed to generate cleanly after the
1060
+ // retry budget. The placeholder keeps \`next build\` green; check
1061
+ // \`wiki/sdk-feedback.md\` for the root cause.
1062
+
1063
+ export default function Page() {
1064
+ const endpoints: readonly string[] = ${endpointsArrayLit};
1065
+ return (
1066
+ <main className="container mx-auto py-10">
1067
+ <p className="text-xs uppercase tracking-wide text-destructive">
1068
+ AutoView render failed · {${pattern}}
1069
+ </p>
1070
+ <h1 className="mt-2 text-3xl font-bold tracking-tight">{${title}}</h1>
1071
+ <p className="mt-3 text-muted-foreground">{${purpose}}</p>
1072
+ <p className="mt-2 text-xs text-muted-foreground">
1073
+ Actor: <span className="font-mono">{${actor}}</span>
1074
+ </p>
1075
+ {endpoints.length > 0 ? (
1076
+ <div className="mt-4 rounded-md border bg-muted/30 p-3">
1077
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1078
+ Planned SDK endpoints
1079
+ </p>
1080
+ <ul className="mt-2 space-y-1 text-xs font-mono">
1081
+ {endpoints.map((accessor) => (
1082
+ <li key={accessor}>{accessor}</li>
1083
+ ))}
1084
+ </ul>
1085
+ </div>
1086
+ ) : null}
1087
+ <details className="mt-6 rounded-lg border border-destructive/40 bg-destructive/5 p-4">
1088
+ <summary className="cursor-pointer text-sm font-medium text-destructive">Show parser diagnostic</summary>
1089
+ <pre className="mt-2 whitespace-pre-wrap text-xs text-muted-foreground">{${reasonLit}}</pre>
1090
+ </details>
1091
+ </main>
1092
+ );
1093
+ }
1094
+ `;
1095
+ }
1096
+
1097
+ /* -------------------------------------------------------------------------- */
1098
+ /* controller wiring */
1099
+ /* -------------------------------------------------------------------------- */
1100
+
1101
+ function createController(props: {
1102
+ build: (next: IAutoViewRenderApplication.IProps) => void;
1103
+ }): IAgenticaController.IClass {
1104
+ const application: ILlmApplication =
1105
+ typia.llm.application<IAutoViewRenderApplication>();
1106
+
1107
+ return {
1108
+ protocol: "class",
1109
+ name: "autoViewRenderPage",
1110
+ application,
1111
+ execute: {
1112
+ renderPage: (next) => {
1113
+ props.build(next);
1114
+ },
1115
+ } satisfies IAutoViewRenderApplication,
1116
+ };
1117
+ }