@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,155 @@
1
+ import { tags } from "typia";
2
+
3
+ import { AutoBeFunctionCallingMetric } from "./misc";
4
+ import { IAutoBeTokenUsageJson } from "./misc";
5
+
6
+ /**
7
+ * Base shape shared by every AutoView event.
8
+ *
9
+ * Self-defined (formerly `AutoBeEventBase` from `@autobe/interface`) so this
10
+ * package carries no AutoBE dependency. The standalone AutoView agent only
11
+ * emits the seven events below, so the base is reproduced here verbatim rather
12
+ * than importing the full AutoBE event hierarchy.
13
+ */
14
+ export interface AutoBeEventBase<Type extends string> {
15
+ /** A unique identifier for the event. */
16
+ id: string;
17
+
18
+ /** Literal discriminator for the event type. */
19
+ type: Type;
20
+
21
+ /** ISO-8601 timestamp of when the event was emitted. */
22
+ created_at: string & tags.Format<"date-time">;
23
+ }
24
+
25
+ /**
26
+ * Event sources surfaced by the agent's `conversate` calls — only the phases
27
+ * that actually invoke the LLM are represented (scaffold / start / complete
28
+ * are deterministic and never appear as a source).
29
+ */
30
+ export type AutoBeEventSource =
31
+ | "autoViewSdkStudy"
32
+ | "autoViewProductPlan"
33
+ | "autoViewRenderPage"
34
+ | "autoViewReview";
35
+
36
+ /** Fired once when an AutoView run begins. */
37
+ export interface AutoBeAutoViewStartEvent
38
+ extends AutoBeEventBase<"autoViewStart"> {
39
+ /** SDK source the run is built against. */
40
+ source: "interface" | "shopping";
41
+
42
+ /** Step counter of the interface phase this run is based on (`0` for shopping). */
43
+ step: number;
44
+
45
+ /** Optional design-theme instruction forwarded to plan + render phases. */
46
+ designTheme: string;
47
+
48
+ /** Human-readable reason the run was triggered. */
49
+ reason: string;
50
+ }
51
+
52
+ /** Fired when Phase 1 (SDK Study) produces the domain map. */
53
+ export interface AutoBeAutoViewSdkStudyEvent
54
+ extends AutoBeEventBase<"autoViewSdkStudy"> {
55
+ step: number;
56
+
57
+ /** Number of resources surfaced in the SDK map. */
58
+ resources: number;
59
+
60
+ /** Number of actors surfaced in the SDK map. */
61
+ actors: number;
62
+
63
+ /** Rendered SDK domain-map markdown. */
64
+ sdkMap: string;
65
+ }
66
+
67
+ /** Fired when Phase 2 (Product Plan) produces the screen IA. */
68
+ export interface AutoBeAutoViewProductPlanEvent
69
+ extends AutoBeEventBase<"autoViewProductPlan"> {
70
+ step: number;
71
+
72
+ /** Number of planned screens. */
73
+ pages: number;
74
+
75
+ /** Number of intentionally omitted screens. */
76
+ intentionalOmissions: number;
77
+
78
+ /** Rendered product-plan markdown. */
79
+ productPlan: string;
80
+ }
81
+
82
+ /** Fired when Phase 3 (Scaffold) finishes writing the deterministic skeleton. */
83
+ export interface AutoBeAutoViewScaffoldEvent
84
+ extends AutoBeEventBase<"autoViewScaffold"> {
85
+ step: number;
86
+
87
+ /** Number of scaffolded files. */
88
+ files: number;
89
+ }
90
+
91
+ /** Fired once per page during Phase 4 (Render). */
92
+ export interface AutoBeAutoViewRenderPageEvent
93
+ extends AutoBeEventBase<"autoViewRenderPage"> {
94
+ step: number;
95
+
96
+ /** Total number of pages to render. */
97
+ total: number;
98
+
99
+ /** Number of pages completed so far (this one inclusive). */
100
+ completed: number;
101
+
102
+ /** Route path of the rendered page. */
103
+ pagePath: string;
104
+
105
+ /** UI pattern the planner assigned to this screen. */
106
+ uiPattern: string;
107
+
108
+ /** Number of render attempts spent on this page. */
109
+ attempts: number;
110
+
111
+ /** Whether the page rendered + parsed successfully. */
112
+ ok: boolean;
113
+
114
+ /** Token usage spent rendering this page. */
115
+ tokenUsage: IAutoBeTokenUsageJson.IComponent;
116
+
117
+ /** Function-calling metric for this page's render. */
118
+ metric: AutoBeFunctionCallingMetric;
119
+ }
120
+
121
+ /** Fired when Phase 5 (UI Review) finishes the code-and-spec audit. */
122
+ export interface AutoBeAutoViewReviewEvent
123
+ extends AutoBeEventBase<"autoViewReview"> {
124
+ step: number;
125
+
126
+ /** Number of screenshots taken (`0` in the standalone code-review path). */
127
+ screenshots: number;
128
+
129
+ /** Number of pages flagged broken. */
130
+ broken: number;
131
+
132
+ /** Number of broken pages recovered by re-render. */
133
+ recovered: number;
134
+
135
+ /** Rendered SDK-feedback markdown. */
136
+ sdkFeedback: string;
137
+ }
138
+
139
+ /** Fired once when an AutoView run completes; carries the assembled files. */
140
+ export interface AutoBeAutoViewCompleteEvent
141
+ extends AutoBeEventBase<"autoViewComplete"> {
142
+ step: number;
143
+
144
+ /** SDK source the run was built against. */
145
+ source: "interface" | "shopping";
146
+
147
+ /** Wall-clock duration of the run in milliseconds. */
148
+ elapsed: number;
149
+
150
+ /** Number of pages in the finished project. */
151
+ pages: number;
152
+
153
+ /** Final `path → content` map of the generated frontend. */
154
+ files: Record<string, string>;
155
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Self-contained type surface that replaces the `@autobe/interface` import in
3
+ * this package. Re-exports only what the AutoView agent uses, so the package
4
+ * carries no `@autobe/*` type dependency.
5
+ *
6
+ * Migrated 2026-06-01 as Step 1 of the AutoBE-independence track.
7
+ */
8
+ export * from "./events";
9
+ export * from "./misc";
10
+ export * from "./compiler";
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Minimal self-defined replacements for the handful of `@autobe/interface`
3
+ * support types the AutoView agent touches. Only the surface the agent
4
+ * actually reads is reproduced — the full AutoBE definitions carry deep
5
+ * dependency trees this standalone package has no reason to vendor.
6
+ */
7
+
8
+ /** Function-calling metric accumulated across one `conversate` call. */
9
+ export interface AutoBeFunctionCallingMetric {
10
+ /** Number of LLM call attempts. */
11
+ attempt: number;
12
+
13
+ /** Number of successful tool executions. */
14
+ success: number;
15
+
16
+ /** Number of consent (re-ask) rounds. */
17
+ consent: number;
18
+
19
+ /** Number of schema-validation failures. */
20
+ validationFailure: number;
21
+
22
+ /** Number of invalid-JSON parse failures. */
23
+ invalidJson: number;
24
+ }
25
+
26
+ /**
27
+ * Token-usage shapes. AutoView only ever constructs / forwards
28
+ * {@link IAutoBeTokenUsageJson.IComponent}, so only that member is defined.
29
+ */
30
+ export namespace IAutoBeTokenUsageJson {
31
+ /** Aggregate token usage for a single component. */
32
+ export interface IComponent {
33
+ /** Total tokens (input + output). */
34
+ total: number;
35
+
36
+ /** Input-side token breakdown. */
37
+ input: IInput;
38
+
39
+ /** Output-side token breakdown. */
40
+ output: IOutput;
41
+ }
42
+
43
+ export interface IInput {
44
+ total: number;
45
+ cached: number;
46
+ }
47
+
48
+ export interface IOutput {
49
+ total: number;
50
+ reasoning: number;
51
+ accepted_prediction: number;
52
+ rejected_prediction: number;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Options accepted by `orchestrateAutoView`. All optional — the orchestrator
58
+ * fills sensible defaults (interface source, empty theme, simulator backend,
59
+ * no runtime audit).
60
+ */
61
+ export interface IAutoBeRunAutoViewOptions {
62
+ /** SDK source for the run. Defaults to `"interface"`. */
63
+ source?: "interface" | "shopping";
64
+
65
+ /** Design-theme instruction forwarded to plan + render phases. */
66
+ designTheme?: string;
67
+
68
+ /** Live backend host wired into `connection.ts`. `null` → simulator mode. */
69
+ backend?: { host: string } | null;
70
+
71
+ /** Opt into Phase 6 Playwright runtime audit. Defaults to `false`. */
72
+ runtimeAudit?: boolean;
73
+
74
+ /**
75
+ * Opt into the workflow verification phase: after typecheck, boot the
76
+ * generated app in a real headless browser and walk the derived user
77
+ * workflows (list → detail, forms, landing) with positive assertions, writing
78
+ * `wiki/verification.md`. Proves the app actually works for a user, not just
79
+ * that it compiles. Heavy (installs Chromium, boots `next dev`); defaults to
80
+ * `false`.
81
+ */
82
+ verify?: boolean;
83
+
84
+ /**
85
+ * Path globs to KEEP — only endpoints whose path matches are turned into
86
+ * screens. Slices a large swagger to one actor surface, e.g.
87
+ * `["shoppings/customers/**"]`. Empty/absent means "all".
88
+ */
89
+ include?: string[];
90
+
91
+ /** Path globs to DROP after include, e.g. `["**​/monitors/**"]`. */
92
+ exclude?: string[];
93
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Minimal self-defined replacement for `@autobe/utils`'s `ArrayUtil`. The
3
+ * self-defined interface compiler only needs `asyncMap` (ordered, sequential
4
+ * async map), so only that is reproduced.
5
+ */
6
+ export namespace ArrayUtil {
7
+ export async function asyncMap<T, U>(
8
+ array: T[],
9
+ callback: (value: T, index: number, array: T[]) => Promise<U>,
10
+ ): Promise<U[]> {
11
+ const result: U[] = new Array(array.length);
12
+ for (let i = 0; i < array.length; i++)
13
+ result[i] = await callback(array[i], i, array);
14
+ return result;
15
+ }
16
+ }
@@ -0,0 +1,29 @@
1
+ import { dedent } from "@typia/utils";
2
+
3
+ /**
4
+ * Minimal self-defined replacement for `@autobe/utils`'s `StringUtil`. The
5
+ * AutoView agent only uses `trim` (template-literal dedent), so only that is
6
+ * reproduced — `dedent` itself still comes from the typia ecosystem, which is
7
+ * not an AutoBE dependency.
8
+ */
9
+ export namespace StringUtil {
10
+ export function trim(
11
+ strings: TemplateStringsArray,
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ ...values: any[]
14
+ ): string {
15
+ return dedent(strings, ...values);
16
+ }
17
+
18
+ /** First sentence / first line of a description, whichever comes first. */
19
+ export function summary(description: string): string {
20
+ const newLine: number = description.indexOf("\n");
21
+ const dot: number = description.indexOf(".");
22
+ const minimum: number = Math.min(
23
+ newLine === -1 ? Number.MAX_SAFE_INTEGER : newLine,
24
+ dot === -1 ? Number.MAX_SAFE_INTEGER : dot,
25
+ description.length,
26
+ );
27
+ return description.substring(0, minimum);
28
+ }
29
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { classifyEndpoint, classifyEndpoints } from "./classifyEndpoints";
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("classifyEndpoint — deterministic CRUD roles", () => {
20
+ it("GET collection → list", () => {
21
+ expect(classifyEndpoint(ep({ method: "get", path: "/items" })).role).toBe("list");
22
+ });
23
+ it("GET collection with query → search", () => {
24
+ expect(
25
+ classifyEndpoint(ep({ method: "get", path: "/items", query: { type: "object" } as never })).role,
26
+ ).toBe("search");
27
+ });
28
+ it("GET item → detail", () => {
29
+ expect(classifyEndpoint(ep({ method: "get", path: "/items/{id}" })).role).toBe("detail");
30
+ });
31
+ it("POST collection → create", () => {
32
+ expect(classifyEndpoint(ep({ method: "post", path: "/items" })).role).toBe("create");
33
+ });
34
+ it("PUT/PATCH item → update", () => {
35
+ expect(classifyEndpoint(ep({ method: "put", path: "/items/{id}" })).role).toBe("update");
36
+ expect(classifyEndpoint(ep({ method: "patch", path: "/items/{id}" })).role).toBe("update");
37
+ });
38
+ it("DELETE item → delete", () => {
39
+ expect(classifyEndpoint(ep({ method: "delete", path: "/items/{id}" })).role).toBe("delete");
40
+ });
41
+ it("POST on item path → action", () => {
42
+ expect(classifyEndpoint(ep({ method: "post", path: "/items/{id}/upload" })).role).toBe("action");
43
+ });
44
+ it("pet-scoped sub-collection is a list, not a detail", () => {
45
+ expect(classifyEndpoint(ep({ method: "get", path: "/pets/{id}/photos" })).role).toBe("list");
46
+ });
47
+
48
+ it("non-item PATCH that returns a collection (nestia search) → search/list", () => {
49
+ // a real PATCH-as-search: array response → a read
50
+ expect(
51
+ classifyEndpoint(ep({ method: "patch", path: "/sales", responseBody: { description: "", typeName: "Sale", isArray: true } })).role,
52
+ ).toBe("list");
53
+ });
54
+
55
+ it("non-item PATCH that returns VOID is a mutation, not a search (safety)", () => {
56
+ // PATCH /databases/{id}/config returns nothing — a state change, not a read.
57
+ // Tagging it a read would tell an agent a mutating call is safe.
58
+ expect(
59
+ classifyEndpoint(ep({ method: "patch", path: "/databases/{id}/config", responseBody: null })).role,
60
+ ).toBe("action");
61
+ });
62
+
63
+ it("non-item PATCH returning a single (non-collection) object is a mutation (with document)", () => {
64
+ // a page wrapper resolves to a collection (read); a plain object does not (write).
65
+ const doc = {
66
+ components: { schemas: { Auth: { type: "object", properties: { token: { type: "string" } } } } },
67
+ } as never;
68
+ const single = ep({ method: "patch", path: "/authenticate/refresh", responseBody: { description: "", typeName: "Auth", isArray: false } });
69
+ expect(classifyEndpoint(single, new Set(), doc).role).toBe("action");
70
+ });
71
+ });
72
+
73
+ describe("classifyEndpoints — resource grouping", () => {
74
+ it("buckets by accessor[0] and records the role set", () => {
75
+ const groups = classifyEndpoints([
76
+ ep({ method: "get", path: "/pets", accessor: ["pets", "index"] }),
77
+ ep({ method: "get", path: "/pets/{id}", accessor: ["pets", "at"] }),
78
+ ep({ method: "post", path: "/pets", accessor: ["pets", "create"] }),
79
+ ep({ method: "get", path: "/users", accessor: ["users", "index"] }),
80
+ ]);
81
+ const pets = groups.find((g) => g.resource === "pets")!;
82
+ expect(pets.endpoints).toHaveLength(3);
83
+ expect([...pets.roles].sort()).toEqual(["create", "detail", "list"]);
84
+ expect(groups.map((g) => g.resource)).toEqual(["pets", "users"]);
85
+ });
86
+ });
@@ -0,0 +1,291 @@
1
+ import { OpenApi } from "@typia/interface";
2
+
3
+ import { extractFields, findCollectionField } from "./extractFields";
4
+ import { IAutoViewEndpoint } from "./toEndpoints";
5
+
6
+ /**
7
+ * Deterministic CRUD role of an endpoint — the first brick of the "정확한 틀".
8
+ *
9
+ * The MAP layer (SdkStudy + ProductPlan) was 100% LLM improvisation: same
10
+ * swagger, different screens every run, no CRUD-completeness guarantee, and a
11
+ * commerce-hardcoded prompt. This classifier derives each endpoint's role from
12
+ * method + path shape + query presence — purely structural, zero LLM, fully
13
+ * reproducible and domain-agnostic.
14
+ *
15
+ * - `list` GET on a collection (no trailing path param), no query
16
+ * - `search` GET on a collection WITH query parameters (filter/paginate)
17
+ * - `detail` GET on a single item (trailing `{param}`)
18
+ * - `create` POST on a collection
19
+ * - `update` PUT / PATCH on a single item
20
+ * - `delete` DELETE on a single item
21
+ * - `action` anything else (e.g. POST /pets/{id}/uploadImage, RPC-ish verbs)
22
+ */
23
+ export type EndpointRole =
24
+ | "list"
25
+ | "search"
26
+ | "detail"
27
+ | "create"
28
+ | "update"
29
+ | "delete"
30
+ | "action";
31
+
32
+ /**
33
+ * One link in a resource hierarchy: a collection name plus the path param that
34
+ * addresses a single item of it. `param` is `null` for a leaf collection that
35
+ * the endpoint does not item-address (`/sales/{saleId}/questions` → the
36
+ * `questions` link has `param: null` on a list, `param: "id"` on a detail).
37
+ */
38
+ export interface IResourceLink {
39
+ name: string;
40
+ param: string | null;
41
+ }
42
+
43
+ export interface IClassifiedEndpoint {
44
+ endpoint: IAutoViewEndpoint;
45
+ /** Top-level resource bucket = the first link of {@link chain}. */
46
+ resource: string;
47
+ /**
48
+ * Full resource hierarchy parsed from the path, skipping namespace/actor
49
+ * prefixes. `/shoppings/admins/sales/{saleId}/questions/{id}` →
50
+ * `[{sales, saleId}, {questions, id}]`. Length 1 = a top-level resource.
51
+ */
52
+ chain: IResourceLink[];
53
+ role: EndpointRole;
54
+ /** True when the path targets a single item (trailing `{param}`). */
55
+ itemScoped: boolean;
56
+ }
57
+
58
+ /** All endpoints of one resource, plus the set of roles it actually exposes. */
59
+ export interface IResourceGroup {
60
+ /** Leaf resource name = the last link of {@link chain}. */
61
+ resource: string;
62
+ /** Full hierarchy of this group (`[sales]`, `[sales, questions]`). */
63
+ chain: IResourceLink[];
64
+ /** `chain.length` — 1 = top-level, ≥2 = nested under a parent. */
65
+ depth: number;
66
+ endpoints: IClassifiedEndpoint[];
67
+ /** Which CRUD roles this resource supports — drives the screen set later. */
68
+ roles: Set<EndpointRole>;
69
+ }
70
+
71
+ const PARAM = /^\{.*\}$/;
72
+
73
+ function segments(path: string): string[] {
74
+ return path.split("/").filter((s) => s.length > 0);
75
+ }
76
+
77
+ /**
78
+ * Whether an endpoint's response is a browsable collection — an array, or a
79
+ * page/wrapper object whose resolved fields contain a collection field
80
+ * (`data` / `items` / `entries` / …). Used to tell a real `PATCH`-as-search
81
+ * (returns a page of results) from a `PATCH` that merely uses the verb to mutate
82
+ * a named sub-resource (`/databases/{id}/config`) and returns void or a single
83
+ * object. Without a document a page wrapper cannot be resolved, so any named
84
+ * body is conservatively treated as a collection (preserving legacy behavior)
85
+ * while a void response is still recognized as a non-collection.
86
+ */
87
+ function returnsCollection(
88
+ endpoint: IAutoViewEndpoint,
89
+ document?: OpenApi.IDocument,
90
+ ): boolean {
91
+ if (endpoint.responseBody === null) return false;
92
+ if (endpoint.responseBody.isArray) return true;
93
+ if (document === undefined) return true;
94
+ return (
95
+ findCollectionField(extractFields(endpoint.responseBody.typeName, document)) !==
96
+ undefined
97
+ );
98
+ }
99
+
100
+ export function classifyEndpoint(
101
+ endpoint: IAutoViewEndpoint,
102
+ candidates: ReadonlySet<string> = new Set<string>(),
103
+ document?: OpenApi.IDocument,
104
+ ): IClassifiedEndpoint {
105
+ const segs = segments(endpoint.path);
106
+ const last = segs.at(-1) ?? "";
107
+ // "single item" = the path's last segment is a parameter (`/pets/{id}`),
108
+ // i.e. it addresses one entity. `/pets/{id}/photos` is a collection again
109
+ // (last segment `photos` is not a param), so it lists pet-scoped photos.
110
+ const itemScoped = PARAM.test(last);
111
+
112
+ // Any path parameter means the write targets a specific entity (or a child
113
+ // of one) — `/orders/{id}/cancel`, `/pets/{id}/photos`, `/items/{id}`. We
114
+ // cannot tell a sub-resource create from an RPC verb deterministically (no
115
+ // noun/verb NLP), so both collapse to `action`: an item-scoped write the
116
+ // detail screen surfaces as a button/sub-form. A `create` is reserved for a
117
+ // POST on a bare collection (`/items`).
118
+ const hasPathParam = segs.some((s) => PARAM.test(s));
119
+ const method = endpoint.method.toLowerCase();
120
+ const hasQuery = endpoint.query !== null;
121
+
122
+ const role = ((): EndpointRole => {
123
+ switch (method) {
124
+ case "get":
125
+ // `/pets/{id}` → detail; `/pets/{id}/photos` → list (sub-collection).
126
+ if (itemScoped) return "detail";
127
+ return hasQuery ? "search" : "list";
128
+ case "post":
129
+ return hasPathParam ? "action" : "create";
130
+ case "put":
131
+ return "update";
132
+ case "patch":
133
+ // nestia / AutoBE convention: PATCH on a collection is index/search
134
+ // (the filter rides in the request body), NOT a partial update — this
135
+ // is how shopping/ERP expose their list endpoints. PATCH on a single
136
+ // item (`/x/{id}`) is a genuine update.
137
+ if (itemScoped) return "update";
138
+ // ...but only when it actually returns a collection. A non-item PATCH
139
+ // that returns void or a single object (`PATCH /databases/{id}/config`)
140
+ // is a mutation, not a search — classifying it as a read would tag the
141
+ // tool `readOnly` and tell an agent a state-changing call is safe.
142
+ if (!returnsCollection(endpoint, document)) return "action";
143
+ return hasQuery ? "search" : "list";
144
+ case "delete":
145
+ return "delete";
146
+ default:
147
+ return "action";
148
+ }
149
+ })();
150
+
151
+ const chain = resourceChainOf(segs, candidates, role);
152
+ return {
153
+ endpoint,
154
+ resource: chain[0]?.name ?? resourceOf(segs, candidates),
155
+ chain,
156
+ role,
157
+ itemScoped,
158
+ };
159
+ }
160
+
161
+ function stripBraces(seg: string): string {
162
+ return seg.replace(/^\{/, "").replace(/\}$/, "");
163
+ }
164
+
165
+ /**
166
+ * Parse a path into its resource hierarchy, skipping namespace / actor prefix
167
+ * segments. A static segment becomes a resource link when it is item-addressed
168
+ * (a `{param}` follows it), is a known resource elsewhere (in `candidates`), or
169
+ * is the terminal collection. Pure namespacing (`shoppings`, `admins`,
170
+ * `systematic`) is dropped.
171
+ *
172
+ * `/shoppings/admins/sales/{saleId}/questions/{id}` → `[{sales,saleId},{questions,id}]`
173
+ * `/shoppings/customers/orders/{id}/publish` (action) → `[{orders,id}]`
174
+ * (the trailing verb is the action name, not a sub-resource).
175
+ */
176
+ function resourceChainOf(
177
+ segs: string[],
178
+ candidates: ReadonlySet<string>,
179
+ role: EndpointRole,
180
+ ): IResourceLink[] {
181
+ const chain: IResourceLink[] = [];
182
+ for (let i = 0; i < segs.length; i++) {
183
+ const seg = segs[i]!;
184
+ if (PARAM.test(seg)) continue;
185
+ const next = segs[i + 1];
186
+ const nextIsParam = next !== undefined && PARAM.test(next);
187
+ const isTerminal = i === segs.length - 1;
188
+ if (nextIsParam) {
189
+ // item-addressed collection → a real resource (`sales/{saleId}`).
190
+ chain.push({ name: seg, param: stripBraces(next) });
191
+ } else if (candidates.has(seg)) {
192
+ // a known resource (item-addressed elsewhere), here without an id.
193
+ chain.push({ name: seg, param: null });
194
+ } else if (isTerminal && chain.length === 0) {
195
+ // a top-level read-only collection that is never item-addressed
196
+ // (`/health`, `/system`, `/store/inventory`).
197
+ chain.push({ name: seg, param: null });
198
+ }
199
+ // else: a namespace/actor prefix, OR a deeper trailing verb/view that is
200
+ // never item-addressed (`/sales/details`, `/channels/hierarchical`,
201
+ // `/orders/incompletes`). The latter is a VARIANT of its parent resource,
202
+ // not a resource of its own — skip it so the endpoint folds into the parent
203
+ // group instead of spawning a junk top-level card.
204
+ }
205
+ // An action's trailing static verb (`.../{id}/publish`) is not a resource —
206
+ // drop it so the action attaches to its parent. Keep length ≥ 1.
207
+ if (role === "action" && chain.length > 1 && chain.at(-1)!.param === null) {
208
+ chain.pop();
209
+ }
210
+ return chain.length > 0 ? chain : [{ name: "root", param: null }];
211
+ }
212
+
213
+ /**
214
+ * The segment immediately before a path's last `{param}` — the entity the path
215
+ * addresses (`/shoppings/admins/coupons/{id}` → `coupons`, `/pet/{petId}` →
216
+ * `pet`). `null` when the path has no param (a collection or verb path).
217
+ */
218
+ function paramAnchoredResource(segs: string[]): string | null {
219
+ for (let i = segs.length - 1; i >= 0; i--) {
220
+ if (PARAM.test(segs[i]!)) return i > 0 ? segs[i - 1]! : null;
221
+ }
222
+ return null;
223
+ }
224
+
225
+ /**
226
+ * Resource bucket of a path, using the set of param-anchored resources as
227
+ * the source of truth.
228
+ *
229
+ * - Param path (`/pet/{petId}`, `/coupons/{id}`) → the segment before the
230
+ * param. This recovers the real resource even under deep accessors that
231
+ * share a `shoppings` prefix.
232
+ * - Collection / verb path with no param (`/pet/findByStatus`, `/pet`,
233
+ * `/user/login`) → absorbed into a known resource if the path passes through
234
+ * one (findByStatus → pet, login → user), so RPC-style verbs do not spawn
235
+ * junk resources. Falls back to its own last segment when it belongs to no
236
+ * known resource (`/store/inventory` → inventory).
237
+ */
238
+ function resourceOf(segs: string[], candidates: ReadonlySet<string>): string {
239
+ const anchored = paramAnchoredResource(segs);
240
+ if (anchored !== null) return anchored;
241
+ const nonParam = segs.filter((s) => !PARAM.test(s));
242
+ for (let i = nonParam.length - 1; i >= 0; i--) {
243
+ if (candidates.has(nonParam[i]!)) return nonParam[i]!;
244
+ }
245
+ return nonParam.at(-1) ?? "root";
246
+ }
247
+
248
+ /**
249
+ * Classify every endpoint and bucket by resource. Resource order follows first
250
+ * appearance; endpoints within a resource keep their input order.
251
+ */
252
+ export function classifyEndpoints(
253
+ endpoints: IAutoViewEndpoint[],
254
+ document?: OpenApi.IDocument,
255
+ ): IResourceGroup[] {
256
+ // Pass 1: collect the real resources — segments that have a `{param}` child.
257
+ const candidates = new Set<string>();
258
+ for (const endpoint of endpoints) {
259
+ const r = paramAnchoredResource(segments(endpoint.path));
260
+ if (r !== null) candidates.add(r);
261
+ }
262
+ // Pass 2: classify and bucket by the FULL resource chain (so a sub-resource
263
+ // like `sales/questions` is its own group, distinct from `sales`), absorbing
264
+ // param-less verb paths into a known resource.
265
+ const groups = new Map<string, IResourceGroup>();
266
+ for (const endpoint of endpoints) {
267
+ const classified = classifyEndpoint(endpoint, candidates, document);
268
+ const chainKey = classified.chain.map((c) => c.name).join("/");
269
+ const existing = groups.get(chainKey);
270
+ const group: IResourceGroup = existing ?? {
271
+ resource: classified.chain.at(-1)!.name,
272
+ chain: classified.chain,
273
+ depth: classified.chain.length,
274
+ endpoints: [],
275
+ roles: new Set<EndpointRole>(),
276
+ };
277
+ // Prefer a chain link that carries an item param (a detail endpoint) so the
278
+ // group's chain has the real param names for routing, not just `null`s.
279
+ if (existing !== undefined) {
280
+ classified.chain.forEach((link, i) => {
281
+ if (link.param !== null && existing.chain[i]?.param == null) {
282
+ existing.chain[i] = link;
283
+ }
284
+ });
285
+ }
286
+ group.endpoints.push(classified);
287
+ group.roles.add(classified.role);
288
+ groups.set(chainKey, group);
289
+ }
290
+ return [...groups.values()];
291
+ }