@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,104 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { resourcePlan } from "./resourcePlan";
4
+ import { IAutoViewEndpoint } from "./toEndpoints";
5
+
6
+ const ep = (over: Partial<IAutoViewEndpoint>): IAutoViewEndpoint => ({
7
+ method: "get",
8
+ path: "/items",
9
+ accessor: ["items", "index"],
10
+ name: "index",
11
+ description: "",
12
+ parameters: [],
13
+ query: null,
14
+ requestBody: null,
15
+ responseBody: null,
16
+ ...over,
17
+ });
18
+
19
+ describe("resourcePlan — deterministic IA", () => {
20
+ it("a full-CRUD resource yields list + detail + create + edit", () => {
21
+ const screens = resourcePlan([
22
+ ep({ method: "get", path: "/pets", accessor: ["pets", "index"] }),
23
+ ep({
24
+ method: "get",
25
+ path: "/pets/{petId}",
26
+ accessor: ["pets", "at"],
27
+ parameters: [{ name: "petId", description: "", schema: { type: "string" } as never }],
28
+ }),
29
+ ep({ method: "post", path: "/pets", accessor: ["pets", "create"] }),
30
+ ep({
31
+ method: "put",
32
+ path: "/pets/{petId}",
33
+ accessor: ["pets", "update"],
34
+ parameters: [{ name: "petId", description: "", schema: { type: "string" } as never }],
35
+ }),
36
+ ep({
37
+ method: "delete",
38
+ path: "/pets/{petId}",
39
+ accessor: ["pets", "erase"],
40
+ parameters: [{ name: "petId", description: "", schema: { type: "string" } as never }],
41
+ }),
42
+ ]);
43
+ expect(screens.map((s) => s.path)).toEqual([
44
+ "/pets",
45
+ "/pets/[petsId]",
46
+ "/pets/new",
47
+ "/pets/[petsId]/edit",
48
+ ]);
49
+ expect(screens.map((s) => s.kind)).toEqual(["list", "detail", "create", "edit"]);
50
+ // detail screen hosts update + delete as secondary actions
51
+ const detail = screens.find((s) => s.kind === "detail")!;
52
+ expect(detail.secondary.some((c) => c.role === "update")).toBe(true);
53
+ expect(detail.secondary.some((c) => c.role === "delete")).toBe(true);
54
+ });
55
+
56
+ it("a read-only resource yields only a list screen", () => {
57
+ const screens = resourcePlan([
58
+ ep({ method: "get", path: "/inventory", accessor: ["inventory", "index"] }),
59
+ ]);
60
+ expect(screens.map((s) => s.kind)).toEqual(["list"]);
61
+ expect(screens[0]!.path).toBe("/inventory");
62
+ });
63
+
64
+ it("uses a canonical per-resource param name in dynamic routes", () => {
65
+ const screens = resourcePlan([
66
+ ep({
67
+ method: "get",
68
+ path: "/users/{username}",
69
+ accessor: ["users", "at"],
70
+ parameters: [{ name: "username", description: "", schema: { type: "string" } as never }],
71
+ }),
72
+ ]);
73
+ expect(screens[0]!.path).toBe("/users/[usersId]");
74
+ });
75
+
76
+ it("nests a sub-resource under its parent, mirroring the swagger path", () => {
77
+ const param = (name: string) => ({
78
+ name,
79
+ description: "",
80
+ schema: { type: "string" } as never,
81
+ });
82
+ const screens = resourcePlan([
83
+ // top-level sales
84
+ ep({ method: "get", path: "/shoppings/admins/sales", accessor: ["shoppings", "admins", "sales", "index"] }),
85
+ ep({ method: "get", path: "/shoppings/admins/sales/{saleId}", accessor: ["shoppings", "admins", "sales", "at"], parameters: [param("saleId")] }),
86
+ // nested questions under a sale
87
+ ep({ method: "get", path: "/shoppings/admins/sales/{saleId}/questions", accessor: ["shoppings", "admins", "sales", "questions", "index"], parameters: [param("saleId")] }),
88
+ ep({ method: "get", path: "/shoppings/admins/sales/{saleId}/questions/{id}", accessor: ["shoppings", "admins", "sales", "questions", "at"], parameters: [param("saleId"), param("id")] }),
89
+ ]);
90
+ const paths = screens.map((s) => s.path);
91
+ // namespace/actor prefix stripped; sales is top-level (depth 1)
92
+ expect(paths).toContain("/sales");
93
+ expect(paths).toContain("/sales/[salesId]");
94
+ // questions nests under the sale, carrying the parent param in the route
95
+ expect(paths).toContain("/sales/[salesId]/questions");
96
+ expect(paths).toContain("/sales/[salesId]/questions/[questionsId]");
97
+ const questionsList = screens.find((s) => s.path === "/sales/[salesId]/questions")!;
98
+ expect(questionsList.depth).toBe(2);
99
+ expect(questionsList.parentPath).toBe("/sales/[salesId]");
100
+ const salesList = screens.find((s) => s.path === "/sales")!;
101
+ expect(salesList.depth).toBe(1);
102
+ expect(salesList.parentPath).toBeNull();
103
+ });
104
+ });
@@ -0,0 +1,180 @@
1
+ import { OpenApi } from "@typia/interface";
2
+
3
+ import {
4
+ classifyEndpoints,
5
+ EndpointRole,
6
+ IClassifiedEndpoint,
7
+ IResourceGroup,
8
+ } from "./classifyEndpoints";
9
+ import { IAutoViewEndpoint } from "./toEndpoints";
10
+
11
+ /**
12
+ * Deterministic information architecture — the second brick of the "정확한 틀".
13
+ *
14
+ * Turns the classified resource groups into a concrete, reproducible screen
15
+ * set: for each resource, exactly the screens its CRUD roles justify, with real
16
+ * Next.js paths and the endpoints each screen composes. No LLM, no commerce
17
+ * baseline, no "5-25 screens, compose, don't enumerate" guesswork — every
18
+ * resource that exposes a capability gets the corresponding screen, and
19
+ * nothing more.
20
+ *
21
+ * This is what replaces the LLM ProductPlan's IShop-flavored improvisation:
22
+ * the Render phase later fills these slots with TSX, but the SET of screens is
23
+ * decided here, structurally.
24
+ */
25
+ export type ScreenKind = "list" | "detail" | "create" | "edit";
26
+
27
+ export interface IPlannedScreen {
28
+ /** Next.js App Router path, dynamic segments in `[brackets]`. */
29
+ path: string;
30
+ resource: string;
31
+ kind: ScreenKind;
32
+ /** Hierarchy depth: 1 = top-level resource, ≥2 = nested under a parent. */
33
+ depth: number;
34
+ /** Parent collection path (`/sales/[saleId]`) for a nested resource, else null. */
35
+ parentPath: string | null;
36
+ title: string;
37
+ /** Primary data-source / target endpoint for the screen. */
38
+ primary: IAutoViewEndpoint;
39
+ /**
40
+ * Secondary endpoints the screen wires in — e.g. a detail screen surfaces
41
+ * its update / delete / action endpoints as buttons; a list screen links to
42
+ * detail. Empty for create.
43
+ */
44
+ secondary: IClassifiedEndpoint[];
45
+ }
46
+
47
+ function pick(group: IResourceGroup, role: EndpointRole): IClassifiedEndpoint[] {
48
+ return group.endpoints.filter((c) => c.role === role);
49
+ }
50
+
51
+ function titleize(resource: string): string {
52
+ const spaced = resource.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2");
53
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
54
+ }
55
+
56
+ /**
57
+ * Canonical route param name for a resource: `<name>Id`. Derived from the
58
+ * resource NAME (not the endpoint's param), so it is identical everywhere the
59
+ * resource appears (its own detail and as a parent of a sub-resource) and
60
+ * distinct from every other resource's param. This guarantees a route never has
61
+ * a sibling-slug conflict (`/sales/[id]` vs `/sales/[saleId]`) nor a repeated
62
+ * slug within one path (`/sales/[id]/questions/[id]`) — both of which Next.js
63
+ * rejects at boot. The SDK's own param names are recovered separately by
64
+ * position when the page issues its fetch.
65
+ */
66
+ function canonParam(name: string): string {
67
+ return `${name.replace(/[^A-Za-z0-9]/g, "")}Id`;
68
+ }
69
+
70
+ /**
71
+ * The collection route for a resource group, mirroring the swagger hierarchy:
72
+ * every parent link contributes `<name>/[<canonParam>]`, the leaf its bare name.
73
+ * `[sales, questions]` → `/sales/[salesId]/questions`. `[sales]` → `/sales`.
74
+ */
75
+ /** A parent link's route part: `sales/[salesId]` when item-addressed, else just
76
+ * `coupons` (a path prefix with no `{param}`, so no bracket). */
77
+ function parentPart(link: { name: string; param: string | null }): string {
78
+ return link.param !== null ? `${link.name}/[${canonParam(link.name)}]` : link.name;
79
+ }
80
+
81
+ function collectionPath(group: IResourceGroup): string {
82
+ const links = group.chain;
83
+ const parts = links.map((c, i) => (i < links.length - 1 ? parentPart(c) : c.name));
84
+ return `/${parts.join("/")}`;
85
+ }
86
+
87
+ /** The parent collection path (`/sales/[salesId]`) for a nested group, else null. */
88
+ function parentPathOf(group: IResourceGroup): string | null {
89
+ if (group.chain.length < 2) return null;
90
+ const parents = group.chain.slice(0, -1);
91
+ return `/${parents.map(parentPart).join("/")}`;
92
+ }
93
+
94
+ /** Item route param for a group's own entity (`/sales` → `[salesId]`). */
95
+ function itemParam(group: IResourceGroup): string {
96
+ return canonParam(group.resource);
97
+ }
98
+
99
+ export function planResource(group: IResourceGroup): IPlannedScreen[] {
100
+ const screens: IPlannedScreen[] = [];
101
+ const resource = group.resource;
102
+ const label = titleize(resource);
103
+ const depth = group.chain.length;
104
+ const parentPath = parentPathOf(group);
105
+ const base = collectionPath(group);
106
+ const common = { resource, depth, parentPath };
107
+
108
+ // Prefer the top-level enumeration as the resource's primary list: the real
109
+ // `/v2/droplets` (no path params) over an id-scoped sub-view that happens to
110
+ // classify as a list (`/v2/droplets/{id}/destroy_with_associated_resources`),
111
+ // whose response is NOT the resource's own rows. Fewest path params wins;
112
+ // stable otherwise (list before search at equal depth).
113
+ const listish = [...pick(group, "list"), ...pick(group, "search")].sort(
114
+ (a, b) => a.endpoint.parameters.length - b.endpoint.parameters.length,
115
+ );
116
+ const details = pick(group, "detail");
117
+ const creates = pick(group, "create");
118
+ const updates = pick(group, "update");
119
+ const deletes = pick(group, "delete");
120
+ const actions = pick(group, "action");
121
+
122
+ // List / search screen — the resource's home.
123
+ if (listish.length > 0) {
124
+ screens.push({
125
+ ...common,
126
+ path: base,
127
+ kind: "list",
128
+ title: label,
129
+ primary: listish[0]!.endpoint,
130
+ // extra search variants + the row-action targets (detail/delete) the
131
+ // list links to.
132
+ secondary: [...listish.slice(1), ...details, ...deletes],
133
+ });
134
+ }
135
+
136
+ // Detail screen — one entity. Hosts update/delete/action as buttons.
137
+ if (details.length > 0) {
138
+ screens.push({
139
+ ...common,
140
+ path: `${base}/[${itemParam(group)}]`,
141
+ kind: "detail",
142
+ title: `${label} detail`,
143
+ primary: details[0]!.endpoint,
144
+ secondary: [...updates, ...deletes, ...actions],
145
+ });
146
+ }
147
+
148
+ // Create screen — a form from the POST requestBody schema.
149
+ if (creates.length > 0) {
150
+ screens.push({
151
+ ...common,
152
+ path: `${base}/new`,
153
+ kind: "create",
154
+ title: `New ${label.toLowerCase()}`,
155
+ primary: creates[0]!.endpoint,
156
+ secondary: [],
157
+ });
158
+ }
159
+
160
+ // Edit screen — a form from the PUT/PATCH requestBody, prefilled by detail.
161
+ if (updates.length > 0) {
162
+ screens.push({
163
+ ...common,
164
+ path: `${base}/[${itemParam(group)}]/edit`,
165
+ kind: "edit",
166
+ title: `Edit ${label.toLowerCase()}`,
167
+ primary: updates[0]!.endpoint,
168
+ secondary: details,
169
+ });
170
+ }
171
+
172
+ return screens;
173
+ }
174
+
175
+ export function resourcePlan(
176
+ endpoints: IAutoViewEndpoint[],
177
+ document?: OpenApi.IDocument,
178
+ ): IPlannedScreen[] {
179
+ return classifyEndpoints(endpoints, document).flatMap(planResource);
180
+ }
@@ -0,0 +1,85 @@
1
+ import { OpenApiConverter } from "@typia/utils";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { sliceDocument } from "./sliceDocument";
5
+ import { toEndpoints } from "./toEndpoints";
6
+
7
+ function doc(
8
+ paths: Record<string, unknown>,
9
+ schemas: Record<string, unknown> = {},
10
+ ) {
11
+ return OpenApiConverter.upgradeDocument({
12
+ openapi: "3.0.0",
13
+ info: { title: "t", version: "1.0.0" },
14
+ paths,
15
+ components: { schemas },
16
+ // biome-ignore lint: test fixture
17
+ } as never);
18
+ }
19
+
20
+ const objResp = (name: string) => ({
21
+ 200: { description: "ok", content: { "application/json": { schema: { $ref: `#/components/schemas/${name}` } } } },
22
+ });
23
+
24
+ const fixture = doc(
25
+ {
26
+ "/sales": { get: { operationId: "listSales", responses: objResp("Sale") } },
27
+ "/orders": { get: { operationId: "listOrders", responses: objResp("Order") } },
28
+ },
29
+ {
30
+ // Sale references Seller (transitively kept); Order references nothing extra.
31
+ Sale: { type: "object", properties: { id: { type: "string" }, seller: { $ref: "#/components/schemas/Seller" } } },
32
+ Seller: { type: "object", properties: { id: { type: "string" }, name: { type: "string" } } },
33
+ Order: { type: "object", properties: { id: { type: "string" } } },
34
+ },
35
+ );
36
+
37
+ describe("sliceDocument", () => {
38
+ it("keeps only the included paths", () => {
39
+ const sliced = sliceDocument(fixture, { include: ["/sales"] });
40
+ expect(Object.keys(sliced.paths)).toEqual(["/sales"]);
41
+ });
42
+
43
+ it("prunes component schemas to the transitive closure of kept operations", () => {
44
+ const sliced = sliceDocument(fixture, { include: ["/sales"] });
45
+ const kept = Object.keys(sliced.components?.schemas ?? {}).sort();
46
+ // Sale (response) + Seller (referenced by Sale) kept; Order dropped.
47
+ expect(kept).toEqual(["Sale", "Seller"]);
48
+ });
49
+
50
+ it("drops a path's unincluded sibling methods but keeps included ones", () => {
51
+ const d = doc(
52
+ {
53
+ "/items": {
54
+ get: { operationId: "list", responses: objResp("Item") },
55
+ },
56
+ "/items/{id}": {
57
+ get: { operationId: "at", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: objResp("Item") },
58
+ },
59
+ },
60
+ { Item: { type: "object", properties: { id: { type: "string" } } } },
61
+ );
62
+ const sliced = sliceDocument(d, { exclude: ["/items/**"] });
63
+ expect(Object.keys(sliced.paths)).toEqual(["/items"]);
64
+ });
65
+
66
+ it("is a no-op when no filter is given (same endpoints)", () => {
67
+ const sliced = sliceDocument(fixture, {});
68
+ expect(toEndpoints(sliced).map((e) => e.path).sort()).toEqual(
69
+ toEndpoints(fixture).map((e) => e.path).sort(),
70
+ );
71
+ });
72
+
73
+ it("does not mutate the input document", () => {
74
+ const before = Object.keys(fixture.components?.schemas ?? {}).length;
75
+ sliceDocument(fixture, { include: ["/sales"] });
76
+ expect(Object.keys(fixture.components?.schemas ?? {}).length).toBe(before);
77
+ });
78
+
79
+ it("leaves the kept operations renderable end to end", () => {
80
+ const sliced = sliceDocument(fixture, { include: ["/sales"] });
81
+ const eps = toEndpoints(sliced);
82
+ expect(eps).toHaveLength(1);
83
+ expect(eps[0]!.responseBody?.typeName).toBe("Sale");
84
+ });
85
+ });
@@ -0,0 +1,119 @@
1
+ import { OpenApi } from "@typia/interface";
2
+
3
+ import { IEndpointFilter, filterEndpoints } from "./endpointFilter";
4
+ import { toEndpoints } from "./toEndpoints";
5
+
6
+ /**
7
+ * Slice an OpenAPI document down to an operator-selected surface — the fix for
8
+ * "`--include` only trims the endpoint list, but the SDK Study still compiles
9
+ * the WHOLE document and overflows the model's context on a large swagger".
10
+ *
11
+ * {@link filterEndpoints} answers *which screens*; this answers *which document*.
12
+ * It keeps only the filtered paths/methods AND prunes `components.schemas` to
13
+ * the transitive closure those operations actually reference, so every
14
+ * downstream phase (SDK Study, Product Plan, Render) sees a document whose size
15
+ * matches the requested slice — not the original 300-type spec.
16
+ *
17
+ * Pure: returns a new document; the input is never mutated. A no-op filter
18
+ * returns an equivalent document (schemas reachable from all operations).
19
+ */
20
+ export function sliceDocument(
21
+ document: OpenApi.IDocument,
22
+ filter: IEndpointFilter = {},
23
+ ): OpenApi.IDocument {
24
+ const kept = new Set(
25
+ filterEndpoints(toEndpoints(document), filter).map(
26
+ (e) => `${e.method.toLowerCase()} ${e.path}`,
27
+ ),
28
+ );
29
+
30
+ // Rebuild paths, keeping only the (method, path) pairs that survived the
31
+ // filter. A path with no surviving method is dropped entirely.
32
+ const paths: Record<string, OpenApi.IPath> = {};
33
+ for (const [path, item] of Object.entries(document.paths ?? {})) {
34
+ if (item === undefined) continue;
35
+ const keptItem: Record<string, unknown> = {};
36
+ for (const [key, value] of Object.entries(item)) {
37
+ // Non-method members of a path item (parameters, $ref, summary…) are
38
+ // carried over only when at least one method on the path survives.
39
+ if (!HTTP_METHODS.has(key.toLowerCase())) continue;
40
+ if (kept.has(`${key.toLowerCase()} ${path}`)) keptItem[key] = value;
41
+ }
42
+ if (Object.keys(keptItem).length === 0) continue;
43
+ for (const [key, value] of Object.entries(item)) {
44
+ if (!HTTP_METHODS.has(key.toLowerCase())) keptItem[key] = value;
45
+ }
46
+ paths[path] = keptItem as OpenApi.IPath;
47
+ }
48
+
49
+ // Compute the transitive schema closure reachable from the kept operations.
50
+ const allSchemas = document.components?.schemas ?? {};
51
+ const reachable = new Set<string>();
52
+ const frontier: string[] = [];
53
+ const seed = (schema: unknown): void => {
54
+ for (const name of collectRefs(schema)) {
55
+ if (!reachable.has(name)) {
56
+ reachable.add(name);
57
+ frontier.push(name);
58
+ }
59
+ }
60
+ };
61
+ for (const item of Object.values(paths)) {
62
+ for (const [key, op] of Object.entries(item ?? {})) {
63
+ if (!HTTP_METHODS.has(key.toLowerCase())) continue;
64
+ seed(op);
65
+ }
66
+ }
67
+ while (frontier.length > 0) {
68
+ const name = frontier.pop()!;
69
+ seed(allSchemas[name]);
70
+ }
71
+
72
+ const schemas: Record<string, OpenApi.IJsonSchema> = {};
73
+ for (const name of reachable) {
74
+ const schema = allSchemas[name];
75
+ if (schema !== undefined) schemas[name] = schema;
76
+ }
77
+
78
+ return {
79
+ ...document,
80
+ paths,
81
+ components: { ...document.components, schemas },
82
+ };
83
+ }
84
+
85
+ const HTTP_METHODS = new Set([
86
+ "get",
87
+ "post",
88
+ "put",
89
+ "patch",
90
+ "delete",
91
+ "head",
92
+ "options",
93
+ "trace",
94
+ ]);
95
+
96
+ /**
97
+ * Every component name a JSON value references via `$ref`, found by walking the
98
+ * whole subtree (properties, items, allOf/oneOf/anyOf, additionalProperties,
99
+ * and arbitrarily nested objects/arrays). Returns bare names (`Sale`), not
100
+ * pointers.
101
+ */
102
+ function collectRefs(value: unknown): string[] {
103
+ const out: string[] = [];
104
+ const walk = (node: unknown): void => {
105
+ if (node === null || typeof node !== "object") return;
106
+ if (Array.isArray(node)) {
107
+ for (const child of node) walk(child);
108
+ return;
109
+ }
110
+ const obj = node as Record<string, unknown>;
111
+ const ref = obj.$ref;
112
+ if (typeof ref === "string" && ref.startsWith("#/components/schemas/")) {
113
+ out.push(ref.slice("#/components/schemas/".length));
114
+ }
115
+ for (const child of Object.values(obj)) walk(child);
116
+ };
117
+ walk(value);
118
+ return out;
119
+ }